WP_REST_Attachments_Controller   D
last analyzed

Complexity

Total Complexity 82

Size/Duplication

Total Lines 606
Duplicated Lines 4.13 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 25
loc 606
rs 4.848
c 0
b 0
f 0
wmc 82
lcom 1
cbo 1

13 Methods

Rating   Name   Duplication   Size   Complexity  
B prepare_items_query() 0 17 8
C create_item_permissions_check() 0 23 7
D create_item() 8 93 16
B update_item() 0 32 6
A prepare_item_for_database() 0 17 4
C prepare_item_for_response() 0 68 10
A get_item_schema() 0 61 1
C upload_from_data() 9 68 9
C get_filename_from_disposition() 0 37 8
A get_collection_params() 0 19 1
A validate_user_can_query_private_statuses() 0 6 2
C upload_from_file() 8 34 7
A get_media_types() 0 11 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WP_REST_Attachments_Controller often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WP_REST_Attachments_Controller, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
4
5
	/**
6
	 * Determine the allowed query_vars for a get_items() response and
7
	 * prepare for WP_Query.
8
	 *
9
	 * @param array           $prepared_args
10
	 * @param WP_REST_Request $request
11
	 * @return array          $query_args
12
	 */
13
	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
14
		$query_args = parent::prepare_items_query( $prepared_args, $request );
15
		if ( empty( $query_args['post_status'] ) || ! in_array( $query_args['post_status'], array( 'inherit', 'private', 'trash' ) ) ) {
16
			$query_args['post_status'] = 'inherit';
17
		}
18
		$media_types = $this->get_media_types();
19
		if ( ! empty( $request['media_type'] ) && in_array( $request['media_type'], array_keys( $media_types ) ) ) {
20
			$query_args['post_mime_type'] = $media_types[ $request['media_type'] ];
21
		}
22
		if ( ! empty( $request['mime_type'] ) ) {
23
			$parts = explode( '/', $request['mime_type'] );
24
			if ( isset( $media_types[ $parts[0] ] ) && in_array( $request['mime_type'], $media_types[ $parts[0] ] ) ) {
25
				$query_args['post_mime_type'] = $request['mime_type'];
26
			}
27
		}
28
		return $query_args;
29
	}
30
31
	/**
32
	 * Check if a given request has access to create an attachment.
33
	 *
34
	 * @param  WP_REST_Request $request Full details about the request.
35
	 * @return WP_Error|boolean
36
	 */
37
	public function create_item_permissions_check( $request ) {
38
		$ret = parent::create_item_permissions_check( $request );
39
		if ( ! $ret || is_wp_error( $ret ) ) {
40
			return $ret;
41
		}
42
43
		// "upload_files" cap is returned for an attachment by $post_type_obj->cap->create_posts
44
		$post_type_obj = get_post_type_object( $this->post_type );
45
		if ( ! current_user_can( $post_type_obj->cap->create_posts ) || ! current_user_can( $post_type_obj->cap->edit_posts ) ) {
46
			return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) );
47
		}
48
49
		// Attaching media to a post requires ability to edit said post
50
		if ( ! empty( $request['post'] ) ) {
51
			$parent = $this->get_post( (int) $request['post'] );
52
			$post_parent_type = get_post_type_object( $parent->post_type );
53
			if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
54
				return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this resource.' ), array( 'status' => rest_authorization_required_code() ) );
55
			}
56
		}
57
58
		return true;
59
	}
60
61
	/**
62
	 * Create a single attachment
63
	 *
64
	 * @param WP_REST_Request $request Full details about the request
65
	 * @return WP_Error|WP_REST_Response
66
	 */
67
	public function create_item( $request ) {
68
69
		if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ) ) ) {
70
			return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
71
		}
72
73
		// Get the file via $_FILES or raw data
74
		$files = $request->get_file_params();
75
		$headers = $request->get_headers();
76
		if ( ! empty( $files ) ) {
77
			$file = $this->upload_from_file( $files, $headers );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->upload_from_file($files, $headers); of type array|WP_Error adds the type array to the return on line 83 which is incompatible with the return type documented by WP_REST_Attachments_Controller::create_item of type WP_Error|WP_REST_Response.
Loading history...
78
		} else {
79
			$file = $this->upload_from_data( $request->get_body(), $headers );
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->upload_from_data(...>get_body(), $headers); of type array|WP_Error adds the type array to the return on line 83 which is incompatible with the return type documented by WP_REST_Attachments_Controller::create_item of type WP_Error|WP_REST_Response.
Loading history...
80
		}
