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

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