Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
12.38% covered (danger)
12.38%
13 / 105
10.00% covered (danger)
10.00%
1 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_REST_API_V2_Attachment_VideoPress_Data
12.62% covered (danger)
12.62%
13 / 103
10.00% covered (danger)
10.00%
1 / 10
900.61
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 register_fields
16.67% covered (danger)
16.67%
2 / 12
0.00% covered (danger)
0.00%
0 / 1
4.31
 add_jetpack_videopress_custom_query_filters
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filter_attachments_by_jetpack_videopress_fields
33.33% covered (danger)
33.33%
10 / 30
0.00% covered (danger)
0.00%
0 / 1
33.00
 get_schema
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 get
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 get_videopress_data
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
90
 is_video
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 remove_field_for_non_videos
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 video_is_private
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2/**
3 * Extend the REST API functionality for VideoPress users.
4 *
5 * @package automattic/jetpack-videopress
6 * @since-jetpack 7.1.0
7 * @since 0.3.1
8 */
9
10namespace Automattic\Jetpack\VideoPress;
11
12use Automattic\Jetpack\Connection\Manager as Jetpack_Connection;
13use WP_Post;
14use WP_REST_Request;
15use WP_REST_Response;
16
17/**
18 * Add per-attachment VideoPress data.
19 *
20 * { # Attachment Object
21 *   ...
22 *   jetpack_videopress: (object) VideoPress data
23 *   ...
24 * }
25 *
26 * @since 7.1.0
27 *
28 * @phan-constructor-used-for-side-effects
29 */
30class WPCOM_REST_API_V2_Attachment_VideoPress_Data {
31    /**
32     * The REST Object Type to which the jetpack_videopress field will be added.
33     *
34     * @var string
35     */
36    protected $object_type = 'attachment';
37
38    /**
39     * The name of the REST API field to add.
40     *
41     * @var string $field_name
42     */
43    protected $field_name = 'jetpack_videopress';
44
45    /**
46     * Constructor.
47     */
48    public function __construct() {
49        add_action( 'rest_api_init', array( $this, 'register_fields' ) );
50
51        if ( ! defined( 'IS_WPCOM' ) || ! IS_WPCOM ) {
52            add_action( 'rest_api_init', array( $this, 'add_jetpack_videopress_custom_query_filters' ) );
53        }
54
55        // do this again later to collect any CPTs that get registered later.
56        add_action( 'restapi_theme_init', array( $this, 'register_fields' ), 20 );
57    }
58
59    /**
60     * Registers the jetpack_videopress field and adds a filter to remove it for attachments that are not videos.
61     */
62    public function register_fields() {
63        global $wp_rest_additional_fields;
64
65        if ( ! empty( $wp_rest_additional_fields[ $this->object_type ][ $this->field_name ] ) ) {
66            return;
67        }
68
69        register_rest_field(
70            $this->object_type,
71            $this->field_name,
72            array(
73                'get_callback'    => array( $this, 'get' ),
74                'update_callback' => null,
75                'schema'          => $this->get_schema(),
76            )
77        );
78
79        add_filter( 'rest_prepare_attachment', array( $this, 'remove_field_for_non_videos' ), 10, 2 );
80    }
81
82    /**
83     * Adds the custom query filters
84     */
85    public function add_jetpack_videopress_custom_query_filters() {
86        add_filter( 'rest_attachment_query', array( $this, 'filter_attachments_by_jetpack_videopress_fields' ), 999, 2 );
87    }
88
89    /**
90     * Filter request args to handle the custom VideoPress query filters
91     *
92     * Possible filters:
93     *
94     * `no_videopress`: the returned attachments should not have a videopress_guid
95     *
96     * @param array           $args The original list of args before the filtering.
97     * @param WP_REST_Request $request The original request data.
98     */
99    public function filter_attachments_by_jetpack_videopress_fields( $args, $request ) {
100
101        if ( ! isset( $args['meta_query'] ) || ! is_array( $args['meta_query'] ) ) {
102            $args['meta_query'] = array();
103        }
104
105        /* To ignore all VideoPress videos, select only attachments without videopress_guid meta field */
106        if ( isset( $request['no_videopress'] ) ) {
107            $args['meta_query'][] = array(
108                'key'     => 'videopress_guid',
109                'compare' => 'NOT EXISTS',
110            );
111        }
112
113        /* Filter using privacy setting meta key */
114        if ( isset( $request['videopress_privacy_setting'] ) ) {
115            $videopress_privacy_setting = sanitize_text_field( $request['videopress_privacy_setting'] );
116
117            /* Allows the filtering to happens using a list of privacy settings separated by comma */
118            $videopress_privacy_setting_list = explode( ',', $videopress_privacy_setting );
119
120            $site_default_is_private = Data::get_videopress_videos_private_for_site();
121
122            if ( $site_default_is_private ) {
123                /**
124                 * If the search is looking for private videos and the site default is private,
125                 * the site default setting should be included on the search.
126                 */
127                if ( in_array( strval( \VIDEOPRESS_PRIVACY::IS_PRIVATE ), $videopress_privacy_setting_list, true ) ) {
128                    $videopress_privacy_setting_list[] = \VIDEOPRESS_PRIVACY::SITE_DEFAULT;
129                }
130            } else { // phpcs:ignore Universal.ControlStructures.DisallowLonelyIf.Found
131                /**
132                 * If the search is looking for public videos and the site default is public,
133                 * the site default setting should be included on the search.
134                 */
135                if ( in_array( strval( \VIDEOPRESS_PRIVACY::IS_PUBLIC ), $videopress_privacy_setting_list, true ) ) {
136                    $videopress_privacy_setting_list[] = \VIDEOPRESS_PRIVACY::SITE_DEFAULT;
137                }
138            }
139
140            $args['meta_query'][] = array(
141                'key'     => 'videopress_privacy_setting',
142                'value'   => $videopress_privacy_setting_list,
143                'compare' => 'IN',
144            );
145        }
146
147        /* Filter using rating meta key */
148        if ( isset( $request['videopress_rating'] ) ) {
149            $videopress_rating = sanitize_text_field( $request['videopress_rating'] );
150
151            /* Allows the filtering to happens using a list of ratings separated by comma */
152            $videopress_rating_list = explode( ',', $videopress_rating );
153
154            $args['meta_query'][] = array(
155                'key'     => 'videopress_rating',
156                'value'   => $videopress_rating_list,
157                'compare' => 'IN',
158            );
159        }
160
161        return $args;
162    }
163
164    /**
165     * Defines data structure and what elements are visible in which contexts
166     */
167    public function get_schema() {
168        return array(
169            '$schema'     => 'http://json-schema.org/draft-04/schema#',
170            'title'       => $this->field_name,
171            'type'        => 'object',
172            'context'     => array( 'view', 'edit' ),
173            'readonly'    => true,
174            'description' => __( 'VideoPress Data', 'jetpack-videopress-pkg' ),
175        );
176    }
177
178    /**
179     * Getter: Retrieve current VideoPress data for a given attachment.
180     *
181     * @param array           $attachment Response from the attachment endpoint.
182     * @param WP_REST_Request $request Request to the attachment endpoint.
183     *
184     * @return array
185     */
186    public function get( $attachment, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
187        if ( ! isset( $attachment['id'] ) ) {
188            return array();
189        }
190
191        $blog_id = Jetpack_Connection::get_site_id();
192        if ( ! is_int( $blog_id ) ) {
193            return array();
194        }
195
196        $videopress = $this->get_videopress_data( (int) $attachment['id'], $blog_id );
197
198        if ( ! $videopress ) {
199            return array();
200        }
201
202        return $videopress;
203    }
204
205    /**
206     * Gets the VideoPress GUID for a given attachment.
207     *
208     * This is pulled out into a separate method to support unit test mocking.
209     *
210     * @param int $attachment_id Attachment ID.
211     * @param int $blog_id Blog ID.
212     *
213     * @return array
214     */
215    public function get_videopress_data( $attachment_id, $blog_id ) {
216        $info = video_get_info_by_blogpostid( $blog_id, $attachment_id );
217        if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
218            $title       = video_get_title( $blog_id, $attachment_id );
219            $description = video_get_description( $blog_id, $attachment_id );
220
221            $video_attachment = get_blog_post( $blog_id, $attachment_id );
222            if ( null === $video_attachment ) {
223                $caption = '';
224            } else {
225                $caption = $video_attachment->post_excerpt;
226            }
227        } else {
228            $title       = $info->title;
229            $description = $info->description;
230            $caption     = $info->caption;
231        }
232
233        $video_privacy_setting    = ! isset( $info->privacy_setting ) ? \VIDEOPRESS_PRIVACY::SITE_DEFAULT : intval( $info->privacy_setting );
234        $private_enabled_for_site = Data::get_videopress_videos_private_for_site();
235        $is_private               = $this->video_is_private( $video_privacy_setting, $private_enabled_for_site );
236
237        // The video needs a playback token if it's private for any reason (video privacy setting or site default privacy setting)
238        $video_needs_playback_token = $is_private;
239
240        return array(
241            'title'                    => $title,
242            'description'              => $description,
243            'caption'                  => $caption,
244            'guid'                     => $info->guid ?? null,
245            'rating'                   => $info->rating ?? null,
246            'allow_download'           =>
247                isset( $info->allow_download ) && $info->allow_download ? 1 : 0,
248            'display_embed'            =>
249                isset( $info->display_embed ) && $info->display_embed ? 1 : 0,
250            'privacy_setting'          => $video_privacy_setting,
251            'needs_playback_token'     => $video_needs_playback_token,
252            'is_private'               => $is_private,
253            'private_enabled_for_site' => $private_enabled_for_site,
254        );
255    }
256
257    /**
258     * Checks if the given attachment is a video.
259     *
260     * @param object $attachment The attachment object.
261     *
262     * @return false|int
263     */
264    public function is_video( $attachment ) {
265        return isset( $attachment->post_mime_type ) && wp_startswith( $attachment->post_mime_type, 'video/' );
266    }
267
268    /**
269     * Removes the jetpack_videopress field from the response if the
270     * given attachment is not a video.
271     *
272     * @param WP_REST_Response $response Response from the attachment endpoint.
273     * @param WP_Post          $attachment The original attachment object.
274     *
275     * @return mixed
276     */
277    public function remove_field_for_non_videos( $response, $attachment ) {
278        if ( ! $this->is_video( $attachment ) ) {
279            unset( $response->data[ $this->field_name ] );
280        }
281
282        return $response;
283    }
284
285    /**
286     * Determines if a video is private based on the video privacy
287     * setting and the site default privacy setting.
288     *
289     * @param int  $video_privacy_setting The privacy setting for the video.
290     * @param bool $private_enabled_for_site Flag stating if the default video privacy is private.
291     *
292     * @return bool
293     */
294    private function video_is_private( $video_privacy_setting, $private_enabled_for_site ) {
295        if ( $video_privacy_setting === \VIDEOPRESS_PRIVACY::IS_PUBLIC ) {
296            return false;
297        }
298        if ( $video_privacy_setting === \VIDEOPRESS_PRIVACY::IS_PRIVATE ) {
299            return true;
300        }
301
302        return $private_enabled_for_site;
303    }
304}
305
306if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
307    wpcom_rest_api_v2_load_plugin( 'Automattic\Jetpack\VideoPress\WPCOM_REST_API_V2_Attachment_VideoPress_Data' );
308}