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
7 changes: 4 additions & 3 deletions includes/class-windows-azure-helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -509,10 +509,11 @@ static public function put_media_to_blob_storage( $container_name, $blob_name, $
* @param string $source_path Local path.
* @param string $account_name Account name.
* @param string $account_key Account key.
* @param int $cache Max-age cache
*
* @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 +522,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
270 changes: 211 additions & 59 deletions includes/class-windows-azure-replace-media.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,20 @@

class Windows_Azure_Replace_Media {

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

/**
* Container name from plugin config
*
* @var string
*/
private $container_name = '';
hugosolar marked this conversation as resolved.
Show resolved Hide resolved

/**
* Class constructor
*
Expand All @@ -55,13 +69,28 @@ 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_azure-storage-media-replace', array( $this, 'process_media_replacement' ) );

/**
* Set a list of mime types allowed to be replaced.
*
* azure_blob_storage_allowed_types_replace filters the default list of mime types allowed to be replaced, for now just common images and pdf files.
*
* @since 4.2.3
*
* @param array $types array of allowed mime types
*/
$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 +110,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 All @@ -98,7 +127,7 @@ public function register_azure_fields_attachment_editor( $form_fields, $post ) {
* @return void
*/
public function enqueue_replace_media_script() {
$js_ext = ( ! defined( 'SCRIPT_DEBUG' ) || false === SCRIPT_DEBUG ) ? '.min.js' : '.js';
$js_ext = ( ! defined( 'SCRIPT_DEBUG' ) || false === SCRIPT_DEBUG ) ? '.min.js' : '.js';
wp_enqueue_script( 'windows-azure-storage-media-replace', MSFT_AZURE_PLUGIN_URL . 'js/windows-azure-storage-media-replace' . $js_ext, array( 'jquery', 'media-editor' ), MSFT_AZURE_PLUGIN_VERSION, true );

wp_localize_script(
Expand Down Expand Up @@ -131,6 +160,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 +198,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 +220,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 +244,198 @@ 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'] );
}

if ( ! empty( $replace_data['meta_data']['sizes'] ) ) {

// 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( $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();

update_post_meta( $source_attachment_id, 'windows_azure_storage_info', $source_azure_data );
// 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() ) );
}
}
}
}

wp_delete_attachment( $media_to_replace_id, true );
/**
* 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 ) {
// Bail early if no attachment meta field
if ( empty( $source_sizes['meta_data'] ) || empty( $target_sizes['meta_data'] ) ) {
return;
}

if ( empty( $source_sizes['meta_data']['file'] ) || empty( $target_sizes['meta_data']['file'] ) ) {
return;
}

$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;
$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 );

if ( empty( $source_sizes['meta_data']['sizes'] ) || empty( $target_sizes['meta_data']['sizes'] ) ) {
return;
}

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;
}
}
Loading
Loading