Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
2.08% covered (danger)
2.08%
3 / 144
3.70% covered (danger)
3.70%
1 / 27
CRAP
n/a
0 / 0
jetpack_boost_minify_cache_buster
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_minify_concat_max_files
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_should_run_daily_network_cron_job
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
jetpack_boost_minify_cron_cache_cleanup
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
jetpack_boost_legacy_minify_cache_cleanup
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
jetpack_boost_minify_cache_cleanup
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
jetpack_boost_delete_expired_files
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
42
jetpack_boost_minify_clear_scheduled_events
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_page_optimize_deactivate
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_enqueued_to_absolute_url
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
jetpack_boost_page_optimize_js_exclude_list
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_page_optimize_css_exclude_list
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_page_optimize_starts_with
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
jetpack_boost_page_optimize_use_concat_base_dir
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
jetpack_boost_page_optimize_remove_concat_base_prefix
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
20
jetpack_boost_page_optimize_schedule_404_tester
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
jetpack_boost_page_optimize_schedule_cache_cleanup
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
jetpack_boost_page_optimize_bail
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
132
jetpack_boost_page_optimize_cache_bust_mtime
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
110
jetpack_boost_get_static_prefix
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
jetpack_boost_get_minify_url
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_get_minify_file_path
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_minify_serve_concatenated
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
jetpack_boost_minify_activation
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
jetpack_boost_minify_is_enabled
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
jetpack_boost_minify_init
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
jetpack_boost_page_optimize_generate_concat_path
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3use Automattic\Jetpack_Boost\Lib\Minify\Cleanup_Stored_Paths;
4use Automattic\Jetpack_Boost\Lib\Minify\Config;
5use Automattic\Jetpack_Boost\Lib\Minify\Dependency_Path_Mapping;
6use Automattic\Jetpack_Boost\Lib\Minify\File_Paths;
7use Automattic\Jetpack_Boost\Modules\Module;
8use Automattic\Jetpack_Boost\Modules\Optimizations\Minify\Minify_CSS;
9use Automattic\Jetpack_Boost\Modules\Optimizations\Minify\Minify_JS;
10
11/**
12 * Get an extra cache key for requests. We can manually bump this when we want
13 * to ensure a new version of Jetpack Boost never reuses old cached URLs.
14 */
15function jetpack_boost_minify_cache_buster() {
16    return 1;
17}
18
19/**
20 * Get the number of maximum files that can be concatenated in a group.
21 */
22function jetpack_boost_minify_concat_max_files() {
23
24    /**
25     * Filter the number of maximum files that can be concatenated in a group.
26     *
27     * @param int $max_files The maximum number of files that can be concatenated.
28     */
29    return apply_filters( 'jetpack_boost_minify_concat_max_files', 150 );
30}
31
32/**
33 * This ensures that the cache cleanup cron job is only run once per day, espicially for multisite.
34 */
35function jetpack_boost_should_run_daily_network_cron_job( $hook ) {
36    // If we see it's been executed within 24 hours, don't run
37    if ( get_site_option( 'jetpack_boost_' . $hook . '_last_run', 0 ) > time() - DAY_IN_SECONDS ) {
38        return false;
39    }
40
41    update_site_option( 'jetpack_boost_' . $hook . '_last_run', time() );
42
43    return true;
44}
45
46/**
47 * This ensures that the cache cleanup cron job is only run once per day, espicially for multisite.
48 */
49function jetpack_boost_minify_cron_cache_cleanup() {
50    if ( ! jetpack_boost_should_run_daily_network_cron_job( 'minify_cron_cache_cleanup' ) ) {
51        return;
52    }
53
54    jetpack_boost_legacy_minify_cache_cleanup();
55    jetpack_boost_minify_cache_cleanup();
56}
57
58/**
59 * Cleanup minify cache files stored using the legacy method.
60 *
61 * @param int $file_age The age of files to purge, in seconds.
62 */
63function jetpack_boost_legacy_minify_cache_cleanup( $file_age = DAY_IN_SECONDS ) {
64    // If file_age is not an int, set it to the default.
65    // $file_age can be an empty string if calling this from an action: https://core.trac.wordpress.org/ticket/14881
66    $file_age = is_int( $file_age ) ? $file_age : DAY_IN_SECONDS;
67
68    $cache_folder = Config::get_legacy_cache_dir_path();
69
70    if ( ! is_dir( $cache_folder ) ) {
71        return;
72    }
73
74    $cache_files = glob( $cache_folder . '/page-optimize-cache-*' );
75
76    jetpack_boost_delete_expired_files( $cache_files, $file_age );
77}
78
79/**
80 * Cleanup obsolete files in static cache folder.
81 *
82 * @param int $file_age The age of files to purge, in seconds.
83 */
84function jetpack_boost_minify_cache_cleanup( $file_age = DAY_IN_SECONDS ) {
85    // Explicitly cast to int as do_action() can pass a non-int value. https://core.trac.wordpress.org/ticket/14881
86    $file_age = is_int( $file_age ) ? $file_age : DAY_IN_SECONDS;
87
88    /*
89     * Cleanup obsolete files in static cache folder.
90     * If $file_age is 0, we can skip this as we will delete all files anyway.
91     */
92    if ( $file_age !== 0 ) {
93        // Cleanup obsolete files in static cache folder
94        jetpack_boost_minify_remove_stale_static_files();
95    }
96
97    $cache_files = glob( Config::get_static_cache_dir_path() . '/*.min.*' );
98
99    jetpack_boost_delete_expired_files( $cache_files, $file_age );
100}
101
102/**
103 * Delete expired files based on access time or file age.
104 *
105 * Only delete files which are either 2x $file_age or have not been accessed in $file_age seconds.
106 *
107 * @param array $files The files to delete.
108 * @param int   $file_age The age of files to purge, in seconds.
109 */
110function jetpack_boost_delete_expired_files( $files, $file_age ) {
111    foreach ( $files as $file ) {
112        if ( ! is_file( $file ) ) {
113            continue;
114        }
115
116        if ( $file_age === 0 ) {
117            wp_delete_file( $file );
118            continue;
119        }
120
121        // Delete files that haven't been accessed in $file_age seconds,
122        // or files that are older than 2 * $file_age regardless of access time
123        if ( ( time() - $file_age ) > fileatime( $file ) ) {
124            wp_delete_file( $file );
125        } elseif ( ( time() - ( 2 * $file_age ) ) > filemtime( $file ) ) {
126            wp_delete_file( $file );
127        }
128    }
129}
130
131/**
132 * Clear scheduled cron events for minification.
133 *
134 * Removes the cache cleanup cron job and the 404 tester cron job.
135 */
136function jetpack_boost_minify_clear_scheduled_events() {
137    wp_unschedule_hook( 'jetpack_boost_minify_cron_cache_cleanup' );
138    wp_unschedule_hook( 'jetpack_boost_404_tester_cron' );
139    Cleanup_Stored_Paths::clear_schedules();
140}
141
142/**
143 * Plugin deactivation hook - unschedule cronjobs and purge cache.
144 */
145function jetpack_boost_page_optimize_deactivate() {
146    // Cleanup all cache files even if they are 0 seconds old
147    jetpack_boost_legacy_minify_cache_cleanup( 0 );
148    jetpack_boost_minify_cache_cleanup( 0 );
149
150    jetpack_boost_minify_clear_scheduled_events();
151}
152
153/**
154 * Convert enqueued home-relative URLs to absolute ones.
155 *
156 * Enqueued script URLs which start with / are relative to WordPress' home URL.
157 * i.e.: "/wp-includes/x.js" should be "WP_HOME/wp-includes/x.js".
158 *
159 * Note: this method uses home_url, so should only be used plugin-side when
160 * generating concatenated URLs.
161 */
162function jetpack_boost_enqueued_to_absolute_url( $url ) {
163    if ( str_starts_with( $url, '/' ) ) {
164        return home_url( $url );
165    }
166
167    return $url;
168}
169
170/**
171 * Get the list of JS slugs to exclude from minification.
172 */
173function jetpack_boost_page_optimize_js_exclude_list() {
174    return jetpack_boost_ds_get( 'minify_js_excludes' );
175}
176
177/**
178 * Get the list of CSS slugs to exclude from minification.
179 */
180function jetpack_boost_page_optimize_css_exclude_list() {
181    return jetpack_boost_ds_get( 'minify_css_excludes' );
182}
183
184/**
185 * Determines whether a string starts with another string.
186 */
187function jetpack_boost_page_optimize_starts_with( $prefix, $str ) {
188    $prefix_length = strlen( $prefix );
189    if ( strlen( $str ) < $prefix_length ) {
190        return false;
191    }
192
193    return substr( $str, 0, $prefix_length ) === $prefix;
194}
195
196/**
197 * Answers whether the plugin should provide concat resource URIs
198 * that are relative to a common ancestor directory. Assuming a common ancestor
199 * allows us to skip resolving resource URIs to filesystem paths later on.
200 */
201function jetpack_boost_page_optimize_use_concat_base_dir() {
202    return defined( 'PAGE_OPTIMIZE_CONCAT_BASE_DIR' ) && file_exists( PAGE_OPTIMIZE_CONCAT_BASE_DIR );
203}
204
205/**
206 * Get a filesystem path relative to a configured base path for resources
207 * that will be concatenated. Assuming a common ancestor allows us to skip
208 * resolving resource URIs to filesystem paths later on.
209 */
210function jetpack_boost_page_optimize_remove_concat_base_prefix( $original_fs_path ) {
211    $abspath = Config::get_abspath();
212
213    // Always check longer path first
214    if ( strlen( $abspath ) > strlen( PAGE_OPTIMIZE_CONCAT_BASE_DIR ) ) {
215        $longer_path  = $abspath;
216        $shorter_path = PAGE_OPTIMIZE_CONCAT_BASE_DIR;
217    } else {
218        $longer_path  = PAGE_OPTIMIZE_CONCAT_BASE_DIR;
219        $shorter_path = $abspath;
220    }
221
222    $prefix_abspath = trailingslashit( $longer_path );
223    if ( jetpack_boost_page_optimize_starts_with( $prefix_abspath, $original_fs_path ) ) {
224        return substr( $original_fs_path, strlen( $prefix_abspath ) );
225    }
226
227    $prefix_basedir = trailingslashit( $shorter_path );
228    if ( jetpack_boost_page_optimize_starts_with( $prefix_basedir, $original_fs_path ) ) {
229        return substr( $original_fs_path, strlen( $prefix_basedir ) );
230    }
231
232    // If we end up here, this is a resource we shouldn't have tried to concat in the first place
233    return '/page-optimize-resource-outside-base-path/' . basename( $original_fs_path );
234}
235
236/**
237 * Schedule a cronjob for the 404 tester, if one isn't already scheduled.
238 */
239function jetpack_boost_page_optimize_schedule_404_tester() {
240    if ( false === wp_next_scheduled( 'jetpack_boost_404_tester_cron' ) ) {
241        wp_schedule_event( time() + DAY_IN_SECONDS, 'daily', 'jetpack_boost_404_tester_cron' );
242
243        // Run the test immediately, so the settings page can show the result.
244        jetpack_boost_404_tester();
245    }
246}
247
248/**
249 * Schedule a cronjob for cache cleanup, if one isn't already scheduled.
250 */
251function jetpack_boost_page_optimize_schedule_cache_cleanup() {
252    // If caching is on, and job isn't queued for current cache folder
253    if ( false === wp_next_scheduled( 'jetpack_boost_minify_cron_cache_cleanup' ) ) {
254        wp_schedule_event( time(), 'daily', 'jetpack_boost_minify_cron_cache_cleanup' );
255    }
256}
257
258/**
259 * Check whether it's safe to minify for the duration of this HTTP request. Checks
260 * for things like page-builder editors, etc.
261 *
262 * @return bool True if we don't want to minify/concatenate CSS/JS for this request.
263 */
264function jetpack_boost_page_optimize_bail() {
265    static $should_bail = null;
266    if ( null !== $should_bail ) {
267        return $should_bail;
268    }
269
270    $should_bail = false;
271
272    // Bail if this is an admin page
273    if ( is_admin() ) {
274        $should_bail = true;
275        return true;
276    }
277
278    // Bail if we're in customizer
279    global $wp_customize;
280    if ( isset( $wp_customize ) ) {
281        $should_bail = true;
282        return true;
283    }
284
285    // Bail if Divi theme is active, and we're in the Divi Front End Builder
286    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
287    if ( ! empty( $_GET['et_fb'] ) && 'Divi' === wp_get_theme()->get_template() ) {
288        $should_bail = true;
289        return true;
290    }
291
292    // Bail if we're editing pages in Brizy Editor
293    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
294    if ( class_exists( 'Brizy_Editor' ) && method_exists( 'Brizy_Editor', 'prefix' ) && ( isset( $_GET[ Brizy_Editor::prefix( '-edit-iframe' ) ] ) || isset( $_GET[ Brizy_Editor::prefix( '-edit' ) ] ) ) ) {
295        $should_bail = true;
296        return true;
297    }
298
299    // Bail in elementor preview
300    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
301    if ( isset( $_GET['elementor-preview'] ) ) {
302        $should_bail = true;
303        return true;
304    }
305
306    return $should_bail;
307}
308
309/**
310 * Return a URL with a cache-busting query string based on the file's mtime.
311 */
312function jetpack_boost_page_optimize_cache_bust_mtime( $path, $siteurl ) {
313    static $dependency_path_mapping;
314
315    // Absolute paths should dump the path component of siteurl.
316    if ( str_starts_with( $path, '/' ) ) {
317        $parts   = wp_parse_url( $siteurl );
318        $siteurl = $parts['scheme'] . '://' . $parts['host'];
319    }
320
321    $url = $siteurl . $path;
322
323    if ( str_contains( $url, '?m=' ) ) {
324        return $url;
325    }
326
327    $parts = wp_parse_url( $url );
328    if ( ! isset( $parts['path'] ) || empty( $parts['path'] ) ) {
329        return $url;
330    }
331
332    if ( empty( $dependency_path_mapping ) ) {
333        $dependency_path_mapping = new Dependency_Path_Mapping();
334    }
335
336    $file = $dependency_path_mapping->dependency_src_to_fs_path( $url );
337
338    $mtime = false;
339    if ( file_exists( $file ) ) {
340        $mtime = filemtime( $file );
341    }
342
343    if ( ! $mtime ) {
344        return $url;
345    }
346
347    if ( ! str_contains( $url, '?' ) ) {
348        $q = '';
349    } else {
350        list( $url, $q ) = explode( '?', $url, 2 );
351        if ( strlen( $q ) ) {
352            $q = '&amp;' . $q;
353        }
354    }
355
356    return "$url?m={$mtime}{$q}";
357}
358
359/**
360 * Get the URL prefix for static minify/concat resources. Defaults to /_jb_static/, but can be
361 * overridden by defining JETPACK_BOOST_STATIC_PREFIX.
362 */
363function jetpack_boost_get_static_prefix() {
364    $prefix = defined( 'JETPACK_BOOST_STATIC_PREFIX' ) ? JETPACK_BOOST_STATIC_PREFIX : '/_jb_static/';
365
366    if ( ! str_starts_with( $prefix, '/' ) ) {
367        $prefix = '/' . $prefix;
368    }
369
370    return trailingslashit( $prefix );
371}
372
373function jetpack_boost_get_minify_url( $file_name = '' ) {
374    return content_url( '/boost-cache/static/' . $file_name );
375}
376
377function jetpack_boost_get_minify_file_path( $file_name = '' ) {
378    return WP_CONTENT_DIR . '/boost-cache/static/' . $file_name;
379}
380
381/**
382 * Detects requests within the `/_jb_static/` directory, and serves minified content.
383 *
384 * @return void
385 */
386function jetpack_boost_minify_serve_concatenated() {
387    // Potential improvement: Make concat URL dir configurable
388    // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
389    if ( isset( $_SERVER['REQUEST_URI'] ) ) {
390        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
391        $request_path = explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) )[0];
392        $prefix       = jetpack_boost_get_static_prefix();
393        if ( $prefix === substr( $request_path, -strlen( $prefix ), strlen( $prefix ) ) ) {
394            require_once __DIR__ . '/functions-service-fallback.php';
395            jetpack_boost_page_optimize_service_request();
396            exit( 0 ); // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though jetpack_boost_page_optimize_service_request() itself never returns.
397        }
398    }
399}
400
401/**
402 * Run during activation of any minify module.
403 *
404 * This handles scheduling cache cleanup, and setting up the cronjob to periodically test for the 404 handler.
405 *
406 * @return void
407 */
408function jetpack_boost_minify_activation() {
409    // Schedule a cronjob for cache cleanup, if one isn't already scheduled.
410    jetpack_boost_page_optimize_schedule_cache_cleanup();
411
412    Cleanup_Stored_Paths::setup_schedule();
413
414    // Setup the cronjob to periodically test for the 404 handler.
415    jetpack_boost_404_setup();
416}
417
418function jetpack_boost_minify_is_enabled() {
419    $minify_css = new Module( new Minify_CSS() );
420    $minify_js  = new Module( new Minify_JS() );
421
422    return $minify_css->is_enabled() || $minify_js->is_enabled();
423}
424
425/**
426 * Run during initialization of any minify module.
427 *
428 * Run during every page load if any minify module is active.
429 */
430function jetpack_boost_minify_init() {
431    add_action( 'jetpack_boost_minify_cron_cache_cleanup', 'jetpack_boost_minify_cron_cache_cleanup' );
432    Cleanup_Stored_Paths::add_cleanup_actions();
433
434    if ( jetpack_boost_page_optimize_bail() ) {
435        return;
436    }
437
438    // Disable Jetpack Site Accelerator CDN for static JS/CSS, if we're minifying this page.
439    add_filter( 'jetpack_force_disable_site_accelerator', '__return_true' );
440}
441
442function jetpack_boost_page_optimize_generate_concat_path( $url_paths, $dependency_path_mapping ) {
443    $fs_paths = array();
444    foreach ( $url_paths as $url_path ) {
445        $fs_paths[] = $dependency_path_mapping->uri_path_to_fs_path( $url_path );
446    }
447
448    $mtime = max( array_map( 'filemtime', $fs_paths ) );
449    if ( jetpack_boost_page_optimize_use_concat_base_dir() ) {
450        $paths = array_map( 'jetpack_boost_page_optimize_remove_concat_base_prefix', $fs_paths );
451    } else {
452        $paths = $url_paths;
453    }
454
455    $file_paths = new File_Paths();
456    $file_paths->set( $paths, $mtime, jetpack_boost_minify_cache_buster() );
457    $file_paths->store();
458
459    return $file_paths->get_cache_id();
460}