Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
43.48% covered (danger)
43.48%
110 / 253
25.00% covered (danger)
25.00%
3 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Data
43.48% covered (danger)
43.48%
110 / 253
25.00% covered (danger)
25.00%
3 / 12
410.66
0.00% covered (danger)
0.00%
0 / 1
 get_blog_id
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_videopress_videos_private_for_site
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
8.12
 get_videopress_auto_subtitles_disabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get_videopress_settings
71.43% covered (warning)
71.43%
10 / 14
0.00% covered (danger)
0.00%
0 / 1
4.37
 get_video_data
96.97% covered (success)
96.97%
32 / 33
0.00% covered (danger)
0.00%
0 / 1
5
 get_user_data
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
20
 get_storage_used
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 get_connected_initial_state
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 has_connected_owner
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
5.02
 can_perform_action
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
5.05
 get_initial_state
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 1
30
 prepare_videopress_video_data
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
1 / 1
6
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
9namespace Automattic\Jetpack\VideoPress;
10
11use Automattic\Jetpack\Connection\Manager as Connection_Manager;
12use Automattic\Jetpack\Status\Host;
13use WP_REST_Request;
14/**
15 * The Data class.
16 */
17class 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 the VideoPress Settings.
66     *
67     * @return array The settings as an associative array.
68     */
69    public static function get_videopress_settings() {
70        $site_type       = 'jetpack';
71        $site_is_private = false;
72
73        if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
74            $site_type       = 'simple';
75            $site_is_private = video_is_private_wpcom_blog( get_current_blog_id() );
76        } elseif ( ( new Host() )->is_woa_site() ) {
77            $site_type       = 'atomic';
78            $site_is_private = intval( get_option( 'blog_public', '' ) ) === -1;
79        }
80
81        return array(
82            'videopress_videos_private_for_site' => self::get_videopress_videos_private_for_site(),
83            'videopress_auto_subtitles_disabled' => self::get_videopress_auto_subtitles_disabled(),
84            'site_is_private'                    => $site_is_private,
85            'site_type'                          => $site_type,
86        );
87    }
88
89    /**
90     * Gets the video data
91     *
92     * @param boolean $is_videopress - True when getting VideoPress data.
93     * @return array
94     */
95    public static function get_video_data( $is_videopress = true ) {
96        $video_data = array(
97            'videos'     => array(),
98            'total'      => 0,
99            'totalPages' => 0,
100            'query'      => array(
101                'order'        => 'desc',
102                'orderBy'      => 'date',
103                'itemsPerPage' => 6,
104                'page'         => 1,
105            ),
106        );
107
108        $args = array(
109            'order'    => $video_data['query']['order'],
110            'orderby'  => $video_data['query']['orderBy'],
111            'per_page' => $video_data['query']['itemsPerPage'],
112            'page'     => $video_data['query']['page'],
113        );
114
115        if ( $is_videopress ) {
116            $args['mime_type'] = 'video/videopress';
117        } else {
118            $args['media_type']    = 'video';
119            $args['no_videopress'] = true;
120        }
121
122        // Do an internal request for the media list
123        $request = new WP_REST_Request( 'GET', '/wp/v2/media' );
124        $request->set_query_params( $args );
125        $response = rest_do_request( $request );
126
127        if ( $response->is_error() ) {
128            // @todo: error handling
129            return $video_data;
130        }
131
132        // load the real values
133        $video_data['videos'] = $response->get_data();
134        $headers              = $response->get_headers();
135
136        if ( isset( $headers['X-WP-Total'] ) ) {
137            $video_data['total'] = $headers['X-WP-Total'];
138        }
139
140        if ( isset( $headers['X-WP-TotalPages'] ) ) {
141            $video_data['totalPages'] = $headers['X-WP-TotalPages'];
142        }
143
144        return $video_data;
145    }
146
147    /**
148     * Gets the user data
149     *
150     * @return array
151     */
152    public static function get_user_data() {
153        $user_data = array(
154            'items'      => array(),
155            'pagination' => array(
156                'total'      => 0,
157                'totalPages' => 1,
158            ),
159            'query'      => array(
160                'order'   => 'asc',
161                'orderBy' => 'name',
162            ),
163            '_meta'      => array(
164                'relyOnInitialState' => true,
165            ),
166        );
167
168        $args = array(
169            'order'   => $user_data['query']['order'],
170            'orderby' => $user_data['query']['orderBy'],
171        );
172
173        // Do an internal request for the user list
174        $request = new WP_REST_Request( 'GET', '/wp/v2/users' );
175        $request->set_query_params( $args );
176        $response = rest_do_request( $request );
177
178        if ( $response->is_error() ) {
179            // @todo: error handling
180            return $user_data;
181        }
182
183        // load the real values
184        $user_data['items'] = $response->get_data();
185        $headers            = $response->get_headers();
186
187        if ( isset( $headers['X-WP-Total'] ) ) {
188            $user_data['pagination']['total'] = $headers['X-WP-Total'];
189        }
190
191        if ( isset( $headers['X-WP-TotalPages'] ) ) {
192            $user_data['pagination']['totalPages'] = $headers['X-WP-TotalPages'];
193        }
194
195        return $user_data;
196    }
197
198    /**
199     * Gets the VideoPress used storage space in bytes
200     *
201     * @return int the used storage space
202     */
203    public static function get_storage_used() {
204        $site_data = Site::get_site_info();
205        if ( is_wp_error( $site_data ) ) {
206            return 0;
207        }
208
209        if ( isset( $site_data['options'] ) && isset( $site_data['options']['videopress_storage_used'] ) ) {
210            return intval( round( $site_data['options']['videopress_storage_used'] * 1024 * 1024 ) );
211        } else {
212            return 0;
213        }
214    }
215
216    /**
217     * Return all the initial state that depends on a valid site connection
218     *
219     * @return array
220     */
221    public static function get_connected_initial_state() {
222        return array(
223            'videos'    => array(
224                'storageUsed' => self::get_storage_used(),
225            ),
226            'purchases' => array(
227                'items'      => Site::get_purchases(),
228                'isFetching' => false,
229            ),
230        );
231    }
232
233    /**
234     * Checks if the site has as connected owner
235     */
236    public static function has_connected_owner() {
237        if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
238            return true;
239        }
240
241        if ( ( new Connection_Manager() )->has_connected_owner() ) {
242            return true;
243        }
244
245        return false;
246    }
247
248    /**
249     * Checks if the user is able to perform actions that modify data
250     */
251    public static function can_perform_action() {
252        if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
253            return true;
254        }
255
256        $connection = new Connection_Manager();
257
258        return (
259            $connection->is_connected() &&
260            self::has_connected_owner() &&
261            $connection->is_user_connected()
262        );
263    }
264
265    /**
266     * Return the initial state of the VideoPress app,
267     * used to render initially the app in the frontend.
268     *
269     * @return array
270     */
271    public static function get_initial_state() {
272        $videopress_data   = self::get_video_data();
273        $local_videos_data = self::get_video_data( false );
274
275        // Tweak local videos data.
276        $local_videos = array_map(
277            function ( $video ) {
278                $video              = (array) $video;
279                $id                 = $video['id'];
280                $media_details      = $video['media_details'];
281                $jetpack_videopress = $video['jetpack_videopress'];
282                $read_error         = null;
283
284                // In malformed files, the media_details or jetpack_videopress properties are not arrays.
285                if ( ! is_array( $media_details ) || ! is_array( $jetpack_videopress ) ) {
286                    $media_details      = (array) $media_details;
287                    $jetpack_videopress = (array) $jetpack_videopress;
288                    $read_error         = Upload_Exception::ERROR_MALFORMED_FILE;
289                }
290
291                // Check if video is already uploaded to VideoPress or has some error.
292                try {
293                    $uploader                  = new Uploader( $id );
294                    $is_uploaded_to_videopress = $uploader->is_uploaded();
295                } catch ( Upload_Exception $e ) {
296                    $is_uploaded_to_videopress = false;
297                    $read_error                = $e->getCode();
298                }
299
300                $upload_date = $video['date'];
301                $url         = $video['source_url'];
302
303                $title       = $jetpack_videopress['title'];
304                $description = $jetpack_videopress['description'];
305                $caption     = $jetpack_videopress['caption'];
306
307                $width    = $media_details['width'] ?? null;
308                $height   = $media_details['height'] ?? null;
309                $duration = $media_details['length'] ?? null;
310
311                return array(
312                    'id'                     => $id,
313                    'title'                  => $title,
314                    'description'            => $description,
315                    'caption'                => $caption,
316                    'width'                  => $width,
317                    'height'                 => $height,
318                    'url'                    => $url,
319                    'uploadDate'             => $upload_date,
320                    'duration'               => $duration,
321                    'isUploadedToVideoPress' => $is_uploaded_to_videopress,
322                    'readError'              => $read_error,
323                );
324            },
325            $local_videos_data['videos']
326        );
327
328        // Tweak VideoPress videos data.
329        $videos = array_map(
330            array( __CLASS__, 'prepare_videopress_video_data' ),
331            $videopress_data['videos']
332        );
333
334        $site_settings = self::get_videopress_settings();
335
336        $initial_state = array(
337            'users'        => self::get_user_data(),
338            'siteSettings' => array(
339                'videoPressVideosPrivateForSite' => $site_settings['videopress_videos_private_for_site'],
340                'siteIsPrivate'                  => $site_settings['site_is_private'],
341                'siteType'                       => $site_settings['site_type'],
342            ),
343            'videos'       => array(
344                'uploadedVideoCount'           => $videopress_data['total'],
345                'items'                        => $videos,
346                'isFetching'                   => false,
347                'isFetchingUploadedVideoCount' => false,
348                'pagination'                   => array(
349                    'totalPages' => $videopress_data['totalPages'],
350                    'total'      => $videopress_data['total'],
351                ),
352                'query'                        => $videopress_data['query'],
353                '_meta'                        => array(
354                    'processedAllVideosBeingRemoved' => true,
355                    'relyOnInitialState'             => true,
356                ),
357            ),
358            'localVideos'  => array(
359                'uploadedVideoCount'           => $local_videos_data['total'],
360                'items'                        => $local_videos,
361                'isFetching'                   => false,
362                'isFetchingUploadedVideoCount' => false,
363                'pagination'                   => array(
364                    'totalPages' => $local_videos_data['totalPages'],
365                    'total'      => $local_videos_data['total'],
366                ),
367                'query'                        => $local_videos_data['query'],
368                '_meta'                        => array(
369                    'relyOnInitialState' => true,
370                ),
371            ),
372        );
373
374        if ( self::has_connected_owner() ) {
375            return array_merge_recursive( $initial_state, self::get_connected_initial_state() );
376        }
377
378        return $initial_state;
379    }
380
381    /**
382     * Maps a single VideoPress video, as returned by the media REST endpoint, to
383     * the shape consumed by the VideoPress dashboard app.
384     *
385     * Videos that are still processing (or otherwise have partial metadata) come
386     * back without `videopress`, `width` or `height` in their media_details, so
387     * every nested access is coalesced to avoid undefined-key and offset-on-null
388     * warnings.
389     *
390     * @param array|object $video A single video entry from the media REST endpoint.
391     * @return array The video data formatted for the dashboard app.
392     */
393    private static function prepare_videopress_video_data( $video ) {
394        $video              = (array) $video;
395        $id                 = $video['id'];
396        $guid               = $video['jetpack_videopress_guid'];
397        $media_details      = (array) $video['media_details'];
398        $jetpack_videopress = (array) $video['jetpack_videopress'];
399
400        $videopress_media_details = $media_details['videopress'] ?? array();
401        $width                    = $media_details['width'] ?? null;
402        $height                   = $media_details['height'] ?? null;
403
404        $title                = $jetpack_videopress['title'];
405        $description          = $jetpack_videopress['description'];
406        $caption              = $jetpack_videopress['caption'];
407        $rating               = $jetpack_videopress['rating'];
408        $allow_download       = $jetpack_videopress['allow_download'];
409        $display_embed        = $jetpack_videopress['display_embed'];
410        $privacy_setting      = $jetpack_videopress['privacy_setting'];
411        $needs_playback_token = $jetpack_videopress['needs_playback_token'];
412        $is_private           = $jetpack_videopress['is_private'];
413
414        $original      = $videopress_media_details['original'] ?? null;
415        $poster        = ( ! $needs_playback_token ) ? ( $videopress_media_details['poster'] ?? null ) : null;
416        $upload_date   = $videopress_media_details['upload_date'] ?? null;
417        $duration      = $videopress_media_details['duration'] ?? null;
418        $file_url_base = $videopress_media_details['file_url_base'] ?? null;
419        $finished      = $videopress_media_details['finished'] ?? null;
420        $files         = $videopress_media_details['files'] ?? null;
421        $filename      = $original !== null ? basename( $original ) : null;
422
423        if ( isset( $files['dvd']['original_img'] ) && isset( $file_url_base['https'] ) && $privacy_setting !== 1 ) {
424            $thumbnail = $file_url_base['https'] . $files['dvd']['original_img'];
425        } else {
426            $thumbnail = null;
427        }
428
429        return array(
430            'id'                 => $id,
431            'guid'               => $guid,
432            'title'              => $title,
433            'description'        => $description,
434            'caption'            => $caption,
435            'url'                => $original,
436            'uploadDate'         => $upload_date,
437            'duration'           => $duration,
438            'isPrivate'          => $is_private,
439            'posterImage'        => $poster,
440            'allowDownload'      => $allow_download,
441            'displayEmbed'       => $display_embed,
442            'rating'             => $rating,
443            'privacySetting'     => $privacy_setting,
444            'needsPlaybackToken' => $needs_playback_token,
445            'width'              => $width,
446            'height'             => $height,
447            'poster'             => array(
448                'src' => $poster,
449            ),
450            'thumbnail'          => $thumbnail,
451            'finished'           => $finished,
452            'filename'           => $filename,
453        );
454    }
455}