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