PluginProbe
UpdraftCentral Dashboard / 0.8.26
UpdraftCentral Dashboard v0.8.26
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.26, at classes/user.php

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