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

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