From 60c81777e5d6fed5d8da312cd9d425654137c9cd Mon Sep 17 00:00:00 2001
From: Pea <pea@misfist.com>
Date: Wed, 20 Sep 2023 19:33:14 -0400
Subject: [PATCH] Patch successfully applied

patching file src/wp-includes/class-wp-image-editor-gd.php
patching file src/wp-includes/class-wp-image-editor-imagick.php
patching file src/wp-includes/class-wp-image-editor.php
patching file tests/phpunit/tests/media.php
Hunk #1 succeeded at 1362 (offset -1 lines).

Update descriptions

Update WP version

5.1.0 >6.4.0

Auto-fix code formatting
---
 src/wp-includes/class-wp-image-editor-gd.php  | 26 +++++++++
 .../class-wp-image-editor-imagick.php         | 26 +++++++++
 src/wp-includes/class-wp-image-editor.php     | 34 ++++++++++-
 tests/phpunit/tests/media.php                 | 58 +++++++++++++++++++
 4 files changed, 143 insertions(+), 1 deletion(-)

diff --git a/src/wp-includes/class-wp-image-editor-gd.php b/src/wp-includes/class-wp-image-editor-gd.php
index de079357fb..7045aa1963 100644
--- a/src/wp-includes/class-wp-image-editor-gd.php
+++ b/src/wp-includes/class-wp-image-editor-gd.php
@@ -153,6 +153,30 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
 		return parent::update_size( $width, $height );
 	}
 
