CI / Tests (PHP 8.1) (pull_request) Successful in 1m0s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m0s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards (pull_request) Successful in 3m8s
CI / Build Plugin Zip (pull_request) Skipped
CI / PHPStan (pull_request) Successful in 2m49s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m44s
Five items from the latest demo pass: - A policy's title can be edited from the Policies screen. Only the title moves; the slug is what the gates resolve policies by, so a rename can never detach a policy from acceptances already recorded against it. - Signup is one page again. The studio's registration questions move from a second step behind "Next" onto the main form, in an "About you" panel above the students being added, and that panel also asks an adult student for their birth year (the same us_birth_year meta a child's uses). register.js disables and hides the whole panel for a pure guardian, since the questions describe a student. - The password is re-scored on submit, not only as it is typed. zxcvbn's dictionary arrives after page load, so a password typed straight away was never scored at all and the first the student heard of it was the server rejecting the whole form. - Group-class sessions appear alongside lessons wherever upcoming lessons are listed: the [us_scheduler] panel (students and instructors) and the admin student detail page. GroupClass\SessionSchedule derives them from Offering::sessionWindows(), the same derivation the billing scan uses. They carry kind = 'group_class' and no Cancel action - a session is one date in a term, not a booked slot. - Deleting a user releases what the account was holding: each upcoming lesson is cancelled, its slot freed for rebooking, its pending payment voided, and active class enrolments cancelled. Past lessons and paid history are left alone. Tests: composer test (851), composer lint, composer cs all pass. Co-Authored-By: Claude Opus 5 <[email protected]>
112 lines
5.1 KiB
PHP
112 lines
5.1 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Unsupervised\Schedular;
|
|
|
|
use Unsupervised\Schedular\Auth\AccountPage;
|
|
use Unsupervised\Schedular\Auth\LoginPage;
|
|
use Unsupervised\Schedular\Auth\RegistrationPage;
|
|
use Unsupervised\Schedular\Booking\BookingPage;
|
|
use Unsupervised\Schedular\GroupClass\GroupClassPage;
|
|
use Unsupervised\Schedular\Guardian\FamilyPage;
|
|
use Unsupervised\Schedular\Payment\StudioSettings;
|
|
|
|
class ShortcodeRegistrar {
|
|
|
|
public function __construct(
|
|
private BookingPage $bookingPage,
|
|
private LoginPage $loginPage,
|
|
private RegistrationPage $registrationPage,
|
|
private GroupClassPage $groupClassPage,
|
|
private FamilyPage $familyPage,
|
|
private AccountPage $accountPage,
|
|
) {}
|
|
|
|
public function register(): void {
|
|
add_shortcode( 'us_booking', self::shortcode( [ $this->bookingPage, 'render' ] ) );
|
|
add_shortcode( 'us_student_login', self::shortcode( [ $this->loginPage, 'render' ] ) );
|
|
add_shortcode( 'us_student_register', self::shortcode( [ $this->registrationPage, 'render' ] ) );
|
|
add_shortcode( 'us_group_classes', self::shortcode( [ $this->groupClassPage, 'render' ] ) );
|
|
add_shortcode( 'us_family', self::shortcode( [ $this->familyPage, 'render' ] ) );
|
|
add_shortcode( 'us_account', self::shortcode( [ $this->accountPage, 'render' ] ) );
|
|
// Process registration submissions before output so the invite branch's
|
|
// auth cookie is actually sent (render() runs too late, during the_content).
|
|
add_action( 'template_redirect', [ $this->registrationPage, 'maybeHandleSubmit' ] );
|
|
add_action( 'template_redirect', [ $this->registrationPage, 'maybeRedirectToRegistrationPage' ] );
|
|
// Same reason as registration: the family form redirects after handling,
|
|
// which render() (running during the_content) is too late to do.
|
|
add_action( 'template_redirect', [ $this->familyPage, 'maybeHandleSubmit' ] );
|
|
add_action( 'wp_enqueue_scripts', [ $this, 'enqueueAssets' ] );
|
|
}
|
|
|
|
/**
|
|
* Wraps a page renderer so bare shortcode usage is safe: WordPress passes
|
|
* an empty string, not an array, to the callback when a shortcode is used
|
|
* without attributes (`shortcode_parse_atts( '' )` returns `''`).
|
|
*
|
|
* @param callable(array<int|string, mixed>): string $render
|
|
* @return \Closure(mixed): string
|
|
*/
|
|
private static function shortcode( callable $render ): \Closure {
|
|
return static fn( mixed $atts ): string => $render( is_array( $atts ) ? $atts : [] );
|
|
}
|
|
|
|
public function enqueueAssets(): void {
|
|
wp_register_style( 'us-scheduler', USC_PLUGIN_URL . 'assets/css/frontend.css', [], USC_VERSION );
|
|
|
|
$settings = new StudioSettings();
|
|
|
|
// Stripe.js (loaded from Stripe's CDN per their terms) only when card billing
|
|
// is available; the payment helper degrades to e-transfer messaging without it.
|
|
$paymentDeps = [];
|
|
if ( $settings->isStripeConfigured() ) {
|
|
// Stripe pins the version in the URL path (/v3/) and forbids self-hosting,
|
|
// so no query-string version applies here.
|
|
wp_register_script( 'us-scheduler-stripe', 'https://js.stripe.com/v3/', [], null, true ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
|
|
$paymentDeps[] = 'us-scheduler-stripe';
|
|
}
|
|
|
|
wp_register_script( 'us-scheduler-payment', USC_PLUGIN_URL . 'assets/js/payment.js', $paymentDeps, USC_VERSION, true );
|
|
|
|
$data = [
|
|
'restUrl' => rest_url( 'us-scheduler/v1/' ),
|
|
'nonce' => wp_create_nonce( 'wp_rest' ),
|
|
'stripeKey' => $settings->publishableKey(),
|
|
'startOfWeek' => Val::int( get_option( 'start_of_week', 1 ) ),
|
|
// The studio HST rate, so a price quoted to a student on the way in
|
|
// matches the total they are actually billed.
|
|
'taxRate' => $settings->hstRate(),
|
|
];
|
|
|
|
// Attach the shared config to the payment helper so it is defined before the
|
|
// booking/group scripts (which depend on it) run.
|
|
wp_localize_script( 'us-scheduler-payment', 'usScheduler', $data );
|
|
|
|
// Price formatting and the pay agreement, shared by booking and enrolment.
|
|
wp_register_script( 'us-scheduler-pricing', USC_PLUGIN_URL . 'assets/js/pricing.js', [ 'us-scheduler-payment' ], USC_VERSION, true );
|
|
|
|
// The "who is this for?" picker, shared by booking and enrolment.
|
|
wp_register_script( 'us-scheduler-guardian', USC_PLUGIN_URL . 'assets/js/guardian.js', [], USC_VERSION, true );
|
|
|
|
wp_register_script( 'us-scheduler', USC_PLUGIN_URL . 'assets/js/booking.js', [ 'us-scheduler-pricing', 'us-scheduler-guardian' ], USC_VERSION, true );
|
|
wp_register_script( 'us-scheduler-group', USC_PLUGIN_URL . 'assets/js/group-classes.js', [ 'us-scheduler-pricing', 'us-scheduler-guardian' ], USC_VERSION, true );
|
|
|
|
/*
|
|
* Progressive enhancement for the registration form.
|
|
*
|
|
* `password-strength-meter` is WordPress's own wrapper around zxcvbn, so
|
|
* the signup form scores a password exactly the way wp-admin does rather
|
|
* than inventing a second opinion. It pulls in `zxcvbn-async`, which
|
|
* fetches the (large) dictionary only once the page has loaded — hence
|
|
* the guard in register.js for the window where it is not there yet.
|
|
*/
|
|
wp_register_script(
|
|
'us-scheduler-register',
|
|
USC_PLUGIN_URL . 'assets/js/register.js',
|
|
[ 'password-strength-meter' ],
|
|
USC_VERSION,
|
|
true
|
|
);
|
|
}
|
|
}
|