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

675 lines 20.3 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 // sometimes returns error due to timeout
295 if (is_wp_error($response)) {
296 $response = file_get_contents($sitemap_url);
297
298 // if response is empty then try load from file.
299 if (empty($response) && '' == $sitemap_url) {
300 $sitemap_file = $this->get_local_sitemap_file();
301
302 if (is_file($sitemap_file)) {
303 $response = file_get_contents($sitemap_file);
304 }
305 }
306
307 if (empty($response)) return $urls;
308
309 $xml = @simplexml_load_string($response); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warnings when error found in XML data
310 } else {
311 // parse xml answer.
312 $xml = @simplexml_load_string(wp_remote_retrieve_body($response)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warnings when error found in XML data
313 }
314
315 // xml file has not valid xml content then return false.
316 if (false === $xml) return false;
317
318 // if exists urls then return them.
319 if (isset($xml->url)) {
320 foreach ($xml->url as $element) {
321 if (!isset($element->loc)) continue;
322 $urls[] = (string) $element->loc;
323 }
324 } elseif (isset($xml->sitemap)) {
325 // if has links to other sitemap files then get urls from them.
326 foreach ($xml->sitemap as $element) {
327 if (!isset($element->loc)) continue;
328
329 $sitemap_urls = $this->get_sitemap_urls($element->loc);
330
331 if (is_array($sitemap_urls)) {
332 $urls = array_merge($urls, $sitemap_urls);
333 }
334 }
335 }
336
337 return $urls;
338 }
339
340 /**
341 * Get all posts of any post type and returns urls for them.
342 *
343 * @return array
344 */
345 public function get_post_urls() {
346 global $post;
347
348 $offset = 0;
349 $posts_per_page = 1000;
350 $urls = array();
351
352 $urls[] = site_url('/');
353
354 do {
355 $query = new WP_Query(array(
356 'post_type' => 'any',
357 'post_status' => 'publish',
358 'posts_per_page' => $posts_per_page,
359 'offset' => $offset,
360 'orderby' => 'ID',
361 'order' => 'ASC',
362 'cache_results' => false, // disable cache to avoid memory error.
363 ));
364
365 $posts_loaded = $query->post_count;
366
367 while ($query->have_posts()) {
368 $query->the_post();
369 $permalink = get_permalink();
370 $urls[] = $permalink;
371
372 // check page separators in the post content
373 preg_match_all('/\<\!--nextpage--\>/', $post->post_content, $matches);
374 // if there any separators add urls for each page
375 if (count($matches[0])) {
376 $prefix = strpos($permalink, '?') ? '&page=' : '';
377 for ($page = 0; $page < count($matches[0]); $page++) {
378 if ('' != $prefix) {
379 $urls[] = $permalink . $prefix . ($page+2);
380 } else {
381 $urls[] = trailingslashit($permalink) . ($page+2);
382 }
383 }
384 }
385 }
386
387 $offset += $posts_loaded;
388 } while ($posts_loaded > 0);
389
390 /**
391 * If domain mapping enabled then replace domains in urls.
392 */
393 if ($this->is_domain_mapping_enabled()) {
394 $blog_id = get_current_blog_id();
395
396 $mapped_domain = $this->get_mapped_domain($blog_id);
397 $blog_details = get_blog_details($blog_id);
398
399 if ($mapped_domain) {
400 foreach ($urls as $i => $url) {
401 $urls[$i] = preg_replace('/'.$blog_details->domain.'/i', $mapped_domain, $url, 1);
402 }
403 }
404 }
405
406 wp_reset_postdata();
407
408 return $urls;
409 }
410
411 /**
412 * Check if domain mapping enabled.
413 *
414 * @return bool
415 */
416 public function is_domain_mapping_enabled() {
417 // SUNRISE constant is defined with installation WordPress MU Domain Mapping plugin.
418 $enabled = is_multisite() && defined('SUNRISE') && 'on' == strtolower(SUNRISE);
419
420 /**
421 * Filters if Multisite Domain mapping is enabled.
422 * Currently, we can only detect if the WordPress MU Domain Mapping plugin is in use.
423 * Using the WP Core functionality should not require this, unless if the domain name is set somewhere else but in the site url option.
424 */
425 return apply_filters('wpo_is_domain_mapping_enabled', $enabled);
426 }
427
428 /**
429 * Return mapped domain by $blog_id.
430 *
431 * @param int $blog_id
432 *
433 * @return string
434 */
435 public function get_mapped_domain($blog_id) {
436 global $wpdb;
437
438 $domain = '';
439 $multisite_plugin_table_name = $wpdb->base_prefix.'domain_mapping';
440 // Check if table exists
441 if ($wpdb->get_var("SHOW TABLES LIKE '$multisite_plugin_table_name'") != $multisite_plugin_table_name) {
442 // This table created in WordPress MU Domain Mapping plugin.
443 $row = $wpdb->get_row("SELECT `domain` FROM {$multisite_plugin_table_name} WHERE `blog_id` = {$blog_id} AND `active` = 1", ARRAY_A);
444 if (!empty($row)) {
445 $domain = $row['domain'];
446 }
447 } else {
448 // When using the WP Core method, the site url option contains the mapped domain.
449 $domain = get_site_url($blog_id);
450 }
451
452 /**
453 * Filters the mapped domain name
454 *
455 * @param string $domain The domain name
456 * @param integer $blog_id The blog ID
457 */
458 return apply_filters('wpo_get_mapped_domain', $domain, $blog_id);
459 }
460
461 /**
462 * Captures and logs any interesting messages
463 *
464 * @param String $message - the error message
465 * @param String $error_type - the error type
466 */
467 public function log($message, $error_type = 'info') {
468
469 if (isset($this->loggers)) {
470 foreach ($this->loggers as $logger) {
471 $logger->log($message, $error_type);
472 }
473 }
474 }
475
476 /**
477 * Instance of WP_Optimize_Page_Cache_Preloader.
478 *
479 * @return WP_Optimize_Page_Cache_Preloader
480 */
481 public static function instance() {
482 if (empty(self::$_instance)) {
483 self::$_instance = new WP_Optimize_Page_Cache_Preloader();
484 }
485
486 return self::$_instance;
487 }
488
489 /**
490 * Check if the URL is already cached, or needs to be preloaded
491 *
492 * @param string $url The preloaded url
493 * @param string $preload_type The preload type (manual | scheduled)
494 * @return boolean
495 */
496 private function url_is_already_cached($url, $preload_type) {
497 static $files = array();
498 $regenerate_count = 0;
499 $folder = trailingslashit(WPO_CACHE_FILES_DIR) . wpo_get_url_path($url);
500 // If the folder does not exist, consider the URL as cleared
501 if (!is_dir($folder)) return false;
502
503 if (empty($files)) {
504 // Check only the base files
505 $files[] = 'index.html';
506
507 if (WPO_Cache_Config::instance()->get_option('enable_mobile_caching')) {
508 $files[] = 'mobile.index.html';
509 }
510 $files = apply_filters('wpo_maybe_clear_files_list', $files);
511 }
512
513 foreach ($files as $file) {
514 $file_path = trailingslashit($folder).$file;
515 if (!file_exists($file_path)) {
516 // The file does not exist, count it as "deleted"
517 $regenerate_count++;
518 continue;
519 }
520
521 if ($this->should_regenerate_file($file_path, $preload_type)) {
522 // delefe the expired cache file
523 unlink($file_path);
524 $regenerate_count++;
525 }
526 }
527
528 // if 0 == $regenerate_count, nothing all the expected files exist, and none were deleted.
529 return 0 == $regenerate_count;
530 }
531
532 /**
533 * Determine if a file should be regenerated
534 *
535 * @param string $path The file to check
536 * @param string $preload_type The preload type (manual | scheduled)
537 *
538 * @return boolean
539 */
540 private function should_regenerate_file($path, $preload_type) {
541 // Store the variables, as they'll be used for each file and each file
542 static $is_preloader_scheduled = null;
543 static $lifespan = null;
544 static $schedule_type = null;
545 static $schedule_interval = null;
546 static $lifespan_expiry_threshold = null;
547 static $always_regenerate_file_if_preload_is_manual = null;
548 static $always_regenerate_file_if_preload_is_scheduled = null;
549 static $regenerate_file_when_no_expiry_date = null;
550
551 // Sets the variables once per request:
552 if (null === $is_preloader_scheduled) {
553 $is_preloader_scheduled = WPO_Cache_Config::instance()->get_option('enable_schedule_preload');
554 $schedule_type = WPO_Cache_Config::instance()->get_option('preload_schedule_type');
555 $lifespan = WPO_Cache_Config::instance()->get_option('page_cache_length');
556 $schedule_interval = $this->get_schedule_interval($schedule_type);
557
558 /**
559 * Expiry threshold: the current file will be considered stale if within the threshold. Default: 600s (10min)
560 */
561 $lifespan_expiry_threshold = apply_filters('wpo_lifespan_expiry_threshold', 600);
562
563 /**
564 * Filters if a cache should systematically be regenerated when running a manual preload. Default: false
565 */
566 $always_regenerate_file_if_preload_is_manual = apply_filters('wpo_always_regenerate_file_if_preload_is_manual', false);
567
568 /**
569 * Filters if a cache should systematically be regenerated when running a scheduled preload. Default: false
570 */
571 $always_regenerate_file_if_preload_is_scheduled = apply_filters('wpo_always_regenerate_file_if_preload_is_scheduled', false);
572
573 /**
574 * Filters if a cache should systematically be regenerated when running a preload and no schedule is set, and cache does not expire. Default: true
575 */
576 $regenerate_file_when_no_expiry_date = apply_filters('wpo_regenerate_file_when_no_expiry_date', true);
577 }
578
579 if (($always_regenerate_file_if_preload_is_manual && 'manual' == $preload_type) || ($always_regenerate_file_if_preload_is_scheduled && 'scheduled' == $preload_type)) {
580 $result = true;
581 } else {
582
583 $modified_time = (int) filemtime($path);
584
585 // cache lifespan is set.
586 if (0 != $lifespan) {
587 $expiry_time = $modified_time + $lifespan - $lifespan_expiry_threshold;
588 $result = time() > $expiry_time;
589 } elseif ($is_preloader_scheduled) {
590 $expiry_time = $modified_time + $schedule_interval - $lifespan_expiry_threshold;
591 $result = time() > $expiry_time;
592 } else {
593 $result = $regenerate_file_when_no_expiry_date;
594 }
595
596 }
597
598 return apply_filters('wpo_preloader_should_regenerate_file', $result, $path, $preload_type);
599 }
600
601 /**
602 * Add preloader headers
603 */
604 public function preload_headers($headers) {
605 $headers['X-WP-Optimize-Cache-Preload'] = 'Yes';
606 return $headers;
607 }
608
609 /**
610 * Option disabled error message
611 *
612 * @return array
613 */
614 protected function get_option_disabled_error() {
615 return array(
616 'success' => false,
617 'error' => __('Page cache is disabled.', 'wp-optimize')
618 );
619 }
620
621 /**
622 * Get preload already running error message
623 *
624 * @return array
625 */
626 protected function get_preload_already_running_error() {
627 return array(
628 'success' => false,
629 'error' => __('Probably page cache preload is running already.', 'wp-optimize')
630 );
631 }
632
633 protected function get_preload_data() {
634 return WP_Optimize()->get_page_cache()->get_cache_size();
635 }
636
637 protected function get_preloading_message($cache_size) {
638 return array(
639 'done' => false,
640 'message' => __('Loading URLs...', 'wp-optimize'),
641 'size' => WP_Optimize()->format_size($cache_size['size']),
642 'file_count' => $cache_size['file_count']
643 );
644 }
645
646 protected function get_last_preload_message($cache_size, $last_preload_time_str) {
647 return array(
648 'done' => true,
649 'message' => sprintf(__('Last preload finished at %s', 'wp-optimize'), $last_preload_time_str),
650 'size' => WP_Optimize()->format_size($cache_size['size']),
651 'file_count' => $cache_size['file_count']
652 );
653 }
654
655 protected function get_preload_success_message($cache_size) {
656 return array(
657 'done' => true,
658 'size' => WP_Optimize()->format_size($cache_size['size']),
659 'file_count' => $cache_size['file_count']
660 );
661 }
662
663 protected function get_preload_progress_message($cache_size, $preloaded_message, $preload_resuming_in) {
664 return array(
665 'done' => false,
666 'message' => $preloaded_message,
667 'size' => WP_Optimize()->format_size($cache_size['size']),
668 'file_count' => $cache_size['file_count'],
669 'resume_in' => $preload_resuming_in
670 );
671 }
672 }
673
674 WP_Optimize_Page_Cache_Preloader::instance();
675