/wp-includes/class-oembed.php

  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_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/1.0/?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. ); 
  112.  
  113. if ( ! empty( self::$early_providers['add'] ) ) { 
  114. foreach ( self::$early_providers['add'] as $format => $data ) { 
  115. $providers[ $format ] = $data; 
  116.  
  117. if ( ! empty( self::$early_providers['remove'] ) ) { 
  118. foreach ( self::$early_providers['remove'] as $format ) { 
  119. unset( $providers[ $format ] ); 
  120.  
  121. self::$early_providers = array(); 
  122.  
  123. /** 
  124. * Filters the list of whitelisted oEmbed providers. 
  125. * 
  126. * Since WordPress 4.4, oEmbed discovery is enabled for all users and allows embedding of sanitized 
  127. * iframes. The providers in this list are whitelisted, meaning they are trusted and allowed to 
  128. * embed any content, such as iframes, videos, JavaScript, and arbitrary HTML. 
  129. * 
  130. * Supported providers: 
  131. * 
  132. * | Provider | Flavor | Supports HTTPS | Since | 
  133. * | ------------ | --------------------- | :------------: | --------- | 
  134. * | Dailymotion | dailymotion.com | Yes | 2.9.0 | 
  135. * | Flickr | flickr.com | Yes | 2.9.0 | 
  136. * | Hulu | hulu.com | Yes | 2.9.0 | 
  137. * | Photobucket | photobucket.com | No | 2.9.0 | 
  138. * | Scribd | scribd.com | Yes | 2.9.0 | 
  139. * | Vimeo | vimeo.com | Yes | 2.9.0 | 
  140. * | WordPress.tv | wordpress.tv | Yes | 2.9.0 | 
  141. * | YouTube | youtube.com/watch | Yes | 2.9.0 | 
  142. * | Funny or Die | funnyordie.com | Yes | 3.0.0 | 
  143. * | Polldaddy | polldaddy.com | Yes | 3.0.0 | 
  144. * | SmugMug | smugmug.com | Yes | 3.0.0 | 
  145. * | YouTube | youtu.be | Yes | 3.0.0 | 
  146. * | Twitter | twitter.com | Yes | 3.4.0 | 
  147. * | Instagram | instagram.com | Yes | 3.5.0 | 
  148. * | Instagram | instagr.am | Yes | 3.5.0 | 
  149. * | Slideshare | slideshare.net | Yes | 3.5.0 | 
  150. * | SoundCloud | soundcloud.com | Yes | 3.5.0 | 
  151. * | Dailymotion | dai.ly | Yes | 3.6.0 | 
  152. * | Flickr | flic.kr | Yes | 3.6.0 | 
  153. * | Spotify | spotify.com | Yes | 3.6.0 | 
  154. * | Imgur | imgur.com | Yes | 3.9.0 | 
  155. * | Meetup.com | meetup.com | Yes | 3.9.0 | 
  156. * | Meetup.com | meetu.ps | Yes | 3.9.0 | 
  157. * | Animoto | animoto.com | Yes | 4.0.0 | 
  158. * | Animoto | video214.com | Yes | 4.0.0 | 
  159. * | CollegeHumor | collegehumor.com | Yes | 4.0.0 | 
  160. * | Issuu | issuu.com | Yes | 4.0.0 | 
  161. * | Mixcloud | mixcloud.com | Yes | 4.0.0 | 
  162. * | Polldaddy | poll.fm | Yes | 4.0.0 | 
  163. * | TED | ted.com | Yes | 4.0.0 | 
  164. * | YouTube | youtube.com/playlist | Yes | 4.0.0 | 
  165. * | Vine | vine.co | Yes | 4.1.0 | 
  166. * | Tumblr | tumblr.com | Yes | 4.2.0 | 
  167. * | Kickstarter | kickstarter.com | Yes | 4.2.0 | 
  168. * | Kickstarter | kck.st | Yes | 4.2.0 | 
  169. * | Cloudup | cloudup.com | Yes | 4.3.0 | 
  170. * | ReverbNation | reverbnation.com | Yes | 4.4.0 | 
  171. * | VideoPress | videopress.com | Yes | 4.4.0 | 
  172. * | Reddit | reddit.com | Yes | 4.4.0 | 
  173. * | Speaker Deck | speakerdeck.com | Yes | 4.4.0 | 
  174. * | Twitter | twitter.com/timelines | Yes | 4.5.0 | 
  175. * | Twitter | twitter.com/moments | Yes | 4.5.0 | 
  176. * | Facebook | facebook.com | Yes | 4.7.0 | 
  177. * | Twitter | twitter.com/user | Yes | 4.7.0 | 
  178. * | Twitter | twitter.com/likes | Yes | 4.7.0 | 
  179. * | Twitter | twitter.com/lists | Yes | 4.7.0 | 
  180. * 
  181. * No longer supported providers: 
  182. * 
  183. * | Provider | Flavor | Supports HTTPS | Since | Removed | 
  184. * | ------------ | -------------------- | :------------: | --------- | --------- | 
  185. * | Qik | qik.com | Yes | 2.9.0 | 3.9.0 | 
  186. * | Viddler | viddler.com | Yes | 2.9.0 | 4.0.0 | 
  187. * | Revision3 | revision3.com | No | 2.9.0 | 4.2.0 | 
  188. * | Blip | blip.tv | No | 2.9.0 | 4.4.0 | 
  189. * | Rdio | rdio.com | Yes | 3.6.0 | 4.4.1 | 
  190. * | Rdio | rd.io | Yes | 3.6.0 | 4.4.1 | 
  191. * 
  192. * @see wp_oembed_add_provider() 
  193. * 
  194. * @since 2.9.0 
  195. * 
  196. * @param array $providers An array of popular oEmbed providers. 
  197. */ 
  198. $this->providers = apply_filters( 'oembed_providers', $providers ); 
  199.  
  200. // Fix any embeds that contain new lines in the middle of the HTML which breaks wpautop(). 
  201. add_filter( 'oembed_dataparse', array($this, '_strip_newlines'), 10, 3 ); 
  202.  
  203. /** 
  204. * Exposes private/protected methods for backward compatibility. 
  205. * 
  206. * @since 4.0.0 
  207. * @access public 
  208. * 
  209. * @param callable $name Method to call. 
  210. * @param array $arguments Arguments to pass when calling. 
  211. * @return mixed|bool Return value of the callback, false otherwise. 
  212. */ 
  213. public function __call( $name, $arguments ) { 
  214. if ( in_array( $name, $this->compat_methods ) ) { 
  215. return call_user_func_array( array( $this, $name ), $arguments ); 
  216. return false; 
  217.  
  218. /** 
  219. * Takes a URL and returns the corresponding oEmbed provider's URL, if there is one. 
  220. * 
  221. * @since 4.0.0 
  222. * @access public 
  223. * 
  224. * @see WP_oEmbed::discover() 
  225. * 
  226. * @param string $url The URL to the content. 
  227. * @param string|array $args Optional provider arguments. 
  228. * @return false|string False on failure, otherwise the oEmbed provider URL. 
  229. */ 
  230. public function get_provider( $url, $args = '' ) { 
  231. $args = wp_parse_args( $args ); 
  232.  
  233. $provider = false; 
  234.  
  235. if ( !isset($args['discover']) ) 
  236. $args['discover'] = true; 
  237.  
  238. foreach ( $this->providers as $matchmask => $data ) { 
  239. list( $providerurl, $regex ) = $data; 
  240.  
  241. // Turn the asterisk-type provider URLs into regex 
  242. if ( !$regex ) { 
  243. $matchmask = '#' . str_replace( '___wildcard___', '(.+)', preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) ) . '#i'; 
  244. $matchmask = preg_replace( '|^#http\\\://|', '#https?\://', $matchmask ); 
  245.  
  246. if ( preg_match( $matchmask, $url ) ) { 
  247. $provider = str_replace( '{format}', 'json', $providerurl ); // JSON is easier to deal with than XML 
  248. break; 
  249.  
  250. if ( !$provider && $args['discover'] ) 
  251. $provider = $this->discover( $url ); 
  252.  
  253. return $provider; 
  254.  
  255. /** 
  256. * Adds an oEmbed provider. 
  257. * 
  258. * The provider is added just-in-time when wp_oembed_add_provider() is called before 
  259. * the {@see 'plugins_loaded'} hook. 
  260. * 
  261. * The just-in-time addition is for the benefit of the {@see 'oembed_providers'} filter. 
  262. * 
  263. * @static 
  264. * @since 4.0.0 
  265. * @access public 
  266. * 
  267. * @see wp_oembed_add_provider() 
  268. * 
  269. * @param string $format Format of URL that this provider can handle. You can use 
  270. * asterisks as wildcards. 
  271. * @param string $provider The URL to the oEmbed provider.. 
  272. * @param bool $regex Optional. Whether the $format parameter is in a regex format. 
  273. * Default false. 
  274. */ 
  275. public static function _add_provider_early( $format, $provider, $regex = false ) { 
  276. if ( empty( self::$early_providers['add'] ) ) { 
  277. self::$early_providers['add'] = array(); 
  278.  
  279. self::$early_providers['add'][ $format ] = array( $provider, $regex ); 
  280.  
  281. /** 
  282. * Removes an oEmbed provider. 
  283. * 
  284. * The provider is removed just-in-time when wp_oembed_remove_provider() is called before 
  285. * the {@see 'plugins_loaded'} hook. 
  286. * 
  287. * The just-in-time removal is for the benefit of the {@see 'oembed_providers'} filter. 
  288. * 
  289. * @since 4.0.0 
  290. * @access public 
  291. * @static 
  292. * 
  293. * @see wp_oembed_remove_provider() 
  294. * 
  295. * @param string $format The format of URL that this provider can handle. You can use 
  296. * asterisks as wildcards. 
  297. */ 
  298. public static function _remove_provider_early( $format ) { 
  299. if ( empty( self::$early_providers['remove'] ) ) { 
  300. self::$early_providers['remove'] = array(); 
  301.  
  302. self::$early_providers['remove'][] = $format; 
  303.  
  304. /** 
  305. * The do-it-all function that takes a URL and attempts to return the HTML. 
  306. * 
  307. * @see WP_oEmbed::fetch() 
  308. * @see WP_oEmbed::data2html() 
  309. * 
  310. * @since 2.9.0 
  311. * @access public 
  312. * 
  313. * @param string $url The URL to the content that should be attempted to be embedded. 
  314. * @param array|string $args Optional. Arguments, usually passed from a shortcode. Default empty. 
  315. * @return false|string False on failure, otherwise the UNSANITIZED (and potentially unsafe) HTML that should be used to embed. 
  316. */ 
  317. public function get_html( $url, $args = '' ) { 
  318. $args = wp_parse_args( $args ); 
  319.  
  320. /** 
  321. * Filters the oEmbed result before any HTTP requests are made. 
  322. * 
  323. * This allows one to short-circuit the default logic, perhaps by 
  324. * replacing it with a routine that is more optimal for your setup. 
  325. * 
  326. * Passing a non-null value to the filter will effectively short-circuit retrieval,  
  327. * returning the passed value instead. 
  328. * 
  329. * @since 4.5.3 
  330. * 
  331. * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. Default null. 
  332. * @param string $url The URL to the content that should be attempted to be embedded. 
  333. * @param array $args Optional. Arguments, usually passed from a shortcode. Default empty. 
  334. */ 
  335. $pre = apply_filters( 'pre_oembed_result', null, $url, $args ); 
  336.  
  337. if ( null !== $pre ) { 
  338. return $pre; 
  339.  
  340. $provider = $this->get_provider( $url, $args ); 
  341.  
  342. if ( ! $provider || false === $data = $this->fetch( $provider, $url, $args ) ) { 
  343. return false; 
  344.  
  345. /** 
  346. * Filters the HTML returned by the oEmbed provider. 
  347. * 
  348. * @since 2.9.0 
  349. * 
  350. * @param string $data The returned oEmbed HTML. 
  351. * @param string $url URL of the content to be embedded. 
  352. * @param array $args Optional arguments, usually passed from a shortcode. 
  353. */ 
  354. return apply_filters( 'oembed_result', $this->data2html( $data, $url ), $url, $args ); 
  355.  
  356. /** 
  357. * Attempts to discover link tags at the given URL for an oEmbed provider. 
  358. * 
  359. * @since 2.9.0 
  360. * @access public 
  361. * 
  362. * @param string $url The URL that should be inspected for discovery `<link>` tags. 
  363. * @return false|string False on failure, otherwise the oEmbed provider URL. 
  364. */ 
  365. public function discover( $url ) { 
  366. $providers = array(); 
  367. $args = array( 
  368. 'limit_response_size' => 153600, // 150 KB 
  369. ); 
  370.  
  371. /** 
  372. * Filters oEmbed remote get arguments. 
  373. * 
  374. * @since 4.0.0 
  375. * 
  376. * @see WP_Http::request() 
  377. * 
  378. * @param array $args oEmbed remote get arguments. 
  379. * @param string $url URL to be inspected. 
  380. */ 
  381. $args = apply_filters( 'oembed_remote_get_args', $args, $url ); 
  382.  
  383. // Fetch URL content 
  384. $request = wp_safe_remote_get( $url, $args ); 
  385. if ( $html = wp_remote_retrieve_body( $request ) ) { 
  386.  
  387. /** 
  388. * Filters the link types that contain oEmbed provider URLs. 
  389. * 
  390. * @since 2.9.0 
  391. * 
  392. * @param array $format Array of oEmbed link types. Accepts 'application/json+oembed',  
  393. * 'text/xml+oembed', and 'application/xml+oembed' (incorrect,  
  394. * used by at least Vimeo). 
  395. */ 
  396. $linktypes = apply_filters( 'oembed_linktypes', array( 
  397. 'application/json+oembed' => 'json',  
  398. 'text/xml+oembed' => 'xml',  
  399. 'application/xml+oembed' => 'xml',  
  400. ) ); 
  401.  
  402. // Strip <body> 
  403. if ( $html_head_end = stripos( $html, '</head>' ) ) { 
  404. $html = substr( $html, 0, $html_head_end ); 
  405.  
  406. // Do a quick check 
  407. $tagfound = false; 
  408. foreach ( $linktypes as $linktype => $format ) { 
  409. if ( stripos($html, $linktype) ) { 
  410. $tagfound = true; 
  411. break; 
  412.  
  413. if ( $tagfound && preg_match_all( '#<link([^<>]+)/?>#iU', $html, $links ) ) { 
  414. foreach ( $links[1] as $link ) { 
  415. $atts = shortcode_parse_atts( $link ); 
  416.  
  417. if ( !empty($atts['type']) && !empty($linktypes[$atts['type']]) && !empty($atts['href']) ) { 
  418. $providers[$linktypes[$atts['type']]] = htmlspecialchars_decode( $atts['href'] ); 
  419.  
  420. // Stop here if it's JSON (that's all we need) 
  421. if ( 'json' == $linktypes[$atts['type']] ) 
  422. break; 
  423.  
  424. // JSON is preferred to XML 
  425. if ( !empty($providers['json']) ) 
  426. return $providers['json']; 
  427. elseif ( !empty($providers['xml']) ) 
  428. return $providers['xml']; 
  429. else 
  430. return false; 
  431.  
  432. /** 
  433. * Connects to a oEmbed provider and returns the result. 
  434. * 
  435. * @since 2.9.0 
  436. * @access public 
  437. * 
  438. * @param string $provider The URL to the oEmbed provider. 
  439. * @param string $url The URL to the content that is desired to be embedded. 
  440. * @param array|string $args Optional. Arguments, usually passed from a shortcode. Default empty. 
  441. * @return false|object False on failure, otherwise the result in the form of an object. 
  442. */ 
  443. public function fetch( $provider, $url, $args = '' ) { 
  444. $args = wp_parse_args( $args, wp_embed_defaults( $url ) ); 
  445.  
  446. $provider = add_query_arg( 'maxwidth', (int) $args['width'], $provider ); 
  447. $provider = add_query_arg( 'maxheight', (int) $args['height'], $provider ); 
  448. $provider = add_query_arg( 'url', urlencode($url), $provider ); 
  449.  
  450. /** 
  451. * Filters the oEmbed URL to be fetched. 
  452. * 
  453. * @since 2.9.0 
  454. * 
  455. * @param string $provider URL of the oEmbed provider. 
  456. * @param string $url URL of the content to be embedded. 
  457. * @param array $args Optional arguments, usually passed from a shortcode. 
  458. */ 
  459. $provider = apply_filters( 'oembed_fetch_url', $provider, $url, $args ); 
  460.  
  461. foreach ( array( 'json', 'xml' ) as $format ) { 
  462. $result = $this->_fetch_with_format( $provider, $format ); 
  463. if ( is_wp_error( $result ) && 'not-implemented' == $result->get_error_code() ) 
  464. continue; 
  465. return ( $result && ! is_wp_error( $result ) ) ? $result : false; 
  466. return false; 
  467.  
  468. /** 
  469. * Fetches result from an oEmbed provider for a specific format and complete provider URL 
  470. * 
  471. * @since 3.0.0 
  472. * @access private 
  473. * 
  474. * @param string $provider_url_with_args URL to the provider with full arguments list (url, maxheight, etc.) 
  475. * @param string $format Format to use 
  476. * @return false|object|WP_Error False on failure, otherwise the result in the form of an object. 
  477. */ 
  478. private function _fetch_with_format( $provider_url_with_args, $format ) { 
  479. $provider_url_with_args = add_query_arg( 'format', $format, $provider_url_with_args ); 
  480.  
  481. /** This filter is documented in wp-includes/class-oembed.php */ 
  482. $args = apply_filters( 'oembed_remote_get_args', array(), $provider_url_with_args ); 
  483.  
  484. $response = wp_safe_remote_get( $provider_url_with_args, $args ); 
  485. if ( 501 == wp_remote_retrieve_response_code( $response ) ) 
  486. return new WP_Error( 'not-implemented' ); 
  487. if ( ! $body = wp_remote_retrieve_body( $response ) ) 
  488. return false; 
  489. $parse_method = "_parse_$format"; 
  490. return $this->$parse_method( $body ); 
  491.  
  492. /** 
  493. * Parses a json response body. 
  494. * 
  495. * @since 3.0.0 
  496. * @access private 
  497. * 
  498. * @param string $response_body 
  499. * @return object|false 
  500. */ 
  501. private function _parse_json( $response_body ) { 
  502. $data = json_decode( trim( $response_body ) ); 
  503. return ( $data && is_object( $data ) ) ? $data : false; 
  504.  
  505. /** 
  506. * Parses an XML response body. 
  507. * 
  508. * @since 3.0.0 
  509. * @access private 
  510. * 
  511. * @param string $response_body 
  512. * @return object|false 
  513. */ 
  514. private function _parse_xml( $response_body ) { 
  515. if ( ! function_exists( 'libxml_disable_entity_loader' ) ) 
  516. return false; 
  517.  
  518. $loader = libxml_disable_entity_loader( true ); 
  519. $errors = libxml_use_internal_errors( true ); 
  520.  
  521. $return = $this->_parse_xml_body( $response_body ); 
  522.  
  523. libxml_use_internal_errors( $errors ); 
  524. libxml_disable_entity_loader( $loader ); 
  525.  
  526. return $return; 
  527.  
  528. /** 
  529. * Serves as a helper function for parsing an XML response body. 
  530. * 
  531. * @since 3.6.0 
  532. * @access private 
  533. * 
  534. * @param string $response_body 
  535. * @return stdClass|false 
  536. */ 
  537. private function _parse_xml_body( $response_body ) { 
  538. if ( ! function_exists( 'simplexml_import_dom' ) || ! class_exists( 'DOMDocument', false ) ) 
  539. return false; 
  540.  
  541. $dom = new DOMDocument; 
  542. $success = $dom->loadXML( $response_body ); 
  543. if ( ! $success ) 
  544. return false; 
  545.  
  546. if ( isset( $dom->doctype ) ) 
  547. return false; 
  548.  
  549. foreach ( $dom->childNodes as $child ) { 
  550. if ( XML_DOCUMENT_TYPE_NODE === $child->nodeType ) 
  551. return false; 
  552.  
  553. $xml = simplexml_import_dom( $dom ); 
  554. if ( ! $xml ) 
  555. return false; 
  556.  
  557. $return = new stdClass; 
  558. foreach ( $xml as $key => $value ) { 
  559. $return->$key = (string) $value; 
  560.  
  561. return $return; 
  562.  
  563. /** 
  564. * Converts a data object from WP_oEmbed::fetch() and returns the HTML. 
  565. * 
  566. * @since 2.9.0 
  567. * @access public 
  568. * 
  569. * @param object $data A data object result from an oEmbed provider. 
  570. * @param string $url The URL to the content that is desired to be embedded. 
  571. * @return false|string False on error, otherwise the HTML needed to embed. 
  572. */ 
  573. public function data2html( $data, $url ) { 
  574. if ( ! is_object( $data ) || empty( $data->type ) ) 
  575. return false; 
  576.  
  577. $return = false; 
  578.  
  579. switch ( $data->type ) { 
  580. case 'photo': 
  581. if ( empty( $data->url ) || empty( $data->width ) || empty( $data->height ) ) 
  582. break; 
  583. if ( ! is_string( $data->url ) || ! is_numeric( $data->width ) || ! is_numeric( $data->height ) ) 
  584. break; 
  585.  
  586. $title = ! empty( $data->title ) && is_string( $data->title ) ? $data->title : ''; 
  587. $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>'; 
  588. break; 
  589.  
  590. case 'video': 
  591. case 'rich': 
  592. if ( ! empty( $data->html ) && is_string( $data->html ) ) 
  593. $return = $data->html; 
  594. break; 
  595.  
  596. case 'link': 
  597. if ( ! empty( $data->title ) && is_string( $data->title ) ) 
  598. $return = '<a href="' . esc_url( $url ) . '">' . esc_html( $data->title ) . '</a>'; 
  599. break; 
  600.  
  601. default: 
  602. $return = false; 
  603.  
  604. /** 
  605. * Filters the returned oEmbed HTML. 
  606. * 
  607. * Use this filter to add support for custom data types, or to filter the result. 
  608. * 
  609. * @since 2.9.0 
  610. * 
  611. * @param string $return The returned oEmbed HTML. 
  612. * @param object $data A data object result from an oEmbed provider. 
  613. * @param string $url The URL of the content to be embedded. 
  614. */ 
  615. return apply_filters( 'oembed_dataparse', $return, $data, $url ); 
  616.  
  617. /** 
  618. * Strips any new lines from the HTML. 
  619. * 
  620. * @since 2.9.0 as strip_scribd_newlines() 
  621. * @since 3.0.0 
  622. * @access public 
  623. * 
  624. * @param string $html Existing HTML. 
  625. * @param object $data Data object from WP_oEmbed::data2html() 
  626. * @param string $url The original URL passed to oEmbed. 
  627. * @return string Possibly modified $html 
  628. */ 
  629. public function _strip_newlines( $html, $data, $url ) { 
  630. if ( false === strpos( $html, "\n" ) ) { 
  631. return $html; 
  632.  
  633. $count = 1; 
  634. $found = array(); 
  635. $token = '__PRE__'; 
  636. $search = array( "\t", "\n", "\r", ' ' ); 
  637. $replace = array( '__TAB__', '__NL__', '__CR__', '__SPACE__' ); 
  638. $tokenized = str_replace( $search, $replace, $html ); 
  639.  
  640. preg_match_all( '#(<pre[^>]*>.+?</pre>)#i', $tokenized, $matches, PREG_SET_ORDER ); 
  641. foreach ( $matches as $i => $match ) { 
  642. $tag_html = str_replace( $replace, $search, $match[0] ); 
  643. $tag_token = $token . $i; 
  644.  
  645. $found[ $tag_token ] = $tag_html; 
  646. $html = str_replace( $tag_html, $tag_token, $html, $count ); 
  647.  
  648. $replaced = str_replace( $replace, $search, $html ); 
  649. $stripped = str_replace( array( "\r\n", "\n" ), '', $replaced ); 
  650. $pre = array_values( $found ); 
  651. $tokens = array_keys( $found ); 
  652.  
  653. return str_replace( $tokens, $pre, $stripped ); 
.