WP_REST_Attachments_Controller

Core controller used to access attachments via the REST API.

Defined (1)

The class is defined in the following location(s).

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