PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / 3.6
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score v3.6
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 / classes / Preloader.php

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

754 lines 19.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Cache Preloading functionalities
4 *
5 * @package PoweredCache
6 */
7
8 namespace PoweredCache;
9
10 use PoweredCache\Async\CachePreloader;
11 use function PoweredCache\Utils\permalink_structure_has_trailingslash;
12 use function PoweredCache\Utils\site_cache_dir;
13 use const PoweredCache\Constants\DEFERRED_PRELOAD_QUEUE_CRON_NAME;
14
15 /**
16 * Class Preloader
17 */
18 class Preloader {
19 /**
20 * Plugin settings
21 *
22 * @var $settings
23 */
24 private $settings;
25
26 /**
27 * Instance of CachePreloader
28 *
29 * @var CachePreloader
30 */
31 private $cache_preloader;
32
33 /**
34 * Tracks whether we’ve added any URLs this request
35 *
36 * @var $queue_dirty
37 */
38 private $queue_dirty = false;
39
40 /**
41 * Return an instance of the current class
42 *
43 * @return Preloader
44 * @since 1.0
45 */
46 public static function factory() {
47
48 static $instance;
49
50 if ( ! $instance ) {
51 $instance = new self();
52 $instance->setup();
53 }
54
55 return $instance;
56 }
57
58 /**
59 * Setup routine
60 */
61 public function setup() {
62 $this->settings = \PoweredCache\Utils\get_settings();
63 add_filter( 'wp_resource_hints', [ $this, 'dns_prefetch' ], 10, 2 );
64 add_filter( 'wp_resource_hints', [ $this, 'preconnect_resources' ], 10, 2 );
65
66 // bail if the preload not activated
67 if ( ! $this->settings['enable_cache_preload'] ) {
68 return;
69 }
70
71 $this->get_preloader();
72
73 /**
74 * Page cache needs to be activated to get benefits of preloading
75 */
76 if ( ! $this->settings['enable_page_cache'] ) {
77 return;
78 }
79
80 add_action( 'admin_bar_menu', [ $this, 'admin_bar_menu' ] );
81 add_action( 'admin_post_powered_cache_preload_cache', [ $this, 'start_preload' ] );
82 add_action( 'powered_cache_purge_all_cache', [ $this, 'setup_preload_queue' ] );
83 add_action( 'powered_cache_clean_site_cache_dir', [ $this, 'setup_preload_queue' ] );
84 add_action( 'powered_cache_advanced_cache_purge_post', [ $this, 'deferred_preload_queue' ], 10, 2 );
85 add_action( 'powered_cache_expired_files_deleted', [ $this, 'add_expired_urls_to_preload_queue' ], 10, 2 );
86 add_action( DEFERRED_PRELOAD_QUEUE_CRON_NAME, [ $this, 'add_purged_urls_to_preload_queue' ], 10, 2 );
87 add_action( 'shutdown', [ $this, 'dispatch_preload_queue' ], 0 );
88 }
89
90 /**
91 * Preload Admin bar menu
92 *
93 * @param object $wp_admin_bar Admin bar object
94 *
95 * @since 1.0
96 */
97 public function admin_bar_menu( $wp_admin_bar ) {
98 if ( POWERED_CACHE_IS_NETWORK && ! current_user_can( 'manage_network' ) ) {
99 return;
100 }
101
102 if ( ! current_user_can( 'manage_options' ) ) {
103 return;
104 }
105
106 $wp_admin_bar->add_menu(
107 array(
108 'id' => 'preload-cache',
109 'title' => __( 'Preload Cache', 'powered-cache' ),
110 'href' => wp_nonce_url( admin_url( 'admin-post.php?action=powered_cache_preload_cache' ), 'powered_cache_preload_cache' ),
111 'parent' => 'powered-cache',
112 )
113 );
114 }
115
116
117 /**
118 * Get the instance of CachePreloader
119 *
120 * @return CachePreloader
121 */
122 private function get_preloader() {
123 if ( ! $this->cache_preloader ) {
124 $this->cache_preloader = CachePreloader::factory();
125 }
126
127 return $this->cache_preloader;
128 }
129
130 /**
131 * Add preloading items to queue
132 */
133 public function start_preload() {
134 if ( ! wp_verify_nonce( $_GET['_wpnonce'], 'powered_cache_preload_cache' ) ) { // phpcs:ignore
135 wp_nonce_ays( '' );
136 }
137
138 if ( POWERED_CACHE_IS_NETWORK && ! current_user_can( 'manage_network' ) ) {
139 $redirect_url = add_query_arg( 'pc_action', 'start_preload_err_permission', wp_get_referer() );
140 wp_safe_redirect( esc_url_raw( $redirect_url ) );
141 exit;
142 }
143
144 if ( ! current_user_can( 'manage_options' ) ) {
145 $redirect_url = add_query_arg( 'pc_action', 'start_preload_err_permission', wp_get_referer() );
146 wp_safe_redirect( esc_url_raw( $redirect_url ) );
147 exit;
148 }
149
150 \PoweredCache\Utils\log( sprintf( 'Preload triggered from admin bar.' ) );
151
152 $this->setup_preload_queue();
153
154 $redirect_url = add_query_arg( 'pc_action', 'start_preload', wp_get_referer() );
155 wp_safe_redirect( esc_url_raw( $redirect_url ) );
156 exit;
157 }
158
159 /**
160 * Setup preload
161 */
162 public function setup_preload_queue() {
163 // cancel existing process before populating
164 $this->get_preloader()->cancel_process();
165
166 if ( POWERED_CACHE_IS_NETWORK ) {
167 $sites = get_sites( [ 'fields' => 'ids' ] );
168 foreach ( $sites as $site_id ) {
169 switch_to_blog( $site_id );
170 \PoweredCache\Utils\log( sprintf( 'Populating preload queue for site: %d', $site_id ) );
171 $this->populate_preload_queue();
172 restore_current_blog();
173 }
174 } else {
175 $this->populate_preload_queue();
176 }
177
178 if ( $this->settings['enable_sitemap_preload'] && function_exists( '\PoweredCachePremium\Utils\preload_sitemap' ) ) {
179 \PoweredCachePremium\Utils\preload_sitemap();
180 }
181 }
182
183 /**
184 * Populate preload queue based on settings
185 */
186 protected function populate_preload_queue() {
187 $preload_urls = [];
188
189 if ( $this->settings['preload_homepage'] ) {
190 $front_page = get_option( 'page_on_front' );
191 if ( ! empty( $front_page ) ) {
192 $front_page_url = get_permalink( $front_page );
193 $preload_urls[] = $front_page_url;
194 \PoweredCache\Utils\log( sprintf( 'Front page URL added to preload queue: %s', $front_page_url ) );
195 }
196
197 $posts_page = get_option( 'page_for_posts' );
198 if ( ! empty( $posts_page ) ) {
199 $posts_page_url = get_permalink( $posts_page );
200 $preload_urls[] = $posts_page_url;
201 \PoweredCache\Utils\log( sprintf( 'Posts Page URL added to preload queue: %s', $posts_page_url ) );
202 }
203
204 /**
205 * trailingslashit important here,
206 * likely redirection is not followed for non-blocking preload request
207 */
208 $home_url = trailingslashit( get_home_url() );
209 $preload_urls[] = $home_url;
210 \PoweredCache\Utils\log( sprintf( 'Home URL added to preload queue: %s', $home_url ) );
211 }
212
213 if ( $this->settings['preload_public_posts'] ) {
214 $public_post_urls = $this->prepare_public_posts_urls();
215 $preload_urls = array_merge( $preload_urls, $public_post_urls );
216 \PoweredCache\Utils\log( sprintf( 'Public posts added to preload queue. ' ) );
217 }
218
219 if ( $this->settings['preload_public_tax'] ) {
220 $public_tax_term_urls = $this->prepare_public_tax_terms_urls();
221 $preload_urls = array_merge( $preload_urls, $public_tax_term_urls );
222 \PoweredCache\Utils\log( sprintf( 'Public tax terms added to preload queue. ' ) );
223 }
224
225 /**
226 * Filters preload urls before sending to queue
227 *
228 * @hook populate_preload_queue_urls
229 *
230 * @param {array} $preload_urls The list of preload urls
231 *
232 * @return {array} New value.
233 * @since 2.4
234 */
235 $preload_urls = apply_filters( 'populate_preload_queue_urls', $preload_urls );
236
237 foreach ( $preload_urls as $url ) {
238 $this->add_url_to_preload_queue( $url );
239 }
240 }
241
242 /**
243 * Add URLs to preload queue with a delay
244 *
245 * @param int $post_id Post ID
246 * @param array $urls The URL list of the related pages that will be preloaded
247 *
248 * @since 3.6
249 */
250 public function deferred_preload_queue( $post_id, $urls ) {
251 \PoweredCache\Utils\log( sprintf( 'Post ID %d purged from cache, adding related URLs to preload queue with a delay.', $post_id ) );
252 wp_schedule_single_event(
253 time() + 10,
254 DEFERRED_PRELOAD_QUEUE_CRON_NAME,
255 [
256 'post_id' => $post_id,
257 'urls' => $urls,
258 ]
259 );
260 }
261
262
263 /**
264 * Add related pages to preload queue when the cache got cleared
265 *
266 * @param int $post_id Post ID
267 * @param array $urls The URL list of the related pages that cleared during post update
268 *
269 * @since 2.0
270 */
271 public function add_purged_urls_to_preload_queue( $post_id, $urls ) {
272 $post_url = get_permalink( $post_id );
273
274 // include post itself all the time
275 if ( ! empty( $post_url ) ) {
276 $this->add_url_to_preload_queue( $post_url );
277 }
278
279 if ( ! $urls ) {
280 return;
281 }
282
283 foreach ( $urls as $url ) {
284 $this->add_url_to_preload_queue( $url );
285 }
286
287 }
288
289 /**
290 * Requeue deleted URLs
291 *
292 * @param array $expired_files Full path of cached item
293 */
294 public function add_expired_urls_to_preload_queue( $expired_files ) {
295
296 /**
297 * Filters expired urls preloading status
298 *
299 * @hook powered_cache_preload_expired_urls
300 *
301 * @param {boolean} $status True by default for preloading urls.
302 *
303 * @return {boolean} New value.
304 * @since 2.0
305 */
306 $preload_expired_urls = apply_filters( 'powered_cache_preload_expired_urls', true );
307
308 if ( true !== $preload_expired_urls ) {
309 return;
310 }
311
312 /**
313 * Get dir path without cache file name (eg index.html)
314 */
315 $expired_files = array_map(
316 function ( $item ) {
317 return dirname( $item );
318 },
319 $expired_files
320 );
321
322 $expired_files = array_unique( $expired_files ); // prevent double request due to meta.php + index.php file
323
324 // replace path with site url
325 $expired_urls = str_replace( site_cache_dir(), trailingslashit( get_site_url() ), $expired_files );
326 $has_trailingslash = permalink_structure_has_trailingslash();
327
328 foreach ( $expired_urls as $url ) {
329 if ( $has_trailingslash ) {
330 $url = trailingslashit( $url );
331 }
332 $this->add_url_to_preload_queue( $url );
333 }
334
335 }
336
337 /**
338 * Prep. public post urls for preload queue
339 */
340 public function prepare_public_posts_urls() {
341 global $wpdb;
342
343 $public_posts_url = [];
344 /**
345 * Filters posts offset for preload.
346 *
347 * @hook powered_cache_preload_public_posts_offset
348 *
349 * @param {int} $offset The offset of the post query.
350 *
351 * @return {int} New value.
352 * @since 2.0
353 */
354 $offset = (int) apply_filters( 'powered_cache_preload_public_posts_offset', 0 );
355 $max_preload_item = $this->preload_max_post_count();
356
357 \PoweredCache\Utils\log( sprintf( 'Adding public posts to queue' ) );
358 \PoweredCache\Utils\log( sprintf( 'OFFSET: %s', $offset ) );
359 \PoweredCache\Utils\log( sprintf( 'LIMIT: %s', $max_preload_item ) );
360
361 /**
362 * Filters public post types.
363 *
364 * @hook powered_cache_preload_post_types
365 *
366 * @param {array} $types Public post types.
367 *
368 * @return {array} New value.
369 * @since 1.0
370 */
371 $types = apply_filters(
372 'powered_cache_preload_post_types',
373 get_post_types(
374 array(
375 'public' => true,
376 'publicly_queryable' => true,
377 ),
378 'names',
379 'or'
380 )
381 );
382
383 $types = array_map( 'esc_sql', $types );
384 $types = "'" . implode( "','", $types ) . "'";
385 $posts = $wpdb->get_col( "SELECT ID FROM {$wpdb->posts} WHERE ( post_type IN ( $types ) ) AND post_status = 'publish' ORDER BY ID ASC LIMIT {$offset},{$max_preload_item}" ); // phpcs:ignore
386
387 \PoweredCache\Utils\log( sprintf( 'Found posts: %s', count( $posts ) ) );
388
389 foreach ( $posts as $post_id ) {
390 $public_posts_url[] = get_permalink( $post_id );
391 }
392
393 return $public_posts_url;
394 }
395
396 /**
397 * Prep. public tax terms urls for preload queue
398 */
399 public function prepare_public_tax_terms_urls() {
400 $public_tax_term_urls = [];
401 $taxonomies = get_taxonomies( [ 'public' => true ] );
402
403 /**
404 * Filters public taxonomies.
405 *
406 * @hook powered_cache_preload_taxonomies
407 *
408 * @param {array} $taxonomies Public taxonomies.
409 *
410 * @return {array} New value.
411 * @since 1.0
412 */
413 $taxonomies = apply_filters( 'powered_cache_preload_taxonomies', $taxonomies );
414
415 foreach ( $taxonomies as $tax ) {
416 \PoweredCache\Utils\log( sprintf( 'Taxonomy added to queue: %s', $tax ) );
417
418 /**
419 * Filters taxonomy offset for preload.
420 *
421 * @hook powered_cache_preload_public_taxonomies_offset
422 *
423 * @param {int} $offset Public taxonomies.
424 *
425 * @return {int} New value.
426 * @since 2.0
427 */
428 $offset = (int) apply_filters( 'powered_cache_preload_public_taxonomies_offset', 0 );
429 $limit = $this->preload_max_term_count();
430
431 /**
432 * Filters term query args.
433 *
434 * @hook powered_cache_preload_tax_term_args
435 *
436 * @param {array} $args Arguments for terms.
437 *
438 * @return {array} New value.
439 * @since 2.0
440 */
441 $args = apply_filters(
442 'powered_cache_preload_tax_term_args',
443 [
444 'hide_empty' => false,
445 'orderby' => 'count',
446 'offset' => $offset,
447 'number' => $limit,
448 ],
449 $tax
450 );
451
452 \PoweredCache\Utils\log( 'Taxonomy preload args: [powered_cache_preload_tax_term_args]:' . print_r( $args, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
453
454 $terms = get_terms( $tax, $args );
455
456 foreach ( $terms as $term ) {
457 $public_tax_term_urls[] = get_term_link( $term );
458 }
459 }
460
461 return $public_tax_term_urls;
462 }
463
464
465 /**
466 * Max number of posts that will preloaded
467 *
468 * @return mixed|void
469 */
470 public function preload_max_post_count() {
471 /**
472 * Filters preload post count
473 *
474 * @hook powered_cache_preload_post_limit
475 *
476 * @param {int} $limit The number of posts that will preloaded.
477 *
478 * @return {int} New value.
479 * @since 2.0
480 */
481 return apply_filters( 'powered_cache_preload_post_limit', 1000 );
482 }
483
484 /**
485 * Max number of public terms that will preloaded
486 *
487 * @return mixed|void
488 */
489 public function preload_max_term_count() {
490 /**
491 * Filters preload term count
492 *
493 * @hook powered_cache_preload_term_limit
494 *
495 * @param {int} $limit The number of terms that will preloaded.
496 *
497 * @return {int} New value.
498 * @since 2.0
499 */
500 return apply_filters( 'powered_cache_preload_term_limit', 100 );
501 }
502
503 /**
504 * Filters domains and URLs for resource hints of relation type
505 *
506 * @param array $urls URLs to print for resource hints.
507 * @param string $relation_type The relation type the URLs are printed for, e.g. 'preconnect' or 'prerender'.
508 *
509 * @return array $urls resource hints
510 * @since 2.2 "preconnect" hint split from dns prefetch
511 */
512 public function dns_prefetch( $urls, $relation_type ) {
513 $domains = $this->get_prefetch_dns();
514 if ( $domains && is_array( $domains ) ) {
515 foreach ( $domains as $domain ) {
516 if ( 'dns-prefetch' === $relation_type ) {
517 $domain = str_replace( [ 'http://', 'https://' ], '//', $domain );
518 $urls[] = $domain;
519 }
520 }
521 }
522
523 return $urls;
524 }
525
526 /**
527 * Add "preconnect" hint for critical resources
528 *
529 * @param array $urls URLs
530 * @param string $relation_type the type of hint
531 *
532 * @return array $urls Resource list
533 * @since 2.2
534 */
535 public function preconnect_resources( $urls, $relation_type ) {
536 $domains = $this->get_preconnect_resources();
537 if ( $domains && is_array( $domains ) ) {
538 foreach ( $domains as $domain ) {
539 if ( 'preconnect' === $relation_type ) {
540 $parsed = wp_parse_url( $domain );
541 if ( empty( $parsed['scheme'] ) ) {
542 $domain = set_url_scheme( $domain );
543 }
544
545 $urls[] = $domain;
546 }
547 }
548 }
549
550 return $urls;
551 }
552
553
554 /**
555 *
556 * Get the list of prefetch domains
557 *
558 * @return mixed|void
559 */
560 public function get_prefetch_dns() {
561 $settings = \PoweredCache\Utils\get_settings();
562
563 $prefetch_dns = preg_split( '#(\r\n|\r|\n)#', $settings['prefetch_dns'], - 1, PREG_SPLIT_NO_EMPTY );
564
565 /**
566 * Filters Prefetched DNS list.
567 *
568 * @hook powered_cache_prefetch_dns
569 *
570 * @param {array} $prefetch_dns The list of prefetch domains.
571 *
572 * @return {array} New value.
573 * @since 2.0
574 */
575 return apply_filters( 'powered_cache_prefetch_dns', $prefetch_dns );
576 }
577
578 /**
579 * Get the list of preconnect domains
580 *
581 * @return mixed|void
582 * @since 2.2
583 */
584 public function get_preconnect_resources() {
585 $settings = \PoweredCache\Utils\get_settings();
586
587 $preconnect_resources = preg_split( '#(\r\n|\r|\n)#', $settings['preconnect_resource'], - 1, PREG_SPLIT_NO_EMPTY );
588
589 /**
590 * Filters Preconnect resource list.
591 *
592 * @hook powered_cache_preconnect_resource
593 *
594 * @param {array} $preconnect_resources The list of prefetch domains.
595 *
596 * @return {array} New value.
597 * @since 2.2
598 */
599 return apply_filters( 'powered_cache_preconnect_resource', $preconnect_resources );
600 }
601
602
603 /**
604 * Make preload request
605 *
606 * @param string $url Target URL
607 * @param array $args request args
608 *
609 * @return array|\WP_Error
610 */
611 public static function preload_request( $url, $args = [] ) {
612
613 /**
614 * Filters args of preload requests.
615 *
616 * @hook powered_cache_preload_url_request_args
617 *
618 * @param {array} $args Request defaults.
619 *
620 * @return {array} New value.
621 * @since 2.0
622 */
623 $request_args = apply_filters(
624 'powered_cache_preload_url_request_args',
625 [
626 'timeout' => 0.01,
627 'blocking' => false,
628 'user-agent' => 'Powered Cache Preloader',
629 'sslverify' => false,
630 ]
631 );
632
633 $args = wp_parse_args( $args, $request_args );
634
635 /**
636 * Fires before doing preload HTTP request.
637 *
638 * @hook powered_cache_preload_http_request
639 *
640 * @param {string} $url Preload URL.
641 * @param {array} $args Request arguments.
642 *
643 * @since 2.0
644 */
645 do_action( 'powered_cache_preload_http_request', $url, $args );
646
647 \PoweredCache\Utils\log( sprintf( 'Processing..: %s', $url ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
648
649 return wp_remote_get( esc_url_raw( $url ), $args );
650 }
651
652 /**
653 * Wait internal between HTTP reqs
654 *
655 * @param int $delay delay time in microseconds
656 */
657 public static function wait( $delay = 500000 ) {
658 /**
659 * Filters wait time between preload requests.
660 *
661 * @hook powered_cache_preload_request_interval
662 *
663 * @param {int} $delay Delay time in microseconds.
664 *
665 * @return {int} New value.
666 * @since 2.0
667 */
668 $delay = absint( apply_filters( 'powered_cache_preload_request_interval', $delay ) );
669
670 // Convert the delay to seconds and microseconds
671 $seconds = intdiv( $delay, 1000000 ); // Get full seconds
672 $microseconds = $delay % 1000000; // Get remaining microseconds
673
674 // Use sleep() for full second delays
675 if ( $seconds > 0 ) {
676 sleep( $seconds );
677 }
678
679 // Use usleep() for remaining microseconds
680 // sleeping more than 1 seconds may not be supported by the operating system with usleep
681 if ( $microseconds > 0 ) {
682 usleep( $microseconds );
683 }
684 }
685
686
687 /**
688 * Get mobile user agent for preload requests
689 *
690 * @return mixed|void
691 */
692 public static function mobile_user_agent() {
693 /**
694 * Filters mobile agent name for mobile preload requests
695 *
696 * @hook powered_cache_preload_mobile_user_agent
697 *
698 * @param {string} $agent Mobile agent name.
699 *
700 * @return {string} New value.
701 * @since 2.0
702 */
703 return apply_filters( 'powered_cache_preload_mobile_user_agent', 'Powered Cache Preloader mobile iPhone' );
704 }
705
706 /**
707 * Add a URL to the preload queue with optional filtering.
708 *
709 * @param string $url The URL to add to the preload queue.
710 *
711 * @since 3.4
712 */
713 protected function add_url_to_preload_queue( $url ) {
714 /**
715 * Filters whether a URL should be added to the preload queue.
716 *
717 * @hook powered_cache_preload_add_url_to_queue
718 *
719 * @param {boolean} $preload Whether to preload the URL. Default true.
720 * @param {string} $url The URL to be preloaded.
721 *
722 * @return {boolean} Whether to preload the URL.
723 * @since 3.4
724 */
725 $do_preload = apply_filters( 'powered_cache_preload_add_url_to_queue', true, $url );
726
727 if ( $do_preload ) {
728 $this->get_preloader()->push_to_queue( $url );
729 $this->queue_dirty = true;
730 \PoweredCache\Utils\log( sprintf( 'URL added to preload queue : %s', $url ) );
731 } else {
732 \PoweredCache\Utils\log( sprintf( 'URL skipped from preload queue: %s', $url ) );
733 }
734 }
735
736 /**
737 * Dispatch the preload queue if it has been modified.
738 *
739 * @return void
740 * @since 3.6
741 */
742 public function dispatch_preload_queue() {
743 if ( ! $this->queue_dirty ) {
744 return;
745 }
746
747 // This will serialize the queue into the DB and fire off the async request
748 $this->get_preloader()->save()->dispatch();
749
750 \PoweredCache\Utils\log( 'Dispatched preload queue.' );
751 }
752
753 }
754