PluginProbe
UpdraftCentral Dashboard / 0.7.3
UpdraftCentral Dashboard v0.7.3
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.7.3, at classes/user.php

1,024 lines 38.3 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_edit_site_configuration', array($this, 'dashboard_ajaxaction_edit_site_configuration'), 10, 2);
38 add_filter('updraftcentral_dashboard_ajaxaction_edit_site_connection_method', array($this, 'dashboard_ajaxaction_edit_site_connection_method'), 10, 2);
39 add_filter('updraftcentral_dashboard_ajaxaction_delete_site', array($this, 'dashboard_ajaxaction_delete_site'), 10, 2);
40 add_filter('updraftcentral_dashboard_ajaxaction_sites_html', array($this, 'dashboard_ajaxaction_sites_html'));
41 add_filter('updraftcentral_dashboard_ajaxaction_site_rpc', array($this, 'dashboard_ajaxaction_site_rpc'), 10, 2);
42 add_filter('updraftcentral_dashboard_ajaxaction_manage_site_order', array($this, 'dashboard_ajaxaction_manage_site_order'), 10, 2);
43
44 add_filter('updraftcentral_load_user_sites', array($this, 'load_user_sites_filter'));
45 add_filter('updraftcentral_dashboard_ajaxaction_manage_site_meta', array($this, 'dashboard_ajaxaction_manage_site_meta'), 10, 2);
46
47 // Manage dashboard shortcuts through ajax request
48 add_filter('updraftcentral_dashboard_ajaxaction_shortcuts', array($this, 'dashboard_ajaxaction_shortcuts'), 10, 2);
49
50 // Handles module visibility
51 add_filter('updraftcentral_main_navigation_items', array($this, 'main_navigation_items'));
52 add_filter('updraftcentral_dashboard_ajaxaction_module_visibility', array($this, 'dashboard_ajaxaction_module_visibility'), 10, 2);
53 add_filter('updraftcentral_dashboard_ajaxaction_reset_modules_visibility', array($this, 'dashboard_ajaxaction_reset_modules_visibility'), 10, 2);
54
55 // Load licence manager - this needs loading before the sites themselves are
56 if (!class_exists('UpdraftCentral_Licence_Manager')) include_once UD_CENTRAL_DIR.'/classes/licence-manager.php';
57
58 // Allow developers to implement their own licence management
59 $licence_manager_class = apply_filters('updraftcentral_licence_manager_class', 'UpdraftCentral_Licence_Manager');
60
61 $this->licence_manager = new $licence_manager_class($this, $this->rc);
62
63 $this->load_user_sites();
64
65 }
66
67 /**
68 * Saves user-defined shortcut keys and returns shortcut collection
69 *
70 * @param array $response A response array where we insert our request response
71 * @param array $post_data Parameters passed as an additional argument(s) to the request
72 * @return array
73 */
74 public function dashboard_ajaxaction_shortcuts($response, $post_data) {
75
76 $response['responsetype'] = 'ok';
77 $response['message'] = 'success';
78
79 $shortcuts = get_user_meta($this->user_id, 'updraftcentral_dashboard_shortcuts', true);
80 if (!is_array($shortcuts)) $shortcuts = array();
81
82 if (isset($post_data['data']['key'])) {
83 $shortcuts[$post_data['data']['name']] = $post_data['data']['key'];
84 } elseif (isset($post_data['data']['clear'])) {
85 $shortcuts = array();
86 }
87
88 update_user_meta($this->user_id, 'updraftcentral_dashboard_shortcuts', $shortcuts);
89
90 // Return current shortcuts collection
91 $response['shortcuts'] = $shortcuts;
92
93 return $response;
94
95 }
96
97 /**
98 * Process site meta management actions (add, delete, update and get) through ajax request
99 *
100 * @param array $response
101 * @param array $post_data - site meta parameters for the current action
102 * @return array
103 */
104 public function dashboard_ajaxaction_manage_site_meta($response, $post_data) {
105
106 try {
107
108 $response['responsetype'] = 'ok';
109 $response['message'] = '';
110
111 $site_meta = $this->rc->site_meta;
112
113 if (!empty($site_meta)) {
114 $data = $post_data['data'];
115
116 switch ($data['action']) {
117 case 'add':
118 $response['data'] = $site_meta->add_site_meta($data['site_id'], $data['meta_key'], $data['meta_value'], $data['unique']);
119 break;
120 case 'delete':
121 $response['data'] = $site_meta->delete_site_meta($data['site_id'], $data['meta_key'], $data['meta_value']);
122 break;
123 case 'get':
124 $response['data'] = $site_meta->get_site_meta($data['site_id'], $data['key'], $data['single']);
125 break;
126 case 'update':
127 $response['data'] = $site_meta->update_site_meta($data['site_id'], $data['meta_key'], $data['meta_value'], $data['prev_value']);
128 break;
129 default:
130 $response['message'] = 'The submitted site meta command was not recognized.';
131 break;
132 }
133 } else {
134 $response['message'] = 'Unable to pull the site meta instance.';
135 }
136 } catch (Exception $e) {
137 $response['responsetype'] = 'error';
138 $response['message'] = $e->getMessage();
139 // @codingStandardsIgnoreLine
140 } catch (Error $e) {
141 $response['responsetype'] = 'error';
142 $response['message'] = $e->getMessage();
143 }
144
145 return $response;
146 }
147
148 /**
149 * Used to update sort order in user meta
150 *
151 * @param array $response
152 * @param array $post_data - site_order is an indexed array of site id's in the sorted order
153 * @return array
154 */
155 public function dashboard_ajaxaction_manage_site_order($response, $post_data) {
156
157 if (isset($post_data['data']['site_order'])) {
158
159 $user_id = $this->user_id;
160 $response['responsetype'] = "ok";
161 $response['message'] = 'No change';
162
163 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)
164
165 if (update_user_meta($user_id, 'updraftcentral_dashboard_site_order', $post_data['data']['site_order'])) {
166 $response['message'] = 'success';
167 } else {
168 $response['message'] = 'fail';
169 }
170 }
171 } else {
172 $response['responsetype'] = "error";
173 $response['message'] = "Missing site order data";
174 }
175
176 return $response;
177 }
178
179 public function get_licence_manager() {
180 return $this->licence_manager;
181 }
182
183 /**
184 * TODO: 1) Catch PHP events on the mothership, pass them on and let them be console.logged
185 * 2) Pass on caught output from the remote side, and get it console.logged
186 *
187 * @param array $response
188 * @param array $post_data
189 * @return array
190 */
191 public function dashboard_ajaxaction_site_rpc($response, $post_data) {
192
193 return $this->send_remote_command($post_data, false, $response);
194
195 }
196
197 /**
198 * Sends UpdraftCentral's command to the remote website
199 *
200 * @param array $data An array container the command to execute along with its command parameters
201 * @param boolean $force_save Optional. A flag that indicates whether UpdraftCentral will save and cache the response from the remote website
202 * @param array $response Optional. A response container that gets populated with the remote website's response from the current request
203 */
204 public function send_remote_command($data, $force_save = false, $response = array()) {
205
206 try {
207
208 // Load site rpc
209 if (!class_exists('UpdraftCentral_Remote_Communications')) include_once UD_CENTRAL_DIR.'/classes/class-siterpc.php';
210 $site_rpc = new UpdraftCentral_Remote_Communications($this, $response, $data);
211
212 if ($validate_result = $site_rpc->validate_input()) {
213 // Send command to remote website.
214 $response = $site_rpc->send_message($force_save);
215 } else {
216 // Return error from validation process
217 $response = $validate_result;
218 }
219
220 } catch (Exception $e) {
221 $response['responsetype'] = 'error';
222 $response['message'] = $e->getMessage();
223 }
224
225 return $response;
226 }
227
228 public function deep_sanitize($input, $sanitize_function = 'htmlspecialchars') {
229 if (is_string($input)) return call_user_func($sanitize_function, $input);
230 if (is_array($input)) {
231 foreach ($input as $k => $v) {
232 $input[$k] = $this->deep_sanitize($v, $sanitize_function);
233 }
234 }
235
236 return $input;
237 }
238
239 public function send_message($ud_rpc, $message, $data = null, $timeout = 30) {
240 $this->php_events = array();
241
242 if ('__updraftcentral_internal_preencrypted' == $message) {
243
244 $post_options = array(
245 'timeout' => $timeout,
246 'body' => $data,
247 );
248
249 $post_options = apply_filters('udrpc_post_options', $post_options, $message, $data, $timeout, $this);
250
251 try {
252 $post = $ud_rpc->http_post($post_options);
253 } catch (Exception $e) {
254 // Curl can return an error code 0, which causes WP_Error to return early, without recording the message. So, we prefix the code.
255 return new WP_Error('http_post_'.$e->getCode(), $e->getMessage());
256 }
257
258 if (is_wp_error($post)) return $post;
259
260 if (empty($post['response']) || empty($post['response']['code'])) return new WP_Error('empty_http_code', 'Unexpected HTTP response code');
261
262 if ($post['response']['code'] < 200 || $post['response']['code'] >= 300) return new WP_Error('unexpected_http_code', 'Unexpected HTTP response code ('.$post['response']['code'].')', $post);
263
264 if (empty($post['body'])) return new WP_Error('empty_response', 'Empty response from remote site');
265
266 return (string) $post['body'];
267
268 } else {
269 $response = $ud_rpc->send_message($message, $data, $timeout);
270 }
271
272 // TODO: Handle caught_output
273
274 if (is_array($response) && !empty($response['data']) && is_array($response['data']) && !empty($response['data']['php_events']) && !empty($response['data']['previous_data'])) {
275 // global $updraftplus;
276 $this->php_events = $response['data']['php_events'];
277 if (defined('WP_DEBUG') && WP_DEBUG) {
278 foreach ($response['data']['php_events'] as $logline) {
279 error_log('From remote side: '.$logline);
280 }
281 }
282 $response['data'] = $response['data']['previous_data'];
283 }
284
285 return $response;
286 }
287
288 public function dashboard_ajaxaction_sites_html($response) {
289 $response['responsetype'] = 'ok';
290 $response['sites_html'] = $this->get_sites_html();
291 $response['status_info'] = array(
292 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
293 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
294 );
295 $response['message'] = __('The site list has been refreshed.', 'updraftcentral');
296
297 return $response;
298 }
299
300 public function dashboard_ajaxaction_delete_site($response, $post_data) {
301 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id'])) {
302
303 $deleted = $this->delete_site_by_id((int) $post_data['data']['site_id']);
304
305 if (is_wp_error($deleted)) {
306 $response = $deleted;
307 } else {
308 $response['responsetype'] = 'ok';
309 $response['status_info'] = array(
310 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
311 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
312 );
313 $response['sites_html'] = $this->get_sites_html();
314 $response['message'] = __('The site was successfully deleted from your dashboard.', 'updraftcentral');
315 }
316
317 } else {
318 $response['responsetype'] = 'error';
319 $response['code'] = 'missing_data';
320 $response['message'] = __('Missing information', 'updraftcentral');
321 }
322
323 return $response;
324 }
325
326 public function dashboard_ajaxaction_edit_site_connection_method($response, $post_data) {
327
328 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id'])) {
329
330 $site_id = (int) $post_data['data']['site_id'];
331
332 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
333
334 $updated = $this->rc->wp_update('sites',
335 array(
336 'connection_method' => $connection_method,
337 ),
338 array(
339 'user_id' => $this->user_id,
340 'site_id' => $site_id,
341 ),
342 array(
343 '%s',
344 ),
345 array(
346 '%d',
347 '%d',
348 )
349 );
350
351 if (is_numeric($updated)) {
352 $response['responsetype'] = 'ok';
353
354 $this->load_user_sites();
355 $response['sites_html'] = $this->get_sites_html();
356 $response['status_info'] = array(
357 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
358 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
359 );
360
361 $response['message'] = __('The site configuration was successfully edited.', 'updraftcentral');
362 } else {
363 $response = $updated;
364 }
365
366 } else {
367 $response['responsetype'] = 'error';
368 $response['code'] = 'missing_data';
369 $response['message'] = __('Missing information', 'updraftcentral');
370 }
371
372 return $response;
373
374 }
375
376 public function dashboard_ajaxaction_edit_site_configuration($response, $post_data) {
377
378 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id']) && isset($post_data['data']['description'])) {
379
380 $site_id = (int) $post_data['data']['site_id'];
381
382 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
383 $send_cors_headers = (isset($post_data['data']['send_cors_headers']) && $post_data['data']['send_cors_headers']) ? 1 : 0;
384
385 $updated = $this->rc->wp_update('sites',
386 array(
387 'description' => (string) $post_data['data']['description'],
388 'connection_method' => $connection_method,
389 'send_cors_headers' => $send_cors_headers,
390 ),
391 array(
392 'user_id' => $this->user_id,
393 'site_id' => $site_id,
394 ),
395 array(
396 '%s',
397 '%s',
398 '%d',
399 ),
400 array(
401 '%d',
402 '%d',
403 )
404 );
405
406 if (is_numeric($updated)) {
407 $response['responsetype'] = 'ok';
408
409 $extra_site_info_unparsed = empty($post_data['data']['extra_site_info']) ? false : $post_data['data']['extra_site_info'];
410 if (!$extra_site_info_unparsed) {
411 $extra_site_info = array();
412 } else {
413 parse_str($extra_site_info_unparsed, $extra_site_info);
414 }
415
416 if (!empty($extra_site_info)) {
417 foreach ($extra_site_info as $meta_key => $meta_value) {
418 $this->rc->site_meta->update_site_meta($site_id, $meta_key, $meta_value);
419 }
420 }
421
422 $this->load_user_sites();
423 $response['sites_html'] = $this->get_sites_html();
424 $response['status_info'] = array(
425 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
426 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
427 );
428
429 $response['message'] = __('The site configuration was successfully edited.', 'updraftcentral');
430 } else {
431
432 $response = $updated;
433 }
434
435 } else {
436 $response['responsetype'] = 'error';
437 $response['code'] = 'missing_data';
438 $response['message'] = __('Missing information', 'updraftcentral');
439 }
440
441 return $response;
442 }
443
444 public function dashboard_ajaxaction_newsite($response, $post_data) {
445
446 if (empty($post_data['data']) || !is_array($post_data['data']) || empty($post_data['data']['key'])) {
447 $response['responsetype'] = 'error';
448 $response['code'] = 'empty';
449 $response['message'] = __('Please enter the site key.', 'updraftcentral');
450 } else {
451
452 $site_key = $post_data['data']['key'];
453
454 $extra_site_info_unparsed = empty($post_data['data']['extra_site_info']) ? false : $post_data['data']['extra_site_info'];
455 if (!$extra_site_info_unparsed) {
456 $extra_site_info = array();
457 } else {
458 parse_str($extra_site_info_unparsed, $extra_site_info);
459 }
460
461 $ud_rpc = $this->rc->get_udrpc();
462
463 // A bundle has these keys: key, name_indicator, url
464 $decode_bundle = $ud_rpc->decode_portable_bundle($site_key, 'base64_with_count');
465
466 if (!is_array($decode_bundle) || !empty($decode_bundle['code'])) {
467 $response['responsetype'] = 'error';
468 $response['message'] = __('Error:', 'updraftcentral');
469 $response['code'] = empty($decode_bundle['code']) ? 'could_not_decode' : $decode_bundle['code'];
470 if (!empty($decode_bundle['code']) && 'invalid_wrong_length' == $decode_bundle['code']) {
471 $response['message'] .= ' '.__('The entered key was the wrong length - please try again.', 'updraftcentral');
472 } elseif (!empty($decode_bundle['code']) && 'invalid_corrupt' == $decode_bundle['code']) {
473 $response['message'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftcentral').' ('.$decode_bundle['data'].')';
474 } elseif (empty($decode_bundle['key']) || empty($decode_bundle['url']) || empty($decode_bundle['name_indicator'])) {
475 $response['message'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftcentral');
476 $response['data'] = $decode_bundle;
477 }
478 } elseif (empty($decode_bundle['key']) || empty($decode_bundle['url']) || empty($decode_bundle['user_id'])) {
479 $response['message'] = __('Error:', 'updraftcentral').' '.__('The entered key was corrupt - please try again.', 'updraftcentral');
480 $response['code'] = 'corrupt_key';
481 $response['data'] = $decode_bundle;
482 } else {
483
484 if (trailingslashit(network_site_url()) == $decode_bundle['url'] && !apply_filters('updraftcentral_allow_self_control', true)) {
485 $response['responsetype'] = 'error';
486 $response['code'] = 'this_site';
487 $response['message'] = __('Error:', 'updraftcentral').' '.__('The entered key does not belong to a remote site (it belongs to this one).', 'updraftcentral');
488 } 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'])) {
489 // 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.
490 $response['responsetype'] = 'error';
491 $response['code'] = 'cant_add_localhost';
492 $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');
493 } else {
494 // Was the key sent SSL to us directly?
495 $key = $decode_bundle['key'];
496 if (is_array($key) && !empty($key['key_hash']) && isset($key['key_id'])) {
497 global $wpdb;
498 // Allow them 3 hours to copy-and-paste their key
499 $wpdb->query('DELETE FROM '.$wpdb->base_prefix.$this->rc->table_prefix.'site_temporary_keys WHERE created<='.(int) (time() - 10800));
500 $key_info = $this->rc->wp_get_row('site_temporary_keys', $wpdb->prepare('key_id=%d', $key['key_id']));
501 if (is_object($key_info) && !empty($key_info->key_local_private) && !empty($key_info->key_remote_public)) {
502 $this->rc->wp_delete('site_temporary_keys', array('key_id' => $key['key_id']));
503 $key_hash = hash('sha256', $key_info->key_remote_public);
504
505 // @codingStandardsIgnoreLine
506 if ((function_exists('hash_equals') && hash_equals($key_hash, $key['key_hash'])) || (!function_exists('hash_equals') && $key_hash === $key['key_hash'])) {
507 $key_local_private = $key_info->key_local_private;
508 $key_remote_public = $key_info->key_remote_public;
509 } else {
510 $response['responsetype'] = 'error';
511 $response['code'] = 'wrong_hash';
512 $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'));
513
514 return $response;
515 }
516 } else {
517 $response['responsetype'] = 'error';
518 $response['code'] = 'no_key_found';
519 $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'));
520
521 return $response;
522 }
523
524 } elseif (!empty($decode_bundle['mothership_firewalled'])) {
525
526 // Need to do direct AJAX from the browser to the mothership to send our key
527
528 $ud_rpc = $this->rc->get_udrpc('central_host.updraftplus.com', true);
529 if (false != $ud_rpc->generate_new_keypair()) {
530 $key_remote_public = $key;
531 $key_local_private = $ud_rpc->get_key_local();
532 } else {
533 $response['responsetype'] = 'error';
534 $response['code'] = 'keygen_error';
535 $response['message'] = 'An error occurred when attempting to generate a new key-pair';
536
537 return $response;
538 }
539
540 } else {
541 $key_remote_public = $key;
542 $key_local_private = false;
543 }
544
545 $remote_site_id = empty($decode_bundle['ms_id']) ? 0 : $decode_bundle['ms_id'];
546 $description = isset($decode_bundle['site_title']) ? (string) $decode_bundle['site_title'] : '';
547
548 $send_cors_headers = (isset($post_data['data']['send_cors_headers']) && !$post_data['data']['send_cors_headers']) ? 0 : 1;
549 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
550
551 $site_url = $decode_bundle['url'];
552
553 // Supply a default for legacy format keys that didn't include the admin URL
554 $admin_url = empty($decode_bundle['admin_url']) ? trailingslashit($site_url).'wp-admin' : $decode_bundle['admin_url'];
555
556 $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);
557
558 if (true === $added) {
559 $response['responsetype'] = 'ok';
560
561 global $wpdb;
562 $new_site_id = $wpdb->insert_id;
563
564 if (!empty($extra_site_info)) {
565 if (!is_array($this->sites_meta)) $this->sites_meta = array();
566 if (empty($this->sites_meta[$new_site_id])) $this->sites_meta[$new_site_id] = array();
567 foreach ($extra_site_info as $meta_key => $meta_value) {
568 if (!$meta_value) continue;
569 // Don't bother to save the default value on the initial adding of the site
570 if ('http_authentication_method' == $meta_key && 'basic' == $meta_value) continue;
571 $result = $this->rc->site_meta->add_site_meta($new_site_id, $meta_key, $meta_value);
572 if (false !== $result) {
573 $created = apply_filters("get_site_metadata_created", false, $new_site_id, $meta_key);
574 if (!$created) {
575 // Fallback if in case we fail to retrieve the "created" value from
576 // the site metadata table. But most likely, if the add_site_meta succeeded
577 // then we would have a value for the $created variable.
578 $created = time();
579 }
580
581 $meta = new stdClass();
582 $meta->value = $meta_value;
583 $meta->created = $created;
584
585 // We update the in-memory copy because this is used by get_sites_html()
586 $this->sites_meta[$new_site_id][$meta_key] = $meta;
587 }
588 }
589 }
590
591 // Return the new HTML widget to the front end
592 $response['sites_html'] = $this->get_sites_html();
593 $response['status_info'] = array(
594 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
595 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
596 );
597
598 $response['message'] = __('The key was successfully added.', 'updraftcentral').' '.__('It is for interacting with the following site: ', 'updraftcentral').htmlspecialchars($decode_bundle['url']);
599
600 if (!empty($decode_bundle['mothership_firewalled_callback_url'])) {
601 $response['key_needs_sending'] = array(
602 'site_id' => $new_site_id,
603 'url' => $decode_bundle['mothership_firewalled_callback_url'],
604 'updraft_key_index' => $decode_bundle['updraft_key_index'],
605 'remote_public_key' => $ud_rpc->get_key_remote(),
606 );
607 }
608
609 } else {
610 $response['responsetype'] = 'error';
611 $response['code'] = $added->get_error_code();
612 $response['message'] = __('Error:', 'updraftcentral').' '.$added->get_error_message();
613 }
614
615 }
616 }
617 }
618
619 return $response;
620 }
621
622 public function load_user_sites_filter($sites) {
623 $how_many_licences_available = $this->licence_manager->how_many_licences_available();
624 $how_many_licences_in_use = count($sites);
625
626 if ($how_many_licences_available >= $how_many_licences_in_use || $how_many_licences_available < 0) return $sites;
627
628 $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);
629
630 $this->rc->log_notice($log_message, 'error', 'not_enough_licences');
631
632 $i = 0;
633 foreach ($sites as $site_id => $site) {
634 if ($i >= $how_many_licences_available) {
635 $site->unlicensed = true;
636 $sites[$site_id] = $site;
637 }
638 ++$i;
639 }
640
641 return $sites;
642 }
643
644 /**
645 * Populate $this->sites and $this->sites_meta in accordance with the current user ($this->user_id)
646 *
647 * @return Array|WP_Error - either the same list of sites as will be in $this->sites, or a WP_Error if something went wrong
648 */
649 public function load_user_sites() {
650 global $wpdb;
651 $sites = $wpdb->get_results('SELECT * FROM '.$this->sites_table.' WHERE user_id='.absint($this->user_id));
652
653 $this->sites_meta = array();
654
655 $subsequent_site = false;
656 if (is_array($sites) && !empty($sites)) {
657 $sites_meta_sql = 'SELECT * FROM '.$this->sitemeta_table.' WHERE site_id IN (';
658 foreach ($sites as $site) {
659 if ($subsequent_site) {
660 $sites_meta_sql .= ',';
661 } else {
662 $subsequent_site = true;
663 }
664 $sites_meta_sql .= absint($site->site_id);
665 }
666 $sites_meta_sql .= ')';
667 $sites_meta = $wpdb->get_results($sites_meta_sql);
668 } else {
669 $sites_meta = array();
670 }
671
672 if (is_array($sites_meta)) {
673 foreach ($sites_meta as $meta_row) {
674 if (isset($meta_row->site_id)) {
675 // N.B. Since we're trying to include the 'created' column in the site metadata when loaded or pulled from
676 // the database, therefore, we assign an anonymous object to encapsulate the value of the current meta_key along with
677 // its created column.
678
679 $meta = new stdClass();
680 $meta->value = $meta_row->meta_value;
681 $meta->created = $meta_row->created;
682
683 $this->sites_meta[$meta_row->site_id][$meta_row->meta_key] = $meta;
684 }
685 }
686 }
687
688 if (is_array($sites)) {
689
690 $processed_sites = array();
691 foreach ($sites as $site) {
692 $processed_sites[$site->site_id] = $site;
693 }
694 $this->sites = apply_filters('updraftcentral_load_user_sites', $processed_sites, $this, $this->licence_manager);
695
696 return $this->sites;
697
698 } elseif (is_wp_error($sites)) {
699
700 $this->rc->log_notice($sites);
701 $this->sites = null;
702
703 return $sites;
704 }
705 }
706
707 public function get_sites_html() {
708
709 $ret = '';
710
711 // Get sites. Print a line for each of them.
712 if (empty($this->sites) || !is_array($this->sites)) {
713 $ret .= $this->rc->include_template('sites/none-set-up.php', true, array('common_urls' => $this->rc->get_common_urls()));
714 } else {
715
716 // retrieve metadata if any exists
717 $user_id = $this->user_id;
718
719 // ensure we have an array (in even of no metadata)
720
721 if (!$site_order_meta = get_user_meta($user_id, 'updraftcentral_dashboard_site_order', true)) {
722 $site_order_meta = array();
723 }
724
725 // Add existing sites if not in siteOrderMeta (i.e. any new added sites or handling no meta data)
726
727 foreach ($this->sites as $site) {
728 if (!in_array($site->site_id, $site_order_meta)) {
729 array_push($site_order_meta, $site->site_id);
730 }
731 }
732
733 // Render the site rows in site_order_meta sequence using site_id to reference sites object
734
735 foreach ($site_order_meta as $site_id_meta) {
736
737 // Ignore invalid or removed sites
738
739 if (!empty($this->sites[$site_id_meta])) {
740
741 $connection_method = isset($this->sites[$site_id_meta]->connection_method) ? (string) $this->sites[$site_id_meta]->connection_method : 'direct_default_auth';
742 $send_cors_headers = (isset($this->sites[$site_id_meta]->send_cors_headers) && !$this->sites[$site_id_meta]->send_cors_headers) ? 0 : 1;
743
744 $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) . '"';
745
746 if (empty($this->sites[$site_id_meta]->admin_url)) {
747 $admin_url = trailingslashit($this->sites[$site_id_meta]->url) . 'wp-admin';
748 } else {
749 $admin_url = $this->sites[$site_id_meta]->admin_url;
750 }
751 $site_data_attributes .= ' data-admin_url="' . esc_attr($admin_url) . '"';
752
753 $site_meta = empty($this->sites_meta[$site_id_meta]) ? array() : $this->sites_meta[$site_id_meta];
754 if (!empty($site_meta)) {
755 if (!empty($site_meta['http_username']->value)) {
756 $http_password = empty($site_meta['http_password']->value) ? '' : $site_meta['http_password']->value;
757 $site_data_attributes .= ' data-http_username="' . esc_attr($site_meta['http_username']->value) . '" data-http_password="' . esc_attr($http_password) . '"';
758 if (!empty($site_meta['http_authentication_method']->value)) $site_data_attributes .= ' data-http_authentication_method="' . $site_meta['http_authentication_method']->value . '"';
759 }
760 }
761
762 if (empty($this->sites[$site_id_meta]->unlicensed)) {
763 if ('via_mothership_encrypting' != $connection_method) {
764 $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) . '"';
765 }
766 } else {
767 $site_data_attributes .= ' data-site_unlicensed="1"';
768 }
769
770 $site_data_attributes .= ' data-connection_method="' . esc_attr($connection_method) . '" data-send_cors_headers="' . $send_cors_headers . '"';
771
772 $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));
773 }
774 }
775 }
776
777 return $ret;
778 }
779
780 /**
781 * Returns false if not authorised at all; or a timestamp if it's authorised until a particular date
782 *
783 * @param int $site_id [description]
784 * @return boolean|integer
785 */
786 public function authorised_for_site_until($site_id) {
787
788 if (!is_array($this->sites)) $this->load_user_sites();
789
790 if (is_array($this->sites)) {
791 foreach ($this->sites as $site) {
792 if ((int) $site_id == (int) $site->site_id && isset($site->licence_until)) {
793 return apply_filters('updraftcentral_authorised_for_site_until', (int) $site->licence_until, $site_id, $this->sites);
794 }
795 }
796 }
797
798 return apply_filters('updraftcentral_authorised_for_site_until', false, $site_id, $this->sites);
799
800 }
801
802 /**
803 * Adding a site
804 *
805 * @param string $url
806 * @param string $admin_url
807 * @param string $key_local_private
808 * @param string $key_remote_public
809 * @param integer $remote_user_id
810 * @param string $remote_user_login
811 * @param string $key_name_indicator
812 * @param integer $remote_site_id
813 * @param string $description
814 * @param string $connection_method
815 * @param Boolean $send_cors_headers
816 * @return Boolean|WP_Error - if a Boolean, then it will be true
817 */
818 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) {
819
820 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);
821
822 if (!$this->licence_manager->is_slot_available()) {
823 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')));
824 }
825
826 $this->delete_site_by_url($url);
827
828 $added = $this->rc->wp_insert('sites',
829 array(
830 'user_id' => $this->user_id,
831 'url' => $url,
832 'admin_url' => $admin_url,
833 'key_local_private' => $key_local_private,
834 'key_remote_public' => $key_remote_public,
835 'description' => $description,
836 'connection_method' => $connection_method,
837 'send_cors_headers' => $send_cors_headers,
838 'sequence_id' => 0,
839 'remote_user_id' => $remote_user_id,
840 'remote_user_login' => $remote_user_login,
841 'remote_site_id' => $remote_site_id,
842 'key_name_indicator' => $key_name_indicator,
843 ),
844 array(
845 '%d',
846 '%s',
847 '%s',
848 '%s',
849 '%s',
850 '%s',
851 '%s',
852 '%d',
853 '%d',
854 '%d',
855 '%s',
856 '%d',
857 '%s',
858 )
859 );
860
861 if (is_numeric($added)) {
862 $result = true;
863 } else {
864 $result = $added;
865 }
866
867 $this->load_user_sites();
868
869 return $result;
870
871 }
872
873 public function delete_site_meta($site_id) {
874 return $this->rc->wp_delete('sitemeta', array('site_id' => $site_id));
875 }
876
877 public function delete_site_by_id($site_id, $reload_user_sites = true) {
878 $result = $this->rc->wp_delete('sites', array('user_id' => $this->user_id, 'site_id' => $site_id));
879 $this->delete_site_meta($site_id);
880 if ($reload_user_sites) $this->load_user_sites();
881
882 return $result;
883 }
884
885 public function delete_site_by_url($url) {
886
887 // We used to do a direct delete... but we need the site ID in order to be able to wipe the site meta
888 // $result = $this->rc->wp_delete('sites', array('user_id' => $this->user_id, 'url' => $url));
889
890 $result = 0;
891
892 foreach ($this->sites as $site_id => $site) {
893 if (strtolower($url) == strtolower($site->url)) {
894 $result += $this->delete_site_by_id($site_id, false);
895 }
896 }
897
898 $this->load_user_sites();
899
900 return $result;
901 }
902
903 /**
904 * This just gives some potential for the future, currently - currently, there's nothing we're forbidding through this mechanism
905 *
906 * @param string $do_what
907 * @return Boolean
908 */
909 public function user_can($do_what) {
910 $result = false;
911
912 $old_user_id = get_current_user_id();
913 if ($old_user_id != $this->user_id) wp_set_current_user($this->user_id);
914
915 $is_admin = apply_filters('updraftcentral_user_can_is_admin', current_user_can('manage_options'), $this);
916
917 if ($old_user_id != $this->user_id) wp_set_current_user($old_user_id);
918
919 switch ($do_what) {
920 case 'add_site':
921 $result = $is_admin;
922 break;
923 case 'delete_site':
924 $result = $is_admin;
925 break;
926 }
927
928 return apply_filters('updraftcentral_user_can', $result, $do_what, $this);
929 }
930
931 /**
932 * Gets list of loaded modules by hooking into `updraftcentral_main_navigation_items` filter
933 * and sets each available module's visibility as user_meta if not already set.
934 *
935 * @param array $loaded_modules An array of loaded modules
936 * @return array An array of loaded modules
937 */
938 public function main_navigation_items($loaded_modules) {
939
940 if ('' === get_user_meta($this->user_id, 'updraftcentral_modules_visibility', true)) {
941 $updraftcentral_modules_visibility = array();
942 foreach ($loaded_modules as $id => $item) {
943 if ('sites' !== $id) {
944 $updraftcentral_modules_visibility[$id] = true;
945 }
946 }
947 // @codingStandardsIgnoreLine
948 add_user_meta($this->user_id, 'updraftcentral_modules_visibility', $updraftcentral_modules_visibility, true);
949 }
950
951 return $loaded_modules;
952 }
953
954 /**
955 * Handles module visibility status when it is changed in frontend.
956 *
957 * It fires when ajax call is made with `module_visibility` action.
958 * Hooks into `updraftcentral_dashboard_ajaxaction_module_visibility` filter.
959 * Receives module_id and its visibility and updates module's visibility status in
960 * database as user_meta and returns the result of ajax call
961 *
962 * @param array $response An array to be returned to ajax call
963 * @param array $post_data An array of data from ajax call
964 * @return array An array of data contains result of ajax call
965 */
966 public function dashboard_ajaxaction_module_visibility($response, $post_data) {
967
968 if (empty($post_data['data']) || !is_array($post_data['data'])) {
969 $response['responsetype'] = 'error';
970 $response['code'] = 'empty';
971 $response['message'] = __('There was a error, please try again.', 'updraftcentral');
972 } else {
973 $module_id = $post_data['data']['module_id'];
974 $visibility = $post_data['data']['visibility'];
975 $updraftcentral_modules_visibility = get_user_meta($this->user_id, 'updraftcentral_modules_visibility', true);
976 if ("true" === $visibility) {
977 $updraftcentral_modules_visibility[$module_id] = true;
978 } else {
979 $updraftcentral_modules_visibility[$module_id] = false;
980 }
981 update_user_meta($this->user_id, 'updraftcentral_modules_visibility', $updraftcentral_modules_visibility);
982
983 $response['responsetype'] = 'ok';
984 $response['message'] = __('Visibility changed successfully', 'updraftcentral');
985 }
986
987 $response['data'] = $post_data;
988
989 return $response;
990 }
991
992 /**
993 * Resets all modules visibility status.
994 *
995 * It fires when ajax call is made with `reset_modules_visibility` action.
996 * Hooks into `updraftcentral_dashboard_ajaxaction_reset_modules_visibility` filter.
997 * Updates every module's visibility status in
998 * database as user_meta and returns the result of ajax call
999 *
1000 * @param array $response An array to be returned to ajax call
1001 * @param array $post_data An array of data from ajax call
1002 * @return array An array of data contains result of ajax call
1003 */
1004 public function dashboard_ajaxaction_reset_modules_visibility($response, $post_data) {
1005 if (empty($post_data['data']) || 'all' !== $post_data['data']) {
1006 $response['responsetype'] = 'error';
1007 $response['code'] = 'empty';
1008 $response['message'] = __('There was a error, please try again.' . $post_data['data'], 'updraftcentral');
1009 } else {
1010 $updraftcentral_modules_visibility = get_user_meta($this->user_id, 'updraftcentral_modules_visibility', true);
1011 foreach ($updraftcentral_modules_visibility as $module_id => $visibility) {
1012 $updraftcentral_modules_visibility[$module_id] = true;
1013 }
1014 update_user_meta($this->user_id, 'updraftcentral_modules_visibility', $updraftcentral_modules_visibility);
1015 $response['responsetype'] = 'ok';
1016 $response['message'] = __('Visibility changed successfully', 'updraftcentral');
1017 }
1018 $response['data'] = $post_data;
1019 return $response;
1020 }
1021 }
1022
1023 endif;
1024