1<?php
2/**
3 * WP_oEmbed_Controller class, used to provide an oEmbed endpoint.
4 *
5 * @package WordPress
6 * @subpackage Embeds
7 * @since 4.4.0
8 */
9
10/**
11 * oEmbed API endpoint controller.
12 *
13 * Registers the REST API route and delivers the response data.
14 * The output format (XML or JSON) is handled by the REST API.
15 *
16 * @since 4.4.0
17 */
18#[AllowDynamicProperties]
19final class WP_oEmbed_Controller {
20 /**
21 * Register the oEmbed REST API route.
22 *
23 * @since 4.4.0
24 */
25 public function register_routes() {
26 /**
27 * Filters the maxwidth oEmbed parameter.
28 *
29 * @since 4.4.0
30 *
31 * @param int $maxwidth Maximum allowed width. Default 600.
32 */
33 $maxwidth = apply_filters( 'oembed_default_width', 600 );
34
35 register_rest_route(
36 'oembed/1.0',
37 '/embed',
38 array(
39 array(
40 'methods' => WP_REST_Server::READABLE,
41 'callback' => array( $this, 'get_item' ),
42 'permission_callback' => '__return_true',
43 'args' => array(
44 'url' => array(
45 'description' => __( 'The URL of the resource for which to fetch oEmbed data.' ),
46 'required' => true,
47 'type' => 'string',
48 'format' => 'uri',
49 ),
50 'format' => array(
51 'default' => 'json',
52 'sanitize_callback' => 'wp_oembed_ensure_format',
53 ),
54 'maxwidth' => array(
55 'default' => $maxwidth,
56 'sanitize_callback' => 'absint',
57 ),
58 ),
59 ),
60 )
61 );
62
63 register_rest_route(
64 'oembed/1.0',
65 '/proxy',
66 array(
67 array(
68 'methods' => WP_REST_Server::READABLE,
69 'callback' => array( $this, 'get_proxy_item' ),
70 'permission_callback' => array( $this, 'get_proxy_item_permissions_check' ),
71 'args' => array(
72 'url' => array(
73 'description' => __( 'The URL of the resource for which to fetch oEmbed data.' ),
74 'required' => true,
75 'type' => 'string',
76 'format' => 'uri',
77 ),
78 'format' => array(
79 'description' => __( 'The oEmbed format to use.' ),
80 'type' => 'string',
81 'default' => 'json',
82 'enum' => array(
83 'json',
84 'xml',
85 ),
86 ),
87 'maxwidth' => array(
88 'description' => __( 'The maximum width of the embed frame in pixels.' ),
89 'type' => 'integer',
90 'default' => $maxwidth,
91 'sanitize_callback' => 'absint',
92 ),
93 'maxheight' => array(
94 'description' => __( 'The maximum height of the embed frame in pixels.' ),
95 'type' => 'integer',
96 'sanitize_callback' => 'absint',
97 ),
98 'discover' => array(
99 'description' => __( 'Whether to perform an oEmbed discovery request for unsanctioned providers.' ),
100 'type' => 'boolean',
101 'default' => true,
102 ),
103 ),
104 ),
105 )
106 );
107 }
108
109 /**
110 * Callback for the embed API endpoint.
111 *
112 * Returns the JSON object for the post.
113 *
114 * @since 4.4.0
115 *
116 * @param WP_REST_Request $request Full data about the request.
117 * @return array|WP_Error oEmbed response data or WP_Error on failure.
118 */
119 public function get_item( $request ) {
120 $post_id = url_to_postid( $request['url'] );
121
122 /**
123 * Filters the determined post ID.
124 *
125 * @since 4.4.0
126 *
127 * @param int $post_id The post ID.
128 * @param string $url The requested URL.
129 */
130 $post_id = apply_filters( 'oembed_request_post_id', $post_id, $request['url'] );
131
132 $data = get_oembed_response_data( $post_id, $request['maxwidth'] );
133
134 if ( ! $data ) {
135 return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) );
136 }
137
138 return $data;
139 }
140
141 /**
142 * Checks if current user can make a proxy oEmbed request.
143 *
144 * @since 4.8.0
145 *
146 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
147 */
148 public function get_proxy_item_permissions_check() {
149 if ( ! current_user_can( 'edit_posts' ) ) {
150 return new WP_Error( 'rest_forbidden', __( 'Sorry, you are not allowed to make proxied oEmbed requests.' ), array( 'status' => rest_authorization_required_code() ) );
151 }
152 return true;
153 }
154
155 /**
156 * Callback for the proxy API endpoint.
157 *
158 * Returns the JSON object for the proxied item.
159 *
160 * @since 4.8.0
161 *
162 * @see WP_oEmbed::get_html()
163 * @global WP_Embed $wp_embed WordPress Embed object.
164 * @global WP_Scripts $wp_scripts
165 *
166 * @param WP_REST_Request $request Full data about the request.
167 * @return object|WP_Error oEmbed response data or WP_Error on failure.
168 */
169 public function get_proxy_item( $request ) {
170 global $wp_embed, $wp_scripts;
171
172 $args = $request->get_params();
173
174 // Serve oEmbed data from cache if set.
175 unset( $args['_wpnonce'] );
176 $cache_key = 'oembed_' . md5( serialize( $args ) );
177 $data = get_transient( $cache_key );
178 if ( ! empty( $data ) ) {
179 return $data;
180 }
181
182 $url = $request['url'];
183 unset( $args['url'] );
184
185 // Copy maxwidth/maxheight to width/height since WP_oEmbed::fetch() uses these arg names.
186 if ( isset( $args['maxwidth'] ) ) {
187 $args['width'] = $args['maxwidth'];
188 }
189 if ( isset( $args['maxheight'] ) ) {
190 $args['height'] = $args['maxheight'];
191 }
192
193 // Short-circuit process for URLs belonging to the current site.
194 $data = get_oembed_response_data_for_url( $url, $args );
195
196 if ( $data ) {
197 return $data;
198 }
199
200 $data = _wp_oembed_get_object()->get_data( $url, $args );
201
202 if ( false === $data ) {
203 // Try using a classic embed, instead.
204 /* @var WP_Embed $wp_embed */
205 $html = $wp_embed->get_embed_handler_html( $args, $url );
206
207 if ( $html ) {
208 // Check if any scripts were enqueued by the shortcode, and include them in the response.
209 $enqueued_scripts = array();
210
211 foreach ( $wp_scripts->queue as $script ) {
212 $enqueued_scripts[] = $wp_scripts->registered[ $script ]->src;
213 }
214
215 return (object) array(
216 'provider_name' => __( 'Embed Handler' ),
217 'html' => $html,
218 'scripts' => $enqueued_scripts,
219 );
220 }
221
222 return new WP_Error( 'oembed_invalid_url', get_status_header_desc( 404 ), array( 'status' => 404 ) );
223 }
224
225 /** This filter is documented in wp-includes/class-wp-oembed.php */
226 $data->html = apply_filters( 'oembed_result', _wp_oembed_get_object()->data2html( (object) $data, $url ), $url, $args );
227
228 /**
229 * Filters the oEmbed TTL value (time to live).
230 *
231 * Similar to the {@see 'oembed_ttl'} filter, but for the REST API
232 * oEmbed proxy endpoint.
233 *
234 * @since 4.8.0
235 *
236 * @param int $time Time to live (in seconds).
237 * @param string $url The attempted embed URL.
238 * @param array $args An array of embed request arguments.
239 */
240 $ttl = apply_filters( 'rest_oembed_ttl', DAY_IN_SECONDS, $url, $args );
241
242 set_transient( $cache_key, $data, $ttl );
243
244 return $data;
245 }
246}
247