PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 3.6.3
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v3.6.3
trunk 1.0 1.0.1 1.1 1.1.1 1.1.2 1.2 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 2.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1 2.1.1 2.1.2 2.2 2.2.1 All 69 releases
powered-cache / includes / utils.php

utils.php in Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score 3.6.3, at includes/utils.php

1,593 lines 40.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Utils
4 *
5 * @package PoweredCache
6 */
7
8 namespace PoweredCache\Utils;
9
10 use PoweredCache\Encryption;
11 use const PoweredCache\Constants\SETTING_OPTION;
12 use RecursiveDirectoryIterator;
13 use RecursiveIteratorIterator;
14
15 /**
16 * Is plugin activated network wide?
17 *
18 * @param string $plugin_file file path
19 *
20 * @return bool
21 * @since 2.0
22 */
23 function is_network_wide( $plugin_file ) {
24 if ( ! is_multisite() ) {
25 return false;
26 }
27
28 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
29 require_once ABSPATH . '/wp-admin/includes/plugin.php';
30 }
31
32 return is_plugin_active_for_network( plugin_basename( $plugin_file ) );
33 }
34
35 /**
36 * Get settings with defaults
37 *
38 * @param bool $force_network_wide Whether getting settings for network or not.
39 * The function respects `POWERED_CACHE_IS_NETWORK` by default.
40 * However, `POWERED_CACHE_IS_NETWORK` is not functional on
41 * (de)activation hooks.
42 *
43 * @return array
44 * @since 2.0
45 */
46 function get_settings( $force_network_wide = false ) {
47 global $is_apache;
48
49 $settings = [
50 // basic options
51 'enable_page_cache' => true,
52 'object_cache' => 'off',
53 'cache_mobile' => true,
54 'cache_mobile_separate_file' => false,
55 'loggedin_user_cache' => false,
56 'ssl_cache' => true, // deprecated
57 'gzip_compression' => false,
58 'cache_timeout' => 1440,
59 // advanced options
60 'auto_configure_htaccess' => $is_apache,
61 'rejected_user_agents' => '',
62 'rejected_cookies' => '',
63 'rejected_referrers' => '',
64 'vary_cookies' => '',
65 'rejected_uri' => '',
66 'ignored_query_strings' => '',
67 'cache_query_strings' => '',
68 'purge_additional_pages' => '',
69 // file optimization
70 'minify_html' => false,
71 'minify_html_dom_optimization' => false,
72 'combine_google_fonts' => false,
73 'swap_google_fonts_display' => true,
74 'use_bunny_fonts' => false,
75 'minify_css' => false,
76 'combine_css' => false,
77 'critical_css' => false,
78 'critical_css_additional_files' => '',
79 'critical_css_excluded_files' => '',
80 'critical_css_appended_content' => '',
81 'critical_css_fallback' => '',
82 'excluded_css_files' => '',
83 'remove_unused_css' => false,
84 'ucss_safelist' => '',
85 'ucss_excluded_files' => '',
86 'minify_js' => false,
87 'combine_js' => false,
88 'excluded_js_files' => '',
89 'js_execution_method' => 'blocking', // deprecated @since 3.2
90 'js_defer' => false,
91 'js_defer_exclusions' => '',
92 'js_delay' => false,
93 'js_delay_exclusions' => '',
94 'js_delay_timeout' => 0,
95 'js_execution_optimized_only' => true, // deprecated @since 3.2
96 'rewrite_file_optimizer' => $is_apache,
97 // media optimization
98 'enable_image_optimization' => false,
99 'image_optimizer_preferred_format' => '',
100 'add_missing_image_dimensions' => false,
101 // lazyload
102 'enable_lazy_load' => false,
103 'lazy_load_post_content' => true,
104 'lazy_load_images' => true,
105 'lazy_load_iframes' => true,
106 'lazy_load_widgets' => true,
107 'lazy_load_post_thumbnail' => true,
108 'lazy_load_avatars' => true,
109 'lazy_load_youtube' => false,
110 'lazy_load_skip_first_nth_img' => 3,
111 'lazy_load_exclusions' => '',
112 'disable_wp_lazy_load' => false,
113 'disable_wp_embeds' => false,
114 'disable_emoji_scripts' => false,
115 // cdn
116 'enable_cdn' => false,
117 'cdn_hostname' => array( '' ),
118 'cdn_zone' => array( '' ),
119 'cdn_rejected_files' => '',
120 // preload
121 'enable_cache_preload' => false,
122 'preload_homepage' => true,
123 'preload_public_posts' => true,
124 'preload_public_tax' => true,
125 'enable_sitemap_preload' => false,
126 'preload_request_interval' => 2, // in seconds
127 'preload_sitemap' => '',
128 'prefetch_dns' => '',
129 'preconnect_resource' => '',
130 'prefetch_links' => true,
131 // db options
132 'db_cleanup_post_revisions' => false,
133 'db_cleanup_auto_drafts' => false,
134 'db_cleanup_trashed_posts' => false,
135 'db_cleanup_spam_comments' => false,
136 'db_cleanup_trashed_comments' => false,
137 'db_cleanup_expired_transients' => false,
138 'db_cleanup_all_transients' => false,
139 'db_cleanup_optimize_tables' => false,
140 'enable_scheduled_db_cleanup' => false,
141 'scheduled_db_cleanup_frequency' => 'daily',
142 // add-ons
143 'enable_cloudflare' => false,
144 'cloudflare_api_token' => '',
145 'cloudflare_email' => '',
146 'cloudflare_api_key' => '',
147 'cloudflare_zone' => '',
148 'enable_heartbeat' => false, // extension status
149 'heartbeat_dashboard_status' => 'enable', // enable,disable,modify
150 'heartbeat_dashboard_interval' => 60, // default interval in seconds
151 'heartbeat_editor_status' => 'enable', // enable,disable,modify
152 'heartbeat_editor_interval' => 15, // default interval in seconds
153 'heartbeat_frontend_status' => 'enable', // enable,disable,modify
154 'heartbeat_frontend_interval' => 60, // default interval in seconds
155 'enable_varnish' => false,
156 'varnish_ip' => '',
157 // misc
158 'cache_footprint' => true,
159 'async_cache_cleaning' => false,
160 'dev_mode' => false,
161 // new options needs to migrate from extensions
162 'enable_google_tracking' => false,
163 'enable_fb_tracking' => false,
164 ];
165
166 /**
167 * Filter default settings.
168 *
169 * @hook powered_cache_default_settings
170 *
171 * @param {array} $settings Default settings.
172 *
173 * @return {array} New value
174 * @since 2.0
175 */
176 $default_settings = apply_filters( 'powered_cache_default_settings', $settings );
177
178 if ( POWERED_CACHE_IS_NETWORK || $force_network_wide ) {
179 $settings = get_site_option( SETTING_OPTION, [] );
180 } else {
181 $settings = get_option( SETTING_OPTION, [] );
182 }
183
184 $settings = wp_parse_args( $settings, $default_settings );
185
186 return $settings;
187 }
188
189
190 /**
191 * return base caching dir
192 * use this function to get base caching directory instead of directly calling constant
193 *
194 * @return string path
195 * @since 1.0
196 */
197 function get_cache_dir() {
198 if ( defined( 'POWERED_CACHE_CACHE_DIR' ) ) {
199 return POWERED_CACHE_CACHE_DIR; // don't change unless have a particular reason
200 }
201
202 return WP_CONTENT_DIR . '/cache/';
203 }
204
205
206 /**
207 * Object cache methods keys will use as option
208 *
209 * @return array $object_caches
210 * @since 1.2 apcu added
211 * @since 1.0
212 */
213 function get_object_cache_dropins() {
214
215 $object_caches = array(
216 'memcache' => POWERED_CACHE_DROPIN_DIR . 'memcache-object-cache.php',
217 'memcached' => POWERED_CACHE_DROPIN_DIR . 'memcached-object-cache.php',
218 'redis' => POWERED_CACHE_DROPIN_DIR . 'redis-object-cache.php',
219 'apcu' => POWERED_CACHE_DROPIN_DIR . 'apcu-object-cache.php',
220 );
221
222 /**
223 * Filter object cache dropins.
224 *
225 * @hook powered_cache_object_cache_dropins
226 *
227 * @param {array} $object_caches The list of supported object-cache dropins.
228 *
229 * @return {array} New value
230 * @since 1.0
231 */
232 return apply_filters( 'powered_cache_object_cache_dropins', $object_caches );
233 }
234
235
236 /**
237 * Get available object cache backends
238 *
239 * @return array
240 * @since 1.2 unset apcu
241 * @since 1.0
242 */
243 function get_available_object_caches() {
244 $object_cache_methods = get_object_cache_dropins();
245
246 if ( ! class_exists( '\Memcache' ) || version_compare( PHP_VERSION, '5.6.20', '<' ) ) {
247 unset( $object_cache_methods['memcache'] );
248 }
249
250 if ( ! class_exists( '\Memcached' ) ) {
251 unset( $object_cache_methods['memcached'] );
252 }
253
254 if ( ! class_exists( '\Redis' ) ) {
255 unset( $object_cache_methods['redis'] );
256 }
257
258 if ( ! function_exists( '\apcu_add' ) ) {
259 unset( $object_cache_methods['apcu'] );
260 }
261
262 return array_keys( $object_cache_methods );
263 }
264
265
266 /**
267 * convert minutes to possible time format
268 *
269 * @param int $timeout_in_minutes TTL in minutes
270 *
271 * @return array
272 * @since 1.1
273 */
274 function get_timeout_with_interval( $timeout_in_minutes ) {
275 $cache_timeout = $timeout_in_minutes;
276 $selected_interval = 'MINUTE';
277
278 if ( $cache_timeout > 0 ) {
279 if ( 0 === (int) ( $cache_timeout % 1440 ) ) {
280 $cache_timeout = $cache_timeout / 1440;
281 $selected_interval = 'DAY';
282 } elseif ( 0 === (int) ( $cache_timeout % 60 ) ) {
283 $cache_timeout = $cache_timeout / 60;
284 $selected_interval = 'HOUR';
285 }
286 }
287
288 return array(
289 $cache_timeout,
290 $selected_interval,
291 );
292 }
293
294 /**
295 * Determine whether display or not display htaccess configuration
296 * .htaccess can affect the way of serving cached files.
297 * Therefore it's only available for network admin on multisite
298 *
299 * @return bool
300 */
301 function can_configure_htaccess() {
302 global $is_apache;
303
304 if ( ! $is_apache ) {
305 return false;
306 }
307
308 if ( POWERED_CACHE_IS_NETWORK && current_user_can( 'manage_network' ) ) {
309 return true;
310 }
311
312 if ( is_multisite() && ! POWERED_CACHE_IS_NETWORK ) {
313 return false;
314 }
315
316 if ( current_user_can( 'manage_options' ) ) {
317 return true;
318 }
319
320 return false;
321 }
322
323 /**
324 * Whether current user capable to do any configuration changes
325 *
326 * @return bool
327 */
328 function can_control_all_settings() {
329 if ( is_multisite() ) {
330 if ( current_user_can( 'manage_network' ) ) {
331 return true;
332 }
333
334 return false;
335 }
336
337 if ( current_user_can( 'manage_options' ) ) {
338 return true;
339 }
340
341 return false;
342 }
343
344
345 /**
346 * Object cache has an effect on all WP
347 * So, it should be available for the network admin on multisite
348 * regardless of network-wide or individual activated
349 *
350 * @return bool
351 */
352 function can_configure_object_cache() {
353 if ( is_multisite() ) {
354 // only allow on network-wide activation
355 if ( POWERED_CACHE_IS_NETWORK && current_user_can( 'manage_network' ) ) {
356 return true;
357 }
358
359 return false;
360 }
361
362 if ( current_user_can( 'manage_options' ) ) {
363 return true;
364 }
365
366 return false;
367 }
368
369 /**
370 * Supported js execution methods
371 *
372 * @depreacated since 3.2
373 *
374 * @return mixed|void
375 */
376 function js_execution_methods() {
377 $methods = [
378 'blocking' => esc_html__( 'Blocking – (default)', 'powered-cache' ),
379 'async' => esc_html__( 'Non-blocking using async', 'powered-cache' ),
380 'defer' => esc_html__( 'Non-blocking using defer', 'powered-cache' ),
381 'delayed' => esc_html__( 'Delayed for user interaction', 'powered-cache' ),
382 ];
383
384 /**
385 * Filter supported JS execution methods.
386 *
387 * @hook powered_cache_js_execution_methods
388 *
389 * @param {array} $powered_cache_js_execution_methods JS execution methods.
390 *
391 * @return {array} New value
392 * @since 2.0
393 */
394 return apply_filters( 'powered_cache_js_execution_methods', $methods );
395 }
396
397
398 /**
399 * Get available zones
400 *
401 * @return mixed|void
402 * @since 1.0
403 */
404 function cdn_zones() {
405 $zones = [
406 'all' => esc_html__( 'All files', 'powered-cache' ),
407 'image' => esc_html__( 'Images', 'powered-cache' ),
408 'js' => esc_html__( 'JavaScript', 'powered-cache' ),
409 'css' => esc_html__( 'CSS', 'powered-cache' ),
410 ];
411
412 /**
413 * Filter CDN zone options.
414 *
415 * @hook powered_cache_cdn_zones
416 *
417 * @param {array} $zones CDN Zones (all,image,js,css)
418 *
419 * @return {array} New value
420 * @since 1.0
421 */
422 return apply_filters( 'powered_cache_cdn_zones', $zones );
423 }
424
425 /**
426 * Which version of plugin running
427 *
428 * @return bool
429 */
430 function is_premium() {
431 if ( defined( 'POWERED_CACHE_PREMIUM_PLUGIN_FILE' ) && POWERED_CACHE_PREMIUM_PLUGIN_FILE ) {
432 return true;
433 }
434
435 return false;
436 }
437
438 /**
439 * Scheduled cleanup options
440 *
441 * @return array
442 */
443 function scheduled_cleanup_frequency_options() {
444 $options = [
445 'daily' => esc_html__( 'Daily', 'powered-cache' ),
446 'weekly' => esc_html__( 'Weekly', 'powered-cache' ),
447 'monthly' => esc_html__( 'Monthly', 'powered-cache' ),
448 ];
449
450 /**
451 * Filter scheduled cleanup options.
452 *
453 * @hook powered_cache_scheduled_cleanup_frequency_options
454 *
455 * @param {array} $options The list of supported schedules.
456 *
457 * @return {array} New value
458 * @since 2.0
459 */
460 return apply_filters( 'powered_cache_scheduled_cleanup_frequency_options', $options );
461 }
462
463 /**
464 * ports \settings_errors for SUI
465 *
466 * @param string $setting Slug title of a specific setting
467 * @param bool $sanitize Whether to re-sanitize the setting value before returning errors
468 * @param bool $hide_on_update Whether hide or not hide on update
469 *
470 * @see settings_errors
471 */
472 function settings_errors( $setting = '', $sanitize = false, $hide_on_update = false ) {
473
474 if ( $hide_on_update && ! empty( $_GET['settings-updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
475 return;
476 }
477
478 $settings_errors = get_settings_errors( $setting, $sanitize );
479
480 if ( empty( $settings_errors ) ) {
481 return;
482 }
483
484 $output = '';
485
486 foreach ( $settings_errors as $key => $details ) {
487 if ( 'updated' === $details['type'] ) {
488 $details['type'] = 'sui-notice-success';
489 }
490
491 if ( in_array( $details['type'], array( 'error', 'success', 'warning', 'info' ), true ) ) {
492 $details['type'] = 'sui-notice-' . $details['type'];
493 }
494
495 $css_id = sprintf(
496 'setting-error-%s',
497 esc_attr( $details['code'] )
498 );
499
500 $css_class = sprintf(
501 'sui-notice %s settings-error is-dismissible',
502 esc_attr( $details['type'] )
503 );
504
505 $output .= "<div id='$css_id' class='$css_class'> \n";
506 $output .= "<div class='sui-notice-content'><div class='sui-notice-message'>";
507 $output .= "<span class='sui-notice-icon sui-icon-info sui-md' aria-hidden='true'></span>";
508 $output .= "<p>{$details['message']}</p></div></div>";
509 $output .= "</div> \n";
510 }
511
512 echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
513 }
514
515 /**
516 * remove directories recursively
517 * Adopted from W3TC Utility
518 *
519 * @param string $path The target path
520 * @param array $exclude list of the files that will excluded
521 *
522 * @return void
523 * @since 1.2.5
524 */
525 function remove_dir( $path, $exclude = array() ) {
526 // phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged
527 $dir = @opendir( $path );
528
529 if ( $dir ) {
530 while ( ( $entry = @readdir( $dir ) ) !== false ) { // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
531 if ( '.' === $entry || '..' === $entry ) {
532 continue;
533 }
534
535 foreach ( $exclude as $mask ) {
536 if ( fnmatch( $mask, basename( $entry ) ) ) {
537 continue 2;
538 }
539 }
540
541 $full_path = $path . DIRECTORY_SEPARATOR . $entry;
542
543 if ( @is_dir( $full_path ) ) {
544 remove_dir( $full_path, $exclude );
545 } else {
546 @unlink( $full_path );
547 }
548 }
549
550 @closedir( $dir );
551 @rmdir( $path );
552 }
553 // phpcs:enable WordPress.PHP.NoSilencedErrors.Discouraged
554 }
555
556 /**
557 * Get base caching directory of the site.
558 *
559 * @return mixed|void
560 * @since 1.1
561 */
562 function site_cache_dir() {
563 $base_dir = get_page_cache_dir();
564
565 // compatible with multisite
566 $site_url = get_site_url();
567
568 $site_url_parsed = wp_parse_url( $site_url );
569
570 $site_path = $site_url_parsed['host'];
571
572 if ( ! empty( $site_url_parsed['path'] ) ) {
573 $site_path .= $site_url_parsed['path'];
574 }
575
576 $site_cache_dir = trailingslashit( $base_dir . $site_path );
577
578 /**
579 * Filter get base caching directory of site
580 *
581 * @hook powered_cache_site_cache_dir
582 *
583 * @param {string} $site_cache_dir Site cache dir.
584 *
585 * @return {string} New value
586 * @since 1.1
587 */
588 return apply_filters( 'powered_cache_site_cache_dir', $site_cache_dir );
589 }
590
591 /**
592 * Page cache base directory.
593 *
594 * @return string
595 * @since 1.1 $url parameter removed
596 * @since 1.0
597 */
598 function get_page_cache_dir() {
599 $path = get_cache_dir() . 'powered-cache/';
600
601 /**
602 * Filter page cache base directory.
603 *
604 * @hook powered_cache_get_page_cache_dir
605 *
606 * @param {string} $path Page cache dir
607 *
608 * @return {string} New value
609 * @since 1.0
610 */
611 return apply_filters( 'powered_cache_get_page_cache_dir', $path );
612 }
613
614 /**
615 * Clean up cache directory
616 *
617 * @return mixed
618 * @since 1.0
619 */
620 function clean_page_cache_dir() {
621 remove_dir( get_page_cache_dir() );
622 }
623
624 /**
625 * Clean cache base for the current site
626 *
627 * @return mixed
628 * @since 1.1
629 */
630 function clean_site_cache_dir() {
631 $site_cache_dir = site_cache_dir();
632
633 /**
634 * When deleting cache for the main site on multisite subdirectory setup
635 * Don't delete other site's cache
636 */
637 if ( is_multisite() && ! is_subdomain_install() && is_main_site() ) {
638 $base_dir = get_page_cache_dir();
639 $directories = glob( $site_cache_dir . '*', GLOB_ONLYDIR );
640 $site_url = get_site_url();
641
642 $site_domain = wp_parse_url( $site_url, PHP_URL_HOST );
643 foreach ( $directories as $directory ) {
644 $dir_name = str_replace( $base_dir . $site_domain, '', $directory );
645 $site_info = get_site_by_path( $site_domain, $dir_name );
646 if ( ! $site_info || is_main_site( $site_info->blog_id ) ) {
647 remove_dir( $directory );
648 }
649 }
650 } else {
651 remove_dir( $site_cache_dir );
652 }
653
654 /**
655 * Fires after deleting site cache dir
656 *
657 * @hook powered_cache_clean_site_cache_dir
658 *
659 * @param {string} $site_cache_dir The caching directory of the current site.
660 *
661 * @since 2.0
662 */
663 do_action( 'powered_cache_clean_site_cache_dir', $site_cache_dir );
664 }
665
666
667 /**
668 * Supported mobile browsers
669 *
670 * @return mixed|void
671 * @since 1.0
672 */
673 function mobile_browsers() {
674 $mobile_browsers
675 = '2.0 MMP, 240x320, 400X240, AvantGo, BlackBerry, Blazer, Cellphone, Danger, DoCoMo, Elaine/3.0, EudoraWeb, Googlebot-Mobile, hiptop, IEMobile, KYOCERA/WX310K, LG/U990, MIDP-2., MMEF20, MOT-V, NetFront, Newt, Nintendo Wii, Nitro, Nokia, Opera Mini, Palm, PlayStation Portable, portalmmm, Proxinet, ProxiNet, SHARP-TQ-GX10, SHG-i900, Small, SonyEricsson, Symbian OS, SymbianOS, TS21i-10, UP.Browser, UP.Link, webOS, Windows CE, WinWAP, YahooSeeker/M1A1-R2D2, iPhone, iPod, Android, BlackBerry9530, LG-TU915 Obigo, LGE VX, webOS, Nokia5800';
676
677 /**
678 * Filters supported mobile browsers.
679 *
680 * @hook powered_cache_mobile_browsers
681 *
682 * @param {string} $mobile_browsers Comma separated list of the defined mobile browsers.
683 *
684 * @since 1.0
685 */
686 return apply_filters( 'powered_cache_mobile_browsers', $mobile_browsers );
687 }
688
689 /**
690 * Supported mobile prefixes
691 *
692 * @return mixed|void
693 * @since 1.0
694 */
695 function mobile_prefixes() {
696 $mobile_prefixes
697 = 'w3c , w3c-, acs-, alav, alca, amoi, audi, avan, benq, bird, blac, blaz, brew, cell, cldc, cmd-, dang, doco, eric, hipt, htc_, inno, ipaq, ipod, jigs, kddi, keji, leno, lg-c, lg-d, lg-g, lge-, lg/u, maui, maxo, midp, mits, mmef, mobi, mot-, moto, mwbp, nec-, newt, noki, palm, pana, pant, phil, play, port, prox, qwap, sage, sams, sany, sch-, sec-, send, seri, sgh-, shar, sie-, siem, smal, smar, sony, sph-, symb, t-mo, teli, tim-, tosh, tsm-, upg1, upsi, vk-v, voda, wap-, wapa, wapi, wapp, wapr, webc, winw, winw, xda , xda-';
698
699 /**
700 * Filters supported mobile prefixes.
701 *
702 * @hook powered_cache_mobile_prefixes
703 *
704 * @param {string} $mobile_prefixes Comma separated list of the defined mobile prefixes.
705 *
706 * @since 1.0
707 */
708 return apply_filters( 'powered_cache_mobile_prefixes', $mobile_prefixes );
709 }
710
711
712 /**
713 * Collect post related urls
714 *
715 * @param int $post_id Post ID
716 *
717 * @return array
718 * @since 1.0
719 * @since 1.1 powered_cache_post_related_urls filter added
720 */
721 function get_post_related_urls( $post_id ) {
722 // Valid post statuses that require cache purging.
723 $valid_post_statuses = [ 'publish', 'private', 'trash', 'pending', 'draft' ];
724 $post_status = get_post_status( $post_id );
725 $post = get_post( $post_id );
726
727 // Post types that should not have their cache purged.
728 $excluded_post_types = [ 'nav_menu_item', 'revision' ];
729 $post_type = get_post_type( $post_id );
730 $rest_api_route = 'wp/v2';
731
732 $related_urls = [];
733
734 if ( false !== get_permalink( $post_id ) && in_array( $post_status, $valid_post_statuses, true ) && ! in_array( $post_type, $excluded_post_types, true ) ) {
735 // Add the post URL.
736 $related_urls[] = get_permalink( $post_id );
737
738 // Add REST API URL if applicable.
739 if ( $rest_api_route ) {
740 $post_type_object = get_post_type_object( $post_type );
741 if ( ! empty( $post_type_object->show_in_rest ) ) {
742 $post_type_base = $post_type_object->rest_base ? $post_type_object->rest_base : $post_type_object->name;
743 $related_urls[] = get_rest_url() . $rest_api_route . '/' . $post_type_base . '/' . $post_id . '/';
744 } elseif ( in_array( $post_type, [ 'post', 'page' ], true ) ) {
745 $related_urls[] = get_rest_url() . $rest_api_route . '/' . $post_type . 's/' . $post_id . '/';
746 }
747 }
748
749 // Add AMP URL if AMP plugin is active.
750 if ( function_exists( 'amp_get_permalink' ) ) {
751 $related_urls[] = amp_get_permalink( $post_id );
752 }
753
754 // Regular AMP url for posts if ant of the following are active:
755 // https://wordpress.org/plugins/accelerated-mobile-pages/
756 if ( defined( 'AMPFORWP_AMP_QUERY_VAR' ) ) {
757 $related_urls[] = get_permalink( $post_id ) . 'amp/';
758 }
759
760 // Handle trashed post URLs.
761 if ( 'trash' === $post_status ) {
762 $trash_permalink = str_replace( '__trashed', '', get_permalink( $post_id ) );
763 $related_urls[] = $trash_permalink;
764 $related_urls[] = $trash_permalink . 'feed/';
765 }
766
767 $taxonomies = get_object_taxonomies( get_post_type( $post_id ), 'objects' );
768
769 // Purge terms associated with the post.
770 foreach ( $taxonomies as $taxonomy ) {
771 // Skip non-public taxonomies.
772 if ( ! $taxonomy->public ) {
773 continue;
774 }
775
776 $terms = get_the_terms( $post_id, $taxonomy->name );
777
778 if ( empty( $terms ) || is_wp_error( $terms ) ) {
779 continue;
780 }
781
782 foreach ( $terms as $term ) {
783 $term_url = get_term_link( $term->slug, $taxonomy->name );
784 if ( ! is_wp_error( $term_url ) ) {
785 $related_urls[] = $term_url;
786 if ( $taxonomy->show_in_rest ) {
787 $taxonomy_base = $taxonomy->rest_base ? $taxonomy->rest_base : $taxonomy->name;
788 $related_urls[] = rest_url( "{$taxonomy->rest_namespace}/{$taxonomy_base}/{$term->term_id}/" ); // REST API URL for the term
789 }
790 }
791
792 if ( ! is_taxonomy_hierarchical( $taxonomy->name ) ) {
793 continue;
794 }
795
796 $ancestors = (array) get_ancestors( $term->term_id, $taxonomy->name );
797 foreach ( $ancestors as $ancestor ) {
798 $ancestor_object = get_term( $ancestor, $taxonomy->name );
799 if ( ! is_a( $ancestor, '\WP_Term' ) ) {
800 continue;
801 }
802
803 $ancestor_term_url = get_term_link( $ancestor_object->slug, $taxonomy->name );
804 if ( ! is_wp_error( $ancestor_term_url ) ) {
805 $related_urls[] = $ancestor_term_url;
806 if ( $taxonomy->show_in_rest ) {
807 $taxonomy_base = $taxonomy->rest_base ? $taxonomy->rest_base : $taxonomy->name;
808 $related_urls[] = rest_url( "{$taxonomy->rest_namespace}/{$taxonomy_base}/{$ancestor_object->term_id}/" ); // REST API URL for the ancestor term
809 }
810 }
811 }
812 }
813 }
814
815 // Purge author and feed URLs for posts.
816 if ( 'post' === $post_type ) {
817 $author_id = get_post_field( 'post_author', $post_id );
818 $related_urls[] = get_author_posts_url( $author_id );
819 $related_urls[] = get_author_feed_link( $author_id );
820 if ( $rest_api_route ) {
821 $related_urls[] = get_rest_url() . $rest_api_route . '/users/' . $author_id . '/';
822 }
823
824 // Include various feed URLs.
825 $feed_urls = [
826 get_bloginfo_rss( 'rdf_url' ),
827 get_bloginfo_rss( 'rss_url' ),
828 get_bloginfo_rss( 'rss2_url' ),
829 get_bloginfo_rss( 'atom_url' ),
830 get_bloginfo_rss( 'comments_rss2_url' ),
831 get_post_comments_feed_link( $post_id ),
832 ];
833 $related_urls = array_merge( $related_urls, $feed_urls );
834 }
835
836 // Purge archive pages if not excluded.
837 if ( ! in_array( $post_type, [ 'post', 'page' ], true ) ) {
838 $related_urls[] = get_post_type_archive_link( $post_type );
839 $related_urls[] = get_post_type_archive_feed_link( $post_type );
840 }
841
842 $post_date = strtotime( $post->post_date );
843
844 if ( $post_date ) {
845 // Generate the date archive URLs
846 $year = gmdate( 'Y', $post_date );
847 $month = gmdate( 'm', $post_date );
848 $day = gmdate( 'd', $post_date );
849
850 $related_urls[] = get_year_link( $year );
851 $related_urls[] = get_month_link( $year, $month );
852 $related_urls[] = get_day_link( $year, $month, $day );
853 }
854
855 // Always purge the home page.
856 $related_urls[] = home_url( '/' );
857
858 // Purge the posts page if it's set to a static page.
859 if ( 'page' === get_option( 'show_on_front' ) ) {
860 $posts_page_id = get_option( 'page_for_posts' );
861 if ( $posts_page_id ) {
862 $related_urls[] = get_permalink( $posts_page_id );
863 }
864 }
865 }
866
867 // Remove query strings and ensure unique URLs before purging.
868 $related_urls = array_unique(
869 array_map(
870 function ( $url ) {
871 return strtok( $url, '?' );
872 },
873 $related_urls
874 )
875 );
876
877 /**
878 * Filters post related urls.
879 *
880 * @hook powered_cache_post_related_urls
881 *
882 * @param {array} $related_urls The list of the URLs that related with the post.
883 *
884 * @return {array} New value.
885 * @since 1.0
886 */
887 $related_urls = apply_filters( 'powered_cache_post_related_urls', $related_urls );
888
889 return $related_urls;
890 }
891
892
893 /**
894 * Delete cache file
895 *
896 * @param string $url Target URL
897 * @param bool $delete_subdirectories Whether delete subdirectories or not
898 *
899 * @return bool true when found cache dir, otherwise false
900 * @since 1.0
901 */
902 function delete_page_cache( $url, $delete_subdirectories = false ) {
903 $dir = get_url_dir( trim( $url ) );
904
905 if ( is_dir( $dir ) ) {
906 $files = scandir( $dir );
907 foreach ( $files as $file ) {
908 /**
909 * Don't need to lookup for index-https, index-https-mobile etc..
910 * Just clean that directory's files only.
911 */
912 if ( ! is_dir( $dir . $file ) && file_exists( $dir . $file ) && ! in_array( $file, array( '.', '..' ), true ) ) {
913 unlink( $dir . $file );
914 }
915 }
916
917 if ( file_exists( $dir ) && ( $delete_subdirectories || is_dir_empty( $dir ) ) ) {
918 remove_dir( $dir );
919 }
920
921 return true;
922 }
923
924 return false;
925 }
926
927
928 /**
929 * Get cache location of given url
930 *
931 * @param string $url The url to retrieve path
932 *
933 * @return mixed|void
934 * @since 1.1
935 */
936 function get_url_dir( $url ) {
937 $url_info = wp_parse_url( $url );
938 $sub_dir = isset( $url_info['host'] ) ? $url_info['host'] : '';
939
940 if ( ! empty( $url_info['path'] ) ) {
941 $sub_dir .= $url_info['path'];
942 }
943
944 $path = trailingslashit( get_page_cache_dir() ) . ltrim( $sub_dir, '/' );
945 $path = trailingslashit( $path );
946
947 /**
948 * Filters the path of the given url in the cache directory.
949 *
950 * @hook powered_cache_get_url_dir
951 *
952 * @param {string} $path The cache directory of the given URL.
953 *
954 * @since 1.1
955 */
956 return apply_filters( 'powered_cache_get_url_dir', $path );
957 }
958
959 /**
960 * Prepare cdn addresses with hostname + zone
961 *
962 * @return mixed|void
963 * @since 1.0
964 */
965 function cdn_addresses() {
966 $settings = get_settings(); // phpcs:ignore WordPress.WP.DeprecatedFunctions.get_settingsFound
967
968 $hostnames = $settings['cdn_hostname'];
969 $zones = $settings['cdn_zone'];
970
971 $cdn_addresses = array();
972 foreach ( $hostnames as $host_key => $host ) {
973 if ( filter_var( $host, FILTER_VALIDATE_URL ) ) {
974 $host = wp_parse_url( $host, PHP_URL_HOST );
975 }
976
977 if ( ! empty( $host ) ) {
978 $cdn_addresses[ $zones[ $host_key ] ][] = $host;
979 }
980 }
981
982 /**
983 * Filters CDN Addresses.
984 *
985 * @hook powered_cache_cdn_addresses
986 *
987 * @param {array} $cdn_addresses CDN Addresses.
988 *
989 * @return {array} New value.
990 * @since 1.0
991 */
992 return apply_filters( 'powered_cache_cdn_addresses', $cdn_addresses );
993 }
994
995
996 /**
997 * Get list of expired files for given directory
998 *
999 * @param string $path directory location
1000 * @param int $lifespan lifespan in seconds
1001 *
1002 * @return array expired file list
1003 * @since 1.1
1004 */
1005 function get_expired_files( $path, $lifespan = 0 ) {
1006
1007 $current_time = time();
1008
1009 $expired_files = array();
1010
1011 // return immediately if the path is not exist!
1012 if ( ! file_exists( $path ) ) {
1013 return $expired_files;
1014 }
1015
1016 $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $path ) );
1017
1018 foreach ( $files as $file ) {
1019
1020 if ( $file->isDir() ) {
1021 continue;
1022 }
1023
1024 $path = $file->getPathname();
1025
1026 if ( @filemtime( $path ) + $lifespan <= $current_time ) { // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
1027 $expired_files[] = $path;
1028 }
1029 }
1030
1031 return $expired_files;
1032 }
1033
1034
1035 /**
1036 * Flush object cache and clean cache directory
1037 *
1038 * @since 1.0
1039 */
1040 function powered_cache_flush() {
1041 if ( function_exists( 'wp_cache_flush' ) ) {
1042 wp_cache_flush();
1043 }
1044
1045 remove_dir( get_cache_dir() );
1046
1047 /**
1048 * Fires after cache flush.
1049 *
1050 * @hook powered_cache_flushed
1051 * @since 1.0
1052 */
1053 do_action( 'powered_cache_flushed' );
1054 }
1055
1056 /**
1057 * Log to stdout or a file
1058 *
1059 * @param string $message Log message
1060 *
1061 * @return bool
1062 */
1063 function log( $message ) {
1064 if ( ! defined( 'POWERED_CACHE_ENABLE_LOG' ) ) {
1065 return false;
1066 }
1067
1068 if ( ! POWERED_CACHE_ENABLE_LOG ) {
1069 return false;
1070 }
1071
1072 $log_message = gmdate( 'H:i:s' ) . ' ' . getmypid() . ' ' . get_client_ip() . " {$message}" . PHP_EOL;
1073
1074 /**
1075 * Filters log message.
1076 *
1077 * @hook powered_cache_log_message
1078 *
1079 * @param {string} $log_message The log message.
1080 *
1081 * @return {string} New value.
1082 * @since 2.0
1083 */
1084 $log_message = apply_filters( 'powered_cache_log_message', $log_message );
1085
1086 /**
1087 * Filters log message type.
1088 *
1089 * @hook powered_cache_log_message_type
1090 *
1091 * @param {int} 0 default message type since 3.6
1092 *
1093 * @return {int} New value.
1094 * @since 2.0
1095 */
1096 $message_type = apply_filters( 'powered_cache_log_message_type', 0 );
1097 $destination = null;
1098
1099 if ( defined( 'POWERED_CACHE_LOG_FILE' ) ) {
1100 $destination = POWERED_CACHE_LOG_FILE;
1101 $message_type = 3;
1102 }
1103
1104 /**
1105 * Filters destination of the log.
1106 *
1107 * @hook powered_cache_log_destination
1108 *
1109 * @param {null|string} $destination The destination of the log.
1110 *
1111 * @return {null|string} New value.
1112 * @since 2.0
1113 */
1114 $log_destination = apply_filters( 'powered_cache_log_destination', $destination );
1115
1116 // don't log anything when it used for particular IP address
1117 if ( defined( 'POWERED_CACHE_LOG_IP' ) && POWERED_CACHE_LOG_IP !== get_client_ip() ) {
1118 return false;
1119 }
1120
1121 $message_type = absint( $message_type );
1122
1123 return error_log( $log_message, $message_type, $log_destination ); // phpcs:ignore
1124 }
1125
1126 /**
1127 * Get client raw ip
1128 *
1129 * @return mixed
1130 */
1131 function get_client_ip() {
1132 if ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1133 return wp_unslash( $_SERVER['HTTP_X_FORWARDED_FOR'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1134 }
1135
1136 if ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
1137 return wp_unslash( $_SERVER['REMOTE_ADDR'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1138 }
1139 }
1140
1141 /**
1142 * Fragment caching
1143 *
1144 * @link https://gist.github.com/markjaquith/2653957
1145 * @see https://gist.github.com/westonruter/5475349
1146 *
1147 * @param string $key Fragment key
1148 * @param int $ttl Cache TTL
1149 * @param callable $function callback
1150 *
1151 * @throws \Exception Exception
1152 * @since 1.2
1153 * @since 2.0 Renamed powered_cache_fragment -> \PoweredCache\Utils\cache_fragment
1154 */
1155 function cache_fragment( $key, $ttl, $function ) {
1156 $group = 'powered-fragments';
1157 $output = wp_cache_get( $key, $group );
1158 if ( empty( $output ) ) {
1159 ob_start();
1160 call_user_func( $function );
1161 $output = ob_get_clean();
1162 wp_cache_add( $key, $output, $group, $ttl );
1163 }
1164 echo $output; // phpcs:ignore
1165 }
1166
1167 /**
1168 * Fetches known headers, ported from WP Super Cache but not using apache_response_headers
1169 *
1170 * @return array|false
1171 * @since 1.2
1172 */
1173 function get_response_headers() {
1174 static $known_headers = array(
1175 'Access-Control-Allow-Origin',
1176 'Accept-Ranges',
1177 'Age',
1178 'Allow',
1179 'Cache-Control',
1180 'Connection',
1181 'Content-Encoding',
1182 'Content-Language',
1183 'Content-Length',
1184 'Content-Location',
1185 'Content-MD5',
1186 'Content-Disposition',
1187 'Content-Range',
1188 'Content-Type',
1189 'Date',
1190 'ETag',
1191 'Expires',
1192 'Last-Modified',
1193 'Link',
1194 'Location',
1195 'P3P',
1196 'Pragma',
1197 'Proxy-Authenticate',
1198 'Referrer-Policy',
1199 'Refresh',
1200 'Retry-After',
1201 'Server',
1202 'Status',
1203 'Strict-Transport-Security',
1204 'Trailer',
1205 'Transfer-Encoding',
1206 'Upgrade',
1207 'Vary',
1208 'Via',
1209 'Warning',
1210 'WWW-Authenticate',
1211 'X-Frame-Options',
1212 'Public-Key-Pins',
1213 'X-XSS-Protection',
1214 'Content-Security-Policy',
1215 'X-Pingback',
1216 'X-Content-Security-Policy',
1217 'X-WebKit-CSP',
1218 'X-Content-Type-Options',
1219 'X-Powered-By',
1220 'X-UA-Compatible',
1221 'X-Robots-Tag',
1222 );
1223
1224 /**
1225 * Filters known headers.
1226 *
1227 * @hook powered_cache_known_headers
1228 *
1229 * @param {array} $known_headers The list of known HTTP headers.
1230 *
1231 * @return {array} New value.
1232 * @since 1.2
1233 */
1234 $known_headers = apply_filters( 'powered_cache_known_headers', $known_headers );
1235
1236 if ( ! isset( $known_headers['age'] ) ) {
1237 $known_headers = array_map( 'strtolower', $known_headers );
1238 }
1239
1240 $headers = array();
1241
1242 if ( function_exists( 'headers_list' ) ) {
1243 $headers = array();
1244 foreach ( headers_list() as $hdr ) {
1245 $header_parts = explode( ':', $hdr, 2 );
1246 $header_name = isset( $header_parts[0] ) ? trim( $header_parts[0] ) : '';
1247 $header_value = isset( $header_parts[1] ) ? trim( $header_parts[1] ) : '';
1248
1249 $headers[ $header_name ] = $header_value;
1250 }
1251 }
1252
1253 foreach ( $headers as $key => $value ) {
1254 if ( ! in_array( strtolower( $key ), $known_headers, true ) ) {
1255 unset( $headers[ $key ] );
1256 }
1257 }
1258
1259 return $headers;
1260 }
1261
1262
1263 /**
1264 * Check if the given url exists in the cache
1265 *
1266 * @param string $url URL
1267 * @param bool $is_mobile Check mobile cache
1268 * @param bool $is_gzip check gzipped cache
1269 *
1270 * @return bool
1271 */
1272 function is_url_cached( $url, $is_mobile = false, $is_gzip = false ) {
1273 $file_name = 'index';
1274 $url_parts = wp_parse_url( $url );
1275
1276 if ( 'https' === $url_parts['scheme'] ) {
1277 $file_name .= '-https';
1278 }
1279
1280 if ( $is_mobile ) {
1281 $file_name .= '-mobile';
1282 }
1283
1284 $file_name .= '.html';
1285
1286 if ( $is_gzip ) {
1287 $file_name .= '.gz';
1288 }
1289
1290 $rel_path = $url_parts['host'];
1291 if ( ! empty( $url_parts['path'] ) ) {
1292 $rel_path .= $url_parts['path'];
1293 }
1294
1295 $path = trailingslashit( get_page_cache_dir() . $rel_path );
1296
1297 $cache_file = $path . $file_name;
1298
1299 return file_exists( $cache_file );
1300 }
1301
1302 /**
1303 * Check if the permalink structure of the site end with trailingslash
1304 *
1305 * @return bool
1306 * @since 2.0
1307 */
1308 function permalink_structure_has_trailingslash() {
1309 if ( '/' === substr( get_option( 'permalink_structure' ), - 1 ) ) {
1310 return true;
1311 }
1312
1313 return false;
1314 }
1315
1316 /**
1317 * Check if the given directory empty
1318 *
1319 * @param string $dir Path
1320 *
1321 * @return bool
1322 */
1323 function is_dir_empty( $dir ) {
1324 foreach ( new \DirectoryIterator( $dir ) as $file_info ) {
1325 if ( $file_info->isDot() ) {
1326 continue;
1327 }
1328
1329 return false;
1330 }
1331
1332 return true;
1333 }
1334
1335 /**
1336 * Get the documentation url
1337 *
1338 * @param string $path The path of documentation
1339 * @param string $fragment URL Fragment
1340 *
1341 * @return string final URL
1342 */
1343 function get_doc_url( $path = null, $fragment = '' ) {
1344 $doc_site = 'https://docs.poweredcache.com/';
1345 $utm_parameters = '?utm_source=wp_admin&utm_medium=plugin&utm_campaign=settings_page';
1346
1347 if ( ! empty( $path ) ) {
1348 $doc_site .= ltrim( $path, '/' );
1349 }
1350
1351 $doc_url = trailingslashit( $doc_site ) . $utm_parameters;
1352
1353 if ( ! empty( $fragment ) ) {
1354 $doc_url .= '#' . $fragment;
1355 }
1356
1357 return $doc_url;
1358 }
1359
1360 /**
1361 * Sanitize CSS
1362 *
1363 * @param string $css Input
1364 *
1365 * @return string|string[] $css
1366 * @since 2.1
1367 */
1368 function sanitize_css( $css ) {
1369 $css = wp_strip_all_tags( $css );
1370
1371 if ( false !== strpos( $css, '<' ) ) {
1372 $css = preg_replace( '#<(\/?\w+)#', '\00003C$1', $css );
1373 }
1374
1375 return $css;
1376 }
1377
1378
1379 /**
1380 * Test if the current browser runs on a mobile device (smart phone, tablet, etc.)
1381 * Sort of custom version of wp_is_mobile
1382 */
1383 function powered_cache_is_mobile() {
1384
1385 global $powered_cache_mobile_browsers, $powered_cache_mobile_prefixes;
1386
1387 $mobile_browsers = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', (string) $powered_cache_mobile_browsers ) ), ' ' );
1388 $mobile_prefixes = addcslashes( implode( '|', preg_split( '/[\s*,\s*]*,+[\s*,\s*]*/', (string) $powered_cache_mobile_prefixes ) ), ' ' );
1389
1390 if ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
1391 return false;
1392 }
1393
1394 $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
1395
1396 if ( ( preg_match( '#^.*(' . $mobile_browsers . ').*#i', $user_agent ) || preg_match( '#^(' . $mobile_prefixes . ').*#i', substr( $user_agent, 0, 4 ) ) ) ) {
1397 return true;
1398 }
1399
1400 return false;
1401 }
1402
1403 /**
1404 * If the site is a local site.
1405 *
1406 * @return bool
1407 * @since 2.2
1408 */
1409 function is_local_site() {
1410 $site_url = site_url();
1411
1412 // Check for localhost and sites using an IP only first.
1413 $is_local = $site_url && false === strpos( $site_url, '.' );
1414
1415 // Use Core's environment check, if available. Added in 5.5.0 / 5.5.1 (for `local` return value).
1416 if ( function_exists( 'wp_get_environment_type' ) && 'local' === wp_get_environment_type() ) {
1417 $is_local = true;
1418 }
1419
1420 // Then check for usual usual domains used by local dev tools.
1421 $known_local = array(
1422 '#\.local$#i',
1423 '#\.localhost$#i',
1424 '#\.test$#i',
1425 '#\.docksal$#i', // Docksal.
1426 '#\.docksal\.site$#i', // Docksal.
1427 '#\.dev\.cc$#i', // ServerPress.
1428 '#\.lndo\.site$#i', // Lando.
1429 );
1430
1431 if ( ! $is_local ) {
1432 foreach ( $known_local as $url ) {
1433 if ( preg_match( $url, $site_url ) ) {
1434 $is_local = true;
1435 break;
1436 }
1437 }
1438 }
1439
1440 /**
1441 * Filters is_local_site check.
1442 *
1443 * @param bool $is_local If the current site is a local site.
1444 *
1445 * @since 2.2
1446 */
1447 $is_local = apply_filters( 'powered_cache_is_local_site', $is_local );
1448
1449 return $is_local;
1450 }
1451
1452 /**
1453 * Check whether request for bypass or process normally
1454 *
1455 * @return bool
1456 * @since 3.0
1457 */
1458 function bypass_request() {
1459 if ( isset( $_GET['nopoweredcache'] ) && $_GET['nopoweredcache'] ) { // phpcs:ignore
1460 return true;
1461 }
1462
1463 return false;
1464 }
1465
1466
1467 /**
1468 * Calculate total amount of autoloaded data.
1469 *
1470 * @return int autoloaded data in bytes.
1471 * @global wpdb $wpdb WordPress database abstraction object.
1472 * @since 3.4
1473 */
1474 function autoloaded_options_size() {
1475 global $wpdb;
1476
1477 return (int) $wpdb->get_var( 'SELECT SUM(LENGTH(option_value)) FROM ' . $wpdb->prefix . 'options WHERE autoload = \'yes\'' ); // phpcs:ignore
1478 }
1479
1480 /**
1481 * Mask the string with asterisk
1482 *
1483 * @param string $input_string String
1484 * @param int $unmask_length The length of the string that will not be masked
1485 *
1486 * @return string
1487 * @since 3.4
1488 */
1489 function mask_string( $input_string, $unmask_length ) {
1490 $output_string = substr( $input_string, 0, $unmask_length );
1491
1492 if ( strlen( $input_string ) > $unmask_length ) {
1493 $output_string .= str_repeat( '*', strlen( $input_string ) - $unmask_length );
1494 }
1495
1496 return $output_string;
1497 }
1498
1499 /**
1500 * Get sensitive data in decrypted form
1501 *
1502 * @param string $field field name
1503 *
1504 * @return bool|mixed|string
1505 */
1506 function get_decrypted_setting( $field ) {
1507 $settings = \PoweredCache\Utils\get_settings();
1508 $value = isset( $settings[ $field ] ) ? $settings[ $field ] : '';
1509
1510 // decrypt the value
1511 $encryption = new Encryption();
1512 $decrypted_value = $encryption->decrypt( $value );
1513 if ( false !== $decrypted_value ) {
1514 return $decrypted_value;
1515 }
1516
1517 return $value;
1518 }
1519
1520
1521 /**
1522 * Check if a given IP is within a specific range.
1523 * Supports both IPv4 and IPv6 addresses.
1524 *
1525 * @param string $ip The IP address to check.
1526 * @param string $range The IP range in CIDR notation.
1527 *
1528 * @return bool True if the IP is in the range, false otherwise.
1529 */
1530 function is_ip_in_range( $ip, $range ) {
1531 if ( false !== strpos( $range, '/' ) ) {
1532 list( $subnet, $bits ) = explode( '/', $range, 2 );
1533 } else {
1534 $subnet = $range;
1535 $bits = ( false === filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) ? 32 : 128;
1536 }
1537
1538 $bits = intval( $bits );
1539
1540 if ( false !== filter_var( $subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
1541 $subnet_bin = inet_pton( $subnet );
1542 $ip_bin = inet_pton( $ip );
1543
1544 if ( false === $subnet_bin || false === $ip_bin ) {
1545 return false;
1546 }
1547
1548 $subnet_bin = str_pad( $subnet_bin, 16, "\0" );
1549 $ip_bin = str_pad( $ip_bin, 16, "\0" );
1550
1551 for ( $i = 0; $i * 8 < $bits; $i ++ ) {
1552 if ( $bits >= ( $i + 1 ) * 8 && $subnet_bin[ $i ] !== $ip_bin[ $i ] ) {
1553 return false;
1554 } elseif ( $bits > $i * 8 ) {
1555 $bitmask = 0xff00 >> ( $bits % 8 );
1556 if ( ( ord( $subnet_bin[ $i ] ) & $bitmask ) !== ( ord( $ip_bin[ $i ] ) & $bitmask ) ) {
1557 return false;
1558 }
1559 }
1560 }
1561
1562 return true;
1563 }
1564
1565 if ( false === filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) ) {
1566 return false;
1567 }
1568
1569 $subnet_decimal = ip2long( $subnet );
1570 $ip_decimal = ip2long( $ip );
1571 $mask_decimal = - 1 << ( 32 - $bits );
1572
1573 return ( $subnet_decimal & $mask_decimal ) === ( $ip_decimal & $mask_decimal );
1574 }
1575
1576 /**
1577 * Check if the dev mode is active
1578 *
1579 * @return bool
1580 * @since 3.6
1581 */
1582 function is_dev_mode_active() {
1583 // Check global config first (fast)
1584 if ( ! empty( $GLOBALS['powered_cache_options']['dev_mode'] ) ) {
1585 return true;
1586 }
1587
1588 // Fallback to database option
1589 $settings = \PoweredCache\Utils\get_settings();
1590
1591 return ! empty( $settings['dev_mode'] );
1592 }
1593