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 / 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.2.21, at includes/class-wp-optimize-preloader.php

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