Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a4a1305
Relocate `$extension` variable closer to where is used
mitogh Feb 28, 2022
20a4b90
Add the generation of sources property for full size image.
mitogh Feb 28, 2022
082bc89
Fix linting errors on PHP file.
mitogh Feb 28, 2022
1b058f3
Handle the deletion of `sources` files
mitogh Feb 28, 2022
a12381a
Remove full sizes images when the attachment is removed.
mitogh Feb 28, 2022
204393f
Merge branch 'trunk' into feature/174-full-image-size
mitogh Feb 28, 2022
50ceba0
Update wording on documentation.
mitogh Feb 28, 2022
add98a7
Consolidate 2 hooks into a single one
mitogh Feb 28, 2022
e57d728
Update docblock and references to the images
mitogh Feb 28, 2022
a53fa55
Removing non required empty space
mitogh Mar 3, 2022
e13ead2
Replace `reset` with access direct to zero index.
mitogh Mar 8, 2022
399309e
Merge branch 'trunk' into feature/174-full-image-size
mitogh Mar 8, 2022
0571010
Merge branch 'feature/174-full-image-size' of github.com:WordPress/pe…
mitogh Mar 8, 2022
7072af6
Remove non requierd defensive conditional check
mitogh Mar 8, 2022
470c416
Merge branch 'trunk' into feature/174-full-image-size
mitogh Mar 8, 2022
ab421c8
Split logic to share with the `full` size image
mitogh Mar 9, 2022
270c696
Mark method as private
mitogh Mar 9, 2022
557d50d
Format doc block using WP Standards.
mitogh Mar 9, 2022
169e7e6
Rollback changes to use previous implementation
mitogh Mar 9, 2022
c035e02
Remove default values for parameters
mitogh Mar 9, 2022
223137d
Merge branch 'feature/174-full-image-size' of github.com:WordPress/pe…
mitogh Mar 9, 2022
958d273
Remove non required conditional
mitogh Mar 9, 2022
df02fde
Prevent to operate with invalid dimensions
mitogh Mar 9, 2022
ef0576b
Add tests for `webp_uploads_generate_additional_image_source`
mitogh Mar 9, 2022
1c77008
Remove any invalid transform if present
mitogh Mar 9, 2022
0b028c2
Fix linting errors on test file
mitogh Mar 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 194 additions & 75 deletions modules/images/webp-uploads/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,88 @@
/**
* Hook called by `wp_generate_attachment_metadata` to create the `sources` property for every image
* size, the sources' property would create a new image size with all the mime types specified in
* `webp_uploads_valid_image_mime_types`. If the original image is one of the mimes from
* `webp_uploads_valid_image_mime_types` the image is just added to the `sources` property and not
* created again. If the uploaded attachment is not a valid image this function does not alter the
* metadata of the attachment, on the other hand a `sources` property is added.
* `webp_uploads_get_supported_image_mime_transforms`. If the original image is one of the mimes from
* `webp_uploads_get_supported_image_mime_transforms` the image is just added to the `sources` property and not
* created again. If the uploaded attachment is not a supported mime by this function, the hook does not alter the
* metadata of the attachment. In addition to every single size the `sources` property is added at the
* top level of the image metadata to store the references for all the mime types for the `full` size image of the
* attachment.
*
* @since 1.0.0
*
* @see wp_generate_attachment_metadata()
* @see webp_uploads_valid_image_mime_types()
* @see webp_uploads_get_supported_image_mime_transforms()
*
* @param array $metadata An array with the metadata from this attachment.
* @param int $attachment_id The ID of the attachment where the hook was dispatched.
* @return array An array with the updated structure for the metadata before is stored in the database.
*/
function webp_uploads_create_sources_property( array $metadata, $attachment_id ) {
// Make sure we have some sizes to work with, otherwise avoid any work.
if ( empty( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ) {
return $metadata;
}
// This should take place only on the JPEG image.
$valid_mime_transforms = webp_uploads_get_supported_image_mime_transforms();

// Not a supported mime type to create the sources property.
$mime_type = get_post_mime_type( $attachment_id );
if ( ! isset( $valid_mime_transforms[ $mime_type ] ) ) {
return $metadata;
}

$file = get_attached_file( $attachment_id, true );

// File does not exist.
if ( ! file_exists( $file ) ) {
return $metadata;
}

$dirname = pathinfo( $file, PATHINFO_DIRNAME );
// Make sure the top level `sources` key is a valid array.
if ( ! isset( $metadata['sources'] ) || ! is_array( $metadata['sources'] ) ) {
$metadata['sources'] = array();
}

if ( empty( $metadata['sources'][ $mime_type ] ) ) {
$metadata['sources'][ $mime_type ] = array(
'file' => wp_basename( $file ),
'filesize' => filesize( $file ),
);
wp_update_attachment_metadata( $attachment_id, $metadata );
}

$original_size_data = array(
'width' => isset( $metadata['width'] ) ? (int) $metadata['width'] : 0,
'height' => isset( $metadata['height'] ) ? (int) $metadata['height'] : 0,
'crop' => false,
);

$original_directory = pathinfo( $file, PATHINFO_DIRNAME );
$filename = pathinfo( $file, PATHINFO_FILENAME );
$allowed_mimes = array_flip( wp_get_mime_types() );
// Create the sources for the full sized image.
foreach ( $valid_mime_transforms[ $mime_type ] as $targeted_mime ) {
// If this property exists no need to create the image again.
if ( ! empty( $metadata['sources'][ $targeted_mime ] ) ) {
continue;
}

// The targeted mime is not allowed in the current installation.
if ( empty( $allowed_mimes[ $targeted_mime ] ) ) {
continue;
}

$extension = explode( '|', $allowed_mimes[ $targeted_mime ] );
$destination = trailingslashit( $original_directory ) . "{$filename}.{$extension[0]}";
$image = webp_uploads_generate_additional_image_source( $attachment_id, $original_size_data, $targeted_mime, $destination );

if ( is_wp_error( $image ) ) {
continue;
}

$metadata['sources'][ $targeted_mime ] = $image;
wp_update_attachment_metadata( $attachment_id, $metadata );
}

// Make sure we have some sizes to work with, otherwise avoid any work.
if ( empty( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ) {
return $metadata;
}

foreach ( $metadata['sizes'] as $size_name => $properties ) {
// This image size is not defined or not an array.
if ( ! is_array( $properties ) ) {
Expand Down Expand Up @@ -77,7 +123,7 @@ function webp_uploads_create_sources_property( array $metadata, $attachment_id )
'filesize' => 0,
);
// Set the filesize from the current mime image.
$file_location = path_join( $dirname, $properties['file'] );
$file_location = path_join( $original_directory, $properties['file'] );
if ( file_exists( $file_location ) ) {
$properties['sources'][ $current_mime ]['filesize'] = filesize( $file_location );
}
Expand Down Expand Up @@ -118,6 +164,8 @@ function webp_uploads_create_sources_property( array $metadata, $attachment_id )
*
* @since 1.0.0
*
* @see wp_create_image_subsizes()
*
* @param int $attachment_id The ID of the attachment we are going to use as a reference to create the image.
* @param string $size The size name that would be used to create this image, out of the registered subsizes.
* @param string $mime A mime type we are looking to use to create this image.
Expand All @@ -136,16 +184,88 @@ function webp_uploads_generate_image_size( $attachment_id, $size, $mime ) {
return new WP_Error( 'image_mime_type_invalid_metadata', __( 'The image does not have a valid metadata.', 'performance-lab' ) );
}

// All subsizes are created out of the attached file.
$file = get_attached_file( $attachment_id );
$size_data = array(
'width' => 0,
'height' => 0,
'crop' => false,
);

if ( isset( $metadata['sizes'][ $size ]['width'] ) ) {
$size_data['width'] = $metadata['sizes'][ $size ]['width'];
} elseif ( isset( $sizes[ $size ]['width'] ) ) {
$size_data['width'] = $sizes[ $size ]['width'];
}

if ( isset( $metadata['sizes'][ $size ]['height'] ) ) {
$size_data['height'] = $metadata['sizes'][ $size ]['height'];
} elseif ( isset( $sizes[ $size ]['height'] ) ) {
$size_data['height'] = $sizes[ $size ]['height'];
}

if ( isset( $sizes[ $size ]['crop'] ) ) {
$size_data['crop'] = (bool) $sizes[ $size ]['crop'];
}

return webp_uploads_generate_additional_image_source( $attachment_id, $size_data, $mime );
}

/**
* Returns an array with the list of valid mime types that a specific mime type can be converted into it,
* for example an image/jpeg can be converted into an image/webp.
*
* @since 1.0.0
*
* @return array<string, array<string>> An array of valid mime types, where the key is the mime type and the value is the extension type.
*/
function webp_uploads_get_supported_image_mime_transforms() {
$image_mime_transforms = array(
'image/jpeg' => array( 'image/webp' ),
'image/webp' => array( 'image/jpeg' ),
);

$valid_transforms = array();
/**
* Filter to allow the definition of a custom mime types, in which a defined mime type
* can be transformed and provide a wide range of mime types.
*
* @since 1.0.0
*
* @param array $image_mime_transforms A map with the valid mime transforms.
*/
$transforms = (array) apply_filters( 'webp_uploads_supported_image_mime_transforms', $image_mime_transforms );
// Remove any invalid transform, by making sure all the transform values are arrays.
foreach ( $transforms as $mime => $list_transforms ) {
if ( ! is_array( $list_transforms ) ) {
continue;
}
$valid_transforms[ $mime ] = $list_transforms;
}
return $valid_transforms;
}

/**
* Creates a resized image with the provided dimensions out of an original attachment, the created image
* would be saved in the specified mime and stored in the destination file. If the image can't be saved correctly
* a WP_Error would be returned otherwise an array with the file and filesize properties.
*
* @since n.e.xt
* @private
*
* @param int $attachment_id The ID of the attachment from where this image would be created.
* @param array $size_data An array with the dimensions of the image: height, width and crop.
* @param string $mime The target mime in which the image should be created.
* @param string $destination_file_name The path where the file would be stored, including the extension. If empty, `generate_filename` is used to create the destination file name.
* @return array|WP_Error An array with the file and filesize if the image was created correctly otherwise a WP_Error
*/
function webp_uploads_generate_additional_image_source( $attachment_id, array $size_data, $mime, $destination_file_name = null ) {
$image_path = get_attached_file( $attachment_id );

// File does not exist.
if ( ! file_exists( $file ) ) {
if ( ! file_exists( $image_path ) ) {
return new WP_Error( 'image_file_size_not_found', __( 'The provided size does not have a valid image file.', 'performance-lab' ) );
}

// Create the subsizes out of the attached file.
$editor = wp_get_image_editor( $file );
$editor = wp_get_image_editor( $image_path );

if ( is_wp_error( $editor ) ) {
return $editor;
Expand All @@ -160,33 +280,22 @@ function webp_uploads_generate_image_size( $attachment_id, $size, $mime ) {
return new WP_Error( 'image_mime_type_not_supported', __( 'The provided mime type is not supported.', 'performance-lab' ) );
}

$extension = explode( '|', $allowed_mimes[ $mime ] );
$extension = reset( $extension );

$width = null;
$height = null;
$crop = false;
$height = isset( $size_data['height'] ) ? (int) $size_data['height'] : 0;
$width = isset( $size_data['width'] ) ? (int) $size_data['width'] : 0;
$crop = isset( $size_data['crop'] ) && $size_data['crop'];

if ( isset( $metadata['sizes'][ $size ]['width'] ) ) {
$width = $metadata['sizes'][ $size ]['width'];
} elseif ( isset( $sizes[ $size ]['width'] ) ) {
$width = $sizes[ $size ]['width'];
if ( $width <= 0 && $height <= 0 ) {
return new WP_Error( 'image_wrong_dimensions', __( 'At least one of the dimensions must be a positive number.', 'performance-lab' ) );
}

if ( isset( $metadata['sizes'][ $size ]['height'] ) ) {
$height = $metadata['sizes'][ $size ]['height'];
} elseif ( isset( $sizes[ $size ]['height'] ) ) {
$height = $sizes[ $size ]['height'];
}
$editor->resize( $width, $height, $crop );

if ( isset( $sizes[ $size ]['crop'] ) ) {
$crop = (bool) $sizes[ $size ]['crop'];
if ( null === $destination_file_name ) {
$extension = explode( '|', $allowed_mimes[ $mime ] );
$destination_file_name = $editor->generate_filename( null, null, $extension[0] );
}

$editor->resize( $width, $height, $crop );
$filename = $editor->generate_filename( null, null, $extension );
$filename = preg_replace( '/-(scaled|rotated|imagifyresized)/', '', $filename );
$image = $editor->save( $filename, $mime );
$image = $editor->save( $destination_file_name, $mime );

if ( is_wp_error( $image ) ) {
return $image;
Expand All @@ -202,31 +311,6 @@ function webp_uploads_generate_image_size( $attachment_id, $size, $mime ) {
);
}

/**
* Returns an array with the list of valid mime types that a specific mime type can be converted into it,
* for example an image/jpeg can be converted into an image/webp.
*
* @since 1.0.0
*
* @return array<string, array<string>> An array of valid mime types, where the key is the mime type and the value is the extension type.
*/
function webp_uploads_get_supported_image_mime_transforms() {
$image_mime_transforms = array(
'image/jpeg' => array( 'image/webp' ),
'image/webp' => array( 'image/jpeg' ),
);

/**
* Filter to allow the definition of a custom mime types, in which a defined mime type
* can be transformed and provide a wide range of mime types.
*
* @since 1.0.0
*
* @param array $image_mime_transforms A map with the valid mime transforms.
*/
return (array) apply_filters( 'webp_uploads_supported_image_mime_transforms', $image_mime_transforms );
}

/**
* Hook fired when an attachment is deleted, this hook is in charge of removing any
* additional mime types created by this plugin besides the original image. Any source
Expand All @@ -241,17 +325,16 @@ function webp_uploads_get_supported_image_mime_transforms() {
* @param int $attachment_id The ID of the attachment the sources are going to be deleted.
*/
function webp_uploads_remove_sources_files( $attachment_id ) {
$metadata = wp_get_attachment_metadata( $attachment_id );
$file = get_attached_file( $attachment_id );
$file = get_attached_file( $attachment_id );

if (
! isset( $metadata['sizes'] )
|| empty( $file )
|| ! is_array( $metadata['sizes'] )
) {
if ( empty( $file ) ) {
return;
}

$metadata = wp_get_attachment_metadata( $attachment_id );
// Make sure $sizes is always defined to allow the removal of original images after the first foreach loop.
$sizes = ! isset( $metadata['sizes'] ) || ! is_array( $metadata['sizes'] ) ? array() : $metadata['sizes'];

$upload_path = wp_get_upload_dir();
if ( empty( $upload_path['basedir'] ) ) {
return;
Expand All @@ -260,7 +343,7 @@ function webp_uploads_remove_sources_files( $attachment_id ) {
$intermediate_dir = path_join( $upload_path['basedir'], dirname( $file ) );
$basename = wp_basename( $file );

foreach ( $metadata['sizes'] as $size ) {
foreach ( $sizes as $size ) {
if ( ! isset( $size['sources'] ) || ! is_array( $size['sources'] ) ) {
continue;
}
Expand All @@ -284,11 +367,47 @@ function webp_uploads_remove_sources_files( $attachment_id ) {
}

$intermediate_file = str_replace( $basename, $properties['file'], $file );
if ( ! empty( $intermediate_file ) ) {
$intermediate_file = path_join( $upload_path['basedir'], $intermediate_file );
wp_delete_file_from_directory( $intermediate_file, $intermediate_dir );
if ( empty( $intermediate_file ) ) {
continue;
}

$intermediate_file = path_join( $upload_path['basedir'], $intermediate_file );
if ( ! file_exists( $intermediate_file ) ) {
continue;
}

wp_delete_file_from_directory( $intermediate_file, $intermediate_dir );
}
}

if ( ! isset( $metadata['sources'] ) || ! is_array( $metadata['sources'] ) ) {
return;
}

$original_mime_from_post = get_post_mime_type( $attachment_id );
$original_mime_from_file = wp_check_filetype( $file )['type'];

// Delete full sizes mime types.
foreach ( $metadata['sources'] as $mime => $properties ) {
// Don't remove the image with the same mime type as the original image as this would be removed by WordPress.
if ( $mime === $original_mime_from_post || $mime === $original_mime_from_file ) {
continue;
}

if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
continue;
}

$full_size = str_replace( $basename, $properties['file'], $file );
if ( empty( $full_size ) ) {
continue;
}

$full_size_file = path_join( $upload_path['basedir'], $full_size );
if ( ! file_exists( $full_size_file ) ) {
continue;
}
wp_delete_file_from_directory( $full_size_file, $intermediate_dir );
}
}

Expand Down
Loading