PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.21
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.21
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.21, at cache/class-wp-optimize-page-cache-preloader.php

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