Files
unsupervised-scheduler/templates/admin/offerings.php
T
thatguygriffandClaude Opus 4.8 a281935811
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
Add invite-only group classes
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

191 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use Unsupervised\Schedular\Offering\Offering;
if (! defined('ABSPATH')) {
exit;
}
/**
* @var list<\Unsupervised\Schedular\Offering\Offering> $offerings
* @var \Unsupervised\Schedular\Offering\Offering|null $editing Offering loaded into the form, or null when adding.
*/
$baseUrl = admin_url('admin.php?page=us-offerings');
// Prefill the sessions control from the stored term dates: a term longer than
// one day was created as weekly sessions one week apart.
$termRecurrence = 'single';
$termSessions = 10;
if ($editing && null !== $editing->termStart && null !== $editing->termEnd && $editing->termEnd !== $editing->termStart) {
$termRecurrence = 'weekly';
$termSessions = (int) round(((int) strtotime($editing->termEnd) - (int) strtotime($editing->termStart)) / 604800) + 1;
}
?>
<div class="wrap">
<h1><?php esc_html_e('Offerings', 'unsupervised-schedular'); ?></h1>
<h2><?php $editing ? esc_html_e('Edit Offering', 'unsupervised-schedular') : esc_html_e('Add Offering', 'unsupervised-schedular'); ?></h2>
<form method="post">
<?php wp_nonce_field('usc_offering_action'); ?>
<?php if ($editing) : ?>
<input type="hidden" name="usc_action" value="update">
<input type="hidden" name="offering_id" value="<?php echo esc_attr((string) $editing->id); ?>">
<?php else : ?>
<input type="hidden" name="usc_action" value="add">
<?php endif; ?>
<table class="form-table">
<tr>
<th><label for="title"><?php esc_html_e('Title', 'unsupervised-schedular'); ?></label></th>
<td><input type="text" name="title" id="title" class="regular-text" required value="<?php echo esc_attr($editing->title ?? ''); ?>"></td>
</tr>
<tr>
<th><label for="kind"><?php esc_html_e('Kind', 'unsupervised-schedular'); ?></label></th>
<td>
<select name="kind" id="kind">
<option value="<?php echo esc_attr(Offering::KIND_PRIVATE_LESSON); ?>"><?php esc_html_e('Private lesson', 'unsupervised-schedular'); ?></option>
<option value="<?php echo esc_attr(Offering::KIND_GROUP_CLASS); ?>" <?php echo $editing && Offering::KIND_GROUP_CLASS === $editing->kind ? 'selected' : ''; ?>><?php esc_html_e('Group class', 'unsupervised-schedular'); ?></option>
</select>
</td>
</tr>
<tr>
<th><label for="description"><?php esc_html_e('Description', 'unsupervised-schedular'); ?></label></th>
<td><textarea name="description" id="description" class="large-text" rows="4"><?php echo esc_textarea($editing->description ?? ''); ?></textarea></td>
</tr>
<tr>
<th><label for="duration_minutes"><?php esc_html_e('Duration (minutes)', 'unsupervised-schedular'); ?></label></th>
<td><input type="number" name="duration_minutes" id="duration_minutes" min="0" step="1" value="<?php echo esc_attr((string) ($editing->durationMinutes ?? '')); ?>"></td>
</tr>
<tr>
<th><label for="price"><?php esc_html_e('Price (dollars)', 'unsupervised-schedular'); ?></label></th>
<td><input type="number" name="price" id="price" min="0" step="0.01" value="<?php echo esc_attr(number_format($editing->price ?? 0.0, 2, '.', '')); ?>"></td>
</tr>
<tr>
<th><label for="billing_mode"><?php esc_html_e('Billing', 'unsupervised-schedular'); ?></label></th>
<td>
<select name="billing_mode" id="billing_mode">
<option value="<?php echo esc_attr(Offering::BILLING_ONE_TIME); ?>"><?php esc_html_e('One-time at booking', 'unsupervised-schedular'); ?></option>
<option value="<?php echo esc_attr(Offering::BILLING_FULL_TERM); ?>" <?php echo $editing && Offering::BILLING_FULL_TERM === $editing->billingMode ? 'selected' : ''; ?>><?php esc_html_e('Full term upfront', 'unsupervised-schedular'); ?></option>
</select>
</td>
</tr>
<tr>
<th><?php esc_html_e('Weekly reservation', 'unsupervised-schedular'); ?></th>
<td><label><input type="checkbox" name="allow_weekly" value="1" <?php echo $editing && $editing->allowWeekly ? 'checked' : ''; ?>> <?php esc_html_e('Allow weekly recurring reservation (private)', 'unsupervised-schedular'); ?></label></td>
</tr>
<tr>
<th><label for="capacity"><?php esc_html_e('Capacity', 'unsupervised-schedular'); ?></label></th>
<td><input type="number" name="capacity" id="capacity" min="0" step="1" value="<?php echo esc_attr((string) ($editing->capacity ?? '')); ?>"> <span class="description"><?php esc_html_e('Group classes only', 'unsupervised-schedular'); ?></span></td>
</tr>
<tr>
<th><label for="term_start"><?php esc_html_e('Start date', 'unsupervised-schedular'); ?></label></th>
<td>
<input type="date" name="term_start" id="term_start" value="<?php echo esc_attr($editing->termStart ?? ''); ?>">
<span class="description"><?php esc_html_e('Group classes only — date of the first class', 'unsupervised-schedular'); ?></span>
</td>
</tr>
<tr>
<th><?php esc_html_e('Sessions', 'unsupervised-schedular'); ?></th>
<td>
<label><input type="radio" name="term_recurrence" value="single" <?php echo 'single' === $termRecurrence ? 'checked' : ''; ?>> <?php esc_html_e('One-off', 'unsupervised-schedular'); ?></label>
&nbsp;
<label><input type="radio" name="term_recurrence" value="weekly" <?php echo 'weekly' === $termRecurrence ? 'checked' : ''; ?>> <?php esc_html_e('Weekly for', 'unsupervised-schedular'); ?></label>
<input type="number" name="term_sessions" min="1" max="52" value="<?php echo esc_attr((string) $termSessions); ?>" style="width:5em;"> <?php esc_html_e('sessions', 'unsupervised-schedular'); ?>
<p class="description"><?php esc_html_e('The end date is calculated from the start date and the number of weekly sessions.', 'unsupervised-schedular'); ?></p>
</td>
</tr>
<tr>
<th><label for="schedule_note"><?php esc_html_e('Schedule note', 'unsupervised-schedular'); ?></label></th>
<td><input type="text" name="schedule_note" id="schedule_note" class="regular-text" placeholder="<?php esc_attr_e('e.g. Tuesdays 4:00pm', 'unsupervised-schedular'); ?>" value="<?php echo esc_attr($editing->scheduleNote ?? ''); ?>"></td>
</tr>
<tr>
<th><label for="etransfer_email"><?php esc_html_e('E-transfer email', 'unsupervised-schedular'); ?></label></th>
<td><input type="email" name="etransfer_email" id="etransfer_email" class="regular-text" placeholder="<?php esc_attr_e('Overrides the studio default', 'unsupervised-schedular'); ?>" value="<?php echo esc_attr($editing->etransferEmail ?? ''); ?>"></td>
</tr>
<tr>
<th><label for="cancellation_cutoff_hours"><?php esc_html_e('Cancellation cutoff (hours)', 'unsupervised-schedular'); ?></label></th>
<td>
<input type="number" name="cancellation_cutoff_hours" id="cancellation_cutoff_hours" min="0" step="1" placeholder="<?php esc_attr_e('Uses the studio default', 'unsupervised-schedular'); ?>" value="<?php echo esc_attr(null === ($editing->cancellationCutoffHours ?? null) ? '' : (string) $editing->cancellationCutoffHours); ?>">
<p class="description"><?php esc_html_e('How many hours before a lesson a student may still cancel it. Leave blank to use the studio default; 0 lets students cancel any time.', 'unsupervised-schedular'); ?></p>
</td>
</tr>
<tr>
<th><?php esc_html_e('Invite only', 'unsupervised-schedular'); ?></th>
<td>
<label><input type="checkbox" name="invite_only" value="1" <?php echo $editing && $editing->isInviteOnly() ? 'checked' : ''; ?>> <?php esc_html_e('Hide from the booking list — students join by invitation only (group classes)', 'unsupervised-schedular'); ?></label>
<p class="description"><?php esc_html_e('Invite-only classes never appear in the student catalog. Add or invite students from My Lessons → My Group Classes.', 'unsupervised-schedular'); ?></p>
</td>
</tr>
<tr>
<th><?php esc_html_e('Active', 'unsupervised-schedular'); ?></th>
<td><label><input type="checkbox" name="is_active" value="1" <?php echo null === $editing || $editing->isActive ? 'checked' : ''; ?>> <?php esc_html_e('Open for registration', 'unsupervised-schedular'); ?></label></td>
</tr>
</table>
<?php submit_button($editing ? esc_html__('Update Offering', 'unsupervised-schedular') : esc_html__('Add Offering', 'unsupervised-schedular')); ?>
<?php if ($editing) : ?>
<p><a href="<?php echo esc_url($baseUrl); ?>"><?php esc_html_e('Cancel editing', 'unsupervised-schedular'); ?></a></p>
<?php endif; ?>
</form>
<h2><?php esc_html_e('Current Offerings', 'unsupervised-schedular'); ?></h2>
<?php if (empty($offerings)) : ?>
<p><?php esc_html_e('No offerings configured.', 'unsupervised-schedular'); ?></p>
<?php else : ?>
<table class="wp-list-table widefat fixed striped">
<thead>
<tr>
<th style="width:4em;"><?php esc_html_e('ID', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Title', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Kind', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Duration', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Price', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Billing', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Term', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Active', 'unsupervised-schedular'); ?></th>
<th><?php esc_html_e('Actions', 'unsupervised-schedular'); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ($offerings as $offering) : ?>
<tr>
<td><?php echo esc_html((string) $offering->id); ?></td>
<td>
<?php echo esc_html($offering->title); ?>
<?php if ($offering->isInviteOnly()) : ?>
<span class="dashicons dashicons-lock" title="<?php esc_attr_e('Invite only', 'unsupervised-schedular'); ?>"></span>
<?php endif; ?>
</td>
<td><?php echo esc_html($offering->kind); ?></td>
<td><?php echo $offering->durationMinutes ? esc_html((string) $offering->durationMinutes . ' min') : '&mdash;'; ?></td>
<td><?php echo esc_html(number_format($offering->price, 2) . ' ' . $offering->currency); ?></td>
<td><?php echo esc_html($offering->billingMode); ?></td>
<td>
<?php if (null === $offering->termStart) : ?>
&mdash;
<?php elseif (null === $offering->termEnd || $offering->termEnd === $offering->termStart) : ?>
<?php echo esc_html((string) mysql2date('M j, Y', $offering->termStart)); ?>
<?php else : ?>
<?php echo esc_html((string) mysql2date('M j, Y', $offering->termStart) . ' ' . (string) mysql2date('M j, Y', $offering->termEnd)); ?>
<?php endif; ?>
</td>
<td><?php echo $offering->isActive ? esc_html__('Yes', 'unsupervised-schedular') : esc_html__('No', 'unsupervised-schedular'); ?></td>
<td>
<a class="button button-small" href="<?php echo esc_url(add_query_arg('usc_edit', (int) $offering->id, $baseUrl)); ?>"><?php esc_html_e('Edit', 'unsupervised-schedular'); ?></a>
<form method="post" style="display:inline;">
<?php wp_nonce_field('usc_offering_action'); ?>
<input type="hidden" name="usc_action" value="delete">
<input type="hidden" name="offering_id" value="<?php echo esc_attr((string) $offering->id); ?>">
<button type="submit" class="button button-small button-link-delete">
<?php esc_html_e('Delete', 'unsupervised-schedular'); ?>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>