81
82
		if ( is_wp_error( $file ) ) {
83
			return $file;
84
		}
85
86
		$name       = basename( $file['file'] );
87
		$name_parts = pathinfo( $name );
88
		$name       = trim( substr( $name, 0, -(1 + strlen( $name_parts['extension'] ) ) ) );
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
89
90
		$url     = $file['url'];
91
		$type    = $file['type'];
92
		$file    = $file['file'];
93
94
		// use image exif/iptc data for title and caption defaults if possible
95
		// @codingStandardsIgnoreStart
96
		$image_meta = @wp_read_image_metadata( $file );
97
		// @codingStandardsIgnoreEnd
98
		if ( ! empty( $image_meta ) ) {
99
			if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
100
				$request['title'] = $image_meta['title'];
101
			}
102
103
			if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
104
				$request['caption'] = $image_meta['caption'];
105
			}
106
		}
107
108
		$attachment = $this->prepare_item_for_database( $request );
109
		$attachment->file = $file;
110
		$attachment->post_mime_type = $type;
111
		$attachment->guid = $url;
112
113
		if ( empty( $attachment->post_title ) ) {
114
			$attachment->post_title = preg_replace( '/\.[^.]+$/', '', basename( $file ) );
115
		}
116
117
		$id = wp_insert_post( $attachment, true );
118 View Code Duplication
		if ( is_wp_error( $id ) ) {
119
			if ( in_array( $id->get_error_code(), array( 'db_update_error' ) ) ) {
120
				$id->add_data( array( 'status' => 500 ) );
121
			} else {
122
				$id->add_data( array( 'status' => 400 ) );
123
			}
124
			return $id;
125
		}
126
		$attachment = $this->get_post( $id );
127
128
		/** Include admin functions to get access to wp_generate_attachment_metadata() */
129
		require_once ABSPATH . 'wp-admin/includes/admin.php';
130
131
		wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
132
133
		if ( isset( $request['alt_text'] ) ) {
134
			update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
135
		}
136
137
		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
0 ignored issues
show
Bug introduced by
It seems like $attachment defined by $this->get_post($id) on line 126 can also be of type null; however, WP_REST_Controller::upda...nal_fields_for_object() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug Compatibility introduced by
The expression $this->update_additional...$attachment, $request); of type boolean|WP_Error adds the type boolean to the return on line 139 which is incompatible with the return type documented by WP_REST_Attachments_Controller::create_item of type WP_Error|WP_REST_Response.
Loading history...
138
		if ( is_wp_error( $fields_update ) ) {
139
			return $fields_update;
140
		}
141
142
		$request->set_param( 'context', 'edit' );
143
		$response = $this->prepare_item_for_response( $attachment, $request );
0 ignored issues
show
Bug introduced by
It seems like $attachment defined by $this->get_post($id) on line 126 can be null; however, WP_REST_Attachments_Cont...are_item_for_response() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
144
		$response = rest_ensure_response( $response );
145
		$response->set_status( 201 );
146
		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
147
148
		/**
149
		 * Fires after a single attachment is created or updated via the REST API.
150
		 *
151
		 * @param object          $attachment Inserted attachment.
152
		 * @param WP_REST_Request $request    The request sent to the API.
153
		 * @param boolean         $creating   True when creating an attachment, false when updating.
154
		 */
155
		do_action( 'rest_insert_attachment', $attachment, $request, true );
156
157
		return $response;
158
159
	}
160
161
	/**
162
	 * Update a single post
163
	 *
164
	 * @param WP_REST_Request $request Full details about the request
165
	 * @return WP_Error|WP_REST_Response
166
	 */
167
	public function update_item( $request ) {
168
		if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ) ) ) {
169
			return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
170
		}
171
		$response = parent::update_item( $request );
172
		if ( is_wp_error( $response ) ) {
173
			return $response;
174
		}
175
176
		$response = rest_ensure_response( $response );
