Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.67% covered (danger)
16.67%
15 / 90
16.67% covered (danger)
16.67%
2 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Attachment_Handler
16.67% covered (danger)
16.67%
15 / 90
16.67% covered (danger)
16.67%
2 / 12
829.25
0.00% covered (danger)
0.00%
0 / 1
 init
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
12
 maybe_get_attached_url_for_videopress
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 add_video_upload_mimes
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 filter_video_mimes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 delete_video_wpcom
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
42
 delete_video_poster_attachment
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 wp_mime_type_icon
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 add_videopress_extenstion
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 prepare_attachment_for_js
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 ajax_query_attachments_args
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 media_list_table_query
21.43% covered (danger)
21.43%
3 / 14
0.00% covered (danger)
0.00%
0 / 1
30.77
 disable_delete_if_disconnected
28.57% covered (danger)
28.57%
2 / 7
0.00% covered (danger)
0.00%
0 / 1
9.83
1<?php
2/**
3 * VideoPress Attachment_Handler
4 *
5 * @package automattic/jetpack-videopress
6 */
7
8namespace Automattic\Jetpack\VideoPress;
9
10use Automattic\Jetpack\Connection\Client;
11use Automattic\Jetpack\Current_Plan;
12use WP_Error;
13use WP_Post;
14
15/**
16 * VideoPress Attachment_Handler class.
17 */
18class Attachment_Handler {
19
20    /**
21     * Initializer
22     *
23     * This method should be called only once by the Initializer class. Do not call this method again.
24     */
25    public static function init() {
26
27        if ( ! Status::is_active() ) {
28            return;
29        }
30
31        add_filter( 'wp_get_attachment_url', array( __CLASS__, 'maybe_get_attached_url_for_videopress' ), 10, 2 );
32        add_filter( 'get_attached_file', array( __CLASS__, 'maybe_get_attached_url_for_videopress' ), 10, 2 );
33
34        if ( Current_Plan::supports( 'videopress' ) ) {
35            add_filter( 'upload_mimes', array( __CLASS__, 'add_video_upload_mimes' ), 999 );
36        }
37
38        add_filter( 'pre_delete_attachment', array( __CLASS__, 'delete_video_wpcom' ), 10, 2 );
39        add_filter( 'wp_mime_type_icon', array( __CLASS__, 'wp_mime_type_icon' ), 10, 3 );
40        add_filter( 'wp_video_extensions', array( __CLASS__, 'add_videopress_extenstion' ) );
41
42        add_filter( 'wp_prepare_attachment_for_js', array( __CLASS__, 'prepare_attachment_for_js' ) );
43        add_filter( 'ajax_query_attachments_args', array( __CLASS__, 'ajax_query_attachments_args' ) );
44        add_action( 'pre_get_posts', array( __CLASS__, 'media_list_table_query' ) );
45
46        add_filter( 'user_has_cap', array( __CLASS__, 'disable_delete_if_disconnected' ), 10, 3 );
47    }
48
49    /**
50     * Returns the VideoPress URL for the give post id, otherwise returns the provided default.
51     *
52     * This is an attachment-based filter handler.
53     *
54     * @param string $default The default return value if post id is not a VideoPress video.
55     * @param int    $post_id The post id for the current attachment.
56     */
57    public static function maybe_get_attached_url_for_videopress( $default, $post_id ) {
58        $videopress_url = videopress_get_attachment_url( $post_id );
59
60        if ( null !== $videopress_url ) {
61            return $videopress_url;
62        }
63
64        return $default;
65    }
66
67    /**
68     * Makes sure that all video mimes are added in, as multi site installs can remove them.
69     *
70     * @param array $existing_mimes Mime types to extend/filter.
71     * @return array
72     */
73    public static function add_video_upload_mimes( $existing_mimes = array() ) {
74        $mime_types  = wp_get_mime_types();
75        $video_types = array_filter( $mime_types, array( __CLASS__, 'filter_video_mimes' ) );
76
77        foreach ( $video_types as $key => $value ) {
78            $existing_mimes[ $key ] = $value;
79        }
80
81        // Make sure that videopress mimes are considered videos.
82        $existing_mimes['videopress'] = 'video/videopress';
83
84        return $existing_mimes;
85    }
86
87    /**
88     * Filter designed to get rid of non video mime types.
89     *
90     * @param string $value Mime type to filter.
91     * @return int
92     */
93    public static function filter_video_mimes( $value ) {
94        return preg_match( '@^video/@', $value );
95    }
96
97    /**
98     * Attempts to delete a VideoPress video from wp.com.
99     * Will block the deletion from continuing if certain errors return from the wp.com API.
100     *
101     * @param Boolean $delete if the deletion should occur or not (unused).
102     * @param WP_Post $post the post object.
103     *
104     * @return null|WP_Error|Boolean null if deletion should continue.
105     */
106    public static function delete_video_wpcom( $delete, $post ) {
107        if ( ! is_videopress_attachment( $post->ID ) ) {
108            return null;
109        }
110
111        $guid = get_post_meta( $post->ID, 'videopress_guid', true );
112        if ( empty( $guid ) ) {
113            self::delete_video_poster_attachment( $post->ID );
114            return null;
115        }
116
117        // Phone home and have wp.com delete the VideoPress entry and files.
118        $wpcom_response = Client::wpcom_json_api_request_as_blog(
119            sprintf( '/videos/%s/delete', $guid ),
120            '1.1',
121            array( 'method' => 'POST' ),
122            array( 'user_id' => get_current_user_id() )
123        );
124
125        if ( is_wp_error( $wpcom_response ) ) {
126            return $wpcom_response;
127        }
128
129        // Upon success or a 404 (video already deleted on wp.com), return null to allow the deletion to continue.
130        if ( 200 === $wpcom_response['response']['code'] || 404 === $wpcom_response['response']['code'] ) {
131            self::delete_video_poster_attachment( $post->ID );
132            return null;
133        }
134
135        // Otherwise we stop the deletion from proceeding.
136        return false;
137    }
138
139    /**
140     * Deletes a video poster attachment if it exists.
141     *
142     * @param int $attachment_id the WP attachment id.
143     */
144    private static function delete_video_poster_attachment( $attachment_id ) {
145        $thumbnail_id = get_post_meta( $attachment_id, '_thumbnail_id', true );
146        if ( ! empty( $thumbnail_id ) ) {
147            // Let's ensure this is a VP poster image before we delete it.
148            if ( '1' === get_post_meta( $thumbnail_id, 'videopress_poster_image', true ) ) {
149                // This call triggers the `delete_video_wpcom` filter again but it bails early at the is_videopress_attachment() check.
150                wp_delete_attachment( $thumbnail_id );
151            }
152        }
153    }
154
155    /**
156     * Filter the mime type icon.
157     *
158     * @param string $icon Icon path.
159     * @param string $mime Mime type.
160     * @param int    $post_id Post ID.
161     *
162     * @return string
163     */
164    public static function wp_mime_type_icon( $icon, $mime, $post_id ) {
165
166        if ( $mime !== 'video/videopress' ) {
167            return $icon;
168        }
169
170        $status = get_post_meta( $post_id, 'videopress_status', true );
171
172        if ( $status === 'complete' ) {
173            return $icon;
174        }
175
176        return 'https://wordpress.com/wp-content/mu-plugins/videopress/images/media-video-processing-icon.png';
177    }
178
179    /**
180     * Filter the list of supported video formats.
181     *
182     * @param array $extensions Supported video formats.
183     *
184     * @return array
185     */
186    public static function add_videopress_extenstion( $extensions ) {
187        $extensions[] = 'videopress';
188        return $extensions;
189    }
190
191    /**
192     * Make sure that any Video that has a VideoPress GUID passes that data back.
193     *
194     * @param WP_Post $post Attachment object.
195     */
196    public static function prepare_attachment_for_js( $post ) {
197        if ( 'video' === $post['type'] ) {
198            $guid = get_post_meta( $post['id'], 'videopress_guid', true );
199            if ( $guid ) {
200                $post['videopress_guid'] = $guid;
201            }
202        }
203        return $post;
204    }
205
206    /**
207     * Media Grid:
208     * Filter out any videopress video posters that we've downloaded,
209     * so that they don't seem to display twice.
210     *
211     * @param array $args Query variables.
212     */
213    public static function ajax_query_attachments_args( $args ) {
214        $meta_query = array(
215            array(
216                'key'     => 'videopress_poster_image',
217                'compare' => 'NOT EXISTS',
218            ),
219        );
220
221        // If there was already a meta query, let's AND it via
222        // nesting it with our new one. No need to specify the
223        // relation, as it defaults to AND.
224        if ( ! empty( $args['meta_query'] ) ) {
225            $meta_query[] = $args['meta_query'];
226        }
227        $args['meta_query'] = $meta_query;
228
229        return $args;
230    }
231
232    /**
233     * Media List:
234     * Do the same as `videopress_ajax_query_attachments_args()` but for the list view.
235     *
236     * @param array $query WP_Query instance.
237     */
238    public static function media_list_table_query( $query ) {
239
240        if (
241            ! function_exists( 'get_current_screen' )
242            || get_current_screen() === null
243        ) {
244            return;
245        }
246
247        if ( is_admin() && $query->is_main_query() && ( 'upload' === get_current_screen()->id ) ) {
248            $meta_query = array(
249                array(
250                    'key'     => 'videopress_poster_image',
251                    'compare' => 'NOT EXISTS',
252                ),
253            );
254
255            $old_meta_query = $query->get( 'meta_query' );
256            if ( $old_meta_query ) {
257                $meta_query[] = $old_meta_query;
258            }
259
260            $query->set( 'meta_query', $meta_query );
261        }
262    }
263
264    /**
265     * Filter to disable the `delete_post` capability
266     * for VideoPress attachments if the current user is
267     * not connected.
268     *
269     * @param array $allcaps All the capabilities of the user.
270     * @param array $cap     [0] Required capability.
271     * @param array $args    [0] Requested capability.
272     *                       [1] User ID.
273     *                       [2] Associated object ID.
274     * @return array the filtered array of capabilities.
275     */
276    public static function disable_delete_if_disconnected( $allcaps, $cap, $args ) {
277
278        // Only apply this filter to `delete_post` checks
279        if ( 'delete_post' !== $args[0] ) {
280            return $allcaps;
281        }
282
283        // Only apply this filter to VideoPress attachments
284        if ( ! is_videopress_attachment( $args[2] ) ) {
285            return $allcaps;
286        }
287
288        // Set the capability to false if the user can't perform the actions
289        if ( ! Data::can_perform_action() ) {
290            $allcaps[ $cap[0] ] = false;
291        }
292
293        return $allcaps;
294    }
295}