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

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

615 lines 17.7 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('Updraft_Task_Manager_1_4')) require_once(WPO_PLUGIN_MAIN_PATH . 'vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-manager.php');
6
7 abstract class WP_Optimize_Preloader extends Updraft_Task_Manager_1_4 {
8
9 protected $options;
10
11 /**
12 * WP_Optimize_Page_Cache_Preloader constructor.
13 */
14 public function __construct() {
15 parent::__construct();
16
17 $this->options = WP_Optimize()->get_options();
18 // setup loggers
19 $this->set_loggers(WP_Optimize()->wpo_loggers());
20
21 add_action('wpo_' . $this->preload_type . '_preload_continue', array($this, 'process_tasks_queue'));
22 add_filter('updraft_interrupt_tasks_queue_'.$this->task_type, array($this, 'maybe_interrupt_queue'), 20);
23 }
24
25 /**
26 * Get a schedule interval
27 *
28 * @param string $schedule_key The schedule to check
29 * @return integer
30 */
31 protected function get_schedule_interval($schedule_key) {
32 $schedules = wp_get_schedules();
33 if (!isset($schedules[$schedule_key])) {
34 $this->log('Could not get interval for event of type '.$schedule_key);
35 return 0;
36 }
37 return isset($schedules[$schedule_key]['interval']) ? $schedules[$schedule_key]['interval'] : 0;
38 }
39
40 /**
41 * Get the interval to continuing a preload task
42 *
43 * @return integer
44 */
45 public function get_continue_preload_cron_interval() {
46 /**
47 * Filters the interval between each preload attempt, in seconds.
48 */
49 return (int) apply_filters('wpo_' . $this->preload_type . '_preload_continue_interval', 600);
50 }
51
52 /**
53 * Schedule action for continuously preload.
54 */
55 public function schedule_preload_continue_action() {
56 $continue_in = wp_next_scheduled('wpo_' . $this->preload_type .'_preload_continue');
57
58 // Action is still scheduled
59 if ($continue_in && $continue_in > 0) return;
60 // Action is overdue, delete it and re schedule it
61 if ($continue_in && $continue_in < 0) $this->delete_preload_continue_action();
62
63 wp_schedule_event(time() + $this->get_schedule_interval('wpo_' . $this->preload_type . '_preload_continue_interval'), 'wpo_' . $this->preload_type . '_preload_continue_interval', 'wpo_' . $this->preload_type . '_preload_continue');
64 }
65
66 /**
67 * Delete scheduled action for continuously preload.
68 */
69 public function delete_preload_continue_action() {
70 wp_clear_scheduled_hook('wpo_' . $this->preload_type . '_preload_continue');
71 }
72
73 /**
74 * Run preload. If task queue is empty it creates tasks for site urls.
75 *
76 * @param string $type - The preload type (schedule | manual)
77 * @param array $response - Specific response for echo into output thread when browser connection closing.
78 * @return array|void - Void when closing the browser connection
79 */
80 public function run($type = 'scheduled', $response = null) {
81 if (!$this->is_option_active()) {
82 return $this->get_option_disabled_error();
83 }
84
85 do_action('wpo_before_' . $this->preload_type . '_preload');
86
87 if (empty($response)) {
88 $response = array('success' => true);
89 }
90
91 $this->delete_cancel_flag();
92
93 // trying to lock semaphore.
94
95 $creating_tasks_semaphore = new Updraft_Semaphore_3_0('wpo_' . $this->preload_type . '_preloader_creating_tasks');
96 $lock = $creating_tasks_semaphore->lock();
97
98 // if semaphore haven't locked then just return response.
99 if (!$lock) {
100 return $this->get_preload_already_running_error();
101 }
102
103 $is_wp_cli = defined('WP_CLI') && WP_CLI;
104
105 // close browser connection and continue work for ajax actions.
106 if (defined('DOING_AJAX') && DOING_AJAX) {
107 WP_Optimize()->close_browser_connection(json_encode($response));
108 }
109
110 // trying to change time limit.
111 WP_Optimize()->change_time_limit();
112
113 $status = $this->get_status($this->task_type);
114
115 if (0 == $status['all_tasks'] && $lock) {
116 if (is_multisite()) {
117 $sites = WP_Optimize()->get_sites();
118
119 foreach ($sites as $site) {
120 switch_to_blog($site->blog_id);
121 $this->create_tasks_for_preload_site_urls($type);
122 restore_current_blog();
123 }
124 } else {
125 $this->create_tasks_for_preload_site_urls($type);
126 }
127 }
128
129 if ($lock) $creating_tasks_semaphore->release();
130
131 $this->process_tasks_queue();
132
133 // return $response in WP-CLI mode
134 if ($is_wp_cli) {
135 return $response;
136 }
137 }
138
139 /**
140 * Process tasks queue.
141 */
142 public function process_tasks_queue() {
143 // schedule continue preload action.
144 $this->schedule_preload_continue_action();
145
146 if (!$this->process_queue($this->task_type)) {
147 return;
148 }
149
150 // delete scheduled continue preload action.
151 $this->delete_preload_continue_action();
152
153 // update last preload time only if processing any tasks, else process was cancelled.
154 if ($this->is_running()) {
155 $this->options->update_option('wpo_last_' . $this->preload_type . '_preload', time());
156 }
157
158 $this->clean_up_old_tasks($this->task_type);
159 }
160
161 /**
162 * Find out if the current queue should be interrupted
163 *
164 * @param boolean $interrupt
165 * @return boolean
166 */
167 public function maybe_interrupt_queue($interrupt) {
168
169 if ($interrupt) return $interrupt;
170
171 static $memory_threshold = null;
172 if (null == $memory_threshold) {
173 /**
174 * Filters the minimum memory required before stopping a queue. Default: 10MB
175 */
176 $memory_threshold = apply_filters('wpo_' . $this->preload_type . '_preload_memory_threshold', 10485760);
177 }
178
179 return WP_Optimize()->get_free_memory() < $memory_threshold;
180 }
181
182 /**
183 * Delete all preload tasks from queue.
184 */
185 public function cancel_preload() {
186 $this->set_cancel_flag();
187 $this->delete_tasks($this->task_type);
188 $this->delete_preload_continue_action();
189 }
190
191 /**
192 * Set 'cancel' option to true.
193 */
194 public function set_cancel_flag() {
195 $this->options->update_option("last_{$this->preload_type}_preload_cancel", true);
196 }
197
198 /**
199 * Delete 'cancel' option.
200 */
201 public function delete_cancel_flag() {
202 $this->options->delete_option('last_' . $this->preload_type . '_preload_cancel');
203 }
204
205 /**
206 * Check if the last preload is cancelled.
207 *
208 * @return bool
209 */
210 public function is_cancelled() {
211 return $this->options->get_option("last_{$this->preload_type}_preload_cancel", false);
212 }
213
214 /**
215 * Check if preloading queue is processing.
216 *
217 * @return bool
218 */
219 public function is_busy() {
220 return $this->is_semaphore_locked($this->task_type) || $this->is_semaphore_locked('wpo_' . $this->preload_type . '_preloader_creating_tasks');
221 }
222
223 /**
224 * Get current status of preloading urls.
225 *
226 * @return array
227 */
228 public function get_status_info() {
229
230 $status = $this->get_status($this->task_type);
231 $preload_data = $this->get_preload_data();
232
233 if ($this->is_semaphore_locked('wpo_' . $this->preload_type . '_preloader_creating_tasks') && !$this->is_cancelled()) {
234 // we are still creating tasks.
235 return $this->get_preloading_message($preload_data);
236 } elseif ($status['complete_tasks'] == $status['all_tasks']) {
237 $gmt_offset = (int) (3600 * get_option('gmt_offset'));
238
239 $last_preload_time = $this->options->get_option('wpo_last_' . $this->preload_type . '_preload');
240
241 if ($last_preload_time) {
242 $last_preload_time_str = date_i18n(get_option('time_format').', '.get_option('date_format'), $last_preload_time + $gmt_offset);
243 return $this->get_last_preload_message($preload_data, $last_preload_time_str);
244 } else {
245 return $this->get_preload_success_message($preload_data);
246 }
247 } else {
248 $preload_resuming_time = wp_next_scheduled('wpo_' . $this->preload_type . '_preload_continue');
249 $preload_resuming_in = $preload_resuming_time ? $preload_resuming_time - time() : 0;
250 $preloaded_message = sprintf(_n('%1$s out of %2$s URL preloaded', '%1$s out of %2$s URLs preloaded', $status['all_tasks'], 'wp-optimize'), $status['complete_tasks'], $status['all_tasks']);
251 if ('sitemap' == $this->options->get_option('wpo_last_' . $this->preload_type . '_preload_type', '')) {
252 $preloaded_message = __('Preloading posts found in sitemap:', 'wp-optimize') .' '. $preloaded_message;
253 }
254 $return = $this->get_preload_progress_message($preload_data, $preloaded_message, $preload_resuming_in);
255 if (defined('DOING_AJAX') && DOING_AJAX) {
256 // if no cron was found or cron is overdue more than 20s, trigger it
257 if (!$preload_resuming_time || $preload_resuming_in < -20) {
258 $this->run($return);
259 }
260 }
261 return $return;
262 }
263 }
264
265 /**
266 * Check if preload action in process.
267 *
268 * @return bool
269 */
270 public function is_running() {
271 $status = $this->get_status($this->task_type);
272
273 if ($status['all_tasks'] > 0) return true;
274 }
275
276 /**
277 * Preload desktop version from url.
278 *
279 * @param string $url
280 *
281 * @return void
282 */
283 public function preload_desktop($url) {
284 $desktop_args = array(
285 'httpversion' => '1.1',
286 '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',
287 'timeout' => 10,
288 'headers' => apply_filters('wpo_preload_headers', array()),
289 );
290
291 $desktop_args = apply_filters('wpo_' . $this->preload_type . '_preloader_desktop_args', $desktop_args, $url);
292
293 $this->log('preload_desktop - '. $url);
294
295 wp_remote_get($url, $desktop_args);
296 }
297
298 /**
299 * Preload amp version from $url.
300 *
301 * @param string $url
302 *
303 * @return void
304 */
305 public function preload_amp($url) {
306 if (!apply_filters('wpo_should_preload_amp', false, $url)) return;
307
308 $amp_args = array(
309 'httpversion' => '1.1',
310 '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',
311 'timeout' => 10,
312 'headers' => apply_filters('wpo_preload_headers', array()),
313 );
314
315 $url = untrailingslashit($url) . '/amp/';
316
317 $amp_args = apply_filters('wpo_' . $this->preload_type . '_preloader_amp_args', $amp_args, $url);
318
319 $this->log('preload_amp - ' . $url);
320
321 wp_remote_get($url, $amp_args);
322 }
323
324 /**
325 * Get sitemap filename.
326 *
327 * @return string
328 */
329 protected function get_sitemap_filename() {
330 /**
331 * Filter the sitemap file used to collect the URLs to preload
332 *
333 * @param string $filename - The sitemap name
334 * @default sitemap.xml
335 */
336 return apply_filters('wpo_cache_preload_sitemap_filename', 'sitemap.xml');
337 }
338
339 /**
340 * Check if sitemap exists then returns list of urls from sitemap file otherwise returns all posts urls.
341 *
342 * @return array
343 */
344 public function get_site_urls() {
345
346 $urls = $this->get_sitemap_urls();
347
348 if (!empty($urls)) {
349 $this->options->update_option('wpo_last_' . $this->preload_type . '_preload_type', 'sitemap');
350 } else {
351 $urls = $this->get_post_urls();
352 $this->options->update_option('wpo_last_' . $this->preload_type . '_preload_type', 'posts');
353 }
354
355 $this->log(sprintf(_n('%d url found.', '%d urls found.', count($urls), 'wp-optimize'), count($urls)));
356
357 /**
358 * Filter the URLs which will be preloaded
359 *
360 * @param array $urls
361 * @return array
362 */
363 return apply_filters('wpo_preload_get_site_urls', $urls);
364 }
365
366 /**
367 * Loads sitemap file and returns list of urls.
368 *
369 * @param string $sitemap_url
370 *
371 * @return array|bool
372 */
373 public function get_sitemap_urls($sitemap_url = '') {
374
375 $urls = array();
376
377 // if sitemap url is empty then use main sitemap file name.
378 $sitemap_url = ('' === $sitemap_url) ? site_url('/'.$this->get_sitemap_filename()) : $sitemap_url;
379
380 // if simplexml_load_string not available then we don't load sitemap.
381 if (!function_exists('simplexml_load_string')) {
382 return $urls;
383 }
384
385 // load sitemap file.
386 $response = wp_remote_get($sitemap_url, array('timeout' => 30));
387
388 // if we get error then
389 if (is_wp_error($response)) {
390 $response = file_get_contents($sitemap_url);
391
392 // if response is empty then try load from file.
393 if (empty($response) && '' == $sitemap_url) {
394 $sitemap_file = $this->get_local_sitemap_file();
395
396 $response = file_get_contents($sitemap_file);
397 }
398
399 if (empty($response)) return $urls;
400
401 $xml = @simplexml_load_string($response); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warnings when error found in XML data
402 } else {
403 // parse xml answer.
404 $xml = @simplexml_load_string(wp_remote_retrieve_body($response)); // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress warnings when error found in XML data
405 }
406
407 // xml file has not valid xml content then return false.
408 if (false === $xml) return false;
409
410 // if exists urls then return them.
411 if (isset($xml->url)) {
412 foreach ($xml->url as $element) {
413 if (!isset($element->loc)) continue;
414 $urls[] = (string) $element->loc;
415 }
416 } elseif (isset($xml->sitemap)) {
417 // if has links to other sitemap files then get urls from them.
418 foreach ($xml->sitemap as $element) {
419 if (!isset($element->loc)) continue;
420
421 $sitemap_urls = $this->get_sitemap_urls($element->loc);
422
423 if (is_array($sitemap_urls)) {
424 $urls = array_merge($urls, $sitemap_urls);
425 }
426 }
427 }
428
429 return $urls;
430 }
431
432 /**
433 * Get the path to a local sitemap file
434 *
435 * @return string
436 */
437 protected function get_local_sitemap_file() {
438 global $wp_file_descriptions; // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Make the scope of $wp_file_descriptions global, so that when wp-admin/includes/file.php assigns to it, it is adjusting the global variable as intended
439
440 if (!function_exists('get_home_path')) {
441 include_once ABSPATH . '/wp-admin/includes/file.php';
442 }
443 return trailingslashit(get_home_path()) . $this->get_sitemap_filename();
444 }
445
446 /**
447 * Get all posts of any post type and returns urls for them.
448 *
449 * @return array
450 */
451 public function get_post_urls() {
452 global $post;
453
454 $offset = 0;
455 $posts_per_page = 1000;
456 $urls = array();
457
458 $urls[] = site_url('/');
459
460 do {
461 $query = new WP_Query(array(
462 'post_type' => 'any',
463 'post_status' => 'publish',
464 'posts_per_page' => $posts_per_page,
465 'offset' => $offset,
466 'orderby' => 'ID',
467 'order' => 'ASC',
468 'cache_results' => false, // disable cache to avoid memory error.
469 ));
470
471 $posts_loaded = $query->post_count;
472
473 while ($query->have_posts()) {
474 $query->the_post();
475 $permalink = get_permalink();
476 $urls[] = $permalink;
477
478 // check page separators in the post content
479 preg_match_all('/\<\!--nextpage--\>/', $post->post_content, $matches);
480 // if there any separators add urls for each page
481 if (count($matches[0])) {
482 $prefix = strpos($permalink, '?') ? '&page=' : '';
483 for ($page = 0; $page < count($matches[0]); $page++) {
484 if ('' != $prefix) {
485 $urls[] = $permalink . $prefix . ($page+2);
486 } else {
487 $urls[] = trailingslashit($permalink) . ($page+2);
488 }
489 }
490 }
491 }
492
493 $offset += $posts_loaded;
494 } while ($posts_loaded > 0);
495
496 /**
497 * If domain mapping enabled then replace domains in urls.
498 */
499 if ($this->is_domain_mapping_enabled()) {
500 $blog_id = get_current_blog_id();
501
502 $mapped_domain = $this->get_mapped_domain($blog_id);
503 $blog_details = get_blog_details($blog_id);
504
505 if ($mapped_domain) {
506 foreach ($urls as $i => $url) {
507 $urls[$i] = preg_replace('/'.$blog_details->domain.'/i', $mapped_domain, $url, 1);
508 }
509 }
510 }
511
512 wp_reset_postdata();
513
514 return $urls;
515 }
516
517 /**
518 * Check if domain mapping enabled.
519 *
520 * @return bool
521 */
522 public function is_domain_mapping_enabled() {
523 // SUNRISE constant is defined with installation WordPress MU Domain Mapping plugin.
524 $enabled = is_multisite() && defined('SUNRISE') && 'on' == strtolower(SUNRISE);
525
526 /**
527 * Filters if Multisite Domain mapping is enabled.
528 * Currently, we can only detect if the WordPress MU Domain Mapping plugin is in use.
529 * Using the WP Core functionality should not require this, unless if the domain name is set somewhere else but in the site url option.
530 */
531 return apply_filters('wpo_is_domain_mapping_enabled', $enabled);
532 }
533
534 /**
535 * Return mapped domain by $blog_id.
536 *
537 * @param int $blog_id
538 *
539 * @return string
540 */
541 public function get_mapped_domain($blog_id) {
542 global $wpdb;
543
544 $domain = '';
545 $multisite_plugin_table_name = $wpdb->base_prefix.'domain_mapping';
546 // Check if table exists
547 if ($wpdb->get_var("SHOW TABLES LIKE '$multisite_plugin_table_name'") != $multisite_plugin_table_name) {
548 // This table created in WordPress MU Domain Mapping plugin.
549 $row = $wpdb->get_row("SELECT `domain` FROM {$multisite_plugin_table_name} WHERE `blog_id` = {$blog_id} AND `active` = 1", ARRAY_A);
550 if (!empty($row)) {
551 $domain = $row['domain'];
552 }
553 } else {
554 // When using the WP Core method, the site url option contains the mapped domain.
555 $domain = get_site_url($blog_id);
556 }
557
558 /**
559 * Filters the mapped domain name
560 *
561 * @param string $domain The domain name
562 * @param integer $blog_id The blog ID
563 */
564 return apply_filters('wpo_get_mapped_domain', $domain, $blog_id);
565 }
566
567 /**
568 * Captures and logs any interesting messages
569 *
570 * @param String $message - the error message
571 * @param String $error_type - the error type
572 */
573 public function log($message, $error_type = 'info') {
574
575 if (isset($this->loggers)) {
576 foreach ($this->loggers as $logger) {
577 $logger->log($message, $error_type);
578 }
579 }
580 }
581
582 /**
583 * Check if semaphore is locked.
584 *
585 * @param string $semaphore
586 * @return bool
587 */
588 protected function is_semaphore_locked($semaphore) {
589 $semaphore = new Updraft_Semaphore_3_0($semaphore);
590 if ($semaphore->lock()) {
591 $semaphore->release();
592 return false;
593 }
594 return true;
595 }
596
597 abstract protected function is_option_active();
598
599 abstract protected function get_option_disabled_error();
600
601 abstract protected function get_preload_already_running_error();
602
603 abstract protected function create_tasks_for_preload_site_urls($type);
604
605 abstract protected function get_preload_data();
606
607 abstract protected function get_preloading_message($data);
608
609 abstract protected function get_last_preload_message($data, $last_preload_time_str);
610
611 abstract protected function get_preload_success_message($data);
612
613 abstract protected function get_preload_progress_message($data, $preloaded_message, $preload_resuming_in);
614 }
615