WP_REST_Attachments_Controller::edit_media_item( WP_REST_Request $request ): WP_REST_Response|WP_Error

In this article

Applies edits to a media item and creates a new attachment record.

Parameters

$requestWP_REST_Requestrequired
Full details about the request.

Return

WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.

Source

public function edit_media_item( $request ) {
	require_once ABSPATH . 'wp-admin/includes/image.php';

	$attachment_id = $request['id'];

	// This also confirms the attachment is an image.
	$image_file = wp_get_original_image_path( $attachment_id );
	$image_meta = wp_get_attachment_metadata( $attachment_id );

	if (
		! $image_meta ||
		! $image_file ||
		! wp_image_file_matches_image_meta( $request['src'], $image_meta, $attachment_id )
	) {
		return new WP_Error(
			'rest_unknown_attachment',
			__( 'Unable to get meta information for file.' ),
			array( 'status' => 404 )
		);
	}

	$supported_types = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' );
	$mime_type       = get_post_mime_type( $attachment_id );
	if ( ! in_array( $mime_type, $supported_types, true ) ) {
		return new WP_Error(
			'rest_cannot_edit_file_type',
			__( 'This type of file cannot be edited.' ),
			array( 'status' => 400 )
		);
	}

	// The `modifiers` param takes precedence over the older format.
	if ( isset( $request['modifiers'] ) ) {
		$modifiers = $request['modifiers'];
	} else {
		$modifiers = array();

		if ( ! empty( $request['rotation'] ) ) {
			$modifiers[] = array(
				'type' => 'rotate',
				'args' => array(
					'angle' => $request['rotation'],
				),
			);
		}

		if ( isset( $request['x'], $request['y'], $request['width'], $request['height'] ) ) {
			$modifiers[] = array(
				'type' => 'crop',
				'args' => array(
					'left'   => $request['x'],
					'top'    => $request['y'],
					'width'  => $request['width'],
					'height' => $request['height'],
				),
			);
		}

		if ( 0 === count( $modifiers ) ) {
			return new WP_Error(
				'rest_image_not_edited',
				__( 'The image was not edited. Edit the image before applying the changes.' ),
				array( 'status' => 400 )
			);
		}
	}

	/*
	 * If the file doesn't exist, attempt a URL fopen on the src link.
	 * This can occur with certain file replication plugins.
	 * Keep the original file path to get a modified name later.
	 */
	$image_file_to_edit = $image_file;
	if ( ! file_exists( $image_file_to_edit ) ) {
		$image_file_to_edit = _load_image_to_edit_path( $attachment_id );
	}

	$image_editor = wp_get_image_editor( $image_file_to_edit );

	if ( is_wp_error( $image_editor ) ) {
		return new WP_Error(
			'rest_unknown_image_file_type',
			__( 'Unable to edit this image.' ),
			array( 'status' => 500 )
		);
	}

	foreach ( $modifiers as $modifier ) {
		$args = $modifier['args'];
		switch ( $modifier['type'] ) {
			case 'rotate':
				// Rotation direction: clockwise vs. counterclockwise.
				$rotate = 0 - $args['angle'];

				if ( 0 !== $rotate ) {
					$result = $image_editor->rotate( $rotate );

					if ( is_wp_error( $result ) ) {
						return new WP_Error(
							'rest_image_rotation_failed',
							__( 'Unable to rotate this image.' ),
							array( 'status' => 500 )
						);
					}
				}

				break;

			case 'crop':
				$size = $image_editor->get_size();

				$crop_x = (int) round( ( $size['width'] * $args['left'] ) / 100.0 );
				$crop_y = (int) round( ( $size['height'] * $args['top'] ) / 100.0 );
				$width  = (int) round( ( $size['width'] * $args['width'] ) / 100.0 );
				$height = (int) round( ( $size['height'] * $args['height'] ) / 100.0 );

				if ( $size['width'] !== $width || $size['height'] !== $height ) {
					$result = $image_editor->crop( $crop_x, $crop_y, $width, $height );

					if ( is_wp_error( $result ) ) {
						return new WP_Error(
							'rest_image_crop_failed',
							__( 'Unable to crop this image.' ),
							array( 'status' => 500 )
						);
					}
				}

				break;

		}
	}

	// Calculate the file name.
	$image_ext  = pathinfo( $image_file, PATHINFO_EXTENSION );
	$image_name = wp_basename( $image_file, ".{$image_ext}" );

	/*
	 * Do not append multiple `-edited` to the file name.
	 * The user may be editing a previously edited image.
	 */
	if ( preg_match( '/-edited(-\d+)?$/', $image_name ) ) {
		// Remove any `-1`, `-2`, etc. `wp_unique_filename()` will add the proper number.
		$image_name = preg_replace( '/-edited(-\d+)?$/', '-edited', $image_name );
	} else {
		// Append `-edited` before the extension.
		$image_name .= '-edited';
	}

	$filename = "{$image_name}.{$image_ext}";

	// Create the uploads subdirectory if needed.
	$uploads = wp_upload_dir();

	// Make the file name unique in the (new) upload directory.
	$filename = wp_unique_filename( $uploads['path'], $filename );

	// Save to disk.
	$saved = $image_editor->save( $uploads['path'] . "/$filename" );

	if ( is_wp_error( $saved ) ) {
		return $saved;
	}

	// Create new attachment post.
	$new_attachment_post = array(
		'post_mime_type' => $saved['mime-type'],
		'guid'           => $uploads['url'] . "/$filename",
		'post_title'     => $image_name,
		'post_content'   => '',
	);

	// Copy post_content, post_excerpt, and post_title from the edited image's attachment post.
	$attachment_post = get_post( $attachment_id );

	if ( $attachment_post ) {
		$new_attachment_post['post_content'] = $attachment_post->post_content;
		$new_attachment_post['post_excerpt'] = $attachment_post->post_excerpt;
		$new_attachment_post['post_title']   = $attachment_post->post_title;
	}

	$new_attachment_id = wp_insert_attachment( wp_slash( $new_attachment_post ), $saved['path'], 0, true );

	if ( is_wp_error( $new_attachment_id ) ) {
		if ( 'db_update_error' === $new_attachment_id->get_error_code() ) {
			$new_attachment_id->add_data( array( 'status' => 500 ) );
		} else {
			$new_attachment_id->add_data( array( 'status' => 400 ) );
		}

		return $new_attachment_id;
	}

	// Copy the image alt text from the edited image.
	$image_alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );

	if ( ! empty( $image_alt ) ) {
		// update_post_meta() expects slashed.
		update_post_meta( $new_attachment_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) );
	}

	if ( wp_is_serving_rest_request() ) {
		/*
		 * Set a custom header with the attachment_id.
		 * Used by the browser/client to resume creating image sub-sizes after a PHP fatal error.
		 */
		header( 'X-WP-Upload-Attachment-ID: ' . $new_attachment_id );
	}

	// Generate image sub-sizes and meta.
	$new_image_meta = wp_generate_attachment_metadata( $new_attachment_id, $saved['path'] );

	// Copy the EXIF metadata from the original attachment if not generated for the edited image.
	if ( isset( $image_meta['image_meta'] ) && isset( $new_image_meta['image_meta'] ) && is_array( $new_image_meta['image_meta'] ) ) {
		// Merge but skip empty values.
		foreach ( (array) $image_meta['image_meta'] as $key => $value ) {
			if ( empty( $new_image_meta['image_meta'][ $key ] ) && ! empty( $value ) ) {
				$new_image_meta['image_meta'][ $key ] = $value;
			}
		}
	}

	// Reset orientation. At this point the image is edited and orientation is correct.
	if ( ! empty( $new_image_meta['image_meta']['orientation'] ) ) {
		$new_image_meta['image_meta']['orientation'] = 1;
	}

	// The attachment_id may change if the site is exported and imported.
	$new_image_meta['parent_image'] = array(
		'attachment_id' => $attachment_id,
		// Path to the originally uploaded image file relative to the uploads directory.
		'file'          => _wp_relative_upload_path( $image_file ),
	);

	/**
	 * Filters the meta data for the new image created by editing an existing image.
	 *
	 * @since 5.5.0
	 *
	 * @param array $new_image_meta    Meta data for the new image.
	 * @param int   $new_attachment_id Attachment post ID for the new image.
	 * @param int   $attachment_id     Attachment post ID for the edited (parent) image.
	 */
	$new_image_meta = apply_filters( 'wp_edited_image_metadata', $new_image_meta, $new_attachment_id, $attachment_id );

	wp_update_attachment_metadata( $new_attachment_id, $new_image_meta );

	$response = $this->prepare_item_for_response( get_post( $new_attachment_id ), $request );
	$response->set_status( 201 );
	$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $new_attachment_id ) ) );

	return $response;
}

Hooks

apply_filters( ‘wp_edited_image_metadata’, array $new_image_meta, int $new_attachment_id, int $attachment_id )

Filters the meta data for the new image created by editing an existing image.

Changelog

VersionDescription
5.5.0Introduced.

User Contributed Notes

You must log in before being able to contribute a note or feedback.