Skip to content

Commit ad0a330

Browse files
authored
Merge pull request #242 from WordPress/feature/228-restore-and-backup-image-sources
Restore and backup image sizes alongside the sources properties
2 parents 04e996f + 188c7df commit ad0a330

3 files changed

Lines changed: 655 additions & 0 deletions

File tree

‎modules/images/webp-uploads/load.php‎

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,3 +683,189 @@ function webp_uploads_update_rest_attachment( WP_REST_Response $response, WP_Pos
683683
return rest_ensure_response( $data );
684684
}
685685
add_filter( 'rest_prepare_attachment', 'webp_uploads_update_rest_attachment', 10, 3 );
686+
687+
/**
688+
* Inspect if the current call to `wp_update_attachment_metadata()` was done from within the context
689+
* of an edit to an attachment either restore or other type of edit, in that case we perform operations
690+
* to save the sources properties, specifically for the `full` size image due this is a virtual image size.
691+
*
692+
* @since n.e.x.t
693+
*
694+
* @see wp_update_attachment_metadata()
695+
*
696+
* @param array $data The current metadata of the attachment.
697+
* @param int $attachment_id The ID of the current attachment.
698+
* @return array The updated metadata for the attachment to be stored in the meta table.
699+
*/
700+
function webp_uploads_update_attachment_metadata( $data, $attachment_id ) {
701+
$trace = debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS, 10 );
702+
703+
foreach ( $trace as $element ) {
704+
if ( ! isset( $element['function'] ) ) {
705+
continue;
706+
}
707+
708+
switch ( $element['function'] ) {
709+
case 'wp_save_image':
710+
// Right after an image has been edited.
711+
return webp_uploads_backup_sources( $attachment_id, $data );
712+
case 'wp_restore_image':
713+
// When an image has been restored.
714+
return webp_uploads_restore_image( $attachment_id, $data );
715+
}
716+
}
717+
718+
return $data;
719+
}
720+
add_filter( 'wp_update_attachment_metadata', 'webp_uploads_update_attachment_metadata', 10, 2 );
721+
722+
/**
723+
* Before saving the metadata of the image store a backup values for the sources and file property
724+
* those files would be used and deleted by the backup mechanism, right after the metadata has
725+
* been updated. It removes the current sources property due once this function is executed
726+
* right after an edit has taken place and the current sources are no longer accurate.
727+
*
728+
* @since n.e.x.t
729+
*
730+
* @param int $attachment_id The ID representing the attachment.
731+
* @param array $data The current metadata of the attachment.
732+
* @return array The updated metadata for the attachment.
733+
*/
734+
function webp_uploads_backup_sources( $attachment_id, $data ) {
735+
$target = isset( $_REQUEST['target'] ) ? $_REQUEST['target'] : 'all';
736+
737+
// When an edit to an image is only applied to a thumbnail there's nothing we need to back up.
738+
if ( 'thumbnail' === $target ) {
739+
return $data;
740+
}
741+
742+
$metadata = wp_get_attachment_metadata( $attachment_id );
743+
// Nothing to back up.
744+
if ( ! isset( $metadata['sources'] ) ) {
745+
return $data;
746+
}
747+
748+
$sources = $metadata['sources'];
749+
// Prevent execution of the callbacks more than once if the callback was already executed.
750+
$has_been_processed = false;
751+
752+
$hook = function ( $meta_id, $post_id, $meta_name ) use ( $attachment_id, $sources, &$has_been_processed ) {
753+
// Make sure this hook is only executed in the same context for the provided $attachment_id.
754+
if ( $post_id !== $attachment_id ) {
755+
return;
756+
}
757+
758+
// This logic should work only if we are looking at the meta key: `_wp_attachment_backup_sizes`.
759+
if ( '_wp_attachment_backup_sizes' !== $meta_name ) {
760+
return;
761+
}
762+
763+
if ( $has_been_processed ) {
764+
return;
765+
}
766+
767+
$has_been_processed = true;
768+
webp_uploads_backup_full_image_sources( $post_id, $sources );
769+
};
770+
771+
add_action( 'added_post_meta', $hook, 10, 3 );
772+
add_action( 'updated_post_meta', $hook, 10, 3 );
773+
774+
// Remove the current sources as at this point the current values are no longer accurate.
775+
// TODO: Requires to be updated from https://github.com/WordPress/performance/issues/158.
776+
unset( $data['sources'] );
777+
778+
return $data;
779+
}
780+
781+
/**
782+
* Stores the provided sources for the attachment ID in the `_wp_attachment_backup_sources` with
783+
* the next available target if target is `null` no source would be stored.
784+
*
785+
* @since n.e.x.t
786+
*
787+
* @param int $attachment_id The ID of the attachment.
788+
* @param array $sources An array with the full sources to be stored on the next available key.
789+
*/
790+
function webp_uploads_backup_full_image_sources( $attachment_id, $sources ) {
791+
if ( empty( $sources ) ) {
792+
return;
793+
}
794+
795+
$target = webp_uploads_get_next_full_size_key_from_backup( $attachment_id );
796+
if ( null === $target ) {
797+
return;
798+
}
799+
800+
$backup_sources = get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true );
801+
$backup_sources = is_array( $backup_sources ) ? $backup_sources : array();
802+
$backup_sources[ $target ] = $sources;
803+
// Store the `sources` property into the full size if present.
804+
update_post_meta( $attachment_id, '_wp_attachment_backup_sources', $backup_sources );
805+
}
806+
807+
/**
808+
* It finds the next available `full-{orig or hash}` key on the images if the name
809+
* has not been used as part of the backup sources it would be used if no size is
810+
* found or backup exists `null` would be returned instead.
811+
*
812+
* @since n.e.x.t
813+
*
814+
* @param int $attachment_id The ID of the attachment.
815+
* @return null|string The next available full size name.
816+
*/
817+
function webp_uploads_get_next_full_size_key_from_backup( $attachment_id ) {
818+
$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
819+
$backup_sizes = is_array( $backup_sizes ) ? $backup_sizes : array();
820+
821+
if ( empty( $backup_sizes ) ) {
822+
return null;
823+
}
824+
825+
$backup_sources = get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true );
826+
$backup_sources = is_array( $backup_sources ) ? $backup_sources : array();
827+
foreach ( array_keys( $backup_sizes ) as $size_name ) {
828+
// If the target already has the sources attributes find the next one.
829+
if ( isset( $backup_sources[ $size_name ] ) ) {
830+
continue;
831+
}
832+
833+
// We are only interested in the `full-` sizes.
834+
if ( strpos( $size_name, 'full-' ) === false ) {
835+
continue;
836+
}
837+
838+
return $size_name;
839+
}
840+
841+
return null;
842+
}
843+
844+
/**
845+
* Restore an image from the backup sizes, the current hook moves the `sources` from the `full-orig` key into
846+
* the top level `sources` into the metadata, in order to ensure the restore process has a reference to the right
847+
* images.
848+
*
849+
* @since n.e.x.t
850+
*
851+
* @param int $attachment_id The ID of the attachment.
852+
* @param array $data The current metadata to be stored in the attachment.
853+
* @return array The updated metadata of the attachment.
854+
*/
855+
function webp_uploads_restore_image( $attachment_id, $data ) {
856+
$backup_sources = get_post_meta( $attachment_id, '_wp_attachment_backup_sources', true );
857+
858+
if ( ! is_array( $backup_sources ) ) {
859+
return $data;
860+
}
861+
862+
if ( ! isset( $backup_sources['full-orig'] ) || ! is_array( $backup_sources['full-orig'] ) ) {
863+
return $data;
864+
}
865+
866+
// TODO: Handle the case If `IMAGE_EDIT_OVERWRITE` is defined and is truthy remove any edited images if present before replacing the metadata.
867+
// See: https://github.com/WordPress/performance/issues/158.
868+
$data['sources'] = $backup_sources['full-orig'];
869+
870+
return $data;
871+
}

0 commit comments

Comments
 (0)