PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.9
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.9
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / cache / class-wp-optimize-page-cache-preloader.php

class-wp-optimize-page-cache-preloader.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.9, at cache/class-wp-optimize-page-cache-preloader.php

679 lines 20.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) die('No direct access allowed');
4
5
6 class WP_Optimize_Page_Cache_Preloader extends WP_Optimize_Preloader {
7
8 protected $preload_type = 'page_cache';
9
10 protected $task_type = 'load-url-task';
11
12 static protected $_instance = null;
13
14 /**
15 * WP_Optimize_Page_Cache_Preloader constructor.
16 */
17 public function __construct() {
18 parent::__construct();
19
20 add_action('wpo_page_cache_schedule_preload', array($this, 'run_scheduled_cache_preload'));
21 add_filter('wpo_preload_headers', array($this, 'preload_headers'));
22 }
23
24 /**
25 * Check if cache is active.
26 *
27 * @return bool
28 */
29 public function is_option_active() {
30 return WP_Optimize()->get_page_cache()->is_enabled();
31 }
32
33 /**
34 * Schedule or delete automatic preload action on cache settings update.
35 *
36 * @param array $new_settings The new settings
37 * @param array $previous_settings Settings before saving
38 */
39 public function cache_settings_updated($new_settings, $previous_settings) {
40 if (!$new_settings['enable_page_caching']) {
41 wp_clear_scheduled_hook('wpo_page_cache_schedule_preload');
42 $this->delete_preload_continue_action();
43 return;
44 }
45
46 if (!empty($new_settings['enable_schedule_preload'])) {
47
48 $last_schedule_type = $previous_settings['preload_schedule_type'];
49
50 if (wp_next_scheduled('wpo_page_cache_schedule_preload')) {
51 // if already scheduled this schedule type
52 if ($new_settings['preload_schedule_type'] == $last_schedule_type) {
53 // If the schedule type is cache lifespan, check if the cache lifespan changed.
54 if ('wpo_use_cache_lifespan' == $new_settings['preload_schedule_type']) {
55 // Else, if the settings cache lifespan settings haven't changed, returns
56 if ($new_settings['page_cache_length_value'] == $previous_settings['page_cache_length_value'] && $new_settings['page_cache_length_unit'] == $previous_settings['page_cache_length_unit']) {
57 return;
58 }
59 } else {
60 return;
61 }
62 }
63 // clear currently scheduled preload action.
64 wp_clear_scheduled_hook('wpo_page_cache_schedule_preload');
65 }
66 // schedule preload action.
67 wp_schedule_event((time() + $this->get_schedule_interval($new_settings['preload_schedule_type'])), $new_settings['preload_schedule_type'], 'wpo_page_cache_schedule_preload');
68 } else {
69 wp_clear_scheduled_hook('wpo_page_cache_schedule_preload');
70 }
71
72 }
73
74 /**
75 * Clear active preload tasks, reschedule preload action.
76 */
77 public function reschedule_preload() {
78 // clear scheduled action.
79 if (wp_next_scheduled('wpo_page_cache_schedule_preload')) {
80 wp_clear_scheduled_hook('wpo_page_cache_schedule_preload');
81 }
82
83 // schedule preload action if need.
84 if ($this->is_scheduled_preload_enabled()) {
85 $preload_schedule_type = $this->get_cache_config('preload_schedule_type');
86 wp_schedule_event(time() + $this->get_schedule_interval($preload_schedule_type), $preload_schedule_type, 'wpo_page_cache_schedule_preload');
87 }
88 }
89
90 /**
91 * Check if scheduled preload enabled.
92 *
93 * @return bool
94 */
95 public function is_scheduled_preload_enabled() {
96 $enable_schedule_preload = $this->get_cache_config('enable_schedule_preload');
97 return !empty($enable_schedule_preload);
98 }
99
100 /**
101 * Check if we need run cache preload and run it.
102 */
103 public function run_scheduled_cache_preload() {
104
105 $schedule_type = WPO_Cache_Config::instance()->get_option('preload_schedule_type');
106 if (!$schedule_type) return;
107
108 // Don't run preload if cache lifespan option enabled and cache not expired yet.
109 if ('wpo_use_cache_lifespan' == $schedule_type) {
110
111 /**
112 * Filters the allowed time difference between the cache exiry and the current time, in seconds.
113 * If the cache expires in less than $allowed_time_difference, preload. Otherwise leave it.
114 *
115 * @param integer $allowed_time_difference The time difference, in seconds (default = 600)
116 */
117 $allowed_time_difference = apply_filters('wpo_preload_allowed_time_difference', 600);
118 $page_cache_lifespan = WPO_Cache_Config::instance()->get_option('page_cache_length', 0);
119 $last_preload_time = $this->options->get_option('wpo_last_page_cache_preload', 0);
120 $time_since_last_preload = time() - $last_preload_time;
121 $minimum_time_to_next_schedule_preload = $page_cache_lifespan - $allowed_time_difference;
122 // Skip this if the last preload is not as old as the cache lifespan minus $allowed_time_difference
123 if ($page_cache_lifespan > 0 && $time_since_last_preload < $minimum_time_to_next_schedule_preload) return;
124 }
125
126 $this->run();
127 }
128
129
130 /**
131 * Get cache config option value.
132 *
133 * @return mixed
134 */
135 public function get_cache_config($option) {
136 static $config = null;
137
138 if (null === $config) $config = WPO_Page_Cache::instance()->config->get();
139
140 if (is_array($config) && array_key_exists($option, $config)) {
141 return $config[$option];
142 }
143
144 return false;
145 }
146
147 /**
148 * Create tasks (WP_Optimize_Load_Url_Task) for preload all urls from site.
149 *
150 * @param string $type The preload type (currently: scheduled, manual)
151 * @return void
152 */
153 public function create_tasks_for_preload_site_urls($type) {
154 $urls = $this->get_site_urls();
155
156 if (!empty($urls)) {
157
158 $this->log(__('Creating tasks for preload site urls.', 'wp-optimize'));
159
160 foreach ($urls as $url) {
161 if (wpo_url_in_exceptions($url)) continue;
162
163 if ($this->url_is_already_cached($url, $type)) {
164 continue;
165 }
166
167 // this description is being used for internal purposes.
168 $description = 'Preload - '.$url;
169 $options = array('url' => $url, 'preload_type' => $type, 'anonymous_user_allowed' => (defined('DOING_CRON') && DOING_CRON) || (defined('WP_CLI') && WP_CLI));
170
171 if (!class_exists('WP_Optimize_Load_Url_Task')) {
172 require_once WPO_PLUGIN_MAIN_PATH . 'cache/class-wpo-load-url-task.php';
173 }
174 WP_Optimize_Load_Url_Task::create_task($this->task_type, $description, $options, 'WP_Optimize_Load_Url_Task');
175 }
176
177 $this->log(__('Tasks for preload site urls created.', 'wp-optimize'));
178 }
179 }
180
181 /**
182 * Preload mobile version from $url.
183 *
184 * @param string $url
185 *
186 * @return void
187 */
188 public function preload_mobile($url) {
189 static $is_mobile_caching_enabled;
190 if (!isset($is_mobile_caching_enabled)) {
191 $is_mobile_caching_enabled = $this->get_cache_config('enable_mobile_caching');
192 }
193
194 // Only run if option is active
195 if (!$is_mobile_caching_enabled) return;
196
197 $mobile_args = array(
198 'httpversion' => '1.1',
199 'user-agent' => 'Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1',
200 'timeout' => 10,
201 'headers' => apply_filters('wpo_preload_headers', array()),
202 );
203
204 $mobile_args = apply_filters('wpo_page_cache_preloader_mobile_args', $mobile_args, $url);
205
206 $this->log('preload_mobile - ' . $url);
207
208 wp_remote_get($url, $mobile_args);
209 }
210
211 /**
212 * Preload amp version from $url.
213 *
214 * @param string $url
215 *
216 * @return void
217 */
218 public function preload_amp($url) {
219 if (!apply_filters('wpo_should_preload_amp', false, $url)) return;
220
221 $amp_args = array(
222 'httpversion' => '1.1',
223 'user-agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.89 Safari/537.36',
224 'timeout' => 10,
225 'headers' => array(
226 'X-WP-Optimize-Cache-Preload' => 'Yes',
227 ),
228 );
229
230 $url = untrailingslashit($url) . '/amp/';
231
232 $amp_args = apply_filters('wpo_page_cache_preloader_amp_args', $amp_args, $url);
233
234 $this->log('preload_amp - ' . $url);
235
236 wp_remote_get($url, $amp_args);
237 }
238
239 /**
240 * Check if sitemap exists then returns list of urls from sitemap file otherwise returns all posts urls.
241 *
242 * @return array
243 */
244 public function get_site_urls() {
245
246 $urls = $this->get_sitemap_urls();
247
248 if (!empty($urls)) {
249 $this->options->update_option('wpo_last_page_cache_preload_type', 'sitemap');
250 } else {
251 $urls = $this->get_post_urls();
252 $this->options->update_option('wpo_last_page_cache_preload_type', 'posts');
253 }
254
255 $this->log(sprintf(_n('%d url found.', '%d urls found.', count($urls), 'wp-optimize'), count($urls)));
256
257 /**
258 * Filter the URLs which will be preloaded
259 *
260 * @param array $urls
261 * @return array
262 */
263 return apply_filters('wpo_preload_get_site_urls', $urls);
264 }
265
266 /**
267 * Loads sitemap file and returns list of urls.
268 *
269 * @param string $sitemap_url
270 *
271 * @return array|bool
272 */
273 public function get_sitemap_urls($sitemap_url = '') {
274
275 $urls = array();
276
277 // if sitemap url is empty then use main sitemap file name.
278 $sitemap_url = ('' === $sitemap_url) ? site_url('/'.$this->get_sitemap_filename()) : $sitemap_url;
279
280 // if simplexml_load_string not available then we don't load sitemap.
281 if (!function_exists('simplexml_load_string')) {
282 return $urls;
283 }
284
285 // load sitemap file.
286 $response = wp_remote_get($sitemap_url, array('timeout' => 30));
287
288 // if we get error then
289 if (is_wp_error($response)) {
290 $response = file_get_contents($sitemap_url);
291
292 // if response is empty then try load from file.
293 if (empty($response) && '' == $sitemap_url) {
294 $sitemap_file = $this->get_local_sitemap_file();
295
296 $response = file_get_contents($sitemap_file);
297 }
298
299 if (empty($response)) return $urls;
300
301 $xml = @simplexml_load_string($response); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
302 } else {
303 // parse xml answer.
304 $xml = @simplexml_load_string(wp_remote_retrieve_body($response)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
305 }
306
307 // xml file has not valid xml content then return false.
308 if (false === $xml) return false;
309
310 // if exists urls then return them.
311 if (isset($xml->url)) {
312 foreach ($xml->url as $element) {
313 if (!isset($element->loc)) continue;
314 $urls[] = (string) $element->loc;
315 }
316 } elseif (isset($xml->sitemap)) {
317 // if has links to other sitemap files then get urls from them.
318 foreach ($xml->sitemap as $element) {
319 if (!isset($element->loc)) continue;
320
321 $sitemap_urls = $this->get_sitemap_urls($element->loc);
322
323 if (is_array($sitemap_urls)) {
324 $urls = array_merge($urls, $sitemap_urls);
325 }
326 }
327 }
328
329 return $urls;
330 }
331
332 /**
333 * Get the path to a local sitemap file
334 *
335 * @return string
336 */
337 private function get_local_sitemap_file() {
338 if (!function_exists('get_home_path')) {
339 include_once ABSPATH . '/wp-admin/includes/file.php';
340 }
341 return trailingslashit(get_home_path()) . $this->get_sitemap_filename();
342 }
343
344 /**
345 * Get all posts of any post type and returns urls for them.
346 *
347 * @return array
348 */
349 public function get_post_urls() {
350 global $post;
351
352 $offset = 0;
353 $posts_per_page = 1000;
354 $urls = array();
355
356 $urls[] = site_url('/');
357
358 do {
359 $query = new WP_Query(array(
360 'post_type' => 'any',
361 'post_status' => 'publish',
362 'posts_per_page' => $posts_per_page,
363 'offset' => $offset,
364 'orderby' => 'ID',
365 'order' => 'ASC',
366 'cache_results' => false, // disable cache to avoid memory error.
367 ));
368
369 $posts_loaded = $query->post_count;
370
371 while ($query->have_posts()) {
372 $query->the_post();
373 $permalink = get_permalink();
374 $urls[] = $permalink;
375
376 // check page separators in the post content
377 preg_match_all('/\<\!--nextpage--\>/', $post->post_content, $matches);
378 // if there any separators add urls for each page
379 if (count($matches[0])) {
380 $prefix = strpos($permalink, '?') ? '&page=' : '';
381 for ($page = 0; $page < count($matches[0]); $page++) {
382 if ('' != $prefix) {
383 $urls[] = $permalink . $prefix . ($page+2);
384 } else {
385 $urls[] = trailingslashit($permalink) . ($page+2);
386 }
387 }
388 }
389 }
390
391 $offset += $posts_loaded;
392 } while ($posts_loaded > 0);
393
394 /**
395 * If domain mapping enabled then replace domains in urls.
396 */
397 if ($this->is_domain_mapping_enabled()) {
398 $blog_id = get_current_blog_id();
399
400 $mapped_domain = $this->get_mapped_domain($blog_id);
401 $blog_details = get_blog_details($blog_id);
402
403 if ($mapped_domain) {
404 foreach ($urls as $i => $url) {
405 $urls[$i] = preg_replace('/'.$blog_details->domain.'/i', $mapped_domain, $url, 1);
406 }
407 }
408 }
409
410 wp_reset_postdata();
411
412 return $urls;
413 }
414
415 /**
416 * Check if domain mapping enabled.
417 *
418 * @return bool
419 */
420 public function is_domain_mapping_enabled() {
421 // SUNRISE constant is defined with installation WordPress MU Domain Mapping plugin.
422 $enabled = is_multisite() && defined('SUNRISE') && 'on' == strtolower(SUNRISE);
423
424 /**
425 * Filters if Multisite Domain mapping is enabled.
426 * Currently, we can only detect if the WordPress MU Domain Mapping plugin is in use.
427 * Using the WP Core functionality should not require this, unless if the domain name is set somewhere else but in the site url option.
428 */
429 return apply_filters('wpo_is_domain_mapping_enabled', $enabled);
430 }
431
432 /**
433 * Return mapped domain by $blog_id.
434 *
435 * @param int $blog_id
436 *
437 * @return string
438 */
439 public function get_mapped_domain($blog_id) {
440 global $wpdb;
441
442 $domain = '';
443 $multisite_plugin_table_name = $wpdb->base_prefix.'domain_mapping';
444 // Check if table exists
445 if ($wpdb->get_var("SHOW TABLES LIKE '$multisite_plugin_table_name'") != $multisite_plugin_table_name) {
446 // This table created in WordPress MU Domain Mapping plugin.
447 $row = $wpdb->get_row("SELECT `domain` FROM {$multisite_plugin_table_name} WHERE `blog_id` = {$blog_id} AND `active` = 1", ARRAY_A);
448 if (!empty($row)) {
449 $domain = $row['domain'];
450 }
451 } else {
452 // When using the WP Core method, the site url option contains the mapped domain.
453 $domain = get_site_url($blog_id);
454 }
455
456 /**
457 * Filters the mapped domain name
458 *
459 * @param string $domain The domain name
460 * @param integer $blog_id The blog ID
461 */
462 return apply_filters('wpo_get_mapped_domain', $domain, $blog_id);
463 }
464
465 /**
466 * Captures and logs any interesting messages
467 *
468 * @param String $message - the error message
469 * @param String $error_type - the error type
470 */
471 public function log($message, $error_type = 'info') {
472
473 if (isset($this->loggers)) {
474 foreach ($this->loggers as $logger) {
475 $logger->log($message, $error_type);
476 }
477 }
478 }
479
480 /**
481 * Instance of WP_Optimize_Page_Cache_Preloader.
482 *
483 * @return WP_Optimize_Page_Cache_Preloader
484 */
485 public static function instance() {
486 if (empty(self::$_instance)) {
487 self::$_instance = new WP_Optimize_Page_Cache_Preloader();
488 }
489
490 return self::$_instance;
491 }
492
493 /**
494 * Check if the URL is already cached, or needs to be preloaded
495 *
496 * @param string $url The preloaded url
497 * @param string $preload_type The preload type (manual | scheduled)
498 * @return boolean
499 */
500 private function url_is_already_cached($url, $preload_type) {
501 static $files = array();
502 $regenerate_count = 0;
503 $folder = trailingslashit(WPO_CACHE_FILES_DIR) . wpo_get_url_path($url);
504 // If the folder does not exist, consider the URL as cleared
505 if (!is_dir($folder)) return false;
506
507 if (empty($files)) {
508 // Check only the base files
509 $files[] = 'index.html';
510
511 if (WPO_Cache_Config::instance()->get_option('enable_mobile_caching')) {
512 $files[] = 'mobile.index.html';
513 }
514 $files = apply_filters('wpo_maybe_clear_files_list', $files);
515 }
516
517 foreach ($files as $file) {
518 $file_path = trailingslashit($folder).$file;
519 if (!file_exists($file_path)) {
520 // The file does not exist, count it as "deleted"
521 $regenerate_count++;
522 continue;
523 }
524
525 if ($this->should_regenerate_file($file_path, $preload_type)) {
526 // delefe the expired cache file
527 unlink($file_path);
528 $regenerate_count++;
529 }
530 }
531
532 // if 0 == $regenerate_count, nothing all the expected files exist, and none were deleted.
533 return 0 == $regenerate_count;
534 }
535
536 /**
537 * Determine if a file should be regenerated
538 *
539 * @param string $path The file to check
540 * @param string $preload_type The preload type (manual | scheduled)
541 *
542 * @return boolean
543 */
544 private function should_regenerate_file($path, $preload_type) {
545 // Store the variables, as they'll be used for each file and each file
546 static $is_preloader_scheduled = null;
547 static $lifespan = null;
548 static $schedule_type = null;
549 static $schedule_interval = null;
550 static $lifespan_expiry_threshold = null;
551 static $always_regenerate_file_if_preload_is_manual = null;
552 static $always_regenerate_file_if_preload_is_scheduled = null;
553 static $regenerate_file_when_no_expiry_date = null;
554
555 // Sets the variables once per request:
556 if (null === $is_preloader_scheduled) {
557 $is_preloader_scheduled = WPO_Cache_Config::instance()->get_option('enable_schedule_preload');
558 $schedule_type = WPO_Cache_Config::instance()->get_option('preload_schedule_type');
559 $lifespan = WPO_Cache_Config::instance()->get_option('page_cache_length');
560 $schedule_interval = $this->get_schedule_interval($schedule_type);
561
562 /**
563 * Expiry threshold: the current file will be considered stale if within the threshold. Default: 600s (10min)
564 */
565 $lifespan_expiry_threshold = apply_filters('wpo_lifespan_expiry_threshold', 600);
566
567 /**
568 * Filters if a cache should systematically be regenerated when running a manual preload. Default: false
569 */
570 $always_regenerate_file_if_preload_is_manual = apply_filters('wpo_always_regenerate_file_if_preload_is_manual', false);
571
572 /**
573 * Filters if a cache should systematically be regenerated when running a scheduled preload. Default: false
574 */
575 $always_regenerate_file_if_preload_is_scheduled = apply_filters('wpo_always_regenerate_file_if_preload_is_scheduled', false);
576
577 /**
578 * Filters if a cache should systematically be regenerated when running a preload and no schedule is set, and cache does not expire. Default: true
579 */
580 $regenerate_file_when_no_expiry_date = apply_filters('wpo_regenerate_file_when_no_expiry_date', true);
581 }
582
583 if (($always_regenerate_file_if_preload_is_manual && 'manual' == $preload_type) || ($always_regenerate_file_if_preload_is_scheduled && 'scheduled' == $preload_type)) {
584 $result = true;
585 } else {
586
587 $modified_time = (int) filemtime($path);
588
589 // cache lifespan is set.
590 if (0 != $lifespan) {
591 $expiry_time = $modified_time + $lifespan - $lifespan_expiry_threshold;
592 $result = time() > $expiry_time;
593 } elseif ($is_preloader_scheduled) {
594 $expiry_time = $modified_time + $schedule_interval - $lifespan_expiry_threshold;
595 $result = time() > $expiry_time;
596 } else {
597 $result = $regenerate_file_when_no_expiry_date;
598 }
599
600 }
601
602 return apply_filters('wpo_preloader_should_regenerate_file', $result, $path, $preload_type);
603 }
604
605 /**
606 * Add preloader headers
607 */
608 public function preload_headers($headers) {
609 $headers['X-WP-Optimize-Cache-Preload'] = 'Yes';
610 return $headers;
611 }
612
613 /**
614 * Option disabled error message
615 *
616 * @return array
617 */
618 protected function get_option_disabled_error() {
619 return array(
620 'success' => false,
621 'error' => __('Page cache is disabled.', 'wp-optimize')
622 );
623 }
624
625 /**
626 * Get preload already running error message
627 *
628 * @return array
629 */
630 protected function get_preload_already_running_error() {
631 return array(
632 'success' => false,
633 'error' => __('Probably page cache preload is running already.', 'wp-optimize')
634 );
635 }
636
637 protected function get_preload_data() {
638 return WP_Optimize()->get_page_cache()->get_cache_size();
639 }
640
641 protected function get_preloading_message($cache_size) {
642 return array(
643 'done' => false,
644 'message' => __('Loading URLs...', 'wp-optimize'),
645 'size' => WP_Optimize()->format_size($cache_size['size']),
646 'file_count' => $cache_size['file_count']
647 );
648 }
649
650 protected function get_last_preload_message($cache_size, $last_preload_time_str) {
651 return array(
652 'done' => true,
653 'message' => sprintf(__('Last preload finished at %s', 'wp-optimize'), $last_preload_time_str),
654 'size' => WP_Optimize()->format_size($cache_size['size']),
655 'file_count' => $cache_size['file_count']
656 );
657 }
658
659 protected function get_preload_success_message($cache_size) {
660 return array(
661 'done' => true,
662 'size' => WP_Optimize()->format_size($cache_size['size']),
663 'file_count' => $cache_size['file_count']
664 );
665 }
666
667 protected function get_preload_progress_message($cache_size, $preloaded_message, $preload_resuming_in) {
668 return array(
669 'done' => false,
670 'message' => $preloaded_message,
671 'size' => WP_Optimize()->format_size($cache_size['size']),
672 'file_count' => $cache_size['file_count'],
673 'resume_in' => $preload_resuming_in
674 );
675 }
676 }
677
678 WP_Optimize_Page_Cache_Preloader::instance();
679