\WP_REST_Server::READABLE, 'callback' => [ $this, 'index' ], 'permission_callback' => [ $this, 'canBook' ], 'args' => [ 'instructor_id' => [ 'type' => 'integer', 'default' => 0, ], 'offering_id' => [ 'type' => 'integer', 'default' => 0, ], 'duration_minutes' => [ 'type' => 'integer', 'default' => 0, ], 'from' => [ 'type' => 'string', 'default' => '', ], 'to' => [ 'type' => 'string', 'default' => '', ], ], ], [ 'methods' => \WP_REST_Server::CREATABLE, 'callback' => [ $this, 'create' ], 'permission_callback' => [ $this, 'canManage' ], 'args' => [ 'start_dt' => [ 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field', ], 'end_dt' => [ 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field', ], 'duration_minutes' => [ 'type' => 'integer', 'default' => 60, ], 'offering_id' => [ 'type' => 'integer', 'default' => 0, ], 'recurrence' => [ 'type' => 'string', 'default' => 'single', ], 'weeks' => [ 'type' => 'integer', 'default' => 1, ], ], ], ] ); register_rest_route( $route_namespace, '/availability/(?P\d+)', [ [ 'methods' => \WP_REST_Server::DELETABLE, 'callback' => [ $this, 'delete' ], 'permission_callback' => [ $this, 'canManage' ], ], ] ); } public function index( \WP_REST_Request $request ): \WP_REST_Response { $slots = $this->repository->findAvailable( Val::int( $request->get_param( 'instructor_id' ) ), Val::int( $request->get_param( 'offering_id' ) ), Val::int( $request->get_param( 'duration_minutes' ) ), Val::string( $request->get_param( 'from' ) ), Val::string( $request->get_param( 'to' ) ), ); return new \WP_REST_Response( array_map( fn( AvailabilitySlot $s ) => $s->toArray(), $slots ), 200 ); } public function create( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error { $instructorId = get_current_user_id(); $offeringId = absint( Val::int( $request->get_param( 'offering_id' ) ) ); $duration = absint( Val::int( $request->get_param( 'duration_minutes' ) ) ); // A slot may only be tied to an offering the instructor owns, so it can // never inherit another instructor's price or payment routing at booking. if ( $offeringId > 0 ) { $offering = $this->offerings->findById( $offeringId ); if ( null === $offering || $offering->instructorId !== $instructorId ) { return new \WP_Error( 'invalid_offering', __( 'That offering is not available.', 'unsupervised-schedular' ), [ 'status' => 400 ] ); } } $startDt = AvailabilitySlot::normalizeDateTime( Val::string( $request->get_param( 'start_dt' ) ) ); $endDt = AvailabilitySlot::normalizeDateTime( Val::string( $request->get_param( 'end_dt' ) ) ); if ( null === $startDt || null === $endDt || $endDt <= $startDt ) { return new \WP_Error( 'invalid_datetime', __( 'Provide a valid start and end, with the end after the start.', 'unsupervised-schedular' ), [ 'status' => 400 ] ); } $slot = new AvailabilitySlot( instructorId: $instructorId, startDt: $startDt, endDt: $endDt, durationMinutes: $duration > 0 ? $duration : 60, offeringId: $offeringId > 0 ? $offeringId : null, ); if ( 'weekly' === $request->get_param( 'recurrence' ) ) { $ids = $this->repository->createWeeklySeries( $slot, absint( Val::int( $request->get_param( 'weeks' ) ) ) ); return new \WP_REST_Response( [ 'ids' => $ids ], 201 ); } $id = $this->repository->insert( $slot ); return new \WP_REST_Response( [ 'id' => $id ], 201 ); } public function delete( \WP_REST_Request $request ): \WP_REST_Response|\WP_Error { $id = absint( Val::int( $request->get_param( 'id' ) ) ); $slot = $this->repository->findById( $id ); if ( null === $slot ) { return new \WP_Error( 'not_found', __( 'Slot not found.', 'unsupervised-schedular' ), [ 'status' => 404 ] ); } if ( get_current_user_id() !== $slot->instructorId ) { return new \WP_Error( 'forbidden', __( 'You cannot delete this slot.', 'unsupervised-schedular' ), [ 'status' => 403 ] ); } if ( $slot->isBooked ) { return new \WP_Error( 'slot_booked', __( 'Cannot delete a booked slot.', 'unsupervised-schedular' ), [ 'status' => 409 ] ); } $this->repository->delete( $id ); return new \WP_REST_Response( null, 204 ); } public function canBook(): bool { return is_user_logged_in() && current_user_can( RoleManager::CAP_BOOK_LESSON ); } public function canManage(): bool { return is_user_logged_in() && current_user_can( RoleManager::CAP_MANAGE_AVAILABILITY ); } }