PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.9-a.7
Jetpack – WP Security, Backup, Speed, & Growth v15.9-a.7
15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / modules / shortcodes / youtube.php
jetpack / modules / shortcodes Last commit date
css 2 weeks ago images 1 year ago img 4 weeks ago js 6 months ago archiveorg-book.php 6 months ago archiveorg.php 6 months ago archives.php 2 weeks ago bandcamp.php 6 months ago brightcove.php 5 months ago cartodb.php 6 months ago class.filter-embedded-html-objects.php 6 months ago codepen.php 6 months ago crowdsignal.php 5 months ago dailymotion.php 6 months ago descript.php 6 months ago facebook.php 6 months ago flatio.php 6 months ago flickr.php 5 months ago getty.php 6 months ago gist.php 6 months ago googleapps.php 6 months ago googlemaps.php 3 weeks ago googleplus.php 6 months ago gravatar.php 6 months ago houzz.php 6 months ago inline-pdfs.php 6 months ago instagram.php 6 months ago kickstarter.php 6 months ago mailchimp.php 5 months ago medium.php 6 months ago mixcloud.php 6 months ago others.php 6 months ago pinterest.php 6 months ago presentations.php 6 months ago quiz.php 6 months ago recipe.php 6 months ago scribd.php 6 months ago shortcode-utils.php 6 months ago sitemap.php 6 months ago slideshare.php 6 months ago slideshow.php 4 weeks ago smartframe.php 6 months ago soundcloud.php 6 months ago spotify.php 6 months ago ted.php 6 months ago tweet.php 6 months ago twitchtv.php 6 months ago twitter-timeline.php 6 months ago twitter.php 6 months ago unavailable.php 6 months ago untappd-menu.php 6 months ago upcoming-events.php 6 months ago ustream.php 6 months ago videopress.php 6 months ago vimeo.php 1 week ago vine.php 6 months ago vr.php 1 week ago wufoo.php 6 months ago youtube.php 3 months ago
youtube.php
669 lines
1 <?php
2 /**
3 * Youtube shortcode
4 *
5 * Contains shortcode + some improvements over the Core Embeds syntax (see http://codex.wordpress.org/Embeds )
6 *
7 * Examples:
8 * [youtube https://www.youtube.com/watch?v=WVbQ-oro7FQ]
9 * [youtube=http://www.youtube.com/watch?v=wq0rXGLs0YM&fs=1&hl=bg_BG&autohide=1&rel=0]
10 * http://www.youtube.com/watch?v=H2Ncxw1xfck&w=320&h=240&fmt=1&rel=0&showsearch=1&hd=0
11 * http://www.youtube.com/v/9FhMMmqzbD8?fs=1&hl=en_US
12 * https://www.youtube.com/playlist?list=PLP7HaNDU4Cifov7C2fQM8Ij6Ew_uPHEXW
13 *
14 * @package automattic/jetpack
15 */
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit( 0 );
19 }
20
21 /**
22 * Replaces YouTube embeds with YouTube shortcodes.
23 *
24 * Covers the following formats:
25 * 2008-07-15:
26 * <object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/bZBHZT3a-FA&hl=en&fs=1"></param><param name="allowFullScreen" value="true"></param><embed src="http://www.youtube.com/v/bZBHZT3a-FA&hl=en&fs=1" type="application/x-shockwave-flash" allowfullscreen="true" width="425" height="344"></embed></object>
27 * around 2008-06-06 youtube changed their old embed code to this:
28 * <object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/M1D30gS7Z8U&hl=en"></param><embed src="http://www.youtube.com/v/M1D30gS7Z8U&hl=en" type="application/x-shockwave-flash" width="425" height="344"></embed></object>
29 * old style was:
30 * <object width="425" height="344"><param name="movie" value="http://www.youtube.com/v/dGY28Qbj76A&rel=0"></param><param name="wmode" value="transparent"></param><embed src="http://www.youtube.com/v/dGY28Qbj76A&rel=0" type="application/x-shockwave-flash" wmode="transparent" width="425" height="344"></embed></object>
31 * 12-2010:
32 * <object width="640" height="385"><param name="movie" value="http://www.youtube.com/v/3H8bnKdf654?fs=1&amp;hl=en_GB"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/3H8bnKdf654?fs=1&amp;hl=en_GB" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"></embed></object>
33 * 01-2011:
34 * <iframe title="YouTube video player" class="youtube-player" width="640" height="390" src="http://www.youtube.com/embed/Qq9El3ki0_g" frameborder="0" allowFullScreen></iframe>
35 * <iframe class="youtube-player" width="640" height="385" src="http://www.youtube.com/embed/VIDEO_ID" frameborder="0"></iframe>
36 *
37 * @param string $content HTML content.
38 * @return string The content with YouTube embeds replaced with YouTube shortcodes.
39 */
40 function jetpack_youtube_embed_to_short_code( $content ) {
41 if ( ! is_string( $content ) || ! str_contains( $content, 'youtube.com' ) ) {
42 return $content;
43 }
44
45 // older codes.
46 $regexp = '!<object(.*?)>.*?<param\s+name=[\'"]movie[\'"]\s+value=[\'"](https?:)?//www\.youtube\.com/v/([^\'"]+)[\'"].*?>.*?</object>!i';
47 $regexp_ent = htmlspecialchars( $regexp, ENT_NOQUOTES );
48 $old_regexp = '!<embed(?:\s+\w+="[^"]*")*\s+src="https?(?:\:|&#0*58;)//www\.youtube\.com/v/([^"]+)"(?:\s+\w+="[^"]*")*\s*(?:/>|>\s*</embed>)!';
49 $old_regexp_ent = str_replace( '&amp;#0*58;', '&amp;#0*58;|&#0*58;', htmlspecialchars( $old_regexp, ENT_NOQUOTES ) );
50
51 // new code.
52 $ifr_regexp = '!<iframe((?:\s+\w+="[^"]*")*?)\s+src="(https?:)?//(?:www\.)*youtube.com/embed/([^"]+)".*?</iframe>!i';
53 $ifr_regexp_ent = str_replace( '&amp;#0*58;', '&amp;#0*58;|&#0*58;', htmlspecialchars( $ifr_regexp, ENT_NOQUOTES ) );
54
55 foreach ( compact( 'regexp', 'regexp_ent', 'old_regexp', 'old_regexp_ent', 'ifr_regexp', 'ifr_regexp_ent' ) as $reg => $regexp ) {
56 if ( ! preg_match_all( $regexp, $content, $matches, PREG_SET_ORDER ) ) {
57 continue;
58 }
59
60 foreach ( $matches as $match ) {
61 /*
62 * Hack, but '?' should only ever appear once, and
63 * it should be for the 1st field-value pair in query string,
64 * if it is present
65 * YouTube changed their embed code.
66 * Example of how it is now:
67 * <object width="640" height="385"><param name="movie" value="http://www.youtube.com/v/aP9AaD4tgBY?fs=1&amp;hl=en_US"></param><param name="allowFullScreen" value="true"></param><param name="allowscriptaccess" value="always"></param><embed src="http://www.youtube.com/v/aP9AaD4tgBY?fs=1&amp;hl=en_US" type="application/x-shockwave-flash" allowscriptaccess="always" allowfullscreen="true" width="640" height="385"></embed></object>
68 * As shown at the start of function, previous YouTube didn't '?'
69 * the 1st field-value pair.
70 */
71 if ( in_array( $reg, array( 'ifr_regexp', 'ifr_regexp_ent', 'regexp', 'regexp_ent' ), true ) ) {
72 $params = $match[1];
73
74 if ( in_array( $reg, array( 'ifr_regexp_ent', 'regexp_ent' ), true ) ) {
75 $params = html_entity_decode( $params, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 );
76 }
77
78 $params = wp_kses_hair( $params, array( 'http' ) );
79
80 $width = isset( $params['width'] ) ? (int) $params['width']['value'] : 0;
81 $height = isset( $params['height'] ) ? (int) $params['height']['value'] : 0;
82 $wh = '';
83
84 if ( $width && $height ) {
85 $wh = "&w=$width&h=$height";
86 }
87
88 $url = esc_url_raw( "https://www.youtube.com/watch?v={$match[3]}{$wh}" );
89 } else {
90 $match[1] = str_replace( '?', '&', $match[1] );
91
92 $url = esc_url_raw( 'https://www.youtube.com/watch?v=' . html_entity_decode( $match[1], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) );
93 }
94
95 $content = str_replace( $match[0], "[youtube $url]", $content );
96
97 /**
98 * Fires before the YouTube embed is transformed into a shortcode.
99 *
100 * @module shortcodes
101 *
102 * @since 1.2.0
103 *
104 * @param string youtube Shortcode name.
105 * @param string $url YouTube video URL.
106 */
107 do_action( 'jetpack_embed_to_shortcode', 'youtube', $url );
108 }
109 }
110
111 return $content;
112 }
113
114 if ( jetpack_shortcodes_should_hook_pre_kses() ) {
115 add_filter( 'pre_kses', 'jetpack_youtube_embed_to_short_code' );
116 }
117
118 /**
119 * Replaces plain-text links to YouTube videos with YouTube embeds.
120 *
121 * @param string $content HTML content.
122 *
123 * @return string The content with embeds instead of URLs
124 */
125 function jetpack_youtube_link( $content ) {
126 return jetpack_preg_replace_callback_outside_tags( '!(?:\n|\A)https?://(?:www\.)?(?:youtube.com/(?:v/|playlist|watch[/\#?])|youtu\.be/)[^\s]+?(?:\n|\Z)!i', 'jetpack_youtube_link_callback', $content, 'youtube.com/' );
127 }
128
129 /**
130 * Callback function for the regex that replaces YouTube URLs with
131 * YouTube embeds.
132 *
133 * @param array $matches An array containing a YouTube URL.
134 */
135 function jetpack_youtube_link_callback( $matches ) {
136 return "\n" . jetpack_youtube_id( $matches[0] ) . "\n";
137 }
138
139 /**
140 * Normalizes a YouTube URL to include a v= parameter and a query string free of encoded ampersands.
141 *
142 * @param string|array $url Youtube URL.
143 * @return string|false The normalized URL or false if input is invalid.
144 */
145 if ( ! function_exists( 'jetpack_youtube_sanitize_url' ) ) :
146 /**
147 * Clean up Youtube URL to match a single format.
148 *
149 * @param string|array $url Youtube URL.
150 */
151 function jetpack_youtube_sanitize_url( $url ) {
152 if ( is_array( $url ) && isset( $url['url'] ) ) {
153 $url = $url['url'];
154 }
155 if ( ! is_string( $url ) ) {
156 return false;
157 }
158
159 $url = trim( $url, ' "' );
160 $url = trim( $url );
161 $url = str_replace( array( 'youtu.be/', '/v/', '/shorts/', '#!v=', '&amp;', '&#038;', 'playlist' ), array( 'youtu.be/?v=', '/?v=', '/watch?v=', '?v=', '&', '&', 'videoseries' ), $url );
162
163 // Replace any extra question marks with ampersands - the result of a URL like "https://www.youtube.com/v/dQw4w9WgXcQ?fs=1&hl=en_US" being passed in.
164 $query_string_start = strpos( $url, '?' );
165
166 if ( false !== $query_string_start ) {
167 $url = substr( $url, 0, $query_string_start + 1 ) . str_replace( '?', '&', substr( $url, $query_string_start + 1 ) );
168 }
169
170 return $url;
171 }
172 endif;
173
174 /**
175 * Converts a YouTube URL into an embedded YouTube video.
176 *
177 * URL can be:
178 * http://www.youtube.com/embed/videoseries?list=PL94269DA08231042B&amp;hl=en_US
179 * http://www.youtube.com/watch#!v=H2Ncxw1xfck
180 * http://www.youtube.com/watch?v=H2Ncxw1xfck
181 * http://www.youtube.com/watch?v=H2Ncxw1xfck&w=320&h=240&fmt=1&rel=0&showsearch=1&hd=0
182 * http://www.youtube.com/v/jF-kELmmvgA
183 * http://www.youtube.com/v/9FhMMmqzbD8?fs=1&hl=en_US
184 * http://youtu.be/Rrohlqeir5E
185 * https://www.youtube.com/watch?v=GJNxoe-iSb4&list=PLAVZ4NFtZX0fE54mDSqNKym-o_rz-8xmk
186 *
187 * @param string $url Youtube URL.
188 */
189 function jetpack_youtube_id( $url ) {
190 $id = jetpack_get_youtube_id( $url );
191
192 if ( ! $id ) {
193 return sprintf( '<!--%s-->', esc_html__( 'YouTube Error: bad URL entered', 'jetpack' ) );
194 }
195
196 $url = jetpack_youtube_sanitize_url( $url );
197 $url = wp_parse_url( $url );
198
199 $thumbnail = "https://i.ytimg.com/vi/$id/hqdefault.jpg";
200 $video_url = add_query_arg( 'v', $id, 'https://www.youtube.com/watch' );
201
202 $args = jetpack_shortcode_youtube_args( $url );
203 if ( empty( $args ) ) {
204 return sprintf( '<!--%s-->', esc_html__( 'YouTube Error: empty URL args', 'jetpack' ) );
205 }
206
207 // Account for URL having both v and list, where jetpack_get_youtube_id() only accounts for one or the other.
208 if ( isset( $args['list'] ) && $args['list'] === $id ) {
209 $id = null;
210 }
211 if ( isset( $args['v'] ) ) {
212 $id = $args['v'];
213 }
214 if ( ! $id && empty( $args['list'] ) ) {
215 return sprintf( '<!--%s-->', esc_html__( 'YouTube Error: missing id and/or list', 'jetpack' ) );
216 }
217
218 list( $w, $h ) = jetpack_shortcode_youtube_dimensions( $args );
219
220 $params = array(
221 'rel' => ( isset( $args['rel'] ) && '0' === $args['rel'] ) ? 0 : 1,
222 'showsearch' => ( isset( $args['showsearch'] ) && '1' === $args['showsearch'] ) ? 1 : 0, // Now deprecated. See https://developers.google.com/youtube/player_parameters#march-29,-2012.
223 'showinfo' => ( isset( $args['showinfo'] ) && '0' === $args['showinfo'] ) ? 0 : 1, // Now obsolete. See https://developers.google.com/youtube/player_parameters#showinfo.
224 'iv_load_policy' => ( isset( $args['iv_load_policy'] ) && '3' === $args['iv_load_policy'] ) ? 3 : 1,
225 'fs' => 1,
226 'hl' => str_replace( '_', '-', get_locale() ),
227 );
228 if ( isset( $args['fmt'] ) && (int) $args['fmt'] ) {
229 $params['fmt'] = (int) $args['fmt']; // Apparently an obsolete parameter. Not referenced on https://developers.google.com/youtube/player_parameters.
230 }
231
232 // The autohide parameter has been deprecated since 2015. See https://developers.google.com/youtube/player_parameters#august-19,-2015.
233 if ( ! isset( $args['autohide'] ) || ( $args['autohide'] < 0 || 2 < $args['autohide'] ) ) {
234 $params['autohide'] = 2;
235 } else {
236 $params['autohide'] = (int) $args['autohide'];
237 }
238
239 $start = 0;
240 if ( isset( $args['start'] ) ) {
241 $start = (int) $args['start'];
242 } elseif ( isset( $args['t'] ) ) {
243 if ( is_numeric( $args['t'] ) ) {
244 $start = (int) $args['t'];
245 } elseif ( is_string( $args['t'] ) ) {
246 $time_pieces = preg_split( '/(?<=\D)(?=\d+)/', $args['t'] );
247
248 foreach ( $time_pieces as $time_piece ) {
249 $int = (int) $time_piece;
250 switch ( substr( $time_piece, - 1 ) ) {
251 case 'h':
252 $start += $int * 3600;
253 break;
254 case 'm':
255 $start += $int * 60;
256 break;
257 case 's':
258 $start += $int;
259 break;
260 }
261 }
262 }
263 }
264 if ( $start ) {
265 $params['start'] = (int) $start;
266 }
267
268 if ( isset( $args['end'] ) && (int) $args['end'] ) {
269 $params['end'] = (int) $args['end'];
270 }
271 if ( isset( $args['hd'] ) && (int) $args['hd'] ) {
272 $params['hd'] = (int) $args['hd']; // Now obsolete per https://developers.google.com/youtube/player_parameters#march-29,-2012.
273 }
274 if ( isset( $args['vq'] ) && in_array( $args['vq'], array( 'hd720', 'hd1080' ), true ) ) {
275 $params['vq'] = $args['vq']; // Note, this appears to be obsolete. Not referenced on https://developers.google.com/youtube/player_parameters.
276 }
277 if ( isset( $args['cc_load_policy'] ) ) {
278 $params['cc_load_policy'] = 1;
279 }
280 if ( isset( $args['cc_lang_pref'] ) ) {
281 $params['cc_lang_pref'] = preg_replace( '/[^_a-z0-9-]/i', '', $args['cc_lang_pref'] );
282 }
283
284 // The wmode parameter appears to be obsolete. Not referenced on https://developers.google.com/youtube/player_parameters.
285 if ( isset( $args['wmode'] ) && in_array( strtolower( $args['wmode'] ), array( 'opaque', 'window', 'transparent' ), true ) ) {
286 $params['wmode'] = $args['wmode'];
287 } else {
288 $params['wmode'] = 'transparent';
289 }
290
291 // The theme parameter is obsolete per https://developers.google.com/youtube/player_parameters#august-19,-2015.
292 if ( isset( $args['theme'] ) && in_array( strtolower( $args['theme'] ), array( 'dark', 'light' ), true ) ) {
293 $params['theme'] = $args['theme'];
294 }
295
296 if ( isset( $args['list'] ) ) {
297 $params['listType'] = 'playlist';
298 $params['list'] = preg_replace( '|[^_a-z0-9-]|i', '', $args['list'] );
299 }
300
301 /**
302 * Allow YouTube videos to start playing automatically.
303 *
304 * @module shortcodes
305 *
306 * @since 2.2.2
307 *
308 * @param bool false Enable autoplay for YouTube videos.
309 */
310 if ( apply_filters( 'jetpack_youtube_allow_autoplay', false ) && isset( $args['autoplay'] ) ) {
311 $params['autoplay'] = (int) $args['autoplay'];
312 }
313
314 $is_amp = class_exists( 'Jetpack_AMP_Support' ) && Jetpack_AMP_Support::is_amp_request();
315
316 if ( $is_amp && $id ) {
317 // Note that <amp-youtube> currently is not well suited for playlists that don't have an individual video selected, hence the $id check above.
318 $placeholder = sprintf(
319 '<a href="%1$s" placeholder><amp-img src="%2$s" alt="%3$s" layout="fill" object-fit="cover"><noscript><img src="%2$s" loading="lazy" decoding="async" alt="%3$s"></noscript></amp-img></a>',
320 esc_url( $video_url ),
321 esc_url( $thumbnail ),
322 esc_attr__( 'YouTube Poster', 'jetpack' ) // Would be preferable to provide YouTube video title, but not available in this non-oEmbed context.
323 );
324
325 $html_attributes = array();
326 foreach ( $params as $param_name => $param_value ) {
327 $html_attributes[] = sprintf(
328 'data-param-%s="%s"',
329 sanitize_key( $param_name ),
330 esc_attr( $param_value )
331 );
332 }
333
334 $html = sprintf(
335 '<amp-youtube data-videoid="%s" %s width="%d" height="%d" layout="responsive">%s</amp-youtube>',
336 esc_attr( $id ),
337 implode( ' ', $html_attributes ), // Note: Escaping done above.
338 esc_attr( $w ),
339 esc_attr( $h ),
340 $placeholder
341 );
342 } else {
343 // In AMP, the AMP_Iframe_Sanitizer will convert into <amp-iframe> as required.
344 $src = 'https://www.youtube.com/embed';
345 if ( $id ) {
346 $src .= "/$id";
347 }
348 $src = add_query_arg(
349 array_merge(
350 array( 'version' => 3 ),
351 $params
352 ),
353 $src
354 );
355
356 $layout = $is_amp ? 'layout="responsive" ' : '';
357
358 $html = sprintf(
359 '<iframe class="youtube-player" width="%s" height="%s" %ssrc="%s" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe>',
360 esc_attr( $w ),
361 esc_attr( $h ),
362 $layout,
363 esc_url( $src )
364 );
365 }
366
367 // Let's do some alignment wonder in a span, unless we're producing a feed.
368 if ( ! is_feed() ) {
369 $alignmentcss = 'text-align:center;';
370 if ( isset( $args['align'] ) ) {
371 switch ( $args['align'] ) {
372 case 'left':
373 $alignmentcss = "float:left; width:{$w}px; height:{$h}px; margin-right:10px; margin-bottom: 10px;";
374 break;
375 case 'right':
376 $alignmentcss = "float:right; width:{$w}px; height:{$h}px; margin-left:10px; margin-bottom: 10px;";
377 break;
378 }
379 }
380
381 $html = sprintf(
382 '<span class="embed-youtube" style="%s display: block;">%s</span>',
383 esc_attr( $alignmentcss ),
384 $html
385 );
386
387 }
388
389 /**
390 * Format output for Calypso Reader/Notifications/Comments
391 */
392 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
393 require_once WP_CONTENT_DIR . '/lib/display-context.php';
394 $context = A8C\Display_Context\get_current_context();
395 if ( A8C\Display_Context\NOTIFICATIONS === $context ) {
396 return sprintf(
397 '<a href="%1$s" target="_blank" rel="noopener noreferrer"><img src="%2$s" alt="%3$s" /></a>',
398 esc_url( $video_url ),
399 esc_url( $thumbnail ),
400 esc_html__( 'YouTube video', 'jetpack' )
401 );
402 }
403 }
404
405 /**
406 * Filter the YouTube video HTML output.
407 *
408 * @module shortcodes
409 *
410 * @since 1.2.3
411 *
412 * @param string $html YouTube video HTML output.
413 */
414 $html = apply_filters( 'video_embed_html', $html );
415
416 return $html;
417 }
418
419 /**
420 * Gets the args present in the YouTube shortcode URL.
421 *
422 * @since 8.0.0
423 *
424 * @param array $url The parsed URL of the shortcode.
425 *
426 * @return array|false The query args of the URL, or false.
427 */
428 function jetpack_shortcode_youtube_args( $url ) {
429 $qargs = array();
430 if ( ! empty( $url['query'] ) ) {
431 wp_parse_str( $url['query'], $qargs );
432 } else {
433 return false;
434 }
435
436 $fargs = array();
437 if ( ! empty( $url['fragment'] ) ) {
438 wp_parse_str( $url['fragment'], $fargs );
439 }
440
441 return array_merge( $fargs, $qargs );
442 }
443
444 /**
445 * Display the Youtube shortcode.
446 *
447 * @param array $atts Shortcode attributes.
448 *
449 * @return string The rendered shortcode.
450 */
451 function jetpack_youtube_shortcode( $atts ) {
452 $url = ( isset( $atts[0] ) ) ? ltrim( $atts[0], '=' ) : shortcode_new_to_old_params( $atts );
453 return jetpack_youtube_id( $url );
454 }
455 add_shortcode( 'youtube', 'jetpack_youtube_shortcode' );
456
457 /**
458 * Gets the dimensions of the [youtube] shortcode.
459 *
460 * Calculates the width and height, taking $content_width into consideration.
461 *
462 * @since 8.0.0
463 *
464 * @param array $query_args The query args of the URL.
465 *
466 * @return array The width and height of the shortcode.
467 */
468 function jetpack_shortcode_youtube_dimensions( $query_args ) {
469 $h = null;
470 global $content_width;
471
472 $input_w = ( isset( $query_args['w'] ) && (int) $query_args['w'] ) ? (int) $query_args['w'] : 0;
473 $input_h = ( isset( $query_args['h'] ) && (int) $query_args['h'] ) ? (int) $query_args['h'] : 0;
474
475 // If we have $content_width, use it.
476 if ( ! empty( $content_width ) ) {
477 $default_width = (int) $content_width;
478 } else {
479 // Otherwise get default width from the old, now deprecated embed_size_w option.
480 $default_width = (int) get_option( 'embed_size_w' );
481 }
482
483 // If we don't know those 2 values use a hardcoded width.
484 if ( empty( $default_width ) ) {
485 $default_width = 640;
486 }
487
488 if ( $input_w > 0 && $input_h > 0 ) {
489 $w = $input_w;
490 $h = $input_h;
491 } elseif ( 0 === $input_w && 0 === $input_h ) {
492 if ( isset( $query_args['fmt'] ) && (int) $query_args['fmt'] ) {
493 $w = ( ! empty( $content_width ) ? min( $content_width, 480 ) : 480 );
494 } else {
495 $w = ( ! empty( $content_width ) ? min( $content_width, $default_width ) : $default_width );
496 $h = ceil( ( $w / 16 ) * 9 );
497 }
498 } elseif ( $input_w > 0 ) {
499 $w = $input_w;
500 $h = ceil( ( $w / 16 ) * 9 );
501 } elseif ( isset( $query_args['fmt'] ) && (int) $query_args['fmt'] ) {
502 $w = ( ! empty( $content_width ) ? min( $content_width, 480 ) : 480 );
503 } else {
504 $w = ( ! empty( $content_width ) ? min( $content_width, $default_width ) : $default_width );
505 $h = $input_h;
506 }
507
508 /**
509 * Filter the YouTube player width.
510 *
511 * @module shortcodes
512 *
513 * @since 1.1.0
514 *
515 * @param int $w Width of the YouTube player in pixels.
516 */
517 $w = (int) apply_filters( 'youtube_width', $w );
518
519 /**
520 * Filter the YouTube player height.
521 *
522 * @module shortcodes
523 *
524 * @since 1.1.0
525 *
526 * @param int $h Height of the YouTube player in pixels.
527 */
528 $h = (int) apply_filters( 'youtube_height', $h );
529
530 return array( $w, $h );
531 }
532
533 /**
534 * For bare URLs on their own line of the form
535 * http://www.youtube.com/v/9FhMMmqzbD8?fs=1&hl=en_US
536 *
537 * @param array $matches Regex partial matches against the URL passed.
538 * @param array $attr Attributes received in embed response.
539 * @param string $url Requested URL to be embedded.
540 */
541 function wpcom_youtube_embed_crazy_url( $matches, $attr, $url ) {
542 return jetpack_youtube_id( $url );
543 }
544
545 /**
546 * Get the regex for Youtube URLs.
547 */
548 function wpcom_youtube_get_regex() {
549 return '#https?://(?:www\.)?(?:youtube.com/(?:v/|playlist|watch[/\#?])|youtu\.be/).*#i';
550 }
551
552 /**
553 * Add a new handler to automatically transform custom Youtube URLs (like playlists) into embeds.
554 */
555 function wpcom_youtube_embed_crazy_url_init() {
556 // Register the custom handler to provide the better support for the private video.
557 wp_embed_register_handler( 'wpcom_youtube_embed_crazy_url', wpcom_youtube_get_regex(), 'wpcom_youtube_embed_crazy_url' );
558 }
559 add_action( 'init', 'wpcom_youtube_embed_crazy_url_init' );
560
561 /**
562 * Filters the oEmbed result before any HTTP requests are made for YouTube.
563 *
564 * @since 13.9
565 *
566 * @param null|string $result The UNSANITIZED (and potentially unsafe) HTML that should be used to embed. Default null.
567 * @param string $url The URL that should be inspected for discovery `<link>` tags.
568 * @param array $args oEmbed remote get arguments.
569 * @return null|string The UNSANITIZED (and potentially unsafe) HTML that should be used to embed.
570 * Null if the URL does not belong to the current site.
571 */
572 function wpcom_youtube_filter_pre_oembed_result( $result, $url, $args ) {
573 // Return early if it's not a YouTube URL.
574 if ( ! preg_match( wpcom_youtube_get_regex(), $url, $matches ) ) {
575 return $result;
576 }
577
578 // Try to get the oembed data by the Core's approach.
579 $wp_oembed = _wp_oembed_get_object();
580 $data = $wp_oembed->get_data( $url, $args );
581 if ( $data ) {
582 /** This filter is documented in wp-includes/class-wp-oembed.php */
583 return apply_filters( 'oembed_result', $wp_oembed->data2html( $data, $url ), $url, $args );
584 }
585
586 // Fallback to the custom handler if the oembed result is not found, especially for the private video.
587 return jetpack_youtube_id( $url );
588 }
589 add_filter( 'pre_oembed_result', 'wpcom_youtube_filter_pre_oembed_result', 10, 3 );
590
591 /**
592 * Remove the ending question mark from the video id of the YouTube URL.
593 *
594 * Example: https://www.youtube.com/watch?v=AVAWwXeOyyQ?
595 *
596 * @since 13.9
597 *
598 * @param string $provider URL of the oEmbed provider.
599 * @param string $url URL of the content to be embedded.
600 *
601 * @return string
602 */
603 function wpcom_youtube_oembed_fetch_url( $provider, $url ) {
604 if ( ! wp_startswith( $provider, 'https://www.youtube.com/oembed' ) ) {
605 return $provider;
606 }
607
608 $parsed = wp_parse_url( $url );
609 if ( ! isset( $parsed['query'] ) ) {
610 return $provider;
611 }
612
613 $query_vars = array();
614 wp_parse_str( $parsed['query'], $query_vars );
615 if ( isset( $query_vars['v'] ) && wp_endswith( $query_vars['v'], '?' ) ) {
616 $url = remove_query_arg( array( 'v' ), $url );
617 $url = add_query_arg( 'v', preg_replace( '/\?$/', '', $query_vars['v'] ), $url );
618 }
619
620 $provider = remove_query_arg( array( 'url' ), $provider );
621 $provider = add_query_arg( 'url', rawurlencode( $url ), $provider );
622
623 return $provider;
624 }
625 add_filter( 'oembed_fetch_url', 'wpcom_youtube_oembed_fetch_url', 10, 2 );
626
627 if (
628 ! is_admin()
629 &&
630 /**
631 * Allow oEmbeds in Jetpack's Comment form.
632 *
633 * @module shortcodes
634 *
635 * @since 2.8.0
636 *
637 * @param int $allow_oembed Option to automatically embed all plain text URLs.
638 */
639 apply_filters( 'jetpack_comments_allow_oembed', true )
640 // No need for this on WordPress.com, this is done for multiple shortcodes at a time there.
641 && ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM )
642 ) {
643 /*
644 * We attach wp_kses_post to comment_text in default-filters.php with priority of 10 anyway,
645 * so the iframe gets filtered out.
646 * Higher priority because we need it before auto-link and autop get to it.
647 */
648 add_filter( 'comment_text', 'jetpack_youtube_link', 1 );
649 }
650
651 /**
652 * Core changes to do_shortcode (https://core.trac.wordpress.org/changeset/34747) broke "improper" shortcodes
653 * with the format [shortcode=http://url.com].
654 *
655 * This removes the "=" from the shortcode so it can be parsed.
656 *
657 * @see https://github.com/Automattic/jetpack/issues/3121
658 *
659 * @param string $content HTML content.
660 */
661 function jetpack_fix_youtube_shortcode_display_filter( $content ) {
662 if ( strpos( $content, '[youtube=' ) !== false ) {
663 $content = preg_replace( '@\[youtube=(.*?)\]@', '[youtube $1]', $content );
664 }
665
666 return $content;
667 }
668 add_filter( 'the_content', 'jetpack_fix_youtube_shortcode_display_filter', 7 );
669