Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/class-oembed.php (7 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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://codex.wordpress.org/oEmbed oEmbed Codex Article
8
 * @link http://oembed.com/ oEmbed Homepage
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
class WP_oEmbed {
20
21
	/**
22
	 * A list of oEmbed providers.
23
	 *
24
	 * @since 2.9.0
25
	 * @access public
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
	 * @access public
35
	 * @static
36
	 * @var array
37
	 */
38
	public static $early_providers = array();
39
40
	/**
41
	 * A list of private/protected methods, used for backward compatibility.
42
	 *
43
	 * @since 4.2.0
44
	 * @access private
45
	 * @var array
46
	 */
47
	private $compat_methods = array( '_fetch_with_format', '_parse_json', '_parse_xml', '_parse_xml_body' );
48
49
	/**
50
	 * Constructor.
51
	 *
52
	 * @since 2.9.0
53
	 * @access public
54
	 */
55
	public function __construct() {
56
		$host = urlencode( home_url() );
57
		$providers = array(
58
			'#https?://((m|www)\.)?youtube\.com/watch.*#i'             => array( 'https://www.youtube.com/oembed',                            true  ),
59
			'#https?://((m|www)\.)?youtube\.com/playlist.*#i'          => array( 'https://www.youtube.com/oembed',                            true  ),
60
			'#https?://youtu\.be/.*#i'                                 => array( 'https://www.youtube.com/oembed',                            true  ),
61
			'#https?://(.+\.)?vimeo\.com/.*#i'                         => array( 'https://vimeo.com/api/oembed.{format}',                     true  ),
62
			'#https?://(www\.)?dailymotion\.com/.*#i'                  => array( 'https://www.dailymotion.com/services/oembed',               true  ),
63
			'#https?://dai\.ly/.*#i'                                   => array( 'https://www.dailymotion.com/services/oembed',               true  ),
64
			'#https?://(www\.)?flickr\.com/.*#i'                       => array( 'https://www.flickr.com/services/oembed/',                   true  ),
65
			'#https?://flic\.kr/.*#i'                                  => array( 'https://www.flickr.com/services/oembed/',                   true  ),
66
			'#https?://(.+\.)?smugmug\.com/.*#i'                       => array( 'https://api.smugmug.com/services/oembed/',                  true  ),
67
			'#https?://(www\.)?hulu\.com/watch/.*#i'                   => array( 'http://www.hulu.com/api/oembed.{format}',                   true  ),
68
			'http://i*.photobucket.com/albums/*'                       => array( 'http://api.photobucket.com/oembed',                         false ),
69
			'http://gi*.photobucket.com/groups/*'                      => array( 'http://api.photobucket.com/oembed',                         false ),
70
			'#https?://(www\.)?scribd\.com/doc/.*#i'                   => array( 'https://www.scribd.com/services/oembed',                    true  ),
71
			'#https?://wordpress\.tv/.*#i'                             => array( 'https://wordpress.tv/oembed/',                              true  ),
72
			'#https?://(.+\.)?polldaddy\.com/.*#i'                     => array( 'https://polldaddy.com/oembed/',                             true  ),
73
			'#https?://poll\.fm/.*#i'                                  => array( 'https://polldaddy.com/oembed/',                             true  ),
74
			'#https?://(www\.)?funnyordie\.com/videos/.*#i'            => array( 'http://www.funnyordie.com/oembed',                          true  ),
75
			'#https?://(www\.)?twitter\.com/\w{1,15}/status(es)?/.*#i' => array( 'https://publish.twitter.com/oembed',                        true  ),
76
			'#https?://(www\.)?twitter\.com/\w{1,15}$#i'               => array( 'https://publish.twitter.com/oembed',                        true  ),
77
			'#https?://(www\.)?twitter\.com/\w{1,15}/likes$#i'         => array( 'https://publish.twitter.com/oembed',                        true  ),
78
			'#https?://(www\.)?twitter\.com/\w{1,15}/lists/.*#i'       => array( 'https://publish.twitter.com/oembed',                        true  ),
79
			'#https?://(www\.)?twitter\.com/\w{1,15}/timelines/.*#i'   => array( 'https://publish.twitter.com/oembed',                        true  ),
80
			'#https?://(www\.)?twitter\.com/i/moments/.*#i'            => array( 'https://publish.twitter.com/oembed',                        true  ),
81
			'#https?://vine\.co/v/.*#i'                                => array( 'https://vine.co/oembed.{format}',                           true  ),
82
			'#https?://(www\.)?soundcloud\.com/.*#i'                   => array( 'https://soundcloud.com/oembed',                             true  ),
83
			'#https?://(.+?\.)?slideshare\.net/.*#i'                   => array( 'https://www.slideshare.net/api/oembed/2',                   true  ),
84
			'#https?://(www\.)?instagr(\.am|am\.com)/p/.*#i'           => array( 'https://api.instagram.com/oembed',                          true  ),
85
			'#https?://(open|play)\.spotify\.com/.*#i'                 => array( 'https://embed.spotify.com/oembed/',                         true  ),
86
			'#https?://(.+\.)?imgur\.com/.*#i'                         => array( 'http://api.imgur.com/oembed',                               true  ),
87
			'#https?://(www\.)?meetu(\.ps|p\.com)/.*#i'                => array( 'https://api.meetup.com/oembed',                             true  ),
88
			'#https?://(www\.)?issuu\.com/.+/docs/.+#i'                => array( 'https://issuu.com/oembed_wp',                               true  ),
89
			'#https?://(www\.)?collegehumor\.com/video/.*#i'           => array( 'http://www.collegehumor.com/oembed.{format}',               true  ),
90
			'#https?://(www\.)?mixcloud\.com/.*#i'                     => array( 'https://www.mixcloud.com/oembed',                           true  ),
91
			'#https?://(www\.|embed\.)?ted\.com/talks/.*#i'            => array( 'https://www.ted.com/services/v1/oembed.{format}',           true  ),
92
			'#https?://(www\.)?(animoto|video214)\.com/play/.*#i'      => array( 'https://animoto.com/oembeds/create',                        true  ),
93
			'#https?://(.+)\.tumblr\.com/post/.*#i'                    => array( 'https://www.tumblr.com/oembed/1.0',                         true  ),
94
			'#https?://(www\.)?kickstarter\.com/projects/.*#i'         => array( 'https://www.kickstarter.com/services/oembed',               true  ),
95
			'#https?://kck\.st/.*#i'                                   => array( 'https://www.kickstarter.com/services/oembed',               true  ),
96
			'#https?://cloudup\.com/.*#i'                              => array( 'https://cloudup.com/oembed',                                true  ),
97
			'#https?://(www\.)?reverbnation\.com/.*#i'                 => array( 'https://www.reverbnation.com/oembed',                       true  ),
98
			'#https?://videopress\.com/v/.*#'                          => array( 'https://public-api.wordpress.com/oembed/?for=' . $host,     true  ),
99
			'#https?://(www\.)?reddit\.com/r/[^/]+/comments/.*#i'      => array( 'https://www.reddit.com/oembed',                             true  ),
100
			'#https?://(www\.)?speakerdeck\.com/.*#i'                  => array( 'https://speakerdeck.com/oembed.{format}',                   true  ),
101
			'#https?://www\.facebook\.com/.*/posts/.*#i'               => array( 'https://www.facebook.com/plugins/post/oembed.json/',        true  ),
102
			'#https?://www\.facebook\.com/.*/activity/.*#i'            => array( 'https://www.facebook.com/plugins/post/oembed.json/',        true  ),
103
			'#https?://www\.facebook\.com/.*/photos/.*#i'              => array( 'https://www.facebook.com/plugins/post/oembed.json/',        true  ),
104
			'#https?://www\.facebook\.com/photo(s/|\.php).*#i'         => array( 'https://www.facebook.com/plugins/post/oembed.json/',        true  ),
105
			'#https?://www\.facebook\.com/permalink\.php.*#i'          => array( 'https://www.facebook.com/plugins/post/oembed.json/',        true  ),
106
			'#https?://www\.facebook\.com/media/.*#i'                  => array( 'https://www.facebook.com/plugins/post/oembed.json/',        true  ),
107
			'#https?://www\.facebook\.com/questions/.*#i'              => array( 'https://www.facebook.com/plugins/post/oembed.json/',        true  ),
108
			'#https?://www\.facebook\.com/notes/.*#i'                  => array( 'https://www.facebook.com/plugins/post/oembed.json/',        true  ),
109
			'#https?://www\.facebook\.com/.*/videos/.*#i'              => array( 'https://www.facebook.com/plugins/video/oembed.json/',       true  ),
110
			'#https?://www\.facebook\.com/video\.php.*#i'              => array( 'https://www.facebook.com/plugins/video/oembed.json/',       true  ),
111
			'#https?://(www\.)?screencast\.com/.*#i'                   => array( 'https://api.screencast.com/external/oembed',                true  ),
112
		);
113
114 View Code Duplication
		if ( ! empty( self::$early_providers['add'] ) ) {
115
			foreach ( self::$early_providers['add'] as $format => $data ) {
116
				$providers[ $format ] = $data;
117
			}
118
		}
119
120 View Code Duplication
		if ( ! empty( self::$early_providers['remove'] ) ) {
121
			foreach ( self::$early_providers['remove'] as $format ) {
122
				unset( $providers[ $format ] );
123
			}
124
		}
125
126
		self::$early_providers = array();
127
128
		/**
129
		 * Filters the list of whitelisted oEmbed providers.
130
		 *
131
		 * Since WordPress 4.4, oEmbed discovery is enabled for all users and allows embedding of sanitized
132
		 * iframes. The providers in this list are whitelisted, meaning they are trusted and allowed to
133
		 * embed any content, such as iframes, videos, JavaScript, and arbitrary HTML.
134
		 *
135
		 * Supported providers:
136
		 *
137
		 * |   Provider   |        Flavor         | Supports HTTPS |   Since   |
138
		 * | ------------ | --------------------- | :------------: | --------- |
139
		 * | Dailymotion  | dailymotion.com       |      Yes       | 2.9.0     |
140
		 * | Flickr       | flickr.com            |      Yes       | 2.9.0     |
141
		 * | Hulu         | hulu.com              |      Yes       | 2.9.0     |
142
		 * | Photobucket  | photobucket.com       |      No        | 2.9.0     |
143
		 * | Scribd       | scribd.com            |      Yes       | 2.9.0     |
144
		 * | Vimeo        | vimeo.com             |      Yes       | 2.9.0     |
145
		 * | WordPress.tv | wordpress.tv          |      Yes       | 2.9.0     |
146
		 * | YouTube      | youtube.com/watch     |      Yes       | 2.9.0     |
147
		 * | Funny or Die | funnyordie.com        |      Yes       | 3.0.0     |
148
		 * | Polldaddy    | polldaddy.com         |      Yes       | 3.0.0     |
149
		 * | SmugMug      | smugmug.com           |      Yes       | 3.0.0     |
150
		 * | YouTube      | youtu.be              |      Yes       | 3.0.0     |
151
		 * | Twitter      | twitter.com           |      Yes       | 3.4.0     |
152
		 * | Instagram    | instagram.com         |      Yes       | 3.5.0     |
153
		 * | Instagram    | instagr.am            |      Yes       | 3.5.0     |
154
		 * | Slideshare   | slideshare.net        |      Yes       | 3.5.0     |
155
		 * | SoundCloud   | soundcloud.com        |      Yes       | 3.5.0     |
156
		 * | Dailymotion  | dai.ly                |      Yes       | 3.6.0     |
157
		 * | Flickr       | flic.kr               |      Yes       | 3.6.0     |
158
		 * | Spotify      | spotify.com           |      Yes       | 3.6.0     |
159
		 * | Imgur        | imgur.com             |      Yes       | 3.9.0     |
160
		 * | Meetup.com   | meetup.com            |      Yes       | 3.9.0     |
161
		 * | Meetup.com   | meetu.ps              |      Yes       | 3.9.0     |
162
		 * | Animoto      | animoto.com           |      Yes       | 4.0.0     |
163
		 * | Animoto      | video214.com          |      Yes       | 4.0.0     |
164
		 * | CollegeHumor | collegehumor.com      |      Yes       | 4.0.0     |
165
		 * | Issuu        | issuu.com             |      Yes       | 4.0.0     |
166
		 * | Mixcloud     | mixcloud.com          |      Yes       | 4.0.0     |
167
		 * | Polldaddy    | poll.fm               |      Yes       | 4.0.0     |
168
		 * | TED          | ted.com               |      Yes       | 4.0.0     |
169
		 * | YouTube      | youtube.com/playlist  |      Yes       | 4.0.0     |
170
		 * | Vine         | vine.co               |      Yes       | 4.1.0     |
171
		 * | Tumblr       | tumblr.com            |      Yes       | 4.2.0     |
172
		 * | Kickstarter  | kickstarter.com       |      Yes       | 4.2.0     |
173
		 * | Kickstarter  | kck.st                |      Yes       | 4.2.0     |
174
		 * | Cloudup      | cloudup.com           |      Yes       | 4.3.0     |
175
		 * | ReverbNation | reverbnation.com      |      Yes       | 4.4.0     |
176
		 * | VideoPress   | videopress.com        |      Yes       | 4.4.0     |
177
		 * | Reddit       | reddit.com            |      Yes       | 4.4.0     |
178
		 * | Speaker Deck | speakerdeck.com       |      Yes       | 4.4.0     |
179
		 * | Twitter      | twitter.com/timelines |      Yes       | 4.5.0     |
180
		 * | Twitter      | twitter.com/moments   |      Yes       | 4.5.0     |
181
		 * | Facebook     | facebook.com          |      Yes       | 4.7.0     |
182
		 * | Twitter      | twitter.com/user      |      Yes       | 4.7.0     |
183
		 * | Twitter      | twitter.com/likes     |      Yes       | 4.7.0     |
184
		 * | Twitter      | twitter.com/lists     |      Yes       | 4.7.0     |
185
		 * | Screencast   | screencast.com        |      Yes       | 4.8.0     |
186
		 *
187
		 * No longer supported providers:
188
		 *
189
		 * |   Provider   |        Flavor        | Supports HTTPS |   Since   |  Removed  |
190
		 * | ------------ | -------------------- | :------------: | --------- | --------- |
191
		 * | Qik          | qik.com              |      Yes       | 2.9.0     | 3.9.0     |
192
		 * | Viddler      | viddler.com          |      Yes       | 2.9.0     | 4.0.0     |
193
		 * | Revision3    | revision3.com        |      No        | 2.9.0     | 4.2.0     |
194
		 * | Blip         | blip.tv              |      No        | 2.9.0     | 4.4.0     |
195
		 * | Rdio         | rdio.com             |      Yes       | 3.6.0     | 4.4.1     |
196
		 * | Rdio         | rd.io                |      Yes       | 3.6.0     | 4.4.1     |
197
		 *
198
		 * @see wp_oembed_add_provider()
199
		 *
200
		 * @since 2.9.0
201
		 *
202
		 * @param array $providers An array of popular oEmbed providers.
203
		 */
204
		$this->providers = apply_filters( 'oembed_providers', $providers );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters('oembed_providers', $providers) of type * is incompatible with the declared type array of property $providers.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
205
206
		// Fix any embeds that contain new lines in the middle of the HTML which breaks wpautop().
207
		add_filter( 'oembed_dataparse', array($this, '_strip_newlines'), 10, 3 );
208
	}
209
210
	/**
211
	 * Exposes private/protected methods for backward compatibility.
212
	 *
213
	 * @since 4.0.0
214
	 * @access public
215
	 *
216
	 * @param callable $name      Method to call.
217
	 * @param array    $arguments Arguments to pass when calling.
218
	 * @return mixed|bool Return value of the callback, false otherwise.
219
	 */
220
	public function __call( $name, $arguments ) {
221
		if ( in_array( $name, $this->compat_methods ) ) {
222
			return call_user_func_array( array( $this, $name ), $arguments );
223
		}
224
		return false;
225
	}
226
227
	/**
228
	 * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one.
229
	 *
230
	 * @since 4.0.0
231
	 * @access public
232
	 *
233
	 * @see WP_oEmbed::discover()
234
	 *
235
	 * @param string        $url  The URL to the content.
236
	 * @param string|array  $args Optional provider arguments.
237
	 * @return false|string False on failure, otherwise the oEmbed provider URL.
238
	 */
239
	public function get_provider( $url, $args = '' ) {
240
		$args = wp_parse_args( $args );
241
242
		$provider = false;
243
244
		if ( !isset($args['discover']) )
245
			$args['discover'] = true;
246
247
		foreach ( $this->providers as $matchmask => $data ) {
248
			list( $providerurl, $regex ) = $data;
249
250
			// Turn the asterisk-type provider URLs into regex
251
			if ( !$regex ) {
252
				$matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i';
253
				$matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask );
254
			}
255
256
			if ( preg_match( $matchmask, $url ) ) {
257
				$provider = str_replace( '{format}', 'json', $providerurl ); // JSON is easier to deal with than XML
258
				break;
259
			}
260
		}
261
262
		if ( !$provider && $args['discover'] )
263
			$provider = $this->discover( $url );
264
265
		return $provider;
266
	}
267
268
	/**
269
	 * Adds an oEmbed provider.
270
	 *
271
	 * The provider is added just-in-time when wp_oembed_add_provider() is called before
272
	 * the {@see 'plugins_loaded'} hook.
273
	 *
274
	 * The just-in-time addition is for the benefit of the {@see 'oembed_providers'} filter.
275
	 *
276
	 * @static
277
	 * @since 4.0.0
278
	 * @access public
279
	 *
280
	 * @see wp_oembed_add_provider()
281
	 *
282
	 * @param string $format   Format of URL that this provider can handle. You can use
283
	 *                         asterisks as wildcards.
284
	 * @param string $provider The URL to the oEmbed provider..
285
	 * @param bool   $regex    Optional. Whether the $format parameter is in a regex format.
286
	 *                         Default false.
287
	 */
288
	public static function _add_provider_early( $format, $provider, $regex = false ) {
289
		if ( empty( self::$early_providers['add'] ) ) {
290
			self::$early_providers['add'] = array();
291
		}
292
293
		self::$early_providers['add'][ $format ] = array( $provider, $regex );
294
	}
295
296
	/**
297
	 * Removes an oEmbed provider.
298
	 *
299
	 * The provider is removed just-in-time when wp_oembed_remove_provider() is called before
300
	 * the {@see 'plugins_loaded'} hook.
301
	 *
302
	 * The just-in-time removal is for the benefit of the {@see 'oembed_providers'} filter.
303
	 *
304
	 * @since 4.0.0
305
	 * @access public
306
	 * @static
307
	 *
308
	 * @see wp_oembed_remove_provider()
309
	 *
310
	 * @param string $format The format of URL that this provider can handle. You can use
311
	 *                       asterisks as wildcards.
312
	 */
313
	public static function _remove_provider_early( $format ) {
314
		if ( empty( self::$early_providers['remove'] ) ) {
315
			self::$early_providers['remove'] = array();
316
		}
317
318
		self::$early_providers['remove'][] = $format;
319
	}
320
321
	/**
322
	 * Takes a URL and attempts to return the oEmbed data.
323
	 *
324
	 * @see WP_oEmbed::fetch()
325
	 *
326
	 * @since 4.8.0
327
	 * @access public
328
	 *
329
	 * @param string       $url  The URL to the content that should be attempted to be embedded.
330
	 * @param array|string $args Optional. Arguments, usually passed from a shortcode. Default empty.
331
	 * @return false|object False on failure, otherwise the result in the form of an object.
332
	 */
333
	public function get_data( $url, $args = '' ) {
334
		$args = wp_parse_args( $args );
335
336
		$provider = $this->get_provider( $url, $args );
337
338
		if ( ! $provider ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $provider of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
339
			return false;
340
		}
341
342
		$data = $this->fetch( $provider, $url, $args );
343
344
		if ( false === $data ) {
345
			return false;
346
		}
347
348
		return $data;
349
	}
350
351
	/**
352
	 * The do-it-all function that takes a URL and attempts to return the HTML.
353
	 *
354
	 * @see WP_oEmbed::fetch()
355
	 * @see WP_oEmbed::data2html()
356
	 *
357
	 * @since 2.9.0
358
	 * @access public
359
	 *
360
	 * @param string       $url  The URL to the content that should be attempted to be embedded.
361
	 * @param array|string $args Optional. Arguments, usually passed from a shortcode. Default empty.
362
	 * @return false|string False on failure, otherwise the UNSANITIZED (and potentially unsafe) HTML that should be used to embed.
363
	 */
364
	public function get_html( $url, $args = '' ) {
365
		/**
366
		 * Filters the oEmbed result before any HTTP requests are made.
367
		 *
368
		 * This allows one to short-circuit the default logic, perhaps by
369
		 * replacing it with a routine that is more optimal for your setup.
370
		 *
371
		 * Passing a non-null value to the filter will effectively short-circuit retrieval,
372
		 * returning the passed value instead.
373
		 *
374
		 * @since 4.5.3
375
		 *
376
		 * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. Default null.
377
		 * @param string      $url    The URL to the content that should be attempted to be embedded.
378
		 * @param array       $args   Optional. Arguments, usually passed from a shortcode. Default empty.
379
		 */
380
		$pre = apply_filters( 'pre_oembed_result', null, $url, $args );
381
382
		if ( null !== $pre ) {
383
			return $pre;
384
		}
385
386
		$data = $this->get_data( $url, $args );
387
388
		if ( false === $data ) {
389
			return false;
390
		}
391
392
		/**
393
		 * Filters the HTML returned by the oEmbed provider.
394
		 *
395
		 * @since 2.9.0
396
		 *
397
		 * @param string $data The returned oEmbed HTML.
398
		 * @param string $url  URL of the content to be embedded.
399
		 * @param array  $args Optional arguments, usually passed from a shortcode.
400
		 */
401
		return apply_filters( 'oembed_result', $this->data2html( $data, $url ), $url, $args );
402
	}
403
404
	/**
405
	 * Attempts to discover link tags at the given URL for an oEmbed provider.
406
	 *
407
	 * @since 2.9.0
408
	 * @access public
409
	 *
410
	 * @param string $url The URL that should be inspected for discovery `<link>` tags.
411
	 * @return false|string False on failure, otherwise the oEmbed provider URL.
412
	 */
413
	public function discover( $url ) {
414
		$providers = array();
415
		$args = array(
416
			'limit_response_size' => 153600, // 150 KB
417
		);
418
419
		/**
420
		 * Filters oEmbed remote get arguments.
421
		 *
422
		 * @since 4.0.0
423
		 *
424
		 * @see WP_Http::request()
425
		 *
426
		 * @param array  $args oEmbed remote get arguments.
427
		 * @param string $url  URL to be inspected.
428
		 */
429
		$args = apply_filters( 'oembed_remote_get_args', $args, $url );
430
431
		// Fetch URL content
432
		$request = wp_safe_remote_get( $url, $args );
433
		if ( $html = wp_remote_retrieve_body( $request ) ) {
434
435
			/**
436
			 * Filters the link types that contain oEmbed provider URLs.
437
			 *
438
			 * @since 2.9.0
439
			 *
440
			 * @param array $format Array of oEmbed link types. Accepts 'application/json+oembed',
441
			 *                      'text/xml+oembed', and 'application/xml+oembed' (incorrect,
442
			 *                      used by at least Vimeo).
443
			 */
444
			$linktypes = apply_filters( 'oembed_linktypes', array(
445
				'application/json+oembed' => 'json',
446
				'text/xml+oembed' => 'xml',
447
				'application/xml+oembed' => 'xml',
448
			) );
449
450
			// Strip <body>
451
			if ( $html_head_end = stripos( $html, '</head>' ) ) {
452
				$html = substr( $html, 0, $html_head_end );
453
			}
454
455
			// Do a quick check
456
			$tagfound = false;
457
			foreach ( $linktypes as $linktype => $format ) {
458
				if ( stripos($html, $linktype) ) {
459
					$tagfound = true;
460
					break;
461
				}
462
			}
463
464
			if ( $tagfound && preg_match_all( '#<link([^<>]+)/?>#iU', $html, $links ) ) {
465
				foreach ( $links[1] as $link ) {
466
					$atts = shortcode_parse_atts( $link );
467
468
					if ( !empty($atts['type']) && !empty($linktypes[$atts['type']]) && !empty($atts['href']) ) {
469
						$providers[$linktypes[$atts['type']]] = htmlspecialchars_decode( $atts['href'] );
470
471
						// Stop here if it's JSON (that's all we need)
472
						if ( 'json' == $linktypes[$atts['type']] )
473
							break;
474
					}
475
				}
476
			}
477
		}
478
479
		// JSON is preferred to XML
480
		if ( !empty($providers['json']) )
481
			return $providers['json'];
482
		elseif ( !empty($providers['xml']) )
483
			return $providers['xml'];
484
		else
485
			return false;
486
	}
487
488
	/**
489
	 * Connects to a oEmbed provider and returns the result.
490
	 *
491
	 * @since 2.9.0
492
	 * @access public
493
	 *
494
	 * @param string       $provider The URL to the oEmbed provider.
495
	 * @param string       $url      The URL to the content that is desired to be embedded.
496
	 * @param array|string $args     Optional. Arguments, usually passed from a shortcode. Default empty.
497
	 * @return false|object False on failure, otherwise the result in the form of an object.
498
	 */
499
	public function fetch( $provider, $url, $args = '' ) {
500
		$args = wp_parse_args( $args, wp_embed_defaults( $url ) );
501
502
		$provider = add_query_arg( 'maxwidth', (int) $args['width'], $provider );
503
		$provider = add_query_arg( 'maxheight', (int) $args['height'], $provider );
504
		$provider = add_query_arg( 'url', urlencode($url), $provider );
505
506
		/**
507
		 * Filters the oEmbed URL to be fetched.
508
		 *
509
		 * @since 2.9.0
510
		 *
511
		 * @param string $provider URL of the oEmbed provider.
512
		 * @param string $url      URL of the content to be embedded.
513
		 * @param array  $args     Optional arguments, usually passed from a shortcode.
514
		 */
515
		$provider = apply_filters( 'oembed_fetch_url', $provider, $url, $args );
516
517
		foreach ( array( 'json', 'xml' ) as $format ) {
518
			$result = $this->_fetch_with_format( $provider, $format );
519
			if ( is_wp_error( $result ) && 'not-implemented' == $result->get_error_code() )
520
				continue;
521
			return ( $result && ! is_wp_error( $result ) ) ? $result : false;
522
		}
523
		return false;
524
	}
525
526
	/**
527
	 * Fetches result from an oEmbed provider for a specific format and complete provider URL
528
	 *
529
	 * @since 3.0.0
530
	 * @access private
531
	 *
532
	 * @param string $provider_url_with_args URL to the provider with full arguments list (url, maxheight, etc.)
533
	 * @param string $format Format to use
534
	 * @return false|object|WP_Error False on failure, otherwise the result in the form of an object.
535
	 */
536
	private function _fetch_with_format( $provider_url_with_args, $format ) {
537
		$provider_url_with_args = add_query_arg( 'format', $format, $provider_url_with_args );
538
539
		/** This filter is documented in wp-includes/class-oembed.php */
540
		$args = apply_filters( 'oembed_remote_get_args', array(), $provider_url_with_args );
541
542
		$response = wp_safe_remote_get( $provider_url_with_args, $args );
543
		if ( 501 == wp_remote_retrieve_response_code( $response ) )
544
			return new WP_Error( 'not-implemented' );
545
		if ( ! $body = wp_remote_retrieve_body( $response ) )
546
			return false;
547
		$parse_method = "_parse_$format";
548
		return $this->$parse_method( $body );
549
	}
550
551
	/**
552
	 * Parses a json response body.
553
	 *
554
	 * @since 3.0.0
555
	 * @access private
556
	 *
557
	 * @param string $response_body
558
	 * @return object|false
0 ignored issues
show
Consider making the return type a bit more specific; maybe use stdClass|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
559
	 */
560
	private function _parse_json( $response_body ) {
561
		$data = json_decode( trim( $response_body ) );
562
		return ( $data && is_object( $data ) ) ? $data : false;
563
	}
564
565
	/**
566
	 * Parses an XML response body.
567
	 *
568
	 * @since 3.0.0
569
	 * @access private
570
	 *
571
	 * @param string $response_body
572
	 * @return object|false
0 ignored issues
show
Consider making the return type a bit more specific; maybe use false|stdClass.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
573
	 */
574
	private function _parse_xml( $response_body ) {
575
		if ( ! function_exists( 'libxml_disable_entity_loader' ) )
576
			return false;
577
578
		$loader = libxml_disable_entity_loader( true );
579
		$errors = libxml_use_internal_errors( true );
580
581
		$return = $this->_parse_xml_body( $response_body );
582
583
		libxml_use_internal_errors( $errors );
584
		libxml_disable_entity_loader( $loader );
585
586
		return $return;
587
	}
588
589
	/**
590
	 * Serves as a helper function for parsing an XML response body.
591
	 *
592
	 * @since 3.6.0
593
	 * @access private
594
	 *
595
	 * @param string $response_body
596
	 * @return stdClass|false
597
	 */
598
	private function _parse_xml_body( $response_body ) {
599
		if ( ! function_exists( 'simplexml_import_dom' ) || ! class_exists( 'DOMDocument', false ) )
600
			return false;
601
602
		$dom = new DOMDocument;
603
		$success = $dom->loadXML( $response_body );
604
		if ( ! $success )
605
			return false;
606
607
		if ( isset( $dom->doctype ) )
608
			return false;
609
610
		foreach ( $dom->childNodes as $child ) {
611
			if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType )
612
				return false;
613
		}
614
615
		$xml = simplexml_import_dom( $dom );
616
		if ( ! $xml )
617
			return false;
618
619
		$return = new stdClass;
620
		foreach ( $xml as $key => $value ) {
621
			$return->$key = (string) $value;
622
		}
623
624
		return $return;
625
	}
