← All changes
|
json-endpoints/class.wpcom-json-api-upload-media-v1-1-endpoint.php
+55
-6
13.0.2
→
16.3-a.5
View file →
| @@ -4,8 +4,12 @@ | ||
| 4 | 4 | * |
| 5 | 5 | * Endpoint: /sites/%s/media/new |
| 6 | 6 | */ |
| 7 | 7 | |
| 8 | +if ( ! defined( 'ABSPATH' ) ) { | |
| 9 | + exit( 0 ); | |
| 10 | +} | |
| 11 | + | |
| 8 | 12 | new WPCOM_JSON_API_Upload_Media_v1_1_Endpoint( |
| 9 | 13 | array( |
| 10 | 14 | 'description' => 'Upload a new piece of media.', |
| 11 | 15 | 'allow_cross_origin_request' => true, |
| @@ -24,9 +28,9 @@ | ||
| 24 | 28 | 'media' => '(media) An array of media to attach to the post. To upload media, the entire request should be multipart/form-data encoded. Accepts jpg, jpeg, png, gif, pdf, doc, ppt, odt, pptx, docx, pps, ppsx, xls, xlsx, key. Audio and Video may also be available. See <code>allowed_file_types</code> in the options response of the site endpoint.<br /><br /><strong>Example</strong>:<br />' . |
| 25 | 29 | "<code>curl \<br />--form 'media[]=@/path/to/file.jpg' \<br />-H 'Authorization: BEARER your-token' \<br />'https://public-api.wordpress.com/rest/v1/sites/123/media/new'</code>", |
| 26 | 30 | 'media_urls' => '(array) An array of URLs to upload to the post. Errors produced by media uploads, if any, will be in `media_errors` in the response.', |
| 27 | 31 | 'attrs' => '(array) An array of attributes (`title`, `description`, `caption` `alt` for images, `artist` for audio, `album` for audio, and `parent_id`) are supported to assign to the media uploaded via the `media` or `media_urls` properties. You must use a numeric index for the keys of `attrs` which follows the same sequence as `media` and `media_urls`. <br /><br /><strong>Example</strong>:<br />' . |
| 28 | - "<code>curl \<br />--form 'media[]=@/path/to/file1.jpg' \<br />--form 'media_urls[]=http://example.com/file2.jpg' \<br /> \<br />--form 'attrs[0][caption]=This will be the caption for file1.jpg' \<br />--form 'attrs[1][title]=This will be the title for file2.jpg' \<br />-H 'Authorization: BEARER your-token' \<br />'https://public-api.wordpress.com/rest/v1/sites/123/posts/new'</code>", | |
| 32 | + "<code>curl \<br />--form 'media[]=@/path/to/file1.jpg' \<br />--form 'media_urls[]=http://example.com/file2.jpg' \<br /> \<br />--form 'attrs[0][caption]=This will be the caption for file1.jpg' \<br />--form 'attrs[1][title]=This will be the title for file2.jpg' \<br />-H 'Authorization: BEARER your-token' \<br />'https://public-api.wordpress.com/rest/v1/sites/123/media/new'</code>", | |
| 29 | 33 | ), |
| 30 | 34 | |
| 31 | 35 | 'response_format' => array( |
| 32 | 36 | 'media' => '(array) Array of uploaded media objects', |
| @@ -47,8 +51,10 @@ | ||
| 47 | 51 | |
| 48 | 52 | // phpcs:disable PEAR.NamingConventions.ValidClassName.Invalid |
| 49 | 53 | /** |
| 50 | 54 | * Upload media item API class v1.1 |
| 55 | + * | |
| 56 | + * @phan-constructor-used-for-side-effects | |
| 51 | 57 | */ |
| 52 | 58 | class WPCOM_JSON_API_Upload_Media_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint { |
| 53 | 59 | /** |
| 54 | 60 | * Upload media item API endpoint callback v1.1 |
| @@ -77,8 +83,52 @@ | ||
| 77 | 83 | if ( empty( $media_files ) && empty( $media_urls ) ) { |
| 78 | 84 | return new WP_Error( 'invalid_input', 'No media provided in input.' ); |
| 79 | 85 | } |
| 80 | 86 | |
| 87 | + /* | |
| 88 | + * Attaching an upload to a post is an edit of that post, so a caller-supplied | |
| 89 | + * `parent_id` takes `edit_post` on the target. `upload_files` above says only that | |
| 90 | + * the caller may upload something, never where they may put it. | |
| 91 | + * | |
| 92 | + * Every target is checked here, before the first file is written. Refusing from | |
| 93 | + * inside `handle_media_creation_v1_1()` would leave the items it already created | |
| 94 | + * on disk and in the database, and a retry would upload them again. | |
| 95 | + * | |
| 96 | + * An upload-token request drops `parent_id` instead of being refused. Such a request | |
| 97 | + * runs with no logged-in user by construction -- `is_authorized_with_upload_token()` | |
| 98 | + * fails as soon as `get_current_user_id()` is non-zero -- so it can never | |
| 99 | + * demonstrate `edit_post` on any target, and refusing would break any client that | |
| 100 | + * pairs a token with `parent_id` for no security gain. Dropping lands the item at | |
| 101 | + * `post_parent` 0, the same place `absint()` already puts unusable input. The token | |
| 102 | + * is not treated as trusted here: it is an opaque bearer credential mintable by any | |
| 103 | + * logged-in user via `/sites/%s/media/token`, so it must not buy an attach. | |
| 104 | + * | |
| 105 | + * Zero is exempt: it names no target, and `edit_post` fails closed on 0. | |
| 106 | + */ | |
| 107 | + if ( $this->api->is_authorized_with_upload_token() ) { | |
| 108 | + foreach ( $media_attrs as $i => $media_attr ) { | |
| 109 | + $media_attr = (array) $media_attr; | |
| 110 | + unset( $media_attr['parent_id'] ); | |
| 111 | + $media_attrs[ $i ] = $media_attr; | |
| 112 | + } | |
| 113 | + } else { | |
| 114 | + foreach ( $media_attrs as $media_attr ) { | |
| 115 | + // An entry may arrive as an object or as something that is neither; casting | |
| 116 | + // keeps a string entry from being indexed as an array. | |
| 117 | + $media_attr = (array) $media_attr; | |
| 118 | + | |
| 119 | + if ( empty( $media_attr['parent_id'] ) ) { | |
| 120 | + continue; | |
| 121 | + } | |
| 122 | + | |
| 123 | + $parent_id = absint( $media_attr['parent_id'] ); | |
| 124 | + | |
| 125 | + if ( $parent_id && ! current_user_can( 'edit_post', $parent_id ) ) { | |
| 126 | + return new WP_Error( 'unauthorized', 'User cannot edit the parent post', 403 ); | |
| 127 | + } | |
| 128 | + } | |
| 129 | + } | |
| 130 | + | |
| 81 | 131 | $jetpack_sync = null; |
| 82 | 132 | $is_jetpack_site = false; |
| 83 | 133 | if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { |
| 84 | 134 | // For jetpack sites, we send the media via a different method, because the sync is very different. |
| @@ -92,9 +142,9 @@ | ||
| 92 | 142 | $errors = array(); |
| 93 | 143 | |
| 94 | 144 | // We're splitting out videos for Jetpack sites. |
| 95 | 145 | foreach ( $media_files as $media_item ) { |
| 96 | - if ( preg_match( '@^video/@', $media_item['type'] ) && $is_jetpack_site ) { | |
| 146 | + if ( isset( $media_item['type'] ) && preg_match( '@^video/@', $media_item['type'] ) && $is_jetpack_site ) { | |
| 97 | 147 | if ( defined( 'IS_WPCOM' ) && IS_WPCOM && |
| 98 | 148 | defined( 'VIDEOPRESS_JETPACK_VIDEO_ENABLED' ) && VIDEOPRESS_JETPACK_VIDEO_ENABLED |
| 99 | 149 | ) { |
| 100 | 150 | // Check that video upload space is available for a Jetpack site (skipped if site is Atomic). |
| @@ -112,9 +162,9 @@ | ||
| 112 | 162 | } |
| 113 | 163 | |
| 114 | 164 | // New Jetpack / VideoPress media upload processing. |
| 115 | 165 | if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) { |
| 116 | - if ( is_countable( $jetpack_media_files ) && count( $jetpack_media_files ) > 0 ) { | |
| 166 | + if ( count( $jetpack_media_files ) > 0 ) { | |
| 117 | 167 | add_filter( 'upload_mimes', array( $this, 'allow_video_uploads' ) ); |
| 118 | 168 | |
| 119 | 169 | // get_space_used() checks blog upload directory storage, |
| 120 | 170 | // so filter it temporarily to return only video storage used. |
| @@ -135,9 +185,9 @@ | ||
| 135 | 185 | } |
| 136 | 186 | } |
| 137 | 187 | |
| 138 | 188 | // Normal WPCOM upload processing. |
| 139 | - if ( ( is_countable( $other_media_files ) && count( $other_media_files ) > 0 ) || ( is_countable( $other_media_files ) && count( $media_urls ) > 0 ) ) { | |
| 189 | + if ( count( $other_media_files ) > 0 || count( $media_urls ) > 0 ) { | |
| 140 | 190 | if ( is_multisite() ) { // Do not check for available space in non multisites. |
| 141 | 191 | add_filter( 'wp_handle_upload_prefilter', array( $this, 'check_upload_size' ), 9 ); // used for direct media uploads. |
| 142 | 192 | add_filter( 'wp_handle_sideload_prefilter', array( $this, 'check_upload_size' ), 9 ); // used for uploading media via url. |
| 143 | 193 | } |
| @@ -164,9 +214,8 @@ | ||
| 164 | 214 | $results = array(); |
| 165 | 215 | foreach ( $media_items as $media_item ) { |
| 166 | 216 | if ( is_wp_error( $media_item ) ) { |
| 167 | 217 | $errors[] = array( |
| 168 | - 'file' => $media_item['ID'], | |
| 169 | 218 | 'error' => $media_item->get_error_code(), |
| 170 | 219 | 'message' => $media_item->get_error_message(), |
| 171 | 220 | ); |
| 172 | 221 | |
| @@ -274,9 +323,9 @@ | ||
| 274 | 323 | return false; |
| 275 | 324 | } |
| 276 | 325 | |
| 277 | 326 | foreach ( $media_files as $media_item ) { |
| 278 | - if ( ! preg_match( '@^video/@', $media_item['type'] ) ) { | |
| 327 | + if ( ! isset( $media_item['type'] ) || ! preg_match( '@^video/@', $media_item['type'] ) ) { | |
| 279 | 328 | return false; |
| 280 | 329 | } |
| 281 | 330 | } |
| 282 | 331 | |