| 1 |
<?php |
| 2 |
|
| 3 |
if (!class_exists('UpdraftCentral_Remote_Communications')) : |
| 4 |
|
| 5 |
/** |
| 6 |
* This class dispatches command(s) to the remote website |
| 7 |
* |
| 8 |
* Pre-validates submitted data, sends command and processes |
| 9 |
* the response received. Also responsible for caching result data |
| 10 |
* whenever applicable, and can pull cached data whether or not having |
| 11 |
* a maximum age (in seconds). |
| 12 |
*/ |
| 13 |
class UpdraftCentral_Remote_Communications { |
| 14 |
|
| 15 |
private $is_preencrypted; |
| 16 |
|
| 17 |
private $command; |
| 18 |
|
| 19 |
private $rc; |
| 20 |
|
| 21 |
private $user; |
| 22 |
|
| 23 |
private $data; |
| 24 |
|
| 25 |
private $site_id; |
| 26 |
|
| 27 |
private $site; |
| 28 |
|
| 29 |
private $ud_rpc; |
| 30 |
|
| 31 |
private $site_meta; |
| 32 |
|
| 33 |
private $admin_url; |
| 34 |
|
| 35 |
private $errors; |
| 36 |
|
| 37 |
private $response; |
| 38 |
|
| 39 |
private $table_prefix; |
| 40 |
|
| 41 |
/** |
| 42 |
* UpdraftCentral_Remote_Communications constructor. |
| 43 |
* |
| 44 |
* Does initial checks, pre-load certain properties needed to communicate to the remote website |
| 45 |
* and loads an error collection needed when returning errors to the caller. |
| 46 |
* |
| 47 |
* @param object $user An UpdraftCentral_User instance |
| 48 |
* @param array $response An initial response array |
| 49 |
* @param array $post_data Request data for processing |
| 50 |
* @return self |
| 51 |
*/ |
| 52 |
public function __construct($user, $response, $post_data) { |
| 53 |
if (!is_a($user, 'UpdraftCentral_User')) { |
| 54 |
throw new Exception('Unexpected parameter. Argument to UpdraftCentral_Remote_Communications needs to be of type UpdraftCentral_User.'); |
| 55 |
} |
| 56 |
|
| 57 |
$this->rc = UpdraftCentral(); |
| 58 |
$this->user = $user; |
| 59 |
$this->data = $post_data; |
| 60 |
$this->response = $response; |
| 61 |
|
| 62 |
$this->is_preencrypted = !empty($post_data['site_rpc_preencrypted']); |
| 63 |
$this->site_id = (int) $post_data['site_id']; |
| 64 |
$this->site = $user->sites[$this->site_id]; |
| 65 |
$this->ud_rpc = $this->rc->get_udrpc($this->site->key_name_indicator); |
| 66 |
$this->table_prefix = defined('UPDRAFTCENTRAL_TABLE_PREFIX') ? UPDRAFTCENTRAL_TABLE_PREFIX : 'updraftcentral_'; |
| 67 |
|
| 68 |
$this->site_meta = empty($user->sites_meta[$this->site->site_id]) ? array() : $user->sites_meta[$this->site->site_id]; |
| 69 |
$this->admin_url = empty($this->site->admin_url) ? $this->site->url : untrailingslashit($this->site->admin_url).'/admin-ajax.php'; |
| 70 |
if (preg_match('#/admin-ajax.php$#', $this->admin_url)) { |
| 71 |
// 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. |
| 72 |
$this->admin_url .= '?action=updraft_central'; |
| 73 |
} |
| 74 |
|
| 75 |
$this->ud_rpc->set_destination_url($this->admin_url); |
| 76 |
$this->load_errors(); |
| 77 |
} |
| 78 |
|
| 79 |
/** |
| 80 |
* Loads required objects if they're currently not available for the |
| 81 |
* current request/process. |
| 82 |
* |
| 83 |
* @internal |
| 84 |
*/ |
| 85 |
private function maybe_load_objects() { |
| 86 |
// We'll make sure that we don't have an empty site_meta instance or |
| 87 |
// any object needed by the process. |
| 88 |
if (empty($this->rc)) { |
| 89 |
$this->rc = UpdraftCentral(); |
| 90 |
} |
| 91 |
|
| 92 |
if (empty($this->rc->site_meta)) { |
| 93 |
if (!class_exists('UpdraftCentral_Site_Meta')) include_once UD_CENTRAL_DIR.'/classes/site-meta.php'; |
| 94 |
$this->rc->site_meta = new UpdraftCentral_Site_Meta($this->table_prefix); |
| 95 |
} |
| 96 |
} |
| 97 |
|
| 98 |
/** |
| 99 |
* Loads error collection that will be referenced when |
| 100 |
* returning specific error code for a failed process. |
| 101 |
* |
| 102 |
* @internal |
| 103 |
*/ |
| 104 |
private function load_errors() { |
| 105 |
$errors = array( |
| 106 |
'generic_error' => __('An error has occurred while processing your request.', 'updraftcentral'), |
| 107 |
'missing_data' => __('Missing information', 'updraftcentral'), |
| 108 |
'nonexistent_site' => sprintf(__('This site (%d / %d) was not found', 'updraftcentral'), $this->user->user_id, $this->site_id), |
| 109 |
'nonexistent_site_key' => sprintf(__('The key for this site (%d / %d) was not found', 'updraftcentral'), $this->user->user_id, $this->site_id), |
| 110 |
'site_unlicensed' => apply_filters('updraftcentral_site_unlicensed_message', __('You have more sites in your dashboard than licences.', 'updraftcentral').' '.__('As a result, you cannot perform actions on this site.', 'updraftcentral').' '.__('You will need to obtain more licences, or remove some sites.', 'updraftcentral')), |
| 111 |
'cannot_contact_localdev' => __('You cannot contact a website hosted on a site-local network (e.g. localhost) from this dashboard - it cannot be reached.', 'updraftcentral'), |
| 112 |
'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), |
| 113 |
'incompatible_udrpc_php' => sprintf(__('The loaded UDRPC library (%s) is too old - you probably need to update your installed UpdraftPlus on the server', 'updraftcentral'), $this->ud_rpc->version), |
| 114 |
); |
| 115 |
|
| 116 |
$this->errors = $errors; |
| 117 |
} |
| 118 |
|
| 119 |
/** |
| 120 |
* Pulls information regarding the error |
| 121 |
* |
| 122 |
* @internal |
| 123 |
* @param string $code A unique error code that is used to pull the error information |
| 124 |
* @return array - Returns the error information for a specific error key/code |
| 125 |
*/ |
| 126 |
private function return_error($code) { |
| 127 |
$response = array( |
| 128 |
'responsetype' => 'error', |
| 129 |
'code' => $code, |
| 130 |
'message' => isset($this->errors[$code]) ? $this->errors[$code] : $this->errors['generic_error'] |
| 131 |
); |
| 132 |
|
| 133 |
return $response; |
| 134 |
} |
| 135 |
|
| 136 |
/** |
| 137 |
* Validates if the cached data is still acceptable based from |
| 138 |
* the maximum age required |
| 139 |
* |
| 140 |
* @internal |
| 141 |
* @param string $created The time (number of seconds) when the data was cached/stored in DB |
| 142 |
* @param int $maximum_age The maximum age (in seconds) to consider the cached data is still acceptable for consumption |
| 143 |
* @return boolean - True if cached data is within the maximum age required, False otherwise |
| 144 |
*/ |
| 145 |
private function check_data_validity($created, $maximum_age) { |
| 146 |
return (time() - (int) $created) <= $maximum_age; |
| 147 |
} |
| 148 |
|
| 149 |
/** |
| 150 |
* Cache the response received from the remote website |
| 151 |
* |
| 152 |
* @internal |
| 153 |
* @param array $response The response array that contains the result of the command that was sent to the remote website |
| 154 |
* @param string $meta_key A unique string that serves as an identifier for the cached data |
| 155 |
* @return boolean - True if data was successfully cached, False otherwise |
| 156 |
*/ |
| 157 |
private function cache_response($response, $meta_key) { |
| 158 |
if (!empty($response)) { |
| 159 |
// Check to see if we have an existing meta for data caching. If so, update the |
| 160 |
// cached data with the current response, otherwise we'll create a new entry in DB. |
| 161 |
$check = $this->rc->site_meta->get_site_meta($this->site_id, $meta_key, true); |
| 162 |
|
| 163 |
if (!empty($check)) { |
| 164 |
$result = $this->rc->site_meta->update_site_meta($this->site_id, $meta_key, $response); |
| 165 |
} else { |
| 166 |
$result = $this->rc->site_meta->add_site_meta($this->site_id, $meta_key, $response); |
| 167 |
} |
| 168 |
|
| 169 |
if (false !== $result) return true; |
| 170 |
} |
| 171 |
|
| 172 |
return false; |
| 173 |
} |
| 174 |
|
| 175 |
/** |
| 176 |
* Pulls the cached/stored data either from the in-memory data loaded |
| 177 |
* by the UpdraftCentral_User or from DB |
| 178 |
* |
| 179 |
* @internal |
| 180 |
* @param string $meta_key A unique string that serves as an identifier for the cached data |
| 181 |
* @param int $maximum_age The maximum age (in seconds) to consider the cached data is still acceptable for consumption |
| 182 |
* @return mixed - Returns the stored data if successful, False otherwise |
| 183 |
*/ |
| 184 |
private function get_cached_data($meta_key, $maximum_age) { |
| 185 |
|
| 186 |
$maximum_age = empty($maximum_age) ? false : (int) $maximum_age; |
| 187 |
if ($maximum_age) { |
| 188 |
// Check in-memory data first (loaded under UpdraftCentral_User->load_user_sites()) before checking the DB |
| 189 |
if (!empty($this->user->sites_meta[$this->site_id]) && isset($this->user->sites_meta[$this->site_id][$meta_key])) { |
| 190 |
$loaded_data = $this->user->sites_meta[$this->site_id][$meta_key]; |
| 191 |
if ($this->check_data_validity($loaded_data->created, $maximum_age)) { |
| 192 |
return $loaded_data->value; |
| 193 |
} |
| 194 |
} |
| 195 |
|
| 196 |
// Anything can happen from the time the sites meta were loaded during UDC page load. |
| 197 |
// Due to ajax requests it can be populated along the way, thus, if the above in-memory check |
| 198 |
// fails we'll proceed in checking the DB. |
| 199 |
$stored_data = $this->rc->site_meta->updraftcentral_get_site_metadata(null, $this->site_id, $meta_key, true, $maximum_age); |
| 200 |
if (!empty($stored_data)) { |
| 201 |
return $stored_data; |
| 202 |
} |
| 203 |
} |
| 204 |
|
| 205 |
return false; |
| 206 |
} |
| 207 |
|
| 208 |
/** |
| 209 |
* Cache response whenever applicable |
| 210 |
* |
| 211 |
* @internal |
| 212 |
* @param string $command The command to execute |
| 213 |
* @param array $response The response array that contains the result of the command that was sent to the remote website |
| 214 |
* @param string $meta_key A unique string that serves as an identifier for the cached data |
| 215 |
* @param boolean $force_save Optional. A flag to indicate whether we need to force the saving of the response from the remote website |
| 216 |
* @return array - The original response array |
| 217 |
*/ |
| 218 |
private function maybe_cache_response($command, $response, $meta_key, $force_save = false) { |
| 219 |
// We're only saving the response to DB if $reply is not an instance |
| 220 |
// of WP_Error class and command is currently not empty. |
| 221 |
$reply = isset($response['reply']) ? $response['reply'] : $response; |
| 222 |
|
| 223 |
if (!is_wp_error($reply) && !empty($command)) { |
| 224 |
$remote_response = isset($reply['response']) ? $reply['response'] : null; |
| 225 |
|
| 226 |
// Caching is only applicable to non-preencrypted data. |
| 227 |
if (!$this->is_preencrypted && !empty($remote_response) && 'rpcerror' !== $remote_response) { |
| 228 |
|
| 229 |
if ($force_save) { |
| 230 |
// If $force_save is true then we're forced to save the response to DB. Most likely, this is |
| 231 |
// set(required) from a CRON process where the results are force to be saved into DB. |
| 232 |
$this->cache_response($response, $meta_key); |
| 233 |
} else { |
| 234 |
// Here, we're only storing/caching the response when needed, as not all commands |
| 235 |
// requires caching. Add an 'updraftcentral_cache_commands' filter the UDC module if you wish to cache |
| 236 |
// any specific commands. |
| 237 |
$commands = apply_filters('updraftcentral_cache_commands', array()); |
| 238 |
if (in_array($command, $commands) && !empty($meta_key)) { |
| 239 |
$this->cache_response($response, $meta_key); |
| 240 |
} |
| 241 |
} |
| 242 |
|
| 243 |
} |
| 244 |
} |
| 245 |
|
| 246 |
return $response; |
| 247 |
} |
| 248 |
|
| 249 |
/** |
| 250 |
* Do a post check of the result/response of the currently executed command |
| 251 |
* |
| 252 |
* @param array $result The result of the command that was sent to the remote website |
| 253 |
* @return array - The request's response |
| 254 |
*/ |
| 255 |
private function response_post_check($result) { |
| 256 |
|
| 257 |
$caught_output = $result['caught_output']; |
| 258 |
$reply = $result['reply']; |
| 259 |
$response = $this->response; |
| 260 |
|
| 261 |
// Pass on PHP events from the remote side |
| 262 |
if (!empty($response['data']['php_events'])) $response['php_events'] = $response['data']['php_events']; |
| 263 |
if (!empty($caught_output)) $response['mothership_caught_output'] = $caught_output; |
| 264 |
if (is_wp_error($reply)) { |
| 265 |
$response['responsetype'] = 'error'; |
| 266 |
$response['message'] = $reply->get_error_message(); |
| 267 |
$response['code'] = $reply->get_error_code(); |
| 268 |
$response['data'] = $reply->get_error_data(); |
| 269 |
} elseif (is_array($reply) && !empty($reply['response']) && 'error' == $reply['response']) { |
| 270 |
$response['responsetype'] = 'error'; |
| 271 |
$response['message'] = empty($reply['message']) ? __('The connection to the remote site returned an error', 'updraftcentral') : $reply['message']; |
| 272 |
$response['data'] = $reply; |
| 273 |
} elseif ((!$this->is_preencrypted && (!is_array($reply) || empty($reply['response']) || (('ping' == $this->command && 'pong' != $reply['response'])) && 'rpcok' != $reply['response'])) || ($this->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))))) { |
| 274 |
// 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. |
| 275 |
$response['responsetype'] = 'error'; |
| 276 |
$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.", 'updraftcentral').' '.__("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'); |
| 277 |
$response['data'] = $reply; |
| 278 |
$response['code'] = 'no_pong'; |
| 279 |
} else { |
| 280 |
$response['responsetype'] = 'ok'; |
| 281 |
$response['message'] = __('The site was connected to, and returned a response', 'updraftcentral'); |
| 282 |
if ($this->is_preencrypted) { |
| 283 |
$response['wrapped_response'] = $decoded_reply; |
| 284 |
} elseif ('siteinfo' == $this->command) { |
| 285 |
$response['rpc_response'] = $this->user->deep_sanitize($reply); |
| 286 |
} else { |
| 287 |
$response['rpc_response'] = $reply; |
| 288 |
} |
| 289 |
} |
| 290 |
|
| 291 |
return $response; |
| 292 |
} |
| 293 |
|
| 294 |
/** |
| 295 |
* Generates a unique key out from the site id, command and data parameters to |
| 296 |
* be used as a meta key when saving the data to the DB. |
| 297 |
* |
| 298 |
* @param string $command The current command to execute |
| 299 |
* @param array $data An array containing the command parameters |
| 300 |
* @return string - The generated key |
| 301 |
*/ |
| 302 |
public function generate_meta_key($command, $data) { |
| 303 |
// The "maximum_age" info was purposely injected to aide in checking |
| 304 |
// the freshness of data. Since this is not part of the original data parameter |
| 305 |
// that is submitted alongside the command therefore, we remove it when generating a meta key. |
| 306 |
|
| 307 |
if (isset($data['commands'])) { |
| 308 |
// For multiplexed command, we run through each registered commands and remove the field |
| 309 |
foreach ($data['commands'] as $key => $value) { |
| 310 |
if (isset($value['maximum_age'])) unset($data['commands'][$key]['maximum_age']); |
| 311 |
} |
| 312 |
|
| 313 |
$data = $data['commands']; |
| 314 |
} else { |
| 315 |
if (isset($data['maximum_age'])) unset($data['maximum_age']); |
| 316 |
} |
| 317 |
|
| 318 |
return $this->user->generate_cache_key($this->site_id, $command, $data); |
| 319 |
} |
| 320 |
|
| 321 |
/** |
| 322 |
* Retrieves a previously stored (cached) response from the remote site for the given command if |
| 323 |
* available and that the maximum age of the data has not been reached or expired. |
| 324 |
* |
| 325 |
* @param string $command The current command to execute |
| 326 |
* @param array $data An array containing the command parameters |
| 327 |
* @return array |
| 328 |
*/ |
| 329 |
private function retrieve_cached_data($command, $data) { |
| 330 |
// Default: 10 minutes (600 seconds) if UPDRAFTCENTRAL_DATA_MAXIMUM_AGE is not defined |
| 331 |
// |
| 332 |
// N.B. The maximum_age should be found attached to the "data" parameter if the developer |
| 333 |
// wishes to have a specific maximum age (freshness of data) for the current command, otherwise |
| 334 |
// the default_maximum_age will be used. |
| 335 |
$default_maximum_age = (defined('UPDRAFTCENTRAL_DATA_MAXIMUM_AGE')) ? UPDRAFTCENTRAL_DATA_MAXIMUM_AGE : 600; |
| 336 |
|
| 337 |
// Generate a key for this command and its underlying data (command parameters) to be use for |
| 338 |
// saving and retrieving the response to/from the DB. |
| 339 |
$key = $this->generate_meta_key($command, $data); |
| 340 |
$maximum_age = isset($data['maximum_age']) ? $data['maximum_age'] : $default_maximum_age; |
| 341 |
|
| 342 |
// Retrieves and return the cached data using the "$key" as "meta_key" field in the "sites_meta" table if available |
| 343 |
// and the maximum_age has not been reached. |
| 344 |
$cached_data = $this->get_cached_data($key, $maximum_age); |
| 345 |
|
| 346 |
$result = array(); |
| 347 |
if (!empty($cached_data)) { |
| 348 |
$result = $this->response_post_check($cached_data); |
| 349 |
} |
| 350 |
|
| 351 |
// The generated key will serve as a reference for saving and retrieving the stored/cached value |
| 352 |
// from the "sites_meta" table in DB. The key is composed of the currently requested or executed command |
| 353 |
// (e.g. 'updates.get_updates') along with its submitted data that serves as parameters to the |
| 354 |
// command (e.g. array('force_refresh' => false)). |
| 355 |
return array( |
| 356 |
'key' => $key, |
| 357 |
'data' => $result |
| 358 |
); |
| 359 |
} |
| 360 |
|
| 361 |
/** |
| 362 |
* Sends command to the remote website and processes the response |
| 363 |
* |
| 364 |
* @param boolean $force_save Optional. A flag to indicate whether we need to force the saving of the response from the remote website |
| 365 |
* @return array - The response array that contains the result of the currently processed command |
| 366 |
*/ |
| 367 |
public function send_message($force_save = false) { |
| 368 |
|
| 369 |
$this->command = isset($this->data['data']['command']) ? (string) $this->data['data']['command'] : ''; |
| 370 |
$is_multiplexed = ('core.execute_commands' === $this->command) ? true : false; |
| 371 |
|
| 372 |
$data = isset($this->data['data']['data']) ? $this->data['data']['data'] : null; |
| 373 |
if ($this->is_preencrypted) $data = $this->data['wrapped_message']; |
| 374 |
|
| 375 |
$cached_data = array(); |
| 376 |
$computed_meta_key = ''; |
| 377 |
|
| 378 |
if (!empty($data)) { |
| 379 |
// Possibly load required objects for this process if not available. |
| 380 |
$this->maybe_load_objects(); |
| 381 |
|
| 382 |
// We're pulling individual cache data for the same sub-command |
| 383 |
// if we've already had a previously cached response. |
| 384 |
if ($is_multiplexed) { |
| 385 |
$computed_keys = array(); |
| 386 |
$result = array(); |
| 387 |
|
| 388 |
// Make sure that we're getting the latest cached data for the command |
| 389 |
// instead of an old result from a multiplexed command's response. |
| 390 |
// |
| 391 |
// N.B. Need to run through all available commands under the multiplexed |
| 392 |
// command executed to get the latest (fresh) data that was previously cached |
| 393 |
// if available. |
| 394 |
foreach ($data['commands'] as $sub_command => $sub_data) { |
| 395 |
$cached = $this->retrieve_cached_data($sub_command, $sub_data); |
| 396 |
|
| 397 |
$computed_keys[$sub_command] = $cached['key']; |
| 398 |
if (!empty($cached['data'])) $result[$sub_command] = $cached['data']; |
| 399 |
} |
| 400 |
|
| 401 |
// Update the reply with the latest cached response whenever applicable. |
| 402 |
if (!empty($result)) { |
| 403 |
$cached_data['data'] = array( |
| 404 |
'reply' => $result |
| 405 |
); |
| 406 |
} |
| 407 |
} else { |
| 408 |
$cached_data = $this->retrieve_cached_data($this->command, $data); |
| 409 |
$computed_meta_key = $cached_data['key']; |
| 410 |
} |
| 411 |
|
| 412 |
// Return any cached data found if not empty. |
| 413 |
if (!empty($cached_data['data'])) { |
| 414 |
// N.B. This is safe since we're only saving (caching) if we don't encounter any error in the caught_output |
| 415 |
// as it won't make any sense if we're saving errors. |
| 416 |
if (isset($cached_data['data']['reply']) && !isset($cached_data['data']['caught_output'])) { |
| 417 |
$command_data = array( |
| 418 |
'caught_output' => '', |
| 419 |
'reply' => $cached_data['data']['reply'] |
| 420 |
); |
| 421 |
|
| 422 |
$cached_data['data'] = $this->response_post_check($command_data); |
| 423 |
} |
| 424 |
|
| 425 |
return $cached_data['data']; |
| 426 |
} |
| 427 |
} |
| 428 |
|
| 429 |
// If we reached this far then that would mean that we currently don't have any cache data |
| 430 |
// associated with the submitted command. Thus, we will proceed in sending the request to the remote website. |
| 431 |
|
| 432 |
// @codingStandardsIgnoreLine |
| 433 |
@ob_start(); |
| 434 |
|
| 435 |
if (!empty($this->site_meta['http_username']->value)) { |
| 436 |
$authentication_method = empty($this->site_meta['http_authentication_method']->value) ? 'basic' : $this->site_meta['http_authentication_method']->value; |
| 437 |
$http_password = empty($this->site_meta['http_password']->value) ? '' : (string) $this->site_meta['http_password']->value; |
| 438 |
|
| 439 |
if ('basic' != $authentication_method && version_compare(PHP_VERSION, '5.4', '<')) { |
| 440 |
$error_code = 'no_digest_before_php54'; |
| 441 |
$reply = new WP_Error($error_code, $this->errors[$error_code], PHP_VERSION); |
| 442 |
} else { |
| 443 |
// Guzzle supports HTTP digest authentication - the WP HTTP API doesn't. |
| 444 |
if (!class_exists('GuzzleHttp\Client')) include_once UD_CENTRAL_DIR.'/vendor/autoload.php'; |
| 445 |
$guzzle_client = new GuzzleHttp\Client(); |
| 446 |
|
| 447 |
if (!method_exists($this->ud_rpc, 'set_http_transport') || !method_exists($this->ud_rpc, 'set_http_credentials')) { |
| 448 |
// That's the probable cause, because we can assume that UC has a bundled UDRPC that's new enough. |
| 449 |
$error_code = 'incompatible_udrpc_php'; |
| 450 |
$reply = new WP_Error($error_code, $this->errors[$error_code]); |
| 451 |
} else { |
| 452 |
$this->ud_rpc->set_http_transport($guzzle_client); |
| 453 |
$this->ud_rpc->set_http_credentials(array('username' => $this->site_meta['http_username']->value, 'password' => $http_password, 'authentication_method' => $authentication_method)); |
| 454 |
} |
| 455 |
} |
| 456 |
} |
| 457 |
|
| 458 |
if ($this->is_preencrypted) { |
| 459 |
// Command is not applicable to this area, so we empty it if we have a pre-encrypted data |
| 460 |
// since subsequent process may want to check the command before proceeding. |
| 461 |
$this->command = ''; |
| 462 |
|
| 463 |
$reply = $this->user->send_message($this->ud_rpc, '__updraftcentral_internal_preencrypted', $data, 30); |
| 464 |
} else { |
| 465 |
$this->ud_rpc->set_key_local($this->site->key_local_private); |
| 466 |
$this->ud_rpc->set_key_remote($this->site->key_remote_public); |
| 467 |
$this->ud_rpc->activate_replay_protection(); |
| 468 |
|
| 469 |
$reply = $this->user->send_message($this->ud_rpc, $this->command, $data, 30); |
| 470 |
} |
| 471 |
|
| 472 |
// @codingStandardsIgnoreStart |
| 473 |
$caught_output = @ob_get_contents(); |
| 474 |
@ob_end_clean(); |
| 475 |
// @codingStandardsIgnoreEnd |
| 476 |
|
| 477 |
// Check if we're currently running a multiplexed command. If so, |
| 478 |
// we're going to save each individual results separately, so that it can be retrieved later |
| 479 |
// when a command is sent individually with the same signature (command name and data parameters). |
| 480 |
if ($is_multiplexed) { |
| 481 |
// N.B. We don't cache the generic multiplexed command ('core.execute_commands') but instead |
| 482 |
// we save each individual commands separately so that we can pull individual request's result |
| 483 |
// that were cached/saved previously. |
| 484 |
$output = array( |
| 485 |
'caught_output' => $caught_output, |
| 486 |
'reply' => $reply |
| 487 |
); |
| 488 |
|
| 489 |
$result = $this->response_post_check($output); |
| 490 |
if ('ok' === $result['responsetype'] && is_array($result['rpc_response'])) { |
| 491 |
// Save generic response signature here, we're going to use this later when |
| 492 |
// caching individual responses for each executed commands by overriding its |
| 493 |
// "data" field with the actual response data for that command before saving it to DB. |
| 494 |
$store_data = $output; |
| 495 |
|
| 496 |
foreach ($result['rpc_response']['data'] as $command => $data) { |
| 497 |
if (isset($computed_keys[$command])) { |
| 498 |
$key = $computed_keys[$command]; |
| 499 |
|
| 500 |
if ('rpcok' == $data['response']) { |
| 501 |
$store_data['reply']['data'] = $data['data']; |
| 502 |
$this->maybe_cache_response($command, $store_data, $key, $force_save); |
| 503 |
} |
| 504 |
} |
| 505 |
} |
| 506 |
} |
| 507 |
|
| 508 |
} else { |
| 509 |
// Make sure that we have a meta_key generated for the new request. |
| 510 |
if (empty($computed_meta_key)) { |
| 511 |
$computed_meta_key = $this->generate_meta_key($this->command, $data); |
| 512 |
} |
| 513 |
|
| 514 |
// Cache response whenever applicable |
| 515 |
$result = $this->response_post_check($this->maybe_cache_response($this->command, array( |
| 516 |
'caught_output' => $caught_output, |
| 517 |
'reply' => $reply |
| 518 |
), $computed_meta_key, $force_save)); |
| 519 |
} |
| 520 |
|
| 521 |
return $result; |
| 522 |
} |
| 523 |
|
| 524 |
/** |
| 525 |
* Validates the submitted data before sending the command to the remote site |
| 526 |
* |
| 527 |
* @return mixed - True on success, an error array containing the error information on failure |
| 528 |
*/ |
| 529 |
public function validate_input() { |
| 530 |
if (!empty($this->data)) { |
| 531 |
|
| 532 |
// Check the sent data |
| 533 |
if ($this->is_preencrypted) { |
| 534 |
if (!isset($this->data['wrapped_message']) || !is_array($this->data['wrapped_message']) || empty($this->data['site_id']) || !is_numeric($this->data['site_id'])) { |
| 535 |
|
| 536 |
return $this->return_error('missing_data'); |
| 537 |
} |
| 538 |
} else { |
| 539 |
if (!isset($this->data['data']) || !is_array($this->data['data']) || empty($this->data['data']['command']) || empty($this->data['site_id']) || !is_numeric($this->data['site_id'])) { |
| 540 |
|
| 541 |
return $this->return_error('missing_data'); |
| 542 |
} |
| 543 |
} |
| 544 |
|
| 545 |
// This is also a security check - whether the specified site belongs to the current user |
| 546 |
if (empty($this->site)) { |
| 547 |
return $this->return_error('nonexistent_site'); |
| 548 |
} |
| 549 |
|
| 550 |
if (empty($this->site->key_local_private)) { |
| 551 |
return $this->return_error('nonexistent_site_key'); |
| 552 |
} |
| 553 |
|
| 554 |
if (!empty($this->site->unlicensed)) { |
| 555 |
return $this->return_error('site_unlicensed'); |
| 556 |
} |
| 557 |
|
| 558 |
if ($this->rc->url_looks_internal($this->site->url) && !$this->rc->url_looks_internal(site_url()) && !apply_filters('updraftcentral_allow_contacting_internal_url_from_server', true, $this->site->url)) { |
| 559 |
return $this->return_error('cannot_contact_localdev'); |
| 560 |
} |
| 561 |
|
| 562 |
return true; |
| 563 |
} else { |
| 564 |
return $this->return_error('missing_data'); |
| 565 |
} |
| 566 |
} |
| 567 |
} |
| 568 |
|
| 569 |
endif; |
| 570 |
|