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

810 lines 32.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('UD_CENTRAL_DIR')) die('Security check');
4
5 if (!class_exists('UpdraftCentral_User')):
6 class UpdraftCentral_User {
7 private $rc;
8 public $user_id = null;
9 public $sites = null;
10 public $sites_meta = null;
11 private $php_events;
12
13 private $licence_manager = null;
14
15 public function __construct($user_id) {
16
17 $this->user_id = (int) $user_id;
18 if (!is_user_logged_in()) throw new Exception('The current visitor is not logged in');
19
20 global $wpdb;
21 $this->rc = UpdraftCentral();
22 $this->sites_table = $wpdb->prefix.$this->rc->table_prefix.'sites';
23 $this->sitemeta_table = $wpdb->prefix.$this->rc->table_prefix.'sitemeta';
24
25 add_filter('updraftcentral_dashboard_ajaxaction_newsite', array($this, 'dashboard_ajaxaction_newsite'), 10, 2);
26 add_filter('updraftcentral_dashboard_ajaxaction_edit_site_configuration', array($this, 'dashboard_ajaxaction_edit_site_configuration'), 10, 2);
27 add_filter('updraftcentral_dashboard_ajaxaction_edit_site_connection_method', array($this, 'dashboard_ajaxaction_edit_site_connection_method'), 10, 2);
28 add_filter('updraftcentral_dashboard_ajaxaction_delete_site', array($this, 'dashboard_ajaxaction_delete_site'), 10, 2);
29 add_filter('updraftcentral_dashboard_ajaxaction_sites_html', array($this, 'dashboard_ajaxaction_sites_html'));
30 add_filter('updraftcentral_dashboard_ajaxaction_site_rpc', array($this, 'dashboard_ajaxaction_site_rpc'), 10, 2);
31
32 add_filter('updraftcentral_load_user_sites', array($this, 'load_user_sites_filter'));
33
34 // Load licence manager - this needs loading before the sites themselves are
35 if (!class_exists('UpdraftCentral_Licence_Manager')) require_once UD_CENTRAL_DIR.'/classes/licence-manager.php';
36
37 // Allow developers to implement their own licence management
38 $licence_manager_class = apply_filters('updraftcentral_licence_manager_class', 'UpdraftCentral_Licence_Manager');
39
40 $this->licence_manager = new $licence_manager_class($this, $this->rc);
41
42 $this->load_user_sites();
43
44 }
45
46 public function get_licence_manager() {
47 return $this->licence_manager;
48 }
49
50 // TODO: 1) Catch PHP events on the mothership, pass them on and let them be console.logged 2) Pass on caught output from the remote side, and get it console.logged
51 public function dashboard_ajaxaction_site_rpc($response, $post_data) {
52
53 $is_preencrypted = !empty($_POST['site_rpc_preencrypted']);
54
55 // Check the sent data
56 if ($is_preencrypted) {
57
58 if (!isset($post_data['wrapped_message']) || !is_array($post_data['wrapped_message']) || empty($post_data['site_id']) || !is_numeric($post_data['site_id'])) {
59 $response['responsetype'] = 'error';
60 $response['code'] = 'missing_data';
61 $response['message'] = __('Missing information', 'updraftcentral');
62
63 return $response;
64 }
65
66 } else {
67
68 if (!isset($post_data['data']) || !is_array($post_data['data']) || empty($post_data['data']['command']) || empty($post_data['site_id']) || !is_numeric($post_data['site_id'])) {
69 $response['responsetype'] = 'error';
70 $response['code'] = 'missing_data';
71 $response['message'] = __('Missing information', 'updraftcentral');
72
73 return $response;
74 }
75
76 }
77
78 $site_id = (int) $post_data['site_id'];
79
80 // This is also a security check - whether the specified site belongs to the current user
81 if (empty($this->sites[$site_id])) {
82 $response['responsetype'] = 'error';
83 $response['code'] = 'nonexistent_site';
84 $response['message'] = sprintf(__('This site (%d / %d) was not found', 'updraftcentral'), $this->user_id, $site_id);
85
86 return $response;
87 }
88
89 $site = $this->sites[$site_id];
90
91 if (empty($site->key_local_private)) {
92 $response['responsetype'] = 'error';
93 $response['code'] = 'nonexistent_site_key';
94 $response['message'] = sprintf(__('The key for this site (%d / %d) was not found', 'updraftcentral'), $this->user_id, $site_id);
95
96 return $response;
97 }
98
99 if (!empty($site->unlicensed)) {
100 $response['responsetype'] = 'error';
101 $response['code'] = 'site_unlicensed';
102 $response['message'] = apply_filters('updraftcentral_site_unlicensed_message', __('You have more sites in your dashboard than licences. As a result, you cannot perform actions on this site.', 'updraftcentral').' '.__('You will need to obtain more licences, or remove some sites.', 'updraftcentral'));
103
104 return $response;
105 }
106
107 if ($this->rc->url_looks_internal($site->url) && !$this->rc->url_looks_internal(site_url()) && !apply_filters('updraftcentral_allow_contacting_internal_url_from_server', true, $url)) {
108 $url_scheme = strtolower(parse_url($site->url, PHP_URL_SCHEME));
109 $response['responsetype'] = 'error';
110 $response['code'] = 'cannot_contact_localdev';
111 $response['message'] = __('You cannot contact a website hosted on a site-local network (e.g. localhost) from this dashboard - it cannot be reached.', 'updraftcentral');
112
113 return $response;
114 }
115
116 @ob_start();
117
118 $site_meta = empty($this->sites_meta[$site->site_id]) ? array() : $this->sites_meta[$site->site_id];
119
120 $ud_rpc = $this->rc->get_udrpc($site->key_name_indicator);
121
122 $admin_url = empty($site->admin_url) ? $site->url : untrailingslashit($site->admin_url).'/admin-ajax.php';
123
124 if (preg_match('#/admin-ajax.php$#', $admin_url)) {
125 // wp-admin/admin-ajax.php before WP 3.5 will die() if $_REQUEST['action'] is not set (3.2) or is empty (3.4). Later WP versions also check that, but after (instead of before) wp-load.php, which is where we are ultimately hooked in.
126 $admin_url .= '?action=updraft_central';
127 }
128
129 $ud_rpc->set_destination_url($admin_url);
130
131 if (!empty($site_meta['http_username'])) {
132
133 $authentication_method = empty($site_meta['http_authentication_method']) ? 'basic' : $site_meta['http_authentication_method'];
134 $http_password = empty($site_meta['http_password']) ? '' : (string) $site_meta['http_password'];
135
136 if ('basic' != $authentication_method && version_compare(PHP_VERSION, '5.4', '<')) {
137 $reply = new WP_Error('no_digest_before_php54', sprintf(__('To use HTTP digest authentication, your server running the UpdraftCentral dashboard needs at least PHP %s (your version is %s)', 'updraftcentral'), '5.4', PHP_VERSION), PHP_VERSION);
138 } else {
139 // Guzzle supports HTTP digest authentication - the WP HTTP API doesn't.
140 require_once UD_CENTRAL_DIR.'/composer/vendor/autoload.php';
141 $guzzle_client = new GuzzleHttp\Client();
142
143 if (!method_exists($ud_rpc, 'set_http_transport') || !method_exists($ud_rpc, 'set_http_credentials')) {
144 // That's the probable cause, because we can assume that UC has a bundled UDRPC that's new enough.
145 $reply = new WP_Error('incompatible_udrpc_php', sprintf(__('The loaded UDRPC library (%s) is too old - you probably need to update your installed UpdraftPlus on the server', 'updraftcentral'), $ud_rpc->version));
146 } else {
147 $ud_rpc->set_http_transport($guzzle_client);
148 $ud_rpc->set_http_credentials(array('username' => $site_meta['http_username'], 'password' => $http_password, 'authentication_method' => $authentication_method));
149 }
150
151 }
152
153 }
154
155 if (!empty($reply)) {
156 // Already an error condition - nothing to do
157 } elseif ($is_preencrypted) {
158 $reply = $this->send_message($ud_rpc, '__updraftcentral_internal_preencrypted', $post_data['wrapped_message'], 30);
159 } else {
160
161 $ud_rpc->set_key_local($site->key_local_private);
162 $ud_rpc->set_key_remote($site->key_remote_public);
163 $ud_rpc->activate_replay_protection();
164
165 $command = (string) $post_data['data']['command'];
166 $data = isset($post_data['data']['data']) ? $post_data['data']['data'] : null;
167
168 $reply = $this->send_message($ud_rpc, $command, $data, 30);
169 }
170
171 $caught_output = @ob_get_contents();
172 @ob_end_clean();
173
174 // Pass on PHP events from the remote side
175 if (!empty($this->php_events)) $response['php_events'] = $this->php_events;
176 if (!empty($caught_output)) $response['mothership_caught_output'] = $caught_output;
177 if (is_wp_error($reply)) {
178 $response['responsetype'] = 'error';
179 $response['message'] = $reply->get_error_message();
180 $response['code'] = $reply->get_error_code();
181 $response['data'] = $reply->get_error_data();
182 } elseif (is_array($reply) && !empty($reply['response']) && 'error' == $reply['response']) {
183 $response['responsetype'] = 'error';
184 $response['message'] = empty($reply['message']) ? __('The connection to the remote site returned an error', 'updraftcentral') : $reply['message'];
185 $response['data'] = $reply;
186 } elseif ((!$is_preencrypted && (!is_array($reply) || empty($reply['response']) || (('ping' == $command && 'pong' != $reply['response'])) && 'rpcok' != $reply['response'])) || ($is_preencrypted && null === ($decoded_reply = json_decode($reply, true)) && (false == ($found_at = strpos($reply, '{"format":')) || null === ($decoded_reply = json_decode(substr($reply, $found_at), true))))) {
187 // If it is pre-encrypted, we expect a field 'udrpc_message' in the reply (after it's been JSON-decoded). We could check that. But instead, we just pass it back to the browser, since it'll be checked there anyway.
188 $response['responsetype'] = 'error';
189 $response['message'] = __('There was an error in contacting the remote site.', 'updraftcentral').' '.__("You should check that the remote site is online, is not firewalled, has remote control enabled, and that no security module is blocking the access. Then, check the logs on the remote site and your browser's JavaScript console.", 'updraftcentral').' '.__('If none of that helps, then you should try re-adding the site with a fresh key.', 'updraftcentral');
190 $response['data'] = $reply;
191 $response['code'] = 'no_pong';
192 } else {
193 $response['responsetype'] = 'ok';
194 $response['message'] = __('The site was connected to, and returned a response', 'updraftcentral');
195 if ($is_preencrypted) {
196 $response['wrapped_response'] = $decoded_reply;
197 } elseif ('siteinfo' == $command) {
198 $response['rpc_response'] = $this->deep_sanitize($reply);
199 } else {
200 $response['rpc_response'] = $reply;
201 }
202 }
203
204 return $response;
205 }
206
207 public function deep_sanitize($input, $sanitize_function = 'htmlspecialchars') {
208 if (is_string($input)) return call_user_func($sanitize_function, $input);
209 if (is_array($input)) {
210 foreach ($input as $k => $v) {
211 $input[$k] = $this->deep_sanitize($v, $sanitize_function);
212 }
213 }
214
215 return $input;
216 }
217
218 private function send_message($ud_rpc, $message, $data = null, $timeout = 30) {
219 $this->php_events = array();
220
221 if ('__updraftcentral_internal_preencrypted' == $message) {
222
223 $post_options = array(
224 'timeout' => $timeout,
225 'body' => $data,
226 );
227
228 $post_options = apply_filters('udrpc_post_options', $post_options, $message, $data, $timeout, $this);
229
230 try {
231 $post = $ud_rpc->http_post($post_options);
232 } catch (Exception $e) {
233 // Curl can return an error code 0, which causes WP_Error to return early, without recording the message. So, we prefix the code.
234 return new WP_Error('http_post_'.$e->getCode(), $e->getMessage());
235 }
236
237 if (is_wp_error($post)) return $post;
238
239 if (empty($post['response']) || empty($post['response']['code'])) return new WP_Error('empty_http_code', 'Unexpected HTTP response code');
240
241 if ($post['response']['code'] < 200 || $post['response']['code'] >= 300) return new WP_Error('unexpected_http_code', 'Unexpected HTTP response code ('.$post['response']['code'].')', $post);
242
243 if (empty($post['body'])) return new WP_Error('empty_response', 'Empty response from remote site');
244
245 return (string) $post['body'];
246
247 } else {
248 $response = $ud_rpc->send_message($message, $data, $timeout);
249 }
250
251 // TODO: Handle caught_output
252
253 if (is_array($response) && !empty($response['data']) && is_array($response['data']) && !empty($response['data']['php_events']) && !empty($response['data']['previous_data'])) {
254 // global $updraftplus;
255 $this->php_events = $response['data']['php_events'];
256 if (defined('WP_DEBUG') && WP_DEBUG) {
257 foreach ($response['data']['php_events'] as $logline) {
258 error_log('From remote side: '.$logline);
259 }
260 }
261 $response['data'] = $response['data']['previous_data'];
262 }
263
264 return $response;
265 }
266
267 public function dashboard_ajaxaction_sites_html($response) {
268 $response['responsetype'] = 'ok';
269 $response['sites_html'] = $this->get_sites_html();
270 $response['status_info'] = array(
271 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
272 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
273 );
274 $response['message'] = __('The site list has been refreshed.', 'updraftcentral');
275
276 return $response;
277 }
278
279 public function dashboard_ajaxaction_delete_site($response, $post_data) {
280 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id'])) {
281
282 $deleted = $this->delete_site_by_id((int) $post_data['data']['site_id']);
283
284 if (is_wp_error($deleted)) {
285 $response = $deleted;
286 } else {
287 $response['responsetype'] = 'ok';
288 $response['status_info'] = array(
289 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
290 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
291 );
292 $response['sites_html'] = $this->get_sites_html();
293 $response['message'] = __('The site was successfully deleted from your dashboard.', 'updraftcentral');
294 }
295
296 } else {
297 $response['responsetype'] = 'error';
298 $response['code'] = 'missing_data';
299 $response['message'] = __('Missing information', 'updraftcentral');
300 }
301
302 return $response;
303 }
304
305 public function dashboard_ajaxaction_edit_site_connection_method($response, $post_data) {
306
307 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id'])) {
308
309 $site_id = (int) $post_data['data']['site_id'];
310
311 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
312
313 $updated = $this->rc->wp_update('sites',
314 array(
315 'connection_method' => $connection_method,
316 ),
317 array(
318 'user_id' => $this->user_id,
319 'site_id' => $site_id,
320 ),
321 array(
322 '%s',
323 ),
324 array(
325 '%d',
326 '%d',
327 )
328 );
329
330 if (is_numeric($updated)) {
331 $response['responsetype'] = 'ok';
332
333 $this->load_user_sites();
334 $response['sites_html'] = $this->get_sites_html();
335 $response['status_info'] = array(
336 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
337 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
338 );
339
340 $response['message'] = __('The site configuration was successfully edited.', 'updraftcentral');
341 } else {
342 $response = $updated;
343 }
344
345 } else {
346 $response['responsetype'] = 'error';
347 $response['code'] = 'missing_data';
348 $response['message'] = __('Missing information', 'updraftcentral');
349 }
350
351 return $response;
352
353 }
354
355 public function dashboard_ajaxaction_edit_site_configuration($response, $post_data) {
356
357 if (isset($post_data['data']) && is_array($post_data['data']) && !empty($post_data['data']['site_id']) && isset($post_data['data']['description'])) {
358
359 $site_id = (int) $post_data['data']['site_id'];
360
361 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
362 $send_cors_headers = (isset($post_data['data']['send_cors_headers']) && $post_data['data']['send_cors_headers']) ? 1 : 0;
363
364 $updated = $this->rc->wp_update('sites',
365 array(
366 'description' => (string) $post_data['data']['description'],
367 'connection_method' => $connection_method,
368 'send_cors_headers' => $send_cors_headers,
369 ),
370 array(
371 'user_id' => $this->user_id,
372 'site_id' => $site_id,
373 ),
374 array(
375 '%s',
376 '%s',
377 '%d',
378 ),
379 array(
380 '%d',
381 '%d',
382 )
383 );
384
385 if (is_numeric($updated)) {
386 $response['responsetype'] = 'ok';
387
388 $extra_site_info_unparsed = empty($post_data['data']['extra_site_info']) ? false : $post_data['data']['extra_site_info'];
389 if (!$extra_site_info_unparsed) {
390 $extra_site_info = array();
391 } else {
392 parse_str($extra_site_info_unparsed, $extra_site_info);
393 }
394
395 if (!empty($extra_site_info)) {
396 foreach ($extra_site_info as $meta_key => $meta_value) {
397 $this->rc->site_meta->update_site_meta($site_id, $meta_key, $meta_value);
398 }
399 }
400
401 $this->load_user_sites();
402 $response['sites_html'] = $this->get_sites_html();
403 $response['status_info'] = array(
404 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
405 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
406 );
407
408 $response['message'] = __('The site configuration was successfully edited.', 'updraftcentral');
409 } else {
410
411 $response = $updated;
412 }
413
414 } else {
415 $response['responsetype'] = 'error';
416 $response['code'] = 'missing_data';
417 $response['message'] = __('Missing information', 'updraftcentral');
418 }
419
420 return $response;
421 }
422
423 public function dashboard_ajaxaction_newsite($response, $post_data) {
424
425 if (empty($post_data['data']) || !is_array($post_data['data']) || empty($post_data['data']['key'])) {
426 $response['responsetype'] = 'error';
427 $response['code'] = 'empty';
428 $response['message'] = __('Please enter the site key.', 'updraftcentral');
429 } else {
430
431 $site_key = $post_data['data']['key'];
432
433 $extra_site_info_unparsed = empty($post_data['data']['extra_site_info']) ? false : $post_data['data']['extra_site_info'];
434 if (!$extra_site_info_unparsed) {
435 $extra_site_info = array();
436 } else {
437 parse_str($extra_site_info_unparsed, $extra_site_info);
438 }
439
440 $ud_rpc = $this->rc->get_udrpc();
441
442 // A bundle has these keys: key, name_indicator, url
443 $decode_bundle = $ud_rpc->decode_portable_bundle($site_key, 'base64_with_count');
444
445 if (!is_array($decode_bundle) || !empty($decode_bundle['code'])) {
446 $response['responsetype'] = 'error';
447 $response['message'] = __('Error:', 'updraftcentral');
448 $response['code'] = empty($decode_bundle['code']) ? 'could_not_decode' : $decode_bundle['code'];
449 if (!empty($decode_bundle['code']) && $decode_bundle['code'] == 'invalid_wrong_length') {
450 $response['message'] .= ' '.__('The entered key was the wrong length - please try again.', 'updraftcentral');
451 } elseif (!empty($decode_bundle['code']) && $decode_bundle['code'] == 'invalid_corrupt') {
452 $response['message'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftcentral').' ('.$decode_bundle['data'].')';
453 } elseif (empty($decode_bundle['key']) || empty($decode_bundle['url']) || empty($decode_bundle['name_indicator'])) {
454 $response['message'] .= ' '.__('The entered key was corrupt - please try again.', 'updraftcentral');
455 $response['data'] = $decode_bundle;
456 }
457 } elseif (empty($decode_bundle['key']) || empty($decode_bundle['url']) || empty($decode_bundle['user_id'])) {
458 $response['message'] = __('Error:', 'updraftcentral').' '.__('The entered key was corrupt - please try again.', 'updraftcentral');
459 $response['code'] = 'corrupt_key';
460 $response['data'] = $decode_bundle;
461 } else {
462
463 if ($decode_bundle['url'] == trailingslashit(network_site_url()) && !apply_filters('updraftcentral_allow_self_control', true)) {
464 $response['responsetype'] = 'error';
465 $response['code'] = 'this_site';
466 $response['message'] = __('Error:', 'updraftcentral').' '.__('The entered key does not belong to a remote site (it belongs to this one).', 'updraftcentral');
467 } 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'])) {
468 // 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.
469 $response['responsetype'] = 'error';
470 $response['code'] = 'cant_add_localhost';
471 $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');
472 } else {
473 // Was the key sent SSL to us directly?
474 $key = $decode_bundle['key'];
475 if (is_array($key) && !empty($key['key_hash']) && isset($key['key_id'])) {
476 global $wpdb;
477 // Allow them 3 hours to copy-and-paste their key
478 $wpdb->query('DELETE FROM '.$wpdb->prefix.$this->rc->table_prefix.'site_temporary_keys WHERE created<='.(int) (time() - 10800));
479 $key_info = $this->rc->wp_get_row('site_temporary_keys', $wpdb->prepare('key_id=%d', $key['key_id']));
480 if (is_object($key_info) && !empty($key_info->key_local_private) && !empty($key_info->key_remote_public)) {
481 $this->rc->wp_delete('site_temporary_keys', array('key_id' => $key['key_id']));
482 $key_hash = hash('sha256', $key_info->key_remote_public);
483 if (hash_equals($key_hash, $key['key_hash'])) {
484 $key_local_private = $key_info->key_local_private;
485 $key_remote_public = $key_info->key_remote_public;
486 } else {
487 $response['responsetype'] = 'error';
488 $response['code'] = 'wrong_hash';
489 $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'));
490
491 return $response;
492 }
493 } else {
494 $response['responsetype'] = 'error';
495 $response['code'] = 'no_key_found';
496 $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'));
497
498 return $response;
499 }
500
501 } elseif (!empty($decode_bundle['mothership_firewalled'])) {
502
503 // Need to do direct AJAX from the browser to the mothership to send our key
504
505 $ud_rpc = $this->rc->get_udrpc('central_host.updraftplus.com', true);
506 if (false != $ud_rpc->generate_new_keypair()) {
507 $key_remote_public = $key;
508 $key_local_private = $ud_rpc->get_key_local();
509 } else {
510 $response['responsetype'] = 'error';
511 $response['code'] = 'keygen_error';
512 $response['message'] = 'An error occurred when attempting to generate a new key-pair';
513
514 return $response;
515 }
516
517 } else {
518 $key_remote_public = $key;
519 $key_local_private = false;
520 }
521
522 $remote_site_id = empty($decode_bundle['ms_id']) ? 0 : $decode_bundle['ms_id'];
523 $description = isset($decode_bundle['site_title']) ? (string) $decode_bundle['site_title'] : '';
524
525 $send_cors_headers = (isset($post_data['data']['send_cors_headers']) && !$post_data['data']['send_cors_headers']) ? 0 : 1;
526 $connection_method = isset($post_data['data']['connection_method']) ? (string) $post_data['data']['connection_method'] : 'direct_default_auth';
527
528 $site_url = $decode_bundle['url'];
529
530 // Supply a default for legacy format keys that didn't include the admin URL
531 $admin_url = empty($decode_bundle['admin_url']) ? trailingslashit($site_url).'wp-admin' : $decode_bundle['admin_url'];
532
533 $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);
534
535 if (true === $added) {
536 $response['responsetype'] = 'ok';
537
538 global $wpdb;
539 $new_site_id = $wpdb->insert_id;
540
541 if (!empty($extra_site_info)) {
542 if (!is_array($this->sites_meta)) $this->sites_meta = array();
543 if (empty($this->sites_meta[$new_site_id])) $this->sites_meta[$new_site_id] = array();
544 foreach ($extra_site_info as $meta_key => $meta_value) {
545 if (!$meta_value) continue;
546 // Don't bother to save the default value on the initial adding of the site
547 if ('http_authentication_method' == $meta_key && 'basic' == $meta_value) continue;
548 // We update the in-memory copy because this is used by get_sites_html()
549 $this->sites_meta[$new_site_id][$meta_key] = $meta_value;
550 $this->rc->site_meta->add_site_meta($new_site_id, $meta_key, $meta_value);
551 }
552 }
553
554 // Return the new HTML widget to the front end
555 $response['sites_html'] = $this->get_sites_html();
556 $response['status_info'] = array(
557 'how_many_licences_in_use' => $this->licence_manager->how_many_licences_in_use(),
558 'how_many_licences_available' => $this->licence_manager->how_many_licences_available(),
559 );
560
561 $response['message'] = __('The key was successfully added.', 'updraftcentral').' '.__('It is for interacting with the following site: ', 'updraftcentral').htmlspecialchars($decode_bundle['url']);
562
563 if (!empty($decode_bundle['mothership_firewalled_callback_url'])) {
564 $response['key_needs_sending'] = array(
565 'site_id' => $new_site_id,
566 'url' => $decode_bundle['mothership_firewalled_callback_url'],
567 'updraft_key_index' => $decode_bundle['updraft_key_index'],
568 'remote_public_key' => $ud_rpc->get_key_remote(),
569 );
570 }
571
572 } else {
573 $response['responsetype'] = 'error';
574 $response['code'] = $added->get_error_code();
575 $response['message'] = __('Error:', 'updraftcentral').' '.$added->get_error_message();
576 }
577
578 }
579 }
580 }
581
582 return $response;
583 }
584
585 public function load_user_sites_filter($sites) {
586 $how_many_licences_available = $this->licence_manager->how_many_licences_available();
587 $how_many_licences_in_use = count($sites);
588
589 if ($how_many_licences_available >= $how_many_licences_in_use || $how_many_licences_available < 0) return $sites;
590
591 $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);
592
593 $this->rc->log_notice($log_message, 'error', 'not_enough_licences');
594
595 $i = 0;
596 foreach ($sites as $site_id => $site) {
597 if ($i >= $how_many_licences_available) {
598 $site->unlicensed = true;
599 $sites[$site_id] = $site;
600 }
601 ++$i;
602 }
603
604 return $sites;
605 }
606
607 public function load_user_sites() {
608 global $wpdb;
609 $sites = $wpdb->get_results('SELECT * FROM '.$this->sites_table.' WHERE user_id='.$this->user_id);
610
611 $this->sites_meta = array();
612 $sites_meta = $wpdb->get_results('SELECT * FROM '.$this->sitemeta_table);
613 if (is_array($sites_meta)) {
614 foreach ($sites_meta as $meta_row) {
615 if (isset($meta_row->site_id)) {
616 // N.B. We are assuming a single value only for each key (the WP general scheme allows for multiple)
617 $this->sites_meta[$meta_row->site_id][$meta_row->meta_key] = $meta_row->meta_value;
618 }
619 }
620 }
621
622 if (is_array($sites)) {
623 $processed_sites = array();
624 foreach ($sites as $site) {
625 $processed_sites[$site->site_id] = $site;
626 }
627 $this->sites = apply_filters('updraftcentral_load_user_sites', $processed_sites, $this, $this->licence_manager);
628
629 return $this->sites;
630 } elseif (is_wp_error($sites)) {
631 $this->rc->log_notice($sites);
632 $this->sites = null;
633
634 return $sites;
635 }
636 }
637
638 public function get_sites_html() {
639
640 $ret = '';
641
642 // Get sites. Print a line for each of them.
643 if (empty($this->sites) || !is_array($this->sites)) {
644 $ret .= $this->rc->include_template('sites/none-set-up.php', true, array('common_urls' => $this->rc->get_common_urls()));
645 } else {
646 foreach ($this->sites as $site) {
647
648 $connection_method = isset($site->connection_method) ? (string) $site->connection_method : 'direct_default_auth';
649 $send_cors_headers = (isset($site->send_cors_headers) && !$site->send_cors_headers) ? 0 : 1;
650
651 $site_data_attributes = 'data-site_url="'.esc_attr($site->url).'" data-site_id="'.(int) $site->site_id.'" data-key_name_indicator="'.esc_attr($site->key_name_indicator).'" data-site_description="'.(($site->description) ? esc_attr($site->description) : esc_attr($site->url)).'" data-remote_user_id="'.(int) $site->remote_user_id.'" data-remote_user_login="'.esc_attr($site->remote_user_login).'"';
652
653 if (empty($site->admin_url)) {
654 $admin_url = trailingslashit($site->url).'wp-admin';
655 } else {
656 $admin_url = $site->admin_url;
657 }
658 $site_data_attributes .= ' data-admin_url="'.esc_attr($admin_url).'"';
659
660 $site_meta = empty($this->sites_meta[$site->site_id]) ? array() : $this->sites_meta[$site->site_id];
661 if (!empty($site_meta)) {
662 if (!empty($site_meta['http_username'])) {
663 $http_password = empty($site_meta['http_password']) ? '' : $site_meta['http_password'];
664 $site_data_attributes .= ' data-http_username="'.esc_attr($site_meta['http_username']).'" data-http_password="'.esc_attr($http_password).'"';
665 if (!empty($site_meta['http_authentication_method'])) $site_data_attributes .= ' data-http_authentication_method="'.$site_meta['http_authentication_method'].'"';
666 }
667 }
668
669 if (empty($site->unlicensed)) {
670 if ('via_mothership_encrypting' != $connection_method) {
671 $site_data_attributes .= ' data-site_remote_public_key="'.esc_attr($site->key_remote_public).'" data-site_local_private_key="'.esc_attr($site->key_local_private).'"';
672 }
673 } else {
674 $site_data_attributes .= ' data-site_unlicensed="1"';
675 }
676
677 $site_data_attributes .= ' data-connection_method="'.esc_attr($connection_method).'" data-send_cors_headers="'.$send_cors_headers.'"';
678
679 $ret .= $this->rc->include_template('sites/site-row.php', true, array('site' => $site, 'site_meta' => $site_meta, 'site_data_attributes' => $site_data_attributes));
680 }
681 }
682
683 return $ret;
684 }
685
686 /*
687 * Returns false if not authorised at all; or a timestamp if it's authorised until a particular date
688 *
689 * @return boolean|integer
690 */
691 public function authorised_for_site_until($site_id) {
692
693 if (!is_array($this->sites)) $this->load_user_sites();
694
695 if (is_array($this->sites)) {
696 foreach ($this->sites as $site) {
697 if ((int) $site_id == (int) $site->site_id && isset($site->licence_until)) {
698 return apply_filters('updraftcentral_authorised_for_site_until', (int) $site->licence_until, $site_id, $this->sites);
699 }
700 }
701 }
702
703 return apply_filters('updraftcentral_authorised_for_site_until', false, $site_id, $this->sites);
704
705 }
706
707 // Returns either true, or a WP_Error
708 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) {
709
710 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);
711
712 if (!$this->licence_manager->is_slot_available()) {
713 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')));
714 }
715
716 $this->delete_site_by_url($url);
717
718 $added = $this->rc->wp_insert('sites',
719 array(
720 'user_id' => $this->user_id,
721 'url' => $url,
722 'admin_url' => $admin_url,
723 'key_local_private' => $key_local_private,
724 'key_remote_public' => $key_remote_public,
725 'description' => $description,
726 'connection_method' => $connection_method,
727 'send_cors_headers' => $send_cors_headers,
728 'sequence_id' => 0,
729 'remote_user_id' => $remote_user_id,
730 'remote_user_login' => $remote_user_login,
731 'remote_site_id' => $remote_site_id,
732 'key_name_indicator' => $key_name_indicator,
733 ),
734 array(
735 '%d',
736 '%s',
737 '%s',
738 '%s',
739 '%s',
740 '%s',
741 '%s',
742 '%d',
743 '%d',
744 '%d',
745 '%s',
746 '%d',
747 '%s',
748 )
749 );
750
751 if (is_numeric($added)) {
752 $result = true;
753 } else {
754 $result = $added;
755 }
756
757 $this->load_user_sites();
758
759 return $result;
760
761 }
762
763 public function delete_site_meta($site_id) {
764 return $this->rc->wp_delete('sitemeta', array('site_id' => $site_id));
765 }
766
767 public function delete_site_by_id($site_id, $reload_user_sites = true) {
768 $result = $this->rc->wp_delete('sites', array('user_id' => $this->user_id, 'site_id' => $site_id));
769 $this->delete_site_meta($site_id);
770 if ($reload_user_sites) $this->load_user_sites();
771
772 return $result;
773 }
774
775 public function delete_site_by_url($url) {
776
777 // We used to do a direct delete... but we need the site ID in order to be able to wipe the site meta
778 // $result = $this->rc->wp_delete('sites', array('user_id' => $this->user_id, 'url' => $url));
779
780 $result = 0;
781
782 foreach ($this->sites as $site_id => $site) {
783 if (strtolower($url) == strtolower($site->url)) {
784 $result += $this->delete_site_by_id($site_id, false);
785 }
786 }
787
788 $this->load_user_sites();
789
790 return $result;
791 }
792
793 // This just gives some potential for the future, currently - currently, there's nothing we're forbidding through this mechanism
794 public function user_can($do_what) {
795 $result = false;
796 switch ($do_what) {
797 case 'add_site':
798 $result = true;
799 break;
800 case 'delete_site':
801 $result = true;
802 break;
803 }
804
805 return apply_filters('updraftcentral_user_can', $result, $do_what, $this);
806 }
807
808 }
809 endif;
810