Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
16.77% covered (danger)
16.77%
28 / 167
17.24% covered (danger)
17.24%
5 / 29
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_page_optimize_is_already_minified
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
jetpack_boost_page_optimize_css_exclude_list
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
jetpack_boost_page_optimize_merge_debug_excludes
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
10
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    // Bumped to 2 so the JS truncation fix actually reaches already-affected sites:
17    // the cached concat bundle is served before any rebuild, so a site that cached a
18    // corrupted bundle would keep shipping it (until its source mtime changed) even
19    // after upgrade. Changing the buster invalidates those cache keys and forces a
20    // rebuild through the new structural-validation/fallback path.
21    return 2;
22}
23
24/**
25 * Whether a file should be treated as already minified (so the serving path must
26 * not re-minify it).
27 *
28 * This is the load-bearing skip for assets shipped as `.min.js` / `.min.css`
29 * (e.g. Boost's own image-guide bundle, which webpack/Terser has already
30 * minified): re-running the ES5-era PHP minifier over modern, already-minified
31 * JS can silently corrupt it. The mime type already routes JS and CSS down
32 * separate branches, so matching both extensions here is safe for either.
33 *
34 * @since 4.6.0
35 *
36 * @param string $fullpath Absolute path to the file being served.
37 *
38 * @return bool True if the filename indicates already-minified content.
39 */
40function jetpack_boost_page_optimize_is_already_minified( $fullpath ) {
41    return (bool) preg_match( '/\.min\.(css|js)$/', (string) $fullpath );
42}
43
44/**
45 * Get the number of maximum files that can be concatenated in a group.
46 */
47function jetpack_boost_minify_concat_max_files() {
48
49    /**
50     * Filter the number of maximum files that can be concatenated in a group.
51     *
52     * @param int $max_files The maximum number of files that can be concatenated.
53     */
54    return apply_filters( 'jetpack_boost_minify_concat_max_files', 150 );
55}
56
57/**
58 * This ensures that the cache cleanup cron job is only run once per day, espicially for multisite.
59 */
60function jetpack_boost_should_run_daily_network_cron_job( $hook ) {
61    // If we see it's been executed within 24 hours, don't run
62    if ( get_site_option( 'jetpack_boost_' . $hook . '_last_run', 0 ) > time() - DAY_IN_SECONDS ) {
63        return false;
64    }
65
66    update_site_option( 'jetpack_boost_' . $hook . '_last_run', time() );
67
68    return true;
69}
70
71/**
72 * This ensures that the cache cleanup cron job is only run once per day, espicially for multisite.
73 */
74function jetpack_boost_minify_cron_cache_cleanup() {
75    if ( ! jetpack_boost_should_run_daily_network_cron_job( 'minify_cron_cache_cleanup' ) ) {
76        return;
77    }
78
79    jetpack_boost_legacy_minify_cache_cleanup();
80    jetpack_boost_minify_cache_cleanup();
81}
82
83/**
84 * Cleanup minify cache files stored using the legacy method.
85 *
86 * @param int $file_age The age of files to purge, in seconds.
87 */
88function jetpack_boost_legacy_minify_cache_cleanup( $file_age = DAY_IN_SECONDS ) {
89    // If file_age is not an int, set it to the default.
90    // $file_age can be an empty string if calling this from an action: https://core.trac.wordpress.org/ticket/14881
91    $file_age = is_int( $file_age ) ? $file_age : DAY_IN_SECONDS;
92
93    $cache_folder = Config::get_legacy_cache_dir_path();
94
95    if ( ! is_dir( $cache_folder ) ) {
96        return;
97    }
98
99    $cache_files = glob( $cache_folder . '/page-optimize-cache-*' );
100
101    jetpack_boost_delete_expired_files( $cache_files, $file_age );
102}
103
104/**
105 * Cleanup obsolete files in static cache folder.
106 *
107 * @param int $file_age The age of files to purge, in seconds.
108 */
109function jetpack_boost_minify_cache_cleanup( $file_age = DAY_IN_SECONDS ) {
110    // Explicitly cast to int as do_action() can pass a non-int value. https://core.trac.wordpress.org/ticket/14881
111    $file_age = is_int( $file_age ) ? $file_age : DAY_IN_SECONDS;
112
113    /*
114     * Cleanup obsolete files in static cache folder.
115     * If $file_age is 0, we can skip this as we will delete all files anyway.
116     */
117    if ( $file_age !== 0 ) {
118        // Cleanup obsolete files in static cache folder
119        jetpack_boost_minify_remove_stale_static_files();
120    }
121
122    $cache_files = glob( Config::get_static_cache_dir_path() . '/*.min.*' );
123
124    jetpack_boost_delete_expired_files( $cache_files, $file_age );
125}
126
127/**
128 * Delete expired files based on access time or file age.
129 *
130 * Only delete files which are either 2x $file_age or have not been accessed in $file_age seconds.
131 *
132 * @param array $files The files to delete.
133 * @param int   $file_age The age of files to purge, in seconds.
134 */
135function jetpack_boost_delete_expired_files( $files, $file_age ) {
136    foreach ( $files as $file ) {
137        if ( ! is_file( $file ) ) {
138            continue;
139        }
140
141        if ( $file_age === 0 ) {
142            wp_delete_file( $file );
143            continue;
144        }
145
146        // Delete files that haven't been accessed in $file_age seconds,
147        // or files that are older than 2 * $file_age regardless of access time
148        if ( ( time() - $file_age ) > fileatime( $file ) ) {
149            wp_delete_file( $file );
150        } elseif ( ( time() - ( 2 * $file_age ) ) > filemtime( $file ) ) {
151            wp_delete_file( $file );
152        }
153    }
154}
155
156/**
157 * Clear scheduled cron events for minification.
158 *
159 * Removes the cache cleanup cron job and the 404 tester cron job.
160 */
161function jetpack_boost_minify_clear_scheduled_events() {
162    wp_unschedule_hook( 'jetpack_boost_minify_cron_cache_cleanup' );
163    wp_unschedule_hook( 'jetpack_boost_404_tester_cron' );
164    Cleanup_Stored_Paths::clear_schedules();
165}
166
167/**
168 * Plugin deactivation hook - unschedule cronjobs and purge cache.
169 */
170function jetpack_boost_page_optimize_deactivate() {
171    // Cleanup all cache files even if they are 0 seconds old
172    jetpack_boost_legacy_minify_cache_cleanup( 0 );
173    jetpack_boost_minify_cache_cleanup( 0 );
174
175    jetpack_boost_minify_clear_scheduled_events();
176}
177
178/**
179 * Convert enqueued home-relative URLs to absolute ones.
180 *
181 * Enqueued script URLs which start with / are relative to WordPress' home URL.
182 * i.e.: "/wp-includes/x.js" should be "WP_HOME/wp-includes/x.js".
183 *
184 * Note: this method uses home_url, so should only be used plugin-side when
185 * generating concatenated URLs.
186 */
187function jetpack_boost_enqueued_to_absolute_url( $url ) {
188    if ( str_starts_with( $url, '/' ) ) {
189        return home_url( $url );
190    }
191
192    return $url;
193}
194
195/**
196 * Get the list of JS handles to exclude from minification.
197 *
198 * Administrators can append extra handles for the current request only via the
199 * `jb-minify-js-excludes` GET parameter (comma-separated handles). See
200 * jetpack_boost_page_optimize_merge_debug_excludes() for details.
201 */
202function jetpack_boost_page_optimize_js_exclude_list() {
203    return jetpack_boost_page_optimize_merge_debug_excludes(
204        jetpack_boost_ds_get( 'minify_js_excludes' ),
205        'jb-minify-js-excludes'
206    );
207}
208
209/**
210 * Get the list of CSS handles to exclude from minification.
211 *
212 * Administrators can append extra handles for the current request only via the
213 * `jb-minify-css-excludes` GET parameter (comma-separated handles). See
214 * jetpack_boost_page_optimize_merge_debug_excludes() for details.
215 */
216function jetpack_boost_page_optimize_css_exclude_list() {
217    return jetpack_boost_page_optimize_merge_debug_excludes(
218        jetpack_boost_ds_get( 'minify_css_excludes' ),
219        'jb-minify-css-excludes'
220    );
221}
222
223/**
224 * Debugging aid: merge per-request exclude handles from a GET parameter into a
225 * saved concatenate/minify exclude list.
226 *
227 * Lets administrators test "would excluding handle X fix this page?" by loading
228 * the page with e.g. `?jb-minify-js-excludes=jquery-core,my-plugin-script`
229 * without editing the saved settings, and without needing a staging site.
230 *
231 * Safety properties:
232 * - Only honored for logged-in users with the `manage_options` capability.
233 *   current_user_can() is available during normal page rendering (the exclude
234 *   lists are read while scripts/styles are printed at wp_head/wp_footer); the
235 *   function_exists() guard defensively no-ops in any pre-WordPress context
236 *   (e.g. the page-cache layer) where it is not yet loaded.
237 * - Each handle is validated against a strict allowlist (alphanumerics, dash,
238 *   underscore and dot); anything else is ignored entirely. Case is preserved
239 *   because script/style handles are matched case-sensitively downstream.
240 * - The number of handles processed is capped to bound the work this debug aid
241 *   can be made to do per request.
242 * - The merged list is never persisted â€” nothing is written to the data sync
243 *   store or options; the merge only affects the current request.
244 * - No Page Cache interaction: logged-in users always bypass Boost's page cache
245 *   (Request::is_cacheable() returns false), so pages rendered with these debug
246 *   parameters are neither served from nor written to the cache.
247 *
248 * Residual note: changing the exclude list changes concatenate-bundle membership,
249 * so each distinct combination an admin renders generates its own concat-path
250 * transient (and static file when static minification is on), just like any new
251 * page does. These are reclaimed by the normal Cleanup_Stored_Paths schedule.
252 * The parameter is intentionally nonce-free so it can be shared as a plain URL,
253 * so this stays capability-gated rather than CSRF-gated.
254 *
255 * @since 4.6.2
256 *
257 * @param mixed  $excludes  The saved exclude list; non-array values are treated as an empty list.
258 * @param string $param     The GET parameter to read extra handles from.
259 *
260 * @return array The exclude list, with any valid per-request handles appended.
261 */
262function jetpack_boost_page_optimize_merge_debug_excludes( $excludes, $param ) {
263    if ( ! is_array( $excludes ) ) {
264        $excludes = array();
265    }
266
267    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only debugging parameter; capability-gated below and never persisted.
268    if ( ! isset( $_GET[ $param ] ) || ! is_string( $_GET[ $param ] ) ) {
269        return $excludes;
270    }
271
272    // Only administrators may use the debug parameters.
273    if ( ! function_exists( 'current_user_can' ) || ! current_user_can( 'manage_options' ) ) {
274        return $excludes;
275    }
276
277    // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each handle is validated against a strict allowlist below.
278    $raw_value = wp_unslash( $_GET[ $param ] );
279
280    // Bound the work: a real debug session never needs more than a handful of
281    // handles, so process at most the first 100. The excess is dropped rather
282    // than discarding the whole value, so a partial list still applies.
283    $raw_handles = array_slice( explode( ',', $raw_value ), 0, 100 );
284
285    $extra_handles = array();
286    foreach ( $raw_handles as $handle ) {
287        $handle = trim( $handle );
288
289        /*
290         * Allowlist of characters valid in script/style handles. Case is preserved
291         * (not lowercased) because handles are matched case-sensitively downstream,
292         * so lowercasing would silently fail to exclude a handle registered with
293         * uppercase letters. Handles containing any other character are ignored.
294         */
295        if ( $handle !== '' && preg_match( '/^[a-zA-Z0-9_.\-]+$/', $handle ) ) {
296            $extra_handles[] = $handle;
297        }
298    }
299
300    if ( empty( $extra_handles ) ) {
301        return $excludes;
302    }
303
304    return array_values( array_unique( array_merge( $excludes, $extra_handles ) ) );
305}
306
307/**
308 * Determines whether a string starts with another string.
309 */
310function jetpack_boost_page_optimize_starts_with( $prefix, $str ) {
311    $prefix_length = strlen( $prefix );
312    if ( strlen( $str ) < $prefix_length ) {
313        return false;
314    }
315
316    return substr( $str, 0, $prefix_length ) === $prefix;
317}
318
319/**
320 * Answers whether the plugin should provide concat resource URIs
321 * that are relative to a common ancestor directory. Assuming a common ancestor
322 * allows us to skip resolving resource URIs to filesystem paths later on.
323 */
324function jetpack_boost_page_optimize_use_concat_base_dir() {
325    return defined( 'PAGE_OPTIMIZE_CONCAT_BASE_DIR' ) && file_exists( PAGE_OPTIMIZE_CONCAT_BASE_DIR );
326}
327
328/**
329 * Get a filesystem path relative to a configured base path for resources
330 * that will be concatenated. Assuming a common ancestor allows us to skip
331 * resolving resource URIs to filesystem paths later on.
332 */
333function jetpack_boost_page_optimize_remove_concat_base_prefix( $original_fs_path ) {
334    $abspath = Config::get_abspath();
335
336    // Always check longer path first
337    if ( strlen( $abspath ) > strlen( PAGE_OPTIMIZE_CONCAT_BASE_DIR ) ) {
338        $longer_path  = $abspath;
339        $shorter_path = PAGE_OPTIMIZE_CONCAT_BASE_DIR;
340    } else {
341        $longer_path  = PAGE_OPTIMIZE_CONCAT_BASE_DIR;
342        $shorter_path = $abspath;
343    }
344
345    $prefix_abspath = trailingslashit( $longer_path );
346    if ( jetpack_boost_page_optimize_starts_with( $prefix_abspath, $original_fs_path ) ) {
347        return substr( $original_fs_path, strlen( $prefix_abspath ) );
348    }
349
350    $prefix_basedir = trailingslashit( $shorter_path );
351    if ( jetpack_boost_page_optimize_starts_with( $prefix_basedir, $original_fs_path ) ) {
352        return substr( $original_fs_path, strlen( $prefix_basedir ) );
353    }
354
355    // If we end up here, this is a resource we shouldn't have tried to concat in the first place
356    return '/page-optimize-resource-outside-base-path/' . basename( $original_fs_path );
357}
358
359/**
360 * Schedule a cronjob for the 404 tester, if one isn't already scheduled.
361 */
362function jetpack_boost_page_optimize_schedule_404_tester() {
363    if ( false === wp_next_scheduled( 'jetpack_boost_404_tester_cron' ) ) {
364        wp_schedule_event( time() + DAY_IN_SECONDS, 'daily', 'jetpack_boost_404_tester_cron' );
365
366        // Run the test immediately, so the settings page can show the result.
367        jetpack_boost_404_tester();
368    }
369}
370
371/**
372 * Schedule a cronjob for cache cleanup, if one isn't already scheduled.
373 */
374function jetpack_boost_page_optimize_schedule_cache_cleanup() {
375    // If caching is on, and job isn't queued for current cache folder
376    if ( false === wp_next_scheduled( 'jetpack_boost_minify_cron_cache_cleanup' ) ) {
377        wp_schedule_event( time(), 'daily', 'jetpack_boost_minify_cron_cache_cleanup' );
378    }
379}
380
381/**
382 * Check whether it's safe to minify for the duration of this HTTP request. Checks
383 * for things like page-builder editors, etc.
384 *
385 * @return bool True if we don't want to minify/concatenate CSS/JS for this request.
386 */
387function jetpack_boost_page_optimize_bail() {
388    static $should_bail = null;
389    if ( null !== $should_bail ) {
390        return $should_bail;
391    }
392
393    $should_bail = false;
394
395    // Bail if this is an admin page
396    if ( is_admin() ) {
397        $should_bail = true;
398        return true;
399    }
400
401    // Bail if we're in customizer
402    global $wp_customize;
403    if ( isset( $wp_customize ) ) {
404        $should_bail = true;
405        return true;
406    }
407
408    // Bail if Divi theme is active, and we're in the Divi Front End Builder
409    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
410    if ( ! empty( $_GET['et_fb'] ) && 'Divi' === wp_get_theme()->get_template() ) {
411        $should_bail = true;
412        return true;
413    }
414
415    // Bail if we're editing pages in Brizy Editor
416    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
417    if ( class_exists( 'Brizy_Editor' ) && method_exists( 'Brizy_Editor', 'prefix' ) && ( isset( $_GET[ Brizy_Editor::prefix( '-edit-iframe' ) ] ) || isset( $_GET[ Brizy_Editor::prefix( '-edit' ) ] ) ) ) {
418        $should_bail = true;
419        return true;
420    }
421
422    // Bail in elementor preview
423    // phpcs:ignore WordPress.Security.NonceVerification.Recommended
424    if ( isset( $_GET['elementor-preview'] ) ) {
425        $should_bail = true;
426        return true;
427    }
428
429    return $should_bail;
430}
431
432/**
433 * Return a URL with a cache-busting query string based on the file's mtime.
434 */
435function jetpack_boost_page_optimize_cache_bust_mtime( $path, $siteurl ) {
436    static $dependency_path_mapping;
437
438    // Absolute paths should dump the path component of siteurl.
439    if ( str_starts_with( $path, '/' ) ) {
440        $parts   = wp_parse_url( $siteurl );
441        $siteurl = $parts['scheme'] . '://' . $parts['host'];
442    }
443
444    $url = $siteurl . $path;
445
446    if ( str_contains( $url, '?m=' ) ) {
447        return $url;
448    }
449
450    $parts = wp_parse_url( $url );
451    if ( ! isset( $parts['path'] ) || empty( $parts['path'] ) ) {
452        return $url;
453    }
454
455    if ( empty( $dependency_path_mapping ) ) {
456        $dependency_path_mapping = new Dependency_Path_Mapping();
457    }
458
459    $file = $dependency_path_mapping->dependency_src_to_fs_path( $url );
460
461    $mtime = false;
462    if ( file_exists( $file ) ) {
463        $mtime = filemtime( $file );
464    }
465
466    if ( ! $mtime ) {
467        return $url;
468    }
469
470    if ( ! str_contains( $url, '?' ) ) {
471        $q = '';
472    } else {
473        list( $url, $q ) = explode( '?', $url, 2 );
474        if ( strlen( $q ) ) {
475            $q = '&amp;' . $q;
476        }
477    }
478
479    return "$url?m={$mtime}{$q}";
480}
481
482/**
483 * Get the URL prefix for static minify/concat resources. Defaults to /_jb_static/, but can be
484 * overridden by defining JETPACK_BOOST_STATIC_PREFIX.
485 */
486function jetpack_boost_get_static_prefix() {
487    $prefix = defined( 'JETPACK_BOOST_STATIC_PREFIX' ) ? JETPACK_BOOST_STATIC_PREFIX : '/_jb_static/';
488
489    if ( ! str_starts_with( $prefix, '/' ) ) {
490        $prefix = '/' . $prefix;
491    }
492
493    return trailingslashit( $prefix );
494}
495
496function jetpack_boost_get_minify_url( $file_name = '' ) {
497    return content_url( '/boost-cache/static/' . $file_name );
498}
499
500function jetpack_boost_get_minify_file_path( $file_name = '' ) {
501    return WP_CONTENT_DIR . '/boost-cache/static/' . $file_name;
502}
503
504/**
505 * Detects requests within the `/_jb_static/` directory, and serves minified content.
506 *
507 * @return void
508 */
509function jetpack_boost_minify_serve_concatenated() {
510    // Potential improvement: Make concat URL dir configurable
511    if ( isset( $_SERVER['REQUEST_URI'] ) ) {
512        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
513        $request_path = explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) )[0];
514        $prefix       = jetpack_boost_get_static_prefix();
515        if ( $prefix === substr( $request_path, -strlen( $prefix ), strlen( $prefix ) ) ) {
516            require_once __DIR__ . '/functions-service-fallback.php';
517            jetpack_boost_page_optimize_service_request();
518            exit( 0 ); // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though jetpack_boost_page_optimize_service_request() itself never returns.
519        }
520    }
521}
522
523/**
524 * Run during activation of any minify module.
525 *
526 * This handles scheduling cache cleanup, and setting up the cronjob to periodically test for the 404 handler.
527 *
528 * @return void
529 */
530function jetpack_boost_minify_activation() {
531    // Schedule a cronjob for cache cleanup, if one isn't already scheduled.
532    jetpack_boost_page_optimize_schedule_cache_cleanup();
533
534    Cleanup_Stored_Paths::setup_schedule();
535
536    // Setup the cronjob to periodically test for the 404 handler.
537    jetpack_boost_404_setup();
538}
539
540function jetpack_boost_minify_is_enabled() {
541    $minify_css = new Module( new Minify_CSS() );
542    $minify_js  = new Module( new Minify_JS() );
543
544    return $minify_css->is_enabled() || $minify_js->is_enabled();
545}
546
547/**
548 * Run during initialization of any minify module.
549 *
550 * Run during every page load if any minify module is active.
551 */
552function jetpack_boost_minify_init() {
553    add_action( 'jetpack_boost_minify_cron_cache_cleanup', 'jetpack_boost_minify_cron_cache_cleanup' );
554    Cleanup_Stored_Paths::add_cleanup_actions();
555
556    if ( jetpack_boost_page_optimize_bail() ) {
557        return;
558    }
559
560    // Disable Jetpack Site Accelerator CDN for static JS/CSS, if we're minifying this page.
561    add_filter( 'jetpack_force_disable_site_accelerator', '__return_true' );
562}
563
564function jetpack_boost_page_optimize_generate_concat_path( $url_paths, $dependency_path_mapping ) {
565    $fs_paths = array();
566    foreach ( $url_paths as $url_path ) {
567        $fs_paths[] = $dependency_path_mapping->uri_path_to_fs_path( $url_path );
568    }
569
570    $mtime = max( array_map( 'filemtime', $fs_paths ) );
571    if ( jetpack_boost_page_optimize_use_concat_base_dir() ) {
572        $paths = array_map( 'jetpack_boost_page_optimize_remove_concat_base_prefix', $fs_paths );
573    } else {
574        $paths = $url_paths;
575    }
576
577    $file_paths = new File_Paths();
578    $file_paths->set( $paths, $mtime, jetpack_boost_minify_cache_buster() );
579    $file_paths->store();
580
581    return $file_paths->get_cache_id();
582}