__( 'Extra configuration.', 'extrova' ), 'type' => 'object', 'required' => true, 'validate_callback' => array( $this, 'validate_extra' ), 'sanitize_callback' => static fn( $value ): array => is_array( $value ) ? $value : array(), ); $revision_arg = array( 'description' => __( 'Revision read by the client (optimistic locking).', 'extrova' ), 'type' => 'integer', 'minimum' => 0, ); register_rest_route( self::REST_NAMESPACE, '/extras', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'list_items' ), 'permission_callback' => array( Capability::class, 'check' ), ), array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'create_item' ), 'permission_callback' => array( Capability::class, 'check' ), 'args' => array( 'extra' => $extra_arg, 'revision' => $revision_arg, ), ), ) ); register_rest_route( self::REST_NAMESPACE, '/extras/reorder', array( array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'reorder_items' ), 'permission_callback' => array( Capability::class, 'check' ), 'args' => array( 'ids' => array( 'type' => 'array', 'required' => true, 'items' => array( 'type' => 'string', 'pattern' => '^ext_[a-f0-9]{8}$', ), ), 'revision' => $revision_arg, ), ), ) ); register_rest_route( self::REST_NAMESPACE, '/extras/(?Pext_[a-f0-9]{8})', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( Capability::class, 'check' ), ), array( 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_item' ), 'permission_callback' => array( Capability::class, 'check' ), 'args' => array( 'extra' => $extra_arg, 'revision' => array_merge( $revision_arg, array( 'required' => true ) ), ), ), array( 'methods' => \WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_item' ), 'permission_callback' => array( Capability::class, 'check' ), 'args' => array( 'revision' => $revision_arg ), ), ) ); } /** * Validates an extra against the (filterable) JSON Schema. * * @param mixed $value Value. * @return true|\WP_Error */ public function validate_extra( $value ) { if ( ! is_array( $value ) ) { return new \WP_Error( 'rest_invalid_param', __( 'The extra must be an object.', 'extrova' ), array( 'status' => 400 ) ); } $result = rest_validate_value_from_schema( $value, $this->schema->extra_schema(), 'extra' ); return is_wp_error( $result ) ? $result : true; } /** * Collection response. */ private function collection(): array { return array( 'extras' => array_map( static fn( Extra $extra ): array => $extra->to_array(), $this->repository->all() ), 'revision' => $this->repository->revision(), 'unsupported' => $this->service->unsupported_ids(), ); } /** * Optional revision param. * * @param \WP_REST_Request $request Request. */ private function revision_param( \WP_REST_Request $request ): ?int { $revision = $request->get_param( 'revision' ); return null === $revision ? null : (int) $revision; } /** * Conflict error. */ private function conflict(): \WP_Error { return new \WP_Error( 'extrova_conflict', __( 'Another user changed the extras in the meantime. The list has been reloaded; please review it and save again.', 'extrova' ), array( 'status' => 409 ) ); } /** * GET /extras. */ public function list_items(): \WP_REST_Response { return rest_ensure_response( $this->collection() ); } /** * GET /extras/{id}. * * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ public function get_item( \WP_REST_Request $request ) { $extra = $this->repository->find( (string) $request['id'] ); if ( ! $extra ) { return new \WP_Error( 'extrova_not_found', __( 'Extra not found.', 'extrova' ), array( 'status' => 404 ) ); } return rest_ensure_response( array( 'extra' => $extra->to_array(), 'revision' => $this->repository->revision(), ) ); } /** * POST /extras. * * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ public function create_item( \WP_REST_Request $request ) { $raw = (array) $request->get_param( 'extra' ); $data = $this->schema->normalize_extra( $raw ); $data['id'] = $this->repository->generate_id(); $data['position'] = $this->repository->next_position(); if ( '' === $data['label'] ) { return new \WP_Error( 'rest_invalid_param', __( 'The extra needs a label.', 'extrova' ), array( 'status' => 400 ) ); } try { $this->repository->save( Extra::from_array( $data ), $this->revision_param( $request ) ); } catch ( ConflictException $e ) { return $this->conflict(); } $response = $this->collection(); $response['extra'] = $data; $result = rest_ensure_response( $response ); $result->set_status( 201 ); return $result; } /** * PUT /extras/{id}. * * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ public function update_item( \WP_REST_Request $request ) { $existing = $this->repository->find( (string) $request['id'] ); if ( ! $existing ) { return new \WP_Error( 'extrova_not_found', __( 'Extra not found.', 'extrova' ), array( 'status' => 404 ) ); } $data = $this->schema->normalize_extra( (array) $request->get_param( 'extra' ), $existing ); if ( '' === $data['label'] ) { return new \WP_Error( 'rest_invalid_param', __( 'The extra needs a label.', 'extrova' ), array( 'status' => 400 ) ); } try { $this->repository->save( Extra::from_array( $data ), $this->revision_param( $request ) ); } catch ( ConflictException $e ) { return $this->conflict(); } $response = $this->collection(); $response['extra'] = $data; return rest_ensure_response( $response ); } /** * DELETE /extras/{id}. * * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ public function delete_item( \WP_REST_Request $request ) { if ( ! $this->repository->find( (string) $request['id'] ) ) { return new \WP_Error( 'extrova_not_found', __( 'Extra not found.', 'extrova' ), array( 'status' => 404 ) ); } try { $this->repository->delete( (string) $request['id'], $this->revision_param( $request ) ); } catch ( ConflictException $e ) { return $this->conflict(); } return rest_ensure_response( $this->collection() ); } /** * POST /extras/reorder. * * @param \WP_REST_Request $request Request. * @return \WP_REST_Response|\WP_Error */ public function reorder_items( \WP_REST_Request $request ) { $ids = array_values( array_filter( array_map( 'strval', (array) $request->get_param( 'ids' ) ) ) ); try { $this->repository->reorder( $ids, $this->revision_param( $request ) ); } catch ( ConflictException $e ) { return $this->conflict(); } return rest_ensure_response( $this->collection() ); } }