PluginProbe
UpdraftCentral Dashboard / 0.8.13
UpdraftCentral Dashboard v0.8.13
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.13, at classes/user.php

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