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

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