177
		$data = $response->get_data();
178
179
		if ( isset( $request['alt_text'] ) ) {
180
			update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
181
		}
182
183
		$attachment = $this->get_post( $request['id'] );
184
185
		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
0 ignored issues
show
Bug introduced by
It seems like $attachment defined by $this->get_post($request['id']) on line 183 can also be of type null; however, WP_REST_Controller::upda...nal_fields_for_object() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug Compatibility introduced by
The expression $this->update_additional...$attachment, $request); of type boolean|WP_Error adds the type boolean to the return on line 187 which is incompatible with the return type documented by WP_REST_Attachments_Controller::update_item of type WP_Error|WP_REST_Response.
Loading history...
186
		if ( is_wp_error( $fields_update ) ) {
187
			return $fields_update;
188
		}
189
190
		$request->set_param( 'context', 'edit' );
191
		$response = $this->prepare_item_for_response( $attachment, $request );
0 ignored issues
show
Bug introduced by
It seems like $attachment defined by $this->get_post($request['id']) on line 183 can be null; however, WP_REST_Attachments_Cont...are_item_for_response() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
192
		$response = rest_ensure_response( $response );
193
194
		/* This action is documented in lib/endpoints/class-wp-rest-attachments-controller.php */
195
		do_action( 'rest_insert_attachment', $data, $request, false );
196
197
		return $response;
198
	}
199
200
	/**
201
	 * Prepare a single attachment for create or update
202
	 *
203
	 * @param WP_REST_Request $request Request object
204
	 * @return WP_Error|stdClass $prepared_attachment Post object
205
	 */
206
	protected function prepare_item_for_database( $request ) {
207
		$prepared_attachment = parent::prepare_item_for_database( $request );
208
209
		if ( isset( $request['caption'] ) ) {
210
			$prepared_attachment->post_excerpt = $request['caption'];
211
		}
212
213
		if ( isset( $request['description'] ) ) {
214
			$prepared_attachment->post_content = $request['description'];
215
		}
216
217
		if ( isset( $request['post'] ) ) {
218
			$prepared_attachment->post_parent = (int) $request['post'];
219
		}
220
221
		return $prepared_attachment;
222
	}
223
224
	/**
225
	 * Prepare a single attachment output for response
226
	 *
227
	 * @param WP_Post $post Post object
228
	 * @param WP_REST_Request $request Request object
229
	 * @return WP_REST_Response $response
230
	 */
231
	public function prepare_item_for_response( $post, $request ) {
232
		$response = parent::prepare_item_for_response( $post, $request );
233
		$data = $response->get_data();
234
235
		$data['alt_text']      = get_post_meta( $post->ID, '_wp_attachment_image_alt', true );
236
		$data['caption']       = $post->post_excerpt;
237
		$data['description']   = $post->post_content;
238
		$data['media_type']    = wp_attachment_is_image( $post->ID ) ? 'image' : 'file';
239
		$data['mime_type']     = $post->post_mime_type;
240
		$data['media_details'] = wp_get_attachment_metadata( $post->ID );
241
		$data['post']          = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
242
		$data['source_url']    = wp_get_attachment_url( $post->ID );
243
244
		// Ensure empty details is an empty object
245
		if ( empty( $data['media_details'] ) ) {
246
			$data['media_details'] = new stdClass;
247
		} elseif ( ! empty( $data['media_details']['sizes'] ) ) {
248
249
			foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {
250
251
				if ( isset( $size_data['mime-type'] ) ) {
252
					$size_data['mime_type'] = $size_data['mime-type'];
253
					unset( $size_data['mime-type'] );
254
				}
255
256
				// Use the same method image_downsize() does
257
				$image_src = wp_get_attachment_image_src( $post->ID, $size );
258
				if ( ! $image_src ) {
259
					continue;
260
				}
261
262
				$size_data['source_url'] = $image_src[0];
263
			}
264
265
			$full_src = wp_get_attachment_image_src( $post->ID, 'full' );
266
			if ( ! empty( $full_src ) ) {
267
				$data['media_details']['sizes']['full'] = array(
268
					'file'          => wp_basename( $full_src[0] ),
269
					'width'         => $full_src[1],
270
					'height'        => $full_src[2],
271
					'mime_type'     => $post->post_mime_type,
272
					'source_url'    => $full_src[0],
273
					);
274
			}
275
		} else {
276
			$data['media_details']['sizes'] = new stdClass;
277
		}
278
279
		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
280
281
		$data = $this->filter_response_by_context( $data, $context );
282
283
		// Wrap the data in a response object
284
		$response = rest_ensure_response( $data );
285
286
		$response->add_links( $this->prepare_links( $post ) );
287
288
		/**
289
		 * Filter an attachment returned from the API.
290
		 *
291
		 * Allows modification of the attachment right before it is returned.
292
		 *
293
		 * @param WP_REST_Response  $response   The response object.
294
		 * @param WP_Post           $post       The original attachment post.
295
		 * @param WP_REST_Request   $request    Request used to generate the response.
296
		 */
297
		return apply_filters( 'rest_prepare_attachment', $response, $post, $request );
298
	}
