diff --git a/.github/workflows/diff-translations.yml b/.github/workflows/diff-translations.yml new file mode 100644 index 000000000..f0ce5a58d --- /dev/null +++ b/.github/workflows/diff-translations.yml @@ -0,0 +1,53 @@ +name: Translations Diff + +on: + pull_request_review: + pull_request: + types: [opened, edited, synchronize, ready_for_review] + branches: + - development + - master + +jobs: + translation: + runs-on: ubuntu-latest + steps: + - name: Checkout Base Branch + uses: actions/checkout@v4 + with: + ref: ${{ github.base_ref }} + path: optimole-base + - name: Setup node 16 + uses: actions/setup-node@v4 + with: + node-version: 16.x + - name: Build POT for Base Branch + run: | + cd optimole-base + composer install --no-dev --prefer-dist --no-progress --no-suggest + npm ci + npm run build + # TODO: when is merged to master, switch to npm run make-pot + docker run --user root --rm --volume $(pwd):/var/www/html/optimole-wp wordpress:cli bash -c 'php -d memory_limit=512M $(which wp) --version --allow-root && wp i18n make-pot optimole-wp ./optimole-wp/languages/optimole-wp.pot --include=inc,assets/src --allow-root --domain=optimole-wp' + ls languages/ + - name: Checkout PR Branch (Head) + uses: actions/checkout@v4 + with: + path: optimole-head + - name: Build POT for PR Branch + run: | + cd optimole-head + composer install --no-dev --prefer-dist --no-progress --no-suggest + npm ci + npm run build + npm run make-pot + ls languages/ + - name: Compare POT files + uses: Codeinwp/action-i18n-string-reviewer@main + with: + fail-on-changes: 'true' + openrouter-key: ${{ secrets.OPEN_ROUTER_API_KEY }} + openrouter-model: 'google/gemini-2.5-flash' + base-pot-file: 'optimole-base/languages/optimole-wp.pot' + target-pot-file: 'optimole-head/languages/optimole-wp.pot' + github-token: ${{ secrets.BOT_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 98897f479..2a3d29e16 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ assets/build test-results tests/assets/filestash coverage +languages \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cba5afa68..368c011e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -998,7 +998,7 @@ Fix edge cases for auto allowing domain on site migration. **Documentation** -* improve readme description of the OptiMole service ([e020300](https://github.com/Codeinwp/optimole-wp/commit/e020300)) +* improve readme description of the Optimole service ([e020300](https://github.com/Codeinwp/optimole-wp/commit/e020300)) diff --git a/README.md b/README.md index 2d130188a..92b961626 100644 --- a/README.md +++ b/README.md @@ -262,12 +262,12 @@ Discover how to make the most of Optimole with our detailed and user-friendly [d ## Installation ## -The following are the steps to install the OptiMole plugin +The following are the steps to install the Optimole plugin 1. In your WordPress Administration Panels, click on Add New option under Plugins from the menu. Click on upload at the top. -2. Browse the location and select the OptiMole Plugin and click install now. -3. Go to Media -> OptiMole and follow in the instructions on how to enable the service. +2. Browse the location and select the Optimole Plugin and click install now. +3. Go to Media -> Optimole and follow in the instructions on how to enable the service. ## Frequently Asked Questions ## diff --git a/assets/src/dashboard/parts/connected/dashboard/LastImages.js b/assets/src/dashboard/parts/connected/dashboard/LastImages.js index 83ac67730..82e6d845d 100644 --- a/assets/src/dashboard/parts/connected/dashboard/LastImages.js +++ b/assets/src/dashboard/parts/connected/dashboard/LastImages.js @@ -55,7 +55,7 @@ const Image = ({ } } ref={ squareRef } /> -

{ getSize() }% { optimoleDashboardApp.strings.latest_images.saved }

+

