PluginProbe
UpdraftCentral Dashboard / trunk
UpdraftCentral Dashboard vtrunk
0.8.33 0.7.2 0.7.3 0.7.4 0.8.0 0.8.1 0.8.10 0.8.11 0.8.12 0.8.13 0.8.14 0.8.15 0.8.16 0.8.17 0.8.18 0.8.19 0.8.2 0.8.20 0.8.21 0.8.22 0.8.23 0.8.24 0.8.25 0.8.26 0.8.27 All 51 releases
updraftcentral / site-management.php

site-management.php in UpdraftCentral Dashboard trunk, at site-management.php

1,924 lines 70.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // @codingStandardsIgnoreStart
3 /*
4 Plugin Name: UpdraftCentral Dashboard
5 Plugin URI: https://updraftcentral.com
6 Description: Manage your WordPress sites from a central dashboard
7 Version: 0.8.30
8 Text Domain: updraftcentral
9 Domain Path: /languages
10 Author: David Anderson + Team Updraft
11 Author URI: https://www.simbahosting.co.uk/s3/shop/
12 Requires at least: 4.6
13 License: MIT
14
15 Copyright: 2015- David Anderson
16 */
17 // @codingStandardsIgnoreEnd
18 if (!defined('ABSPATH')) die('Access denied.');
19
20 define('UD_CENTRAL_DIR', dirname(__FILE__));
21 define('UD_CENTRAL_URL', plugins_url('', __FILE__));
22
23 // The name of the plugin sub-directory/file.
24 define('UD_CENTRAL_PLUGIN_NAME', basename(UD_CENTRAL_DIR).'/'.basename(__FILE__));
25
26 if (!defined('UPDRAFTCENTRAL_TABLE_PREFIX')) define('UPDRAFTCENTRAL_TABLE_PREFIX', 'updraftcentral_');
27
28 if (!class_exists('UpdraftCentral')) :
29 class UpdraftCentral {
30 const VERSION = '0.8.30';
31
32 // Minimum PHP version required to run this plugin
33 const PHP_REQUIRED = '5.3';
34
35 // Minimum WP version required to run this plugin
36 const WP_REQUIRED = '4.6';
37
38 protected static $_instance = null;
39
40 protected static $_logger_instance = null;
41
42 // This gets filled from the constant, for more convenient access
43 public $version;
44
45 // An instance of UpdraftCentral_User
46 public $user;
47
48 // An instance of UpdraftCentral_Site_Meta
49 public $site_meta;
50
51 private $inited = false;
52
53 private $notices = array();
54
55 public $table_prefix;
56
57 private $template_directories;
58
59 private $semaphores = array();
60
61 public $export_settings_version = '1';
62
63 private $start_time;
64
65 public $db;
66
67 private $uc_tables = array();
68
69 /**
70 * Creates an instance of this class. Singleton Pattern
71 *
72 * @return object Instance of this class
73 */
74 public static function instance() {
75 if (empty(self::$_instance)) {
76 self::$_instance = new self();
77 }
78
79 return self::$_instance;
80 }
81
82 /**
83 * UpdraftCentral constructor.
84 *
85 * Does initial checks. Adds necessary hooks, filters and shortcode. Loads modules
86 *
87 * @return self
88 */
89 public function __construct() {
90
91 $ucdb = apply_filters('updraftcentral_get_sites_db_object', $GLOBALS['wpdb']);
92
93 // Making sure that the user supplied db is a valid "wpdb" instance, if not then
94 // we fall back to the default global $wpdb object.
95 if (!is_a($ucdb, 'wpdb')) {
96 error_log("UpdraftCentral::__construct(): filtered database access object was invalid: will not be used");
97 $ucdb = $GLOBALS['wpdb'];
98 }
99 $this->db = $ucdb;
100
101 $this->version = self::VERSION;
102
103 // The shortcode will handle + provide output if running on an insufficient PHP/WP version; hence, this goes before the check/return.
104 add_shortcode('updraft_central', array($this, 'shortcode'));
105
106 add_action('enqueue_block_assets', array($this, 'enqueue_gutenberg_block_scripts'));
107
108 if (version_compare(PHP_VERSION, self::PHP_REQUIRED, '<')) {
109 add_action('all_admin_notices', array($this, 'admin_notice_insufficient_php'));
110 $abort = true;
111 }
112
113 include ABSPATH.WPINC.'/version.php';
114 if (version_compare($wp_version, self::WP_REQUIRED, '<')) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- $wp_version is part of Wordpress and fine to be ignored
115 add_action('all_admin_notices', array($this, 'admin_notice_insufficient_wp'));
116 $abort = true;
117 }
118
119 if (!empty($abort)) return;
120
121 $this->table_prefix = defined('UPDRAFTCENTRAL_TABLE_PREFIX') ? UPDRAFTCENTRAL_TABLE_PREFIX : 'updraftcentral_';
122
123 // Populate the UpdraftCentral::uc_tables property.
124 //
125 // N.B. This line needs to be declared after the $this->db
126 // and $this->table_prefix has been defined otherwise, we
127 // won't be able to populate the property successfully.
128 // @see UpdraftCentral::populate_updraftcentral_tables_list
129 $this->populate_updraftcentral_tables_list();
130
131 add_action('plugins_loaded', array($this, 'plugins_loaded'));
132 add_action('init', array($this, 'wp_init'));
133 register_activation_hook(__FILE__, array($this, 'activation_hook'));
134 register_deactivation_hook(__FILE__, array($this, 'deactivation_hook'));
135
136 if (is_admin()) {
137 add_action('admin_menu', array($this, 'admin_menu'));
138 // Add settings link in plugin list
139 $plugin = plugin_basename(__FILE__);
140 add_filter('plugin_action_links_'.$plugin, array($this, 'plugin_action_links'));
141 add_filter('network_admin_plugin_action_links_'.$plugin, array($this, 'plugin_action_links'));
142 }
143
144 // Possibly redirect on login back to the UC dashboard
145 add_action('woocommerce_login_redirect', array($this, 'woocommerce_login_redirect'));
146
147 add_action('updraftcentral_print_dashboard_notices', array($this, 'print_dashboard_notices'));
148
149 if (!empty($_POST['updraftcentral_action']) && 'receive_key' == $_POST['updraftcentral_action']) { //phpcs:ignore WordPress.Security - does not require a nonce as the encryption key itself has to be valid for anything to work
150 add_action('init', array($this, 'init_updraftcentral_action_receive_key'));
151 }
152
153 add_action('wp_ajax_updraftcentral_dashboard_ajax', array($this, 'updraftcentral_dashboard_ajax'));
154
155 // Needed in Site Meta changes (do_action('updraftcentral_inited') is located within this file)
156 add_action('updraftcentral_inited', array($this, 'updraftcentral_add_site_metadata_filter'));
157
158 add_action('updraftcentral_cron', array($this, 'process_cron'));
159 add_action('shutdown', array($this, 'schedule_event'));
160
161 add_action('delete_user', array($this, 'delete_user'));
162
163 // Add our custom cron schedule:
164 add_filter('cron_schedules', array($this, 'add_custom_cron_schedules'));
165
166 // Add WP personal Exporter
167 add_filter('wp_privacy_personal_data_exporters', array($this, 'plugin_register_exporters'));
168
169 // Allow both bundled and external modules to hook into various parts of the dashboard
170 $this->load_modules();
171 }
172
173 /**
174 * Make sure phpseclib classes are autoloaded
175 */
176 public function ensure_crypto_is_autoloaded() {
177 $pdir = UD_CENTRAL_DIR.'/vendor/phpseclib/phpseclib/phpseclib';
178 if (false === strpos(get_include_path(), $pdir)) set_include_path($pdir.PATH_SEPARATOR.get_include_path());
179 spl_autoload_register(array($this, 'autoload_phpseclib_class'));
180 }
181
182 /**
183 * Load phpseclib class automatically. Note that this method is hooked into the PHP's spl_auto_register and this is exclusively used for phpseclib only
184 *
185 * @param String $class A class name that's going to be used for instantiating an object
186 */
187 public function autoload_phpseclib_class($class) {
188 if (!preg_match('#^phpseclib_#', $class)) return;
189 $phpseclib_dir = UD_CENTRAL_DIR.'/vendor/phpseclib/phpseclib/phpseclib';
190 $class = str_replace('_', '/', $class);
191 $class = preg_replace('#^phpseclib/(.+)$#', "$1", $class);
192 if (file_exists($phpseclib_dir.'/'.$class.'.php') == true) {
193 $phpseclib_class_v2 = 'phpseclib\\'.str_replace('/', '\\', $class);
194 $phpseclib_updraft_class = 'phpseclib_'.str_replace('/', '_', $class);
195 require_once UD_CENTRAL_DIR."/vendor/autoload.php";
196 if (class_exists($phpseclib_class_v2) && !class_exists($phpseclib_updraft_class)) class_alias($phpseclib_class_v2, $phpseclib_updraft_class); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.class_aliasFound -- the use of class_alias here to map our customised phpseclib class to the real one owned by the phpseclib v2 class (e.g. phpseclib_Crypt_RSA => phpseclib\Crypt\RSA)
197 }
198 }
199
200 /**
201 * Returns the version to use when enqueueing or loading scripts and styles.
202 * If WP_DEBUG is on then time is padded to the VERSION constant.
203 *
204 * @return string
205 */
206 private function get_enqueue_version() {
207 return (defined('WP_DEBUG') && WP_DEBUG) ? self::VERSION.'.'.time() : self::VERSION;
208 }
209
210 /**
211 * Checks whether SCRIPT_DEBUG is on or not. If it is on then unminified script
212 * will be used.
213 *
214 * @return boolean
215 */
216 private function use_unminified_scripts() {
217 return (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG);
218 }
219
220 /**
221 * Register the data exporter
222 *
223 * @param array $exporters
224 * @return array modified exporters
225 */
226 public function plugin_register_exporters($exporters) {
227 $exporters[] = array(
228 'exporter_friendly_name' => __('Export UpdraftCentral registered sites', 'updraftcentral'),
229 'callback' => array($this, 'export_registered_sites'),
230 );
231 return $exporters;
232 }
233
234 /**
235 * Runs upon the WP delete_user action. We then delete their data.
236 *
237 * @param Integer $user_id - user being deleted
238 */
239 public function delete_user($user_id) {
240 try {
241 $user = $this->get_user_object($user_id);
242 if (is_a($user, 'UpdraftCentral_User')) {
243 $user->delete_all_sites();
244 }
245 } catch (Exception $e) {
246 error_log("UpdraftCentral::delete_user($user_id): exception: ".get_class($e).": ".$e->getMessage());
247 // @codingStandardsIgnoreLine
248 } catch (Error $e) {
249 error_log("UpdraftCentral::delete_user($user_id): error: ".get_class($e).": ".$e->getMessage());
250 }
251 }
252
253 /**
254 * Inserts the custom "everyminute" schedule as cron option
255 *
256 * @param array $schedules Contains the current list of schedule options
257 * @return array
258 */
259 public function add_custom_cron_schedules($schedules) {
260 if (!isset($schedules['everyminute'])) {
261 $schedules['everyminute'] = array(
262 'interval' => 60,
263 'display' => __('Once every minute', 'updraftcentral')
264 );
265 }
266
267 return $schedules;
268 }
269
270 /**
271 * Initializes a semaphore object based on a lock name and store it in a semaphores collection/array
272 *
273 * @param string $lock_name The lock name to use for the current semaphore object
274 *
275 * @return void
276 */
277 private function init_semaphore($lock_name) {
278 try {
279 if (empty($lock_name)) {
280 UpdraftCentral()->log('UpdraftCentral: a lock name is needed to initialize the semaphore class', 'error');
281 return;
282 }
283
284 if (!isset($this->semaphores[$lock_name])) {
285 if (!class_exists('Updraft_Semaphore_3_0')) include_once UD_CENTRAL_DIR.'/vendor/team-updraft/common-libs/src/updraft-semaphore/class-updraft-semaphore.php';
286
287 $this->semaphores[$lock_name] = new Updraft_Semaphore_3_0($lock_name);
288 $this->semaphores[$lock_name]->add_logger(self::get_logger());
289 }
290 } catch (Exception $e) {
291 UpdraftCentral()->log('UpdraftCentral: error initializing semaphore: '.$e->getMessage(), 'error');
292 }
293 }
294
295 /**
296 * Checks whether there are any available commands waiting to be process
297 *
298 * @return boolean - true when commands are available to process, false otherwise.
299 */
300 public function has_scheduled_commands() {
301 global $wp_filter;
302
303 if (empty($wp_filter['updraftcentral_scheduled_commands'])) return false;
304
305 return $wp_filter['updraftcentral_scheduled_commands']->has_filters();
306 }
307
308 /**
309 * Rearrange sites sequence based on availability (reachable status)
310 *
311 * @param array $sites An array containing the current user sites
312 *
313 * @return array
314 */
315 private function rearrange_priority($sites) {
316 if (empty($sites) || !is_array($sites)) return $sites;
317
318 // Sites are processed in sequence in the same order when they were pulled from the database.
319 // Thus, we need to sort them according to their availability. We prioritized those sites that don't
320 // have issues when we last run our background process and is reachable in the last 96 hours.
321
322 $reachable = array();
323 $unreachable = array();
324
325 foreach ($sites as $site) {
326 $alert_icon = apply_filters('updraftcentral_site_alert_icon', '', $site->site_id);
327 if ('' == $alert_icon) {
328 // No alert icon: signifies that the site was reachable in the last 96 hours.
329 $reachable[] = $site;
330 } else {
331 $unreachable[] = $site;
332 }
333 }
334
335 return array_merge($reachable, $unreachable);
336 }
337
338 /**
339 * Processes scheduled commands for the given user
340 *
341 * @param integer $user_id The current user ID that the command is associated with
342 *
343 * @return void
344 */
345 public function process_scheduled_commands($user_id) {
346 $user = $this->get_user_object($user_id);
347 if (!empty($user) && is_a($user, 'UpdraftCentral_User')) {
348
349 // Pull user sites
350 $sites = $user->load_user_sites();
351
352 // No point in continuing if the user currently don't have any sites to
353 // execute the commands.
354 if (empty($sites) || !is_array($sites)) return;
355
356 $sites = $this->rearrange_priority($sites);
357
358 /**
359 * Commands when added using this filter should have the following structure:
360 *
361 * $scheduled_commands []= array(
362 * 'command' => ,
363 * 'data' => ,
364 * 'maximum_age' => ,
365 * 'is_long_running' =>
366 * );
367 */
368 $scheduled_commands = apply_filters('updraftcentral_scheduled_commands', array());
369 if (!empty($scheduled_commands)) {
370 $command_pipeline = array();
371 $short_commands = array();
372
373 // Default: 10 minutes (600 seconds) if UPDRAFTCENTRAL_DATA_MAXIMUM_AGE is not defined
374 $default_maximum_age = defined('UPDRAFTCENTRAL_DATA_MAXIMUM_AGE') ? UPDRAFTCENTRAL_DATA_MAXIMUM_AGE : 600;
375
376 foreach ($scheduled_commands as $task) {
377 $maximum_age = isset($task['maximum_age']) ? $task['maximum_age'] : $default_maximum_age;
378 $is_long_running = isset($task['is_long_running']) ? $task['is_long_running'] : false;
379
380 // Insert maximum_age (freshness of data) into the actual data parameter which will be
381 // referenced later in the subsequent process.
382 $task['data']['maximum_age'] = $maximum_age;
383
384 // If we don't have a valid command format then we continue with the next.
385 if (!preg_match('/^([a-z0-9]+)\.(.*)$/', $task['command'], $matches)) {
386 UpdraftCentral()->log('UpdraftCentral: "'.$task['command'].'" is not a valid command. Valid command format comes in the form of {command_class_identifier}.{command_action} (e.g. updates.get_updates, etc.)', 'debug');
387 continue;
388 }
389
390 if (!$is_long_running) {
391 // We're preserving these non long running commands that will be wrapped
392 // and added as multiplexed commands later.
393 $short_commands[$task['command']] = $task['data'];
394 } elseif ($is_long_running) {
395 $command_pipeline[] = array(
396 'data' => array(
397 'command' => $task['command'],
398 'data' => $task['data']
399 )
400 );
401 }
402 }
403
404 // Add non long running commands into pipeline as multiplexed commands
405 if (!empty($short_commands)) {
406 $command_pipeline[] = array(
407 'data' => array(
408 'command' => 'core.execute_commands',
409 'data' => array(
410 'commands' => $short_commands,
411 'error_flag' => 3 /* Abort when all command fails - default */
412 )
413 )
414 );
415 }
416
417 if (!empty($command_pipeline)) {
418
419 // Set the semaphore lock name for the background process.
420 $background_lock_name = 'updraftcentral_cron';
421
422 // Make sure we have a valid semaphore to work on by initializing it
423 // whenever needed or make use of one if it was already been set.
424 $this->init_semaphore($background_lock_name);
425
426 if (empty($this->semaphores[$background_lock_name])) {
427 UpdraftCentral()->log('Failed to initialize a semaphore object - exiting', 'info');
428 return;
429 }
430
431 $max_execution_time = ini_get('max_execution_time');
432
433 // Since the semaphore lock is for 3 minutes, we want to try to run for at least in that region; otherwise the queue may get longer whilst a lock is stuck. We go for 170 to allow a bit of margin for really slow database updates.
434 $time_limit = (defined('UPDRAFTCENTRAL_PROCESS_CRON_TIME_LIMIT') && UPDRAFTCENTRAL_PROCESS_CRON_TIME_LIMIT > 5) ? UPDRAFTCENTRAL_PROCESS_CRON_TIME_LIMIT : 170;
435 if ((defined('UPDRAFTCENTRAL_PROCESS_CRON_TIME_LIMIT') || $max_execution_time > 5) && $max_execution_time < $time_limit) set_time_limit($time_limit);
436
437 if (!$this->semaphores[$background_lock_name]->lock()) {
438 UpdraftCentral()->log('Failed to gain semaphore lock - An active background data fetching process appears to be running, if the other process crashed without removing the lock, then another can be started after 3 minutes)', 'info');
439 return;
440 }
441
442 // N.B. We temporarily store processed sites in the 'uc_cron_sites_processed' user meta
443 // in order not to re-processed them, especially when the "last_run" flag for the current user
444 // is not updated yet until all sites are processed.
445
446 $processed_sites = get_user_meta($user->user_id, 'uc_cron_sites_processed', true);
447 if (empty($processed_sites)) $processed_sites = array();
448
449 foreach ($sites as $site) {
450 // Additional check will ensure that we're not running the same process
451 // more than once with this current event run.
452 if (!in_array($site->site_id, $processed_sites)) {
453 // Compute for the elapsed time (in minutes) since we started.
454 $elapsed_time = (time() - $this->start_time) / 60;
455
456 // Maximum of 3 minutes (default) process time. If succeeding loop goes beyond the allowable
457 // time then the process will be terminated.
458 if ($elapsed_time > apply_filters('updraftcentral_max_process_time', 3)) break;
459
460 // Execute all available commands for the currently
461 // selected site
462 foreach ($command_pipeline as $data) {
463 $data['site_id'] = $site->site_id;
464
465 // Setting the "force_save" flag to true saves and caches the result to DB.
466 //
467 // N.B. This overrides the use of the "updraftcentral_cache_commands" filter where UDC only
468 // saves and cache response when any module add certain command(s) that they wish the response
469 // to be cached to DB. In this case, we're forcing the save since we're running it in cron.
470
471 // Need to check the result if we were able to connect to the site successfully, otherwise,
472 // we'll flag it as disconnected.
473 $result = $user->send_remote_command($data, true, array());
474
475 if (!empty($result)) {
476 $error_data = array();
477 if ('error' === $result['responsetype']) {
478 $data = array();
479 if (isset($result['data'])) {
480 $data = $result['data'];
481 } else {
482 if (isset($result['rpc_response'])) $data = $result['rpc_response'];
483 if (isset($result['wrapped_response'])) $data = $result['wrapped_response'];
484 }
485
486 $error_data = array(
487 'message' => $result['message'],
488 'timestamp' => date('Y-m-d H:i:s'),
489 'data' => $data
490 );
491 }
492
493 if (is_a($this->site_meta, 'UpdraftCentral_Site_Meta')) {
494 $background_error_key = 'background_request_error';
495 $site_error = $this->site_meta->get_site_meta($site->site_id, $background_error_key, true);
496
497 if (!empty($site_error) && is_array($site_error)) {
498 if (empty($error_data)) {
499 // If we reached this area then that would mean that we were able to connect
500 // to the remote site wihout issues. Thus, we will remove the flag now.
501 $this->site_meta->delete_site_meta($site->site_id, $background_error_key);
502 }
503 } else {
504 if (!empty($error_data)) {
505 // No error flag record yet so, we create one. We only need to save once because
506 // we're going to compute the time between the last error and the new error from
507 // the latest request that we sent.
508 $this->site_meta->add_site_meta($site->site_id, $background_error_key, $error_data);
509 }
510 }
511 }
512 }
513 }
514
515 // By the time we reach here all commands for this particular site have been executed.
516 // Thus, we're going to add the site to the $processed_sites array. We cannot put this on top
517 // as we need to be sure that all commands are executed before we add this to the "$processed_sites" array.
518 array_push($processed_sites, $site->site_id);
519
520 // We need to set/update the 'uc_cron_sites_processed' user meta here since all process
521 // will be cut-off abruptly either after the max process time has expired (default 3 minutes)
522 // or the seamlock has been released therefore we need to set which sites have already been
523 // processed before the process is stopped so that other sites which are not yet processed
524 // will get a chance.
525 update_user_meta($user->user_id, 'uc_cron_sites_processed', $processed_sites);
526 }
527 }
528
529 // If the scheduled commands are executed to all user sites then we delete the user meta entry
530 // to give room for the next round of process (e.g. after 12 hours).
531 if (count($sites) == count($processed_sites)) {
532 delete_user_meta($user->user_id, 'uc_cron_sites_processed');
533 }
534
535 // Release lock
536 $this->semaphores[$background_lock_name]->release();
537 }
538 }
539 }
540 }
541
542 /**
543 * Process scheduled events (fetching UpdraftCentral data in the background) through cron
544 *
545 * @return void
546 */
547 public function process_cron() {
548
549 // Bypass processing if we don't have any existing commands
550 if (!$this->has_scheduled_commands()) return;
551
552 try {
553
554 if (!class_exists('UpdraftCentral_User_Cron')) include_once UD_CENTRAL_DIR.'/classes/user-cron.php';
555 $user_cron = new UpdraftCentral_User_Cron();
556
557 $queue = $user_cron->get_process_queue();
558 if (!empty($queue)) {
559 $this->start_time = time();
560
561 foreach ($queue as $user_id) {
562 // Compute for the elapsed time (in minutes) since we started.
563 $elapsed_time = (time() - $this->start_time) / 60;
564
565 // Maximum of 3 minutes (default) process time. If succeeding loop goes beyond the allowable
566 // time then the process will be terminated.
567 if ($elapsed_time > apply_filters('updraftcentral_max_process_time', 3)) break;
568
569 // Process any scheduled commands (if there are any) for the given user.
570 $this->process_scheduled_commands($user_id);
571
572 // Update user's last (cron) run field
573 $user_cron->update_last_run($user_id);
574 }
575 }
576
577 } catch (Exception $e) {
578 UpdraftCentral()->log('UpdraftCentral: error when running cron event: '.$e->getMessage(), 'error');
579 }
580
581 }
582
583 /**
584 * Initializes cron event for background data fetching
585 */
586 public function schedule_event() {
587 try {
588 $current_user = wp_get_current_user();
589 if (!function_exists('is_plugin_active')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
590
591 // Make sure we have an entry in the "updraftcentral_user_cron" for the current user
592 // for executing the scheduled commands at a later time.
593 if (!class_exists('UpdraftCentral_User_Cron')) include_once UD_CENTRAL_DIR.'/classes/user-cron.php';
594 $user_cron = new UpdraftCentral_User_Cron();
595 $user_cron->maybe_insert_entry($current_user->ID);
596
597 // Register our cron if not yet registered and making sure that we're scheduling our event
598 // only if UpdraftCentral is active since we're clearing all events when this plugin is deactivated
599 // by the user, so that we won't be registering those already cleared events again by mistake.
600 //
601 // Updates: "updraftcentral_cron" is now global and runs accros the whole UpdraftCentral dashboard
602 // instead of associating it to the individual user.
603 if (!wp_next_scheduled('updraftcentral_cron') && is_plugin_active(UD_CENTRAL_PLUGIN_NAME)) {
604 wp_schedule_event(time(), 'everyminute', 'updraftcentral_cron');
605 }
606 } catch (Exception $e) {
607 UpdraftCentral()->log($e->getMessage(), 'error');
608 }
609 }
610
611
612 public function updraftcentral_add_site_metadata_filter() {
613 if (is_a($this->site_meta, 'UpdraftCentral_Site_Meta')) {
614 // We're short-circuiting the return by using our own implementation since we have a custom column "created"
615 // that would check for the maximum age of the content to return.
616 add_filter('get_site_metadata', array($this->site_meta, 'updraftcentral_get_site_metadata'), 10, 6);
617 add_filter('get_site_metadata_created', array($this->site_meta, 'updraftcentral_get_site_metadata_created'), 10, 3);
618 }
619 }
620
621 /**
622 * Compares installed and available version and takes necessary action
623 */
624 public function wp_init() {
625 include_once UD_CENTRAL_DIR.'/classes/activation.php';
626 UpdraftCentral_Activation::check_updates();
627
628 $this->register_gutenberg_block_scripts();
629 }
630
631 /**
632 * Prevents WooCommerce default redirection to My Accounts page
633 *
634 * @param string $redirect_to
635 * @return mixed
636 */
637 public function woocommerce_login_redirect($redirect_to) {
638 $redirect_on_wc_login = '';
639 if (isset($_POST['updraftcentral_redirect_on_wc_login'])) {//phpcs:ignore WordPress.Security - no nonce check required, user is logging in
640 $redirect_on_wc_login = esc_url_raw(wp_unslash($_POST['updraftcentral_redirect_on_wc_login']));//phpcs:ignore WordPress.Security
641 }
642 return empty($redirect_on_wc_login) ? $redirect_to : $redirect_on_wc_login;
643 }
644
645 /**
646 * Adds admin notice for insufficient php version.
647 *
648 * @return void
649 */
650 public function admin_notice_insufficient_php() {
651 $this->show_admin_warning('<strong>'.__('Higher PHP version required', 'updraftcentral').'</strong><br> '.sprintf(__('The %s plugin requires %s version %s or higher - your current version is only %s.', 'updraftcentral'), 'UpdraftCentral', 'PHP', self::PHP_REQUIRED, PHP_VERSION), 'error');
652 }
653
654 /**
655 * Adds admin notice for insufficient wp version
656 *
657 * @return void
658 */
659 public function admin_notice_insufficient_wp() {
660 include ABSPATH.WPINC.'/version.php';
661 $this->show_admin_warning('<strong>'.__('Higher WordPress version required', 'updraftcentral').'</strong><br> '.sprintf(__('The %s plugin requires %s version %s or higher - your current version is only %s.', 'updraftcentral'), 'UpdraftCentral', 'WordPress', self::WP_REQUIRED, $wp_version), 'error');// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- Fine to ignore
662 }
663
664 /**
665 * Warning message for admin notice
666 *
667 * @param string $message
668 * @param string $class
669 *
670 * @return void
671 */
672 public function show_admin_warning($message, $class = 'updated') {
673 echo '<div class="updraftcentral_message '.esc_attr($class).'"><p>'.esc_html($message).'</p></div>';
674 }
675
676 /**
677 * Adds admin page for UpdraftCentral plugin
678 *
679 * @return void
680 */
681 public function admin_menu() {
682 add_menu_page('UpdraftCentral', 'UpdraftCentral', 'manage_options', 'updraft-central', array($this, 'wp_dashboard_page'), UD_CENTRAL_URL.'/images/dashicon.png', 56.8467664);
683 }
684
685 /**
686 * These are also available from JS via udclion.common_urls
687 *
688 * @return array
689 */
690 public function get_common_urls() {
691 return apply_filters('updraftcentral_common_urls', array(
692 'support_forum' => 'https://wordpress.org/support/plugin/updraftcentral',
693 'faqs' => 'https://updraftplus.com/updraftcentral-frequently-asked-questions/',
694 'idea_suggestion' => 'https://updraftplus.com/make-a-suggestion',
695 'first_link' => '<a href="http://updraftcentral.com">'.__('Home', 'updraftcentral').'</a>',
696 'how_to_install' => 'https://updraftplus.com/faqs/how-do-i-install-updraftcentral/',
697 'how_to_add_site' => 'https://updraftplus.com/updraftcentral-how-to-add-a-site/',
698 'paid_support' => 'https://updraftplus.com/paid-support-requests/',
699 'connection_checklist' => 'https://updraftplus.com/troubleshooting-updraftcentral-connection-issues/',
700 'connection_advanced_issues' => 'https://updraftplus.com/faqs/how-can-i-control-a-site-that-has-access-controls-e-g-brower-password-ip-address-restrictions/',
701 'get_licences' => false,
702 ));
703 }
704
705 /**
706 * Checks whether UpdraftCentral Premium is installed or not
707 *
708 * @param bool $also_require_active
709 * @return bool
710 */
711 public function is_premium_installed($also_require_active = false) {
712 if ($also_require_active) return class_exists('UpdraftCentral_Premium');
713 if (!function_exists('get_plugins')) include_once(ABSPATH.'wp-admin/includes/plugin.php');
714 $plugins = get_plugins();
715 $updraftcentral_premium_file = false;
716 foreach ($plugins as $key => $value) {
717 if ("updraftcentral-premium" == $value['TextDomain']) {
718 $updraftcentral_premium_file = $key;
719 break;
720 }
721 }
722 return $updraftcentral_premium_file ? true : false;
723 }
724
725 /**
726 * Includes dashboard page template and content
727 */
728 public function wp_dashboard_page() {
729 $extract_these = $this->get_common_urls();
730 $this->include_template('wp-admin/dashboard-page.php', false, $extract_these);
731 }
732
733 public function init_updraftcentral_action_receive_key() {
734 // @codingStandardsIgnoreLine
735 @header('Content-Type: application/json');
736
737 $response_array = array(
738 'mothership' => 'thatsus',
739 'mothership_info' => array('version' => self::VERSION),
740 );
741
742 $post_key = isset($_POST['key']) ? stripslashes($_POST['key']) : '';//phpcs:ignore WordPress.Security - does not need a nonce check as the user is not logged in (and an invalid key will simply be unable to perform any decryptions, so it is self-validating)
743 if (empty($post_key)) {
744 $response_array['code'] = 'key_invalid';
745 $response_array['message'] = 'Necessary data was not supplied';
746 } else {
747
748 $ucdb = UpdraftCentral()->db;
749 $ud_rpc = $this->get_udrpc('central_host.updraftplus.com');
750
751 // Normally, key generation takes seconds, even on a slow machine. However, some Windows machines appear to have a setup in which it takes a minute or more. And then, if you're on a double-localhost setup on slow hardware - even worse. It doesn't hurt to just raise the maximum execution time.
752
753 $key_generation_time_limit = (defined('UPDRAFTCENTRAL_SET_TIME_LIMIT') && is_numeric(UPDRAFTCENTRAL_SET_TIME_LIMIT) && UPDRAFTCENTRAL_SET_TIME_LIMIT > 10) ? UPDRAFTCENTRAL_SET_TIME_LIMIT : 900;
754
755 // @codingStandardsIgnoreLine
756 @set_time_limit($key_generation_time_limit);
757
758 if (false != $ud_rpc->generate_new_keypair()) {
759 $response_array['key_public'] = $ud_rpc->get_key_remote();
760 $inserted = $this->wp_insert('site_temporary_keys', array('key_remote_public' => $post_key, 'key_local_private' => $ud_rpc->get_key_local(), 'created' => time()), array('%s', '%s', '%d'));
761 if ($inserted) {
762 $response_array['key_id'] = $ucdb->insert_id;
763 } else {
764 $response_array['code'] = 'insert_error';
765 $response_array['message'] = 'A database error occurred when attempting to load the key';
766 }
767 } else {
768 $response_array['code'] = 'keygen_error';
769 $response_array['message'] = 'An error occurred when attempting to generate a new key-pair';
770 }
771
772 }
773 echo json_encode($response_array);
774 die;
775 }
776
777 /**
778 * Handles all ajax requests to this plugin.
779 *
780 * This single method handles and delegates all ajax requests in this plugin.
781 * By doing this, there is no need to add `wp_ajax_{your_action}` hook for every single actions
782 *
783 * @return void
784 */
785 public function updraftcentral_dashboard_ajax() {
786 if (empty($_REQUEST['subaction']) || empty($_REQUEST['nonce']) || empty($_REQUEST['component']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_REQUEST['nonce'])), 'updraftcentral_dashboard_nonce')) die('Security check');
787
788 if ('dashboard' != $_REQUEST['component']) die;
789
790 $response = array();
791
792 if (!$this->init()) {
793 $response = array('responsetype' => 'error', 'code' => 'init_failure', 'message' => __('Error:', 'updraftcentral').' '.__('failed to initialise', 'updraftcentral'));
794 } else {
795
796 $post_data = stripslashes_deep($_POST);
797
798 if (isset($post_data['data']) && is_array($post_data['data']) && isset($post_data['data']['site_id'])) {
799 $site_id = (int) $post_data['data']['site_id'];
800 if (!in_array($site_id, array_keys($this->user->sites))) {
801 $response = array('responsetype' => 'error', 'code' => 'unauthorised', 'message' => __('Error:', 'updraftcentral').' '.__('you are not authorized to access this site', 'updraftcentral'));
802 }
803 }
804
805 // Remember, if doing any processing here, that the site has not yet been checked as to whether it is licenced
806
807 // Any data will be in $_REQUEST['data'];
808 switch ($_REQUEST['subaction']) {
809 default:
810 $response = apply_filters('updraftcentral_dashboard_ajaxaction_'.sanitize_text_field(wp_unslash($_REQUEST['subaction'])), $response, $post_data);
811 break;
812 }
813
814 }
815
816 if (empty($response)) {
817 $response['responsetype'] = 'error';
818 $response['code'] = 'empty';
819 $response['message'] = __('Error:', 'updraftcentral').' '.sprintf(__('This action (%s) could not be handled', 'updraftcentral'), sanitize_text_field(wp_unslash($_REQUEST['subaction'])));
820 } elseif (is_wp_error($response)) {
821 $new_response = array(
822 'responsetype' => 'error',
823 'code' => $response->get_error_code(),
824 'message' => __('Error:', 'updraftcentral').' '.$response->get_error_message().' ('.$response->get_error_code().')',
825 'data' => $response->get_error_data(),
826 );
827 $response = $new_response;
828 }
829
830 echo json_encode($response);
831
832 die;
833
834 }
835
836 /**
837 * This is shortcode function for [updraft_central] checks the user's access level, and then dispatches to the appropriate page in the /pages sub-directory
838 *
839 * @param array $atts
840 * @return string
841 */
842 public function shortcode($atts) {
843
844 // When the UpdraftCentral block is added to the Editor we display a placeholder
845 // image rather than rendering the whole UpdraftCentral dashboard.
846 //
847 // The server side rendered block in the backend is rendered via the REST API endpoint
848 // thus, the usual is_admin() check won't work here as it will always return false even
849 // if the post/page is currently opened in the block editor. Therefore, we need to check
850 // if the REST_REQUEST constant has been defined so that we can actually distinquish
851 // whether the UC block is actually viewed in the backend (block editor) or the frontend.
852 if (defined('REST_REQUEST') && REST_REQUEST) {
853 // Save layout's load setting
854 if (isset($atts['fill'])) {
855 $fill_status = $atts['fill'] ? 'active' : 'inactive';
856 update_user_meta(get_current_user_id(), 'updraftcentral_dashboard_load_setting', $fill_status);
857 }
858
859 return '<img src="'.UD_CENTRAL_URL.'/images/updraftcentral.svg" onclick="javascript: wp.data.dispatch(\'core/edit-post\').openGeneralSidebar(\'edit-post/block\');return false;" />';
860 }
861
862 // Short-circuit plugins that run do_shortcode out-of-context (e.g. Relevansii)
863 if (is_admin()) return '';
864
865 if (version_compare(PHP_VERSION, self::PHP_REQUIRED, '<')) {
866 return sprintf(__('The %s plugin requires %s version %s or higher - your current version is only %s.', 'updraftcentral'), 'UpdraftCentral', 'PHP', self::PHP_REQUIRED, PHP_VERSION);
867 }
868 include ABSPATH.WPINC.'/version.php';
869 if (version_compare($wp_version, self::WP_REQUIRED, '<')) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- Fine to ignore
870 return sprintf(__('The %s plugin requires %s version %s or higher - your current version is only %s.', 'updraftcentral'), 'UpdraftCentral', 'WordPress', self::WP_REQUIRED, $wp_version);// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- Fine to ignore
871 }
872
873 $atts = shortcode_atts(array(
874 'page' => 'dashboard',
875 'require_role' => 'administrator',
876 'require_cap' => false,
877 ), $atts, 'updraft_central');
878
879 // Security check - only valid characters
880 if (!preg_match('/^[_a-z]+$/', $atts['page'])) return;
881
882 ob_start();
883
884 if (!is_user_logged_in()) {
885 $this->include_template('dashboard/not-logged-in.php');
886 } else {
887
888 add_action('wp_footer', array($this, 'wp_footer'));
889
890 if (!$this->init()) {
891
892 // We want the notice styles
893 $this->load_dashboard_css();
894
895 // Get the header, which invokes the notice-printing actions
896 $this->include_template('dashboard/header.php');
897
898 // Print this, in case for some reason the notices don't display (or there weren't any)
899 echo 'Setup error';
900
901 } else {
902
903 // Check they are a customer (i.e. have customer role)
904
905 $require_role = empty($atts['require_role']) ? false : explode(',', str_replace(' ', '', $atts['require_role']));
906 $require_cap = empty($atts['require_cap']) ? false : explode(',', str_replace(' ', '', $atts['require_cap']));
907
908 if (!$this->access_role_check($atts['page'], $require_role, $require_cap)) {
909 $this->include_template('dashboard/not-authorised.php');
910 } elseif ('dashboard' == $atts['page']) {
911 include_once UD_CENTRAL_DIR.'/pages/dashboard.php';
912 do_action('updraftcentral_dashboard_loaded');
913 }
914
915 }
916
917 }
918
919 return ob_get_clean();
920 }
921
922 /**
923 * Includes all module loader files from modules folder.
924 *
925 * @return void
926 */
927 private function load_modules() {
928
929 do_action('updraftcentral_load_modules');
930
931 if (is_dir(UD_CENTRAL_DIR.'/modules') && $dir_handle = opendir(UD_CENTRAL_DIR.'/modules')) {
932 while (false !== ($e = readdir($dir_handle))) {
933 if (is_dir(UD_CENTRAL_DIR.'/modules/'.$e) && file_exists(UD_CENTRAL_DIR.'/modules/'.$e.'/loader.php') && apply_filters('updraftcentral_load_module', true, $e, UD_CENTRAL_DIR.'/modules')) {
934 include_once UD_CENTRAL_DIR.'/modules/'.$e.'/loader.php';
935 }
936 }
937 // @codingStandardsIgnoreLine
938 @closedir($dir_handle);
939 }
940
941 do_action('updraftcentral_loaded_modules');
942
943 }
944
945 /**
946 * Sorts navigation items
947 *
948 * @param string $a - Navigation item to compare
949 * @param string $b - Navigation item to compare
950 * @return int - Result of comparison
951 */
952 public function sort_navigation_items($a, $b) {
953 if (!is_array($a) || !isset($a['sort_order']) || !is_numeric($a['sort_order'])) return 1;
954 if (!is_array($b) || !isset($b['sort_order']) || !is_numeric($b['sort_order'])) return -1;
955 if ($a['sort_order'] < $b['sort_order']) return -1;
956 if ($a['sort_order'] > $b['sort_order']) return 1;
957
958 return 0;
959 }
960
961 /**
962 * Registers the UpdraftCentral block and script
963 *
964 * @retun void
965 */
966 public function register_gutenberg_block_scripts() {
967 $enqueue_version = $this->get_enqueue_version();
968 $min_or_not = $this->use_unminified_scripts() ? '' : '.min';
969
970 wp_register_script('uc-gutenberg-blocks', UD_CENTRAL_URL.'/js/uc-block'.$min_or_not.'.js', array('wp-blocks', 'wp-element', 'wp-server-side-render', 'wp-components', 'wp-editor'), $enqueue_version);
971
972 register_block_type('updraftcentral/dashboard', array(
973 'attributes' => array(
974 'require_role' => array('type' => 'string', 'default' => 'administrator'),
975 'fill' => array('type' => 'boolean', 'default' => true),
976 ),
977 'editor_script' => 'uc-gutenberg-blocks',
978 'render_callback' => array($this, 'shortcode'),
979 ));
980 }
981
982 /**
983 * Enqueues the UpdraftCentral block and creates a localized script for
984 * frontend/JS consumption/reference
985 *
986 * @retun void
987 */
988 public function enqueue_gutenberg_block_scripts() {
989 global $wp_roles;
990
991 wp_enqueue_script('uc-gutenberg-blocks');
992 wp_localize_script('uc-gutenberg-blocks', 'uc_block',
993 array(
994 'title' => __('UpdraftCentral', 'updraftcentral'),
995 'description' => __('Manage your WordPress sites from a central dashboard', 'updraftcentral'),
996 'settings' => __('Dashboard Settings', 'updraftcentral'),
997 'layout' => __('Layout', 'updraftcentral'),
998 'require_role' => __('Require role', 'updraftcentral'),
999 'fill_content' => __('Fill entire content area', 'updraftcentral'),
1000 'roles' => $wp_roles->roles,
1001 )
1002 );
1003 }
1004
1005 /**
1006 * Register and enqueues needed js files
1007 *
1008 * @retun void
1009 */
1010 public function load_dashboard_js() {
1011 $enqueue_version = $this->get_enqueue_version();
1012 $min_or_not = $this->use_unminified_scripts() ? '' : '.min';
1013
1014 // https://github.com/alexei/sprintf.js
1015 wp_register_script('sprintf', UD_CENTRAL_URL.'/js/sprintf/sprintf'.$min_or_not.'.js', array(), '20151204');
1016
1017 // https://github.com/digitalbazaar/forge
1018 wp_register_script('forge', UD_CENTRAL_URL.'/js/forge-js/forge.min.js', array(), '0.7.0');
1019
1020 wp_register_script('class-udrpc', UD_CENTRAL_URL.'/js/class-udrpc'.$min_or_not.'.js', array('forge'), '0.3.3');
1021
1022 /*
1023 // https://github.com/google/caja/
1024 wp_register_script('caja-html4-defs', UD_CENTRAL_URL.'/js/caja/html4-defs.js', array(), '20151215');
1025 wp_register_script('caja-uri', UD_CENTRAL_URL.'/js/caja/uri.js', array(), '20151215');
1026 wp_register_script('google-caja-sanitizer', UD_CENTRAL_URL.'/js/caja/sanitizer.js', array('caja-html4-defs', 'caja-uri'), '20151215');
1027 */
1028
1029 wp_register_script('tether', UD_CENTRAL_URL.'/js/tether/tether'.$min_or_not.'.js', array(), '1.4.0');
1030
1031 // We used the bundle version of latest bootstrap so that we no longer need to add the
1032 // PopperJS library separately. Bootstrap's dropdowns and tooltips requires Popper
1033 // for positioning.
1034 wp_register_script('bootstrap5', UD_CENTRAL_URL.'/js/bootstrap/bootstrap.bundle'.$min_or_not.'.js', array('jquery', 'tether'), '5.2.3');
1035
1036 // https://github.com/makeusabrew/bootbox/releases/download/v(version)/bootbox(.min).js / http://bootboxjs.com/#download
1037 wp_register_script('bootbox', UD_CENTRAL_URL.'/js/bootbox/bootbox'.$min_or_not.'.js', array('bootstrap5'), '6.0.0');
1038
1039 // https://github.com/iyogeshjoshi/google-caja-sanitizer/
1040 wp_register_script('google-caja-sanitizer', UD_CENTRAL_URL.'/js/caja/sanitizer'.$min_or_not.'.js', array(), '20150315');
1041
1042 // Handlebars - http://www.handlebarsjs.com - https://github.com/wycats/handlebars.js
1043 // The "run-time" build handles only pre-compiled templates
1044 // Visit http://builds.handlebarsjs.com.s3.amazonaws.com/bucket-listing.html?sort=lastmod&sortdir=desc to spot the Git ID for new versions
1045 // http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.runtime.min-(Git ID).js - then rename it to match the non-minified version
1046 // http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.runtime-v(version).js
1047 // Rename them all to get rid of the version number from the file - ever-shifting filenames irritate when managing within SVN
1048 // We used to put the handlebars version here - but then it needed manual updating, which didn't happen, and would result in problems where the old version was cached.
1049 wp_register_script('handlebars', UD_CENTRAL_URL.'/js/handlebars/handlebars'.$min_or_not.'.js', array(), $enqueue_version);
1050
1051 wp_register_script('jquery-uc-override', UD_CENTRAL_URL.'/js/deprecated.js', array('jquery'), $enqueue_version);
1052
1053 // https://github.com/private-face/jquery.fullscreen
1054 wp_register_script('jquery-fullscreen', UD_CENTRAL_URL.'/js/jquery-fullscreen/jquery.fullscreen'.$min_or_not.'.js', array('jquery', 'jquery-uc-override'), '0.5.1');
1055
1056 wp_register_script('modernizr-custom', UD_CENTRAL_URL.'/js/modernizr/modernizr-custom'.$min_or_not.'.js', array(), '3.3.1');
1057
1058 wp_register_script('updraftcentral-queue', UD_CENTRAL_URL.'/js/queue.js', array(), $enqueue_version);
1059 wp_register_script('d3-queue', UD_CENTRAL_URL.'/js/d3-queue/d3-queue'.$min_or_not.'.js', array(), '3.0.3');
1060
1061 $library_deps = array('jquery', 'jquery-fullscreen', 'sprintf', 'google-caja-sanitizer', 'bootbox', 'handlebars', 'forge');
1062 wp_register_script('uc-library', UD_CENTRAL_URL.'/js/uc-library'.$min_or_not.'.js', $library_deps, $enqueue_version);
1063
1064 wp_register_script('datatables', UD_CENTRAL_URL.'/js/datatables.net/jquery.dataTables'.$min_or_not.'.js', array('jquery'), $enqueue_version);
1065 wp_register_script('datatables-responsive', UD_CENTRAL_URL.'/js/datatables.net/dataTables.responsive'.$min_or_not.'.js', array('datatables'), $enqueue_version);
1066
1067 $dashboard_deps = array('jquery', 'bootbox', 'jquery-fullscreen', 'sprintf', 'class-udrpc', 'google-caja-sanitizer', 'handlebars', 'modernizr-custom', 'updraftcentral-queue', 'd3-queue', 'uc-library', 'jquery-ui-sortable', 'datatables-responsive');
1068
1069 include ABSPATH.WPINC.'/version.php';
1070 global $wpdb;
1071
1072 if (function_exists('curl_version')) {
1073 $curl_version = curl_version();
1074 $curl_version = $curl_version['version'];
1075 if (!function_exists('curl_exec')) $curl_version .= '/Disabled';
1076 } else {
1077 $curl_version = '-';
1078 }
1079
1080 $shortcuts = get_user_meta($this->user->user_id, 'updraftcentral_dashboard_shortcuts', true);
1081 if (!is_array($shortcuts)) $shortcuts = array();
1082
1083 $pass_to_js = array(
1084 'udc_version' => self::VERSION,
1085 'php_version' => PHP_VERSION,
1086 'wp_version' => $wp_version,// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- Fine to ignore
1087 'mysql_version' => $wpdb->db_version(),
1088 'curl_version' => $curl_version,
1089 'home_url' => home_url(),
1090 'handlebars' => $this->get_handlebars_data(),
1091 'show_licence_counts' => apply_filters('updraftcentral_show_licence_counts', false),
1092 'user_defined_timeout' => $this->user->get_user_defined_timeout(),
1093 'shortcut_status' => $this->user->get_keyboard_shortcut_status(),
1094 'load_setting' => $this->user->get_load_setting(),
1095 'user_defined_shortcuts' => $shortcuts,
1096 'backup_choice' => $this->user->get_backup_choice(),
1097 'max_http_connection' => apply_filters('updraftcentral_max_http_connection', 6),
1098 );
1099
1100 if (!empty($pass_to_js['handlebars']['enqueue'])) {
1101 $dashboard_deps[] = 'updraftcentral-handlebars-compiled';
1102 wp_register_script('updraftcentral-handlebars-compiled', $pass_to_js['handlebars']['enqueue']['url'], array('handlebars'), filemtime($pass_to_js['handlebars']['enqueue']['file']));
1103 unset($pass_to_js['handlebars']['enqueue']);
1104 }
1105
1106 // Our dashboard framework
1107 wp_register_script('updraftcentral-dashboard', UD_CENTRAL_URL.'/js/dashboard'.$min_or_not.'.js', $dashboard_deps, $enqueue_version);
1108
1109 $localize = array_merge(
1110 array('common_urls' => $this->get_common_urls()),
1111 $pass_to_js,
1112 include(UD_CENTRAL_DIR.'/dashboard-translations.php'),
1113 include(UD_CENTRAL_DIR.'/keyboard-mappings.php')
1114 );
1115
1116 wp_localize_script('updraftcentral-dashboard', 'udclion', apply_filters('updraftcentral_udrclion', $localize));
1117
1118 do_action('updraftcentral_load_dashboard_js', $enqueue_version);
1119
1120 wp_enqueue_script('jquery-ui-tabs', array('jquery', 'jquery-ui'));
1121 }
1122
1123 /**
1124 * Gets template directory
1125 *
1126 * @return mixed
1127 */
1128 public function get_templates_dir() {
1129 return apply_filters('updraftcentral_templates_dir', wp_normalize_path(UD_CENTRAL_DIR.'/templates'));
1130 }
1131
1132 /**
1133 * Gets templates URL
1134 *
1135 * @return mixed
1136 */
1137 public function get_templates_url() {
1138 return apply_filters('updraftcentral_templates_url', UD_CENTRAL_URL.'/templates');
1139 }
1140
1141 /**
1142 * Stores all template directories in an array
1143 *
1144 * @return void
1145 */
1146 private function register_template_directories() {
1147
1148 $template_directories = array();
1149
1150 $templates_dir = $this->get_templates_dir();
1151
1152 if ($dh = opendir($templates_dir)) {
1153 while (($file = readdir($dh)) !== false) {
1154 if ('.' == $file || '..' == $file) continue;
1155 if (is_dir($templates_dir.'/'.$file)) {
1156 $template_directories[$file] = $templates_dir.'/'.$file;
1157 }
1158 }
1159 closedir($dh);
1160 }
1161
1162 // This is the optimal hook for most extensions to hook into
1163 $this->template_directories = apply_filters('updraftcentral_template_directories', $template_directories);
1164
1165 }
1166
1167 /**
1168 * Compile and return all handlebars templates
1169 *
1170 * @return array A list of handlebars templates to compile and/or scripts to enqueue, plus a base URL
1171 */
1172 private function get_handlebars_data() {
1173
1174 $templates_dir = $this->get_templates_dir();
1175 $templates_url = $this->get_templates_url();
1176
1177 $handlebars = array('base' => $templates_url);
1178
1179 $handlebars_compile = array();
1180
1181 $compiled_file = apply_filters('updraftcentral_handlebars_templates_compiled_file', $templates_dir.'/handlebars-compiled.js');
1182
1183 if ((!defined('UPDRAFTCENTRAL_DEV_ENVIRONMENT') || !UPDRAFTCENTRAL_DEV_ENVIRONMENT) && file_exists($compiled_file)) {
1184 $handlebars['enqueue'] = apply_filters('updraftcentral_handlebars_templates_enqueue', array('url' => $templates_url.'/handlebars-compiled.js', 'file' => $compiled_file));
1185 }
1186
1187 if ((defined('UPDRAFTCENTRAL_DEV_ENVIRONMENT') && UPDRAFTCENTRAL_DEV_ENVIRONMENT) || !file_exists($compiled_file)) {
1188 foreach ($this->template_directories as $prefix => $directory) {
1189 $templates = $this->register_handlebars_templates($prefix, $directory);
1190 $handlebars_compile = array_merge($handlebars_compile, $templates);
1191 }
1192 }
1193
1194 $handlebars['compile'] = apply_filters('updraftcentral_handlebars_compile', $handlebars_compile);
1195
1196 return $handlebars;
1197 }
1198
1199 /**
1200 * This method is for telling UpdraftCentral to recursively scan the indicated directory, and to add all found handlebars templates (i.e. ones ending in .handlebars.html) to the list of templates to (potentially) compile
1201 *
1202 * @param string $prefix
1203 * @param string $directory
1204 * @return array
1205 */
1206 public function register_handlebars_templates($prefix, $directory) {
1207
1208 $handlebars_compile = array();
1209
1210 try {
1211
1212 $directory_iterator = new RecursiveDirectoryIterator($directory);
1213 $iterator = new RecursiveIteratorIterator($directory_iterator);
1214 $regex = new RegexIterator($iterator, '/^.+\.handlebars\.html$/i', RecursiveRegexIterator::GET_MATCH);
1215
1216 foreach ($regex as $files) {
1217 foreach ($files as $file) {
1218 $basename = substr($file, 1 + strlen($directory));
1219 $template_name = $prefix.'-'.str_replace('/', '-', wp_normalize_path(substr($basename, 0, -strlen('.handlebars.html'))));
1220 $get_template_from = apply_filters('updraftcentral_handlebars_template_file', $file, $template_name);
1221 $handlebars_compile[$template_name] = apply_filters('updraftcentral_handlebars_template_contents', file_get_contents($get_template_from), $template_name, $get_template_from);
1222 }
1223
1224 }
1225 } catch (Exception $e) {
1226 error_log('UpdraftCentral: error when scanning for handlebars templates: '.$e->getMessage());
1227 }
1228
1229 return $handlebars_compile;
1230 }
1231
1232 /**
1233 * Enqueues required stylesheets
1234 *
1235 * @return void
1236 */
1237 public function load_dashboard_css() {
1238 $enqueue_version = $this->get_enqueue_version();
1239
1240 wp_enqueue_style('updraftcentral-dashboard-css', UD_CENTRAL_URL.'/css/dashboard.css', array('dashicons'), $enqueue_version);
1241
1242 wp_enqueue_style('updraftcentral-mobile-css', UD_CENTRAL_URL.'/css/mobile.css', array('updraftcentral-dashboard-css'), $enqueue_version);
1243
1244 // Temporary file for developers to add CSS in without stepping on the toes of people working on styling
1245 wp_enqueue_style('updraftcentral-dashboard-temp-css', UD_CENTRAL_URL.'/css/dashboard-temp.css', array('updraftcentral-dashboard-css'), $enqueue_version);
1246
1247 // Loads Google fonts.
1248 wp_enqueue_style('updraftcentral-google-fonts-source-sans-pro', 'https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700', $enqueue_version);
1249
1250 // Temp new design
1251 wp_enqueue_style('updraftcentral-new-design', UD_CENTRAL_URL.'/css/dashboard-design-temp.css', array('updraftcentral-dashboard-css'), $enqueue_version);
1252
1253 wp_enqueue_style('datatables', UD_CENTRAL_URL.'/css/datatables.net/css/jquery.dataTables.css', array('updraftcentral-dashboard-temp-css'), $enqueue_version);
1254 wp_enqueue_style('datatables-responsive', UD_CENTRAL_URL.'/css/datatables.net/css/responsive.dataTables.css', array('datatables'), $enqueue_version);
1255
1256 do_action('updraftcentral_load_dashboard_css', $enqueue_version);
1257 }
1258
1259 /**
1260 * Enqueues Bootstrap stylesheet and dequeues old PageLines
1261 *
1262 * @return void
1263 */
1264 public function wp_footer() {
1265 $enqueue_version = $this->get_enqueue_version();
1266 $min_or_not = $this->use_unminified_scripts() ? '' : '.min';
1267
1268 wp_enqueue_style('bootstrap5', UD_CENTRAL_URL.'/css/bootstrap/bootstrap'.$min_or_not.'.css', array(), $enqueue_version);
1269
1270 // Old testing sandbox used PageLines; no longer; but perhaps someone else will be doing
1271 wp_dequeue_script('pagelines-bootstrap-all');
1272 }
1273
1274 /**
1275 * Sets up text domain for plugin upon the WordPress action plugins_loaded
1276 *
1277 * @return void
1278 */
1279 public function plugins_loaded() {
1280 load_plugin_textdomain('updraftcentral', false, basename(UD_CENTRAL_DIR).'/languages');
1281 }
1282
1283 /**
1284 * Verify whether the currently logged-in WP user is allowed to access the specified page (or any pages)
1285 *
1286 * @param Boolean|String $page - The page
1287 * @param Boolean|String $require_role - Require the user to have a particular role
1288 * @param Boolean $require_cap - Require the user to have a particular capability
1289 *
1290 * @return Boolean - the result
1291 */
1292 public function access_role_check($page = false, $require_role = false, $require_cap = false) {
1293
1294 $current_user = wp_get_current_user();
1295 $user_roles = $current_user->roles;
1296
1297 $allowed = false;
1298
1299 if (is_super_admin()) $allowed = true;
1300
1301 // With this option, *any* matching role grants access
1302 if (is_array($require_role) && !empty($require_role)) {
1303 foreach ($require_role as $rr) {
1304 if (in_array(trim(strtolower($rr)), $user_roles)) {
1305 $allowed = true;
1306 break;
1307 }
1308 }
1309 } else {
1310 $allowed = true;
1311 }
1312
1313 // With this option, *all* specified capabilities are required
1314 if ($allowed && is_array($require_cap) && !empty($require_cap)) {
1315 foreach ($require_cap as $rc) {
1316 if (!current_user_can($rc)) {
1317 $allowed = false;
1318 break;
1319 }
1320 }
1321 }
1322
1323 return apply_filters('updraftcentral_access_role_check', $allowed, $current_user, $page, $require_role, $require_cap);
1324 }
1325
1326 /**
1327 * Runs upon plugin activation to check requirements and create tables
1328 *
1329 * @return void
1330 */
1331 public function activation_hook() {
1332 include_once UD_CENTRAL_DIR.'/classes/activation.php';
1333 UpdraftCentral_Activation::install();
1334 }
1335
1336 /**
1337 * Runs upon plugin deactivation to do housekeeping or cleanup
1338 *
1339 * @return void
1340 */
1341 public function deactivation_hook() {
1342 if (!function_exists('_get_cron_array')) include ABSPATH.WPINC.'/cron.php';
1343
1344 $crons = _get_cron_array();
1345 if (!empty($crons)) {
1346 foreach ($crons as $cron) {
1347 foreach ($cron as $key => $value) {
1348 if (preg_match('#^updraftcentral#', $key)) {
1349 foreach ($value as $schedule) {
1350 wp_clear_scheduled_hook($key, $schedule['args']);
1351 }
1352 }
1353 }
1354 }
1355 }
1356 }
1357
1358 /**
1359 * Stores notices in an array
1360 *
1361 * @param string $content Notice message to log
1362 * @param string $level Notice level e.g. error
1363 * @param bool $unique_id Unique ID of notice message
1364 * @param array $options Options for notice message. eg. can close?
1365 * @return void
1366 */
1367 public function log_notice($content, $level = 'error', $unique_id = false, $options = array()) {
1368 if (apply_filters('updraftcentral_log_notice', true, $content, $level, $unique_id)) {
1369
1370 $defaults = array(
1371 'show_dismiss' => 'true'
1372 );
1373
1374 $options = wp_parse_args($options, $defaults);
1375
1376 $log_this = apply_filters('updraftcentral_log_notice_content', array('level' => $level, 'content' => $content, 'options' => $options), $level, $unique_id);
1377
1378 if ($unique_id) {
1379 $this->notices[$unique_id] = $log_this;
1380 } else {
1381 $this->notices[] = $log_this;
1382 }
1383 }
1384 }
1385
1386 /**
1387 * Remove a previously added notice
1388 *
1389 * @param string $notice_id
1390 * @return void
1391 */
1392 public function remove_notice($notice_id) {
1393 if (isset($this->notices[$notice_id])) {
1394 unset($this->notices[$notice_id]);
1395 }
1396 }
1397
1398 /**
1399 * Includes a notices template and displays logged notices using that template
1400 *
1401 * @return void
1402 */
1403 public function print_dashboard_notices() {
1404 // We only want one notice area
1405 static $printed_notice_container = false;
1406 if ($printed_notice_container) return;
1407 $this->include_template('dashboard/notices.php', false, array('notices' => $this->notices));
1408 $printed_notice_container = true;
1409 }
1410
1411 /**
1412 * Initalizes plugin functionality
1413 *
1414 * Sets up an array of template directories to use, current user
1415 * Includes Site meta and options class
1416 *
1417 * @return bool Returns true upon successful initialisation, else false.
1418 */
1419 private function init() {
1420 if ($this->inited) return true;
1421
1422 if (!is_user_logged_in()) return false;
1423
1424 $this->register_template_directories();
1425
1426 $user = wp_get_current_user();
1427
1428 try {
1429 $this->user = $this->get_user_object($user->ID);
1430 } catch (Exception $e) {
1431 $failure = true;
1432 $this->log_notice($e->getMessage().' ('.get_class($e).')', 'error');
1433 }
1434
1435 if (!class_exists('UpdraftCentral_Site_Meta')) include_once UD_CENTRAL_DIR.'/classes/site-meta.php';
1436
1437 if (!class_exists('UpdraftCentral_Options')) include_once UD_CENTRAL_DIR.'/classes/updraftcentral-options.php';
1438
1439 try {
1440 $this->site_meta = new UpdraftCentral_Site_Meta($this->table_prefix);
1441 } catch (Exception $e) {
1442 $failure = true;
1443 $this->log_notice($e->getMessage().' ('.get_class($e).')', 'error');
1444 }
1445
1446 if (empty($failure)) {
1447 $this->inited = true;
1448 do_action('updraftcentral_inited');
1449 }
1450
1451 return $this->inited;
1452 }
1453
1454 /**
1455 * Get user object based on id
1456 *
1457 * @param int $user_id - User ID
1458 * @return UpdraftCentral_User - UpdraftCentral user object
1459 */
1460 public function get_user_object($user_id) {
1461 if (!class_exists('UpdraftCentral_User')) include_once UD_CENTRAL_DIR.'/classes/user.php';
1462
1463 return new UpdraftCentral_User($user_id);
1464 }
1465
1466 /**
1467 * Gets an RPC object, and sets some defaults on it that we always want
1468 *
1469 * @param string $indicator_name
1470 * @return UpdraftPlus_Remote_Communications_V2
1471 */
1472 public function get_udrpc($indicator_name = 'central.updraftplus.com') {
1473
1474 if (class_exists('GuzzleHttp\Client') && class_exists('GuzzleHttp\Utils')) {
1475 // No-op. Trying to reduce changes of one class getting loaded from a different version of Guzzle when more than one auto-loader has registered a version, via invoking any existing auto-loader first.
1476 }
1477 // Include composer autoload.php to get libraries
1478 $this->ensure_crypto_is_autoloaded();
1479
1480 // Check if UpdraftPlus_Remote_Communications_V2 is present before including class-udrpc.php
1481 if (!class_exists('UpdraftPlus_Remote_Communications_V2')) include_once UD_CENTRAL_DIR.'/vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc2.php';
1482
1483 $ud_rpc = new UpdraftPlus_Remote_Communications_V2($indicator_name);
1484 $ud_rpc->set_can_generate(true);
1485
1486 return $ud_rpc;
1487 }
1488
1489 /**
1490 * Adds settings and UDC site links to plugin links in WordPress Dashboard Plugins Page
1491 *
1492 * @param string $links - Existing links to be used in plugin dashboard page
1493 * @return mixed $links - Updated links array
1494 */
1495 public function plugin_action_links($links) {
1496
1497 $link = '<a href="'.admin_url('admin.php?page=updraft-central').'">'.__('Settings', 'updraftcentral').'</a>';
1498 array_unshift($links, $link);
1499
1500 $link2 = '<a href="http://updraftcentral.com">'.__('UpdraftCentral website', 'updraftcentral').'</a>';
1501
1502 array_unshift($links, $link2);
1503
1504 return $links;
1505 }
1506
1507 /**
1508 * Includes templates from mentioned path. Either returns it or echos it
1509 *
1510 * @param string $path - Path of template file to be included
1511 * @param bool $return_instead_of_echo - Option to echo or return template
1512 * @param array $extract_these - Data available to template
1513 * @return string
1514 */
1515 public function include_template($path, $return_instead_of_echo = false, $extract_these = array()) {
1516 if ($return_instead_of_echo) ob_start();
1517
1518 if (preg_match('#^([^/]+)/(.*)$#', $path, $matches)) {
1519 $prefix = $matches[1];
1520 $suffix = $matches[2];
1521 if (isset($this->template_directories[$prefix])) {
1522 $template_file = $this->template_directories[$prefix].'/'.$suffix;
1523 }
1524 }
1525
1526 // @codingStandardsIgnoreStart
1527 // Not yet used
1528 // public function wp_get_var($table, $where) {
1529 // global $wpdb;
1530 // $var = $wpdb->get_var("SELECT * FROM ".$wpdb->base_prefix.$this->table_prefix.$table." WHERE ".$where);
1531 // if (null === $var && !empty($wpdb->last_error)) return new WP_Error('database_getvar_error', $wpdb->last_error);
1532 // return $var;
1533 // }
1534 // @codingStandardsIgnoreEnd
1535
1536 if (!isset($template_file)) {
1537 $template_file = UD_CENTRAL_DIR.'/templates/'.$path;
1538 }
1539
1540 $template_file = apply_filters('updraftcentral_template', $template_file, $path);
1541
1542 do_action('updraftcentral_before_template', $path, $template_file, $return_instead_of_echo, $extract_these);
1543
1544 if (!file_exists($template_file)) {
1545 error_log("UpdraftCentral: template not found: $template_file");
1546 echo esc_html__('Error:', 'updraftcentral').' '.esc_html__('template not found', 'updraftcentral')." (".esc_html($path).")";
1547 } else {
1548 extract($extract_these);
1549 $updraft_central = $this;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Fine to ignore since usage is done within the included template file.
1550 include $template_file;
1551 }
1552
1553 do_action('updraftcentral_after_template', $path, $template_file, $return_instead_of_echo, $extract_these);
1554
1555 if ($return_instead_of_echo) return ob_get_clean();
1556 }
1557
1558 /**
1559 * Retrieves the appropriate database object for the current table
1560 *
1561 * @param string $table - Table name to be checked
1562 * @return wpdb
1563 */
1564 private function get_db_for_table($table) {
1565 if (in_array($table, $this->uc_tables)) {
1566 return $this->db;
1567 } else {
1568 global $wpdb;
1569 return $wpdb;
1570 }
1571 }
1572
1573 /**
1574 * Retrieves the tables from the designated UpdraftCentral database and
1575 * fills the UpdraftCentral::uc_tables property
1576 *
1577 * @return void
1578 */
1579 private function populate_updraftcentral_tables_list() {
1580 // If UpdraftCentral::uc_tables property has already been filled or $this->db
1581 // and the $this->table_prefix are not yet defined then we return.
1582 if (!empty($this->uc_tables) || empty($this->db) || empty($this->table_prefix)) return;
1583
1584 $tables = $this->db->get_results('SHOW TABLES', ARRAY_A);
1585 if (!empty($tables)) {
1586 foreach ($tables as $value) {
1587 foreach ($value as $table) {
1588 $this->uc_tables[] = $this->str_replace_once($this->table_prefix, '', $this->str_replace_once($this->db->base_prefix, '', $table, false), false);
1589 }
1590 }
1591 }
1592 }
1593
1594 /**
1595 * Replace the first, and only the first, instance within a string
1596 *
1597 * @param String $needle - the search term
1598 * @param String $replace - the replacement term
1599 * @param String $haystack - the string to replace within
1600 * @param Boolean $case_sensitive - whether the replacement should be case sensitive or not
1601 *
1602 * @return String - the filtered string
1603 */
1604 private function str_replace_once($needle, $replace, $haystack, $case_sensitive = true) {
1605 $pos = $case_sensitive ? strpos($haystack, $needle) : stripos($haystack, $needle);
1606 return (false !== $pos) ? substr_replace($haystack, $replace, $pos, strlen($needle)) : $haystack;
1607 }
1608
1609 /**
1610 * Selects a row of data and returns it
1611 *
1612 * @see WPDB::get_row()
1613 * @param string $table - Table name to be retrieved
1614 * @param string $where - Condition to match with retrieved data
1615 * @return array | WP_Error - Array of retrieved row data or a WP_Error object
1616 */
1617 public function wp_get_row($table, $where) {
1618 $db = $this->get_db_for_table($table);
1619
1620 $row = $db->get_row('SELECT * FROM '.$db->base_prefix.$this->table_prefix.$table.' WHERE '.$where);
1621 if (null === $row && !empty($db->last_error)) return new WP_Error('database_get_row_error', $db->last_error);
1622
1623 return $row;
1624 }
1625
1626 /**
1627 * Performs a database delete operation. A thin layer on WPDB::delete().
1628 *
1629 * @see WPDB::delete()
1630 * @param String $table - the table to delete from (without any of our prefixes)
1631 * @param Array $where - A named array of WHERE clauses (in column/value pairs)
1632 * @return Integer|WP_Error - the number of rows deleted, or a WP_Error object
1633 */
1634 public function wp_delete($table, $where) {
1635 $db = $this->get_db_for_table($table);
1636
1637 $deleted = $db->delete($db->base_prefix.$this->table_prefix.$table, $where);
1638 if (false === $deleted) return new WP_Error('database_delete_error', $db->last_error);
1639
1640 return $deleted;
1641 }
1642
1643 /**
1644 * Performs a database insert operation. A thin layer on WPDB::insert().
1645 *
1646 * @see WPDB::insert()
1647 * @param String $table - the table to insert into (without any of our prefixes)
1648 * @param Array $data - A named array of column/value pairs
1649 * @param Array|String|null $format - An array of formats to be mapped to each of the value in $data. If string, that format will be used for all of the values in $data.
1650 * @return Integer|WP_Error - the number of rows inserted, or a WP_Error object
1651 */
1652 public function wp_insert($table, $data, $format = null) {
1653 $db = $this->get_db_for_table($table);
1654
1655 $inserted = $db->insert($db->base_prefix.$this->table_prefix.$table, $data, $format);
1656 if (false === $inserted) return new WP_Error('database_add_error', $db->last_error);
1657
1658 return $inserted;
1659 }
1660
1661 /**
1662 * Performs a database update operation. A thin layer on WPDB::update().
1663 *
1664 * @see WPDB::insert()
1665 * @param String $table - the table to insert into (without any of our prefixes)
1666 * @param Array $data - A named array of column/value pairs
1667 * @param Array $where - A named array of WHERE clauses (in column/value pairs)
1668 * @param Array|String|null $format - An array of formats to be mapped to each of the value in $data. If string, that format will be used for all of the values in $data.
1669 * @param Array|String|null $where_format - An array of formats to be mapped to each of the value in $where. If string, that format will be used for all of the values in $data.
1670 * @return Integer|WP_Error - the number of rows updated, or a WP_Error object
1671 */
1672 public function wp_update($table, $data, $where, $format = null, $where_format = null) {
1673 $db = $this->get_db_for_table($table);
1674
1675 $updated = $db->update($db->base_prefix.$this->table_prefix.$table, $data, $where, $format, $where_format);
1676 if (false === $updated) return new WP_Error('database_update_error', $db->last_error);
1677
1678 return $updated;
1679 }
1680
1681 /**
1682 * Does not have to (and should not be relied upon to) be able to infallibly detect
1683 *
1684 * @param string $url
1685 * @return boolean
1686 */
1687 public function url_looks_internal($url) {
1688 $url_host = strtolower(parse_url($url, PHP_URL_HOST));
1689 if (0 === strpos($url_host, 'localhost') || strpos($url_host, '127.') === 0 || strpos($url_host, '10.') === 0 || '::1' == $url_host || substr($url_host, -10, 10) == '.localhost' || substr($url_host, -4, 4) == '.dev' || '.localdomain' == substr($url_host, -12, 12)) return true;
1690
1691 return false;
1692 }
1693
1694 /**
1695 * Return instance of Updraft_Logger
1696 *
1697 * @return Updraft_Logger
1698 */
1699 public static function get_logger() {
1700 if (empty(self::$_logger_instance)) {
1701 if (!class_exists('UpdraftCentral_Logger')) include_once UD_CENTRAL_DIR.'/classes/class-updraftcentral-logger.php';
1702 $updraft_logger = new UpdraftCentral_Logger();
1703
1704 // Loggers must implement the "Updraft_Logger_Interface"
1705 // interface to be added as valid loggers
1706 $loggers = apply_filters('updraftcentral_loggers', $updraft_logger->get_loggers());
1707 if (!empty($loggers)) {
1708 foreach ($loggers as $logger) {
1709 $updraft_logger->add_logger($logger);
1710 }
1711 }
1712
1713 // Making sure that we have at least 1 logger to use in case all else fails.
1714 $updraft_loggers = $updraft_logger->get_loggers();
1715 if (empty($updraft_loggers)) {
1716 // Add PHP Logger as the default logger if no logger is available
1717 if (!class_exists('UpdraftCentral_PHP_Logger')) include_once UD_CENTRAL_DIR.'/classes/class-updraftcentral-php-logger.php';
1718 $logger = new UpdraftCentral_PHP_Logger();
1719 $updraft_logger->add_logger($logger);
1720 }
1721
1722 self::$_logger_instance = $updraft_logger;
1723 }
1724
1725 return self::$_logger_instance;
1726 }
1727
1728 /**
1729 * Message to capture or log
1730 *
1731 * @param string $message Message to insert into the log.
1732 * @param string $level The type of message this current log contain (e.g. info, warning, debug, error)
1733 * @param array $context Context of the log.
1734 */
1735 public function log($message, $level = 'debug', $context = array()) {
1736 // For now, we set the $level to 'debug' as default when logging (mostly for debugging purposes).
1737 // The level can be in the form of 'info', 'warning', etc. Please refer to the 'Updraft_Log_Levels'
1738 // class for a complete list of level definitions.
1739 self::get_logger()->log($message, $level, $context);
1740 }
1741
1742 /**
1743 * Checks whether the submitted data is a valid json-encoded string
1744 *
1745 * @param Mixed $data The data to validate
1746 * @return Boolean - True when $data is a valid json string, False otherwise.
1747 */
1748 public function is_json($data) {
1749 return is_string($data) && is_array(json_decode($data, true)) ? true : false;
1750 }
1751
1752 /**
1753 * JSON encodes data whenever applicable
1754 *
1755 * @param Mixed $data The data to process
1756 * @return Mixed
1757 */
1758 public function maybe_json_encode($data) {
1759 // We only encode data that is currently not json-encoded and
1760 // those that has 'object' or 'array' data types. Otherwise, we'll return
1761 // them as is.
1762 if (in_array(gettype($data), array('object', 'array'))) {
1763 $data = json_encode($data);
1764 }
1765
1766 return $data;
1767 }
1768
1769 /**
1770 * JSON decodes data to its original form whenever applicable
1771 *
1772 * @param Mixed $data The data to process
1773 * @return Mixed
1774 */
1775 public function maybe_json_decode($data) {
1776 // Decode data to its original form only when the given $data
1777 // is a valid json string.
1778 if (is_array($data)) {
1779 foreach ($data as $key => $value) {
1780 if ($this->is_json($value)) {
1781 $data[$key] = json_decode($value, true);
1782 }
1783 }
1784 } else {
1785 if ($this->is_json($data)) {
1786 $data = json_decode($data, true);
1787 }
1788 }
1789
1790 return $data;
1791 }
1792
1793 /**
1794 * This is part of the wp_privacy_personal_data_erasers
1795 * THis will export the list of sites registered by the user
1796 *
1797 * @param string $email_address The email of user sites to be exported
1798 * @return array
1799 */
1800 public function export_registered_sites($email_address) {
1801 // Get user ID by emails
1802 $user_id = get_user_by('email', $email_address);
1803 $export_items = array();
1804
1805 // Check to make sure a user has returned
1806 if ($user_id && $user_id->ID) {
1807 // Get user
1808 $user = $this->get_user_object($user_id->ID);
1809
1810 // Get user sites
1811 $sites = $user->load_user_sites();
1812
1813 // Check if the user has added any sites
1814 if (!empty($sites)) {
1815 // Get each site information and add it to the export array
1816 foreach ($sites as $site) {
1817 // Add this group of items to the exporters data array.
1818 $export_items[] = array(
1819 'group_id' => "registered-sites",
1820 'group_label' => __('UpdraftCentral Registered sites', 'updraftcentral'),
1821 'item_id' => "registered-sites-{$user_id->ID}",
1822 'data' => array(
1823 array(
1824 'name' => __('Remote user login', 'updraftcentral'),
1825 'value' => $site->remote_user_login
1826 ),
1827 array(
1828 'name' => __('Remote user ID', 'updraftcentral'),
1829 'value' => $site->remote_user_id
1830 ),
1831 array(
1832 'name' => __('Description', 'updraftcentral'),
1833 'value' => $site->description
1834 ),
1835 array(
1836 'name' => __('URL', 'updraftcentral'),
1837 'value' => $site->url
1838 ),
1839 )
1840 );
1841
1842 // Get site meta info
1843 if (!class_exists('UpdraftCentral_Site_Meta')) include_once UD_CENTRAL_DIR.'/classes/site-meta.php';
1844
1845 // Call site meta class
1846 $get_site_meta = new UpdraftCentral_Site_Meta($this->table_prefix);
1847
1848 // Add site meta data to the sites
1849 $sites_meta = $get_site_meta->get_site_meta($site->site_id, 'site_tag', false, false, true);
1850
1851
1852 if (!empty($sites_meta)) {
1853 foreach ($sites_meta as $meta) {
1854 $export_items[] = array(
1855 'data' => array(
1856 array(
1857 'name' => __('Meta ID', 'updraftcentral'),
1858 'value' => $meta->meta_id
1859 ),
1860 array(
1861 'name' => __('Meta key', 'updraftcentral'),
1862 'value' => $meta->meta_key
1863 ),
1864 array(
1865 'name' => __('Meta value', 'updraftcentral'),
1866 'value' => $meta->meta_value
1867 ),
1868 array(
1869 'name' => __('Created', 'updraftmanager'),
1870 'value' => $meta->updraftcentral
1871 ),
1872 )
1873 );
1874 }
1875 }
1876 }
1877 } else {
1878 $export_items[] = array(
1879 'group_id' => "registered-sites",
1880 'group_label' => __('registered sites', 'updraftcentral'),
1881 'item_id' => "registered-sites-{$user_id->ID}",
1882 'data' => array(
1883 array(
1884 'name' => __('No sites found', 'updraftcentral'),
1885 'value' => 'No Sites Found'
1886 ),
1887 )
1888 );
1889 }
1890 } else {
1891 $export_items[] = array(
1892 'group_id' => "registered-sites",
1893 'group_label' => __('registered sites', 'updraftcentral'),
1894 'item_id' => "registered-sites-{$user_id->ID}",
1895 'data' => array(
1896 array(
1897 'name' => __('Not Found', 'updraftcentral'),
1898 'value' => 'User Not Found'
1899 ),
1900 )
1901 );
1902 }
1903
1904 // Return once completed
1905 return array(
1906 'data' => $export_items,
1907 'done' => true,
1908 );
1909 }
1910 }
1911
1912 endif;
1913
1914 /**
1915 * Creates an instance of UpdraftCentral class
1916 *
1917 * @return object
1918 */
1919 function UpdraftCentral() {
1920 return UpdraftCentral::instance();
1921 }
1922
1923 $GLOBALS['updraft_central'] = UpdraftCentral();
1924