Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add image replacement feature #230

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
6 changes: 3 additions & 3 deletions includes/class-windows-azure-helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ static public function put_media_to_blob_storage( $container_name, $blob_name, $
*
* @return bool|string|WP_Error False or WP_Error on failure URI on success.
*/
static public function copy_media_to_blob_storage( $container_name, $destination_path, $source_path, $mime_type, $account_name = '', $account_key = '' ) {
static public function copy_media_to_blob_storage( $container_name, $destination_path, $source_path, $mime_type, $account_name = '', $account_key = '', $cache = null ) {
hugosolar marked this conversation as resolved.
Show resolved Hide resolved
list( $account_name, $account_key ) = self::get_api_credentials( $account_name, $account_key );
$rest_api_client = new Windows_Azure_Rest_Api_Client( $account_name, $account_key );

Expand All @@ -521,12 +521,12 @@ static public function copy_media_to_blob_storage( $container_name, $destination
return $result;
}

$cache_control = Windows_Azure_Helper::get_cache_control();
$cache_control = ( empty( $cache ) ) ? Windows_Azure_Helper::get_cache_control() : $cache;
if ( is_numeric( $cache_control ) ) {
$cache_control = sprintf( "max-age=%d, must-revalidate", $cache_control );
}

$rest_api_client->put_blob_properties( $container_name, $destination_path, array(
$rest_api_client->put_blob_properties( $container_name, $source_path, array(
Windows_Azure_Rest_Api_Client::API_HEADER_MS_BLOB_CONTENT_TYPE => $mime_type,
Windows_Azure_Rest_Api_Client::API_HEADER_MS_BLOB_CACHE_CONTROL => apply_filters( 'windows_azure_blob_cache_control', $cache_control ),
Windows_Azure_Rest_Api_Client::API_HEADER_MS_ACCESS_TIER => apply_filters( 'windows_azure_blob_access_tier', 'Hot' ),
Expand Down
246 changes: 188 additions & 58 deletions includes/class-windows-azure-replace-media.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@

class Windows_Azure_Replace_Media {

/**
* Allowed types to be replaced
*
* @var array
*/
private $allowed_types = [];

private $container_name = '';
hugosolar marked this conversation as resolved.
Show resolved Hide resolved

/**
* Class constructor
*
Expand All @@ -55,13 +64,21 @@ public function __construct() {
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_replace_media_script' ) );

// ajax event to replace media
add_action( 'wp_ajax_nopriv_azure-storage-media-replace', array( $this, 'process_media_replacement' ) );
add_action( 'wp_ajax_azure-storage-media-replace', array( $this, 'process_media_replacement' ) );

// Ajax event to set transient for replacement
add_action( 'wp_ajax_nopriv_azure-storage-media-replace-set-transient', array( $this, 'set_media_replacement_transient' ) );
add_action( 'wp_ajax_azure-storage-media-replace-set-transient', array( $this, 'set_media_replacement_transient' ) );
add_action( 'wp_ajax_nopriv_azure-storage-media-replace', array( $this, 'process_media_replacement' ) );
hugosolar marked this conversation as resolved.
Show resolved Hide resolved
add_action( 'wp_ajax_azure-storage-media-replace', array( $this, 'process_media_replacement' ) );

// Set allowed mime types to be replaced
$this->allowed_types = apply_filters(
hugosolar marked this conversation as resolved.
Show resolved Hide resolved
'azure_blob_storage_allowed_types_replace',
array(
'image/jpeg',
'image/png',
'image/gif',
'image/bmp',
'image/webp',
'application/pdf',
)
);
}


Expand All @@ -81,7 +98,7 @@ public function register_azure_fields_attachment_editor( $form_fields, $post ) {
}
wp_enqueue_media();
$mime_type = get_post_mime_type( $post->ID );
if ( 'application/pdf' === $mime_type ) {
if ( in_array( $mime_type, $this->allowed_types, true ) ) {
$form_fields['azure-media-replace'] = array(
'label' => esc_html__( 'Replace media', 'windows-azure-storage' ),
'input' => 'html',
Expand Down Expand Up @@ -131,6 +148,8 @@ public function process_media_replacement() {
$current_attachment = filter_input( INPUT_POST, 'current_attachment', FILTER_VALIDATE_INT );
$replace_attachment = filter_input( INPUT_POST, 'replace_attachment', FILTER_VALIDATE_INT );

$this->container_name = \Windows_Azure_Helper::get_default_container();

wp_send_json( $this->replace_media_with( $current_attachment, $replace_attachment ) );
}

Expand Down Expand Up @@ -167,18 +186,18 @@ private function replace_media_with( $source_attachment_id, $media_to_replace_id
return esc_html__( 'File type mismatch', 'windows-azure-storage' );
}

// Let's replace the file remotely
$default_azure_storage_account_container_name = \Windows_Azure_Helper::get_default_container();

// only upload file if file exists locally
try {
$full_blob_url = \Windows_Azure_Helper::get_full_blob_url( $replace_file );
if ( ! empty( $full_blob_url ) ) {
\Windows_Azure_Helper::copy_media_to_blob_storage(
$default_azure_storage_account_container_name,
$this->container_name,
$replace_file,
$source_file,
$replace_filetype['type']
$replace_filetype['type'],
'',
'',
30
);
}
} catch ( Exception $e ) {
Expand All @@ -189,9 +208,9 @@ private function replace_media_with( $source_attachment_id, $media_to_replace_id
$replacement = array();

$replacement['is_image'] = $this->is_image( $source_filetype );
$replacement['file_name'] = basename( $replacement['original_image'] );
$replacement['file_name'] = basename( $replace_file );

$replacement = $this->media_meta_replacement( $source_attachment_id, $media_to_replace_id );
$replacement = array_merge( $replacement, $this->media_meta_replacement_prepare( $source_attachment_id, $media_to_replace_id ) );

return $replacement;
}
Expand All @@ -213,77 +232,188 @@ private function is_image( $filetype ) {
* @param int $media_to_replace_id Replacement file ID
* @return array
*/
private function media_meta_replacement( $source_attachment_id, $media_to_replace_id ) {
private function media_meta_replacement_prepare( $source_attachment_id, $media_to_replace_id ) {
$replacement_meta_attachment_file = get_post_meta( $media_to_replace_id, '_wp_attached_file', true );
$replacement_azure_data = get_post_meta( $media_to_replace_id, 'windows_azure_storage_info', true );
$replacement_attachment_data = get_post_meta( $media_to_replace_id, '_wp_attachment_metadata', true );
$replace_filename = pathinfo( basename( $replacement_meta_attachment_file ) );
$replacement_mime_type = get_post_mime_type( $media_to_replace_id );

$source_meta_attachment_file = get_post_meta( $source_attachment_id, '_wp_attached_file', true );
$source_azure_data = get_post_meta( $source_attachment_id, 'windows_azure_storage_info', true );
$source_attachment_data = get_post_meta( $source_attachment_id, '_wp_attachment_metadata', true );
$source_attachment_version = get_post_meta( $source_attachment_id, '_wp_attachment_replace_version', true );
$source_filename = pathinfo( basename( $source_meta_attachment_file ) );
$source_mime_type = get_post_mime_type( $source_attachment_id );

if ( empty( $source_attachment_data ) ) {
$source_attachment_version = 1;
}
$new_version = ++$source_attachment_version;

$replace_data = array(
'id' => $media_to_replace_id,
'meta_file' => $replacement_meta_attachment_file,
'meta_azure' => $replacement_azure_data,
'meta_data' => $replacement_attachment_data,
'version' => 0,
'filename' => $replace_filename,
'mime_type' => $replacement_mime_type,
);

$source_data = array(
'id' => $source_attachment_id,
'meta_file' => $source_meta_attachment_file,
'meta_azure' => $source_azure_data,
'meta_data' => $source_attachment_data,
'version' => $new_version,
'filename' => $source_filename,
'mime_type' => $source_mime_type,
);

$return_data = array();

$return_data['ID'] = $source_attachment_id;
$return_data['old_ID'] = $media_to_replace_id;

$source_filename = pathinfo( basename( $source_meta_attachment_file ) );
$replace_filename = pathinfo( basename( $replacement_meta_attachment_file ) );
$return_replacement = $this->process_media_thumbnails( $source_data, $replace_data );

if ( 'pdf' === $source_filename['extension'] ) {
unset( $source_attachment_data['sizes'] );
if ( ! empty( $replacement_attachment_data['sizes'] ) ) {
foreach ( $replacement_attachment_data['sizes'] as $size_key => $size_data ) {
$size_data['file'] = str_replace( $replace_filename, $source_filename, $size_data['file'] );
$source_attachment_data['sizes'][ $size_key ] = $size_data;
}
return array_merge( $return_data, $return_replacement );
}

update_post_meta( $source_attachment_id, '_wp_attachment_metadata', $source_attachment_data );
/**
* Process media and replace
*
* @param array $source_data source data
* @param array $replace_data replacement file data
* @return array
*/
public function process_media_thumbnails( $source_data, $replace_data ) {

$sizes = $this->find_nearest_size( $source_data, $replace_data );

if ( ! empty( $sizes ) ) {
foreach ( $sizes as $size_key => $size_data ) {
$source_data['meta_data']['sizes'][ $size_key ] = $size_data['source_data'];
}

if ( ! empty( $replacement_azure_data['thumbnails'] ) ) {
// Let's replace the file remotely
$default_azure_storage_account_container_name = \Windows_Azure_Helper::get_default_container();

unset( $source_azure_data['thumbnails'] );

foreach ( $replacement_azure_data['thumbnails'] as $thumbnails ) {
$new_filename = str_replace( $replace_filename, $source_filename, $thumbnails );
$source_azure_data['thumbnails'][] = $new_filename;
try {
$full_blob_url = \Windows_Azure_Helper::get_full_blob_url( $thumbnails );
if ( ! empty( $full_blob_url ) ) {
\Windows_Azure_Helper::copy_media_to_blob_storage(
$default_azure_storage_account_container_name,
$thumbnails,
$new_filename,
$replacement_mime_type
);
}
} catch ( Exception $e ) {
// translators: %s would be an error message
printf( esc_html__( 'Error in uploading file. Error: %s', 'windows-azure-storage' ), esc_html( $e->getMessage() ) );
}
}
update_post_meta( $source_data['id'], '_wp_attachment_metadata', $source_data['meta_data'] );
}

update_post_meta( $source_attachment_id, 'windows_azure_storage_info', $source_azure_data );
if ( ! empty( $replace_data['meta_data']['sizes'] ) ) {
// Remove previous thumbnails
// $this->delete_previous_thumbnails( $source_data );
hugosolar marked this conversation as resolved.
Show resolved Hide resolved

// Let's replace the file remotely
foreach ( $sizes as $source_size => $sizes_source_data ) {
try {
\Windows_Azure_Helper::copy_media_to_blob_storage(
$this->container_name,
$sizes_source_data['replace_file'],
$sizes_source_data['source_file'],
$replace_data['mime_type'],
'',
'',
30,
);
} catch ( Exception $e ) {
// translators: %s would be an error message
printf( esc_html__( 'Error in uploading file. Error: %s', 'windows-azure-storage' ), esc_html( $e->getMessage() ) );
}
}
}

wp_delete_attachment( $media_to_replace_id, true );
wp_delete_attachment( $replace_data['id'], true );

return $source_data;
}

/**
* Delete blobs from source
*
* @param array $data source or replacement data
* @return void
*/
public function delete_previous_thumbnails( $data ) {
$default_azure_storage_account_container_name = \Windows_Azure_Helper::get_default_container();

// delete remaining blobs from source
if ( ! empty( $data['meta_azure']['thumbnails'] ) ) {
foreach ( $data['meta_azure']['thumbnails'] as $blob_location ) {
try {
$full_blob_url = \Windows_Azure_Helper::get_full_blob_url( $blob_location );
if ( ! empty( $full_blob_url ) ) {
\Windows_Azure_Helper::delete_blob(
$default_azure_storage_account_container_name,
$blob_location,
);
}
} catch ( Exception $e ) {
// translators: %s would be an error message
printf( esc_html__( 'Blob could not be removed. Error: %s', 'windows-azure-storage' ), esc_html( $e->getMessage() ) );
}
}
}
}

$return_data['original_image'] = $source_filename;
$return_data['attachment_data'] = $source_attachment_data;
$return_data['attachment_data'] = $source_attachment_data;
$return_data['azure_data'] = $source_azure_data;
$return_data['version'] = $new_version;
/**
* Get the nearest size from the replacement image
*
* @param array $source_sizes Source image data
* @param array $target_sizes Replacement image data
* @return array
*/
private function find_nearest_size( $source_sizes, $target_sizes ) {

$convert_sizes = array();
$filename_path = $source_sizes['meta_data']['file'];
$file_path = dirname( $filename_path );

$target_filename = $target_sizes['meta_data']['file'];
Comment on lines +386 to +389
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we need to be more defensive here? For both $source_sizes and $target_sizes, will these always be arrays and will the keys we want always exist? Or do we need to check for those before proceeding?

Copy link
Author

@hugosolar hugosolar Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dkotter since this array is made above, it stores WordPress default _wp_attachment_metadata so it's always expected to exist if it's a valid attachment. Although I agree with the extra check because we never knows if we're about to handle an invalid attachment or a similar situation.
I've added some extra checks to bail early

$target_file_path = dirname( $target_filename );

foreach ( $source_sizes['meta_data']['sizes'] as $size => $size_data ) {
$target_width = ! empty( $target_sizes['meta_data']['sizes'][ $size ] ) ? $target_sizes['meta_data']['sizes'][ $size ]['width'] : 0;
$source_width = $size_data['width'];

$diff = abs( $source_width - $target_width );

$target_file = $target_sizes['meta_data']['sizes'][ $size ];
$convert_sizes[ $size ] = array(
'source_file' => $file_path . '/' . $size_data['file'],
'replace_file' => $target_file_path . '/' . $target_file['file'],
'source_data' => array(
'file' => $size_data['file'],
'width' => $target_file['width'],
'height' => $target_file['height'],
'mime-type' => $target_file['mime-type'],
'filesize' => $target_file['filesize'],
),
);

foreach ( $target_sizes['meta_data']['sizes'] as $target_size => $target_data ) {
$target_width = $target_data['width'];
$source_width = $size_data['width'];

$new_diff = abs( $source_width - $target_width );

if ( $new_diff < $diff ) {
$diff = $new_diff;

$convert_sizes[ $size ] = array(
'source_file' => $file_path . '/' . $size_data['file'],
'replace_file' => $target_file_path . '/' . $target_data['file'],
'source_data' => array(
'file' => $size_data['file'],
'width' => $target_data['width'],
'height' => $target_data['height'],
'mime-type' => $target_data['mime-type'],
'filesize' => $target_data['filesize'],
),
);
}
}
}

return $return_data;
return $convert_sizes;
}
}
11 changes: 10 additions & 1 deletion includes/class-windows-azure-rest-api-client.php
Original file line number Diff line number Diff line change
Expand Up @@ -923,7 +923,11 @@ public function sanitize_blobs_names( $container, array $files = array() ) {
$cycles = 0;
do {
$sanitized_group_contents = $this->_sanitize_remote_paths( $container, $prefix_group, $group_contents );
$was_sanitized = $sanitized_group_contents !== $group_contents;
if ( is_wp_error( $sanitized_group_contents ) ) {
continue;
}

$was_sanitized = $sanitized_group_contents !== $group_contents;
if ( $was_sanitized ) {
$group_contents = $sanitized_group_contents;
}
Expand Down Expand Up @@ -1033,6 +1037,7 @@ public function copy_blob( $container, $source_path, $destination_path ) {
} catch ( Exception $exception ) {
return new \WP_Error( $exception->getMessage() );
}
$destination_path = '/' . ltrim( $destination_path, '/' );

return $this->_build_api_endpoint_url( $container . $destination_path );
}
Expand Down Expand Up @@ -1133,6 +1138,10 @@ protected function _build_canonicalized_resource( $url, $account_name ) {
* @return array|WP_Error Sanitized remote paths array or WP_Error on failure.
*/
protected function _sanitize_remote_paths( $container, $prefix_group, $group_contents ) {
if ( ! is_array( $group_contents ) ) {
return new \WP_Error( -1, __( 'Error when sanitizing filename.', 'windows-azure-storage' ) );
}

$remote_paths = array_flip( $group_contents );
$blobs = $this->list_blobs( $container, $prefix_group );

Expand Down
Loading
Loading