Never drop an enrolled class from upcoming lessons; delete a guardian's children with them; pin the panel's line spacing
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m58s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m47s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 1m1s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 58s
CI / PHPStan (pull_request) Successful in 2m53s
CI / Coding Standards (pull_request) Successful in 2m58s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m47s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 1m1s
CI / No Debug Code (pull_request) Successful in 3s
Three fixes from testing the branch. A group class was only listed when its schedule resolved to exact datetimes, which needs a class time *and* a duration — both optional on the offering form, and the schedule note exists precisely so a studio can write "Tuesdays 4:00pm" instead. A class configured that way vanished from the list, which is the one thing this feature must never do. So Offering::sessionStarts() splits "when does it meet" from "how long does it run" (sessionWindows() is that plus the duration, unchanged), and SessionSchedule degrades instead of disappearing: dated rows with an open end when there is no duration, and a single row carrying Offering::scheduleLabel() when there is no time to derive dates from. Only a class whose last day has passed drops out. Deleting a guardian now deletes the children linked to them, releasing each one's lessons and enrolments first. A child account is login-less and exists only so the guardian has somebody to book for; without the guardian nobody can reach it, book for it, or be billed for it, so it was left stranded on the roster still holding slots. A `handled` set makes the re-entrant delete_user each child deletion fires a no-op, and stops a circular link recursing. The upcoming panel never stated its own line-height, so a theme setting line-height: 0 above it — the usual icon-font reset — was inherited straight through. Below 1 that produces both reported symptoms at once: stacked lines overlap, and the status pill's background is shorter than the text in it. Pinned at the same id-level specificity as the rest. Tests: composer test (863), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -8,10 +8,13 @@ use Unsupervised\Schedular\Booking\BookingRepository;
|
||||
use Unsupervised\Schedular\Booking\Lesson;
|
||||
use Unsupervised\Schedular\GroupClass\Enrollment;
|
||||
use Unsupervised\Schedular\GroupClass\EnrollmentRepository;
|
||||
use Unsupervised\Schedular\Guardian\GuardianRepository;
|
||||
use Unsupervised\Schedular\Guardian\GuardianService;
|
||||
use Unsupervised\Schedular\Payment\PaymentService;
|
||||
|
||||
/**
|
||||
* Gives back what a deleted account was holding.
|
||||
* Gives back what a deleted account was holding, and takes the accounts that
|
||||
* only existed underneath it with it.
|
||||
*
|
||||
* WordPress deletes a user without knowing anything about lessons, so a student
|
||||
* removed from **Users → Delete** used to leave their bookings behind: the
|
||||
@@ -24,6 +27,13 @@ use Unsupervised\Schedular\Payment\PaymentService;
|
||||
* lessons are deliberately left alone: they happened, they may have been paid
|
||||
* for, and the payment report has to keep adding up.
|
||||
*
|
||||
* A **guardian** takes their children with them. A child account is login-less
|
||||
* and exists only so the guardian has somebody to book for; without the
|
||||
* guardian nobody can reach it, book for it, or be billed for it, so leaving it
|
||||
* behind leaves an unreachable student on the roster holding slots that will
|
||||
* never be used. Each child's bookings are released on the same terms, the link
|
||||
* row goes, and the account is deleted.
|
||||
*
|
||||
* No account credit is issued for a paid lesson, unlike a cancellation the
|
||||
* student asks for. A credit only has value against future billing on the
|
||||
* account it belongs to, and that account is being deleted; a refund owed to
|
||||
@@ -32,38 +42,62 @@ use Unsupervised\Schedular\Payment\PaymentService;
|
||||
*/
|
||||
class DeletedUserCleanup {
|
||||
|
||||
/**
|
||||
* Accounts already dealt with this request, so deleting a guardian's child
|
||||
* — which fires `delete_user` again and re-enters this very handler — cannot
|
||||
* loop or redo work. It also makes a self-referential or circular guardian
|
||||
* link, however it got into the table, terminate rather than recurse.
|
||||
*
|
||||
* @var array<int, true>
|
||||
*/
|
||||
private array $handled = [];
|
||||
|
||||
public function __construct(
|
||||
private BookingRepository $bookings,
|
||||
private AvailabilityRepository $availability,
|
||||
private EnrollmentRepository $enrollments,
|
||||
private PaymentService $payments,
|
||||
private GuardianRepository $links,
|
||||
private GuardianService $guardians,
|
||||
) {}
|
||||
|
||||
public function register(): void {
|
||||
// `delete_user` fires before the row goes, which is what lets the lookups
|
||||
// below still find the account's bookings. `wpmu_delete_user` is the
|
||||
// multisite equivalent for a user removed from the network entirely.
|
||||
// below still find the account's bookings and children. `wpmu_delete_user`
|
||||
// is the multisite equivalent for a user removed from the network entirely.
|
||||
add_action( 'delete_user', [ $this, 'releaseBookings' ] );
|
||||
add_action( 'wpmu_delete_user', [ $this, 'releaseBookings' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel and release everything the account had booked ahead of it.
|
||||
* Release everything the account had booked ahead of it, then remove any
|
||||
* children that only existed to be booked for.
|
||||
*/
|
||||
public function releaseBookings( int $userId ): void {
|
||||
if ( $userId <= 0 ) {
|
||||
if ( $userId <= 0 || isset( $this->handled[ $userId ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->handled[ $userId ] = true;
|
||||
|
||||
$this->release( $userId );
|
||||
$this->removeChildren( $userId );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel one account's upcoming lessons and active enrolments, freeing the
|
||||
* slot and voiding the pending payment behind each.
|
||||
*/
|
||||
private function release( int $studentId ): void {
|
||||
// Upcoming and not already cancelled — the only bookings that are still
|
||||
// holding anything.
|
||||
foreach ( $this->bookings->findUpcomingForStudent( $userId ) as $lesson ) {
|
||||
foreach ( $this->bookings->findUpcomingForStudent( $studentId ) as $lesson ) {
|
||||
$this->bookings->updateStatus( (int) $lesson->id, Lesson::STATUS_CANCELLED );
|
||||
$this->availability->release( $lesson->slotId );
|
||||
$this->payments->voidPending( $lesson->paymentId );
|
||||
}
|
||||
|
||||
foreach ( $this->enrollments->findByStudent( $userId ) as $enrollment ) {
|
||||
foreach ( $this->enrollments->findByStudent( $studentId ) as $enrollment ) {
|
||||
if ( Enrollment::STATUS_ACTIVE !== $enrollment->status ) {
|
||||
continue;
|
||||
}
|
||||
@@ -72,4 +106,25 @@ class DeletedUserCleanup {
|
||||
$this->payments->voidPending( $enrollment->paymentId );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete every child linked to a departing guardian, releasing what each was
|
||||
* holding first. Each child is marked handled *before* it is deleted, so the
|
||||
* `delete_user` this fires re-enters and returns without redoing the release.
|
||||
*/
|
||||
private function removeChildren( int $guardianId ): void {
|
||||
foreach ( $this->links->findByGuardian( $guardianId ) as $link ) {
|
||||
$childId = $link->studentId;
|
||||
|
||||
if ( $childId <= 0 || $childId === $guardianId || isset( $this->handled[ $childId ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->handled[ $childId ] = true;
|
||||
|
||||
$this->release( $childId );
|
||||
$this->links->delete( $guardianId, $childId );
|
||||
$this->guardians->deleteUser( $childId );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ class StudentController {
|
||||
return [
|
||||
'id' => (int) $lesson->id,
|
||||
'kind' => 'lesson',
|
||||
'schedule' => null,
|
||||
'start_dt' => $slot ? $slot->startDt : '',
|
||||
'end_dt' => $slot ? $slot->endDt : '',
|
||||
'offering' => $offering ? $offering->title : '—',
|
||||
@@ -231,6 +232,9 @@ class StudentController {
|
||||
'kind' => SessionSchedule::KIND,
|
||||
'start_dt' => $session['start_dt'],
|
||||
'end_dt' => $session['end_dt'],
|
||||
// Set when the class has no time to put on a clock; shown in the
|
||||
// When column in place of a date. See GroupClass\SessionSchedule.
|
||||
'schedule' => $session['schedule'],
|
||||
'offering' => $session['offering_title'],
|
||||
'instructor' => $instructor ? $instructor->display_name : (string) $session['instructor_id'],
|
||||
'status' => $session['status'],
|
||||
|
||||
@@ -13,13 +13,25 @@ use Unsupervised\Schedular\Offering\OfferingRepository;
|
||||
* A group class is stored as a term (`term_start`, `term_end`, `class_time`)
|
||||
* rather than as rows in `us_availability`, which is why an enrolment on its own
|
||||
* has no date on it and why nothing that listed lessons ever showed one. The
|
||||
* concrete windows come from {@see Offering::sessionWindows()} — the same
|
||||
* derivation the billing scan and the class-slot reconciler use, so a student's
|
||||
* list, an instructor's list and the invoice all agree on when the class meets.
|
||||
* dates come from {@see Offering::sessionStarts()} — the same derivation the
|
||||
* billing scan and the class-slot reconciler build on, so a student's list, an
|
||||
* instructor's list and the invoice all agree on when the class meets.
|
||||
*
|
||||
* A class whose schedule is not fully specified yields no windows and so
|
||||
* contributes no rows: better to leave it out of a dated list than to invent a
|
||||
* time for it.
|
||||
* **A class you are enrolled in must never silently vanish from the list.** Both
|
||||
* the class time and the duration are optional on the offering form, and the
|
||||
* schedule note exists precisely so a studio can write "Tuesdays 4:00pm" instead
|
||||
* of pinning the class to a clock. So the schedule degrades rather than
|
||||
* disappearing:
|
||||
*
|
||||
* - date **and** time set — one dated row per remaining session, closed off with
|
||||
* the duration when there is one and left open-ended when there is not;
|
||||
* - no time to derive dates from — a single row for the class as a whole, sorted
|
||||
* by when the term starts and labelled with `schedule` text
|
||||
* ({@see Offering::scheduleLabel()}) in place of a time.
|
||||
*
|
||||
* A row's `schedule` is the tell: non-null means "this is a class, described in
|
||||
* words, not a session at a known time", and every renderer shows that text
|
||||
* instead of a date and time.
|
||||
*/
|
||||
class SessionSchedule {
|
||||
|
||||
@@ -42,7 +54,7 @@ class SessionSchedule {
|
||||
* kept, since "completed" describes the enrolment's billing state and says
|
||||
* nothing about whether the class has met yet.
|
||||
*
|
||||
* @return list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null}>
|
||||
* @return list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null, schedule: string|null}>
|
||||
*/
|
||||
public function upcomingForStudent( int $studentId, string $now ): array {
|
||||
$rows = [];
|
||||
@@ -57,18 +69,10 @@ class SessionSchedule {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $this->windowsFrom( $offering, $now ) as $window ) {
|
||||
$rows[] = [
|
||||
'enrollment_id' => (int) $enrollment->id,
|
||||
'offering_id' => (int) $offering->id,
|
||||
'offering_title' => $offering->title,
|
||||
'instructor_id' => $enrollment->instructorId,
|
||||
'status' => $enrollment->status,
|
||||
'start_dt' => $window['start'],
|
||||
'end_dt' => $window['end'],
|
||||
'duration_minutes' => $offering->durationMinutes,
|
||||
];
|
||||
}
|
||||
$rows = array_merge(
|
||||
$rows,
|
||||
$this->rowsFor( $offering, $now, (int) $enrollment->id, $enrollment->instructorId, $enrollment->status )
|
||||
);
|
||||
}
|
||||
|
||||
return self::sortedByStart( $rows );
|
||||
@@ -80,7 +84,7 @@ class SessionSchedule {
|
||||
* are not consulted at all: a class the instructor has to turn up and teach
|
||||
* belongs on their schedule whether or not anyone has signed up yet.
|
||||
*
|
||||
* @return list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null}>
|
||||
* @return list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null, schedule: string|null}>
|
||||
*/
|
||||
public function upcomingForInstructor( int $instructorId, string $now ): array {
|
||||
$rows = [];
|
||||
@@ -88,43 +92,119 @@ class SessionSchedule {
|
||||
$classes = $this->offerings->findAll( $instructorId, Offering::KIND_GROUP_CLASS, activeOnly: true );
|
||||
|
||||
foreach ( $classes as $offering ) {
|
||||
foreach ( $this->windowsFrom( $offering, $now ) as $window ) {
|
||||
$rows[] = [
|
||||
'enrollment_id' => 0,
|
||||
'offering_id' => (int) $offering->id,
|
||||
'offering_title' => $offering->title,
|
||||
'instructor_id' => $instructorId,
|
||||
'status' => Enrollment::STATUS_ACTIVE,
|
||||
'start_dt' => $window['start'],
|
||||
'end_dt' => $window['end'],
|
||||
'duration_minutes' => $offering->durationMinutes,
|
||||
];
|
||||
}
|
||||
$rows = array_merge( $rows, $this->rowsFor( $offering, $now, 0, $instructorId, Enrollment::STATUS_ACTIVE ) );
|
||||
}
|
||||
|
||||
return self::sortedByStart( $rows );
|
||||
}
|
||||
|
||||
/**
|
||||
* The class's session windows that have not started yet.
|
||||
* One class's contribution to an upcoming list: its remaining dated sessions,
|
||||
* or — when it has no time to derive dates from — a single row describing the
|
||||
* class in words. Empty only when the class has demonstrably finished.
|
||||
*
|
||||
* @return list<array{start: string, end: string}>
|
||||
* @return list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null, schedule: string|null}>
|
||||
*/
|
||||
private function windowsFrom( Offering $offering, string $now ): array {
|
||||
return array_values(
|
||||
array_filter(
|
||||
$offering->sessionWindows(),
|
||||
static fn( array $window ): bool => $window['start'] >= $now
|
||||
)
|
||||
);
|
||||
private function rowsFor( Offering $offering, string $now, int $enrollmentId, int $instructorId, string $status ): array {
|
||||
$base = [
|
||||
'enrollment_id' => $enrollmentId,
|
||||
'offering_id' => (int) $offering->id,
|
||||
'offering_title' => $offering->title,
|
||||
'instructor_id' => $instructorId,
|
||||
'status' => $status,
|
||||
'duration_minutes' => $offering->durationMinutes,
|
||||
];
|
||||
|
||||
$starts = $offering->sessionStarts();
|
||||
|
||||
// Dated: the class says exactly when it meets, so list what is left of it
|
||||
// — and nothing at all once the term is over.
|
||||
if ( [] !== $starts ) {
|
||||
$rows = [];
|
||||
|
||||
foreach ( $starts as $start ) {
|
||||
if ( $start < $now ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows[] = $base + [
|
||||
'start_dt' => $start,
|
||||
// Left open when no duration is set. Knowing a class starts at
|
||||
// four o'clock is worth showing even without knowing when it
|
||||
// ends; guessing an end time is not.
|
||||
'end_dt' => $this->endOf( $offering, $start ),
|
||||
'schedule' => null,
|
||||
];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
// Undated: no class time, so there is nothing to put on a clock. The class
|
||||
// still gets a row — it is enrolled in and running — described by the
|
||||
// studio's own schedule note or its term dates.
|
||||
if ( ! $this->isStillRunning( $offering, $now ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
$base + [
|
||||
// A sort key, not a claim about when the class meets: a class yet to
|
||||
// start sorts to its first day, one already under way to right now.
|
||||
// `schedule` is what any renderer actually shows.
|
||||
'start_dt' => $this->sortKeyFor( $offering, $now ),
|
||||
'end_dt' => '',
|
||||
'schedule' => $offering->scheduleLabel(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* When a session that starts at `$start` finishes, or an empty string when the
|
||||
* class has no duration to close it off with.
|
||||
*/
|
||||
private function endOf( Offering $offering, string $start ): string {
|
||||
if ( null === $offering->durationMinutes || $offering->durationMinutes <= 0 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return ( new \DateTimeImmutable( $start ) )
|
||||
->add( new \DateInterval( 'PT' . $offering->durationMinutes . 'M' ) )
|
||||
->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an undated class still has life in it: its last day has not passed,
|
||||
* or it has no dates at all (in which case nothing says it has ended, and
|
||||
* dropping it would be the very disappearance this class exists to prevent).
|
||||
*/
|
||||
private function isStillRunning( Offering $offering, string $now ): bool {
|
||||
$lastDay = $offering->lastClassDay();
|
||||
|
||||
return null === $lastDay || $lastDay >= substr( $now, 0, 10 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Where an undated class sits in a list ordered by time: at its first day when
|
||||
* that is still ahead, otherwise at `$now`, so a term already under way reads
|
||||
* as current rather than as ancient history.
|
||||
*/
|
||||
private function sortKeyFor( Offering $offering, string $now ): string {
|
||||
if ( null === $offering->termStart ) {
|
||||
return $now;
|
||||
}
|
||||
|
||||
$firstDay = $offering->termStart . ' 00:00:00';
|
||||
|
||||
return $firstDay > $now ? $firstDay : $now;
|
||||
}
|
||||
|
||||
/**
|
||||
* Soonest session first, so classes from separate enrolments interleave by
|
||||
* date rather than arriving grouped by class.
|
||||
*
|
||||
* @param list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null}> $rows
|
||||
* @return list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null}>
|
||||
* @param list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null, schedule: string|null}> $rows
|
||||
* @return list<array{enrollment_id: int, offering_id: int, offering_title: string, instructor_id: int, status: string, start_dt: string, end_dt: string, duration_minutes: int|null, schedule: string|null}>
|
||||
*/
|
||||
private static function sortedByStart( array $rows ): array {
|
||||
usort( $rows, static fn( array $a, array $b ): int => strcmp( $a['start_dt'], $b['start_dt'] ) );
|
||||
|
||||
+78
-21
@@ -175,22 +175,19 @@ class Offering {
|
||||
}
|
||||
|
||||
/**
|
||||
* The concrete start/end datetimes of every session of this group class,
|
||||
* derived from the class date(s), the class time, and the duration. A weekly
|
||||
* class yields one window per week from `term_start` through `term_end`; a
|
||||
* one-off class yields a single window. Returns an empty list unless the
|
||||
* schedule is fully specified (date, time, and a positive duration), so it can
|
||||
* never fabricate a session window from partial data.
|
||||
* The datetime each session of this group class starts, derived from the class
|
||||
* date(s) and the class time. A weekly class yields one per week from
|
||||
* `term_start` through `term_end`; a one-off class yields a single one.
|
||||
*
|
||||
* @return list<array{start: string, end: string}>
|
||||
* Deliberately does **not** need a duration: knowing *when* a class meets is a
|
||||
* separate question from knowing how long it runs, and a studio can quite
|
||||
* reasonably set the first without the second. Returns an empty list when
|
||||
* there is no date or no time, since neither can be invented.
|
||||
*
|
||||
* @return list<string> `Y-m-d H:i:s` starts, earliest first.
|
||||
*/
|
||||
public function sessionWindows(): array {
|
||||
if (
|
||||
null === $this->termStart
|
||||
|| null === $this->classTime
|
||||
|| null === $this->durationMinutes
|
||||
|| $this->durationMinutes <= 0
|
||||
) {
|
||||
public function sessionStarts(): array {
|
||||
if ( null === $this->termStart || null === $this->classTime ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -200,25 +197,85 @@ class Offering {
|
||||
}
|
||||
|
||||
$lastDay = null !== $this->termEnd ? $this->termEnd : $this->termStart;
|
||||
$step = new \DateInterval( 'PT' . $this->durationMinutes . 'M' );
|
||||
|
||||
$windows = [];
|
||||
$starts = [];
|
||||
$cursor = $first;
|
||||
$cursorDay = $cursor->format( 'Y-m-d' );
|
||||
|
||||
// Cap the walk at ten years of weeks so a term_end before term_start (or a
|
||||
// bad value) can never spin into an unbounded loop.
|
||||
for ( $i = 0; $i < 520 && $cursorDay <= $lastDay; $i++ ) {
|
||||
$windows[] = [
|
||||
'start' => $cursor->format( 'Y-m-d H:i:s' ),
|
||||
'end' => $cursor->add( $step )->format( 'Y-m-d H:i:s' ),
|
||||
];
|
||||
$starts[] = $cursor->format( 'Y-m-d H:i:s' );
|
||||
|
||||
$cursor = $cursor->modify( '+7 days' );
|
||||
$cursorDay = $cursor->format( 'Y-m-d' );
|
||||
}
|
||||
|
||||
return $windows;
|
||||
return $starts;
|
||||
}
|
||||
|
||||
/**
|
||||
* The concrete start/end datetimes of every session of this group class:
|
||||
* {@see sessionStarts()} closed off with the class duration. Returns an empty
|
||||
* list unless the schedule is fully specified (date, time, *and* a positive
|
||||
* duration), so it can never fabricate a session window from partial data —
|
||||
* callers that block availability or bill per session need both ends.
|
||||
*
|
||||
* @return list<array{start: string, end: string}>
|
||||
*/
|
||||
public function sessionWindows(): array {
|
||||
if ( null === $this->durationMinutes || $this->durationMinutes <= 0 ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$step = new \DateInterval( 'PT' . $this->durationMinutes . 'M' );
|
||||
|
||||
return array_map(
|
||||
static fn( string $start ): array => [
|
||||
'start' => $start,
|
||||
'end' => ( new \DateTimeImmutable( $start ) )->add( $step )->format( 'Y-m-d H:i:s' ),
|
||||
],
|
||||
$this->sessionStarts()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The last day this class meets, or null when it has no dates at all.
|
||||
*/
|
||||
public function lastClassDay(): ?string {
|
||||
return $this->termEnd ?? $this->termStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plain-language wording for when this class meets, for the places that have
|
||||
* to say something about a class whose schedule cannot be resolved to dates.
|
||||
* Prefers the studio's own note ("Tuesdays 4:00pm") — that field exists
|
||||
* precisely so a class can describe its schedule without pinning it to a
|
||||
* time — then the term dates, and finally an honest admission that nothing
|
||||
* has been set.
|
||||
*/
|
||||
public function scheduleLabel(): string {
|
||||
$note = null !== $this->scheduleNote ? trim( $this->scheduleNote ) : '';
|
||||
if ( '' !== $note ) {
|
||||
return $note;
|
||||
}
|
||||
|
||||
if ( null === $this->termStart ) {
|
||||
return __( 'Schedule to be confirmed', 'unsupervised-schedular' );
|
||||
}
|
||||
|
||||
$start = (string) mysql2date( 'M j, Y', $this->termStart );
|
||||
|
||||
if ( null === $this->termEnd || $this->termEnd === $this->termStart ) {
|
||||
return $start;
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
/* translators: 1: first class date, 2: last class date. */
|
||||
__( '%1$s – %2$s', 'unsupervised-schedular' ),
|
||||
$start,
|
||||
(string) mysql2date( 'M j, Y', $this->termEnd )
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromRow( \stdClass $row ): self {
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ class Plugin {
|
||||
( new RegistrationLoginGate() )->register();
|
||||
( new ChildLoginGate() )->register();
|
||||
( new StudentAdminGuard() )->register();
|
||||
( new DeletedUserCleanup( $bookings, $availability, $enrollments, $paymentService ) )->register();
|
||||
( new DeletedUserCleanup( $bookings, $availability, $enrollments, $paymentService, $guardianRepo, $guardians ) )->register();
|
||||
( new EmailConfirmationHandler( $settings, $registrationMailer ) )->register();
|
||||
( new AdminMenu( $availability, $bookings, $offerings, $questions, $answers, $policies, $policyVersions, $policyService, $acceptances, $invites, $enrollments, $groupAccess, $settings, $paymentRepo, $paymentService, $resolver, $registrationMailer, $creditRepo, $guardians ) )->register();
|
||||
( new RestRegistrar( $availability, $bookings, $offerings, $questions, $policies, $policyVersions, $policyService, $registrationGate, $enrollments, $groupAccess, $paymentService, $guardians ) )->register();
|
||||
|
||||
Reference in New Issue
Block a user