Add an account block showing who is signed in #156
+1
-1
@@ -14,7 +14,7 @@ each change under the current top section as you work.
|
|||||||
## [1.3.1]
|
## [1.3.1]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- An **Account** block (`[us_account]`) showing who is signed in — their name, their email, the students they book for if that is anyone besides themselves — 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.
|
- 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.
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
- Signup now checks the password properly. The form scores it as you type with the same zxcvbn meter wp-admin uses and will not submit a weak one, and the server refuses — regardless of what the browser allowed — anything shorter than 8 characters, one of the well-known leaked passwords, one built from barely any distinct characters, or one containing your own name or email address. Composition rules ("must contain a symbol") are deliberately not imposed: they mostly produce predictable substitutions. Email addresses are validated on the server on every signup path, with a clear message when one is already registered.
|
- Signup now checks the password properly. The form scores it as you type with the same zxcvbn meter wp-admin uses and will not submit a weak one, and the server refuses — regardless of what the browser allowed — anything shorter than 8 characters, one of the well-known leaked passwords, one built from barely any distinct characters, or one containing your own name or email address. Composition rules ("must contain a symbol") are deliberately not imposed: they mostly produce predictable substitutions. Email addresses are validated on the server on every signup path, with a clear message when one is already registered.
|
||||||
|
|||||||
@@ -562,8 +562,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.us-account-email,
|
.us-account-email {
|
||||||
.us-account-students {
|
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|||||||
+1
-1
@@ -310,7 +310,7 @@
|
|||||||
{
|
{
|
||||||
name: 'us-scheduler/account',
|
name: 'us-scheduler/account',
|
||||||
title: __('Account', 'unsupervised-schedular'),
|
title: __('Account', 'unsupervised-schedular'),
|
||||||
description: __('Shows who is signed in, who they book for, and a sign out link. Renders nothing for signed-out visitors unless a login page is chosen.', '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',
|
icon: 'admin-users',
|
||||||
keywords: ['account', 'sign out', 'log out', 'signed in', 'profile'],
|
keywords: ['account', 'sign out', 'log out', 'signed in', 'profile'],
|
||||||
shortcode: 'us_account',
|
shortcode: 'us_account',
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ header is noise that cannot be acted on, so:
|
|||||||
- **Login page chosen** → a single **Sign in** link.
|
- **Login page chosen** → a single **Sign in** link.
|
||||||
|
|
||||||
Signed in, it shows the display name (`Auth\UserName::format()`, so a username
|
Signed in, it shows the display name (`Auth\UserName::format()`, so a username
|
||||||
is never exposed), the account email, a **Sign out** link, and — only on an
|
is never exposed), the account email, and a **Sign out** link — deliberately
|
||||||
account that books for someone other than itself — the students it books for.
|
nothing else. Signing out returns to the chosen login page, or to the current page when there
|
||||||
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.
|
is none, so a header sign-out does not also navigate the visitor somewhere.
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Unsupervised\Schedular\Auth;
|
namespace Unsupervised\Schedular\Auth;
|
||||||
|
|
||||||
use Unsupervised\Schedular\Guardian\GuardianService;
|
|
||||||
use Unsupervised\Schedular\Val;
|
use Unsupervised\Schedular\Val;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,8 +14,6 @@ use Unsupervised\Schedular\Val;
|
|||||||
*/
|
*/
|
||||||
class AccountPage {
|
class AccountPage {
|
||||||
|
|
||||||
public function __construct( private GuardianService $guardians ) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the account shortcode/block output.
|
* Renders the account shortcode/block output.
|
||||||
*
|
*
|
||||||
@@ -55,16 +52,6 @@ class AccountPage {
|
|||||||
$name = UserName::format( $user, get_current_user_id() );
|
$name = UserName::format( $user, get_current_user_id() );
|
||||||
$email = $user->user_email;
|
$email = $user->user_email;
|
||||||
|
|
||||||
// Whose lessons this account books, when that is more than just their own.
|
|
||||||
// A parent's first question on seeing "signed in as Grace" is whether this
|
|
||||||
// is the account their children's lessons are on.
|
|
||||||
$students = [];
|
|
||||||
foreach ( $this->guardians->bookableStudents( get_current_user_id() ) as $student ) {
|
|
||||||
if ( ! $student['is_self'] ) {
|
|
||||||
$students[] = $student['name'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Back to where they were, so signing out of a header link does not also
|
// 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
|
// navigate them somewhere. The login page is the better landing spot when
|
||||||
// one is configured, since the current page may be members-only.
|
// one is configured, since the current page may be members-only.
|
||||||
|
|||||||
@@ -220,18 +220,10 @@ class BlockPreview {
|
|||||||
'<div class="us-account">%s'
|
'<div class="us-account">%s'
|
||||||
. '<p class="us-account-who"><span class="us-account-name">%s</span>'
|
. '<p class="us-account-who"><span class="us-account-name">%s</span>'
|
||||||
. '<span class="us-account-email">%s</span></p>'
|
. '<span class="us-account-email">%s</span></p>'
|
||||||
. '<p class="us-account-students">%s</p>'
|
|
||||||
. '<p class="us-account-actions"><a class="us-account-signout" href="#">%s</a></p></div>',
|
. '<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' ) ),
|
self::note( __( 'Editor preview — each visitor sees their own account here.', 'unsupervised-schedular' ) ),
|
||||||
esc_html__( 'Grace Hopper', 'unsupervised-schedular' ),
|
esc_html__( 'Grace Hopper', 'unsupervised-schedular' ),
|
||||||
esc_html__( '[email protected]', 'unsupervised-schedular' ),
|
esc_html__( '[email protected]', 'unsupervised-schedular' ),
|
||||||
esc_html(
|
|
||||||
sprintf(
|
|
||||||
/* translators: %s: comma-separated list of the students this account books for. */
|
|
||||||
__( 'Booking for %s', 'unsupervised-schedular' ),
|
|
||||||
__( 'Ada, Alan', 'unsupervised-schedular' )
|
|
||||||
)
|
|
||||||
),
|
|
||||||
esc_html__( 'Sign out', 'unsupervised-schedular' )
|
esc_html__( 'Sign out', 'unsupervised-schedular' )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -100,7 +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( $guardians );
|
$accountPage = new AccountPage();
|
||||||
|
|
||||||
( new ScheduledBillingRunner( $paymentService, $bookings, $enrollments, $offerings, new PaymentDueMailer(), $guardians ) )->register();
|
( new ScheduledBillingRunner( $paymentService, $bookings, $enrollments, $offerings, new PaymentDueMailer(), $guardians ) )->register();
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ if (! defined('ABSPATH')) {
|
|||||||
/**
|
/**
|
||||||
* @var string $name Display name of the signed-in visitor.
|
* @var string $name Display name of the signed-in visitor.
|
||||||
* @var string $email Their account email.
|
* @var string $email Their account email.
|
||||||
* @var list<string> $students Names this account books for, excluding themselves; empty for a plain student account.
|
|
||||||
* @var string $logoutUrl Nonced sign-out URL, already carrying its redirect.
|
* @var string $logoutUrl Nonced sign-out URL, already carrying its redirect.
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
@@ -20,18 +19,6 @@ if (! defined('ABSPATH')) {
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<?php if (! empty($students)) : ?>
|
|
||||||
<p class="us-account-students">
|
|
||||||
<?php
|
|
||||||
printf(
|
|
||||||
/* translators: %s: comma-separated list of the students this account books for. */
|
|
||||||
esc_html__('Booking for %s', 'unsupervised-schedular'),
|
|
||||||
esc_html(implode(', ', $students))
|
|
||||||
);
|
|
||||||
?>
|
|
||||||
</p>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<p class="us-account-actions">
|
<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>
|
<a class="us-account-signout" href="<?php echo esc_url($logoutUrl); ?>"><?php esc_html_e('Sign out', 'unsupervised-schedular'); ?></a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -6,20 +6,17 @@ namespace Unsupervised\Schedular\Tests\Unit\Auth;
|
|||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
use Unsupervised\Schedular\Auth\AccountPage;
|
use Unsupervised\Schedular\Auth\AccountPage;
|
||||||
use Unsupervised\Schedular\Guardian\GuardianService;
|
|
||||||
use Unsupervised\Schedular\Tests\Unit\TestCase;
|
use Unsupervised\Schedular\Tests\Unit\TestCase;
|
||||||
|
|
||||||
class AccountPageTest extends TestCase
|
class AccountPageTest extends TestCase
|
||||||
{
|
{
|
||||||
private GuardianService&Mockery\MockInterface $guardians;
|
|
||||||
private AccountPage $page;
|
private AccountPage $page;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$this->guardians = Mockery::mock(GuardianService::class);
|
$this->page = new AccountPage();
|
||||||
$this->page = new AccountPage($this->guardians);
|
|
||||||
|
|
||||||
Functions\when('is_user_logged_in')->justReturn(true);
|
Functions\when('is_user_logged_in')->justReturn(true);
|
||||||
Functions\when('get_current_user_id')->justReturn(5);
|
Functions\when('get_current_user_id')->justReturn(5);
|
||||||
@@ -47,16 +44,8 @@ class AccountPageTest extends TestCase
|
|||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param list<array{id: int, name: string, is_self: bool}> $students */
|
|
||||||
private function bookable(array $students): void
|
|
||||||
{
|
|
||||||
$this->guardians->shouldReceive('bookableStudents')->with(5)->andReturn($students);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testShowsTheSignedInNameAndEmail(): void
|
public function testShowsTheSignedInNameAndEmail(): void
|
||||||
{
|
{
|
||||||
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
|
|
||||||
|
|
||||||
$html = $this->page->render([]);
|
$html = $this->page->render([]);
|
||||||
|
|
||||||
self::assertStringContainsString('Grace Hopper', $html);
|
self::assertStringContainsString('Grace Hopper', $html);
|
||||||
@@ -64,32 +53,8 @@ class AccountPageTest extends TestCase
|
|||||||
self::assertStringContainsString('Sign out', $html);
|
self::assertStringContainsString('Sign out', $html);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testNamesTheStudentsTheAccountBooksFor(): void
|
|
||||||
{
|
|
||||||
$this->bookable([
|
|
||||||
['id' => 42, 'name' => 'Ada', 'is_self' => false],
|
|
||||||
['id' => 43, 'name' => 'Alan', 'is_self' => false],
|
|
||||||
['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true],
|
|
||||||
]);
|
|
||||||
|
|
||||||
self::assertStringContainsString('Booking for Ada, Alan', $this->page->render([]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An account that only books for itself has nothing to add — "Booking for
|
|
||||||
* Grace Hopper" under "Grace Hopper" is noise.
|
|
||||||
*/
|
|
||||||
public function testSaysNothingAboutStudentsOnAPlainAccount(): void
|
|
||||||
{
|
|
||||||
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
|
|
||||||
|
|
||||||
self::assertStringNotContainsString('Booking for', $this->page->render([]));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testSigningOutReturnsToTheConfiguredLoginPage(): void
|
public function testSigningOutReturnsToTheConfiguredLoginPage(): void
|
||||||
{
|
{
|
||||||
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
|
|
||||||
|
|
||||||
self::assertStringContainsString(
|
self::assertStringContainsString(
|
||||||
rawurlencode('https://studio.test/sign-in/'),
|
rawurlencode('https://studio.test/sign-in/'),
|
||||||
$this->page->render(['loginPageId' => 9])
|
$this->page->render(['loginPageId' => 9])
|
||||||
@@ -102,8 +67,6 @@ class AccountPageTest extends TestCase
|
|||||||
*/
|
*/
|
||||||
public function testSigningOutReturnsToTheCurrentPageWhenNoLoginPageIsSet(): void
|
public function testSigningOutReturnsToTheCurrentPageWhenNoLoginPageIsSet(): void
|
||||||
{
|
{
|
||||||
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
|
|
||||||
|
|
||||||
self::assertStringContainsString(
|
self::assertStringContainsString(
|
||||||
rawurlencode('https://studio.test/current/'),
|
rawurlencode('https://studio.test/current/'),
|
||||||
$this->page->render([])
|
$this->page->render([])
|
||||||
@@ -112,8 +75,6 @@ class AccountPageTest extends TestCase
|
|||||||
|
|
||||||
public function testTheShortcodeAttributeNameIsAccepted(): void
|
public function testTheShortcodeAttributeNameIsAccepted(): void
|
||||||
{
|
{
|
||||||
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
|
|
||||||
|
|
||||||
self::assertStringContainsString(
|
self::assertStringContainsString(
|
||||||
rawurlencode('https://studio.test/sign-in/'),
|
rawurlencode('https://studio.test/sign-in/'),
|
||||||
$this->page->render(['login_page_id' => 9])
|
$this->page->render(['login_page_id' => 9])
|
||||||
@@ -127,7 +88,6 @@ class AccountPageTest extends TestCase
|
|||||||
public function testRendersNothingForASignedOutVisitorWithNoLoginPage(): void
|
public function testRendersNothingForASignedOutVisitorWithNoLoginPage(): void
|
||||||
{
|
{
|
||||||
Functions\when('is_user_logged_in')->justReturn(false);
|
Functions\when('is_user_logged_in')->justReturn(false);
|
||||||
$this->guardians->shouldNotReceive('bookableStudents');
|
|
||||||
|
|
||||||
self::assertSame('', $this->page->render([]));
|
self::assertSame('', $this->page->render([]));
|
||||||
}
|
}
|
||||||
@@ -135,7 +95,6 @@ class AccountPageTest extends TestCase
|
|||||||
public function testOffersASignInLinkToASignedOutVisitorWhenAPageIsChosen(): void
|
public function testOffersASignInLinkToASignedOutVisitorWhenAPageIsChosen(): void
|
||||||
{
|
{
|
||||||
Functions\when('is_user_logged_in')->justReturn(false);
|
Functions\when('is_user_logged_in')->justReturn(false);
|
||||||
$this->guardians->shouldNotReceive('bookableStudents');
|
|
||||||
|
|
||||||
$html = $this->page->render(['loginPageId' => 9]);
|
$html = $this->page->render(['loginPageId' => 9]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user