Let offering managers read the offerings catalogue
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 56s
CI / PHPStan (pull_request) Successful in 2m56s
CI / Coding Standards (pull_request) Successful in 2m58s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m41s
CI / Build Plugin Zip (pull_request) Skipped
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 56s
CI / PHPStan (pull_request) Successful in 2m56s
CI / Coding Standards (pull_request) Successful in 2m58s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m41s
CI / Build Plugin Zip (pull_request) Skipped
The block editor's group-class picker fetches GET /offerings, whose permission callback only accepted book_lesson — a capability held by students alone. Administrators and instructors editing a page were rejected with a 403 and the picker silently rendered an empty list. Read access now accepts book_lesson or manage_offerings. The listing is unchanged: active offerings only, public ones plus the invite-only classes the caller has been granted, without the e-transfer email. Closes #121 Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -107,7 +107,7 @@ Studio admin and instructors manage offerings under **Offerings** in wp-admin.
|
|||||||
## REST API
|
## REST API
|
||||||
| Method | Endpoint | Permission |
|
| Method | Endpoint | Permission |
|
||||||
|----------|---------------------------------------------|----------------------------------|
|
|----------|---------------------------------------------|----------------------------------|
|
||||||
| `GET` | `/wp-json/us-scheduler/v1/offerings` | Public (active offerings only) |
|
| `GET` | `/wp-json/us-scheduler/v1/offerings` | `book_lesson` or `manage_offerings` (active offerings only) |
|
||||||
| `POST` | `/wp-json/us-scheduler/v1/offerings` | `manage_offerings` |
|
| `POST` | `/wp-json/us-scheduler/v1/offerings` | `manage_offerings` |
|
||||||
| `PATCH` | `/wp-json/us-scheduler/v1/offerings/{id}` | `manage_offerings` + owner |
|
| `PATCH` | `/wp-json/us-scheduler/v1/offerings/{id}` | `manage_offerings` + owner |
|
||||||
| `DELETE` | `/wp-json/us-scheduler/v1/offerings/{id}` | `manage_offerings` + owner |
|
| `DELETE` | `/wp-json/us-scheduler/v1/offerings/{id}` | `manage_offerings` + owner |
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class OfferingEndpoint {
|
|||||||
[
|
[
|
||||||
'methods' => \WP_REST_Server::READABLE,
|
'methods' => \WP_REST_Server::READABLE,
|
||||||
'callback' => [ $this, 'index' ],
|
'callback' => [ $this, 'index' ],
|
||||||
'permission_callback' => [ $this, 'canBook' ],
|
'permission_callback' => [ $this, 'canRead' ],
|
||||||
'args' => [
|
'args' => [
|
||||||
'instructor_id' => [
|
'instructor_id' => [
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
@@ -262,12 +262,16 @@ class OfferingEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reading the offerings catalogue is only needed by the logged-in student
|
* Reading the offerings catalogue has no anonymous consumer, so it stays
|
||||||
* booking flow, so it requires the same capability as booking — there is no
|
* behind a login. Students reach it through the booking flow, and studio
|
||||||
* anonymous consumer.
|
* admins and instructors reach it from the block editor's group-class
|
||||||
|
* pickers — an administrator holds `manage_offerings` but not
|
||||||
|
* `book_lesson`, so both capabilities open the listing.
|
||||||
*/
|
*/
|
||||||
public function canBook(): bool {
|
public function canRead(): bool {
|
||||||
return is_user_logged_in() && current_user_can( RoleManager::CAP_BOOK_LESSON );
|
return is_user_logged_in()
|
||||||
|
&& ( current_user_can( RoleManager::CAP_BOOK_LESSON )
|
||||||
|
|| current_user_can( RoleManager::CAP_MANAGE_OFFERINGS ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ namespace Unsupervised\Schedular\Tests\Unit\Offering;
|
|||||||
|
|
||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
|
use Unsupervised\Schedular\Auth\RoleManager;
|
||||||
use Unsupervised\Schedular\GroupClass\GroupAccessRepository;
|
use Unsupervised\Schedular\GroupClass\GroupAccessRepository;
|
||||||
use Unsupervised\Schedular\Offering\Offering;
|
use Unsupervised\Schedular\Offering\Offering;
|
||||||
use Unsupervised\Schedular\Offering\OfferingEndpoint;
|
use Unsupervised\Schedular\Offering\OfferingEndpoint;
|
||||||
@@ -119,6 +120,42 @@ class OfferingEndpointTest extends TestCase
|
|||||||
self::assertArrayNotHasKey('etransfer_email', $data[0]);
|
self::assertArrayNotHasKey('etransfer_email', $data[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCanReadAllowsStudentsWhoMayBook(): void
|
||||||
|
{
|
||||||
|
Functions\when('is_user_logged_in')->justReturn(true);
|
||||||
|
Functions\when('current_user_can')->alias(
|
||||||
|
static fn (string $cap): bool => RoleManager::CAP_BOOK_LESSON === $cap
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertTrue($this->endpoint->canRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanReadAllowsOfferingManagersWhoCannotBook(): void
|
||||||
|
{
|
||||||
|
Functions\when('is_user_logged_in')->justReturn(true);
|
||||||
|
Functions\when('current_user_can')->alias(
|
||||||
|
static fn (string $cap): bool => RoleManager::CAP_MANAGE_OFFERINGS === $cap
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertTrue($this->endpoint->canRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanReadRejectsLoggedInUserWithNeitherCapability(): void
|
||||||
|
{
|
||||||
|
Functions\when('is_user_logged_in')->justReturn(true);
|
||||||
|
Functions\when('current_user_can')->justReturn(false);
|
||||||
|
|
||||||
|
self::assertFalse($this->endpoint->canRead());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testCanReadRejectsLoggedOutVisitors(): void
|
||||||
|
{
|
||||||
|
Functions\when('is_user_logged_in')->justReturn(false);
|
||||||
|
Functions\when('current_user_can')->justReturn(true);
|
||||||
|
|
||||||
|
self::assertFalse($this->endpoint->canRead());
|
||||||
|
}
|
||||||
|
|
||||||
public function testCreateRejectsTitleLongerThanColumnLimit(): void
|
public function testCreateRejectsTitleLongerThanColumnLimit(): void
|
||||||
{
|
{
|
||||||
Functions\when('sanitize_text_field')->returnArg();
|
Functions\when('sanitize_text_field')->returnArg();
|
||||||
|
|||||||
Reference in New Issue
Block a user