Make WordPress Core

Ticket #55443: 55443-multi-mime-with-edit-and-restore-flows.diff

File 55443-multi-mime-with-edit-and-restore-flows.diff, 17.6 KB (added by adamsilverstein, 21 months ago)
  • src/wp-admin/includes/image-edit.php

    diff --git src/wp-admin/includes/image-edit.php src/wp-admin/includes/image-edit.php
    index e814fc47e7..b79c07ed12 100644
    function stream_preview_image( $post_id ) { 
    735735 */
    736736function wp_restore_image( $post_id ) {
    737737        $meta             = wp_get_attachment_metadata( $post_id );
     738        $old_meta         = $meta;
    738739        $file             = get_attached_file( $post_id );
    739740        $backup_sizes     = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
    740741        $old_backup_sizes = $backup_sizes;
     742        $backup_sources   = get_post_meta( $post_id, '_wp_attachment_backup_sources', true );
    741743        $restored         = false;
    742744        $msg              = new stdClass;
    743745
    function wp_restore_image( $post_id ) { 
    799801                }
    800802        }
    801803
     804        // Get the next available `full-{orig or hash}` key on the images if the name
     805        // has not been used as part of the backup sources it would be used if no size is
     806        // found or backup exists `null` would be used instead.
     807        $next_full_size_key_from_backup = null;
     808        foreach ( array_keys( $backup_sizes ) as $size_name ) {
     809                // If the target already has the sources attributes find the next one.
     810                if ( isset( $backup_sources[ $size_name ] ) ) {
     811                        continue;
     812                }
     813
     814                // We are only interested in the `full-` sizes.
     815                if ( strpos( $size_name, 'full-' ) === false ) {
     816                        continue;
     817                }
     818
     819                $next_full_size_key_from_backup = $size_name;
     820                break;
     821        }
     822
     823        if ( null !== $next_full_size_key_from_backup ) {
     824                $backup_sources[ $next_full_size_key_from_backup ] = $old_meta['sources'];
     825                // Store the `sources` property into the full size if present.
     826                update_post_meta( $post_id, '_wp_attachment_backup_sources', $backup_sources );
     827        }
     828
     829        if ( isset( $backup_sources['full-orig'] ) && is_array( $backup_sources['full-orig'] ) ) {
     830                $meta['sources'] = $backup_sources['full-orig'];
     831        }
     832
    802833        if ( ! wp_update_attachment_metadata( $post_id, $meta ) ||
    803834                ( $old_backup_sizes !== $backup_sizes && ! update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes ) ) ) {
    804835
    function wp_save_image( $post_id ) { 
    873904                return $return;
    874905        }
    875906
    876         $meta         = wp_get_attachment_metadata( $post_id );
    877         $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
     907        $meta           = wp_get_attachment_metadata( $post_id );
     908        $backup_sizes   = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
     909        $backup_sources = get_post_meta( $post->ID, '_wp_attachment_backup_sources', true );
    878910
    879911        if ( ! is_array( $meta ) ) {
    880912                $return->error = esc_js( __( 'Image data does not exist. Please re-upload the image.' ) );
    881913                return $return;
    882914        }
    883915
     916        $old_metadata = $meta;
     917
    884918        if ( ! is_array( $backup_sizes ) ) {
    885919                $backup_sizes = array();
    886920        }
    887921
     922        if ( ! is_array( $backup_sources ) ) {
     923                $backup_sources = array();
     924        }
     925
     926        $transforms      = wp_upload_image_mime_transforms( $post_id );
     927        $mime_transforms = array();
     928
     929        if ( ! empty( $transforms[ $post->post_mime_type ] ) ) {
     930                $mime_transforms = $transforms[ $post->post_mime_type ];
     931        }
     932
    888933        // Generate new filename.
    889934        $path = get_attached_file( $post_id );
    890935
    function wp_save_image( $post_id ) { 
    9821027                foreach ( $meta['sizes'] as $size ) {
    9831028                        if ( ! empty( $size['file'] ) && preg_match( '/-e[0-9]{13}-/', $size['file'] ) ) {
    9841029                                $delete_file = path_join( $dirname, $size['file'] );
    985                                 wp_delete_file( $delete_file );
     1030                                if ( file_exists( $delete_file ) ) {
     1031                                        wp_delete_file( $delete_file );
     1032                                }
     1033
     1034                                // Delete generated secondary mimes.
     1035                                foreach ( $mime_transforms as $mime ) {
     1036                                        if ( ! empty( $size['sources'][ $mime ]['file'] ) ) {
     1037                                                $delete_file = path_join( $dirname, $size['sources'][ $mime ]['file'] );
     1038
     1039                                                if ( file_exists( $delete_file ) ) {
     1040                                                        wp_delete_file( $delete_file );
     1041                                                }
     1042                                        }
     1043                                }
    9861044                        }
    9871045                }
    9881046        }
    9891047
     1048        $_sizes = array();
    9901049        if ( isset( $sizes ) ) {
    991                 $_sizes = array();
    9921050
    9931051                foreach ( $sizes as $size ) {
    9941052                        $tag = false;
    function wp_save_image( $post_id ) { 
    10261084                $meta['sizes'] = array_merge( $meta['sizes'], $img->multi_resize( $_sizes ) );
    10271085        }
    10281086
     1087        $original_directory = pathinfo( $new_path, PATHINFO_DIRNAME );
     1088        $filename           = pathinfo( $new_path, PATHINFO_FILENAME );
     1089        $main_images        = array();
     1090        $subsized_images    = array();
     1091
     1092        foreach ( $mime_transforms as $targeted_mime ) {
     1093                if ( $targeted_mime !== $post->post_mime_type ) {
     1094                        if ( ! $img::supports_mime_type( $targeted_mime ) ) {
     1095                                continue;
     1096                        }
     1097
     1098                        $extension = wp_get_default_extension_for_mime_type( $targeted_mime );
     1099                        if ( false === $extension ) {
     1100                                continue;
     1101                        }
     1102                        $img->set_output_mime_type( $targeted_mime );
     1103
     1104                        if ( 'thumbnail' === $target ) {
     1105                                if ( ! isset( $subsized_images[ $post->post_mime_type ]['thumbnail']['file'] ) ) {
     1106                                        continue;
     1107                                }
     1108
     1109                                $result = $img->make_subsize( $subsized_images[ $post->post_mime_type ]['thumbnail'] );
     1110
     1111                                if ( is_wp_error( $result ) ) {
     1112                                        continue;
     1113                                }
     1114
     1115                                $subsized_images[ $targeted_mime ] = array( 'thumbnail' => $result );
     1116                        } else {
     1117                                $result = $img->save( $img->generate_filename( '', $filename, $extension ) );
     1118
     1119                                if ( is_wp_error( $result ) ) {
     1120                                        continue;
     1121                                }
     1122
     1123                                $main_images[ $targeted_mime ] = $result;
     1124                                if ( method_exists( $img, 'make_subsize' ) ) {
     1125                                        foreach ( $_sizes as $size_name => $size_data ) {
     1126                                                $size_meta = $img->make_subsize( $size_data );
     1127
     1128                                                if ( ! is_wp_error( $size_meta ) ) {
     1129                                                        $subsized_images[ $targeted_mime ][ $size_name ] = $size_meta;
     1130                                                }
     1131                                        }
     1132                                } else {
     1133                                        $subsized_images[ $targeted_mime ] = $img->multi_resize( $_sizes );
     1134                                }
     1135                        }
     1136                } else {
     1137                        // If the target is `thumbnail` make sure it is the only selected size.
     1138                        // When the targeted thumbnail is selected no additional size and subsize is set.
     1139                        if ( 'thumbnail' === $target && isset( $meta['sizes']['thumbnail'] ) ) {
     1140                                $subsized_images[ $targeted_mime ] = array( 'thumbnail' => $meta['sizes']['thumbnail'] );
     1141                        } else {
     1142                                $main_images[ $targeted_mime ]     = array(
     1143                                        'path' => $new_path,
     1144                                        'file' => pathinfo( $new_path, PATHINFO_BASENAME ),
     1145                                );
     1146                                $subsized_images[ $targeted_mime ] = $meta['sizes'];
     1147                        }
     1148                }
     1149
     1150                if ( isset( $main_images[ $targeted_mime ]['path'], $main_images[ $targeted_mime ]['file'] ) && file_exists( $main_images[ $targeted_mime ]['path'] ) ) {
     1151                        // Add sources to original image metadata.
     1152                        $meta['sources'][ $targeted_mime ] = array(
     1153                                'file'     => $main_images[ $targeted_mime ]['file'],
     1154                                'filesize' => isset( $main_images[ $targeted_mime ]['filesize'] ) ? $main_images[ $targeted_mime ]['filesize'] : wp_filesize( $main_images[ $targeted_mime ]['path'] ),
     1155                        );
     1156                }
     1157
     1158                foreach ( $_sizes as $size_name => $size_details ) {
     1159                        if ( empty( $subsized_images[ $targeted_mime ][ $size_name ]['file'] ) ) {
     1160                                continue;
     1161                        }
     1162
     1163                        // Add sources to resized image metadata.
     1164                        $subsize_path = path_join( $original_directory, $subsized_images[ $targeted_mime ][ $size_name ]['file'] );
     1165                        if ( ! file_exists( $subsize_path ) ) {
     1166                                continue;
     1167                        }
     1168
     1169                        $meta['sizes'][ $size_name ]['sources'][ $targeted_mime ] = array(
     1170                                'file'     => $subsized_images[ $targeted_mime ][ $size_name ]['file'],
     1171                                'filesize' => wp_filesize( $subsize_path ),
     1172                        );
     1173                }
     1174        }
     1175
    10291176        unset( $img );
    10301177
    10311178        if ( $success ) {
    10321179                wp_update_attachment_metadata( $post_id, $meta );
    10331180                update_post_meta( $post_id, '_wp_attachment_backup_sizes', $backup_sizes );
    10341181
     1182                $next_target_size_name_from_source = null;
     1183                // Store the provided sources for the attachment ID in the `_wp_attachment_backup_sources` with the next available target if target is `null` no source would be stored.
     1184                foreach ( array_keys( $backup_sizes ) as $size_name ) {
     1185                        // If the target already has the sources attributes find the next one.
     1186                        if ( isset( $backup_sources[ $size_name ] ) ) {
     1187                                continue;
     1188                        }
     1189
     1190                        // We are only interested in the `full-` sizes.
     1191                        if ( strpos( $size_name, 'full-' ) === false ) {
     1192                                continue;
     1193                        }
     1194
     1195                        $next_target_size_name_from_source = $size_name;
     1196                }
     1197
     1198                if ( null !== $next_target_size_name_from_source && ! empty( $old_metadata['sources'] ) ) {
     1199                        $backup_sources[ $next_target_size_name_from_source ] = $old_metadata['sources'];
     1200                        // Store the `sources` property into the full size if present.
     1201                        update_post_meta( $post_id, '_wp_attachment_backup_sources', $backup_sources );
     1202                }
     1203
    10351204                if ( 'thumbnail' === $target || 'all' === $target || 'full' === $target ) {
    10361205                        // Check if it's an image edit from attachment edit screen.
    10371206                        if ( ! empty( $_REQUEST['context'] ) && 'edit-attachment' === $_REQUEST['context'] ) {
  • src/wp-includes/post.php

    diff --git src/wp-includes/post.php src/wp-includes/post.php
    index df509fd114..e8abd37bfe 100644
    function wp_delete_attachment( $post_id, $force_delete = false ) { 
    63886388        delete_post_meta( $post_id, '_wp_trash_meta_status' );
    63896389        delete_post_meta( $post_id, '_wp_trash_meta_time' );
    63906390
    6391         $meta         = wp_get_attachment_metadata( $post_id );
    6392         $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
    6393         $file         = get_attached_file( $post_id );
     6391        $meta           = wp_get_attachment_metadata( $post_id );
     6392        $backup_sizes   = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
     6393        $backup_sources = get_post_meta( $post->ID, '_wp_attachment_backup_sources', true );
     6394        $file           = get_attached_file( $post_id );
    63946395
    63956396        if ( is_multisite() && is_string( $file ) && ! empty( $file ) ) {
    63966397                clean_dirsize_cache( $file );
    function wp_delete_attachment( $post_id, $force_delete = false ) { 
    64366437        /** This action is documented in wp-includes/post.php */
    64376438        do_action( 'deleted_post', $post_id, $post );
    64386439
    6439         wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file );
     6440        wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file, $backup_sources );
    64406441
    64416442        clean_post_cache( $post );
    64426443
    function wp_delete_attachment( $post_id, $force_delete = false ) { 
    64506451 *
    64516452 * @global wpdb $wpdb WordPress database abstraction object.
    64526453 *
    6453  * @param int    $post_id      Attachment ID.
    6454  * @param array  $meta         The attachment's meta data.
    6455  * @param array  $backup_sizes The meta data for the attachment's backup images.
    6456  * @param string $file         Absolute path to the attachment's file.
     6454 * @param int    $post_id        Attachment ID.
     6455 * @param array  $meta           The attachment's meta data.
     6456 * @param array  $backup_sizes   The meta data for the attachment's backup images.
     6457 * @param string $file           Absolute path to the attachment's file.
     6458 * @param array  $backup_sources The meta data for the attachment's backup sources images.
    64576459 * @return bool True on success, false on failure.
    64586460 */
    6459 function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) {
     6461function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file, $backup_sources ) {
    64606462        global $wpdb;
    64616463
    64626464        $uploadpath = wp_get_upload_dir();
    function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) { 
    65806582                }
    65816583        }
    65826584
     6585        // Delete full sizes backup sources mime types.
     6586        if ( is_array( $backup_sources ) ) {
     6587                foreach ( $backup_sources as $backup_source ) {
     6588
     6589                        foreach ( $backup_source as $properties ) {
     6590                                if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
     6591                                        continue;
     6592                                }
     6593
     6594                                $source_file = str_replace( wp_basename( $file ), $properties['file'], $file );
     6595                                $del_file    = path_join( $uploadpath['basedir'], $source_file );
     6596                                if ( ! wp_delete_file_from_directory( $del_file, $intermediate_dir ) ) {
     6597                                        $deleted = false;
     6598                                }
     6599                        }
     6600                }
     6601        }
     6602
    65836603        return $deleted;
    65846604}
    65856605
  • tests/phpunit/tests/image/editor.php

    diff --git tests/phpunit/tests/image/editor.php tests/phpunit/tests/image/editor.php
    index c051ffec2b..b83eff1ae0 100644
    class Tests_Image_Editor extends WP_Image_UnitTestCase { 
    627627                );
    628628        }
    629629
     630        /**
     631         * Test removing the backup sources files when image is removed after edited.
     632         *
     633         * @ticket 55443
     634         */
     635        public function it_should_remove_the_backup_sizes_and_sources_if_the_attachment_is_deleted_after_edit() {
     636                require_once ABSPATH . 'wp-admin/includes/image-edit.php';
     637
     638                $attachment_id = $this->factory->attachment->create_upload_object(
     639                        DIR_TESTDATA . '/tests/testdata/modules/images/leafs.jpg'
     640                );
     641
     642                $file    = get_attached_file( $attachment_id, true );
     643                $dirname = pathinfo( $file, PATHINFO_DIRNAME );
     644
     645                $this->assertIsString( $file );
     646                $this->assertFileExists( $file );
     647
     648                $_REQUEST['action']  = 'image-editor';
     649                $_REQUEST['context'] = 'edit-attachment';
     650                $_REQUEST['postid']  = $attachment_id;
     651                $_REQUEST['target']  = 'all';
     652                $_REQUEST['do']      = 'save';
     653                $_REQUEST['history'] = '[{"r":-90}]';
     654
     655                wp_save_image( $attachment_id );
     656
     657                $backup_sources = get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true );
     658                $this->assertNotEmpty( $backup_sources );
     659                $this->assertIsArray( $backup_sources );
     660
     661                $backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
     662                $this->assertNotEmpty( $backup_sizes );
     663                $this->assertIsArray( $backup_sizes );
     664
     665                wp_delete_attachment( $attachment_id, true );
     666
     667                $this->assertFileDoesNotExist( path_join( $dirname, $backup_sources['full-orig']['image/webp']['file'] ) );
     668                $this->assertFileDoesNotExist( path_join( $dirname, $backup_sizes['thumbnail-orig']['sources']['image/webp']['file'] ) );
     669        }
     670
    630671        /**
    631672         * Test avoiding the change of URLs of images that are not part of the media library.
    632673         *
  • tests/phpunit/tests/image/functions.php

    diff --git tests/phpunit/tests/image/functions.php tests/phpunit/tests/image/functions.php
    index cb5a7971c9..95bb5cae1e 100644
    class Tests_Image_Functions extends WP_UnitTestCase { 
    248248                fclose( $handle );
    249249        }
    250250
     251        /**
     252         * Backup the sources structure alongside the full size
     253         *
     254         * @ticket 55443
     255         */
     256        public function test_backup_the_sources_structure_alongside_the_full_size() {
     257                require_once ABSPATH . 'wp-admin/includes/image-edit.php';
     258
     259                $attachment_id = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/canola.jpg' );
     260
     261                $metadata = wp_get_attachment_metadata( $attachment_id );
     262                $this->assertEmpty( get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true ) );
     263                $this->assertEmpty( get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true ) );
     264
     265                $_REQUEST['action']  = 'image-editor';
     266                $_REQUEST['context'] = 'edit-attachment';
     267                $_REQUEST['postid']  = $attachment_id;
     268                $_REQUEST['target']  = 'all';
     269                $_REQUEST['do']      = 'save';
     270                $_REQUEST['history'] = '[{"r":-90}]';
     271
     272                $ret = wp_save_image( $attachment_id );
     273
     274                $backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
     275
     276                $this->assertNotEmpty( $backup_sizes );
     277                $this->assertIsArray( $backup_sizes );
     278
     279                $backup_sources = get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true );
     280                $this->assertIsArray( $backup_sources );
     281                $this->assertArrayHasKey( 'full-orig', $backup_sources );
     282                $this->assertSame( $metadata['sources'], $backup_sources['full-orig'] );
     283
     284                foreach ( $backup_sizes as $size => $properties ) {
     285                        $size_name = str_replace( '-orig', '', $size );
     286
     287                        if ( 'full-orig' === $size ) {
     288                                continue;
     289                        }
     290
     291                        $this->assertArrayHasKey( 'sources', $properties );
     292                        $this->assertSame( $metadata['sizes'][ $size_name ]['sources'], $properties['sources'] );
     293                }
     294        }
     295
     296        /**
     297         * Restore the sources array from the backup when an image is edited
     298         *
     299         * @ticket 55443
     300         */
     301        public function test_restore_the_sources_array_from_the_backup_when_an_image_is_edited() {
     302                require_once ABSPATH . 'wp-admin/includes/image-edit.php';
     303
     304                $attachment_id = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/canola.jpg' );
     305
     306                $metadata = wp_get_attachment_metadata( $attachment_id );
     307
     308                $_REQUEST['action']  = 'image-editor';
     309                $_REQUEST['context'] = 'edit-attachment';
     310                $_REQUEST['postid']  = $attachment_id;
     311                $_REQUEST['target']  = 'all';
     312                $_REQUEST['do']      = 'save';
     313                $_REQUEST['history'] = '[{"r":-90}]';
     314
     315                $ret = wp_save_image( $attachment_id );
     316
     317                $backup_sources = get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true );
     318
     319                $this->assertArrayHasKey( 'full-orig', $backup_sources );
     320                $this->assertIsArray( $backup_sources['full-orig'] );
     321                $this->assertSame( $metadata['sources'], $backup_sources['full-orig'] );
     322
     323                wp_restore_image( $attachment_id );
     324
     325                $metadata               = wp_get_attachment_metadata( $attachment_id );
     326                $updated_backup_sources = get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true );
     327
     328                $this->assertSame( $backup_sources['full-orig'], $metadata['sources'] );
     329                $this->assertNotSame( $backup_sources, $updated_backup_sources );
     330                $this->assertCount( 1, $backup_sources );
     331                $this->assertCount( 2, $updated_backup_sources );
     332
     333                $backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
     334                foreach ( $backup_sizes as $size_name => $properties ) {
     335                        // We are only interested in the original filenames to be compared against the backup and restored values.
     336                        if ( false === strpos( $size_name, '-orig' ) ) {
     337                                continue;
     338                        }
     339
     340                        $size_name = str_replace( '-orig', '', $size_name );
     341                        // Full name is verified above.
     342                        if ( 'full' === $size_name ) {
     343                                continue;
     344                        }
     345
     346                        $this->assertArrayHasKey( $size_name, $metadata['sizes'] );
     347                        $this->assertArrayHasKey( 'sources', $metadata['sizes'][ $size_name ] );
     348                        $this->assertSame( $properties['sources'], $metadata['sizes'][ $size_name ]['sources'] );
     349                }
     350        }
     351
    251352        /**
    252353         * Tests wp_save_image_file() and mime types.
    253354         *