- 
                Notifications
    You must be signed in to change notification settings 
- Fork 3.1k
Abilities API: Add Ability Categories REST API controller #10380
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,293 @@ | ||
| <?php | ||
| /** | ||
| * REST API ability categories controller for Abilities API. | ||
| * | ||
| * @package WordPress | ||
| * @subpackage Abilities_API | ||
| * @since 6.9.0 | ||
| */ | ||
|  | ||
| declare( strict_types = 1 ); | ||
|  | ||
| /** | ||
| * Core controller used to access ability categories via the REST API. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @see WP_REST_Controller | ||
| */ | ||
| class WP_REST_Abilities_V1_Categories_Controller extends WP_REST_Controller { | ||
|  | ||
| /** | ||
| * REST API namespace. | ||
| * | ||
| * @since 6.9.0 | ||
| * @var string | ||
| */ | ||
| protected $namespace = 'wp-abilities/v1'; | ||
|  | ||
| /** | ||
| * REST API base route. | ||
| * | ||
| * @since 6.9.0 | ||
| * @var string | ||
| */ | ||
| protected $rest_base = 'categories'; | ||
|  | ||
| /** | ||
| * Registers the routes for ability categories. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @see register_rest_route() | ||
| */ | ||
| public function register_routes(): void { | ||
| register_rest_route( | ||
| $this->namespace, | ||
| '/' . $this->rest_base, | ||
| array( | ||
| array( | ||
| 'methods' => WP_REST_Server::READABLE, | ||
| 'callback' => array( $this, 'get_items' ), | ||
| 'permission_callback' => array( $this, 'get_items_permissions_check' ), | ||
| 'args' => $this->get_collection_params(), | ||
| ), | ||
| 'schema' => array( $this, 'get_public_item_schema' ), | ||
| ) | ||
| ); | ||
|  | ||
| register_rest_route( | ||
| $this->namespace, | ||
| '/' . $this->rest_base . '/(?P<slug>[a-z0-9]+(?:-[a-z0-9]+)*)', | ||
| array( | ||
| 'args' => array( | ||
| 'slug' => array( | ||
| 'description' => __( 'Unique identifier for the ability category.' ), | ||
| 'type' => 'string', | ||
| 'pattern' => '^[a-z0-9]+(?:-[a-z0-9]+)*$', | ||
| ), | ||
| ), | ||
| array( | ||
| 'methods' => WP_REST_Server::READABLE, | ||
| 'callback' => array( $this, 'get_item' ), | ||
| 'permission_callback' => array( $this, 'get_item_permissions_check' ), | ||
| ), | ||
| 'schema' => array( $this, 'get_public_item_schema' ), | ||
| ) | ||
| ); | ||
| } | ||
|  | ||
| /** | ||
| * Retrieves all ability categories. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @param WP_REST_Request $request Full details about the request. | ||
| * @return WP_REST_Response Response object on success. | ||
| */ | ||
| public function get_items( $request ) { | ||
| $categories = wp_get_ability_categories(); | ||
|  | ||
| $page = $request['page']; | ||
| $per_page = $request['per_page']; | ||
| $offset = ( $page - 1 ) * $per_page; | ||
|  | ||
| $total_categories = count( $categories ); | ||
| $max_pages = ceil( $total_categories / $per_page ); | ||
| There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to cast int here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I see other occurrences use  There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Addressed with https://core.trac.wordpress.org/changeset/61047. | ||
|  | ||
| if ( $request->get_method() === 'HEAD' ) { | ||
| $response = new WP_REST_Response( array() ); | ||
| } else { | ||
| $categories = array_slice( $categories, $offset, $per_page ); | ||
|  | ||
| $data = array(); | ||
| foreach ( $categories as $category ) { | ||
| $item = $this->prepare_item_for_response( $category, $request ); | ||
| $data[] = $this->prepare_response_for_collection( $item ); | ||
| } | ||
|  | ||
| $response = rest_ensure_response( $data ); | ||
| } | ||
|  | ||
| $response->header( 'X-WP-Total', (string) $total_categories ); | ||
| $response->header( 'X-WP-TotalPages', (string) $max_pages ); | ||
|  | ||
| $query_params = $request->get_query_params(); | ||
| $base = add_query_arg( | ||
| urlencode_deep( $query_params ), | ||
| rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) | ||
| ); | ||
|  | ||
| if ( $page > 1 ) { | ||
| $prev_page = $page - 1; | ||
| $prev_link = add_query_arg( 'page', $prev_page, $base ); | ||
| $response->link_header( 'prev', $prev_link ); | ||
| } | ||
|  | ||
| if ( $page < $max_pages ) { | ||
| $next_page = $page + 1; | ||
| $next_link = add_query_arg( 'page', $next_page, $base ); | ||
| $response->link_header( 'next', $next_link ); | ||
| } | ||
|  | ||
| return $response; | ||
| } | ||
|  | ||
| /** | ||
| * Retrieves a specific ability category. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @param WP_REST_Request $request Full details about the request. | ||
| * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. | ||
| */ | ||
| public function get_item( $request ) { | ||
| $category = wp_get_ability_category( $request['slug'] ); | ||
| if ( ! $category ) { | ||
| return new WP_Error( | ||
| 'rest_ability_category_not_found', | ||
| __( 'Ability category not found.' ), | ||
| array( 'status' => 404 ) | ||
| ); | ||
| } | ||
|  | ||
| $data = $this->prepare_item_for_response( $category, $request ); | ||
| return rest_ensure_response( $data ); | ||
| } | ||
|  | ||
| /** | ||
| * Checks if a given request has access to read ability categories. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @param WP_REST_Request $request Full details about the request. | ||
| * @return bool True if the request has read access. | ||
| */ | ||
| public function get_items_permissions_check( $request ) { | ||
| return current_user_can( 'read' ); | ||
| } | ||
|  | ||
| /** | ||
| * Checks if a given request has access to read an ability category. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @param WP_REST_Request $request Full details about the request. | ||
| * @return bool True if the request has read access. | ||
| */ | ||
| public function get_item_permissions_check( $request ) { | ||
| return current_user_can( 'read' ); | ||
| } | ||
|  | ||
| /** | ||
| * Prepares an ability category for response. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @param WP_Ability_Category $category The ability category object. | ||
| * @param WP_REST_Request $request Request object. | ||
| * @return WP_REST_Response Response object. | ||
| */ | ||
| public function prepare_item_for_response( $category, $request ) { | ||
| $data = array( | ||
| 'slug' => $category->get_slug(), | ||
| 'label' => $category->get_label(), | ||
| 'description' => $category->get_description(), | ||
| 'meta' => $category->get_meta(), | ||
| ); | ||
|  | ||
| $context = $request['context'] ?? 'view'; | ||
| $data = $this->add_additional_fields_to_object( $data, $request ); | ||
| $data = $this->filter_response_by_context( $data, $context ); | ||
|  | ||
| $response = rest_ensure_response( $data ); | ||
|  | ||
| $fields = $this->get_fields_for_response( $request ); | ||
| if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { | ||
| $links = array( | ||
| 'self' => array( | ||
| 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $category->get_slug() ) ), | ||
| ), | ||
| 'collection' => array( | ||
| 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), | ||
| ), | ||
| 'abilities' => array( | ||
| 'href' => rest_url( sprintf( '%s/abilities?category=%s', $this->namespace, $category->get_slug() ) ), | ||
| ), | ||
| ); | ||
|  | ||
| $response->add_links( $links ); | ||
| } | ||
|  | ||
| return $response; | ||
| } | ||
|  | ||
| /** | ||
| * Retrieves the ability category's schema, conforming to JSON Schema. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @return array<string, mixed> Item schema data. | ||
| */ | ||
| public function get_item_schema(): array { | ||
| $schema = array( | ||
| '$schema' => 'http://json-schema.org/draft-04/schema#', | ||
| 'title' => 'ability-category', | ||
| 'type' => 'object', | ||
| 'properties' => array( | ||
| 'slug' => array( | ||
| 'description' => __( 'Unique identifier for the ability category.' ), | ||
| 'type' => 'string', | ||
| 'context' => array( 'view', 'edit', 'embed' ), | ||
| 'readonly' => true, | ||
| ), | ||
| 'label' => array( | ||
| 'description' => __( 'Display label for the category.' ), | ||
| 'type' => 'string', | ||
| 'context' => array( 'view', 'edit', 'embed' ), | ||
| 'readonly' => true, | ||
| ), | ||
| 'description' => array( | ||
| 'description' => __( 'Description of the category.' ), | ||
| 'type' => 'string', | ||
| 'context' => array( 'view', 'edit' ), | ||
| 'readonly' => true, | ||
| ), | ||
| 'meta' => array( | ||
| 'description' => __( 'Meta information about the category.' ), | ||
| 'type' => 'object', | ||
| 'context' => array( 'view', 'edit' ), | ||
| 'readonly' => true, | ||
| ), | ||
| ), | ||
| ); | ||
|  | ||
| return $this->add_additional_fields_schema( $schema ); | ||
| } | ||
|  | ||
| /** | ||
| * Retrieves the query params for collections. | ||
| * | ||
| * @since 6.9.0 | ||
| * | ||
| * @return array<string, mixed> Collection parameters. | ||
| */ | ||
| public function get_collection_params(): array { | ||
| return array( | ||
| 'context' => $this->get_context_param( array( 'default' => 'view' ) ), | ||
| 'page' => array( | ||
| 'description' => __( 'Current page of the collection.' ), | ||
| 'type' => 'integer', | ||
| 'default' => 1, | ||
| 'minimum' => 1, | ||
| ), | ||
| 'per_page' => array( | ||
| 'description' => __( 'Maximum number of items to be returned in result set.' ), | ||
| 'type' => 'integer', | ||
| 'default' => 50, | ||
| 'minimum' => 1, | ||
| 'maximum' => 100, | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good! The only thing absent are hooks. I don't think we need a ton (not quite so many as WP_REST_Posts_Controller, but a few for filtering responses could be useful. Since it's all read-only I don't think we need actions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We filter abilities only by category slug:
wordpress-develop/src/wp-includes/rest-api/endpoints/class-wp-rest-abilities-v1-list-controller.php
Lines 96 to 107 in c6c922c
For v1, this should be a good starting point.
In the WordPress 7.0 release cycle, we should expand support for other filtering options after we sort out:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I don't mean filtering via REST, I mean filtering via
apply_filters(). 😄Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I committed the changes as having the controller is the most important.
@JasonTheAdams, can you draft something explaining your proposal further? Do you want to filter the list by categories, individual items, or both? How about the endpoint with abilities? Should it have the same extensibility in place for consistency?