diff --git a/components/imageOptimizationSettings/index.js b/components/imageOptimizationSettings/index.js index 0141d0f..b34efc8 100644 --- a/components/imageOptimizationSettings/index.js +++ b/components/imageOptimizationSettings/index.js @@ -1,10 +1,5 @@ import { useState, useEffect } from '@wordpress/element'; -import { - Alert, - Container, - ToggleField, - Button, -} from '@newfold/ui-component-library'; +import { Alert, Container, ToggleField } from '@newfold/ui-component-library'; import defaultText from '../performance/defaultText'; @@ -69,6 +64,7 @@ const ImageOptimizationSettings = ( { methods } ) => { updatedSettings.auto_optimized_uploaded_images.enabled = value; updatedSettings.bulk_optimization = value; updatedSettings.lazy_loading.enabled = value; + updatedSettings.prefer_optimized_image_when_exists = value; break; case 'autoOptimizeEnabled': @@ -88,6 +84,10 @@ const ImageOptimizationSettings = ( { methods } ) => { value; break; + case 'preferOptimizedImageWhenExists': + updatedSettings.prefer_optimized_image_when_exists = value; + break; + default: break; } @@ -135,6 +135,8 @@ const ImageOptimizationSettings = ( { methods } ) => { const { enabled, + prefer_optimized_image_when_exists: + preferOptimizedImageWhenExists = true, auto_optimized_uploaded_images: autoOptimizedUploadedImages, lazy_loading: lazyLoading = { enabled: true }, bulk_optimization: bulkOptimization = false, @@ -145,6 +147,11 @@ const ImageOptimizationSettings = ( { methods } ) => { auto_delete_original_image: autoDeleteOriginalImage, } = autoOptimizedUploadedImages || {}; + const mediaLibraryLink = () => { + const basePath = window.location.pathname.split( '/wp-admin' )[ 0 ]; + return `${ window.location.origin }${ basePath }/wp-admin/upload.php`; + }; + return ( { /> - { - defaultText.imageOptimizationAutoDeleteDescription - } -

+

{ - defaultText.imageOptimizationAutoDeleteCaution + defaultText.imageOptimizationBulkOptimizeDescription }

+ + { + defaultText.imageOptimizationBulkOptimizeButtonLabel + } + } + checked={ bulkOptimization } + onChange={ () => + handleToggleChange( 'bulkOptimize', ! bulkOptimization ) + } + disabled={ ! enabled } + /> + + + handleToggleChange( + 'preferOptimizedImageWhenExists', + ! preferOptimizedImageWhenExists + ) + } + disabled={ ! enabled } + /> + + handleToggleChange( @@ -227,40 +264,6 @@ const ImageOptimizationSettings = ( { methods } ) => { } disabled={ ! enabled } /> - - - handleToggleChange( 'bulkOptimize', ! bulkOptimization ) - } - disabled={ ! enabled } - /> - - { bulkOptimization && ( -
- -
- ) }
); diff --git a/components/performance/defaultText.js b/components/performance/defaultText.js index 27298eb..9ce1b63 100644 --- a/components/performance/defaultText.js +++ b/components/performance/defaultText.js @@ -100,10 +100,6 @@ const defaultText = { 'When enabled, the original uploaded image is deleted and replaced with the optimized version, helping to save storage space. If disabled, the optimized image is saved as a separate file, retaining the original.', 'wp-module-performance' ), - imageOptimizationAutoDeleteCaution: __( - 'Caution: If the original image is being referenced elsewhere (e.g., in posts, pages, or custom templates), those references will break. You will need to manually update those references to use the optimized image.', - 'wp-module-performance' - ), imageOptimizationNoSettings: __( 'No settings available.', 'wp-module-performance' @@ -156,6 +152,14 @@ const defaultText = { 'Error Updating Settings', 'wp-module-performance' ), + imageOptimizationPreferWebPLabel: __( + 'Prefer Optimized Image When Exists', + 'wp-module-performance' + ), + imageOptimizationPreferWebPDescription: __( + 'When enabled, optimized images will be served in place of original images when they exist, improving performance.', + 'wp-module-performance' + ), imageOptimizationGenericErrorMessage: __( 'Something went wrong while updating the settings. Please try again.', 'wp-module-performance' diff --git a/includes/Images/ImageManager.php b/includes/Images/ImageManager.php index 2c65f34..c0e9c9d 100644 --- a/includes/Images/ImageManager.php +++ b/includes/Images/ImageManager.php @@ -2,24 +2,26 @@ namespace NewfoldLabs\WP\Module\Performance\Images; +use NewfoldLabs\WP\ModuleLoader\Container; use NewfoldLabs\WP\Module\Performance\Permissions; use NewfoldLabs\WP\Module\Performance\Images\RestApi\RestApi; +use NewfoldLabs\WP\Module\Performance\Images\ImageRewriteHandler; /** * Manages the initialization of image optimization settings and listeners. */ class ImageManager { + /** * Constructor to initialize the ImageManager. - * It registers settings and conditionally initializes services. + * + * Registers settings and conditionally initializes related services. + * + * @param Container $container Dependency injection container. */ - public function __construct() { + public function __construct( Container $container ) { $this->initialize_settings(); - $this->maybe_initialize_upload_listener(); - $this->maybe_initialize_lazy_loader(); - $this->maybe_initialize_bulk_optimizer(); - $this->maybe_initialize_rest_api(); - $this->maybe_initialize_marker(); + $this->initialize_services( $container ); } /** @@ -30,17 +32,30 @@ private function initialize_settings() { } /** - * Conditionally initializes the ImageUploadListener based on the settings. + * Initializes conditional services based on settings and environment. + * + * @param Container $container Dependency injection container. + */ + private function initialize_services( Container $container ) { + $this->maybe_initialize_upload_listener(); + $this->maybe_initialize_lazy_loader(); + $this->maybe_initialize_bulk_optimizer(); + $this->maybe_initialize_rest_api(); + $this->maybe_initialize_marker(); + $this->maybe_initialize_image_rewrite_handler( $container ); + } + + /** + * Initializes the ImageUploadListener if auto-optimization is enabled. */ private function maybe_initialize_upload_listener() { if ( ImageSettings::is_optimization_enabled() && ImageSettings::is_auto_optimization_enabled() ) { - $auto_delete_original_image = ImageSettings::is_auto_delete_enabled(); - new ImageUploadListener( $auto_delete_original_image ); + new ImageUploadListener( ImageSettings::is_auto_delete_enabled() ); } } /** - * Conditionally initializes the LazyLoader based on settings. + * Initializes the LazyLoader if lazy loading is enabled. */ private function maybe_initialize_lazy_loader() { if ( ImageSettings::is_optimization_enabled() && ImageSettings::is_lazy_loading_enabled() ) { @@ -49,7 +64,7 @@ private function maybe_initialize_lazy_loader() { } /** - * Conditionally initializes the ImageBulkOptimizer only within `wp-admin`. + * Initializes the ImageBulkOptimizer if bulk optimization is enabled and user is an admin. */ private function maybe_initialize_bulk_optimizer() { if ( Permissions::is_authorized_admin() && ImageSettings::is_bulk_optimization_enabled() ) { @@ -58,7 +73,7 @@ private function maybe_initialize_bulk_optimizer() { } /** - * Conditionally initializes the REST API routes only when called via REST. + * Initializes the REST API routes if accessed via REST and user is an admin. */ private function maybe_initialize_rest_api() { if ( Permissions::rest_is_authorized_admin() ) { @@ -67,11 +82,24 @@ private function maybe_initialize_rest_api() { } /** - * Conditionally initializes the ImageOptimizedMarker if image optimization is enabled. + * Initializes the ImageOptimizedMarker if image optimization is enabled. */ private function maybe_initialize_marker() { if ( ImageSettings::is_optimization_enabled() ) { new ImageOptimizedMarker(); } } + + /** + * Initializes the ImageRewriteHandler for managing WebP redirects if the server is Apache. + * + * @param Container $container Dependency injection container. + */ + private function maybe_initialize_image_rewrite_handler( Container $container ) { + if ( Permissions::rest_is_authorized_admin() + && $container->has( 'isApache' ) + && $container->get( 'isApache' ) ) { + new ImageRewriteHandler(); + } + } } diff --git a/includes/Images/ImageRewriteHandler.php b/includes/Images/ImageRewriteHandler.php new file mode 100644 index 0000000..5a48849 --- /dev/null +++ b/includes/Images/ImageRewriteHandler.php @@ -0,0 +1,117 @@ +', + "\tRewriteEngine On", + "\tRewriteCond %{REQUEST_FILENAME} !-f", + "\tRewriteCond %{REQUEST_FILENAME} !-d", + "\tRewriteCond %{REQUEST_URI} (.+)\\.(gif|bmp|jpg|jpeg|png|tiff|svg|webp)$ [NC]", + "\tRewriteCond %{DOCUMENT_ROOT}%1.webp -f", + "\tRewriteRule ^(.+)\\.(gif|bmp|jpg|jpeg|png|tiff|svg|webp)$ $1.webp [T=image/webp,E=WEBP_REDIRECT:1,L]", + '', + ); + + $htaccess = new htaccess( self::MISSING_IMAGE_MARKER ); + return $htaccess->addContent( $rules ); + } + + /** + * Add the existing image redirect rule to .htaccess. + */ + public function add_existing_image_rule() { + $rules = array( + '', + "\tRewriteEngine On", + "\tRewriteCond %{REQUEST_FILENAME} -f", + "\tRewriteCond %{REQUEST_URI} (.+)\\.(gif|bmp|jpg|jpeg|png|tiff|svg|webp)$ [NC]", + "\tRewriteCond %{DOCUMENT_ROOT}%1.webp -f", + "\tRewriteRule ^(.+)\\.(gif|bmp|jpg|jpeg|png|tiff|svg|webp)$ $1.webp [T=image/webp,E=WEBP_REDIRECT:1,L]", + '', + ); + + $htaccess = new htaccess( self::EXISTING_IMAGE_MARKER ); + return $htaccess->addContent( $rules ); + } + + /** + * Remove both rules from the .htaccess file. + */ + public function remove_rules() { + removeMarkers( self::MISSING_IMAGE_MARKER ); + removeMarkers( self::EXISTING_IMAGE_MARKER ); + } + + /** + * Activate the rules when needed. + */ + public function on_activation() { + $this->add_missing_image_rule(); + $this->add_existing_image_rule(); + } + + /** + * Deactivate the rules when needed. + */ + public function on_deactivation() { + $this->remove_rules(); + } + + /** + * Handle changes to image optimization settings. + * + * @param array $old_value The previous settings (not used). + * @param array $new_value The updated settings. + */ + public function on_image_setting_change( $old_value, $new_value ) { + // If the image optimization is disabled, remove all rules and return. + if ( empty( $new_value['enabled'] ) ) { + $this->remove_rules(); + return; + } + + // Handle 'auto_delete_original_image' setting. + if ( ! empty( $new_value['auto_optimized_uploaded_images']['auto_delete_original_image'] ) ) { + $this->add_missing_image_rule(); + } else { + removeMarkers( self::MISSING_IMAGE_MARKER ); + } + + // Handle 'prefer_optimized_image_when_exists' setting. + if ( ! empty( $new_value['prefer_optimized_image_when_exists'] ) ) { + $this->add_existing_image_rule(); + } else { + removeMarkers( self::EXISTING_IMAGE_MARKER ); + } + } +} diff --git a/includes/Images/ImageSettings.php b/includes/Images/ImageSettings.php index 58f39dd..13aa6ce 100644 --- a/includes/Images/ImageSettings.php +++ b/includes/Images/ImageSettings.php @@ -6,6 +6,7 @@ * Manages the registration and sanitization of image optimization settings. */ class ImageSettings { + /** * The setting key for image optimization. */ @@ -17,13 +18,14 @@ class ImageSettings { * @var array */ private const DEFAULT_SETTINGS = array( - 'enabled' => true, - 'bulk_optimization' => true, - 'auto_optimized_uploaded_images' => array( + 'enabled' => true, + 'bulk_optimization' => true, + 'prefer_optimized_image_when_exists' => true, + 'auto_optimized_uploaded_images' => array( 'enabled' => true, 'auto_delete_original_image' => true, ), - 'lazy_loading' => array( + 'lazy_loading' => array( 'enabled' => true, ), ); @@ -57,9 +59,14 @@ private function register_settings() { 'description' => __( 'Enable image optimization.', 'wp-module-performance' ), 'default' => self::DEFAULT_SETTINGS['enabled'], ), + 'prefer_optimized_image_when_exists' => array( + 'type' => 'boolean', + 'description' => __( 'Prefer WebP format when it exists.', 'wp-module-performance' ), + 'default' => self::DEFAULT_SETTINGS['prefer_optimized_image_when_exists'], + ), 'auto_optimized_uploaded_images' => array( 'type' => 'object', - 'description' => __( 'Settings for auto-optimized uploaded images.', 'wp-module-performance' ), + 'description' => __( 'Auto-optimized uploaded images settings.', 'wp-module-performance' ), 'properties' => array( 'enabled' => array( 'type' => 'boolean', @@ -68,7 +75,7 @@ private function register_settings() { ), 'auto_delete_original_image' => array( 'type' => 'boolean', - 'description' => __( 'Automatically delete original uploaded image.', 'wp-module-performance' ), + 'description' => __( 'Delete the original uploaded image after optimization.', 'wp-module-performance' ), 'default' => self::DEFAULT_SETTINGS['auto_optimized_uploaded_images']['auto_delete_original_image'], ), ), @@ -79,7 +86,7 @@ private function register_settings() { 'properties' => array( 'enabled' => array( 'type' => 'boolean', - 'description' => __( 'Enable lazy loading for images.', 'wp-module-performance' ), + 'description' => __( 'Enable lazy loading.', 'wp-module-performance' ), 'default' => self::DEFAULT_SETTINGS['lazy_loading']['enabled'], ), ), @@ -90,7 +97,7 @@ private function register_settings() { 'default' => self::DEFAULT_SETTINGS['bulk_optimization'], ), ), - 'additionalProperties' => false, // Disallow undefined properties + 'additionalProperties' => false, ), ), ) @@ -117,15 +124,16 @@ private function initialize_settings() { */ public function sanitize_settings( $settings ) { return array( - 'enabled' => ! empty( $settings['enabled'] ), - 'auto_optimized_uploaded_images' => array( + 'enabled' => ! empty( $settings['enabled'] ), + 'prefer_optimized_image_when_exists' => ! empty( $settings['prefer_optimized_image_when_exists'] ), + 'auto_optimized_uploaded_images' => array( 'enabled' => ! empty( $settings['auto_optimized_uploaded_images']['enabled'] ), 'auto_delete_original_image' => ! empty( $settings['auto_optimized_uploaded_images']['auto_delete_original_image'] ), ), - 'lazy_loading' => array( + 'lazy_loading' => array( 'enabled' => ! empty( $settings['lazy_loading']['enabled'] ), ), - 'bulk_optimization' => ! empty( $settings['bulk_optimization'] ), + 'bulk_optimization' => ! empty( $settings['bulk_optimization'] ), ); } @@ -178,4 +186,14 @@ public static function is_bulk_optimization_enabled() { $settings = get_option( self::SETTING_KEY, self::DEFAULT_SETTINGS ); return ! empty( $settings['bulk_optimization'] ); } + + /** + * Checks if WebP preference is enabled. + * + * @return bool True if WebP preference is enabled, false otherwise. + */ + public static function is_webp_preference_enabled() { + $settings = get_option( self::SETTING_KEY, self::DEFAULT_SETTINGS ); + return ! empty( $settings['prefer_optimized_image_when_exists'] ); + } } diff --git a/includes/Performance.php b/includes/Performance.php index 6a1c260..e2ca7f1 100644 --- a/includes/Performance.php +++ b/includes/Performance.php @@ -76,7 +76,7 @@ public function __construct( Container $container ) { $cacheManager = new CacheManager( $container ); $cachePurger = new CachePurgingService( $cacheManager->getInstances() ); new Constants( $container ); - new ImageManager(); + new ImageManager( $container ); add_action( 'admin_bar_menu', array( $this, 'adminBarMenu' ), 100 ); new LinkPrefetch( $container );