PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.0-beta
Jetpack – WP Security, Backup, Speed, & Growth v16.0-beta
16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 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 / videopress / class.videopress-player.php
jetpack / modules / videopress Last commit date
css 1 month ago js 1 month ago class-videopress-attachment-metadata.php 1 month ago class.jetpack-videopress.php 7 months ago class.videopress-cli.php 1 month ago class.videopress-edit-attachment.php 1 month ago class.videopress-gutenberg.php 7 months ago class.videopress-options.php 7 months ago class.videopress-player.php 4 days ago class.videopress-scheduler.php 7 months ago class.videopress-video.php 7 months ago class.videopress-xmlrpc.php 7 months ago editor-media-view.php 4 months ago shortcode.php 1 month ago videopress-admin-rtl.css 7 months ago videopress-admin-rtl.min.css 7 months ago videopress-admin.css 7 months ago videopress-admin.min.css 7 months ago
class.videopress-player.php
942 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 use Automattic\Jetpack\VideoPress\Jwt_Token_Bridge;
3
4 if ( ! defined( 'ABSPATH' ) ) {
5 exit( 0 );
6 }
7
8 /**
9 * VideoPress playback module markup generator.
10 *
11 * @since 1.3
12 */
13 class VideoPress_Player {
14 /**
15 * Video data for the requested guid and maximum width
16 *
17 * @since 1.3
18 * @var VideoPress_Video
19 */
20 protected $video;
21
22 /**
23 * DOM identifier of the video container
24 *
25 * @var string
26 * @since 1.3
27 */
28 protected $video_container_id;
29
30 /**
31 * DOM identifier of the video element (video, object, embed)
32 *
33 * @var string
34 * @since 1.3
35 */
36 protected $video_id;
37
38 /**
39 * Array of playback options: force_flash or freedom
40 *
41 * @var array
42 * @since 1.3
43 */
44 protected $options;
45
46 /**
47 * Array of video GUIDs shown and their counts,
48 * moved from the old VideoPress class.
49 *
50 * @var array
51 */
52 public static $shown = array();
53
54 /**
55 * Fallback video title.
56 *
57 * @var ?string
58 */
59 protected $title;
60
61 /**
62 * Initiate a player object based on shortcode values and possible blog-level option overrides
63 *
64 * @since 1.3
65 * @param string $guid VideoPress unique identifier.
66 * @param int $maxwidth Maximum desired width of the video player if specified.
67 * @param array $options Player customizations.
68 */
69 public function __construct( $guid, $maxwidth = 0, $options = array() ) {
70 if ( empty( self::$shown[ $guid ] ) ) {
71 self::$shown[ $guid ] = 0;
72 }
73
74 ++self::$shown[ $guid ];
75
76 $this->video_container_id = 'v-' . $guid . '-' . self::$shown[ $guid ];
77 $this->video_id = $this->video_container_id . '-video';
78
79 if ( is_array( $options ) ) {
80 $this->options = $options;
81 } else {
82 $this->options = array();
83 }
84
85 // set up the video
86 $cache_key = null;
87
88 // disable cache in debug mode
89 if ( defined( 'WP_DEBUG' ) && WP_DEBUG === true ) {
90 $cached_video = null;
91 } else {
92 $cache_key_pieces = array( 'video' );
93
94 if ( is_multisite() && is_subdomain_install() ) {
95 $cache_key_pieces[] = get_current_blog_id();
96 }
97
98 $cache_key_pieces[] = $guid;
99 if ( $maxwidth > 0 ) {
100 $cache_key_pieces[] = $maxwidth;
101 }
102 if ( is_ssl() ) {
103 $cache_key_pieces[] = 'ssl';
104 }
105 $cache_key = implode( '-', $cache_key_pieces );
106 unset( $cache_key_pieces );
107 $cached_video = wp_cache_get( $cache_key, 'video' );
108 }
109 if ( empty( $cached_video ) ) {
110 $video = new VideoPress_Video( $guid, $maxwidth );
111 if ( isset( $video->error ) ) {
112 $this->video = $video->error;
113 return;
114 } elseif ( is_wp_error( $video ) ) {
115 $this->video = $video;
116 return;
117 }
118
119 $this->video = $video;
120 unset( $video );
121
122 if ( ! defined( 'WP_DEBUG' ) || WP_DEBUG !== true ) {
123 $expire = 3600;
124 if ( isset( $this->video->expires ) && is_int( $this->video->expires ) ) {
125 $expires_diff = time() - $this->video->expires;
126 if ( $expires_diff > 0 && $expires_diff < 86400 ) { // allowed range: 1 second to 1 day
127 $expire = $expires_diff;
128 }
129 unset( $expires_diff );
130 }
131
132 wp_cache_set( $cache_key, serialize( $this->video ), 'video', $expire ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
133 unset( $expire );
134 }
135 } else {
136 $this->video = unserialize( $cached_video ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- Make sure to unserialize as VideoPress_Video class.
137 }
138 unset( $cache_key );
139 unset( $cached_video );
140 }
141
142 /**
143 * Wrap output in a VideoPress player container.
144 *
145 * @since 1.3
146 * @param string $content HTML string.
147 * @return string HTML string or blank string if nothing to wrap.
148 */
149 private function html_wrapper( $content ) {
150 if ( empty( $content ) ) {
151 return '';
152 } else {
153 return '<div id="' . esc_attr( $this->video_container_id ) . '" class="video-player">' . $content . '</div>';
154 }
155 }
156
157 /**
158 * Output content suitable for a feed reader displaying RSS or Atom feeds
159 * We do not display error messages in the feed view due to caching concerns.
160 * Flash content presented using <embed> markup for feed reader compatibility.
161 *
162 * @since 1.3
163 * @return string HTML string or empty string if error
164 */
165 public function as_xml() {
166 if ( empty( $this->video ) || is_wp_error( $this->video ) ) {
167 return '';
168 }
169
170 if ( isset( $this->options['force_flash'] ) && true === $this->options['force_flash'] ) {
171 $content = $this->flash_embed();
172
173 } else {
174 $content = $this->html5_static();
175 }
176
177 return $this->html_wrapper( $content );
178 }
179
180 /**
181 * Video player markup for best matching the current request and publisher options
182 *
183 * @since 1.3
184 * @return string HTML markup string or empty string if no video property found
185 */
186 public function as_html() {
187 if ( empty( $this->video ) ) {
188 $content = '';
189
190 } elseif ( is_wp_error( $this->video ) ) {
191 $content = $this->error_message( $this->video );
192
193 } elseif ( isset( $this->options['force_flash'] ) && true === $this->options['force_flash'] ) {
194 $content = $this->flash_object();
195
196 } elseif ( isset( $this->video->restricted_embed ) && true === $this->video->restricted_embed ) {
197
198 if ( $this->options['forcestatic'] ) {
199 $content = $this->flash_object();
200
201 } else {
202 $content = $this->html5_dynamic();
203 }
204 } elseif ( isset( $this->options['freedom'] ) && true === $this->options['freedom'] ) {
205 $content = $this->html5_static();
206
207 } else {
208 $content = $this->html5_dynamic();
209 }
210
211 return $this->html_wrapper( $content );
212 }
213
214 /**
215 * Display an error message to users capable of doing something about the error
216 *
217 * @since 1.3
218 * @uses current_user_can() to test if current user has edit_posts capability.
219 * @param WP_Error $error WordPress error.
220 * @return string HTML string
221 */
222 private function error_message( $error ) {
223 if ( ! current_user_can( 'edit_posts' ) || empty( $error ) ) {
224 return '';
225 }
226
227 $html = '<div class="videopress-error" style="background-color:rgb(255,0,0);color:rgb(255,255,255);font-family:font-family:\'Helvetica Neue\',Arial,Helvetica,\'Nimbus Sans L\',sans-serif;font-size:140%;min-height:10em;padding-top:1.5em;padding-bottom:1.5em">';
228 /* translators: %s is 'VideoPress' */
229 $html .= '<h1 style="font-size:180%;font-style:bold;line-height:130%;text-decoration:underline">' . esc_html( sprintf( __( '%s Error', 'jetpack' ), 'VideoPress' ) ) . '</h1>';
230 foreach ( $error->get_error_messages() as $message ) {
231 $html .= $message;
232 }
233 $html .= '</div>';
234 return $html;
235 }
236
237 /**
238 * Rating agencies and industry associations require a potential viewer verify their age before a video or its poster frame are displayed.
239 * Content rated for audiences 17 years of age or older requires such verification across multiple rating agencies and industry associations
240 *
241 * @since 1.3
242 * @return bool true if video requires the viewer verify they are 17 years of age or older
243 */
244 private function age_gate_required() {
245 if ( isset( $this->video->age_rating ) && $this->video->age_rating >= 17 ) {
246 return true;
247 } else {
248 return false;
249 }
250 }
251
252 /**
253 * Select a date of birth using HTML form elements.
254 *
255 * @since 1.5
256 * @return string HTML markup
257 */
258 private function html_age_gate() {
259 global $wp_locale;
260 $text_align = 'left';
261 if ( $this->video->text_direction === 'rtl' ) {
262 $text_align = 'right';
263 }
264
265 $html = '<div class="videopress-age-gate" style="margin:0 60px">';
266 $html .= '<p class="instructions" style="color:rgb(255, 255, 255);font-size:21px;padding-top:60px;padding-bottom:20px;text-align:' . $text_align . '">' . esc_html( __( 'This video is intended for mature audiences.', 'jetpack' ) ) . '<br />' . esc_html( __( 'Please verify your birthday.', 'jetpack' ) ) . '</p>';
267 $html .= '<fieldset id="birthday" style="border:0 none;text-align:' . $text_align . ';padding:0;">';
268 $inputs_style = 'border:1px solid #444;margin-';
269 if ( $this->video->text_direction === 'rtl' ) {
270 $inputs_style .= 'left';
271 } else {
272 $inputs_style .= 'right';
273 }
274 $inputs_style .= ':10px;background-color:rgb(0, 0, 0);font-size:14px;color:rgb(255,255,255);padding:4px 6px;line-height: 2em;vertical-align: middle';
275
276 /**
277 * Display a list of months in the Gregorian calendar.
278 * Set values to 0-based to match JavaScript Date.
279 *
280 * @link https://developer.mozilla.org/en/JavaScript/Reference/global_objects/date Mozilla JavaScript Reference: Date
281 */
282 $html .= '<select name="month" style="' . $inputs_style . '">';
283
284 for ( $i = 0; $i < 12; $i++ ) {
285 $html .= '<option value="' . esc_attr( $i ) . '">' . esc_html( $wp_locale->get_month( $i + 1 ) ) . '</option>';
286 }
287 $html .= '</select>';
288
289 /**
290 * Todo: numdays variance by month.
291 */
292 $html .= '<select name="day" style="' . $inputs_style . '">';
293 for ( $i = 1; $i < 32; $i++ ) {
294 $html .= '<option>' . $i . '</option>';
295 }
296 $html .= '</select>';
297
298 /**
299 * Current record for human life is 122. Go back 130 years and no one is left out.
300 * Don't ask infants younger than 2 for their birthday
301 * Default to 13
302 */
303 $html .= '<select name="year" style="' . $inputs_style . '">';
304 $start_year = gmdate( 'Y' ) - 2;
305 $default_year = $start_year - 11;
306 $end_year = $start_year - 128;
307 for ( $year = $start_year; $year > $end_year; $year-- ) {
308 $html .= '<option';
309 if ( $year === $default_year ) {
310 $html .= ' selected="selected"';
311 }
312 $html .= '>' . $year . '</option>';
313 }
314 unset( $start_year );
315 unset( $default_year );
316 unset( $end_year );
317 $html .= '</select>';
318
319 $html .= '<input type="submit" value="' . __( 'Submit', 'jetpack' ) . '" style="cursor:pointer;border-radius: 1em;border:1px solid #333;background-color:#333;background:-webkit-gradient( linear, left top, left bottom, color-stop(0.0, #444), color-stop(1, #111) );background:-moz-linear-gradient(center top, #444 0%, #111 100%);font-size:13px;padding:4px 10px 5px;line-height:1em;vertical-align:top;color:white;text-decoration:none;margin:0" />';
320
321 $html .= '</fieldset>';
322 $html .= '<p style="padding-top:20px;padding-bottom:60px;text-align:' . $text_align . ';"><a rel="nofollow noopener noreferrer" href="https://videopress.com/" target="_blank" style="color:rgb(128,128,128);text-decoration:underline;font-size:15px">' . __( 'More information', 'jetpack' ) . '</a></p>';
323
324 $html .= '</div>';
325 return $html;
326 }
327
328 /**
329 * Return HTML5 video static markup for the given video parameters.
330 * Use default browser player controls.
331 * No Flash fallback.
332 *
333 * @since 1.2
334 * @link https://html.spec.whatwg.org/multipage/media.html#the-video-element HTML5 video
335 * @return string HTML5 video element and children
336 */
337 private function html5_static() {
338 wp_enqueue_script( 'videopress' );
339 $thumbnail = esc_url( $this->video->poster_frame_uri );
340 $html = "<video id=\"{$this->video_id}\" width=\"{$this->video->calculated_width}\" height=\"{$this->video->calculated_height}\" poster=\"$thumbnail\" controls=\"true\"";
341
342 $preload = 'metadata';
343 if ( isset( $this->options['preloadContent'] ) && videopress_is_valid_preload( $this->options['preloadContent'] ) ) {
344 $preload = $this->options['preloadContent'];
345 }
346
347 if ( isset( $this->options['autoplay'] ) && $this->options['autoplay'] === true ) {
348 $html .= ' autoplay="true"';
349 } else {
350 $html .= ' preload="' . esc_attr( $preload ) . '"';
351 }
352 if ( isset( $this->video->text_direction ) ) {
353 $html .= ' dir="' . esc_attr( $this->video->text_direction ) . '"';
354 }
355 if ( isset( $this->video->language ) ) {
356 $html .= ' lang="' . esc_attr( $this->video->language ) . '"';
357 }
358 $html .= '>';
359 if (
360 ( ! isset( $this->options['freedom'] ) || $this->options['freedom'] === false )
361 && isset( $this->video->videos->mp4 )
362 ) {
363 $mp4 = $this->video->videos->mp4->url;
364 if ( ! empty( $mp4 ) ) {
365 $html .= '<source src="' . esc_url( $mp4 ) . '" type="video/mp4; codecs=&quot;' . esc_attr( $this->video->videos->mp4->codecs ) . '&quot;" />';
366 }
367 unset( $mp4 );
368 }
369
370 if ( isset( $this->video->videos->ogv ) ) {
371 $ogg = $this->video->videos->ogv->url;
372 if ( ! empty( $ogg ) ) {
373 $html .= '<source src="' . esc_url( $ogg ) . '" type="video/ogg; codecs=&quot;' . esc_attr( $this->video->videos->ogv->codecs ) . '&quot;" />';
374 }
375
376 unset( $ogg );
377 }
378
379 $html .= '<div><img alt="';
380 if ( isset( $this->video->title ) ) {
381 $html .= esc_attr( $this->video->title );
382 }
383 $html .= '" src="' . $thumbnail . '" width="' . $this->video->calculated_width . '" height="' . $this->video->calculated_height . '" /></div>';
384 if ( isset( $this->options['freedom'] ) && $this->options['freedom'] === true ) {
385 /* translators: %s url to the gnu.org website */
386 $html .= '<p class="robots-nocontent">' . sprintf( __( 'You do not have sufficient <a rel="nofollow noopener noreferrer" href="%s" target="_blank">freedom levels</a> to view this video. Support free software and upgrade.', 'jetpack' ), 'https://www.gnu.org/philosophy/free-sw.html' ) . '</p>';
387 } elseif ( isset( $this->video->title ) ) {
388 $html .= '<p>' . esc_html( $this->video->title ) . '</p>';
389 }
390 $html .= '</video>';
391 return $html;
392 }
393
394 /**
395 * Click to play dynamic HTML5-capable player.
396 * The player displays a video preview section including poster frame,
397 * video title, play button and watermark on the original page load
398 * and calculates the playback capabilities of the browser. The video player
399 * is loaded when the visitor clicks on the video preview area.
400 * If Flash Player 10 or above is available the browser will display
401 * the Flash version of the video. If HTML5 video appears to be supported
402 * and the browser may be capable of MP4 (H.264, AAC) or OGV (Theora, Vorbis)
403 * playback the browser will display its native HTML5 player.
404 *
405 * @since 1.5
406 * @return string HTML markup
407 */
408 private function html5_dynamic() {
409
410 /**
411 * Filter the VideoPress legacy player feature
412 *
413 * This filter allows you to control whether the legacy VideoPress player should be used
414 * instead of the improved one.
415 *
416 * @module videopress
417 *
418 * @since 3.7.0
419 *
420 * @param boolean $videopress_use_legacy_player
421 */
422 if ( ! apply_filters( 'jetpack_videopress_use_legacy_player', false ) ) {
423 return $this->html5_dynamic_next();
424 }
425
426 wp_enqueue_script( 'videopress' );
427 $video_placeholder_id = $this->video_container_id . '-placeholder';
428 $age_gate_required = $this->age_gate_required();
429 $width = absint( $this->video->calculated_width );
430 $height = absint( $this->video->calculated_height );
431
432 $html = '<div id="' . $video_placeholder_id . '" class="videopress-placeholder" style="';
433 if ( $age_gate_required ) {
434 $html .= "min-width:{$width}px;min-height:{$height}px";
435 } else {
436 $html .= "width:{$width}px;height:{$height}px";
437 }
438 $html .= ';display:none;cursor:pointer !important;position:relative;';
439 if ( isset( $this->video->skin ) && isset( $this->video->skin->background_color ) ) {
440 $html .= 'background-color:' . esc_attr( $this->video->skin->background_color ) . ';';
441 }
442 $html .= 'font-family: \'Helvetica Neue\',Arial,Helvetica,\'Nimbus Sans L\',sans-serif;font-weight:bold;font-size:18px">' . PHP_EOL;
443
444 /**
445 * Do not display a poster frame, title, or any other content hints for mature content.
446 */
447 if ( ! $age_gate_required ) {
448 if ( ! empty( $this->video->title ) ) {
449 $html .= '<div class="videopress-title" style="display:inline;position:absolute;margin:20px 20px 0 20px;padding:4px 8px;vertical-align:top;text-align:';
450 if ( $this->video->text_direction === 'rtl' ) {
451 $html .= 'right" dir="rtl"';
452 } else {
453 $html .= 'left" dir="ltr"';
454 }
455 if ( isset( $this->video->language ) ) {
456 $html .= ' lang="' . esc_attr( $this->video->language ) . '"';
457 }
458 $html .= '><span style="padding:3px 0;line-height:1.5em;';
459 if ( isset( $this->video->skin ) && isset( $this->video->skin->background_color ) ) {
460 $html .= 'background-color:';
461 if ( $this->video->skin->background_color === 'rgb(0,0,0)' ) {
462 $html .= 'rgba(0,0,0,0.8)';
463 } else {
464 $html .= esc_attr( $this->video->skin->background_color );
465 }
466 $html .= ';';
467 }
468 $html .= 'color:rgb(255,255,255)">' . esc_html( $this->video->title ) . '</span></div>';
469 }
470 $html .= '<img class="videopress-poster" alt="';
471 if ( ! empty( $this->video->title ) ) {
472 /* translators: %s is the video title */
473 $html .= esc_attr( $this->video->title ) . '" title="' . esc_attr( sprintf( _x( 'Watch: %s', 'watch a video title', 'jetpack' ), $this->video->title ) );
474 }
475 $html .= '" src="' . esc_url( $this->video->poster_frame_uri, array( 'http', 'https' ) ) . '" width="' . $width . '" height="' . $height . '" />' . PHP_EOL;
476
477 // style a play button hovered over the poster frame
478 $html .= '<div class="play-button"><span style="z-index:2;display:block;position:absolute;top:50%;left:50%;text-align:center;vertical-align:middle;color:rgb(255,255,255);opacity:0.9;margin:0 0 0 -0.45em;padding:0;line-height:0;font-size:500%;text-shadow:0 0 40px rgba(0,0,0,0.5)">&#9654;</span></div>' . PHP_EOL;
479
480 // watermark
481 if ( isset( $this->video->skin ) && isset( $this->video->skin->watermark ) ) {
482 $html .= '<div style="position:relative;margin-top:-40px;height:25px;margin-bottom:35px;';
483 if ( $this->video->text_direction === 'rtl' ) {
484 $html .= 'margin-left:20px;text-align:left;';
485 } else {
486 $html .= 'margin-right:20px;text-align:right;';
487 }
488 $html .= 'vertical-align:bottom;z-index:3">';
489 $html .= '<img alt="" src="' . esc_url( $this->video->skin->watermark, array( 'http', 'https' ) ) . '" width="90" height="13" style="background-color:transparent;background-image:none;background-repeat:no-repeat;border:none;margin:0;padding:0"/>';
490 $html .= '</div>' . PHP_EOL;
491 }
492 }
493
494 $data = array(
495 'blog' => absint( $this->video->blog_id ),
496 'post' => absint( $this->video->post_id ),
497 'duration' => absint( $this->video->duration ),
498 'poster' => esc_url_raw( $this->video->poster_frame_uri, array( 'http', 'https' ) ),
499 'hd' => (bool) $this->options['hd'],
500 );
501 if ( isset( $this->video->videos ) ) {
502 if ( isset( $this->video->videos->mp4 ) && isset( $this->video->videos->mp4->url ) ) {
503 $data['mp4'] = array(
504 'size' => $this->video->videos->mp4->format,
505 'uri' => esc_url_raw( $this->video->videos->mp4->url, array( 'http', 'https' ) ),
506 );
507 }
508 if ( isset( $this->video->videos->ogv ) && isset( $this->video->videos->ogv->url ) ) {
509 $data['ogv'] = array(
510 'size' => 'std',
511 'uri' => esc_url_raw( $this->video->videos->ogv->url, array( 'http', 'https' ) ),
512 );
513 }
514 }
515 $locale = array( 'dir' => $this->video->text_direction );
516 if ( isset( $this->video->language ) ) {
517 $locale['lang'] = $this->video->language;
518 }
519 $data['locale'] = $locale;
520 unset( $locale );
521
522 $guid = $this->video->guid;
523 $guid_js = wp_json_encode( $guid, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
524 $html .= '<script type="text/javascript">' . PHP_EOL;
525 $html .= 'jQuery(document).ready(function() {';
526
527 $html .= 'if ( !jQuery.VideoPress.data[' . $guid_js . '] ) { jQuery.VideoPress.data[' . $guid_js . '] = new Array(); }' . PHP_EOL;
528 $html .= 'jQuery.VideoPress.data[' . $guid_js . '][' . self::$shown[ $guid ] . ']=' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';' . PHP_EOL;
529 unset( $data );
530
531 $jq_container = wp_json_encode( '#' . $this->video_container_id, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
532 $jq_placeholder = wp_json_encode( '#' . $video_placeholder_id, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
533 $player_config = "{width:{$width},height:{$height},";
534 if ( isset( $this->options['freedom'] ) && $this->options['freedom'] === true ) {
535 $player_config .= 'freedom:"true",';
536 }
537 $player_config .= 'container:jQuery(' . $jq_container . ')}';
538
539 $html .= "jQuery({$jq_placeholder}).show(0,function(){jQuery.VideoPress.analytics.impression({$guid_js})});" . PHP_EOL;
540
541 if ( $age_gate_required ) {
542 $html .= 'if ( jQuery.VideoPress.support.flash() ) {' . PHP_EOL;
543 /**
544 * Insert alternative content for Flash players.
545 *
546 * @link https://github.com/swfobject/swfobject/wiki/SWFObject-API#swfobjectembedswfswfurlstr-replaceelemidstr-widthstr-heightstr-swfversionstr-xiswfurlstr-flashvarsobj-parobj-attobj-callbackfn
547 */
548 $html .= 'swfobject.embedSWF(' . implode(
549 ',',
550 array(
551 'jQuery.VideoPress.video.flash.player_uri',
552 wp_json_encode( $this->video_container_id, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ),
553 wp_json_encode( $width, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ),
554 wp_json_encode( $height, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ),
555 'jQuery.VideoPress.video.flash.min_version',
556 'jQuery.VideoPress.video.flash.expressinstall', // attempt to upgrade the Flash player if less than min_version. requires a 310x137 container or larger but we will always try to include
557 '{guid:' . $guid_js . '}', // FlashVars
558 'jQuery.VideoPress.video.flash.params',
559 'null', // no attributes
560 'jQuery.VideoPress.video.flash.embedCallback', // error fallback
561 )
562 ) . ');';
563 $html .= '} else {' . PHP_EOL;
564 $html .= "if ( jQuery.VideoPress.video.prepare({$guid_js},{$player_config}," . self::$shown[ $guid ] . ') ) {' . PHP_EOL;
565 $html .= 'if ( jQuery(' . $jq_container . ').data( "player" ) === "flash" ){jQuery.VideoPress.video.play(jQuery(' . wp_json_encode( '#' . $this->video_container_id, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . '));}else{';
566 $html .= 'jQuery(' . $jq_placeholder . ').html(' . wp_json_encode( $this->html_age_date(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ');' . PHP_EOL;
567 $html .= 'jQuery(' . wp_json_encode( '#' . $video_placeholder_id . ' input[type="submit"]', JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ').one("click", function(event){jQuery.VideoPress.requirements.isSufficientAge(jQuery(' . $jq_container . '),' . absint( $this->video->age_rating ) . ')});' . PHP_EOL;
568 $html .= '}}}' . PHP_EOL;
569 } else {
570 $html .= "if ( jQuery.VideoPress.video.prepare({$guid_js}, {$player_config}," . self::$shown[ $guid ] . ') ) {' . PHP_EOL;
571 if ( isset( $this->options['autoplay'] ) && $this->options['autoplay'] === true ) {
572 $html .= "jQuery.VideoPress.video.play(jQuery({$jq_container}));";
573 } else {
574 $html .= 'jQuery(' . $jq_placeholder . ').one("click",function(){jQuery.VideoPress.video.play(jQuery(' . $jq_container . '))});';
575 }
576 $html .= '}';
577
578 // close the jQuery(document).ready() function
579 $html .= '});';
580 }
581 $html .= '</script>' . PHP_EOL;
582 $html .= '</div>' . PHP_EOL;
583
584 /*
585 * JavaScript required
586 */
587 $noun = __( 'this video', 'jetpack' );
588 if ( ! $age_gate_required ) {
589 $vid_type = '';
590 if ( ( isset( $this->options['freedom'] ) && $this->options['freedom'] === true ) && ( isset( $this->video->videos->ogv ) && isset( $this->video->videos->ogv->url ) ) ) {
591 $vid_type = 'ogv';
592 } elseif ( isset( $this->video->videos->mp4 ) && isset( $this->video->videos->mp4->url ) ) {
593 $vid_type = 'mp4';
594 } elseif ( isset( $this->video->videos->ogv ) && isset( $this->video->videos->ogv->url ) ) {
595 $vid_type = 'ogv';
596 }
597
598 if ( $vid_type !== '' ) {
599 $noun = '<a ';
600 if ( isset( $this->video->language ) ) {
601 $noun .= 'hreflang="' . esc_attr( $this->video->language ) . '" ';
602 }
603 if ( $vid_type === 'mp4' ) {
604 $noun .= 'type="video/mp4" href="' . esc_url( $this->video->videos->mp4->url, array( 'http', 'https' ) );
605 } elseif ( $vid_type === 'ogv' ) {
606 $noun .= 'type="video/ogv" href="' . esc_url( $this->video->videos->ogv->url, array( 'http', 'https' ) );
607 }
608 $noun .= '">';
609 if ( isset( $this->video->title ) ) {
610 $noun .= esc_html( $this->video->title );
611 } else {
612 $noun .= __( 'this video', 'jetpack' );
613 }
614 $noun .= '</a>';
615 } elseif ( ! empty( $this->title ) ) {
616 $noun = esc_html( $this->title );
617 }
618 unset( $vid_type );
619 }
620 /* translators: %s video title or generic 'this video' string */
621 $html .= '<noscript><p>' . sprintf( _x( 'JavaScript required to play %s.', 'Play as in playback or view a movie', 'jetpack' ), $noun ) . '</p></noscript>';
622
623 return $html;
624 }
625
626 /**
627 * Output for the non-legacy HTML5 player.
628 */
629 public function html5_dynamic_next() {
630 $video_container_id = 'v-' . $this->video->guid;
631
632 Jwt_Token_Bridge::enqueue_jwt_token_bridge();
633
634 // Must not use iframes for IE11 due to a fullscreen bug
635 if ( isset( $_SERVER['HTTP_USER_AGENT'] ) && stristr( sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ), 'Trident/7.0; rv:11.0' ) ) {
636 $iframe_embed = false;
637 } else {
638
639 /**
640 * Filter the VideoPress iframe embed
641 *
642 * This filter allows you to control whether the videos will be embedded using an iframe.
643 * Set this to false in order to use an in-page embed rather than an iframe.
644 *
645 * @module videopress
646 *
647 * @since 3.7.0
648 *
649 * @param boolean $videopress_player_use_iframe
650 */
651 $iframe_embed = apply_filters( 'jetpack_videopress_player_use_iframe', true );
652 }
653
654 if ( ! array_key_exists( 'hd', $this->options ) ) {
655 $this->options['hd'] = (bool) get_option( 'video_player_high_quality', false );
656 }
657
658 if ( ! array_key_exists( 'cover', $this->options ) ) {
659 $this->options['cover'] = true;
660 }
661
662 $videopress_options = array(
663 'width' => absint( $this->video->calculated_width ),
664 'height' => absint( $this->video->calculated_height ),
665 );
666 foreach ( $this->options as $option => $value ) {
667 switch ( $option ) {
668 case 'at':
669 if ( (int) $value ) {
670 $videopress_options[ $option ] = (int) $value;
671 }
672 break;
673 case 'autoplay':
674 $option = 'autoPlay'; // Fall-through ok.
675 case 'hd':
676 case 'loop':
677 case 'permalink':
678 case 'cover':
679 case 'muted':
680 case 'controls':
681 case 'playsinline':
682 case 'useAverageColor':
683 if ( in_array( $value, array( true, 1, 'true' ), true ) ) {
684 $videopress_options[ $option ] = true;
685 } elseif ( in_array( $value, array( false, 0, 'false' ), true ) ) {
686 $videopress_options[ $option ] = false;
687 }
688 // phpcs:enable
689 break;
690 case 'defaultlangcode':
691 $option = 'defaultLangCode';
692 if ( $value ) {
693 $videopress_options[ $option ] = $value;
694 }
695 break;
696 case 'preloadContent':
697 if ( $value ) {
698 $videopress_options['preloadContent'] = $value;
699 }
700 }
701 }
702
703 if ( $iframe_embed ) {
704 $iframe_url = "https://videopress.com/embed/{$this->video->guid}";
705
706 foreach ( $videopress_options as $option => $value ) {
707 if ( ! in_array( $option, array( 'width', 'height' ), true ) ) {
708
709 // add_query_arg ignores false as a value, so replacing it with 0
710 // @phan-suppress-next-line PhanPluginSimplifyExpressionBool -- Probably it could, but semantically let's keep it as-is.
711 $iframe_url = add_query_arg( $option, ( false === $value ) ? 0 : $value, $iframe_url );
712 }
713 }
714
715 $cover = $videopress_options['cover'] ? ' data-resize-to-parent="true"' : '';
716
717 wp_enqueue_script( 'videopress-iframe', 'https://videopress.com/videopress-iframe.js', array(), JETPACK__VERSION, true );
718
719 return "<iframe title='" . __( 'VideoPress Video Player', 'jetpack' )
720 . "' aria-label='" . __( 'VideoPress Video Player', 'jetpack' )
721 . "' width='" . esc_attr( $videopress_options['width'] )
722 . "' height='" . esc_attr( $videopress_options['height'] )
723 . "' src='" . esc_attr( $iframe_url )
724 . "' frameborder='0' allowfullscreen"
725 . $cover
726 . " allow='clipboard-write; presentation'></iframe>";
727
728 } else {
729 $videopress_options = wp_json_encode( $videopress_options, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
730 $js_url = 'https://s0.wp.com/wp-content/plugins/video/assets/js/videojs/videopress.js';
731 $css_url = 'https://s0.wp.com/wp-content/plugins/video/assets/js/videojs/videopress.css';
732 $guid_js = wp_json_encode( (string) $this->video->guid, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
733 $selector_js = wp_json_encode( '#' . $video_container_id, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP );
734
735 // Without the player styles the <video> renders at its native size and overflows the page.
736 wp_enqueue_style( 'videopress-videojs', $css_url, array(), JETPACK__VERSION );
737 wp_enqueue_script( 'videopress-videojs', $js_url, array(), JETPACK__VERSION, true );
738 wp_add_inline_script(
739 'videopress-videojs',
740 "videopress({$guid_js}, document.querySelector({$selector_js}), {$videopress_options});"
741 );
742
743 return "<div id='" . esc_attr( $video_container_id ) . "'></div>";
744 }
745 }
746
747 /**
748 * Only allow legitimate Flash parameters and their values
749 *
750 * @since 1.2
751 * @link https://helpx.adobe.com/flash/kb/flash-object-embed-tag-attributes.html Flash object and embed attributes
752 * @link https://helpx.adobe.com/flash/kb/font-outlines-device-fonts.html devicefont
753 * @link https://helpx.adobe.com/flash/kb/control-access-scripts-host-web.html allowscriptaccess
754 * @link https://www.adobe.com/devnet/flashplayer/articles/full_screen_mode.html full screen mode
755 * @link https://help.adobe.com/en_US/as3/dev/WS1EFE2EDA-026D-4d14-864E-79DFD56F87C6.html allownetworking
756 * @param array $flash_params Flash parameters expressed in key-value form.
757 * @return array validated Flash parameters
758 */
759 public static function esc_flash_params( $flash_params ) {
760 $allowed_params = array(
761 'swliveconnect' => array( 'true', 'false' ),
762 'play' => array( 'true', 'false' ),
763 'loop' => array( 'true', 'false' ),
764 'menu' => array( 'true', 'false' ),
765 'quality' => array( 'low', 'autolow', 'autohigh', 'medium', 'high', 'best' ),
766 'scale' => array( 'default', 'noborder', 'exactfit', 'noscale' ),
767 'align' => array( 'l', 'r', 't' ),
768 'salign' => array( 'l', 'r', 't', 'tl', 'tr', 'bl', 'br' ),
769 'wmode' => array( 'window', 'opaque', 'transparent', 'direct', 'gpu' ),
770 'devicefont' => array( '_sans', '_serif', '_typewriter' ),
771 'allowscriptaccess' => array( 'always', 'samedomain', 'never' ),
772 'allownetworking' => array( 'all', 'internal', 'none' ),
773 'seamlesstabbing' => array( 'true', 'false' ),
774 'allowfullscreen' => array( 'true', 'false' ),
775 'fullScreenAspectRatio' => array( 'portrait', 'landscape' ),
776 'base',
777 'bgcolor',
778 'flashvars',
779 );
780
781 $allowed_params_keys = array_keys( $allowed_params );
782
783 $filtered_params = array();
784 foreach ( $flash_params as $param => $value ) {
785 if ( empty( $param ) || empty( $value ) ) {
786 continue;
787 }
788 $param = strtolower( $param );
789 if ( in_array( $param, $allowed_params_keys, true ) ) {
790 if ( isset( $allowed_params[ $param ] ) && is_array( $allowed_params[ $param ] ) ) {
791 $value = strtolower( $value );
792 if ( in_array( $value, $allowed_params[ $param ], true ) ) {
793 $filtered_params[ $param ] = $value;
794 }
795 } else {
796 $filtered_params[ $param ] = $value;
797 }
798 }
799 }
800 unset( $allowed_params_keys );
801
802 /**
803 * Flash specifies sameDomain, not samedomain. change from lowercase value for preciseness
804 */
805 if ( isset( $filtered_params['allowscriptaccess'] ) && $filtered_params['allowscriptaccess'] === 'samedomain' ) {
806 $filtered_params['allowscriptaccess'] = 'sameDomain';
807 }
808
809 return $filtered_params;
810 }
811
812 /**
813 * Filter Flash variables from the response, taking into consideration player options.
814 *
815 * @since 1.3
816 * @return array Flash variable key value pairs
817 */
818 private function get_flash_variables() {
819 if ( ! isset( $this->video->players->swf->vars ) ) {
820 return array();
821 }
822
823 $flashvars = (array) $this->video->players->swf->vars;
824 if ( isset( $this->options['autoplay'] ) && $this->options['autoplay'] === true ) {
825 $flashvars['autoPlay'] = 'true';
826 }
827 return $flashvars;
828 }
829
830 /**
831 * Validate and filter Flash parameters
832 *
833 * @since 1.3
834 * @return array Flash parameters passed through key and value validation
835 */
836 private function get_flash_parameters() {
837 if ( ! isset( $this->video->players->swf->params ) ) {
838 return array();
839 } else {
840 return self::esc_flash_params(
841 /**
842 * Filters the Flash parameters of the VideoPress player.
843 *
844 * @module videopress
845 *
846 * @since 1.2.0
847 *
848 * @param array $this->video->players->swf->params Array of swf parameters for the VideoPress flash player.
849 */
850 apply_filters( 'video_flash_params', (array) $this->video->players->swf->params, 10, 1 )
851 );
852 }
853 }
854
855 /**
856 * Flash player markup in a HTML embed element.
857 *
858 * @since 1.1
859 * @link https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-embed-element embed element
860 * @link http://www.google.com/support/reader/bin/answer.py?answer=70664 Google Reader markup support
861 * @return string HTML markup. Embed element with no children
862 */
863 private function flash_embed() {
864 wp_enqueue_script( 'videopress' );
865 if ( ! isset( $this->video->players->swf ) || ! isset( $this->video->players->swf->url ) ) {
866 return '';
867 }
868
869 $embed = array(
870 'id' => $this->video_id,
871 'src' => esc_url_raw( $this->video->players->swf->url . '&' . http_build_query( $this->get_flash_variables(), '', '&' ), array( 'http', 'https' ) ),
872 'type' => 'application/x-shockwave-flash',
873 'width' => $this->video->calculated_width,
874 'height' => $this->video->calculated_height,
875 );
876 if ( isset( $this->video->title ) ) {
877 $embed['title'] = $this->video->title;
878 }
879 $embed = array_merge( $embed, $this->get_flash_parameters() );
880
881 $html = '<embed';
882 foreach ( $embed as $attribute => $value ) {
883 $html .= ' ' . esc_html( $attribute ) . '="' . esc_attr( $value ) . '"';
884 }
885 unset( $embed );
886 $html .= '></embed>';
887 return $html;
888 }
889
890 /**
891 * Double-baked Flash object markup for Internet Explorer and more standards-friendly consuming agents.
892 *
893 * @since 1.1
894 * @return string HTML markup. Object and children.
895 */
896 private function flash_object() {
897 wp_enqueue_script( 'videopress' );
898 if ( ! isset( $this->video->players->swf ) || ! isset( $this->video->players->swf->url ) ) {
899 return '';
900 }
901
902 $thumbnail_html = '<img alt="';
903 if ( isset( $this->video->title ) ) {
904 $thumbnail_html .= esc_attr( $this->video->title );
905 }
906 $thumbnail_html .= '" src="' . esc_url( $this->video->poster_frame_uri, array( 'http', 'https' ) ) . '" width="' . $this->video->calculated_width . '" height="' . $this->video->calculated_height . '" />';
907 $flash_vars = esc_attr( http_build_query( $this->get_flash_variables(), '', '&' ) );
908 $flash_params = '';
909 foreach ( $this->get_flash_parameters() as $attribute => $value ) {
910 $flash_params .= '<param name="' . esc_attr( $attribute ) . '" value="' . esc_attr( $value ) . '" />';
911 }
912 /* translators: %s url to the Adobe Flash Player website */
913 $flash_help = sprintf( __( 'This video requires <a rel="nofollow noopener noreferrer" href="%s" target="_blank">Adobe Flash</a> for playback.', 'jetpack' ), 'https://get.adobe.com/flashplayer/' );
914 $flash_player_url = esc_url( $this->video->players->swf->url, array( 'http', 'https' ) );
915 $description = '';
916 if ( isset( $this->video->title ) ) {
917 $standby = $this->video->title;
918 $description = '<p><strong>' . esc_html( $this->video->title ) . '</strong></p>';
919 } else {
920 $standby = __( 'Loading video...', 'jetpack' );
921 }
922 $standby = ' standby="' . esc_attr( $standby ) . '"';
923 return <<<OBJECT
924 <script type="text/javascript">if(typeof swfobject!=="undefined"){swfobject.registerObject("{$this->video_id}", "{$this->video->players->swf->version}");}</script>
925 <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="{$this->video->calculated_width}" height="{$this->video->calculated_height}" id="{$this->video_id}"{$standby}>
926 <param name="movie" value="{$flash_player_url}" />
927 {$flash_params}
928 <param name="flashvars" value="{$flash_vars}" />
929 <!--[if !IE]>-->
930 <object type="application/x-shockwave-flash" data="{$flash_player_url}" width="{$this->video->calculated_width}" height="{$this->video->calculated_height}"{$standby}>
931 {$flash_params}
932 <param name="flashvars" value="{$flash_vars}" />
933 <!--<![endif]-->
934 {$thumbnail_html}{$description}<p class="robots-nocontent">{$flash_help}</p>
935 <!--[if !IE]>-->
936 </object>
937 <!--<![endif]-->
938 </object>
939 OBJECT;
940 }
941 }
942