Commit Graph
100 Commits
Author SHA1 Message Date
thatguygriffandClaude Opus 5 9d11cc3b01 Collapse the lesson-type filter behind a Show Only button
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / Tests (PHP 8.2) (pull_request) Successful in 54s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m52s
CI / PHPStan (pull_request) Successful in 3m3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m41s
CI / Build Plugin Zip (pull_request) Skipped
The filter took a row of the booking calendar before a student had asked for
it. The view toggle and a new "Show Only" button now share one control row,
and the lesson-type list is revealed between that row and the calendar.

The list stays open across re-renders once revealed, and collapsing it leaves
the filter applied — the button keeps its active styling and carries the
number of ticked types, so a collapsed filter is never invisible.

Closes #119

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 12:47:24 -03:00
thatguygriff add6605141 Merge pull request 'Filter booking calendar slots by available lesson type' (#118) from feature/lesson-type-filter into main
CI / Tests (PHP 8.1) (push) Successful in 53s
CI / Tests (PHP 8.2) (push) Successful in 52s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m49s
CI / Coding Standards (push) Successful in 2m58s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
CI / Build Plugin Zip (push) Successful in 2m49s
Reviewed-on: #118
2026-07-28 15:32:14 +00:00
thatguygriffandClaude Opus 5 edcacae816 Filter booking calendar slots by available lesson type
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
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 2m51s
CI / PHPStan (pull_request) Successful in 2m59s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m37s
CI / Build Plugin Zip (pull_request) Skipped
Not every open time can be booked as every private-lesson type: a slot tied
to an offering takes that offering only, and a generic slot only takes types
whose length fits. Students had no way to see that before clicking a time.

The booking calendar now carries a lesson-type filter — a checkbox per active
private-lesson type, fetched once from GET /offerings?kind=private_lesson.
Ticking types narrows the calendar to the times bookable as one of them and
re-anchors the week view on the earliest match. The registration form's
Lesson type picker is narrowed the same way, and a lone remaining type is
pre-selected with its intake questions loaded.

Bookability is decided by offeringFitsSlot(), the client-side mirror of the
rule POST /bookings enforces; the filter is a browsing aid and the server
still validates every booking. No ticks means no filter, and the whole
control is hidden when the studio offers fewer than two private-lesson types.

Closes #117

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 12:27:37 -03:00
thatguygriff 17487cde46 Merge pull request 'Send students to a chosen page when registration succeeds' (#116) from feature/registration-success-redirect into main
CI / Tests (PHP 8.2) (push) Successful in 54s
CI / Tests (PHP 8.1) (push) Successful in 55s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m47s
CI / PHPStan (push) Successful in 2m56s
CI / Tests (PHP 8.3) (push) Successful in 2m41s
CI / Build Plugin Zip (push) Successful in 2m49s
Reviewed-on: #116
2026-07-28 15:04:44 +00:00
thatguygriffandClaude Opus 5 13d6b3e14e Send students to a chosen page when registration succeeds
CI / Tests (PHP 8.2) (pull_request) Successful in 49s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m0s
CI / Coding Standards (pull_request) Successful in 2m51s
CI / PHPStan (pull_request) Successful in 2m57s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped
CI / No Debug Code (pull_request) Successful in 2s
The Student Registration block's "After email confirmation" panel becomes
"After registration": the page it selects is now where a newly registered
student continues to, and a new autoRedirect toggle sends them there
instead of showing the link.

Only the two finished states qualify (RegistrationPage::isRegistrationComplete):
an invited student who is now logged in, and a self-signup back from the
emailed confirmation link. A validation error, an expired confirmation
link, and the intermediate "check your email" step all stay on the page so
their message is read.

The invited-student success previously had no link at all; it gains a
"Continue to your account" one. That path deliberately has no
WordPress-login-screen fallback — pointing someone already signed in at the
login screen helps nobody — so continueUrl() distinguishes "no page chosen"
from "page chosen", and the redirect does nothing until one is picked.

Closes #115

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 10:58:17 -03:00
thatguygriff d866aa7295 Merge pull request 'Omit the class description when the group block shows a single class' (#113) from feature/group-class-single-embed-description into main
CI / Tests (PHP 8.1) (push) Successful in 1m9s
CI / PHPStan (push) Successful in 2m56s
CI / Tests (PHP 8.3) (push) Successful in 2m41s
CI / Build Plugin Zip (push) Successful in 2m48s
CI / Tests (PHP 8.2) (push) Successful in 1m2s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 3m4s
Reviewed-on: #113
2026-07-28 13:48:07 +00:00
thatguygriffandClaude Opus 5 c4acdb7ca4 Omit the class description when the group block shows one class
CI / Tests (PHP 8.1) (pull_request) Successful in 1m13s
CI / Coding Standards (pull_request) Successful in 3m1s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m2s
CI / PHPStan (pull_request) Successful in 3m3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped
The Group Classes block can be pinned to a single class via its Class
option so it can be embedded on a page dedicated to that class. On such a
page the surrounding copy already describes the class, so the card
repeated it. In single-class mode the description is now left out and the
card shows only the schedule, instructor, schedule note, price, enrolment
deadline and the enrol/withdraw controls.

The editor preview follows the same rule: BlockPreview::groupClasses()
takes the mode from the block's offeringId attribute, drops the sample
description when a class is pinned, and notes what the published page
shows. Its sample card also gained the .us-class-when and
.us-enrol-deadline elements the live markup has always rendered.

Closes #114

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-28 10:39:09 -03:00
thatguygriff 30928addf8 Merge pull request 'Bump version to 1.2.2' (#112) from release/bump-1.2.2 into main
CI / Tests (PHP 8.2) (push) Successful in 46s
CI / Tests (PHP 8.1) (push) Successful in 48s
CI / No Debug Code (push) Successful in 3s
CI / Coding Standards (push) Successful in 2m47s
CI / PHPStan (push) Successful in 3m12s
CI / Tests (PHP 8.3) (push) Successful in 2m41s
CI / Build Plugin Zip (push) Successful in 2m48s
Reviewed-on: #112
2026-07-24 23:56:56 +00:00
thatguygriff c611268bdb Merge pull request 'Fix field-length saves, student wp-admin access, and empty instructor picker' (#111) from fix/field-length-student-admin-instructor-picker into main
CI / Tests (PHP 8.1) (push) Successful in 39s
CI / Tests (PHP 8.2) (push) Successful in 1m2s
CI / No Debug Code (push) Successful in 3s
CI / PHPStan (push) Successful in 2m54s
CI / Coding Standards (push) Successful in 2m58s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
Release / Build and Publish Release (push) Successful in 2m59s
Release / Open next-version bump PR (push) Successful in 5s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #111
2026-07-24 23:27:30 +00:00
thatguygriffandClaude Opus 4.8 721c4be1d6 Fix field-length saves, student wp-admin access, and empty instructor picker
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / Tests (PHP 8.2) (pull_request) Successful in 49s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m47s
CI / PHPStan (pull_request) Successful in 3m16s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m41s
CI / Build Plugin Zip (pull_request) Skipped
Three bug fixes for the 1.2.1 section:

- Fixed-size fields (question labels, offering titles/notes/e-transfer
  email, policy titles/slugs) no longer silently fail to save when the
  value exceeds its column length. The REST endpoints reject over-long
  values with a 400, the admin controllers refuse to insert them, and the
  form inputs carry a maxlength so the browser blocks over-long entry.
  Limits are MAX_* constants on the value objects, kept in lockstep with
  the schema columns.

- Students are kept out of wp-admin entirely. New StudentAdminGuard
  redirects front-end-only users (no back-office capability) away from the
  dashboard and hides the admin bar for them, while administrators, studio
  admins, and instructors keep full access.

- The Add/Edit Offering instructor picker now includes WordPress
  administrators when they act as instructors (the default single-account
  setup), so a solo studio owner is selectable instead of the dropdown
  being empty.

composer test (618), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 20:22:04 -03:00
thatguygriff 3aa65bad06 Merge pull request 'Bump version to 1.2.1' (#110) from release/bump-1.2.1 into main
CI / Tests (PHP 8.1) (push) Successful in 41s
CI / Tests (PHP 8.2) (push) Successful in 44s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m52s
CI / Coding Standards (push) Successful in 3m4s
CI / Tests (PHP 8.3) (push) Successful in 2m41s
CI / Build Plugin Zip (push) Successful in 2m49s
Reviewed-on: #110
2026-07-24 19:34:30 +00:00
thatguygriff 771942be8b Merge pull request 'Fix invite sign-in, add customizable invite-only text, repair account questions' (#109) from fix/registration-signin-and-account-questions into main
CI / Tests (PHP 8.1) (push) Successful in 40s
CI / Tests (PHP 8.2) (push) Successful in 1m5s
CI / No Debug Code (push) Successful in 3s
CI / Coding Standards (push) Successful in 2m53s
CI / PHPStan (push) Successful in 2m52s
CI / Tests (PHP 8.3) (push) Successful in 2m36s
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 2m47s
Reviewed-on: #109
2026-07-24 19:29:52 +00:00
thatguygriffandClaude Opus 4.8 242150569b Fix invite sign-in persistence, add invite-only text option, repair account questions
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m37s
CI / PHPStan (pull_request) Successful in 2m52s
CI / Coding Standards (pull_request) Successful in 3m6s
CI / Build Plugin Zip (pull_request) Skipped
Three registration fixes reported from live use:

- Accepting an invite now keeps the student signed in. The form was
  processed inside render() during the_content, so wp_set_auth_cookie()
  ran after headers were sent and the cookie never persisted — the new
  student was bounced back to the logged-out registration page. The
  submission is now handled on template_redirect (before output) with a
  post/redirect/get, so the cookie sticks and the student lands logged in.

- The "registration is by invitation only" message is now customisable via
  a new block attribute (inviteOnlyMessage / shortcode invite_only_message),
  falling back to the default wording when blank.

- Account-registration questions save again. dbDelta does not reliably
  relax a column from NOT NULL to NULL, so sites created before account-
  scope questions kept us_questions.offering_id NOT NULL and rejected
  account inserts ("Column 'offering_id' cannot be null"). A one-time,
  self-healing migration (guarded by its own option, not the version gate)
  re-applies the nullable definition on next load.

composer test, composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 16:24:15 -03:00
thatguygriff fae1fd08ba Merge pull request 'Add group-class withdrawal deadline and kind-aware offering form' (#108) from feature/group-class-withdrawal-deadline into main
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / Tests (PHP 8.2) (push) Successful in 47s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m58s
CI / PHPStan (push) Successful in 3m18s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #108
2026-07-24 19:01:48 +00:00
thatguygriffandClaude Opus 4.8 2c4b481077 Add group-class withdrawal deadline and kind-aware offering form
CI / Tests (PHP 8.1) (pull_request) Successful in 44s
CI / Tests (PHP 8.2) (pull_request) Successful in 59s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 2m53s
CI / PHPStan (pull_request) Successful in 2m55s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m41s
CI / Build Plugin Zip (pull_request) Skipped
Group classes now carry an optional per-class withdrawal deadline. Up to
that day a student may withdraw themselves from the class; the withdrawal
frees the seat and voids any pending payment but never issues an account
credit. After the deadline self-withdrawal closes and a studio admin must
withdraw the student by hand (the admin path is never subject to the
deadline). A blank deadline keeps self-withdrawal open indefinitely.

Also make the Add/Edit Offering form show only the fields relevant to the
selected kind: group settings for group classes, weekly reservation for
private lessons. Progressive enhancement — without JS every field renders.

- New nullable us_offerings.withdrawal_deadline column; Offering model gains
  $withdrawalDeadline + isWithdrawalOpen().
- New student endpoint POST /enrollments/{id}/withdraw, gated by the deadline
  (403 withdrawal_closed), ownership-checked, idempotent.
- Front-end group-class page shows a Withdraw button while open.
- No USC_VERSION bump: 1.2.0 is unreleased and accumulates schema changes
  under its section, matching the scheduled-billing and credit features.

Tests: composer test (596), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 15:56:40 -03:00
thatguygriff f552c3952a Merge pull request 'Credit students for cancelled paid lessons' (#107) from feature/credit-cancelled-paid-lessons into main
CI / Tests (PHP 8.1) (push) Successful in 40s
CI / Tests (PHP 8.2) (push) Successful in 1m2s
CI / No Debug Code (push) Successful in 3s
CI / PHPStan (push) Successful in 2m52s
CI / Coding Standards (push) Successful in 2m57s
CI / Tests (PHP 8.3) (push) Successful in 2m38s
CI / Build Plugin Zip (push) Successful in 2m46s
Reviewed-on: #107
2026-07-24 18:40:10 +00:00
thatguygriffandClaude Opus 4.8 e8e66eef3c Credit students for cancelled paid lessons
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / PHPStan (pull_request) Successful in 3m12s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 2m52s
Cancelling a lesson that was already paid for now credits the student
that money instead of leaving it as a manual refund, and the daily
scheduled-billing scan applies any available credit against their due
charges before emailing the notice.

- New us_credits ledger + us_payments.credit_applied column (Payment::netDue).
- PaymentService::creditForCancelledLesson issues a per-lesson share of the
  covering payment's total; wired into all three cancel paths (student
  self-cancel, instructor status update, admin student-detail cancel).
- PaymentService::applyCredits draws credit down FIFO across a run's charges,
  marking a fully-covered charge paid-by-credit; the notice shows the credit
  applied and reduced total, and the admin queue shows net due.
- Student detail page shows a student's credit balance and history.

Ships as part of the unreleased 1.2.0 (same release as scheduled billing).

Tests: composer test (585), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 15:32:20 -03:00
thatguygriff cf296329a0 Merge pull request 'Consolidate changelog into 1.2.0 section ahead of release' (#106) from docs/changelog-1.2.0 into main
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / Tests (PHP 8.1) (push) Successful in 48s
CI / PHPStan (push) Successful in 2m52s
CI / Coding Standards (push) Successful in 3m8s
CI / Build Plugin Zip (push) Successful in 2m50s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 2m38s
Reviewed-on: #106
2026-07-24 18:14:15 +00:00
thatguygriffandClaude Opus 4.8 3f9aef7746 Consolidate changelog into 1.2.0 section
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Failing after 2m33s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped
The plugin header carries 1.2.0 but CHANGELOG.md still topped out at the
untagged 1.1.3 section, and two shipped features (#104 lesson booking
detail, #105 weekly/monthly scheduled billing) were unrecorded. Neither
1.1.2 nor 1.1.3 was ever tagged, so their changes belong to the 1.2.0
release. Merge the untagged sections into a single 1.2.0 section and add
the two missing features so the release workflow publishes real notes.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 12:55:08 -03:00
thatguygriff d1dd30dc60 Merge pull request 'Add weekly and monthly scheduled billing for offerings' (#105) from feature/weekly-monthly-billing into main
CI / Tests (PHP 8.2) (push) Successful in 47s
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m46s
CI / PHPStan (push) Successful in 3m16s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
CI / Build Plugin Zip (push) Successful in 2m46s
Reviewed-on: #105
2026-07-24 15:38:59 +00:00
thatguygriffandClaude Opus 4.8 4328e8fb5f Add weekly and monthly scheduled billing for offerings
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m12s
CI / No Debug Code (pull_request) Successful in 3s
CI / PHPStan (pull_request) Successful in 2m52s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m39s
CI / Build Plugin Zip (pull_request) Skipped
Offerings can now bill weekly (a pending payment 24h before each lesson)
or monthly (one payment on the 1st for that month's lessons), alongside
one-time and full-term. Applies to both private lessons and group classes.

- Offering: new `weekly`/`monthly` billing modes + `isScheduledBilling()`
- Booking/enrolment defer payment for scheduled modes; a single lesson
  booked after its due date has passed (e.g. an add-on in an already-billed
  month) is charged at booking instead
- ScheduledBillingRunner: daily WP-Cron scan generates due payments across
  four cases (private/group × weekly/monthly), deduped via lesson.payment_id
  and payments.period_key
- PaymentDueMailer: one consolidated itemised email per student per scan
- Notice batch: payments emailed together share a reference; the admin
  Payments queue groups them with a lump-sum total for e-transfer reconciliation
- Cancellation never voids a scheduled payment (Payment::isScheduled())
- Schema: us_payments gains due_date, period_key, notice_batch; USC_VERSION 1.2.0

composer test, composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 12:06:37 -03:00
thatguygriff 36e7178158 Merge pull request 'Show booked lesson info on upcoming lists and add admin booking detail' (#104) from feature/lesson-booking-detail into main
CI / Tests (PHP 8.2) (push) Successful in 39s
CI / Coding Standards (push) Successful in 2m52s
CI / PHPStan (push) Successful in 2m55s
CI / Tests (PHP 8.3) (push) Successful in 2m36s
CI / Tests (PHP 8.1) (push) Successful in 55s
CI / No Debug Code (push) Successful in 2s
CI / Build Plugin Zip (push) Successful in 2m46s
Reviewed-on: #104
2026-07-24 14:12:33 +00:00
thatguygriffandClaude Opus 4.8 32619a1b75 Show booked lesson info on upcoming lists and add admin booking detail
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Coding Standards (pull_request) Successful in 2m53s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 3s
CI / PHPStan (pull_request) Successful in 3m12s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped
Front end: the student "upcoming lessons" panel now shows each booked
offering's name and length next to the time, and renders only the soonest
five lessons with a "Show all" reveal. GET /bookings returns offering_title
and duration_minutes so the list needs no extra request.

Admin: the Scheduler and My Lessons week/list views now show the booked
offering, and each lesson links to a detail view showing the policy versions
the student accepted (with acceptance time and IP) and their intake answers.
On My Lessons an instructor may only open their own lessons; the studio
Scheduler may open any.

composer test / composer lint / composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 11:07:06 -03:00
thatguygriff 1a447743b3 Merge pull request 'Group-class enrolment deadline with instructor late-enrolment override' (#103) from feature/group-class-enrollment-deadline into main
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / PHPStan (push) Successful in 2m52s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / Tests (PHP 8.2) (push) Successful in 47s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 3m4s
CI / Build Plugin Zip (push) Successful in 2m47s
Reviewed-on: #103
2026-07-24 13:45:54 +00:00
thatguygriffandClaude Opus 4.8 fc7c0fa966 Show "Enrol by <date>" on the group-class card while enrolment is open
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m47s
CI / Coding Standards (pull_request) Successful in 3m1s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
The front end used the deadline only to gate the Enrol button; students had
no way to see when enrolment closes. Add an "Enrol by <date>" line to each
class card, shown while enrolment is still open, for the effective deadline
(the instructor's date, or the first class day by default).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 10:36:51 -03:00
thatguygriffandClaude Opus 4.8 bf29162587 Add group-class enrolment deadline with instructor late-enrolment override
CI / Tests (PHP 8.1) (pull_request) Successful in 48s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / No Debug Code (pull_request) Successful in 3s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Coding Standards (pull_request) Successful in 3m2s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m37s
CI / Build Plugin Zip (pull_request) Skipped
Group classes gain an instructor-set enrolment deadline (new
us_offerings.enrollment_deadline column) that defaults to the first day of
the class (term_start). Past the deadline students can no longer self-enrol:
the enrolment endpoint rejects it (403 enrollment_closed) and the front-end
class list shows "Enrolment has closed." in place of the Enrol button.

Instructors keep a manual path: the "Add students directly" control on each
class's details page now renders for public classes too (not just
invite-only) and deliberately bypasses the deadline and capacity, so a
student can be added as a late enrolment after the class has closed. Past
the deadline the details page labels these as late enrolments.

Bumps USC_VERSION to 1.1.3 for the schema change.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 10:19:38 -03:00
thatguygriff 991ed2f5ad Merge pull request 'Bump version to 1.1.2' (#102) from release/bump-1.1.2 into main
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 2m36s
CI / Coding Standards (push) Successful in 2m45s
CI / PHPStan (push) Successful in 3m15s
CI / Build Plugin Zip (push) Successful in 2m53s
Reviewed-on: #102
2026-07-24 11:56:08 +00:00
thatguygriff 1fe28d5575 Merge pull request 'Show the Enable auto-updates toggle for the self-updater' (#101) from feature/auto-update-toggle into main
CI / Coding Standards (push) Successful in 2m52s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.2) (push) Successful in 40s
CI / Tests (PHP 8.1) (push) Successful in 54s
CI / PHPStan (push) Successful in 2m49s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
Release / Build and Publish Release (push) Successful in 3m1s
Release / Open next-version bump PR (push) Successful in 5s
CI / Build Plugin Zip (push) Successful in 2m47s
Reviewed-on: #101
2026-07-24 11:47:24 +00:00
thatguygriffandClaude Opus 4.8 51dd032668 Show the Enable auto-updates toggle for the self-updater
CI / Tests (PHP 8.1) (pull_request) Successful in 47s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 3m7s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Build Plugin Zip (pull_request) Skipped
WordPress only renders the "Enable auto-updates" toggle for a plugin that
appears in the update_plugins transient's response or no_update list, which
is what sets core's update-supported flag. UpdateChecker only populated the
response side (when a newer release existed), so between releases the plugin
was absent from the transient and the toggle never showed.

provideUpdate() now returns a no_update payload (installed version, empty
package) whenever no newer release is offered — including when the release
lookup fails — so the plugin stays in the transient and the toggle appears.
The response path (one-click and unattended updates) is unchanged.

Bumps to 1.1.1 so the fix ships to installed sites via the self-updater.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-24 08:46:31 -03:00
thatguygriff 37ec8a3315 Merge pull request 'Bump version to 1.1.1' (#100) from release/bump-1.1.1 into main
CI / Tests (PHP 8.2) (push) Successful in 46s
CI / Tests (PHP 8.1) (push) Successful in 46s
CI / PHPStan (push) Successful in 2m47s
CI / Coding Standards (push) Successful in 2m50s
CI / Build Plugin Zip (push) Successful in 2m49s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 2m40s
Reviewed-on: #100
2026-07-23 21:29:37 +00:00
thatguygriff 013558019e Merge pull request 'Group-class scheduling, instructor assignment, and details/invite management' (#99) from feature/group-class-scheduling into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.1) (push) Successful in 49s
CI / Tests (PHP 8.2) (push) Successful in 46s
CI / Coding Standards (push) Successful in 2m54s
CI / PHPStan (push) Successful in 2m54s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / Build Plugin Zip (push) Successful in 2m46s
Release / Build and Publish Release (push) Successful in 2m47s
Release / Open next-version bump PR (push) Successful in 4s
Reviewed-on: #99
2026-07-23 20:56:59 +00:00
thatguygriffandClaude Opus 4.8 87cfe921a9 Show instructor real name or nickname in group-class views, not the login
CI / Tests (PHP 8.2) (pull_request) Successful in 46s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m47s
CI / Coding Standards (pull_request) Successful in 2m51s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m41s
CI / Build Plugin Zip (pull_request) Skipped
Add Auth\UserName::format(), which prefers a user's first + last name, then
their nickname, avoiding display_name (which can be the login/username).
Route the instructor name through it in both the front-end offerings response
(instructor_name) and the back-end group-class summary and details views.

Tests: composer test (513), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 17:50:34 -03:00
thatguygriff 00376799b9 Merge pull request 'Group-class scheduling, instructor assignment, and details/invite management' (#98) from feature/group-class-scheduling into main
CI / Coding Standards (push) Successful in 2m51s
CI / PHPStan (push) Successful in 2m55s
CI / Tests (PHP 8.3) (push) Successful in 2m37s
CI / Tests (PHP 8.2) (push) Successful in 37s
CI / Tests (PHP 8.1) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
CI / Build Plugin Zip (push) Successful in 2m47s
Reviewed-on: #98
2026-07-23 20:34:16 +00:00
thatguygriffandClaude Opus 4.8 b066bef353 Add group-class scheduling, instructor assignment, and details/invite management
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m52s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped
Group classes now carry a specific class time (alongside date and duration)
and an assigned instructor:

- Schema: add `class_time` (TIME) to `us_offerings`; `Offering` gains
  `normalizeTime`/`sessionWindows`. (Rides the pending 1.0.0->1.1.0 dbDelta
  upgrade, so no version bump.)
- Offering form: class-time field, plus a studio-admin instructor picker
  (plain instructors always own their own classes).
- `ClassSlotReconciler`: assigning an instructor clears their open booking
  slots overlapping each session and flags already-booked lessons that clash
  (a booked lesson is never deleted). Uses new
  `AvailabilityRepository::findOverlapping`.
- Front end: `GET /offerings` exposes `instructor_name`; the enrolment page
  shows who teaches each class and when it meets.

Back-office group-class views redesigned:

- Instructor **My Group Classes** and studio-admin **Group Classes** are now
  per-class summaries with enrolment counts, not flat student lists.
- Each links through (`?class_id=<id>`) to a per-class **details page**
  (schedule panel, roster with payment status, and — for invite-only classes
  — the add/make-available/invite-by-email controls). Invite-only membership
  is managed entirely from this page.
- Invite actions are allowed for the class's owning instructor or any
  `view_all_lessons` studio admin, so an owner-operator (studio admin who also
  teaches) can reach every class's roster and invites from the Group Classes
  page.

Tests: composer test (508), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 17:29:48 -03:00
thatguygriff bba24566a6 Merge pull request 'Add CHANGELOG and publish it as release notes; auto-open next-version bump PR' (#97) from feature/changelog-release-notes into main
CI / Tests (PHP 8.1) (push) Successful in 39s
CI / Tests (PHP 8.2) (push) Successful in 48s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m53s
CI / Coding Standards (push) Successful in 2m56s
CI / Tests (PHP 8.3) (push) Successful in 2m36s
CI / Build Plugin Zip (push) Successful in 2m48s
Reviewed-on: #97
2026-07-23 19:54:40 +00:00
thatguygriffandClaude Opus 4.8 4bca2fbc96 Add CHANGELOG and publish it as release notes; auto-open next-version bump PR
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.3) (pull_request) Successful in 7m54s
CI / PHPStan (pull_request) Successful in 9m2s
CI / Coding Standards (pull_request) Successful in 9m12s
CI / Build Plugin Zip (pull_request) Skipped
Add CHANGELOG.md (one section per version, newest first; the top section
always reflects the current plugin header version — release status is
purely a matter of tagging).

The Release workflow now extracts the tagged version's changelog section
and publishes it as the Gitea release body (POST on create, PATCH when the
release was pre-created via the UI). After a stable release, a new
bump-version job bumps the plugin to the next patch version, opens a fresh
changelog section, and opens a PR. Pre-release tags are skipped.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 16:34:52 -03:00
thatguygriff 06c8d42cc0 Merge pull request 'Add invite-only group classes' (#96) from feature/invite-only-group-classes into main
CI / Tests (PHP 8.1) (push) Successful in 46s
CI / Tests (PHP 8.2) (push) Successful in 46s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m49s
CI / Coding Standards (push) Successful in 2m58s
CI / Tests (PHP 8.3) (push) Successful in 2m36s
CI / Build Plugin Zip (push) Successful in 2m50s
Reviewed-on: #96
2026-07-23 16:55:37 +00:00
thatguygriffandClaude Opus 4.8 a281935811 Add invite-only group classes
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m49s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped
Group classes can now be marked invite-only (us_offerings.access_mode).
Invite-only classes are hidden from the public catalog and reachable only
when the instructor lets someone in via one of three paths, managed from
My Lessons -> My Group Classes:

- Add students directly: enrols them now with a pending payment.
- Make available: grants registered students access to self-enrol through
  the normal paid flow (multi-select, emailed a notice).
- Invite by email: tokenised registration invite tied to the class for a
  non-account address; after they register the class becomes enrollable.
  Reuses an existing pending invite instead of sending a second link.

New us_group_access table records grants; GET /offerings merges granted
invite-only classes for the caller; enrolment requires a grant
(403 invite_required) and flips it to enrolled on success.

composer test (487), composer lint, composer cs all pass.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 13:51:02 -03:00
thatguygriff 25aeba9dc1 Merge pull request 'Add instructor group-class roster view under My Lessons' (#95) from feature/instructor-group-classes into main
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m53s
CI / PHPStan (push) Successful in 2m49s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / Build Plugin Zip (push) Successful in 2m49s
Reviewed-on: #95
2026-07-23 15:58:09 +00:00
thatguygriffandClaude Opus 4.8 5f9d5ffc4f Add instructor group-class roster view under My Lessons
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / Tests (PHP 8.1) (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m50s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
Instructors can now see their own group classes under My Lessons →
My Group Classes (view_own_lessons): each class shows its active
enrolment count against capacity plus a roster of enrolled students
with enrolment and payment status.

GroupClassController gains renderInstructorPage(), backed by the
existing per-instructor enrolment query and a newly injected
PaymentRepository for payment status. Wired as a submenu under the
existing My Lessons menu, inside the same !view_all_lessons guard so
owner-operators don't get a duplicate item.

Closes #71

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 12:52:27 -03:00
thatguygriff 90fdde8c06 Merge pull request 'Cancellation cutoff: limit how close to a lesson a student can cancel' (#94) from feature/cancellation-cutoff into main
CI / Tests (PHP 8.2) (push) Successful in 44s
CI / Tests (PHP 8.1) (push) Successful in 49s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m47s
CI / Coding Standards (push) Successful in 2m59s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / Build Plugin Zip (push) Successful in 2m44s
Reviewed-on: #94
2026-07-23 15:14:56 +00:00
thatguygriffandClaude Opus 4.8 169f7b6a13 Accept whole days only for the studio cancellation cutoff
CI / Tests (PHP 8.2) (pull_request) Successful in 38s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Coding Standards (pull_request) Successful in 3m2s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m40s
CI / Build Plugin Zip (pull_request) Skipped
The Studio Settings cutoff field now takes an integer number of days (step 1,
coerced with Val::int) instead of allowing half-day fractions, and displays the
stored hours rounded to whole days.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 12:10:55 -03:00
thatguygriffandClaude Opus 4.8 8b90b8d78d Add cancellation cutoff limiting how close to a lesson a student can cancel
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Coding Standards (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / Build Plugin Zip (pull_request) Skipped
Students can no longer cancel their own lesson online once it starts within a
configured window; instructors and studio admins can always cancel.

- Studio default `us_cancellation_cutoff_hours` (stored/computed in hours,
  entered and displayed in days under Studio Settings → Cancellations).
- Optional per-offering override `cancellation_cutoff_hours` (entered in hours);
  blank inherits the studio default, 0 allows anytime cancellation.
- `Booking\CancellationPolicy` resolves the effective window and decides;
  `BookingEndpoint::cancel()` returns a 403 `cancellation_closed` when too late.
  The instructor status endpoint and studio-admin student actions bypass it.

Closes #93

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 11:57:18 -03:00
thatguygriff a777f5d05a Merge pull request 'Point plugin metadata links at Unsupervised and Gitea' (#92) from feature/view-details-gitea-releases into main
CI / Tests (PHP 8.1) (push) Successful in 42s
CI / Tests (PHP 8.2) (push) Successful in 44s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / PHPStan (push) Successful in 2m46s
CI / Coding Standards (push) Successful in 3m2s
CI / Build Plugin Zip (push) Successful in 2m46s
Reviewed-on: #92
2026-07-23 14:26:51 +00:00
thatguygriffandClaude Opus 4.8 83be388186 Point plugin metadata links at Unsupervised and Gitea
CI / No Debug Code (pull_request) Successful in 4s
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m21s
CI / Coding Standards (pull_request) Successful in 2m52s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped
Make the "By Unsupervised" author link go to https://unsupervised.ca
(via a new Author URI header) and the plugin site / "View details" link
point at the Gitea project instead of WordPress.org.

Core's "View details" link opened a thickbox iframe against the
WordPress.org plugin-information API, which 404s ("Plugin not found")
for this off-directory plugin. A plugin_row_meta filter now replaces it
with a new-tab link to the matching Gitea release tag page; embedding
Gitea in the iframe is blocked by its X-Frame-Options anyway.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 11:14:44 -03:00
thatguygriff 8d6615da67 Merge pull request 'Studio-defined account-registration questions' (#91) from feature/account-registration-questions into main
CI / Tests (PHP 8.1) (push) Successful in 43s
CI / Tests (PHP 8.2) (push) Successful in 44s
CI / Build Plugin Zip (push) Successful in 2m48s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m48s
CI / PHPStan (push) Successful in 2m56s
CI / Tests (PHP 8.3) (push) Successful in 2m35s
Reviewed-on: #91
2026-07-23 13:50:34 +00:00
thatguygriffandClaude Opus 4.8 49c59a950c Add studio-defined account-registration questions
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m46s
CI / PHPStan (pull_request) Successful in 2m58s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m41s
CI / Build Plugin Zip (pull_request) Skipped
Studio admins can now define registration questions that every new student
answers as a required second step during signup, with each student's answers
shown under a "Registration Information" section in the admin.

Extends the existing Registration domain: us_questions gains a scope column
(offering | account) and a nullable offering_id, and account answers reuse
us_question_answers with registration_type = 'account'. Authoring reuses the
Offerings -> Questions page via an "Account signup" scope (studio-admin only).
The registration form becomes two steps (progressive enhancement via
assets/js/register.js; works without JS); required answers are validated before
the account is created and apply to all signup paths (invite, group link,
self-approval). StudentHistory::registrationInfo() powers the admin section.

Bumps the plugin version to 1.1.0 so dbDelta runs the schema migration.

Closes #90

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-23 09:59:59 -03:00
thatguygriff 34e8f660ab Merge pull request 'Bump version to 1.0.0 for the first stable release' (#89) from release/1.0.0 into main
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / Coding Standards (push) Successful in 2m47s
CI / PHPStan (push) Successful in 2m54s
CI / Tests (PHP 8.3) (push) Successful in 2m38s
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / No Debug Code (push) Successful in 2s
CI / Build Plugin Zip (push) Successful in 2m34s
Release / Build and Publish Release (push) Successful in 2m45s
Reviewed-on: #89
2026-07-22 15:02:46 +00:00
thatguygriffandClaude Fable 5 358deda868 Bump version to 1.0.0 for the first stable release
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 38s
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / Coding Standards (pull_request) Successful in 2m50s
CI / PHPStan (pull_request) Successful in 2m48s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m34s
CI / Build Plugin Zip (pull_request) Skipped
Version header, USC_VERSION, and the README version line move from
1.0.0-rc.3 (README was stale at rc.1) to 1.0.0. Tag v1.0.0 on the merge
commit to publish the release.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:59:17 -03:00
thatguygriff 9d8d93ea60 Merge pull request 'Bump plugin version so the us_invites schema migration actually runs' (#88) from fix/invite-schema-migration into main
CI / Build Plugin Zip (push) Successful in 2m34s
CI / Tests (PHP 8.1) (push) Successful in 45s
CI / Tests (PHP 8.2) (push) Successful in 52s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m48s
CI / Coding Standards (push) Successful in 2m52s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
Reviewed-on: #88
2026-07-22 14:41:20 +00:00
thatguygriffandClaude Fable 5 0d9aafbb5b Bump plugin version so the us_invites schema migration actually runs
CI / Tests (PHP 8.2) (pull_request) Successful in 37s
CI / Tests (PHP 8.1) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Skipped
PR #83 added kind and expires_at to us_invites and the repository started
writing them, but USC_VERSION stayed at 1.0.0-rc.2 — Plugin::boot() only
re-runs Installer/dbDelta on a version mismatch, so upgraded sites never got
the columns. Every invite insert then failed silently: nothing appeared under
Pending Invites while the admin was still shown a registration link whose
token hash was never stored.

- Version / USC_VERSION -> 1.0.0-rc.3 (triggers dbDelta on next load).
- InviteRepository::insert() returns 0 on failure instead of a stale
  insert_id, and the Invites page now shows an error notice instead of a
  dead link when creation fails (personal and group forms), including
  clearer validation messages.
- CLAUDE.md: schema changes must bump the version.

Closes #87

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:37:14 -03:00
thatguygriff c9eaa6f3bc Merge pull request 'Hide the My Lessons menu for users who already see Scheduler' (#86) from fix/hide-my-lessons-menu into main
CI / Tests (PHP 8.1) (push) Successful in 40s
CI / Coding Standards (push) Successful in 2m46s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.2) (push) Successful in 41s
CI / PHPStan (push) Successful in 2m44s
CI / Tests (PHP 8.3) (push) Successful in 2m36s
CI / Build Plugin Zip (push) Successful in 2m43s
Reviewed-on: #86
2026-07-22 14:19:05 +00:00
thatguygriff 1bd0e33401 Merge pull request 'Default the availability admin page to the week view' (#84) from fix/availability-week-default into main
CI / Tests (PHP 8.1) (push) Successful in 43s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / No Debug Code (push) Successful in 3s
CI / PHPStan (push) Successful in 2m44s
CI / Coding Standards (push) Successful in 2m46s
CI / Tests (PHP 8.3) (push) Successful in 2m41s
CI / Build Plugin Zip (push) Successful in 2m46s
Reviewed-on: #84
2026-07-22 14:18:21 +00:00
thatguygriffandClaude Fable 5 e324c5d585 Hide the My Lessons menu for users who already see Scheduler
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / No Debug Code (pull_request) Successful in 1s
CI / Coding Standards (pull_request) Successful in 2m46s
CI / PHPStan (pull_request) Successful in 2m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m34s
CI / Build Plugin Zip (pull_request) Skipped
Scheduler (view_all_lessons) is a superset of My Lessons — same template,
every instructor's lessons, same payment edit forms — so for an
owner-operator both menu items showed the same data twice. The My Lessons
menu item is now only registered for users without view_all_lessons;
instructors are unaffected.

Closes #85

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:11:22 -03:00
thatguygriffandClaude Fable 5 266f572884 Default the availability admin page to the week view too
CI / Tests (PHP 8.2) (pull_request) Successful in 38s
CI / Tests (PHP 8.1) (pull_request) Successful in 48s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 2m49s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / Build Plugin Zip (pull_request) Skipped
Follow-up demo feedback: the My Availability page now opens in its weekly
calendar (usc_view=list opts back into the table, which keeps the bulk-delete
form), matching the new lessons defaults.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 11:09:23 -03:00
thatguygriff 5808defd1a Merge pull request 'Add multi-use group invite links with expiry and auto-approval on email confirmation' (#83) from feature/group-invite-links into main
CI / Tests (PHP 8.1) (push) Successful in 38s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m50s
CI / PHPStan (push) Successful in 2m49s
CI / Tests (PHP 8.3) (push) Successful in 2m38s
CI / Build Plugin Zip (push) Successful in 2m47s
Reviewed-on: #83
2026-07-22 13:49:15 +00:00
thatguygriffandClaude Fable 5 356d9f984d Add multi-use group invite links with expiry and auto-approval on email confirmation
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m46s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / Build Plugin Zip (pull_request) Skipped
A studio admin can generate a shareable group invite link (e.g. for a
newsletter) from the Invites page, choosing a required expiry date. Anyone
with the link may register while it is valid, in any registration mode: the
form collects their own email, they must confirm it via the usual hashed
token, and confirming approves the account immediately — group signups never
enter the Pending Students queue.

- us_invites grows kind (personal/group) and expires_at; an explicit expiry
  wins over the personal 14-day window. Group links stay pending (multi-use)
  until revoked or expired.
- RegistrationPage: group signups create the account pending with the
  us_auto_approve marker and send the confirmation email; no auto-login.
- EmailConfirmationHandler: auto-approve accounts are approved on
  confirmation, emailed the approved notice, and redirected to a new
  us_confirmed=ready notice with a sign-in link.

Closes #77

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 10:44:16 -03:00
thatguygriff b89a44047d Merge pull request 'Lock the registration email to the invite only when the invite is redeemable' (#82) from fix/invite-email-lock into main
CI / Build Plugin Zip (push) Successful in 2m45s
CI / Tests (PHP 8.1) (push) Successful in 40s
CI / Tests (PHP 8.2) (push) Successful in 38s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m45s
CI / PHPStan (push) Successful in 2m50s
CI / Tests (PHP 8.3) (push) Successful in 2m35s
Reviewed-on: #82
2026-07-22 13:31:17 +00:00
thatguygriff 4c2cc31c34 Merge pull request 'Charge weekly reservations for every claimed occurrence and confirm the whole series' (#81) from fix/recurring-payment-amount into main
CI / Tests (PHP 8.1) (push) Successful in 38s
CI / Coding Standards (push) Successful in 2m47s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.3) (push) Successful in 2m39s
CI / PHPStan (push) Successful in 2m51s
CI / Build Plugin Zip (push) Successful in 2m43s
Reviewed-on: #81
2026-07-22 13:26:15 +00:00
thatguygriffandClaude Fable 5 681fc5ae07 Lock the registration email to the invite only when the invite is redeemable
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / Tests (PHP 8.2) (pull_request) Successful in 37s
CI / PHPStan (pull_request) Successful in 2m45s
CI / Build Plugin Zip (pull_request) Skipped
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
The register form keyed the read-only, prefilled email off any invite row
matching the token. A stale token (expired / accepted / revoked) with open
registration on therefore showed the stale invite's address read-only while
the submit handler took the open branch and required a posted email the
locked field never submits, dead-ending the form. The lock now applies
exactly when the invite is acceptable; otherwise the editable field renders.

Closes #78

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 10:24:44 -03:00
thatguygriffandClaude Fable 5 ff059909a5 Charge weekly reservations for every claimed occurrence and confirm the whole series
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m50s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / PHPStan (pull_request) Successful in 2m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m38s
CI / Build Plugin Zip (pull_request) Skipped
A weekly booking on a per-lesson (one_time) priced offering was creating its
single upfront payment for one week's price while reserving up to 12 weeks,
and settling that payment confirmed only the anchor lesson, leaving the rest
of the series pending forever.

- BookingEndpoint now charges price x claimed occurrences for one_time
  billing; a full_term price is still charged once since it covers the term.
- PaymentService::confirmRegistration resolves the anchor lesson's series and
  confirms every non-cancelled row via the new
  BookingRepository::updateStatusForSeries().

Closes #79

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 10:19:53 -03:00
thatguygriff 972c0624c1 Merge pull request 'Default lessons to a week view on the booking page and in wp-admin' (#80) from fix/week-view-default into main
CI / Tests (PHP 8.2) (push) Successful in 38s
CI / Tests (PHP 8.1) (push) Successful in 43s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m52s
CI / PHPStan (push) Successful in 2m53s
CI / Tests (PHP 8.3) (push) Successful in 2m42s
CI / Build Plugin Zip (push) Successful in 2m46s
Reviewed-on: #80
2026-07-22 13:19:47 +00:00
thatguygriffandClaude Fable 5 14f43232c9 Default lessons to a week view on the booking page and in wp-admin
CI / Tests (PHP 8.2) (pull_request) Successful in 1m15s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m16s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 3m15s
CI / PHPStan (pull_request) Successful in 3m14s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m37s
CI / Build Plugin Zip (pull_request) Skipped
The front-end booking calendar now opens in the Week view (anchored to the
week of the earliest open slot) with List still available. The Scheduler and
My Lessons admin pages gain a week calendar (usc_view/usc_week, bucketed via
a new generic WeekCalendar::bucket()) and open in it by default; the original
table remains as the List view since it carries the HST / e-transfer forms.

Closes #76

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-22 10:13:04 -03:00
thatguygriff caa402778d Merge pull request 'Student detail: history sections and admin actions (cancel, withdraw, edit account)' (#75) from feature/student-detail-history into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.2) (push) Successful in 37s
CI / Tests (PHP 8.1) (push) Successful in 43s
CI / Coding Standards (push) Successful in 2m56s
CI / PHPStan (push) Successful in 2m57s
CI / Tests (PHP 8.3) (push) Successful in 2m37s
CI / Build Plugin Zip (push) Successful in 2m44s
Reviewed-on: #75
2026-07-18 21:28:53 +00:00
thatguygriffandClaude Fable 5 5808523140 Add admin actions to the student detail view: cancel, withdraw, edit account
CI / Tests (PHP 8.2) (pull_request) Successful in 43s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m37s
CI / Tests (PHP 8.1) (pull_request) Successful in 44s
CI / Coding Standards (pull_request) Successful in 2m43s
CI / PHPStan (pull_request) Successful in 2m50s
CI / Build Plugin Zip (pull_request) Skipped
Adds the #70 follow-up onto the student detail page: studio admins can now
cancel an upcoming lesson (same path as student cancellation — slot freed,
pending payment voided), withdraw an active group-class enrolment (seat
freed, pending payment voided), and edit the student's display name and
email with validation and uniqueness checks.

Action logic lives in the new Auth\StudentActions (unit-tested with mocked
repositories); the controller routes nonce-protected POSTs to it and shows
success/error notices.

Closes #70

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 18:25:12 -03:00
thatguygriffandClaude Fable 5 c49171695a Add policy, intake, and payment history to the admin student detail view
CI / Coding Standards (pull_request) Successful in 2m47s
CI / PHPStan (pull_request) Successful in 2m56s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m39s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.2) (pull_request) Successful in 43s
CI / Tests (PHP 8.1) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 2s
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]>
2026-07-18 18:17:20 -03:00
thatguygriff 05d1728248 Merge pull request 'Update README and group-classes doc to reflect shipped Stripe payments' (#74) from docs/readme-payments-status into main
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / Coding Standards (push) Successful in 2m48s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.2) (push) Successful in 38s
CI / PHPStan (push) Successful in 2m45s
CI / Tests (PHP 8.3) (push) Successful in 2m34s
CI / Build Plugin Zip (push) Successful in 2m48s
Reviewed-on: #74
2026-07-18 21:08:07 +00:00
thatguygriffandClaude Fable 5 da9a449d55 Update README and group-classes doc to reflect shipped Stripe payments
CI / Tests (PHP 8.2) (pull_request) Successful in 38s
CI / Tests (PHP 8.1) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 1s
CI / Coding Standards (pull_request) Successful in 2m49s
CI / PHPStan (pull_request) Successful in 2m54s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
The README still listed Payments as partial with the Stripe card charge
pending, and group-classes.md still described the pre-#7 payment seam.
Both are behind the code: StripeGateway/PaymentEndpoint ship the live
card charge, and enrolments create and link payments via PaymentService.

Fixes #73

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 18:06:58 -03:00
thatguygriff ca2607e5ec Merge pull request 'Show a sign-in link instead of the registration form after email confirmation' (#68) from feature/confirmed-signin-link into main
CI / Tests (PHP 8.1) (push) Successful in 37s
CI / Coding Standards (push) Successful in 2m46s
CI / Build Plugin Zip (push) Successful in 2m44s
CI / Tests (PHP 8.2) (push) Successful in 47s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m53s
CI / Tests (PHP 8.3) (push) Successful in 2m36s
Reviewed-on: #68
2026-07-18 21:00:22 +00:00
thatguygriffandClaude Fable 5 7b00811133 Show a sign-in link instead of the form after email confirmation
CI / Tests (PHP 8.2) (pull_request) Successful in 41s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 2m45s
CI / Coding Standards (pull_request) Successful in 2m55s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m35s
CI / Build Plugin Zip (pull_request) Skipped
Closes #67

When a student lands on the registration page from the confirmation
email (?us_confirmed=1), replace the registration form with the
confirmation message and a "Sign in to your account" link — the form
is useless at that point and re-submitting would only produce an
"account already exists" error. A confirmed-but-unapproved student can
already log in (the pending gate only withholds booking), so signing in
is the natural next step.

The link target follows the booking block's pattern: a loginPageId
block attribute (page picker in the editor sidebar) or login_page_id
shortcode attribute, falling back to wp_login_url(). The expired-link
notice keeps the form as before.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 17:53:38 -03:00
thatguygriff 8d79fbdb1f Merge pull request 'Auto-update the plugin from tagged Gitea releases' (#66) from feature/plugin-self-update into main
CI / Tests (PHP 8.1) (push) Successful in 41s
CI / PHPStan (push) Successful in 2m51s
CI / Tests (PHP 8.2) (push) Successful in 43s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 2m35s
CI / Coding Standards (push) Successful in 3m59s
CI / Build Plugin Zip (push) Successful in 2m43s
Reviewed-on: #66
2026-07-18 14:22:05 +00:00
thatguygriffandClaude Fable 5 ab055c7a0c Serve plugin updates from tagged Gitea releases
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m43s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m34s
CI / Build Plugin Zip (pull_request) Skipped
Closes #65

Declare an Update URI header and answer core's update_plugins_{hostname}
filter from a new Update\UpdateChecker that offers the latest published
Gitea release's zip asset when it is newer than the installed version,
with transient caching and silent degradation on API failures.

Add a release workflow that fires on v* tag pushes: verifies the tag
matches the plugin Version header, runs the tests, builds the plugin zip,
and attaches it to the release (reusing a UI-created release, flagging
hyphenated versions as pre-release so /releases/latest skips them).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 11:09:50 -03:00
thatguygriff e4af0c327c Merge pull request 'Open student registration with email confirmation and admin approval' (#64) from feature/student-self-signup into main
CI / Tests (PHP 8.1) (push) Successful in 36s
CI / Tests (PHP 8.2) (push) Successful in 44s
CI / No Debug Code (push) Successful in 4s
CI / Coding Standards (push) Successful in 2m46s
CI / PHPStan (push) Successful in 2m54s
CI / Tests (PHP 8.3) (push) Successful in 2m37s
CI / Build Plugin Zip (push) Successful in 2m48s
Reviewed-on: #64
2026-07-18 13:58:00 +00:00
thatguygriffandClaude Opus 4.8 7370755951 Add open student registration with email confirmation and approval
CI / Tests (PHP 8.1) (pull_request) Successful in 1m18s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m18s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 3m20s
CI / Coding Standards (pull_request) Successful in 3m25s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m33s
CI / Build Plugin Zip (pull_request) Skipped
Students could previously join by invite only. Add an optional
self-approval mode, toggled from Studio Settings → Registration: anyone
may sign up on the existing [us_student_register] page, confirm their
email via a tokenised link, and then be approved by a studio admin
before the account is usable.

- Enabling the toggle mirrors WordPress's own membership settings
  (users_can_register + default_role = us_student) and snapshots their
  previous values so disabling restores them.
- WordPress's native registration form is blocked while open
  registration is on (login_init redirect + registration_errors
  fail-safe + register_url) so it cannot bypass signup policy acceptance.
- Pending accounts: unconfirmed email cannot log in; confirmed but
  unapproved can log in but the booking capability is withheld and the
  booking page shows an "awaiting approval" screen.
- Approve/reject from Students → Pending Students; reject hard-deletes
  the account so the email is freed to re-apply.
- Invite registration is unchanged; both modes coexist.

Account lifecycle lives in user meta (RegistrationStatus); no new tables.

Closes #63

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-18 10:50:21 -03:00
thatguygriff e7d8257973 Merge pull request 'Show enrolment status on the student group classes page' (#62) from feature/enrolled-status into main
CI / Tests (PHP 8.1) (push) Successful in 46s
CI / Tests (PHP 8.2) (push) Successful in 46s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 2m36s
CI / Build Plugin Zip (push) Successful in 2m45s
CI / PHPStan (push) Successful in 1m41s
CI / Coding Standards (push) Successful in 2m48s
Reviewed-on: #62
2026-07-06 02:44:20 +00:00
thatguygriffandClaude Fable 5 30b0112431 Show enrolment status on the student group classes page
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / No Debug Code (pull_request) Successful in 2s
CI / PHPStan (pull_request) Successful in 1m17s
CI / Tests (PHP 8.3) (pull_request) Successful in 37s
CI / Coding Standards (pull_request) Successful in 2m47s
CI / Build Plugin Zip (pull_request) Has been skipped
The class list now loads the student's own enrolments alongside the
catalog; a class they already have an active enrolment in shows "You
are enrolled in this class." instead of the Enrol button, in both the
browse-all catalog and the single-class embed mode. Previously the
button always rendered and a duplicate attempt walked the student
through the whole questions/policies flow before failing with 409
already_enrolled. A cancelled enrolment does not block re-enrolling,
matching the server-side duplicate rule.

Closes #61

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 23:41:01 -03:00
thatguygriff a9f0b8c066 Merge pull request 'Group class term dates, single-class embed mode, and offering editing' (#60) from feature/group-class-terms into main
CI / Tests (PHP 8.2) (push) Successful in 44s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.3) (push) Successful in 32s
CI / Build Plugin Zip (push) Successful in 1m42s
CI / Tests (PHP 8.1) (push) Successful in 43s
CI / PHPStan (push) Successful in 49s
CI / Coding Standards (push) Successful in 2m43s
Reviewed-on: #60
2026-07-06 02:22:06 +00:00
thatguygriffandClaude Fable 5 cde8704267 Group class term dates, single-class embed mode, and offering editing
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / Tests (PHP 8.1) (pull_request) Successful in 48s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 1m14s
CI / PHPStan (pull_request) Successful in 1m16s
CI / Tests (PHP 8.3) (pull_request) Successful in 37s
CI / Build Plugin Zip (pull_request) Has been skipped
Group class offerings now carry real dates: the add/edit form takes a
start date plus a sessions control (one-off, or weekly for N sessions;
the end date is computed as start + (N-1) weeks via
Offering::weeklyTermEnd). Dates are validated strictly (Y-m-d) and shown
in the offerings list and on the student-facing class card, including
the weekly session count.

[us_group_classes offering="<id>"] (block attribute offeringId, chosen
from a dropdown of active classes fetched from the public offerings
endpoint) restricts the page to a single class so the enrolment flow can
be embedded on a page dedicated to that class; a pinned class that is no
longer offered reports itself closed instead of falling back to the
catalog.

Offerings are now editable from the admin screen: an Edit button
prefills the shared add/edit form and saving posts usc_action=update.
Updates always preserve the original owner and currency, and non-admin
instructors can only load and update their own offerings. The form also
gains the previously missing description field and an Active toggle (the
admin-UI counterpart of the REST is_active flag) so an edit cannot wipe
data the form never collected.

Closes #59

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 23:18:59 -03:00
thatguygriff 9d8924132c Merge pull request 'Bulk delete availability slots from the admin list view' (#58) from feature/availability-bulk-delete into main
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / Tests (PHP 8.2) (push) Successful in 47s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 1m11s
CI / Tests (PHP 8.3) (push) Successful in 35s
CI / Coding Standards (push) Successful in 2m14s
CI / Build Plugin Zip (push) Successful in 1m42s
Reviewed-on: #58
2026-07-06 01:32:31 +00:00
thatguygriffandClaude Fable 5 c743ed5459 Bulk delete availability slots from the admin list view
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 1m40s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m34s
CI / PHPStan (pull_request) Successful in 2m52s
CI / Build Plugin Zip (pull_request) Has been skipped
The list view of Current Slots gets a checkbox per unbooked slot, a
select-all header checkbox, and a Delete selected button submitting a
new bulk_delete form action. Each id is ownership-checked through the
same path as single delete; the repository's is_booked guard refuses
booked slots as a second layer. Row checkboxes attach to the bulk form
via the HTML form attribute because the table already contains the
per-row delete forms and forms cannot nest.

Closes #57

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 22:25:35 -03:00
thatguygriff aaa24e524f Merge pull request 'Require an offering on every lesson booking, with a student-facing picker' (#56) from fix/require-booking-offering into main
CI / Tests (PHP 8.1) (push) Successful in 42s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 2m17s
CI / Tests (PHP 8.3) (push) Successful in 2m2s
CI / Coding Standards (push) Successful in 2m49s
CI / Build Plugin Zip (push) Successful in 2m15s
Reviewed-on: #56
2026-07-06 01:14:18 +00:00
thatguygriffandClaude Fable 5 c7d5d72c9c Require an offering on every lesson booking, with a student-facing picker
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m40s
CI / PHPStan (pull_request) Successful in 2m24s
CI / Coding Standards (pull_request) Successful in 2m49s
CI / Build Plugin Zip (pull_request) Has been skipped
Generic slots (no tied offering) were bookable with no offering at all:
free, instantly confirmed, and with no intake questions. POST /bookings
now rejects offering-less bookings (400 offering_required), and a
student-chosen offering must be an active private-lesson type owned by
the slot's instructor whose duration matches the slot.

The registration form gains a Lesson type field: locked to the slot's
tied offering (title, duration, price) so the student sees what they
are booking, or a required picker of fitting offerings for generic
slots, with intake questions following the selection.

Fixes #55

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 22:09:27 -03:00
thatguygriffandClaude Fable 5 ab90dae638 Let students cancel their own lessons from the booking page
CI / No Debug Code (push) Successful in 3s
CI / Coding Standards (push) Successful in 46s
CI / Tests (PHP 8.1) (push) Successful in 45s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / PHPStan (push) Successful in 1m11s
CI / Tests (PHP 8.3) (push) Successful in 1m0s
CI / Build Plugin Zip (push) Successful in 1m10s
Adds POST /bookings/{id}/cancel (owner-only, idempotent): marks the lesson
cancelled, releases the availability slot for rebooking, and voids a
still-pending payment so it leaves the admin confirmation queue. Paid
payments are untouched — refunds stay a manual admin decision.

The instructor PATCH /bookings/{id}/status path now does the same slot
release and payment voiding on cancellation (previously cancelled lessons
left their slot permanently booked), and reinstating a cancelled lesson
re-claims the slot, rejecting with 409 if the freed time was rebooked.

The "Your upcoming lessons" panel gets a Cancel button with a confirm
prompt; on success both the lesson list and the slot calendar refresh.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 17:17:30 -03:00
thatguygriff 9dd3c39ddd Merge pull request 'Skip payment step for unpriced bookings, confirm them immediately, show students their lessons' (#54) from fix/unpriced-booking-flow into main
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / Tests (PHP 8.2) (push) Successful in 46s
CI / No Debug Code (push) Successful in 2s
CI / PHPStan (push) Successful in 1m45s
CI / Tests (PHP 8.3) (push) Successful in 1m4s
CI / Coding Standards (push) Successful in 2m23s
CI / Build Plugin Zip (push) Successful in 2m16s
Reviewed-on: #54
2026-07-05 20:06:46 +00:00
thatguygriffandClaude Fable 5 5888032ed7 Skip payment step for unpriced bookings, confirm them immediately, show students their lessons
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 2m46s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Coding Standards (pull_request) Successful in 47s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m35s
CI / Build Plugin Zip (pull_request) Has been skipped
Booking a slot with no priced offering created the lesson but no payment,
yet the front end still called POST /payments/intent, which 400ed with
"Could not start payment for this registration" — the student saw an error
while the backend held a claimed slot and a lesson stuck at pending.

- POST /bookings and POST /enrollments now return a `payment` summary
  ({id, method, status}) or null when nothing is owed; the JS only runs
  the payment step when a payment exists.
- Bookings with nothing owed are confirmed at creation — there is no
  payment step that would ever confirm them later.
- The booking page now shows the student's upcoming lessons (GET /bookings,
  now scoped to upcoming non-cancelled lessons with slot start/end times)
  with a pending-payment/confirmed status badge.

Fixes #53

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 17:02:38 -03:00
thatguygriff 93dccd6352 Merge pull request 'Block options for login/booking link targets with optional auto-redirect' (#52) from feature/block-link-targets into main
CI / Tests (PHP 8.1) (push) Successful in 54s
CI / PHPStan (push) Successful in 57s
CI / Tests (PHP 8.2) (push) Successful in 53s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 1m53s
CI / Tests (PHP 8.3) (push) Successful in 2m38s
CI / Build Plugin Zip (push) Successful in 53s
Reviewed-on: #52
2026-07-05 19:19:30 +00:00
thatguygriffandClaude Fable 5 9d89bc6d0e Add link-target and auto-redirect options to booking/login blocks
CI / Coding Standards (pull_request) Successful in 51s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m2s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 53s
CI / PHPStan (pull_request) Successful in 1m24s
The booking block gains a loginPageId attribute choosing which page its
logged-out "log in to book a lesson" link points to (default remains the
WordPress login screen), and the student-login block gains a
bookingPageId attribute controlling the logged-in "View available
lessons" link and the post-login redirect target (default remains the
current page). Both blocks also gain an autoRedirect toggle, off by
default, that sends the visitor straight to the target page; block
rendering starts after output, so the redirect runs on
template_redirect by parsing the queried page's content for the block,
with a self-target guard against redirect loops. The link targets are
also available to the shortcodes as login_page_id/booking_page_id.

Also fixes a pre-existing fatal: WordPress passes an empty string (not
an array) to shortcode callbacks when a shortcode is used without
attributes, so bare [us_booking] etc. threw a TypeError against the
strictly-typed render(array $atts) methods. ShortcodeRegistrar now
wraps each callback to normalize non-array attribute values.

Closes #51

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 16:16:52 -03:00
thatguygriff 43497503b9 Merge pull request 'Split availability windows into bookable lesson-length slots; weekly calendar views; 12-hour times' (#50) from feature/slot-splitting-and-week-view into main
CI / PHPStan (push) Successful in 1m42s
CI / Tests (PHP 8.2) (push) Successful in 40s
CI / Tests (PHP 8.1) (push) Successful in 56s
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.3) (push) Successful in 34s
CI / Coding Standards (push) Successful in 2m45s
CI / Build Plugin Zip (push) Successful in 1m14s
Reviewed-on: #50
2026-07-05 19:14:50 +00:00
thatguygriffandClaude Fable 5 dbf61e8593 Word-bound the no-debug CI grep so method calls like ->add() don't match dd(
CI / Tests (PHP 8.2) (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 1m20s
CI / PHPStan (pull_request) Successful in 1m43s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m5s
The unanchored dd\( pattern matched the substring in DateTimeImmutable::add(),
failing the check on non-debug code.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 16:12:37 -03:00
thatguygriffandClaude Fable 5 b7d5e3039e Split availability windows into bookable lesson-length slots with weekly calendar views
CI / Build Plugin Zip (pull_request) Has been skipped
CI / Tests (PHP 8.2) (pull_request) Successful in 45s
CI / PHPStan (pull_request) Successful in 2m48s
CI / Tests (PHP 8.1) (pull_request) Successful in 41s
CI / No Debug Code (pull_request) Failing after 2s
CI / Coding Standards (pull_request) Successful in 52s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
Availability windows were stored and served as a single bookable row, so a
9:00 AM-4:00 PM window showed to students as one giant slot and booking it
consumed the whole day; past and multi-day windows also leaked into the
booking page as nonsense entries.

- Split windows into consecutive lesson-length slots on save (REST and admin
  form); each chunk is independently bookable and weekly recurrence creates a
  series per chunk so "reserve this time weekly" holds the same hour each week
- Reject windows spanning multiple days or shorter than the lesson length
  (400 invalid_window)
- Never return slots whose start has passed from GET /availability
- Migrate pre-split rows: Plugin::boot re-runs the Installer on version change
  and AvailabilityRepository::splitOversizedWindows() rewrites unbooked
  same-day oversized windows in place
- Display all times in 12-hour AM/PM form (booking page, wp-admin lists,
  editor previews)
- Add a List | Week view toggle to the student booking page and the
  instructor availability page, with previous/next-week navigation honouring
  the site's start_of_week option (new WeekCalendar helper)

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 16:00:47 -03:00
thatguygriff 66f308e1da Merge pull request 'Replace Slot ID column in lessons list with the lesson's date/time' (#48) from feature/lessons-datetime-column into main
CI / Tests (PHP 8.1) (push) Successful in 54s
CI / PHPStan (push) Successful in 1m38s
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / No Debug Code (push) Successful in 2s
CI / Coding Standards (push) Successful in 2m44s
CI / Build Plugin Zip (push) Successful in 1m43s
CI / Tests (PHP 8.3) (push) Successful in 2m35s
Reviewed-on: #48
2026-07-05 18:57:16 +00:00
thatguygriffandClaude Fable 5 6ff733a71f Replace Slot ID column in lessons list with the lesson's date/time
CI / Tests (PHP 8.2) (pull_request) Successful in 1m20s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m21s
CI / Coding Standards (pull_request) Successful in 1m45s
CI / PHPStan (pull_request) Successful in 3m22s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m36s
CI / Build Plugin Zip (pull_request) Has been skipped
The admin dashboard and instructor My Lessons pages showed the raw
availability-slot database ID, which is meaningless to admins and
instructors. LessonController now takes AvailabilityRepository, looks up
each lesson's slot, and renders its window as e.g.
"Jul 6, 2026 9:00 AM-10:00 AM" via mysql2date. The date is repeated on
the end time only when a slot crosses midnight, and lessons whose slot
row no longer exists show an em dash.

Closes #47

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-05 15:51:54 -03:00
thatguygriff aea731c2f8 Merge pull request 'Upgrade PHPStan to 2.x and raise analysis level from 6 to 10' (#46) from chore/phpstan-2-upgrade into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.1) (push) Successful in 45s
CI / Tests (PHP 8.2) (push) Successful in 52s
CI / Tests (PHP 8.3) (push) Successful in 56s
CI / PHPStan (push) Successful in 1m2s
CI / Coding Standards (push) Successful in 1m5s
CI / Build Plugin Zip (push) Successful in 56s
Reviewed-on: #46
2026-06-12 16:54:57 +00:00
thatguygriffandClaude Fable 5 1d6ac46ba3 Upgrade PHPStan to 2.x and raise analysis level from 6 to 10
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / Tests (PHP 8.3) (pull_request) Successful in 52s
CI / Coding Standards (pull_request) Successful in 57s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m1s
CI / PHPStan (pull_request) Successful in 1m11s
CI / Build Plugin Zip (pull_request) Has been skipped
- 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]>
2026-06-12 13:42:50 -03:00
thatguygriff b23508f726 Merge pull request 'Gutenberg dynamic-block wrappers for shortcodes with editor previews' (#45) from feature/editor-blocks into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.2) (push) Successful in 54s
CI / Tests (PHP 8.1) (push) Successful in 54s
CI / Tests (PHP 8.3) (push) Successful in 1m6s
CI / Coding Standards (push) Successful in 1m12s
CI / PHPStan (push) Successful in 1m12s
CI / Build Plugin Zip (push) Successful in 1m27s
Reviewed-on: #45
2026-06-12 15:14:02 +00:00
thatguygriffandClaude Fable 5 fc70cde9d5 Add Gutenberg dynamic-block wrappers for the front-end shortcodes
CI / No Debug Code (pull_request) Successful in 4s
CI / Tests (PHP 8.2) (pull_request) Successful in 52s
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m29s
CI / Coding Standards (pull_request) Successful in 1m57s
CI / PHPStan (pull_request) Successful in 2m14s
CI / Build Plugin Zip (pull_request) Has been skipped
Wrap the four shortcodes (us_booking, us_student_login,
us_student_register, us_group_classes) in dynamic blocks so pages can be
previewed and styled in the block editor. Front-end rendering delegates
to the same page objects the shortcodes use; in the editor's
block-renderer REST preview a static, script-free BlockPreview is
rendered instead (no live REST calls, redirects, or Stripe.js). The
editor script (vanilla JS, no build step) registers each block with
wp.serverSideRender previews and shortcode transforms; frontend.css is
attached as the block style so previews pick up theme styling.

Resolves #44

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-12 12:03:27 -03:00
thatguygriff 63e2fbcc5b Merge pull request 'Security fixes: CSV injection, policy body output, invite token hashing, slot datetime validation' (#43) from feature/security-review-fixes into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / Tests (PHP 8.1) (push) Successful in 50s
CI / Tests (PHP 8.3) (push) Successful in 54s
CI / Coding Standards (push) Successful in 1m1s
CI / PHPStan (push) Successful in 1m9s
CI / Build Plugin Zip (push) Successful in 1m6s
Reviewed-on: #43
2026-06-10 19:51:20 +00:00
thatguygriffandClaude Fable 5 f3f5c7801f Security fixes: CSV injection, policy body output, invite hashing, slot datetimes
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / Tests (PHP 8.3) (pull_request) Successful in 49s
CI / Tests (PHP 8.2) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 1m11s
CI / PHPStan (pull_request) Successful in 1m20s
CI / Build Plugin Zip (pull_request) Has been skipped
Four fixes from a security review pass:

- Neutralise CSV formula injection in the payments export: fields with a
  leading =, +, -, @, tab, or CR (e.g. a hostile student display name) are
  apostrophe-prefixed in PaymentReport::csvLine() so they open as text in
  Excel/Google Sheets. Fixes #39.
- Sanitise policy bodies with wp_kses_post at output in
  PolicyEndpoint::index() (the booking JS renders that HTML raw), so a
  future write path that forgets kses can never become stored XSS.
  Fixes #40.
- Store invite tokens hashed (SHA-256) at rest: a database leak can no
  longer redeem pending invites. The registration link is shown once, at
  creation; the pending list shows email/invited date; lookups hash the
  submitted token. Existing plaintext pending invites must be re-issued.
  Fixes #41.
- Validate availability slot datetimes on both creation paths (REST and
  admin form) via AvailabilitySlot::normalizeDateTime(): canonical and
  datetime-local forms normalise to Y-m-d H:i:s, garbage and end <= start
  are rejected (REST 400) instead of reaching the DATETIME column or
  throwing inside the weekly-series date arithmetic. Fixes #42.

composer test (204 tests, 594 assertions), PHPStan L6, and PHPCS all green.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-06-10 16:36:26 -03:00
thatguygriff 693246c1c1 Merge pull request 'Security hardening: booking auth, offering exposure, payments, invites (#31–#37)' (#38) from feature/security-fixes into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / Tests (PHP 8.2) (push) Successful in 51s
CI / Coding Standards (push) Successful in 58s
CI / PHPStan (push) Successful in 1m2s
CI / Tests (PHP 8.3) (push) Successful in 1m41s
CI / Build Plugin Zip (push) Successful in 55s
Reviewed-on: #38
2026-06-09 20:11:34 +00:00
thatguygriffandClaude Opus 4.8 061d09e034 Harden booking, offering exposure, payments, and invites
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / Coding Standards (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 1m7s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m41s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / Build Plugin Zip (pull_request) Has been skipped
Security fixes from a pen-test review (issues #31–#37):

- #31 Booking no longer trusts a client-supplied offering_id: a slot-tied
  offering is authoritative and any offering used must belong to the slot's
  instructor, closing a free/misrouted-payment bypass.
- #34 Availability slot creation rejects an offering the instructor does not
  own (AvailabilityEndpoint now takes OfferingRepository).
- #32 Offering/question/policy listing endpoints now require book_lesson
  instead of being public (no anonymous consumer exists); Offering::toArray
  also omits etransfer_email from listings as defense-in-depth.
- #33 Slots are claimed atomically (UPDATE ... WHERE is_booked = 0) before a
  lesson is inserted, preventing a double-booking race.
- #35 A single weekly booking is capped (MAX_WEEKLY_OCCURRENCES) and only
  creates lessons for slots it actually claimed.
- #36 Stripe secret/webhook keys are write-only in the settings UI and a blank
  submit keeps the stored value; secrets are never echoed back into HTML.
- #37 Pending invites expire after 14 days (Invite::isAcceptable), enforced at
  registration and surfaced on the admin invites list.

Adds BookingEndpointTest plus Invite/Offering/AvailabilityRepository coverage
and minimal WP_REST_Request/WP_REST_Response stubs. composer test (200),
lint, and cs all green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-09 17:08:22 -03:00