299
300
	/**
301
	 * Get the Attachment's schema, conforming to JSON Schema
302
	 *
303
	 * @return array
304
	 */
305
	public function get_item_schema() {
306
307
		$schema = parent::get_item_schema();
308
309
		$schema['properties']['alt_text'] = array(
310
			'description'     => __( 'Alternative text to display when resource is not displayed.' ),
311
			'type'            => 'string',
312
			'context'         => array( 'view', 'edit', 'embed' ),
313
			'arg_options'     => array(
314
				'sanitize_callback' => 'sanitize_text_field',
315
			),
316
		);
317
		$schema['properties']['caption'] = array(
318
			'description'     => __( 'The caption for the resource.' ),
319
			'type'            => 'string',
320
			'context'         => array( 'view', 'edit' ),
321
			'arg_options'     => array(
322
				'sanitize_callback' => 'wp_filter_post_kses',
323
			),
324
		);
325
		$schema['properties']['description'] = array(
326
			'description'     => __( 'The description for the resource.' ),
327
			'type'            => 'string',
328
			'context'         => array( 'view', 'edit' ),
329
			'arg_options'     => array(
330
				'sanitize_callback' => 'wp_filter_post_kses',
331
			),
332
		);
333
		$schema['properties']['media_type'] = array(
334
			'description'     => __( 'Type of resource.' ),
335
			'type'            => 'string',
336
			'enum'            => array( 'image', 'file' ),
337
			'context'         => array( 'view', 'edit', 'embed' ),
338
			'readonly'        => true,
339
		);
340
		$schema['properties']['mime_type'] = array(
341
			'description'     => __( 'Mime type of resource.' ),
342
			'type'            => 'string',
343
			'context'         => array( 'view', 'edit', 'embed' ),
344
			'readonly'        => true,
345
		);
346
		$schema['properties']['media_details'] = array(
347
			'description'     => __( 'Details about the resource file, specific to its type.' ),
348
			'type'            => 'object',
349
			'context'         => array( 'view', 'edit', 'embed' ),
350
			'readonly'        => true,
351
		);
352
		$schema['properties']['post'] = array(
353
			'description'     => __( 'The id for the associated post of the resource.' ),
354
			'type'            => 'integer',
355
			'context'         => array( 'view', 'edit' ),
356
		);
357
		$schema['properties']['source_url'] = array(
358
			'description'     => __( 'URL to the original resource file.' ),
359
			'type'            => 'string',
360
			'format'          => 'uri',
361
			'context'         => array( 'view', 'edit', 'embed' ),
362
			'readonly'        => true,
363
		);
364
		return $schema;
365
	}
366
367
	/**
368
	 * Handle an upload via raw POST data
369
	 *
370
	 * @param array $data Supplied file data
371
	 * @param array $headers HTTP headers from the request
372
	 * @return array|WP_Error Data from {@see wp_handle_sideload()}
373
	 */
