root = BitsUtil::fs_get_wp_config_path();
}
function get_server() {
if(isset($GLOBALS["BITS_ENVIRONMENT"]) && $GLOBALS["BITS_ENVIRONMENT"] == "TEST") {
return "http://localhost:3000";
}
if(isset($GLOBALS["BITS_ANYBACKUP_SERVER"])) {
return $GLOBALS["BITS_ANYBACKUP_SERVER"];
}
//return "http://localhost:3000";
return "https://anybackup.io";
}
function get_asset_server() {
return $this->get_server();
}
function get_api_key() {
return $this->api_key;
}
/**
* Sets the API key. You must call this before using any api function.
*/
function set_api_key($api_key) {
$this->api_key = $api_key;
}
function create_api_key() {
$site_server = $this->call_api("POST", "site_servers");
if(is_wp_error($site_server)) {
return $site_server;
}
//TODO: error handling
return $site_server["api_key"];
}
/**
* Called when the plugin is activated
*/
function activate() {
$email = get_option("admin_email");
return $this->call_api("POST", "site_servers/activate", array("email" => $email));
}
/**
* Called when the plugin is deactivated
*/
function deactivate() {
return $this->call_api("POST", "site_servers/deactivate", array());
}
/**
* Register an account
**/
function create_account($email, $password) {
return $this->call_api("POST", "accounts", array("email" => $email, "password" => $password));
}
/**
* Login to an existing account
*/
function login_account($email, $password) {
return $this->call_api("POST", "accounts/login", array("email" => $email, "password" => $password));
}
/**
* Update an account - used in upgrading / downgrading
*/
function update_account($plan_id, $token) {
return $this->call_api("POST", "accounts/update", array("plan_id" => $plan_id, "token" => $token));
}
/**
* List all plans available to this site_server.
*/
function get_plans() {
return $this->call_api("GET", "plans");
}
/**
* Create a support ticket.
*/
function create_support_ticket($urgent, $content) {
return $this->call_api("POST", "support_tickets", array("content" => $content, "urgent" => $urgent));
}
/**
* Transfers a site_server to a new site. This allows for migration onto new servers.
*/
function update_site_server($site_id) {
return $this->call_api("POST", "site_servers/update", array("site_id" => $site_id));
}
/**
* Returns a list of sites associated with the api key
*/
function get_sites() {
return $this->call_api("GET", "sites", array());
}
/**
* Gets the next step for this site server.
* It could be a restore, a backup, or neither.
**/
function next_step() {
return $this->call_api("GET", "steps/next", array());
}
/**
* Step summary lets us update progress bar
*/
function step_summary() {
return $this->call_api("GET", "steps/summary");
}
function complete_step($step_id) {
return $this->call_api("POST", "steps/$step_id/complete", array());
}
/**
* $opts can take any arbitrary metadata. This can include things like:
* "wordpress_version" => "4.1"
*
* or any other information that isn't tied to a higher order object.
*/
function create_backup($opts=array()) {
$args = array_merge($this->get_platform_metadata(), $opts);
return $this->call_api("POST", "backups", $args);
}
/**
* Internal, platform specific metadata
*/
function get_platform_metadata() {
global $wpdb;
global $wp_theme_directories;
$name = get_bloginfo("name");
$version = get_bloginfo("version");
$uri = home_url();
$wp_config_php_path = BitsUtil::fs_get_wp_config_path();
$content_path = WP_CONTENT_DIR; # TODO what happens if this is not explicitly set
$plugins_path = WP_PLUGIN_DIR;
$current_theme_path = get_template_directory();
$stylesheet_path = get_stylesheet_directory();
$extra_themes_paths = $wp_theme_directories;
$wp_lang_dir = null;
if(defined("WP_LANG_DIR")) {
$wp_lang_dir = WP_LANG_DIR;
}
//TODO any of these arrays?
$upload_dir = wp_upload_dir();
if(isset($upload_dir['basedir'])) {
$uploads_path = $upload_dir['basedir'];
} else {
$uploads_path = WP_CONTENT_DIR."/uploads";
}
return array(
"plugin_version" => $GLOBALS["BITS_ANYBACKUP_PLUGIN_VERSION"],
"site_name" => $name,
"site_type" => "wordpress",
"site_uri" => $uri,
"wordpress_version" => $version,
"table_prefix" => $wpdb->prefix,
"php_version" => phpversion(),
"php_os" => PHP_OS,
"php_uname_s" => php_uname('s'),
"gzip" => function_exists("gzencode"),
"bzip2" => function_exists("bzcompress"),
"native_json_support" => function_exists("json_encode"),
"paths" => array(
"wp-config.php" => $wp_config_php_path,
"wp-content" => $content_path,
"plugins" => $plugins_path,
"extra_themes" => $extra_themes_paths,
"uploads" => $uploads_path,
"theme" => $current_theme_path,
"stylesheets" => $stylesheet_path,
"lang" => $wp_lang_dir
)
);
}
/**
* Returns a list of all backups for the site.
*
* Each associative array is of the form:
* {
* id: BACKUP_ID,
* state: [UNCOMMITTED|COMMITTED],
* links: ...,
* created_at: ...,
* committed_at: ...
* }
*/
function list_backups() {
return $this->call_api("GET", "backups");
}
/**
* $id is a valid backup $id in an UNCOMMITTED state
*
* $row_contents is of the form:
*
* array(array( "field1" => "value1", "field2" => "value2" ), ...);
*
* $schema_fingerprint is passed in from the current Step on the server
*/
function add_rows_to_backup($id, $row_contents, $schema_fingerprint) {
if(sizeof($row_contents) == 0) {
return array();
}
$rows = $this->build_rows_with_schema($row_contents, $schema_fingerprint);
// calculate row_group
$row_group = array("schema" => $schema_fingerprint, "rows" => $rows);
$row_group_fingerprint_map = $this->map_row_group_fingerprint($row_group);
$row_group_fingerprint = $row_group_fingerprint_map["fingerprint"];
// add row group
$add_row_group_args = array("row_groups" =>[$row_group_fingerprint]);
$result = $this->call_api("POST", "backups/$id/add", $add_row_group_args);
if(is_wp_error($result)) {
return $result;
}
$row_group_exists = $result["row_groups"][0]["status"] == 200;
if(!$row_group_exists) {
// if not exists:
$row_fingerprints = array_map(array($this, "fingerprint_for_row"), $rows);
$row_show_responses = $this->get_rows($row_fingerprints);
if(is_wp_error($row_show_responses)) {
return $row_show_responses;
}
// find missing rows
$missing_rows = $this->select_404_results($row_show_responses, $rows);
if(count($missing_rows) > 0) {
$result = $this->create_rows($missing_rows);
if(is_wp_error($result)) {
return $result;
}
}
// create row_group
$new_row_groups = $this->create_row_groups([$row_group]);
if(is_wp_error($new_row_groups)) {
return $new_row_groups;
}
// add row group
$result = $this->call_api("POST", "backups/$id/add", $add_row_group_args);
if(is_wp_error($result)) {
return $result;
}
}
return $rows;
}
function build_rows_with_schema($row_contents, $schema_fingerprint) {
$rows_with_content_and_schema = array();
foreach($row_contents as $row) {
array_push( $rows_with_content_and_schema, array(
"content" => $row,
"schema" => $schema_fingerprint
));
}
return $rows_with_content_and_schema;
}
/**
* $id is a valid backup $id in an UNCOMMITTED state
*
* $schemas is of the form:
*
* array("CREATE TABLE ...", "CREATE TABLE ...");
*/
function add_schemas_to_backup($id, $schemas) {
//$GLOBALS["BITS_DEBUG"]=true;
$schema_fingerprints = array_map(array($this, "schema_fingerprint"), $schemas);
$result = $this->call_api("POST", "backups/$id/add", array("schemas" => $schema_fingerprints));
if(is_wp_error($result)) {
return $result;
}
$schemas_to_create = $this->select_404_results($result["schemas"], $schemas);
if(sizeof($schemas_to_create) == 0) {
return $result;
}
$this->create_schemas($schemas_to_create);
//$GLOBALS["BITS_DEBUG"]=false;
return $this->call_api("POST", "backups/$id/add", array("schemas" => $schema_fingerprints));
}
/**
* Used internally
*/
function schema_fingerprint($schema) {
return sha1($this->schema_fingerprint_replace_autoincrement($schema));
}
/**
* Used internally
*/
function schema_fingerprint_replace_autoincrement($schema) {
return preg_replace('/ AUTO_INCREMENT=\d+/i', "", $schema);
}
/**
* Used internally
*/
function select_404_results($results, $original) {
$selected = array();
for($i = 0; $i < sizeof($results); $i += 1) {
$item = $results[$i];
if($item['status'] == 404) {
array_push($selected, $original[$i]);
}
}
return $selected;
}
/**
*
* Changes an UNCOMMITTED backup to a COMMITTED backup.
*
* COMMITTED backups cannot add additional data
*
*/
function commit_backup($id) {
return $this->call_api("POST", "backups/$id/commit");
}
/**
*
* Cancels a running backup.
*
*/
function cancel_backup($id) {
return $this->call_api("POST", "backups/$id/cancel");
}
/**
* Returns a list of backups
*
*/
function get_backups() {
return $this->call_api("GET", "backups");
}
/**
*
* Returns a backup of the form:
*
* array(
* "id" => $backup_id,
* ...
* )
*
* See API documentation for more details
*/
function get_backup($id) {
if($id == null) {
return new WP_Error('invalid api usage', "Need an id when calling get_backup");
}
return $this->call_api("GET", "backups/$id");
}
/**
* Used internally
*/
function get_schemas($fingerprints) {
$inline = implode(',', $fingerprints);
return $this->call_api("GET", "schemas/$inline");
}
/**
* $schemas is of the form
* array("CREATE TABLE ...", ...);
*
* Calling add_schemas_to_backup will create any missing schemas.
*/
function create_schemas($schemas) {
$objs = array_map(array($this, "map_schema_fingerprint"), $schemas);
return $this->call_api("POST", "schemas", array("schemas" => $objs));
}
/**
* Used internally
*/
function map_schema_fingerprint($sql) {
$sha1 = $this->schema_fingerprint($sql);
return array("fingerprint" => $sha1, "sql" => $sql);
}
/**
* Used internally
*/
function get_rows($fingerprints) {
return $this->call_api("POST", "rows/lookup", array("fingerprints" => $fingerprints));
}
/**
*
* $rows is of the form:
*
* array(array( "field1" => "value1", "field2" => "value2" ), ...);
*
* Calling add_rows_to_backup will create any missing rows.
*/
function create_rows($rows) {
$objs = array_map(array($this, "map_row_fingerprint"), $rows);
return $this->call_api("POST", "rows", array("rows" => $objs));
}
/**
* Used internally
*/
function map_row_fingerprint($row) {
$sha1 = $this->fingerprint_for_row($row);
$content = $this->json($row['content']);
return array("fingerprint" => $sha1, "content" => $content, "schema" => $row["schema"]);
}
/**
* Used internally
*/
function map_row_group_fingerprint($row_group) {
$sha1 = $this->fingerprint_for_row_group($row_group);
$row_fingerprints = array_map(array($this, "fingerprint_for_row"), $row_group["rows"]);
return array("fingerprint" => $sha1, "rows" => $row_fingerprints, "schema" => $row_group["schema"]);
}
/**
* Used internally
*/
function get_row_groups($fingerprints) {
$inline = implode(',', $fingerprints);
return $this->call_api("GET", "row_groups/$inline");
}
/**
* Used internally
*
* $row_groups is a list of objects in the form:
* "schema" => "CREATE TABLE ...",
* "rows" => [full_row1, full_row2]
*/
function create_row_groups($row_groups) {
$objs = array_map(array($this, "map_row_group_fingerprint"), $row_groups);
return $this->call_api("POST", "row_groups", array("row_groups" => $objs));
}
/**
* Used internally
*/
function fingerprint_for_row($row) {
ksort($row['content']);
return sha1($this->json($row));
}
function json($obj) {
if($obj == null) {
return "{}";
}
if(function_exists("json_encode")) {
return json_encode($obj);
}
$json = new Services_JSON();
$data = $json->encode($obj);
return $data;
}
function json_decode($response) {
if(function_exists("json_decode")) {
return json_decode($response);
}
$json = new Services_JSON();
return $json->decode($response);
}
/**
* Used internally
*/
function fingerprint_for_row_group($row_group) {
ksort($row_group['rows']);
return sha1($this->json($row_group));
}
/**
*
* Will lazily upload a set of files. Files are only uploaded if
* they haven't been uploaded in the past.
*
* Ex:
*
* upload_files(["/tmp/myfile", "/tmp"]);
*
* Result:
*
* array(
* array(
* "/tmp/myfile" => "https://..." # location of file
* ),
* array(
* "/tmp" => null # Will not upload directories or symlinks
* )
* )
*/
function upload_files($files) {
// grab sha1s of to_upload and see if they exist
// upload non-existant
$file_sha1s = array_map("sha1_file", $files);
$blobs = $this->get_blobs($file_sha1s);
$result = array();
for($i = 0; $i < count($files); $i+=1) {
$file = $files[$i];
$blob = $blobs[$i];
$upload_path = $this->upload_file($file, $blob);
if(is_wp_error($upload_path)) {
return $upload_path;
}
$result[$file] = $upload_path;
}
return $result;
}
function upload_file($file, $blob) {
if(!is_file($file)) {
return $this->get_file_type($file);
}
if($blob['status'] == 200) {
return $blob['url'];
}
if($blob['status'] != 404) {
var_dump($blob);
# TODO: Server problem! Handle this edge case (alert us?)
return "ERROR";
}
if(!is_readable($file)) {
return new WP_Error("cannot-read-file", "file $file is not readable. Cannot backup");
}
if(filesize($file) <= 1024*1024) {
$uploaded = $this->upload_small_file($file, $blob);
} else {
$initialize = $this->multipart_upload_initialize($blob);
if(is_wp_error($initialize)) {
return $initialize;
}
$parts = $this->multipart_upload_parts($file, $blob);
if(is_wp_error($parts)) {
return $parts;
}
$uploaded = $this->multipart_upload_complete($blob);
}
if(is_wp_error($uploaded)) {
return $uploaded;
}
#TODO: edge case: upload failure
return $uploaded['url'];
}
function upload_small_file($file, $blob) {
$fingerprint = $blob["fingerprint"];
$size = filesize($file);
$file_handle = fopen($file, "rb");
if($file_handle == false) {
return new WP_Error("cannot-open-file", $file);
}
$bytes = fread($file_handle, $size);
$sha1 = sha1($bytes);
fclose($file_handle);
$headers = array(
'Content-Type' => 'application/octet-stream',
'Content-Length' => $size
);
$result = $this->call_api("POST", "blobs/".$fingerprint."/upload_small", $bytes, $headers);
return $result;
}
function multipart_upload_initialize($blob) {
$fingerprint = $blob["fingerprint"];
echo "==Initializing multipart upload for:\n";
var_dump($blob);
return $this->call_api("POST", "blobs/".$fingerprint."/start");
}
function multipart_upload_parts($file, $blob) {
$size = filesize($file);
$chunk_size = 1024*1024*5; # in bytes, 5 MB, least allowed
$max = intval($size/$chunk_size) + (($size%$chunk_size) > 0 ? 1 : 0);
$file_handle = fopen($file, "rb");
echo "==Uploading parts\n";
var_dump($blob);
for($i = 0; $i < $max; $i += 1) {
echo "== $i\n";
$upload = $this->multipart_upload_part($file_handle, $blob, $i, $chunk_size);
if(is_wp_error($upload)) {
fclose($file_handle);
echo "== WPERROR\n";
return $upload;
}
echo "== $i complete\n";
}
fclose($file_handle);
}
function multipart_upload_part($file_handle, $blob, $part_number, $size) {
$fingerprint = $blob["fingerprint"];
$bytes = fread($file_handle, $size);
$headers = array(
'Content-Type' => 'application/octet-stream',
'Content-Length' => strlen($bytes)
);
return $this->call_api("POST", "blobs/".$fingerprint."/parts/".($part_number+1), $bytes, $headers);
}
function multipart_upload_complete($blob) {
$fingerprint = $blob["fingerprint"];
echo "==Completing\n";
return $this->call_api("POST", "blobs/".$fingerprint."/complete");
}
function get_blobs($fingerprints) {
return $this->call_api("POST", "blobs/lookup", array("fingerprints" => $fingerprints));
}
/**
* Add files to an UNCOMMITTED backup.
*
* $files takes the form:
* array(
* array(
* "/path/to/file" => "http://uploaded.file.url"
* )
* )
*
* returns:
*
* array(
* array(
* "/path/to/file" => array(
* "status" => "200",
* "type" => "file",
* ... other information - see main api docs ..
* )
* )
* )
*
**/
function add_files_to_backup($id, $files) {
//$GLOBALS['BITS_DEBUG']=true;
// find meta info on each file (fingerprint, type, etc).
$metadatas = $this->metadata_for_files($files);
// call POST /backup/add to see if it exists
$fingerprints = array_map(array($this, "fingerprint_for_file"), $metadatas);
$add_files_args = array("files" => $fingerprints);
$added_file_responses = $this->call_api("POST", "backups/$id/add", $add_files_args);
if(is_wp_error($added_file_responses)) {
return $added_file_responses;
}
// call POST /files on any missing elements
$missing_files = $this->select_404_results($added_file_responses['files'], $metadatas);
if(count($missing_files) == 0) {
//$GLOBALS['BITS_DEBUG']=false;
return $added_file_responses;
}
$this->create_files($missing_files);
// call POST /backup/add again for missing files
$result = $this->call_api("POST", "backups/$id/add", $add_files_args);
//$GLOBALS['BITS_DEBUG']=false;
return $result;
}
private function metadata_for_files($files) {
$result = array();
foreach($files as $key => $value) {
$meta = $this->metadata_for_file($key, $value);
array_push($result, $meta);
}
return $result;
}
private function get_file_type($path) {
if(is_dir($path)) {
return 'directory';
} elseif(is_file($path)) {
return 'file';
} else {
// We will probably not get here, unless someone backups a block device or something really weird
return "UNKNOWN";
}
}
function join_paths($original, $next) {
if($next[0] == "/") {
return $next;
}
$paths = array($original, $next);
return preg_replace('#/+#','/',join('/', $paths));
}
function get_file_metadata($path) {
$ctime = filectime($path);
$mtime = filemtime($path);
if(function_exists("posix_getgrgid")) {
$group = posix_getgrgid(filegroup($path))["name"];
$user = posix_getpwuid(fileowner($path))["name"];
} else {
$group = "windows";
$user = "windows";
}
$mode = fileperms($path);
$type = $this->get_file_type($path);
if($type == "file") {
$sha1 = sha1_file($path);
} else {
$sha1 = null;
}
return array(
"ctime" => $ctime,
"type" => $type,
"group" => $group,
"user" => $user,
"mtime" => $mtime,
"path" => $path,
"mode" => $mode,
"content_fingerprint" => $sha1
);
}
private function metadata_for_file($path, $url) {
$result = $this->get_file_metadata($path);
$type = $result['type'];
if($type == "directory") {
$result["fingerprint"] = sha1($this->json($result));
} else if($type == "file") {
//TODO: investigate why this has /anybackup/
//WARNING: The order here matters
$result["size"]=filesize($path);
$result["fingerprint"] = sha1($this->json($result));
$result["url"]=$url;
}
$fingerprint = $result["fingerprint"];
//echo "'$fingerprint' - ".$result['path']." - url($url) type($type)
";
return $result;
}
private function fingerprint_for_file($file_meta) {
return $file_meta["fingerprint"];
}
function log($log_level, $message, $options=array()) {
if($log_level == 'error') {
$message = "[WP] ".$message;
}
$params = array_merge(array("message" => $message, "log_level" => $log_level), $options);
return $this->call_api("POST", "logs", $params);
}
function create_files($files) {
$files_args = array( "files" => $files);
return $this->call_api("POST", "files", $files_args);
}
// Restore
function create_restore($backup_id) {
$opts = array("backup_id" => $backup_id);
$args = array_merge($this->get_platform_metadata(), $opts);
return $this->call_api("POST", "restores", $args);
}
function complete_restore($restore_id) {
return $this->call_api("POST", "restores/$restore_id/complete");
}
function cancel_restore($restore_id) {
return $this->call_api("POST", "restores/$restore_id/cancel");
}
function get_restore($id) {
$path = "restores/$id";
return $this->call_api("GET", $path);
}
function get_restore_schemas($id) {
$path = "restores/$id/schemas";
return $this->call_api("GET", $path);
}
function get_restore_rows($id, $schema_fingerprint, $page) {
$path = "restores/$id/rows";
$args = array(
"schema" => $schema_fingerprint,
"page" => $page
);
return $this->call_api("GET", $path, $args);
}
function swap_schema_operations($id) {
$sqls = BitsUtil::get_all_table_sqls();
$path = "restores/$id/schema_operations";
$args = array("schemas" => $sqls);
$result = $this->call_api("POST", $path, $args);
if(is_wp_error($result)) {
return $result;
}
return $result['operations'];
}
function restore_file_operations($id, $files) {
$path = "restores/$id/file_operations";
$file_details = array_map(array($this, "get_file_details"), $files);
$result = $this->call_api("POST", $path, array("files" => $file_details));
if(is_wp_error($result)) {
return $result;
}
return $result['operations'];
}
function restore_missing_files($id, $files) {
$path = "restores/$id/missing_files";
$file_details = array_map(array($this, "get_file_details"), $files);
$result = $this->call_api("POST", $path, array("files" => $file_details));
return $result;
}
private function get_file_details($file) {
if(!file_exists($file)) {
return new WP_Error("file-doesnt-exist", "Could not get details for '$file'");
}
$sha1 = sha1_file($file);
$url = $this->get_content_url_for_file($sha1);
$content_fingerprint = array("content_fingerprint" => $sha1);
return array_merge($this->metadata_for_file($file, $url), $content_fingerprint);
}
private function get_content_url_for_file($sha1) {
return "https://s3.amazonaws.com/any-backup/".$sha1;
}
function raw_call_http_retry($times, $method, $url, $data, $headers) {
$retries = 1;
$response = $this->raw_call_http($method, $url, $data, $headers);
while(is_wp_error($response) && $retries < $times) {
sleep(pow(2,$retries+1));
$response = $this->raw_call_http($method, $url, $data, $headers);
$retries += 1;
}
return $response;
}
function raw_call_http($method, $url, $data, $headers) {
$request = null;
if(isset($GLOBALS["BITS_DEBUG"]) && $GLOBALS["BITS_DEBUG"]) {
echo("
");
echo "PATH: $method $url\n";
if($headers) {
//echo "HEADERS:";
//var_dump($headers);
}
echo("");
if($data) {
echo "";
}
}
if(!isset($headers["Content-Type"]) || $headers["Content-Type"] == null) {
$headers["Content-Type"] = "application/json";
$headers["Accept"] = "application/json";
if($method == "POST") {
$data = $this->json($data);
}
}
$timeout = 60*5;
switch ($method)
{
case "POST":
$request = wp_remote_post($url, array( "body" => $data, "headers" => $headers, "timeout" => $timeout ));
break;
case "GET":
if ($data)
$url_with_params = sprintf("%s?%s", $url, http_build_query($data));
else
$url_with_params = $url;
$request = wp_remote_get($url_with_params, array("headers" => $headers, "timeout" => $timeout ));
}
if(is_wp_error($request)) {
if(substr($url, -4) == "logs") {
//ignore, error when logging
} else {
$this->log("error", "Error when calling $method $url : " .$request->get_error_message());
}
return $request;
}
$response = wp_remote_retrieve_body($request);
if($request["response"]["code"] != 200) {
if(isset($GLOBALS["BITS_DEBUG"]) && $GLOBALS["BITS_DEBUG"]) {
echo("");
echo "Error communicating\n";
var_dump($request);
echo("\n\nBody:\n");
var_dump($response);
echo("");
}
$status = $request["response"]["code"];
return new WP_Error("error-communicating-with-anybackup", "response $status from anybackup server call to $method $url");
}
if(isset($GLOBALS["BITS_DEBUG"]) && $GLOBALS["BITS_DEBUG"]) {
echo "";
// TODO: deal with error codes
}
return $response;
}
/**
* Used internally
* $method is either 'POST' or 'GET'
* $data is an array("param" => "value") which will map to index.php?param=value
* or in the case of a POST, will be added to the body.
*/
function call_api($method, $path, $data = array(), $headers = array())
{
if($this->api_key == null && $path != "site_servers") {
trigger_error("Error: API key is not initialized. You must call set_api_key before calling api method($method $path).", E_USER_ERROR);
return null;
}
if($this->api_key != null && !is_wp_error($this->api_key)) {
$headers['Authorization']="Token ".$this->api_key;
}
$url = $this->get_server()."/v".self::API_VERSION."/".$path;
$json = new Services_JSON();
$headers["PROCESS_ID"] = getmypid();
$response = $this->raw_call_http_retry(3, $method, $url, $data, $headers);
if(is_wp_error($response)) {
return $response;
}
$json_obj = $this->json_decode($response);
if($json_obj == null) {
return new WP_Error('bad json response from anybackup server', $response);
}
$return_value = $this->recurse_translate_object_to_array($json_obj);
if(isset($return_value['status']) &&
$return_value['status'] &&
$return_value["status"] != 200 &&
$return_value["status"] != 302 &&
$return_value["status"] != 403) {
if(isset($return_value['error'])) {
$message = $return_value['error'];
} else {
$message = "unknown-error-in-api";
}
return new WP_Error($message, "Error in API call to $method $path: $response");
}
return $return_value;
}
/**
* Used internally
*/
function recurse_translate_object_to_array($obj) {
if($obj instanceOf stdClass) {
$array = get_object_vars($obj);
foreach($array as $key => $value) {
$array[$key] = $this->recurse_translate_object_to_array($value);
}
return $array;
} elseif (is_array($obj)) {
return array_map(array($this, "recurse_translate_object_to_array"), $obj);
}
return $obj;
}
}
?>