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

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