wp/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
    15  * @see WP_REST_Posts_Controller
    15  * @see WP_REST_Posts_Controller
    16  */
    16  */
    17 class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
    17 class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
    18 
    18 
    19 	/**
    19 	/**
       
    20 	 * Registers the routes for attachments.
       
    21 	 *
       
    22 	 * @since 5.3.0
       
    23 	 *
       
    24 	 * @see register_rest_route()
       
    25 	 */
       
    26 	public function register_routes() {
       
    27 		parent::register_routes();
       
    28 		register_rest_route(
       
    29 			$this->namespace,
       
    30 			'/' . $this->rest_base . '/(?P<id>[\d]+)/post-process',
       
    31 			array(
       
    32 				'methods'             => WP_REST_Server::CREATABLE,
       
    33 				'callback'            => array( $this, 'post_process_item' ),
       
    34 				'permission_callback' => array( $this, 'post_process_item_permissions_check' ),
       
    35 				'args'                => array(
       
    36 					'id'     => array(
       
    37 						'description' => __( 'Unique identifier for the object.' ),
       
    38 						'type'        => 'integer',
       
    39 					),
       
    40 					'action' => array(
       
    41 						'type'     => 'string',
       
    42 						'enum'     => array( 'create-image-subsizes' ),
       
    43 						'required' => true,
       
    44 					),
       
    45 				),
       
    46 			)
       
    47 		);
       
    48 		register_rest_route(
       
    49 			$this->namespace,
       
    50 			'/' . $this->rest_base . '/(?P<id>[\d]+)/edit',
       
    51 			array(
       
    52 				'methods'             => WP_REST_Server::CREATABLE,
       
    53 				'callback'            => array( $this, 'edit_media_item' ),
       
    54 				'permission_callback' => array( $this, 'edit_media_item_permissions_check' ),
       
    55 				'args'                => $this->get_edit_media_item_args(),
       
    56 			)
       
    57 		);
       
    58 	}
       
    59 
       
    60 	/**
    20 	 * Determines the allowed query_vars for a get_items() response and
    61 	 * Determines the allowed query_vars for a get_items() response and
    21 	 * prepares for WP_Query.
    62 	 * prepares for WP_Query.
    22 	 *
    63 	 *
    23 	 * @since 4.7.0
    64 	 * @since 4.7.0
    24 	 *
    65 	 *
    58 	 * Checks if a given request has access to create an attachment.
    99 	 * Checks if a given request has access to create an attachment.
    59 	 *
   100 	 *
    60 	 * @since 4.7.0
   101 	 * @since 4.7.0
    61 	 *
   102 	 *
    62 	 * @param WP_REST_Request $request Full details about the request.
   103 	 * @param WP_REST_Request $request Full details about the request.
    63 	 * @return WP_Error|true Boolean true if the attachment may be created, or a WP_Error if not.
   104 	 * @return true|WP_Error Boolean true if the attachment may be created, or a WP_Error if not.
    64 	 */
   105 	 */
    65 	public function create_item_permissions_check( $request ) {
   106 	public function create_item_permissions_check( $request ) {
    66 		$ret = parent::create_item_permissions_check( $request );
   107 		$ret = parent::create_item_permissions_check( $request );
    67 
   108 
    68 		if ( ! $ret || is_wp_error( $ret ) ) {
   109 		if ( ! $ret || is_wp_error( $ret ) ) {
    69 			return $ret;
   110 			return $ret;
    70 		}
   111 		}
    71 
   112 
    72 		if ( ! current_user_can( 'upload_files' ) ) {
   113 		if ( ! current_user_can( 'upload_files' ) ) {
    73 			return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) );
   114 			return new WP_Error(
       
   115 				'rest_cannot_create',
       
   116 				__( 'Sorry, you are not allowed to upload media on this site.' ),
       
   117 				array( 'status' => 400 )
       
   118 			);
    74 		}
   119 		}
    75 
   120 
    76 		// Attaching media to a post requires ability to edit said post.
   121 		// Attaching media to a post requires ability to edit said post.
    77 		if ( ! empty( $request['post'] ) ) {
   122 		if ( ! empty( $request['post'] ) && ! current_user_can( 'edit_post', (int) $request['post'] ) ) {
    78 			$parent           = get_post( (int) $request['post'] );
   123 			return new WP_Error(
    79 			$post_parent_type = get_post_type_object( $parent->post_type );
   124 				'rest_cannot_edit',
    80 
   125 				__( 'Sorry, you are not allowed to upload media to this post.' ),
    81 			if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
   126 				array( 'status' => rest_authorization_required_code() )
    82 				return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this post.' ), array( 'status' => rest_authorization_required_code() ) );
   127 			);
    83 			}
       
    84 		}
   128 		}
    85 
   129 
    86 		return true;
   130 		return true;
    87 	}
   131 	}
    88 
   132 
    90 	 * Creates a single attachment.
   134 	 * Creates a single attachment.
    91 	 *
   135 	 *
    92 	 * @since 4.7.0
   136 	 * @since 4.7.0
    93 	 *
   137 	 *
    94 	 * @param WP_REST_Request $request Full details about the request.
   138 	 * @param WP_REST_Request $request Full details about the request.
    95 	 * @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure.
   139 	 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
    96 	 */
   140 	 */
    97 	public function create_item( $request ) {
   141 	public function create_item( $request ) {
    98 
       
    99 		if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
   142 		if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
   100 			return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
   143 			return new WP_Error(
   101 		}
   144 				'rest_invalid_param',
   102 
   145 				__( 'Invalid parent type.' ),
       
   146 				array( 'status' => 400 )
       
   147 			);
       
   148 		}
       
   149 
       
   150 		$insert = $this->insert_attachment( $request );
       
   151 
       
   152 		if ( is_wp_error( $insert ) ) {
       
   153 			return $insert;
       
   154 		}
       
   155 
       
   156 		$schema = $this->get_item_schema();
       
   157 
       
   158 		// Extract by name.
       
   159 		$attachment_id = $insert['attachment_id'];
       
   160 		$file          = $insert['file'];
       
   161 
       
   162 		if ( isset( $request['alt_text'] ) ) {
       
   163 			update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
       
   164 		}
       
   165 
       
   166 		if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
       
   167 			$meta_update = $this->meta->update_value( $request['meta'], $attachment_id );
       
   168 
       
   169 			if ( is_wp_error( $meta_update ) ) {
       
   170 				return $meta_update;
       
   171 			}
       
   172 		}
       
   173 
       
   174 		$attachment    = get_post( $attachment_id );
       
   175 		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
       
   176 
       
   177 		if ( is_wp_error( $fields_update ) ) {
       
   178 			return $fields_update;
       
   179 		}
       
   180 
       
   181 		$request->set_param( 'context', 'edit' );
       
   182 
       
   183 		/**
       
   184 		 * Fires after a single attachment is completely created or updated via the REST API.
       
   185 		 *
       
   186 		 * @since 5.0.0
       
   187 		 *
       
   188 		 * @param WP_Post         $attachment Inserted or updated attachment object.
       
   189 		 * @param WP_REST_Request $request    Request object.
       
   190 		 * @param bool            $creating   True when creating an attachment, false when updating.
       
   191 		 */
       
   192 		do_action( 'rest_after_insert_attachment', $attachment, $request, true );
       
   193 
       
   194 		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
       
   195 			// Set a custom header with the attachment_id.
       
   196 			// Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
       
   197 			header( 'X-WP-Upload-Attachment-ID: ' . $attachment_id );
       
   198 		}
       
   199 
       
   200 		// Include media and image functions to get access to wp_generate_attachment_metadata().
       
   201 		require_once ABSPATH . 'wp-admin/includes/media.php';
       
   202 		require_once ABSPATH . 'wp-admin/includes/image.php';
       
   203 
       
   204 		// Post-process the upload (create image sub-sizes, make PDF thumbnails, etc.) and insert attachment meta.
       
   205 		// At this point the server may run out of resources and post-processing of uploaded images may fail.
       
   206 		wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
       
   207 
       
   208 		$response = $this->prepare_item_for_response( $attachment, $request );
       
   209 		$response = rest_ensure_response( $response );
       
   210 		$response->set_status( 201 );
       
   211 		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $attachment_id ) ) );
       
   212 
       
   213 		return $response;
       
   214 	}
       
   215 
       
   216 	/**
       
   217 	 * Inserts the attachment post in the database. Does not update the attachment meta.
       
   218 	 *
       
   219 	 * @since 5.3.0
       
   220 	 *
       
   221 	 * @param WP_REST_Request $request
       
   222 	 * @return array|WP_Error
       
   223 	 */
       
   224 	protected function insert_attachment( $request ) {
   103 		// Get the file via $_FILES or raw data.
   225 		// Get the file via $_FILES or raw data.
   104 		$files   = $request->get_file_params();
   226 		$files   = $request->get_file_params();
   105 		$headers = $request->get_headers();
   227 		$headers = $request->get_headers();
   106 
   228 
   107 		if ( ! empty( $files ) ) {
   229 		if ( ! empty( $files ) ) {
   123 		$file = $file['file'];
   245 		$file = $file['file'];
   124 
   246 
   125 		// Include image functions to get access to wp_read_image_metadata().
   247 		// Include image functions to get access to wp_read_image_metadata().
   126 		require_once ABSPATH . 'wp-admin/includes/image.php';
   248 		require_once ABSPATH . 'wp-admin/includes/image.php';
   127 
   249 
   128 		// use image exif/iptc data for title and caption defaults if possible
   250 		// Use image exif/iptc data for title and caption defaults if possible.
   129 		$image_meta = wp_read_image_metadata( $file );
   251 		$image_meta = wp_read_image_metadata( $file );
   130 
   252 
   131 		if ( ! empty( $image_meta ) ) {
   253 		if ( ! empty( $image_meta ) ) {
   132 			if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
   254 			if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
   133 				$request['title'] = $image_meta['title'];
   255 				$request['title'] = $image_meta['title'];
   136 			if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
   258 			if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
   137 				$request['caption'] = $image_meta['caption'];
   259 				$request['caption'] = $image_meta['caption'];
   138 			}
   260 			}
   139 		}
   261 		}
   140 
   262 
   141 		$attachment                 = $this->prepare_item_for_database( $request );
   263 		$attachment = $this->prepare_item_for_database( $request );
       
   264 
   142 		$attachment->post_mime_type = $type;
   265 		$attachment->post_mime_type = $type;
   143 		$attachment->guid           = $url;
   266 		$attachment->guid           = $url;
   144 
   267 
   145 		if ( empty( $attachment->post_title ) ) {
   268 		if ( empty( $attachment->post_title ) ) {
   146 			$attachment->post_title = preg_replace( '/\.[^.]+$/', '', wp_basename( $file ) );
   269 			$attachment->post_title = preg_replace( '/\.[^.]+$/', '', wp_basename( $file ) );
   153 			if ( 'db_update_error' === $id->get_error_code() ) {
   276 			if ( 'db_update_error' === $id->get_error_code() ) {
   154 				$id->add_data( array( 'status' => 500 ) );
   277 				$id->add_data( array( 'status' => 500 ) );
   155 			} else {
   278 			} else {
   156 				$id->add_data( array( 'status' => 400 ) );
   279 				$id->add_data( array( 'status' => 400 ) );
   157 			}
   280 			}
       
   281 
   158 			return $id;
   282 			return $id;
   159 		}
   283 		}
   160 
   284 
   161 		$attachment = get_post( $id );
   285 		$attachment = get_post( $id );
   162 
   286 
   170 		 * @param WP_REST_Request $request    The request sent to the API.
   294 		 * @param WP_REST_Request $request    The request sent to the API.
   171 		 * @param bool            $creating   True when creating an attachment, false when updating.
   295 		 * @param bool            $creating   True when creating an attachment, false when updating.
   172 		 */
   296 		 */
   173 		do_action( 'rest_insert_attachment', $attachment, $request, true );
   297 		do_action( 'rest_insert_attachment', $attachment, $request, true );
   174 
   298 
   175 		// Include admin function to get access to wp_generate_attachment_metadata().
   299 		return array(
   176 		require_once ABSPATH . 'wp-admin/includes/media.php';
   300 			'attachment_id' => $id,
   177 
   301 			'file'          => $file,
   178 		wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
   302 		);
       
   303 	}
       
   304 
       
   305 	/**
       
   306 	 * Updates a single attachment.
       
   307 	 *
       
   308 	 * @since 4.7.0
       
   309 	 *
       
   310 	 * @param WP_REST_Request $request Full details about the request.
       
   311 	 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
       
   312 	 */
       
   313 	public function update_item( $request ) {
       
   314 		if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
       
   315 			return new WP_Error(
       
   316 				'rest_invalid_param',
       
   317 				__( 'Invalid parent type.' ),
       
   318 				array( 'status' => 400 )
       
   319 			);
       
   320 		}
       
   321 
       
   322 		$response = parent::update_item( $request );
       
   323 
       
   324 		if ( is_wp_error( $response ) ) {
       
   325 			return $response;
       
   326 		}
       
   327 
       
   328 		$response = rest_ensure_response( $response );
       
   329 		$data     = $response->get_data();
   179 
   330 
   180 		if ( isset( $request['alt_text'] ) ) {
   331 		if ( isset( $request['alt_text'] ) ) {
   181 			update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
   332 			update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
   182 		}
   333 		}
       
   334 
       
   335 		$attachment = get_post( $request['id'] );
   183 
   336 
   184 		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
   337 		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
   185 
   338 
   186 		if ( is_wp_error( $fields_update ) ) {
   339 		if ( is_wp_error( $fields_update ) ) {
   187 			return $fields_update;
   340 			return $fields_update;
   188 		}
   341 		}
   189 
   342 
   190 		$request->set_param( 'context', 'edit' );
   343 		$request->set_param( 'context', 'edit' );
   191 
   344 
   192 		/**
   345 		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */
   193 		 * Fires after a single attachment is completely created or updated via the REST API.
   346 		do_action( 'rest_after_insert_attachment', $attachment, $request, false );
   194 		 *
       
   195 		 * @since 5.0.0
       
   196 		 *
       
   197 		 * @param WP_Post         $attachment Inserted or updated attachment object.
       
   198 		 * @param WP_REST_Request $request    Request object.
       
   199 		 * @param bool            $creating   True when creating an attachment, false when updating.
       
   200 		 */
       
   201 		do_action( 'rest_after_insert_attachment', $attachment, $request, true );
       
   202 
   347 
   203 		$response = $this->prepare_item_for_response( $attachment, $request );
   348 		$response = $this->prepare_item_for_response( $attachment, $request );
   204 		$response = rest_ensure_response( $response );
   349 		$response = rest_ensure_response( $response );
       
   350 
       
   351 		return $response;
       
   352 	}
       
   353 
       
   354 	/**
       
   355 	 * Performs post processing on an attachment.
       
   356 	 *
       
   357 	 * @since 5.3.0
       
   358 	 *
       
   359 	 * @param WP_REST_Request $request Full details about the request.
       
   360 	 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
       
   361 	 */
       
   362 	public function post_process_item( $request ) {
       
   363 		switch ( $request['action'] ) {
       
   364 			case 'create-image-subsizes':
       
   365 				require_once ABSPATH . 'wp-admin/includes/image.php';
       
   366 				wp_update_image_subsizes( $request['id'] );
       
   367 				break;
       
   368 		}
       
   369 
       
   370 		$request['context'] = 'edit';
       
   371 
       
   372 		return $this->prepare_item_for_response( get_post( $request['id'] ), $request );
       
   373 	}
       
   374 
       
   375 	/**
       
   376 	 * Checks if a given request can perform post processing on an attachment.
       
   377 	 *
       
   378 	 * @since 5.3.0
       
   379 	 *
       
   380 	 * @param WP_REST_Request $request Full details about the request.
       
   381 	 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
       
   382 	 */
       
   383 	public function post_process_item_permissions_check( $request ) {
       
   384 		return $this->update_item_permissions_check( $request );
       
   385 	}
       
   386 
       
   387 	/**
       
   388 	 * Checks if a given request has access to editing media.
       
   389 	 *
       
   390 	 * @since 5.5.0
       
   391 	 *
       
   392 	 * @param WP_REST_Request $request Full details about the request.
       
   393 	 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
       
   394 	 */
       
   395 	public function edit_media_item_permissions_check( $request ) {
       
   396 		if ( ! current_user_can( 'upload_files' ) ) {
       
   397 			return new WP_Error(
       
   398 				'rest_cannot_edit_image',
       
   399 				__( 'Sorry, you are not allowed to upload media on this site.' ),
       
   400 				array( 'status' => rest_authorization_required_code() )
       
   401 			);
       
   402 		}
       
   403 
       
   404 		return $this->update_item_permissions_check( $request );
       
   405 	}
       
   406 
       
   407 	/**
       
   408 	 * Applies edits to a media item and creates a new attachment record.
       
   409 	 *
       
   410 	 * @since 5.5.0
       
   411 	 *
       
   412 	 * @param WP_REST_Request $request Full details about the request.
       
   413 	 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
       
   414 	 */
       
   415 	public function edit_media_item( $request ) {
       
   416 		require_once ABSPATH . 'wp-admin/includes/image.php';
       
   417 
       
   418 		$attachment_id = $request['id'];
       
   419 
       
   420 		// This also confirms the attachment is an image.
       
   421 		$image_file = wp_get_original_image_path( $attachment_id );
       
   422 		$image_meta = wp_get_attachment_metadata( $attachment_id );
       
   423 
       
   424 		if (
       
   425 			! $image_meta ||
       
   426 			! $image_file ||
       
   427 			! wp_image_file_matches_image_meta( $request['src'], $image_meta, $attachment_id )
       
   428 		) {
       
   429 			return new WP_Error(
       
   430 				'rest_unknown_attachment',
       
   431 				__( 'Unable to get meta information for file.' ),
       
   432 				array( 'status' => 404 )
       
   433 			);
       
   434 		}
       
   435 
       
   436 		$supported_types = array( 'image/jpeg', 'image/png', 'image/gif' );
       
   437 		$mime_type       = get_post_mime_type( $attachment_id );
       
   438 		if ( ! in_array( $mime_type, $supported_types, true ) ) {
       
   439 			return new WP_Error(
       
   440 				'rest_cannot_edit_file_type',
       
   441 				__( 'This type of file cannot be edited.' ),
       
   442 				array( 'status' => 400 )
       
   443 			);
       
   444 		}
       
   445 
       
   446 		// Check if we need to do anything.
       
   447 		$rotate = 0;
       
   448 		$crop   = false;
       
   449 
       
   450 		if ( ! empty( $request['rotation'] ) ) {
       
   451 			// Rotation direction: clockwise vs. counter clockwise.
       
   452 			$rotate = 0 - (int) $request['rotation'];
       
   453 		}
       
   454 
       
   455 		if ( isset( $request['x'], $request['y'], $request['width'], $request['height'] ) ) {
       
   456 			$crop = true;
       
   457 		}
       
   458 
       
   459 		if ( ! $rotate && ! $crop ) {
       
   460 			return new WP_Error(
       
   461 				'rest_image_not_edited',
       
   462 				__( 'The image was not edited. Edit the image before applying the changes.' ),
       
   463 				array( 'status' => 400 )
       
   464 			);
       
   465 		}
       
   466 
       
   467 		/*
       
   468 		 * If the file doesn't exist, attempt a URL fopen on the src link.
       
   469 		 * This can occur with certain file replication plugins.
       
   470 		 * Keep the original file path to get a modified name later.
       
   471 		 */
       
   472 		$image_file_to_edit = $image_file;
       
   473 		if ( ! file_exists( $image_file_to_edit ) ) {
       
   474 			$image_file_to_edit = _load_image_to_edit_path( $attachment_id );
       
   475 		}
       
   476 
       
   477 		$image_editor = wp_get_image_editor( $image_file_to_edit );
       
   478 
       
   479 		if ( is_wp_error( $image_editor ) ) {
       
   480 			return new WP_Error(
       
   481 				'rest_unknown_image_file_type',
       
   482 				__( 'Unable to edit this image.' ),
       
   483 				array( 'status' => 500 )
       
   484 			);
       
   485 		}
       
   486 
       
   487 		if ( 0 !== $rotate ) {
       
   488 			$result = $image_editor->rotate( $rotate );
       
   489 
       
   490 			if ( is_wp_error( $result ) ) {
       
   491 				return new WP_Error(
       
   492 					'rest_image_rotation_failed',
       
   493 					__( 'Unable to rotate this image.' ),
       
   494 					array( 'status' => 500 )
       
   495 				);
       
   496 			}
       
   497 		}
       
   498 
       
   499 		if ( $crop ) {
       
   500 			$size = $image_editor->get_size();
       
   501 
       
   502 			$crop_x = round( ( $size['width'] * floatval( $request['x'] ) ) / 100.0 );
       
   503 			$crop_y = round( ( $size['height'] * floatval( $request['y'] ) ) / 100.0 );
       
   504 			$width  = round( ( $size['width'] * floatval( $request['width'] ) ) / 100.0 );
       
   505 			$height = round( ( $size['height'] * floatval( $request['height'] ) ) / 100.0 );
       
   506 
       
   507 			$result = $image_editor->crop( $crop_x, $crop_y, $width, $height );
       
   508 
       
   509 			if ( is_wp_error( $result ) ) {
       
   510 				return new WP_Error(
       
   511 					'rest_image_crop_failed',
       
   512 					__( 'Unable to crop this image.' ),
       
   513 					array( 'status' => 500 )
       
   514 				);
       
   515 			}
       
   516 		}
       
   517 
       
   518 		// Calculate the file name.
       
   519 		$image_ext  = pathinfo( $image_file, PATHINFO_EXTENSION );
       
   520 		$image_name = wp_basename( $image_file, ".{$image_ext}" );
       
   521 
       
   522 		// Do not append multiple `-edited` to the file name.
       
   523 		// The user may be editing a previously edited image.
       
   524 		if ( preg_match( '/-edited(-\d+)?$/', $image_name ) ) {
       
   525 			// Remove any `-1`, `-2`, etc. `wp_unique_filename()` will add the proper number.
       
   526 			$image_name = preg_replace( '/-edited(-\d+)?$/', '-edited', $image_name );
       
   527 		} else {
       
   528 			// Append `-edited` before the extension.
       
   529 			$image_name .= '-edited';
       
   530 		}
       
   531 
       
   532 		$filename = "{$image_name}.{$image_ext}";
       
   533 
       
   534 		// Create the uploads sub-directory if needed.
       
   535 		$uploads = wp_upload_dir();
       
   536 
       
   537 		// Make the file name unique in the (new) upload directory.
       
   538 		$filename = wp_unique_filename( $uploads['path'], $filename );
       
   539 
       
   540 		// Save to disk.
       
   541 		$saved = $image_editor->save( $uploads['path'] . "/$filename" );
       
   542 
       
   543 		if ( is_wp_error( $saved ) ) {
       
   544 			return $saved;
       
   545 		}
       
   546 
       
   547 		// Create new attachment post.
       
   548 		$new_attachment_post = array(
       
   549 			'post_mime_type' => $saved['mime-type'],
       
   550 			'guid'           => $uploads['url'] . "/$filename",
       
   551 			'post_title'     => $image_name,
       
   552 			'post_content'   => '',
       
   553 		);
       
   554 
       
   555 		// Copy post_content, post_excerpt, and post_title from the edited image's attachment post.
       
   556 		$attachment_post = get_post( $attachment_id );
       
   557 
       
   558 		if ( $attachment_post ) {
       
   559 			$new_attachment_post['post_content'] = $attachment_post->post_content;
       
   560 			$new_attachment_post['post_excerpt'] = $attachment_post->post_excerpt;
       
   561 			$new_attachment_post['post_title']   = $attachment_post->post_title;
       
   562 		}
       
   563 
       
   564 		$new_attachment_id = wp_insert_attachment( wp_slash( $new_attachment_post ), $saved['path'], 0, true );
       
   565 
       
   566 		if ( is_wp_error( $new_attachment_id ) ) {
       
   567 			if ( 'db_update_error' === $new_attachment_id->get_error_code() ) {
       
   568 				$new_attachment_id->add_data( array( 'status' => 500 ) );
       
   569 			} else {
       
   570 				$new_attachment_id->add_data( array( 'status' => 400 ) );
       
   571 			}
       
   572 
       
   573 			return $new_attachment_id;
       
   574 		}
       
   575 
       
   576 		// Copy the image alt text from the edited image.
       
   577 		$image_alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
       
   578 
       
   579 		if ( ! empty( $image_alt ) ) {
       
   580 			// update_post_meta() expects slashed.
       
   581 			update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) );
       
   582 		}
       
   583 
       
   584 		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
       
   585 			// Set a custom header with the attachment_id.
       
   586 			// Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
       
   587 			header( 'X-WP-Upload-Attachment-ID: ' . $new_attachment_id );
       
   588 		}
       
   589 
       
   590 		// Generate image sub-sizes and meta.
       
   591 		$new_image_meta = wp_generate_attachment_metadata( $new_attachment_id, $saved['path'] );
       
   592 
       
   593 		// Copy the EXIF metadata from the original attachment if not generated for the edited image.
       
   594 		if ( isset( $image_meta['image_meta'] ) && isset( $new_image_meta['image_meta'] ) && is_array( $new_image_meta['image_meta'] ) ) {
       
   595 			// Merge but skip empty values.
       
   596 			foreach ( (array) $image_meta['image_meta'] as $key => $value ) {
       
   597 				if ( empty( $new_image_meta['image_meta'][ $key ] ) && ! empty( $value ) ) {
       
   598 					$new_image_meta['image_meta'][ $key ] = $value;
       
   599 				}
       
   600 			}
       
   601 		}
       
   602 
       
   603 		// Reset orientation. At this point the image is edited and orientation is correct.
       
   604 		if ( ! empty( $new_image_meta['image_meta']['orientation'] ) ) {
       
   605 			$new_image_meta['image_meta']['orientation'] = 1;
       
   606 		}
       
   607 
       
   608 		// The attachment_id may change if the site is exported and imported.
       
   609 		$new_image_meta['parent_image'] = array(
       
   610 			'attachment_id' => $attachment_id,
       
   611 			// Path to the originally uploaded image file relative to the uploads directory.
       
   612 			'file'          => _wp_relative_upload_path( $image_file ),
       
   613 		);
       
   614 
       
   615 		/**
       
   616 		 * Filters the meta data for the new image created by editing an existing image.
       
   617 		 *
       
   618 		 * @since 5.5.0
       
   619 		 *
       
   620 		 * @param array $new_image_meta    Meta data for the new image.
       
   621 		 * @param int   $new_attachment_id Attachment post ID for the new image.
       
   622 		 * @param int   $attachment_id     Attachment post ID for the edited (parent) image.
       
   623 		 */
       
   624 		$new_image_meta = apply_filters( 'wp_edited_image_metadata', $new_image_meta, $new_attachment_id, $attachment_id );
       
   625 
       
   626 		wp_update_attachment_metadata( $new_attachment_id, $new_image_meta );
       
   627 
       
   628 		$response = $this->prepare_item_for_response( get_post( $new_attachment_id ), $request );
   205 		$response->set_status( 201 );
   629 		$response->set_status( 201 );
   206 		$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
   630 		$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $new_attachment_id ) ) );
   207 
   631 
   208 		return $response;
   632 		return $response;
   209 	}
   633 	}
   210 
   634 
   211 	/**
   635 	/**
   212 	 * Updates a single attachment.
   636 	 * Prepares a single attachment for create or update.
   213 	 *
   637 	 *
   214 	 * @since 4.7.0
   638 	 * @since 4.7.0
   215 	 *
   639 	 *
   216 	 * @param WP_REST_Request $request Full details about the request.
       
   217 	 * @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure.
       
   218 	 */
       
   219 	public function update_item( $request ) {
       
   220 		if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
       
   221 			return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
       
   222 		}
       
   223 
       
   224 		$response = parent::update_item( $request );
       
   225 
       
   226 		if ( is_wp_error( $response ) ) {
       
   227 			return $response;
       
   228 		}
       
   229 
       
   230 		$response = rest_ensure_response( $response );
       
   231 		$data     = $response->get_data();
       
   232 
       
   233 		if ( isset( $request['alt_text'] ) ) {
       
   234 			update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
       
   235 		}
       
   236 
       
   237 		$attachment = get_post( $request['id'] );
       
   238 
       
   239 		$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
       
   240 
       
   241 		if ( is_wp_error( $fields_update ) ) {
       
   242 			return $fields_update;
       
   243 		}
       
   244 
       
   245 		$request->set_param( 'context', 'edit' );
       
   246 
       
   247 		/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */
       
   248 		do_action( 'rest_after_insert_attachment', $attachment, $request, false );
       
   249 
       
   250 		$response = $this->prepare_item_for_response( $attachment, $request );
       
   251 		$response = rest_ensure_response( $response );
       
   252 
       
   253 		return $response;
       
   254 	}
       
   255 
       
   256 	/**
       
   257 	 * Prepares a single attachment for create or update.
       
   258 	 *
       
   259 	 * @since 4.7.0
       
   260 	 *
       
   261 	 * @param WP_REST_Request $request Request object.
   640 	 * @param WP_REST_Request $request Request object.
   262 	 * @return WP_Error|stdClass $prepared_attachment Post object.
   641 	 * @return stdClass|WP_Error Post object.
   263 	 */
   642 	 */
   264 	protected function prepare_item_for_database( $request ) {
   643 	protected function prepare_item_for_database( $request ) {
   265 		$prepared_attachment = parent::prepare_item_for_database( $request );
   644 		$prepared_attachment = parent::prepare_item_for_database( $request );
   266 
   645 
   267 		// Attachment caption (post_excerpt internally)
   646 		// Attachment caption (post_excerpt internally).
   268 		if ( isset( $request['caption'] ) ) {
   647 		if ( isset( $request['caption'] ) ) {
   269 			if ( is_string( $request['caption'] ) ) {
   648 			if ( is_string( $request['caption'] ) ) {
   270 				$prepared_attachment->post_excerpt = $request['caption'];
   649 				$prepared_attachment->post_excerpt = $request['caption'];
   271 			} elseif ( isset( $request['caption']['raw'] ) ) {
   650 			} elseif ( isset( $request['caption']['raw'] ) ) {
   272 				$prepared_attachment->post_excerpt = $request['caption']['raw'];
   651 				$prepared_attachment->post_excerpt = $request['caption']['raw'];
   273 			}
   652 			}
   274 		}
   653 		}
   275 
   654 
   276 		// Attachment description (post_content internally)
   655 		// Attachment description (post_content internally).
   277 		if ( isset( $request['description'] ) ) {
   656 		if ( isset( $request['description'] ) ) {
   278 			if ( is_string( $request['description'] ) ) {
   657 			if ( is_string( $request['description'] ) ) {
   279 				$prepared_attachment->post_content = $request['description'];
   658 				$prepared_attachment->post_content = $request['description'];
   280 			} elseif ( isset( $request['description']['raw'] ) ) {
   659 			} elseif ( isset( $request['description']['raw'] ) ) {
   281 				$prepared_attachment->post_content = $request['description']['raw'];
   660 				$prepared_attachment->post_content = $request['description']['raw'];
   311 			);
   690 			);
   312 		}
   691 		}
   313 
   692 
   314 		if ( in_array( 'caption', $fields, true ) ) {
   693 		if ( in_array( 'caption', $fields, true ) ) {
   315 			/** This filter is documented in wp-includes/post-template.php */
   694 			/** This filter is documented in wp-includes/post-template.php */
   316 			$caption         = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) );
   695 			$caption = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post );
       
   696 
       
   697 			/** This filter is documented in wp-includes/post-template.php */
       
   698 			$caption = apply_filters( 'the_excerpt', $caption );
       
   699 
   317 			$data['caption'] = array(
   700 			$data['caption'] = array(
   318 				'raw'      => $post->post_excerpt,
   701 				'raw'      => $post->post_excerpt,
   319 				'rendered' => $caption,
   702 				'rendered' => $caption,
   320 			);
   703 			);
   321 		}
   704 		}
   376 			$data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
   759 			$data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
   377 		}
   760 		}
   378 
   761 
   379 		if ( in_array( 'source_url', $fields, true ) ) {
   762 		if ( in_array( 'source_url', $fields, true ) ) {
   380 			$data['source_url'] = wp_get_attachment_url( $post->ID );
   763 			$data['source_url'] = wp_get_attachment_url( $post->ID );
       
   764 		}
       
   765 
       
   766 		if ( in_array( 'missing_image_sizes', $fields, true ) ) {
       
   767 			require_once ABSPATH . 'wp-admin/includes/image.php';
       
   768 			$data['missing_image_sizes'] = array_keys( wp_get_missing_image_subsizes( $post->ID ) );
   381 		}
   769 		}
   382 
   770 
   383 		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
   771 		$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
   384 
   772 
   385 		$data = $this->filter_response_by_context( $data, $context );
   773 		$data = $this->filter_response_by_context( $data, $context );
   415 	 * @since 4.7.0
   803 	 * @since 4.7.0
   416 	 *
   804 	 *
   417 	 * @return array Item schema as an array.
   805 	 * @return array Item schema as an array.
   418 	 */
   806 	 */
   419 	public function get_item_schema() {
   807 	public function get_item_schema() {
       
   808 		if ( $this->schema ) {
       
   809 			return $this->add_additional_fields_schema( $this->schema );
       
   810 		}
   420 
   811 
   421 		$schema = parent::get_item_schema();
   812 		$schema = parent::get_item_schema();
   422 
   813 
   423 		$schema['properties']['alt_text'] = array(
   814 		$schema['properties']['alt_text'] = array(
   424 			'description' => __( 'Alternative text to display when attachment is not displayed.' ),
   815 			'description' => __( 'Alternative text to display when attachment is not displayed.' ),
   432 		$schema['properties']['caption'] = array(
   823 		$schema['properties']['caption'] = array(
   433 			'description' => __( 'The attachment caption.' ),
   824 			'description' => __( 'The attachment caption.' ),
   434 			'type'        => 'object',
   825 			'type'        => 'object',
   435 			'context'     => array( 'view', 'edit', 'embed' ),
   826 			'context'     => array( 'view', 'edit', 'embed' ),
   436 			'arg_options' => array(
   827 			'arg_options' => array(
   437 				'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
   828 				'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
   438 				'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
   829 				'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
   439 			),
   830 			),
   440 			'properties'  => array(
   831 			'properties'  => array(
   441 				'raw'      => array(
   832 				'raw'      => array(
   442 					'description' => __( 'Caption for the attachment, as it exists in the database.' ),
   833 					'description' => __( 'Caption for the attachment, as it exists in the database.' ),
   443 					'type'        => 'string',
   834 					'type'        => 'string',
   455 		$schema['properties']['description'] = array(
   846 		$schema['properties']['description'] = array(
   456 			'description' => __( 'The attachment description.' ),
   847 			'description' => __( 'The attachment description.' ),
   457 			'type'        => 'object',
   848 			'type'        => 'object',
   458 			'context'     => array( 'view', 'edit' ),
   849 			'context'     => array( 'view', 'edit' ),
   459 			'arg_options' => array(
   850 			'arg_options' => array(
   460 				'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
   851 				'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database().
   461 				'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
   852 				'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database().
   462 			),
   853 			),
   463 			'properties'  => array(
   854 			'properties'  => array(
   464 				'raw'      => array(
   855 				'raw'      => array(
   465 					'description' => __( 'Description for the object, as it exists in the database.' ),
   856 					'description' => __( 'Description for the object, as it exists in the database.' ),
   466 					'type'        => 'string',
   857 					'type'        => 'string',
   509 			'format'      => 'uri',
   900 			'format'      => 'uri',
   510 			'context'     => array( 'view', 'edit', 'embed' ),
   901 			'context'     => array( 'view', 'edit', 'embed' ),
   511 			'readonly'    => true,
   902 			'readonly'    => true,
   512 		);
   903 		);
   513 
   904 
       
   905 		$schema['properties']['missing_image_sizes'] = array(
       
   906 			'description' => __( 'List of the missing image sizes of the attachment.' ),
       
   907 			'type'        => 'array',
       
   908 			'items'       => array( 'type' => 'string' ),
       
   909 			'context'     => array( 'edit' ),
       
   910 			'readonly'    => true,
       
   911 		);
       
   912 
   514 		unset( $schema['properties']['password'] );
   913 		unset( $schema['properties']['password'] );
   515 
   914 
   516 		return $schema;
   915 		$this->schema = $schema;
       
   916 
       
   917 		return $this->add_additional_fields_schema( $this->schema );
   517 	}
   918 	}
   518 
   919 
   519 	/**
   920 	/**
   520 	 * Handles an upload via raw POST data.
   921 	 * Handles an upload via raw POST data.
   521 	 *
   922 	 *
   525 	 * @param array $headers HTTP headers from the request.
   926 	 * @param array $headers HTTP headers from the request.
   526 	 * @return array|WP_Error Data from wp_handle_sideload().
   927 	 * @return array|WP_Error Data from wp_handle_sideload().
   527 	 */
   928 	 */
   528 	protected function upload_from_data( $data, $headers ) {
   929 	protected function upload_from_data( $data, $headers ) {
   529 		if ( empty( $data ) ) {
   930 		if ( empty( $data ) ) {
   530 			return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) );
   931 			return new WP_Error(
       
   932 				'rest_upload_no_data',
       
   933 				__( 'No data supplied.' ),
       
   934 				array( 'status' => 400 )
       
   935 			);
   531 		}
   936 		}
   532 
   937 
   533 		if ( empty( $headers['content_type'] ) ) {
   938 		if ( empty( $headers['content_type'] ) ) {
   534 			return new WP_Error( 'rest_upload_no_content_type', __( 'No Content-Type supplied.' ), array( 'status' => 400 ) );
   939 			return new WP_Error(
       
   940 				'rest_upload_no_content_type',
       
   941 				__( 'No Content-Type supplied.' ),
       
   942 				array( 'status' => 400 )
       
   943 			);
   535 		}
   944 		}
   536 
   945 
   537 		if ( empty( $headers['content_disposition'] ) ) {
   946 		if ( empty( $headers['content_disposition'] ) ) {
   538 			return new WP_Error( 'rest_upload_no_content_disposition', __( 'No Content-Disposition supplied.' ), array( 'status' => 400 ) );
   947 			return new WP_Error(
       
   948 				'rest_upload_no_content_disposition',
       
   949 				__( 'No Content-Disposition supplied.' ),
       
   950 				array( 'status' => 400 )
       
   951 			);
   539 		}
   952 		}
   540 
   953 
   541 		$filename = self::get_filename_from_disposition( $headers['content_disposition'] );
   954 		$filename = self::get_filename_from_disposition( $headers['content_disposition'] );
   542 
   955 
   543 		if ( empty( $filename ) ) {
   956 		if ( empty( $filename ) ) {
   544 			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 ) );
   957 			return new WP_Error(
       
   958 				'rest_upload_invalid_disposition',
       
   959 				__( 'Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as `attachment; filename="image.png"` or similar.' ),
       
   960 				array( 'status' => 400 )
       
   961 			);
   545 		}
   962 		}
   546 
   963 
   547 		if ( ! empty( $headers['content_md5'] ) ) {
   964 		if ( ! empty( $headers['content_md5'] ) ) {
   548 			$content_md5 = array_shift( $headers['content_md5'] );
   965 			$content_md5 = array_shift( $headers['content_md5'] );
   549 			$expected    = trim( $content_md5 );
   966 			$expected    = trim( $content_md5 );
   550 			$actual      = md5( $data );
   967 			$actual      = md5( $data );
   551 
   968 
   552 			if ( $expected !== $actual ) {
   969 			if ( $expected !== $actual ) {
   553 				return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) );
   970 				return new WP_Error(
       
   971 					'rest_upload_hash_mismatch',
       
   972 					__( 'Content hash did not match expected.' ),
       
   973 					array( 'status' => 412 )
       
   974 				);
   554 			}
   975 			}
   555 		}
   976 		}
   556 
   977 
   557 		// Get the content-type.
   978 		// Get the content-type.
   558 		$type = array_shift( $headers['content_type'] );
   979 		$type = array_shift( $headers['content_type'] );
   559 
   980 
   560 		/** Include admin functions to get access to wp_tempnam() and wp_handle_sideload(). */
   981 		// Include filesystem functions to get access to wp_tempnam() and wp_handle_sideload().
   561 		require_once ABSPATH . 'wp-admin/includes/file.php';
   982 		require_once ABSPATH . 'wp-admin/includes/file.php';
   562 
   983 
   563 		// Save the file.
   984 		// Save the file.
   564 		$tmpfname = wp_tempnam( $filename );
   985 		$tmpfname = wp_tempnam( $filename );
   565 
   986 
   566 		$fp = fopen( $tmpfname, 'w+' );
   987 		$fp = fopen( $tmpfname, 'w+' );
   567 
   988 
   568 		if ( ! $fp ) {
   989 		if ( ! $fp ) {
   569 			return new WP_Error( 'rest_upload_file_error', __( 'Could not open file handle.' ), array( 'status' => 500 ) );
   990 			return new WP_Error(
       
   991 				'rest_upload_file_error',
       
   992 				__( 'Could not open file handle.' ),
       
   993 				array( 'status' => 500 )
       
   994 			);
   570 		}
   995 		}
   571 
   996 
   572 		fwrite( $fp, $data );
   997 		fwrite( $fp, $data );
   573 		fclose( $fp );
   998 		fclose( $fp );
   574 
   999 
   592 		$sideloaded = wp_handle_sideload( $file_data, $overrides );
  1017 		$sideloaded = wp_handle_sideload( $file_data, $overrides );
   593 
  1018 
   594 		if ( isset( $sideloaded['error'] ) ) {
  1019 		if ( isset( $sideloaded['error'] ) ) {
   595 			@unlink( $tmpfname );
  1020 			@unlink( $tmpfname );
   596 
  1021 
   597 			return new WP_Error( 'rest_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) );
  1022 			return new WP_Error(
       
  1023 				'rest_upload_sideload_error',
       
  1024 				$sideloaded['error'],
       
  1025 				array( 'status' => 500 )
       
  1026 			);
   598 		}
  1027 		}
   599 
  1028 
   600 		return $sideloaded;
  1029 		return $sideloaded;
   601 	}
  1030 	}
   602 
  1031 
   621 	 *                         | ext-token "=" ext-value
  1050 	 *                         | ext-token "=" ext-value
   622 	 *     ext-token           = <the characters in token, followed by "*">
  1051 	 *     ext-token           = <the characters in token, followed by "*">
   623 	 *
  1052 	 *
   624 	 * @since 4.7.0
  1053 	 * @since 4.7.0
   625 	 *
  1054 	 *
   626 	 * @link http://tools.ietf.org/html/rfc2388
  1055 	 * @link https://tools.ietf.org/html/rfc2388
   627 	 * @link http://tools.ietf.org/html/rfc6266
  1056 	 * @link https://tools.ietf.org/html/rfc6266
   628 	 *
  1057 	 *
   629 	 * @param string[] $disposition_header List of Content-Disposition header values.
  1058 	 * @param string[] $disposition_header List of Content-Disposition header values.
   630 	 * @return string|null Filename if available, or null if not found.
  1059 	 * @return string|null Filename if available, or null if not found.
   631 	 */
  1060 	 */
   632 	public static function get_filename_from_disposition( $disposition_header ) {
  1061 	public static function get_filename_from_disposition( $disposition_header ) {
   708 	 * @param array $headers HTTP headers from the request.
  1137 	 * @param array $headers HTTP headers from the request.
   709 	 * @return array|WP_Error Data from wp_handle_upload().
  1138 	 * @return array|WP_Error Data from wp_handle_upload().
   710 	 */
  1139 	 */
   711 	protected function upload_from_file( $files, $headers ) {
  1140 	protected function upload_from_file( $files, $headers ) {
   712 		if ( empty( $files ) ) {
  1141 		if ( empty( $files ) ) {
   713 			return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) );
  1142 			return new WP_Error(
       
  1143 				'rest_upload_no_data',
       
  1144 				__( 'No data supplied.' ),
       
  1145 				array( 'status' => 400 )
       
  1146 			);
   714 		}
  1147 		}
   715 
  1148 
   716 		// Verify hash, if given.
  1149 		// Verify hash, if given.
   717 		if ( ! empty( $headers['content_md5'] ) ) {
  1150 		if ( ! empty( $headers['content_md5'] ) ) {
   718 			$content_md5 = array_shift( $headers['content_md5'] );
  1151 			$content_md5 = array_shift( $headers['content_md5'] );
   719 			$expected    = trim( $content_md5 );
  1152 			$expected    = trim( $content_md5 );
   720 			$actual      = md5_file( $files['file']['tmp_name'] );
  1153 			$actual      = md5_file( $files['file']['tmp_name'] );
   721 
  1154 
   722 			if ( $expected !== $actual ) {
  1155 			if ( $expected !== $actual ) {
   723 				return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) );
  1156 				return new WP_Error(
       
  1157 					'rest_upload_hash_mismatch',
       
  1158 					__( 'Content hash did not match expected.' ),
       
  1159 					array( 'status' => 412 )
       
  1160 				);
   724 			}
  1161 			}
   725 		}
  1162 		}
   726 
  1163 
   727 		// Pass off to WP to handle the actual upload.
  1164 		// Pass off to WP to handle the actual upload.
   728 		$overrides = array(
  1165 		$overrides = array(
   737 		$size_check = self::check_upload_size( $files['file'] );
  1174 		$size_check = self::check_upload_size( $files['file'] );
   738 		if ( is_wp_error( $size_check ) ) {
  1175 		if ( is_wp_error( $size_check ) ) {
   739 			return $size_check;
  1176 			return $size_check;
   740 		}
  1177 		}
   741 
  1178 
   742 		/** Include admin function to get access to wp_handle_upload(). */
  1179 		// Include filesystem functions to get access to wp_handle_upload().
   743 		require_once ABSPATH . 'wp-admin/includes/file.php';
  1180 		require_once ABSPATH . 'wp-admin/includes/file.php';
   744 
  1181 
   745 		$file = wp_handle_upload( $files['file'], $overrides );
  1182 		$file = wp_handle_upload( $files['file'], $overrides );
   746 
  1183 
   747 		if ( isset( $file['error'] ) ) {
  1184 		if ( isset( $file['error'] ) ) {
   748 			return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) );
  1185 			return new WP_Error(
       
  1186 				'rest_upload_unknown_error',
       
  1187 				$file['error'],
       
  1188 				array( 'status' => 500 )
       
  1189 			);
   749 		}
  1190 		}
   750 
  1191 
   751 		return $file;
  1192 		return $file;
   752 	}
  1193 	}
   753 
  1194 
   796 		}
  1237 		}
   797 
  1238 
   798 		$space_left = get_upload_space_available();
  1239 		$space_left = get_upload_space_available();
   799 
  1240 
   800 		$file_size = filesize( $file['tmp_name'] );
  1241 		$file_size = filesize( $file['tmp_name'] );
       
  1242 
   801 		if ( $space_left < $file_size ) {
  1243 		if ( $space_left < $file_size ) {
   802 			/* translators: %s: required disk space in kilobytes */
  1244 			return new WP_Error(
   803 			return new WP_Error( 'rest_upload_limited_space', sprintf( __( 'Not enough space to upload. %s KB needed.' ), number_format( ( $file_size - $space_left ) / KB_IN_BYTES ) ), array( 'status' => 400 ) );
  1245 				'rest_upload_limited_space',
       
  1246 				/* translators: %s: Required disk space in kilobytes. */
       
  1247 				sprintf( __( 'Not enough space to upload. %s KB needed.' ), number_format( ( $file_size - $space_left ) / KB_IN_BYTES ) ),
       
  1248 				array( 'status' => 400 )
       
  1249 			);
   804 		}
  1250 		}
   805 
  1251 
   806 		if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) {
  1252 		if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) {
   807 			/* translators: %s: maximum allowed file size in kilobytes */
  1253 			return new WP_Error(
   808 			return new WP_Error( 'rest_upload_file_too_big', sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) ), array( 'status' => 400 ) );
  1254 				'rest_upload_file_too_big',
   809 		}
  1255 				/* translators: %s: Maximum allowed file size in kilobytes. */
   810 
  1256 				sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) ),
   811 		// Include admin function to get access to upload_is_user_over_quota().
  1257 				array( 'status' => 400 )
       
  1258 			);
       
  1259 		}
       
  1260 
       
  1261 		// Include multisite admin functions to get access to upload_is_user_over_quota().
   812 		require_once ABSPATH . 'wp-admin/includes/ms.php';
  1262 		require_once ABSPATH . 'wp-admin/includes/ms.php';
   813 
  1263 
   814 		if ( upload_is_user_over_quota( false ) ) {
  1264 		if ( upload_is_user_over_quota( false ) ) {
   815 			return new WP_Error( 'rest_upload_user_quota_exceeded', __( 'You have used your space quota. Please delete files before uploading.' ), array( 'status' => 400 ) );
  1265 			return new WP_Error(
   816 		}
  1266 				'rest_upload_user_quota_exceeded',
       
  1267 				__( 'You have used your space quota. Please delete files before uploading.' ),
       
  1268 				array( 'status' => 400 )
       
  1269 			);
       
  1270 		}
       
  1271 
   817 		return true;
  1272 		return true;
   818 	}
  1273 	}
   819 
  1274 
       
  1275 	/**
       
  1276 	 * Gets the request args for the edit item route.
       
  1277 	 *
       
  1278 	 * @since 5.5.0
       
  1279 	 *
       
  1280 	 * @return array
       
  1281 	 */
       
  1282 	protected function get_edit_media_item_args() {
       
  1283 		return array(
       
  1284 			'rotation' => array(
       
  1285 				'description'      => __( 'The amount to rotate the image clockwise in degrees.' ),
       
  1286 				'type'             => 'integer',
       
  1287 				'minimum'          => 0,
       
  1288 				'exclusiveMinimum' => true,
       
  1289 				'maximum'          => 360,
       
  1290 				'exclusiveMaximum' => true,
       
  1291 			),
       
  1292 			'x'        => array(
       
  1293 				'description' => __( 'As a percentage of the image, the x position to start the crop from.' ),
       
  1294 				'type'        => 'number',
       
  1295 				'minimum'     => 0,
       
  1296 				'maximum'     => 100,
       
  1297 			),
       
  1298 			'y'        => array(
       
  1299 				'description' => __( 'As a percentage of the image, the y position to start the crop from.' ),
       
  1300 				'type'        => 'number',
       
  1301 				'minimum'     => 0,
       
  1302 				'maximum'     => 100,
       
  1303 			),
       
  1304 			'width'    => array(
       
  1305 				'description' => __( 'As a percentage of the image, the width to crop the image to.' ),
       
  1306 				'type'        => 'number',
       
  1307 				'minimum'     => 0,
       
  1308 				'maximum'     => 100,
       
  1309 			),
       
  1310 			'height'   => array(
       
  1311 				'description' => __( 'As a percentage of the image, the height to crop the image to.' ),
       
  1312 				'type'        => 'number',
       
  1313 				'minimum'     => 0,
       
  1314 				'maximum'     => 100,
       
  1315 			),
       
  1316 			'src'      => array(
       
  1317 				'description' => __( 'URL to the edited image file.' ),
       
  1318 				'type'        => 'string',
       
  1319 				'format'      => 'uri',
       
  1320 				'required'    => true,
       
  1321 			),
       
  1322 		);
       
  1323 	}
       
  1324 
   820 }
  1325 }