73 return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) ); |
73 return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) ); |
74 } |
74 } |
75 |
75 |
76 // Attaching media to a post requires ability to edit said post. |
76 // Attaching media to a post requires ability to edit said post. |
77 if ( ! empty( $request['post'] ) ) { |
77 if ( ! empty( $request['post'] ) ) { |
78 $parent = get_post( (int) $request['post'] ); |
78 $parent = get_post( (int) $request['post'] ); |
79 $post_parent_type = get_post_type_object( $parent->post_type ); |
79 $post_parent_type = get_post_type_object( $parent->post_type ); |
80 |
80 |
81 if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) { |
81 if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) { |
82 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this post.' ), array( 'status' => rest_authorization_required_code() ) ); |
82 return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this post.' ), array( 'status' => rest_authorization_required_code() ) ); |
83 } |
83 } |
99 if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) { |
99 if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) { |
100 return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) ); |
100 return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) ); |
101 } |
101 } |
102 |
102 |
103 // Get the file via $_FILES or raw data. |
103 // Get the file via $_FILES or raw data. |
104 $files = $request->get_file_params(); |
104 $files = $request->get_file_params(); |
105 $headers = $request->get_headers(); |
105 $headers = $request->get_headers(); |
106 |
106 |
107 if ( ! empty( $files ) ) { |
107 if ( ! empty( $files ) ) { |
108 $file = $this->upload_from_file( $files, $headers ); |
108 $file = $this->upload_from_file( $files, $headers ); |
109 } else { |
109 } else { |
112 |
112 |
113 if ( is_wp_error( $file ) ) { |
113 if ( is_wp_error( $file ) ) { |
114 return $file; |
114 return $file; |
115 } |
115 } |
116 |
116 |
117 $name = basename( $file['file'] ); |
117 $name = wp_basename( $file['file'] ); |
118 $name_parts = pathinfo( $name ); |
118 $name_parts = pathinfo( $name ); |
119 $name = trim( substr( $name, 0, -(1 + strlen( $name_parts['extension'] ) ) ) ); |
119 $name = trim( substr( $name, 0, -( 1 + strlen( $name_parts['extension'] ) ) ) ); |
120 |
120 |
121 $url = $file['url']; |
121 $url = $file['url']; |
122 $type = $file['type']; |
122 $type = $file['type']; |
123 $file = $file['file']; |
123 $file = $file['file']; |
|
124 |
|
125 // Include image functions to get access to wp_read_image_metadata(). |
|
126 require_once ABSPATH . 'wp-admin/includes/image.php'; |
124 |
127 |
125 // use image exif/iptc data for title and caption defaults if possible |
128 // use image exif/iptc data for title and caption defaults if possible |
126 $image_meta = wp_read_image_metadata( $file ); |
129 $image_meta = wp_read_image_metadata( $file ); |
127 |
130 |
128 if ( ! empty( $image_meta ) ) { |
131 if ( ! empty( $image_meta ) ) { |
133 if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) { |
136 if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) { |
134 $request['caption'] = $image_meta['caption']; |
137 $request['caption'] = $image_meta['caption']; |
135 } |
138 } |
136 } |
139 } |
137 |
140 |
138 $attachment = $this->prepare_item_for_database( $request ); |
141 $attachment = $this->prepare_item_for_database( $request ); |
139 $attachment->post_mime_type = $type; |
142 $attachment->post_mime_type = $type; |
140 $attachment->guid = $url; |
143 $attachment->guid = $url; |
141 |
144 |
142 if ( empty( $attachment->post_title ) ) { |
145 if ( empty( $attachment->post_title ) ) { |
143 $attachment->post_title = preg_replace( '/\.[^.]+$/', '', basename( $file ) ); |
146 $attachment->post_title = preg_replace( '/\.[^.]+$/', '', wp_basename( $file ) ); |
144 } |
147 } |
145 |
148 |
146 // $post_parent is inherited from $attachment['post_parent']. |
149 // $post_parent is inherited from $attachment['post_parent']. |
147 $id = wp_insert_attachment( wp_slash( (array) $attachment ), $file, 0, true ); |
150 $id = wp_insert_attachment( wp_slash( (array) $attachment ), $file, 0, true ); |
148 |
151 |
167 * @param WP_REST_Request $request The request sent to the API. |
170 * @param WP_REST_Request $request The request sent to the API. |
168 * @param bool $creating True when creating an attachment, false when updating. |
171 * @param bool $creating True when creating an attachment, false when updating. |
169 */ |
172 */ |
170 do_action( 'rest_insert_attachment', $attachment, $request, true ); |
173 do_action( 'rest_insert_attachment', $attachment, $request, true ); |
171 |
174 |
172 // Include admin functions to get access to wp_generate_attachment_metadata(). |
175 // Include admin function to get access to wp_generate_attachment_metadata(). |
173 require_once ABSPATH . 'wp-admin/includes/admin.php'; |
176 require_once ABSPATH . 'wp-admin/includes/media.php'; |
174 |
177 |
175 wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) ); |
178 wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) ); |
176 |
179 |
177 if ( isset( $request['alt_text'] ) ) { |
180 if ( isset( $request['alt_text'] ) ) { |
178 update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) ); |
181 update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) ); |
183 if ( is_wp_error( $fields_update ) ) { |
186 if ( is_wp_error( $fields_update ) ) { |
184 return $fields_update; |
187 return $fields_update; |
185 } |
188 } |
186 |
189 |
187 $request->set_param( 'context', 'edit' ); |
190 $request->set_param( 'context', 'edit' ); |
|
191 |
|
192 /** |
|
193 * Fires after a single attachment is completely created or updated via the REST API. |
|
194 * |
|
195 * @since 5.0.0 |
|
196 * |
|
197 * @param WP_Post $attachment Inserted or updated attachment object. |
|
198 * @param WP_REST_Request $request Request object. |
|
199 * @param bool $creating True when creating an attachment, false when updating. |
|
200 */ |
|
201 do_action( 'rest_after_insert_attachment', $attachment, $request, true ); |
|
202 |
188 $response = $this->prepare_item_for_response( $attachment, $request ); |
203 $response = $this->prepare_item_for_response( $attachment, $request ); |
189 $response = rest_ensure_response( $response ); |
204 $response = rest_ensure_response( $response ); |
190 $response->set_status( 201 ); |
205 $response->set_status( 201 ); |
191 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) ); |
206 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) ); |
192 |
207 |
211 if ( is_wp_error( $response ) ) { |
226 if ( is_wp_error( $response ) ) { |
212 return $response; |
227 return $response; |
213 } |
228 } |
214 |
229 |
215 $response = rest_ensure_response( $response ); |
230 $response = rest_ensure_response( $response ); |
216 $data = $response->get_data(); |
231 $data = $response->get_data(); |
217 |
232 |
218 if ( isset( $request['alt_text'] ) ) { |
233 if ( isset( $request['alt_text'] ) ) { |
219 update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] ); |
234 update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] ); |
220 } |
235 } |
221 |
236 |
222 $attachment = get_post( $request['id'] ); |
237 $attachment = get_post( $request['id'] ); |
223 |
238 |
224 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */ |
|
225 do_action( 'rest_insert_attachment', $data, $request, false ); |
|
226 |
|
227 $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); |
239 $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); |
228 |
240 |
229 if ( is_wp_error( $fields_update ) ) { |
241 if ( is_wp_error( $fields_update ) ) { |
230 return $fields_update; |
242 return $fields_update; |
231 } |
243 } |
232 |
244 |
233 $request->set_param( 'context', 'edit' ); |
245 $request->set_param( 'context', 'edit' ); |
|
246 |
|
247 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */ |
|
248 do_action( 'rest_after_insert_attachment', $attachment, $request, false ); |
|
249 |
234 $response = $this->prepare_item_for_response( $attachment, $request ); |
250 $response = $this->prepare_item_for_response( $attachment, $request ); |
235 $response = rest_ensure_response( $response ); |
251 $response = rest_ensure_response( $response ); |
236 |
252 |
237 return $response; |
253 return $response; |
238 } |
254 } |
370 |
386 |
371 $links = $response->get_links(); |
387 $links = $response->get_links(); |
372 |
388 |
373 // Wrap the data in a response object. |
389 // Wrap the data in a response object. |
374 $response = rest_ensure_response( $data ); |
390 $response = rest_ensure_response( $data ); |
375 $response->add_links( $links ); |
391 |
|
392 foreach ( $links as $rel => $rel_links ) { |
|
393 foreach ( $rel_links as $link ) { |
|
394 $response->add_link( $rel, $link['href'], $link['attributes'] ); |
|
395 } |
|
396 } |
376 |
397 |
377 /** |
398 /** |
378 * Filters an attachment returned from the REST API. |
399 * Filters an attachment returned from the REST API. |
379 * |
400 * |
380 * Allows modification of the attachment right before it is returned. |
401 * Allows modification of the attachment right before it is returned. |
398 public function get_item_schema() { |
419 public function get_item_schema() { |
399 |
420 |
400 $schema = parent::get_item_schema(); |
421 $schema = parent::get_item_schema(); |
401 |
422 |
402 $schema['properties']['alt_text'] = array( |
423 $schema['properties']['alt_text'] = array( |
403 'description' => __( 'Alternative text to display when attachment is not displayed.' ), |
424 'description' => __( 'Alternative text to display when attachment is not displayed.' ), |
404 'type' => 'string', |
425 'type' => 'string', |
405 'context' => array( 'view', 'edit', 'embed' ), |
426 'context' => array( 'view', 'edit', 'embed' ), |
406 'arg_options' => array( |
427 'arg_options' => array( |
407 'sanitize_callback' => 'sanitize_text_field', |
428 'sanitize_callback' => 'sanitize_text_field', |
408 ), |
429 ), |
409 ); |
430 ); |
410 |
431 |
411 $schema['properties']['caption'] = array( |
432 $schema['properties']['caption'] = array( |
415 'arg_options' => array( |
436 'arg_options' => array( |
416 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database() |
437 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database() |
417 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database() |
438 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database() |
418 ), |
439 ), |
419 'properties' => array( |
440 'properties' => array( |
420 'raw' => array( |
441 'raw' => array( |
421 'description' => __( 'Caption for the attachment, as it exists in the database.' ), |
442 'description' => __( 'Caption for the attachment, as it exists in the database.' ), |
422 'type' => 'string', |
443 'type' => 'string', |
423 'context' => array( 'edit' ), |
444 'context' => array( 'edit' ), |
424 ), |
445 ), |
425 'rendered' => array( |
446 'rendered' => array( |
438 'arg_options' => array( |
459 'arg_options' => array( |
439 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database() |
460 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database() |
440 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database() |
461 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database() |
441 ), |
462 ), |
442 'properties' => array( |
463 'properties' => array( |
443 'raw' => array( |
464 'raw' => array( |
444 'description' => __( 'Description for the object, as it exists in the database.' ), |
465 'description' => __( 'Description for the object, as it exists in the database.' ), |
445 'type' => 'string', |
466 'type' => 'string', |
446 'context' => array( 'edit' ), |
467 'context' => array( 'edit' ), |
447 ), |
468 ), |
448 'rendered' => array( |
469 'rendered' => array( |
453 ), |
474 ), |
454 ), |
475 ), |
455 ); |
476 ); |
456 |
477 |
457 $schema['properties']['media_type'] = array( |
478 $schema['properties']['media_type'] = array( |
458 'description' => __( 'Attachment type.' ), |
479 'description' => __( 'Attachment type.' ), |
459 'type' => 'string', |
480 'type' => 'string', |
460 'enum' => array( 'image', 'file' ), |
481 'enum' => array( 'image', 'file' ), |
461 'context' => array( 'view', 'edit', 'embed' ), |
482 'context' => array( 'view', 'edit', 'embed' ), |
462 'readonly' => true, |
483 'readonly' => true, |
463 ); |
484 ); |
464 |
485 |
465 $schema['properties']['mime_type'] = array( |
486 $schema['properties']['mime_type'] = array( |
466 'description' => __( 'The attachment MIME type.' ), |
487 'description' => __( 'The attachment MIME type.' ), |
467 'type' => 'string', |
488 'type' => 'string', |
468 'context' => array( 'view', 'edit', 'embed' ), |
489 'context' => array( 'view', 'edit', 'embed' ), |
469 'readonly' => true, |
490 'readonly' => true, |
470 ); |
491 ); |
471 |
492 |
472 $schema['properties']['media_details'] = array( |
493 $schema['properties']['media_details'] = array( |
473 'description' => __( 'Details about the media file, specific to its type.' ), |
494 'description' => __( 'Details about the media file, specific to its type.' ), |
474 'type' => 'object', |
495 'type' => 'object', |
475 'context' => array( 'view', 'edit', 'embed' ), |
496 'context' => array( 'view', 'edit', 'embed' ), |
476 'readonly' => true, |
497 'readonly' => true, |
477 ); |
498 ); |
478 |
499 |
479 $schema['properties']['post'] = array( |
500 $schema['properties']['post'] = array( |
480 'description' => __( 'The ID for the associated post of the attachment.' ), |
501 'description' => __( 'The ID for the associated post of the attachment.' ), |
481 'type' => 'integer', |
502 'type' => 'integer', |
482 'context' => array( 'view', 'edit' ), |
503 'context' => array( 'view', 'edit' ), |
483 ); |
504 ); |
484 |
505 |
485 $schema['properties']['source_url'] = array( |
506 $schema['properties']['source_url'] = array( |
486 'description' => __( 'URL to the original attachment file.' ), |
507 'description' => __( 'URL to the original attachment file.' ), |
487 'type' => 'string', |
508 'type' => 'string', |
488 'format' => 'uri', |
509 'format' => 'uri', |
489 'context' => array( 'view', 'edit', 'embed' ), |
510 'context' => array( 'view', 'edit', 'embed' ), |
490 'readonly' => true, |
511 'readonly' => true, |
491 ); |
512 ); |
492 |
513 |
493 unset( $schema['properties']['password'] ); |
514 unset( $schema['properties']['password'] ); |
494 |
515 |
495 return $schema; |
516 return $schema; |
534 } |
555 } |
535 |
556 |
536 // Get the content-type. |
557 // Get the content-type. |
537 $type = array_shift( $headers['content_type'] ); |
558 $type = array_shift( $headers['content_type'] ); |
538 |
559 |
539 /** Include admin functions to get access to wp_tempnam() and wp_handle_sideload() */ |
560 /** Include admin functions to get access to wp_tempnam() and wp_handle_sideload(). */ |
540 require_once ABSPATH . 'wp-admin/includes/admin.php'; |
561 require_once ABSPATH . 'wp-admin/includes/file.php'; |
541 |
562 |
542 // Save the file. |
563 // Save the file. |
543 $tmpfname = wp_tempnam( $filename ); |
564 $tmpfname = wp_tempnam( $filename ); |
544 |
565 |
545 $fp = fopen( $tmpfname, 'w+' ); |
566 $fp = fopen( $tmpfname, 'w+' ); |
655 * @since 4.7.0 |
676 * @since 4.7.0 |
656 * |
677 * |
657 * @return array Query parameters for the attachment collection as an array. |
678 * @return array Query parameters for the attachment collection as an array. |
658 */ |
679 */ |
659 public function get_collection_params() { |
680 public function get_collection_params() { |
660 $params = parent::get_collection_params(); |
681 $params = parent::get_collection_params(); |
661 $params['status']['default'] = 'inherit'; |
682 $params['status']['default'] = 'inherit'; |
662 $params['status']['items']['enum'] = array( 'inherit', 'private', 'trash' ); |
683 $params['status']['items']['enum'] = array( 'inherit', 'private', 'trash' ); |
663 $media_types = $this->get_media_types(); |
684 $media_types = $this->get_media_types(); |
664 |
685 |
665 $params['media_type'] = array( |
686 $params['media_type'] = array( |
666 'default' => null, |
687 'default' => null, |
667 'description' => __( 'Limit result set to attachments of a particular media type.' ), |
688 'description' => __( 'Limit result set to attachments of a particular media type.' ), |
668 'type' => 'string', |
689 'type' => 'string', |
669 'enum' => array_keys( $media_types ), |
690 'enum' => array_keys( $media_types ), |
670 ); |
691 ); |
671 |
692 |
672 $params['mime_type'] = array( |
693 $params['mime_type'] = array( |
673 'default' => null, |
694 'default' => null, |
674 'description' => __( 'Limit result set to attachments of a particular MIME type.' ), |
695 'description' => __( 'Limit result set to attachments of a particular MIME type.' ), |
675 'type' => 'string', |
696 'type' => 'string', |
676 ); |
697 ); |
677 |
698 |
678 return $params; |
699 return $params; |
679 } |
|
680 |
|
681 /** |
|
682 * Validates whether the user can query private statuses. |
|
683 * |
|
684 * @since 4.7.0 |
|
685 * |
|
686 * @param mixed $value Status value. |
|
687 * @param WP_REST_Request $request Request object. |
|
688 * @param string $parameter Additional parameter to pass for validation. |
|
689 * @return WP_Error|bool True if the user may query, WP_Error if not. |
|
690 */ |
|
691 public function validate_user_can_query_private_statuses( $value, $request, $parameter ) { |
|
692 if ( 'inherit' === $value ) { |
|
693 return true; |
|
694 } |
|
695 |
|
696 return parent::validate_user_can_query_private_statuses( $value, $request, $parameter ); |
|
697 } |
700 } |
698 |
701 |
699 /** |
702 /** |
700 * Handles an upload via multipart/form-data ($_FILES). |
703 * Handles an upload via multipart/form-data ($_FILES). |
701 * |
704 * |
721 } |
724 } |
722 } |
725 } |
723 |
726 |
724 // Pass off to WP to handle the actual upload. |
727 // Pass off to WP to handle the actual upload. |
725 $overrides = array( |
728 $overrides = array( |
726 'test_form' => false, |
729 'test_form' => false, |
727 ); |
730 ); |
728 |
731 |
729 // Bypasses is_uploaded_file() when running unit tests. |
732 // Bypasses is_uploaded_file() when running unit tests. |
730 if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) { |
733 if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) { |
731 $overrides['action'] = 'wp_handle_mock_upload'; |
734 $overrides['action'] = 'wp_handle_mock_upload'; |
734 $size_check = self::check_upload_size( $files['file'] ); |
737 $size_check = self::check_upload_size( $files['file'] ); |
735 if ( is_wp_error( $size_check ) ) { |
738 if ( is_wp_error( $size_check ) ) { |
736 return $size_check; |
739 return $size_check; |
737 } |
740 } |
738 |
741 |
739 /** Include admin functions to get access to wp_handle_upload() */ |
742 /** Include admin function to get access to wp_handle_upload(). */ |
740 require_once ABSPATH . 'wp-admin/includes/admin.php'; |
743 require_once ABSPATH . 'wp-admin/includes/file.php'; |
741 |
744 |
742 $file = wp_handle_upload( $files['file'], $overrides ); |
745 $file = wp_handle_upload( $files['file'], $overrides ); |
743 |
746 |
744 if ( isset( $file['error'] ) ) { |
747 if ( isset( $file['error'] ) ) { |
745 return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) ); |
748 return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) ); |
803 if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) { |
806 if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) { |
804 /* translators: %s: maximum allowed file size in kilobytes */ |
807 /* translators: %s: maximum allowed file size in kilobytes */ |
805 return new WP_Error( 'rest_upload_file_too_big', sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) ), array( 'status' => 400 ) ); |
808 return new WP_Error( 'rest_upload_file_too_big', sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) ), array( 'status' => 400 ) ); |
806 } |
809 } |
807 |
810 |
|
811 // Include admin function to get access to upload_is_user_over_quota(). |
|
812 require_once ABSPATH . 'wp-admin/includes/ms.php'; |
|
813 |
808 if ( upload_is_user_over_quota( false ) ) { |
814 if ( upload_is_user_over_quota( false ) ) { |
809 return new WP_Error( 'rest_upload_user_quota_exceeded', __( 'You have used your space quota. Please delete files before uploading.' ), array( 'status' => 400 ) ); |
815 return new WP_Error( 'rest_upload_user_quota_exceeded', __( 'You have used your space quota. Please delete files before uploading.' ), array( 'status' => 400 ) ); |
810 } |
816 } |
811 return true; |
817 return true; |
812 } |
818 } |