626
627
	/**
628
	 * Converts a data object from WP_oEmbed::fetch() and returns the HTML.
629
	 *
630
	 * @since 2.9.0
631
	 * @access public
632
	 *
633
	 * @param object $data A data object result from an oEmbed provider.
634
	 * @param string $url The URL to the content that is desired to be embedded.
635
	 * @return false|string False on error, otherwise the HTML needed to embed.
636
	 */
637
	public function data2html( $data, $url ) {
638
		if ( ! is_object( $data ) || empty( $data->type ) )
639
			return false;
640
641
		$return = false;
642
643
		switch ( $data->type ) {
644
			case 'photo':
645
				if ( empty( $data->url ) || empty( $data->width ) || empty( $data->height ) )
646
					break;
647
				if ( ! is_string( $data->url ) || ! is_numeric( $data->width ) || ! is_numeric( $data->height ) )
648
					break;
649
650
				$title = ! empty( $data->title ) && is_string( $data->title ) ? $data->title : '';
651
				$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>';
652
				break;
653
654
			case 'video':
655
			case 'rich':
656
				if ( ! empty( $data->html ) && is_string( $data->html ) )
657
					$return = $data->html;
658
				break;
659
660
			case 'link':
661
				if ( ! empty( $data->title ) && is_string( $data->title ) )
662
					$return = '<a href="' . esc_url( $url ) . '">' . esc_html( $data->title ) . '</a>';
663
				break;
664
665
			default:
666
				$return = false;
667
		}
668
669
		/**
670
		 * Filters the returned oEmbed HTML.
671
		 *
672
		 * Use this filter to add support for custom data types, or to filter the result.
673
		 *
674
		 * @since 2.9.0
675
		 *
676
		 * @param string $return The returned oEmbed HTML.
677
		 * @param object $data   A data object result from an oEmbed provider.
678
		 * @param string $url    The URL of the content to be embedded.
679
		 */
680
		return apply_filters( 'oembed_dataparse', $return, $data, $url );
681
	}
