diff --git a/.wordpress-org/blueprints/blueprint.json b/.wordpress-org/blueprints/blueprint.json
new file mode 100644
index 0000000..5cb8cf9
--- /dev/null
+++ b/.wordpress-org/blueprints/blueprint.json
@@ -0,0 +1,30 @@
+{
+ "landingPage": "\/wp-admin\/plugins.php",
+ "preferredVersions": {
+ "php": "8.0",
+ "wp": "latest"
+ },
+ "phpExtensionBundles": [
+ "kitchen-sink"
+ ],
+ "features": {
+ "networking": true
+ },
+ "steps": [
+ {
+ "step": "installPlugin",
+ "pluginZipFile": {
+ "resource": "url",
+ "url": "https:\/\/downloads.wordpress.org\/plugin\/cf-images.zip"
+ },
+ "options": {
+ "activate": true
+ }
+ },
+ {
+ "step": "login",
+ "username": "admin",
+ "password": "password"
+ }
+ ]
+}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 05b3508..6705b8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,20 @@
+= 1.9.2 - 17.07.2024 =
+
+Added:
+* Integration with WPBakery page builder image galleries
+* Integration with Elementor Pro Gallery
+* Integration with Flatsome theme gallery
+* cf_images_upload_host filter to adjust the image host ID
+
+Changed:
+* Improve image AI modules
+* Improve performance when Rank Math image SEO is active
+
+Fixed:
+* Only allow generating image alt text for supported formats (JPEG, PNG, GIF, BMP)
+* Duplicate queries for images that are not part of the media library
+* Rank Math image SEO module not working with custom domains
+
= 1.9.1 - 23.04.2024 =
Added:
diff --git a/README.md b/README.md
index cfb3162..94ea19e 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,12 @@
=== Offload, AI & Optimize with Cloudflare Images ===
Plugin Name: Offload, AI & Optimize with Cloudflare Images
Contributors: vanyukov
-Tags: cdn, cloudflare images, image ai, compress, optimize
+Tags: cdn, cloudflare images, image AI, compress, optimize
Donate link: https://www.paypal.com/donate/?business=JRR6QPRGTZ46N&no_recurring=0&item_name=Help+support+the+development+of+the+Cloudflare+Images+plugin+for+WordPress¤cy_code=AUD
Requires at least: 5.6
Requires PHP: 7.0
-Tested up to: 6.5
-Stable tag: 1.9.1
+Tested up to: 6.6
+Stable tag: 1.9.2
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -102,6 +102,23 @@ If something is still not working for you, please let me know by creating a supp
== Changelog ==
+= 1.9.2 - 17.07.2024 =
+
+Added:
+* Integration with WPBakery page builder image galleries
+* Integration with Elementor Pro Gallery
+* Integration with Flatsome theme gallery
+* cf_images_upload_host filter to adjust the image host ID
+
+Changed:
+* Improve image AI modules
+* Improve performance when Rank Math image SEO is active
+
+Fixed:
+* Only allow generating image alt text for supported formats (JPEG, PNG, GIF, BMP)
+* Duplicate queries for images that are not part of the media library
+* Rank Math image SEO module not working with custom domains
+
= 1.9.1 - 23.04.2024 =
Added:
diff --git a/app/class-core.php b/app/class-core.php
index 9c0ef4e..48be090 100644
--- a/app/class-core.php
+++ b/app/class-core.php
@@ -178,6 +178,8 @@ private function set_cdn_domain() {
* @see Integrations\Spectra
* @see Integrations\Wpml
* @see Integrations\Shortpixel
+ * @see Integrations\JS_Composer
+ * @see Integrations\Flatsome
*/
private function init_integrations() {
$loader = Loader::get_instance();
@@ -189,6 +191,8 @@ private function init_integrations() {
$loader->integration( 'wpml' );
$loader->integration( 'shortpixel' );
$loader->integration( 'elementor' );
+ $loader->integration( 'js-composer' );
+ $loader->integration( 'flatsome' );
}
/**
diff --git a/app/class-image.php b/app/class-image.php
index 860548e..488d7ee 100644
--- a/app/class-image.php
+++ b/app/class-image.php
@@ -439,7 +439,7 @@ private function get_crop_string( int $width, array $size ): string {
private function attachment_url_to_post_id( string $url ): int {
$post_id = wp_cache_get( $url, 'cf_images' );
- if ( ! $post_id ) {
+ if ( false === $post_id ) {
global $wpdb;
$sql = $wpdb->prepare(
@@ -452,16 +452,17 @@ private function attachment_url_to_post_id( string $url ): int {
if ( $results ) {
$post_id = reset( $results )->ID;
- wp_cache_add( $url, $post_id, 'cf_images' );
} else {
// This is a fallback, in case the above doesn't work for some reason.
$results = attachment_url_to_postid( $url );
if ( $results ) {
$post_id = $results;
- wp_cache_add( $url, $post_id, 'cf_images' );
}
}
+
+ // Store this regardless if we have the post ID, prevents duplicate queries.
+ wp_cache_add( $url, $post_id, 'cf_images' );
}
return $post_id;
diff --git a/app/class-media.php b/app/class-media.php
index 86385e7..a132f1d 100644
--- a/app/class-media.php
+++ b/app/class-media.php
@@ -421,7 +421,17 @@ public function upload_image( $metadata, int $attachment_id, string $action = ''
$host = $url['host'];
}
- $name = trailingslashit( $host ) . str_replace( trailingslashit( $dir['basedir'] ), '', $path );
+ /**
+ * This filters allows modifying the host slug in the image path that is used to identify the image on Cloudflare.
+ *
+ * @since 1.9.2
+ *
+ * @param string $host Site domain.
+ * @param int $attachment_id Attachment ID.
+ */
+ $host = apply_filters( 'cf_images_upload_host', $host, $attachment_id );
+
+ $name = ( $host ? trailingslashit( $host ) : '' ) . str_replace( trailingslashit( $dir['basedir'] ), '', $path );
try {
// This allows us to replace the image on Cloudflare.
diff --git a/app/integrations/class-elementor.php b/app/integrations/class-elementor.php
index 9883555..5c889ad 100644
--- a/app/integrations/class-elementor.php
+++ b/app/integrations/class-elementor.php
@@ -17,6 +17,7 @@
use CF_Images\App\Traits;
use Elementor\Widget_Base;
use Elementor\Widget_Image_Carousel;
+use ElementorPro\Modules\Gallery\Widgets\Gallery;
if ( ! defined( 'WPINC' ) ) {
die;
@@ -53,17 +54,18 @@ public function __construct() {
* @param Widget_Base $widget The widget.
*/
public function add_lightbox_support( string $widget_content, Widget_Base $widget ): string {
- if ( ! $widget instanceof Widget_Image_Carousel ) {
+ if ( ! $widget instanceof Widget_Image_Carousel && ! $widget instanceof Gallery ) {
return $widget_content;
}
// Regular expression to find tags with data-elementor-open-lightbox="yes" and Cloudflare Images links.
- $pattern = '/(]*data-elementor-open-lightbox="yes"[^>]*href=")(' . preg_quote( $this->get_cdn_domain(), '/' ) . '[^"]+)(")/i';
+ $pattern = '/(]*href="' . preg_quote( $this->get_cdn_domain(), '/' ) . '[^"#]*)(#[^"]*)?(".*?data-elementor-open-lightbox="yes".*?>)/i';
// Callback function to append '#.jpg' to the href attribute.
$callback = function ( $matches ) {
- // Append '#.jpg' only if it's not already appended.
- return $matches[1] . $matches[2] . ( substr( $matches[2], -5 ) !== '#.jpg' ? '#.jpg' : '' ) . $matches[3];
+ // Check if '#.jpg' is not already appended, and append if necessary.
+ $new_url = $matches[1] . ( isset( $matches[2] ) && strpos( $matches[2], '#.jpg' ) !== false ? $matches[2] : '#.jpg' );
+ return $new_url . $matches[3];
};
return preg_replace_callback( $pattern, $callback, $widget_content );
diff --git a/app/integrations/class-flatsome.php b/app/integrations/class-flatsome.php
new file mode 100644
index 0000000..2c1326f
--- /dev/null
+++ b/app/integrations/class-flatsome.php
@@ -0,0 +1,48 @@
+
+ * @since 1.9.2
+ */
+
+namespace CF_Images\App\Integrations;
+
+use CF_Images\App\Modules\Cloudflare_Images;
+
+if ( ! defined( 'WPINC' ) ) {
+ die;
+}
+
+/**
+ * Flatsome class.
+ *
+ * @since 1.9.2
+ */
+class Flatsome {
+ /**
+ * Class constructor.
+ *
+ * @since 1.9.2
+ */
+ public function __construct() {
+ add_action( 'wp_ajax_flatsome_additional_variation_images_load_images_ajax_frontend', array( $this, 'load_images_ajax' ) );
+ add_action( 'wp_ajax_nopriv_flatsome_additional_variation_images_load_images_ajax_frontend', array( $this, 'load_images_ajax' ) );
+ }
+
+ /**
+ * Add support for additional variation images (Flatsome gallery).
+ *
+ * @since 1.9.2
+ */
+ public function load_images_ajax() {
+ $cf_images = new Cloudflare_Images( 'cloudflare-images' );
+ add_filter( 'wp_get_attachment_image_src', array( $cf_images, 'get_attachment_image_src' ), 10, 3 );
+ }
+}
diff --git a/app/integrations/class-js-composer.php b/app/integrations/class-js-composer.php
new file mode 100644
index 0000000..4d9bc89
--- /dev/null
+++ b/app/integrations/class-js-composer.php
@@ -0,0 +1,94 @@
+
+ * @since 1.9.2
+ */
+
+namespace CF_Images\App\Integrations;
+
+use CF_Images\App\Modules\Cloudflare_Images;
+use CF_Images\App\Traits\Helpers;
+
+if ( ! defined( 'WPINC' ) ) {
+ die;
+}
+
+/**
+ * JS_Composer class.
+ *
+ * @since 1.9.2
+ */
+class JS_Composer {
+ use Helpers;
+
+ /**
+ * Class constructor.
+ *
+ * @since 1.9.2
+ */
+ public function __construct() {
+ add_filter( 'vc_wpb_getimagesize', array( $this, 'fix_getimagesize_paths' ), 10, 3 );
+ }
+
+ /**
+ * When using custom image sizes on gallery images, WPBakery strips out Cloudflare Images parameters,
+ * breaking the images. This fixes the images, by appending the required image parameters.
+ *
+ * @since 1.9.2
+ *
+ * @param array|bool $image_data Array with image data.
+ * @param string|int $attachment_id Attachment ID.
+ * @param array $params Image parameters.
+ *
+ * @return array
+ */
+ public function fix_getimagesize_paths( $image_data, $attachment_id, array $params ): array {
+ if ( ! isset( $image_data['thumbnail'] ) || ! isset( $image_data['p_img_large'] ) || ! is_array( $image_data['p_img_large'] ) ) {
+ return $image_data;
+ }
+
+ $pattern = '/<(?:img|source)\b(?>\s+(?:src=[\'"]([^\'"]*)[\'"]|srcset=[\'"]([^\'"]*)[\'"])|[^\s>]+|\s+)*>/i';
+ if ( ! preg_match_all( $pattern, $image_data['thumbnail'], $images ) ) {
+ do_action( 'cf_images_log', 'Running fix_getimagesize_paths(), `src` not found, returning image. Attachment ID: %s. Image: %s', $attachment_id, $image_data['thumbnail'] );
+ return $image_data;
+ }
+
+ // Check if the image has the 'w' or 'h' attribute set.
+ if ( preg_match( '/[?&]([wh])=\d+/', $images[1][0] ) ) {
+ return $image_data;
+ }
+
+ // The image is not on Cloudflare, exit.
+ if ( false === strpos( $image_data['p_img_large'][0], $this->get_cdn_domain() ) ) {
+ return $image_data;
+ }
+
+ // Now let's add the correct parameters to the original image.
+ if ( ! isset( $params['thumb_size'] ) || ! is_string( $params['thumb_size'] ) || ! preg_match( '/(\d+)x(\d+)/', $params['thumb_size'], $size ) ) {
+ return $image_data;
+ }
+
+ list( $hash, $cloudflare_image_id ) = Cloudflare_Images::get_hash_id_url_string( (int) $attachment_id );
+
+ if ( empty( $cloudflare_image_id ) || ( empty( $hash ) && ! apply_filters( 'cf_images_module_enabled', false, 'custom-path' ) ) ) {
+ return $image_data;
+ }
+
+ $image_url = trailingslashit( $this->get_cdn_domain() . "/$hash" ) . "$cloudflare_image_id/w=$size[1],h=$size[2]";
+ if ( $size[1] === $size[2] ) {
+ $image_url .= ',fit=crop';
+ }
+
+ $image_data['thumbnail'] = str_replace( $images[1][0], $image_url, $image_data['thumbnail'] );
+
+ return $image_data;
+ }
+}
diff --git a/app/integrations/class-rank-math.php b/app/integrations/class-rank-math.php
index 1d6d5fc..38c03c2 100644
--- a/app/integrations/class-rank-math.php
+++ b/app/integrations/class-rank-math.php
@@ -17,6 +17,7 @@
use CF_Images\App\Traits;
use Exception;
use MyThemeShop\Helpers\Str;
+use RankMath\Helper;
use WP_Query;
use function pathinfo;
@@ -32,14 +33,25 @@
class Rank_Math {
use Traits\Helpers;
+ /**
+ * Rank Math Image SEO active flag.
+ *
+ * @since 1.9.2
+ *
+ * @var bool
+ */
+ private $image_seo_active = false;
+
/**
* Class constructor.
*
* @since 1.1.5
*/
public function __construct() {
+ add_action( 'init', array( $this, 'is_image_seo_active' ) );
add_filter( 'rank_math/replacements', array( $this, 'fix_file_name_replacement' ), 10, 2 );
add_filter( 'cf_images_can_run', array( $this, 'can_run' ) );
+ add_action( 'cf_images_get_attachment_image_src', array( $this, 'cache_image_ids' ), 10, 2 );
}
/**
@@ -100,12 +112,21 @@ private function get_image_id_from_url( string $image ) {
return false;
}
- preg_match( '/\/([^\/]+?)\/[^\/]+$/', $image, $matches );
+ $url_path = wp_parse_url( $image, PHP_URL_PATH );
+ $pattern = '/\/[a-zA-Z0-9-]+\/([^\/]+(?:\/[^\/]+)*\.[a-z]+|[^\/]+)(?:\/.*)?/';
+
+ preg_match( $pattern, $url_path, $matches );
if ( ! isset( $matches[1] ) ) {
return false;
}
+ $post_id = wp_cache_get( $matches[1], 'cf_images' );
+
+ if ( false !== $post_id ) {
+ return $post_id;
+ }
+
$args = array(
'fields' => 'ids',
'meta_key' => '_cloudflare_image_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
@@ -124,6 +145,8 @@ private function get_image_id_from_url( string $image ) {
return false;
}
+ wp_cache_add( $matches[1], $results->posts[0], 'cf_images' );
+
return $results->posts[0];
}
@@ -154,4 +177,36 @@ private function get_filename( string $file ) {
return '' !== $name ? $name : null;
}
+
+ /**
+ * Check is Rank Math Image SEO is active.
+ *
+ * @since 1.9.2
+ */
+ public function is_image_seo_active() {
+ if ( ! method_exists( '\RankMath\Helper', 'get_settings' ) ) {
+ return;
+ }
+
+ $is_alt = Helper::get_settings( 'general.add_img_alt' ) && Helper::get_settings( 'general.img_alt_format' ) && trim( Helper::get_settings( 'general.img_alt_format' ) );
+ $is_title = Helper::get_settings( 'general.add_img_title' ) && Helper::get_settings( 'general.img_title_format' ) && trim( Helper::get_settings( 'general.img_title_format' ) );
+
+ $this->image_seo_active = $is_alt || $is_title;
+ }
+
+ /**
+ * Cache Cloudflare Image ID and WordPress attachment ID.
+ *
+ * @since 1.9.2
+ *
+ * @param string $cloudflare_image_id Cloudflare Image ID.
+ * @param int|string $attachment_id Attachment ID.
+ */
+ public function cache_image_ids( string $cloudflare_image_id, $attachment_id ) {
+ if ( ! $this->image_seo_active ) {
+ return;
+ }
+
+ wp_cache_add( $cloudflare_image_id, $attachment_id, 'cf_images' );
+ }
}
diff --git a/app/modules/class-cloudflare-images.php b/app/modules/class-cloudflare-images.php
index 9d7f42b..75500c6 100644
--- a/app/modules/class-cloudflare-images.php
+++ b/app/modules/class-cloudflare-images.php
@@ -159,6 +159,8 @@ public function get_attachment_image_src( $image, $attachment_id, $size ) {
return $image;
}
+ do_action( 'cf_images_get_attachment_image_src', $cloudflare_image_id, $attachment_id );
+
$cf_image = trailingslashit( $this->get_cdn_domain() . "/$hash" ) . $cloudflare_image_id;
// If this is a known crop image.
diff --git a/app/modules/class-image-ai.php b/app/modules/class-image-ai.php
index 56d7167..2bfaad2 100644
--- a/app/modules/class-image-ai.php
+++ b/app/modules/class-image-ai.php
@@ -147,10 +147,10 @@ private function caption_image( int $attachment_id ) {
list( $hash, $cloudflare_image_id ) = Cloudflare_Images::get_hash_id_url_string( $attachment_id );
if ( empty( $cloudflare_image_id ) || ( empty( $hash ) && ! $this->is_module_enabled( false, 'custom-path' ) ) ) {
- $image = wp_get_original_image_url( $attachment_id );
+ $image = wp_get_attachment_image_url( $attachment_id, 'full' );
} else {
// Use the default Cloudflare Images URL here, so we do not get issues with access.
- $image = trailingslashit( "https://imagedelivery.net/$hash" ) . "$cloudflare_image_id/w=9999";
+ $image = trailingslashit( "https://imagedelivery.net/$hash" ) . "$cloudflare_image_id/w=2560";
}
if ( $restore_filter ) {
@@ -230,6 +230,9 @@ public function add_wp_query_args( array $args, string $action ): array {
return $args;
}
+ // Allow only supported mime types.
+ $args['post_mime_type'] = array( 'image/jpeg', 'image/png', 'image/gif', 'image/bmp' );
+
$args['meta_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
array(
'key' => '_wp_attachment_image_alt',
diff --git a/cf-images.php b/cf-images.php
index 27df360..d796dc8 100644
--- a/cf-images.php
+++ b/cf-images.php
@@ -14,7 +14,7 @@
* Plugin Name: Offload Media to Cloudflare Images
* Plugin URI: https://vcore.au
* Description: Offload media library images to the `Cloudflare Images` service.
- * Version: 1.9.1
+ * Version: 1.9.2
* Author: Anton Vanyukov
* Author URI: https://vcore.au
* License: GPL-2.0+
@@ -31,7 +31,7 @@
die;
}
-define( 'CF_IMAGES_VERSION', '1.9.1' );
+define( 'CF_IMAGES_VERSION', '1.9.2' );
define( 'CF_IMAGES_DIR_URL', plugin_dir_url( __FILE__ ) );
require_once 'app/class-activator.php';
diff --git a/package.json b/package.json
index 7abcf72..e5ee6d6 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cf-images",
"description": "Offload, Store, Resize & Optimize with Cloudflare Images",
- "version": "1.9.1",
+ "version": "1.9.2",
"main": "cf-images.php",
"author": "Anton Vanyukov",
"license": "GPL-2.0-or-later",
@@ -10,26 +10,26 @@
"optimization"
],
"devDependencies": {
- "@babel/plugin-transform-react-jsx": "^7.23.4",
- "@babel/preset-env": "^7.24.3",
- "@babel/preset-typescript": "^7.24.1",
+ "@babel/plugin-transform-react-jsx": "^7.24.7",
+ "@babel/preset-env": "^7.24.7",
+ "@babel/preset-typescript": "^7.24.7",
"@creativebulma/bulma-tooltip": "^1.2.0",
- "@types/jquery": "^3.5.29",
- "@types/react": "^18.2.67",
- "@types/react-dom": "^18.2.22",
- "@wordpress/babel-plugin-import-jsx-pragma": "^4.37.0",
- "@wordpress/eslint-plugin": "^17.11.0",
- "@wordpress/i18n": "^4.54.0",
- "@wordpress/prettier-config": "^3.11.0",
+ "@types/jquery": "^3.5.30",
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@wordpress/babel-plugin-import-jsx-pragma": "^5.2.0",
+ "@wordpress/eslint-plugin": "^19.2.0",
+ "@wordpress/i18n": "^5.2.0",
+ "@wordpress/prettier-config": "^4.2.0",
"babel-loader": "^9.1.3",
- "css-loader": "^6.10.0",
- "mini-css-extract-plugin": "^2.8.1",
- "prettier": "^3.2.5",
- "sass": "^1.72.0",
- "sass-loader": "^14.1.1",
+ "css-loader": "^7.1.2",
+ "mini-css-extract-plugin": "^2.9.0",
+ "prettier": "^3.3.2",
+ "sass": "^1.77.6",
+ "sass-loader": "^14.2.1",
"terser-webpack-plugin": "^5.3.10",
- "typescript": "^5.4.3",
- "webpack": "^5.91.0",
+ "typescript": "^5.5.3",
+ "webpack": "^5.92.1",
"webpack-cli": "^5.1.4"
},
"scripts": {
@@ -44,9 +44,9 @@
"bulma": "^0.9.4",
"bulma-switch": "^2.0.4",
"classnames": "^2.5.1",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "react-router-dom": "^6.22.3"
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-router-dom": "^6.24.0"
},
"babel": {
"presets": [