client_id = defined('UPDRAFTPLUS_PCLOUD_CLIENT_ID') ? UPDRAFTPLUS_PCLOUD_CLIENT_ID : 'zrkDNwnlAGj'; $this->callback_url = defined('UPDRAFTPLUS_PCLOUD_CALLBACK_URL') ? UPDRAFTPLUS_PCLOUD_CALLBACK_URL : 'https://auth.updraftplus.com/auth/pcloud'; } /** * Supported features. * * @return Array */ public function get_supported_features() { // These options format is handled via only accessing options via $this->get_options(). return array( 'multi_options', 'config_templates', 'multi_storage', 'conditional_logic', 'manual_authentication', ); } /** * Default options * * @return Array */ public function get_default_options() { return array( 'pclauth' => '', 'pcllocation' => '1', 'folderid' => 0, 'uploadid' => 0, 'folder' => '' ); } /** * Check whether options have been set up by the user, or not * * @param Array $opts - the potential options. * * @return Boolean */ public function options_exist($opts) { if (is_array($opts) && !empty($opts['pclauth'])) { return true; } return false; } /** * Acts as a WordPress options filter * * @param Array $pcloud - An array of pCloud options. * * @return Array - the returned array can either be the set of updated pCloud settings or a WordPress error array */ public function options_filter($pcloud) { $opts = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format('pcloud'); if (is_wp_error($opts)) { if ('recursion' !== $opts->get_error_code()) { $msg = "(".$opts->get_error_code()."): ".$opts->get_error_message(); $this->log($msg); error_log("UpdraftPlus: pCloud: $msg"); } // The saved options had a problem; so, return the new ones return $pcloud; } if (!is_array($pcloud)) return $opts; // Handle only version field being saved in the plugin free version. if (!isset($opts['settings'])) return $pcloud; // Remove instances that no longer exist foreach ($opts['settings'] as $instance_id => $storage_options) { if (!isset($pcloud['settings'][$instance_id])) unset($opts['settings'][$instance_id]); } if (empty($pcloud['settings'])) return $opts; foreach ($pcloud['settings'] as $instance_id => $storage_options) { // Now loop over the new options, and replace old options with them foreach ($storage_options as $key => $value) { // Reset "folderid" if folder name is changed if ('folder' == $key && isset($opts['settings'][$instance_id][$key]) && $opts['settings'][$instance_id][$key] != $value) $opts['settings'][$instance_id]['folderid'] = 0; if (null === $value) { unset($opts['settings'][$instance_id][$key]); } else { if (!isset($opts['settings'][$instance_id])) $opts['settings'][$instance_id] = array(); $opts['settings'][$instance_id][$key] = $value; } } } return $opts; } /** * Proceed with the backup * * @param Array $backup_array - Array of files to be backed up. * * @return false|void|null */ public function backup($backup_array) { global $updraftplus; try { $pcloud = $this->bootstrap(); $info = $pcloud->account_info(); if (is_wp_error($info)) { $this->log('pCloud ('.$info->get_error_code().'): '.$info->get_error_message(), 'error'); $space_available = 0; } else { $space_available = $info['quota'] - $info['usedquota']; } } catch (Exception $e) { $this->log('Exception ('.get_class($e).') when trying to backup: ' . $e->getMessage() . ' (line: ' . $e->getLine() . ', file: ' . $e->getFile() . ')'); $this->log(sprintf(__('error: %s (see log file for more)', 'updraftplus'), $e->getMessage()), 'error'); return false; } $updraft_dir = $updraftplus->backups_dir_location(); $opts = $this->get_options(); foreach ($backup_array as $file) { $hash = md5($file); $file_success = false; if (!file_exists($updraft_dir . '/' . $file)) { $file_success = true; } $filesize = filesize($updraft_dir . '/' . $file); $microtime = microtime(true); $pcl_upload_id = $this->jobdata_get('upload_pclid_' . $hash, 'None'); if ('None' === $pcl_upload_id) { $upload = $pcloud->create_upload(); if (is_wp_error($upload)) { $this->log('pCloud ('.$upload->get_error_code().'): '.$upload->get_error_message(), 'error'); return false; } $pcl_upload_id = $upload['uploadid']; $this->jobdata_set('upload_pclid_' . $hash, $pcl_upload_id); } else { $pcl_upload_id = intval($pcl_upload_id); } if ('None' !== $this->jobdata_get('upload_id_' . $hash, 'None')) { // Resume. $offset = $this->jobdata_get('upload_offset_' . $hash, 0); if ($offset) { $this->log("This is a resumption: $offset bytes had already been uploaded"); } $offset = intval($offset); } else { $offset = 0; } // We don't actually abort now - there's no harm in letting it try and then fail. if ($space_available < ($filesize - $offset)) { $this->log('File upload expected to fail: file data remaining to upload ($file) size is ' . (($filesize - $offset) / 1024) . ' Kb (overall file size; .' . $filesize . " b), whereas available quota is only $space_available b"); } $ufile = $file; $this->log("Attempt to upload: $file to: $ufile"); $upload_tick = microtime(true); $retries = 0; if (false === $file_success) { while (true) { $prev_offset = $offset; try { $new_offset = $pcloud->chunked_upload($updraft_dir . '/' . $file, $pcl_upload_id, $offset); if (is_wp_error($new_offset)) { throw new Exception($new_offset->get_error_message()); } $offset = $new_offset; if ($prev_offset === $offset) { // Failed, will retry. $retries++; } if (-2 === $offset) { // Success. $file_success = true; break; } $this->jobdata_set('upload_offset_' . $hash, $offset); } catch (Exception $e) { $this->log('chunked upload exception (' . get_class($e) . '): ' . $e->getMessage() . ' (line: ' . $e->getLine() . ', file: ' . $e->getFile() . ')'); if ($upload_tick > 0 && time() - $upload_tick > 800) { UpdraftPlus_Job_Scheduler::reschedule(60); $this->log('Select/poll returned after a long time: scheduling a resumption and terminating for now'); UpdraftPlus_Job_Scheduler::record_still_alive(); $result = $pcloud->save($pcl_upload_id, $updraft_dir . '/' . $file, $opts['folderid']); if (is_wp_error($result)) $this->log('pCloud ('.$result->get_error_code().'): '.$result->get_error_message(), 'error'); die; } $retries++; } if (5 < $retries) { $this->log('chunked upload failed: too many failures.'); $this->log(__('Chunked upload failed', 'updraftplus'), 'error'); break; } } } if ($file_success) { $updraftplus->uploaded_file($file); $microtime_elapsed = microtime(true) - $microtime; $speedps = ($microtime_elapsed > 0) ? $filesize / $microtime_elapsed : 0; $speed = sprintf('%.2d', ($filesize / 1024)) . ' KB in ' . sprintf('%.2d', $microtime_elapsed) . 's (' . sprintf('%.2d', $speedps) . ' KB/s)'; $this->log('File upload success (' . $file . "): $speed"); $this->jobdata_delete('upload_id_' . $hash); $this->jobdata_delete('upload_pclid_' . $hash); $this->jobdata_delete('upload_offset_' . $hash); $result = $pcloud->save($pcl_upload_id, $updraft_dir . '/' . $file, $opts['folderid']); if (is_wp_error($result)) $this->log('pCloud ('.$result->get_error_code().'): '.$result->get_error_message(), 'error'); } } return null; } /** * This method gets a list of files from the remote storage that match the string passed in and returns an array of backups * * @param String $match a substring to require (tested via strpos() !== false). * * @return Array|WP_Error */ public function listfiles($match = 'backup_') { try { $opts = $this->get_options(); if (!$this->options_exist($opts)) return new WP_Error('no_settings', sprintf(__('No %s settings were found', 'updraftplus'), 'pCloud')); $pcloud = $this->bootstrap(); } catch (Exception $e) { $this->log($e->getMessage() . ' (line: ' . $e->getLine() . ', file: ' . $e->getFile() . ')'); $this->log(__('Listing the files failed:', 'updraftplus').' '.$e->getMessage(), 'warning'); return new WP_Error('listfiles', $e->getMessage()); } $results = array(); $backups = $pcloud->list_backups(); if (is_wp_error($backups)) { $this->log('pCloud ('.$backups->get_error_code().'): '.$backups->get_error_message(), 'error'); return $backups; } foreach ($backups as $backup) { $regex = str_replace('/', '\/', $match); if (empty($match) || preg_match('/' . $regex . '/', $backup['path'])) { $results[] = array( 'name' => $backup['name'], 'size' => $backup['size'], ); } } return $results; } /** * Delete files from the service using the pCloud API * * @param Array $files - array of filenames to delete. * * @return Boolean|String - either a boolean true or an error code string */ public function delete($files) { if (is_string($files)) { $files = array($files); } try { $pcloud = $this->bootstrap(); } catch (Exception $e) { $this->log($e->getMessage() . ' (line: ' . $e->getLine() . ', file: ' . $e->getFile() . ')'); $this->log(sprintf(__('Failed to access %s when deleting (see log file for more)', 'updraftplus'), 'pCloud'), 'warning'); return 'service_unavailable'; } $any_failures = false; foreach ($files as $file) { $fullpath = '/' . $pcloud->get_backup_dir() . '/' . $file; $this->log("request deletion: $file"); $result = $pcloud->delete($fullpath); if (is_wp_error($result)) { $this->log('pCloud ('.$result->get_error_code().'): '.$result->get_error_message(), 'error'); } else { $file_success = 1; } if (!isset($file_success)) $any_failures = true; } return $any_failures ? 'file_delete_error' : true; } /** * Download method * * @param string $file File to be downloaded. * * @return false */ public function download($file) { global $updraftplus; $opts = $this->get_options(); $pclauth = !empty($opts['pclauth']) ? $opts['pclauth'] : ''; if (20 > strlen($pclauth)) { $this->log('You are not authenticated with pCloud'); $this->log(__('You are not authenticated with pCloud', 'updraftplus'), 'error'); return false; } try { $pcloud = $this->bootstrap(); } catch (Exception $e) { $this->log($e->getMessage() . ' (line: ' . $e->getLine() . ', file: ' . $e->getFile() . ')'); $this->log($e->getMessage() . ' (line: ' . $e->getLine() . ', file: ' . $e->getFile() . ')', 'error'); return false; } if (!$pcloud) { return false; } $remote_files = $this->listfiles($file); foreach ($remote_files as $file_info) { if (basename($file_info['name']) === basename($file)) { $fname = basename($file_info['name']); return $updraftplus->chunked_download($fname, $this, $file_info['size'], apply_filters('updraftplus_pcloud_downloads_manually_break_up', false), null, 2 * 1048576); } } $this->log("$file: file not found in listing of remote directory"); return false; } /** * Callback used by chunked downloading API * * @param string $file - the file (basename) to be downloaded. * @param array $headers - supplied headers. * @param mixed $data - pass-back from our call to the API (which we don't use). * @param resource $fh - the local file handle. * * @return bool - the data downloaded */ public function chunked_download($file, $headers, $data, $fh) { try { $pcloud = $this->bootstrap(); $needed_file = $pcloud->get_file_info(basename($file)); if (is_wp_error($needed_file)) { $this->log('pCloud ('.$needed_file->get_error_code().'): '.$needed_file->get_error_message(), 'error'); return false; } } catch (Exception $e) { $this->log($e->getMessage() . ' (line: ' . $e->getLine() . ', file: ' . $e->getFile() . ')'); $this->log(sprintf(__('Failed to access %s when deleting (see log file for more)', 'updraftplus'), 'pCloud'), 'warning'); return false; } if (count($needed_file) < 2 || !isset($needed_file['fileid'])) { $this->log('The requested file is no longer in the pCloud backup folder.'); return false; } $offset = 0; $retries = 0; while (true) { try { $offset = $pcloud->download($needed_file['fileid'], $fh, $headers, $offset); if (is_wp_error($offset)) throw new Exception($offset->get_error_message()); if ($offset >= ($needed_file['size'] - 1)) { fclose($fh); $get = true; break; } } catch (Exception $e) { $this->log($e); $this->log($e->getMessage(), 'error'); $get = false; $retries++; if (40 < $retries) { break; } // Sometimes the server can not deliver the file content for some many reasons, we can wait a little and try again. sleep(2); } } return $get; } /** * Get the pre configuration template * * @return String - the template */ public function get_pre_configuration_template() { ?>
{{{storage_long_description}}}
{{already_authenticated_label}} {{deauthentication_link_text}}
{{/if}} {{#if ownername_sentence}}