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