diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 875b0518eb..93b6608e7a 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -1587,6 +1587,23 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { } } + $post_type_obj = get_post_type_object( $post->post_type ); + if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { + + if ( ! function_exists( 'get_sample_permalink' ) ) { + require_once ABSPATH . '/wp-admin/includes/post.php'; + } + + $sample_permalink = get_sample_permalink( $post->ID, $post->post_title, '' ); + + if ( in_array( 'permalink_template', $fields, true ) ) { + $data['permalink_template'] = $sample_permalink[0]; + } + if ( in_array( 'generated_slug', $fields, true ) ) { + $data['generated_slug'] = $sample_permalink[1]; + } + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -1918,6 +1935,21 @@ class WP_REST_Posts_Controller extends WP_REST_Controller { ); $post_type_obj = get_post_type_object( $this->post_type ); + if ( is_post_type_viewable( $post_type_obj ) && $post_type_obj->public ) { + $schema['properties']['permalink_template'] = array( + 'description' => __( 'Permalink template for the object.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + 'readonly' => true, + ); + + $schema['properties']['generated_slug'] = array( + 'description' => __( 'Slug automatically generated from the object title.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + 'readonly' => true, + ); + } if ( $post_type_obj->hierarchical ) { $schema['properties']['parent'] = array( diff --git a/tests/phpunit/includes/testcase-rest-controller.php b/tests/phpunit/includes/testcase-rest-controller.php index bafd1f8508..bc7d409368 100644 --- a/tests/phpunit/includes/testcase-rest-controller.php +++ b/tests/phpunit/includes/testcase-rest-controller.php @@ -40,7 +40,7 @@ abstract class WP_Test_REST_Controller_Testcase extends WP_Test_REST_TestCase { abstract public function test_get_item_schema(); public function filter_rest_url_for_leading_slash( $url, $path ) { - if ( is_multisite() ) { + if ( is_multisite() || get_option( 'permalink_structure' ) ) { return $url; } diff --git a/tests/phpunit/tests/rest-api/rest-attachments-controller.php b/tests/phpunit/tests/rest-api/rest-attachments-controller.php index fbbc0517f6..465b58bf93 100644 --- a/tests/phpunit/tests/rest-api/rest-attachments-controller.php +++ b/tests/phpunit/tests/rest-api/rest-attachments-controller.php @@ -1295,7 +1295,7 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 24, count( $properties ) ); + $this->assertEquals( 26, count( $properties ) ); $this->assertArrayHasKey( 'author', $properties ); $this->assertArrayHasKey( 'alt_text', $properties ); $this->assertArrayHasKey( 'caption', $properties ); @@ -1307,6 +1307,7 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control $this->assertArrayHasKey( 'comment_status', $properties ); $this->assertArrayHasKey( 'date', $properties ); $this->assertArrayHasKey( 'date_gmt', $properties ); + $this->assertArrayHasKey( 'generated_slug', $properties ); $this->assertArrayHasKey( 'guid', $properties ); $this->assertArrayHasKey( 'id', $properties ); $this->assertArrayHasKey( 'link', $properties ); @@ -1318,6 +1319,7 @@ class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Control $this->assertArrayHasKey( 'modified_gmt', $properties ); $this->assertArrayHasKey( 'post', $properties ); $this->assertArrayHasKey( 'ping_status', $properties ); + $this->assertArrayHasKey( 'permalink_template', $properties ); $this->assertArrayHasKey( 'status', $properties ); $this->assertArrayHasKey( 'slug', $properties ); $this->assertArrayHasKey( 'source_url', $properties ); diff --git a/tests/phpunit/tests/rest-api/rest-pages-controller.php b/tests/phpunit/tests/rest-api/rest-pages-controller.php index 98033dc8a1..79da6b05e8 100644 --- a/tests/phpunit/tests/rest-api/rest-pages-controller.php +++ b/tests/phpunit/tests/rest-api/rest-pages-controller.php @@ -678,12 +678,13 @@ class WP_Test_REST_Pages_Controller extends WP_Test_REST_Post_Type_Controller_Te $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 22, count( $properties ) ); + $this->assertEquals( 24, count( $properties ) ); $this->assertArrayHasKey( 'author', $properties ); $this->assertArrayHasKey( 'comment_status', $properties ); $this->assertArrayHasKey( 'content', $properties ); $this->assertArrayHasKey( 'date', $properties ); $this->assertArrayHasKey( 'date_gmt', $properties ); + $this->assertArrayHasKey( 'generated_slug', $properties ); $this->assertArrayHasKey( 'guid', $properties ); $this->assertArrayHasKey( 'excerpt', $properties ); $this->assertArrayHasKey( 'featured_media', $properties ); @@ -695,6 +696,7 @@ class WP_Test_REST_Pages_Controller extends WP_Test_REST_Post_Type_Controller_Te $this->assertArrayHasKey( 'modified_gmt', $properties ); $this->assertArrayHasKey( 'parent', $properties ); $this->assertArrayHasKey( 'password', $properties ); + $this->assertArrayHasKey( 'permalink_template', $properties ); $this->assertArrayHasKey( 'ping_status', $properties ); $this->assertArrayHasKey( 'slug', $properties ); $this->assertArrayHasKey( 'status', $properties ); diff --git a/tests/phpunit/tests/rest-api/rest-posts-controller.php b/tests/phpunit/tests/rest-api/rest-posts-controller.php index 9a7790e11b..eef8bc85dc 100644 --- a/tests/phpunit/tests/rest-api/rest-posts-controller.php +++ b/tests/phpunit/tests/rest-api/rest-posts-controller.php @@ -3479,7 +3479,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te $response = rest_get_server()->dispatch( $request ); $data = $response->get_data(); $properties = $data['schema']['properties']; - $this->assertEquals( 24, count( $properties ) ); + $this->assertEquals( 26, count( $properties ) ); $this->assertArrayHasKey( 'author', $properties ); $this->assertArrayHasKey( 'comment_status', $properties ); $this->assertArrayHasKey( 'content', $properties ); @@ -3487,6 +3487,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te $this->assertArrayHasKey( 'date_gmt', $properties ); $this->assertArrayHasKey( 'excerpt', $properties ); $this->assertArrayHasKey( 'featured_media', $properties ); + $this->assertArrayHasKey( 'generated_slug', $properties ); $this->assertArrayHasKey( 'guid', $properties ); $this->assertArrayHasKey( 'format', $properties ); $this->assertArrayHasKey( 'id', $properties ); @@ -3495,6 +3496,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te $this->assertArrayHasKey( 'modified', $properties ); $this->assertArrayHasKey( 'modified_gmt', $properties ); $this->assertArrayHasKey( 'password', $properties ); + $this->assertArrayHasKey( 'permalink_template', $properties ); $this->assertArrayHasKey( 'ping_status', $properties ); $this->assertArrayHasKey( 'slug', $properties ); $this->assertArrayHasKey( 'status', $properties ); @@ -3564,6 +3566,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te 'excerpt', 'featured_media', 'format', + 'generated_slug', 'guid', 'id', 'link', @@ -3571,6 +3574,7 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te 'modified', 'modified_gmt', 'password', + 'permalink_template', 'ping_status', 'slug', 'status', @@ -4086,7 +4090,76 @@ class WP_Test_REST_Posts_Controller extends WP_Test_REST_Post_Type_Controller_Te $this->assertArrayNotHasKey( 'https://api.w.org/action-unfiltered-html', $links ); } + public function test_generated_permalink_template_generated_slug_for_non_viewable_posts() { + register_post_type( + 'private-post', + array( + 'label' => 'Private Posts', + 'supports' => array( 'title', 'editor', 'author' ), + 'show_in_rest' => true, + 'publicly_queryable' => false, + 'public' => true, + 'rest_base' => 'private-post', + ) + ); + create_initial_rest_routes(); + + wp_set_current_user( self::$editor_id ); + + $post_id = $this->factory->post->create( + array( + 'post_title' => 'Permalink Template', + 'post_type' => 'private-post', + 'post_status' => 'draft', + ) + ); + + // Neither 'permalink_template' and 'generated_slug' are expected for this post type. + $request = new WP_REST_Request( 'GET', '/wp/v2/private-post/' . $post_id ); + $request->set_param( 'context', 'edit' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertArrayNotHasKey( 'permalink_template', $data ); + $this->assertArrayNotHasKey( 'generated_slug', $data ); + } + + public function test_generated_permalink_template_generated_slug_for_posts() { + $this->set_permalink_structure( '/%postname%/' ); + $expected_permalink_template = trailingslashit( home_url( '/%postname%/' ) ); + + wp_set_current_user( self::$editor_id ); + + $post_id = $this->factory->post->create( + array( + 'post_title' => 'Permalink Template', + 'post_type' => 'post', + 'post_status' => 'draft', + ) + ); + + // Both 'permalink_template' and 'generated_slug' are expected for context=edit. + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); + $request->set_param( 'context', 'edit' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( $expected_permalink_template, $data['permalink_template'] ); + $this->assertEquals( 'permalink-template', $data['generated_slug'] ); + + // Neither 'permalink_template' and 'generated_slug' are expected for context=view. + $request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $post_id ); + $request->set_param( 'context', 'view' ); + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertArrayNotHasKey( 'permalink_template', $data ); + $this->assertArrayNotHasKey( 'generated_slug', $data ); + + } + public function tearDown() { + _unregister_post_type( 'private-post' ); _unregister_post_type( 'youseeeme' ); if ( isset( $this->attachment_id ) ) { $this->remove_added_uploads();