Ticket #40450: 40450.1.diff
File 40450.1.diff, 22.7 KB (added by , 8 years ago) |
---|
-
src/wp-includes/class-oembed.php
diff --git a/src/wp-includes/class-oembed.php b/src/wp-includes/class-oembed.php index 49a189f..0a64305 100644
a b public static function _remove_provider_early( $format ) { 317 317 } 318 318 319 319 /** 320 * Takes a URL and attempts to return the oEmbed data. 321 * 322 * @see WP_oEmbed::fetch() 323 * 324 * @since 4.8.0 325 * @access public 326 * 327 * @param string $url The URL to the content that should be attempted to be embedded. 328 * @param array|string $args Optional. Arguments, usually passed from a shortcode. Default empty. 329 * @return false|object False on failure, otherwise the result in the form of an object. 330 */ 331 public function get_data( $url, $args = '' ) { 332 $args = wp_parse_args( $args ); 333 334 $provider = $this->get_provider( $url, $args ); 335 336 if ( ! $provider ) { 337 return false; 338 } 339 340 $data = $this->fetch( $provider, $url, $args ); 341 342 if ( false === $data ) { 343 return false; 344 } 345 346 return $data; 347 } 348 349 /** 320 350 * The do-it-all function that takes a URL and attempts to return the HTML. 321 351 * 322 352 * @see WP_oEmbed::fetch() … … public static function _remove_provider_early( $format ) { 330 360 * @return false|string False on failure, otherwise the UNSANITIZED (and potentially unsafe) HTML that should be used to embed. 331 361 */ 332 362 public function get_html( $url, $args = '' ) { 333 $args = wp_parse_args( $args );334 335 363 /** 336 364 * Filters the oEmbed result before any HTTP requests are made. 337 365 * … … public function get_html( $url, $args = '' ) { 353 381 return $pre; 354 382 } 355 383 356 $ provider = $this->get_provider( $url, $args );384 $data = $this->get_data( $url, $args ); 357 385 358 if ( ! $provider || false === $data = $this->fetch( $provider, $url, $args )) {386 if ( false === $data ) { 359 387 return false; 360 388 } 361 389 -
src/wp-includes/class-wp-oembed-controller.php
diff --git a/src/wp-includes/class-wp-oembed-controller.php b/src/wp-includes/class-wp-oembed-controller.php index 13fed83..d825c41 100644
a b public function register_routes() { 52 52 ), 53 53 ), 54 54 ) ); 55 56 register_rest_route( 'oembed/1.0', '/proxy', array( 57 array( 58 'methods' => WP_REST_Server::READABLE, 59 'callback' => array( $this, 'get_proxy_item' ), 60 'permission_callback' => array( $this, 'get_proxy_item_permissions_check' ), 61 'args' => array( 62 'url' => array( 63 'description' => __( 'The URL of the resource for which to fetch oEmbed data.' ), 64 'type' => 'string', 65 'required' => true, 66 'sanitize_callback' => 'esc_url_raw', 67 ), 68 'format' => array( 69 'description' => __( 'The oEmbed format to use.' ), 70 'type' => 'string', 71 'default' => 'json', 72 'enum' => array( 73 'json', 74 'xml', 75 ), 76 ), 77 'maxwidth' => array( 78 'description' => __( 'The maximum width of the embed frame in pixels.' ), 79 'type' => 'integer', 80 'default' => $maxwidth, 81 'sanitize_callback' => 'absint', 82 ), 83 'maxheight' => array( 84 'description' => __( 'The maximum height of the embed frame in pixels.' ), 85 'type' => 'integer', 86 'sanitize_callback' => 'absint', 87 ), 88 'discover' => array( 89 'description' => __( 'Whether to perform an oEmbed discovery request for non-whitelisted providers.' ), 90 'type' => 'boolean', 91 'default' => true, 92 ), 93 ), 94 ), 95 ) ); 55 96 } 56 97 57 98 /** 58 * Callback for the API endpoint.99 * Callback for the embed API endpoint. 59 100 * 60 101 * Returns the JSON object for the post. 61 102 * … … public function get_item( $request ) { 86 127 87 128 return $data; 88 129 } 130 131 /** 132 * Checks if current user can make a proxy oEmbed request. 133 * 134 * @since 4.8.0 135 * @access public 136 * 137 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 138 */ 139 public function get_proxy_item_permissions_check() { 140 if ( ! current_user_can( 'edit_posts' ) ) { 141 return new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to make proxied oEmbed requests.' ), array( 'status' => rest_authorization_required_code() ) ); 142 } 143 return true; 144 } 145 146 /** 147 * Callback for the proxy API endpoint. 148 * 149 * Returns the JSON object for the proxied item. 150 * 151 * @since 4.8.0 152 * @access public 153 * 154 * @see WP_oEmbed::get_html() 155 * @param WP_REST_Request $request Full data about the request. 156 * @return WP_Error|array oEmbed response data or WP_Error on failure. 157 */ 158 public function get_proxy_item( $request ) { 159 $args = $request->get_params(); 160 161 // Serve oEmbed data from cache if set. 162 $cache_key = 'oembed_' . md5( serialize( $args ) ); 163 $data = get_transient( $cache_key ); 164 if ( ! empty( $data ) ) { 165 return $data; 166 } 167 168 $url = $request['url']; 169 unset( $args['url'] ); 170 171 $data = _wp_oembed_get_object()->get_data( $url, $args ); 172 173 if ( false === $data ) { 174 return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) ); 175 } 176 177 /** 178 * Filters the oEmbed TTL value (time to live). 179 * 180 * Similar to the {@see 'oembed_ttl'} filter, but for the REST API 181 * oEmbed proxy endpoint. 182 * 183 * @since 4.8.0 184 * 185 * @param int $time Time to live (in seconds). 186 * @param string $url The attempted embed URL. 187 * @param array $args An array of embed request arguments. 188 */ 189 $ttl = apply_filters( 'rest_oembed_ttl', DAY_IN_SECONDS, $url, $args ); 190 191 set_transient( $cache_key, $data, $ttl ); 192 193 return $data; 194 } 89 195 } -
src/wp-includes/js/media-views.js
diff --git a/src/wp-includes/js/media-views.js b/src/wp-includes/js/media-views.js index be802d7..6c1aa51 100644
a b EmbedLink = wp.media.view.Settings.extend({ 4624 4624 }, wp.media.controller.Embed.sensitivity ), 4625 4625 4626 4626 fetch: function() { 4627 var embed;4628 4627 4629 4628 // check if they haven't typed in 500 ms 4630 4629 if ( $('#embed-url-field').val() !== this.model.get('url') ) { … … EmbedLink = wp.media.view.Settings.extend({ 4635 4634 this.dfd.abort(); 4636 4635 } 4637 4636 4638 embed = new wp.shortcode({4639 tag: 'embed',4640 attrs: _.pick( this.model.attributes, [ 'width', 'height', 'src' ] ),4641 content: this.model.get('url')4642 });4643 4644 4637 this.dfd = $.ajax({ 4645 type: 'POST', 4646 url: wp.ajax.settings.url, 4647 context: this, 4648 data: { 4649 action: 'parse-embed', 4650 post_ID: wp.media.view.settings.post.id, 4651 shortcode: embed.string() 4652 } 4638 url: wp.media.view.settings.oEmbedProxyUrl, 4639 data: { 4640 url: this.model.get( 'url' ), 4641 maxwidth: this.model.get( 'width' ), 4642 maxheight: this.model.get( 'height' ), 4643 _wpnonce: wp.media.view.settings.nonce.wpRestApi 4644 }, 4645 type: 'GET', 4646 dataType: 'json', 4647 context: this 4653 4648 }) 4654 .done( this.renderoEmbed ) 4649 .done( function( response ) { 4650 this.renderoEmbed( { 4651 data: { 4652 body: response.html || '' 4653 } 4654 } ); 4655 } ) 4655 4656 .fail( this.renderFail ); 4656 4657 }, 4657 4658 -
src/wp-includes/js/media/views/embed/link.js
diff --git a/src/wp-includes/js/media/views/embed/link.js b/src/wp-includes/js/media/views/embed/link.js index 86aa5c8..1af96cf 100644
a b EmbedLink = wp.media.view.Settings.extend({ 35 35 }, wp.media.controller.Embed.sensitivity ), 36 36 37 37 fetch: function() { 38 var embed;39 38 40 39 // check if they haven't typed in 500 ms 41 40 if ( $('#embed-url-field').val() !== this.model.get('url') ) { … … EmbedLink = wp.media.view.Settings.extend({ 46 45 this.dfd.abort(); 47 46 } 48 47 49 embed = new wp.shortcode({50 tag: 'embed',51 attrs: _.pick( this.model.attributes, [ 'width', 'height', 'src' ] ),52 content: this.model.get('url')53 });54 55 48 this.dfd = $.ajax({ 56 type: 'POST', 57 url: wp.ajax.settings.url, 58 context: this, 59 data: { 60 action: 'parse-embed', 61 post_ID: wp.media.view.settings.post.id, 62 shortcode: embed.string() 63 } 49 url: wp.media.view.settings.oEmbedProxyUrl, 50 data: { 51 url: this.model.get( 'url' ), 52 maxwidth: this.model.get( 'width' ), 53 maxheight: this.model.get( 'height' ), 54 _wpnonce: wp.media.view.settings.nonce.wpRestApi 55 }, 56 type: 'GET', 57 dataType: 'json', 58 context: this 64 59 }) 65 .done( this.renderoEmbed ) 60 .done( function( response ) { 61 this.renderoEmbed( { 62 data: { 63 body: response.html || '' 64 } 65 } ); 66 } ) 66 67 .fail( this.renderFail ); 67 68 }, 68 69 -
src/wp-includes/media.php
diff --git a/src/wp-includes/media.php b/src/wp-includes/media.php index 6253bd5..76aaa1c 100644
a b function wp_enqueue_media( $args = array() ) { 3414 3414 'captions' => ! apply_filters( 'disable_captions', '' ), 3415 3415 'nonce' => array( 3416 3416 'sendToEditor' => wp_create_nonce( 'media-send-to-editor' ), 3417 'wpRestApi' => wp_create_nonce( 'wp_rest' ), 3417 3418 ), 3418 3419 'post' => array( 3419 3420 'id' => 0, … … function wp_enqueue_media( $args = array() ) { 3423 3424 'audio' => ( $show_audio_playlist ) ? 1 : 0, 3424 3425 'video' => ( $show_video_playlist ) ? 1 : 0, 3425 3426 ), 3427 'oEmbedProxyUrl' => rest_url( 'oembed/1.0/proxy' ), 3426 3428 'embedExts' => $exts, 3427 3429 'embedMimes' => $ext_mimes, 3428 3430 'contentWidth' => $content_width, -
tests/phpunit/tests/oembed/controller.php
diff --git a/tests/phpunit/tests/oembed/controller.php b/tests/phpunit/tests/oembed/controller.php index 3c9d051..db88a65 100644
a b class Test_oEmbed_Controller extends WP_UnitTestCase { 9 9 * @var WP_REST_Server 10 10 */ 11 11 protected $server; 12 protected static $editor; 13 protected static $subscriber; 14 const YOUTUBE_VIDEO_ID = 'OQSNhk5ICTI'; 15 const INVALID_OEMBED_URL = 'https://www.notreallyanoembedprovider.com/watch?v=awesome-cat-video'; 16 17 public static function wpSetUpBeforeClass( $factory ) { 18 self::$subscriber = $factory->user->create( array( 19 'role' => 'subscriber', 20 ) ); 21 self::$editor = $factory->user->create( array( 22 'role' => 'editor', 23 'user_email' => 'editor@example.com', 24 ) ); 25 } 26 27 public static function wpTearDownAfterClass() { 28 self::delete_user( self::$subscriber ); 29 self::delete_user( self::$editor ); 30 } 12 31 13 32 public function setUp() { 14 33 parent::setUp(); … … public function setUp() { 18 37 $this->server = $wp_rest_server = new Spy_REST_Server(); 19 38 20 39 do_action( 'rest_api_init', $this->server ); 40 add_filter( 'pre_http_request', array( $this, 'mock_embed_request' ), 10, 3 ); 41 $this->request_count = 0; 42 } 43 44 public function tearDown() { 45 parent::tearDown(); 46 47 remove_filter( 'pre_http_request', array( $this, 'mock_embed_request' ), 10, 3 ); 48 } 49 50 /** 51 * Count of the number of requests attempted. 52 * 53 * @var int 54 */ 55 public $request_count = 0; 56 57 /** 58 * Intercept oEmbed requests and mock responses. 59 * 60 * @param mixed $preempt Whether to preempt an HTTP request's return value. Default false. 61 * @param mixed $r HTTP request arguments. 62 * @param string $url The request URL. 63 * @return array Response data. 64 */ 65 public function mock_embed_request( $preempt, $r, $url ) { 66 unset( $preempt, $r ); 67 68 $this->request_count += 1; 69 70 // Mock request to YouTube Embed. 71 if ( false !== strpos( $url, self::YOUTUBE_VIDEO_ID ) ) { 72 return array( 73 'response' => array( 74 'code' => 200, 75 ), 76 'body' => wp_json_encode( 77 array( 78 'version' => '1.0', 79 'type' => 'video', 80 'provider_name' => 'YouTube', 81 'provider_url' => 'https://www.youtube.com', 82 'thumbnail_width' => 480, 83 'width' => 500, 84 'thumbnail_height' => 360, 85 'html' => '<iframe width="500" height="375" src="https://www.youtube.com/embed/' . self::YOUTUBE_VIDEO_ID . '?feature=oembed" frameborder="0" allowfullscreen></iframe>', 86 'author_name' => 'Yosemitebear62', 87 'thumbnail_url' => 'https://i.ytimg.com/vi/' . self::YOUTUBE_VIDEO_ID . '/hqdefault.jpg', 88 'title' => 'Yosemitebear Mountain Double Rainbow 1-8-10', 89 'height' => 375, 90 ) 91 ), 92 ); 93 } else { 94 return array( 95 'response' => array( 96 'code' => 404, 97 ), 98 ); 99 } 21 100 } 22 101 23 102 function test_wp_oembed_ensure_format() { … … public function test_route_availability() { 86 165 $this->assertArrayHasKey( 'callback', $route[0] ); 87 166 $this->assertArrayHasKey( 'methods', $route[0] ); 88 167 $this->assertArrayHasKey( 'args', $route[0] ); 168 169 // Check proxy route registration. 170 $this->assertArrayHasKey( '/oembed/1.0/proxy', $filtered_routes ); 171 $proxy_route = $filtered_routes['/oembed/1.0/proxy']; 172 $this->assertCount( 1, $proxy_route ); 173 $this->assertArrayHasKey( 'callback', $proxy_route[0] ); 174 $this->assertArrayHasKey( 'permission_callback', $proxy_route[0] ); 175 $this->assertArrayHasKey( 'methods', $proxy_route[0] ); 176 $this->assertArrayHasKey( 'args', $proxy_route[0] ); 89 177 } 90 178 91 179 function test_request_with_wrong_method() { … … function test_get_oembed_endpoint_url_pretty_permalinks() { 351 439 352 440 update_option( 'permalink_structure', '' ); 353 441 } 442 443 public function test_proxy_without_permission() { 444 // Test without a login. 445 $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' ); 446 $response = $this->server->dispatch( $request ); 447 448 $this->assertEquals( 400, $response->get_status() ); 449 450 // Test with a user that does not have edit_posts capability. 451 wp_set_current_user( self::$subscriber ); 452 $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' ); 453 $request->set_param( 'url', self::INVALID_OEMBED_URL ); 454 $response = $this->server->dispatch( $request ); 455 456 $this->assertEquals( 403, $response->get_status() ); 457 $data = $response->get_data(); 458 $this->assertEquals( $data['code'], 'rest_forbidden' ); 459 } 460 461 public function test_proxy_with_invalid_oembed_provider() { 462 wp_set_current_user( self::$editor ); 463 $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' ); 464 $request->set_param( 'url', self::INVALID_OEMBED_URL ); 465 $response = $this->server->dispatch( $request ); 466 $this->assertEquals( 404, $response->get_status() ); 467 $data = $response->get_data(); 468 $this->assertEquals( 'oembed_invalid_url', $data['code'] ); 469 } 470 471 public function test_proxy_with_invalid_type() { 472 wp_set_current_user( self::$editor ); 473 $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' ); 474 $request->set_param( 'type', 'xml' ); 475 $response = $this->server->dispatch( $request ); 476 477 $this->assertEquals( 400, $response->get_status() ); 478 $data = $response->get_data(); 479 } 480 481 public function test_proxy_with_valid_oembed_provider() { 482 wp_set_current_user( self::$editor ); 483 484 $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' ); 485 $request->set_param( 'url', 'https://www.youtube.com/watch?v=' . self::YOUTUBE_VIDEO_ID ); 486 $response = $this->server->dispatch( $request ); 487 $this->assertEquals( 200, $response->get_status() ); 488 $this->assertEquals( 1, $this->request_count ); 489 490 // Subsequent request is cached and so it should not cause a request. 491 $response = $this->server->dispatch( $request ); 492 $this->assertEquals( 1, $this->request_count ); 493 494 // Test data object. 495 $data = $response->get_data(); 496 497 $this->assertNotEmpty( $data ); 498 $this->assertTrue( is_object( $data ) ); 499 $this->assertEquals( 'YouTube', $data->provider_name ); 500 $this->assertEquals( 'https://i.ytimg.com/vi/' . self::YOUTUBE_VIDEO_ID . '/hqdefault.jpg', $data->thumbnail_url ); 501 } 502 503 public function test_proxy_with_invalid_oembed_provider_no_discovery() { 504 wp_set_current_user( self::$editor ); 505 506 // If discover is false for an unkown provider, no discovery request should take place. 507 $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' ); 508 $request->set_param( 'url', self::INVALID_OEMBED_URL ); 509 $request->set_param( 'discover', 0 ); 510 $response = $this->server->dispatch( $request ); 511 $this->assertEquals( 404, $response->get_status() ); 512 $this->assertEquals( 0, $this->request_count ); 513 } 514 515 public function test_proxy_with_invalid_oembed_provider_with_default_discover_param() { 516 wp_set_current_user( self::$editor ); 517 518 // For an unkown provider, a discovery request should happen. 519 $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' ); 520 $request->set_param( 'url', self::INVALID_OEMBED_URL ); 521 $response = $this->server->dispatch( $request ); 522 $this->assertEquals( 404, $response->get_status() ); 523 $this->assertEquals( 1, $this->request_count ); 524 } 525 526 public function test_proxy_with_invalid_discover_param() { 527 wp_set_current_user( self::$editor ); 528 $request = new WP_REST_Request( 'GET', '/oembed/1.0/proxy' ); 529 $request->set_param( 'url', self::INVALID_OEMBED_URL ); 530 $request->set_param( 'discover', 'notaboolean' ); 531 532 $response = $this->server->dispatch( $request ); 533 534 $this->assertEquals( 400, $response->get_status() ); 535 $data = $response->get_data(); 536 $this->assertEquals( $data['code'], 'rest_invalid_param' ); 537 } 354 538 } -
tests/phpunit/tests/rest-api/rest-schema-setup.php
diff --git a/tests/phpunit/tests/rest-api/rest-schema-setup.php b/tests/phpunit/tests/rest-api/rest-schema-setup.php index 360a075..0c86255 100644
a b public function test_expected_routes_in_schema() { 43 43 '/', 44 44 '/oembed/1.0', 45 45 '/oembed/1.0/embed', 46 '/oembed/1.0/proxy', 46 47 '/wp/v2', 47 48 '/wp/v2/posts', 48 49 '/wp/v2/posts/(?P<id>[\\d]+)', … … public function test_build_wp_api_client_fixtures() { 166 167 'name' => 'oembeds', 167 168 ), 168 169 array( 170 'route' => '/oembed/1.0/proxy', 171 'name' => 'oembedProxy', 172 ), 173 array( 169 174 'route' => '/wp/v2/posts', 170 175 'name' => 'PostsCollection', 171 176 ), -
tests/qunit/fixtures/wp-api-generated.js
diff --git a/tests/qunit/fixtures/wp-api-generated.js b/tests/qunit/fixtures/wp-api-generated.js index 0df5b9e..f6a5bfe 100644
a b mockedApiResponse.Schema = { 95 95 "self": "http://example.org/?rest_route=/oembed/1.0/embed" 96 96 } 97 97 }, 98 "/oembed/1.0/proxy": { 99 "namespace": "oembed/1.0", 100 "methods": [ 101 "GET" 102 ], 103 "endpoints": [ 104 { 105 "methods": [ 106 "GET" 107 ], 108 "args": { 109 "url": { 110 "required": true, 111 "description": "The URL of the resource for which to fetch oEmbed data.", 112 "type": "string" 113 }, 114 "format": { 115 "required": false, 116 "default": "json", 117 "enum": [ 118 "json", 119 "xml" 120 ], 121 "description": "The oEmbed format to use.", 122 "type": "string" 123 }, 124 "maxwidth": { 125 "required": false, 126 "default": 600, 127 "description": "The maximum width of the embed frame in pixels.", 128 "type": "integer" 129 }, 130 "maxheight": { 131 "required": false, 132 "description": "The maximum height of the embed frame in pixels.", 133 "type": "integer" 134 }, 135 "discover": { 136 "required": false, 137 "default": true, 138 "description": "Whether to perform an oEmbed discovery request for non-whitelisted providers.", 139 "type": "boolean" 140 } 141 } 142 } 143 ], 144 "_links": { 145 "self": "http://example.org/?rest_route=/oembed/1.0/proxy" 146 } 147 }, 98 148 "/wp/v2": { 99 149 "namespace": "wp/v2", 100 150 "methods": [ … … mockedApiResponse.oembed = { 3387 3437 "_links": { 3388 3438 "self": "http://example.org/?rest_route=/oembed/1.0/embed" 3389 3439 } 3440 }, 3441 "/oembed/1.0/proxy": { 3442 "namespace": "oembed/1.0", 3443 "methods": [ 3444 "GET" 3445 ], 3446 "endpoints": [ 3447 { 3448 "methods": [ 3449 "GET" 3450 ], 3451 "args": { 3452 "url": { 3453 "required": true, 3454 "description": "The URL of the resource for which to fetch oEmbed data.", 3455 "type": "string" 3456 }, 3457 "format": { 3458 "required": false, 3459 "default": "json", 3460 "enum": [ 3461 "json", 3462 "xml" 3463 ], 3464 "description": "The oEmbed format to use.", 3465 "type": "string" 3466 }, 3467 "maxwidth": { 3468 "required": false, 3469 "default": 600, 3470 "description": "The maximum width of the embed frame in pixels.", 3471 "type": "integer" 3472 }, 3473 "maxheight": { 3474 "required": false, 3475 "description": "The maximum height of the embed frame in pixels.", 3476 "type": "integer" 3477 }, 3478 "discover": { 3479 "required": false, 3480 "default": true, 3481 "description": "Whether to perform an oEmbed discovery request for non-whitelisted providers.", 3482 "type": "boolean" 3483 } 3484 } 3485 } 3486 ], 3487 "_links": { 3488 "self": "http://example.org/?rest_route=/oembed/1.0/proxy" 3489 } 3390 3490 } 3391 3491 } 3392 3492 }; … … mockedApiResponse.oembeds = { 3402 3502 } 3403 3503 }; 3404 3504 3505 mockedApiResponse.oembedProxy = { 3506 "code": "rest_missing_callback_param", 3507 "message": "Missing parameter(s): url", 3508 "data": { 3509 "status": 400, 3510 "params": [ 3511 "url" 3512 ] 3513 } 3514 }; 3515 3405 3516 mockedApiResponse.PostsCollection = [ 3406 3517 { 3407 3518 "id": 3,