diff --git a/languages/cloudinary.pot b/languages/cloudinary.pot index 741a7f2d0..457551a5a 100644 --- a/languages/cloudinary.pot +++ b/languages/cloudinary.pot @@ -4,7 +4,7 @@ msgid "" msgstr "" "Project-Id-Version: Cloudinary STABLETAG\n" "Report-Msgid-Bugs-To: https://github.com/cloudinary/cloudinary_wordpress\n" -"POT-Creation-Date: 2025-06-27 06:59:54+00:00\n" +"POT-Creation-Date: 2025-08-06 09:40:15+00:00\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" @@ -63,11 +63,11 @@ msgstr[1] "" msgid "No items found." msgstr "" -#: php/cache/class-cache-point.php:962 php/class-assets.php:1201 +#: php/cache/class-cache-point.php:962 php/class-assets.php:1239 msgid "Cloudinary Asset" msgstr "" -#: php/cache/class-cache-point.php:963 php/class-assets.php:1202 +#: php/cache/class-cache-point.php:963 php/class-assets.php:1240 msgid "Post type to represent a non-media library asset." msgstr "" @@ -87,114 +87,114 @@ msgstr "" msgid "Enable Cloudinary status" msgstr "" -#: php/class-assets.php:756 +#: php/class-assets.php:794 msgid "Caching" msgstr "" -#: php/class-assets.php:767 +#: php/class-assets.php:805 msgid "Creating shadow assets" msgstr "" -#: php/class-assets.php:778 +#: php/class-assets.php:816 msgid "Updating asset storage" msgstr "" -#: php/class-assets.php:1245 +#: php/class-assets.php:1283 msgid "Additional Asset Sync Settings" msgstr "" -#: php/class-assets.php:1265 +#: php/class-assets.php:1303 msgid "Additional asset sync settings" msgstr "" -#: php/class-assets.php:1266 +#: php/class-assets.php:1304 msgid "" "Enabling additional asset syncing will sync the toggled assets with " "Cloudinary to make use of advanced optimization and CDN delivery " "functionality." msgstr "" -#: php/class-assets.php:1267 +#: php/class-assets.php:1305 msgid "Enable additional asset syncing" msgstr "" -#: php/class-assets.php:1306 +#: php/class-assets.php:1344 msgid "External Asset Sync Settings" msgstr "" -#: php/class-assets.php:1355 php/class-cache.php:615 +#: php/class-assets.php:1393 php/class-cache.php:615 msgid "Plugin" msgstr "" -#: php/class-assets.php:1391 php/class-cache.php:792 +#: php/class-assets.php:1429 php/class-cache.php:792 msgid "Plugins" msgstr "" -#: php/class-assets.php:1409 php/class-cache.php:809 +#: php/class-assets.php:1447 php/class-cache.php:809 msgid "Deliver assets from all plugin folders" msgstr "" -#: php/class-assets.php:1441 php/class-cache.php:652 +#: php/class-assets.php:1479 php/class-cache.php:652 msgid "Theme" msgstr "" -#: php/class-assets.php:1473 php/class-cache.php:843 +#: php/class-assets.php:1511 php/class-cache.php:843 msgid "Themes" msgstr "" -#: php/class-assets.php:1491 php/class-cache.php:860 +#: php/class-assets.php:1529 php/class-cache.php:860 msgid "Deliver all assets from active theme." msgstr "" -#: php/class-assets.php:1516 php/class-assets.php:1551 php/class-cache.php:684 +#: php/class-assets.php:1554 php/class-assets.php:1589 php/class-cache.php:684 #: php/class-cache.php:895 msgid "WordPress" msgstr "" -#: php/class-assets.php:1525 php/class-cache.php:668 +#: php/class-assets.php:1563 php/class-cache.php:668 msgid "WordPress Admin" msgstr "" -#: php/class-assets.php:1532 php/class-cache.php:675 +#: php/class-assets.php:1570 php/class-cache.php:675 msgid "WordPress Includes" msgstr "" -#: php/class-assets.php:1569 php/class-cache.php:912 +#: php/class-assets.php:1607 php/class-cache.php:912 msgid "Deliver all assets from WordPress core." msgstr "" -#: php/class-assets.php:1595 php/class-assets.php:1621 php/class-cache.php:708 +#: php/class-assets.php:1633 php/class-assets.php:1659 php/class-cache.php:708 #: php/class-cache.php:947 msgid "Content" msgstr "" -#: php/class-assets.php:1602 php/class-cache.php:699 +#: php/class-assets.php:1640 php/class-cache.php:699 msgid "Uploads" msgstr "" -#: php/class-assets.php:1639 php/class-cache.php:964 +#: php/class-assets.php:1677 php/class-cache.php:964 msgid "Deliver all content assets from WordPress Media Library." msgstr "" -#: php/class-assets.php:1663 +#: php/class-assets.php:1701 msgid "Enable external assets" msgstr "" -#: php/class-assets.php:1664 +#: php/class-assets.php:1702 msgid "" "Enabling external assets allows you to sync assets from specific external " "sources with Cloudinary." msgstr "" -#: php/class-assets.php:1674 +#: php/class-assets.php:1712 msgid "Domains for each external source." msgstr "" -#: php/class-assets.php:1677 +#: php/class-assets.php:1715 msgid "Enter a domain" msgstr "" -#: php/class-assets.php:1678 +#: php/class-assets.php:1716 msgid "Press ENTER or SPACE or type comma or tab to continue." msgstr "" @@ -498,22 +498,22 @@ msgstr "" msgid "Cloudinary Media Library" msgstr "" -#: php/class-media.php:184 +#: php/class-media.php:185 msgid "Error" msgstr "" -#: php/class-media.php:185 +#: php/class-media.php:186 msgid "Unsynced" msgstr "" -#: php/class-media.php:458 +#: php/class-media.php:459 #. translators: variable is file size. msgid "" "File size exceeds the maximum of %s. This media asset will be served from " "WordPress." msgstr "" -#: php/class-media.php:2066 +#: php/class-media.php:2067 msgid "Import" msgstr "" @@ -521,43 +521,43 @@ msgstr "" msgid "Cloudinary" msgstr "" -#: php/class-media.php:2422 +#: php/class-media.php:2423 msgid "The delivery for this asset is disabled." msgstr "" -#: php/class-media.php:2426 +#: php/class-media.php:2427 msgid "Not syncable. This is an external media." msgstr "" -#: php/class-media.php:2430 +#: php/class-media.php:2431 msgid "This media is Fetch type." msgstr "" -#: php/class-media.php:2434 +#: php/class-media.php:2435 msgid "This media is Sprite type." msgstr "" -#: php/class-media.php:2444 +#: php/class-media.php:2445 msgid "Not Synced" msgstr "" -#: php/class-media.php:2449 +#: php/class-media.php:2450 msgid "Synced" msgstr "" -#: php/class-media.php:3100 +#: php/class-media.php:3109 msgid "No Cloudinary filters" msgstr "" -#: php/class-media.php:3200 +#: php/class-media.php:3209 msgid "Media Settings" msgstr "" -#: php/class-media.php:3203 +#: php/class-media.php:3212 msgid "Media Display" msgstr "" -#: php/class-media.php:3207 php/media/class-global-transformations.php:620 +#: php/class-media.php:3216 php/media/class-global-transformations.php:620 #: php/ui/component/class-asset-preview.php:73 #: php/ui/component/class-plan-details.php:119 #: php/ui/component/class-plan-status.php:128 @@ -565,7 +565,7 @@ msgstr "" msgid "Transformations" msgstr "" -#: php/class-media.php:3208 +#: php/class-media.php:3217 msgid "" "Cloudinary allows you to easily transform your images on-the-fly to any " "required format, style and dimension, and also optimizes images for minimal " @@ -574,7 +574,7 @@ msgid "" "transformation and delivery URLs." msgstr "" -#: php/class-media.php:3213 ui-definitions/settings-image.php:174 +#: php/class-media.php:3222 ui-definitions/settings-image.php:174 #: ui-definitions/settings-video.php:260 msgid "See examples" msgstr "" diff --git a/package-lock.json b/package-lock.json index 890d22f61..ba640f0ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cloudinary", - "version": "3.2.7", + "version": "3.2.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cloudinary", - "version": "3.2.7", + "version": "3.2.10", "hasInstallScript": true, "license": "GPL-2.0+", "devDependencies": { @@ -44,6 +44,7 @@ "cross-env": "^7.0.2", "css-loader": "^5.0.1", "css-minimizer-webpack-plugin": "^4.2.2", + "css-unicode-loader": "^1.0.3", "cssnano": "^5.0.2", "dot-object": "^2.1.4", "dotenv": "^8.2.0", @@ -18728,6 +18729,12 @@ "node": ">=8.0.0" } }, + "node_modules/css-unicode-loader": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/css-unicode-loader/-/css-unicode-loader-1.0.3.tgz", + "integrity": "sha512-mszxqB0LCBO2ixbaz/tAH17iEAXmytUv0j/YXtPxhOi8D8Teh+mOXqmz+DZRtDil5tFx7ltbgw16dUptEw4jOg==", + "dev": true + }, "node_modules/css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", @@ -55147,6 +55154,12 @@ "source-map": "^0.6.1" } }, + "css-unicode-loader": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/css-unicode-loader/-/css-unicode-loader-1.0.3.tgz", + "integrity": "sha512-mszxqB0LCBO2ixbaz/tAH17iEAXmytUv0j/YXtPxhOi8D8Teh+mOXqmz+DZRtDil5tFx7ltbgw16dUptEw4jOg==", + "dev": true + }, "css-what": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", diff --git a/package.json b/package.json index 2b4d07a54..972a0ec87 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "cross-env": "^7.0.2", "css-loader": "^5.0.1", "css-minimizer-webpack-plugin": "^4.2.2", + "css-unicode-loader": "^1.0.3", "cssnano": "^5.0.2", "dot-object": "^2.1.4", "dotenv": "^8.2.0", @@ -144,4 +145,4 @@ "webpackbar": "^5.0.2" }, "version": "3.2.11" -} \ No newline at end of file +} diff --git a/php/class-assets.php b/php/class-assets.php index 601ba06be..52287e99f 100644 --- a/php/class-assets.php +++ b/php/class-assets.php @@ -156,7 +156,7 @@ protected function register_hooks() { add_filter( 'cloudinary_asset_state', array( $this, 'filter_asset_state' ), 10, 2 ); add_filter( 'cloudinary_set_usable_asset', array( $this, 'check_usable_asset' ) ); // Actions. - add_action( 'cloudinary_init_settings', array( $this, 'setup' ) ); + add_action( 'cloudinary_ready', array( $this, 'setup' ) ); add_action( 'cloudinary_thread_queue_details_query', array( $this, 'connect_post_type' ) ); add_action( 'cloudinary_build_queue_query', array( $this, 'connect_post_type' ) ); add_action( 'cloudinary_string_replace', array( $this, 'add_url_replacements' ), 20 ); @@ -441,6 +441,7 @@ public function update_asset_paths() { // Check and update version if needed. if ( $this->media->get_post_meta( $asset_path->ID, Sync::META_KEYS['version'], true ) !== $version ) { $this->media->update_post_meta( $asset_path->ID, Sync::META_KEYS['version'], $version ); + $this->sync_parent( $asset_path->ID ); } } } @@ -538,11 +539,12 @@ public function create_asset_parent( $path, $version ) { } /** - * Purge a single asset parent. + * Process all child assets of a parent with a given callback. * - * @param int $parent_id The Asset parent to purge. + * @param int $parent_id The Asset parent to process. + * @param callable $callback The callback function to execute on each post. */ - public function purge_parent( $parent_id ) { + private function process_parent_assets( $parent_id, $callback ) { $query_args = array( 'post_type' => self::POST_TYPE_SLUG, 'posts_per_page' => 100, @@ -554,11 +556,13 @@ public function purge_parent( $parent_id ) { ); $query = new \WP_Query( $query_args ); $previous_total = $query->found_posts; + do { $this->lock_assets(); $posts = $query->get_posts(); + foreach ( $posts as $post_id ) { - wp_delete_post( $post_id ); + call_user_func( $callback, $post_id ); } $query_args = $query->query_vars; @@ -569,6 +573,40 @@ public function purge_parent( $parent_id ) { } while ( $query->have_posts() ); } + /** + * Sync the assets of a parent. + * + * @param int $parent_id The Asset parent to sync. + */ + public function sync_parent( $parent_id ) { + $this->process_parent_assets( + $parent_id, + function ( $post_id ) { + if ( empty( $this->media->sync ) || ! $this->media->sync->can_sync( $post_id ) ) { + return; + } + + $this->media->sync->set_signature_item( $post_id, 'file', '' ); + $this->media->sync->set_signature_item( $post_id, 'cld_asset' ); + $this->media->sync->add_to_sync( $post_id ); + } + ); + } + + /** + * Purge a single asset parent. + * + * @param int $parent_id The Asset parent to purge. + */ + public function purge_parent( $parent_id ) { + $this->process_parent_assets( + $parent_id, + function ( $post_id ) { + wp_delete_post( $post_id ); + } + ); + } + /** * Lock asset creation for performing things like purging that require no changes. */ diff --git a/php/class-cron.php b/php/class-cron.php index 5f3923dd6..065b3ac04 100644 --- a/php/class-cron.php +++ b/php/class-cron.php @@ -391,16 +391,20 @@ public function run_queue( WP_REST_Request $request ) { $this->init_time = (int) $request->get_param( 'time' ); $queue = $this->locker->get_lock_file( $this->init_time ); register_shutdown_function( array( $this, 'cleanup_failed_cron' ) ); - foreach ( $queue as $name ) { - if ( ! isset( $this->processes[ $name ] ) ) { - continue; - } - $process = $this->processes[ $name ]; - $data = $process['callback']( $name ); - // @todo: Log data result. - $this->unlock_schedule_process( $name ); + if ( ! empty( $queue ) ) { + foreach ( $queue as $name ) { + if ( ! isset( $this->processes[ $name ] ) ) { + continue; + } + $process = $this->processes[ $name ]; + $data = $process['callback']( $name ); + // @todo: Log data result. + + $this->unlock_schedule_process( $name ); + } } + $this->locker->delete_lock_file( $this->init_time ); } diff --git a/php/class-delivery.php b/php/class-delivery.php index 703618123..564f967a0 100644 --- a/php/class-delivery.php +++ b/php/class-delivery.php @@ -725,7 +725,20 @@ protected function setup_hooks() { */ public function dns_prefetch( $urls, $relation_type ) { - if ( 'dns-prefetch' === $relation_type || 'preconnect' === $relation_type ) { + /** + * Filter to provide option to omit prefetch. + * + * @hook cloudinary_dns_prefetch_types + * @since 3.2.12 + * @default array ( 'dns-prefetch', 'preconnect' ) + * + * @param $types {array} The types of resource hints to use. + * + * @return {array} The modified resource hints to use. + */ + $resource_hints = apply_filters( 'cloudinary_dns_prefetch_types', array( 'dns-prefetch', 'preconnect' ) ); + + if ( in_array( $relation_type, $resource_hints, true ) ) { $urls[] = $this->media->base_url; } @@ -883,6 +896,7 @@ public function find_attachment_size_urls() { $dirs = wp_get_upload_dir(); $baseurl = Utils::clean_url( $dirs['baseurl'] ); $search = array(); + foreach ( $this->unknown as $url ) { $url = ltrim( str_replace( $baseurl, '', $url ), '/' ); $search[] = $url; @@ -900,6 +914,7 @@ public function find_attachment_size_urls() { $key = md5( $sql ); $cached = wp_cache_get( $key ); $auto_sync = $this->sync->is_auto_sync_enabled(); + if ( false === $cached ) { $cached = array(); $results = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared @@ -917,12 +932,14 @@ public function find_attachment_size_urls() { * @return {int} */ $post_id = apply_filters( 'cloudinary_contextualized_post_id', $result->post_id ); + if ( ! $this->is_deliverable( $post_id ) ) { continue; } // If we are here, it means that an attachment in the media library doesn't have a delivery for the url. // Reset the signature for delivery and add to sync, to update it. $this->create_delivery( $post_id ); + if ( true === $auto_sync ) { $this->sync->add_to_sync( $post_id ); } @@ -1920,7 +1937,70 @@ public function prepare_delivery( $content ) { $this->set_usability( $result, $auto_sync ); } // Set unknowns. - $this->unknown = array_diff( $urls, array_keys( $this->known ) ); + $this->unknown = $this->filter_unknown_urls( $urls ); + } + + /** + * Filter URLs to determine which are truly unknown, considering image size variations. + * + * This method treats image size variations (e.g., example-300x224.png) as "known" + * if their base image (e.g., example.png) exists in the known URLs, while still + * catching genuinely unknown URLs. + * + * @param array $urls All URLs found in content. + * + * @return array Array of genuinely unknown URLs. + */ + protected function filter_unknown_urls( $urls ) { + $known_keys = array_keys( $this->known ); + + if ( empty( $known_keys ) ) { + return $urls; + } + + $known_lookup = array_flip( $known_keys ); + $potential_unknown = array_diff( $urls, $known_keys ); + + if ( empty( $potential_unknown ) ) { + return array(); + } + + $truly_unknown = array(); + + foreach ( $potential_unknown as $url ) { + // Check if this might be a sized variation of a known image. + $base_url = $this->maybe_unsize_url( $url ); + + // If base image is known, skip this variation. + if ( isset( $known_lookup[ $base_url ] ) ) { + continue; + } + + // Check scaled version if base wasn't found and URL was actually "unsized". + if ( $base_url !== $url ) { + $scaled_url = Utils::make_scaled_url( $base_url ); + if ( isset( $known_lookup[ $scaled_url ] ) ) { + continue; // Scaled version is known, skip this variation. + } + } + + // This URL is genuinely unknown. + $truly_unknown[] = $url; + } + + /** + * Filter the list of truly unknown URLs after filtering out image size variations. + * + * @hook cloudinary_filter_unknown_urls + * @since 3.2.12 + * + * @param array $truly_unknown The filtered list of unknown URLs. + * @param array $urls The original list of all URLs. + * @param array $known_keys The list of known URL keys. + * + * @return array The filtered list of unknown URLs. + */ + return apply_filters( 'cloudinary_filter_unknown_urls', $truly_unknown, $urls, $known_keys ); } /** diff --git a/php/class-media.php b/php/class-media.php index aaf1cb5a6..182782b3d 100644 --- a/php/class-media.php +++ b/php/class-media.php @@ -7,6 +7,7 @@ namespace Cloudinary; +use Cloudinary\Assets; use Cloudinary\Component\Setup; use Cloudinary\Connect\Api; use Cloudinary\Media\Filter; @@ -2963,9 +2964,17 @@ public function apply_srcset( $content, $attachment_id, $overwrite_transformatio public function get_cloudinary_version( $attachment_id ) { $version = (int) $this->get_post_meta( $attachment_id, Sync::META_KEYS['version'], true ); + if ( empty( $version ) ) { + // This might be also an asset from the hidden post type (Assets::POST_TYPE_SLUG). + $attachment = get_post( $attachment_id ); + + if ( ! empty( $attachment ) && Assets::POST_TYPE_SLUG === $attachment->post_type && ! empty( $attachment->post_parent ) ) { + $version = (int) preg_replace( '/\D/', '', $this->get_post_meta( (int) $attachment->post_parent, Sync::META_KEYS['version'], true ) ); + } + } + return $version ? $version : 1; } - /** * Upgrade media related settings, including global transformations etc. * diff --git a/php/cron/class-lock-file.php b/php/cron/class-lock-file.php index 70b299849..2dcb0e755 100644 --- a/php/cron/class-lock-file.php +++ b/php/cron/class-lock-file.php @@ -33,7 +33,13 @@ class Lock_File { */ public function get_lock_file( $file = null ) { $lock_file = $this->get_lock_file_name( $file ); - $data = file_get_contents( $lock_file ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown + + if ( ! file_exists( $lock_file ) ) { + return ''; + } + + $data = file_get_contents( $lock_file ); // phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown + if ( false !== strpos( $data, '[' ) ) { $data = json_decode( $data, true ); } @@ -106,5 +112,4 @@ public function delete_lock_file( $file = null ) { $file = $this->get_lock_file_name( $file ); wp_delete_file( $file ); } - } diff --git a/php/media/class-video.php b/php/media/class-video.php index f8a14d87e..72d57041b 100644 --- a/php/media/class-video.php +++ b/php/media/class-video.php @@ -242,9 +242,12 @@ public function has_video_block( $source_block ) { if ( 'core/video' === $source_block['blockName'] ) { return true; } - foreach ( $source_block['innerBlocks'] as $block ) { - if ( $this->has_video_block( $block ) ) { - return true; + + if ( ! empty( $source_block['innerBlocks'] ) ) { + foreach ( $source_block['innerBlocks'] as $block ) { + if ( $this->has_video_block( $block ) ) { + return true; + } } } diff --git a/webpack.config.js b/webpack.config.js index feca91fe7..40f9f00c0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -118,6 +118,7 @@ const cldCore = { loader: MiniCssExtractPlugin.loader, }, 'css-loader', + 'css-unicode-loader', 'sass-loader', ], },