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

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