Diff: STRATO-apps/wordpress_03/app/wp-includes/class-wp-oembed.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
1 + <?php
2 + /**
3 + * API for fetching the HTML to embed remote content based on a provided URL
4 + *
5 + * Used internally by the WP_Embed class, but is designed to be generic.
6 + *
7 + * @link https://developer.wordpress.org/advanced-administration/wordpress/oembed/
8 + * @link http://oembed.com/
9 + *
10 + * @package WordPress
11 + * @subpackage oEmbed
12 + */
13 +
14 + /**
15 + * Core class used to implement oEmbed functionality.
16 + *
17 + * @since 2.9.0
18 + */
19 + #[AllowDynamicProperties]
20 + class WP_oEmbed {
21 +
22 + /**
23 + * A list of oEmbed providers.
24 + *
25 + * @since 2.9.0
26 + * @var array
27 + */
28 + public $providers = array();
29 +
30 + /**
31 + * A list of an early oEmbed providers.
32 + *
33 + * @since 4.0.0
34 + * @var array
35 + */
36 + public static $early_providers = array();
37 +
38 + /**
39 + * A list of private/protected methods, used for backward compatibility.
40 + *
41 + * @since 4.2.0
42 + * @var array
43 + */
44 + private $compat_methods = array( '_fetch_with_format', '_parse_json', '_parse_xml', '_parse_xml_body' );
45 +
46 + /**
47 + * Constructor.
48 + *
49 + * @since 2.9.0
50 + */
51 + public function __construct() {
52 + $host = urlencode( home_url() );
53 + $providers = array(
54 + '#https?://((m|www)\.)?youtube\.com/watch.*#i' => array( 'https://www.youtube.com/oembed', true ),
55 + '#https?://((m|www)\.)?youtube\.com/playlist.*#i' => array( 'https://www.youtube.com/oembed', true ),
56 + '#https?://((m|www)\.)?youtube\.com/shorts/*#i' => array( 'https://www.youtube.com/oembed', true ),
57 + '#https?://((m|www)\.)?youtube\.com/live/*#i' => array( 'https://www.youtube.com/oembed', true ),
58 + '#https?://youtu\.be/.*#i' => array( 'https://www.youtube.com/oembed', true ),
59 + '#https?://(.+\.)?vimeo\.com/.*#i' => array( 'https://vimeo.com/api/oembed.{format}', true ),
60 + '#https?://(www\.)?dailymotion\.com/.*#i' => array( 'https://www.dailymotion.com/services/oembed', true ),
61 + '#https?://dai\.ly/.*#i' => array( 'https://www.dailymotion.com/services/oembed', true ),
62 + '#https?://(www\.)?flickr\.com/.*#i' => array( 'https://www.flickr.com/services/oembed/', true ),
63 + '#https?://flic\.kr/.*#i' => array( 'https://www.flickr.com/services/oembed/', true ),
64 + '#https?://(.+\.)?smugmug\.com/.*#i' => array( 'https://api.smugmug.com/services/oembed/', true ),
65 + '#https?://(www\.)?scribd\.com/(doc|document)/.*#i' => array( 'https://www.scribd.com/services/oembed', true ),
66 + '#https?://wordpress\.tv/.*#i' => array( 'https://wordpress.tv/oembed/', true ),
67 + '#https?://(.+\.)?crowdsignal\.net/.*#i' => array( 'https://api.crowdsignal.com/oembed', true ),
68 + '#https?://(.+\.)?polldaddy\.com/.*#i' => array( 'https://api.crowdsignal.com/oembed', true ),
69 + '#https?://poll\.fm/.*#i' => array( 'https://api.crowdsignal.com/oembed', true ),
70 + '#https?://(.+\.)?survey\.fm/.*#i' => array( 'https://api.crowdsignal.com/oembed', true ),
71 + '#https?://(www\.)?twitter\.com/\w{1,15}/status(es)?/.*#i' => array( 'https://publish.twitter.com/oembed', true ),
72 + '#https?://(www\.)?twitter\.com/\w{1,15}$#i' => array( 'https://publish.twitter.com/oembed', true ),
73 + '#https?://(www\.)?twitter\.com/\w{1,15}/likes$#i' => array( 'https://publish.twitter.com/oembed', true ),
74 + '#https?://(www\.)?twitter\.com/\w{1,15}/lists/.*#i' => array( 'https://publish.twitter.com/oembed', true ),
75 + '#https?://(www\.)?twitter\.com/\w{1,15}/timelines/.*#i' => array( 'https://publish.twitter.com/oembed', true ),
76 + '#https?://(www\.)?twitter\.com/i/moments/.*#i' => array( 'https://publish.twitter.com/oembed', true ),
77 + '#https?://(www\.)?soundcloud\.com/.*#i' => array( 'https://soundcloud.com/oembed', true ),
78 + '#https?://(open|play)\.spotify\.com/.*#i' => array( 'https://embed.spotify.com/oembed/', true ),
79 + '#https?://(.+\.)?imgur\.com/.*#i' => array( 'https://api.imgur.com/oembed', true ),
80 + '#https?://(www\.)?issuu\.com/.+/docs/.+#i' => array( 'https://issuu.com/oembed_wp', true ),
81 + '#https?://(www\.)?mixcloud\.com/.*#i' => array( 'https://app.mixcloud.com/oembed/', true ),
82 + '#https?://(www\.|embed\.)?ted\.com/talks/.*#i' => array( 'https://www.ted.com/services/v1/oembed.{format}', true ),
83 + '#https?://(www\.)?(animoto|video214)\.com/play/.*#i' => array( 'https://animoto.com/oembeds/create', true ),
84 + '#https?://(.+)\.tumblr\.com/.*#i' => array( 'https://www.tumblr.com/oembed/1.0', true ),
85 + '#https?://(www\.)?kickstarter\.com/projects/.*#i' => array( 'https://www.kickstarter.com/services/oembed', true ),
86 + '#https?://kck\.st/.*#i' => array( 'https://www.kickstarter.com/services/oembed', true ),
87 + '#https?://cloudup\.com/.*#i' => array( 'https://cloudup.com/oembed', true ),
88 + '#https?://(www\.)?reverbnation\.com/.*#i' => array( 'https://www.reverbnation.com/oembed', true ),
89 + '#https?://videopress\.com/v/.*#' => array( 'https://public-api.wordpress.com/oembed/?for=' . $host, true ),
90 + '#https?://(www\.)?reddit\.com/r/[^/]+/comments/.*#i' => array( 'https://www.reddit.com/oembed', true ),
91 + '#https?://(www\.)?speakerdeck\.com/.*#i' => array( 'https://speakerdeck.com/oembed.{format}', true ),
92 + '#https?://([a-z0-9-]+\.)?amazon\.(com|com\.mx|com\.br|ca)/.*#i' => array( 'https://read.amazon.com/kp/api/oembed', true ),
93 + '#https?://([a-z0-9-]+\.)?amazon\.(co\.uk|de|fr|it|es|in|nl|ru)/.*#i' => array( 'https://read.amazon.co.uk/kp/api/oembed', true ),
94 + '#https?://([a-z0-9-]+\.)?amazon\.(co\.jp|com\.au)/.*#i' => array( 'https://read.amazon.com.au/kp/api/oembed', true ),
95 + '#https?://([a-z0-9-]+\.)?amazon\.cn/.*#i' => array( 'https://read.amazon.cn/kp/api/oembed', true ),
96 + '#https?://(www\.)?a\.co/.*#i' => array( 'https://read.amazon.com/kp/api/oembed', true ),
97 + '#https?://(www\.)?amzn\.to/.*#i' => array( 'https://read.amazon.com/kp/api/oembed', true ),
98 + '#https?://(www\.)?amzn\.eu/.*#i' => array( 'https://read.amazon.co.uk/kp/api/oembed', true ),
99 + '#https?://(www\.)?amzn\.in/.*#i' => array( 'https://read.amazon.in/kp/api/oembed', true ),
100 + '#https?://(www\.)?amzn\.asia/.*#i' => array( 'https://read.amazon.com.au/kp/api/oembed', true ),
101 + '#https?://(www\.)?z\.cn/.*#i' => array( 'https://read.amazon.cn/kp/api/oembed', true ),
102 + '#https?://www\.someecards\.com/.+-cards/.+#i' => array( 'https://www.someecards.com/v2/oembed/', true ),
103 + '#https?://www\.someecards\.com/usercards/viewcard/.+#i' => array( 'https://www.someecards.com/v2/oembed/', true ),
104 + '#https?://some\.ly\/.+#i' => array( 'https://www.someecards.com/v2/oembed/', true ),
105 + '#https?://(www\.)?tiktok\.com/.*/video/.*#i' => array( 'https://www.tiktok.com/oembed', true ),
106 + '#https?://(www\.)?tiktok\.com/@.*#i' => array( 'https://www.tiktok.com/oembed', true ),
107 + '#https?://([a-z]{2}|www)\.pinterest\.com(\.(au|mx))?/.*#i' => array( 'https://www.pinterest.com/oembed.json', true ),
108 + '#https?://(www\.)?wolframcloud\.com/obj/.+#i' => array( 'https://www.wolframcloud.com/oembed', true ),
109 + '#https?://pca\.st/.+#i' => array( 'https://pca.st/oembed.json', true ),
110 + '#https?://((play|www)\.)?anghami\.com/.*#i' => array( 'https://api.anghami.com/rest/v1/oembed.view', true ),
111 + '#https?://bsky.app/profile/.*/post/.*#i' => array( 'https://embed.bsky.app/oembed', true ),
112 + '#https?://(www\.)?canva\.com/design/.*/view.*#i' => array( 'https://canva.com/_oembed', true ),
113 + );
114 +
115 + if ( ! empty( self::$early_providers['add'] ) ) {
116 + foreach ( self::$early_providers['add'] as $format => $data ) {
117 + $providers[ $format ] = $data;
118 + }
119 + }
120 +
121 + if ( ! empty( self::$early_providers['remove'] ) ) {
122 + foreach ( self::$early_providers['remove'] as $format ) {
123 + unset( $providers[ $format ] );
124 + }
125 + }
126 +
127 + self::$early_providers = array();
128 +
129 + /**
130 + * Filters the list of sanctioned oEmbed providers.
131 + *
132 + * Since WordPress 4.4, oEmbed discovery is enabled for all users and allows embedding of sanitized
133 + * iframes. The providers in this list are sanctioned, meaning they are trusted and allowed to
134 + * embed any content, such as iframes, videos, JavaScript, and arbitrary HTML.
135 + *
136 + * Supported providers:
137 + *
138 + * | Provider | Flavor | Since |
139 + * | ------------ | ----------------------------------------- | ------- |
140 + * | Dailymotion | dailymotion.com | 2.9.0 |
141 + * | Flickr | flickr.com | 2.9.0 |
142 + * | Scribd | scribd.com | 2.9.0 |
143 + * | Vimeo | vimeo.com | 2.9.0 |
144 + * | WordPress.tv | wordpress.tv | 2.9.0 |
145 + * | YouTube | youtube.com/watch | 2.9.0 |
146 + * | Crowdsignal | polldaddy.com | 3.0.0 |
147 + * | SmugMug | smugmug.com | 3.0.0 |
148 + * | YouTube | youtu.be | 3.0.0 |
149 + * | Twitter | twitter.com | 3.4.0 |
150 + * | SoundCloud | soundcloud.com | 3.5.0 |
151 + * | Dailymotion | dai.ly | 3.6.0 |
152 + * | Flickr | flic.kr | 3.6.0 |
153 + * | Spotify | spotify.com | 3.6.0 |
154 + * | Imgur | imgur.com | 3.9.0 |
155 + * | Animoto | animoto.com | 4.0.0 |
156 + * | Animoto | video214.com | 4.0.0 |
157 + * | Issuu | issuu.com | 4.0.0 |
158 + * | Mixcloud | mixcloud.com | 4.0.0 |
159 + * | Crowdsignal | poll.fm | 4.0.0 |
160 + * | TED | ted.com | 4.0.0 |
161 + * | YouTube | youtube.com/playlist | 4.0.0 |
162 + * | Tumblr | tumblr.com | 4.2.0 |
163 + * | Kickstarter | kickstarter.com | 4.2.0 |
164 + * | Kickstarter | kck.st | 4.2.0 |
165 + * | Cloudup | cloudup.com | 4.3.0 |
166 + * | ReverbNation | reverbnation.com | 4.4.0 |
167 + * | VideoPress | videopress.com | 4.4.0 |
168 + * | Reddit | reddit.com | 4.4.0 |
169 + * | Speaker Deck | speakerdeck.com | 4.4.0 |
170 + * | Twitter | twitter.com/timelines | 4.5.0 |
171 + * | Twitter | twitter.com/moments | 4.5.0 |
172 + * | Twitter | twitter.com/user | 4.7.0 |
173 + * | Twitter | twitter.com/likes | 4.7.0 |
174 + * | Twitter | twitter.com/lists | 4.7.0 |
175 + * | Screencast | screencast.com | 4.8.0 |
176 + * | Amazon | amazon.com (com.mx, com.br, ca) | 4.9.0 |
177 + * | Amazon | amazon.de (fr, it, es, in, nl, ru, co.uk) | 4.9.0 |
178 + * | Amazon | amazon.co.jp (com.au) | 4.9.0 |
179 + * | Amazon | amazon.cn | 4.9.0 |
180 + * | Amazon | a.co | 4.9.0 |
181 + * | Amazon | amzn.to (eu, in, asia) | 4.9.0 |
182 + * | Amazon | z.cn | 4.9.0 |
183 + * | Someecards | someecards.com | 4.9.0 |
184 + * | Someecards | some.ly | 4.9.0 |
185 + * | Crowdsignal | survey.fm | 5.1.0 |
186 + * | TikTok | tiktok.com | 5.4.0 |
187 + * | Pinterest | pinterest.com | 5.9.0 |
188 + * | WolframCloud | wolframcloud.com | 5.9.0 |
189 + * | Pocket Casts | pocketcasts.com | 6.1.0 |
190 + * | Crowdsignal | crowdsignal.net | 6.2.0 |
191 + * | Anghami | anghami.com | 6.3.0 |
192 + * | Bluesky | bsky.app | 6.6.0 |
193 + * | Canva | canva.com | 6.8.0 |
194 + *
195 + * No longer supported providers:
196 + *
197 + * | Provider | Flavor | Since | Removed |
198 + * | ------------ | -------------------- | --------- | --------- |
199 + * | Qik | qik.com | 2.9.0 | 3.9.0 |
200 + * | Viddler | viddler.com | 2.9.0 | 4.0.0 |
201 + * | Revision3 | revision3.com | 2.9.0 | 4.2.0 |
202 + * | Blip | blip.tv | 2.9.0 | 4.4.0 |
203 + * | Rdio | rdio.com | 3.6.0 | 4.4.1 |
204 + * | Rdio | rd.io | 3.6.0 | 4.4.1 |
205 + * | Vine | vine.co | 4.1.0 | 4.9.0 |
206 + * | Photobucket | photobucket.com | 2.9.0 | 5.1.0 |
207 + * | Funny or Die | funnyordie.com | 3.0.0 | 5.1.0 |
208 + * | CollegeHumor | collegehumor.com | 4.0.0 | 5.3.1 |
209 + * | Hulu | hulu.com | 2.9.0 | 5.5.0 |
210 + * | Instagram | instagram.com | 3.5.0 | 5.5.2 |
211 + * | Instagram | instagr.am | 3.5.0 | 5.5.2 |
212 + * | Instagram TV | instagram.com | 5.1.0 | 5.5.2 |
213 + * | Instagram TV | instagr.am | 5.1.0 | 5.5.2 |
214 + * | Facebook | facebook.com | 4.7.0 | 5.5.2 |
215 + * | Meetup.com | meetup.com | 3.9.0 | 6.0.1 |
216 + * | Meetup.com | meetu.ps | 3.9.0 | 6.0.1 |
217 + * | SlideShare | slideshare.net | 3.5.0 | 6.6.0 |
218 + * | Screencast | screencast.com | 4.8.0 | 6.8.2 |
219 + *
220 + * @see wp_oembed_add_provider()
221 + *
222 + * @since 2.9.0
223 + *
224 + * @param array[] $providers An array of arrays containing data about popular oEmbed providers.
225 + */
226 + $this->providers = apply_filters( 'oembed_providers', $providers );
227 +
228 + // Fix any embeds that contain new lines in the middle of the HTML which breaks wpautop().
229 + add_filter( 'oembed_dataparse', array( $this, '_strip_newlines' ), 10, 3 );
230 + }
231 +
232 + /**
233 + * Exposes private/protected methods for backward compatibility.
234 + *
235 + * @since 4.0.0
236 + *
237 + * @param string $name Method to call.
238 + * @param array $arguments Arguments to pass when calling.
239 + * @return mixed|false Return value of the callback, false otherwise.
240 + */
241 + public function __call( $name, $arguments ) {
242 + if ( in_array( $name, $this->compat_methods, true ) ) {
243 + return $this->$name( ...$arguments );
244 + }
245 +
246 + return false;
247 + }
248 +
249 + /**
250 + * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one.
251 + *
252 + * @since 4.0.0
253 + *
254 + * @see WP_oEmbed::discover()
255 + *
256 + * @param string $url The URL to the content.
257 + * @param string|array $args {
258 + * Optional. Additional provider arguments. Default empty.
259 + *
260 + * @type bool $discover Optional. Determines whether to attempt to discover link tags
261 + * at the given URL for an oEmbed provider when the provider URL
262 + * is not found in the built-in providers list. Default true.
263 + * }
264 + * @return string|false The oEmbed provider URL on success, false on failure.
265 + */
266 + public function get_provider( $url, $args = '' ) {
267 + $args = wp_parse_args( $args );
268 +
269 + $provider = false;
270 +
271 + if ( ! isset( $args['discover'] ) ) {
272 + $args['discover'] = true;
273 + }
274 +
275 + foreach ( $this->providers as $matchmask => $data ) {
276 + list( $providerurl, $regex ) = $data;
277 +
278 + // Turn the asterisk-type provider URLs into regex.
279 + if ( ! $regex ) {
280 + $matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i';
281 + $matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask );
282 + }
283 +
284 + if ( preg_match( $matchmask, $url ) ) {
285 + $provider = str_replace( '{format}', 'json', $providerurl ); // JSON is easier to deal with than XML.
286 + break;
287 + }
288 + }
289 +
290 + if ( ! $provider && $args['discover'] ) {
291 + $provider = $this->discover( $url );
292 + }
293 +
294 + return $provider;
295 + }
296 +
297 + /**
298 + * Adds an oEmbed provider.
299 + *
300 + * The provider is added just-in-time when wp_oembed_add_provider() is called before
301 + * the {@see 'plugins_loaded'} hook.
302 + *
303 + * The just-in-time addition is for the benefit of the {@see 'oembed_providers'} filter.
304 + *
305 + * @since 4.0.0
306 + *
307 + * @see wp_oembed_add_provider()
308 + *
309 + * @param string $format Format of URL that this provider can handle. You can use
310 + * asterisks as wildcards.
311 + * @param string $provider The URL to the oEmbed provider..
312 + * @param bool $regex Optional. Whether the $format parameter is in a regex format.
313 + * Default false.
314 + */
315 + public static function _add_provider_early( $format, $provider, $regex = false ) {
316 + if ( empty( self::$early_providers['add'] ) ) {
317 + self::$early_providers['add'] = array();
318 + }
319 +
320 + self::$early_providers['add'][ $format ] = array( $provider, $regex );
321 + }
322 +
323 + /**
324 + * Removes an oEmbed provider.
325 + *
326 + * The provider is removed just-in-time when wp_oembed_remove_provider() is called before
327 + * the {@see 'plugins_loaded'} hook.
328 + *
329 + * The just-in-time removal is for the benefit of the {@see 'oembed_providers'} filter.
330 + *
331 + * @since 4.0.0
332 + *
333 + * @see wp_oembed_remove_provider()
334 + *
335 + * @param string $format The format of URL that this provider can handle. You can use
336 + * asterisks as wildcards.
337 + */
338 + public static function _remove_provider_early( $format ) {
339 + if ( empty( self::$early_providers['remove'] ) ) {
340 + self::$early_providers['remove'] = array();
341 + }
342 +
343 + self::$early_providers['remove'][] = $format;
344 + }
345 +
346 + /**
347 + * Takes a URL and attempts to return the oEmbed data.
348 + *
349 + * @see WP_oEmbed::fetch()
350 + *
351 + * @since 4.8.0
352 + *
353 + * @param string $url The URL to the content that should be attempted to be embedded.
354 + * @param string|array $args Optional. Additional arguments for retrieving embed HTML.
355 + * See wp_oembed_get() for accepted arguments. Default empty.
356 + * @return object|false The result in the form of an object on success, false on failure.
357 + */
358 + public function get_data( $url, $args = '' ) {
359 + $args = wp_parse_args( $args );
360 +
361 + $provider = $this->get_provider( $url, $args );
362 +
363 + if ( ! $provider ) {
364 + return false;
365 + }
366 +
367 + $data = $this->fetch( $provider, $url, $args );
368 +
369 + if ( false === $data ) {
370 + return false;
371 + }
372 +
373 + return $data;
374 + }
375 +
376 + /**
377 + * The do-it-all function that takes a URL and attempts to return the HTML.
378 + *
379 + * @see WP_oEmbed::fetch()
380 + * @see WP_oEmbed::data2html()
381 + *
382 + * @since 2.9.0
383 + *
384 + * @param string $url The URL to the content that should be attempted to be embedded.
385 + * @param string|array $args Optional. Additional arguments for retrieving embed HTML.
386 + * See wp_oembed_get() for accepted arguments. Default empty.
387 + * @return string|false The UNSANITIZED (and potentially unsafe) HTML that should be used to embed
388 + * on success, false on failure.
389 + */
390 + public function get_html( $url, $args = '' ) {
391 + /**
392 + * Filters the oEmbed result before any HTTP requests are made.
393 + *
394 + * This allows one to short-circuit the default logic, perhaps by
395 + * replacing it with a routine that is more optimal for your setup.
396 + *
397 + * Returning a non-null value from the filter will effectively short-circuit retrieval
398 + * and return the passed value instead.
399 + *
400 + * @since 4.5.3
401 + *
402 + * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed.
403 + * Default null to continue retrieving the result.
404 + * @param string $url The URL to the content that should be attempted to be embedded.
405 + * @param string|array $args Optional. Additional arguments for retrieving embed HTML.
406 + * See wp_oembed_get() for accepted arguments. Default empty.
407 + */
408 + $pre = apply_filters( 'pre_oembed_result', null, $url, $args );
409 +
410 + if ( null !== $pre ) {
411 + return $pre;
412 + }
413 +
414 + $data = $this->get_data( $url, $args );
415 +
416 + if ( false === $data ) {
417 + return false;
418 + }
419 +
420 + /**
421 + * Filters the HTML returned by the oEmbed provider.
422 + *
423 + * @since 2.9.0
424 + *
425 + * @param string|false $data The returned oEmbed HTML (false if unsafe).
426 + * @param string $url URL of the content to be embedded.
427 + * @param string|array $args Optional. Additional arguments for retrieving embed HTML.
428 + * See wp_oembed_get() for accepted arguments. Default empty.
429 + */
430 + return apply_filters( 'oembed_result', $this->data2html( $data, $url ), $url, $args );
431 + }
432 +
433 + /**
434 + * Attempts to discover link tags at the given URL for an oEmbed provider.
435 + *
436 + * @since 2.9.0
437 + *
438 + * @param string $url The URL that should be inspected for discovery `<link>` tags.
439 + * @return string|false The oEmbed provider URL on success, false on failure.
440 + */
441 + public function discover( $url ) {
442 + $providers = array();
443 + $args = array(
444 + 'limit_response_size' => 153600, // 150 KB
445 + );
446 +
447 + /**
448 + * Filters oEmbed remote get arguments.
449 + *
450 + * @since 4.0.0
451 + *
452 + * @see WP_Http::request()
453 + *
454 + * @param array $args oEmbed remote get arguments.
455 + * @param string $url URL to be inspected.
456 + */
457 + $args = apply_filters( 'oembed_remote_get_args', $args, $url );
458 +
459 + // Fetch URL content.
460 + $request = wp_safe_remote_get( $url, $args );
461 + $html = wp_remote_retrieve_body( $request );
462 + if ( $html ) {
463 +
464 + /**
465 + * Filters the link types that contain oEmbed provider URLs.
466 + *
467 + * @since 2.9.0
468 + *
469 + * @param string[] $format Array of oEmbed link types. Accepts 'application/json+oembed',
470 + * 'text/xml+oembed', and 'application/xml+oembed' (incorrect,
471 + * used by at least Vimeo).
472 + */
473 + $linktypes = apply_filters(
474 + 'oembed_linktypes',
475 + array(
476 + 'application/json+oembed' => 'json',
477 + 'text/xml+oembed' => 'xml',
478 + 'application/xml+oembed' => 'xml',
479 + )
480 + );
481 +
482 + // Strip <body>.
483 + $html_head_end = stripos( $html, '</head>' );
484 + if ( $html_head_end ) {
485 + $html = substr( $html, 0, $html_head_end );
486 + }
487 +
488 + // Do a quick check.
489 + $tagfound = false;
490 + foreach ( $linktypes as $linktype => $format ) {
491 + if ( stripos( $html, $linktype ) ) {
492 + $tagfound = true;
493 + break;
494 + }
495 + }
496 +
497 + if ( $tagfound && preg_match_all( '#<link([^<>]+)/?>#iU', $html, $links ) ) {
498 + foreach ( $links[1] as $link ) {
499 + $atts = shortcode_parse_atts( $link );
500 +
501 + if ( ! empty( $atts['type'] ) && ! empty( $linktypes[ $atts['type'] ] ) && ! empty( $atts['href'] ) ) {
502 + $providers[ $linktypes[ $atts['type'] ] ] = htmlspecialchars_decode( $atts['href'] );
503 +
504 + // Stop here if it's JSON (that's all we need).
505 + if ( 'json' === $linktypes[ $atts['type'] ] ) {
506 + break;
507 + }
508 + }
509 + }
510 + }
511 + }
512 +
513 + // JSON is preferred to XML.
514 + if ( ! empty( $providers['json'] ) ) {
515 + return $providers['json'];
516 + } elseif ( ! empty( $providers['xml'] ) ) {
517 + return $providers['xml'];
518 + } else {
519 + return false;
520 + }
521 + }
522 +
523 + /**
524 + * Connects to an oEmbed provider and returns the result.
525 + *
526 + * @since 2.9.0
527 + *
528 + * @param string $provider The URL to the oEmbed provider.
529 + * @param string $url The URL to the content that is desired to be embedded.
530 + * @param string|array $args Optional. Additional arguments for retrieving embed HTML.
531 + * See wp_oembed_get() for accepted arguments. Default empty.
532 + * @return object|false The result in the form of an object on success, false on failure.
533 + */
534 + public function fetch( $provider, $url, $args = '' ) {
535 + $args = wp_parse_args( $args, wp_embed_defaults( $url ) );
536 +
537 + $provider = add_query_arg( 'maxwidth', (int) $args['width'], $provider );
538 + $provider = add_query_arg( 'maxheight', (int) $args['height'], $provider );
539 + $provider = add_query_arg( 'url', urlencode( $url ), $provider );
540 + $provider = add_query_arg( 'dnt', 1, $provider );
541 +
542 + /**
543 + * Filters the oEmbed URL to be fetched.
544 + *
545 + * @since 2.9.0
546 + * @since 4.9.0 The `dnt` (Do Not Track) query parameter was added to all oEmbed provider URLs.
547 + *
548 + * @param string $provider URL of the oEmbed provider.
549 + * @param string $url URL of the content to be embedded.
550 + * @param array $args Optional. Additional arguments for retrieving embed HTML.
551 + * See wp_oembed_get() for accepted arguments. Default empty.
552 + */
553 + $provider = apply_filters( 'oembed_fetch_url', $provider, $url, $args );
554 +
555 + foreach ( array( 'json', 'xml' ) as $format ) {
556 + $result = $this->_fetch_with_format( $provider, $format );
557 + if ( is_wp_error( $result ) && 'not-implemented' === $result->get_error_code() ) {
558 + continue;
559 + }
560 +
561 + return ( $result && ! is_wp_error( $result ) ) ? $result : false;
562 + }
563 +
564 + return false;
565 + }
566 +
567 + /**
568 + * Fetches result from an oEmbed provider for a specific format and complete provider URL
569 + *
570 + * @since 3.0.0
571 + *
572 + * @param string $provider_url_with_args URL to the provider with full arguments list (url, maxheight, etc.)
573 + * @param string $format Format to use.
574 + * @return object|false|WP_Error The result in the form of an object on success, false on failure.
575 + */
576 + private function _fetch_with_format( $provider_url_with_args, $format ) {
577 + $provider_url_with_args = add_query_arg( 'format', $format, $provider_url_with_args );
578 +
579 + /** This filter is documented in wp-includes/class-wp-oembed.php */
580 + $args = apply_filters( 'oembed_remote_get_args', array(), $provider_url_with_args );
581 +
582 + $response = wp_safe_remote_get( $provider_url_with_args, $args );
583 +
584 + if ( 501 === wp_remote_retrieve_response_code( $response ) ) {
585 + return new WP_Error( 'not-implemented' );
586 + }
587 +
588 + $body = wp_remote_retrieve_body( $response );
589 + if ( ! $body ) {
590 + return false;
591 + }
592 +
593 + $parse_method = "_parse_$format";
594 +
595 + return $this->$parse_method( $body );
596 + }
597 +
598 + /**
599 + * Parses a json response body.
600 + *
601 + * @since 3.0.0
602 + *
603 + * @param string $response_body
604 + * @return object|false
605 + */
606 + private function _parse_json( $response_body ) {
607 + $data = json_decode( trim( $response_body ) );
608 +
609 + return ( $data && is_object( $data ) ) ? $data : false;
610 + }
611 +
612 + /**
613 + * Parses an XML response body.
614 + *
615 + * @since 3.0.0
616 + *
617 + * @param string $response_body
618 + * @return object|false
619 + */
620 + private function _parse_xml( $response_body ) {
621 + if ( ! function_exists( 'libxml_disable_entity_loader' ) ) {
622 + return false;
623 + }
624 +
625 + if ( PHP_VERSION_ID < 80000 ) {
626 + /*
627 + * This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading
628 + * is disabled by default, so this function is no longer needed to protect against XXE attacks.
629 + */
630 + $loader = libxml_disable_entity_loader( true );
631 + }
632 +
633 + $errors = libxml_use_internal_errors( true );
634 +
635 + $return = $this->_parse_xml_body( $response_body );
636 +
637 + libxml_use_internal_errors( $errors );
638 +
639 + if ( PHP_VERSION_ID < 80000 && isset( $loader ) ) {
640 + // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.libxml_disable_entity_loaderDeprecated
641 + libxml_disable_entity_loader( $loader );
642 + }
643 +
644 + return $return;
645 + }
646 +
647 + /**
648 + * Serves as a helper function for parsing an XML response body.
649 + *
650 + * @since 3.6.0
651 + *
652 + * @param string $response_body
653 + * @return stdClass|false
654 + */
655 + private function _parse_xml_body( $response_body ) {
656 + if ( ! function_exists( 'simplexml_import_dom' ) || ! class_exists( 'DOMDocument', false ) ) {
657 + return false;
658 + }
659 +
660 + $dom = new DOMDocument();
661 + $success = $dom->loadXML( $response_body );
662 + if ( ! $success ) {
663 + return false;
664 + }
665 +
666 + if ( isset( $dom->doctype ) ) {
667 + return false;
668 + }
669 +
670 + foreach ( $dom->childNodes as $child ) {
671 + if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType ) {
672 + return false;
673 + }
674 + }
675 +
676 + $xml = simplexml_import_dom( $dom );
677 + if ( ! $xml ) {
678 + return false;
679 + }
680 +
681 + $return = new stdClass();
682 + foreach ( $xml as $key => $value ) {
683 + $return->$key = (string) $value;
684 + }
685 +
686 + return $return;
687 + }
688 +
689 + /**
690 + * Converts a data object from WP_oEmbed::fetch() and returns the HTML.
691 + *
692 + * @since 2.9.0
693 + *
694 + * @param object $data A data object result from an oEmbed provider.
695 + * @param string $url The URL to the content that is desired to be embedded.
696 + * @return string|false The HTML needed to embed on success, false on failure.
697 + */
698 + public function data2html( $data, $url ) {
699 + if ( ! is_object( $data ) || empty( $data->type ) ) {
700 + return false;
701 + }
702 +
703 + $return = false;
704 +
705 + switch ( $data->type ) {
706 + case 'photo':
707 + if ( empty( $data->url ) || empty( $data->width ) || empty( $data->height ) ) {
708 + break;
709 + }
710 + if ( ! is_string( $data->url ) || ! is_numeric( $data->width ) || ! is_numeric( $data->height ) ) {
711 + break;
712 + }
713 +
714 + $title = ! empty( $data->title ) && is_string( $data->title ) ? $data->title : '';
715 + $return = '<a href="' . esc_url( $url ) . '"><img src="' . esc_url( $data->url ) . '" alt="' . esc_attr( $title ) . '" width="' . esc_attr( $data->width ) . '" height="' . esc_attr( $data->height ) . '" /></a>';
716 + break;
717 +
718 + case 'video':
719 + case 'rich':
720 + if ( ! empty( $data->html ) && is_string( $data->html ) ) {
721 + $return = $data->html;
722 + }
723 + break;
724 +
725 + case 'link':
726 + if ( ! empty( $data->title ) && is_string( $data->title ) ) {
727 + $return = '<a href="' . esc_url( $url ) . '">' . esc_html( $data->title ) . '</a>';
728 + }
729 + break;
730 +
731 + default:
732 + $return = false;
733 + }
734 +
735 + /**
736 + * Filters the returned oEmbed HTML.
737 + *
738 + * Use this filter to add support for custom data types, or to filter the result.
739 + *
740 + * @since 2.9.0
741 + *
742 + * @param string|false $return The returned oEmbed HTML, or false on failure.
743 + * @param object $data A data object result from an oEmbed provider.
744 + * @param string $url The URL of the content to be embedded.
745 + */
746 + return apply_filters( 'oembed_dataparse', $return, $data, $url );
747 + }
748 +
749 + /**
750 + * Strips any new lines from the HTML.
751 + *
752 + * @since 2.9.0 as strip_scribd_newlines()
753 + * @since 3.0.0
754 + *
755 + * @param string|false $html Existing HTML.
756 + * @param object $data Data object from WP_oEmbed::data2html()
757 + * @param string $url The original URL passed to oEmbed.
758 + * @return string|false Possibly modified $html.
759 + */
760 + public function _strip_newlines( $html, $data, $url ) {
761 + if ( ! str_contains( $html, "\n" ) ) {
762 + return $html;
763 + }
764 +
765 + $count = 1;
766 + $found = array();
767 + $token = '__PRE__';
768 + $search = array( "\t", "\n", "\r", ' ' );
769 + $replace = array( '__TAB__', '__NL__', '__CR__', '__SPACE__' );
770 + $tokenized = str_replace( $search, $replace, $html );
771 +
772 + preg_match_all( '#(<pre[^>]*>.+?</pre>)#i', $tokenized, $matches, PREG_SET_ORDER );
773 + foreach ( $matches as $i => $match ) {
774 + $tag_html = str_replace( $replace, $search, $match[0] );
775 + $tag_token = $token . $i;
776 +
777 + $found[ $tag_token ] = $tag_html;
778 + $html = str_replace( $tag_html, $tag_token, $html, $count );
779 + }
780 +
781 + $replaced = str_replace( $replace, $search, $html );
782 + $stripped = str_replace( array( "\r\n", "\n" ), '', $replaced );
783 + $pre = array_values( $found );
784 + $tokens = array_keys( $found );
785 +
786 + return str_replace( $tokens, $pre, $stripped );
787 + }
788 + }
789 +