From eb0e1144ccc077ea9f7d6abd50433caebc15f761 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 23 Jul 2020 08:01:44 +0200 Subject: [PATCH 01/10] add generate new public ID and check methods on sync --- .../php/class-sync.php | 24 +++++++++++++++++++ .../php/connect/class-api.php | 14 +++++++++++ .../php/sync/class-push-sync.php | 11 ++++++--- 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php index a33f2208f..707f633f0 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php @@ -172,6 +172,30 @@ public function get_signature( $post_id ) { return $return; } + /** + * Generate a new Public ID for an asset. + * + * @param int $attachment_id The attachment ID for the new public ID. + * + * @return string + */ + public function generate_public_id( $attachment_id, $prefix = null ) { + $settings = $this->plugin->config['settings']; + $cld_folder = trailingslashit( $settings['sync_media']['cloudinary_folder'] ); + $file = get_attached_file( $attachment_id ); + $file_info = pathinfo( $file ); + $public_id = $cld_folder . $prefix . $file_info['filename']; + + $cld_asset = $this->plugin->components['connect']->api->get_asset_details( $public_id, 'image' ); + if ( ! is_wp_error( $cld_asset ) && ! empty( $cld_asset['public_id'] ) ) { + // Exists, generate a new ID with a prefix. + $prefix = uniqid() . '-'; + $public_id = $this->generate_public_id( $attachment_id, $prefix ); + } + + return $public_id; + } + /** * Additional component setup. */ diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/connect/class-api.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/connect/class-api.php index 279a7da93..08d80d6ec 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/connect/class-api.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/connect/class-api.php @@ -269,6 +269,20 @@ public function cloudinary_url( $public_id, $args = array(), $size = array(), $c return implode( '/', $url_parts ); } + /** + * Get the details of an asset by public ID. + * + * @param string $public_id The public_id to check. + * @param string $type The asset type. + * + * @return array|\WP_Error + */ + public function get_asset_details( $public_id, $type ) { + $url = $this->url( 'resources', $type . '/upload/' . $public_id, true ); + + return $this->call( $url, array( 'body' => $args ), 'get' ); + } + /** * Upload a large asset in chunks. * diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php index 09ef6f25d..c39fb9442 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php @@ -331,11 +331,11 @@ private function get_sync_type( $attachment ) { * Prepare an attachment for upload. * * @param int|\WP_Post $post The attachment to prepare. - * @param bool $down_sync Flag to determine if a missing file starts a downsync. + * @param bool $push_sync Flag to determine if this prep is for a sync. True = build for syncing, false = build for validation. * * @return array|\WP_Error */ - public function prepare_upload( $post, $down_sync = false ) { + public function prepare_upload( $post, $push_sync = false ) { if ( is_numeric( $post ) ) { $post = get_post( $post ); @@ -365,7 +365,7 @@ public function prepare_upload( $post, $down_sync = false ) { $src = get_post_meta( $post->ID, '_wp_attached_file', true ); if ( $media->is_cloudinary_url( $src ) ) { // Download first maybe. - if ( true === $down_sync ) { + if ( true === $push_sync ) { $download = $this->plugin->components['sync']->managers['download']->down_sync( $post->ID ); if ( is_wp_error( $download ) ) { update_post_meta( $post->ID, Sync::META_KEYS['sync_error'], $download->get_error_message() ); @@ -403,8 +403,13 @@ public function prepare_upload( $post, $down_sync = false ) { $public_id = $post->{Sync::META_KEYS['public_id']}; // use the __get method on the \WP_Post to get post_meta. $cld_folder = trailingslashit( $settings['sync_media']['cloudinary_folder'] ); if ( empty( $public_id ) ) { + // Build a default ID. $file_info = pathinfo( $file ); $public_id = $cld_folder . $file_info['filename']; + // Check if this is a sync prep. If so, generate a safe ID. + if ( true === $push_sync ) { + $public_id = $this->plugin->components['sync']->generate_public_id( $post->ID ); + } } // Assume that the public_id is a root item. From c2f5f7ef26382d34cdb113586696472e96640dd5 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 23 Jul 2020 08:29:13 +0200 Subject: [PATCH 02/10] add generate new public ID if image, video, or audio only --- .../php/class-sync.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php index 707f633f0..09afc3647 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php @@ -177,7 +177,7 @@ public function get_signature( $post_id ) { * * @param int $attachment_id The attachment ID for the new public ID. * - * @return string + * @return string|null */ public function generate_public_id( $attachment_id, $prefix = null ) { $settings = $this->plugin->config['settings']; @@ -185,10 +185,20 @@ public function generate_public_id( $attachment_id, $prefix = null ) { $file = get_attached_file( $attachment_id ); $file_info = pathinfo( $file ); $public_id = $cld_folder . $prefix . $file_info['filename']; - - $cld_asset = $this->plugin->components['connect']->api->get_asset_details( $public_id, 'image' ); + // Get the type. + if ( wp_attachment_is( 'image', $attachment_id ) ) { + $type = 'image'; + } elseif ( wp_attachment_is( 'video', $attachment_id ) ) { + $type = 'video'; + } elseif ( wp_attachment_is( 'video', $attachment_id ) ) { + $type = 'audio'; + } else { + // not supported. + return null; + } + $cld_asset = $this->plugin->components['connect']->api->get_asset_details( $public_id, $type ); if ( ! is_wp_error( $cld_asset ) && ! empty( $cld_asset['public_id'] ) ) { - // Exists, generate a new ID with a prefix. + // Exists, generate a new ID with a uniqueID prefix. $prefix = uniqid() . '-'; $public_id = $this->generate_public_id( $attachment_id, $prefix ); } From 8422db947dc2d5ca680a1b770a190d0f18f8f65b Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 23 Jul 2020 08:48:29 +0200 Subject: [PATCH 03/10] shorten component usage --- .../php/sync/class-push-sync.php | 101 +++++++++++------- 1 file changed, 65 insertions(+), 36 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php index c39fb9442..d1cac64c1 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php @@ -47,6 +47,34 @@ class Push_Sync { */ protected $post_id; + /** + * Holds the media component. + * + * @var \Cloudinary\Media + */ + protected $media; + + /** + * Holds the sync component. + * + * @var \Cloudinary\Sync\ + */ + protected $sync; + + /** + * Holds the connect component. + * + * @var \Cloudinary\Connect + */ + protected $connect; + + /** + * Holds the Rest_API component. + * + * @var \Cloudinary\REST_API + */ + protected $api; + /** * Push_Sync constructor. * @@ -55,6 +83,12 @@ class Push_Sync { public function __construct( \Cloudinary\Plugin $plugin ) { $this->plugin = $plugin; + // Setup components. + $this->media = $this->plugin->components['media']; + $this->sync = $this->plugin->components['sync']; + $this->connect = $this->plugin->components['connect']; + $this->api = $this->plugin->components['api']; + // Define the sync types and their option keys. $sync_types = array( 'cloud_name' => 'upload', @@ -143,7 +177,7 @@ public function rest_get_queue_status() { return rest_ensure_response( array( 'success' => true, - 'data' => $this->plugin->components['sync']->managers['queue']->get_queue_status(), + 'data' => $this->sync->managers['queue']->get_queue_status(), ) ); } @@ -158,13 +192,13 @@ public function rest_get_queue_status() { public function rest_start_sync( \WP_REST_Request $request ) { $stop = $request->get_param( 'stop' ); - $queue = $this->plugin->components['sync']->managers['queue']->get_queue(); + $queue = $this->sync->managers['queue']->get_queue(); if ( empty( $queue['pending'] ) || ! empty( $stop ) ) { - $this->plugin->components['sync']->managers['queue']->stop_queue(); + $this->sync->managers['queue']->stop_queue(); return $this->rest_get_queue_status(); // Nothing to sync. } - $this->plugin->components['sync']->managers['queue']->start_queue(); + $this->sync->managers['queue']->start_queue(); return $this->call_thread(); @@ -202,18 +236,18 @@ public function rest_push_attachments( \WP_REST_Request $request ) { // Process queue based. if ( ! empty( $last_id ) && ! empty( $last_result ) ) { - $this->plugin->components['sync']->managers['queue']->mark( $last_id, $last_result ); + $this->sync->managers['queue']->mark( $last_id, $last_result ); } - if ( ! $this->plugin->components['sync']->managers['queue']->is_running() ) { // Check it wasn't stopped. + if ( ! $this->sync->managers['queue']->is_running() ) { // Check it wasn't stopped. return $this->rest_get_queue_status(); } - $this->post_id = $this->plugin->components['sync']->managers['queue']->get_post(); + $this->post_id = $this->sync->managers['queue']->get_post(); // No post, end of queue. if ( empty( $this->post_id ) ) { - $this->plugin->components['sync']->managers['queue']->stop_queue(); + $this->sync->managers['queue']->stop_queue(); return $this->rest_get_queue_status(); } @@ -233,7 +267,7 @@ public function resume_queue() { add_filter( 'cloudinary_on_demand_sync_enabled', '__return_false' ); // Disable the on-demand sync since we want the status. define( 'DOING_BULK_SYNC', true ); // Define bulk sync in action. - if ( ! $this->plugin->components['sync']->is_synced( $this->post_id ) ) { + if ( ! $this->sync->is_synced( $this->post_id ) ) { $stat = $this->push_attachments( array( $this->post_id ) ); if ( ! empty( $stat['processed'] ) ) { $result = 'done'; @@ -269,7 +303,7 @@ private function call_thread( $last_id = null, $last_result = null ) { } // Setup background call to continue the queue. - $this->plugin->components['api']->background_request( 'process', $params ); + $this->api->background_request( 'process', $params ); return $this->rest_get_queue_status(); } @@ -304,7 +338,7 @@ private function get_sync_type( $attachment ) { $type = 'upload'; // Check for explicit (has public_id, but no breakpoints). - $attachment_signature = $this->plugin->components['sync']->get_signature( $attachment->ID ); + $attachment_signature = $this->sync->get_signature( $attachment->ID ); if ( empty( $attachment_signature ) ) { if ( ! empty( $attachment->{Sync::META_KEYS['public_id']} ) ) { // Has a public id but no signature, explicit update to complete download. @@ -313,7 +347,7 @@ private function get_sync_type( $attachment ) { // fallback to upload. } else { // Has signature find differences and use specific sync method. - $required_signature = $this->plugin->components['sync']->generate_signature( $attachment->ID ); + $required_signature = $this->sync->generate_signature( $attachment->ID ); foreach ( $required_signature as $key => $signature ) { if ( ( ! isset( $attachment_signature[ $key ] ) || $attachment_signature[ $key ] !== $signature ) && isset( $this->sync_types[ $key ] ) ) { return $this->sync_types[ $key ]; @@ -351,9 +385,6 @@ public function prepare_upload( $post, $push_sync = false ) { return new \WP_Error( 'attachment_post_expected', __( 'An attachment post was expected.', 'cloudinary' ) ); } - // Get the media component. - $media = $this->plugin->components['media']; - // First check if this has a file and it can be uploaded. $file = get_attached_file( $post->ID ); $file_size = 0; @@ -363,10 +394,10 @@ public function prepare_upload( $post, $push_sync = false ) { } elseif ( ! file_exists( $file ) ) { // May be an old upload type. $src = get_post_meta( $post->ID, '_wp_attached_file', true ); - if ( $media->is_cloudinary_url( $src ) ) { + if ( $this->media->is_cloudinary_url( $src ) ) { // Download first maybe. if ( true === $push_sync ) { - $download = $this->plugin->components['sync']->managers['download']->down_sync( $post->ID ); + $download = $this->sync->managers['download']->down_sync( $post->ID ); if ( is_wp_error( $download ) ) { update_post_meta( $post->ID, Sync::META_KEYS['sync_error'], $download->get_error_message() ); @@ -384,12 +415,12 @@ public function prepare_upload( $post, $push_sync = false ) { $resource_type = $this->get_resource_type( $post ); $max_size = ( 'image' === $resource_type ? 'max_image_size' : 'max_video_size' ); - if ( ! empty( $this->plugin->components['connect']->usage[ $max_size ] ) && $file_size > $this->plugin->components['connect']->usage[ $max_size ] ) { - $max_size_hr = size_format( $this->plugin->components['connect']->usage[ $max_size ] ); + if ( ! empty( $this->connect->usage[ $max_size ] ) && $file_size > $this->connect->usage[ $max_size ] ) { + $max_size_hr = size_format( $this->connect->usage[ $max_size ] ); // translators: variable is file size. $error = sprintf( __( 'File size exceeds the maximum of %s. This media asset will be served from WordPress.', 'cloudinary' ), $max_size_hr ); - $media->delete_post_meta( $post->ID, Sync::META_KEYS['pending'] ); // Remove Flag. + $this->media->delete_post_meta( $post->ID, Sync::META_KEYS['pending'] ); // Remove Flag. // Cleanup flags delete_post_meta( $post->ID, Sync::META_KEYS['syncing'] ); @@ -408,7 +439,7 @@ public function prepare_upload( $post, $push_sync = false ) { $public_id = $cld_folder . $file_info['filename']; // Check if this is a sync prep. If so, generate a safe ID. if ( true === $push_sync ) { - $public_id = $this->plugin->components['sync']->generate_public_id( $post->ID ); + $public_id = $this->sync->generate_public_id( $post->ID ); } } @@ -424,7 +455,7 @@ public function prepare_upload( $post, $push_sync = false ) { $public_id_file = $public_id_info['filename']; } // Check if this asset is a folder sync. - $folder_sync = $media->get_post_meta( $post->ID, Sync::META_KEYS['folder_sync'], true ); + $folder_sync = $this->media->get_post_meta( $post->ID, Sync::META_KEYS['folder_sync'], true ); if ( ! empty( $folder_sync ) && false === $downsync ) { $public_id_folder = $cld_folder; // Ensure the public ID folder is constant. } else { @@ -457,7 +488,7 @@ public function prepare_upload( $post, $push_sync = false ) { $imagesize = getimagesize( $file ); $meta['width'] = $imagesize[0]; } - $max_width = $media->get_max_width(); + $max_width = $this->media->get_max_width(); // Add breakpoints request options. if ( ! empty( $settings['global_transformations']['enable_breakpoints'] ) ) { $options['responsive_breakpoints'] = array( @@ -467,7 +498,7 @@ public function prepare_upload( $post, $push_sync = false ) { 'max_width' => $meta['width'] < $max_width ? $meta['width'] : $max_width, 'min_width' => $settings['global_transformations']['min_width'], ); - $transformations = $media->get_transformation_from_meta( $post->ID ); + $transformations = $this->media->get_transformation_from_meta( $post->ID ); if ( ! empty( $transformations ) ) { $options['responsive_breakpoints']['transformation'] = Api::generate_transformation_string( $transformations ); } @@ -547,8 +578,6 @@ public function push_attachments( $attachments ) { 'total' => count( $attachments ), 'processed' => 0, ); - // Get media component. - $media = $this->plugin->components['media']; // Go over each attachment. foreach ( $attachments as $attachment ) { @@ -602,48 +631,48 @@ public function push_attachments( $attachments ) { if ( ! empty( $upload['options']['context'] ) ) { $args['context'] = $upload['options']['context']; } - $result = $this->plugin->components['connect']->api->explicit( $args ); + $result = $this->connect->api->explicit( $args ); } elseif ( 'rename' === $sync_type ) { // Rename an asset. $args = array( - 'from_public_id' => $media->get_post_meta( $attachment->ID, Sync::META_KEYS['public_id'] ), + 'from_public_id' => $this->media->get_post_meta( $attachment->ID, Sync::META_KEYS['public_id'] ), 'to_public_id' => $upload['public_id'], ); - $result = $this->plugin->components['connect']->api->{$upload['options']['resource_type']}( 'rename', 'POST', $args ); + $result = $this->connect->api->{$upload['options']['resource_type']}( 'rename', 'POST', $args ); } else { // dynamic sync type.. - $result = $this->plugin->components['connect']->api->{$sync_type}( $upload['file'], $upload['options'] ); + $result = $this->connect->api->{$sync_type}( $upload['file'], $upload['options'] ); } } else { // Large Upload. - $result = $this->plugin->components['connect']->api->upload_large( $upload['file'], $upload['options'] ); + $result = $this->connect->api->upload_large( $upload['file'], $upload['options'] ); } // Exceptions are handled by the Upload wrapper class and returned as \WP_Error, so check for it. if ( is_wp_error( $result ) ) { $error = $result->get_error_message(); $stats['fail'][] = $error; - $media->update_post_meta( $attachment->ID, Sync::META_KEYS['sync_error'], $error ); + $this->media->update_post_meta( $attachment->ID, Sync::META_KEYS['sync_error'], $error ); continue; } // Successful upload, so lets update meta. if ( is_array( $result ) && ( array_key_exists( 'public_id', $result ) || array_key_exists( 'public_ids', $result ) ) ) { $meta_data = array( - Sync::META_KEYS['signature'] => $this->plugin->components['sync']->generate_signature( $attachment->ID ), + Sync::META_KEYS['signature'] => $this->sync->generate_signature( $attachment->ID ), ); // We only have a version if updating a file or an upload. if ( ! empty( $result['version'] ) ) { $meta_data[ Sync::META_KEYS['version'] ] = $result['version']; } - $media->delete_post_meta( $attachment->ID, Sync::META_KEYS['pending'] ); + $this->media->delete_post_meta( $attachment->ID, Sync::META_KEYS['pending'] ); // Cleanup flags delete_post_meta( $attachment->ID, Sync::META_KEYS['downloading'] ); delete_post_meta( $attachment->ID, Sync::META_KEYS['syncing'] ); - $media->delete_post_meta( $attachment->ID, Sync::META_KEYS['sync_error'], false ); + $this->media->delete_post_meta( $attachment->ID, Sync::META_KEYS['sync_error'], false ); if ( ! empty( $this->plugin->config['settings']['global_transformations']['enable_breakpoints'] ) ) { if ( ! empty( $result['responsive_breakpoints'] ) ) { // Images only. $meta_data[ Sync::META_KEYS['breakpoints'] ] = $result['responsive_breakpoints'][0]['breakpoints']; @@ -664,7 +693,7 @@ public function push_attachments( $attachments ) { $meta = wp_get_attachment_metadata( $attachment->ID, true ); $meta[ Sync::META_KEYS['cloudinary'] ] = $meta_data; wp_update_attachment_metadata( $attachment->ID, $meta ); - $media->update_post_meta( $attachment->ID, Sync::META_KEYS['public_id'], $upload['options']['public_id'] ); + $this->media->update_post_meta( $attachment->ID, Sync::META_KEYS['public_id'], $upload['options']['public_id'] ); // Search and update link references in content. $content_search = new \WP_Query( array( 's' => 'wp-image-' . $attachment->ID, 'fields' => 'ids', 'posts_per_page' => 1000 ) ); if ( ! empty( $content_search->found_posts ) ) { From 6fb302df20fa1c8b295eed50a062e23b55fbb2db Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 23 Jul 2020 08:52:57 +0200 Subject: [PATCH 04/10] setup component --- .../php/class-sync.php | 1 + .../php/sync/class-push-sync.php | 17 +++++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php index 09afc3647..be2cfbcfa 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php @@ -213,6 +213,7 @@ public function setup() { if ( $this->plugin->config['connect'] ) { $this->managers['upload']->setup(); $this->managers['delete']->setup(); + $this->managers['push']->setup(); } } } diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php index d1cac64c1..dbebab742 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php @@ -83,12 +83,6 @@ class Push_Sync { public function __construct( \Cloudinary\Plugin $plugin ) { $this->plugin = $plugin; - // Setup components. - $this->media = $this->plugin->components['media']; - $this->sync = $this->plugin->components['sync']; - $this->connect = $this->plugin->components['connect']; - $this->api = $this->plugin->components['api']; - // Define the sync types and their option keys. $sync_types = array( 'cloud_name' => 'upload', @@ -110,6 +104,17 @@ private function register_hooks() { add_filter( 'cloudinary_api_rest_endpoints', array( $this, 'rest_endpoints' ) ); } + /** + * Setup this component. + */ + public function setup(){ + // Setup components. + $this->media = $this->plugin->components['media']; + $this->sync = $this->plugin->components['sync']; + $this->connect = $this->plugin->components['connect']; + $this->api = $this->plugin->components['api']; + } + /** * Add endpoints to the \Cloudinary\REST_API::$endpoints array. * From f15528912d57fb90b6d5b18f3277c8f3fc3cc7ad Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 23 Jul 2020 09:14:58 +0200 Subject: [PATCH 05/10] catch context ID to attempt a reconnection rather than generating a new ID --- .../php/class-sync.php | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php index be2cfbcfa..c0b2e9d79 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php @@ -198,9 +198,19 @@ public function generate_public_id( $attachment_id, $prefix = null ) { } $cld_asset = $this->plugin->components['connect']->api->get_asset_details( $public_id, $type ); if ( ! is_wp_error( $cld_asset ) && ! empty( $cld_asset['public_id'] ) ) { - // Exists, generate a new ID with a uniqueID prefix. - $prefix = uniqid() . '-'; - $public_id = $this->generate_public_id( $attachment_id, $prefix ); + $context_id = null; + + // Exists, check to see if this asset originally belongs to this ID. + if ( ! empty( $cld_asset['context'] ) && ! empty( $cld_asset['context']['custom'] ) && ! empty( $cld_asset['context']['custom']['wp_id'] ) ) { + $context_id = (int) $cld_asset['context']['custom']['wp_id']; + } + + // Generate new ID only if context ID is not related. + if ( $context_id !== $attachment_id ) { + // Generate a new ID with a uniqueID prefix. + $prefix = uniqid() . '-'; + $public_id = $this->generate_public_id( $attachment_id, $prefix ); + } } return $public_id; From a99ea464e438df56a17089c076130f7013dc86e8 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Thu, 23 Jul 2020 17:19:46 +0200 Subject: [PATCH 06/10] Address issues --- .../php/class-sync.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php index c0b2e9d79..4e13ae342 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php @@ -175,7 +175,8 @@ public function get_signature( $post_id ) { /** * Generate a new Public ID for an asset. * - * @param int $attachment_id The attachment ID for the new public ID. + * @param int $attachment_id The attachment ID for the new public ID. + * @param string $suffix The suffic to prepend the ID. * * @return string|null */ @@ -190,7 +191,7 @@ public function generate_public_id( $attachment_id, $prefix = null ) { $type = 'image'; } elseif ( wp_attachment_is( 'video', $attachment_id ) ) { $type = 'video'; - } elseif ( wp_attachment_is( 'video', $attachment_id ) ) { + } elseif ( wp_attachment_is( 'audio', $attachment_id ) ) { $type = 'audio'; } else { // not supported. From 7b7d3ad0cf5263a5c154b24547774126562961f1 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 24 Jul 2020 08:40:23 +0200 Subject: [PATCH 07/10] check file existance over fetch details, change prefix to suffix --- .../php/class-sync.php | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php index 4e13ae342..3aeaa7c89 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php @@ -175,17 +175,17 @@ public function get_signature( $post_id ) { /** * Generate a new Public ID for an asset. * - * @param int $attachment_id The attachment ID for the new public ID. - * @param string $suffix The suffic to prepend the ID. + * @param int $attachment_id The attachment ID for the new public ID. + * @param string|null $suffix The suffix to prepend the ID. * * @return string|null */ - public function generate_public_id( $attachment_id, $prefix = null ) { + public function generate_public_id( $attachment_id, $suffix = null ) { $settings = $this->plugin->config['settings']; $cld_folder = trailingslashit( $settings['sync_media']['cloudinary_folder'] ); $file = get_attached_file( $attachment_id ); $file_info = pathinfo( $file ); - $public_id = $cld_folder . $prefix . $file_info['filename']; + $public_id = $cld_folder . $file_info['filename'] . $suffix; // Get the type. if ( wp_attachment_is( 'image', $attachment_id ) ) { $type = 'image'; @@ -197,20 +197,30 @@ public function generate_public_id( $attachment_id, $prefix = null ) { // not supported. return null; } - $cld_asset = $this->plugin->components['connect']->api->get_asset_details( $public_id, $type ); - if ( ! is_wp_error( $cld_asset ) && ! empty( $cld_asset['public_id'] ) ) { - $context_id = null; - // Exists, check to see if this asset originally belongs to this ID. - if ( ! empty( $cld_asset['context'] ) && ! empty( $cld_asset['context']['custom'] ) && ! empty( $cld_asset['context']['custom']['wp_id'] ) ) { - $context_id = (int) $cld_asset['context']['custom']['wp_id']; - } + // Test if asset exists by calling just the head on the asset url, to prevent API rate limits. + $url = $this->plugin->components['connect']->api->cloudinary_url( $public_id ); + $req = wp_remote_head( $url, array( 'body' => array( 'rdm' => wp_rand( 100, 999 ) ) ) ); + $asset_error = strtolower( wp_remote_retrieve_header( $req, 'x-cld-error' ) ); + $code = wp_remote_retrieve_response_code( $req ); + + // If the request is not a 404 & does not have a cld-error header stating resource not found, it exists and should be checked that it's not a resync or generate a prefixed ID. + if ( 404 !== $code && false === strpos( $asset_error, 'resource not found' ) ) { + $cld_asset = $this->plugin->components['connect']->api->get_asset_details( $public_id, $type ); + if ( ! is_wp_error( $cld_asset ) && ! empty( $cld_asset['public_id'] ) ) { + $context_id = null; + + // Exists, check to see if this asset originally belongs to this ID. + if ( ! empty( $cld_asset['context'] ) && ! empty( $cld_asset['context']['custom'] ) && ! empty( $cld_asset['context']['custom']['wp_id'] ) ) { + $context_id = (int) $cld_asset['context']['custom']['wp_id']; + } - // Generate new ID only if context ID is not related. - if ( $context_id !== $attachment_id ) { - // Generate a new ID with a uniqueID prefix. - $prefix = uniqid() . '-'; - $public_id = $this->generate_public_id( $attachment_id, $prefix ); + // Generate new ID only if context ID is not related. + if ( $context_id !== $attachment_id ) { + // Generate a new ID with a uniqueID prefix. + $suffix = '-' . uniqid(); + $public_id = $this->generate_public_id( $attachment_id, $suffix ); + } } } From 28aa354bceec88fe2d711670318c0fc8cc68c767 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Fri, 24 Jul 2020 14:10:01 +0200 Subject: [PATCH 08/10] split process to make sure the ID is unique after filtering. --- .../php/class-sync.php | 54 ++++++++++++------- .../php/sync/class-push-sync.php | 17 +++--- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php index 3aeaa7c89..deb8f1d80 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php @@ -175,37 +175,51 @@ public function get_signature( $post_id ) { /** * Generate a new Public ID for an asset. * - * @param int $attachment_id The attachment ID for the new public ID. - * @param string|null $suffix The suffix to prepend the ID. + * @param int $attachment_id The attachment ID for the new public ID. * * @return string|null */ - public function generate_public_id( $attachment_id, $suffix = null ) { + public function generate_public_id( $attachment_id ) { $settings = $this->plugin->config['settings']; $cld_folder = trailingslashit( $settings['sync_media']['cloudinary_folder'] ); $file = get_attached_file( $attachment_id ); $file_info = pathinfo( $file ); - $public_id = $cld_folder . $file_info['filename'] . $suffix; - // Get the type. - if ( wp_attachment_is( 'image', $attachment_id ) ) { - $type = 'image'; - } elseif ( wp_attachment_is( 'video', $attachment_id ) ) { - $type = 'video'; - } elseif ( wp_attachment_is( 'audio', $attachment_id ) ) { - $type = 'audio'; - } else { - // not supported. - return null; - } + $public_id = $cld_folder . $file_info['filename']; + + return $public_id; + } + + /** + * Maybe add a suffix to the public ID if it's not unique. + * + * @param string $public_id The public ID to maybe add a suffix. + * @param int $attachment_id The attachment ID. + * @param string|null $suffix The suffix to maybe add. + * + * @return string The public ID. + */ + public function add_suffix_maybe( $public_id, $attachment_id, $suffix = null ) { // Test if asset exists by calling just the head on the asset url, to prevent API rate limits. - $url = $this->plugin->components['connect']->api->cloudinary_url( $public_id ); + $url = $this->plugin->components['connect']->api->cloudinary_url( $public_id . $suffix ); $req = wp_remote_head( $url, array( 'body' => array( 'rdm' => wp_rand( 100, 999 ) ) ) ); $asset_error = strtolower( wp_remote_retrieve_header( $req, 'x-cld-error' ) ); $code = wp_remote_retrieve_response_code( $req ); // If the request is not a 404 & does not have a cld-error header stating resource not found, it exists and should be checked that it's not a resync or generate a prefixed ID. if ( 404 !== $code && false === strpos( $asset_error, 'resource not found' ) ) { + + // Get the attachment type. + if ( wp_attachment_is( 'image', $attachment_id ) ) { + $type = 'image'; + } elseif ( wp_attachment_is( 'video', $attachment_id ) ) { + $type = 'video'; + } elseif ( wp_attachment_is( 'audio', $attachment_id ) ) { + $type = 'audio'; + } else { + // not supported. + return null; + } $cld_asset = $this->plugin->components['connect']->api->get_asset_details( $public_id, $type ); if ( ! is_wp_error( $cld_asset ) && ! empty( $cld_asset['public_id'] ) ) { $context_id = null; @@ -218,13 +232,15 @@ public function generate_public_id( $attachment_id, $suffix = null ) { // Generate new ID only if context ID is not related. if ( $context_id !== $attachment_id ) { // Generate a new ID with a uniqueID prefix. - $suffix = '-' . uniqid(); - $public_id = $this->generate_public_id( $attachment_id, $suffix ); + $suffix = '-' . uniqid(); + + // Return new potential suffixed ID. + return $this->add_suffix_maybe( $public_id, $attachment_id, $suffix ); } } } - return $public_id; + return $public_id . $suffix; } /** diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php index dbebab742..cbb881ae0 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php @@ -107,7 +107,7 @@ private function register_hooks() { /** * Setup this component. */ - public function setup(){ + public function setup() { // Setup components. $this->media = $this->plugin->components['media']; $this->sync = $this->plugin->components['sync']; @@ -439,13 +439,8 @@ public function prepare_upload( $post, $push_sync = false ) { $public_id = $post->{Sync::META_KEYS['public_id']}; // use the __get method on the \WP_Post to get post_meta. $cld_folder = trailingslashit( $settings['sync_media']['cloudinary_folder'] ); if ( empty( $public_id ) ) { - // Build a default ID. - $file_info = pathinfo( $file ); - $public_id = $cld_folder . $file_info['filename']; - // Check if this is a sync prep. If so, generate a safe ID. - if ( true === $push_sync ) { - $public_id = $this->sync->generate_public_id( $post->ID ); - } + // Create a new public_id. + $public_id = $this->sync->generate_public_id( $post->ID ); } // Assume that the public_id is a root item. @@ -534,7 +529,11 @@ public function prepare_upload( $post, $push_sync = false ) { } // Restructure the path to the filename to allow correct placement in Cloudinary. - $public_id = ltrim( $public_id_folder . $options['public_id'], '/' ); + $public_id = ltrim( $public_id_folder . $options['public_id'], '/' ); + // If this is a push sync, make sure the ID is allowed and unique. + if ( true === $push_sync ) { + $public_id = $this->sync->add_suffix_maybe( $public_id, $post->ID ); + } $return = array( 'file' => $file, 'folder' => ltrim( $cld_folder, '/' ), From dbcf3cd9949ddd7426e9a5f4cb09c85607374a5d Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 27 Jul 2020 10:43:46 +0200 Subject: [PATCH 09/10] add suffix data to enable splitting public_id and unique suffix --- .../php/class-media.php | 6 +++- .../php/class-sync.php | 3 +- .../php/sync/class-push-sync.php | 33 ++++++++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php index 2ffd89d18..3ebc2bf00 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php @@ -655,7 +655,11 @@ public function get_public_id( $attachment_id ) { public function get_cloudinary_id( $attachment_id ) { // A cloudinary_id is a public_id with a file extension. - $public_id = $this->get_public_id( $attachment_id ); + $public_id = $this->get_public_id( $attachment_id ); + $suffix_data = $this->get_post_meta( $post->ID, Sync::META_KEYS['suffix'], true ); + if ( is_array( $suffix_data ) && ! empty( $suffix_data['suffix'] ) && $suffix_data['public_id'] === $public_id ) { + $public_id = $public_id . $suffix_data['suffix']; + } $file = get_attached_file( $attachment_id ); $info = pathinfo( $file ); $cloudinary_id = $public_id . '.' . $info['extension']; diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php index deb8f1d80..d0b4067ce 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-sync.php @@ -49,6 +49,7 @@ class Sync implements Setup, Assets { 'sync_error' => '_sync_error', 'cloudinary' => '_cloudinary_v2', 'folder_sync' => '_folder_sync', + 'suffix' => '_suffix', 'syncing' => '_cloudinary_syncing', 'downloading' => '_cloudinary_downloading', ); @@ -240,7 +241,7 @@ public function add_suffix_maybe( $public_id, $attachment_id, $suffix = null ) { } } - return $public_id . $suffix; + return $suffix; } /** diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php index cbb881ae0..e95bc6e31 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/sync/class-push-sync.php @@ -531,13 +531,37 @@ public function prepare_upload( $post, $push_sync = false ) { // Restructure the path to the filename to allow correct placement in Cloudinary. $public_id = ltrim( $public_id_folder . $options['public_id'], '/' ); // If this is a push sync, make sure the ID is allowed and unique. + + // Setup suffix data for unique ids. + $suffix_defaults = array( + 'public_id' => $public_id, + 'suffix' => null, + ); + $suffix_meta = $this->media->get_post_meta( $post->ID, Sync::META_KEYS['suffix'], true ); + $suffix_data = wp_parse_args( $suffix_meta, $suffix_defaults ); + + // Prepare a uniqueness check and get a suffix if needed. if ( true === $push_sync ) { - $public_id = $this->sync->add_suffix_maybe( $public_id, $post->ID ); + if ( $public_id !== $suffix_data['public_id'] || empty( $suffix_data['suffix'] ) ) { + $suffix_data['suffix'] = $this->sync->add_suffix_maybe( $public_id, $post->ID ); + if ( ! empty( $suffix_data['suffix'] ) ) { + // Only save if there is a suffix to save on metadata. + $this->media->update_post_meta( $post->ID, Sync::META_KEYS['suffix'], $suffix_data ); + } else { + // Clear meta data in case of a unique name on a rename etc. + $this->media->delete_post_meta( $post->ID, Sync::META_KEYS['suffix'] ); + } + } + } + // Add Suffix to public_id. + if ( ! empty( $suffix_data['suffix'] ) ) { + $public_id = $public_id . $suffix_data['suffix']; } $return = array( 'file' => $file, 'folder' => ltrim( $cld_folder, '/' ), 'public_id' => $public_id, + 'suffix' => $suffix_data['suffix'], 'breakpoints' => array(), 'options' => $options, ); @@ -685,6 +709,12 @@ public function push_attachments( $attachments ) { delete_post_meta( $attachment->ID, Sync::META_KEYS['breakpoints'] ); } } + + // Reset public_id without suffix. + if ( ! empty( $upload['suffix'] ) ) { + $upload['options']['public_id'] = strstr( $upload['options']['public_id'], $upload['suffix'], true ); + } + // Generate a public_id sync hash. if ( ! empty( $upload['options']['public_id'] ) ) { // a transformation breakpoints only ever happens on a down sync. $sync_key = '_' . md5( $upload['options']['public_id'] ); @@ -697,6 +727,7 @@ public function push_attachments( $attachments ) { $meta = wp_get_attachment_metadata( $attachment->ID, true ); $meta[ Sync::META_KEYS['cloudinary'] ] = $meta_data; wp_update_attachment_metadata( $attachment->ID, $meta ); + $this->media->update_post_meta( $attachment->ID, Sync::META_KEYS['public_id'], $upload['options']['public_id'] ); // Search and update link references in content. $content_search = new \WP_Query( array( 's' => 'wp-image-' . $attachment->ID, 'fields' => 'ids', 'posts_per_page' => 1000 ) ); From 97a564efba1a3ead9407629af0afb4e7aa39fa78 Mon Sep 17 00:00:00 2001 From: David Cramer Date: Mon, 27 Jul 2020 13:35:40 +0200 Subject: [PATCH 10/10] typo on post_id --- .../php/class-media.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php index 3ebc2bf00..004d4fb07 100644 --- a/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php +++ b/cloudinary-image-management-and-manipulation-in-the-cloud-cdn/php/class-media.php @@ -656,7 +656,7 @@ public function get_cloudinary_id( $attachment_id ) { // A cloudinary_id is a public_id with a file extension. $public_id = $this->get_public_id( $attachment_id ); - $suffix_data = $this->get_post_meta( $post->ID, Sync::META_KEYS['suffix'], true ); + $suffix_data = $this->get_post_meta( $attachment_id, Sync::META_KEYS['suffix'], true ); if ( is_array( $suffix_data ) && ! empty( $suffix_data['suffix'] ) && $suffix_data['public_id'] === $public_id ) { $public_id = $public_id . $suffix_data['suffix']; }