Completed
Push — add/jetpack-icon-gutenberg ( b0ee9a...546ac6 )
by
unknown
21:27 queued 10:33
created

class.wpcom-json-api-edit-media-v1-2-endpoint.php (15 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
jetpack_require_lib( 'class.media' );
4
5
const REVISION_HISTORY_MAXIMUM_AMOUNT = 0;
6
const WP_ATTACHMENT_IMAGE_ALT = '_wp_attachment_image_alt';
7
8
new WPCOM_JSON_API_Edit_Media_v1_2_Endpoint( array(
9
	'description' => 'Edit a media item.',
10
	'group'       => 'media',
11
	'stat'        => 'media:1:POST',
12
	'min_version' => '1',
13
	'max_version' => '1.2',
14
	'method'      => 'POST',
15
	'path'        => '/sites/%s/media/%d/edit',
16
	'path_labels' => array(
17
		'$site'    => '(int|string) Site ID or domain',
18
		'$media_ID' => '(int) The ID of the media item',
19
	),
20
21
	'request_format' => array(
22
		'parent_id'   => '(int) ID of the post this media is attached to',
23
		'title'       => '(string) The file name.',
24
		'caption'     => '(string) File caption.',
25
		'description' => '(HTML) Description of the file.',
26
		'alt'         => "(string) Alternative text for image files.",
27
		'artist'      => "(string) Audio Only. Artist metadata for the audio track.",
28
		'album'       => "(string) Audio Only. Album metadata for the audio track.",
29
		'media'       => "(object) An object file to attach to the post. To upload media, " .
30
						   "the entire request should be multipart/form-data encoded. " .
31
						   "Multiple media items will be displayed in a gallery. Accepts " .
32
						   "jpg, jpeg, png, gif, pdf, doc, ppt, odt, pptx, docx, pps, ppsx, xls, xlsx, key. " .
33
						   "Audio and Video may also be available. See <code>allowed_file_types</code> " .
34
						   "in the options response of the site endpoint. " .
35
						   "<br /><br /><strong>Example</strong>:<br />" .
36
						   "<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>",
37
		'attrs'       => "(object) An Object of attributes (`title`, `description` and `caption`) " .
38
						   "are supported to assign to the media uploaded via the `media` or `media_url`",
39
		'media_url'   => "(string) An URL of the image to attach to a post.",
40
	),
41
42
	'response_format' => array(
43
		'ID'               => '(int) The ID of the media item',
44
		'date'             => '(ISO 8601 datetime) The date the media was uploaded',
45
		'post_ID'          => '(int) ID of the post this media is attached to',
46
		'author_ID'        => '(int) ID of the user who uploaded the media',
47
		'URL'              => '(string) URL to the file',
48
		'guid'             => '(string) Unique identifier',
49
		'file'             => '(string) File name',
50
		'extension'        => '(string) File extension',
51
		'mime_type'        => '(string) File mime type',
52
		'title'            => '(string) File name',
53
		'caption'          => '(string) User provided caption of the file',
54
		'description'      => '(string) Description of the file',
55
		'alt'              => '(string)  Alternative text for image files.',
56
		'thumbnails'       => '(object) Media item thumbnail URL options',
57
		'height'           => '(int) (Image & video only) Height of the media item',
58
		'width'            => '(int) (Image & video only) Width of the media item',
59
		'length'           => '(int) (Video & audio only) Duration of the media item, in seconds',
60
		'exif'             => '(array) (Image & audio only) Exif (meta) information about the media item',
61
		'videopress_guid'  => '(string) (Video only) VideoPress GUID of the video when uploaded on a blog with VideoPress',
62
		'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.',
63
		'revision_history' => '(object) An object with `items` and `original` keys. ' .
64
								'`original` is an object with data about the original image. ' .
65
								'`items` is an array of snapshots of the previous images of this Media. ' .
66
								'Each item has the `URL`, `file, `extension`, `date`, and `mime_type` fields.'
67
	),
68
69
	'example_request'      => 'https://public-api.wordpress.com/rest/v1.2/sites/82974409/media/446',
70
	'example_request_data' =>  array(
71
		'headers' => array(
72
			'authorization' => 'Bearer YOUR_API_TOKEN'
73
		),
74
		'body' => array(
75
			'title' => 'Updated Title'
76
		)
77
	)
78
) );
79
80
class WPCOM_JSON_API_Edit_Media_v1_2_Endpoint extends WPCOM_JSON_API_Update_Media_v1_1_Endpoint {
81
	/**
82
	 * Return an array of mime_type items allowed when the media file is uploaded.
83
	 *
84
	 * @return {Array} mime_type array
0 ignored issues
show
The doc-type {Array} could not be parsed: Unknown type name "{Array}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
85
	 */
86 View Code Duplication
	static function get_allowed_mime_types( $default_mime_types ) {
87
		return array_unique( array_merge( $default_mime_types, array(
88
			'application/msword',                                                         // .doc
89
			'application/vnd.ms-powerpoint',                                              // .ppt, .pps
90
			'application/vnd.ms-excel',                                                   // .xls
91
			'application/vnd.openxmlformats-officedocument.presentationml.presentation',  // .pptx
92
			'application/vnd.openxmlformats-officedocument.presentationml.slideshow',     // .ppsx
93
			'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',          // .xlsx
94
			'application/vnd.openxmlformats-officedocument.wordprocessingml.document',    // .docx
95
			'application/vnd.oasis.opendocument.text',                                    // .odt
96
			'application/pdf',                                                            // .pdf
97
		) ) );
98
	}
99
100
	/**
101
	 * Update the media post grabbing the post values from
102
	 * the `attrs` parameter
103
	 *
104
	 * @param  {Number} $media_id - post media ID
105
	 * @param  {Object} $attrs - `attrs` parameter sent from the client in the request body
106
	 * @return
107
	 */
108
	private function update_by_attrs_parameter( $media_id, $attrs ) {
109
		$insert = array();
110
111
		// Attributes: Title, Caption, Description
112
		if ( isset( $attrs['title'] ) ) {
113
			$insert['post_title'] = $attrs['title'];
114
		}
115
116
		if ( isset( $attrs['caption'] ) ) {
117
			$insert['post_excerpt'] = $attrs['caption'];
118
		}
119
120
		if ( isset( $attrs['description'] ) ) {
121
			$insert['post_content'] = $attrs['description'];
122
		}
123
124
		if ( ! empty( $insert ) ) {
125
			$insert['ID'] = $media_id;
126
			$update_action = wp_update_post( (object) $insert );
127
			if ( is_wp_error( $update_action ) ) {
128
				return $update_action;
129
			}
130
		}
131
132
		// Attributes: Alt
133
		if ( isset( $attrs['alt'] ) ) {
134
			$alt = wp_strip_all_tags( $attrs['alt'], true );
135
			$post_update_action = update_post_meta( $media_id, WP_ATTACHMENT_IMAGE_ALT, $alt );
136
137
			if ( is_wp_error( $post_update_action ) ) {
138
				return $post_update_action;
139
			}
140
		}
141
142
		// Attributes: Artist, Album
143
		$id3_meta = array();
144
145 View Code Duplication
		foreach ( array( 'artist', 'album' ) as $key ) {
146
			if ( isset( $attrs[ $key ] ) ) {
147
				$id3_meta[ $key ] = wp_strip_all_tags( $attrs[ $key ], true );
148
			}
149
		}
150
151
		if ( ! empty( $id3_meta ) ) {
152
			// Before updating metadata, ensure that the item is audio
153
			$item = $this->get_media_item_v1_1( $media_id );
154
			if ( 0 === strpos( $item->mime_type, 'audio/' ) ) {
155
				$update_action = wp_update_attachment_metadata( $media_id, $id3_meta );
156
				if ( is_wp_error( $update_action ) ) {
157
					return $update_action;
158
				}
159
			}
160
		}
161
162
		return $post_update_action;
0 ignored issues
show
The variable $post_update_action does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
163
	}
164
165
	/**
166
	 * Return an object to be used to store into the revision_history
167
	 *
168
	 * @param  {Object} $media_item - media post object
0 ignored issues
show
The doc-type {Object} could not be parsed: Unknown type name "{Object}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
169
	 * @return {Object} the snapshot object
0 ignored issues
show
The doc-type {Object} could not be parsed: Unknown type name "{Object}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
170
	 */
171 View Code Duplication
	private function get_snapshot( $media_item ) {
172
		$current_file = get_attached_file( $media_item->ID );
173
		$file_paths = pathinfo( $current_file );
174
175
		$snapshot = array(
176
			'date'             => (string) $this->format_date( $media_item->post_modified_gmt, $media_item->post_modified ),
177
			'URL'              => (string) wp_get_attachment_url( $media_item->ID ),
178
			'file'             => (string) $file_paths['basename'],
179
			'extension'        => (string) $file_paths['extension'],
180
			'mime_type'        => (string) $media_item->post_mime_type,
181
			'size'             => (int) filesize( $current_file )
182
		);
183
184
		return (object) $snapshot;
185
	}
186
187
	/**
188
	 * Try to remove the temporal file from the given file array.
189
	 *
190
	 * @param  {Array} $file_array - Array with data about the temporal file
0 ignored issues
show
The doc-type {Array} could not be parsed: Unknown type name "{Array}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
191
	 * @return {Boolean} `true` if the file has been removed.
0 ignored issues
show
The doc-type {Boolean} could not be parsed: Unknown type name "{Boolean}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
192
	 *                   `false` either the file doesn't exist or it couldn't be removed.
193
	 */
194
	private function remove_tmp_file( $file_array ) {
195
		if ( ! file_exists ( $file_array['tmp_name'] ) ) {
196
			return false;
197
		}
198
		return @unlink( $file_array['tmp_name'] );
199
	}
200
201
	/**
202
	 * Save the given temporal file in a local folder.
203
	 *
204
	 * @param  {Array} $file_array
0 ignored issues
show
The doc-type {Array} could not be parsed: Unknown type name "{Array}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
205
	 * @param  {Number} $media_id
0 ignored issues
show
The doc-type {Number} could not be parsed: Unknown type name "{Number}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
206
	 * @return {Array|WP_Error} An array with information about the new file saved or a WP_Error is something went wrong.
0 ignored issues
show
The doc-type {Array|WP_Error} could not be parsed: Unknown type name "{Array" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
207
	 */
208
	private function save_temporary_file( $file_array, $media_id ) {
209
		$tmp_filename = $file_array['tmp_name'];
210
211
		if ( ! file_exists( $tmp_filename ) ) {
212
			return new WP_Error( 'invalid_input', 'No media provided in input.' );
213
		}
214
215
		// add additional mime_types through of the `jetpack_supported_media_sideload_types` filter
216
		$mime_type_static_filter = array(
217
			'WPCOM_JSON_API_Edit_Media_v1_2_Endpoint',
218
			'get_allowed_mime_types'
219
		);
220
221
		add_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter );
222 View Code Duplication
		if (
223
			! $this->is_file_supported_for_sideloading( $tmp_filename ) &&
224
			! file_is_displayable_image( $tmp_filename )
225
		) {
226
			@unlink( $tmp_filename );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
227
			return new WP_Error( 'invalid_input', 'Invalid file type.', 403 );
228
		}
229
		remove_filter( 'jetpack_supported_media_sideload_types', $mime_type_static_filter );
230
231
		// generate a new file name
232
		$tmp_new_filename = Jetpack_Media::generate_new_filename( $media_id, $file_array[ 'name' ] );
233
234
		// start to create the parameters to move the temporal file
235
		$overrides = array( 'test_form' => false );
236
237
		$time = $this->get_time_string_from_guid( $media_id );
238
239
		$file_array['name'] = $tmp_new_filename;
240
		$file = wp_handle_sideload( $file_array, $overrides, $time );
241
242
		$this->remove_tmp_file( $file_array );
243
244
		if ( isset( $file['error'] ) ) {
245
			return new WP_Error( 'upload_error', $file['error'] );
246
		}
247
248
		return $file;
249
	}
250
251
	/**
252
	 * File urls use the post date to generate a folder path.
253
	 * Post dates can change, so we use the original date used in the guid
254
	 * url so edits can remain in the same folder. In the following function
255
	 * we capture a string in the format of `YYYY/MM` from the guid.
256
	 *
257
	 * For example with a guid of
258
	 * "http://test.files.wordpress.com/2016/10/test.png" the resulting string
259
	 * would be: "2016/10"
260
	 *
261
	 * @param $media_id
262
	 *
263
	 * @return string
264
	 */
265 View Code Duplication
	private function get_time_string_from_guid( $media_id ) {
266
		$time = date( "Y/m", strtotime( current_time( 'mysql' ) ) );
267
		if ( $media = get_post( $media_id ) ) {
268
			$pattern = '/\/(\d{4}\/\d{2})\//';
269
			preg_match( $pattern, $media->guid, $matches );
270
			if ( count( $matches ) > 1 ) {
271
				$time = $matches[1];
272
			}
273
		}
274
		return $time;
275
	}
276
277
	/**
278
	 * Get the image from a remote url and then save it locally.
279
	 *
280
	 * @param  {Number} $media_id - media post ID
281
	 * @param  {String} $url - image URL to save locally
282
	 * @return {Array|WP_Error} An array with information about the new file saved or a WP_Error is something went wrong.
0 ignored issues
show
The doc-type {Array|WP_Error} could not be parsed: Unknown type name "{Array" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
283
	 */
284
	private function build_file_array_from_url( $media_id, $url ) {
285
		if ( ! $url ) {
286
			return null;
287
		}
288
289
		// if we didn't get a URL, let's bail
290
		$parsed = @parse_url( $url );
291
		if ( empty( $parsed ) ) {
292
			return new WP_Error( 'invalid_url', 'No media provided in url.' );
293
		}
294
295
		// save the remote image into a tmp file
296
		$tmp = download_url( wpcom_get_private_file( $url ) );
297
		if ( is_wp_error( $tmp ) ) {
298
			return $tmp;
299
		}
300
301
		return array(
302
			'name' => basename( $url ),
303
			'tmp_name' => $tmp
304
		);
305
	}
306
307
	/**
308
	 * Add a new item into revision_history array.
309
	 *
310
	 * @param  {Object} $media_item         - media post
0 ignored issues
show
The doc-type {Object} could not be parsed: Unknown type name "{Object}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
311
	 * @param  {file} $file               - file recentrly added
0 ignored issues
show
The doc-type {file} could not be parsed: Unknown type name "{file}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
312
	 * @param  {Boolean} $has_original_media - condition is the original media has been already added
0 ignored issues
show
The doc-type {Boolean} could not be parsed: Unknown type name "{Boolean}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
313
	 * @return {Boolean} `true` if the item has been added. Otherwise `false`.
0 ignored issues
show
The doc-type {Boolean} could not be parsed: Unknown type name "{Boolean}" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
314
	 */
315
	private function register_revision( $media_item, $file, $has_original_media ) {
316
		if (
317
			is_wp_error( $file ) ||
318
			! $has_original_media
319
		) {
320
			return false;
321
		}
322
323
		add_post_meta( $media_item->ID, Jetpack_Media::$WP_REVISION_HISTORY, $this->get_snapshot( $media_item ) );
324
	}
325
326
	function callback( $path = '', $blog_id = 0, $media_id = 0 ) {
327
		$blog_id = $this->api->switch_to_blog_and_validate_user( $this->api->get_blog_id( $blog_id ) );
328
		if ( is_wp_error( $blog_id ) ) {
329
			return $blog_id;
330
		}
331
332
		$media_item = get_post( $media_id );
333
334
		if ( ! $media_item || is_wp_error( $media_item ) ) {
335
			return new WP_Error( 'unknown_media', 'Unknown Media', 404 );
336
		}
337
338
		if ( is_wp_error( $media_item ) ) {
339
			return $media_item;
340
		}
341
342
		if ( ! current_user_can( 'upload_files', $media_id ) ) {
343
			return new WP_Error( 'unauthorized', 'User cannot view media', 403 );
344
		}
345
346
		$input = $this->input( true );
347
348
		// images
349
		$media_file = $input['media'] ? (array) $input['media'] : null;
350
		$media_url = $input['media_url'];
351
		$media_attrs = $input['attrs'] ? (array) $input['attrs'] : null;
352
353
		if ( isset( $media_url ) || $media_file ) {
354
			$user_can_upload_files = current_user_can( 'upload_files' ) || $this->api->is_authorized_with_upload_token();
355
356
			if ( ! $user_can_upload_files  ) {
357
				return new WP_Error( 'unauthorized', 'User cannot upload media.', 403 );
358
			}
359
360
			$has_original_media = Jetpack_Media::get_original_media( $media_id );
361
362 View Code Duplication
			if ( ! $has_original_media ) {
363
				// The first time that the media is updated
364
				// the original media is stored into the revision_history
365
				$snapshot = $this->get_snapshot( $media_item );
366
				add_post_meta( $media_id, Jetpack_Media::$WP_ORIGINAL_MEDIA, $snapshot, true );
367
			}
368
369
			// save the temporal file locally
370
			$temporal_file = $media_file ? $media_file : $this->build_file_array_from_url( $media_id, $media_url );
371
372
			if ( is_wp_error( $temporal_file ) ) {
373
				return $temporal_file;
374
			}
375
376
			$uploaded_file = $this->save_temporary_file( $temporal_file, $media_id );
377
378
			if ( is_wp_error( $uploaded_file ) ) {
379
				return $uploaded_file;
380
			}
381
382
			// revision_history control
383
			$this->register_revision( $media_item, $uploaded_file, $has_original_media );
384
385
			$uploaded_path = $uploaded_file['file'];
386
			$udpated_mime_type = $uploaded_file['type'];
387
			$was_updated = update_attached_file( $media_id, $uploaded_path );
388
389
			if ( $was_updated ) {
390
				$new_metadata = wp_generate_attachment_metadata( $media_id, $uploaded_path );
391
				wp_update_attachment_metadata( $media_id, $new_metadata );
392
393
				// check maximum amount of revision_history
394
				Jetpack_Media::limit_revision_history( $media_id, REVISION_HISTORY_MAXIMUM_AMOUNT );
395
396
				wp_update_post( (object) array(
397
					'ID'             => $media_id,
398
					'post_mime_type' => $udpated_mime_type
399
				) );
400
			}
401
402
			unset( $input['media'] );
403
			unset( $input['media_url'] );
404
			unset( $input['attrs'] );
405
		}
406
407
		// update media through of `attrs` value it it's defined
408
		if ( ( $media_file || isset( $media_url ) ) && $media_attrs ) {
409
			$was_updated = $this->update_by_attrs_parameter( $media_id, $media_attrs );
410
411
			if ( is_wp_error( $was_updated ) ) {
412
				return $was_updated;
413
			}
414
		}
415
416
		// call parent method
417
		$response = parent::callback( $path, $blog_id, $media_id );
418
419
		// expose `revision_history` object
420
		$response->revision_history = (object) array(
421
			'items'       => (array) Jetpack_Media::get_revision_history( $media_id ),
422
			'original'    => (object) Jetpack_Media::get_original_media( $media_id )
423
		);
424
425
		return $response;
426
	}
427
}
428
429