PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 15.1.2
Jetpack – WP Security, Backup, Speed, & Growth v15.1.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 1 year ago class.jetpack-videopress.php 1 year ago class.videopress-cli.php 11 months ago class.videopress-edit-attachment.php 1 year ago class.videopress-gutenberg.php 1 year ago class.videopress-options.php 1 year ago class.videopress-player.php 11 months ago class.videopress-scheduler.php 1 year ago class.videopress-video.php 1 year ago class.videopress-xmlrpc.php 1 year ago editor-media-view.php 1 year 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
936 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 );
524 $html .= '<script type="text/javascript">' . PHP_EOL;
525 $html .= 'jQuery(document).ready(function() {';
526
527 $html .= 'if ( !jQuery.VideoPress.data[' . wp_json_encode( $guid ) . '] ) { jQuery.VideoPress.data[' . wp_json_encode( $guid ) . '] = new Array(); }' . PHP_EOL;
528 $html .= 'jQuery.VideoPress.data[' . wp_json_encode( $guid ) . '][' . self::$shown[ $guid ] . ']=' . wp_json_encode( $data ) . ';' . PHP_EOL;
529 unset( $data );
530
531 $jq_container = wp_json_encode( '#' . $this->video_container_id );
532 $jq_placeholder = wp_json_encode( '#' . $video_placeholder_id );
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 ),
553 wp_json_encode( $width ),
554 wp_json_encode( $height ),
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 ) . '));}else{';
566 $html .= 'jQuery(' . $jq_placeholder . ').html(' . wp_json_encode( $this->html_age_date() ) . ');' . PHP_EOL;
567 $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;
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 $js_url = 'https://s0.wp.com/wp-content/plugins/video/assets/js/next/videopress-iframe.js';
717 // phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript
718 return "<iframe title='" . __( 'VideoPress Video Player', 'jetpack' )
719 . "' aria-label='" . __( 'VideoPress Video Player', 'jetpack' )
720 . "' width='" . esc_attr( $videopress_options['width'] )
721 . "' height='" . esc_attr( $videopress_options['height'] )
722 . "' src='" . esc_attr( $iframe_url )
723 . "' frameborder='0' allowfullscreen"
724 . $cover
725 . " allow='clipboard-write'></iframe>"
726 . "<script src='" . esc_attr( $js_url ) . "'></script>";
727
728 } else {
729 $videopress_options = wp_json_encode( $videopress_options );
730 $js_url = 'https://s0.wp.com/wp-content/plugins/video/assets/js/videojs/videopress.js';
731
732 return "<div id='{$video_container_id}'></div>
733 <script src='{$js_url}'></script>
734 <script>
735 videopress('{$this->video->guid}', document.querySelector('#{$video_container_id}'), {$videopress_options});
736 </script>";
737 // phpcs:enable WordPress.WP.EnqueuedResources.NonEnqueuedScript
738 }
739 }
740
741 /**
742 * Only allow legitimate Flash parameters and their values
743 *
744 * @since 1.2
745 * @link https://helpx.adobe.com/flash/kb/flash-object-embed-tag-attributes.html Flash object and embed attributes
746 * @link https://helpx.adobe.com/flash/kb/font-outlines-device-fonts.html devicefont
747 * @link https://helpx.adobe.com/flash/kb/control-access-scripts-host-web.html allowscriptaccess
748 * @link https://www.adobe.com/devnet/flashplayer/articles/full_screen_mode.html full screen mode
749 * @link https://help.adobe.com/en_US/as3/dev/WS1EFE2EDA-026D-4d14-864E-79DFD56F87C6.html allownetworking
750 * @param array $flash_params Flash parameters expressed in key-value form.
751 * @return array validated Flash parameters
752 */
753 public static function esc_flash_params( $flash_params ) {
754 $allowed_params = array(
755 'swliveconnect' => array( 'true', 'false' ),
756 'play' => array( 'true', 'false' ),
757 'loop' => array( 'true', 'false' ),
758 'menu' => array( 'true', 'false' ),
759 'quality' => array( 'low', 'autolow', 'autohigh', 'medium', 'high', 'best' ),
760 'scale' => array( 'default', 'noborder', 'exactfit', 'noscale' ),
761 'align' => array( 'l', 'r', 't' ),
762 'salign' => array( 'l', 'r', 't', 'tl', 'tr', 'bl', 'br' ),
763 'wmode' => array( 'window', 'opaque', 'transparent', 'direct', 'gpu' ),
764 'devicefont' => array( '_sans', '_serif', '_typewriter' ),
765 'allowscriptaccess' => array( 'always', 'samedomain', 'never' ),
766 'allownetworking' => array( 'all', 'internal', 'none' ),
767 'seamlesstabbing' => array( 'true', 'false' ),
768 'allowfullscreen' => array( 'true', 'false' ),
769 'fullScreenAspectRatio' => array( 'portrait', 'landscape' ),
770 'base',
771 'bgcolor',
772 'flashvars',
773 );
774
775 $allowed_params_keys = array_keys( $allowed_params );
776
777 $filtered_params = array();
778 foreach ( $flash_params as $param => $value ) {
779 if ( empty( $param ) || empty( $value ) ) {
780 continue;
781 }
782 $param = strtolower( $param );
783 if ( in_array( $param, $allowed_params_keys, true ) ) {
784 if ( isset( $allowed_params[ $param ] ) && is_array( $allowed_params[ $param ] ) ) {
785 $value = strtolower( $value );
786 if ( in_array( $value, $allowed_params[ $param ], true ) ) {
787 $filtered_params[ $param ] = $value;
788 }
789 } else {
790 $filtered_params[ $param ] = $value;
791 }
792 }
793 }
794 unset( $allowed_params_keys );
795
796 /**
797 * Flash specifies sameDomain, not samedomain. change from lowercase value for preciseness
798 */
799 if ( isset( $filtered_params['allowscriptaccess'] ) && $filtered_params['allowscriptaccess'] === 'samedomain' ) {
800 $filtered_params['allowscriptaccess'] = 'sameDomain';
801 }
802
803 return $filtered_params;
804 }
805
806 /**
807 * Filter Flash variables from the response, taking into consideration player options.
808 *
809 * @since 1.3
810 * @return array Flash variable key value pairs
811 */
812 private function get_flash_variables() {
813 if ( ! isset( $this->video->players->swf->vars ) ) {
814 return array();
815 }
816
817 $flashvars = (array) $this->video->players->swf->vars;
818 if ( isset( $this->options['autoplay'] ) && $this->options['autoplay'] === true ) {
819 $flashvars['autoPlay'] = 'true';
820 }
821 return $flashvars;
822 }
823
824 /**
825 * Validate and filter Flash parameters
826 *
827 * @since 1.3
828 * @return array Flash parameters passed through key and value validation
829 */
830 private function get_flash_parameters() {
831 if ( ! isset( $this->video->players->swf->params ) ) {
832 return array();
833 } else {
834 return self::esc_flash_params(
835 /**
836 * Filters the Flash parameters of the VideoPress player.
837 *
838 * @module videopress
839 *
840 * @since 1.2.0
841 *
842 * @param array $this->video->players->swf->params Array of swf parameters for the VideoPress flash player.
843 */
844 apply_filters( 'video_flash_params', (array) $this->video->players->swf->params, 10, 1 )
845 );
846 }
847 }
848
849 /**
850 * Flash player markup in a HTML embed element.
851 *
852 * @since 1.1
853 * @link https://html.spec.whatwg.org/multipage/iframe-embed-object.html#the-embed-element embed element
854 * @link http://www.google.com/support/reader/bin/answer.py?answer=70664 Google Reader markup support
855 * @return string HTML markup. Embed element with no children
856 */
857 private function flash_embed() {
858 wp_enqueue_script( 'videopress' );
859 if ( ! isset( $this->video->players->swf ) || ! isset( $this->video->players->swf->url ) ) {
860 return '';
861 }
862
863 $embed = array(
864 'id' => $this->video_id,
865 'src' => esc_url_raw( $this->video->players->swf->url . '&' . http_build_query( $this->get_flash_variables(), '', '&' ), array( 'http', 'https' ) ),
866 'type' => 'application/x-shockwave-flash',
867 'width' => $this->video->calculated_width,
868 'height' => $this->video->calculated_height,
869 );
870 if ( isset( $this->video->title ) ) {
871 $embed['title'] = $this->video->title;
872 }
873 $embed = array_merge( $embed, $this->get_flash_parameters() );
874
875 $html = '<embed';
876 foreach ( $embed as $attribute => $value ) {
877 $html .= ' ' . esc_html( $attribute ) . '="' . esc_attr( $value ) . '"';
878 }
879 unset( $embed );
880 $html .= '></embed>';
881 return $html;
882 }
883
884 /**
885 * Double-baked Flash object markup for Internet Explorer and more standards-friendly consuming agents.
886 *
887 * @since 1.1
888 * @return string HTML markup. Object and children.
889 */
890 private function flash_object() {
891 wp_enqueue_script( 'videopress' );
892 if ( ! isset( $this->video->players->swf ) || ! isset( $this->video->players->swf->url ) ) {
893 return '';
894 }
895
896 $thumbnail_html = '<img alt="';
897 if ( isset( $this->video->title ) ) {
898 $thumbnail_html .= esc_attr( $this->video->title );
899 }
900 $thumbnail_html .= '" src="' . esc_url( $this->video->poster_frame_uri, array( 'http', 'https' ) ) . '" width="' . $this->video->calculated_width . '" height="' . $this->video->calculated_height . '" />';
901 $flash_vars = esc_attr( http_build_query( $this->get_flash_variables(), '', '&' ) );
902 $flash_params = '';
903 foreach ( $this->get_flash_parameters() as $attribute => $value ) {
904 $flash_params .= '<param name="' . esc_attr( $attribute ) . '" value="' . esc_attr( $value ) . '" />';
905 }
906 /* translators: %s url to the Adobe Flash Player website */
907 $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/' );
908 $flash_player_url = esc_url( $this->video->players->swf->url, array( 'http', 'https' ) );
909 $description = '';
910 if ( isset( $this->video->title ) ) {
911 $standby = $this->video->title;
912 $description = '<p><strong>' . esc_html( $this->video->title ) . '</strong></p>';
913 } else {
914 $standby = __( 'Loading video...', 'jetpack' );
915 }
916 $standby = ' standby="' . esc_attr( $standby ) . '"';
917 return <<<OBJECT
918 <script type="text/javascript">if(typeof swfobject!=="undefined"){swfobject.registerObject("{$this->video_id}", "{$this->video->players->swf->version}");}</script>
919 <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="{$this->video->calculated_width}" height="{$this->video->calculated_height}" id="{$this->video_id}"{$standby}>
920 <param name="movie" value="{$flash_player_url}" />
921 {$flash_params}
922 <param name="flashvars" value="{$flash_vars}" />
923 <!--[if !IE]>-->
924 <object type="application/x-shockwave-flash" data="{$flash_player_url}" width="{$this->video->calculated_width}" height="{$this->video->calculated_height}"{$standby}>
925 {$flash_params}
926 <param name="flashvars" value="{$flash_vars}" />
927 <!--<![endif]-->
928 {$thumbnail_html}{$description}<p class="robots-nocontent">{$flash_help}</p>
929 <!--[if !IE]>-->
930 </object>
931 <!--<![endif]-->
932 </object>
933 OBJECT;
934 }
935 }
936