diff --git a/js/inline-loader.asset.php b/js/inline-loader.asset.php index 3a34e2f86..380f5c0cf 100644 --- a/js/inline-loader.asset.php +++ b/js/inline-loader.asset.php @@ -1 +1 @@ - array(), 'version' => '25ccbb8ce37353267420'); + array(), 'version' => '4e42165f1fda84e18ed5'); diff --git a/js/inline-loader.js b/js/inline-loader.js index 2471a028c..e2c766225 100644 --- a/js/inline-loader.js +++ b/js/inline-loader.js @@ -1 +1 @@ -!function(){"use strict";function t(t,e){(null==e||e>t.length)&&(e=t.length);for(var i=0,r=new Array(e);i%26%23x26A0%3B︎',e.rObserver.unobserve(t)}))):this.setupFallback(t)},buildImage:function(t){t.dataset.srcset?(t.cld_loaded=!0,t.srcset=t.dataset.srcset):(t.src=this.getSizeURL(t),t.dataset.responsive&&this.rObserver.observe(t))},inInitialView:function(t){var e=t.getBoundingClientRect();return this.aboveFold=e.top=e;)t-=i,this.sizeBands.push(t);"undefined"!=typeof IntersectionObserver&&this._setupObservers(),this.enabled=!0},_setupObservers:function(){var t=this,e={rootMargin:this.lazyThreshold+"px 0px "+this.lazyThreshold+"px 0px"},i=this.minPlaceholderThreshold<2*this.lazyThreshold?2*this.lazyThreshold:this.minPlaceholderThreshold,r={rootMargin:i+"px 0px "+i+"px 0px"};this.rObserver=new ResizeObserver((function(e,i){e.forEach((function(e){e.target.cld_loaded&&e.contentRect.width>=e.target.cld_loaded&&(e.target.src=t.getSizeURL(e.target))}))})),this.iObserver=new IntersectionObserver((function(e,i){e.forEach((function(e){e.isIntersecting&&(t.buildImage(e.target),i.unobserve(e.target),t.pObserver.unobserve(e.target))}))}),e),this.pObserver=new IntersectionObserver((function(e,i){e.forEach((function(e){e.isIntersecting&&(e.target.src=t.getPlaceholderURL(e.target),i.unobserve(e.target))}))}),r)},_calcThreshold:function(){var t=this.config.lazy_threshold.replace(/[^0-9]/g,""),e=0;switch(this.config.lazy_threshold.replace(/[0-9]/g,"").toLowerCase()){case"em":e=parseFloat(getComputedStyle(document.body).fontSize)*t;break;case"rem":e=parseFloat(getComputedStyle(document.documentElement).fontSize)*t;break;case"vh":e=window.innerHeight/t*100;break;default:e=t}this.lazyThreshold=parseInt(e,10)},_getDensity:function(){var t=this.config.dpr?this.config.dpr.replace("X",""):"off";if("off"===t)return this.density=1,1;var e=this.deviceDensity;"max"!==t&&"auto"!==e&&(t=parseFloat(t),e=e>Math.ceil(t)?t:e),this.density=e},scaleWidth:function(t,e,i){var r=parseInt(this.config.max_width),n=Math.round(r/i);if(!e){e=t.width;for(var a=Math.round(e/i);-1===this.sizeBands.indexOf(e)&&ar&&(e=r),t.originalWidtht.length)&&(e=t.length);for(var i=0,r=new Array(e);i%26%23x26A0%3B︎',e.rObserver.unobserve(t)}))):this.setupFallback(t)},buildImage:function(t){t.dataset.srcset?(t.cld_loaded=!0,t.srcset=t.dataset.srcset):(t.src=this.getSizeURL(t),t.dataset.responsive&&this.rObserver.observe(t))},inInitialView:function(t){var e=t.getBoundingClientRect();return this.aboveFold=e.top=e;)t-=i,this.sizeBands.push(t);"undefined"!=typeof IntersectionObserver&&this._setupObservers(),this.enabled=!0},_setupObservers:function(){var t=this,e={rootMargin:this.lazyThreshold+"px 0px "+this.lazyThreshold+"px 0px"},i=this.minPlaceholderThreshold<2*this.lazyThreshold?2*this.lazyThreshold:this.minPlaceholderThreshold,r={rootMargin:i+"px 0px "+i+"px 0px"};this.rObserver=new ResizeObserver((function(e,i){e.forEach((function(e){e.target.cld_loaded&&e.contentRect.width>=e.target.cld_loaded&&(e.target.src=t.getSizeURL(e.target))}))})),this.iObserver=new IntersectionObserver((function(e,i){e.forEach((function(e){e.isIntersecting&&(t.buildImage(e.target),i.unobserve(e.target),t.pObserver.unobserve(e.target))}))}),e),this.pObserver=new IntersectionObserver((function(e,i){e.forEach((function(e){e.isIntersecting&&(e.target.src=t.getPlaceholderURL(e.target),i.unobserve(e.target))}))}),r)},_calcThreshold:function(){var t=this.config.lazy_threshold.replace(/[^0-9]/g,""),e=0;switch(this.config.lazy_threshold.replace(/[0-9]/g,"").toLowerCase()){case"em":e=parseFloat(getComputedStyle(document.body).fontSize)*t;break;case"rem":e=parseFloat(getComputedStyle(document.documentElement).fontSize)*t;break;case"vh":e=window.innerHeight/t*100;break;default:e=t}this.lazyThreshold=parseInt(e,10)},_getDensity:function(){var t=this.config.dpr?this.config.dpr.replace("X",""):"off";if("off"===t)return this.density=1,1;var e=this.deviceDensity;"max"!==t&&"auto"!==e&&(t=parseFloat(t),e=e>Math.ceil(t)?t:e),this.density=e},scaleWidth:function(t,e,i){var r=parseInt(this.config.max_width),n=Math.round(r/i);if(!e){e=t.width;for(var a=Math.round(e/i);-1===this.sizeBands.indexOf(e)&&ar&&(e=r),t.originalWidthmedia->get_cloudinary_version( $tag_element['id'] ); + /** + * Bypass Cloudinary's SEO URLs. + * + * @hook cloudinary_bypass_seo_url + * @since 3.1.5 + * + * @param $bypass_seo_url {bool} Whether to bypass SEO URLs. + * + * @return {bool} + */ + $bypass_seo_url = apply_filters( 'cloudinary_bypass_seo_url', false ); + + $tag_element['atts']['data-seo'] = ! $bypass_seo_url; + + $resource_type = in_array( $tag_element['type'], array( 'image', 'video' ), true ) ? $tag_element['type'] : 'raw'; + + $args = array( + 'delivery' => $this->media->get_media_delivery( $tag_element['id'] ), + 'resource_type' => $resource_type, + ); + + $tag_element['atts']['data-public-id'] = $this->plugin->get_component( 'connect' )->api->get_public_id( $tag_element['id'], '', $args ); + return $tag_element; } @@ -1182,9 +1209,10 @@ public function rebuild_tag( $tag_element ) { if ( apply_filters( 'cloudinary_apply_breakpoints', true ) ) { $meta = wp_get_attachment_metadata( $tag_element['id'] ); if ( ! empty( $meta['width'] ) && ! empty( $meta['height'] ) ) { + $relationship = Relationship::get_relationship( $tag_element['id'] ); // Check overwrite. $meta['overwrite_transformations'] = $tag_element['overwrite_transformations']; - $meta['cloudinary_id'] = $tag_element['atts']['data-public-id']; + $meta['cloudinary_id'] = $relationship->public_id; $meta['transformations'] = $tag_element['transformations']; // Add new srcset. $element = wp_image_add_srcset_and_sizes( $tag_element['original'], $meta, $tag_element['id'] ); @@ -1318,7 +1346,7 @@ public function parse_element( $element ) { $tag_element['height'] = ! empty( $attributes['height'] ) ? $attributes['height'] : $item['height']; $attributes['data-public-id'] = $public_id; $tag_element['format'] = $item['format']; - + if ( 'img' === $tag_element['tag'] ) { // Check if this is a crop or a scale. $has_size = $this->media->get_size_from_url( $this->sanitize_url( $raw_url ) ); diff --git a/php/class-media.php b/php/class-media.php index 180919a5b..3443c3570 100644 --- a/php/class-media.php +++ b/php/class-media.php @@ -1383,7 +1383,7 @@ public function cloudinary_url( $attachment_id, $size = array(), $transformation 'secure' => is_ssl(), 'version' => $this->get_cloudinary_version( $attachment_id ), 'resource_type' => $resource_type, - 'delivery_type' => $delivery, + 'delivery' => $delivery, ); $set_size = array(); if ( 'upload' === $delivery ) { @@ -1397,7 +1397,7 @@ public function cloudinary_url( $attachment_id, $size = array(), $transformation // Make a copy as not to destroy the options in \Cloudinary::cloudinary_url(). $args = $pre_args; - $url = $this->plugin->components['connect']->api->cloudinary_url( $cloudinary_id, $args, $set_size ); + $url = $this->plugin->components['connect']->api->cloudinary_url( $cloudinary_id, $args, $set_size, $attachment_id ); // Check if this type is a preview only type. i.e PDF. if ( ! empty( $set_size ) && $this->is_preview_only( $attachment_id ) ) { diff --git a/php/connect/class-api.php b/php/connect/class-api.php index 1a91ff23f..a3f392c12 100644 --- a/php/connect/class-api.php +++ b/php/connect/class-api.php @@ -7,8 +7,9 @@ namespace Cloudinary\Connect; +use Cloudinary\Relate\Relationship; +use Cloudinary\Sync; use Cloudinary\Utils; -use function Cloudinary\get_plugin_instance; use Cloudinary\Plugin; use Cloudinary\Media; @@ -196,7 +197,19 @@ public function url( $resource, $function = null, $endpoint = false ) { $parts[] = $this->credentials['cloud_name']; } - if ( false === $endpoint && 'image' === $resource && 'upload' === $function ) { + /** + * Bypass Cloudinary's SEO URLs. + * + * @hook cloudinary_bypass_seo_url + * @since 3.1.5 + * + * @param $bypass_seo_url {bool} Whether to bypass SEO URLs. + * + * @return {bool} + */ + $bypass_seo_url = apply_filters( 'cloudinary_bypass_seo_url', false ); + + if ( false === $endpoint && 'image' === $resource && 'upload' === $function && ! $bypass_seo_url ) { $parts[] = 'images'; } else { $parts[] = $resource; @@ -252,20 +265,21 @@ function ( $item ) use ( $transformation_index ) { /** * Generate a Cloudinary URL. * - * @param string|null $public_id The Public ID to get a url for. - * @param array $args Additional args. - * @param array $size The WP Size array. + * @param string|null $public_id The Public ID to get a url for. + * @param array $args Additional args. + * @param array $size The WP Size array. + * @param int|null $attachment_id The attachment ID. * * @return string */ - public function cloudinary_url( $public_id = null, $args = array(), $size = array() ) { + public function cloudinary_url( $public_id = null, $args = array(), $size = array(), $attachment_id = null ) { if ( null === $public_id ) { return 'https://' . $this->url( null, null ); } $defaults = array( 'resource_type' => 'image', - 'delivery_type' => 'upload', + 'delivery' => 'upload', 'version' => 'v1', ); $args = wp_parse_args( array_filter( $args ), $defaults ); @@ -277,19 +291,14 @@ public function cloudinary_url( $public_id = null, $args = array(), $size = arra // Determine if we're dealing with a fetched. // ...or uploaded image and update the URL accordingly. - $asset_endpoint = filter_var( $public_id, FILTER_VALIDATE_URL ) ? 'fetch' : $args['delivery_type']; + $asset_endpoint = filter_var( $public_id, FILTER_VALIDATE_URL ) ? 'fetch' : $args['delivery']; $url_parts = array( 'https:/', $this->url( $args['resource_type'], $asset_endpoint ), ); - $base = Utils::pathinfo( $public_id ); - // Only do dynamic naming and sizes if upload type. - if ( 'image' === $args['resource_type'] && 'upload' === $args['delivery_type'] ) { - $new_path = $base['filename'] . '/' . $base['basename']; - $public_id = str_replace( $base['basename'], $new_path, $public_id ); - } + $base = Utils::pathinfo( $public_id ); // Add size. if ( ! empty( $size ) && is_array( $size ) ) { if ( ! empty( $size['transformation'] ) ) { @@ -304,6 +313,10 @@ public function cloudinary_url( $public_id = null, $args = array(), $size = arra $url_parts[] = self::generate_transformation_string( $args['transformation'], $args['resource_type'] ); } + if ( $attachment_id ) { + $public_id = $this->get_public_id( $attachment_id, $public_id, $args ); + } + $url_parts[] = $args['version']; $url_parts[] = $public_id; @@ -780,6 +793,73 @@ public function get_upload_prefix() { return $upload_prefix; } + /** + * Get the Cloudinary public_id. + * + * @param int $attachment_id The attachment ID. + * @param null|string $original_public_id The original public ID. + * @param array $args The args. + * + * @return string + */ + public function get_public_id( $attachment_id, $original_public_id = null, $args = array() ) { + + $relationship = Relationship::get_relationship( $attachment_id ); + $public_id = null; + + if ( $relationship instanceof Relationship ) { + $public_id = $relationship->public_id; + } + + /** + * Bypass Cloudinary's SEO URLs. + * + * @hook cloudinary_bypass_seo_url + * @since 3.1.5 + * + * @param $bypass_seo_url {bool} Whether to bypass SEO URLs. + * + * @return {bool} + */ + $bypass_seo_url = apply_filters( 'cloudinary_bypass_seo_url', false ); + + if ( + $public_id + && ! $bypass_seo_url + && ( + // Get the SEO `public_id` for images with `upload` delivery. + ( + ! empty( $args['resource_type'] ) && 'image' === $args['resource_type'] + && ! empty( $args['delivery'] ) && 'upload' === $args['delivery'] + ) + // Get the SEO `public_id` for PDFs as they are regarded as images. + || ( + ! empty( $args['resource_type'] ) && 'image' === $args['resource_type'] + && 'application' === $relationship->asset_type + ) + ) + ) { + + $parts = explode( '/', $public_id ); + $filename = end( $parts ); + + /** + * Filter the SEO public ID. + * + * @hook cloudinary_seo_public_id + * @since 3.1.5 + * + * @param $public_id {string} The suffixed public_id. + * @param $original_public_id {string} The original public_id. + * + * @return {string} + */ + $public_id = apply_filters( 'cloudinary_seo_public_id', "{$public_id}/{$filename}.{$relationship->format}", $original_public_id ); + } + + return $public_id; + } + /** * Check if a string is a qualified `upload_prefix`. * diff --git a/php/relate/class-relationship.php b/php/relate/class-relationship.php index 56a1b9489..13a9b22ca 100644 --- a/php/relate/class-relationship.php +++ b/php/relate/class-relationship.php @@ -19,6 +19,7 @@ * @property string|null $public_id * @property string|null $signature * @property string|null $transformations + * @property string|null $sized_url */ class Relationship { diff --git a/src/js/inline-loader.js b/src/js/inline-loader.js index 45a4c9573..18a54019a 100644 --- a/src/js/inline-loader.js +++ b/src/js/inline-loader.js @@ -215,13 +215,15 @@ const CloudinaryLoader = { nameExtension: scaledWidth + 'x' + scaledHeight, }; }, + getDeliveryMethod( image ) { + return image.dataset.seo && 'upload' === image.dataset.delivery ? 'images' : 'image/' + image.dataset.delivery; + }, getSizeURL( image, width ) { const newSize = this.scaleSize( image, width, true ); const parts = [ this.config.base_url, - 'image', - image.dataset.delivery, + this.getDeliveryMethod( image ), 'upload' === image.dataset.delivery ? newSize.transformation : '', image.dataset.transformations, 'v' + image.dataset.version, @@ -236,8 +238,7 @@ const CloudinaryLoader = { const parts = [ this.config.base_url, - 'image', - image.dataset.delivery, + this.getDeliveryMethod( image ), newSize.transformation, this.config.placeholder, image.dataset.publicId