Compare commits
2
Commits
v1.4.0
...
4c92d19d2e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c92d19d2e
|
||
|
|
46cee7a454
|
@@ -13,6 +13,9 @@ each change under the current top section as you work.
|
|||||||
|
|
||||||
## [1.3.1]
|
## [1.3.1]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- An **Account** block (`[us_account]`) showing who is signed in — their name and their email — and a **Sign out** link. Signing out returns to the login page chosen in the block, or to the page the visitor was already on when none is set, so putting it in a site header does not also move people somewhere. To a signed-out visitor it shows a **Sign in** link when a login page is chosen, and nothing at all when one is not: a panel about who is signed in has nothing to tell a stranger, and a notice they cannot act on is just clutter in a header.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- A student's **name and birth year are now required**, marked in the form the same way a required registration question is and enforced on the server whichever way they were submitted. On signup the requirement applies only once the parent/guardian box is ticked, so registering for yourself is unaffected. A student block you have started filling in is now reported back to you rather than silently dropped when the name is missing — only a completely untouched spare block is still ignored.
|
- A student's **name and birth year are now required**, marked in the form the same way a required registration question is and enforced on the server whichever way they were submitted. On signup the requirement applies only once the parent/guardian box is ticked, so registering for yourself is unaffected. A student block you have started filling in is now reported back to you rather than silently dropped when the name is missing — only a completely untouched spare block is still ignored.
|
||||||
- Signup and the profile page now ask for a **birth year** rather than a full date of birth — a four-digit year between 1900 and the current year, with anything else discarded rather than stored. Students added before this change keep showing a birth year, derived from the date already on file; that old full date is then dropped the first time the record is saved, so the studio ends up holding only what it now asks for. No bulk purge runs, so a site wanting the remaining old dates gone should clear the `us_date_of_birth` user meta directly.
|
- Signup and the profile page now ask for a **birth year** rather than a full date of birth — a four-digit year between 1900 and the current year, with anything else discarded rather than stored. Students added before this change keep showing a birth year, derived from the date already on file; that old full date is then dropped the first time the record is saved, so the studio ends up holding only what it now asks for. No bulk purge runs, so a site wanting the remaining old dates gone should clear the `us_date_of_birth` user meta directly.
|
||||||
|
|||||||
@@ -525,6 +525,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The account panel: who is signed in, and the way out. Sized to sit in a
|
||||||
|
* header or sidebar, so the rules stay minimal and inherit the theme's type —
|
||||||
|
* a block that lands in a site header should look like it belongs there.
|
||||||
|
*/
|
||||||
|
.us-account p {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.us-account-name {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.us-account-email {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.us-account-actions {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Shown only in block-editor previews (see BlockPreview). */
|
/* Shown only in block-editor previews (see BlockPreview). */
|
||||||
.us-editor-note {
|
.us-editor-note {
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
|
|||||||
@@ -307,6 +307,28 @@
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'us-scheduler/account',
|
||||||
|
title: __('Account', 'unsupervised-schedular'),
|
||||||
|
description: __('Shows the name and email of whoever is signed in, with a sign out link. Renders nothing for signed-out visitors unless a login page is chosen.', 'unsupervised-schedular'),
|
||||||
|
icon: 'admin-users',
|
||||||
|
keywords: ['account', 'sign out', 'log out', 'signed in', 'profile'],
|
||||||
|
shortcode: 'us_account',
|
||||||
|
attributes: {
|
||||||
|
loginPageId: { type: 'number', default: 0 },
|
||||||
|
},
|
||||||
|
inspector: (attributes, setAttributes) => el(
|
||||||
|
PanelBody,
|
||||||
|
{ title: __('Signing in and out', 'unsupervised-schedular') },
|
||||||
|
el(PageSelect, {
|
||||||
|
label: __('Login page', 'unsupervised-schedular'),
|
||||||
|
help: __('Where signing out returns to, and where signed-out visitors are offered a link to sign in. Without one, signing out returns to the current page and signed-out visitors see nothing.', 'unsupervised-schedular'),
|
||||||
|
defaultLabel: __('Stay on the current page', 'unsupervised-schedular'),
|
||||||
|
value: attributes.loginPageId,
|
||||||
|
onChange: (loginPageId) => setAttributes({ loginPageId }),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
blocks.forEach((def) => {
|
blocks.forEach((def) => {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Editor Blocks
|
# Editor Blocks
|
||||||
|
|
||||||
Gutenberg dynamic-block wrappers for the plugin's four front-end shortcodes,
|
Gutenberg dynamic-block wrappers for the plugin's front-end shortcodes, so the
|
||||||
so the pages can be previewed and styled inside the block editor instead of
|
pages can be previewed and styled inside the block editor instead of appearing
|
||||||
appearing as grey shortcode text.
|
as grey shortcode text.
|
||||||
|
|
||||||
## Blocks
|
## Blocks
|
||||||
|
|
||||||
@@ -12,6 +12,8 @@ appearing as grey shortcode text.
|
|||||||
| `us-scheduler/student-login` | `[us_student_login]` | `Auth\LoginPage::render()` |
|
| `us-scheduler/student-login` | `[us_student_login]` | `Auth\LoginPage::render()` |
|
||||||
| `us-scheduler/student-register` | `[us_student_register]` | `Auth\RegistrationPage::render()` |
|
| `us-scheduler/student-register` | `[us_student_register]` | `Auth\RegistrationPage::render()` |
|
||||||
| `us-scheduler/group-classes` | `[us_group_classes]` | `GroupClass\GroupClassPage::render()` |
|
| `us-scheduler/group-classes` | `[us_group_classes]` | `GroupClass\GroupClassPage::render()` |
|
||||||
|
| `us-scheduler/family` | `[us_family]` | `Guardian\FamilyPage::render()` |
|
||||||
|
| `us-scheduler/account` | `[us_account]` | `Auth\AccountPage::render()` |
|
||||||
|
|
||||||
The shortcodes remain registered for back-compat; blocks and shortcodes share
|
The shortcodes remain registered for back-compat; blocks and shortcodes share
|
||||||
the same page objects (constructed once in `Plugin::boot()`), so front-end
|
the same page objects (constructed once in `Plugin::boot()`), so front-end
|
||||||
@@ -21,7 +23,7 @@ transform.
|
|||||||
|
|
||||||
## Block options
|
## Block options
|
||||||
|
|
||||||
Four blocks have sidebar (inspector) options:
|
Most blocks have sidebar (inspector) options:
|
||||||
|
|
||||||
| Block | Attribute | Default | Effect |
|
| Block | Attribute | Default | Effect |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
@@ -34,6 +36,8 @@ Four blocks have sidebar (inspector) options:
|
|||||||
| `us-scheduler/student-login` | `autoRedirect` (boolean) | `false` | Send logged-in visitors straight to the booking page instead of showing the link. Does nothing until a booking page is chosen. |
|
| `us-scheduler/student-login` | `autoRedirect` (boolean) | `false` | Send logged-in visitors straight to the booking page instead of showing the link. Does nothing until a booking page is chosen. |
|
||||||
| `us-scheduler/student-register` | `loginPageId` (number) | `0` | Page students continue to once registration finishes — the "Sign in to your account" link after they confirm their email, and the "Continue to your account" link an invited student gets on the spot. `0` = the WordPress login screen for the confirmation link, and no link at all for the (already signed-in) invited student. Shortcode equivalent: `[us_student_register login_page_id="…"]`. |
|
| `us-scheduler/student-register` | `loginPageId` (number) | `0` | Page students continue to once registration finishes — the "Sign in to your account" link after they confirm their email, and the "Continue to your account" link an invited student gets on the spot. `0` = the WordPress login screen for the confirmation link, and no link at all for the (already signed-in) invited student. Shortcode equivalent: `[us_student_register login_page_id="…"]`. |
|
||||||
| `us-scheduler/student-register` | `autoRedirect` (boolean) | `false` | Send students straight to that page instead of showing the link. Does nothing until a page is chosen — there is no login-screen fallback here. |
|
| `us-scheduler/student-register` | `autoRedirect` (boolean) | `false` | Send students straight to that page instead of showing the link. Does nothing until a page is chosen — there is no login-screen fallback here. |
|
||||||
|
| `us-scheduler/family` | `loginPageId` (number) | `0` | Where visitors who are not signed in are sent to log in. Shortcode equivalent: `[us_family login_page_id="…"]`. |
|
||||||
|
| `us-scheduler/account` | `loginPageId` (number) | `0` | Where signing out returns to, and where a signed-out visitor is offered a **Sign in** link. `0` = signing out returns to the current page, and a signed-out visitor sees **nothing at all** — see below. Shortcode equivalent: `[us_account login_page_id="…"]`. |
|
||||||
| `us-scheduler/group-classes` | `offeringId` (number) | `0` | Restrict the page to a single group class, for embedding on a page dedicated to that class. The class description is then omitted — only the schedule, instructor, price and enrolment controls are shown, so the surrounding page's own copy is not repeated. `0` = browse all classes, descriptions included. Shortcode equivalent: `[us_group_classes offering="…"]`. |
|
| `us-scheduler/group-classes` | `offeringId` (number) | `0` | Restrict the page to a single group class, for embedding on a page dedicated to that class. The class description is then omitted — only the schedule, instructor, price and enrolment controls are shown, so the surrounding page's own copy is not repeated. `0` = browse all classes, descriptions included. Shortcode equivalent: `[us_group_classes offering="…"]`. |
|
||||||
|
|
||||||
The page selects list all published pages; if a chosen page is later deleted,
|
The page selects list all published pages; if a chosen page is later deleted,
|
||||||
@@ -105,6 +109,10 @@ placeholder content:
|
|||||||
- **Login** — the real `templates/frontend/login-page.php` template (it has
|
- **Login** — the real `templates/frontend/login-page.php` template (it has
|
||||||
no request-state dependencies).
|
no request-state dependencies).
|
||||||
- **Registration** — a disabled sample of the `.us-register-form` fields.
|
- **Registration** — a disabled sample of the `.us-register-form` fields.
|
||||||
|
- **Account** — a populated sample panel. Deliberately populated whatever the
|
||||||
|
editor user's own state: on the published page a signed-out visitor may see
|
||||||
|
nothing at all, and an empty box tells the person placing the block nothing
|
||||||
|
about where it will sit.
|
||||||
|
|
||||||
Each preview starts with a `.us-editor-note` paragraph explaining what the
|
Each preview starts with a `.us-editor-note` paragraph explaining what the
|
||||||
published page shows instead. The note class only appears in editor previews.
|
published page shows instead. The note class only appears in editor previews.
|
||||||
@@ -118,5 +126,22 @@ published page shows instead. The note class only appears in editor previews.
|
|||||||
and fallbacks.
|
and fallbacks.
|
||||||
- `tests/Unit/Auth/LoginPageTest.php` — logged-in booking-link targets and
|
- `tests/Unit/Auth/LoginPageTest.php` — logged-in booking-link targets and
|
||||||
fallbacks.
|
fallbacks.
|
||||||
|
- `tests/Unit/Auth/AccountPageTest.php` — what each visitor sees, the
|
||||||
|
sign-out redirect target, and the signed-out empty render.
|
||||||
- `tests/Unit/BlockPreviewTest.php` — preview markup mirrors the live CSS
|
- `tests/Unit/BlockPreviewTest.php` — preview markup mirrors the live CSS
|
||||||
classes/ids and includes the editor note.
|
classes/ids and includes the editor note.
|
||||||
|
|
||||||
|
## The account block's signed-out behaviour
|
||||||
|
|
||||||
|
`us-scheduler/account` is the one block that can render **nothing**. It is meant
|
||||||
|
for a header, sidebar or account page, and its whole subject is the person
|
||||||
|
signed in — which a stranger is not. A bare "you are not signed in" in a site
|
||||||
|
header is noise that cannot be acted on, so:
|
||||||
|
|
||||||
|
- **No login page chosen** → empty string for signed-out visitors.
|
||||||
|
- **Login page chosen** → a single **Sign in** link.
|
||||||
|
|
||||||
|
Signed in, it shows the display name (`Auth\UserName::format()`, so a username
|
||||||
|
is never exposed), the account email, and a **Sign out** link — deliberately
|
||||||
|
nothing else. Signing out returns to the chosen login page, or to the current page when there
|
||||||
|
is none, so a header sign-out does not also navigate the visitor somewhere.
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Unsupervised\Schedular\Auth;
|
||||||
|
|
||||||
|
use Unsupervised\Schedular\Val;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Who is signed in, and the way out.
|
||||||
|
*
|
||||||
|
* Meant for a header, sidebar or account page — somewhere it sits alongside
|
||||||
|
* other content rather than being the whole of it. That shapes the two
|
||||||
|
* decisions below.
|
||||||
|
*/
|
||||||
|
class AccountPage {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the account shortcode/block output.
|
||||||
|
*
|
||||||
|
* Signed out, this renders a sign-in link when a login page is configured and
|
||||||
|
* **nothing at all** when one is not. A block whose whole job is "you are
|
||||||
|
* signed in as X" has nothing to say to a stranger, and a bare "you are not
|
||||||
|
* signed in" in a site header is noise with no way to act on it. The editor
|
||||||
|
* preview shows the populated state regardless, so the block is never
|
||||||
|
* invisible to the person placing it.
|
||||||
|
*
|
||||||
|
* @param array<int|string, mixed> $atts Block attributes (`loginPageId`) or
|
||||||
|
* shortcode attributes (`login_page_id`).
|
||||||
|
*/
|
||||||
|
public function render( array $atts ): string {
|
||||||
|
$loginPageId = Val::int( $atts['loginPageId'] ?? $atts['login_page_id'] ?? 0 );
|
||||||
|
$loginUrl = $this->pageUrl( $loginPageId );
|
||||||
|
|
||||||
|
wp_enqueue_style( 'us-scheduler' );
|
||||||
|
|
||||||
|
if ( ! is_user_logged_in() ) {
|
||||||
|
if ( null === $loginUrl ) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf(
|
||||||
|
'<div class="us-account us-account-out"><a class="us-account-signin" href="%s">%s</a></div>',
|
||||||
|
esc_url( $loginUrl ),
|
||||||
|
esc_html__( 'Sign in', 'unsupervised-schedular' )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always a WP_User here — is_user_logged_in() above rules out the
|
||||||
|
// id-0 placeholder wp_get_current_user() returns for a visitor.
|
||||||
|
$user = wp_get_current_user();
|
||||||
|
|
||||||
|
$name = UserName::format( $user, get_current_user_id() );
|
||||||
|
$email = $user->user_email;
|
||||||
|
|
||||||
|
// Back to where they were, so signing out of a header link does not also
|
||||||
|
// navigate them somewhere. The login page is the better landing spot when
|
||||||
|
// one is configured, since the current page may be members-only.
|
||||||
|
$logoutUrl = wp_logout_url( $loginUrl ?? (string) get_permalink() );
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
include USC_PLUGIN_DIR . 'templates/frontend/account-page.php';
|
||||||
|
return (string) ob_get_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permalink of a configured page, or null when none is chosen or the chosen
|
||||||
|
* page has since been deleted.
|
||||||
|
*/
|
||||||
|
private function pageUrl( int $pageId ): ?string {
|
||||||
|
if ( $pageId <= 0 ) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = get_permalink( $pageId );
|
||||||
|
|
||||||
|
return is_string( $url ) ? $url : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -210,6 +210,24 @@ class BlockPreview {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sample account panel. Shown populated whatever the editor's own login
|
||||||
|
* state, since on the published page a signed-out visitor may see nothing at
|
||||||
|
* all and an empty box tells the person placing the block nothing.
|
||||||
|
*/
|
||||||
|
public static function account(): string {
|
||||||
|
return sprintf(
|
||||||
|
'<div class="us-account">%s'
|
||||||
|
. '<p class="us-account-who"><span class="us-account-name">%s</span>'
|
||||||
|
. '<span class="us-account-email">%s</span></p>'
|
||||||
|
. '<p class="us-account-actions"><a class="us-account-signout" href="#">%s</a></p></div>',
|
||||||
|
self::note( __( 'Editor preview — each visitor sees their own account here.', 'unsupervised-schedular' ) ),
|
||||||
|
esc_html__( 'Grace Hopper', 'unsupervised-schedular' ),
|
||||||
|
esc_html__( '[email protected]', 'unsupervised-schedular' ),
|
||||||
|
esc_html__( 'Sign out', 'unsupervised-schedular' )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static function note( string $text ): string {
|
private static function note( string $text ): string {
|
||||||
return '<p class="us-editor-note">' . esc_html( $text ) . '</p>';
|
return '<p class="us-editor-note">' . esc_html( $text ) . '</p>';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Unsupervised\Schedular;
|
namespace Unsupervised\Schedular;
|
||||||
|
|
||||||
|
use Unsupervised\Schedular\Auth\AccountPage;
|
||||||
use Unsupervised\Schedular\Auth\LoginPage;
|
use Unsupervised\Schedular\Auth\LoginPage;
|
||||||
use Unsupervised\Schedular\Auth\RegistrationPage;
|
use Unsupervised\Schedular\Auth\RegistrationPage;
|
||||||
use Unsupervised\Schedular\Booking\BookingPage;
|
use Unsupervised\Schedular\Booking\BookingPage;
|
||||||
@@ -30,6 +31,7 @@ class BlockRegistrar {
|
|||||||
private RegistrationPage $registrationPage,
|
private RegistrationPage $registrationPage,
|
||||||
private GroupClassPage $groupClassPage,
|
private GroupClassPage $groupClassPage,
|
||||||
private FamilyPage $familyPage,
|
private FamilyPage $familyPage,
|
||||||
|
private AccountPage $accountPage,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function register(): void {
|
public function register(): void {
|
||||||
@@ -148,6 +150,15 @@ class BlockRegistrar {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'us-scheduler/account' => [
|
||||||
|
'render' => [ $this, 'renderAccount' ],
|
||||||
|
'attributes' => [
|
||||||
|
'loginPageId' => [
|
||||||
|
'type' => 'number',
|
||||||
|
'default' => 0,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +206,15 @@ class BlockRegistrar {
|
|||||||
return BlockPreview::groupClasses( Val::int( $attributes['offeringId'] ?? 0 ) > 0 );
|
return BlockPreview::groupClasses( Val::int( $attributes['offeringId'] ?? 0 ) > 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the account (who is signed in) block.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $attributes Block attributes.
|
||||||
|
*/
|
||||||
|
public function renderAccount( array $attributes = [] ): string {
|
||||||
|
return $this->isEditorPreview() ? BlockPreview::account() : $this->accountPage->render( $attributes );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the family (manage-children) block.
|
* Renders the family (manage-children) block.
|
||||||
*
|
*
|
||||||
|
|||||||
+4
-2
@@ -5,6 +5,7 @@ namespace Unsupervised\Schedular;
|
|||||||
|
|
||||||
use Unsupervised\Schedular\Auth\EmailConfirmationHandler;
|
use Unsupervised\Schedular\Auth\EmailConfirmationHandler;
|
||||||
use Unsupervised\Schedular\Auth\InviteRepository;
|
use Unsupervised\Schedular\Auth\InviteRepository;
|
||||||
|
use Unsupervised\Schedular\Auth\AccountPage;
|
||||||
use Unsupervised\Schedular\Auth\LoginPage;
|
use Unsupervised\Schedular\Auth\LoginPage;
|
||||||
use Unsupervised\Schedular\Auth\RegistrationLoginGate;
|
use Unsupervised\Schedular\Auth\RegistrationLoginGate;
|
||||||
use Unsupervised\Schedular\Auth\RegistrationMailer;
|
use Unsupervised\Schedular\Auth\RegistrationMailer;
|
||||||
@@ -99,6 +100,7 @@ class Plugin {
|
|||||||
$registrationPage = new RegistrationPage( $invites, $policies, $policyVersions, $acceptances, $settings, $registrationMailer, $questions, $answers, $groupAccess, $guardians );
|
$registrationPage = new RegistrationPage( $invites, $policies, $policyVersions, $acceptances, $settings, $registrationMailer, $questions, $answers, $groupAccess, $guardians );
|
||||||
$groupClassPage = new GroupClassPage( $guardians );
|
$groupClassPage = new GroupClassPage( $guardians );
|
||||||
$familyPage = new FamilyPage( $guardians, $questions, $answers );
|
$familyPage = new FamilyPage( $guardians, $questions, $answers );
|
||||||
|
$accountPage = new AccountPage();
|
||||||
|
|
||||||
( new ScheduledBillingRunner( $paymentService, $bookings, $enrollments, $offerings, new PaymentDueMailer(), $guardians ) )->register();
|
( new ScheduledBillingRunner( $paymentService, $bookings, $enrollments, $offerings, new PaymentDueMailer(), $guardians ) )->register();
|
||||||
|
|
||||||
@@ -110,7 +112,7 @@ class Plugin {
|
|||||||
( new EmailConfirmationHandler( $settings, $registrationMailer ) )->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 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();
|
( new RestRegistrar( $availability, $bookings, $offerings, $questions, $policies, $policyVersions, $policyService, $registrationGate, $enrollments, $groupAccess, $paymentService, $guardians ) )->register();
|
||||||
( new ShortcodeRegistrar( $bookingPage, $loginPage, $registrationPage, $groupClassPage, $familyPage ) )->register();
|
( new ShortcodeRegistrar( $bookingPage, $loginPage, $registrationPage, $groupClassPage, $familyPage, $accountPage ) )->register();
|
||||||
( new BlockRegistrar( $bookingPage, $loginPage, $registrationPage, $groupClassPage, $familyPage ) )->register();
|
( new BlockRegistrar( $bookingPage, $loginPage, $registrationPage, $groupClassPage, $familyPage, $accountPage ) )->register();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Unsupervised\Schedular;
|
namespace Unsupervised\Schedular;
|
||||||
|
|
||||||
|
use Unsupervised\Schedular\Auth\AccountPage;
|
||||||
use Unsupervised\Schedular\Auth\LoginPage;
|
use Unsupervised\Schedular\Auth\LoginPage;
|
||||||
use Unsupervised\Schedular\Auth\RegistrationPage;
|
use Unsupervised\Schedular\Auth\RegistrationPage;
|
||||||
use Unsupervised\Schedular\Booking\BookingPage;
|
use Unsupervised\Schedular\Booking\BookingPage;
|
||||||
@@ -18,6 +19,7 @@ class ShortcodeRegistrar {
|
|||||||
private RegistrationPage $registrationPage,
|
private RegistrationPage $registrationPage,
|
||||||
private GroupClassPage $groupClassPage,
|
private GroupClassPage $groupClassPage,
|
||||||
private FamilyPage $familyPage,
|
private FamilyPage $familyPage,
|
||||||
|
private AccountPage $accountPage,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function register(): void {
|
public function register(): void {
|
||||||
@@ -26,6 +28,7 @@ class ShortcodeRegistrar {
|
|||||||
add_shortcode( 'us_student_register', self::shortcode( [ $this->registrationPage, 'render' ] ) );
|
add_shortcode( 'us_student_register', self::shortcode( [ $this->registrationPage, 'render' ] ) );
|
||||||
add_shortcode( 'us_group_classes', self::shortcode( [ $this->groupClassPage, 'render' ] ) );
|
add_shortcode( 'us_group_classes', self::shortcode( [ $this->groupClassPage, 'render' ] ) );
|
||||||
add_shortcode( 'us_family', self::shortcode( [ $this->familyPage, '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
|
// Process registration submissions before output so the invite branch's
|
||||||
// auth cookie is actually sent (render() runs too late, during the_content).
|
// 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, 'maybeHandleSubmit' ] );
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
if (! defined('ABSPATH')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $name Display name of the signed-in visitor.
|
||||||
|
* @var string $email Their account email.
|
||||||
|
* @var string $logoutUrl Nonced sign-out URL, already carrying its redirect.
|
||||||
|
*/
|
||||||
|
?>
|
||||||
|
<div class="us-account">
|
||||||
|
<p class="us-account-who">
|
||||||
|
<span class="us-account-name"><?php echo esc_html($name); ?></span>
|
||||||
|
<?php if ($email !== '') : ?>
|
||||||
|
<span class="us-account-email"><?php echo esc_html($email); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="us-account-actions">
|
||||||
|
<a class="us-account-signout" href="<?php echo esc_url($logoutUrl); ?>"><?php esc_html_e('Sign out', 'unsupervised-schedular'); ?></a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Unsupervised\Schedular\Tests\Unit\Auth;
|
||||||
|
|
||||||
|
use Brain\Monkey\Functions;
|
||||||
|
use Mockery;
|
||||||
|
use Unsupervised\Schedular\Auth\AccountPage;
|
||||||
|
use Unsupervised\Schedular\Tests\Unit\TestCase;
|
||||||
|
|
||||||
|
class AccountPageTest extends TestCase
|
||||||
|
{
|
||||||
|
private AccountPage $page;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->page = new AccountPage();
|
||||||
|
|
||||||
|
Functions\when('is_user_logged_in')->justReturn(true);
|
||||||
|
Functions\when('get_current_user_id')->justReturn(5);
|
||||||
|
Functions\when('wp_enqueue_style')->justReturn(null);
|
||||||
|
Functions\when('get_permalink')->alias(
|
||||||
|
static fn (int $id = 0): string => $id > 0
|
||||||
|
? 'https://studio.test/sign-in/'
|
||||||
|
: 'https://studio.test/current/'
|
||||||
|
);
|
||||||
|
Functions\when('wp_logout_url')->alias(
|
||||||
|
static fn (string $redirect): string => 'https://studio.test/wp-login.php?action=logout&redirect_to=' . rawurlencode($redirect)
|
||||||
|
);
|
||||||
|
Functions\when('wp_get_current_user')->justReturn($this->user('Grace', 'Hopper', '[email protected]'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function user(string $first, string $last, string $email): \WP_User
|
||||||
|
{
|
||||||
|
$user = Mockery::mock(\WP_User::class);
|
||||||
|
$user->ID = 5;
|
||||||
|
$user->first_name = $first;
|
||||||
|
$user->last_name = $last;
|
||||||
|
$user->nickname = '';
|
||||||
|
$user->user_email = $email;
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testShowsTheSignedInNameAndEmail(): void
|
||||||
|
{
|
||||||
|
$html = $this->page->render([]);
|
||||||
|
|
||||||
|
self::assertStringContainsString('Grace Hopper', $html);
|
||||||
|
self::assertStringContainsString('[email protected]', $html);
|
||||||
|
self::assertStringContainsString('Sign out', $html);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSigningOutReturnsToTheConfiguredLoginPage(): void
|
||||||
|
{
|
||||||
|
self::assertStringContainsString(
|
||||||
|
rawurlencode('https://studio.test/sign-in/'),
|
||||||
|
$this->page->render(['loginPageId' => 9])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* With no page chosen, signing out from a header link should leave the
|
||||||
|
* visitor where they were rather than navigating them somewhere.
|
||||||
|
*/
|
||||||
|
public function testSigningOutReturnsToTheCurrentPageWhenNoLoginPageIsSet(): void
|
||||||
|
{
|
||||||
|
self::assertStringContainsString(
|
||||||
|
rawurlencode('https://studio.test/current/'),
|
||||||
|
$this->page->render([])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTheShortcodeAttributeNameIsAccepted(): void
|
||||||
|
{
|
||||||
|
self::assertStringContainsString(
|
||||||
|
rawurlencode('https://studio.test/sign-in/'),
|
||||||
|
$this->page->render(['login_page_id' => 9])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block whose whole job is "you are signed in as X" has nothing to say to
|
||||||
|
* a stranger, and a bare notice in a site header cannot be acted on.
|
||||||
|
*/
|
||||||
|
public function testRendersNothingForASignedOutVisitorWithNoLoginPage(): void
|
||||||
|
{
|
||||||
|
Functions\when('is_user_logged_in')->justReturn(false);
|
||||||
|
|
||||||
|
self::assertSame('', $this->page->render([]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOffersASignInLinkToASignedOutVisitorWhenAPageIsChosen(): void
|
||||||
|
{
|
||||||
|
Functions\when('is_user_logged_in')->justReturn(false);
|
||||||
|
|
||||||
|
$html = $this->page->render(['loginPageId' => 9]);
|
||||||
|
|
||||||
|
self::assertStringContainsString('https://studio.test/sign-in/', $html);
|
||||||
|
self::assertStringContainsString('Sign in', $html);
|
||||||
|
self::assertStringNotContainsString('Sign out', $html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A page can be deleted after it has been chosen in the block, which
|
||||||
|
* get_permalink() reports as false.
|
||||||
|
*/
|
||||||
|
public function testTreatsADeletedLoginPageAsNoneChosen(): void
|
||||||
|
{
|
||||||
|
Functions\when('is_user_logged_in')->justReturn(false);
|
||||||
|
Functions\when('get_permalink')->justReturn(false);
|
||||||
|
|
||||||
|
self::assertSame('', $this->page->render(['loginPageId' => 9]));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ namespace Unsupervised\Schedular\Tests\Unit;
|
|||||||
use Brain\Monkey\Actions;
|
use Brain\Monkey\Actions;
|
||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
|
use Unsupervised\Schedular\Auth\AccountPage;
|
||||||
use Unsupervised\Schedular\Auth\LoginPage;
|
use Unsupervised\Schedular\Auth\LoginPage;
|
||||||
use Unsupervised\Schedular\Auth\RegistrationPage;
|
use Unsupervised\Schedular\Auth\RegistrationPage;
|
||||||
use Unsupervised\Schedular\BlockRegistrar;
|
use Unsupervised\Schedular\BlockRegistrar;
|
||||||
@@ -43,6 +44,7 @@ class BlockRegistrarTest extends TestCase
|
|||||||
private RegistrationPage&Mockery\MockInterface $registrationPage;
|
private RegistrationPage&Mockery\MockInterface $registrationPage;
|
||||||
private GroupClassPage&Mockery\MockInterface $groupClassPage;
|
private GroupClassPage&Mockery\MockInterface $groupClassPage;
|
||||||
private FamilyPage&Mockery\MockInterface $familyPage;
|
private FamilyPage&Mockery\MockInterface $familyPage;
|
||||||
|
private AccountPage&Mockery\MockInterface $accountPage;
|
||||||
private TestableBlockRegistrar $registrar;
|
private TestableBlockRegistrar $registrar;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
@@ -54,6 +56,7 @@ class BlockRegistrarTest extends TestCase
|
|||||||
$this->registrationPage = Mockery::mock(RegistrationPage::class);
|
$this->registrationPage = Mockery::mock(RegistrationPage::class);
|
||||||
$this->groupClassPage = Mockery::mock(GroupClassPage::class);
|
$this->groupClassPage = Mockery::mock(GroupClassPage::class);
|
||||||
$this->familyPage = Mockery::mock(FamilyPage::class);
|
$this->familyPage = Mockery::mock(FamilyPage::class);
|
||||||
|
$this->accountPage = Mockery::mock(AccountPage::class);
|
||||||
|
|
||||||
// Most requests are not a just-finished registration; the tests that
|
// Most requests are not a just-finished registration; the tests that
|
||||||
// exercise that path override this.
|
// exercise that path override this.
|
||||||
@@ -67,6 +70,7 @@ class BlockRegistrarTest extends TestCase
|
|||||||
$this->registrationPage,
|
$this->registrationPage,
|
||||||
$this->groupClassPage,
|
$this->groupClassPage,
|
||||||
$this->familyPage,
|
$this->familyPage,
|
||||||
|
$this->accountPage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +120,7 @@ class BlockRegistrarTest extends TestCase
|
|||||||
'us-scheduler/student-register',
|
'us-scheduler/student-register',
|
||||||
'us-scheduler/group-classes',
|
'us-scheduler/group-classes',
|
||||||
'us-scheduler/family',
|
'us-scheduler/family',
|
||||||
|
'us-scheduler/account',
|
||||||
],
|
],
|
||||||
array_keys($registered)
|
array_keys($registered)
|
||||||
);
|
);
|
||||||
@@ -213,6 +218,7 @@ class BlockRegistrarTest extends TestCase
|
|||||||
$this->registrationPage,
|
$this->registrationPage,
|
||||||
$this->groupClassPage,
|
$this->groupClassPage,
|
||||||
$this->familyPage,
|
$this->familyPage,
|
||||||
|
$this->accountPage,
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->bookingPage->shouldReceive('render')->once()->with([])->andReturn('live');
|
$this->bookingPage->shouldReceive('render')->once()->with([])->andReturn('live');
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace Unsupervised\Schedular\Tests\Unit;
|
|||||||
use Brain\Monkey\Actions;
|
use Brain\Monkey\Actions;
|
||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
|
use Unsupervised\Schedular\Auth\AccountPage;
|
||||||
use Unsupervised\Schedular\Auth\LoginPage;
|
use Unsupervised\Schedular\Auth\LoginPage;
|
||||||
use Unsupervised\Schedular\Auth\RegistrationPage;
|
use Unsupervised\Schedular\Auth\RegistrationPage;
|
||||||
use Unsupervised\Schedular\Booking\BookingPage;
|
use Unsupervised\Schedular\Booking\BookingPage;
|
||||||
@@ -20,6 +21,7 @@ class ShortcodeRegistrarTest extends TestCase
|
|||||||
private RegistrationPage&Mockery\MockInterface $registrationPage;
|
private RegistrationPage&Mockery\MockInterface $registrationPage;
|
||||||
private GroupClassPage&Mockery\MockInterface $groupClassPage;
|
private GroupClassPage&Mockery\MockInterface $groupClassPage;
|
||||||
private FamilyPage&Mockery\MockInterface $familyPage;
|
private FamilyPage&Mockery\MockInterface $familyPage;
|
||||||
|
private AccountPage&Mockery\MockInterface $accountPage;
|
||||||
private ShortcodeRegistrar $registrar;
|
private ShortcodeRegistrar $registrar;
|
||||||
|
|
||||||
/** @var array<string, callable> */
|
/** @var array<string, callable> */
|
||||||
@@ -37,6 +39,7 @@ class ShortcodeRegistrarTest extends TestCase
|
|||||||
$this->registrationPage = Mockery::mock(RegistrationPage::class);
|
$this->registrationPage = Mockery::mock(RegistrationPage::class);
|
||||||
$this->groupClassPage = Mockery::mock(GroupClassPage::class);
|
$this->groupClassPage = Mockery::mock(GroupClassPage::class);
|
||||||
$this->familyPage = Mockery::mock(FamilyPage::class);
|
$this->familyPage = Mockery::mock(FamilyPage::class);
|
||||||
|
$this->accountPage = Mockery::mock(AccountPage::class);
|
||||||
|
|
||||||
$this->registrar = new ShortcodeRegistrar(
|
$this->registrar = new ShortcodeRegistrar(
|
||||||
$this->bookingPage,
|
$this->bookingPage,
|
||||||
@@ -44,6 +47,7 @@ class ShortcodeRegistrarTest extends TestCase
|
|||||||
$this->registrationPage,
|
$this->registrationPage,
|
||||||
$this->groupClassPage,
|
$this->groupClassPage,
|
||||||
$this->familyPage,
|
$this->familyPage,
|
||||||
|
$this->accountPage,
|
||||||
);
|
);
|
||||||
|
|
||||||
$shortcodes = &$this->shortcodes;
|
$shortcodes = &$this->shortcodes;
|
||||||
@@ -66,7 +70,7 @@ class ShortcodeRegistrarTest extends TestCase
|
|||||||
$this->registrar->register();
|
$this->registrar->register();
|
||||||
|
|
||||||
self::assertSame(
|
self::assertSame(
|
||||||
['us_booking', 'us_student_login', 'us_student_register', 'us_group_classes', 'us_family'],
|
['us_booking', 'us_student_login', 'us_student_register', 'us_group_classes', 'us_family', 'us_account'],
|
||||||
array_keys($this->shortcodes)
|
array_keys($this->shortcodes)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user