summaryrefslogtreecommitdiff
blob: 89d1a67cd47126ca09dd8aeb8f047a605c977f86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
<?php

jetpack_require_lib( 'class.media' );

define( 'REVISION_HISTORY_MAXIMUM_AMOUNT', 0 );
define( 'WP_ATTACHMENT_IMAGE_ALT', '_wp_attachment_image_alt' );

new WPCOM_JSON_API_Edit_Media_v1_2_Endpoint( array(
	'description' => 'Edit a media item.',
	'group'       => 'media',
	'stat'        => 'media:1:POST',
	'min_version' => '1',
	'max_version' => '1.2',
	'method'      => 'POST',
	'path'        => '/sites/%s/media/%d/edit',
	'path_labels' => array(
		'$site'    => '(int|string) Site ID or domain',
		'$media_ID' => '(int) The ID of the media item',
	),

	'request_format' => array(
		'parent_id'   => '(int) ID of the post this media is attached to',
		'title'       => '(string) The file name.',
		'caption'     => '(string) File caption.',
		'description' => '(HTML) Description of the file.',
		'alt'         => "(string) Alternative text for image files.",
		'artist'      => "(string) Audio Only. Artist metadata for the audio track.",
		'album'       => "(string) Audio Only. Album metadata for the audio track.",
		'media'       => "(object) An object file to attach to the post. To upload media, " .
						   "the entire request should be multipart/form-data encoded. " .
						   "Multiple media items will be displayed in a gallery. 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 />" .
						   "<code>curl \<br />--form 'title=Image' \<br />--form 'media=@/path/to/file.jpg' \<br />-H 'Authorization: BEARER your-token' \<br />'https://public-api.wordpress.com/rest/v1/sites/123/posts/new'</code>",
		'attrs'       => "(object) An Object of attributes (`title`, `description` and `caption`) " .
						   "are supported to assign to the media uploaded via the `media` or `media_url`",
		'media_url'   => "(string) An URL of the image to attach to a post.",
	),

	'response_format' => array(
		'ID'               => '(int) The ID of the media item',
		'date'             => '(ISO 8601 datetime) The date the media was uploaded',
		'post_ID'          => '(int) ID of the post this media is attached to',
		'author_ID'        => '(int) ID of the user who uploaded the media',
		'URL'              => '(string) URL to the file',
		'guid'             => '(string) Unique identifier',
		'file'             => '(string) File name',
		'extension'        => '(string) File extension',
		'mime_type'        => '(string) File mime type',
		'title'            => '(string) File name',
		'caption'          => '(string) User provided caption of the file',
		'description'      => '(string) Description of the file',
		'alt'              => '(string)  Alternative text for image files.',
		'thumbnails'       => '(object) Media item thumbnail URL options',
		'height'           => '(int) (Image & video only) Height of the media item',
		'width'            => '(int) (Image & video only) Width of the media item',
		'length'           => '(int) (Video & audio only) Duration of the media item, in seconds',
		'exif'             => '(array) (Image & audio only) Exif (meta) information about the media item',
		'videopress_guid'  => '(string) (Video only) VideoPress GUID of the video when uploaded on a blog with VideoPress',
		'videopress_processing_done'  => '(bool) (Video only) If the video is uploaded on a blog with VideoPress, this will return the status of processing on the video.',
		'revision_history' => '(object) An object with `items` and `original` keys. ' .
								'`original` is an object with data about the original image. ' .
								'`items` is an array of snapshots of the previous images of this Media. ' .
								'Each item has the `URL`, `file, `extension`, `date`, and `mime_type` fields.'
	),

	'example_request'      => 'https://public-api.wordpress.com/rest/v1.2/sites/82974409/media/446',
	'example_request_data' =>  array(
		'headers' => array(
			'authorization' => 'Bearer YOUR_API_TOKEN'
		),
		'body' => array(
			'title' => 'Updated Title'
		)
	)
) );