{ optimoleDashboardApp.strings.latest_images.percentage_saved.replace( '{ratio}', getSize() ) }

); }; @@ -136,7 +136,7 @@ const LastImages = () => { return (
-

{ optimoleDashboardApp.strings.latest_images.last } { optimoleDashboardApp.strings.latest_images.optimized_images }

+

{ optimoleDashboardApp.strings.latest_images.last_optimized_images }

{ ( isInitialLoading && ! isLoaded ) && (
diff --git a/assets/src/dashboard/parts/connected/settings/Compression.js b/assets/src/dashboard/parts/connected/settings/Compression.js index a55ac8998..b44dc63c0 100644 --- a/assets/src/dashboard/parts/connected/settings/Compression.js +++ b/assets/src/dashboard/parts/connected/settings/Compression.js @@ -352,7 +352,7 @@ const Compression = ({ { ( sampleImages.id && 0 < sampleImages.original_size ) && (
{ 0 < getCompressionRatio() ? ( -

{ 100 - getCompressionRatio() }% { optimoleDashboardApp.strings.latest_images.smaller }

+

{ optimoleDashboardApp.strings.latest_images.percentage_smaller.replace( '{ratio}', 100 - getCompressionRatio() ) }

) : (

{ optimoleDashboardApp.strings.latest_images.same_size }

) } diff --git a/assets/src/dashboard/utils/api.js b/assets/src/dashboard/utils/api.js index e89332259..63f7dfd86 100644 --- a/assets/src/dashboard/utils/api.js +++ b/assets/src/dashboard/utils/api.js @@ -153,7 +153,7 @@ export const connectAccount = ( data, callback = () => {}) => { sendOnboardingImages(); toggleDashboardSidebarSubmenu( true ); - console.log( '%c OptiMole API connection successful.', 'color: #59B278' ); + console.log( '%c Optimole API connection successful.', 'color: #59B278' ); } else { setHasValidKey( false ); @@ -195,7 +195,7 @@ export const disconnectAccount = () => { sethasDashboardLoaded( false ); setShowDisconnect( false ); toggleDashboardSidebarSubmenu( false ); - console.log( '%c Disconnected from OptiMole API.', 'color: #59B278' ); + console.log( '%c Disconnected from Optimole API.', 'color: #59B278' ); } else { console.error( response ); } @@ -221,7 +221,7 @@ export const selectDomain = ( data, callback = () => {}) => { setAPIKey( response.data.api_key ); setUserData( response.data ); setAvailableApps( response.data ); - console.log( '%c OptiMole API connection successful.', 'color: #59B278' ); + console.log( '%c Optimole API connection successful.', 'color: #59B278' ); } else { setHasValidKey( false ); console.log( '%c Invalid API Key.', 'color: #E7602A' ); @@ -271,7 +271,7 @@ export const requestStatsUpdate = () => { if ( 'disconnected' === response.code ) { setIsConnected( false ); sethasDashboardLoaded( false ); - console.log( '%c Disconnected from OptiMole API.', 'color: #59B278' ); + console.log( '%c Disconnected from Optimole API.', 'color: #59B278' ); } }); }; diff --git a/assets/src/global.d.ts b/assets/src/global.d.ts index df811f82d..541e9aeac 100644 --- a/assets/src/global.d.ts +++ b/assets/src/global.d.ts @@ -481,10 +481,9 @@ export interface LatestImages { no_images_found: string compression: string loading_latest_images: string - last: string - saved: string - smaller: string - optimized_images: string + last_optimized_images: string + percentage_saved: string + percentage_smaller: string same_size: string small_optimization: string medium_optimization: string diff --git a/assets/src/widget/components/WidgetFooter.js b/assets/src/widget/components/WidgetFooter.js index 14753ee13..b399fd020 100644 --- a/assets/src/widget/components/WidgetFooter.js +++ b/assets/src/widget/components/WidgetFooter.js @@ -1,7 +1,7 @@ import { Icon, external } from '@wordpress/icons'; export default function WidgetFooter() { - const { i18n, dashboardURL, adminPageURL } = optimoleDashboardWidget; + const { i18n, dashboardURL, dashboardMetricsURL } = optimoleDashboardWidget; return (
@@ -10,8 +10,9 @@ export default function WidgetFooter() { Optimole - + { i18n.viewAllStats } +
); diff --git a/composer.lock b/composer.lock index 18aefafa4..2ea1c186b 100644 --- a/composer.lock +++ b/composer.lock @@ -64,16 +64,16 @@ }, { "name": "codeinwp/themeisle-sdk", - "version": "3.3.49", + "version": "3.3.50", "source": { "type": "git", "url": "https://github.com/Codeinwp/themeisle-sdk.git", - "reference": "605f78bbbd8526f7597a89077791043d9ecc8c20" + "reference": "3c1f8dfc2390e667bbc086c5d660900a7985efa6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/605f78bbbd8526f7597a89077791043d9ecc8c20", - "reference": "605f78bbbd8526f7597a89077791043d9ecc8c20", + "url": "https://api.github.com/repos/Codeinwp/themeisle-sdk/zipball/3c1f8dfc2390e667bbc086c5d660900a7985efa6", + "reference": "3c1f8dfc2390e667bbc086c5d660900a7985efa6", "shasum": "" }, "require-dev": { @@ -99,9 +99,9 @@ ], "support": { "issues": "https://github.com/Codeinwp/themeisle-sdk/issues", - "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.49" + "source": "https://github.com/Codeinwp/themeisle-sdk/tree/v3.3.50" }, - "time": "2025-09-18T13:41:05+00:00" + "time": "2025-11-25T19:36:35+00:00" }, { "name": "enshrined/svg-sanitize", diff --git a/inc/admin.php b/inc/admin.php index 0b5b705c1..1ca0dd712 100755 --- a/inc/admin.php +++ b/inc/admin.php @@ -635,22 +635,17 @@ public function add_notice_upgrade() { } ?>
+ style="background-color: #577BF9; color:white; border: none !important; display: flex; align-items: center;">
- '> + '>
-
+

', - number_format_i18n( 2000 ), - '', - '', - '', - '

' + /* translators: 1 - visits limit */ + __( 'You\'re nearing your %1$s-visit monthly cap on Optimole. If you exceed it, we\'ll serve original (unoptimized) images - expect slower pages.', 'optimole-wp' ), + '' . number_format_i18n( 2000 ) . '' ); ?>

@@ -864,7 +859,7 @@ public function add_notice() {

+ class="button button-primary button-hero"> @@ -2286,10 +2281,11 @@ private function get_dashboard_strings() { 'no_images_found' => sprintf( /* translators: 1 is the starting anchor tag, 2 is the ending anchor tag */ __( 'We are currently optimizing your images. Meanwhile you can visit your %1$shomepage%2$s and check how our plugin performs.', 'optimole-wp' ), '', '' ), 'compression' => __( 'Optimization', 'optimole-wp' ), 'loading_latest_images' => __( 'Loading your optimized images...', 'optimole-wp' ), - 'last' => __( 'Last', 'optimole-wp' ), - 'saved' => __( 'Saved', 'optimole-wp' ), - 'smaller' => __( 'smaller', 'optimole-wp' ), - 'optimized_images' => __( 'optimized images', 'optimole-wp' ), + 'last_optimized_images' => __( 'Last optimized images', 'optimole-wp' ), + // translators: %s is the percentage (e.g. 10%). + 'percentage_saved' => sprintf( __( '%s Saved', 'optimole-wp' ), '{ratio}%' ), + // translators: %s is the percentage (e.g. 10%). + 'percentage_smaller' => sprintf( __( '%s smaller', 'optimole-wp' ), '{ratio}%' ), 'same_size' => __( '🙉 We couldn\'t do better, this image is already optimized at maximum.', 'optimole-wp' ), 'small_optimization' => __( '😬 Not that much, just {ratio} smaller.', 'optimole-wp' ), 'medium_optimization' => __( '🤓 We are on the right track, {ratio} squeezed.', 'optimole-wp' ), diff --git a/inc/cli/cli_setting.php b/inc/cli/cli_setting.php index 2ef670ffa..743d7dd6f 100644 --- a/inc/cli/cli_setting.php +++ b/inc/cli/cli_setting.php @@ -32,12 +32,11 @@ public function connect( $args ) { if ( $data === false || is_wp_error( $data ) ) { $extra = ''; if ( is_wp_error( $data ) ) { - /** - * Error from api. - * - * @var WP_Error $data Error object. - */ - $extra = sprintf( /* translators: errors details */ __( '. ERROR details: %s', 'optimole-wp' ), $data->get_error_message() ); + $extra = sprintf( + /* translators: Error details */ + __( '. ERROR details: %s', 'optimole-wp' ), + $data->get_error_message() + ); } return \WP_CLI::error( __( 'Can not connect to Optimole service', 'optimole-wp' ) . $extra ); diff --git a/inc/compatibilities/aruba_hsc.php b/inc/compatibilities/aruba_hsc.php new file mode 100644 index 000000000..52df8bafe --- /dev/null +++ b/inc/compatibilities/aruba_hsc.php @@ -0,0 +1,68 @@ +setPurger( AHSC_PURGER ); + + // Purge all cache when no location is provided. + if ( $location === true && method_exists( $purge, 'purgeAll' ) ) { + $purge->purgeAll(); + return; + } + + // Purge single URL based on the location parameter. + if ( ! method_exists( $purge, 'purgeUrl' ) ) { + return; + } + + $purge->purgeUrl( $location ); + } +} diff --git a/inc/compatibilities/beaver_builder.php b/inc/compatibilities/beaver_builder.php index ffc400b87..82fe3bd9e 100644 --- a/inc/compatibilities/beaver_builder.php +++ b/inc/compatibilities/beaver_builder.php @@ -28,6 +28,7 @@ public function register() { function ( $all_watchers ) { $all_watchers[] = '.fl-col-content'; $all_watchers[] = '.fl-row-bg-photo > .fl-row-content-wrap'; + $all_watchers[] = '.fl-module-box'; return $all_watchers; } diff --git a/inc/compatibilities/cache_enabler.php b/inc/compatibilities/cache_enabler.php index 4dd046c0d..11c80991b 100644 --- a/inc/compatibilities/cache_enabler.php +++ b/inc/compatibilities/cache_enabler.php @@ -32,6 +32,23 @@ function () { do_action( 'cache_enabler_clear_site_cache' ); } ); + + add_action( 'optml_clear_cache', [ $this, 'add_clear_cache_action' ] ); + } + + /** + * Clear cache for Super Page Cache for Cloudflare. + * + * @param string|bool $location The location to clear the cache for. If true, clear the cache globally. If a string, clear the cache for a particular url. + * @return void + */ + public function add_clear_cache_action( $location ) { + if ( $location === true ) { + do_action( 'cache_enabler_clear_site_cache' ); + return; + } + + do_action( 'cache_enabler_clear_page_cache_by_url', $location ); } /** diff --git a/inc/compatibilities/elementor_builder.php b/inc/compatibilities/elementor_builder.php index a910dec70..891e6acd9 100644 --- a/inc/compatibilities/elementor_builder.php +++ b/inc/compatibilities/elementor_builder.php @@ -33,6 +33,11 @@ public function register() { function ( $all_watchers ) { $all_watchers[] = '.elementor-widget-container'; $all_watchers[] = '.elementor-background-slideshow__slide__image'; + $all_watchers[] = '.elementor-section[data-settings*="background_background"]'; + $all_watchers[] = '.elementor-column[data-settings*="background_background"] > .elementor-widget-wrap'; + $all_watchers[] = '.elementor-element[data-settings*="background_background"]'; + $all_watchers[] = '.elementor-section > .elementor-background-overlay'; + return $all_watchers; } ); diff --git a/inc/compatibilities/essential_blocks.php b/inc/compatibilities/essential_blocks.php new file mode 100644 index 000000000..835fc4c76 --- /dev/null +++ b/inc/compatibilities/essential_blocks.php @@ -0,0 +1,37 @@ +clear_cache(); + return; + } + + // Clear specific URL + if ( ! is_string( $location ) || empty( $location ) ) { + return; + } + + $url_path = wp_parse_url( $location, PHP_URL_PATH ); + if ( $url_path ) { + $page_cache->clear_cache( trailingslashit( $url_path ), true ); + } + } +} diff --git a/inc/compatibilities/kadence_blocks.php b/inc/compatibilities/kadence_blocks.php new file mode 100644 index 000000000..bf7926c3a --- /dev/null +++ b/inc/compatibilities/kadence_blocks.php @@ -0,0 +1,39 @@ +manager->url_replacer, 'build_url' ], 99 ); + + // Ensure replacer is initialized for Otter REST API routes (where register_hooks isn't called). + add_filter( 'rest_pre_dispatch', [ $this, 'maybe_init_replacer_for_rest' ], 10, 3 ); + } + + /** + * Initialize replacer for Otter REST API routes. + * + * @param mixed $result Response to replace the requested version with. + * @param \WP_REST_Server $server Server instance. + * @param \WP_REST_Request $request Request used to generate the response. + * @return mixed Unmodified result. + */ + public function maybe_init_replacer_for_rest( $result, \WP_REST_Server $server, \WP_REST_Request $request ) { + $route = $request->get_route(); + + // Only initialize for Otter styles REST routes. + if ( strpos( $route, '/otter/v1/post_styles' ) === false + && strpos( $route, '/otter/v1/widget_styles' ) === false + && strpos( $route, '/otter/v1/block_styles' ) === false + ) { + return $result; + } + + if ( ! did_action( 'optml_replacer_setup' ) ) { + do_action( 'optml_replacer_setup' ); + } + + return $result; + } + + /** + * Should we early load the compatibility? + * + * @return bool Whether to load the compatibility early. + */ + public function should_load_early() { + return true; } } diff --git a/inc/compatibilities/spc.php b/inc/compatibilities/spc.php new file mode 100644 index 000000000..e8fd786d8 --- /dev/null +++ b/inc/compatibilities/spc.php @@ -0,0 +1,54 @@ + tsdk_translate_link( 'https://dashboard.optimole.com/settings/billing', 'query' ), 'serviceData' => $this->get_service_data(), 'assetsURL' => OPTML_URL . 'assets/', - 'adminPageURL' => esc_url( admin_url( 'admin.php?page=optimole' ) ), + 'dashboardMetricsURL' => esc_url( 'https://dashboard.optimole.com/metrics' ), 'dashboardURL' => esc_url( tsdk_translate_link( 'https://dashboard.optimole.com' ) ), ]; } diff --git a/inc/lazyload_replacer.php b/inc/lazyload_replacer.php index 1284a71e3..bf5114ddb 100644 --- a/inc/lazyload_replacer.php +++ b/inc/lazyload_replacer.php @@ -125,9 +125,6 @@ public static function get_background_lazyload_selectors() { return self::$background_lazyload_selectors; } $default_watchers = [ - '.elementor-section[data-settings*="background_background"]', - '.elementor-column[data-settings*="background_background"] > .elementor-widget-wrap', - '.elementor-section > .elementor-background-overlay', '[class*="wp-block-cover"][style*="background-image"]', '[style*="background-image:url("]', '[style*="background-image: url("]', '[style*="background:url("]', '[style*="background: url("]', diff --git a/inc/manager.php b/inc/manager.php index a0a25eb75..b6d90c0cf 100644 --- a/inc/manager.php +++ b/inc/manager.php @@ -69,6 +69,8 @@ final class Optml_Manager { * @var array Integrations classes. */ private $possible_compatibilities = [ + 'kadence_blocks', + 'essential_blocks', 'shortcode_ultimate', 'foogallery', 'envira', @@ -110,6 +112,9 @@ final class Optml_Manager { 'endurance_cache', 'rocketnet', 'speedycache', + 'hummingbird', + 'aruba_hsc', + 'spc', ]; /** * The current state of the buffer. diff --git a/optimole-wp.php b/optimole-wp.php index baf09f498..b4b84aa1c 100644 --- a/optimole-wp.php +++ b/optimole-wp.php @@ -58,14 +58,12 @@ function optml_php_notice() { ', '7.4', '', '', - '', - '', '', '', '

' diff --git a/package.json b/package.json index ff0c32062..924b194cb 100755 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "release": "semantic-release --debug", "e2e:open": "playwright test --ui", "e2e:run": "playwright test", - "dist": "./bin/dist.sh" + "dist": "./bin/dist.sh", + "make-pot": "docker run --user root --rm --volume $(pwd):/var/www/html/optimole-wp wordpress:cli bash -c 'php -d memory_limit=512M $(which wp) --version --allow-root && wp i18n make-pot optimole-wp ./optimole-wp/languages/optimole-wp.pot --include=inc,assets/src --allow-root --domain=optimole-wp'" }, "devDependencies": { "@playwright/test": "^1.50.1", diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index a002da255..e60e42483 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1020,6 +1020,12 @@ parameters: count: 1 path: inc/compatibilities/metaslider.php + - + message: '#^Method Optml_otter_blocks\:\:maybe_init_replacer_for_rest\(\) has parameter \$request with generic class WP_REST_Request but does not specify its types\: T$#' + identifier: missingType.generics + count: 1 + path: inc/compatibilities/otter_blocks.php + - message: '#^Method Optml_otter_blocks\:\:register\(\) has no return type specified\.$#' identifier: missingType.return diff --git a/readme.txt b/readme.txt index 7a30912a9..dcfe6eed2 100755 --- a/readme.txt +++ b/readme.txt @@ -70,8 +70,7 @@ Optimize your entire media library with a single click. Optimole processes your #### 🚀 One-Click Image Offloading [PRO] -Free up valuable server space by offloading your entire media library to Optimole's secure cloud storage. All your images are safely stored and delivered from our optimized CDN, reducing your hosting costs and server load. Need your images back on your server? Our seamless one-click restoration process makes it easy to transfer everything back -whenever you want, giving you complete flexibility and control over your media assets. +Free up valuable server space by offloading your entire media library to Optimole's secure cloud storage. All your images are safely stored and delivered from our optimized CDN, reducing your hosting costs and server load. Need your images back on your server? Our seamless one-click restoration process makes it easy to transfer everything back whenever you want, giving you complete flexibility and control over your media assets. #### 🌎 CDN @@ -262,12 +261,12 @@ Discover how to make the most of Optimole with our detailed and user-friendly [d == Installation == -The following are the steps to install the OptiMole plugin +The following are the steps to install the Optimole plugin -1. In your WordPress Administration Panels, click on Add New option under Plugins from the menu. +1. In your WordPress Dashboard, click on Add New option under Plugins from the menu. Click on upload at the top. -2. Browse the location and select the OptiMole Plugin and click install now. -3. Go to Media -> OptiMole and follow in the instructions on how to enable the service. +2. Browse the location, select the Optimole plugin and click "Install now". +3. Go to Media -> Optimole and follow in the instructions on how to enable the service. == Frequently Asked Questions == @@ -307,7 +306,7 @@ We use your original images as sources when deliver the optimized images. Unless A higher compression might result in a small loss of image quality. Selecting the auto level will let Optimole choose the minimum size with no loss in the quality of your picture. -= I used Kraken, Shortpixel, Optimus, EWWW or WP Smush, Imagify will Optimole further optimize my images? = += I used Kraken, Shortpixel, Optimus, EWWW or WP Smush, Imagify. Will Optimole further optimize my images? = Yes, Optimole will also take care of serving your image at the RIGHT size for your visitors and optimize them to the best possible format for their browser.