Let offering managers read the offerings catalogue #122

Merged
thatguygriff merged 1 commits from fix/offerings-read-permission into main 2026-07-28 16:07:09 +00:00
3 changed files with 48 additions and 7 deletions
Showing only changes of commit 0f30f28e92 - Show all commits
+1 -1
View File
@@ -107,7 +107,7 @@ Studio admin and instructors manage offerings under **Offerings** in wp-admin.
## REST API
| 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` |
| `PATCH` | `/wp-json/us-scheduler/v1/offerings/{id}` | `manage_offerings` + owner |
| `DELETE` | `/wp-json/us-scheduler/v1/offerings/{id}` | `manage_offerings` + owner |
+10 -6
View File
@@ -28,7 +28,7 @@ class OfferingEndpoint {
[
'methods' => \WP_REST_Server::READABLE,
'callback' => [ $this, 'index' ],
'permission_callback' => [ $this, 'canBook' ],
'permission_callback' => [ $this, 'canRead' ],
'args' => [
'instructor_id' => [
'type' => 'integer',
@@ -262,12 +262,16 @@ class OfferingEndpoint {
}
/**
* Reading the offerings catalogue is only needed by the logged-in student
* booking flow, so it requires the same capability as booking — there is no
* anonymous consumer.
* Reading the offerings catalogue has no anonymous consumer, so it stays
* behind a login. Students reach it through the booking flow, and studio
* 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 {
return is_user_logged_in() && current_user_can( RoleManager::CAP_BOOK_LESSON );
public function canRead(): bool {
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 Mockery;
use Unsupervised\Schedular\Auth\RoleManager;
use Unsupervised\Schedular\GroupClass\GroupAccessRepository;
use Unsupervised\Schedular\Offering\Offering;
use Unsupervised\Schedular\Offering\OfferingEndpoint;
@@ -119,6 +120,42 @@ class OfferingEndpointTest extends TestCase
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
{
Functions\when('sanitize_text_field')->returnArg();