class WPCOM_JSON_API_Edit_Media_v1_2_Endpoint extends WPCOM_JSON_API_Update_Media_v1_1_Endpoint {
	/**
	 * Return an array of mime_type items allowed when the media file is uploaded.
	 *
	 * @return {Array} mime_type array
	 */
	static function get_allowed_mime_types( $default_mime_types ) {
		return array_unique( array_merge( $default_mime_types, array(
			'application/msword',                                                         // .doc
			'application/vnd.ms-powerpoint',                                              // .ppt, .pps
			'application/vnd.ms-excel',                                                   // .xls
			'application/vnd.openxmlformats-officedocument.presentationml.presentation',  // .pptx
			'application/vnd.openxmlformats-officedocument.presentationml.slideshow',     // .ppsx
			'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',          // .xlsx
			'application/vnd.openxmlformats-officedocument.wordprocessingml.document',    // .docx
			'application/vnd.oasis.opendocument.text',                                    // .odt
			'application/pdf',                                                            // .pdf
		) ) );
	}

	/**
	 * Update the media post grabbing the post values from
	 * the `attrs` parameter
	 *
	 * @param  {Number} $media_id - post media ID
	 * @param  {Object} $attrs - `attrs` parameter sent from the client in the request body
	 * @return
	 */
	private function update_by_attrs_parameter( $media_id, $attrs ) {
		$insert = array();

		// Attributes: Title, Caption, Description
		if ( isset( $attrs['title'] ) ) {
			$insert['post_title'] = $attrs['title'];
		}

		if ( isset( $attrs['caption'] ) ) {
			$insert['post_excerpt'] = $attrs['caption'];
		}

		if ( isset( $attrs['description'] ) ) {
			$insert['post_content'] = $attrs['description'];
		}

		if ( ! empty( $insert ) ) {
			$insert['ID'] = $media_id;
			$update_action = wp_update_post( (object) $insert );
			if ( is_wp_error( $update_action ) ) {
				return $update_action;
			}
		}

		// Attributes: Alt
		if ( isset( $attrs['alt'] ) ) {
			$alt = wp_strip_all_tags( $attrs['alt'], true );
			$post_update_action = update_post_meta( $media_id, WP_ATTACHMENT_IMAGE_ALT, $alt );

			if ( is_wp_error( $post_update_action ) ) {
				return $post_update_action;
			}
		}

		// Attributes: Artist, Album
		$id3_meta = array();

		foreach ( array( 'artist', 'album' ) as $key ) {
			if ( isset( $attrs[ $key ] ) ) {
				$id3_meta[ $key ] = wp_strip_all_tags( $attrs[ $key ], true );
			}
		}

		if ( ! empty( $id3_meta ) ) {
			// Before updating metadata, ensure that the item is audio
			$item = $this->get_media_item_v1_1( $media_id );
			if ( 0 === strpos( $item->mime_type, 'audio/' ) ) {
				$update_action = wp_update_attachment_metadata( $media_id, $id3_meta );
				if ( is_wp_error( $update_action ) ) {
					return $update_action;
				}
			}
		}

		return $post_update_action;
	}

	/**
	 * Return an object to be used to store into the revision_history
	 *
	 * @param  {Object} $media_item - media post object
	 * @return {Object} the snapshot object
	 */
	private function get_snapshot( $media_item ) {
		$current_file = get_attached_file( $media_item->ID );
		$file_paths = pathinfo( $current_file );

		$snapshot = array(
			'date'             => (string) $this->format_date( $media_item->post_modified_gmt, $media_item->post_modified ),
			'URL'              => (string) wp_get_attachment_url( $media_item->ID ),
			'file'             => (string) $file_paths['basename'],
			'extension'        => (string) $file_paths['extension'],
			'mime_type'        => (string) $media_item->post_mime_type,
			'size'             => (int) filesize( $current_file )
		);

		return (object) $snapshot;
	}

