PluginProbe
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score / trunk
Powered Cache – Caching and Optimization for WordPress – Easily Improve PageSpeed & Web Vitals Score vtrunk
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 trunk, at includes/classes/Preloader.php

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