Completed
Push — update/dialogue-focus-on-conte... ( 9f1745...fa862f )
by
unknown
80:03 queued 71:18
created

...s.wpcom-json-api-upload-media-v1-1-endpoint.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
new WPCOM_JSON_API_Upload_Media_v1_1_Endpoint( array(
4
	'description' => 'Upload a new piece of media.',
5
	'allow_cross_origin_request' => true,
6
	'allow_upload_token_auth' => true,
7
	'group'       => 'media',
8
	'stat'        => 'media:new',
9
	'min_version' => '1.1',
10
	'max_version' => '1.1',
11
	'method'      => 'POST',
12
	'path'        => '/sites/%s/media/new',
13
	'path_labels' => array(
14
		'$site' => '(int|string) Site ID or domain',
15
	),
16
17
	'request_format' => array(
18
		'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 />" .
19
		                "<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>",
20
		'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.",
21
		'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 />" .
22
		                 "<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>",
23
	),
24
25
	'response_format' => array(
26
		'media' => '(array) Array of uploaded media objects',
27
		'errors' => '(array) Array of error messages of uploading media failures',
28
	),
29
30
	'example_request' => 'https://public-api.wordpress.com/rest/v1.1/sites/82974409/media/new',
31
	'example_request_data' =>  array(
32
		'headers' => array(
33
			'authorization' => 'Bearer YOUR_API_TOKEN',
34
		),
35
		'body' => array(
36
			'media_urls' => 'https://s.w.org/about/images/logos/codeispoetry-rgb.png',
37
		),
38
	)
39
) );
40
41
class WPCOM_JSON_API_Upload_Media_v1_1_Endpoint extends WPCOM_JSON_API_Endpoint {
42
43
	/**
44
	 * @param string $path
45
	 * @param int $blog_id
46
	 *
47
	 * @return array|int|WP_Error|void
48
	 */
49
	function callback( $path = '', $blog_id = 0 ) {
50
		$blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
51
		if ( is_wp_error( $blog_id ) ) {
52
			return $blog_id;
53
		}
54
55
		if ( ! current_user_can( 'upload_files' ) && ! $this->api->is_authorized_with_upload_token() ) {
0 ignored issues
show
The method is_authorized_with_upload_token() does not seem to exist on object<WPCOM_JSON_API>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
56
			return new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
0 ignored issues
show
The call to WP_Error::__construct() has too many arguments starting with 'unauthorized'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
57
		}
58
59
		$input = $this->input( true );
60
61
		$media_files = ! empty( $input['media'] ) ? $input['media'] : array();
62
		$media_urls = ! empty( $input['media_urls'] ) ? $input['media_urls'] : array();
63
		$media_attrs = ! empty( $input['attrs'] ) ? $input['attrs'] : array();
64
65
		if ( empty( $media_files ) && empty( $media_urls ) ) {
66
			return new WP_Error( 'invalid_input', 'No media provided in input.' );
0 ignored issues
show
The call to WP_Error::__construct() has too many arguments starting with 'invalid_input'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
67
		}
68
69
		$is_jetpack_site = false;
70
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
71
			// For jetpack sites, we send the media via a different method, because the sync is very different.
72
			$jetpack_sync = Jetpack_Media_Sync::summon( $blog_id );
73
			$is_jetpack_site = $jetpack_sync->is_jetpack_site();
74
		}
75
76
		$jetpack_media_files = array();
77
		$other_media_files   = array();
78
		$media_items         = array();
79
		$errors              = array();
80
81
		// We're splitting out videos for Jetpack sites
82
		foreach ( $media_files as $media_item ) {
83
			if ( preg_match( '@^video/@', $media_item['type'] ) && $is_jetpack_site ) {
84
				$jetpack_media_files[] = $media_item;
85
86
			} else {
87
				$other_media_files[] = $media_item;
88
			}
89
		}
90
91
		// New Jetpack / VideoPress media upload processing
92
		if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
93
			if ( count( $jetpack_media_files ) > 0 ) {
94
				add_filter( 'upload_mimes', array( $this, 'allow_video_uploads' ) );
95
96
				$media_items = $jetpack_sync->upload_media( $jetpack_media_files, $this->api );
97
98
				$errors = $jetpack_sync->get_errors();
99
100
				foreach ( $media_items as & $media_item ) {
101
					// More than likely a post has not been created yet, so we pass in the media item we
102
					// got back from the Jetpack site.
103
					$post       = (object) $media_item['post'];
104
					$media_item = $this->get_media_item_v1_1( $post->ID, $post, $media_item['file'] );
105
				}
106
			}
107
		}
108
109
		// Normal WPCOM upload processing