	/**
	 * Try to remove the temporal file from the given file array.
	 *
	 * @param  {Array} $file_array - Array with data about the temporal file
	 * @return {Boolean} `true` if the file has been removed.
	 *                   `false` either the file doesn't exist or it couldn't be removed.
	 */
	private function remove_tmp_file( $file_array ) {
		if ( ! file_exists ( $file_array['tmp_name'] ) ) {
			return false;
		}
		return @unlink( $file_array['tmp_name'] );
	}

	/**
	 * Save the given temporal file in a local folder.
	 *
	 * @param  {Array} $file_array
	 * @param  {Number} $media_id
	 * @return {Array|WP_Error} An array with information about the new file saved or a WP_Error is something went wrong.
	 */
	private function save_temporary_file( $file_array, $media_id ) {
		$tmp_filename = $file_array['tmp_name'];

		if ( ! file_exists( $tmp_filename ) ) {
			return new WP_Error( 'invalid_input', 'No media provided in input.' );
		}

		// add additional mime_types through of the `jetpack_supported_media_sideload_types` filter
		$mime_type_static_filter = array(
			'WPCOM_JSON_API_Edit_Media_v1_2_Endpoint',
			'get_allowed_mime_types'
		);

		add_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter );
		if (
			! $this->is_file_supported_for_sideloading( $tmp_filename ) &&
			! file_is_displayable_image( $tmp_filename )
		) {
			@unlink( $tmp_filename );
			return new WP_Error( 'invalid_input', 'Invalid file type.', 403 );
		}
		remove_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter );

		// generate a new file name
		$tmp_new_filename = Jetpack_Media::generate_new_filename( $media_id, $file_array[ 'name' ] );

		// start to create the parameters to move the temporal file
		$overrides = array( 'test_form' => false );

		$time = $this->get_time_string_from_guid( $media_id );

		$file_array['name'] = $tmp_new_filename;
		$file = wp_handle_sideload( $file_array, $overrides, $time );

		$this->remove_tmp_file( $file_array );

		if ( isset( $file['error'] ) ) {
			return new WP_Error( 'upload_error', $file['error'] );
		}

		return $file;
	}

	/**
	 * File urls use the post date to generate a folder path.
	 * Post dates can change, so we use the original date used in the guid
	 * url so edits can remain in the same folder. In the following function
	 * we capture a string in the format of `YYYY/MM` from the guid.
	 *
	 * For example with a guid of
	 * "http://test.files.wordpress.com/2016/10/test.png" the resulting string
	 * would be: "2016/10"
	 *
	 * @param $media_id
	 *
	 * @return string
	 */
	private function get_time_string_from_guid( $media_id ) {
		$time = date( "Y/m", strtotime( current_time( 'mysql' ) ) );
		if ( $media = get_post( $media_id ) ) {
			$pattern = '/\/(\d{4}\/\d{2})\//';
			preg_match( $pattern, $media->guid, $matches );
			if ( count( $matches ) > 1 ) {
				$time = $matches[1];
			}
		}
		return $time;
	}

	/**
	 * Get the image from a remote url and then save it locally.
	 *
	 * @param  {Number} $media_id - media post ID
	 * @param  {String} $url - image URL to save locally
	 * @return {Array|WP_Error} An array with information about the new file saved or a WP_Error is something went wrong.
	 */
	private function build_file_array_from_url( $media_id, $url ) {
		if ( ! $url ) {
			return null;
		}

		// if we didn't get a URL, let's bail
		$parsed = @parse_url( $url );
		if ( empty( $parsed ) ) {
			return new WP_Error( 'invalid_url', 'No media provided in url.' );
		}

		// save the remote image into a tmp file
		$tmp = download_url( wpcom_get_private_file( $url ) );
		if ( is_wp_error( $tmp ) ) {
			return $tmp;
		}

		return array(
			'name' => basename( $url ),
			'tmp_name' => $tmp
		);
	}

	/**
	 * Add a new item into revision_history array.
	 *
	 * @param  {Object} $media_item         - media post
	 * @param  {file} $file               - file recentrly added
	 * @param  {Boolean} $has_original_media - condition is the original media has been already added
	 * @return {Boolean} `true` if the item has been added. Otherwise `false`.
	 */
	private function register_revision( $media_item, $file, $has_original_media ) {
		if (
			is_wp_error( $file ) ||
			! $has_original_media
		) {
			return false;
		}

		add_post_meta( $media_item->ID, Jetpack_Media::$WP_REVISION_HISTORY, $this->get_snapshot( $media_item ) );
	}

	function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
		$blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
		if ( is_wp_error( $blog_id ) ) {
			return $blog_id;
		}

		$media_item = get_post( $media_id );

		if ( ! $media_item || is_wp_error( $media_item ) ) {
			return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
		}

		if ( is_wp_error( $media_item ) ) {
			return $media_item;
		}

		if ( ! current_user_can( 'upload_files', $media_id ) ) {
			return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
		}

		$input = $this->input( true );

		// images
		$media_file = $input['media'] ? (array) $input['media'] : null;
		$media_url = $input['media_url'];
		$media_attrs = $input['attrs'] ? (array) $input['attrs'] : null;

		if ( isset( $media_url ) || $media_file ) {
			$user_can_upload_files = current_user_can( 'upload_files' ) || $this->api->is_authorized_with_upload_token();

			if ( ! $user_can_upload_files  ) {
				return new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
			}

			$has_original_media = Jetpack_Media::get_original_media( $media_id );

			if ( ! $has_original_media ) {
				// The first time that the media is updated
				// the original media is stored into the revision_history
				$snapshot = $this->get_snapshot( $media_item );
				add_post_meta( $media_id, Jetpack_Media::$WP_ORIGINAL_MEDIA, $snapshot, true );
			}

			// save the temporal file locally
			$temporal_file = $media_file ? $media_file : $this->build_file_array_from_url( $media_id, $media_url );

			if ( is_wp_error( $temporal_file ) ) {
				return $temporal_file;
			}

			$uploaded_file = $this->save_temporary_file( $temporal_file, $media_id );

			if ( is_wp_error( $uploaded_file ) ) {
				return $uploaded_file;
			}

			// revision_history control
			$this->register_revision( $media_item, $uploaded_file, $has_original_media );

			$uploaded_path = $uploaded_file['file'];
			$udpated_mime_type = $uploaded_file['type'];
			$was_updated = update_attached_file( $media_id, $uploaded_path );

			if ( $was_updated ) {
				$new_metadata = wp_generate_attachment_metadata( $media_id, $uploaded_path );
				wp_update_attachment_metadata( $media_id, $new_metadata );

				// check maximum amount of revision_history
				Jetpack_Media::limit_revision_history( $media_id, REVISION_HISTORY_MAXIMUM_AMOUNT );

				wp_update_post( (object) array(
					'ID'             => $media_id,
					'post_mime_type' => $udpated_mime_type
				) );
			}

			unset( $input['media'] );
			unset( $input['media_url'] );
			unset( $input['attrs'] );
		}

		// update media through of `attrs` value it it's defined
		if ( ( $media_file || isset( $media_url ) ) && $media_attrs ) {
			$was_updated = $this->update_by_attrs_parameter( $media_id, $media_attrs );

			if ( is_wp_error( $was_updated ) ) {
				return $was_updated;
			}
		}

		// call parent method
		$response = parent::callback( $path, $blog_id, $media_id );

		// expose `revision_history` object
		$response->revision_history = (object) array(
			'items'       => (array) Jetpack_Media::get_revision_history( $media_id ),
			'original'    => (object) Jetpack_Media::get_original_media( $media_id )
		);

		return $response;
	}
}