374
	protected function upload_from_data( $data, $headers ) {
375
		if ( empty( $data ) ) {
376
			return new WP_Error( 'rest_upload_no_data', __( 'No data supplied' ), array( 'status' => 400 ) );
377
		}
378
379
		if ( empty( $headers['content_type'] ) ) {
380
			return new WP_Error( 'rest_upload_no_content_type', __( 'No Content-Type supplied' ), array( 'status' => 400 ) );
381
		}
382
383
		if ( empty( $headers['content_disposition'] ) ) {
384
			return new WP_Error( 'rest_upload_no_content_disposition', __( 'No Content-Disposition supplied' ), array( 'status' => 400 ) );
385
		}
386
387
		$filename = $this->get_filename_from_disposition( $headers['content_disposition'] );
388
389
		if ( empty( $filename ) ) {
390
			return new WP_Error( 'rest_upload_invalid_disposition', __( 'Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as `attachment; filename="image.png"` or similar.' ), array( 'status' => 400 ) );
391
		}
392
393 View Code Duplication
		if ( ! empty( $headers['content_md5'] ) ) {
394
			$content_md5 = array_shift( $headers['content_md5'] );
395
			$expected = trim( $content_md5 );
396
			$actual   = md5( $data );
397
398
			if ( $expected !== $actual ) {
399
				return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected' ), array( 'status' => 412 ) );
400
			}
401
		}
402
403
		// Get the content-type
404
		$type = array_shift( $headers['content_type'] );
405
406
		/** Include admin functions to get access to wp_tempnam() and wp_handle_sideload() */
407
		require_once ABSPATH . 'wp-admin/includes/admin.php';
408
409
		// Save the file
410
		$tmpfname = wp_tempnam( $filename );
411
412
		$fp = fopen( $tmpfname, 'w+' );
413
414
		if ( ! $fp ) {
415
			return new WP_Error( 'rest_upload_file_error', __( 'Could not open file handle' ), array( 'status' => 500 ) );
416
		}
417
418
		fwrite( $fp, $data );
419
		fclose( $fp );
420
421
		// Now, sideload it in
422
		$file_data = array(
423
			'error'    => null,
424
			'tmp_name' => $tmpfname,
425
			'name'     => $filename,
426
			'type'     => $type,
427
		);
428
		$overrides = array(
429
			'test_form' => false,
430
		);
431
		$sideloaded = wp_handle_sideload( $file_data, $overrides );
432
433
		if ( isset( $sideloaded['error'] ) ) {
434
			// @codingStandardsIgnoreStart
435
			@unlink( $tmpfname );
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...
436
			// @codingStandardsIgnoreEnd
437
			return new WP_Error( 'rest_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) );
438
		}
439
440
		return $sideloaded;
441
	}
442
443
	/**
444
	 * Parse filename from a Content-Disposition header value.
445
	 *
446
	 * As per RFC6266:
447
	 *
448
	 *     content-disposition = "Content-Disposition" ":"
449
	 *                            disposition-type *( ";" disposition-parm )
450
	 *
451
	 *     disposition-type    = "inline" | "attachment" | disp-ext-type
452
	 *                         ; case-insensitive
453
	 *     disp-ext-type       = token
454
	 *
455
	 *     disposition-parm    = filename-parm | disp-ext-parm
456
	 *
457
	 *     filename-parm       = "filename" "=" value
458
	 *                         | "filename*" "=" ext-value
459
	 *
460
	 *     disp-ext-parm       = token "=" value
461
	 *                         | ext-token "=" ext-value
462
	 *     ext-token           = <the characters in token, followed by "*">
463
	 *
464
	 * @see http://tools.ietf.org/html/rfc2388
465
	 * @see http://tools.ietf.org/html/rfc6266
466
	 *
467
	 * @param string[] $disposition_header List of Content-Disposition header values.
468
	 * @return string|null Filename if available, or null if not found.
469
	 */
470
	public static function get_filename_from_disposition( $disposition_header ) {
471
		// Get the filename
472
		$filename = null;
473
474
		foreach ( $disposition_header as $value ) {
475
			$value = trim( $value );
476
477
			if ( strpos( $value, ';' ) === false ) {
478
				continue;
479
			}
480
481
			list( $type, $attr_parts ) = explode( ';', $value, 2 );
0 ignored issues
show
Unused Code introduced by
The assignment to $type is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
482
			$attr_parts = explode( ';', $attr_parts );
483
			$attributes = array();
484
			foreach ( $attr_parts as $part ) {
485
				if ( strpos( $part, '=' ) === false ) {
486
					continue;
487
				}
488
489
				list( $key, $value ) = explode( '=', $part, 2 );
490
				$attributes[ trim( $key ) ] = trim( $value );
491
			}
492
493
			if ( empty( $attributes['filename'] ) ) {
494
				continue;
495
			}
496
497
			$filename = trim( $attributes['filename'] );
498
499
			// Unquote quoted filename, but after trimming.
500
			if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) {
501
				$filename = substr( $filename, 1, -1 );
502
			}
503
		}
504
505
		return $filename;
506
	}
