PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 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 All 506 releases
jetpack / jetpack_vendor / automattic / jetpack-videopress / src / class-data.php

class-data.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at jetpack_vendor/automattic/jetpack-videopress/src/class-data.php

542 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The Data class.
4 * This class provides methods for data VideoPress access and manipulation.
5 *
6 * @package automattic/jetpack-videopress
7 */
8
9 namespace Automattic\Jetpack\VideoPress;
10
11 use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12 use Automattic\Jetpack\Status\Host;
13 use WP_REST_Request;
14 /**
15 * The Data class.
16 */
17 class Data {
18
19 /**
20 * Gets the Jetpack blog ID
21 *
22 * @return int The blog ID
23 */
24 public static function get_blog_id() {
25 return VideoPressToken::blog_id();
26 }
27
28 /**
29 * Gets the VideoPress site privacy configuration.
30 *
31 * @return boolean If all the videos are private on the site
32 */
33 public static function get_videopress_videos_private_for_site() {
34 /**
35 * If it's a Simple site, returns the site privacy setting.
36 */
37 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
38 return video_is_private_wpcom_blog( get_current_blog_id() );
39 }
40 /**
41 * If it's a private Atomic site, the default setting is private as well.
42 */
43 if ( ( new Host() )->is_woa_site() ) {
44 if ( ( intval( get_option( 'blog_public', '' ) ) === -1 ) ) {
45 return true;
46 }
47 }
48
49 /* If it's a Jetpack site or a public Atomic site, check the settings */
50 return boolval( get_option( 'videopress_private_enabled_for_site', false ) );
51 }
52
53 /**
54 * Gets whether auto-generated subtitles are disabled for the site.
55 *
56 * Subtitles are generated by default, so this opt-out option defaults to false.
57 *
58 * @return boolean If auto-generated subtitles should be skipped for new videos.
59 */
60 public static function get_videopress_auto_subtitles_disabled() {
61 return boolval( get_option( 'videopress_auto_subtitles_disabled', false ) );
62 }
63
64 /**
65 * Gets whether player preloading is turned off for every embed on the site.
66 *
67 * Preloading is on by default, so this opt-out option defaults to false.
68 *
69 * @return boolean If embeds should wait for playback before fetching video data and player assets.
70 */
71 public static function get_videopress_player_preload_disabled() {
72 return boolval( get_option( 'videopress_player_preload_disabled', false ) );
73 }
74
75 /**
76 * Gets whether embeds render an inline player instead of one iframe per video.
77 *
78 * Iframes are the default, so this opt-in option defaults to false.
79 *
80 * @return boolean If embeds should mount players in the page from one shared player script.
81 */
82 public static function get_videopress_inline_player_enabled() {
83 return boolval( get_option( 'videopress_inline_player_enabled', false ) );
84 }
85
86 /**
87 * Gets the VideoPress Settings.
88 *
89 * @return array The settings as an associative array.
90 */
91 public static function get_videopress_settings() {
92 $site_type = 'jetpack';
93 $site_is_private = false;
94
95 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
96 $site_type = 'simple';
97 $site_is_private = video_is_private_wpcom_blog( get_current_blog_id() );
98 } elseif ( ( new Host() )->is_woa_site() ) {
99 $site_type = 'atomic';
100 $site_is_private = intval( get_option( 'blog_public', '' ) ) === -1;
101 }
102
103 return array(
104 'videopress_videos_private_for_site' => self::get_videopress_videos_private_for_site(),
105 'videopress_auto_subtitles_disabled' => self::get_videopress_auto_subtitles_disabled(),
106 'videopress_player_preload_disabled' => self::get_videopress_player_preload_disabled(),
107 'videopress_inline_player_enabled' => self::get_videopress_inline_player_enabled(),
108 'site_is_private' => $site_is_private,
109 'site_type' => $site_type,
110 );
111 }
112
113 /**
114 * Gets the video data
115 *
116 * @param boolean $is_videopress - True when getting VideoPress data.
117 * @return array
118 */
119 public static function get_video_data( $is_videopress = true ) {
120 $video_data = array(
121 'videos' => array(),
122 'total' => 0,
123 'totalPages' => 0,
124 'query' => array(
125 'order' => 'desc',
126 'orderBy' => 'date',
127 'itemsPerPage' => 6,
128 'page' => 1,
129 ),
130 );
131
132 $args = array(
133 'order' => $video_data['query']['order'],
134 'orderby' => $video_data['query']['orderBy'],
135 'per_page' => $video_data['query']['itemsPerPage'],
136 'page' => $video_data['query']['page'],
137 );
138
139 if ( $is_videopress ) {
140 $args['mime_type'] = 'video/videopress';
141 } else {
142 $args['media_type'] = 'video';
143 $args['no_videopress'] = true;
144 }
145
146 // Do an internal request for the media list
147 $request = new WP_REST_Request( 'GET', '/wp/v2/media' );
148 $request->set_query_params( $args );
149 $response = rest_do_request( $request );
150
151 if ( $response->is_error() ) {
152 // @todo: error handling
153 return $video_data;
154 }
155
156 // load the real values
157 $video_data['videos'] = $response->get_data();
158 $headers = $response->get_headers();
159
160 if ( isset( $headers['X-WP-Total'] ) ) {
161 $video_data['total'] = $headers['X-WP-Total'];
162 }
163
164 if ( isset( $headers['X-WP-TotalPages'] ) ) {
165 $video_data['totalPages'] = $headers['X-WP-TotalPages'];
166 }
167
168 return $video_data;
169 }
170
171 /**
172 * Gets the site's newest VideoPress videos as playlist entries, newest first.
173 *
174 * Goes through the media REST endpoint rather than a direct WP_Query so the
175 * package's rest_attachment_query filter resolves VideoPress membership on
176 * every host: by mime off WordPress.com Simple, from wpcom's videos table on it.
177 * The editor's block preview sends the same request.
178 *
179 * @param int $count How many videos to fetch.
180 *
181 * @return array Entries with guid, durationMs and height keys.
182 */
183 public static function get_latest_videopress_playlist_entries( $count ) {
184 $args = array(
185 'per_page' => max( 1, (int) $count ),
186 'orderby' => 'date',
187 'order' => 'desc',
188 'videopress_has_guid' => 1,
189 );
190
191 // Attachments keep their original mime there, so narrow to videos before the ID-set constraint.
192 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
193 $args['videopress_only_videos'] = 1;
194 }
195
196 $request = new WP_REST_Request( 'GET', '/wp/v2/media' );
197 $request->set_query_params( $args );
198 $response = rest_do_request( $request );
199
200 if ( $response->is_error() ) {
201 return array();
202 }
203
204 $entries = array();
205 foreach ( (array) $response->get_data() as $item ) {
206 $item = (array) $item;
207 $guid = $item['jetpack_videopress_guid'] ?? '';
208
209 // The REST field is empty on a site without a connection; the attachment meta still knows.
210 if ( ( ! is_string( $guid ) || '' === $guid ) && ! empty( $item['id'] ) ) {
211 $guid = get_post_meta( (int) $item['id'], 'videopress_guid', true );
212 }
213
214 if ( ! is_string( $guid ) || ! preg_match( '/^[a-zA-Z0-9]{8}$/', $guid ) ) {
215 continue;
216 }
217
218 $details = isset( $item['media_details'] ) ? (array) $item['media_details'] : array();
219 $videopress = isset( $details['videopress'] ) ? (array) $details['videopress'] : array();
220 $duration = $videopress['duration'] ?? 0;
221 $height = $details['height'] ?? $videopress['height'] ?? 0;
222
223 $entries[] = array(
224 'guid' => $guid,
225 'durationMs' => is_numeric( $duration ) ? max( 0, (int) $duration ) : 0,
226 'height' => is_numeric( $height ) ? max( 0, (int) $height ) : 0,
227 );
228 }
229
230 return $entries;
231 }
232
233 /**
234 * Gets the user data
235 *
236 * @return array
237 */
238 public static function get_user_data() {
239 $user_data = array(
240 'items' => array(),
241 'pagination' => array(
242 'total' => 0,
243 'totalPages' => 1,
244 ),
245 'query' => array(
246 'order' => 'asc',
247 'orderBy' => 'name',
248 ),
249 '_meta' => array(
250 'relyOnInitialState' => true,
251 ),
252 );
253
254 $args = array(
255 'order' => $user_data['query']['order'],
256 'orderby' => $user_data['query']['orderBy'],
257 );
258
259 // Do an internal request for the user list
260 $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
261 $request->set_query_params( $args );
262 $response = rest_do_request( $request );
263
264 if ( $response->is_error() ) {
265 // @todo: error handling
266 return $user_data;
267 }
268
269 // load the real values
270 $user_data['items'] = $response->get_data();
271 $headers = $response->get_headers();
272
273 if ( isset( $headers['X-WP-Total'] ) ) {
274 $user_data['pagination']['total'] = $headers['X-WP-Total'];
275 }
276
277 if ( isset( $headers['X-WP-TotalPages'] ) ) {
278 $user_data['pagination']['totalPages'] = $headers['X-WP-TotalPages'];
279 }
280
281 return $user_data;
282 }
283
284 /**
285 * Gets the VideoPress used storage space in bytes
286 *
287 * @return int the used storage space
288 */
289 public static function get_storage_used() {
290 $site_data = Site::get_site_info();
291 if ( is_wp_error( $site_data ) ) {
292 return 0;
293 }
294
295 if ( isset( $site_data['options'] ) && isset( $site_data['options']['videopress_storage_used'] ) ) {
296 return intval( round( $site_data['options']['videopress_storage_used'] * 1024 * 1024 ) );
297 } else {
298 return 0;
299 }
300 }
301
302 /**
303 * Return all the initial state that depends on a valid site connection
304 *
305 * @return array
306 */
307 public static function get_connected_initial_state() {
308 return array(
309 'videos' => array(
310 'storageUsed' => self::get_storage_used(),
311 ),
312 'purchases' => array(
313 'items' => Site::get_purchases(),
314 'isFetching' => false,
315 ),
316 );
317 }
318
319 /**
320 * Checks if the site has as connected owner
321 */
322 public static function has_connected_owner() {
323 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
324 return true;
325 }
326
327 if ( ( new Connection_Manager() )->has_connected_owner() ) {
328 return true;
329 }
330
331 return false;
332 }
333
334 /**
335 * Checks if the user is able to perform actions that modify data
336 */
337 public static function can_perform_action() {
338 if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
339 return true;
340 }
341
342 $connection = new Connection_Manager();
343
344 return (
345 $connection->is_connected() &&
346 self::has_connected_owner() &&
347 $connection->is_user_connected()
348 );
349 }
350
351 /**
352 * Return the initial state of the VideoPress app,
353 * used to render initially the app in the frontend.
354 *
355 * @return array
356 */
357 public static function get_initial_state() {
358 $videopress_data = self::get_video_data();
359 $local_videos_data = self::get_video_data( false );
360
361 // Tweak local videos data.
362 $local_videos = array_map(
363 function ( $video ) {
364 $video = (array) $video;
365 $id = $video['id'];
366 $media_details = $video['media_details'];
367 $jetpack_videopress = $video['jetpack_videopress'];
368 $read_error = null;
369
370 // In malformed files, the media_details or jetpack_videopress properties are not arrays.
371 if ( ! is_array( $media_details ) || ! is_array( $jetpack_videopress ) ) {
372 $media_details = (array) $media_details;
373 $jetpack_videopress = (array) $jetpack_videopress;
374 $read_error = Upload_Exception::ERROR_MALFORMED_FILE;
375 }
376
377 // Check if video is already uploaded to VideoPress or has some error.
378 try {
379 $uploader = new Uploader( $id );
380 $is_uploaded_to_videopress = $uploader->is_uploaded();
381 } catch ( Upload_Exception $e ) {
382 $is_uploaded_to_videopress = false;
383 $read_error = $e->getCode();
384 }
385
386 $upload_date = $video['date'];
387 $url = $video['source_url'];
388
389 $title = $jetpack_videopress['title'];
390 $description = $jetpack_videopress['description'];
391 $caption = $jetpack_videopress['caption'];
392
393 $width = $media_details['width'] ?? null;
394 $height = $media_details['height'] ?? null;
395 $duration = $media_details['length'] ?? null;
396
397 return array(
398 'id' => $id,
399 'title' => $title,
400 'description' => $description,
401 'caption' => $caption,
402 'width' => $width,
403 'height' => $height,
404 'url' => $url,
405 'uploadDate' => $upload_date,
406 'duration' => $duration,
407 'isUploadedToVideoPress' => $is_uploaded_to_videopress,
408 'readError' => $read_error,
409 );
410 },
411 $local_videos_data['videos']
412 );
413
414 // Tweak VideoPress videos data.
415 $videos = array_map(
416 array( __CLASS__, 'prepare_videopress_video_data' ),
417 $videopress_data['videos']
418 );
419
420 $site_settings = self::get_videopress_settings();
421
422 $initial_state = array(
423 'users' => self::get_user_data(),
424 'siteSettings' => array(
425 'videoPressVideosPrivateForSite' => $site_settings['videopress_videos_private_for_site'],
426 'siteIsPrivate' => $site_settings['site_is_private'],
427 'siteType' => $site_settings['site_type'],
428 ),
429 'videos' => array(
430 'uploadedVideoCount' => $videopress_data['total'],
431 'items' => $videos,
432 'isFetching' => false,
433 'isFetchingUploadedVideoCount' => false,
434 'pagination' => array(
435 'totalPages' => $videopress_data['totalPages'],
436 'total' => $videopress_data['total'],
437 ),
438 'query' => $videopress_data['query'],
439 '_meta' => array(
440 'processedAllVideosBeingRemoved' => true,
441 'relyOnInitialState' => true,
442 ),
443 ),
444 'localVideos' => array(
445 'uploadedVideoCount' => $local_videos_data['total'],
446 'items' => $local_videos,
447 'isFetching' => false,
448 'isFetchingUploadedVideoCount' => false,
449 'pagination' => array(
450 'totalPages' => $local_videos_data['totalPages'],
451 'total' => $local_videos_data['total'],
452 ),
453 'query' => $local_videos_data['query'],
454 '_meta' => array(
455 'relyOnInitialState' => true,
456 ),
457 ),
458 );
459
460 if ( self::has_connected_owner() ) {
461 return array_merge_recursive( $initial_state, self::get_connected_initial_state() );
462 }
463
464 return $initial_state;
465 }
466
467 /**
468 * Maps a single VideoPress video, as returned by the media REST endpoint, to
469 * the shape consumed by the VideoPress dashboard app.
470 *
471 * Videos that are still processing (or otherwise have partial metadata) come
472 * back without `videopress`, `width` or `height` in their media_details, so
473 * every nested access is coalesced to avoid undefined-key and offset-on-null
474 * warnings.
475 *
476 * @param array|object $video A single video entry from the media REST endpoint.
477 * @return array The video data formatted for the dashboard app.
478 */
479 private static function prepare_videopress_video_data( $video ) {
480 $video = (array) $video;
481 $id = $video['id'];
482 $guid = $video['jetpack_videopress_guid'];
483 $media_details = (array) $video['media_details'];
484 $jetpack_videopress = (array) $video['jetpack_videopress'];
485
486 $videopress_media_details = $media_details['videopress'] ?? array();
487 $width = $media_details['width'] ?? null;
488 $height = $media_details['height'] ?? null;
489
490 $title = $jetpack_videopress['title'];
491 $description = $jetpack_videopress['description'];
492 $caption = $jetpack_videopress['caption'];
493 $rating = $jetpack_videopress['rating'];
494 $allow_download = $jetpack_videopress['allow_download'];
495 $display_embed = $jetpack_videopress['display_embed'];
496 $privacy_setting = $jetpack_videopress['privacy_setting'];
497 $needs_playback_token = $jetpack_videopress['needs_playback_token'];
498 $is_private = $jetpack_videopress['is_private'];
499
500 $original = $videopress_media_details['original'] ?? null;
501 $poster = ( ! $needs_playback_token ) ? ( $videopress_media_details['poster'] ?? null ) : null;
502 $upload_date = $videopress_media_details['upload_date'] ?? null;
503 $duration = $videopress_media_details['duration'] ?? null;
504 $file_url_base = $videopress_media_details['file_url_base'] ?? null;
505 $finished = $videopress_media_details['finished'] ?? null;
506 $files = $videopress_media_details['files'] ?? null;
507 $filename = $original !== null ? basename( $original ) : null;
508
509 if ( isset( $files['dvd']['original_img'] ) && isset( $file_url_base['https'] ) && $privacy_setting !== 1 ) {
510 $thumbnail = $file_url_base['https'] . $files['dvd']['original_img'];
511 } else {
512 $thumbnail = null;
513 }
514
515 return array(
516 'id' => $id,
517 'guid' => $guid,
518 'title' => $title,
519 'description' => $description,
520 'caption' => $caption,
521 'url' => $original,
522 'uploadDate' => $upload_date,
523 'duration' => $duration,
524 'isPrivate' => $is_private,
525 'posterImage' => $poster,
526 'allowDownload' => $allow_download,
527 'displayEmbed' => $display_embed,
528 'rating' => $rating,
529 'privacySetting' => $privacy_setting,
530 'needsPlaybackToken' => $needs_playback_token,
531 'width' => $width,
532 'height' => $height,
533 'poster' => array(
534 'src' => $poster,
535 ),
536 'thumbnail' => $thumbnail,
537 'finished' => $finished,
538 'filename' => $filename,
539 );
540 }
541 }
542