+	/**
+	 * Generates an hash for the current image.
+	 *
+	 * Concatenates crop coordinates, destination width and destination 
+	 * height into a string, calculates an MD5 hash, and returns first 8 characters.
+	 *
+	 * @since 6.4.0
+	 *
+	 * @param bool|array $crop Boolean or array with crop coordinates (e.g., array( x, y )).
+	 * @param int        $dst_w The destination width.
+	 * @param int        $dst_h The destination height.
+	 * @return false|string 8-char MD5 hash. False for non-array $crop.
+	 */
+	protected function create_crop_hash( $crop, $dst_w, $dst_h ) {
+		if ( ! is_array( $crop ) ) {
+			return false;
+		}
+
+		$str  = $crop[0] . $crop[1] . $dst_w . $dst_h;
+		$hash = substr( md5( $str ), 0, 8 );
+
+		return parent::update_crop_hash( $hash );
+	}
+
 	/**
 	 * Resizes current image.
 	 *
@@ -220,6 +244,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
 		imagecopyresampled( $resized, $this->image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h );
 
 		if ( is_gd_image( $resized ) ) {
+			$this->create_crop_hash( $crop, $dst_w, $dst_h );
 			$this->update_size( $dst_w, $dst_h );
 			return $resized;
 		}
@@ -534,6 +559,7 @@ class WP_Image_Editor_GD extends WP_Image_Editor {
 			'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
 			'width'     => $this->size['width'],
 			'height'    => $this->size['height'],
+			'hash'      => $this->hash,
 			'mime-type' => $mime_type,
 			'filesize'  => wp_filesize( $filename ),
 		);
diff --git a/src/wp-includes/class-wp-image-editor-imagick.php b/src/wp-includes/class-wp-image-editor-imagick.php
index 03fe0bca69..3a6a3067bf 100644
--- a/src/wp-includes/class-wp-image-editor-imagick.php
+++ b/src/wp-includes/class-wp-image-editor-imagick.php
@@ -309,6 +309,30 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
 		}
 	}
 
+	/**
+	 * Generates an hash for the current image.
+	 *
+	 * Concatenates crop coordinates, destination width and destination 
+	 * height into a string, calculates an MD5 hash, and returns first 8 characters.
+	 *
+	 * @since 6.4.0
+	 *
+	 * @param bool|array $crop Boolean or array with crop coordinates (e.g., array( x, y )).
+	 * @param int        $dst_w The destination width.
+	 * @param int        $dst_h The destination height.
+	 * @return false|string 8-char MD5 hash. False for non-array $crop.
+	 */
+	protected function create_crop_hash( $crop, $dst_w, $dst_h ) {
+		if ( ! is_array( $crop ) ) {
+			return false;
+		}
+
+		$str  = $crop[0] . $crop[1] . $dst_w . $dst_h;
+		$hash = substr( md5( $str ), 0, 8 );
+
+		return parent::update_crop_hash( $hash );
+	}
+
 	/**
 	 * Resizes current image.
 	 *
@@ -343,6 +367,7 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
 		list( $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h ) = $dims;
 
 		if ( $crop ) {
+			$this->create_crop_hash( $crop, $dst_w, $dst_h );
 			return $this->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h );
 		}
 
@@ -837,6 +862,7 @@ class WP_Image_Editor_Imagick extends WP_Image_Editor {
 			'file'      => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ),
 			'width'     => $this->size['width'],
 			'height'    => $this->size['height'],
+			'hash'      => $this->hash,
 			'mime-type' => $mime_type,
 			'filesize'  => wp_filesize( $filename ),
 		);
diff --git a/src/wp-includes/class-wp-image-editor.php b/src/wp-includes/class-wp-image-editor.php
index 3c636dc6ba..da3bb14312 100644
--- a/src/wp-includes/class-wp-image-editor.php
+++ b/src/wp-includes/class-wp-image-editor.php
@@ -15,6 +15,7 @@
 abstract class WP_Image_Editor {
 	protected $file              = null;
 	protected $size              = null;
+	protected $hash              = null;
 	protected $mime_type         = null;
 	protected $output_mime_type  = null;
 	protected $default_mime_type = 'image/jpeg';
@@ -221,6 +222,31 @@ abstract class WP_Image_Editor {
 		return true;
 	}
 
+
+	/**
+	 * Gets hash for the current image.
+	 *
+	 * @since 6.4.0
+	 *
+	 * @return string $hash 8-character MD5 hash.
+	 */
+	public function get_crop_hash() {
+		return $this->hash;
+	}
+
+	/**
+	 * Sets hash for the current image.
+	 *
+	 * @since 6.4.0
+	 *
+	 * @param string $hash 8-character MD5 hash.
+	 * @return true
+	 */
+	protected function update_crop_hash( $hash = null ) {
+		$this->hash = $hash;
+		return true;
+	}
+
 	/**
 	 * Gets the Image Compression quality on a 1-100% scale.
 	 *
@@ -486,7 +512,13 @@ abstract class WP_Image_Editor {
 			return false;
 		}
 
-		return "{$this->size['width']}x{$this->size['height']}";
+		$suffix = "{$this->size['width']}x{$this->size['height']}";
+
+		if( $this->get_crop_hash() ){
+			$suffix = $suffix . "-{$this->hash}";
+		}
+		
+		return $suffix;
 	}
 
 	/**
diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php
index 1ef1b93d4e..92fb182146 100644
--- a/tests/phpunit/tests/media.php
+++ b/tests/phpunit/tests/media.php
@@ -1362,6 +1362,64 @@ VIDEO;
 		$this->assertSame( 'This is a test', $post->post_title );
 	}
 
+	/**
+	 * @ticket 40370
+	 */
+	public function test_media_handle_upload_add_image_size() {
+		global $_wp_additional_image_sizes; 
+
+		$iptc_file = DIR_TESTDATA . '/images/test-image-iptc.jpg';
+
+		// Make a copy of this file as it gets moved during the file upload
+		$tmp_name = wp_tempnam( $iptc_file );
+
+		copy( $iptc_file, $tmp_name );
+
+		// FIXME : change to correct upload method that creates thumbnails
+		// and resuts in the new filenames with hashes
+		$_FILES['async-upload'] = array(
+			'tmp_name' => $tmp_name,
+			'name'     => 'test-image-iptc.jpg',
+			'type'     => 'image/jpeg',
+			'error'    => 0,
+			'size'     => filesize( $iptc_file ),
+		);
+
+		// add multiple image sizes
+		add_image_size( 'newscentered', 400, 400, array( 'center', 'center') ); 
+		add_image_size( 'newstop', 400, 400, array( 'center', 'top' ) ); 
+		add_image_size( 'newsbottom', 400, 400, array( 'center', 'bottom' ) ); 
+
+		$info = wp_upload_dir();
+
+		$orig = array_map('basename', glob( ABSPATH . 'wp-content/uploads'
+					. $info['subdir'] .'/*.png'));
+
+		// upload file
+		$post_id = media_handle_upload(
+			'async-upload',
+			0,
+			array(),
+			array(
+				'action'    => 'test_iptc_upload',
+				'test_form' => false,
+			)
+		);
+
+		unset( $_FILES['upload'] );
+
+		// Clean up.
+		wp_delete_attachment( $post_id );
+
+		// 3 new files should be created
+		$new = array_map('basename', glob( ABSPATH . 'wp-content/uploads'
+					. $info['subdir'] .'/*.{jpg, png}', GLOB_BRACE));
+		$new_files = array_diff($new, $orig);
+
+		$this->assertCount(3, $new_files );
+		
+	}
+
 	/**
 	 * @ticket 33016
 	 */
-- 
2.31.1

