PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 4.7.0
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v4.7.0
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 / wp-optimize.php

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

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