110
		if ( count( $other_media_files ) > 0 || count( $media_urls ) > 0 ) {
111
			if ( is_multisite() ) { // Do not check for available space in non multisites.
112
				add_filter( 'wp_handle_upload_prefilter', array( $this, 'check_upload_size' ), 9 ); // used for direct media uploads.
113
				add_filter( 'wp_handle_sideload_prefilter', array( $this, 'check_upload_size' ), 9 ); // used for uploading media via url.
114
			}
115
116
			$create_media = $this->handle_media_creation_v1_1( $other_media_files, $media_urls, $media_attrs );
117
			$media_ids = $create_media['media_ids'];
118
			$errors = $create_media['errors'];
119
120
			$media_items = array();
121
			foreach ( $media_ids as $media_id ) {
122
				$media_items[] = $this->get_media_item_v1_1( $media_id );
123
			}
124
		}
125
126
		if ( count( $media_items ) <= 0 ) {
127
			return $this->api->output_early( 400, array( 'errors' => $this->rewrite_generic_upload_error( $errors ) ) );
128
		}
129
130
		$results = array();
131
		foreach ( $media_items as $media_item ) {
132
			if ( is_wp_error( $media_item ) ) {
133
				$errors[] =  array( 'file' => $media_item['ID'], 'error' => $media_item->get_error_code(), 'message' => $media_item->get_error_message() );
134
135
			} else {
136
				$results[] = $media_item;
137
			}
138
		}
139
140
		$response = array( 'media' => $results );
141
142
		if ( count( $errors ) > 0 ) {
143
			$response['errors'] = $this->rewrite_generic_upload_error( $errors );
144
		}
145
146
		return $response;
147
	}
148
149
	/**
150
	 * This changes the generic "upload_error" code to something more meaningful if possible
151
	 *
152
	 * @param  array $errors Errors for the uploaded file.
153
	 * @return array         The same array with an improved error message.
154
	 */
155
	function rewrite_generic_upload_error( $errors ) {
156
		foreach ( $errors as $k => $error ) {
157
			if ( 'upload_error' === $error['error'] && false !== strpos( $error['message'], '|' ) ) {
158
				list( $errors[ $k ]['error'], $errors[ $k ]['message'] ) = explode( '|', $error['message'], 2 );
159
			}
160
		}
161
		return $errors;
162
	}
163
164
	/**
165
	 * Determine if uploaded file exceeds space quota on multisite.
166
	 *
167
	 * This is a copy of the core function with added functionality, synced
168
	 * with this with WP_REST_Attachments_Controller::check_upload_size()
169
	 * to allow for specifying a better error message.
170
	 *
171
	 * @param array $file $_FILES array for a given file.
172
	 * @return array Maybe extended with an error message.
173
	 */
174
	function check_upload_size( $file ) {
175
		if ( get_site_option( 'upload_space_check_disabled' ) ) {
176
			return $file;
177
		}
178
179
		if ( isset( $file['error'] ) && $file['error'] > 0 ) { // There's already an error. Error Codes Reference: https://www.php.net/manual/en/features.file-upload.errors.php .
180
			return $file;
181
		}
182
183
		if ( defined( 'WP_IMPORTING' ) ) {
184
			return $file;
185
		}
186
187
		$space_left = get_upload_space_available();
188
189
		$file_size = filesize( $file['tmp_name'] );
190
		if ( $space_left < $file_size ) {
191
			/* translators: %s: Required disk space in kilobytes. */
192
			$file['error'] = 'rest_upload_limited_space|' . sprintf( __( 'Not enough space to upload. %s KB needed.', 'default' ), number_format( ( $file_size - $space_left ) / KB_IN_BYTES ) );
193
		}
194
195
		if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) {
196
			/* translators: %s: Maximum allowed file size in kilobytes. */
197
			$file['error'] = 'rest_upload_file_too_big|' . sprintf( __( 'This file is too big. Files must be less than %s KB in size.', 'default' ), get_site_option( 'fileupload_maxk', 1500 ) );
198
		}
199
200
		if ( upload_is_user_over_quota( false ) ) {
201
			$file['error'] = 'rest_upload_user_quota_exceeded|' . __( 'You have used your space quota. Please delete files before uploading.', 'default' );
202
		}
203
204
		return $file;
205
	}
206
	/**
207
	 * Force to use the WPCOM API instead of proxy back to the Jetpack API if the blog is a paid Jetpack
208
	 * blog w/ the VideoPress module enabled AND the uploaded file is a video.
209
	 *
210
	 * @param int $blog_id
211
	 * @return bool
212
	 */
213
	function force_wpcom_request( $blog_id ) {
214
215
		// We don't need to do anything if VideoPress is not enabled for the blog.
216
		if ( ! is_videopress_enabled_on_jetpack_blog( $blog_id ) ) {
217
			return false;
218
		}
219
220
		// Check to see if the upload is not a video type, if not then return false.
221
		$input = $this->input( true );
222
		$media_files = ! empty( $input['media'] ) ? $input['media'] : array();
223
224
		if ( empty( $media_files ) ) {
225
			return false;
226
		}
227
228
		foreach ( $media_files as $media_item ) {
229
			if ( ! preg_match( '@^video/@', $media_item['type'] ) ) {
230
				return false;
231
			}
232
		}
233
234
		// The API request should be for a blog w/ Jetpack, A valid plan, has VideoPress enabled,
235
		// and is a video file. Let's let it through.
236
		return true;
237
	}
238
}
239