PluginProbe
UpdraftCentral Dashboard / 0.8.16
UpdraftCentral Dashboard v0.8.16
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.16, at classes/user.php

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