diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php index 305647451b02c..112b1f3ae3b23 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-blocks-controller.php @@ -97,4 +97,60 @@ public function get_item_schema() { return $this->add_additional_fields_schema( $this->schema ); } + + /** + * Gets the link relations available for the post and current user. + * + * @since 6.4.0 Ensures that only users with `edit_terms` capability can add `wp_pattern_category` + * taxonomy terms. + * + * @param WP_Post $post Post object. + * @param WP_REST_Request $request Request object. + * @return array List of link relations. + */ + protected function get_available_actions( $post, $request ) { + if ( 'edit' !== $request['context'] ) { + return array(); + } + + $rels = array(); + + $post_type = get_post_type_object( $post->post_type ); + + if ( 'attachment' !== $this->post_type && current_user_can( $post_type->cap->publish_posts ) ) { + $rels[] = 'https://api.w.org/action-publish'; + } + + if ( current_user_can( 'unfiltered_html' ) ) { + $rels[] = 'https://api.w.org/action-unfiltered-html'; + } + + if ( 'post' === $post_type->name ) { + if ( current_user_can( $post_type->cap->edit_others_posts ) && current_user_can( $post_type->cap->publish_posts ) ) { + $rels[] = 'https://api.w.org/action-sticky'; + } + } + + if ( post_type_supports( $post_type->name, 'author' ) ) { + if ( current_user_can( $post_type->cap->edit_others_posts ) ) { + $rels[] = 'https://api.w.org/action-assign-author'; + } + } + + $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); + + foreach ( $taxonomies as $tax ) { + $tax_base = ! empty( $tax->rest_base ) ? $tax->rest_base : $tax->name; + + if ( current_user_can( $tax->cap->edit_terms ) ) { + $rels[] = 'https://api.w.org/action-create-' . $tax_base; + } + + if ( current_user_can( $tax->cap->assign_terms ) ) { + $rels[] = 'https://api.w.org/action-assign-' . $tax_base; + } + } + + return $rels; + } } diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-categories-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-categories-controller.php new file mode 100644 index 0000000000000..4069b791904c6 --- /dev/null +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-pattern-categories-controller.php @@ -0,0 +1,45 @@ +check_is_taxonomy_allowed( $this->taxonomy ) ) { + return false; + } + + $taxonomy_obj = get_taxonomy( $this->taxonomy ); + + // Patterns categories are a flat hierarchy (like tags), but work more like post categories in terms of permissions. + if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) { + return new WP_Error( + 'rest_cannot_create', + __( 'Sorry, you are not allowed to create terms in this taxonomy.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } +} diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index f0e3be17d34c2..cc6c69da47d06 100644 --- a/src/wp-includes/taxonomy.php +++ b/src/wp-includes/taxonomy.php @@ -227,20 +227,21 @@ function create_initial_taxonomies() { 'wp_pattern_category', array( 'wp_block' ), array( - 'public' => true, - 'publicly_queryable' => false, - 'hierarchical' => false, - 'labels' => array( + 'public' => true, + 'publicly_queryable' => false, + 'hierarchical' => false, + 'labels' => array( 'name' => _x( 'Pattern Categories', 'taxonomy general name' ), 'singular_name' => _x( 'Pattern Category', 'taxonomy singular name' ), ), - 'query_var' => false, - 'rewrite' => false, - 'show_ui' => true, - '_builtin' => true, - 'show_in_nav_menus' => false, - 'show_in_rest' => true, - 'show_admin_column' => true, + 'query_var' => false, + 'rewrite' => false, + 'show_ui' => true, + '_builtin' => true, + 'show_in_nav_menus' => false, + 'show_in_rest' => true, + 'show_admin_column' => true, + 'rest_controller_class' => 'WP_REST_Pattern_Categories_Controller', ) ); } diff --git a/src/wp-settings.php b/src/wp-settings.php index 38b03ecf7268f..cdff607966142 100644 --- a/src/wp-settings.php +++ b/src/wp-settings.php @@ -303,6 +303,7 @@ require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-templates-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-url-details-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-navigation-fallback-controller.php'; +require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-pattern-categories-controller.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php'; diff --git a/tests/phpunit/tests/rest-api/rest-categories-controller.php b/tests/phpunit/tests/rest-api/rest-categories-controller.php index 9da866a87bc63..749f8d45b8023 100644 --- a/tests/phpunit/tests/rest-api/rest-categories-controller.php +++ b/tests/phpunit/tests/rest-api/rest-categories-controller.php @@ -12,6 +12,7 @@ class WP_Test_REST_Categories_Controller extends WP_Test_REST_Controller_Testcas protected static $administrator; protected static $contributor; protected static $subscriber; + protected static $author; protected static $category_ids = array(); protected static $total_categories = 30; @@ -33,6 +34,11 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { 'role' => 'subscriber', ) ); + self::$author = $factory->user->create( + array( + 'role' => 'author', + ) + ); // Set up categories for pagination tests. for ( $i = 0; $i < self::$total_categories - 1; $i++ ) { @@ -47,6 +53,7 @@ public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { public static function wpTearDownAfterClass() { self::delete_user( self::$administrator ); self::delete_user( self::$subscriber ); + self::delete_user( self::$author ); // Remove categories for pagination tests. foreach ( self::$category_ids as $category_id ) { @@ -1178,6 +1185,18 @@ public function test_get_additional_field_registration() { $wp_rest_additional_fields = array(); } + /** + * @ticket 59660 + */ + public function test_create_item_pattern_category_incorrect_permissions_author() { + wp_set_current_user( self::$author ); + + $request = new WP_REST_Request( 'POST', '/wp/v2/wp_pattern_category' ); + $request->set_param( 'name', 'Incorrect permissions' ); + $response = rest_get_server()->dispatch( $request ); + $this->assertErrorResponse( 'rest_cannot_create', $response, 403 ); + } + public function additional_field_get_callback( $response_data, $field_name ) { return 123; }