PluginProbe
UpdraftCentral Dashboard / 0.8.32
UpdraftCentral Dashboard v0.8.32
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 / classes / user.php

user.php in UpdraftCentral Dashboard 0.8.32, at classes/user.php

2,162 lines 77.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('UD_CENTRAL_DIR')) die('Security check');
4
5 if (!class_exists('UpdraftCentral_User')) :
6
7 class UpdraftCentral_User {
8
9 private $rc;
10
11 public $user_id = null;
12
13 public $sites = null;
14
15 public $sites_meta = null;
16
17 private $php_events;
18
19 private $licence_manager = null;
20
21 private $sites_table;
22
23 private $sitemeta_table;
24
25 public function __construct($user_id) {
26
27 $this->user_id = (int) $user_id;
28
29 // We only check for the login state if the request is not coming from
30 // a cron event call
31 if (!defined('DOING_CRON') || !DOING_CRON) {
32 if (!is_user_logged_in()) throw new Exception('The current visitor is not logged in');
33 }
34
35 $ucdb = UpdraftCentral()->db;
36
37 $this->rc = UpdraftCentral();
38 $this->sites_table = $ucdb->base_prefix.$this->rc->table_prefix.'sites';
39 $this->sitemeta_table = $ucdb->base_prefix.$this->rc->table_prefix.'sitemeta';
40
41 add_filter('updraftcentral_dashboard_ajaxaction_newsite', array($this, 'dashboard_ajaxaction_newsite'), 10, 2);
42 add_filter('updraftcentral_dashboard_ajaxaction_import_settings', array($this, 'dashboard_ajaxaction_import_settings'), 10, 2);
43 add_filter('updraftcentral_dashboard_ajaxaction_export_settings', array($this, 'dashboard_ajaxaction_export_settings'), 10, 2);
44 add_filter('updraftcentral_dashboard_ajaxaction_edit_site_configuration', array($this, 'dashboard_ajaxaction_edit_site_configuration'), 10, 2);
45 add_filter('updraftcentral_dashboard_ajaxaction_edit_site_connection_method', array($this, 'dashboard_ajaxaction_edit_site_connection_method'), 10, 2);
46 add_filter('updraftcentral_dashboard_ajaxaction_delete_site', array($this, 'dashboard_ajaxaction_delete_site'), 10, 2);
47 add_filter('updraftcentral_dashboard_ajaxaction_sites_html', array($this, 'dashboard_ajaxaction_sites_html'));
48 add_filter('updraftcentral_dashboard_ajaxaction_site_rpc', array($this, 'dashboard_ajaxaction_site_rpc'), 10, 2);
49 add_filter('updraftcentral_dashboard_ajaxaction_manage_site_order', array($this, 'dashboard_ajaxaction_manage_site_order'), 10, 2);
50
51 add_filter('updraftcentral_load_user_sites', array($this, 'load_user_sites_filter'));
52 add_filter('updraftcentral_dashboard_ajaxaction_manage_site_meta', array($this, 'dashboard_ajaxaction_manage_site_meta'), 10, 2);
53
54 // Manage dashboard shortcuts through ajax request
55 add_filter('updraftcentral_dashboard_ajaxaction_shortcuts', array($this, 'dashboard_ajaxaction_shortcuts'), 10, 2);
56
57 // Handles module visibility
58 add_filter('updraftcentral_main_navigation_items', array($this, 'main_navigation_items'));
59 add_filter('updraftcentral_dashboard_ajaxaction_module_visibility', array($this, 'dashboard_ajaxaction_module_visibility'), 10, 2);
60 add_filter('updraftcentral_dashboard_ajaxaction_reset_modules_visibility', array($this, 'dashboard_ajaxaction_reset_modules_visibility'), 10, 2);
61
62 // Save timeout settings
63 add_filter('updraftcentral_dashboard_ajaxaction_save_timeout', array($this, 'dashboard_ajaxaction_save_timeout'), 10, 2);
64
65 add_filter('updraftcentral_dashboard_ajaxaction_cache_response', array($this, 'dashboard_ajaxaction_cache_response'), 10, 2);
66 add_filter('updraftcentral_dashboard_ajaxaction_save_settings', array($this, 'dashboard_ajaxaction_save_settings'), 10, 2);
67 add_filter('updraftcentral_site_alert_icon', array($this, 'dashboard_site_alert_icon'), 10, 2);
68 add_filter('updraftcentral_dashboard_ajaxaction_log_event', array($this, 'dashboard_ajaxaction_log_event'), 10, 2);
69 add_filter('updraftcentral_dashboard_ajaxaction_events', array($this, 'dashboard_ajaxaction_events'), 10, 2);
70 add_filter('updraftcentral_site_data_attributes', array($this, 'dashboard_attach_cached_data'), 10, 2);
71 add_filter('updraftcentral_dashboard_ajaxaction_load_event_sites', array($this, 'dashboard_ajaxaction_load_event_sites'), 10, 2);
72
73 // Load licence manager - this needs loading before the sites themselves are
74 if (!class_exists('UpdraftCentral_Licence_Manager')) include_once UD_CENTRAL_DIR.'/classes/licence-manager.php';
75
76 // Allow developers to implement their own licence management
77 $licence_manager_class = apply_filters('updraftcentral_licence_manager_class', 'UpdraftCentral_Licence_Manager');
78
79 $this->licence_manager = new $licence_manager_class($this, $this->rc);
80
81 $this->load_user_sites();
82
83 }
84
85 /**
86 * Retrieves user sites with captured events
87 *
88 * @param array $response A response array where we insert our request response
89 *
90 * @return array
91 */
92 public function dashboard_ajaxaction_load_event_sites($response) {
93 $response['responsetype'] = 'ok';
94 $response['message'] = 'success';
95
96 $ucdb = UpdraftCentral()->db;
97 $our_prefix = $ucdb->base_prefix.$this->rc->table_prefix;
98
99 $sql = 'SELECT s.description, e.site_id FROM '.$our_prefix.'events e, '.$our_prefix.'sites s WHERE e.site_id = s.site_id AND s.user_id = %d GROUP BY e.site_id';
100
101 $response['event_sites'] = $ucdb->get_results($ucdb->prepare($sql, $this->user_id), ARRAY_A);
102
103 return $response;
104 }
105
106 /**
107 * Attach cache data of a particular site to its site row attributes
108 *
109 * @param string $site_attributes Attribute string of the site
110 * @param object $site The site where the cached data attribute is to be added
111 *
112 * @return string
113 */
114 public function dashboard_attach_cached_data($site_attributes, $site) {
115 $data = $this->load_cached_data($site->site_id);
116 if (!empty($data)) {
117 $site_attributes .= ' data-cached_data="'.base64_encode(json_encode($data)).'"';
118 }
119
120 return $site_attributes;
121 }
122
123 /**
124 * Loads all events recorded/logged and their corresponding response data
125 *
126 * @param array $response A response array where we insert our request response
127 * @param array $post_data Parameters passed as an additional argument(s) to the request
128 *
129 * @calls die()
130 */
131 public function dashboard_ajaxaction_events($response, $post_data) {
132 $ucdb = UpdraftCentral()->db;
133 $our_prefix = $ucdb->base_prefix.$this->rc->table_prefix;
134
135 $response = array(
136 "draw" => 0,
137 "recordsTotal" => 0,
138 "recordsFiltered" => 0,
139 "data" => array()
140 );
141
142 $data = $post_data['data'];
143 if (!empty($data)) {
144 $draw = $data['draw'];
145 $row = $data['start'];
146 $row_per_page = $data['length'];
147 $column_index = $data['order'][0]['column'];
148 $column_name = $data['columns'][$column_index]['data'];
149 $column_sort_order = $data['order'][0]['dir'];
150 $search_value = $data['search']['value'];
151
152 $sql = 'SELECT e.*, s.description';
153 $sql_count = 'SELECT COUNT(e.event_id)';
154 $from = 'FROM '.$our_prefix.'events e, '.$our_prefix.'sites s';
155 $where = 'WHERE e.site_id = s.site_id AND s.user_id = '.$this->user_id;
156 if (isset($data['site_id'])) {
157 $where .= ' AND e.site_id = '.sanitize_text_field($data['site_id']);
158 }
159 $order = 'ORDER BY e.time DESC';
160 $limit = 'LIMIT %d,%d';
161 $order_by = false;
162
163 // If event_name is passed as sorting field we will use the combination of the
164 // event_name and the new field event_item in order to get the expected result
165 if ('event_name' === $column_name) {
166 if (false == (bool) $data['initial_load']) {
167 $order_by = sanitize_sql_orderby('event_name '.$column_sort_order.', event_item '.$column_sort_order);
168 }
169 } else {
170 // We can't add the order clause fields into wpdb->prepare since it will convert the
171 // values into 'field_name' 'ASC' or 'field_name' 'DESC', thus breaking the sorting
172 // capability of the datatables.net plugin. Therefore, we'll just have to sanitize
173 // it manually through WordPress' sanitize_sql_orderby method.
174 $order_by = sanitize_sql_orderby($column_name.' '.$column_sort_order);
175 }
176
177 if (false !== $order_by) {
178 $order = 'ORDER BY '.$order_by;
179 }
180
181 if (!empty($search_value)) {
182 $where .= ' AND (e.event_type LIKE %s OR e.event_name LIKE %s OR e.event_status LIKE %s OR s.description LIKE %s OR DATE(FROM_UNIXTIME(e.time)) LIKE %s OR SUBSTRING_INDEX(e.event_data, "\",", 1) LIKE %s)';
183 }
184
185 $sql = $sql.' '.$from.' '.$where.' '.$order.' '.$limit;
186 $sql_count = $sql_count.' '.$from.' '.$where;
187
188 $total_records = 0;
189 if (!empty($search_value)) {
190 $search_value = '%'.$search_value.'%';
191 $search_event_data = '%"name":"%'.$search_value.'%';
192
193 $total_records = $ucdb->get_var($ucdb->prepare($sql_count, $search_value, $search_value, $search_value, $search_value, $search_value, $search_event_data));
194 $result = $ucdb->get_results($ucdb->prepare($sql, $search_value, $search_value, $search_value, $search_value, $search_value, $search_event_data, $row, $row_per_page), ARRAY_A);
195 } else {
196 // We don't need the wpdb->prepare in getting the $total_records here since there's no token or
197 // user inputted data to replace in this COUNT query.
198 $total_records = $ucdb->get_var($sql_count);
199 $result = $ucdb->get_results($ucdb->prepare($sql, $row, $row_per_page), ARRAY_A);
200 }
201
202 $result_data = array();
203
204 $total_records_with_limit = 0;
205 if (!empty($result)) {
206 $total_records_with_limit = count($result);
207 foreach ($result as $item) {
208 $event_name = $this->get_readable_text($item['event_data'], $item['event_name'], $item['event_type']);
209 $result_data[] = array(
210 'event_name' => $event_name,
211 'description' => $item['description'],
212 'event_status' => $item['event_status'],
213 'time' => date('Y-m-d H:i:s', $item['time']),
214 'event_result_data' => '<a href="#" class="updraftcentral_events_result_view" data-event_result_data="'.base64_encode($item['event_result_data']).'">view</a>'
215 );
216 }
217 }
218
219 $response = array(
220 "draw" => intval($draw),
221 "recordsTotal" => $total_records ?: 0,
222 "recordsFiltered" => $total_records_with_limit ?: 0,
223 "data" => $result_data
224 );
225 }
226
227 echo json_encode($response);
228 die();
229 }
230
231 /**
232 * Builds a readable report/line to be presented to the user on the events table
233 *
234 * @param string $event_data The stored/logged event information
235 * @param string $event_name The name of the event
236 * @return string
237 */
238 private function get_readable_text($event_data, $event_name) {
239 $event_data = json_decode($event_data, true);
240 $text = '';
241 if (!empty($event_data) && isset($event_data['name'])) {
242 list($event_type, $item_type) = explode('.', $event_name);
243 $item = $event_data['name'];
244
245 if ('plugin' === $item_type) {
246 $item_type = __('plugin', 'updraftcentral');
247 } elseif ('theme' === $item_type) {
248 $item_type = __('theme', 'updraftcentral');
249 } elseif ('core' === $item_type) {
250 $item_type = __('core', 'updraftcentral');
251 } elseif ('translation' === $item_type) {
252 $item_type = __('translation', 'updraftcentral');
253 }
254
255 switch ($event_type) {
256 case 'install':
257 $text = sprintf(__('Installation of the %s %s', 'updraftcentral'), $item, $item_type);
258 break;
259 case 'update':
260 $text = sprintf(__('Update of the %s %s', 'updraftcentral'), $item, $item_type);
261 break;
262 case 'delete':
263 $text = sprintf(__('Deletion of the %s %s', 'updraftcentral'), $item, $item_type);
264 break;
265 case 'activate':
266 $text = sprintf(__('Activation of the %s %s', 'updraftcentral'), $item, $item_type);
267 break;
268 case 'deactivate':
269 $text = sprintf(__('Deactivation of the %s %s', 'updraftcentral'), $item, $item_type);
270 break;
271 case 'install_activate':
272 $text = sprintf(__('Installation and activation of the %s %s', 'updraftcentral'), $item, $item_type);
273 break;
274 }
275 }
276
277 return $text;
278 }
279
280 /**
281 * Returns an icon string if a site is not reacheable in the last 96 hours
282 *
283 * Used by the WP filter updraftcentral_site_alert_icon
284 *
285 * @param string $html HTML to use for an icon
286 * @param integer $site_id The ID of the site to check
287 *
288 * @return string - filtered value
289 */
290 public function dashboard_site_alert_icon($html, $site_id) {
291
292 // We're going to make sure that site_meta is not null before using it as it is being
293 // called early in the process within cron (see UpdraftCentral::rearrange_priority)
294 $site_meta = $this->rc->site_meta;
295 if (!is_a($site_meta, 'UpdraftCentral_Site_Meta')) {
296 if (!class_exists('UpdraftCentral_Site_Meta')) include_once UD_CENTRAL_DIR.'/classes/site-meta.php';
297 $site_meta = new UpdraftCentral_Site_Meta($this->rc->table_prefix);
298 }
299
300 $meta = $site_meta->get_site_meta($site_id, 'background_request_error', true, false, true);
301
302 if (!empty($meta) && is_array($meta) && !empty($meta['created'])) {
303 $result = $meta['meta_value'];
304
305 if (!empty($result) && is_array($result) && isset($result['message'])) {
306 $timestamp = $meta['created'];
307 $elapsed_hours = (time() - $timestamp)/3600;
308
309 // If it has not been possible to contact the site using our background/cron mechanism in the last 96 hours
310 // then display an alert icon next to the site.
311 if ($elapsed_hours >= 96) {
312 $message = !empty($result['message']) ? $result['message'] : __('Could not connect to remote site successfully.', 'updraftcentral');
313 return '<span class="dashicons dashicons-warning uc_site_alert_icon" title="'.$message.'"></span>';
314 }
315 }
316 }
317
318 return $html;
319 }
320
321 /**
322 * Saves a copy of the remote response as a result of executing the
323 * the submitted command along with its parameters.
324 *
325 * @param array $response A response array where we insert our request response
326 * @param array $post_data Parameters passed as an additional argument(s) to the request
327 * @return array
328 */
329 public function dashboard_ajaxaction_log_event($response, $post_data) {
330 $ucdb = UpdraftCentral()->db;
331 $our_prefix = $ucdb->base_prefix.$this->rc->table_prefix;
332
333 $response['responsetype'] = 'ok';
334 $response['message'] = 'success';
335 $data = $post_data['data'];
336
337 if (!empty($data)) {
338 $format = array('%d', '%s', '%s', '%s', '%s', '%s', '%d', '%s');
339 if (isset($data['bulk']) && 1 === intval($data['bulk'])) {
340 $data = $data['data'];
341 if (is_array($data)) {
342 foreach ($data as $arg) {
343 $arg['time'] = time();
344 $event_data = json_decode($arg['event_data'], true);
345 if (!empty($event_data['name'])) $arg['event_item'] = $event_data['name'];
346 $ucdb->insert($our_prefix.'events', $arg, $format);
347 }
348 }
349 } else {
350 $data['time'] = time();
351 $event_data = json_decode($data['event_data'], true);
352 if (!empty($event_data['name'])) $data['event_item'] = $event_data['name'];
353 $ucdb->insert($our_prefix.'events', $data, $format);
354 }
355 }
356
357 return $response;
358 }
359
360 /**
361 * Saves a copy of the remote (successful) response as a result of executing the
362 * the submitted command along with its parameters.
363 *
364 * @param array $response A response array where we insert our request response
365 * @param array $post_data Parameters passed as an additional argument(s) to the request
366 * @return array
367 */
368 public function dashboard_ajaxaction_cache_response($response, $post_data) {
369 $response['responsetype'] = 'ok';
370 $response['message'] = 'success';
371 $data = $post_data['data'];
372 $site_meta = $this->rc->site_meta;
373
374 if (isset($data['data']['force_refresh'])) {
375 // Make sure that we are passing an actual boolean value rather than a string
376 // representation of any boolean passed to the "force_refresh" parameter.
377 $data['data']['force_refresh'] = filter_var($data['data']['force_refresh'], FILTER_VALIDATE_BOOLEAN);
378 }
379
380 if (!isset($data['data'])) $data['data'] = array();
381 $cache_key = $this->generate_cache_key($data['site_id'], $data['command'], $data['data']);
382
383 if (!empty($data['response_data'])) {
384 $check = $site_meta->get_site_meta($data['site_id'], $cache_key, true);
385 if (!empty($check)) {
386 $result = $site_meta->update_site_meta($data['site_id'], $cache_key, $data['response_data']);
387 } else {
388 $result = $site_meta->add_site_meta($data['site_id'], $cache_key, $data['response_data']);
389 }
390
391 if (false !== $result) {
392 $result = $this->get_sites_cached_responses($cache_key, $data['site_id']);
393 if (!empty($result)) {
394 $response['data'] = $result;
395 }
396 }
397 }
398
399 return $response;
400 }
401
402 /**
403 * Loads cached data for the given site as a result from a previously run cron process
404 *
405 * @param integer $site_id The ID of the site where the cached data is to be pulled from
406 *
407 * @return array
408 */
409 public function load_cached_data($site_id) {
410 $cached_data = array();
411
412 // Retrieves all (scheduled) commands that were registered using the
413 // "updraftcentral_scheduled_commands" filter.
414 $scheduled_commands = apply_filters('updraftcentral_scheduled_commands', array());
415 if (!empty($scheduled_commands)) {
416 if (!is_array($this->sites)) $this->load_user_sites();
417
418 foreach ($scheduled_commands as $item) {
419 $command = $item['command'];
420 $data = $item['data'];
421
422 $cache_key = $this->generate_cache_key($site_id, $command, $data);
423 $cached_data[$command] = $this->get_sites_cached_responses($cache_key, $site_id);
424 }
425 }
426
427 return $cached_data;
428 }
429
430 /**
431 * Retrieves all stored (cached) responses for the given site
432 *
433 * @param string $key The meta key
434 * @param integer $site_id The ID of the site
435 *
436 * @return array|object|null
437 */
438 private function get_sites_cached_responses($key, $site_id) {
439 $ucdb = UpdraftCentral()->db;
440 $our_prefix = $ucdb->base_prefix.$this->rc->table_prefix;
441
442 $responses = $ucdb->get_row($ucdb->prepare("SELECT `site_id`, `created`, `meta_value` as `response` FROM ".$our_prefix."sitemeta WHERE `site_id` = %d AND `meta_key` = %s", $site_id, $key));
443
444 return $responses;
445 }
446
447 /**
448 * Retrieves available items for update from the stored/cached data
449 *
450 * @param integer $site_id The ID of the site where to pull the available updates from
451 *
452 * @return array
453 */
454 private function get_updates_count_from_cache($site_id) {
455
456 $site_meta = $this->rc->site_meta;
457
458 $cache_key = $this->generate_cache_key($site_id, 'updates.get_updates', array('force_refresh' => false));
459 $response = $site_meta->get_site_meta($site_id, $cache_key, true);
460
461 $counts = array('plugins' => 0, 'themes' => 0, 'core' => 0, 'translations' => 0);
462 if (is_array($response) && !empty($response)) {
463 if (isset($response['reply']) && isset($response['reply']['data'])) {
464 $data = $response['reply']['data'];
465 foreach ($data as $key => $value) {
466 if (isset($counts[$key])) {
467 if ('translations' === $key && isset($value['items'])) {
468 $counts[$key] = count($value['items']);
469 } else {
470 $counts[$key] = count($value);
471 }
472 }
473 }
474 }
475 }
476
477 return $counts;
478 }
479
480 /**
481 * Generates a unique key out from the site id, command and data parameters to
482 * be used as a "meta_key" field when saving the data to the DB.
483 *
484 * @param integer $site_id The ID of the site where the command is to be executed
485 * @param string $command The current command to execute
486 * @param array $data An array containing the command parameters
487 * @return string - The generated key
488 */
489 public function generate_cache_key($site_id, $command, $data) {
490 // N.B. The "meta_key" field format in the "sites_meta" table is done this way in order to store distinct
491 // responses for each command submitted. So, we better reconstruct the same key for each sites before
492 // saving and retrieving any available data to/in the DB as cached information.
493 $command_data_key = '_command'.$command.serialize($data);
494 return 'cached_data_'.md5('_site'.$site_id.$command_data_key);
495 }
496
497 /**
498 * Saves updraftcentral settings
499 *
500 * @param array $response A response array where we insert our request response
501 * @param array $post_data Parameters passed as an additional argument(s) to the request
502 * @return array
503 */
504 public function dashboard_ajaxaction_save_settings($response, $post_data) {
505 $response['responsetype'] = 'ok';
506 $response['message'] = 'success';
507
508 $data = $post_data['data'];
509
510 // Save backup choice setting
511 if (!empty($data['backup_choice'])) {
512 update_user_meta($this->user_id, 'updraftcentral_dashboard_backup_choice', $data['backup_choice']);
513 }
514
515 // Save load setting
516 if (!empty($data['load_setting'])) {
517 update_user_meta($this->user_id, 'updraftcentral_dashboard_load_setting', $data['load_setting']);
518 }
519
520 // Save timeout settings
521 if (!empty($data['timeout'])) {
522 update_user_meta($this->user_id, 'updraftcentral_dashboard_user_defined_timeout', $data['timeout']);
523 }
524
525 // Save keyboard shortcuts status
526 if (!empty($data['shortcut_status'])) {
527 update_user_meta($this->user_id, 'updraftcentral_dashboard_shortcut_status', $data['shortcut_status']);
528 }
529
530 return $response;
531 }
532
533 /**
534 * Gets the UpdraftCentral backup choice setting
535 *
536 * @return string
537 */
538 public function get_backup_choice() {
539 $backup_choice = 'default';
540 if (!empty($this->user_id)) {
541 $value = get_user_meta($this->user_id, 'updraftcentral_dashboard_backup_choice', true);
542 if (!empty($value)) $backup_choice = $value;
543 }
544
545 return $backup_choice;
546 }
547
548 /**
549 * Gets the UpdraftCentral load setting
550 *
551 * @return string
552 */
553 public function get_load_setting() {
554 $load_setting = 'active'; // Default: "active"
555 if (!empty($this->user_id)) {
556 $value = get_user_meta($this->user_id, 'updraftcentral_dashboard_load_setting', true);
557 if (!empty($value)) $load_setting = $value;
558 }
559
560 return $load_setting;
561 }
562
563 /**
564 * Gets the keyboard shortcut active/inactive status
565 *
566 * @return integer
567 */
568 public function get_keyboard_shortcut_status() {
569 $shortcut_status = 'active'; // Default: "active" (UpdraftCentral keyboard shortcuts features is active by default)
570 if (!empty($this->user_id)) {
571 $value = get_user_meta($this->user_id, 'updraftcentral_dashboard_shortcut_status', true);
572 if (!empty($value)) $shortcut_status = $value;
573 }
574
575 return $shortcut_status;
576 }
577
578 /**
579 * Saves user-defined timeout settings
580 *
581 * @param array $response A response array where we insert our request response
582 * @param array $post_data Parameters passed as an additional argument(s) to the request
583 * @return array
584 */
585 public function dashboard_ajaxaction_save_timeout($response, $post_data) {
586 $response['responsetype'] = 'ok';
587 $response['message'] = 'success';
588
589 $timeout = $post_data['data']['timeout'];
590 if (!empty($timeout)) {
591 update_user_meta($this->user_id, 'updraftcentral_dashboard_user_defined_timeout', $timeout);
592 }
593
594 return $response;
595 }
596
597 /**
598 * Gets the user defined timeout settings
599 *
600 * @param integer $default_timeout The default timeout when no user defined timeout is set. Defaults to 30 seconds.
601 * @return integer
602 */
603 public function get_user_defined_timeout($default_timeout = 30) {
604 if (!empty($this->user_id)) {
605 $timeout = get_user_meta($this->user_id, 'updraftcentral_dashboard_user_defined_timeout', true);
606 if (!empty($timeout)) return (int) $timeout;
607 }
608
609 return $default_timeout;
610 }
611
612 /**
613 * Retrieves all available tags for this user, filterable through site id and/or tag name
614 *
615 * @param integer $site_id Optional. The ID of the site where the tags is to be pulled from
616 * @param string $tag_name Optional. The name of the tag to search for
617 *
618 * @return array|string|null
619 */
620 public function get_site_tags($site_id = 0, $tag_name = '') {
621
622 if (!$data = wp_cache_get($this->user_id, 'updraftcentral_tags')) {
623 $all_tags = $site_tags = $site_tags_by_name = array();
624
625 $user_tags = $this->get_site_tags_from_db();
626 foreach ($user_tags as $tag) {
627 $site_tags[$tag->site_id][$tag->meta_id] = $tag->meta_value;
628
629 $key_name = strtolower($tag->meta_value);
630 $site_tags_by_name[$tag->site_id][$key_name] = $tag->meta_value;
631 $all_tags[$tag->meta_id] = $tag->meta_value;
632 }
633
634 $data = array(
635 'site_tags' => $site_tags,
636 'site_tags_by_name' => $site_tags_by_name,
637 'all_tags' => $all_tags
638 );
639 wp_cache_add($this->user_id, $data, 'updraftcentral_tags');
640 }
641
642 if (!empty($site_id) && empty($tag_name)) {
643 $site_tags = $data['site_tags'];
644 if (isset($site_tags[$site_id])) return $site_tags[$site_id];
645
646 } elseif (!empty($site_id) && !empty($tag_name)) {
647 $site_tags = $data['site_tags_by_name'];
648
649 $key_name = strtolower($tag_name);
650 if (isset($site_tags[$site_id]) && isset($site_tags[$site_id][$key_name])) return $site_tags[$site_id][$key_name];
651
652 } elseif (empty($site_id) && empty($tag_name)) {
653 return $data['all_tags'];
654 }
655
656 return null;
657 }
658
659 /**
660 * Force refresh of site tags by removing previously stored cache
661 * in order to give way for a newly pulled data from database.
662 *
663 * @return void
664 */
665 public function refresh_site_tags() {
666
667 wp_cache_delete($this->user_id, 'updraftcentral_tags');
668
669 // Load the latest or updated list of tags from DB and save result in cache.
670 $this->get_site_tags();
671 }
672
673 /**
674 * Retrieves all available tags for this user, filterable through site id and/or tag name
675 *
676 * @param integer $site_id Optional. The ID of the site where the tags is to be pulled from
677 * @param string $tag_name Optional. The name of the tag to search for
678 *
679 * @return array|object|null
680 */
681 private function get_site_tags_from_db($site_id = 0, $tag_name = '') {
682 $ucdb = UpdraftCentral()->db;
683 $our_prefix = $ucdb->base_prefix.$this->rc->table_prefix;
684
685 $sql = 'SELECT m.*, s.`user_id` FROM '.$our_prefix.'sitemeta AS m INNER JOIN '.$our_prefix.'sites AS s ON m.`site_id` = s.`site_id` WHERE m.`meta_key` = "site_tag" AND s.`user_id` = %d';
686
687 $args = array($this->user_id);
688 if (!empty($site_id)) {
689 $sql .= ' AND s.`site_id` = %d';
690 $args[] = $site_id;
691 }
692
693 if (!empty($tag_name)) {
694 $sql .= ' AND m.`meta_value` = %s';
695 $args[] = $tag_name;
696 }
697
698 $sql .= ' ORDER BY m.`meta_value` ASC';
699
700 $tags = $ucdb->get_results($ucdb->prepare($sql, $args));
701 return $tags;
702 }
703
704 /**
705 * Saves user-defined shortcut keys and returns shortcut collection
706 *
707 * @param array $response A response array where we insert our request response
708 * @param array $post_data Parameters passed as an additional argument(s) to the request
709 * @return array
710 */
711 public function dashboard_ajaxaction_shortcuts($response, $post_data) {
712
713 $response['responsetype'] = 'ok';
714 $response['message'] = 'success';
715
716 $shortcuts = get_user_meta($this->user_id, 'updraftcentral_dashboard_shortcuts', true);
717 if (!is_array($shortcuts)) $shortcuts = array();
718
719 if (isset($post_data['data']['key'])) {
720 $shortcuts[$post_data['data']['name']] = $post_data['data']['key'];
721 } elseif (isset($post_data['data']['clear'])) {
722 $shortcuts = array();
723 }
724
725 update_user_meta($this->user_id, 'updraftcentral_dashboard_shortcuts', $shortcuts);
726
727 // Return current shortcuts collection
728 $response['shortcuts'] = $shortcuts;
729
730 return $response;
731
732 }
733
734 /**
735 * Process site meta management actions (add, delete, update and get) through ajax request
736 *
737 * @param array $response
738 * @param array $post_data - site meta parameters for the current action
739 * @return array
740 */
741 public function dashboard_ajaxaction_manage_site_meta($response, $post_data) {
742
743 try {
744
745 $response['responsetype'] = 'ok';
746 $response['message'] = '';
747
748 $site_meta = $this->rc->site_meta;
749
750 if (!empty($site_meta)) {
751 $data = $post_data['data'];
752
753 switch ($data['action']) {
754 case 'add':
755 $response['data'] = $site_meta->add_site_meta($data['site_id'], $data['meta_key'], $data['meta_value'], $data['unique']);
756 break;
757 case 'delete':
758 $response['data'] = $site_meta->delete_site_meta($data['site_id'], $data['meta_key'], $data['meta_value']);
759 break;
760 case 'get':
761 $response['data'] = $site_meta->get_site_meta($data['site_id'], $data['key'], $data['single']);
762 break;
763 case 'update':
764 $response['data'] = $site_meta->update_site_meta($data['site_id'], $data['meta_key'], $data['meta_value'], $data['prev_value']);
765 break;
766 default:
767 $response['message'] = 'The submitted site meta command was not recognized.';
768 break;
769 }
770 } else {
771 $response['message'] = 'Unable to pull the site meta instance.';
772 }
773 } catch (Exception $e) {
774 $response['responsetype'] = 'error';
775 $response['message'] = $e->getMessage();
776 // @codingStandardsIgnoreLine
777 } catch (Error $e) {
778 $response['responsetype'] = 'error';
779 $response['message'] = $e->getMessage();
780 }
781
782 return $response;
783 }
784
785 /**
786 * Used to update sort order in user meta
787 *
788 * @param array $response
789 * @param array $post_data - site_order is an indexed array of site id's in the sorted order
790 * @return array
791 */
792 public function dashboard_ajaxaction_manage_site_order($response, $post_data) {
793
794 if (isset($post_data['data']['site_order'])) {
795
796 $user_id = $this->user_id;
797 $response['responsetype'] = "ok";
798 $response['message'] = 'No change';
799
800 if (get_user_meta($user_id, 'updraftcentral_dashboard_site_order', true) !== $post_data['data']['site_order']) { // only update if needed (user dragged and dropped in same place)
801
802 if (update_user_meta($user_id, 'updraftcentral_dashboard_site_order', $post_data['data']['site_order'])) {
803 $response['message'] = 'success';
804 } else {
805 $response['message'] = 'fail';
806 }
807 }
808 } else {
809 $response['responsetype'] = "error";
810 $response['message'] = "Missing site order data";
811 }
812
813 return $response;
814 }
815
816 public function get_licence_manager() {
817 return $this->licence_manager;
818 }
819
820 /**
821 * TODO: 1) Catch PHP events on the mothership, pass them on and let them be console.logged
822 * 2) Pass on caught output from the remote side, and get it console.logged
823 *
824 * @param array $response
825 * @param array $post_data
826 * @return array
827 */
828 public function dashboard_ajaxaction_site_rpc($response, $post_data) {
829
830 return $this->send_remote_command($post_data, false, $response);
831
832 }
833
834 /**
835 * Sends UpdraftCentral's command to the remote website
836 *
837 * @param array $data An array container the command to execute along with its command parameters
838 * @param boolean $force_save Optional. A flag that indicates whether UpdraftCentral will save and cache the response from the remote website
839 * @param array $response Optional. A response container that gets populated with the remote website's response from the current request
840 */
841 public function send_remote_command($data, $force_save = false, $response = array()) {
842
843 // Allow other components to intercept and deal with the command
844 if (null !== ($response = apply_filters('updraftcentral_send_remote_command_shortcircuit', null, $this, $data, $response))) {
845 return $response;
846 }
847
848 try {
849
850 // Load site rpc
851 if (!class_exists('UpdraftCentral_Remote_Communications')) include_once UD_CENTRAL_DIR.'/classes/class-siterpc.php';
852 $site_rpc = new UpdraftCentral_Remote_Communications($this, $response, $data);
853
854 if ($validate_result = $site_rpc->validate_input()) {
855 // Send command to remote website.
856 $response = $site_rpc->send_message($force_save);
857 } else {
858 // Return error from validation process
859 $response = $validate_result;
860 }
861
862 } catch (Exception $e) {
863 $response['responsetype'] = 'error';
864 $response['message'] = $e->getMessage();
865 }
866
867 return $response;
868 }
869
870 public function deep_sanitize($input, $sanitize_function = 'htmlspecialchars') {
871 if (is_string($input)) return call_user_func($sanitize_function, $input);
872 if (is_array($input)) {
873 foreach ($input as $k => $v) {
874 $input[$k] = $this->deep_sanitize($v, $sanitize_function);
875 }
876 }
877
878 return $input;
879 }
880
881 public function send_message($ud_rpc, $message, $data = null, $timeout = 30) {
882 $this->php_events = array();
883
884 // Override http timeout argument based from the user defined timeout
885 $timeout = $this->get_user_defined_timeout($timeout);
886
887 if ('__updraftcentral_internal_preencrypted' == $message) {
888
889 $post_options = array(
890 'timeout' => $timeout,
891 'body' => $data,
892 );
893
894 $post_options = apply_filters('udrpc_post_options', $post_options, $message, $data, $timeout, $this);
895
896 try {
897 $post = $ud_rpc->http_post($post_options);
898 } catch (Exception $e) {
899 // Curl can return an error code 0, which causes WP_Error to return early, without recording the message. So, we prefix the code.
900 return new WP_Error('http_post_'.$e->getCode(), $e->getMessage());
901 }
902
903 if (is_wp_error($post)) return $post;
904
905 if (empty($post['response']) || empty($post['response']['code'])) return new WP_Error('empty_http_code', 'Unexpected HTTP response code');
906
907 if ($post['response']['code'] < 200 || $post['response']['code'] >= 300) return new WP_Error('unexpected_http_code', 'Unexpected HTTP response code ('.$post['response']['code'].')', $post);
908
909 if (empty($post['body'])) return new WP_Error('empty_response', 'Empty response from remote site');
910
911 return (string) $post['body'];
912
913 } else {
914 $response = $ud_rpc->send_message($message, $data, $timeout);
915 }
916
917 // TODO: Handle caught_output
918
919 if (is_array($response) && !empty($response['data']) && is_array($response['data']) && !empty($response['data']['php_events']) && !empty($response['data']['previous_data'])) {
920 // global $updraftplus;
921 $this->php_events = $response['data']['php_events'];
922 if (defined('WP_DEBUG') && WP_DEBUG) {
923 foreach ($response['data']['php_events'] as $logline) {
924 error_log('From remote side: '.$logline);
925 }
926 }
927 $response['data'] = $response['data']['previous_data'];
928 }
929
930 return $response;
931 }
932
933 public function dashboard_ajaxaction_sites_html($response) {
934 $response['responsetype'] = 'ok';
935 $response['sites_html'] = $this->get_sites_html();
936 $response['status_info'] = array(
937 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
938 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
939 );
940 $response['message'] = __('The site list has been refreshed.', 'updraftcentral');
941
942 return $response;
943 }
944
945 public function dashboard_ajaxaction_delete_site($response, $post_data) {
946 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id'])) {
947
948 $deleted = $this->delete_site_by_id((int) $post_data['data']['site_id']);
949
950 if (is_wp_error($deleted)) {
951 $response = $deleted;
952 } else {
953 $response['responsetype'] = 'ok';
954 $response['status_info'] = array(
955 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
956 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
957 );
958 $response['sites_html'] = $this->get_sites_html();
959 $response['message'] = __('The site was successfully deleted from your dashboard.', 'updraftcentral');
960 }
961
962 } else {
963 $response['responsetype'] = 'error';
964 $response['code'] = 'missing_data';
965 $response['message'] = __('Missing information', 'updraftcentral');
966 }
967
968 return $response;
969 }
970
971 public function dashboard_ajaxaction_edit_site_connection_method($response, $post_data) {
972
973 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id'])) {
974
975 $site_id = (int) $post_data['data']['site_id'];
976
977 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
978
979 $updated = $this->rc->wp_update('sites',
980 array(
981 'connection_method' => $connection_method,
982 ),
983 array(
984 'user_id' => $this->user_id,
985 'site_id' => $site_id,
986 ),
987 array(
988 '%s',
989 ),
990 array(
991 '%d',
992 '%d',
993 )
994 );
995
996 if (is_numeric($updated)) {
997 $response['responsetype'] = 'ok';
998
999 $this->load_user_sites();
1000 $response['sites_html'] = $this->get_sites_html();
1001 $response['status_info'] = array(
1002 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
1003 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
1004 );
1005
1006 $response['message'] = __('The site configuration was successfully edited.', 'updraftcentral');
1007 } else {
1008 $response = $updated;
1009 }
1010
1011 } else {
1012 $response['responsetype'] = 'error';
1013 $response['code'] = 'missing_data';
1014 $response['message'] = __('Missing information', 'updraftcentral');
1015 }
1016
1017 return $response;
1018
1019 }
1020
1021 public function dashboard_ajaxaction_edit_site_configuration($response, $post_data) {
1022
1023 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id']) && isset($post_data['data']['description'])) {
1024
1025 $site_id = (int) $post_data['data']['site_id'];
1026
1027 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
1028 $send_cors_headers = (isset($post_data['data']['send_cors_headers']) && $post_data['data']['send_cors_headers']) ? 1 : 0;
1029
1030 $updated = $this->rc->wp_update('sites',
1031 array(
1032 'description' => (string) $post_data['data']['description'],
1033 'connection_method' => $connection_method,
1034 'send_cors_headers' => $send_cors_headers,
1035 ),
1036 array(
1037 'user_id' => $this->user_id,
1038 'site_id' => $site_id,
1039 ),
1040 array(
1041 '%s',
1042 '%s',
1043 '%d',
1044 ),
1045 array(
1046 '%d',
1047 '%d',
1048 )
1049 );
1050
1051 if (is_numeric($updated)) {
1052 $response['responsetype'] = 'ok';
1053
1054 $extra_site_info_unparsed = empty($post_data['data']['extra_site_info']) ? false : $post_data['data']['extra_site_info'];
1055 if (!$extra_site_info_unparsed) {
1056 $extra_site_info = array();
1057 } else {
1058 parse_str($extra_site_info_unparsed, $extra_site_info);
1059 }
1060
1061 if (!empty($extra_site_info)) {
1062 foreach ($extra_site_info as $meta_key => $meta_value) {
1063 $this->rc->site_meta->update_site_meta($site_id, $meta_key, $meta_value);
1064 }
1065 }
1066
1067 $this->load_user_sites();
1068 $response['sites_html'] = $this->get_sites_html();
1069 $response['status_info'] = array(
1070 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
1071 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
1072 );
1073
1074 $response['message'] = __('The site configuration was successfully edited.', 'updraftcentral');
1075 } else {
1076
1077 $response = $updated;
1078 }
1079
1080 } else {
1081 $response['responsetype'] = 'error';
1082 $response['code'] = 'missing_data';
1083 $response['message'] = __('Missing information', 'updraftcentral');
1084 }
1085
1086 return $response;
1087 }
1088
1089 /**
1090 * Imports user's settings
1091 *
1092 * @param array $response Respose array to be filtered
1093 * @param array $post_data The request data
1094 * @return array
1095 */
1096 public function dashboard_ajaxaction_import_settings($response, $post_data) {
1097
1098 $posted_data = json_decode($post_data['data'], true);
1099 if (empty($posted_data)) {
1100 return;
1101 }
1102
1103 try {
1104
1105 // "tmp_name" field of the $_FILES array is auto-generated by PHP, not a user input.
1106 // It contains the temporary location/path of the uploaded file. Adding "wp_unslash" will
1107 // break this upload feature on a Windows System thus, we added the phpcs:ignore
1108 // annotation here.
1109 $tmp_name = isset($_FILES['file']['tmp_name']) ? sanitize_text_field($_FILES['file']['tmp_name']) : '';// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash - this value is not slashed
1110 $ext = isset($_FILES['file']['name']) ? pathinfo(sanitize_text_field(wp_unslash($_FILES['file']['name'])), PATHINFO_EXTENSION) : '';
1111 $errors = array();
1112
1113 if (isset($_FILES['file']['error']) && UPLOAD_ERR_OK == sanitize_text_field(wp_unslash($_FILES['file']['error'])) && is_uploaded_file($tmp_name)) {
1114 $content = json_decode(file_get_contents($tmp_name), true);
1115 if ('json' === $ext && $content) {
1116 $encrypted = false;
1117
1118 // Check whether the imported file is encrypted or not by identifying
1119 // any encryption marks padded during the export process
1120 $version = base64_decode($content['version']);
1121 if (!empty($version)) {
1122 if (false !== strpos($version, ':')) $encrypted = true;
1123 }
1124
1125 if ($encrypted) {
1126 if (!empty($posted_data['phrase'])) {
1127 $content = $this->decrypt_data($content, $posted_data['phrase']);
1128 } else {
1129 $errors[] = __('It appears that the imported file is encrypted.', 'updraftcentral').' '.__('Please provide the phrase you used to encrypt the file and try again.', 'updraftcentral');
1130 }
1131 }
1132
1133 if (isset($content['version'])) {
1134 if (version_compare($content['version'], $this->rc->export_settings_version, '>')) {
1135 $errors[] = sprintf(__('The imported file was created by a later version of UpdraftCentral (%s).', 'updraftcentral').' '.__('Your current UpdraftCentral version is %s.', 'updraftcentral').' '.__('Please upgrade your UpdraftCentral plugin and try again.', 'updraftcentral'), $content['updraftcentral_version'], $this->rc->version);
1136 }
1137 } else {
1138 $errors[] = $content;
1139 }
1140
1141 if (empty($errors)) {
1142 $ucdb = UpdraftCentral()->db;
1143
1144 $site_meta = $this->rc->site_meta;
1145 if (!empty($content['sites'])) {
1146 foreach ($content['sites'] as $site) {
1147 // The "delete_site_by_url" is already called by the "add_site" function prior to inserting
1148 // a new site record, thus, we no longer have to explicitly call it here.
1149 $result = $this->add_site($site['url'], $site['admin_url'], $site['key_local_private'], $site['key_remote_public'], $site['remote_user_id'], $site['remote_user_login'], $site['key_name_indicator'], $site['remote_site_id'], $site['description'], $site['connection_method'], $site['send_cors_headers']);
1150
1151 if (is_wp_error($result)) {
1152 $error_message = $result->get_error_message();
1153
1154 // Making sure that the same error does not repeat twice or more.
1155 if (!in_array($error_message, $errors)) {
1156 $errors[] = $error_message;
1157 }
1158 } else {
1159 $site_id = $ucdb->insert_id;
1160 if (!empty($site['sitemeta']) && !empty($site_meta)) {
1161 $site_meta->delete_site_meta_by_site_id($site_id);
1162
1163 foreach ($site['sitemeta'] as $meta) {
1164 $site_meta->add_site_meta($site_id, $meta['meta_key'], $meta['meta_value']);
1165 }
1166 }
1167 }
1168 }
1169 }
1170
1171 if (!empty($content['usermeta'])) {
1172 $this->set_updraftcentral_usermeta($content['usermeta'], $this->user_id);
1173 }
1174 }
1175 } else {
1176 $errors[] = __('Invalid import file', 'updraftcentral');
1177 }
1178 } else {
1179 $errors[] = __('There appears to be a problem uploading the file.', 'updraftcentral').' '.__('Please make sure that you have the right permission to upload files in this server.', 'updraftcentral');
1180 }
1181
1182 $response['data'] = array(
1183 'errors' => $errors
1184 );
1185
1186 $response['responsetype'] = 'ok';
1187 $response['code'] = 'import_settings';
1188 $response['message'] = 'success';
1189 } catch (Exception $e) {
1190 $response['responsetype'] = 'error';
1191 $response['code'] = 'import_failed';
1192 $response['message'] = $e->getMessage();
1193 }
1194
1195 return $response;
1196 }
1197
1198 /**
1199 * Exports user's settings
1200 *
1201 * @param array $response Respose array to be filtered
1202 * @param array $post_data The request data
1203 * @return array
1204 */
1205 public function dashboard_ajaxaction_export_settings($response, $post_data) {
1206
1207 $posted_data = $post_data['data'];
1208 if (empty($posted_data)) {
1209 return;
1210 }
1211
1212 $uc_version = UpdraftCentral()->version;
1213 $user = wp_get_current_user();
1214
1215 // Other keys might be added in the future (e.g. in new modules or future tasks), thus,
1216 // we're having the keys filterable here so that anyone can add their respective user meta keys
1217 // to be included during the export process.
1218 $uc_usermeta_keys = apply_filters('updraftcentral_usermeta_keys', array(
1219 'updraftcentral_modules_visibility',
1220 'updraftcentral_dashboard_shortcuts',
1221 'updraftcentral_dashboard_user_defined_timeout',
1222 ));
1223
1224 $data = array(
1225 'updraftcentral_version' => $uc_version,
1226 'user' => $user->display_name,
1227 'user_email' => $user->user_email,
1228 'site_name' => get_bloginfo('name'),
1229 'site_url' => get_bloginfo('url'),
1230 'date' => date('Y-m-d H:i:s'),
1231 'version' => $this->rc->export_settings_version,
1232 'sites' => $this->load_sites_for_export(),
1233 'usermeta' => $this->get_updraftcentral_usermeta($uc_usermeta_keys, $user->ID),
1234 );
1235
1236 try {
1237
1238 if (!empty($posted_data['phrase'])) {
1239 $data = $this->encrypt_data($data, $posted_data['phrase']);
1240 }
1241
1242 $blog_name = sanitize_title(get_bloginfo('name'));
1243 $file_name = apply_filters('updraftcentral_export_file_name', 'updraftcentral-settings-'.$blog_name.'.json');
1244
1245 $response['data'] = array(
1246 'json_data' => json_encode($data),
1247 'file_name' => $file_name,
1248 );
1249
1250 $response['responsetype'] = 'ok';
1251 $response['code'] = 'export_settings';
1252 $response['message'] = 'success';
1253 return $response;
1254
1255 } catch (Exception $e) {
1256 $response['responsetype'] = 'error';
1257 $response['code'] = 'export_failed';
1258 $response['message'] = $e->getMessage();
1259
1260 return $response;
1261 }
1262 }
1263
1264 /**
1265 * Sets usermeta information in bulk based from an array of meta collection
1266 *
1267 * @param array $usermetas A collection of user meta to add
1268 * @param integer $user_id The ID of the current user
1269 * @return boolean
1270 */
1271 private function set_updraftcentral_usermeta($usermetas, $user_id) {
1272 global $wpdb;
1273
1274 if (empty($usermetas) || empty($user_id)) return false;
1275
1276 $meta_keys = array_map(function($item) {
1277 if (false !== strpos($item['meta_key'], 'updraftcentral')) {
1278 return esc_sql($item['meta_key']);
1279 }
1280 }, $usermetas);
1281
1282 $items = array();
1283
1284 if (!empty($meta_keys)) {
1285 $meta_keys = "'".implode("','", $meta_keys)."'";
1286
1287 // Delete any existing usermeta found
1288 $wpdb->query($wpdb->prepare("DELETE FROM $wpdb->usermeta WHERE `user_id` = %d AND `meta_key` IN (".$meta_keys.")", $user_id));
1289
1290 // Create/insert new usermeta based from the submitted information
1291 $user_id = get_current_user_id();
1292 foreach ($usermetas as $meta) {
1293 if (false !== strpos($meta['meta_key'], 'updraftcentral')) {
1294 $meta['user_id'] = $user_id;
1295 $result = $wpdb->insert($wpdb->usermeta, $meta);
1296 if ($result) {
1297 array_push($items, $wpdb->insert_id);
1298 }
1299 }
1300 }
1301 }
1302
1303 return count($items) ? true : false;
1304 }
1305
1306 /**
1307 * Retrieves usermeta information based from an array of meta keys
1308 *
1309 * @param array $keys A collection of meta keys where the data is to be pulled from
1310 * @param integer $user_id The ID of the current user
1311 * @return array
1312 */
1313 private function get_updraftcentral_usermeta($keys, $user_id) {
1314 global $wpdb;
1315
1316 if (empty($keys) || empty($user_id)) return array();
1317
1318 $meta_keys = array_map(function($item) {
1319 if (false !== strpos($item, 'updraftcentral')) {
1320 return esc_sql($item);
1321 }
1322 }, $keys);
1323
1324 $metas = array();
1325 if (!empty($meta_keys)) {
1326 $meta_keys = "'".implode("','", $meta_keys)."'";
1327 $metas = $wpdb->get_results($wpdb->prepare("SELECT `meta_key`, `meta_value` FROM $wpdb->usermeta WHERE `user_id` = %d AND `meta_key` IN (".$meta_keys.")", $user_id), ARRAY_A);
1328
1329 if (!empty($metas)) {
1330 $metas = array_map('maybe_unserialize', $metas);
1331 }
1332 }
1333
1334 return $metas;
1335 }
1336
1337 /**
1338 * Encrypts data using an encryption or pass phrase
1339 *
1340 * @param array $data The data to encrypt
1341 * @param array $passphrase A phrase to be used to encrypt the data into ciphertext
1342 * @param array $result_data The resulting array that will contained the encrypted data
1343 * @return array
1344 */
1345 private function encrypt_data($data, $passphrase, &$result_data = array()) {
1346 if (!empty($data)) {
1347 foreach ($data as $key => $value) {
1348 if (is_object($value)) {
1349 // Here, we're converting the object into array before we proceed
1350 // with the rest of the process to cover all data as possible during encryption.
1351 //
1352 // N.B. We're using deep conversion using the two json functions (json_encode and json_decode)
1353 // rather than casting the object directly using (array) since we wanted to convert all
1354 // object instances found within the $value down to the lowest level.
1355 $value = json_decode(json_encode($value), true);
1356 }
1357
1358 if (is_array($value)) {
1359 $result_data[$key] = array();
1360 $this->encrypt_data($value, $passphrase, $result_data[$key]);
1361 } else {
1362 $result_data[$key] = $this->encrypt_with_passphrase($value, $passphrase);
1363 }
1364 }
1365 }
1366
1367 return $result_data;
1368 }
1369
1370 /**
1371 * Decrypts data using an encryption or pass phrase
1372 *
1373 * @param array $data The data to decrypt
1374 * @param array $passphrase A phrase to be used to decrypt the data back into plaintext
1375 * @param array $result_data The resulting array that will contained the decrypted data
1376 * @return array
1377 */
1378 private function decrypt_data($data, $passphrase, &$result_data = array()) {
1379 if (!empty($data)) {
1380 foreach ($data as $key => $value) {
1381 if (is_object($value)) {
1382 // Here, we're converting the object into array before we proceed
1383 // with the rest of the process to cover all data as possible during decryption.
1384 //
1385 // N.B. We're using deep conversion using the two json functions (json_encode and json_decode)
1386 // rather than casting the object directly using (array) since we wanted to convert all
1387 // object instances found within the $value down to the lowest level.
1388 $value = json_decode(json_encode($value), true);
1389 }
1390
1391 if (is_array($value)) {
1392 $result_data[$key] = array();
1393 $this->decrypt_data($value, $passphrase, $result_data[$key]);
1394 } else {
1395 $decrypt_result = $this->decrypt_with_passphrase($value, $passphrase);
1396 if (!is_wp_error($decrypt_result)) {
1397 $result_data[$key] = $decrypt_result;
1398 } else {
1399 return $decrypt_result->get_error_message();
1400 }
1401 }
1402 }
1403 }
1404
1405 return $result_data;
1406 }
1407
1408 /**
1409 * Encrypts information using an encryption or pass phrase
1410 *
1411 * @param array $plaintext The information to encrypt
1412 * @param array $passphrase A phrase to be used to encrypt the data into ciphertext
1413 * @return array
1414 */
1415 public function encrypt_with_passphrase($plaintext, $passphrase) {
1416
1417 UpdraftCentral()->ensure_crypto_is_autoloaded();
1418
1419 $crypto = new phpseclib_Crypt_Rijndael(phpseclib_Crypt_Base::MODE_CTR);
1420 $hmac = ':';
1421 if (!empty($passphrase)) {
1422 $crypto->setKey($passphrase);
1423
1424 $hash = new phpseclib_Crypt_Hash('sha256');
1425 $hash->setKey($passphrase);
1426 $hmac = base64_encode($hash->hash($passphrase)).':';
1427 }
1428
1429 $crypto_string = phpseclib_Crypt_Random::string($crypto->getBlockLength() >> 3);
1430 $crypto->setIV($crypto_string);
1431 $crypto->disablePadding();
1432
1433 $encrypted = base64_encode($crypto_string).':'.base64_encode($crypto->encrypt($plaintext));
1434
1435 // Here, we're keeping the actual boolean representation of the original data
1436 // making sure that "true" or "false" boolean value are not converted into
1437 // its numerical counterpart as "1" or "0" after decryption.
1438 $boolean_str = array('false', 'true');
1439 if (in_array(strtolower($plaintext), $boolean_str) || is_bool($plaintext)) {
1440 $encrypted .= ':bool'.(is_string($plaintext) ? '/s' : '/b');
1441 }
1442 return base64_encode($hmac.$encrypted);
1443 }
1444
1445 /**
1446 * Decrypts information using an encryption or pass phrase
1447 *
1448 * @param array $ciphertext The information to decrypt
1449 * @param array $passphrase A phrase to be used to decrypt the data back into plaintext
1450 * @return array
1451 */
1452 public function decrypt_with_passphrase($ciphertext, $passphrase) {
1453
1454 UpdraftCentral()->ensure_crypto_is_autoloaded();
1455
1456 $struct = explode(':', base64_decode($ciphertext));
1457 $hmac = $struct[0];
1458 $crypt_string = $struct[1];
1459 $ciphertext = $struct[2];
1460 $bool_flag = isset($struct[3]) ? $struct[3] : 0;
1461
1462 $ciphertext = base64_decode($ciphertext);
1463 $crypt_string = base64_decode($crypt_string);
1464
1465 $crypto = new phpseclib_Crypt_Rijndael(phpseclib_Crypt_Base::MODE_CTR);
1466 if (!empty($passphrase)) {
1467 $hash = new phpseclib_Crypt_Hash('sha256');
1468 $hash->setKey($passphrase);
1469 if (base64_decode($hmac) !== $hash->hash($passphrase)) {
1470 return new WP_Error('updraftcentral_unauthorized', __('You are not authorized to view this information.', 'updraftcentral').' '.__('The encryption phrase needed to unlock this file is incorrect.', 'updraftcentral'));
1471 }
1472
1473 $crypto->setKey($passphrase);
1474 }
1475
1476 $crypto->setIV($crypt_string);
1477 $crypto->disablePadding();
1478
1479 $plaintext = $crypto->decrypt($ciphertext);
1480
1481 $boolean_arr = array('false', 'true');
1482 if (!empty($bool_flag) && in_array($bool_flag, array('bool/s', 'bool/b'))) {
1483 if (is_numeric($plaintext)) $plaintext = $boolean_arr[intval($plaintext)];
1484 if ('bool/b' === $bool_flag) $plaintext = filter_var($plaintext, FILTER_VALIDATE_BOOLEAN) ? true : false;
1485 }
1486
1487 return $plaintext;
1488 }
1489
1490 /**
1491 * Loads all available sites along with their current sitemeta informations
1492 * for the given user
1493 *
1494 * @return array
1495 */
1496 private function load_sites_for_export() {
1497 $ucdb = UpdraftCentral()->db;
1498 $list = $ucdb->get_results('SELECT * FROM `'.$this->sites_table.'` WHERE `user_id`='.absint($this->user_id));
1499
1500 $sites = array();
1501 if (is_array($list) && !empty($list)) {
1502 foreach ($list as $site) {
1503 $sitemeta = $ucdb->get_results('SELECT `meta_key`, `meta_value` FROM `'.$this->sitemeta_table.'` WHERE `site_id`='.absint($site->site_id));
1504
1505 $metas = array();
1506 if (is_array($sitemeta) && !empty($sitemeta)) {
1507 foreach ($sitemeta as $meta) {
1508 array_push($metas, (array) $meta);
1509 }
1510 }
1511
1512 // Remove unwanted fields (we don't need this ID fields in the export file)
1513 unset($site->site_id);
1514 unset($site->user_id);
1515
1516 $site->sitemeta = $metas;
1517 array_push($sites, (array) $site);
1518 }
1519 }
1520
1521 return $sites;
1522 }
1523
1524 /**
1525 * Adds a new site to the user's sites collection
1526 *
1527 * @param array $response
1528 * @param array $post_data
1529 * @param bool $render_sites - Controls whether to render the sites' HTML or not.
1530 * @return array
1531 */
1532 public function dashboard_ajaxaction_newsite($response, $post_data, $render_sites = true) {
1533
1534 if (empty($post_data['data']) || !is_array($post_data['data']) || empty($post_data['data']['key'])) {
1535 $response['responsetype'] = 'error';
1536 $response['code'] = 'empty';
1537 $response['message'] = __('Please enter the site key.', 'updraftcentral');
1538 } else {
1539
1540 $site_key = $post_data['data']['key'];
1541
1542 $extra_site_info_unparsed = empty($post_data['data']['extra_site_info']) ? false : $post_data['data']['extra_site_info'];
1543 if (!$extra_site_info_unparsed) {
1544 $extra_site_info = array();
1545 } else {
1546 parse_str($extra_site_info_unparsed, $extra_site_info);
1547 }
1548
1549 $ud_rpc = $this->rc->get_udrpc();
1550
1551 // A bundle has these keys: key, name_indicator, url
1552 $decode_bundle = $ud_rpc->decode_portable_bundle($site_key, 'base64_with_count');
1553
1554 if (!is_array($decode_bundle) || !empty($decode_bundle['code'])) {
1555 $response['responsetype'] = 'error';
1556 $response['message'] = __('Error:', 'updraftcentral');
1557 $response['code'] = empty($decode_bundle['code']) ? 'could_not_decode' : $decode_bundle['code'];
1558 if (!empty($decode_bundle['code']) && 'invalid_wrong_length' == $decode_bundle['code']) {
1559 $response['message'] .= ' '.__('The entered key was the wrong length - please try again.', 'updraftcentral');
1560 } elseif (!empty($decode_bundle['code']) && 'invalid_corrupt' == $decode_bundle['code']) {
1561 $response['message'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftcentral').' ('.$decode_bundle['data'].')';
1562 } elseif (empty($decode_bundle['key']) || empty($decode_bundle['url']) || empty($decode_bundle['name_indicator'])) {
1563 $response['message'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftcentral');
1564 $response['data'] = $decode_bundle;
1565 }
1566 } elseif (empty($decode_bundle['key']) || empty($decode_bundle['url']) || empty($decode_bundle['user_id'])) {
1567 $response['message'] = __('Error:', 'updraftcentral').' '.__('The entered key was corrupt - please try again.', 'updraftcentral');
1568 $response['code'] = 'corrupt_key';
1569 $response['data'] = $decode_bundle;
1570 } else {
1571
1572 if (trailingslashit(network_site_url()) == $decode_bundle['url'] && !apply_filters('updraftcentral_allow_self_control', true)) {
1573 $response['responsetype'] = 'error';
1574 $response['code'] = 'this_site';
1575 $response['message'] = __('Error:', 'updraftcentral').' '.__('The entered key does not belong to a remote site (it belongs to this one).', 'updraftcentral');
1576 } elseif ($this->rc->url_looks_internal($decode_bundle['url']) && !$this->rc->url_looks_internal(site_url()) && !apply_filters('updraftcentral_allow_adding_internal_url', true, $decode_bundle['url'])) {
1577 // The default is to allow it, because as long as your browser is running on the same machine as the site is on, it can work.
1578 $response['responsetype'] = 'error';
1579 $response['code'] = 'cant_add_localhost';
1580 $response['message'] = __('Error:', 'updraftcentral').' '.__('The entered key belongs to a local development website - these cannot be controlled from this dashboard because it is not reachable from an external network.', 'updraftcentral');
1581 } else {
1582 // Was the key sent SSL to us directly?
1583 $key = $decode_bundle['key'];
1584 if (is_array($key) && !empty($key['key_hash']) && isset($key['key_id'])) {
1585 $ucdb = UpdraftCentral()->db;
1586 // Allow them 3 hours to copy-and-paste their key
1587 $ucdb->query('DELETE FROM '.$ucdb->base_prefix.$this->rc->table_prefix.'site_temporary_keys WHERE created<='.(int) (time() - 10800));
1588 $key_info = $this->rc->wp_get_row('site_temporary_keys', $ucdb->prepare('key_id=%d', $key['key_id']));
1589 if (is_object($key_info) && !empty($key_info->key_local_private) && !empty($key_info->key_remote_public)) {
1590 $this->rc->wp_delete('site_temporary_keys', array('key_id' => $key['key_id']));
1591 $key_hash = hash('sha256', $key_info->key_remote_public);
1592
1593 // @codingStandardsIgnoreLine
1594 if ((function_exists('hash_equals') && hash_equals($key_hash, $key['key_hash'])) || (!function_exists('hash_equals') && $key_hash === $key['key_hash'])) {
1595 $key_local_private = $key_info->key_local_private;
1596 $key_remote_public = $key_info->key_remote_public;
1597 } else {
1598 $response['responsetype'] = 'error';
1599 $response['code'] = 'wrong_hash';
1600 $response['message'] = __('Error:', 'updraftcentral').' '.apply_filters('updraftcentral_wrong_hash_message', __('This key could not be added, as it appears to be corrupt - please try again.', 'updraftcentral'));
1601
1602 return $response;
1603 }
1604 } else {
1605 $response['responsetype'] = 'error';
1606 $response['code'] = 'no_key_found';
1607 $response['message'] = __('Error:', 'updraftcentral').' '.apply_filters('updraftcentral_no_key_found_message', __('This key could not be added - it may be too long since you generated it; please try again.', 'updraftcentral'));
1608
1609 return $response;
1610 }
1611
1612 } elseif (!empty($decode_bundle['mothership_firewalled'])) {
1613
1614 // Need to do direct AJAX from the browser to the mothership to send our key
1615
1616 $ud_rpc = $this->rc->get_udrpc('central_host.updraftplus.com');
1617 if (false != $ud_rpc->generate_new_keypair()) {
1618 $key_remote_public = $key;
1619 $key_local_private = $ud_rpc->get_key_local();
1620 } else {
1621 $response['responsetype'] = 'error';
1622 $response['code'] = 'keygen_error';
1623 $response['message'] = 'An error occurred when attempting to generate a new key-pair';
1624
1625 return $response;
1626 }
1627
1628 } else {
1629 $key_remote_public = $key;
1630 $key_local_private = false;
1631 }
1632
1633 $remote_site_id = empty($decode_bundle['ms_id']) ? 0 : $decode_bundle['ms_id'];
1634 $description = isset($decode_bundle['site_title']) ? (string) $decode_bundle['site_title'] : '';
1635
1636 $send_cors_headers = (isset($post_data['data']['send_cors_headers']) && !$post_data['data']['send_cors_headers']) ? 0 : 1;
1637 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
1638
1639 $site_url = $decode_bundle['url'];
1640
1641 // Supply a default for legacy format keys that didn't include the admin URL
1642 $admin_url = empty($decode_bundle['admin_url']) ? trailingslashit($site_url).'wp-admin' : $decode_bundle['admin_url'];
1643
1644 $added = $this->add_site($site_url, $admin_url, $key_local_private, $key_remote_public, $decode_bundle['user_id'], $decode_bundle['user_login'], $decode_bundle['name_indicator'], $remote_site_id, $description, $connection_method, $send_cors_headers);
1645
1646 if (true === $added) {
1647 $response['responsetype'] = 'ok';
1648
1649 $ucdb = UpdraftCentral()->db;
1650 $new_site_id = $ucdb->insert_id;
1651
1652 if (!empty($extra_site_info)) {
1653 if (!is_array($this->sites_meta)) $this->sites_meta = array();
1654 if (empty($this->sites_meta[$new_site_id])) $this->sites_meta[$new_site_id] = array();
1655 foreach ($extra_site_info as $meta_key => $meta_value) {
1656 if (!$meta_value) continue;
1657 // Don't bother to save the default value on the initial adding of the site
1658 if ('http_authentication_method' == $meta_key && 'basic' == $meta_value) continue;
1659 $result = $this->rc->site_meta->add_site_meta($new_site_id, $meta_key, $meta_value);
1660 if (false !== $result) {
1661 $created = apply_filters("get_site_metadata_created", false, $new_site_id, $meta_key);
1662 if (!$created) {
1663 // Fallback if in case we fail to retrieve the "created" value from
1664 // the site metadata table. But most likely, if the add_site_meta succeeded
1665 // then we would have a value for the $created variable.
1666 $created = time();
1667 }
1668
1669 $meta = new stdClass();
1670 $meta->value = $meta_value;
1671 $meta->created = $created;
1672
1673 // We update the in-memory copy because this is used by get_sites_html()
1674 $this->sites_meta[$new_site_id][$meta_key] = $meta;
1675 }
1676 }
1677 }
1678
1679 if (!empty($new_site_id)) {
1680 if (isset($post_data['data']['tags'])) {
1681 do_action('updraftcentral_new_site_added', array('tags' => $post_data['data']['tags'], 'site_id' => $new_site_id));
1682 }
1683 $response['tags'] = apply_filters('updraftcentral_get_user_tags', array(), $this);
1684 }
1685
1686 // Return the new HTML widget to the front end
1687 $response['sites_html'] = $render_sites ? $this->get_sites_html() : '';
1688 $response['status_info'] = array(
1689 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
1690 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
1691 );
1692
1693 $response['message'] = __('The key was successfully added.', 'updraftcentral').' '.__('It is for interacting with the following site: ', 'updraftcentral').htmlspecialchars($decode_bundle['url']);
1694
1695 if (!empty($decode_bundle['mothership_firewalled_callback_url'])) {
1696 $response['key_needs_sending'] = array(
1697 'site_id' => $new_site_id,
1698 'url' => $decode_bundle['mothership_firewalled_callback_url'],
1699 'updraft_key_index' => $decode_bundle['updraft_key_index'],
1700 'remote_public_key' => $ud_rpc->get_key_remote(),
1701 );
1702 }
1703
1704 } else {
1705 $response['responsetype'] = 'error';
1706 $response['code'] = $added->get_error_code();
1707 $response['message'] = __('Error:', 'updraftcentral').' '.$added->get_error_message();
1708 }
1709
1710 }
1711 }
1712 }
1713
1714 return $response;
1715 }
1716
1717 public function load_user_sites_filter($sites) {
1718 $how_many_licences_available = $this->licence_manager->how_many_licences_available();
1719 $how_many_licences_in_use = count($sites);
1720
1721 if ($how_many_licences_available >= $how_many_licences_in_use || $how_many_licences_available < 0) return $sites;
1722
1723 $log_message = sprintf(__('You have more sites being managed (%d) than active licences (%d) - you will need to obtain more licences in order to manage all of your managed sites.', 'updraftcentral'), $how_many_licences_in_use, $how_many_licences_available);
1724
1725 $this->rc->log_notice($log_message, 'error', 'not_enough_licences');
1726
1727 $i = 0;
1728 foreach ($sites as $site_id => $site) {
1729 if ($i >= $how_many_licences_available) {
1730 $site->unlicensed = true;
1731 $sites[$site_id] = $site;
1732 }
1733 ++$i;
1734 }
1735
1736 return $sites;
1737 }
1738
1739 /**
1740 * Populate $this->sites and $this->sites_meta in accordance with the current user ($this->user_id)
1741 *
1742 * @return Array|WP_Error - either the same list of sites as will be in $this->sites, or a WP_Error if something went wrong
1743 */
1744 public function load_user_sites() {
1745 $ucdb = UpdraftCentral()->db;
1746 $sites = $ucdb->get_results('SELECT * FROM '.$this->sites_table.' WHERE user_id='.absint($this->user_id));
1747
1748 $this->sites_meta = array();
1749
1750 $subsequent_site = false;
1751 if (is_array($sites) && !empty($sites)) {
1752 $sites_meta_sql = 'SELECT * FROM '.$this->sitemeta_table.' WHERE site_id IN (';
1753 foreach ($sites as $site) {
1754 if ($subsequent_site) {
1755 $sites_meta_sql .= ',';
1756 } else {
1757 $subsequent_site = true;
1758 }
1759 $sites_meta_sql .= absint($site->site_id);
1760 }
1761 $sites_meta_sql .= ')';
1762 $sites_meta = $ucdb->get_results($sites_meta_sql, ARRAY_A);
1763
1764 if (!empty($sites_meta)) {
1765 $sites_meta = array_map(array(UpdraftCentral(), 'maybe_json_decode'), $sites_meta);
1766 }
1767 } else {
1768 $sites_meta = array();
1769 }
1770
1771 if (is_array($sites_meta)) {
1772 foreach ($sites_meta as $meta_row) {
1773 if (isset($meta_row['site_id'])) {
1774 // N.B. Since we're trying to include the 'created' column in the site metadata when loaded or pulled from
1775 // the database, therefore, we assign an anonymous object to encapsulate the value of the current meta_key along with
1776 // its created column.
1777
1778 $meta = new stdClass();
1779 $meta->value = $meta_row['meta_value'];
1780 $meta->created = $meta_row['created'];
1781
1782 $this->sites_meta[$meta_row['site_id']][$meta_row['meta_key']] = $meta;
1783 }
1784 }
1785 }
1786
1787 if (is_array($sites)) {
1788
1789 $processed_sites = array();
1790 foreach ($sites as $site) {
1791 $processed_sites[$site->site_id] = $site;
1792 }
1793 $this->sites = apply_filters('updraftcentral_load_user_sites', $processed_sites, $this, $this->licence_manager);
1794
1795 return $this->sites;
1796
1797 } elseif (is_wp_error($sites)) {
1798
1799 $this->rc->log_notice($sites);
1800 $this->sites = null;
1801
1802 return $sites;
1803 }
1804 }
1805
1806 /**
1807 * Get the HTML to render the site list in the dashboard
1808 *
1809 * @return String
1810 */
1811 public function get_sites_html() {
1812
1813 $ret = '';
1814
1815 // Get sites. Print a line for each of them.
1816 if (empty($this->sites) || !is_array($this->sites)) {
1817 $ret .= $this->rc->include_template('sites/none-set-up.php', true, array('common_urls' => $this->rc->get_common_urls()));
1818 } else {
1819
1820 // retrieve metadata if any exists
1821 $user_id = $this->user_id;
1822
1823 // ensure we have an array (in even of no metadata)
1824
1825 if (!$site_order_meta = get_user_meta($user_id, 'updraftcentral_dashboard_site_order', true)) {
1826 $site_order_meta = array();
1827 }
1828
1829 // Add existing sites if not in siteOrderMeta (i.e. any new added sites or handling no meta data)
1830
1831 foreach ($this->sites as $site) {
1832 if (!in_array($site->site_id, $site_order_meta)) {
1833 array_push($site_order_meta, $site->site_id);
1834 }
1835 }
1836
1837 // Render the site rows in site_order_meta sequence using site_id to reference sites object
1838
1839 foreach ($site_order_meta as $site_id_meta) {
1840
1841 // Ignore invalid or removed sites
1842
1843 if (!empty($this->sites[$site_id_meta])) {
1844
1845 $connection_method = isset($this->sites[$site_id_meta]->connection_method) ? (string) $this->sites[$site_id_meta]->connection_method : 'direct_default_auth';
1846 $send_cors_headers = (isset($this->sites[$site_id_meta]->send_cors_headers) && !$this->sites[$site_id_meta]->send_cors_headers) ? 0 : 1;
1847
1848 $site_data_attributes = 'data-site_url="' . esc_attr($this->sites[$site_id_meta]->url) . '" data-site_id="' . (int) $site_id_meta . '" data-key_name_indicator="' . esc_attr($this->sites[$site_id_meta]->key_name_indicator) . '" data-site_description="' . (($this->sites[$site_id_meta]->description) ? esc_attr($this->sites[$site_id_meta]->description) : esc_attr($this->sites[$site_id_meta]->url)) . '" data-remote_user_id="' . (int) $this->sites[$site_id_meta]->remote_user_id . '" data-remote_user_login="' . esc_attr($this->sites[$site_id_meta]->remote_user_login) . '"';
1849
1850 if (empty($this->sites[$site_id_meta]->admin_url)) {
1851 $admin_url = trailingslashit($this->sites[$site_id_meta]->url) . 'wp-admin';
1852 } else {
1853 $admin_url = $this->sites[$site_id_meta]->admin_url;
1854 }
1855 $site_data_attributes .= ' data-admin_url="' . esc_attr($admin_url) . '"';
1856
1857 $site_meta = empty($this->sites_meta[$site_id_meta]) ? array() : $this->sites_meta[$site_id_meta];
1858 if (!empty($site_meta)) {
1859 if (!empty($site_meta['http_username']->value)) {
1860 $http_password = empty($site_meta['http_password']->value) ? '' : $site_meta['http_password']->value;
1861 $site_data_attributes .= ' data-http_username="' . esc_attr($site_meta['http_username']->value) . '" data-http_password="' . esc_attr($http_password) . '"';
1862 if (!empty($site_meta['http_authentication_method']->value)) $site_data_attributes .= ' data-http_authentication_method="' . $site_meta['http_authentication_method']->value . '"';
1863 }
1864 }
1865
1866 if (empty($this->sites[$site_id_meta]->unlicensed)) {
1867 if ('via_mothership_encrypting' != $connection_method) {
1868 $site_data_attributes .= ' data-site_remote_public_key="' . esc_attr($this->sites[$site_id_meta]->key_remote_public) . '" data-site_local_private_key="' . esc_attr($this->sites[$site_id_meta]->key_local_private) . '"';
1869 }
1870 } else {
1871 $site_data_attributes .= ' data-site_unlicensed="1"';
1872 }
1873
1874 $site_data_attributes .= ' data-connection_method="' . esc_attr($connection_method) . '" data-send_cors_headers="' . $send_cors_headers . '"';
1875
1876 // Check whether this site was tagged as suspended
1877 $tagged = $this->get_site_tags($site_id_meta, 'Suspended');
1878 $suspended = !empty($tagged);
1879
1880 $site_data_attributes = apply_filters('updraftcentral_site_data_attributes', $site_data_attributes, $this->sites[$site_id_meta]);
1881 $site_alert_icon = apply_filters('updraftcentral_site_alert_icon', '', $site_id_meta);
1882 $available_updates = $this->get_updates_count_from_cache($site_id_meta);
1883
1884 $ret .= $this->rc->include_template('sites/site-row.php', true, array('site' => $this->sites[$site_id_meta], 'site_meta' => $site_meta, 'site_data_attributes' => $site_data_attributes, 'suspended' => $suspended, 'site_alert_icon' => $site_alert_icon, 'available_updates' => $available_updates));
1885 }
1886 }
1887 }
1888
1889 return $ret;
1890 }
1891
1892 /**
1893 * Returns false if not authorised at all; or a timestamp if it's authorised until a particular date
1894 *
1895 * @param int $site_id [description]
1896 * @return boolean|integer
1897 */
1898 public function authorised_for_site_until($site_id) {
1899
1900 if (!is_array($this->sites)) $this->load_user_sites();
1901
1902 if (is_array($this->sites)) {
1903 foreach ($this->sites as $site) {
1904 if ((int) $site_id == (int) $site->site_id && isset($site->licence_until)) {
1905 return apply_filters('updraftcentral_authorised_for_site_until', (int) $site->licence_until, $site_id, $this->sites);
1906 }
1907 }
1908 }
1909
1910 return apply_filters('updraftcentral_authorised_for_site_until', false, $site_id, $this->sites);
1911
1912 }
1913
1914 /**
1915 * Adding a site
1916 *
1917 * @param string $url
1918 * @param string $admin_url
1919 * @param string $key_local_private
1920 * @param string $key_remote_public
1921 * @param integer $remote_user_id
1922 * @param string $remote_user_login
1923 * @param string $key_name_indicator
1924 * @param integer $remote_site_id
1925 * @param string $description
1926 * @param string $connection_method
1927 * @param Boolean $send_cors_headers
1928 * @return Boolean|WP_Error - if a Boolean, then it will be true
1929 */
1930 public function add_site($url, $admin_url, $key_local_private, $key_remote_public, $remote_user_id, $remote_user_login, $key_name_indicator, $remote_site_id = 0, $description = '', $connection_method = 'direct_default_auth', $send_cors_headers = 1) {
1931
1932 if (!$this->user_can('add_site')) return new WP_Error('permission_denied', __('You do not have the permission to do this.', 'updraftcentral'), $this->user_id);
1933
1934 if (!$this->licence_manager->is_slot_available(array('url' => $url))) {
1935 return new WP_Error('no_licences_available', apply_filters('updraftcentral_no_licences_available_message', __('You have no licences available - to add a site, you will need to obtain some more.', 'updraftcentral')));
1936 }
1937
1938 $this->delete_site_by_url($url);
1939
1940 $added = $this->rc->wp_insert('sites',
1941 array(
1942 'user_id' => $this->user_id,
1943 'url' => $url,
1944 'admin_url' => $admin_url,
1945 'key_local_private' => $key_local_private,
1946 'key_remote_public' => $key_remote_public,
1947 'description' => $description,
1948 'connection_method' => $connection_method,
1949 'send_cors_headers' => $send_cors_headers,
1950 'sequence_id' => 0,
1951 'remote_user_id' => $remote_user_id,
1952 'remote_user_login' => $remote_user_login,
1953 'remote_site_id' => $remote_site_id,
1954 'key_name_indicator' => $key_name_indicator,
1955 ),
1956 array(
1957 '%d',
1958 '%s',
1959 '%s',
1960 '%s',
1961 '%s',
1962 '%s',
1963 '%s',
1964 '%d',
1965 '%d',
1966 '%d',
1967 '%s',
1968 '%d',
1969 '%s',
1970 )
1971 );
1972
1973 if (is_numeric($added)) {
1974 $result = true;
1975 } else {
1976 $result = $added;
1977 }
1978
1979 $this->load_user_sites();
1980
1981 return $result;
1982
1983 }
1984
1985 /**
1986 * Delete all site meta for a specified site
1987 *
1988 * @param Integer $site_id - the site ID
1989 *
1990 * @uses UpdraftCentral::wp_delete()
1991 *
1992 * @return Integer|WP_Error - the number of rows deleted, or a WP_Error object
1993 */
1994 public function delete_site_meta($site_id) {
1995 return $this->rc->wp_delete('sitemeta', array('site_id' => $site_id));
1996 }
1997
1998 /**
1999 * Delete all sites for the user
2000 */
2001 public function delete_all_sites() {
2002
2003 if (!is_array($this->sites)) return;
2004
2005 $counter = 1;
2006
2007 foreach ($this->sites as $site_id => $site) {
2008 $reload_user_sites = ($counter >= $this->sites);
2009 $this->delete_site_by_id($site_id, $reload_user_sites);
2010 $counter ++;
2011 }
2012
2013 }
2014
2015 public function delete_site_by_id($site_id, $reload_user_sites = true) {
2016 $result = $this->rc->wp_delete('sites', array('user_id' => $this->user_id, 'site_id' => $site_id));
2017 $this->delete_site_meta($site_id);
2018 if ($reload_user_sites) $this->load_user_sites();
2019
2020 return $result;
2021 }
2022
2023 public function delete_site_by_url($url) {
2024
2025 // We used to do a direct delete... but we need the site ID in order to be able to wipe the site meta
2026 // $result = $this->rc->wp_delete('sites', array('user_id' => $this->user_id, 'url' => $url));
2027
2028 $result = 0;
2029
2030 foreach ($this->sites as $site_id => $site) {
2031 if (strtolower($url) == strtolower($site->url)) {
2032 $result += $this->delete_site_by_id($site_id, false);
2033 }
2034 }
2035
2036 $this->load_user_sites();
2037
2038 return $result;
2039 }
2040
2041 /**
2042 * This just gives some potential for the future, currently - currently, there's nothing we're forbidding through this mechanism
2043 *
2044 * @param string $do_what
2045 * @return Boolean
2046 */
2047 public function user_can($do_what) {
2048 $result = false;
2049
2050 $old_user_id = get_current_user_id();
2051 if ($old_user_id != $this->user_id) wp_set_current_user($this->user_id);
2052
2053 $is_admin = apply_filters('updraftcentral_user_can_is_admin', current_user_can('manage_options'), $this);
2054
2055 if ($old_user_id != $this->user_id) wp_set_current_user($old_user_id);
2056
2057 switch ($do_what) {
2058 case 'add_site':
2059 $result = $is_admin;
2060 break;
2061 case 'delete_site':
2062 $result = $is_admin;
2063 break;
2064 }
2065
2066 return apply_filters('updraftcentral_user_can', $result, $do_what, $this);
2067 }
2068
2069 /**
2070 * Gets list of loaded modules by hooking into `updraftcentral_main_navigation_items` filter
2071 * and sets each available module's visibility as user_meta if not already set.
2072 *
2073 * @param array $loaded_modules An array of loaded modules
2074 * @return array An array of loaded modules
2075 */
2076 public function main_navigation_items($loaded_modules) {
2077
2078 if ('' === get_user_meta($this->user_id, 'updraftcentral_modules_visibility', true)) {
2079 $updraftcentral_modules_visibility = array();
2080 foreach ($loaded_modules as $id => $item) {
2081 if ('sites' !== $id) {
2082 $updraftcentral_modules_visibility[$id] = true;
2083 }
2084 }
2085 // @codingStandardsIgnoreLine
2086 add_user_meta($this->user_id, 'updraftcentral_modules_visibility', $updraftcentral_modules_visibility, true);
2087 }
2088
2089 return $loaded_modules;
2090 }
2091
2092 /**
2093 * Handles module visibility status when it is changed in frontend.
2094 *
2095 * It fires when ajax call is made with `module_visibility` action.
2096 * Hooks into `updraftcentral_dashboard_ajaxaction_module_visibility` filter.
2097 * Receives module_id and its visibility and updates module's visibility status in
2098 * database as user_meta and returns the result of ajax call
2099 *
2100 * @param array $response An array to be returned to ajax call
2101 * @param array $post_data An array of data from ajax call
2102 * @return array An array of data contains result of ajax call
2103 */
2104 public function dashboard_ajaxaction_module_visibility($response, $post_data) {
2105
2106 if (empty($post_data['data']) || !is_array($post_data['data'])) {
2107 $response['responsetype'] = 'error';
2108 $response['code'] = 'empty';
2109 $response['message'] = __('There was a error, please try again.', 'updraftcentral');
2110 } else {
2111 $module_id = $post_data['data']['module_id'];
2112 $visibility = $post_data['data']['visibility'];
2113 $updraftcentral_modules_visibility = get_user_meta($this->user_id, 'updraftcentral_modules_visibility', true);
2114 if ("true" === $visibility) {
2115 $updraftcentral_modules_visibility[$module_id] = true;
2116 } else {
2117 $updraftcentral_modules_visibility[$module_id] = false;
2118 }
2119 update_user_meta($this->user_id, 'updraftcentral_modules_visibility', $updraftcentral_modules_visibility);
2120
2121 $response['responsetype'] = 'ok';
2122 $response['message'] = __('Visibility changed successfully', 'updraftcentral');
2123 }
2124
2125 $response['data'] = $post_data;
2126
2127 return $response;
2128 }
2129
2130 /**
2131 * Resets all modules visibility status.
2132 *
2133 * It fires when ajax call is made with `reset_modules_visibility` action.
2134 * Hooks into `updraftcentral_dashboard_ajaxaction_reset_modules_visibility` filter.
2135 * Updates every module's visibility status in
2136 * database as user_meta and returns the result of ajax call
2137 *
2138 * @param array $response An array to be returned to ajax call
2139 * @param array $post_data An array of data from ajax call
2140 * @return array An array of data contains result of ajax call
2141 */
2142 public function dashboard_ajaxaction_reset_modules_visibility($response, $post_data) {
2143 if (empty($post_data['data']) || 'all' !== $post_data['data']) {
2144 $response['responsetype'] = 'error';
2145 $response['code'] = 'empty';
2146 $response['message'] = __('There was a error, please try again.' . $post_data['data'], 'updraftcentral');
2147 } else {
2148 $updraftcentral_modules_visibility = get_user_meta($this->user_id, 'updraftcentral_modules_visibility', true);
2149 foreach ($updraftcentral_modules_visibility as $module_id => $visibility) {
2150 $updraftcentral_modules_visibility[$module_id] = true;
2151 }
2152 update_user_meta($this->user_id, 'updraftcentral_modules_visibility', $updraftcentral_modules_visibility);
2153 $response['responsetype'] = 'ok';
2154 $response['message'] = __('Visibility changed successfully', 'updraftcentral');
2155 }
2156 $response['data'] = $post_data;
2157 return $response;
2158 }
2159 }
2160
2161 endif;
2162