Make WordPress Core


Ignore:
Timestamp:
06/15/2019 01:01:48 AM (6 years ago)
Author:
azaozz
Message:

Save progress of intermediate image creation after upload. First run.

  • Introduces wp_get_missing_image_subsizes() and wp_update_image_subsizes() to generate image sub-sizes that are missing or were not created after the upload.
  • Adds a way to display errors that happened while creating sub-sizes.
  • Introduces wp_create_image_subsizes() intended for use after an image was uploaded. It saves/updates the image metadata immediately after each sub-size is created. This fixes the (long standing) problem when some of the sub-size image files were created but there was a timeout or an error and the metadata was not saved. Until now such uploads were considered "failed" which usually resulted in the user trying to upload the same image again, creating even more "orphan" image files.

Note that the patch also includes some unrelated WPCS fixes.

Props mikeschroder, azaozz.
See #40439.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-admin/includes/image.php

    r44785 r45538  
    6868
    6969/**
    70  * Generate post thumbnail attachment meta data.
     70 * Compare the existing image sub-sizes (as saved in the attachment meta)
     71 * to the currently registered image sub-sizes, and return the difference.
     72 *
     73 * Registered sub-sizes that are larger than the image are skipped.
     74 *
     75 * @since 5.3.0
     76 *
     77 * @param int $attachment_id The image attachment post ID.
     78 * @return array An array of the image sub-sizes that are currently defined but don't exist for this image.
     79 */
     80function wp_get_missing_image_subsizes( $attachment_id ) {
     81    if ( ! wp_attachment_is_image( $attachment_id ) ) {
     82        return array();
     83    }
     84
     85    $registered_sizes = wp_get_registered_image_subsizes();
     86    $image_meta       = wp_get_attachment_metadata( $attachment_id );
     87
     88    // Meta error?
     89    if ( empty( $image_meta ) ) {
     90        return $defined_sizes;
     91    }
     92
     93    $full_width     = (int) $image_meta['width'];
     94    $full_height    = (int) $image_meta['height'];
     95    $possible_sizes = array();
     96
     97    // Skip registered sizes that are too large for the uploaded image.
     98    foreach ( $registered_sizes as $size_name => $size_data ) {
     99        if ( image_resize_dimensions( $full_width, $full_height, $size_data['width'], $size_data['height'], $size_data['crop'] ) ) {
     100            $possible_sizes[ $size_name ] = $size_data;
     101        }
     102    }
     103
     104    if ( empty( $image_meta['sizes'] ) ) {
     105        $image_meta['sizes'] = array();
     106    }
     107
     108    // Remove sizes that already exist. Only checks for matching "size names".
     109    // It is possible that the dimensions for a particular size name have changed.
     110    // For example the user has changed the values on the Settings -> Media screen.
     111    // However we keep the old sub-sizes with the previous dimensions
     112    // as the image may have been used in an older post.
     113    $missing_sizes = array_diff_key( $possible_sizes, $image_meta['sizes'] );
     114
     115    /**
     116     * Filters the array of missing image sub-sizes for an uploaded image.
     117     *
     118     * @since 5.3.0
     119     *
     120     * @param array $missing_sizes Array with the missing image sub-sizes.
     121     * @param array $image_meta    The image meta data.
     122     * @param int   $attachment_id The image attachment post ID.
     123     */
     124    return apply_filters( 'wp_get_missing_image_subsizes', $missing_sizes, $image_meta, $attachment_id );
     125}
     126
     127/**
     128 * If any of the currently registered image sub-sizes are missing,
     129 * create them and update the image meta data.
     130 *
     131 * @since 5.3.0
     132 *
     133 * @param int $attachment_id The image attachment post ID.
     134 * @return array The updated image meta data array.
     135 */
     136function wp_update_image_subsizes( $attachment_id ) {
     137    $missing_sizes = wp_get_missing_image_subsizes( $attachment_id );
     138    $image_meta    = wp_get_attachment_metadata( $attachment_id );
     139
     140    if ( empty( $missing_sizes ) ) {
     141        return $image_meta;
     142    }
     143
     144    $image_file = get_attached_file( $attachment_id );
     145
     146    // This also updates the image meta.
     147    return _wp_make_subsizes( $missing_sizes, $image_file, $image_meta, $attachment_id );
     148}
     149
     150/**
     151 * Creates image sub-sizes, adds the new data to the image meta `sizes` array, and updates the image metadata.
     152 *
     153 * Intended for use after an image is uploaded. Saves/updates the image metadata after each
     154 * sub-size is created. If there was an error, it is added to the returned image metadata array.
     155 *
     156 * @since 5.3.0
     157 *
     158 * @param string $file          Full path to the image file.
     159 * @param array  $image_meta    The attachment meta data array.
     160 * @param int    $attachment_id Attachment Id to process.
     161 * @return array The attachment metadata with updated `sizes` array. Includes an array of errors encountered while resizing.
     162 */
     163function wp_create_image_subsizes( $file, $image_meta, $attachment_id ) {
     164    if ( empty( $image_meta ) || ! isset( $image_meta['width'], $image_meta['height'] ) ) {
     165        // New uploaded image.
     166        $imagesize            = getimagesize( $file );
     167        $image_meta['width']  = $imagesize[0];
     168        $image_meta['height'] = $imagesize[1];
     169
     170        // Make the file path relative to the upload dir.
     171        $image_meta['file'] = _wp_relative_upload_path( $file );
     172
     173        // Fetch additional metadata from EXIF/IPTC.
     174        $exif_meta = wp_read_image_metadata( $file );
     175
     176        if ( $exif_meta ) {
     177            $image_meta['image_meta'] = $exif_meta;
     178        }
     179    }
     180
     181    $new_sizes = wp_get_registered_image_subsizes();
     182
     183    /**
     184     * Filters the image sizes automatically generated when uploading an image.
     185     *
     186     * @since 2.9.0
     187     * @since 4.4.0 Added the `$image_meta` argument.
     188     * @since 5.3.0 Added the `$attachment_id` argument.
     189     *
     190     * @param array $new_sizes     Associative array of image sizes to be created.
     191     * @param array $image_meta    The image meta data: width, height, file, sizes, etc.
     192     * @param int   $attachment_id The attachment post ID for the image.
     193     */
     194    $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $image_meta, $attachment_id );
     195
     196    return _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id );
     197}
     198
     199/**
     200 * Low-level function to create image sub-sizes.
     201 *
     202 * Updates the image meta after each sub-size is created.
     203 * Errors are stored in the returned image metadata array.
     204 *
     205 * @since 5.3.0
     206 * @access private
     207 *
     208 * $padam array  $new_sizes     Array defining what sizes to create.
     209 * @param string $file          Full path to the image file.
     210 * @param array  $image_meta    The attachment meta data array.
     211 * @param int    $attachment_id Attachment Id to process.
     212 * @return array The attachment meta data with updated `sizes` array. Includes an array of errors encountered while resizing.
     213 */
     214function _wp_make_subsizes( $new_sizes, $file, $image_meta, $attachment_id ) {
     215    // Check if any of the new sizes already exist.
     216    if ( isset( $image_meta['sizes'] ) && is_array( $image_meta['sizes'] ) ) {
     217        foreach ( $image_meta['sizes'] as $size_name => $size_meta ) {
     218            // Only checks "size name" so we don't override existing images even if the dimensions
     219            // don't match the currently defined size with the same name.
     220            // To change the behavior, unset changed/mismatched sizes in the `sizes` array in image meta.
     221            if ( array_key_exists( $size_name, $new_sizes ) ) {
     222                unset( $new_sizes[ $size_name ] );
     223            }
     224        }
     225    } else {
     226        $image_meta['sizes'] = array();
     227    }
     228
     229    if ( ! empty( $new_sizes ) ) {
     230        $editor = wp_get_image_editor( $file );
     231
     232        if ( ! is_wp_error( $editor ) ) {
     233            if ( method_exists( $editor, 'make_subsize' ) ) {
     234                foreach ( $new_sizes as $new_size_name => $new_size_data ) {
     235                    $new_size_meta = $editor->make_subsize( $new_size_data );
     236
     237                    if ( is_wp_error( $new_size_meta ) ) {
     238                        if ( empty( $image_meta['subsize_errors'] ) ) {
     239                            $image_meta['subsize_errors'] = array();
     240                        }
     241
     242                        $error = array(
     243                            'error_code'    => $new_size_meta->get_error_code(),
     244                            'error_message' => $new_size_meta->get_error_message(),
     245                        );
     246
     247                        // Store the error code and error message for displaying in the UI.
     248                        $image_meta['subsize_errors'][ $new_size_name ] = $error;
     249                    } else {
     250                        // The sub-size was created successfully.
     251                        // Clear out previous errors in creating this subsize.
     252                        if ( ! empty( $image_meta['subsize_errors'][ $new_size_name ] ) ) {
     253                            unset( $image_meta['subsize_errors'][ $new_size_name ] );
     254                        }
     255
     256                        if ( empty( $image_meta['subsize_errors'] ) ) {
     257                            unset( $image_meta['subsize_errors'] );
     258                        }
     259
     260                        // Save the size meta value.
     261                        $image_meta['sizes'][ $new_size_name ] = $new_size_meta;
     262                    }
     263
     264                    wp_update_attachment_metadata( $attachment_id, $image_meta );
     265                }
     266            } else {
     267                // Fall back to `$editor->multi_resize()`.
     268                $created_sizes = $editor->multi_resize( $new_sizes );
     269
     270                if ( ! empty( $created_sizes ) ) {
     271                    $image_meta['sizes'] = array_merge( $image_meta['sizes'], $created_sizes );
     272                    unset( $image_meta['subsize_errors'] );
     273                    wp_update_attachment_metadata( $attachment_id, $image_meta );
     274                }
     275            }
     276        }
     277    }
     278
     279    return $image_meta;
     280}
     281
     282/**
     283 * Generate attachment meta data and create image sub-sizes for images.
    71284 *
    72285 * @since 2.1.0
     
    84297
    85298    if ( preg_match( '!^image/!', $mime_type ) && file_is_displayable_image( $file ) ) {
    86         $imagesize          = getimagesize( $file );
    87         $metadata['width']  = $imagesize[0];
    88         $metadata['height'] = $imagesize[1];
    89 
    90         // Make the file path relative to the upload dir.
    91         $metadata['file'] = _wp_relative_upload_path( $file );
    92 
    93299        // Make thumbnails and other intermediate sizes.
    94         $_wp_additional_image_sizes = wp_get_additional_image_sizes();
    95 
    96         $sizes = array();
    97         foreach ( get_intermediate_image_sizes() as $s ) {
    98             $sizes[ $s ] = array(
    99                 'width'  => '',
    100                 'height' => '',
    101                 'crop'   => false,
    102             );
    103             if ( isset( $_wp_additional_image_sizes[ $s ]['width'] ) ) {
    104                 // For theme-added sizes
    105                 $sizes[ $s ]['width'] = intval( $_wp_additional_image_sizes[ $s ]['width'] );
    106             } else {
    107                 // For default sizes set in options
    108                 $sizes[ $s ]['width'] = get_option( "{$s}_size_w" );
    109             }
    110 
    111             if ( isset( $_wp_additional_image_sizes[ $s ]['height'] ) ) {
    112                 // For theme-added sizes
    113                 $sizes[ $s ]['height'] = intval( $_wp_additional_image_sizes[ $s ]['height'] );
    114             } else {
    115                 // For default sizes set in options
    116                 $sizes[ $s ]['height'] = get_option( "{$s}_size_h" );
    117             }
    118 
    119             if ( isset( $_wp_additional_image_sizes[ $s ]['crop'] ) ) {
    120                 // For theme-added sizes
    121                 $sizes[ $s ]['crop'] = $_wp_additional_image_sizes[ $s ]['crop'];
    122             } else {
    123                 // For default sizes set in options
    124                 $sizes[ $s ]['crop'] = get_option( "{$s}_crop" );
    125             }
    126         }
    127 
    128         /**
    129          * Filters the image sizes automatically generated when uploading an image.
    130          *
    131          * @since 2.9.0
    132          * @since 4.4.0 Added the `$metadata` argument.
    133          * @since 5.1.0 Added the `$attachment_id` argument.
    134          *
    135          * @param array $sizes         An associative array of image sizes.
    136          * @param array $metadata      An associative array of image metadata: width, height, file.
    137          * @param int   $attachment_id Current attachment ID.
    138          */
    139         $sizes = apply_filters( 'intermediate_image_sizes_advanced', $sizes, $metadata, $attachment_id );
    140 
    141         if ( $sizes ) {
    142             $editor = wp_get_image_editor( $file );
    143 
    144             if ( ! is_wp_error( $editor ) ) {
    145                 $metadata['sizes'] = $editor->multi_resize( $sizes );
    146             }
    147         } else {
    148             $metadata['sizes'] = array();
    149         }
    150 
    151         // Fetch additional metadata from EXIF/IPTC.
    152         $image_meta = wp_read_image_metadata( $file );
    153         if ( $image_meta ) {
    154             $metadata['image_meta'] = $image_meta;
    155         }
     300        $metadata = wp_create_image_subsizes( $file, $metadata, $attachment_id );
    156301    } elseif ( wp_attachment_is( 'video', $attachment ) ) {
    157302        $metadata = wp_read_video_metadata( $file );
     
    235380        $fallback_sizes = apply_filters( 'fallback_intermediate_image_sizes', $fallback_sizes, $metadata );
    236381
    237         $sizes                      = array();
    238         $_wp_additional_image_sizes = wp_get_additional_image_sizes();
    239 
    240         foreach ( $fallback_sizes as $s ) {
    241             if ( isset( $_wp_additional_image_sizes[ $s ]['width'] ) ) {
    242                 $sizes[ $s ]['width'] = intval( $_wp_additional_image_sizes[ $s ]['width'] );
    243             } else {
    244                 $sizes[ $s ]['width'] = get_option( "{$s}_size_w" );
    245             }
    246 
    247             if ( isset( $_wp_additional_image_sizes[ $s ]['height'] ) ) {
    248                 $sizes[ $s ]['height'] = intval( $_wp_additional_image_sizes[ $s ]['height'] );
    249             } else {
    250                 $sizes[ $s ]['height'] = get_option( "{$s}_size_h" );
    251             }
    252 
    253             if ( isset( $_wp_additional_image_sizes[ $s ]['crop'] ) ) {
    254                 $sizes[ $s ]['crop'] = $_wp_additional_image_sizes[ $s ]['crop'];
    255             } else {
    256                 // Force thumbnails to be soft crops.
    257                 if ( 'thumbnail' !== $s ) {
    258                     $sizes[ $s ]['crop'] = get_option( "{$s}_crop" );
    259                 }
    260             }
     382        $defined_sizes = wp_get_registered_image_subsizes();
     383        $merged_sizes  = array_intersect_key( $defined_sizes, array_flip( $fallback_sizes ) );
     384
     385        // Force thumbnails to be soft crops.
     386        if ( isset( $merged_sizes['thumbnail'] ) && is_array( $merged_sizes['thumbnail'] ) ) {
     387            $merged_sizes['thumbnail']['crop'] = false;
    261388        }
    262389
    263390        // Only load PDFs in an image editor if we're processing sizes.
    264         if ( ! empty( $sizes ) ) {
     391        if ( ! empty( $merged_sizes ) ) {
    265392            $editor = wp_get_image_editor( $file );
    266393
     
    283410
    284411                    if ( ! is_wp_error( $editor ) ) {
    285                         $metadata['sizes']         = $editor->multi_resize( $sizes );
     412                        $metadata['sizes']         = $editor->multi_resize( $merged_sizes );
    286413                        $metadata['sizes']['full'] = $uploaded;
    287414                    }
     
    450577    $exif_image_types = apply_filters( 'wp_read_image_metadata_types', array( IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM ) );
    451578
    452     if ( is_callable( 'exif_read_data' ) && in_array( $image_type, $exif_image_types ) ) {
     579    if ( is_callable( 'exif_read_data' ) && in_array( $image_type, $exif_image_types, true ) ) {
    453580        $exif = @exif_read_data( $file );
    454581
     
    572699    if ( empty( $info ) ) {
    573700        $result = false;
    574     } elseif ( ! in_array( $info[2], $displayable_image_types ) ) {
     701    } elseif ( ! in_array( $info[2], $displayable_image_types, true ) ) {
    575702        $result = false;
    576703    } else {
     
    655782
    656783    if ( $filepath && file_exists( $filepath ) ) {
    657         if ( 'full' != $size && ( $data = image_get_intermediate_size( $attachment_id, $size ) ) ) {
     784        $data = image_get_intermediate_size( $attachment_id, $size );
     785
     786        if ( 'full' != $size && $data ) {
     787            $filepath = path_join( dirname( $filepath ), $data['file'] );
     788
    658789            /**
    659790             * Filters the path to the current image.
     
    667798             * @param string $size          Size of the image.
    668799             */
    669             $filepath = apply_filters( 'load_image_to_edit_filesystempath', path_join( dirname( $filepath ), $data['file'] ), $attachment_id, $size );
    670         }
    671     } elseif ( function_exists( 'fopen' ) && true == ini_get( 'allow_url_fopen' ) ) {
     800            $filepath = apply_filters( 'load_image_to_edit_filesystempath', $filepath, $attachment_id, $size );
     801        }
     802    } elseif ( function_exists( 'fopen' ) && true === ini_get( 'allow_url_fopen' ) ) {
    672803        /**
    673804         * Filters the image URL if not in the local filesystem.
     
    706837 */
    707838function _copy_image_file( $attachment_id ) {
    708     $dst_file = $src_file = get_attached_file( $attachment_id );
     839    $dst_file = get_attached_file( $attachment_id );
     840    $src_file = $dst_file;
     841
    709842    if ( ! file_exists( $src_file ) ) {
    710843        $src_file = _load_image_to_edit_path( $attachment_id );
Note: See TracChangeset for help on using the changeset viewer.