[1-9][0-9]*)/cancellation-quote', [ 'methods' => 'GET,POST', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->cancellation->quote( (int) $r['id'], 'GET' === $r->get_method() ? [] : $this->body( $r ), get_current_user_id() ) ) ] ); foreach ( [ 'mark-paid' => 'markPaid', 'refund' => 'refund' ] as $path => $method ) { register_rest_route( $ns, '/bookings/(?P[1-9][0-9]*)/' . $path, [ 'methods' => 'POST', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->service->payments->$method( (int) $r['id'], $this->body( $r ), get_current_user_id(), $this->service ) ) ] ); } register_rest_route( $ns, '/bookings', [ [ 'methods' => 'GET', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->listing( $r, true ), 'args' => $this->pagination( true ) ], ] ); register_rest_route( $ns, '/admin/bookings', [ 'methods' => 'POST', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->service->create( $this->body( $r ), get_current_user_id(), true ), 201 ) ] ); register_rest_route( $ns, '/bookings/(?P[1-9][0-9]*)', [ 'methods' => 'PATCH', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->management->edit( (int) $r['id'], $this->body( $r ), get_current_user_id() ) ) ] ); register_rest_route( $ns, '/bookings/(?P[1-9][0-9]*)/void', [ 'methods' => 'POST', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->management->void( (int) $r['id'], $this->body( $r ), get_current_user_id() ) ) ] ); register_rest_route( $ns, '/bookings/(?P[1-9][0-9]*|SB-[0-9]{4}-[0-9]{6,})', [ 'methods' => 'GET', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->service->detail( $r['key'], get_current_user_id() ) ) ] ); register_rest_route( $ns, '/bookings/(?P[1-9][0-9]*)/cancel', [ 'methods' => 'POST', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->cancellation->cancel( (int) $r['id'], $this->body( $r ), get_current_user_id() ) ) ] ); register_rest_route( $ns, '/bookings/(?P[1-9][0-9]*)/no-show', [ 'methods' => 'POST', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->no_show->mark( (int) $r['id'], $this->body( $r ), get_current_user_id() ) ) ] ); register_rest_route($ns,'/admin/booking-options',['methods'=>'GET','permission_callback'=>[$this,'manage'],'callback'=>fn($r)=>$this->respond(fn()=>$this->service->options(get_current_user_id(),isset($r['date'])?sanitize_text_field($r['date']):null,true))]); register_rest_route( $ns, '/booking-customers', [ 'methods' => 'GET', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->customers->searchCustomers( $r['q'], $r['date'], $r['type'], get_current_user_id() ) ), 'args' => $this->searchArgs() + [ 'type' => [ 'type' => 'string', 'required' => true, 'enum' => [ 'member', 'registered_non_member' ], 'sanitize_callback' => 'sanitize_text_field' ] ] ] ); register_rest_route( $ns, '/admin/booking-times', [ 'methods'=>'GET', 'permission_callback'=>[$this,'manage'], 'callback'=>fn($r)=>$this->respond(fn()=>$this->service->adminBookingTimes($r->get_params(),get_current_user_id())), 'args'=>['date'=>['type'=>'string','required'=>true,'sanitize_callback'=>'sanitize_text_field'],'resource_id'=>['type'=>'integer','required'=>true,'minimum'=>1,'sanitize_callback'=>'absint'],'booking_id'=>['type'=>'integer','minimum'=>1,'sanitize_callback'=>'absint']] ] ); register_rest_route( $ns, '/admin/booking-availability', [ 'methods' => 'GET', 'permission_callback' => [ $this, 'manage' ], 'callback' => fn( $r ) => $this->respond( fn() => $this->service->adminAvailability( $r->get_params(), get_current_user_id() ) ), 'args' => [ 'date' => [ 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ], 'resource_id' => [ 'type' => 'integer', 'required' => true, 'minimum' => 1, 'sanitize_callback' => 'absint' ], 'customer_type' => [ 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ], 'customer_user_id' => [ 'type' => 'integer', 'minimum' => 1, 'sanitize_callback' => 'absint' ], 'customer_member_id' => [ 'type' => 'integer', 'minimum' => 1, 'sanitize_callback' => 'absint' ], 'booking_id' => [ 'type' => 'integer', 'minimum' => 1, 'sanitize_callback' => 'absint' ] ] ] ); } private function searchArgs(): array { return [ 'q' => [ 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ], 'date' => [ 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field' ] ]; } public function login(): bool|WP_Error { return is_user_logged_in() ?: new WP_Error( 'abece_booking_login_required', __( 'Log in to manage your bookings.', 'abece-booking' ), [ 'status' => 401 ] ); } public function manage(): bool|WP_Error { $login = $this->login(); if ( $login instanceof WP_Error ) { return $login; } return current_user_can( Capabilities::MANAGE_BOOKINGS ) ?: new WP_Error( 'abece_booking_booking_forbidden', __( 'You do not have permission to manage bookings.', 'abece-booking' ), [ 'status' => 403 ] ); } private function body( WP_REST_Request $request ): array { $params = $request->get_json_params(); if ( ! is_array( $params ) || ( [] !== $params && array_is_list( $params ) ) || ! str_starts_with( ltrim( $request->get_body() ), '{' ) ) { throw new CatalogException( 'abece_booking_invalid_json', __( 'A JSON object is required.', 'abece-booking' ), 400 ); } return $params; } private function pagination( bool $admin ): array { return [ 'page' => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1, 'sanitize_callback' => 'absint' ], 'per_page' => [ 'type' => 'integer', 'default' => 25, 'minimum' => 1, 'maximum' => 100, 'sanitize_callback' => 'absint' ], 'filter' => [ 'type' => 'string', 'default' => $admin ? 'all' : 'upcoming', 'enum' => $admin ? [ 'all', 'upcoming', 'past', 'cancelled', 'no_show', 'voided' ] : [ 'upcoming' ], 'sanitize_callback' => 'sanitize_text_field' ], ]; } private function listing( WP_REST_Request $r, bool $admin ): WP_REST_Response|WP_Error { return $this->respond( fn() => $this->service->list( get_current_user_id(), $admin, $r['filter'], (int) $r['page'], (int) $r['per_page'] ) ); } private function respond( callable $operation, int $status = 200 ): WP_REST_Response|WP_Error { try { $response = new WP_REST_Response( $operation(), $status ); $response->header( 'Cache-Control', 'private, no-store' ); return $response; } catch ( CatalogException $e ) { return new WP_Error( $e->errorCode, $e->getMessage(), [ 'status' => $e->status, 'field' => $e->field ] + $e->details ); } } }