[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 /** 11 * Core controller used to access attachments via the REST API. 12 * 13 * @since 4.7.0 14 * 15 * @see WP_REST_Posts_Controller 16 */ 17 class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller { 18 19 /** 20 * Whether the controller supports batching. 21 * 22 * @since 5.9.0 23 * @var false 24 */ 25 protected $allow_batch = false; 26 27 /** 28 * Registers the routes for attachments. 29 * 30 * @since 5.3.0 31 * 32 * @see register_rest_route() 33 */ 34 public function register_routes() { 35 parent::register_routes(); 36 register_rest_route( 37 $this->namespace, 38 '/' . $this->rest_base . '/(?P<id>[\d]+)/post-process', 39 array( 40 'methods' => WP_REST_Server::CREATABLE, 41 'callback' => array( $this, 'post_process_item' ), 42 'permission_callback' => array( $this, 'post_process_item_permissions_check' ), 43 'args' => array( 44 'id' => array( 45 'description' => __( 'Unique identifier for the attachment.' ), 46 'type' => 'integer', 47 ), 48 'action' => array( 49 'type' => 'string', 50 'enum' => array( 'create-image-subsizes' ), 51 'required' => true, 52 ), 53 ), 54 ) 55 ); 56 register_rest_route( 57 $this->namespace, 58 '/' . $this->rest_base . '/(?P<id>[\d]+)/edit', 59 array( 60 'methods' => WP_REST_Server::CREATABLE, 61 'callback' => array( $this, 'edit_media_item' ), 62 'permission_callback' => array( $this, 'edit_media_item_permissions_check' ), 63 'args' => $this->get_edit_media_item_args(), 64 ) 65 ); 66 } 67 68 /** 69 * Determines the allowed query_vars for a get_items() response and 70 * prepares for WP_Query. 71 * 72 * @since 4.7.0 73 * 74 * @param array $prepared_args Optional. Array of prepared arguments. Default empty array. 75 * @param WP_REST_Request $request Optional. Request to prepare items for. 76 * @return array Array of query arguments. 77 */ 78 protected function prepare_items_query( $prepared_args = array(), $request = null ) { 79 $query_args = parent::prepare_items_query( $prepared_args, $request ); 80 81 if ( empty( $query_args['post_status'] ) ) { 82 $query_args['post_status'] = 'inherit'; 83 } 84 85 $media_types = $this->get_media_types(); 86 87 if ( ! empty( $request['media_type'] ) && isset( $media_types[ $request['media_type'] ] ) ) { 88 $query_args['post_mime_type'] = $media_types[ $request['media_type'] ]; 89 } 90 91 if ( ! empty( $request['mime_type'] ) ) { 92 $parts = explode( '/', $request['mime_type'] ); 93 if ( isset( $media_types[ $parts[0] ] ) && in_array( $request['mime_type'], $media_types[ $parts[0] ], true ) ) { 94 $query_args['post_mime_type'] = $request['mime_type']; 95 } 96 } 97 98 // Filter query clauses to include filenames. 99 if ( isset( $query_args['s'] ) ) { 100 add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' ); 101 } 102 103 return $query_args; 104 } 105 106 /** 107 * Checks if a given request has access to create an attachment. 108 * 109 * @since 4.7.0 110 * 111 * @param WP_REST_Request $request Full details about the request. 112 * @return true|WP_Error Boolean true if the attachment may be created, or a WP_Error if not. 113 */ 114 public function create_item_permissions_check( $request ) { 115 $ret = parent::create_item_permissions_check( $request ); 116 117 if ( ! $ret || is_wp_error( $ret ) ) { 118 return $ret; 119 } 120 121 if ( ! current_user_can( 'upload_files' ) ) { 122 return new WP_Error( 123 'rest_cannot_create', 124 __( 'Sorry, you are not allowed to upload media on this site.' ), 125 array( 'status' => 400 ) 126 ); 127 } 128 129 // Attaching media to a post requires ability to edit said post. 130 if ( ! empty( $request['post'] ) && ! current_user_can( 'edit_post', (int) $request['post'] ) ) { 131 return new WP_Error( 132 'rest_cannot_edit', 133 __( 'Sorry, you are not allowed to upload media to this post.' ), 134 array( 'status' => rest_authorization_required_code() ) 135 ); 136 } 137 $files = $request->get_file_params(); 138 139 /** 140 * Filter whether the server should prevent uploads for image types it doesn't support. Default true. 141 * 142 * Developers can use this filter to enable uploads of certain image types. By default image types that are not 143 * supported by the server are prevented from being uploaded. 144 * 145 * @since 6.8.0 146 * 147 * @param bool $check_mime Whether to prevent uploads of unsupported image types. 148 * @param string|null $mime_type The mime type of the file being uploaded (if available). 149 */ 150 $prevent_unsupported_uploads = apply_filters( 'wp_prevent_unsupported_mime_type_uploads', true, isset( $files['file']['type'] ) ? $files['file']['type'] : null ); 151 152 // If the upload is an image, check if the server can handle the mime type. 153 if ( 154 $prevent_unsupported_uploads && 155 isset( $files['file']['type'] ) && 156 str_starts_with( $files['file']['type'], 'image/' ) 157 ) { 158 // Check if the image editor supports the type. 159 if ( ! wp_image_editor_supports( array( 'mime_type' => $files['file']['type'] ) ) ) { 160 return new WP_Error( 161 'rest_upload_image_type_not_supported', 162 __( 'The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading.' ), 163 array( 'status' => 400 ) 164 ); 165 } 166 } 167 168 return true; 169 } 170 171 /** 172 * Creates a single attachment. 173 * 174 * @since 4.7.0 175 * 176 * @param WP_REST_Request $request Full details about the request. 177 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. 178 */ 179 public function create_item( $request ) { 180 if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) { 181 return new WP_Error( 182 'rest_invalid_param', 183 __( 'Invalid parent type.' ), 184 array( 'status' => 400 ) 185 ); 186 } 187 188 $insert = $this->insert_attachment( $request ); 189 190 if ( is_wp_error( $insert ) ) { 191 return $insert; 192 } 193 194 $schema = $this->get_item_schema(); 195 196 // Extract by name. 197 $attachment_id = $insert['attachment_id']; 198 $file = $insert['file']; 199 200 if ( isset( $request['alt_text'] ) ) { 201 update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) ); 202 } 203 204 if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { 205 $thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment_id ); 206 207 if ( is_wp_error( $thumbnail_update ) ) { 208 return $thumbnail_update; 209 } 210 } 211 212 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 213 $meta_update = $this->meta->update_value( $request['meta'], $attachment_id ); 214 215 if ( is_wp_error( $meta_update ) ) { 216 return $meta_update; 217 } 218 } 219 220 $attachment = get_post( $attachment_id ); 221 $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); 222 223 if ( is_wp_error( $fields_update ) ) { 224 return $fields_update; 225 } 226 227 $terms_update = $this->handle_terms( $attachment_id, $request ); 228 229 if ( is_wp_error( $terms_update ) ) { 230 return $terms_update; 231 } 232 233 $request->set_param( 'context', 'edit' ); 234 235 /** 236 * Fires after a single attachment is completely created or updated via the REST API. 237 * 238 * @since 5.0.0 239 * 240 * @param WP_Post $attachment Inserted or updated attachment object. 241 * @param WP_REST_Request $request Request object. 242 * @param bool $creating True when creating an attachment, false when updating. 243 */ 244 do_action( 'rest_after_insert_attachment', $attachment, $request, true ); 245 246 wp_after_insert_post( $attachment, false, null ); 247 248 if ( wp_is_serving_rest_request() ) { 249 /* 250 * Set a custom header with the attachment_id. 251 * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. 252 */ 253 header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id ); 254 } 255 256 // Include media and image functions to get access to wp_generate_attachment_metadata(). 257 require_once ABSPATH . 'wp-admin/includes/media.php'; 258 require_once ABSPATH . 'wp-admin/includes/image.php'; 259 260 /* 261 * Post-process the upload (create image sub-sizes, make PDF thumbnails, etc.) and insert attachment meta. 262 * At this point the server may run out of resources and post-processing of uploaded images may fail. 263 */ 264 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) ); 265 266 $response = $this->prepare_item_for_response( $attachment, $request ); 267 $response = rest_ensure_response( $response ); 268 $response->set_status( 201 ); 269 $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $attachment_id ) ) ); 270 271 return $response; 272 } 273 274 /** 275 * Inserts the attachment post in the database. Does not update the attachment meta. 276 * 277 * @since 5.3.0 278 * 279 * @param WP_REST_Request $request 280 * @return array|WP_Error 281 */ 282 protected function insert_attachment( $request ) { 283 // Get the file via $_FILES or raw data. 284 $files = $request->get_file_params(); 285 $headers = $request->get_headers(); 286 287 $time = null; 288 289 // Matches logic in media_handle_upload(). 290 if ( ! empty( $request['post'] ) ) { 291 $post = get_post( $request['post'] ); 292 // The post date doesn't usually matter for pages, so don't backdate this upload. 293 if ( $post && 'page' !== $post->post_type && substr( $post->post_date, 0, 4 ) > 0 ) { 294 $time = $post->post_date; 295 } 296 } 297 298 if ( ! empty( $files ) ) { 299 $file = $this->upload_from_file( $files, $headers, $time ); 300 } else { 301 $file = $this->upload_from_data( $request->get_body(), $headers, $time ); 302 } 303 304 if ( is_wp_error( $file ) ) { 305 return $file; 306 } 307 308 $name = wp_basename( $file['file'] ); 309 $name_parts = pathinfo( $name ); 310 $name = trim( substr( $name, 0, -( 1 + strlen( $name_parts['extension'] ) ) ) ); 311 312 $url = $file['url']; 313 $type = $file['type']; 314 $file = $file['file']; 315 316 // Include image functions to get access to wp_read_image_metadata(). 317 require_once ABSPATH . 'wp-admin/includes/image.php'; 318 319 // Use image exif/iptc data for title and caption defaults if possible. 320 $image_meta = wp_read_image_metadata( $file ); 321 322 if ( ! empty( $image_meta ) ) { 323 if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) { 324 $request['title'] = $image_meta['title']; 325 } 326 327 if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) { 328 $request['caption'] = $image_meta['caption']; 329 } 330 } 331 332 $attachment = $this->prepare_item_for_database( $request ); 333 334 $attachment->post_mime_type = $type; 335 $attachment->guid = $url; 336 337 // If the title was not set, use the original filename. 338 if ( empty( $attachment->post_title ) && ! empty( $files['file']['name'] ) ) { 339 // Remove the file extension (after the last `.`) 340 $tmp_title = substr( $files['file']['name'], 0, strrpos( $files['file']['name'], '.' ) ); 341 342 if ( ! empty( $tmp_title ) ) { 343 $attachment->post_title = $tmp_title; 344 } 345 } 346 347 // Fall back to the original approach. 348 if ( empty( $attachment->post_title ) ) { 349 $attachment->post_title = preg_replace( '/\.[^.]+$/', '', wp_basename( $file ) ); 350 } 351 352 // $post_parent is inherited from $attachment['post_parent']. 353 $id = wp_insert_attachment( wp_slash( (array) $attachment ), $file, 0, true, false ); 354 355 if ( is_wp_error( $id ) ) { 356 if ( 'db_update_error' === $id->get_error_code() ) { 357 $id->add_data( array( 'status' => 500 ) ); 358 } else { 359 $id->add_data( array( 'status' => 400 ) ); 360 } 361 362 return $id; 363 } 364 365 $attachment = get_post( $id ); 366 367 /** 368 * Fires after a single attachment is created or updated via the REST API. 369 * 370 * @since 4.7.0 371 * 372 * @param WP_Post $attachment Inserted or updated attachment object. 373 * @param WP_REST_Request $request The request sent to the API. 374 * @param bool $creating True when creating an attachment, false when updating. 375 */ 376 do_action( 'rest_insert_attachment', $attachment, $request, true ); 377 378 return array( 379 'attachment_id' => $id, 380 'file' => $file, 381 ); 382 } 383 384 /** 385 * Determines the featured media based on a request param. 386 * 387 * @since 6.5.0 388 * 389 * @param int $featured_media Featured Media ID. 390 * @param int $post_id Post ID. 391 * @return bool|WP_Error Whether the post thumbnail was successfully deleted, otherwise WP_Error. 392 */ 393 protected function handle_featured_media( $featured_media, $post_id ) { 394 $post_type = get_post_type( $post_id ); 395 $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ); 396 397 // Similar check as in wp_insert_post(). 398 if ( ! $thumbnail_support && get_post_mime_type( $post_id ) ) { 399 if ( wp_attachment_is( 'audio', $post_id ) ) { 400 $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' ); 401 } elseif ( wp_attachment_is( 'video', $post_id ) ) { 402 $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' ); 403 } 404 } 405 406 if ( $thumbnail_support ) { 407 return parent::handle_featured_media( $featured_media, $post_id ); 408 } 409 410 return new WP_Error( 411 'rest_no_featured_media', 412 sprintf( 413 /* translators: %s: attachment mime type */ 414 __( 'This site does not support post thumbnails on attachments with MIME type %s.' ), 415 get_post_mime_type( $post_id ) 416 ), 417 array( 'status' => 400 ) 418 ); 419 } 420 421 /** 422 * Updates a single attachment. 423 * 424 * @since 4.7.0 425 * 426 * @param WP_REST_Request $request Full details about the request. 427 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. 428 */ 429 public function update_item( $request ) { 430 if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) { 431 return new WP_Error( 432 'rest_invalid_param', 433 __( 'Invalid parent type.' ), 434 array( 'status' => 400 ) 435 ); 436 } 437 438 $attachment_before = get_post( $request['id'] ); 439 $response = parent::update_item( $request ); 440 441 if ( is_wp_error( $response ) ) { 442 return $response; 443 } 444 445 $response = rest_ensure_response( $response ); 446 $data = $response->get_data(); 447 448 if ( isset( $request['alt_text'] ) ) { 449 update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] ); 450 } 451 452 $attachment = get_post( $request['id'] ); 453 454 if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { 455 $thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment->ID ); 456 457 if ( is_wp_error( $thumbnail_update ) ) { 458 return $thumbnail_update; 459 } 460 } 461 462 $fields_update = $this->update_additional_fields_for_object( $attachment, $request ); 463 464 if ( is_wp_error( $fields_update ) ) { 465 return $fields_update; 466 } 467 468 $request->set_param( 'context', 'edit' ); 469 470 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */ 471 do_action( 'rest_after_insert_attachment', $attachment, $request, false ); 472 473 wp_after_insert_post( $attachment, true, $attachment_before ); 474 475 $response = $this->prepare_item_for_response( $attachment, $request ); 476 $response = rest_ensure_response( $response ); 477 478 return $response; 479 } 480 481 /** 482 * Performs post-processing on an attachment. 483 * 484 * @since 5.3.0 485 * 486 * @param WP_REST_Request $request Full details about the request. 487 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. 488 */ 489 public function post_process_item( $request ) { 490 switch ( $request['action'] ) { 491 case 'create-image-subsizes': 492 require_once ABSPATH . 'wp-admin/includes/image.php'; 493 wp_update_image_subsizes( $request['id'] ); 494 break; 495 } 496 497 $request['context'] = 'edit'; 498 499 return $this->prepare_item_for_response( get_post( $request['id'] ), $request ); 500 } 501 502 /** 503 * Checks if a given request can perform post-processing on an attachment. 504 * 505 * @since 5.3.0 506 * 507 * @param WP_REST_Request $request Full details about the request. 508 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. 509 */ 510 public function post_process_item_permissions_check( $request ) { 511 return $this->update_item_permissions_check( $request ); 512 } 513 514 /** 515 * Checks if a given request has access to editing media. 516 * 517 * @since 5.5.0 518 * 519 * @param WP_REST_Request $request Full details about the request. 520 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 521 */ 522 public function edit_media_item_permissions_check( $request ) { 523 if ( ! current_user_can( 'upload_files' ) ) { 524 return new WP_Error( 525 'rest_cannot_edit_image', 526 __( 'Sorry, you are not allowed to upload media on this site.' ), 527 array( 'status' => rest_authorization_required_code() ) 528 ); 529 } 530 531 return $this->update_item_permissions_check( $request ); 532 } 533 534 /** 535 * Applies edits to a media item and creates a new attachment record. 536 * 537 * @since 5.5.0 538 * 539 * @param WP_REST_Request $request Full details about the request. 540 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. 541 */ 542 public function edit_media_item( $request ) { 543 require_once ABSPATH . 'wp-admin/includes/image.php'; 544 545 $attachment_id = $request['id']; 546 547 // This also confirms the attachment is an image. 548 $image_file = wp_get_original_image_path( $attachment_id ); 549 $image_meta = wp_get_attachment_metadata( $attachment_id ); 550 551 if ( 552 ! $image_meta || 553 ! $image_file || 554 ! wp_image_file_matches_image_meta( $request['src'], $image_meta, $attachment_id ) 555 ) { 556 return new WP_Error( 557 'rest_unknown_attachment', 558 __( 'Unable to get meta information for file.' ), 559 array( 'status' => 404 ) 560 ); 561 } 562 563 $supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' ); 564 $mime_type = get_post_mime_type( $attachment_id ); 565 if ( ! in_array( $mime_type, $supported_types, true ) ) { 566 return new WP_Error( 567 'rest_cannot_edit_file_type', 568 __( 'This type of file cannot be edited.' ), 569 array( 'status' => 400 ) 570 ); 571 } 572 573 // The `modifiers` param takes precedence over the older format. 574 if ( isset( $request['modifiers'] ) ) { 575 $modifiers = $request['modifiers']; 576 } else { 577 $modifiers = array(); 578 579 if ( ! empty( $request['rotation'] ) ) { 580 $modifiers[] = array( 581 'type' => 'rotate', 582 'args' => array( 583 'angle' => $request['rotation'], 584 ), 585 ); 586 } 587 588 if ( isset( $request['x'], $request['y'], $request['width'], $request['height'] ) ) { 589 $modifiers[] = array( 590 'type' => 'crop', 591 'args' => array( 592 'left' => $request['x'], 593 'top' => $request['y'], 594 'width' => $request['width'], 595 'height' => $request['height'], 596 ), 597 ); 598 } 599 600 if ( 0 === count( $modifiers ) ) { 601 return new WP_Error( 602 'rest_image_not_edited', 603 __( 'The image was not edited. Edit the image before applying the changes.' ), 604 array( 'status' => 400 ) 605 ); 606 } 607 } 608 609 /* 610 * If the file doesn't exist, attempt a URL fopen on the src link. 611 * This can occur with certain file replication plugins. 612 * Keep the original file path to get a modified name later. 613 */ 614 $image_file_to_edit = $image_file; 615 if ( ! file_exists( $image_file_to_edit ) ) { 616 $image_file_to_edit = _load_image_to_edit_path( $attachment_id ); 617 } 618 619 $image_editor = wp_get_image_editor( $image_file_to_edit ); 620 621 if ( is_wp_error( $image_editor ) ) { 622 return new WP_Error( 623 'rest_unknown_image_file_type', 624 __( 'Unable to edit this image.' ), 625 array( 'status' => 500 ) 626 ); 627 } 628 629 foreach ( $modifiers as $modifier ) { 630 $args = $modifier['args']; 631 switch ( $modifier['type'] ) { 632 case 'rotate': 633 // Rotation direction: clockwise vs. counterclockwise. 634 $rotate = 0 - $args['angle']; 635 636 if ( 0 !== $rotate ) { 637 $result = $image_editor->rotate( $rotate ); 638 639 if ( is_wp_error( $result ) ) { 640 return new WP_Error( 641 'rest_image_rotation_failed', 642 __( 'Unable to rotate this image.' ), 643 array( 'status' => 500 ) 644 ); 645 } 646 } 647 648 break; 649 650 case 'crop': 651 $size = $image_editor->get_size(); 652 653 $crop_x = (int) round( ( $size['width'] * $args['left'] ) / 100.0 ); 654 $crop_y = (int) round( ( $size['height'] * $args['top'] ) / 100.0 ); 655 $width = (int) round( ( $size['width'] * $args['width'] ) / 100.0 ); 656 $height = (int) round( ( $size['height'] * $args['height'] ) / 100.0 ); 657 658 if ( $size['width'] !== $width || $size['height'] !== $height ) { 659 $result = $image_editor->crop( $crop_x, $crop_y, $width, $height ); 660 661 if ( is_wp_error( $result ) ) { 662 return new WP_Error( 663 'rest_image_crop_failed', 664 __( 'Unable to crop this image.' ), 665 array( 'status' => 500 ) 666 ); 667 } 668 } 669 670 break; 671 672 } 673 } 674 675 // Calculate the file name. 676 $image_ext = pathinfo( $image_file, PATHINFO_EXTENSION ); 677 $image_name = wp_basename( $image_file, ".{$image_ext}" ); 678 679 /* 680 * Do not append multiple `-edited` to the file name. 681 * The user may be editing a previously edited image. 682 */ 683 if ( preg_match( '/-edited(-\d+)?$/', $image_name ) ) { 684 // Remove any `-1`, `-2`, etc. `wp_unique_filename()` will add the proper number. 685 $image_name = preg_replace( '/-edited(-\d+)?$/', '-edited', $image_name ); 686 } else { 687 // Append `-edited` before the extension. 688 $image_name .= '-edited'; 689 } 690 691 $filename = "{$image_name}.{$image_ext}"; 692 693 // Create the uploads subdirectory if needed. 694 $uploads = wp_upload_dir(); 695 696 // Make the file name unique in the (new) upload directory. 697 $filename = wp_unique_filename( $uploads['path'], $filename ); 698 699 // Save to disk. 700 $saved = $image_editor->save( $uploads['path'] . "/$filename" ); 701 702 if ( is_wp_error( $saved ) ) { 703 return $saved; 704 } 705 706 // Create new attachment post. 707 $new_attachment_post = array( 708 'post_mime_type' => $saved['mime-type'], 709 'guid' => $uploads['url'] . "/$filename", 710 'post_title' => $image_name, 711 'post_content' => '', 712 ); 713 714 // Copy post_content, post_excerpt, and post_title from the edited image's attachment post. 715 $attachment_post = get_post( $attachment_id ); 716 717 if ( $attachment_post ) { 718 $new_attachment_post['post_content'] = $attachment_post->post_content; 719 $new_attachment_post['post_excerpt'] = $attachment_post->post_excerpt; 720 $new_attachment_post['post_title'] = $attachment_post->post_title; 721 } 722 723 $new_attachment_id = wp_insert_attachment( wp_slash( $new_attachment_post ), $saved['path'], 0, true ); 724 725 if ( is_wp_error( $new_attachment_id ) ) { 726 if ( 'db_update_error' === $new_attachment_id->get_error_code() ) { 727 $new_attachment_id->add_data( array( 'status' => 500 ) ); 728 } else { 729 $new_attachment_id->add_data( array( 'status' => 400 ) ); 730 } 731 732 return $new_attachment_id; 733 } 734 735 // Copy the image alt text from the edited image. 736 $image_alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); 737 738 if ( ! empty( $image_alt ) ) { 739 // update_post_meta() expects slashed. 740 update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) ); 741 } 742 743 if ( wp_is_serving_rest_request() ) { 744 /* 745 * Set a custom header with the attachment_id. 746 * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error. 747 */ 748 header( 'X-WP-Upload-Attachment-ID: ' . $new_attachment_id ); 749 } 750 751 // Generate image sub-sizes and meta. 752 $new_image_meta = wp_generate_attachment_metadata( $new_attachment_id, $saved['path'] ); 753 754 // Copy the EXIF metadata from the original attachment if not generated for the edited image. 755 if ( isset( $image_meta['image_meta'] ) && isset( $new_image_meta['image_meta'] ) && is_array( $new_image_meta['image_meta'] ) ) { 756 // Merge but skip empty values. 757 foreach ( (array) $image_meta['image_meta'] as $key => $value ) { 758 if ( empty( $new_image_meta['image_meta'][ $key ] ) && ! empty( $value ) ) { 759 $new_image_meta['image_meta'][ $key ] = $value; 760 } 761 } 762 } 763 764 // Reset orientation. At this point the image is edited and orientation is correct. 765 if ( ! empty( $new_image_meta['image_meta']['orientation'] ) ) { 766 $new_image_meta['image_meta']['orientation'] = 1; 767 } 768 769 // The attachment_id may change if the site is exported and imported. 770 $new_image_meta['parent_image'] = array( 771 'attachment_id' => $attachment_id, 772 // Path to the originally uploaded image file relative to the uploads directory. 773 'file' => _wp_relative_upload_path( $image_file ), 774 ); 775 776 /** 777 * Filters the meta data for the new image created by editing an existing image. 778 * 779 * @since 5.5.0 780 * 781 * @param array $new_image_meta Meta data for the new image. 782 * @param int $new_attachment_id Attachment post ID for the new image. 783 * @param int $attachment_id Attachment post ID for the edited (parent) image. 784 */ 785 $new_image_meta = apply_filters( 'wp_edited_image_metadata', $new_image_meta, $new_attachment_id, $attachment_id ); 786 787 wp_update_attachment_metadata( $new_attachment_id, $new_image_meta ); 788 789 $response = $this->prepare_item_for_response( get_post( $new_attachment_id ), $request ); 790 $response->set_status( 201 ); 791 $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $new_attachment_id ) ) ); 792 793 return $response; 794 } 795 796 /** 797 * Prepares a single attachment for create or update. 798 * 799 * @since 4.7.0 800 * 801 * @param WP_REST_Request $request Request object. 802 * @return stdClass|WP_Error Post object. 803 */ 804 protected function prepare_item_for_database( $request ) { 805 $prepared_attachment = parent::prepare_item_for_database( $request ); 806 807 // Attachment caption (post_excerpt internally). 808 if ( isset( $request['caption'] ) ) { 809 if ( is_string( $request['caption'] ) ) { 810 $prepared_attachment->post_excerpt = $request['caption']; 811 } elseif ( isset( $request['caption']['raw'] ) ) { 812 $prepared_attachment->post_excerpt = $request['caption']['raw']; 813 } 814 } 815 816 // Attachment description (post_content internally). 817 if ( isset( $request['description'] ) ) { 818 if ( is_string( $request['description'] ) ) { 819 $prepared_attachment->post_content = $request['description']; 820 } elseif ( isset( $request['description']['raw'] ) ) { 821 $prepared_attachment->post_content = $request['description']['raw']; 822 } 823 } 824 825 if ( isset( $request['post'] ) ) { 826 $prepared_attachment->post_parent = (int) $request['post']; 827 } 828 829 return $prepared_attachment; 830 } 831 832 /** 833 * Prepares a single attachment output for response. 834 * 835 * @since 4.7.0 836 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support. 837 * 838 * @param WP_Post $item Attachment object. 839 * @param WP_REST_Request $request Request object. 840 * @return WP_REST_Response Response object. 841 */ 842 public function prepare_item_for_response( $item, $request ) { 843 // Restores the more descriptive, specific name for use within this method. 844 $post = $item; 845 846 $response = parent::prepare_item_for_response( $post, $request ); 847 $fields = $this->get_fields_for_response( $request ); 848 $data = $response->get_data(); 849 850 if ( in_array( 'description', $fields, true ) ) { 851 $data['description'] = array( 852 'raw' => $post->post_content, 853 /** This filter is documented in wp-includes/post-template.php */ 854 'rendered' => apply_filters( 'the_content', $post->post_content ), 855 ); 856 } 857 858 if ( in_array( 'caption', $fields, true ) ) { 859 /** This filter is documented in wp-includes/post-template.php */ 860 $caption = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ); 861 862 /** This filter is documented in wp-includes/post-template.php */ 863 $caption = apply_filters( 'the_excerpt', $caption ); 864 865 $data['caption'] = array( 866 'raw' => $post->post_excerpt, 867 'rendered' => $caption, 868 ); 869 } 870 871 if ( in_array( 'alt_text', $fields, true ) ) { 872 $data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ); 873 } 874 875 if ( in_array( 'media_type', $fields, true ) ) { 876 $data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file'; 877 } 878 879 if ( in_array( 'mime_type', $fields, true ) ) { 880 $data['mime_type'] = $post->post_mime_type; 881 } 882 883 if ( in_array( 'media_details', $fields, true ) ) { 884 $data['media_details'] = wp_get_attachment_metadata( $post->ID ); 885 886 // Ensure empty details is an empty object. 887 if ( empty( $data['media_details'] ) ) { 888 $data['media_details'] = new stdClass(); 889 } elseif ( ! empty( $data['media_details']['sizes'] ) ) { 890 891 foreach ( $data['media_details']['sizes'] as $size => &$size_data ) { 892 893 if ( isset( $size_data['mime-type'] ) ) { 894 $size_data['mime_type'] = $size_data['mime-type']; 895 unset( $size_data['mime-type'] ); 896 } 897 898 // Use the same method image_downsize() does. 899 $image_src = wp_get_attachment_image_src( $post->ID, $size ); 900 if ( ! $image_src ) { 901 continue; 902 } 903 904 $size_data['source_url'] = $image_src[0]; 905 } 906 907 $full_src = wp_get_attachment_image_src( $post->ID, 'full' ); 908 909 if ( ! empty( $full_src ) ) { 910 $data['media_details']['sizes']['full'] = array( 911 'file' => wp_basename( $full_src[0] ), 912 'width' => $full_src[1], 913 'height' => $full_src[2], 914 'mime_type' => $post->post_mime_type, 915 'source_url' => $full_src[0], 916 ); 917 } 918 } else { 919 $data['media_details']['sizes'] = new stdClass(); 920 } 921 } 922 923 if ( in_array( 'post', $fields, true ) ) { 924 $data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null; 925 } 926 927 if ( in_array( 'source_url', $fields, true ) ) { 928 $data['source_url'] = wp_get_attachment_url( $post->ID ); 929 } 930 931 if ( in_array( 'missing_image_sizes', $fields, true ) ) { 932 require_once ABSPATH . 'wp-admin/includes/image.php'; 933 $data['missing_image_sizes'] = array_keys( wp_get_missing_image_subsizes( $post->ID ) ); 934 } 935 936 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 937 938 $data = $this->filter_response_by_context( $data, $context ); 939 940 $links = $response->get_links(); 941 942 // Wrap the data in a response object. 943 $response = rest_ensure_response( $data ); 944 945 foreach ( $links as $rel => $rel_links ) { 946 foreach ( $rel_links as $link ) { 947 $response->add_link( $rel, $link['href'], $link['attributes'] ); 948 } 949 } 950 951 /** 952 * Filters an attachment returned from the REST API. 953 * 954 * Allows modification of the attachment right before it is returned. 955 * 956 * @since 4.7.0 957 * 958 * @param WP_REST_Response $response The response object. 959 * @param WP_Post $post The original attachment post. 960 * @param WP_REST_Request $request Request used to generate the response. 961 */ 962 return apply_filters( 'rest_prepare_attachment', $response, $post, $request ); 963 } 964 965 /** 966 * Retrieves the attachment's schema, conforming to JSON Schema. 967 * 968 * @since 4.7.0 969 * 970 * @return array Item schema as an array. 971 */ 972 public function get_item_schema() { 973 if ( $this->schema ) { 974 return $this->add_additional_fields_schema( $this->schema ); 975 } 976 977 $schema = parent::get_item_schema(); 978 979 $schema['properties']['alt_text'] = array( 980 'description' => __( 'Alternative text to display when attachment is not displayed.' ), 981 'type' => 'string', 982 'context' => array( 'view', 'edit', 'embed' ), 983 'arg_options' => array( 984 'sanitize_callback' => 'sanitize_text_field', 985 ), 986 ); 987 988 $schema['properties']['caption'] = array( 989 'description' => __( 'The attachment caption.' ), 990 'type' => 'object', 991 'context' => array( 'view', 'edit', 'embed' ), 992 'arg_options' => array( 993 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 994 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). 995 ), 996 'properties' => array( 997 'raw' => array( 998 'description' => __( 'Caption for the attachment, as it exists in the database.' ), 999 'type' => 'string', 1000 'context' => array( 'edit' ), 1001 ), 1002 'rendered' => array( 1003 'description' => __( 'HTML caption for the attachment, transformed for display.' ), 1004 'type' => 'string', 1005 'context' => array( 'view', 'edit', 'embed' ), 1006 'readonly' => true, 1007 ), 1008 ), 1009 ); 1010 1011 $schema['properties']['description'] = array( 1012 'description' => __( 'The attachment description.' ), 1013 'type' => 'object', 1014 'context' => array( 'view', 'edit' ), 1015 'arg_options' => array( 1016 'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database(). 1017 'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database(). 1018 ), 1019 'properties' => array( 1020 'raw' => array( 1021 'description' => __( 'Description for the attachment, as it exists in the database.' ), 1022 'type' => 'string', 1023 'context' => array( 'edit' ), 1024 ), 1025 'rendered' => array( 1026 'description' => __( 'HTML description for the attachment, transformed for display.' ), 1027 'type' => 'string', 1028 'context' => array( 'view', 'edit' ), 1029 'readonly' => true, 1030 ), 1031 ), 1032 ); 1033 1034 $schema['properties']['media_type'] = array( 1035 'description' => __( 'Attachment type.' ), 1036 'type' => 'string', 1037 'enum' => array( 'image', 'file' ), 1038 'context' => array( 'view', 'edit', 'embed' ), 1039 'readonly' => true, 1040 ); 1041 1042 $schema['properties']['mime_type'] = array( 1043 'description' => __( 'The attachment MIME type.' ), 1044 'type' => 'string', 1045 'context' => array( 'view', 'edit', 'embed' ), 1046 'readonly' => true, 1047 ); 1048 1049 $schema['properties']['media_details'] = array( 1050 'description' => __( 'Details about the media file, specific to its type.' ), 1051 'type' => 'object', 1052 'context' => array( 'view', 'edit', 'embed' ), 1053 'readonly' => true, 1054 ); 1055 1056 $schema['properties']['post'] = array( 1057 'description' => __( 'The ID for the associated post of the attachment.' ), 1058 'type' => 'integer', 1059 'context' => array( 'view', 'edit' ), 1060 ); 1061 1062 $schema['properties']['source_url'] = array( 1063 'description' => __( 'URL to the original attachment file.' ), 1064 'type' => 'string', 1065 'format' => 'uri', 1066 'context' => array( 'view', 'edit', 'embed' ), 1067 'readonly' => true, 1068 ); 1069 1070 $schema['properties']['missing_image_sizes'] = array( 1071 'description' => __( 'List of the missing image sizes of the attachment.' ), 1072 'type' => 'array', 1073 'items' => array( 'type' => 'string' ), 1074 'context' => array( 'edit' ), 1075 'readonly' => true, 1076 ); 1077 1078 unset( $schema['properties']['password'] ); 1079 1080 $this->schema = $schema; 1081 1082 return $this->add_additional_fields_schema( $this->schema ); 1083 } 1084 1085 /** 1086 * Handles an upload via raw POST data. 1087 * 1088 * @since 4.7.0 1089 * @since 6.6.0 Added the `$time` parameter. 1090 * 1091 * @param string $data Supplied file data. 1092 * @param array $headers HTTP headers from the request. 1093 * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null. 1094 * @return array|WP_Error Data from wp_handle_sideload(). 1095 */ 1096 protected function upload_from_data( $data, $headers, $time = null ) { 1097 if ( empty( $data ) ) { 1098 return new WP_Error( 1099 'rest_upload_no_data', 1100 __( 'No data supplied.' ), 1101 array( 'status' => 400 ) 1102 ); 1103 } 1104 1105 if ( empty( $headers['content_type'] ) ) { 1106 return new WP_Error( 1107 'rest_upload_no_content_type', 1108 __( 'No Content-Type supplied.' ), 1109 array( 'status' => 400 ) 1110 ); 1111 } 1112 1113 if ( empty( $headers['content_disposition'] ) ) { 1114 return new WP_Error( 1115 'rest_upload_no_content_disposition', 1116 __( 'No Content-Disposition supplied.' ), 1117 array( 'status' => 400 ) 1118 ); 1119 } 1120 1121 $filename = self::get_filename_from_disposition( $headers['content_disposition'] ); 1122 1123 if ( empty( $filename ) ) { 1124 return new WP_Error( 1125 'rest_upload_invalid_disposition', 1126 __( 'Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as `attachment; filename="image.png"` or similar.' ), 1127 array( 'status' => 400 ) 1128 ); 1129 } 1130 1131 if ( ! empty( $headers['content_md5'] ) ) { 1132 $content_md5 = array_shift( $headers['content_md5'] ); 1133 $expected = trim( $content_md5 ); 1134 $actual = md5( $data ); 1135 1136 if ( $expected !== $actual ) { 1137 return new WP_Error( 1138 'rest_upload_hash_mismatch', 1139 __( 'Content hash did not match expected.' ), 1140 array( 'status' => 412 ) 1141 ); 1142 } 1143 } 1144 1145 // Get the content-type. 1146 $type = array_shift( $headers['content_type'] ); 1147 1148 // Include filesystem functions to get access to wp_tempnam() and wp_handle_sideload(). 1149 require_once ABSPATH . 'wp-admin/includes/file.php'; 1150 1151 // Save the file. 1152 $tmpfname = wp_tempnam( $filename ); 1153 1154 $fp = fopen( $tmpfname, 'w+' ); 1155 1156 if ( ! $fp ) { 1157 return new WP_Error( 1158 'rest_upload_file_error', 1159 __( 'Could not open file handle.' ), 1160 array( 'status' => 500 ) 1161 ); 1162 } 1163 1164 fwrite( $fp, $data ); 1165 fclose( $fp ); 1166 1167 // Now, sideload it in. 1168 $file_data = array( 1169 'error' => null, 1170 'tmp_name' => $tmpfname, 1171 'name' => $filename, 1172 'type' => $type, 1173 ); 1174 1175 $size_check = self::check_upload_size( $file_data ); 1176 if ( is_wp_error( $size_check ) ) { 1177 return $size_check; 1178 } 1179 1180 $overrides = array( 1181 'test_form' => false, 1182 ); 1183 1184 $sideloaded = wp_handle_sideload( $file_data, $overrides, $time ); 1185 1186 if ( isset( $sideloaded['error'] ) ) { 1187 @unlink( $tmpfname ); 1188 1189 return new WP_Error( 1190 'rest_upload_sideload_error', 1191 $sideloaded['error'], 1192 array( 'status' => 500 ) 1193 ); 1194 } 1195 1196 return $sideloaded; 1197 } 1198 1199 /** 1200 * Parses filename from a Content-Disposition header value. 1201 * 1202 * As per RFC6266: 1203 * 1204 * content-disposition = "Content-Disposition" ":" 1205 * disposition-type *( ";" disposition-parm ) 1206 * 1207 * disposition-type = "inline" | "attachment" | disp-ext-type 1208 * ; case-insensitive 1209 * disp-ext-type = token 1210 * 1211 * disposition-parm = filename-parm | disp-ext-parm 1212 * 1213 * filename-parm = "filename" "=" value 1214 * | "filename*" "=" ext-value 1215 * 1216 * disp-ext-parm = token "=" value 1217 * | ext-token "=" ext-value 1218 * ext-token = <the characters in token, followed by "*"> 1219 * 1220 * @since 4.7.0 1221 * 1222 * @link https://tools.ietf.org/html/rfc2388 1223 * @link https://tools.ietf.org/html/rfc6266 1224 * 1225 * @param string[] $disposition_header List of Content-Disposition header values. 1226 * @return string|null Filename if available, or null if not found. 1227 */ 1228 public static function get_filename_from_disposition( $disposition_header ) { 1229 // Get the filename. 1230 $filename = null; 1231 1232 foreach ( $disposition_header as $value ) { 1233 $value = trim( $value ); 1234 1235 if ( ! str_contains( $value, ';' ) ) { 1236 continue; 1237 } 1238 1239 list( , $attr_parts ) = explode( ';', $value, 2 ); 1240 1241 $attr_parts = explode( ';', $attr_parts ); 1242 $attributes = array(); 1243 1244 foreach ( $attr_parts as $part ) { 1245 if ( ! str_contains( $part, '=' ) ) { 1246 continue; 1247 } 1248 1249 list( $key, $value ) = explode( '=', $part, 2 ); 1250 1251 $attributes[ trim( $key ) ] = trim( $value ); 1252 } 1253 1254 if ( empty( $attributes['filename'] ) ) { 1255 continue; 1256 } 1257 1258 $filename = trim( $attributes['filename'] ); 1259 1260 // Unquote quoted filename, but after trimming. 1261 if ( str_starts_with( $filename, '"' ) && str_ends_with( $filename, '"' ) ) { 1262 $filename = substr( $filename, 1, -1 ); 1263 } 1264 } 1265 1266 return $filename; 1267 } 1268 1269 /** 1270 * Retrieves the query params for collections of attachments. 1271 * 1272 * @since 4.7.0 1273 * 1274 * @return array Query parameters for the attachment collection as an array. 1275 */ 1276 public function get_collection_params() { 1277 $params = parent::get_collection_params(); 1278 $params['status']['default'] = 'inherit'; 1279 $params['status']['items']['enum'] = array( 'inherit', 'private', 'trash' ); 1280 $media_types = $this->get_media_types(); 1281 1282 $params['media_type'] = array( 1283 'default' => null, 1284 'description' => __( 'Limit result set to attachments of a particular media type.' ), 1285 'type' => 'string', 1286 'enum' => array_keys( $media_types ), 1287 ); 1288 1289 $params['mime_type'] = array( 1290 'default' => null, 1291 'description' => __( 'Limit result set to attachments of a particular MIME type.' ), 1292 'type' => 'string', 1293 ); 1294 1295 return $params; 1296 } 1297 1298 /** 1299 * Handles an upload via multipart/form-data ($_FILES). 1300 * 1301 * @since 4.7.0 1302 * @since 6.6.0 Added the `$time` parameter. 1303 * 1304 * @param array $files Data from the `$_FILES` superglobal. 1305 * @param array $headers HTTP headers from the request. 1306 * @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null. 1307 * @return array|WP_Error Data from wp_handle_upload(). 1308 */ 1309 protected function upload_from_file( $files, $headers, $time = null ) { 1310 if ( empty( $files ) ) { 1311 return new WP_Error( 1312 'rest_upload_no_data', 1313 __( 'No data supplied.' ), 1314 array( 'status' => 400 ) 1315 ); 1316 } 1317 1318 // Verify hash, if given. 1319 if ( ! empty( $headers['content_md5'] ) ) { 1320 $content_md5 = array_shift( $headers['content_md5'] ); 1321 $expected = trim( $content_md5 ); 1322 $actual = md5_file( $files['file']['tmp_name'] ); 1323 1324 if ( $expected !== $actual ) { 1325 return new WP_Error( 1326 'rest_upload_hash_mismatch', 1327 __( 'Content hash did not match expected.' ), 1328 array( 'status' => 412 ) 1329 ); 1330 } 1331 } 1332 1333 // Pass off to WP to handle the actual upload. 1334 $overrides = array( 1335 'test_form' => false, 1336 ); 1337 1338 // Bypasses is_uploaded_file() when running unit tests. 1339 if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) { 1340 $overrides['action'] = 'wp_handle_mock_upload'; 1341 } 1342 1343 $size_check = self::check_upload_size( $files['file'] ); 1344 if ( is_wp_error( $size_check ) ) { 1345 return $size_check; 1346 } 1347 1348 // Include filesystem functions to get access to wp_handle_upload(). 1349 require_once ABSPATH . 'wp-admin/includes/file.php'; 1350 1351 $file = wp_handle_upload( $files['file'], $overrides, $time ); 1352 1353 if ( isset( $file['error'] ) ) { 1354 return new WP_Error( 1355 'rest_upload_unknown_error', 1356 $file['error'], 1357 array( 'status' => 500 ) 1358 ); 1359 } 1360 1361 return $file; 1362 } 1363 1364 /** 1365 * Retrieves the supported media types. 1366 * 1367 * Media types are considered the MIME type category. 1368 * 1369 * @since 4.7.0 1370 * 1371 * @return array Array of supported media types. 1372 */ 1373 protected function get_media_types() { 1374 $media_types = array(); 1375 1376 foreach ( get_allowed_mime_types() as $mime_type ) { 1377 $parts = explode( '/', $mime_type ); 1378 1379 if ( ! isset( $media_types[ $parts[0] ] ) ) { 1380 $media_types[ $parts[0] ] = array(); 1381 } 1382 1383 $media_types[ $parts[0] ][] = $mime_type; 1384 } 1385 1386 return $media_types; 1387 } 1388 1389 /** 1390 * Determine if uploaded file exceeds space quota on multisite. 1391 * 1392 * Replicates check_upload_size(). 1393 * 1394 * @since 4.9.8 1395 * 1396 * @param array $file $_FILES array for a given file. 1397 * @return true|WP_Error True if can upload, error for errors. 1398 */ 1399 protected function check_upload_size( $file ) { 1400 if ( ! is_multisite() ) { 1401 return true; 1402 } 1403 1404 if ( get_site_option( 'upload_space_check_disabled' ) ) { 1405 return true; 1406 } 1407 1408 $space_left = get_upload_space_available(); 1409 1410 $file_size = filesize( $file['tmp_name'] ); 1411 1412 if ( $space_left < $file_size ) { 1413 return new WP_Error( 1414 'rest_upload_limited_space', 1415 /* translators: %s: Required disk space in kilobytes. */ 1416 sprintf( __( 'Not enough space to upload. %s KB needed.' ), number_format( ( $file_size - $space_left ) / KB_IN_BYTES ) ), 1417 array( 'status' => 400 ) 1418 ); 1419 } 1420 1421 if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) { 1422 return new WP_Error( 1423 'rest_upload_file_too_big', 1424 /* translators: %s: Maximum allowed file size in kilobytes. */ 1425 sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) ), 1426 array( 'status' => 400 ) 1427 ); 1428 } 1429 1430 // Include multisite admin functions to get access to upload_is_user_over_quota(). 1431 require_once ABSPATH . 'wp-admin/includes/ms.php'; 1432 1433 if ( upload_is_user_over_quota( false ) ) { 1434 return new WP_Error( 1435 'rest_upload_user_quota_exceeded', 1436 __( 'You have used your space quota. Please delete files before uploading.' ), 1437 array( 'status' => 400 ) 1438 ); 1439 } 1440 1441 return true; 1442 } 1443 1444 /** 1445 * Gets the request args for the edit item route. 1446 * 1447 * @since 5.5.0 1448 * 1449 * @return array 1450 */ 1451 protected function get_edit_media_item_args() { 1452 return array( 1453 'src' => array( 1454 'description' => __( 'URL to the edited image file.' ), 1455 'type' => 'string', 1456 'format' => 'uri', 1457 'required' => true, 1458 ), 1459 'modifiers' => array( 1460 'description' => __( 'Array of image edits.' ), 1461 'type' => 'array', 1462 'minItems' => 1, 1463 'items' => array( 1464 'description' => __( 'Image edit.' ), 1465 'type' => 'object', 1466 'required' => array( 1467 'type', 1468 'args', 1469 ), 1470 'oneOf' => array( 1471 array( 1472 'title' => __( 'Rotation' ), 1473 'properties' => array( 1474 'type' => array( 1475 'description' => __( 'Rotation type.' ), 1476 'type' => 'string', 1477 'enum' => array( 'rotate' ), 1478 ), 1479 'args' => array( 1480 'description' => __( 'Rotation arguments.' ), 1481 'type' => 'object', 1482 'required' => array( 1483 'angle', 1484 ), 1485 'properties' => array( 1486 'angle' => array( 1487 'description' => __( 'Angle to rotate clockwise in degrees.' ), 1488 'type' => 'number', 1489 ), 1490 ), 1491 ), 1492 ), 1493 ), 1494 array( 1495 'title' => __( 'Crop' ), 1496 'properties' => array( 1497 'type' => array( 1498 'description' => __( 'Crop type.' ), 1499 'type' => 'string', 1500 'enum' => array( 'crop' ), 1501 ), 1502 'args' => array( 1503 'description' => __( 'Crop arguments.' ), 1504 'type' => 'object', 1505 'required' => array( 1506 'left', 1507 'top', 1508 'width', 1509 'height', 1510 ), 1511 'properties' => array( 1512 'left' => array( 1513 'description' => __( 'Horizontal position from the left to begin the crop as a percentage of the image width.' ), 1514 'type' => 'number', 1515 ), 1516 'top' => array( 1517 'description' => __( 'Vertical position from the top to begin the crop as a percentage of the image height.' ), 1518 'type' => 'number', 1519 ), 1520 'width' => array( 1521 'description' => __( 'Width of the crop as a percentage of the image width.' ), 1522 'type' => 'number', 1523 ), 1524 'height' => array( 1525 'description' => __( 'Height of the crop as a percentage of the image height.' ), 1526 'type' => 'number', 1527 ), 1528 ), 1529 ), 1530 ), 1531 ), 1532 ), 1533 ), 1534 ), 1535 'rotation' => array( 1536 'description' => __( 'The amount to rotate the image clockwise in degrees. DEPRECATED: Use `modifiers` instead.' ), 1537 'type' => 'integer', 1538 'minimum' => 0, 1539 'exclusiveMinimum' => true, 1540 'maximum' => 360, 1541 'exclusiveMaximum' => true, 1542 ), 1543 'x' => array( 1544 'description' => __( 'As a percentage of the image, the x position to start the crop from. DEPRECATED: Use `modifiers` instead.' ), 1545 'type' => 'number', 1546 'minimum' => 0, 1547 'maximum' => 100, 1548 ), 1549 'y' => array( 1550 'description' => __( 'As a percentage of the image, the y position to start the crop from. DEPRECATED: Use `modifiers` instead.' ), 1551 'type' => 'number', 1552 'minimum' => 0, 1553 'maximum' => 100, 1554 ), 1555 'width' => array( 1556 'description' => __( 'As a percentage of the image, the width to crop the image to. DEPRECATED: Use `modifiers` instead.' ), 1557 'type' => 'number', 1558 'minimum' => 0, 1559 'maximum' => 100, 1560 ), 1561 'height' => array( 1562 'description' => __( 'As a percentage of the image, the height to crop the image to. DEPRECATED: Use `modifiers` instead.' ), 1563 'type' => 'number', 1564 'minimum' => 0, 1565 'maximum' => 100, 1566 ), 1567 ); 1568 } 1569 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Apr 10 08:20:01 2025 | Cross-referenced by PHPXref |