diff --git a/components/cacheExclusion/index.js b/components/cacheExclusion/index.js index 74697b0..b031e2e 100644 --- a/components/cacheExclusion/index.js +++ b/components/cacheExclusion/index.js @@ -2,41 +2,56 @@ import { Button, Container, TextareaField, + Checkbox, } from '@newfold/ui-component-library'; const CacheExclusion = ( { methods, constants } ) => { const [ isEdited, setIsEdited ] = methods.useState( false ); const [ isError, setIsError ] = methods.useState( false ); const [ isSaved, setIsSaved ] = methods.useState( false ); - const [ currentValue, setCurrentValue ] = methods.useState( - methods.NewfoldRuntime.sdk.cacheExclusion + + // Separate states for excluded URLs and error page exclusion + const [ excludedUrls, setExcludedUrls ] = methods.useState( + methods.NewfoldRuntime.sdk.performance.excludedUrls || '' ); - const [ cacheExclusion, setCacheExclusion ] = methods.useState( - methods.NewfoldRuntime.sdk.cacheExclusion + const [ doNotCacheErrorPages, setDoNotCacheErrorPages ] = methods.useState( + methods.NewfoldRuntime.sdk.performance.doNotCacheErrorPages || false ); + const apiUrl = methods.NewfoldRuntime.createApiUrl( '/newfold-performance/v1/cache-exclusion/update' ); - const handleCacheExclusionChange = ( e ) => { - if ( e.target.value !== currentValue ) { + // Handle changes to excluded URLs + const handleExcludedUrlsChange = ( e ) => { + if ( e.target.value !== excludedUrls ) { setIsEdited( true ); } else { setIsEdited( false ); } - setCurrentValue( e.target.value ); + setExcludedUrls( e.target.value ); + }; + + // Handle checkbox toggle for error page exclusion + const handleDoNotCacheErrorPagesChange = () => { + const newValue = ! doNotCacheErrorPages; + setDoNotCacheErrorPages( newValue ); + setIsEdited( true ); }; - const handlingSaveButton = () => { + // Save settings to the API + const handleSaveButton = () => { methods .apiFetch( { url: apiUrl, method: 'POST', - data: { cacheExclusion: currentValue }, + data: { + excludedUrls, + doNotCacheErrorPages, + }, } ) .then( () => { setIsSaved( true ); - setCacheExclusion( currentValue ); setIsEdited( false ); } ) .catch( ( error ) => { @@ -44,14 +59,18 @@ const CacheExclusion = ( { methods, constants } ) => { } ); }; + // Update notices and store on save/error methods.useUpdateEffect( () => { methods.setStore( { ...constants.store, - CacheExclusion: cacheExclusion, + performance: { + excludedUrls, + doNotCacheErrorPages, + }, } ); methods.makeNotice( - 'cache-exlusion-notice', + 'cache-exclusion-notice', constants.text.cacheExclusionTitle, ! isError ? constants.text.cacheExclusionSaved : isError, ! isError ? 'success' : 'error', @@ -65,19 +84,29 @@ const CacheExclusion = ( { methods, constants } ) => { description={ constants.text.cacheExclusionDescription } > + + { isEdited && ( diff --git a/components/performance/defaultText.js b/components/performance/defaultText.js index bba4251..5287888 100644 --- a/components/performance/defaultText.js +++ b/components/performance/defaultText.js @@ -73,14 +73,18 @@ const defaultText = { ), cacheExclusionSaved: __( 'Cache Exclusion saved', 'wp-module-performance' ), cacheExclusionSaveButton: __( 'Save', 'wp-module-performance' ), - // Skip 404 + doNotCacheErrorPagesLabel: __( + 'Exclude Error Pages from Cache', + 'wp-module-performance' + ), + // Skip 404 skip404Title: __( 'Skip 404', 'wp-module-performance' ), skip404Description: __( 'When enabled, static resources like images and fonts will use a default server 404 page and not WordPress 404 pages. Pages and posts will continue using WordPress for 404 pages. This can considerably speed up your website if a static resource like an image or font is missing.', 'wp-module-performance' ), skip404NoticeTitle: __( 'Skip 404 saved', 'wp-module-performance' ), - skip404Notice: __( 'Skip 404 saved', 'wp-module-performance' ), + skip404Notice: __( 'Skip 404 saved', 'wp-module-performance' ), // Image Optimization imageOptimizationSettingsTitle: __( 'Image Optimization', diff --git a/includes/CacheExclusion.php b/includes/CacheExclusion.php index 39d8977..8249b86 100644 --- a/includes/CacheExclusion.php +++ b/includes/CacheExclusion.php @@ -17,7 +17,7 @@ class CacheExclusion { protected $container; /** - * Option used to store all pages should be excluded from cache. + * Option used to store cache exclusion settings. * * @var string */ @@ -33,6 +33,7 @@ public function __construct( Container $container ) { add_filter( 'newfold-runtime', array( $this, 'add_to_runtime' ) ); } + /** * Add values to the runtime object. * @@ -41,6 +42,20 @@ public function __construct( Container $container ) { * @return array */ public function add_to_runtime( $sdk ) { - return array_merge( $sdk, array( 'cacheExclusion' => get_option( self::OPTION_CACHE_EXCLUSION, get_default_cache_exclusions() ) ) ); + $cache_exclusion = get_option( self::OPTION_CACHE_EXCLUSION, $this->get_default_cache_exclusion() ); + + return array_merge( $sdk, $cache_exclusion ); + } + + /** + * Get default cache exclusion settings. + * + * @return array + */ + private function get_default_cache_exclusion() { + return array( + 'excludedUrls' => get_default_cache_exclusions(), + 'doNotCacheErrorPages' => false, + ); } } diff --git a/includes/CacheTypes/Browser.php b/includes/CacheTypes/Browser.php index c90899a..8874024 100644 --- a/includes/CacheTypes/Browser.php +++ b/includes/CacheTypes/Browser.php @@ -37,9 +37,7 @@ public static function shouldEnable( Container $container ) { * Constructor. */ public function __construct() { - new OptionListener( Performance::OPTION_CACHE_LEVEL, array( __CLASS__, 'maybeAddRules' ) ); - new OptionListener( CacheExclusion::OPTION_CACHE_EXCLUSION, array( __CLASS__, 'exclusionChange' ) ); add_filter( 'newfold_update_htaccess', array( $this, 'onRewrite' ) ); @@ -53,7 +51,7 @@ public function onRewrite() { } /** - * Manage on exlcusion option change. + * Manage on exclusion option change. */ public static function exclusionChange() { self::maybeAddRules( getCacheLevel() ); @@ -83,7 +81,6 @@ public static function removeRules() { * @return bool */ public static function addRules( $cacheLevel ) { - $fileTypeExpirations = self::getFileTypeExpirations( $cacheLevel ); $tab = "\t"; @@ -100,22 +97,45 @@ public static function addRules( $cacheLevel ) { } $rules[] = ''; - $cache_exclusion = get_option( CacheExclusion::OPTION_CACHE_EXCLUSION, '' ); - if ( is_string( $cache_exclusion ) && '' !== $cache_exclusion ) { - $cache_exclusion_parameters = array_map( 'trim', explode( ',', sanitize_text_field( get_option( CacheExclusion::OPTION_CACHE_EXCLUSION, '' ) ) ) ); + $cache_exclusion = get_option( + CacheExclusion::OPTION_CACHE_EXCLUSION, + array( + 'excludedUrls' => '', + 'doNotCacheErrorPages' => false, + ) + ); + + if ( ! empty( $cache_exclusion['excludedUrls'] ) ) { + $cache_exclusion_parameters = array_map( 'trim', explode( ',', sanitize_text_field( $cache_exclusion['excludedUrls'] ) ) ); $cache_exclusion_parameters = implode( '|', $cache_exclusion_parameters ); // Add the cache exclusion rules. $rules[] = ''; - $rules[] = 'RewriteEngine On'; - $rules[] = "RewriteCond %{REQUEST_URI} ^/({$cache_exclusion_parameters}) [NC]"; - $rules[] = ''; - $rules[] = 'Header set Cache-Control "no-cache, no-store, must-revalidate"'; - $rules[] = 'Header set Pragma "no-cache"'; - $rules[] = 'Header set Expires 0'; + $rules[] = "{$tab}RewriteEngine On"; + $rules[] = "{$tab}RewriteCond %{REQUEST_URI} ^/({$cache_exclusion_parameters}) [NC]"; + $rules[] = "{$tab}"; + $rules[] = "{$tab}{$tab}Header set Cache-Control \"no-cache, no-store, must-revalidate\""; + $rules[] = "{$tab}{$tab}Header set Pragma \"no-cache\""; + $rules[] = "{$tab}{$tab}Header set Expires 0"; + $rules[] = "{$tab}"; $rules[] = ''; - $rules[] = ''; - // Add the end of the rules about cache exclusion. + + } + + // Handle "Do Not Cache Error Pages" + if ( ! empty( $cache_exclusion['doNotCacheErrorPages'] ) ) { + $expr_condition = '"expr=%{REQUEST_STATUS} >= 400 && %{REQUEST_STATUS} < 600"'; + $rules = array_merge( + $rules, + array( + "{$tab}# Set Cache-Control headers for 400 and 500 status codes", + '', + "{$tab}Header always set Cache-Control \"no-store, no-cache, must-revalidate\" {$expr_condition}", + "{$tab}Header always set Pragma \"no-cache\" {$expr_condition}", + "{$tab}Header always set Expires 0 {$expr_condition}", + '', + ) + ); } $htaccess = new htaccess( self::MARKER ); diff --git a/includes/RestApi/CacheExclusionController.php b/includes/RestApi/CacheExclusionController.php index ce056f4..8ec8d92 100644 --- a/includes/RestApi/CacheExclusionController.php +++ b/includes/RestApi/CacheExclusionController.php @@ -42,6 +42,7 @@ public function register_routes() { ), ) ); + \register_rest_route( $this->namespace, $this->rest_base . '/update', @@ -50,6 +51,32 @@ public function register_routes() { 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'update_settings' ), 'permission_callback' => array( Permissions::class, 'rest_is_authorized_admin' ), + 'args' => array( + 'excludedUrls' => array( + 'type' => 'string', + 'description' => 'Comma-separated string of URLs to exclude from caching.', + 'required' => true, // Now marked as required + 'sanitize_callback' => function ( $value ) { + // Sanitize each URL in the comma-separated string + return implode( + ',', + array_map( 'sanitize_text_field', explode( ',', $value ) ) + ); + }, + ), + + 'doNotCacheErrorPages' => array( + 'type' => 'boolean', + 'description' => 'Whether to prevent caching of error pages (400 and 500).', + 'required' => true, + 'sanitize_callback' => 'rest_sanitize_boolean', + 'validate_callback' => function ( $param ) { + // Ensure the value is a valid boolean or equivalent + return is_bool( $param ) || in_array( $param, array( 'true', 'false', true, false ), true ); + }, + ), + + ), ), ) ); @@ -61,14 +88,23 @@ public function register_routes() { * @return \WP_REST_Response */ public function get_settings() { + // Retrieve the cache exclusion option once + $cache_exclusion = get_option( CacheExclusion::OPTION_CACHE_EXCLUSION, get_default_cache_exclusions() ); + + // Extract the specific keys + $excluded_urls = $cache_exclusion['excludedUrls'] ?? ''; + $do_not_cache_error_pages = $cache_exclusion['doNotCacheErrorPages'] ?? false; + return new \WP_REST_Response( array( - 'cacheExclusion' => get_option( CacheExclusion::OPTION_CACHE_EXCLUSION, get_default_cache_exclusions() ), + 'excludedUrls' => $excluded_urls, + 'doNotCacheErrorPages' => $do_not_cache_error_pages, ), 200 ); } + /** * Update the settings * @@ -76,8 +112,24 @@ public function get_settings() { * @return \WP_REST_Response */ public function update_settings( \WP_REST_Request $request ) { - $cache_exclusion = $request->get_param( 'cacheExclusion' ); - if ( update_option( CacheExclusion::OPTION_CACHE_EXCLUSION, $cache_exclusion ) ) { + + // Retrieve current settings and merge with defaults + $current_cache_exclusion = get_option( CacheExclusion::OPTION_CACHE_EXCLUSION, array() ); + + // Extract and sanitize the new values from the request + $excluded_urls = $request->get_param( 'excludedUrls' ); + $do_not_cache_error_pages = $request->get_param( 'doNotCacheErrorPages' ); + + // Merge the updated values into the current settings + $updated_cache_exclusion = array_merge( + $current_cache_exclusion, + array( + 'excludedUrls' => $excluded_urls, + 'doNotCacheErrorPages' => $do_not_cache_error_pages, + ) + ); + + if ( update_option( CacheExclusion::OPTION_CACHE_EXCLUSION, $updated_cache_exclusion ) ) { return new \WP_REST_Response( array( 'result' => true, diff --git a/styles/styles.css b/styles/styles.css index 50b61cb..5ac0ba0 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -1,22 +1,23 @@ .nfd-container__block.newfold-link-prefetch .nfd-toggle-field.nfd-mb-6 { display: flex; flex-direction: row; - } -.nfd-performance-jetpack-boost-upsell{ +} + +.nfd-performance-jetpack-boost-upsell { position: relative; } -.nfd-performance-jetpack-boost-upsell:before { - content: ''; +.nfd-performance-jetpack-boost-upsell::before { + content: ""; display: block; width: 100%; height: 100%; - background-color: rgba(255,255,0,0.1); + background-color: rgba(255, 255, 0, 0.1); position: absolute; } -.nfd-performance-jetpack-boost-container-install-activate-button{ +.nfd-performance-jetpack-boost-container-install-activate-button { position: absolute; width: 300px; left: calc(50% - 150px); @@ -24,12 +25,12 @@ z-index: 10; } -.nfd-performance-jetpack-boost-container-install-activate-button svg{ +.nfd-performance-jetpack-boost-container-install-activate-button svg { display: inline-block; margin-right: 10px; } -.nfd-performance-jetpack-boost-container-install-activate-button button{ +.nfd-performance-jetpack-boost-container-install-activate-button button { padding: 10px; border-radius: 6px; width: 100%; @@ -39,16 +40,16 @@ justify-content: left; } -.nfd-performance-jetpack-boost-single-option .child-field .wrap-button button{ +.nfd-performance-jetpack-boost-single-option .child-field .wrap-button button { text-decoration: underline; - margin: 10px 0 0 0; + margin: 10px 0 0 0; } -.nfd-performance-jetpack-boost-single-option-container{ - margin-bottom: 20px; +.nfd-performance-jetpack-boost-single-option-container { + margin-bottom: 20px; } -.margin20{ +.margin20 { margin: 20px 0; } @@ -59,3 +60,12 @@ .newfold-image-optimization a { color: #05c !important; } + +.nfd-performance-cache-exclusion-checkbox { + margin-top: 10px; + margin-bottom: 10px; +} + +.nfd-performance-save-cache-exclusion-button { + margin-top: 10px !important; +}