Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 

755 linhas
23 KiB

  1. <?php
  2. /**
  3. * REST API: WP_REST_Attachments_Controller class
  4. *
  5. * @package WordPress
  6. * @subpackage REST_API
  7. * @since 4.7.0
  8. */
  9. /**
  10. * Core controller used to access attachments via the REST API.
  11. *
  12. * @since 4.7.0
  13. *
  14. * @see WP_REST_Posts_Controller
  15. */
  16. class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
  17. /**
  18. * Determines the allowed query_vars for a get_items() response and
  19. * prepares for WP_Query.
  20. *
  21. * @since 4.7.0
  22. * @access protected
  23. *
  24. * @param array $prepared_args Optional. Array of prepared arguments. Default empty array.
  25. * @param WP_REST_Request $request Optional. Request to prepare items for.
  26. * @return array Array of query arguments.
  27. */
  28. protected function prepare_items_query( $prepared_args = array(), $request = null ) {
  29. $query_args = parent::prepare_items_query( $prepared_args, $request );
  30. if ( empty( $query_args['post_status'] ) ) {
  31. $query_args['post_status'] = 'inherit';
  32. }
  33. $media_types = $this->get_media_types();
  34. if ( ! empty( $request['media_type'] ) && isset( $media_types[ $request['media_type'] ] ) ) {
  35. $query_args['post_mime_type'] = $media_types[ $request['media_type'] ];
  36. }
  37. if ( ! empty( $request['mime_type'] ) ) {
  38. $parts = explode( '/', $request['mime_type'] );
  39. if ( isset( $media_types[ $parts[0] ] ) && in_array( $request['mime_type'], $media_types[ $parts[0] ], true ) ) {
  40. $query_args['post_mime_type'] = $request['mime_type'];
  41. }
  42. }
  43. // Filter query clauses to include filenames.
  44. if ( isset( $query_args['s'] ) ) {
  45. add_filter( 'posts_clauses', '_filter_query_attachment_filenames' );
  46. }
  47. return $query_args;
  48. }
  49. /**
  50. * Checks if a given request has access to create an attachment.
  51. *
  52. * @since 4.7.0
  53. * @access public
  54. *
  55. * @param WP_REST_Request $request Full details about the request.
  56. * @return WP_Error|true Boolean true if the attachment may be created, or a WP_Error if not.
  57. */
  58. public function create_item_permissions_check( $request ) {
  59. $ret = parent::create_item_permissions_check( $request );
  60. if ( ! $ret || is_wp_error( $ret ) ) {
  61. return $ret;
  62. }
  63. if ( ! current_user_can( 'upload_files' ) ) {
  64. return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) );
  65. }
  66. // Attaching media to a post requires ability to edit said post.
  67. if ( ! empty( $request['post'] ) ) {
  68. $parent = get_post( (int) $request['post'] );
  69. $post_parent_type = get_post_type_object( $parent->post_type );
  70. if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
  71. return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this post.' ), array( 'status' => rest_authorization_required_code() ) );
  72. }
  73. }
  74. return true;
  75. }
  76. /**
  77. * Creates a single attachment.
  78. *
  79. * @since 4.7.0
  80. * @access public
  81. *
  82. * @param WP_REST_Request $request Full details about the request.
  83. * @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure.
  84. */
  85. public function create_item( $request ) {
  86. if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
  87. return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
  88. }
  89. // Get the file via $_FILES or raw data.
  90. $files = $request->get_file_params();
  91. $headers = $request->get_headers();
  92. if ( ! empty( $files ) ) {
  93. $file = $this->upload_from_file( $files, $headers );
  94. } else {
  95. $file = $this->upload_from_data( $request->get_body(), $headers );
  96. }
  97. if ( is_wp_error( $file ) ) {
  98. return $file;
  99. }
  100. $name = basename( $file['file'] );
  101. $name_parts = pathinfo( $name );
  102. $name = trim( substr( $name, 0, -(1 + strlen( $name_parts['extension'] ) ) ) );
  103. $url = $file['url'];
  104. $type = $file['type'];
  105. $file = $file['file'];
  106. // use image exif/iptc data for title and caption defaults if possible
  107. $image_meta = @wp_read_image_metadata( $file );
  108. if ( ! empty( $image_meta ) ) {
  109. if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
  110. $request['title'] = $image_meta['title'];
  111. }
  112. if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
  113. $request['caption'] = $image_meta['caption'];
  114. }
  115. }
  116. $attachment = $this->prepare_item_for_database( $request );
  117. $attachment->file = $file;
  118. $attachment->post_mime_type = $type;
  119. $attachment->guid = $url;
  120. if ( empty( $attachment->post_title ) ) {
  121. $attachment->post_title = preg_replace( '/\.[^.]+$/', '', basename( $file ) );
  122. }
  123. $id = wp_insert_post( wp_slash( (array) $attachment ), true );
  124. if ( is_wp_error( $id ) ) {
  125. if ( 'db_update_error' === $id->get_error_code() ) {
  126. $id->add_data( array( 'status' => 500 ) );
  127. } else {
  128. $id->add_data( array( 'status' => 400 ) );
  129. }
  130. return $id;
  131. }
  132. $attachment = get_post( $id );
  133. /**
  134. * Fires after a single attachment is created or updated via the REST API.
  135. *
  136. * @since 4.7.0
  137. *
  138. * @param WP_Post $attachment Inserted or updated attachment
  139. * object.
  140. * @param WP_REST_Request $request The request sent to the API.
  141. * @param bool $creating True when creating an attachment, false when updating.
  142. */
  143. do_action( 'rest_insert_attachment', $attachment, $request, true );
  144. // Include admin functions to get access to wp_generate_attachment_metadata().
  145. require_once ABSPATH . 'wp-admin/includes/admin.php';
  146. wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
  147. if ( isset( $request['alt_text'] ) ) {
  148. update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
  149. }
  150. $fields_update = $this->update_additional_fields_for_object( $attachment, $request );
  151. if ( is_wp_error( $fields_update ) ) {
  152. return $fields_update;
  153. }
  154. $request->set_param( 'context', 'edit' );
  155. $response = $this->prepare_item_for_response( $attachment, $request );
  156. $response = rest_ensure_response( $response );
  157. $response->set_status( 201 );
  158. $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
  159. return $response;
  160. }
  161. /**
  162. * Updates a single attachment.
  163. *
  164. * @since 4.7.0
  165. * @access public
  166. *
  167. * @param WP_REST_Request $request Full details about the request.
  168. * @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure.
  169. */
  170. public function update_item( $request ) {
  171. if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
  172. return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
  173. }
  174. $response = parent::update_item( $request );
  175. if ( is_wp_error( $response ) ) {
  176. return $response;
  177. }
  178. $response = rest_ensure_response( $response );
  179. $data = $response->get_data();
  180. if ( isset( $request['alt_text'] ) ) {
  181. update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
  182. }
  183. $attachment = get_post( $request['id'] );
  184. /* This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */
  185. do_action( 'rest_insert_attachment', $data, $request, false );
  186. $fields_update = $this->update_additional_fields_for_object( $attachment, $request );
  187. if ( is_wp_error( $fields_update ) ) {
  188. return $fields_update;
  189. }
  190. $request->set_param( 'context', 'edit' );
  191. $response = $this->prepare_item_for_response( $attachment, $request );
  192. $response = rest_ensure_response( $response );
  193. return $response;
  194. }
  195. /**
  196. * Prepares a single attachment for create or update.
  197. *
  198. * @since 4.7.0
  199. * @access public
  200. *
  201. * @param WP_REST_Request $request Request object.
  202. * @return WP_Error|stdClass $prepared_attachment Post object.
  203. */
  204. protected function prepare_item_for_database( $request ) {
  205. $prepared_attachment = parent::prepare_item_for_database( $request );
  206. // Attachment caption (post_excerpt internally)
  207. if ( isset( $request['caption'] ) ) {
  208. if ( is_string( $request['caption'] ) ) {
  209. $prepared_attachment->post_excerpt = $request['caption'];
  210. } elseif ( isset( $request['caption']['raw'] ) ) {
  211. $prepared_attachment->post_excerpt = $request['caption']['raw'];
  212. }
  213. }
  214. // Attachment description (post_content internally)
  215. if ( isset( $request['description'] ) ) {
  216. if ( is_string( $request['description'] ) ) {
  217. $prepared_attachment->post_content = $request['description'];
  218. } elseif ( isset( $request['description']['raw'] ) ) {
  219. $prepared_attachment->post_content = $request['description']['raw'];
  220. }
  221. }
  222. if ( isset( $request['post'] ) ) {
  223. $prepared_attachment->post_parent = (int) $request['post'];
  224. }
  225. return $prepared_attachment;
  226. }
  227. /**
  228. * Prepares a single attachment output for response.
  229. *
  230. * @since 4.7.0
  231. * @access public
  232. *
  233. * @param WP_Post $post Attachment object.
  234. * @param WP_REST_Request $request Request object.
  235. * @return WP_REST_Response Response object.
  236. */
  237. public function prepare_item_for_response( $post, $request ) {
  238. $response = parent::prepare_item_for_response( $post, $request );
  239. $data = $response->get_data();
  240. $data['description'] = array(
  241. 'raw' => $post->post_content,
  242. /** This filter is documented in wp-includes/post-template.php */
  243. 'rendered' => apply_filters( 'the_content', $post->post_content ),
  244. );
  245. /** This filter is documented in wp-includes/post-template.php */
  246. $caption = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) );
  247. $data['caption'] = array(
  248. 'raw' => $post->post_excerpt,
  249. 'rendered' => $caption,
  250. );
  251. $data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true );
  252. $data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file';
  253. $data['mime_type'] = $post->post_mime_type;
  254. $data['media_details'] = wp_get_attachment_metadata( $post->ID );
  255. $data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
  256. $data['source_url'] = wp_get_attachment_url( $post->ID );
  257. // Ensure empty details is an empty object.
  258. if ( empty( $data['media_details'] ) ) {
  259. $data['media_details'] = new stdClass;
  260. } elseif ( ! empty( $data['media_details']['sizes'] ) ) {
  261. foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {
  262. if ( isset( $size_data['mime-type'] ) ) {
  263. $size_data['mime_type'] = $size_data['mime-type'];
  264. unset( $size_data['mime-type'] );
  265. }
  266. // Use the same method image_downsize() does.
  267. $image_src = wp_get_attachment_image_src( $post->ID, $size );
  268. if ( ! $image_src ) {
  269. continue;
  270. }
  271. $size_data['source_url'] = $image_src[0];
  272. }
  273. $full_src = wp_get_attachment_image_src( $post->ID, 'full' );
  274. if ( ! empty( $full_src ) ) {
  275. $data['media_details']['sizes']['full'] = array(
  276. 'file' => wp_basename( $full_src[0] ),
  277. 'width' => $full_src[1],
  278. 'height' => $full_src[2],
  279. 'mime_type' => $post->post_mime_type,
  280. 'source_url' => $full_src[0],
  281. );
  282. }
  283. } else {
  284. $data['media_details']['sizes'] = new stdClass;
  285. }
  286. $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
  287. $data = $this->filter_response_by_context( $data, $context );
  288. // Wrap the data in a response object.
  289. $response = rest_ensure_response( $data );
  290. $response->add_links( $this->prepare_links( $post ) );
  291. /**
  292. * Filters an attachment returned from the REST API.
  293. *
  294. * Allows modification of the attachment right before it is returned.
  295. *
  296. * @since 4.7.0
  297. *
  298. * @param WP_REST_Response $response The response object.
  299. * @param WP_Post $post The original attachment post.
  300. * @param WP_REST_Request $request Request used to generate the response.
  301. */
  302. return apply_filters( 'rest_prepare_attachment', $response, $post, $request );
  303. }
  304. /**
  305. * Retrieves the attachment's schema, conforming to JSON Schema.
  306. *
  307. * @since 4.7.0
  308. * @access public
  309. *
  310. * @return array Item schema as an array.
  311. */
  312. public function get_item_schema() {
  313. $schema = parent::get_item_schema();
  314. $schema['properties']['alt_text'] = array(
  315. 'description' => __( 'Alternative text to display when attachment is not displayed.' ),
  316. 'type' => 'string',
  317. 'context' => array( 'view', 'edit', 'embed' ),
  318. 'arg_options' => array(
  319. 'sanitize_callback' => 'sanitize_text_field',
  320. ),
  321. );
  322. $schema['properties']['caption'] = array(
  323. 'description' => __( 'The attachment caption.' ),
  324. 'type' => 'object',
  325. 'context' => array( 'view', 'edit', 'embed' ),
  326. 'arg_options' => array(
  327. 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
  328. ),
  329. 'properties' => array(
  330. 'raw' => array(
  331. 'description' => __( 'Caption for the attachment, as it exists in the database.' ),
  332. 'type' => 'string',
  333. 'context' => array( 'edit' ),
  334. ),
  335. 'rendered' => array(
  336. 'description' => __( 'HTML caption for the attachment, transformed for display.' ),
  337. 'type' => 'string',
  338. 'context' => array( 'view', 'edit', 'embed' ),
  339. 'readonly' => true,
  340. ),
  341. ),
  342. );
  343. $schema['properties']['description'] = array(
  344. 'description' => __( 'The attachment description.' ),
  345. 'type' => 'object',
  346. 'context' => array( 'view', 'edit' ),
  347. 'arg_options' => array(
  348. 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
  349. ),
  350. 'properties' => array(
  351. 'raw' => array(
  352. 'description' => __( 'Description for the object, as it exists in the database.' ),
  353. 'type' => 'string',
  354. 'context' => array( 'edit' ),
  355. ),
  356. 'rendered' => array(
  357. 'description' => __( 'HTML description for the object, transformed for display.' ),
  358. 'type' => 'string',
  359. 'context' => array( 'view', 'edit' ),
  360. 'readonly' => true,
  361. ),
  362. ),
  363. );
  364. $schema['properties']['media_type'] = array(
  365. 'description' => __( 'Attachment type.' ),
  366. 'type' => 'string',
  367. 'enum' => array( 'image', 'file' ),
  368. 'context' => array( 'view', 'edit', 'embed' ),
  369. 'readonly' => true,
  370. );
  371. $schema['properties']['mime_type'] = array(
  372. 'description' => __( 'The attachment MIME type.' ),
  373. 'type' => 'string',
  374. 'context' => array( 'view', 'edit', 'embed' ),
  375. 'readonly' => true,
  376. );
  377. $schema['properties']['media_details'] = array(
  378. 'description' => __( 'Details about the media file, specific to its type.' ),
  379. 'type' => 'object',
  380. 'context' => array( 'view', 'edit', 'embed' ),
  381. 'readonly' => true,
  382. );
  383. $schema['properties']['post'] = array(
  384. 'description' => __( 'The ID for the associated post of the attachment.' ),
  385. 'type' => 'integer',
  386. 'context' => array( 'view', 'edit' ),
  387. );
  388. $schema['properties']['source_url'] = array(
  389. 'description' => __( 'URL to the original attachment file.' ),
  390. 'type' => 'string',
  391. 'format' => 'uri',
  392. 'context' => array( 'view', 'edit', 'embed' ),
  393. 'readonly' => true,
  394. );
  395. unset( $schema['properties']['password'] );
  396. return $schema;
  397. }
  398. /**
  399. * Handles an upload via raw POST data.
  400. *
  401. * @since 4.7.0
  402. * @access protected
  403. *
  404. * @param array $data Supplied file data.
  405. * @param array $headers HTTP headers from the request.
  406. * @return array|WP_Error Data from wp_handle_sideload().
  407. */
  408. protected function upload_from_data( $data, $headers ) {
  409. if ( empty( $data ) ) {
  410. return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) );
  411. }
  412. if ( empty( $headers['content_type'] ) ) {
  413. return new WP_Error( 'rest_upload_no_content_type', __( 'No Content-Type supplied.' ), array( 'status' => 400 ) );
  414. }
  415. if ( empty( $headers['content_disposition'] ) ) {
  416. return new WP_Error( 'rest_upload_no_content_disposition', __( 'No Content-Disposition supplied.' ), array( 'status' => 400 ) );
  417. }
  418. $filename = self::get_filename_from_disposition( $headers['content_disposition'] );
  419. if ( empty( $filename ) ) {
  420. 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 ) );
  421. }
  422. if ( ! empty( $headers['content_md5'] ) ) {
  423. $content_md5 = array_shift( $headers['content_md5'] );
  424. $expected = trim( $content_md5 );
  425. $actual = md5( $data );
  426. if ( $expected !== $actual ) {
  427. return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) );
  428. }
  429. }
  430. // Get the content-type.
  431. $type = array_shift( $headers['content_type'] );
  432. /** Include admin functions to get access to wp_tempnam() and wp_handle_sideload() */
  433. require_once ABSPATH . 'wp-admin/includes/admin.php';
  434. // Save the file.
  435. $tmpfname = wp_tempnam( $filename );
  436. $fp = fopen( $tmpfname, 'w+' );
  437. if ( ! $fp ) {
  438. return new WP_Error( 'rest_upload_file_error', __( 'Could not open file handle.' ), array( 'status' => 500 ) );
  439. }
  440. fwrite( $fp, $data );
  441. fclose( $fp );
  442. // Now, sideload it in.
  443. $file_data = array(
  444. 'error' => null,
  445. 'tmp_name' => $tmpfname,
  446. 'name' => $filename,
  447. 'type' => $type,
  448. );
  449. $overrides = array(
  450. 'test_form' => false,
  451. );
  452. $sideloaded = wp_handle_sideload( $file_data, $overrides );
  453. if ( isset( $sideloaded['error'] ) ) {
  454. @unlink( $tmpfname );
  455. return new WP_Error( 'rest_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) );
  456. }
  457. return $sideloaded;
  458. }
  459. /**
  460. * Parses filename from a Content-Disposition header value.
  461. *
  462. * As per RFC6266:
  463. *
  464. * content-disposition = "Content-Disposition" ":"
  465. * disposition-type *( ";" disposition-parm )
  466. *
  467. * disposition-type = "inline" | "attachment" | disp-ext-type
  468. * ; case-insensitive
  469. * disp-ext-type = token
  470. *
  471. * disposition-parm = filename-parm | disp-ext-parm
  472. *
  473. * filename-parm = "filename" "=" value
  474. * | "filename*" "=" ext-value
  475. *
  476. * disp-ext-parm = token "=" value
  477. * | ext-token "=" ext-value
  478. * ext-token = <the characters in token, followed by "*">
  479. *
  480. * @since 4.7.0
  481. * @access public
  482. *
  483. * @link http://tools.ietf.org/html/rfc2388
  484. * @link http://tools.ietf.org/html/rfc6266
  485. *
  486. * @param string[] $disposition_header List of Content-Disposition header values.
  487. * @return string|null Filename if available, or null if not found.
  488. */
  489. public static function get_filename_from_disposition( $disposition_header ) {
  490. // Get the filename.
  491. $filename = null;
  492. foreach ( $disposition_header as $value ) {
  493. $value = trim( $value );
  494. if ( strpos( $value, ';' ) === false ) {
  495. continue;
  496. }
  497. list( $type, $attr_parts ) = explode( ';', $value, 2 );
  498. $attr_parts = explode( ';', $attr_parts );
  499. $attributes = array();
  500. foreach ( $attr_parts as $part ) {
  501. if ( strpos( $part, '=' ) === false ) {
  502. continue;
  503. }
  504. list( $key, $value ) = explode( '=', $part, 2 );
  505. $attributes[ trim( $key ) ] = trim( $value );
  506. }
  507. if ( empty( $attributes['filename'] ) ) {
  508. continue;
  509. }
  510. $filename = trim( $attributes['filename'] );
  511. // Unquote quoted filename, but after trimming.
  512. if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) {
  513. $filename = substr( $filename, 1, -1 );
  514. }
  515. }
  516. return $filename;
  517. }
  518. /**
  519. * Retrieves the query params for collections of attachments.
  520. *
  521. * @since 4.7.0
  522. * @access public
  523. *
  524. * @return array Query parameters for the attachment collection as an array.
  525. */
  526. public function get_collection_params() {
  527. $params = parent::get_collection_params();
  528. $params['status']['default'] = 'inherit';
  529. $params['status']['items']['enum'] = array( 'inherit', 'private', 'trash' );
  530. $media_types = $this->get_media_types();
  531. $params['media_type'] = array(
  532. 'default' => null,
  533. 'description' => __( 'Limit result set to attachments of a particular media type.' ),
  534. 'type' => 'string',
  535. 'enum' => array_keys( $media_types ),
  536. );
  537. $params['mime_type'] = array(
  538. 'default' => null,
  539. 'description' => __( 'Limit result set to attachments of a particular MIME type.' ),
  540. 'type' => 'string',
  541. );
  542. return $params;
  543. }
  544. /**
  545. * Validates whether the user can query private statuses.
  546. *
  547. * @since 4.7.0
  548. * @access public
  549. *
  550. * @param mixed $value Status value.
  551. * @param WP_REST_Request $request Request object.
  552. * @param string $parameter Additional parameter to pass for validation.
  553. * @return WP_Error|bool True if the user may query, WP_Error if not.
  554. */
  555. public function validate_user_can_query_private_statuses( $value, $request, $parameter ) {
  556. if ( 'inherit' === $value ) {
  557. return true;
  558. }
  559. return parent::validate_user_can_query_private_statuses( $value, $request, $parameter );
  560. }
  561. /**
  562. * Handles an upload via multipart/form-data ($_FILES).
  563. *
  564. * @since 4.7.0
  565. * @access protected
  566. *
  567. * @param array $files Data from the `$_FILES` superglobal.
  568. * @param array $headers HTTP headers from the request.
  569. * @return array|WP_Error Data from wp_handle_upload().
  570. */
  571. protected function upload_from_file( $files, $headers ) {
  572. if ( empty( $files ) ) {
  573. return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) );
  574. }
  575. // Verify hash, if given.
  576. if ( ! empty( $headers['content_md5'] ) ) {
  577. $content_md5 = array_shift( $headers['content_md5'] );
  578. $expected = trim( $content_md5 );
  579. $actual = md5_file( $files['file']['tmp_name'] );
  580. if ( $expected !== $actual ) {
  581. return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) );
  582. }
  583. }
  584. // Pass off to WP to handle the actual upload.
  585. $overrides = array(
  586. 'test_form' => false,
  587. );
  588. // Bypasses is_uploaded_file() when running unit tests.
  589. if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) {
  590. $overrides['action'] = 'wp_handle_mock_upload';
  591. }
  592. /** Include admin functions to get access to wp_handle_upload() */
  593. require_once ABSPATH . 'wp-admin/includes/admin.php';
  594. $file = wp_handle_upload( $files['file'], $overrides );
  595. if ( isset( $file['error'] ) ) {
  596. return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) );
  597. }
  598. return $file;
  599. }
  600. /**
  601. * Retrieves the supported media types.
  602. *
  603. * Media types are considered the MIME type category.
  604. *
  605. * @since 4.7.0
  606. * @access protected
  607. *
  608. * @return array Array of supported media types.
  609. */
  610. protected function get_media_types() {
  611. $media_types = array();
  612. foreach ( get_allowed_mime_types() as $mime_type ) {
  613. $parts = explode( '/', $mime_type );
  614. if ( ! isset( $media_types[ $parts[0] ] ) ) {
  615. $media_types[ $parts[0] ] = array();
  616. }
  617. $media_types[ $parts[0] ][] = $mime_type;
  618. }
  619. return $media_types;
  620. }
  621. }