1 | <?php |
---|
2 | /** |
---|
3 | * Class providing additional functionality to WordPress' native oEmbed |
---|
4 | * functionality. |
---|
5 | * |
---|
6 | * @link http://codex.wordpress.org/oEmbed |
---|
7 | * @link http://oembed.com/ oEmbed Homepage |
---|
8 | * @link https://github.com/WordPress/WordPress/tree/master/wp-includes/class-oembed.php |
---|
9 | * |
---|
10 | * @package Featured Video Plus |
---|
11 | * @subpackage oEmbed |
---|
12 | * @since 2.0.0 |
---|
13 | */ |
---|
14 | class FVP_oEmbed { |
---|
15 | private $oembed; |
---|
16 | |
---|
17 | public function __construct() { |
---|
18 | // Does not extend oEmbed in order to not initialize it a second time. |
---|
19 | require_once( ABSPATH . '/' . WPINC . '/class-oembed.php' ); |
---|
20 | $this->oembed = _wp_oembed_get_object(); |
---|
21 | |
---|
22 | add_filter( |
---|
23 | 'oembed_fetch_url', array( $this, 'additional_arguments' ), 10, 3 |
---|
24 | ); |
---|
25 | } |
---|
26 | |
---|
27 | |
---|
28 | /** |
---|
29 | * Call methods from 'oembed' class if they don't exist here. |
---|
30 | * |
---|
31 | * @param {string} $method |
---|
32 | * @param {array} $args |
---|
33 | * @return {} Whatever the other method returns. |
---|
34 | */ |
---|
35 | public function __call( $method, $args ) { |
---|
36 | return call_user_func_array( array( $this->oembed, $method ), $args ); |
---|
37 | } |
---|
38 | |
---|
39 | |
---|
40 | /** |
---|
41 | * Utilizes the WordPress oembed class for fetching the oembed info object. |
---|
42 | * |
---|
43 | * @see http://oembed.com/ |
---|
44 | * @since 2.0.0 |
---|
45 | */ |
---|
46 | public function request( $url ) { |
---|
47 | // fetch the oEmbed data with some arbitrary big size to get the biggest |
---|
48 | // thumbnail possible. |
---|
49 | $raw = $this->oembed->fetch( |
---|
50 | $this->get_provider( $url ), |
---|
51 | $url, |
---|
52 | array( |
---|
53 | 'width' => 4096, |
---|
54 | 'height' => 4096, |
---|
55 | ) |
---|
56 | ); |
---|
57 | |
---|
58 | return ! empty( $raw ) ? $raw : false; |
---|
59 | } |
---|
60 | |
---|
61 | |
---|
62 | /** |
---|
63 | * The do-it-all function that takes a URL and attempts to return the HTML. |
---|
64 | * |
---|
65 | * @see WP_oEmbed::get_html() |
---|
66 | * |
---|
67 | * @param {string} $url The URL to the content that should be embedded. |
---|
68 | * @param {array} $args Optional arguments. Usually passed from a shortcode. |
---|
69 | * @return {false/string} False on failure, other the embed HTML. |
---|
70 | */ |
---|
71 | public function get_html( $url, $args = array(), $provider = null ) { |
---|
72 | if ( $provider ) { |
---|
73 | $args = $this->filter_legal_args( $provider, $args ); |
---|
74 | } |
---|
75 | |
---|
76 | $html = $this->oembed->get_html( $url, $args ); |
---|
77 | |
---|
78 | if ( empty( $provider ) ) { |
---|
79 | return $html; |
---|
80 | } |
---|
81 | |
---|
82 | // Some providers do not provide it's player API to oEmbed requests, |
---|
83 | // therefore the plugin needs to manually interfere with their iframe's |
---|
84 | // source URL.. |
---|
85 | switch ( $provider ) { |
---|
86 | |
---|
87 | // YouTube.com |
---|
88 | case 'youtube': |
---|
89 | // Add `origin` paramter. |
---|
90 | $args['origin'] = urlencode( get_bloginfo( 'url' ) ); |
---|
91 | |
---|
92 | // The loop parameter does not work with single videos if there is no |
---|
93 | // playlist defined. Workaround: Set playlist to video itself. |
---|
94 | // @see https://developers.google.com/youtube/player_parameters#loop-supported-players |
---|
95 | if ( ! empty( $args['loop'] ) && $args['loop'] ) { |
---|
96 | $args['playlist'] = $this->get_youtube_video_id( $url ); |
---|
97 | } |
---|
98 | |
---|
99 | // Remove fullscreen button manually because the YouTube API |
---|
100 | // does not care about `&fs=0`. |
---|
101 | if ( array_key_exists( 'fs', $args ) && $args['fs'] == 0 ) { |
---|
102 | $html = str_replace( 'allowfullscreen', '', $html ); |
---|
103 | } |
---|
104 | |
---|
105 | // We strip the 'feature=oembed' from the parameters because it breaks |
---|
106 | // some parameters. |
---|
107 | $hook = '?feature=oembed'; |
---|
108 | $html = str_replace( $hook, '', $html ); |
---|
109 | break; |
---|
110 | |
---|
111 | // DailyMotion.com |
---|
112 | case 'dailymotion': |
---|
113 | $args = $this->translate_time_arg( $args ); |
---|
114 | break; |
---|
115 | } |
---|
116 | |
---|
117 | if ( ! empty( $args ) ) { |
---|
118 | $pattern = "/src=([\"'])([^\"']*)[\"']/"; |
---|
119 | preg_match( $pattern, $html, $match ); |
---|
120 | if ( ! empty( $match[1] ) && ! empty( $match[2] ) ) { |
---|
121 | $code = $this->clean_url( $match[2] ); |
---|
122 | $replace = sprintf( 'src=$1%s$1', add_query_arg( $args, $code ) ); |
---|
123 | $html = preg_replace( $pattern, $replace, $html ); |
---|
124 | } |
---|
125 | } |
---|
126 | |
---|
127 | return $html; |
---|
128 | } |
---|
129 | |
---|
130 | |
---|
131 | /** |
---|
132 | * Cleans up ? and & in urls such that before all & there is a single ? |
---|
133 | * with none following. |
---|
134 | * |
---|
135 | * @see wordpress.org/support/topic/fix-for-wordpress-442-for-youtube-video-error |
---|
136 | * |
---|
137 | * @param {string} $urls |
---|
138 | * @return {string} |
---|
139 | */ |
---|
140 | public function clean_url($url) { |
---|
141 | $url = preg_replace( '/\?/', '&', $url, 1); |
---|
142 | return preg_replace( '/&/', '?', $url, 1); |
---|
143 | } |
---|
144 | |
---|
145 | |
---|
146 | /** |
---|
147 | * Enable additional parameters for oEmbed requests from inside the plugin. |
---|
148 | * The plugin only allows a limited set of parameters. |
---|
149 | * |
---|
150 | * @see https://core.trac.wordpress.org/ticket/16996#comment:18 |
---|
151 | * @param {string} $provider The oEmbed provider (as URL) |
---|
152 | * @param {string} $url The oEmbed request URL |
---|
153 | * @param {assoc} $args The additional parameters for the provider |
---|
154 | * @return {string} $provider with added parameters. |
---|
155 | */ |
---|
156 | public function additional_arguments( $provider, $url, $args ) { |
---|
157 | unset( |
---|
158 | $args['width'], |
---|
159 | $args['height'], |
---|
160 | $args['discover'] |
---|
161 | ); |
---|
162 | |
---|
163 | return add_query_arg( $args, $provider );; |
---|
164 | } |
---|
165 | |
---|
166 | |
---|
167 | /** |
---|
168 | * Extracts the arguments (query and fragment) from a given URL and filters |
---|
169 | * them to only contain arguments legal for the given provider. |
---|
170 | * |
---|
171 | * @since 2.0.0 |
---|
172 | * |
---|
173 | * @param {string} $url oEmbed request URL |
---|
174 | * @param {string} $provider Provider name (optional) |
---|
175 | * @param {bool} $filter_legals If the retrieved arguments should be |
---|
176 | * filtered to only contain legals |
---|
177 | * @return {assoc} Associative array containing the arguments as key value |
---|
178 | * pairs |
---|
179 | */ |
---|
180 | public function get_args( $url, $provider = null, $filter_legals = true ) { |
---|
181 | $args = $this->parse_url_args( $url ); |
---|
182 | |
---|
183 | if ( $filter_legals ) { |
---|
184 | $provider = empty( $provider ) ? |
---|
185 | $this->get_provider_name( $url ) : |
---|
186 | $provider; |
---|
187 | |
---|
188 | $args = $this->filter_legal_args( $provider, $args ); |
---|
189 | } |
---|
190 | |
---|
191 | return $args; |
---|
192 | } |
---|
193 | |
---|
194 | |
---|
195 | /** |
---|
196 | * Extracts the provider name in lowercase from a given URL. |
---|
197 | * |
---|
198 | * @since 2.0.0 |
---|
199 | * @param {string} $url oEmbed request URL |
---|
200 | * @return {string} Provider name |
---|
201 | */ |
---|
202 | public function get_provider_name( $url ) { |
---|
203 | $host = parse_url( $url, PHP_URL_HOST ); |
---|
204 | |
---|
205 | $tlds_set = array( |
---|
206 | 'com', |
---|
207 | 'net', |
---|
208 | 'tv', |
---|
209 | ); |
---|
210 | |
---|
211 | $tlds = '(?:' . implode( ')|(?:', $tlds_set ) . ')'; |
---|
212 | $pattern = '/(?:www\.)?(.*)\.' . $tlds . '/'; |
---|
213 | |
---|
214 | preg_match( $pattern , $host, $match ); |
---|
215 | |
---|
216 | if ( ! empty( $match[1] ) ) { |
---|
217 | return strtolower( $match[1] ); |
---|
218 | } |
---|
219 | |
---|
220 | return false; |
---|
221 | } |
---|
222 | |
---|
223 | |
---|
224 | /** |
---|
225 | * Takes a URL and returns the corresponding oEmbed provider's URL, if there |
---|
226 | * is one. |
---|
227 | * |
---|
228 | * Backport from WordPress 4.0.0 to make this method available for earlier |
---|
229 | * WordPress releases. |
---|
230 | * @see https://github.com/WordPress/WordPress/blob/ed4aafa6929b36dc1d06708831a7cef258c16b54/wp-includes/class-oembed.php#L188-L226 |
---|
231 | * |
---|
232 | * @param string $url The URL to the content. |
---|
233 | * @param string|array $args Optional provider arguments. |
---|
234 | * @return false|string False on failure, otherwise the oEmbed provider URL. |
---|
235 | */ |
---|
236 | public function get_provider( $url, $args = '' ) { |
---|
237 | // If the WordPress native oembed class has the get_provider function, |
---|
238 | // use that one. |
---|
239 | if ( method_exists( $this->oembed, 'get_provider' ) ) { |
---|
240 | return $this->oembed->get_provider( $url, $args ); |
---|
241 | } |
---|
242 | |
---|
243 | $args = wp_parse_args( $args ); |
---|
244 | |
---|
245 | $provider = false; |
---|
246 | if ( ! isset( $args['discover'] ) ) { |
---|
247 | $args['discover'] = true; |
---|
248 | } |
---|
249 | |
---|
250 | foreach ( $this->oembed->providers AS $matchmask => $data ) { |
---|
251 | list( $providerurl, $regex ) = $data; |
---|
252 | |
---|
253 | // Turn the asterisk-type provider URLs into regex |
---|
254 | if ( ! $regex ) { |
---|
255 | $matchmask = '#' . str_replace( |
---|
256 | '___wildcard___', |
---|
257 | '(.+)', |
---|
258 | preg_quote( str_replace( '*', '___wildcard___', $matchmask ), '#' ) |
---|
259 | ) . '#i'; |
---|
260 | $matchmask = preg_replace( |
---|
261 | '|^#http\\\://|', '#https?\://', $matchmask |
---|
262 | ); |
---|
263 | } |
---|
264 | |
---|
265 | if ( preg_match( $matchmask, $url ) ) { |
---|
266 | // JSON is easier to deal with than XML |
---|
267 | $provider = str_replace( '{format}', 'json', $providerurl ); |
---|
268 | break; |
---|
269 | } |
---|
270 | } |
---|
271 | |
---|
272 | if ( ! $provider && $args['discover'] ) { |
---|
273 | $provider = $this->oembed->discover( $url ); |
---|
274 | } |
---|
275 | |
---|
276 | return $provider; |
---|
277 | } |
---|
278 | |
---|
279 | |
---|
280 | /** |
---|
281 | * Get video id from url. |
---|
282 | * |
---|
283 | * @param {string} $url |
---|
284 | * @return {string/bool} Video ID or false on error. |
---|
285 | */ |
---|
286 | public function get_video_id( $url ) { |
---|
287 | if ( empty( $url ) ) { |
---|
288 | return false; |
---|
289 | } |
---|
290 | |
---|
291 | $provider = $this->get_provider_name( $url ); |
---|
292 | if ( empty( $provider ) ) { |
---|
293 | return false; |
---|
294 | } |
---|
295 | |
---|
296 | switch ( $provider ) { |
---|
297 | �� case 'dailymotion': |
---|
298 | return strtok( basename( $url ), '_' ); |
---|
299 | break; |
---|
300 | } |
---|
301 | |
---|
302 | return false; |
---|
303 | } |
---|
304 | |
---|
305 | |
---|
306 | /** |
---|
307 | * Get video thumbnail url by provider and video id. |
---|
308 | * |
---|
309 | * @param {string} $provider |
---|
310 | * @param {string} $id |
---|
311 | * @return {string/bool} Video URL or false on error. |
---|
312 | */ |
---|
313 | public function get_thumbnail_url( $provider, $id ) { |
---|
314 | static $thumbnail_apis = array( |
---|
315 | 'dailymotion' => |
---|
316 | 'https://api.dailymotion.com/video/%s?fields=thumbnail_url,poster_url', |
---|
317 | ); |
---|
318 | |
---|
319 | if ( empty( $provider ) || empty( $id ) ) { |
---|
320 | return false; |
---|
321 | } |
---|
322 | |
---|
323 | $result = @file_get_contents( sprintf( $thumbnail_apis[$provider], $id ) ); |
---|
324 | if ( ! empty( $result ) ) { |
---|
325 | switch ( $provider ) { |
---|
326 | case 'dailymotion': |
---|
327 | $data = json_decode( $result, true ); |
---|
328 | return ! empty( $data['thumbnail_url'] ) ? |
---|
329 | $data['thumbnail_url'] : $data['poster_url']; |
---|
330 | break; |
---|
331 | } |
---|
332 | } |
---|
333 | |
---|
334 | return false; |
---|
335 | } |
---|
336 | |
---|
337 | |
---|
338 | /** |
---|
339 | * Only keeps key value pairs of the source $array if their keys are listed |
---|
340 | * in the $filter array. |
---|
341 | * |
---|
342 | * @since 2.0.0 |
---|
343 | * |
---|
344 | * @param {assoc} $array Associative array to filter |
---|
345 | * @param {array} $filter Enumerated array containing the legal keys to keep |
---|
346 | * @return {assoc} Filtered array |
---|
347 | */ |
---|
348 | private function filter_legal_args( $provider, $args ) { |
---|
349 | $legals = array( |
---|
350 | |
---|
351 | // YouTube.com |
---|
352 | // https://developers.google.com/youtube/player_parameters |
---|
353 | 'youtube' => array( |
---|
354 | 'autohide', |
---|
355 | 'autoplay', |
---|
356 | 'cc_load_policy', |
---|
357 | 'color', |
---|
358 | 'controls', |
---|
359 | 'disablekb', |
---|
360 | 'enablejsapi', |
---|
361 | 'end', |
---|
362 | 'fs', |
---|
363 | 'hl', |
---|
364 | 'iv_load_policy', |
---|
365 | 'list', |
---|
366 | 'listType', |
---|
367 | 'loop', |
---|
368 | 'modestbranding', |
---|
369 | 'origin', |
---|
370 | 'playerapiid', |
---|
371 | 'playlist', |
---|
372 | 'playsinline', |
---|
373 | 'rel', |
---|
374 | 'showinfo', |
---|
375 | 'start', |
---|
376 | 'theme', |
---|
377 | ), |
---|
378 | |
---|
379 | // Vimeo.com |
---|
380 | // http://developer.vimeo.com/apis/oembed |
---|
381 | 'vimeo' => array( |
---|
382 | 'byline', |
---|
383 | 'title', |
---|
384 | 'portrait', |
---|
385 | 'color', |
---|
386 | 'autoplay', |
---|
387 | 'autopause', |
---|
388 | 'loop', |
---|
389 | 'api', |
---|
390 | 'player_id', |
---|
391 | ), |
---|
392 | |
---|
393 | // DailyMotion.com |
---|
394 | // http://www.dailymotion.com/doc/api/player.html |
---|
395 | 'dailymotion' => array( |
---|
396 | 'wmode', |
---|
397 | 'autoplay', |
---|
398 | 'api', |
---|
399 | 'background', |
---|
400 | 'chromeless', |
---|
401 | 'controls', |
---|
402 | 'foreground', |
---|
403 | 'highlight', |
---|
404 | 'html', |
---|
405 | 'id', |
---|
406 | 'info', |
---|
407 | 'logo', |
---|
408 | 'network', |
---|
409 | 'quality', |
---|
410 | 'related', |
---|
411 | 'startscreen', |
---|
412 | 'start', |
---|
413 | 't', |
---|
414 | 'syndication', |
---|
415 | ), |
---|
416 | |
---|
417 | // SoundCloud.com |
---|
418 | // https://developers.soundcloud.com/docs/oembed |
---|
419 | 'soundcloud' => array( |
---|
420 | 'auto_play', |
---|
421 | 'auto_play' => 'autoplay', // map autoplay to auto_play |
---|
422 | 'color', |
---|
423 | 'show_comments', |
---|
424 | ), |
---|
425 | ); |
---|
426 | |
---|
427 | $result = array(); |
---|
428 | |
---|
429 | if ( ! empty( $legals[ $provider ] ) ) { |
---|
430 | foreach ( $legals[ $provider ] AS $key => $value ) { |
---|
431 | if ( array_key_exists( $value, $args ) && |
---|
432 | ! is_null( $args[ $value ] ) |
---|
433 | ) { |
---|
434 | $key = is_numeric( $key ) ? $value : $key; |
---|
435 | $result[ $key ] = urlencode( $args[ $value ] ); |
---|
436 | } |
---|
437 | } |
---|
438 | } |
---|
439 | |
---|
440 | return $result; |
---|
441 | } |
---|
442 | |
---|
443 | |
---|
444 | /** |
---|
445 | * Function used for retrieving query (?..&..) and fragment (#..) arguments |
---|
446 | * of a given URL. |
---|
447 | * |
---|
448 | * @see http://php.net/manual/en/function.parse-url.php |
---|
449 | * @see http://php.net/manual/en/function.parse-str.php |
---|
450 | * @since 2.0.0 |
---|
451 | * |
---|
452 | * @param {string} $url the URL to parse for arguments |
---|
453 | * @return array containing query and fragment arguments |
---|
454 | */ |
---|
455 | private function parse_url_args( $url ) { |
---|
456 | // parse query |
---|
457 | $query = parse_url( $url, PHP_URL_QUERY ); |
---|
458 | $query_args = array(); |
---|
459 | parse_str( $query, $query_args ); |
---|
460 | |
---|
461 | // parse fragment |
---|
462 | $fragment = parse_url( $url, PHP_URL_FRAGMENT ); |
---|
463 | $fragment_args = array(); |
---|
464 | parse_str( $fragment, $fragment_args ); |
---|
465 | |
---|
466 | // merge query and fragment args |
---|
467 | $args = array_merge( |
---|
468 | $query_args, |
---|
469 | $fragment_args |
---|
470 | ); |
---|
471 | |
---|
472 | return $args; |
---|
473 | } |
---|
474 | |
---|
475 | |
---|
476 | /** |
---|
477 | * Calculates the amount of seconds depicted by a string structured like one |
---|
478 | * of the following possibilities: |
---|
479 | * ##m##s |
---|
480 | * ##m |
---|
481 | * ##s |
---|
482 | * ## |
---|
483 | * |
---|
484 | * @since 2.0.0 |
---|
485 | * |
---|
486 | * @param {string} $t |
---|
487 | * @return {int} seconds |
---|
488 | */ |
---|
489 | private function handle_m_s_string( $t ) { |
---|
490 | $seconds = 0; |
---|
491 | |
---|
492 | preg_match( '/(\d+)m/', $t, $m ); |
---|
493 | if ( ! empty( $m[1] ) ) { |
---|
494 | $seconds += $m[1] * 60; |
---|
495 | } |
---|
496 | |
---|
497 | preg_match( '/(\d+)s/', $t, $s ); |
---|
498 | if ( ! empty( $s[1] ) ) { |
---|
499 | $seconds += $s[1]; |
---|
500 | } |
---|
501 | |
---|
502 | if ( empty( $m[1] ) && empty( $s[1] ) ) { |
---|
503 | $seconds += intval( $t ); |
---|
504 | } |
---|
505 | |
---|
506 | return $seconds; |
---|
507 | } |
---|
508 | |
---|
509 | |
---|
510 | /** |
---|
511 | * Translates a source time parameter (mostly given as 't' in the |
---|
512 | * fragment (#..) of an URL in seconds to a destination parameter. |
---|
513 | * |
---|
514 | * Note: The source parameter overwrites the destination parameter! |
---|
515 | * |
---|
516 | * @since 2.0.0 |
---|
517 | * |
---|
518 | * @param {array} $parameters Array of parameters, containing $src |
---|
519 | * @param {string} $src Key of the source parameter |
---|
520 | * @param {string} $dst Key of the destination parameter |
---|
521 | * @return {array} Updated $parameters array |
---|
522 | */ |
---|
523 | private function translate_time_arg( $args, $src = 't', $dst = 'start' ) { |
---|
524 | if ( ! empty( $args[ $src ] ) ) { |
---|
525 | $t = $this->handle_m_s_string( $args[ $src ] ); |
---|
526 | |
---|
527 | unset( $args[ $src ] ); |
---|
528 | |
---|
529 | if ( $t ) { |
---|
530 | $args[ $dst ] = $t; |
---|
531 | } |
---|
532 | } |
---|
533 | |
---|
534 | return $args; |
---|
535 | } |
---|
536 | |
---|
537 | |
---|
538 | /** |
---|
539 | * Extract the YouTube Video ID from an URL. |
---|
540 | * |
---|
541 | * @see http://stackoverflow.com/a/6382259/1447384 |
---|
542 | * |
---|
543 | * @param {string} $url |
---|
544 | * @return {string} YouTube ID |
---|
545 | */ |
---|
546 | private function get_youtube_video_id( $url ) { |
---|
547 | $pattern = |
---|
548 | '/(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)'. |
---|
549 | '|youtu\.be\/)([^"&?\/ ]{11})/i'; |
---|
550 | |
---|
551 | if ( preg_match( $pattern, $url, $match ) ) { |
---|
552 | return $match[1]; |
---|
553 | } |
---|
554 | |
---|
555 | return false; |
---|
556 | } |
---|
557 | } |
---|