682
683
	/**
684
	 * Strips any new lines from the HTML.
685
	 *
686
	 * @since 2.9.0 as strip_scribd_newlines()
687
	 * @since 3.0.0
688
	 * @access public
689
	 *
690
	 * @param string $html Existing HTML.
691
	 * @param object $data Data object from WP_oEmbed::data2html()
692
	 * @param string $url The original URL passed to oEmbed.
693
	 * @return string Possibly modified $html
694
	 */
695
	public function _strip_newlines( $html, $data, $url ) {
0 ignored issues
show
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $url is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
696
		if ( false === strpos( $html, "\n" ) ) {
697
			return $html;
698
		}
699
700
		$count = 1;
701
		$found = array();
702
		$token = '__PRE__';
703
		$search = array( "\t", "\n", "\r", ' ' );
704
		$replace = array( '__TAB__', '__NL__', '__CR__', '__SPACE__' );
705
		$tokenized = str_replace( $search, $replace, $html );
706
707
		preg_match_all( '#(<pre[^>]*>.+?</pre>)#i', $tokenized, $matches, PREG_SET_ORDER );
708
		foreach ( $matches as $i => $match ) {
0 ignored issues
show
The expression $matches of type null|array<integer,array<integer,string>> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
709
			$tag_html = str_replace( $replace, $search, $match[0] );
710
			$tag_token = $token . $i;
711
712
			$found[ $tag_token ] = $tag_html;
713
			$html = str_replace( $tag_html, $tag_token, $html, $count );
714
		}
715
716
		$replaced = str_replace( $replace, $search, $html );
717
		$stripped = str_replace( array( "\r\n", "\n" ), '', $replaced );
718
		$pre = array_values( $found );
719
		$tokens = array_keys( $found );
720
721
		return str_replace( $tokens, $pre, $stripped );
722
	}
723
}
724