Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
21.43% covered (danger)
21.43%
15 / 70
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
WPCOM_JSON_API_Update_Media_Endpoint
53.57% covered (warning)
53.57%
15 / 28
50.00% covered (danger)
50.00%
1 / 2
23.11
0.00% covered (danger)
0.00%
0 / 1
 current_user_can_edit_media_item
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
4
 callback
35.00% covered (danger)
35.00%
7 / 20
0.00% covered (danger)
0.00%
0 / 1
20.46
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2/**
3 * Update media item info endpoint.
4 *
5 * Endpoint: /sites/%s/media/%d
6 */
7
8if ( ! defined( 'ABSPATH' ) ) {
9    exit( 0 );
10}
11
12new WPCOM_JSON_API_Update_Media_Endpoint(
13    array(
14        'description'          => 'Edit basic information about a media item.',
15        'group'                => 'media',
16        'stat'                 => 'media:1:POST',
17        'method'               => 'POST',
18        'path'                 => '/sites/%s/media/%d',
19        'deprecated'           => true,
20        'max_version'          => '1',
21        'new_version'          => '1.1',
22        'path_labels'          => array(
23            '$site'     => '(int|string) Site ID or domain',
24            '$media_ID' => '(int) The ID of the media item',
25        ),
26
27        'request_format'       => array(
28            'title'       => '(string) The file name.',
29            'caption'     => '(string) File caption.',
30            'description' => '(HTML) Description of the file.',
31        ),
32
33        'response_format'      => array(
34            'id'          => '(int) The ID of the media item',
35            'date'        => '(ISO 8601 datetime) The date the media was uploaded',
36            'parent'      => '(int) ID of the post this media is attached to',
37            'link'        => '(string) URL to the file',
38            'title'       => '(string) File name',
39            'caption'     => '(string) User provided caption of the file',
40            'description' => '(string) Description of the file',
41            'metadata'    => '(array) Array of metadata about the file, such as Exif data or sizes',
42        ),
43        'example_request'      => 'https://public-api.wordpress.com/rest/v1.1/sites/82974409/media/446',
44        'example_request_data' => array(
45            'headers' => array(
46                'authorization' => 'Bearer YOUR_API_TOKEN',
47            ),
48            'body'    => array(
49                'title' => 'Updated Title',
50            ),
51        ),
52    )
53);
54
55/**
56 * Update media item info class.
57 *
58 * @phan-constructor-used-for-side-effects
59 */
60class WPCOM_JSON_API_Update_Media_Endpoint extends WPCOM_JSON_API_Endpoint {
61    /**
62     * Whether the current user may edit the given media item.
63     *
64     * `upload_files` is a primitive capability and ignores any object passed to it,
65     * so it only tells us the caller may upload something, never that they may edit
66     * this particular item. A missing item is passed through so the caller receives
67     * the endpoint's own 404 rather than a 403. A userless request gets no exemption:
68     * `edit_post` fails closed for user 0 like any other caller.
69     *
70     * Non-attachments are refused outright. `get_post()` resolves any post type, so
71     * without this test a media endpoint edits ordinary posts, pages and revisions.
72     * The post-type test must stay below the missing-post passthrough: that branch
73     * returns true, so testing there would skip `edit_post` for ordinary posts.
74     *
75     * A non-attachment yields 403, not the 404 a missing item gets. This is a boolean
76     * gate, and `get_media_item*()` resolves any post type, so a passthrough would
77     * return 200 rather than 404. Revisit if clients conflate it with an auth failure.
78     *
79     * Do not move this into a trait: this file instantiates the endpoint above the
80     * class declaration, and `use Trait;` disables PHP early binding, which makes the
81     * file fatal with "Class not found".
82     *
83     * @param int $media_id Media post ID.
84     * @return bool
85     */
86    protected function current_user_can_edit_media_item( $media_id ) {
87        if ( ! current_user_can( 'upload_files' ) ) {
88            return false;
89        }
90
91        $post = get_post( $media_id );
92
93        if ( ! $post ) {
94            return true;
95        }
96
97        if ( 'attachment' !== $post->post_type ) {
98            return false;
99        }
100
101        return current_user_can( 'edit_post', $media_id );
102    }
103
104    /**
105     * Update media item info API callback.
106     *
107     * @param string $path API path.
108     * @param int    $blog_id Blog ID.
109     * @param int    $media_id Media ID.
110     *
111     * @return object|WP_Error
112     */
113    public function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
114        $blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
115        if ( is_wp_error( $blog_id ) ) {
116            return $blog_id;
117        }
118
119        if ( ! $this->current_user_can_edit_media_item( $media_id ) ) {
120            return new WP_Error( 'unauthorized', 'User cannot edit media', 403 );
121        }
122
123        $item = $this->get_media_item( $media_id );
124
125        if ( is_wp_error( $item ) ) {
126            return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
127        }
128
129        $input  = $this->input( true );
130        $insert = array();
131
132        if ( ! empty( $input['title'] ) ) {
133            $insert['post_title'] = $input['title'];
134        }
135
136        if ( ! empty( $input['caption'] ) ) {
137            $insert['post_excerpt'] = $input['caption'];
138        }
139
140        if ( ! empty( $input['description'] ) ) {
141            $insert['post_content'] = $input['description'];
142        }
143
144        $insert['ID'] = $media_id;
145        wp_update_post( (object) $insert );
146
147        $item = $this->get_media_item( $media_id );
148        return $item;
149    }
150}