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

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