PluginProbe
UpdraftCentral Dashboard / 0.8.21
UpdraftCentral Dashboard v0.8.21
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.21, at classes/user.php

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