Plugin Directory

Changeset 3317868


Ignore:
Timestamp:
06/25/2025 09:12:31 PM (9 months ago)
Author:
sethta
Message:

Update to version 1.2.0

Location:
easy-critical-css
Files:
294 added
4 deleted
17 edited

Legend:

Unmodified
Added
Removed
  • easy-critical-css/trunk/easy-critical-css.php

    r3307644 r3317868  
    33 * Plugin Name:       Easy Critical CSS
    44 * Description:       Easily inject Critical CSS and optimized Secondary CSS to improve page speed and performance.
    5  * Version:           1.1.0
     5 * Version:           1.2.0
    66 * Requires at least: 6.2
    77 * Tested up to:      6.8.1
  • easy-critical-css/trunk/inc/class-admin-settings.php

    r3295017 r3317868  
    1515        add_action( 'admin_enqueue_scripts', [ __CLASS__, 'enqueue_scripts' ] );
    1616        add_action( 'updated_option', [ __CLASS__, 'trigger_cache_clear' ] );
     17        add_action( 'updated_option_easy_cc_debug_mode', [ __CLASS__, 'trigger_log_deletion' ], 10, 2 );
    1718
    1819        add_action( 'admin_footer', [ easy_cc_fs(), '_add_license_activation_dialog_box' ] );
     
    278279                'default_value' => 1,
    279280                'default_label' => __( 'Checked', 'easy-critical-css' ),
    280                 'warning'       => __( 'Warning: Disabling this may cause issues with ad scripts or dynamically loaded styles.', 'easy-critical-css' ),
     281                'warning'       => __( 'Warning: Unchecking this may cause issues with ad scripts or dynamically loaded styles.', 'easy-critical-css' ),
    281282
    282283            ],
     
    288289                    'label' => __( 'Serve Critical CSS from static files instead of the database.', 'easy-critical-css' ),
    289290                ],
    290                 'desc'          => __( 'By default, Critical CSS is stored and served from the database. Checking this saves it as static files, which may improve performance on some hosting environments.', 'easy-critical-css' ),
    291                 'default_value' => false,
    292                 'default_label' => __( 'Unchecked', 'easy-critical-css' ),
     291                'desc'          => __( 'By default, Critical CSS is stored and served as static files. Checking this stores and saves it in the database.', 'easy-critical-css' ),
     292                'default_value' => true,
     293                'default_label' => __( 'Checked', 'easy-critical-css' ),
    293294            ],
    294295            'regenerate_triggers'        => [
     
    325326                'default_value' => 'use_expired',
    326327                'default_label' => 'Use expired Critical CSS',
     328            ],
     329            'debug_mode'                 => [
     330                'label'         => __( 'Debug Mode', 'easy-critical-css' ),
     331                'type'          => 'checkbox',
     332                'value_type'    => 'boolean',
     333                'options'       => [
     334                    'label' => __( 'Enable Debug for Support.', 'easy-critical-css' ),
     335                ],
     336                'desc'          => __( 'This creates an Easy Critical CSS specific debug log and opens REST endpoints for support to receive it. Support must have an account on your site to be able to access this information.', 'easy-critical-css' ),
     337                'default_value' => false,
     338                'default_label' => __( 'Unchecked', 'easy-critical-css' ),
    327339            ],
    328340        ];
     
    462474        }
    463475    }
     476
     477    public static function trigger_log_deletion( $old_value, $new_value ) {
     478        // If mode is inactive, delete the file.
     479        if ( ! $new_value ) {
     480            Debug::clear_log();
     481        }
     482    }
    464483}
  • easy-critical-css/trunk/inc/class-api-request-handler.php

    r3307644 r3317868  
    3131        }
    3232
    33         /**
    34          * Filters the list of excluded URL parameters when generating CSS.
    35          *
    36          * @since 1.0.0
    37          *
    38          * @param array $excluded_url_params List of query paramters that should be excluded.
    39          */
    40         $excluded_url_params = apply_filters( 'easy_cc_excluded_url_params', [ 'wp_scrape_key', 'wp_scrape_nonce' ] );
     33        // Clean the URL from unwanted params.
     34        $excluded_url_params = Helpers::get_excluded_url_params();
    4135        $url                 = remove_query_arg( $excluded_url_params, $url );
    4236
  • easy-critical-css/trunk/inc/class-api-service.php

    r3284313 r3317868  
    1616
    1717    public static function request_critical_css( $url_or_id, $options = [] ) {
     18        Debug::ecc_log(
     19            [
     20                'step'   => 'entering_request_critical_css',
     21                'id'     => $url_or_id,
     22                'status' => Critical_CSS_Status::get_status( $url_or_id ),
     23                'trace'  => wp_debug_backtrace_summary(), // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_wp_debug_backtrace_summary -- This is for debug purposes when debug mode is enabled.
     24            ]
     25        );
     26
    1827        if ( empty( $url_or_id ) ) {
    1928            return new WP_Error( 'missing_url_or_id', __( 'URL or Post ID is required.', 'easy-critical-css' ) );
  • easy-critical-css/trunk/inc/class-compatibility-wp-rocket.php

    r3307644 r3317868  
    1313    }
    1414
     15    public static function is_rocket_installed() {
     16        return defined( 'WP_ROCKET_VERSION' );
     17    }
     18
     19    public static function get_rocket_css_optimization_status() {
     20        if ( ! self::is_rocket_installed() || ! function_exists( 'get_rocket_option' ) ) {
     21            return 'disabled';
     22        }
     23
     24        $method = get_rocket_option( 'css_delivery_method' );
     25
     26        if ( $method === 'async' ) {
     27            return 'optimize_css_delivery';
     28        }
     29
     30        if ( $method === 'remove_unused_css' ) {
     31            return 'remove_unused_css';
     32        }
     33
     34        // Fallback for older versions of WP Rocket.
     35        if ( get_rocket_option( 'optimize_css_delivery' ) ) {
     36            return 'optimize_css_delivery';
     37        }
     38
     39        if ( get_rocket_option( 'remove_unused_css' ) ) {
     40            return 'remove_unused_css';
     41        }
     42
     43        return 'disabled';
     44    }
     45
    1546    public static function is_rocket_critical_activated() {
    1647        // Allow override for testing or advanced use
     
    2051        }
    2152
    22         // Is WP Rocket installed?
    23         if ( ! defined( 'WP_ROCKET_VERSION' ) || ! function_exists( 'get_rocket_option' ) ) {
    24             return false;
    25         }
    26 
    27         return get_rocket_option( 'optimize_css_delivery', false );
     53        return self::get_rocket_css_optimization_status() !== 'disabled';
    2854    }
    2955
     
    5985        }
    6086
    61         // Ensure WP Rocket is installed before proceeding.
    62         if ( ! defined( 'WP_ROCKET_VERSION' ) || ! function_exists( 'update_rocket_option' ) ) {
     87        // Ensure WP Rocket is installed with the function we need before proceeding.
     88        if ( ! self::is_rocket_installed() || ! function_exists( 'update_rocket_option' ) ) {
    6389            wp_die( esc_html( __( 'WP Rocket is not installed.', 'easy-critical-css' ) ) );
    6490        }
    6591
    66         // Disable WP Rocket's Critical CSS setting.
     92        // Disable WP Rocket's Critical CSS settings.
    6793        update_rocket_option( 'optimize_css_delivery', 0 );
     94        update_rocket_option( 'remove_unused_css', 0 );
     95        update_rocket_option( 'css_delivery_method', '' );
    6896
    6997        wp_safe_redirect( admin_url( 'index.php' ) );
  • easy-critical-css/trunk/inc/class-critical-css-injector.php

    r3295017 r3317868  
    9797
    9898        global $wp_styles;
     99        $site_host = wp_parse_url( home_url(), PHP_URL_HOST );
    99100
    100101        $excluded_handles = [
     
    102103            'admin-bar',
    103104            'query-monitor',
     105            'rank-math-analytics-stats',
    104106            'yoast-seo-adminbar',
    105107        ];
     
    136138            if ( in_array( $handle, $excluded_handles, true ) || Helpers::is_excluded_css_file( $src ) ) {
    137139                continue;
     140            }
     141
     142            // Exclude Cross-Domain CSS if setting exists.
     143            if (
     144                Settings::get_global_ignore_cross_domain_css()
     145                && ( preg_match( '~^https?://~', $src ) || strpos( $src, '//' ) === 0 )
     146            ) {
     147                $style_host = wp_parse_url( $src, PHP_URL_HOST );
     148                if ( $style_host && strcasecmp( $style_host, $site_host ) !== 0 ) {
     149                    // it’s coming from fonts.googleapis.com (or any other 3rd-party), so leave it alone
     150                    continue;
     151                }
    138152            }
    139153
  • easy-critical-css/trunk/inc/class-critical-css-regenerate.php

    r3284313 r3317868  
    1212    }
    1313
    14     public static function expire_post_critical( $post_id ) {
     14    public static function expire_post_critical( $post_id, \WP_Post $after, \WP_Post $before ) {
    1515        // We only want real posts.
    1616        if ( wp_is_post_autosave( $post_id ) || wp_is_post_revision( $post_id ) ) {
     17            return;
     18        }
     19
     20        // if title, content AND excerpt are all identical, do nothing as nothing is really expired.
     21        if (
     22            $after->post_title === $before->post_title &&
     23            $after->post_content === $before->post_content &&
     24            $after->post_excerpt === $before->post_excerpt
     25        ) {
    1726            return;
    1827        }
     
    6776    }
    6877
    69     public static function expire_archive_css( $term_id, $taxonomy ) {
     78    public static function expire_archive_critical( $term_id, $taxonomy ) {
    7079        // Expire Critical CSS for an archive.
    7180        $archive_url = get_term_link( $term_id, $taxonomy );
     
    7584    }
    7685
    77     public static function expire_all_css_on_update( $upgrader, $options ) {
    78         // Only expire on our selected updates
     86    public static function expire_all_critical_on_update( $upgrader, $options ) {
     87        // Make sure we are updating.
     88        if ( $options['action'] !== 'update' ) {
     89            return;
     90        }
     91
     92        // Only expire on our selected updates.
    7993        if ( ! empty( $options['type'] ) && in_array( $options['type'], [ 'core', 'plugin', 'theme' ], true ) ) {
    8094            Critical_CSS::expire_all_css();
     
    87101        // Expire when post updated (post, home page, related archives).
    88102        if ( in_array( 'post_updated', $triggers, true ) ) {
    89             add_action( 'post_updated', [ __CLASS__, 'expire_post_critical' ], 10 );
     103            add_action( 'post_updated', [ __CLASS__, 'expire_post_critical' ], 10, 3 );
    90104        }
    91105
     
    108122        // Expire everything with an update to core, plugin, or theme.
    109123        if ( in_array( 'upgrader_complete', $triggers, true ) ) {
    110             add_action( 'upgrader_process_complete', [ __CLASS__, 'expire_all_css_on_update' ], 10, 2 );
     124            add_action( 'upgrader_process_complete', [ __CLASS__, 'expire_all_critical_on_update' ], 10, 2 );
    111125        }
    112126    }
  • easy-critical-css/trunk/inc/class-critical-css.php

    r3307644 r3317868  
    5555        }
    5656
     57        // Clean the URL from unwanted params.
     58        $excluded_url_params = Helpers::get_excluded_url_params();
     59        $clean_url           = esc_url_raw( remove_query_arg( $excluded_url_params, $current_url ) );
     60
    5761        // If it's a singular post, return post ID instead of URL.
    5862        if ( is_singular() ) {
     
    6670
    6771        // Allow filtering of the final page identifier.
    68         $identifier = apply_filters( 'easy_cc_page_identifier', $current_url );
     72        $identifier = apply_filters( 'easy_cc_page_identifier', $clean_url );
    6973
    7074        return $identifier;
     
    112116            'url_hash'       => ( isset( $data['url_hash'] ) ) ? $data['url_hash'] : '',
    113117            'post_id'        => ( isset( $data['post_id'] ) ) ? $data['post_id'] : '',
     118            'size_savings'   => ( isset( $data['size_savings'] ) ) ? $data['size_savings'] : '',
    114119        ];
    115120
     
    218223        }
    219224
     225        // Skip Cron requests.
     226        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- No need for nonce verification as we are using this only for skipping purposes.
     227        if ( ( defined( 'DOING_CRON' ) && DOING_CRON ) || isset( $_GET['doing_wp_cron'] ) ) {
     228            return;
     229        }
     230
     231        // Skip REST, AJAX, XML-RPC follow-ups.
     232        if (
     233            ( defined( 'REST_REQUEST' ) && REST_REQUEST )
     234                || ( defined( 'DOING_AJAX' ) && DOING_AJAX )
     235                || ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST )
     236        ) {
     237            return;
     238        }
     239
     240        // Skip known User-Agents.
     241        $request_method = isset( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
     242        if ( $request_method === 'HEAD' ) {
     243            return;
     244        }
     245
     246        $blocked_user_agents = [
     247            'EasyCriticalCSS WP Fetch',
     248            'Uptime', // Catch any uptime monitor.
     249            'WP Rocket/Preload',
     250        ];
     251
     252        /**
     253         * Filters the list of blocked User-Agents from triggering a Critical CSS regeneration.
     254         *
     255         * @since 1.2.0
     256         *
     257         * @param array $user_agents List of User-Agent fragments to ignore.
     258         */
     259        $blocked_user_agents = apply_filters( 'easy_cc_blocked_user_agents', $blocked_user_agents );
     260
     261        $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
     262        foreach ( $blocked_user_agents as $blocked_user_agent ) {
     263            if ( stripos( $ua, $blocked_user_agent ) !== false ) {
     264                return;
     265            }
     266        }
     267
     268        // Skip if we have no identifier or the mode is manual.
    220269        $identifier = self::get_page_identifier();
    221 
    222         // Skip if we have no identifier or the mode is manual.
    223270        if ( empty( $identifier ) || Settings::get_individual_mode( $identifier ) === 'manual' ) {
    224271            return;
    225272        }
     273
     274        // Get current status and time, pulling directly from the DB to prevent any caching issues.
     275        $database_row   = Database::get_row( $identifier );
     276        $status         = ! empty( $database_row['processing_status'] ) ? $database_row['processing_status'] : 'unprocessed';
     277        $generated_time = ! empty( $database_row['generated_time'] ) ? $database_row['generated_time'] : '';
     278
     279        // If we've generated before AND it's not expired, only proceed once per hour.
     280        if ( $status !== 'expired' && ! empty( $generated_time ) ) {
     281            $throttle = HOUR_IN_SECONDS;
     282            if ( strtotime( current_time( 'mysql' ) ) - strtotime( $generated_time ) < $throttle ) {
     283                return;
     284            }
     285        }
     286
     287        // Only move forward if we aren't pending or completed.
     288        if ( in_array( $status, [ 'pending', 'completed' ], true ) ) {
     289            return;
     290        }
     291
     292        // Immediately mark it as pending so that any concurrent request sees pending and runs away.
     293        Critical_CSS_Status::update_critical_css_status( $identifier, 'pending' );
    226294
    227295        // Request Critical CSS generation
  • easy-critical-css/trunk/inc/class-helpers.php

    r3307644 r3317868  
    179179    }
    180180
    181     public static function is_local_site( $host ) {
     181    public static function is_local_site( $host, $debug = false ) {
    182182        // Check transient.
    183183        $cached = get_transient( 'easy_cc_is_local_site' );
    184         if ( $cached !== false ) {
     184        if ( ! $debug && $cached !== false ) {
    185185            return (bool) $cached;
    186186        }
    187187
    188         $is_local = false;
     188        $is_local     = false;
     189        $debug_output = [];
    189190
    190191        // Do we fail WP's local test?
    191192        if ( wp_get_environment_type() === 'local' ) {
    192193            $is_local = true;
     194
     195            $debug_output['wp_get_environment_type'] = true;
    193196        }
    194197
     
    199202        if ( in_array( $host, [ 'localhost', '127.0.0.1', '::1' ], true ) ) {
    200203            $is_local = true;
     204
     205            $debug_output['localhost'] = true;
    201206        }
    202207
     
    204209        if ( preg_match( '/\.(local|test|localhost|invalid)$/', $host ) ) {
    205210            $is_local = true;
     211
     212            $debug_output['tld'] = true;
    206213        }
    207214
     
    209216        if ( function_exists( 'getenv' ) && getenv( 'WP_ENV' ) === 'local' ) {
    210217            $is_local = true;
     218
     219            $debug_output['env'] = true;
    211220        }
    212221
     
    221230        $is_local = (bool) apply_filters( 'easy_cc_is_local_site', $is_local, $host );
    222231
    223         // We need to store both true and false values as a transient.
    224         set_transient( 'easy_cc_is_local_site', $is_local ? '1' : '0', 6 * HOUR_IN_SECONDS );
     232        // We need to store both true and false values as a transient, but only if no debug.
     233        if ( ! $debug ) {
     234            set_transient( 'easy_cc_is_local_site', $is_local ? '1' : '0', 6 * HOUR_IN_SECONDS );
     235        } else {
     236            // Give more info if debug.
     237            $debug_output['is_local'] = $is_local;
     238
     239            return $debug_output;
     240        }
    225241
    226242        return $is_local;
    227243    }
    228244
    229     public static function is_rest_api_reachable() {
     245    public static function is_rest_api_reachable( $debug = false ) {
    230246        // Check transient.
    231247        $cached = get_transient( 'easy_cc_is_rest_api_reachable' );
    232         if ( $cached !== false ) {
     248        if ( ! $debug && $cached !== false ) {
    233249            return (bool) $cached;
    234250        }
    235251
    236         $rest_url     = rest_url( 'wp/v2/types' );
    237         $response     = wp_remote_get(
     252        // Use rest route param to avoid potential redirect conflicts
     253        $rest_url = home_url( '/index.php?rest_route=/wp/v2/types' );
     254        $response = wp_remote_get(
    238255            $rest_url,
    239256            [
     
    242259            ]
    243260        );
     261
    244262        $is_reachable = ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200;
    245263
    246         // We need to store both true and false values as a transient.
    247         set_transient( 'easy_cc_is_rest_api_reachable', $is_reachable ? '1' : '0', 6 * HOUR_IN_SECONDS );
     264        // We need to store both true and false values as a transient, but only if no debug.
     265        if ( ! $debug ) {
     266            set_transient( 'easy_cc_is_rest_api_reachable', $is_reachable ? '1' : '0', 6 * HOUR_IN_SECONDS );
     267        } else {
     268            // Give more info if debug.
     269            return [
     270                'is_reachable' => $is_reachable,
     271                'url'          => rest_url( 'wp/v2/types' ),
     272                'is_error'     => is_wp_error( $response ),
     273                'error'        => is_wp_error( $response ) ? $response->get_error_message() : null,
     274                'code'         => wp_remote_retrieve_response_code( $response ),
     275                'headers'      => wp_remote_retrieve_headers( $response ),
     276            ];
     277        }
    248278
    249279        return $is_reachable;
     
    275305        );
    276306    }
     307
     308    /**
     309     * Gets the list of URL params to exclude from URLs.
     310     *
     311     * @return array List of URL params to exclude.
     312     */
     313    public static function get_excluded_url_params() {
     314        $excluded_url_params = [
     315            'breeze_check_cache',
     316            'rnd',
     317            'no-cache',
     318            'sucurianticache',
     319            'wp_scrape_key',
     320            'wp_scrape_nonce',
     321        ];
     322
     323        /**
     324         * Filters the list of excluded URL parameters when generating CSS.
     325         *
     326         * @since 1.0.0
     327         *
     328         * @param array $excluded_url_params List of query paramters that should be excluded.
     329         */
     330        return apply_filters( 'easy_cc_excluded_url_params', $excluded_url_params );
     331    }
    277332}
  • easy-critical-css/trunk/inc/class-plugin.php

    r3307644 r3317868  
    1010    private static $instance = null;
    1111
    12     private static $plugin_version = '1.1.0';
     12    private static $plugin_version = '1.2.0';
    1313
    1414    private static $db_version = '2';
  • easy-critical-css/trunk/inc/class-rest-api.php

    r3284313 r3317868  
    130130            ]
    131131        );
     132
     133        register_rest_route(
     134            self::$route_namespace,
     135            '/content',
     136            [
     137                'methods'             => 'GET',
     138                'callback'            => [ __CLASS__, 'get_page_content' ],
     139                // Open permission_callback because request validation is securely handled at the start of `get_page_content` callback through a handshake validation.
     140                'permission_callback' => '__return_true',
     141                'args'                => [
     142                    'url'                  => [
     143                        'required'          => true,
     144                        'type'              => 'string',
     145                        'validate_callback' => 'esc_url_raw',
     146                    ],
     147                    'ignoreCrossDomainCSS' => [
     148                        'required' => false,
     149                        'default'  => true,
     150                        'type'     => 'boolean',
     151                    ],
     152                    'excludeCSS'           => [
     153                        'required' => false,
     154                        'type'     => 'array',
     155                        'items'    => [ 'type' => 'string' ],
     156                    ],
     157                ],
     158            ]
     159        );
     160
     161        register_rest_route(
     162            self::$route_namespace,
     163            '/debug',
     164            [
     165                'methods'             => 'GET',
     166                'callback'            => [ 'EasyCriticalCSS\Debug', 'debug' ],
     167                'permission_callback' => [ 'EasyCriticalCSS\Debug', 'debug_permission' ],
     168            ]
     169        );
     170
     171        register_rest_route(
     172            self::$route_namespace,
     173            '/debug/(?P<hash>[A-Za-z0-9_-]+)',
     174            [
     175                'methods'             => 'GET',
     176                'callback'            => [ 'EasyCriticalCSS\Debug', 'debug_generated_css' ],
     177                'permission_callback' => [ 'EasyCriticalCSS\Debug', 'debug_permission' ],
     178                'args'                => [
     179                    'hash' => [
     180                        'required'          => true,
     181                        'validate_callback' => function ( $param ) {
     182                            return is_string( $param ) && preg_match( '#^[A-Za-z0-9_-]+$#', $param );
     183                        },
     184                    ],
     185                ],
     186            ]
     187        );
     188
     189        register_rest_route(
     190            self::$route_namespace,
     191            '/debug-log',
     192            [
     193                'methods'             => 'GET',
     194                'callback'            => [ 'EasyCriticalCSS\Debug', 'view_log' ],
     195                'permission_callback' => [ 'EasyCriticalCSS\Debug', 'debug_permission' ],
     196            ]
     197        );
    132198    }
    133199
     
    166232
    167233    public static function handle_critical_css( WP_REST_Request $request ) {
    168         $params = $request->get_params(); // Get all request parameters at once.
     234        $params    = $request->get_params(); // Get all request parameters at once.
     235        $handshake = sanitize_text_field( $request->get_param( 'handshake' ) );
    169236
    170237        // Check for required params.
    171         if ( empty( $params['handshake'] ) ) {
     238        if ( empty( $handshake ) ) {
    172239            return new WP_Error(
    173240                'missing_handshake',
     
    195262        }
    196263
    197         if ( $params['handshake'] !== $existing['handshake'] ) {
     264        if ( ! hash_equals( sanitize_text_field( $existing['handshake'] ), $handshake ) ) {
    198265            return new WP_Error(
    199266                'invalid_handshake',
     
    309376                'status'         => $status,
    310377                'generated_time' => $generated_css['generated_time'],
     378                'requested_time' => $generated_css['requested_time'],
     379                'size_savings'   => $generated_css['size_savings'],
    311380                'critical_css'   => ! empty( $generated_css['critical_css'] ),
    312381                'secondary_css'  => ! empty( $generated_css['secondary_css'] ),
     
    347416        return rest_ensure_response( $result );
    348417    }
     418
     419    /**
     420     * Checks if a handshake matches the stored value (or global override).
     421     *
     422     * @param  string $url
     423     * @param  string $handshake
     424     *
     425     * @return boolean|WP_Error  True if valid, false if mismatch, WP_Error if URL not found.
     426     */
     427    public static function has_valid_handshake( $url, $handshake ) {
     428        // Is there a global override in place?
     429        if ( defined( 'EASY_CC_GLOBAL_API_KEY' ) && hash_equals( EASY_CC_GLOBAL_API_KEY, sanitize_text_field( $handshake ) ) ) {
     430            return true;
     431        }
     432
     433        $existing = Database::get_row_by_url( $url );
     434        if ( empty( $existing ) ) {
     435            return new WP_Error(
     436                'url_not_found',
     437                __( 'No matching record found for the provided URL.', 'easy-critical-css' ),
     438                [ 'status' => 404 ]
     439            );
     440        }
     441
     442        if ( ! hash_equals( sanitize_text_field( $existing['handshake'] ), $handshake ) ) {
     443            return false;
     444        }
     445
     446        return true;
     447    }
     448
     449    public static function get_page_content( WP_REST_Request $request ) {
     450        $url       = esc_url_raw( $request->get_param( 'url' ) );
     451        $handshake = sanitize_text_field( $request->get_param( 'handshake' ) );
     452
     453        // Check for required handshake.
     454        if ( empty( $handshake ) ) {
     455            return new WP_Error(
     456                'missing_handshake',
     457                __( 'Handshake key is missing.', 'easy-critical-css' ),
     458                [ 'status' => 400 ]
     459            );
     460        }
     461
     462        $valid_handshake = self::has_valid_handshake( $url, $handshake );
     463        if ( is_wp_error( $valid_handshake ) ) {
     464            return $valid_handshake;
     465        }
     466        if ( ! $valid_handshake ) {
     467            return new WP_Error(
     468                'invalid_handshake',
     469                __( 'Invalid handshake key.', 'easy-critical-css' ),
     470                [ 'status' => 403 ] // HTTP 403 Forbidden.
     471            );
     472        }
     473
     474        $ignore_cross_domain = (bool) $request->get_param( 'ignoreCrossDomainCSS' );
     475        $exclude_patterns    = ! empty( $request->get_param( 'excludeCSS' ) ) ? $request->get_param( 'excludeCSS' ) : [];
     476
     477        $response = wp_remote_get( $url, [ 'User-Agent' => 'EasyCriticalCSS WP Fetch' ] );
     478        if ( is_wp_error( $response ) ) {
     479            return new WP_Error( 'fetch_error', 'Unable to fetch content', [ 'status' => 500 ] );
     480        }
     481
     482        $body = wp_remote_retrieve_body( $response );
     483
     484        // Load HTML to get stylesheets
     485        preg_match_all( '#<link[^>]+rel=["\']stylesheet["\'][^>]*>#i', $body, $matches );
     486        $stylesheet_hrefs = [];
     487        foreach ( $matches[0] as $tag ) {
     488            if ( preg_match( '/href=["\']([^"\']+)["\']/', $tag, $href_match ) ) {
     489                $stylesheet_hrefs[] = $href_match[1];
     490            }
     491        }
     492
     493        // Filter by cross-domain & excludeCSS
     494        $site_host = wp_parse_url( $url, PHP_URL_HOST );
     495        $allowed   = array_filter(
     496            $stylesheet_hrefs,
     497            function ( $href ) use ( $site_host, $ignore_cross_domain, $exclude_patterns ) {
     498                $parsed_host = wp_parse_url( $href, PHP_URL_HOST );
     499                $host        = ! empty( $parsed_host ) ? $parsed_host : $site_host;
     500                // Cross-domain?
     501                if ( $ignore_cross_domain && $host !== $site_host ) {
     502                    return false;
     503                }
     504                // Excluded pattern?
     505                foreach ( $exclude_patterns as $pat ) {
     506                    if ( strpos( $href, $pat ) !== false ) {
     507                        return false;
     508                    }
     509                }
     510                return true;
     511            }
     512        );
     513
     514        // Fetch CSS and rewrite any non-absolute URLs
     515        $css_combined = '';
     516        foreach ( $allowed as $href ) {
     517            $css_res = wp_remote_get( $href );
     518            if ( is_wp_error( $css_res ) ) {
     519                continue;
     520            }
     521
     522            $css_body = wp_remote_retrieve_body( $css_res );
     523
     524            // Rewrite every url(...) found: https://regex101.com/r/Kk9s8b/1
     525            $rewritten = preg_replace_callback(
     526                '/url\(\s*([\'"]?)([^\'")]+)\1\s*\)/i',
     527                function ( $matches ) use ( $href ) {
     528                    list(, $quote, $asset_path) = $matches;
     529
     530                    // Data-URIs or full URLs? Leave alone!
     531                    if ( preg_match( '#^(?:https?:|data:)#i', $asset_path ) ) {
     532                        return $matches[0];
     533                    }
     534
     535                    // Protocol-relative? Leave alone!
     536                    if ( strpos( $asset_path, '//' ) === 0 ) {
     537                        return "url({$quote}{$asset_path}{$quote})";
     538                    }
     539
     540                    // Root-relative? Make absolute!
     541                    if ( strpos( $asset_path, '/' ) === 0 ) {
     542                        $origin = untrailingslashit( home_url() );
     543                        return "url({$quote}{$origin}{$asset_path}{$quote})";
     544                    }
     545
     546                    // Root-relative? Make absolute!
     547                    $base     = trailingslashit( dirname( $href ) );
     548                    $absolute = $base . $asset_path;
     549                    return "url({$quote}{$absolute}{$quote})";
     550                },
     551                $css_body
     552            );
     553
     554            $css_combined .= $rewritten . ' ';
     555        }
     556
     557        return [
     558            'html'        => $body,
     559            'stylesheets' => $stylesheet_hrefs,
     560            'allowed'     => $allowed,
     561            'css'         => $css_combined,
     562        ];
     563    }
    349564}
  • easy-critical-css/trunk/inc/class-settings.php

    r3307644 r3317868  
    217217     */
    218218    public static function get_global_serve_css_from_files() {
    219         return (bool) self::get_global_setting( 'serve_css_from_files', false );
     219        return (bool) self::get_global_setting( 'serve_css_from_files', true );
    220220    }
    221221
     
    255255        return (string) self::get_global_setting( 'expired_css_behavior', 'use_expired' );
    256256    }
     257
     258    /**
     259     * Gets the global Debug Mode setting.
     260     *
     261     * @return bool
     262     */
     263    public static function get_global_debug_mode() {
     264        return (bool) self::get_global_setting( 'debug_mode', false );
     265    }
    257266}
  • easy-critical-css/trunk/inc/class-uninstall-handler.php

    r3284313 r3317868  
    5252        }
    5353
     54        // Delete log file.
     55        Debug::clear_log();
     56
    5457        // Remove options.
    5558        delete_option( 'easy_cc_version' );
  • easy-critical-css/trunk/readme.txt

    r3307644 r3317868  
    103103== Changelog ==
    104104
     105= 1.2.0 =
     106- FEATURE: Adds Debug Mode setting with secure, locked-down endpoints
     107- FEATURE: Introduces new fallback endpointfor fetching page HTML and CSS, protected by a rolling handshake
     108- OPTIMIZATION: Adjusts default CSS service method to be static files instead of the database
     109- FIX: Respects Ignore Cross-Domain CSS setting when dequeuing stylesheets
     110- FIX: Fixes compatibility issue with WP Rocket's Critical CSS feature
     111- FIX: Improves reliability of Critical CSS expiration logic for individual posts and pages
     112- FIX: Prevents uptime-monitor User-Agents from triggering unnecessary regenerations
     113- FIX: Addresses compatibility issue with Sucuri Website Firewall
     114
    105115= 1.1.0 =
    106 - OPTIMIZATION: Improved database structure for better performance and compatibility
    107116- FEATURE: Adds compatibility with Trellis theme
     117- OPTIMIZATION: Improves database structure for better performance and compatibility
    108118- FIX: Excludes additional non-page file types from generating Critical CSS
    109119- FIX: Excludes non-SSL requests from generating Critical CSS
    110 - FIX: More accurate detection and fallback for Critical CSS settings
     120- FIX: Provides more accurate detection and fallback for Critical CSS settings
    111121- FIX: Fixes compatibility issue with WP Rocket
    112122
  • easy-critical-css/trunk/vendor/composer/autoload_classmap.php

    r3307644 r3317868  
    2020    'EasyCriticalCSS\\Critical_CSS_Status' => $baseDir . '/inc/class-critical-css-status.php',
    2121    'EasyCriticalCSS\\Database' => $baseDir . '/inc/class-database.php',
     22    'EasyCriticalCSS\\Debug' => $baseDir . '/inc/class-debug.php',
    2223    'EasyCriticalCSS\\Gutenberg' => $baseDir . '/inc/class-gutenberg.php',
    2324    'EasyCriticalCSS\\Helpers' => $baseDir . '/inc/class-helpers.php',
  • easy-critical-css/trunk/vendor/composer/autoload_static.php

    r3307644 r3317868  
    3939        'EasyCriticalCSS\\Critical_CSS_Status' => __DIR__ . '/../..' . '/inc/class-critical-css-status.php',
    4040        'EasyCriticalCSS\\Database' => __DIR__ . '/../..' . '/inc/class-database.php',
     41        'EasyCriticalCSS\\Debug' => __DIR__ . '/../..' . '/inc/class-debug.php',
    4142        'EasyCriticalCSS\\Gutenberg' => __DIR__ . '/../..' . '/inc/class-gutenberg.php',
    4243        'EasyCriticalCSS\\Helpers' => __DIR__ . '/../..' . '/inc/class-helpers.php',
  • easy-critical-css/trunk/vendor/composer/installed.php

    r3307644 r3317868  
    44        'pretty_version' => 'dev-main',
    55        'version' => 'dev-main',
    6         'reference' => '31bc22ab17fe61a4e32d8212848435a763845868',
     6        'reference' => '9d1beb8466b4051bfb4b005c65b0ec5faa125478',
    77        'type' => 'wordpress-plugin',
    88        'install_path' => __DIR__ . '/../../',
     
    3232            'pretty_version' => 'dev-main',
    3333            'version' => 'dev-main',
    34             'reference' => '31bc22ab17fe61a4e32d8212848435a763845868',
     34            'reference' => '9d1beb8466b4051bfb4b005c65b0ec5faa125478',
    3535            'type' => 'wordpress-plugin',
    3636            'install_path' => __DIR__ . '/../../',
Note: See TracChangeset for help on using the changeset viewer.