PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.6.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.6.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 3.8.0 All 110 releases
wp-optimize / wp-optimize.php

wp-optimize.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 4.6.0, at wp-optimize.php

2,175 lines 71.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 Plugin Name: WP-Optimize - Clean, Compress, Cache
4 Plugin URI: https://teamupdraft.com/wp-optimize
5 Description: WP-Optimize makes your site fast and efficient. It cleans the database, compresses images and caches pages. Fast sites attract more traffic and users.
6 Version: 4.6.0
7 Requires at least: 4.9
8 Requires PHP: 7.2
9 Update URI: https://wordpress.org/plugins/wp-optimize/
10 Author: TeamUpdraft, DavidAnderson
11 Author URI: https://teamupdraft.com/wp-optimize?utm_source=wpo-wp-dir&utm_medium=referral&utm_campaign=plugin-dir&utm_content=teamupdraft&utm_creative_format=author
12 Text Domain: wp-optimize
13 Domain Path: /languages
14 License: GPLv2 or later
15 */
16
17 if (!defined('ABSPATH')) die('No direct access allowed');
18
19 // Check to make sure if WP_Optimize is already call and returns.
20 if (!class_exists('WP_Optimize')) :
21 define('WPO_VERSION', '4.6.0');
22 define('WPO_PLUGIN_URL', plugin_dir_url(__FILE__));
23 define('WPO_PLUGIN_MAIN_PATH', plugin_dir_path(__FILE__));
24 define('WPO_PLUGIN_SLUG', plugin_basename(__FILE__));
25 define('WPO_PREMIUM_NOTIFICATION', false);
26 define('WPO_REQUIRED_PHP_VERSION', '7.2');
27 define('WPO_REQUIRED_WP_VERSION', '4.9');
28 if (!defined('WPO_USE_WEBP_CONVERSION')) define('WPO_USE_WEBP_CONVERSION', true);
29 require_once(WPO_PLUGIN_MAIN_PATH.'includes/fragments/input-processing.php');
30
31 class WP_Optimize {
32
33 public $premium_version_link = 'https://teamupdraft.com/wp-optimize/pricing/?utm_source=wpo-plugin&utm_medium=referral&utm_campaign=paac&utm_creative_format=overlay';
34
35 private $template_directories;
36
37 protected static $_instance = null;
38
39 protected $_cache_init_status = null;
40
41 /**
42 * An array of schedule types
43 *
44 * @param boolean $placeholder Determines whether a placeholder select option should be included or not
45 *
46 * @return array An array of schedule types
47 */
48 public static function get_schedule_types($placeholder = false) {
49 $schedule_types = array(
50 'wpo_daily' => __('Daily', 'wp-optimize'),
51 'wpo_weekly' => __('Weekly', 'wp-optimize'),
52 'wpo_fortnightly' => __('Fortnightly', 'wp-optimize'),
53 'wpo_monthly' => __('Monthly', 'wp-optimize'),
54 );
55
56 $schedule_types = apply_filters('wp_optimize_schedule_types', $schedule_types);
57 if (true === $placeholder) {
58 $schedule_types = array_merge(array('' => __('Select schedule', 'wp-optimize')), $schedule_types);
59 }
60 return $schedule_types;
61 }
62
63 /**
64 * Class constructor
65 */
66 public function __construct() {
67
68 spl_autoload_register(array($this, 'loader'));
69
70 // Don't process when accessing without normal WordPress core loading.
71 if (!defined('WPINC')) return;
72
73 $bypass_instance = $this->get_bypass_instance();
74 if ($bypass_instance->should_bypass()) {
75 // Show a bypass notice in the admin bar
76 $bypass_instance->show_admin_notice();
77 if (!headers_sent()) {
78 header('WPO-Bypass-Mode: active');
79 }
80 return;
81 }
82
83 // Checks if premium is installed along with plugins needed.
84 add_action('plugins_loaded', array($this, 'plugins_loaded'), 1);
85
86 register_activation_hook(__FILE__, array('WPO_Activation', 'actions'));
87 register_deactivation_hook(__FILE__, array('WPO_Deactivation', 'actions'));
88 register_uninstall_hook(__FILE__, array('WPO_Uninstall', 'actions'));
89
90 WPO_Page_Optimizer::instance()->maybe_initialise();
91
92 $this->load_admin();
93 add_action('admin_init', array($this, 'admin_init'));
94
95 add_filter("plugin_action_links_".plugin_basename(__FILE__), array($this, 'plugin_settings_link'));
96 add_action('wpo_cron_event2', array($this, 'cron_action'));
97 add_filter('cron_schedules', array($this, 'cron_schedules'));
98
99 if (!$this->get_options()->get_option('installed-for', false)) $this->get_options()->update_option('installed-for', time());
100
101 add_action('admin_footer-upload.php', array($this, 'add_smush_popup_template'));
102
103 add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
104
105 add_action('wp_enqueue_scripts', array($this, 'frontend_enqueue_scripts'));
106
107 if ($this->get_options()->get_option('404_detector', 0)) {
108 WP_Optimize_Performance::get_instance($this->get_404_detector())->hook_404_handler();
109 }
110
111 $this->load_ajax_handler();
112
113 // Show update to Premium notice for non-premium multisite.
114 add_action('wpo_additional_options', array($this, 'show_multisite_update_to_premium_notice'));
115
116 // Action column (show repair button, if needed).
117 add_filter('wpo_tables_list_additional_column_data', array($this, 'tables_list_additional_column_data'), 15, 2);
118
119 /**
120 * Add action for display Images > Compress images tab.
121 */
122 add_action('wp_optimize_admin_page_wpo_images_smush', array($this, 'admin_page_wpo_images_smush'));
123
124 include_once(WPO_PLUGIN_MAIN_PATH.'includes/updraftcentral.php');
125
126 include_once(WPO_PLUGIN_MAIN_PATH.'includes/backward-compatibility-functions.php');
127
128 register_shutdown_function(array($this, 'log_fatal_errors'));
129
130 add_action('wpo_admin_before_closing_wrap', array($this, 'load_modal_template'), 20);
131
132 add_action('upgrader_process_complete', array($this, 'detect_active_plugins_and_themes_updates'), 10, 2);
133
134 $import_done_hooks = array(
135 'import_end', // WordPress importer
136 'pmxi_after_xml_import', // wp all import
137 );
138
139 $db_update_hooks = apply_filters('wp_optimize_db_update_hooks', $import_done_hooks);
140
141 foreach ($db_update_hooks as $hook) {
142 add_action($hook, array($this, 'maybe_schedule_update_record_count_event'));
143 }
144 add_action('wpo_update_record_count_event', array($this->get_db_info(), 'wpo_update_record_count'));
145
146 add_action('wpo_reset_stats_counter', array($this, 'reset_stats_counters'));
147
148 $this->schedule_reset_stats_counters_event();
149
150 }
151
152 /**
153 * Reset stats counter at the start of each month
154 */
155 public function reset_stats_counters() {
156 // Save current total cleaned as previous month data
157 $options = $this->get_options();
158 $last_month_total_cleaned = $options->get_option('total-cleaned-current-month');
159 $options->update_option('total-cleaned-previous-month', $last_month_total_cleaned);
160 $options->update_option('total-cleaned-current-month', '0');
161 $this->schedule_reset_stats_counters_event();
162 }
163
164 /**
165 * Schedule the reset of stat's counter at the start of each month
166 */
167 private function schedule_reset_stats_counters_event() {
168 if (!wp_next_scheduled('wpo_reset_stats_counter')) {
169 $next_reset = strtotime('first day of next month midnight');
170 wp_schedule_single_event($next_reset, 'wpo_reset_stats_counter');
171 }
172 }
173
174 /**
175 * Returns Onboarding class instance
176 *
177 * @return WPO_Onboarding
178 */
179 public function get_onboarding() {
180 return WPO_Onboarding::instance();
181 }
182
183 /**
184 * Check if minimum requirements for WP-Optimize is met or not.
185 *
186 * @return bool
187 */
188 public function is_minimum_requirement_met() {
189 global $wp_version;
190 return (version_compare(WPO_REQUIRED_PHP_VERSION, PHP_VERSION, '<=') && version_compare(WPO_REQUIRED_WP_VERSION, $wp_version, '<='));
191 }
192
193 /**
194 * Add admin notice about minimum server requirements.
195 */
196 public function add_notice_minimum_requirements_not_met() {
197 add_action('admin_notices', array($this, 'output_minimum_requirements_notice'));
198 }
199
200 /**
201 * Deactivate the plugin programmatically
202 */
203 public function deactivate_plugin() {
204 if (!function_exists('deactivate_plugins')) {
205 require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
206 }
207 deactivate_plugins(plugin_basename(__FILE__));
208 }
209
210 /**
211 * Die after stating minimum requirements
212 */
213 public function die_minimum_requirement_not_met() {
214 $message = $this->get_minimum_requirements_notice_message();
215 $message .= ' <a href="'.esc_url(admin_url('plugins.php')).'">'.esc_html__('Back to Plugins.', 'wp-optimize').'</a>';
216 wp_die(wp_kses_post($message));
217 }
218
219 /**
220 * Autoloads classes.
221 *
222 * @param string $class_name The name of the class.
223 */
224 public function loader($class_name) {
225 $dirs = $this->get_class_directories();
226
227 foreach ($dirs as $dir) {
228 $class_file = WPO_PLUGIN_MAIN_PATH . trailingslashit($dir) . 'class-' . str_replace('_', '-', strtolower($class_name)) . '.php';
229 if (file_exists($class_file)) {
230 require_once($class_file);
231 return;
232 }
233
234 $interface_file = WPO_PLUGIN_MAIN_PATH . trailingslashit($dir) . 'interface-' . str_replace('_', '-', strtolower($class_name)) . '.php';
235 if (file_exists($interface_file)) {
236 require_once($interface_file);
237 return;
238 }
239 }
240
241 // Include PHP Minify - https://github.com/matthiasmullie/minify
242 if (strpos($class_name, 'MatthiasMullie') !== false) {
243 $class_name_parts = explode('\\', $class_name);
244 $class_file = WPO_PLUGIN_MAIN_PATH.'vendor/'.strtolower($class_name_parts[0]).'/'.strtolower(preg_replace('/([a-z])([A-Z])/', '$1-$2', $class_name_parts[1])).'/src/'.implode('/', array_slice($class_name_parts, 2)).'.php';
245 if (file_exists($class_file)) {
246 require_once($class_file);
247 return;
248 }
249 }
250
251 if ('Minify_HTML' === $class_name) {
252 require_once WPO_PLUGIN_MAIN_PATH.'vendor/mrclay/minify/lib/Minify/HTML.php';
253 }
254 }
255
256 /**
257 * Returns an array of class directories
258 *
259 * @return array
260 */
261 private function get_class_directories() {
262 return array(
263 'cache',
264 'compatibility',
265 'includes',
266 'includes/tables',
267 'includes/list-tables',
268 'includes/gravatars',
269 'includes/lcp',
270 'minify',
271 'optimizations',
272 'webp',
273 );
274 }
275
276 /**
277 * Initialize Admin class to load admin UI
278 */
279 private function load_admin() {
280 $this->get_admin_instance();
281 }
282
283 /**
284 * Returns Bypass class instance
285 *
286 * @return WP_Optimize_Bypass
287 */
288 private function get_bypass_instance() {
289 return WP_Optimize_Bypass::instance();
290 }
291
292 /**
293 * Returns Admin class instance
294 *
295 * @return WP_Optimize_Admin
296 */
297 public function get_admin_instance() {
298 return WP_Optimize_Admin::instance();
299 }
300
301 /**
302 * Detect when an active plugin or theme is updated, and trigger an action
303 *
304 * @param object $upgrader_object
305 * @param array $options
306 * @return void
307 */
308 public function detect_active_plugins_and_themes_updates($upgrader_object, $options) {
309 if (empty($options) || !isset($options['type'])) return;
310
311 $should_purge_cache = false;
312 $skin = $upgrader_object->skin;
313 if ('plugin' === $options['type']) {
314 // A plugin is updated using the default update system (upgrader_overwrote_package is used for the upload method)
315 $plugins = array();
316 if (!empty($options['plugin'])) {
317 $plugins = array($options['plugin']);
318 } elseif (!empty($options['plugins']) && is_array($options['plugins'])) {
319 $plugins = $options['plugins'];
320 }
321
322 foreach ($plugins as $plugin) {
323 if (!empty($plugin) && ! is_wp_error($upgrader_object->result)) {
324 if (is_plugin_active($plugin)) {
325 $should_purge_cache = true;
326 break;
327 }
328 }
329 }
330 } elseif ('theme' === $options['type']) {
331 $active_theme = get_stylesheet();
332 $parent_theme = get_template();
333 // A theme is updated using the upload system
334 if (isset($options['action']) && 'install' === $options['action'] && isset($skin->options['overwrite']) && 'update-theme' === $skin->options['overwrite']) {
335 $updated_theme = $upgrader_object->result['destination_name'];
336 // Check if the theme is in use
337 if ($active_theme === $updated_theme || $parent_theme === $updated_theme) {
338 $should_purge_cache = true;
339 }
340 // A theme is updated using the classic update system
341 } elseif (isset($options['action']) && 'update' === $options['action'] && isset($options['themes']) && is_array($options['themes'])) {
342 // Check if the theme is in use
343 if (in_array($active_theme, $options['themes']) || in_array($parent_theme, $options['themes'])) {
344 $should_purge_cache = true;
345 }
346 }
347 }
348
349 /**
350 * Action executed when an active theme or plugin was updated
351 */
352 if ($should_purge_cache) do_action('wpo_active_plugin_or_theme_updated');
353
354 }
355
356 /**
357 * Sets a flag to indicate an import action is done, if needed
358 */
359 public function maybe_schedule_update_record_count_event() {
360 if (!wp_next_scheduled('wpo_update_record_count_event')) {
361 wp_schedule_single_event(time() + 60, 'wpo_update_record_count_event');
362 }
363 }
364
365 public function admin_page_wpo_images_smush() {
366 $options = Updraft_Smush_Manager()->get_smush_options();
367 $custom = 90 >= $options['image_quality'] && 65 <= $options['image_quality'];
368 $sites = $this->get_sites();
369 $compression_server_hint = Updraft_Smush_Manager()->get_compression_server_hint();
370 $this->include_template('images/smush.php', false, array('smush_options' => $options, 'custom' => $custom, 'sites' => $sites, 'does_server_allow_local_webp_conversion' => $this->get_server_compatibility_instance()->does_server_allow_local_webp_conversion(), 'compression_server_hint' => $compression_server_hint));
371 $this->add_smush_popup_template();
372 }
373
374 public static function instance() {
375 if (empty(self::$_instance)) {
376 self::$_instance = new self();
377 }
378 return self::$_instance;
379 }
380
381 public function get_optimizer() {
382 return WP_Optimizer::instance();
383 }
384
385 /**
386 * Adds 3rd party plugin compatibilities.
387 */
388 public function load_compatibilities() {
389
390 if (class_exists('Polylang')) {
391 WPO_Polylang_Compatibility::instance();
392 }
393
394 WPO_Page_Builder_Compatibility::instance();
395 WPO_KD_Submissions_Compatibility::instance();
396
397 if (class_exists('Custom_Permalinks')) {
398 WPO_Custom_Permalink_Compatibility::instance();
399 }
400
401 if (class_exists('TRP_Translate_Press')) {
402 WPO_TranslatePress_Compatibility::instance();
403 }
404
405 do_action('wpo_load_compatibilities');
406 }
407
408 /**
409 * Get and instantiate WP_Optimize_Minify
410 *
411 * @return WP_Optimize_Minify
412 */
413 public function get_minify() {
414 return WP_Optimize_Minify::instance();
415 }
416
417 public function get_options() {
418 return WP_Optimize_Options::instance();
419 }
420
421 public function get_notices() {
422 return WP_Optimize_Notices::instance();
423 }
424
425 /**
426 * Get and instantiate WP_Optimize_Delay_JS
427 *
428 * @return WP_Optimize_Delay_JS
429 */
430 private function get_delay_js() {
431 return WP_Optimize_Delay_JS::instance();
432 }
433
434 /**
435 * Returns instance of WPO_Page_Optimizer class.
436 *
437 * @return WPO_Page_Optimizer
438 */
439 public function get_page_optimizer() {
440 return WPO_Page_Optimizer::instance();
441 }
442
443 /**
444 * Returns instance 404 Detector Cron class.
445 *
446 * @return WP_Optimize_404_Detector_Cron
447 */
448 private function get_404_detector_cron() {
449 return WP_Optimize_404_Detector_Cron::get_instance();
450 }
451
452 /**
453 * Returns instance of WPO_Page_Cache class.
454 *
455 * @return WPO_Page_Cache
456 */
457 public function get_page_cache() {
458 return WPO_Page_Cache::instance();
459 }
460
461 /**
462 * Returns instance of WP_Optimize_WebP class.
463 *
464 * @return WP_Optimize_WebP
465 */
466 public function get_webp_instance() {
467 return WP_Optimize_WebP::get_instance();
468 }
469
470 /**
471 * Returns instance of WP_Optimize_Server_Compatibility class.
472 *
473 * @return WP_Optimize_Server_Compatibility
474 */
475 public function get_server_compatibility_instance(): WP_Optimize_Server_Compatibility {
476 return WP_Optimize_Server_Compatibility::get_instance();
477 }
478
479 /**
480 * Create instance of WP_Optimize_Browser_Cache.
481 *
482 * @return WP_Optimize_Browser_Cache
483 */
484 public function get_browser_cache() {
485 return WP_Optimize_Browser_Cache::instance();
486 }
487
488 /**
489 * Returns WP_Optimize_Database_Information instance.
490 *
491 * @return WP_Optimize_Database_Information
492 */
493 public function get_db_info() {
494 return WP_Optimize_Database_Information::instance();
495 }
496
497 /**
498 * Returns instance of WP_Optimize_Gzip_Compression.
499 *
500 * @return WP_Optimize_Gzip_Compression
501 */
502 public function get_gzip_compression() {
503 return WP_Optimize_Gzip_Compression::instance();
504 }
505
506 /**
507 * Create instance of WP_Optimize_Htaccess.
508 *
509 * @param string $htaccess_file absolute path to htaccess file, by default it use .htaccess in WordPress root directory.
510 * @return WP_Optimize_Htaccess
511 */
512 public static function get_htaccess($htaccess_file = '') {
513 return new WP_Optimize_Htaccess($htaccess_file);
514 }
515
516 /**
517 * Return instance of Updraft_Logger
518 *
519 * @return Updraft_Logger
520 */
521 public function get_logger() {
522 return Updraft_Logger::instance();
523 }
524
525 /**
526 * Check if the current page belongs to WP-Optimize.
527 *
528 * @return bool
529 */
530 public function is_wpo_page() {
531 $current_screen = get_current_screen();
532
533 return (bool) preg_match('/wp\-optimize/i', $current_screen->id);
534 }
535
536 /**
537 * Enqueue scripts and styles on WP-Optimize pages.
538 */
539 public function admin_enqueue_scripts() {
540 global $wp_version;
541 $enqueue_version = $this->get_enqueue_version();
542 $min_or_not = $this->get_min_or_not_string();
543 $min_or_not_internal = $this->get_min_or_not_internal_string();
544
545 // Register or enqueue common scripts
546 wp_register_script('wp-optimize-send-command', WPO_PLUGIN_URL.'js/send-command'.$min_or_not_internal.'.js', array(), $enqueue_version);
547 wp_localize_script('wp-optimize-send-command', 'wp_optimize_send_command_data', array('nonce' => wp_create_nonce('wp-optimize-ajax-nonce')));
548 wp_register_script('wp-optimize-block-ui', WPO_PLUGIN_URL.'js/blockUI'.$min_or_not_internal.'.js', array('jquery'), $enqueue_version);
549 wp_enqueue_style('wp-optimize-global', WPO_PLUGIN_URL.'css/wp-optimize-global'.$min_or_not_internal.'.css', array(), $enqueue_version);
550
551 // load scripts and styles only on WP-Optimize pages.
552 if (!$this->is_wpo_page()) return;
553
554 wp_enqueue_script('jquery-serialize-json', WPO_PLUGIN_URL.'js/serialize-json/jquery.serializejson'.$min_or_not.'.js', array('jquery'), $enqueue_version);
555
556 wp_register_script('updraft-queue-js', WPO_PLUGIN_URL.'js/queue'.$min_or_not_internal.'.js', array(), $enqueue_version);
557
558 wp_enqueue_script('wp-optimize-modal', WPO_PLUGIN_URL.'js/modal'.$min_or_not_internal.'.js', array('jquery', 'backbone', 'wp-util'), $enqueue_version);
559 wp_enqueue_script('wp-optimize-cache-js', WPO_PLUGIN_URL.'js/cache'.$min_or_not_internal.'.js', array('wp-optimize-send-command', 'smush-js', 'wp-optimize-heartbeat-js', 'wp-optimize-block-ui'), $enqueue_version);
560 wp_enqueue_script('wp-optimize-admin-js', WPO_PLUGIN_URL.'js/wpoadmin'.$min_or_not_internal.'.js', array('jquery', 'updraft-queue-js', 'wp-optimize-send-command', 'smush-js', 'wp-optimize-modal', 'wp-optimize-cache-js', 'wp-optimize-heartbeat-js'), $enqueue_version);
561 wp_enqueue_style('wp-optimize-admin-css', WPO_PLUGIN_URL.'css/wp-optimize-admin'.$min_or_not_internal.'.css', array(), $enqueue_version);
562 // Using table sorter to help with organising the DB size on Table Information
563 // https://github.com/tofsjonas/sortable/
564 wp_enqueue_script('sortable-js', WPO_PLUGIN_URL.'js/sortable/sortable'.$min_or_not.'.js', array('wp-optimize-send-command'), $enqueue_version);
565 wp_enqueue_script('sortable-a11y-js', WPO_PLUGIN_URL.'js/sortable/sortable.a11y'.$min_or_not.'.js', array('wp-optimize-send-command'), $enqueue_version);
566
567 wp_enqueue_style('sortable-css', WPO_PLUGIN_URL.'css/sortable/sortable'.$min_or_not_internal.'.css', array(), $enqueue_version);
568
569 $js_variables = $this->wpo_js_translations();
570 $js_variables['loggers_classes_info'] = $this->get_loggers_classes_info();
571 wp_localize_script('wp-optimize-admin-js', 'wpoptimize', $js_variables);
572
573 do_action('wpo_premium_scripts_styles', $min_or_not_internal, $min_or_not, $enqueue_version);
574
575 $status_report_dependencies = array('wp-optimize-admin-js');
576
577 // Only include wp-api-fetch if WP >= 5.0
578 if (version_compare($wp_version, '5.0', '>=')) {
579 $status_report_dependencies[] = 'wp-api-fetch';
580 }
581
582 wp_enqueue_script('wp-optimize-status-report', WPO_PLUGIN_URL.'js/status'.$min_or_not_internal.'.js', $status_report_dependencies, $enqueue_version);
583
584 wp_enqueue_script('js-zip', WPO_PLUGIN_URL.'/js/jszip/jszip' . $min_or_not . '.js', array(), $enqueue_version);
585
586 }
587
588 /**
589 * Enqueue any required front-end scripts
590 *
591 * @return void
592 */
593 public function frontend_enqueue_scripts() {
594 if (!$this->current_user_can('manage_options') || !is_admin_bar_showing()) return;
595 $enqueue_version = $this->get_enqueue_version();
596 $min_or_not_internal = $this->get_min_or_not_internal_string();
597
598 // Register or enqueue common scripts
599 wp_enqueue_style('wp-optimize-global', WPO_PLUGIN_URL.'css/wp-optimize-global'.$min_or_not_internal.'.css', array(), $enqueue_version);
600 }
601
602 /**
603 * Load Task Manager
604 *
605 * @return Updraft_Smush_Manager
606 */
607 public function get_task_manager() {
608 include_once(WPO_PLUGIN_MAIN_PATH.'vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-tasks-activation.php');
609
610 Updraft_Tasks_Activation::check_updates();
611
612 include_once(WPO_PLUGIN_MAIN_PATH . 'vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-meta.php');
613 include_once(WPO_PLUGIN_MAIN_PATH . 'vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task-options.php');
614 include_once(WPO_PLUGIN_MAIN_PATH . 'vendor/team-updraft/common-libs/src/updraft-tasks/class-updraft-task.php');
615
616 include_once(WPO_PLUGIN_MAIN_PATH . 'includes/class-updraft-smush-task.php');
617 include_once(WPO_PLUGIN_MAIN_PATH . 'includes/class-updraft-smush-manager.php');
618
619 return Updraft_Smush_Manager();
620 }
621
622 /**
623 * Indicate whether we have an associated instance of WP-Optimize Premium or not.
624 *
625 * @returns bool
626 */
627 public static function is_premium() {
628 if (file_exists(WPO_PLUGIN_MAIN_PATH.'premium.php') && function_exists('WP_Optimize_Premium')) {
629 $wp_optimize_premium = WP_Optimize_Premium();
630 if (is_a($wp_optimize_premium, 'WP_Optimize_Premium')) return true;
631 }
632 return false;
633 }
634
635 /**
636 * Check if script running on Apache web server. $is_apache is set in wp-includes/vars.php. Also returns true if the server uses litespeed.
637 *
638 * @return bool
639 */
640 public function is_apache_server() {
641 global $is_apache;
642 return (bool) $is_apache;
643 }
644
645 /**
646 * Check if script running on IIS web server.
647 *
648 * @return bool
649 */
650 public function is_IIS_server() {
651 global $is_IIS, $is_iis7;
652 return $is_IIS || $is_iis7;
653 }
654
655 /**
656 * Check if Apache module or modules active.
657 *
658 * @param string|array $module - single Apache module name or list of Apache module names.
659 *
660 * @return bool|null - if null, the result was indeterminate
661 */
662 public function is_apache_module_loaded($module) {
663 if (!$this->is_apache_server()) return false;
664
665 if (!function_exists('apache_get_modules')) return null;
666
667 $module_loaded = true;
668
669 if (is_array($module)) {
670 foreach ($module as $single_module) {
671 if (!in_array($single_module, apache_get_modules())) {
672 $module_loaded = false;
673 break;
674 }
675 }
676 } else {
677 $module_loaded = in_array($module, apache_get_modules());
678 }
679
680 return $module_loaded;
681 }
682
683 /**
684 * Checks if this is the premium version and loads it. It also ensures that if the free version is installed then it is disabled with an appropriate error message.
685 */
686 public function plugins_loaded() {
687
688 if (!$this->is_minimum_requirement_met()) {
689 $this->add_notice_minimum_requirements_not_met();
690 $this->deactivate_plugin();
691 WPO_Deactivation::actions();
692 return;
693 }
694
695 if (is_admin() && $this->current_user_can()) {
696 WP_Optimize_Heartbeat::get_instance();
697 }
698
699 if ($this->get_server_compatibility_instance()->does_server_handle_cache()) {
700 add_filter('wp_optimize_admin_page_wpo_cache_tabs', array($this, 'filter_cache_tabs'), 99, 1);
701
702 // If newly migrated to a server that handles cache, disable wpo cache
703 $cache = $this->get_page_cache();
704 if ($cache->is_enabled()) {
705 $cache->disable();
706 }
707 }
708
709 add_filter('robots_txt', array($this, 'robots_txt'), 99, 1);
710
711 // Run Premium loader if it exists
712 if (file_exists(WPO_PLUGIN_MAIN_PATH.'premium.php') && !class_exists('WP_Optimize_Premium')) {
713 include_once(WPO_PLUGIN_MAIN_PATH.'premium.php');
714 }
715
716 if (!self::is_premium()) {
717 $this->get_server_compatibility_instance()->maybe_disable_unsupported_table_optimization();
718 add_action('auto_option_settings', array($this->get_options(), 'auto_option_settings'));
719 }
720
721 // load defaults
722 $this->get_options()->set_default_options();
723
724 // Initialize loggers.
725 add_action('init', array($this, 'setup_loggers'));
726
727 if ($this->is_active('premium') && false !== ($free_plugin = $this->is_active('free'))) {
728 if (!function_exists('deactivate_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
729 deactivate_plugins($free_plugin);
730
731 // If WPO_ADVANCED_CACHE is defined, we empty advanced-cache.php to regenerate later. Otherwise, it contains the path to free.
732 if (defined('WPO_ADVANCED_CACHE') && WPO_ADVANCED_CACHE) {
733 $advanced_cache_filename = trailingslashit(WP_CONTENT_DIR) . 'advanced-cache.php';
734
735 if (!is_file($advanced_cache_filename) && wp_is_writable(dirname($advanced_cache_filename)) || (is_file($advanced_cache_filename) && wp_is_writable($advanced_cache_filename))) {
736 file_put_contents($advanced_cache_filename, '');
737 }
738 }
739
740 // Registers the notice letting the user know it cannot be active if premium is active.
741 add_action('admin_notices', array($this, 'show_admin_notice_premium'));
742 return;
743 }
744
745 add_action('init', array($this, 'init'));
746
747 // add_filter('updraftcentral_host_plugins', array($this, 'attach_updraftcentral_host'));
748 // if (file_exists(WPO_PLUGIN_MAIN_PATH.'central/factory.php')) include_once(WPO_PLUGIN_MAIN_PATH.'central/factory.php');
749 }
750
751 /**
752 * Methods needed to be run in init hook.
753 *
754 * @return void
755 */
756 public function init() {
757 $this->load_language_file();
758 $this->schedule_plugin_cron_tasks();
759 $this->init_page_cache();
760 $this->get_minify();
761 $this->get_delay_js();
762
763 // Load 3rd party plugin compatibilities.
764 $this->load_compatibilities();
765
766 $this->run_updates();
767
768 // We need this here because webp can be unavailable because of server moves
769 // This deletes already converted webp images and original image file when a media is deleted
770 WP_Optimize_WebP_Images::get_instance();
771
772 // Loads the task manager
773 $this->get_task_manager();
774
775 // Include WebP
776 if (WPO_USE_WEBP_CONVERSION) {
777 $this->get_webp_instance();
778 }
779
780 $this->get_onboarding()->init();
781 }
782
783 /**
784 * Loads the language file.
785 *
786 * @return void
787 */
788 private function load_language_file() {
789 load_plugin_textdomain('wp-optimize', false, dirname(plugin_basename(__FILE__)) . '/languages');
790 }
791
792 /**
793 * Attach this wp-optimize plugin as host of the UpdraftCentral libraries
794 * (e.g. "central" folder)
795 *
796 * @param array $hosts List of plugins having the "central" library integrated into them
797 *
798 * @return array
799 */
800 public function attach_updraftcentral_host($hosts) {
801 $hosts[] = 'wp-optimize';
802 return $hosts;
803 }
804
805 /**
806 * Filter cache tabs (when the server handles it or do not allow it, like Kinsta)
807 *
808 * @param array $tabs An array of tabs
809 *
810 * @return array $tabs An array of tabs
811 */
812 public function filter_cache_tabs($tabs) {
813 unset($tabs['preload']);
814 unset($tabs['advanced']);
815 unset($tabs['gzip']);
816 unset($tabs['settings']);
817 return $tabs;
818 }
819
820 /**
821 * Check whether one of free/Premium is active (whether it is this instance or not)
822 *
823 * @param string $which - 'free' or 'premium'
824 *
825 * @return string|boolean - plugin path (if installed) or false if not
826 */
827 public function is_active($which = 'free') {
828 $active_plugins = $this->get_active_plugins();
829 foreach ($active_plugins as $file) {
830 if ('wp-optimize.php' == basename($file)) {
831 $plugin_dir = WP_PLUGIN_DIR.'/'.dirname($file);
832 if (('free' === $which && !file_exists($plugin_dir.'/premium.php')) || ('free' !== $which && file_exists($plugin_dir.'/premium.php'))) return $file;
833 }
834 }
835 return false;
836 }
837
838 /**
839 * Gets an array of plugins active on either the current site, or site-wide
840 *
841 * @return array - a list of plugin paths (relative to the plugin directory)
842 */
843 private function get_active_plugins() {
844 // Gets all active plugins on the current site
845 $active_plugins = (array) get_option('active_plugins', array());
846
847 if (is_multisite()) {
848 $network_active_plugins = get_site_option('active_sitewide_plugins');
849 if (!empty($network_active_plugins)) {
850 $network_active_plugins = array_keys($network_active_plugins);
851 $active_plugins = array_merge($active_plugins, $network_active_plugins);
852 }
853 }
854
855 return $active_plugins;
856 }
857
858 /**
859 * This function checks whether a specific plugin is installed, and returns information about it
860 *
861 * @param string $name Specify "Plugin Name" to return details about it.
862 * @return array Returns an array of details such as if installed, the name of the plugin and if it is active.
863 */
864 public function is_installed($name) {
865
866 // Needed to have the 'get_plugins()' function
867 include_once(ABSPATH.'wp-admin/includes/plugin.php');
868
869 // Gets all plugins available
870 $get_plugins = get_plugins();
871
872 $active_plugins = $this->get_active_plugins();
873
874 $plugin_info = array();
875 $plugin_info['installed'] = false;
876 $plugin_info['active'] = false;
877
878 // Loops around each plugin available.
879 foreach ($get_plugins as $key => $value) {
880 // If the plugin name matches that of the specified name, it will gather details.
881 if ($value['Name'] !== $name && $value['TextDomain'] !== $name) continue;
882 $plugin_info['installed'] = true;
883 $plugin_info['name'] = $key;
884 $plugin_info['version'] = $value['Version'];
885 if (in_array($key, $active_plugins)) {
886 $plugin_info['active'] = true;
887 }
888 break;
889 }
890 return $plugin_info;
891 }
892
893 /**
894 * This is a notice to show users that premium is installed
895 */
896 public function show_admin_notice_premium() {
897 echo '<div id="wp-optimize-premium-installed-warning" class="error"><p>'.esc_html__('WP-Optimize (Free) has been de-activated, because WP-Optimize Premium is active.', 'wp-optimize').'</p></div>';
898 if (isset($_GET['activate'])) unset($_GET['activate']); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- only unsetting value, not using it.
899 }
900
901 /**
902 * Show update to Premium notice for non-premium multisite.
903 */
904 public function show_multisite_update_to_premium_notice() {
905 if (!is_multisite() || self::is_premium()) return;
906
907 echo '<p><a href="'.esc_url($this->premium_version_link). '&utm_content=multisite-upsell' .'">'.esc_html__('New feature: WP-Optimize Premium can now optimize all sites within a multisite install, not just the main one.', 'wp-optimize').'</a></p>';
908 }
909
910 public function admin_init() {
911 $pagenow = $GLOBALS['pagenow'];
912
913 $this->register_template_directories();
914
915 if (('index.php' === $pagenow && $this->current_user_can('update_plugins')) || ('index.php' === $pagenow && defined('WP_OPTIMIZE_FORCE_DASHNOTICE') && WP_OPTIMIZE_FORCE_DASHNOTICE)) {
916 $options = $this->get_options();
917
918 $dismissed_until = $options->get_option('dismiss_dash_notice_until', 0);
919
920 if (file_exists(WPO_PLUGIN_MAIN_PATH . 'index.html')) {
921 $installed = filemtime(WPO_PLUGIN_MAIN_PATH . 'index.html');
922 $installed_for = (time() - $installed);
923 }
924
925 if (($installed && time() > $dismissed_until && $installed_for > (14 * 86400) && !defined('WP_OPTIMIZE_NOADS_B')) || (defined('WP_OPTIMIZE_FORCE_DASHNOTICE') && WP_OPTIMIZE_FORCE_DASHNOTICE)) {
926 add_action('all_admin_notices', array($this, 'show_admin_notice_upgraded'));
927 }
928 }
929
930 if ($this->is_wp_smush_installed()) {
931 add_filter('transient_wp-smush-conflict_check', array($this, 'modify_wp_smush_conflict_check'), 9, 1);
932 }
933 if ($this->get_options()->get_option('404_detector', 0)) {
934 WP_Optimize_Performance::get_instance($this->get_404_detector())->init();
935 }
936 }
937
938 /**
939 * Checks whether the WP Smush plugin is active or not
940 *
941 * @return bool
942 */
943 private function is_wp_smush_installed() {
944 return is_plugin_active('wp-smushit/wp-smush.php');
945 }
946
947 /**
948 * Remove WPO plugin name from WP Smushit transient value
949 *
950 * @return array $active_plugins
951 */
952 public function modify_wp_smush_conflict_check($active_plugins) {
953 // This can be boolean value since it is return value of get_transient
954 if (!is_array($active_plugins)) return $active_plugins;
955
956 if (false !== ($key = array_search('WP-Optimize - Clean, Compress, Cache', $active_plugins))) {
957 unset($active_plugins[$key]);
958 }
959 return $active_plugins;
960 }
961
962 /**
963 * Get the installation or update notice instance
964 *
965 * @return WP_Optimize_Install_Or_Update_Notice
966 */
967 public function get_install_or_update_notice() {
968 static $instance = null;
969 if (is_a($instance, 'WP_Optimize_Install_Or_Update_Notice')) return $instance;
970 $instance = new WP_Optimize_Install_Or_Update_Notice();
971 return $instance;
972 }
973
974 /**
975 * Display an admin notice for an upgraded version.
976 */
977 public function show_admin_notice_upgraded() {
978 $this->include_template('notices/thanks-for-using-main-dash.php', false, array(
979 'is_premium' => WP_Optimize::is_premium()
980 ));
981 }
982
983 /**
984 * Returns required capability string
985 *
986 * @return string
987 */
988 public function capability_required() {
989 $capability = is_multisite() ? 'manage_network_options' : 'manage_options';
990
991 $filtered_capability = apply_filters('wp_optimize_capability_required', $capability);
992
993 return is_string($filtered_capability) ? $filtered_capability : $capability;
994 }
995
996 /**
997 * Check if the current user has a given capability.
998 * If no capability is provided, it checks for the default capability required to access WP-Optimize.
999 *
1000 * @param string|null $capability The capability to check. Null = use default.
1001 * @return bool
1002 */
1003 public function current_user_can($capability = null) {
1004 if (null === $capability) {
1005 $capability = $this->capability_required(); // falls back to default
1006 }
1007
1008 if (current_user_can($capability)) {
1009 return true;
1010 }
1011
1012 return false;
1013 }
1014
1015 /**
1016 * Returns array of translations used in javascript code.
1017 *
1018 * @return array
1019 */
1020 public function wpo_js_translations() {
1021 $log_message = __('For more details, please check your logs configured in logging destinations settings.', 'wp-optimize');
1022 $translations = array(
1023 'automatic_backup_before_optimizations' => __('Automatic backup before optimizations', 'wp-optimize'),
1024 'error_unexpected_response' => __('An unexpected response was received.', 'wp-optimize'),
1025 'optimization_complete' => __('Optimization complete', 'wp-optimize'),
1026 'with_warnings' => __('(with warnings - open the browser console for more details)', 'wp-optimize'),
1027 'optimizing_table' => __('Optimizing table:', 'wp-optimize'),
1028 'run_optimizations' => __('Run optimizations', 'wp-optimize'),
1029 'table_optimization_timeout' => 120000,
1030 'add' => __('Add', 'wp-optimize'),
1031 'cancel' => __('Cancel', 'wp-optimize'),
1032 'cancelling' => __('Cancelling...', 'wp-optimize'),
1033 'enable' => __('Enable', 'wp-optimize'),
1034 'disable' => __('Disable', 'wp-optimize'),
1035 'please_select_settings_file' => __('Please, select settings file.', 'wp-optimize'),
1036 'are_you_sure_you_want_to_remove_logging_destination' => __('Are you sure you want to remove this logging destination?', 'wp-optimize'),
1037 'fill_all_settings_fields' => __('Before saving, you need to complete the currently incomplete settings (or remove them).', 'wp-optimize'),
1038 // translators: %s is the table name
1039 'table_was_not_repaired' => __('%s was not repaired.', 'wp-optimize') . ' ' . $log_message,
1040 // translators: %s is the table name
1041 'table_was_not_deleted' => __('%s was not deleted.', 'wp-optimize') . ' ' . $log_message,
1042 // translators: %s is the table name
1043 'table_was_not_converted' => __('%s was not converted to InnoDB.', 'wp-optimize') . ' ' . $log_message,
1044 'please_use_positive_integers' => __('Please use positive integers.', 'wp-optimize'),
1045 'please_use_valid_values' => __('Please use valid values.', 'wp-optimize'),
1046 'update' => __('Update', 'wp-optimize'),
1047 'run_now' => __('Run now', 'wp-optimize'),
1048 'starting_preload' => __('Started preload...', 'wp-optimize'),
1049 'loading_urls' => __('Loading URLs...', 'wp-optimize'),
1050 'current_cache_size' => __('Current cache size:', 'wp-optimize'),
1051 'number_of_files' => __('Number of files:', 'wp-optimize'),
1052 'toggle_info' => __('Show information', 'wp-optimize'),
1053 'delete_file' => __('Delete', 'wp-optimize'),
1054 'deleting' => __('Deleting...', 'wp-optimize'),
1055 'added_to_list' => __('Added to the list', 'wp-optimize'),
1056 'added_notice' => __('The file was added to the list', 'wp-optimize'),
1057 'save_notice' => __('Save the changes', 'wp-optimize'),
1058 'saving' => __('Saving...', 'wp-optimize'),
1059 'clearing_cache' => __('Clearing cache...', 'wp-optimize'),
1060 'creating_cache' => __('Creating cache...', 'wp-optimize'),
1061 'page_refresh' => __('Refreshing the page to reflect changes...', 'wp-optimize'),
1062 'cache_file_not_found' => __('Cache file was not found.', 'wp-optimize'),
1063 'settings_have_been_deleted_successfully' => __('WP-Optimize settings have been deleted successfully.', 'wp-optimize'),
1064 'loading_data' => __('Loading data...', 'wp-optimize'),
1065 'spinner_src' => esc_url(admin_url('images/spinner-2x.gif')),
1066 'logo_src' => esc_url(WPO_PLUGIN_URL.'images/notices/wp_optimize_logo.png'),
1067 'settings_page_url' => is_multisite() ? network_admin_url('admin.php?page=wpo_settings') : admin_url('admin.php?page=wpo_settings'),
1068 'sites' => $this->get_sites(),
1069 'user_always_ignores_table_deletion_warning' => (bool) get_user_meta(get_current_user_id(), 'wpo-ignores-table-deletion-warning', true),
1070 'user_always_ignores_post_meta_deletion_warning' => (bool) get_user_meta(get_current_user_id(), 'wpo-ignores-post-meta-deletion-warning', true),
1071 'user_always_ignores_orphaned_relationship_data_deletion_warning' => (bool) get_user_meta(get_current_user_id(), 'wpo-ignores-orphaned-relationship-data-deletion-warning', true),
1072 'post_meta_tweak_completed' => __('The tweak has been performed.', 'wp-optimize'),
1073 'no_minified_assets' => __('No minified files are present', 'wp-optimize'),
1074 'network_site_url' => network_site_url(),
1075 'export_settings_file_name' => 'wpoptimize-settings-'.sanitize_title(get_bloginfo('name')).'.json',
1076 'import_select_file' => __('You have not yet selected a file to import.', 'wp-optimize'),
1077 'import_invalid_json_file' => __('Error: The chosen file is corrupt.', 'wp-optimize') . ' ' . __('Please choose a valid WP-Optimize export file.', 'wp-optimize'),
1078 'importing' => __('Importing...', 'wp-optimize'),
1079 'importing_data_from' => __('This will import data from:', 'wp-optimize'),
1080 'exported_on' => __('Which was exported on:', 'wp-optimize'),
1081 'continue_import' => __('Do you want to carry out the import?', 'wp-optimize'),
1082 'select_destination' => __('Select destination', 'wp-optimize'),
1083 'show' => __('Show', 'wp-optimize'),
1084 'hide' => __('Hide', 'wp-optimize'),
1085 'please_wait' => __('Please wait a moment...', 'wp-optimize'),
1086 'clipboard_failed' => __('Copy to clipboard failed, please do it manually', 'wp-optimize'),
1087 'clipboard_success' => __('System status has been copied to the clipboard', 'wp-optimize'),
1088 'show_information' => __('Show information', 'wp-optimize'),
1089 'hide_information' => __('Hide information', 'wp-optimize'),
1090 'data_not_available' => __('Not available', 'wp-optimize'),
1091 'something_wrong_try_again' => __('Something went wrong; please try again.', 'wp-optimize')
1092 );
1093 $filtered_translations = apply_filters('wpo_js_translations', $translations);
1094 return is_array($filtered_translations) ? $filtered_translations : $translations;
1095 }
1096
1097 /**
1098 * Add settings link on plugin page
1099 *
1100 * @param array $links Passing through the URL to be used within the HREF.
1101 * @return array Returns the Links.
1102 */
1103 public function plugin_settings_link($links) {
1104
1105 $admin_page_url = $this->get_options()->admin_page_url();
1106 $settings_page_url = $this->get_options()->admin_page_url('wpo_settings');
1107
1108 if (!self::is_premium()) {
1109 $premium_link = '<a href="' . esc_url($this->premium_version_link) . '&utm_content=plugin-page' . '" target="_blank">' . __('Premium', 'wp-optimize') . '</a>';
1110 array_unshift($links, $premium_link);
1111 }
1112
1113 $settings_link = '<a href="' . esc_url($settings_page_url) . '">' . __('Settings', 'wp-optimize') . '</a>';
1114 array_unshift($links, $settings_link);
1115
1116 $optimize_link = '<a href="' . esc_url($admin_page_url) . '">' . __('Optimize', 'wp-optimize') . '</a>';
1117 array_unshift($links, $optimize_link);
1118 return $links;
1119 }
1120
1121 /**
1122 * Action wpo_tables_list_additional_column_data. Output button Optimize in the action column.
1123 *
1124 * @param string $content String for output to column
1125 * @param object $table_info Object with table info.
1126 *
1127 * @return string
1128 */
1129 public function tables_list_additional_column_data($content, $table_info) {
1130 if ($table_info->is_needing_repair) {
1131 $content .= '<div class="wpo_button_wrap">'
1132 . '<button class="button button-secondary run-single-table-repair" data-table="' . esc_attr($table_info->Name) . '">' . __('Repair', 'wp-optimize') . '</button>'
1133 . '<img class="optimization_spinner visibility-hidden" src="' . esc_attr(admin_url('images/spinner-2x.gif')) . '" width="20" height="20" alt="...">' // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage -- N/A
1134 . '<span class="optimization_done_icon dashicons dashicons-yes visibility-hidden"></span>'
1135 . '</div>';
1136 }
1137
1138 // table belongs to plugin.
1139 if ($table_info->can_be_removed) {
1140 $content .= '<div>'
1141 . '<button class="button button-secondary run-single-table-delete" data-table="' . esc_attr($table_info->Name) . '">' . __('Remove', 'wp-optimize') . '</button>'
1142 . '<img class="optimization_spinner visibility-hidden" src="' . esc_attr(admin_url('images/spinner-2x.gif')) . '" width="20" height="20" alt="...">' // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage -- N/A
1143 . '<span class="optimization_done_icon dashicons dashicons-yes visibility-hidden"></span>'
1144 . '</div>';
1145 }
1146
1147 // Add option for MyISAM to InnoDB conversion.
1148 if ('MyISAM' === $table_info->Engine) {
1149 $content .= '<div class="wpo_button_convert wpo_button_wrap">'
1150 . '<button class="button button-secondary toinnodb" data-table="' . esc_attr($table_info->Name) . '">' . __('Convert to InnoDB', 'wp-optimize') . '</button>'
1151 . '<img class="optimization_spinner visibility-hidden" src="' . esc_attr(admin_url('images/spinner-2x.gif')) . '" width="20" height="20" alt="...">' // phpcs:ignore PluginCheck.CodeAnalysis.ImageFunctions.NonEnqueuedImage -- N/A
1152 . '<span class="optimization_done_icon dashicons dashicons-yes visibility-hidden"></span>'
1153 . '</div>';
1154
1155 }
1156
1157 return $content;
1158 }
1159
1160 /**
1161 * Initialize WP-Optimize page cache.
1162 */
1163 private function init_page_cache() {
1164 if ($this->get_page_cache()->config->get_option('enable_page_caching', false)) {
1165 if ((!(defined('WP_CLI') && WP_CLI)) && (!defined('DOING_AJAX') || !DOING_AJAX)) {
1166 $this->_cache_init_status = $this->get_page_cache()->enable();
1167 }
1168 }
1169 }
1170
1171 /**
1172 * Get init_page_cache() status.
1173 *
1174 * @return bool|WP_Error
1175 */
1176 public function get_init_page_cache_status() {
1177 return $this->_cache_init_status;
1178 }
1179
1180 /**
1181 * Schedules cron event based on selected schedule type
1182 *
1183 * @return void
1184 */
1185 public function cron_activate() {
1186 $gmt_offset = (int) (3600 * get_option('gmt_offset'));
1187
1188 $options = $this->get_options();
1189
1190 if (false === $options->get_option('schedule')) {
1191 $options->set_default_options();
1192 } else {
1193 if ('true' === $options->get_option('schedule')) {
1194 if (!wp_next_scheduled('wpo_cron_event2')) {
1195 $schedule_type = $options->get_option('schedule-type', 'wpo_weekly');
1196
1197 // Backward compatibility
1198 if ('wpo_otherweekly' === $schedule_type) $schedule_type = 'wpo_fortnightly';
1199
1200 $this_time = (86400 * 7);
1201
1202 switch ($schedule_type) {
1203 case "wpo_daily":
1204 $this_time = 86400;
1205 break;
1206
1207 case "wpo_weekly":
1208 $this_time = (86400 * 7);
1209 break;
1210
1211 case "wpo_fortnightly":
1212 $this_time = (86400 * 14);
1213 break;
1214
1215 case "wpo_monthly":
1216 $this_time = (86400 * 30);
1217 break;
1218 }
1219
1220 add_action('wpo_cron_event2', array($this, 'cron_action'));
1221 $result = wp_schedule_event((current_time("timestamp", 0) + $this_time - $gmt_offset), $schedule_type, 'wpo_cron_event2');
1222 $this->log('running wp_schedule_event()');
1223 if (is_wp_error($result)) {
1224 $error_msg = $result->get_error_message();
1225 $this->log($error_msg);
1226 $this->log(print_r($result, true)); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Using for debugging purpose, logged into separate file
1227 } else {
1228 $this->log($result);
1229 }
1230 }
1231 }
1232 }
1233 }
1234
1235 /**
1236 * Unschedules WP-Optimize cron events on deactivation.
1237 *
1238 * @return void
1239 */
1240 public function wpo_cron_deactivate() {
1241 if (is_multisite()) {
1242 $sites = $this->get_sites();
1243 foreach ($sites as $site) {
1244 switch_to_blog($site->blog_id);
1245 $this->unschedule_wpo_cron_jobs();
1246 restore_current_blog();
1247 }
1248 } else {
1249 $this->unschedule_wpo_cron_jobs();
1250 }
1251 }
1252
1253 /**
1254 * Unschedules all WP-Optimize cron jobs on the current site.
1255 *
1256 * @return void
1257 */
1258 private function unschedule_wpo_cron_jobs() {
1259 $cron_jobs = _get_cron_array();
1260 foreach ($cron_jobs as $job) {
1261 foreach (array_keys($job) as $hook) {
1262 if (preg_match('/^wpo_/', $hook)) wp_unschedule_hook($hook);
1263 }
1264 }
1265 }
1266
1267 /**
1268 * Scheduler public functions to update schedulers
1269 *
1270 * @param array $schedules An array of schedules being passed.
1271 * @return array An array of schedules being returned.
1272 */
1273 public function cron_schedules($schedules) {
1274 $schedules['wpo_daily'] = array('interval' => 86400, 'display' => 'Once Daily');
1275 $schedules['wpo_weekly'] = array('interval' => 86400 * 7, 'display' => 'Once Weekly');
1276 $schedules['wpo_fortnightly'] = array('interval' => 86400 * 14, 'display' => 'Once Every Fortnight');
1277 $schedules['wpo_monthly'] = array('interval' => 86400 * 30, 'display' => 'Once Every Month');
1278 return $schedules;
1279 }
1280
1281 /**
1282 * Returns count of overdue cron jobs.
1283 *
1284 * @return integer
1285 */
1286 public function howmany_overdue_crons() {
1287 $how_many_overdue = 0;
1288 if (function_exists('_get_cron_array') || (is_file(ABSPATH.WPINC.'/cron.php') && include_once(ABSPATH.WPINC.'/cron.php') && function_exists('_get_cron_array'))) {
1289 $crons = _get_cron_array();
1290 if (is_array($crons)) {
1291 $timenow = time();
1292 foreach ($crons as $jt => $job) {
1293 if ($jt < $timenow) {
1294 $how_many_overdue++;
1295 }
1296 }
1297 }
1298 }
1299 return $how_many_overdue;
1300 }
1301
1302 /**
1303 * Run updates on plugin activation.
1304 */
1305 public function run_updates() {
1306 include_once(WPO_PLUGIN_MAIN_PATH.'includes/class-wp-optimize-updates.php');
1307 WP_Optimize_Updates::check_updates();
1308 }
1309
1310 /**
1311 * Returns warning about overdue crons.
1312 *
1313 * @param int $howmany count of overdue crons
1314 * @return string
1315 */
1316 public function show_admin_warning_overdue_crons($howmany) {
1317 $ret = '<div class="updated below-h2"><p>';
1318 // translators: %d is number of overdue cron jobs
1319 $ret .= '<strong>'.esc_html__('Warning', 'wp-optimize').':</strong> '.sprintf(esc_html__('WordPress has a number (%d) of scheduled tasks which are overdue.', 'wp-optimize'), $howmany).' '. esc_html__('Unless this is a development site, this probably means that the scheduler in your WordPress install is not working.', 'wp-optimize').' <a target="_blank" href="'.esc_url(apply_filters('wpoptimize_com_link', "https://teamupdraft.com/blog/the-scheduler-in-wordpress-is-not-working-what-should-i-do/")).'">'.esc_html__('Read this page for a guide to possible causes and how to fix it.', 'wp-optimize').'</a>';
1320 $ret .= '</p></div>';
1321 return $ret;
1322 }
1323
1324 /**
1325 * Normalizes path string
1326 *
1327 * @param string $path
1328 *
1329 * @return string
1330 */
1331 private function wp_normalize_path($path) {
1332 // Wp_normalize_path is not present before WP 3.9.
1333 if (function_exists('wp_normalize_path')) return wp_normalize_path($path);
1334 // Taken from WP 4.6.
1335 $path = str_replace('\\', '/', $path);
1336 $path = preg_replace('|(?<=.)/+|', '/', $path);
1337 if (':' === substr($path, 1, 1)) {
1338 $path = ucfirst($path);
1339 }
1340 return $path;
1341 }
1342
1343 /**
1344 * Returns templates directory path
1345 *
1346 * @return string
1347 */
1348 public function get_templates_dir() {
1349 $templates_dir = $this->wp_normalize_path(WPO_PLUGIN_MAIN_PATH.'templates');
1350 $filtered_templates_dir = apply_filters('wp_optimize_templates_dir', $templates_dir);
1351 return is_string($filtered_templates_dir) ? $filtered_templates_dir : $templates_dir;
1352 }
1353
1354 /**
1355 * Returns templates URL
1356 *
1357 * @return string
1358 */
1359 public function get_templates_url() {
1360 $templates_url = WPO_PLUGIN_URL . 'templates';
1361 $filtered_templates_url = apply_filters('wp_optimize_templates_url', $templates_url);
1362 return is_string($filtered_templates_url) ? $filtered_templates_url : $templates_url;
1363 }
1364
1365 /**
1366 * Return or output view content
1367 *
1368 * @param string $path - path to template, usually relative to templates/ within the WP-O directory
1369 * @param boolean $return_instead_of_echo - what to do with the results
1370 * @param array $extract_these - key/value pairs for substitution into the scope of the template
1371 *
1372 * @return string|void
1373 */
1374 public function include_template($path, $return_instead_of_echo = false, $extract_these = array()) {
1375 if ($return_instead_of_echo) ob_start();
1376
1377 if (preg_match('#^([^/]+)/(.*)$#', $path, $matches)) {
1378 $prefix = $matches[1];
1379 $suffix = $matches[2];
1380 if (isset($this->template_directories[$prefix])) {
1381 $template_file = $this->template_directories[$prefix].'/'.$suffix;
1382 }
1383 }
1384
1385 if (!isset($template_file)) {
1386 $template_file = WPO_PLUGIN_MAIN_PATH.'templates/'.$path;
1387 }
1388
1389 $template_file = apply_filters('wp_optimize_template', $template_file, $path);
1390
1391 do_action('wp_optimize_before_template', $path, $template_file, $return_instead_of_echo, $extract_these);
1392
1393 if (!file_exists($template_file)) {
1394 error_log("WP Optimize: template not found: ".$template_file); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Using for debugging purpose such as incomplete installation
1395 echo esc_html__('Error:', 'wp-optimize').' '.esc_html__('template not found', 'wp-optimize')." (".esc_html($path).")";
1396 } else {
1397 extract($extract_these);
1398 // The following are useful variables which can be used in the template.
1399 // They appear as unused, but may be used in the $template_file.
1400 $wpdb = $GLOBALS['wpdb'];// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wpdb might be used in the included template
1401 $wp_optimize = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize might be used in the included template
1402 $optimizer = $this->get_optimizer();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $optimizer might be used in the included template
1403 $options = $this->get_options();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $options might be used in the included template
1404 $wp_optimize_notices = $this->get_notices();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $wp_optimize_notices might be used in the included template
1405 include $template_file;
1406 }
1407
1408 do_action('wp_optimize_after_template', $path, $template_file, $return_instead_of_echo, $extract_these);
1409
1410 if ($return_instead_of_echo) return ob_get_clean();
1411 }
1412
1413 /**
1414 * Build a list of template directories (stored in self::$template_directories)
1415 */
1416 private function register_template_directories() {
1417
1418 $template_directories = array();
1419
1420 $templates_dir = $this->get_templates_dir();
1421
1422 if ($dh = opendir($templates_dir)) {
1423 while (($file = readdir($dh)) !== false) {
1424 if ('.' === $file || '..' === $file) continue;
1425 if (is_dir($templates_dir.'/'.$file)) {
1426 $template_directories[$file] = $templates_dir.'/'.$file;
1427 }
1428 }
1429 closedir($dh);
1430 }
1431
1432 // Optimal hook for most extensions to hook into.
1433 $this->template_directories = apply_filters('wp_optimize_template_directories', $template_directories);
1434
1435 }
1436
1437 /**
1438 * Message to debug
1439 *
1440 * @param string $message Message to insert into the log.
1441 * @param array $context array with variables used in $message like in template,
1442 * for ex.
1443 * $message = 'Hello {message}';
1444 * $context = ['message' => 'world']
1445 * 'Hello world' string will be saved in log.
1446 */
1447 public function log($message, $context = array()) {
1448 $this->get_logger()->debug($message, $context);
1449 }
1450
1451 /**
1452 * Format Bytes Into KB/MB
1453 *
1454 * @param mixed $bytes Number of bytes to be converted.
1455 * @param integer $decimals the number of decimal digits
1456 * @return string return the correct format size.
1457 */
1458 public function format_size($bytes, $decimals = 2) {
1459 if (!is_numeric($bytes)) return __('N/A', 'wp-optimize');
1460
1461 if (1073741824 <= $bytes) {
1462 $bytes = number_format($bytes / 1073741824, $decimals) . ' GB';
1463 } elseif (1048576 <= $bytes) {
1464 $bytes = number_format($bytes / 1048576, $decimals) . ' MB';
1465 } elseif (1024 <= $bytes) {
1466 $bytes = number_format($bytes / 1024, $decimals) . ' KB';
1467 } elseif (1 < $bytes) {
1468 $bytes = $bytes . ' bytes';
1469 } elseif (1 === (int) $bytes) {
1470 $bytes = $bytes . ' byte';
1471 } else {
1472 $bytes = '0 bytes';
1473 }
1474
1475 return $bytes;
1476 }
1477
1478 /**
1479 * Format a timestamp into a human-readable date time
1480 *
1481 * @param int $timestamp Epoch timestamp to convert
1482 * @param string $separator String separator
1483 * @return string
1484 */
1485 public function format_date_time($timestamp, $separator = ' @ ') {
1486 return date_i18n(get_option('date_format'). $separator .get_option('time_format'), ($timestamp + get_option('gmt_offset') * 3600));
1487 }
1488
1489 /**
1490 * Executed this function on cron event.
1491 *
1492 * @return void
1493 */
1494 public function cron_action() {
1495
1496 $optimizer = $this->get_optimizer();
1497 $options = $this->get_options();
1498
1499 $this->log('WPO: Starting cron_action()');
1500 $options->update_option('last-optimized', time());
1501 if ('true' === $options->get_option('schedule')) {
1502 $this_options = $options->get_option('auto');
1503
1504 // Currently the output of the optimizations is not saved/used/logged.
1505 $optimizer->do_optimizations($this_options, 'auto');
1506 }
1507
1508 }
1509
1510 /**
1511 * Schedule cron tasks used by plugin.
1512 *
1513 * @return void
1514 */
1515 private function schedule_plugin_cron_tasks() {
1516 if (!wp_next_scheduled('wpo_weekly_cron_tasks')) {
1517 wp_schedule_event(current_time("timestamp", 0), 'wpo_weekly', 'wpo_weekly_cron_tasks');
1518 }
1519
1520 add_action('wpo_weekly_cron_tasks', array($this, 'do_weekly_cron_tasks'));
1521
1522 // Run 404 detector cron.
1523 $this->get_404_detector_cron();
1524 }
1525
1526 /**
1527 * Do plugin background tasks.
1528 *
1529 * @return void
1530 */
1531 public function do_weekly_cron_tasks() {
1532 // add tasks here.
1533 $this->get_db_info()->update_plugin_json();
1534 }
1535
1536 /**
1537 * This will customize a URL with a correct Affiliate link
1538 * This function can be updated to suit any URL as longs as the URL is passed
1539 *
1540 * @param string $url - URL to check to see if it is an updraftplus match.
1541 * @param ?string $text - Text to be entered within the href a tags.
1542 * @param ?string $html - Any specific HTML to be added. Supplied parameter will not be escaped in this function. Provide escaped, safe HTML
1543 * @param string|array $attrs - Specify the HTML attributes as an array or string. Use the array format for multiple attributes (e.g., array( "class" => "lorem-ipsum", "title" => "Highlighting text" )), and use the string format for a single attribute (e.g., 'class="lorem-ipsum"').
1544 * @param bool $return_instead_of_echo - if set, then the result will be returned, not echo-ed.
1545 * @return string|void
1546 */
1547 public function wp_optimize_url($url, $text = '', $html = '', $attrs = '', $return_instead_of_echo = false) {
1548 // Check if the URL is UpdraftPlus.
1549 $url = $this->maybe_add_affiliate_params($url); // Return URL - check if there is HTML such as images.
1550
1551 // Check if the variable $text is empty (null value included), otherwise assign $html.
1552 $content = empty($text) ? $html : esc_html($text);
1553
1554 // Check if $attrs is an array to convert the attributes into a string line.
1555 $str_attrs = '';
1556 if (is_array($attrs)) {
1557 foreach ($attrs as $attr => $value) {
1558 $str_attrs .= $attr . '="' . esc_attr($value) . '" ';
1559 }
1560 } else {
1561 // If $attrs is empty, the `explode` function will only return an empty array.
1562 $attrs = explode('=', $attrs);
1563 // Check if $attrs in positions 1 and 2 are not empty and exist; otherwise, return an empty string.
1564 $str_attrs = !empty($attrs[0]) && !empty($attrs[1]) ? $attrs[0] . '="' . esc_attr(str_replace('"', '', $attrs[1])) . '"' : '';
1565 }
1566
1567 // Check if it is necessary to add a target value if the url is external
1568 $is_external_url = $this->is_external_url($url);
1569 if ($is_external_url) {
1570 $str_attrs = preg_replace('/\s+target="_blank"/i', '', $str_attrs);
1571 $str_attrs .= ' target="_blank"';
1572 }
1573
1574 $result = sprintf(
1575 '<a href="%s" %s>%s</a>',
1576 esc_url($url),
1577 $str_attrs,
1578 wp_kses_post($content)
1579 );
1580
1581 if ($return_instead_of_echo) return $result;
1582 echo wp_kses_post($result);
1583 }
1584
1585 /**
1586 * Check if a URL is external
1587 *
1588 * @param string $url
1589 * @return bool
1590 */
1591 public function is_external_url($url) {
1592 if (empty($url)) {
1593 return false;
1594 }
1595
1596 $current_domain = wp_parse_url(home_url(), PHP_URL_HOST);
1597 $url = wp_parse_url($url, PHP_URL_HOST);
1598
1599 // Compare the domains and return true if they are different
1600 return $current_domain !== $url;
1601 }
1602
1603 /**
1604 * Get a URL with an eventual affiliate ID
1605 *
1606 * @param string $url
1607 * @return string
1608 */
1609 public function maybe_add_affiliate_params($url) {
1610 // Check if the URL is UpdraftPlus.
1611 if (false !== strpos($url, '//updraftplus.com')) {
1612 // Set URL with Affiliate ID.
1613 $url = add_query_arg(array('afref' => $this->get_notices()->get_affiliate_id()), $url);
1614
1615 // Apply filters.
1616 $url = apply_filters('wpoptimize_updraftplus_com_link', $url);
1617 }
1618 $filtered_url = apply_filters('wpoptimize_maybe_add_affiliate_params', $url);
1619 return is_string($filtered_url) ? $filtered_url : $url;
1620 }
1621
1622 /**
1623 * Setup WPO logger(s)
1624 */
1625 public function setup_loggers() {
1626
1627 $logger = $this->get_logger();
1628 $loggers = $this->wpo_loggers();
1629
1630 if (!empty($loggers)) {
1631 foreach ($loggers as $_logger) {
1632 $logger->add_logger($_logger);
1633 }
1634 }
1635
1636 add_action('wp_optimize_after_optimizations', array($this, 'after_optimizations_logger_action'));
1637 }
1638
1639 /**
1640 * Run logger actions after all optimizations done
1641 */
1642 public function after_optimizations_logger_action() {
1643 $loggers = $this->get_logger()->get_loggers();
1644 if (!empty($loggers)) {
1645 foreach ($loggers as $logger) {
1646 if (is_a($logger, 'Updraft_Email_Logger')) {
1647 $logger->flush_log();
1648 }
1649 }
1650 }
1651 }
1652
1653 /**
1654 * Returns list of WPO loggers instances
1655 * Apply filter wp_optimize_loggers
1656 *
1657 * @return array
1658 */
1659 public function wpo_loggers() {
1660
1661 $loggers = array();
1662 $loggers_classes_by_id = array();
1663 $options_keys = array();
1664
1665 $loggers_classes = $this->get_loggers_classes();
1666
1667 foreach ($loggers_classes as $logger_class => $source) {
1668 $loggers_classes_by_id[strtolower($logger_class)] = $logger_class;
1669 }
1670
1671 $options = $this->get_options();
1672
1673 $saved_loggers = $options->get_option('logging');
1674 $logger_additional_options = $options->get_option('logging-additional');
1675
1676 // create loggers classes instances.
1677 if (!empty($saved_loggers)) {
1678 // check for previous version options format.
1679 $keys = array_keys($saved_loggers);
1680
1681 // if options stored in old format then reformat it.
1682 if (false === is_numeric($keys[0])) {
1683 $_saved_loggers = array();
1684 foreach ($saved_loggers as $logger_id => $enabled) {
1685 if ($enabled) {
1686 $_saved_loggers[] = $logger_id;
1687 }
1688 }
1689
1690 // fill email with admin.
1691 if (array_key_exists('updraft_email_logger', $saved_loggers) && $saved_loggers['updraft_email_logger']) {
1692 $logger_additional_options['updraft_email_logger'] = array(
1693 get_option('admin_email')
1694 );
1695 }
1696
1697 $saved_loggers = $_saved_loggers;
1698 }
1699
1700 foreach ($saved_loggers as $i => $logger_id) {
1701
1702 if (!array_key_exists($logger_id, $loggers_classes_by_id)) continue;
1703
1704 $logger_class = $loggers_classes_by_id[$logger_id];
1705
1706 $logger = new $logger_class();
1707
1708 $logger_options = $logger->get_options_list();
1709
1710 if (!empty($logger_options)) {
1711 foreach (array_keys($logger_options) as $option_name) {
1712 if (array_key_exists($option_name, $options_keys)) {
1713 $options_keys[$option_name]++;
1714 } else {
1715 $options_keys[$option_name] = 0;
1716 }
1717
1718 $option_value = isset($logger_additional_options[$option_name][$options_keys[$option_name]]) ? $logger_additional_options[$option_name][$options_keys[$option_name]] : '';
1719
1720 // if options in old format then get correct value.
1721 if ('' === $option_value && array_key_exists($logger_id, $logger_additional_options)) {
1722 $option_value = array_shift($logger_additional_options[$logger_id]);
1723 }
1724
1725 $logger->set_option($option_name, $option_value);
1726 }
1727 }
1728
1729 // check if logger is active.
1730 $active = (!is_array($logger_additional_options) || (array_key_exists('active', $logger_additional_options) && empty($logger_additional_options['active'][$i]))) ? false : true;
1731
1732 if ($active) {
1733 $logger->enable();
1734 } else {
1735 $logger->disable();
1736 }
1737
1738 $loggers[] = $logger;
1739 }
1740 }
1741
1742 $filtered_loggers = apply_filters('wp_optimize_loggers', $loggers);
1743 return is_array($filtered_loggers) ? $filtered_loggers : $loggers;
1744 }
1745
1746 /**
1747 * Returns associative array with logger class name in a key and path to class file in a value.
1748 *
1749 * @return array
1750 */
1751 public function get_loggers_classes() {
1752 $loggers_classes = array(
1753 'Updraft_PHP_Logger' => WPO_PLUGIN_MAIN_PATH . 'includes/class-updraft-php-logger.php',
1754 'Updraft_Email_Logger' => WPO_PLUGIN_MAIN_PATH . 'includes/class-updraft-email-logger.php',
1755 'Updraft_Ring_Logger' => WPO_PLUGIN_MAIN_PATH . 'includes/class-updraft-ring-logger.php'
1756 );
1757
1758 $loggers_classes = apply_filters('wp_optimize_loggers_classes', $loggers_classes);
1759
1760 if (!empty($loggers_classes)) {
1761 foreach ($loggers_classes as $logger_class => $logger_file) {
1762 if (!class_exists($logger_class)) {
1763 if (is_file($logger_file)) {
1764 include_once($logger_file);
1765 }
1766 }
1767 }
1768 }
1769
1770 return $loggers_classes;
1771 }
1772
1773 /**
1774 * Returns information about all loggers classes.
1775 *
1776 * @return array
1777 */
1778 public function get_loggers_classes_info() {
1779 $loggers_classes = $this->get_loggers_classes();
1780
1781 $loggers_classes_info = array();
1782
1783 if (!empty($loggers_classes)) {
1784 foreach (array_keys($loggers_classes) as $logger_class_name) {
1785
1786 if (!class_exists($logger_class_name)) continue;
1787
1788 $logger_id = strtolower($logger_class_name);
1789 $logger_class = new $logger_class_name();
1790
1791 $loggers_classes_info[$logger_id] = array(
1792 'description' => $logger_class->get_description(),
1793 'available' => $logger_class->is_available(),
1794 'allow_multiple' => $logger_class->is_allow_multiple(),
1795 'options' => $logger_class->get_options_list()
1796 );
1797 }
1798 }
1799
1800 return $loggers_classes_info;
1801 }
1802
1803 /**
1804 * Returns true if optimization works in multisite mode
1805 *
1806 * @return boolean
1807 */
1808 public function is_multisite_mode() {
1809 return (is_multisite() && self::is_premium());
1810 }
1811
1812 /**
1813 * Returns true if the current user can run optimizations.
1814 *
1815 * @return bool
1816 */
1817 public function can_run_optimizations(): bool {
1818 // we don't check permissions for cron jobs.
1819 if (defined('DOING_CRON') && DOING_CRON) return true;
1820
1821 if (self::is_premium() && false === user_can(get_current_user_id(), 'wpo_run_optimizations')) return false;
1822 return true;
1823 }
1824
1825 /**
1826 * Returns true if current user can manage plugin options.
1827 *
1828 * @return bool
1829 */
1830 public function can_manage_options() {
1831 if (self::is_premium() && false == user_can(get_current_user_id(), 'wpo_manage_settings')) return false;
1832 return true;
1833 }
1834
1835 /**
1836 * Returns list of all sites in multisite
1837 *
1838 * @return array
1839 */
1840 public function get_sites() {
1841 $sites = array();
1842 if (function_exists('get_sites')) {
1843 $sites = get_sites(array('network_id' => null, 'deleted' => 0, 'number' => 999999));
1844 }
1845 return $sites;
1846 }
1847
1848 /**
1849 * Returns script memory limit in megabytes.
1850 *
1851 * @param string|bool $memory_limit
1852 * @return int
1853 */
1854 public function get_memory_limit($memory_limit = false) {
1855 // Returns in megabytes
1856 if (false == $memory_limit) $memory_limit = ini_get('memory_limit');
1857 $memory_limit = rtrim($memory_limit);
1858
1859 return $this->return_bytes($memory_limit);
1860 }
1861
1862 /**
1863 * Returns free memory in bytes.
1864 *
1865 * @return int
1866 */
1867 public function get_free_memory() {
1868 return $this->get_memory_limit() - memory_get_usage();
1869 }
1870
1871 /**
1872 * Checks PHP memory_limit and WP_MAX_MEMORY_LIMIT values and return minimal.
1873 *
1874 * @return int memory limit in bytes.
1875 */
1876 public function get_script_memory_limit() {
1877 $memory_limit = $this->get_memory_limit();
1878
1879 if (defined('WP_MAX_MEMORY_LIMIT')) {
1880 $wp_memory_limit = $this->get_memory_limit(WP_MAX_MEMORY_LIMIT);
1881
1882 if ($wp_memory_limit > 0 && $wp_memory_limit < $memory_limit) {
1883 $memory_limit = $wp_memory_limit;
1884 }
1885 }
1886
1887 return $memory_limit;
1888 }
1889
1890 /**
1891 * Returns max packet size for database.
1892 *
1893 * @return int|string
1894 */
1895 public function get_max_packet_size() {
1896 global $wpdb;
1897 static $mp = 0;
1898
1899 if ($mp > 0) return $mp;
1900
1901 $mp = (int) $wpdb->get_var("SELECT @@session.max_allowed_packet");
1902 // Default to 1MB
1903 $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576;
1904 // 32MB
1905 if ($mp < 33554432) {
1906 $save = $wpdb->show_errors(false);
1907 @$wpdb->query("SET GLOBAL max_allowed_packet=33554432");// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- suppress errors from displaying
1908 $wpdb->show_errors($save);
1909
1910 $mp = (int) $wpdb->get_var("SELECT @@session.max_allowed_packet");
1911 // Default to 1MB
1912 $mp = (is_numeric($mp) && $mp > 0) ? $mp : 1048576;
1913 }
1914
1915 return $mp;
1916 }
1917
1918 /**
1919 * Converts shorthand memory notation value to bytes.
1920 * From http://php.net/manual/en/function.ini-get.php
1921 *
1922 * @param string $val shorthand memory notation value.
1923 *
1924 * @return string
1925 */
1926 public function return_bytes($val) {
1927 $val = trim($val);
1928 $last = strtolower($val[strlen($val)-1]);
1929 $val = (int) $val;
1930 switch ($last) {
1931 case 'g':
1932 $val *= 1024;
1933 // no break
1934 case 'm':
1935 $val *= 1024;
1936 // no break
1937 case 'k':
1938 $val *= 1024;
1939 }
1940
1941 return $val;
1942 }
1943
1944 /**
1945 * Log fatal errors to defined log destinations.
1946 */
1947 public function log_fatal_errors() {
1948 $last_error = error_get_last();
1949
1950 if (isset($last_error['type']) && E_ERROR === $last_error['type']) {
1951 $this->get_logger()->critical($last_error['message']);
1952 }
1953 }
1954
1955 /**
1956 * Close browser connection and continue script work. - Taken from UpdraftPlus
1957 *
1958 * @param string $txt Response to browser; this must be JSON (or if not, alter the Content-Type header handling below)
1959 * @return void
1960 */
1961 public function close_browser_connection($txt = '') {
1962 if (!headers_sent()) {
1963 // Close browser connection so that it can resume AJAX polling
1964 header('Content-Length: '.(empty($txt) ? '0' : 4+strlen($txt)));
1965 header('Content-Type: application/json');
1966 header('Connection: close');
1967 header('Content-Encoding: none');
1968 }
1969
1970 if (session_id()) session_write_close();
1971 echo "\r\n\r\n";
1972 echo $txt; // phpcs:ignore WordPress.Security.EscapeOutput -- Output is already escaped
1973 // These two added - 19-Feb-15 - started being required on local dev machine, for unknown reason (probably some plugin that started an output buffer).
1974 $ob_level = ob_get_level();
1975 while ($ob_level > 0) {
1976 ob_end_flush();
1977 $ob_level--;
1978 }
1979 flush();
1980 if (function_exists('fastcgi_finish_request')) fastcgi_finish_request();
1981 if (function_exists('litespeed_finish_request')) litespeed_finish_request();
1982 }
1983
1984 /**
1985 * Try to change PHP script time limit.
1986 */
1987 public function change_time_limit() {
1988 $time_limit = (defined('WP_OPTIMIZE_SET_TIME_LIMIT') && WP_OPTIMIZE_SET_TIME_LIMIT > 15) ? WP_OPTIMIZE_SET_TIME_LIMIT : 1800;
1989
1990 // phpcs:disable
1991 // Generic.PHP.NoSilencedErrors.Discouraged -- Try to reduce the chances of PHP self-terminating via reaching max_execution_time.
1992 // Squiz.PHP.DiscouragedFunctions.Discouraged -- Try to reduce the chances of PHP self-terminating via reaching max_execution_time.
1993 @set_time_limit($time_limit);
1994 // phpcs:enable
1995 }
1996
1997 /**
1998 * Does the request come from UDC
1999 *
2000 * @return boolean
2001 */
2002 public function is_updraft_central_request() {
2003 return defined('UPDRAFTCENTRAL_COMMAND') && UPDRAFTCENTRAL_COMMAND;
2004 }
2005
2006 /**
2007 * Does the data need to be included in this request. Currently only true if the request is made from UpdraftCentral.
2008 *
2009 * @return boolean
2010 */
2011 public function template_should_include_data() {
2012 /**
2013 * Filters whether data should be included in certain templates or not.
2014 */
2015 return (bool) apply_filters('wpo_template_should_include_data', $this->is_updraft_central_request());
2016 }
2017
2018 /**
2019 * Load the templates for the modal window
2020 */
2021 public function load_modal_template() {
2022 $this->include_template('modal.php');
2023 }
2024
2025 /**
2026 * Adds compress popup template
2027 */
2028 public function add_smush_popup_template() {
2029 if ($this->current_user_can()) {
2030 $compression_server_hint = Updraft_Smush_Manager()->get_compression_server_hint();
2031 $this->include_template('images/smush-popup.php', false, array('compression_server_hint' => $compression_server_hint));
2032 }
2033 }
2034
2035 /**
2036 * Delete transients and semaphores data from options table.
2037 */
2038 public function delete_transients_and_semaphores() {
2039 global $wpdb;
2040
2041 $masks = array(
2042 'updraft_locked_wpo_%',
2043 'updraft_unlocked_wpo_%',
2044 'updraft_last_lock_time_wpo_%',
2045 'updraft_semaphore_wpo_%',
2046 'wpo_locked_%',
2047 'wpo_unlocked_%',
2048 'wpo_last_lock_time_%',
2049 'wpo_semaphore_%',
2050 '_transient_timeout_wpo_%',
2051 '_transient_wpo_%',
2052 'updraft_lock_wpo_%',
2053 'wpo_last_scheduled_%',
2054 );
2055
2056 $where_parts = array();
2057 foreach ($masks as $mask) {
2058 $where_parts[] = "(`option_name` LIKE '{$mask}')";
2059 }
2060
2061 $where_clause = implode(' OR ', $where_parts);
2062 $wpdb->query($wpdb->prepare("DELETE FROM `{$wpdb->options}` WHERE %s", $where_clause));
2063 }
2064
2065 /**
2066 * Prevents bots from indexing plugins list
2067 *
2068 * @param string $output
2069 * @return string
2070 */
2071 public function robots_txt($output) {
2072 $upload_dir = wp_upload_dir();
2073 $path = wp_parse_url($upload_dir['baseurl']);
2074 $output .= "\nUser-agent: *";
2075 $output .= "\nDisallow: " . str_replace($path['scheme'].'://'.$path['host'], '', $upload_dir['baseurl']) . "/wpo/wpo-plugins-tables-list.json\n";
2076 return $output;
2077 }
2078
2079 /**
2080 * Returns desired enqueue version string
2081 *
2082 * @return string Enqueue version as string
2083 */
2084 public function get_enqueue_version() {
2085 return (defined('WP_DEBUG') && WP_DEBUG) ? WPO_VERSION.'.'.time() : WPO_VERSION;
2086 }
2087
2088 /**
2089 * Returns script suffix string
2090 *
2091 * @return string empty or `.min` suffix string
2092 */
2093 public function get_min_or_not_string() {
2094 return (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '.min';
2095 }
2096
2097 /**
2098 * Returns script suffix string with WPO_VERSION
2099 *
2100 * @return string empty or min suffix with wpo_version string
2101 */
2102 public function get_min_or_not_internal_string() {
2103 return (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) ? '' : '-' . str_replace('.', '-', WPO_VERSION) . '.min';
2104 }
2105
2106 /**
2107 * Instantiate Ajax handling class
2108 */
2109 private function load_ajax_handler() {
2110 WPO_Ajax::get_instance();
2111 }
2112
2113 /**
2114 * Get instance of WP_Optimize_Table_Management
2115 *
2116 * @return WP_Optimize_Table_Management
2117 */
2118 public function get_table_management() {
2119 return WP_Optimize_Table_Management::get_instance();
2120 }
2121
2122 /**
2123 * Get instance of WP_Optimize_404_Detector
2124 *
2125 * @return WP_Optimize_404_Detector
2126 */
2127 public function get_404_detector() {
2128 return WP_Optimize_404_Detector::get_instance();
2129 }
2130
2131 /**
2132 * Returns the message used to notify the user that the PHP version or WordPress version doesn't meet the requirements.
2133 *
2134 * @return string
2135 */
2136 public function get_minimum_requirements_notice_message() {
2137 // translators: %1$s is minimum required PHP version, %2$s is minimum required WordPress version
2138 return sprintf(esc_html__('WP-Optimize requires a minimum PHP version of %1$s and WordPress version of %2$s or higher', 'wp-optimize'), esc_html(WPO_REQUIRED_PHP_VERSION), esc_html(WPO_REQUIRED_WP_VERSION));
2139 }
2140
2141 /**
2142 * Output notice when PHP version or WordPress version is not meet for WP-Optimize.
2143 *
2144 * @return void
2145 */
2146 public function output_minimum_requirements_notice() {
2147 ?>
2148 <div class="notice notice-error is-dismissible">
2149 <p><?php echo $this->get_minimum_requirements_notice_message(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is already escaped ?></p>
2150 </div>
2151 <?php
2152 }
2153 }
2154
2155 /**
2156 * Unschedule specific tasks:
2157 * `wpo_cron_event2` => DB optimizations
2158 * `wpo_weekly_cron_tasks` => ex: plugin.json auto update
2159 *
2160 * @return void
2161 */
2162 function wpo_cron_deactivate() {
2163 WP_Optimize()->log('running wpo_cron_deactivate()');
2164 wp_clear_scheduled_hook('wpo_cron_event2');
2165 wp_clear_scheduled_hook('wpo_weekly_cron_tasks');
2166 }
2167
2168 function WP_Optimize() {
2169 return WP_Optimize::instance();
2170 }
2171
2172 endif;
2173
2174 $GLOBALS['wp_optimize'] = WP_Optimize();
2175