507
508
	/**
509
	 * Get the query params for collections of attachments.
510
	 *
511
	 * @return array
512
	 */
513
	public function get_collection_params() {
514
		$params = parent::get_collection_params();
515
		$params['status']['default'] = 'inherit';
516
		$params['status']['enum'] = array( 'inherit', 'private', 'trash' );
517
		$media_types = $this->get_media_types();
518
		$params['media_type'] = array(
519
			'default'            => null,
520
			'description'        => __( 'Limit result set to attachments of a particular media type.' ),
521
			'type'               => 'string',
522
			'enum'               => array_keys( $media_types ),
523
			'validate_callback'  => 'rest_validate_request_arg',
524
		);
525
		$params['mime_type'] = array(
526
			'default'            => null,
527
			'description'        => __( 'Limit result set to attachments of a particular mime type.' ),
528
			'type'               => 'string',
529
		);
530
		return $params;
531
	}
532
533
	/**
534
	 * Validate whether the user can query private statuses
535
	 *
536
	 * @param  mixed $value
537
	 * @param  WP_REST_Request $request
538
	 * @param  string $parameter
539
	 * @return WP_Error|boolean
540
	 */
541
	public function validate_user_can_query_private_statuses( $value, $request, $parameter ) {
542
		if ( 'inherit' === $value ) {
543
			return true;
544
		}
545
		return parent::validate_user_can_query_private_statuses( $value, $request, $parameter );
546
	}
547
548
	/**
549
	 * Handle an upload via multipart/form-data ($_FILES)
550
	 *
551
	 * @param array $files Data from $_FILES
552
	 * @param array $headers HTTP headers from the request
553
	 * @return array|WP_Error Data from {@see wp_handle_upload()}
554
	 */
555
	protected function upload_from_file( $files, $headers ) {
556
		if ( empty( $files ) ) {
557
			return new WP_Error( 'rest_upload_no_data', __( 'No data supplied' ), array( 'status' => 400 ) );
558
		}
559
560
		// Verify hash, if given
561 View Code Duplication
		if ( ! empty( $headers['content_md5'] ) ) {
562
			$content_md5 = array_shift( $headers['content_md5'] );
563
			$expected = trim( $content_md5 );
564
			$actual = md5_file( $files['file']['tmp_name'] );
565
			if ( $expected !== $actual ) {
566
				return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected' ), array( 'status' => 412 ) );
567
			}
568
		}
569
570
		// Pass off to WP to handle the actual upload
571
		$overrides = array(
572
			'test_form'   => false,
573
		);
574
		// Bypasses is_uploaded_file() when running unit tests
575
		if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) {
576
			$overrides['action'] = 'wp_handle_mock_upload';
577
		}
578
579
		/** Include admin functions to get access to wp_handle_upload() */
580
		require_once ABSPATH . 'wp-admin/includes/admin.php';
581
		$file = wp_handle_upload( $files['file'], $overrides );
582
583
		if ( isset( $file['error'] ) ) {
584
			return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) );
585
		}
586
587
		return $file;
588
	}
589
590
	/**
591
	 * Get the supported media types.
592
	 * Media types are considered the mime type category
593
	 *
594
	 * @return array
595
	 */
596
	protected function get_media_types() {
597
		$media_types = array();
598
		foreach ( get_allowed_mime_types() as $mime_type ) {
599
			$parts = explode( '/', $mime_type );
600
			if ( ! isset( $media_types[ $parts[0] ] ) ) {
601
				$media_types[ $parts[0] ] = array();
602
			}
603
			$media_types[ $parts[0] ][] = $mime_type;
604
		}
605
		return $media_types;
606
	}
607
608
}
609