0) $this->chunk_size = max(UPDRAFTPLUS_UPLOAD_CHUNKSIZE, 5000000); } /** * Upload a single file * * @param String $file - the basename of the file to upload * @param String $local_path - the full path of the file * * @return Boolean - success status. Failures can also be thrown as exceptions. */ public function do_upload($file, $local_path) { global $updraftplus; $opts = $this->options; $storage = $this->get_storage(); $instance_id = $this->get_instance_id(); if (is_wp_error($storage)) throw new Exception($storage->get_error_message()); if (!is_object($storage)) throw new Exception("Backblaze service error (got a ".gettype($storage).")"); $backup_path = empty($opts['backup_path']) ? '' : trailingslashit($opts['backup_path']); $remote_path = $backup_path.$file; $file_hash = md5($file); $this->_uploaded_size = $this->jobdata_get('total_bytes_sent_'.$file_hash, 0); if (!file_exists($local_path) || !is_readable($local_path)) throw new Exception('Could not read file: '.$local_path); // Backblaze bucket names are case insensitive $bucket_name = strtolower($opts['bucket_name']); // Create bucket if bucket doesn't exists if (!isset($this->is_upload_bucket_exist) && $this->is_valid_bucket_name($bucket_name)) { $buckets = $this->get_bucket_names_array(); if (!in_array($bucket_name, $buckets)) { $new_bucket_created = $storage->createPrivateBucket($bucket_name); if ($new_bucket_created) { $this->is_upload_bucket_exist = true; $this->log("bucket was not found, but a new private bucket has now been created: ".$bucket_name); } else { $this->log("bucket was not found, and creation of a new private bucket failed: ".$bucket_name); } } else { $this->is_upload_bucket_exist = true; } } if (1 === ($ret = $updraftplus->chunked_upload($this, $file, "backblaze://".trailingslashit($bucket_name).$backup_path.$file, 'Backblaze', $this->chunk_size, $this->_uploaded_size))) { if (!empty($opts['object_lock_duration'])) { $buckets = $this->get_bucket_names_array(); if (!in_array($bucket_name, $buckets)) $this->buckets[$instance_id] = $storage->listBuckets(); // Check if Object Lock is enabled for the bucket foreach ($this->buckets[$instance_id] as $bucket) { if ($bucket->getName() !== $bucket_name || $bucket->isObjectLockEnabled()) continue; // Update bucket to enable Object Lock $response = $storage->setObjectLockToBucket($bucket->getId()); if (!isset($response['fileLockConfiguration']['value']['isFileLockEnabled']) || !$response['fileLockConfiguration']['value']['isFileLockEnabled']) { $this->log(sprintf(__('Error: unable to set object lock for bucket: %s', 'updraftplus'), $bucket->getName()), 'error'); $this->log("Unable to set object lock for bucket: ".$bucket->getName()); } else { $this->log("Object lock is enabled for bucket: ".$bucket->getName()); } break; } } $result = $storage->upload(array( 'BucketName' => $opts['bucket_name'], 'FileName' => $remote_path, 'Body' => file_get_contents($local_path), )); if (is_object($result) && is_callable(array($result, 'getSize')) && $result->getSize() > 1) { $ret = true; $this->maybe_set_object_lock($opts, $result); } else { $ret = false; $this->log("all-in-one upload fail: ".serialize($result)); } } return $ret; } /** * N.B. If we ever use varying-size chunks, we must be careful as to what we do with $chunk_index * * @param String $file - Basename for the file being uploaded * @param Resource|String $fp - Data to send, or a file handle to read upload data from * @param Integer $chunk_index - Index of chunked upload * @param Integer $upload_size - Size of the upload, in bytes (this and the next are only used if a resource was given for $fp) * @param Integer $upload_start - How many bytes into the file the upload process has got * @param Integer $upload_end - How many bytes into the file we will be after this chunk is uploaded (not currently used) * @param Integer $total_file_size - Total file size (not currently used) * * @return Boolean|WP_Error */ public function chunked_upload($file, $fp, $chunk_index, $upload_size = 0, $upload_start = 0, $upload_end = 0, $total_file_size = 0) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Filter use // Already done? This is not checked if we are sent data directly, as that implies forcing. if (is_resource($fp) && $upload_start < $this->_uploaded_size) return 1; $storage = $this->get_storage(); if (is_wp_error($storage)) return $storage; if (!is_object($storage)) return new WP_Error('no_backblaze_service', "Backblaze service error (got a ".gettype($storage).")"); $file_hash = md5($file); $upload_state = $this->jobdata_get('upload_state_'.$file_hash, array()); // An upload URL is valid for 24 hours. But, we'll only use them for 1 hour, in case something else happens to invalidate it (we don't want to wait a whole day before getting a new one). if (!empty($upload_state['saved_at']) && $upload_state['saved_at'] < time() - 3600) $upload_state = array(); $large_file_id = empty($upload_state['large_file_id']) ? false : $upload_state['large_file_id']; $upload_url = empty($upload_state['upload_url']) ? false : $upload_state['upload_url']; $auth_token = empty($upload_state['auth_token']) ? false : $upload_state['auth_token']; $need_new_state = ($large_file_id && $upload_url && $auth_token) ? false : true; $opts = $this->options; $backup_path = empty($opts['backup_path']) ? '' : trailingslashit($opts['backup_path']); $remote_path = $backup_path.$file; if (!$large_file_id) { $this->log("initiating multi-part upload"); try { $response = $storage->uploadLargeStart(array( 'FileName' => $remote_path, 'BucketName' => $opts['bucket_name'], )); if (empty($response['fileId'])) { $this->log('Unexpected response to uploadLargeStart: '.serialize($response)); return false; } } catch (Exception $e) { $this->log('Unexpected chunk uploading exception ('.get_class($e).'): '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); return false; } $large_file_id = $response['fileId']; } $this->_large_file_id = $large_file_id; if (!$upload_url || !$auth_token) { try { $this->log("requesting multi-part file upload url (id $large_file_id)"); $response = $storage->uploadLargeUrl(array( 'FileId' => $large_file_id, )); if (empty($response['authorizationToken']) || empty($response['uploadUrl'])) { $this->log('Unexpected response to uploadLargeUrl: '.serialize($response)); return false; } } catch (Exception $e) { $this->log('Unexpected error when getting upload URL ('.get_class($e).'): '.$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); return false; } $auth_token = $response['authorizationToken']; $upload_url = $response['uploadUrl']; } if ($need_new_state) { $this->jobdata_set('upload_state_'.$file_hash, array( 'large_file_id' => $large_file_id, 'upload_url' => $upload_url, 'auth_token' => $auth_token, // N.B. An upload URL is valid for 24 hours 'saved_at' => time() )); } if (is_resource($fp)) { if (false === ($data = fread($fp, $upload_size))) { $this->log(__('Error: unexpected file read fail', 'updraftplus'), 'error'); $this->log("File read fail (fread() returned false)"); return false; } } elseif (is_string($fp)) { $data = $fp; } else { return new WP_Error('backblaze_chunk_data_error', __('Error:', 'updraftplus')." backblaze::chunked_upload() received invalid input"); } $sha1_of_parts = $this->jobdata_get('sha1_of_parts_'.$file_hash, array()); $sha1_of_parts[$chunk_index - 1] = sha1($data); try { $response = $storage->uploadLargePart(array( 'AuthorizationToken' => $auth_token, 'FilePartNo' => $chunk_index, 'UploadUrl' => $upload_url, 'Body' => $data, )); if (!is_array($response) || !isset($response['partNumber'])) { $this->log("Unexpected response to uploadLargePart: ".serialize($response)); return false; } } catch (Exception $e) { if ($e->getCode() >= 500 && $e->getCode() <= 599) { $this->jobdata_set('upload_state_'.$file_hash, array( 'large_file_id' => $large_file_id, 'upload_url' => '', 'auth_token' => '', )); } return new WP_Error('backblaze_chunk_upload_error', __('Error:', 'updraftplus')." {$e->getCode()}, Message: {$e->getMessage()}"); } $this->_sha1_of_parts = $sha1_of_parts; $this->jobdata_set('sha1_of_parts_'.$file_hash, $sha1_of_parts); $this->jobdata_set('total_bytes_sent_'.$file_hash, $upload_end + 1); return true; } /** * Called when all chunks have been uploaded, to allow any required finishing actions to be carried out * * @param String $file - the basename of the file being uploaded * * @return Integer|Boolean - success or failure state of any finishing actions */ public function chunked_upload_finish($file) { $file_hash = md5($file); $opts = $this->options; $storage = $this->get_storage(); // This happens if chunked_upload_finish is called without chunked_upload having been called if (empty($this->_large_file_id)) { $upload_state = $this->jobdata_get('upload_state_'.$file_hash, array()); // An upload URL is valid for 24 hours. But, we'll only use them for 1 hour, in case something else happens to invalidate it (we don't want to wait a whole day before getting a new one). if (!empty($upload_state['saved_at']) && $upload_state['saved_at'] < time() - 3600) $upload_state = array(); $this->_large_file_id = empty($upload_state['large_file_id']) ? false : $upload_state['large_file_id']; $this->_sha1_of_parts = $this->jobdata_get('sha1_of_parts_'.$file_hash, array()); } try { $response = $storage->uploadLargeFinish(array( 'FileId' => $this->_large_file_id, 'FilePartSha1Array' => $this->_sha1_of_parts, )); $this->maybe_set_object_lock($opts, $response); } catch (Exception $e) { global $updraftplus; if (preg_match('/No active upload for: .*/', $e->getMessage())) { $this->log("upload: b2_finish_large_file has already been called ('".$e->getMessage()."')"); return 1; } elseif (preg_match('/Part number (\d+) has not been uploaded/i', $e->getMessage(), $matches)) { $missing_chunk_index = $matches[1]; $this->log("Exception in uploadLargeFinish(); will retry part $missing_chunk_index: {$e->getCode()}, Message: {$e->getMessage()} (line: {$e->getLine()}, file: {$e->getFile()})"); $updraft_dir = $updraftplus->backups_dir_location(); // If more than this are needed, they will happen on the next resumption static $retries = 12; if (false === ($data = file_get_contents($updraft_dir.'/'.$file, false, null, ($missing_chunk_index - 1 ) * $this->chunk_size, $this->chunk_size))) { $retry_part = new WP_Error('file_read_failed', "Could not read: $file"); } elseif ($retries > 0) { $retries--; $retry_part = $this->chunked_upload($file, $data, $missing_chunk_index); // Missing part was uploaded; try the whole again if (true === $retry_part) { return $this->chunked_upload_finish($file); } // N.B. chunked_upload() does its own logging when returning false } if (is_wp_error($retry_part)) { $this->log("Failed ".$retry_part->get_error_code().": ".$retry_part->get_error_message()); } } else { $this->log("Exception in uploadLargeFinish(): {$e->getCode()}, Message: {$e->getMessage()} (line: {$e->getLine()}, file: {$e->getFile()})"); } return false; } global $updraftplus; $this->log('upload: success (b2_finish_large_file called successfully; chunks='.count($this->_sha1_of_parts).', file ID returned='.$response->getId().', size='.$response->getSize().')'); // Clean-up $this->jobdata_delete('upload_state_'.$file_hash); $this->jobdata_delete('sha1_of_parts_'.$file_hash); // (int)1 means 'we already logged', as opposed to (boolean)true which does not return 1; } /** * Perform a download of the requested file * * @param String $file - the file (basename) to download * @param String $fullpath - the full path to download it too * @param Integer $start_offset - byte marker to begin at (starting from 0) * * @return Boolean|Integer - success/failure, or a byte counter of how much has been downloaded. Exceptions can also be thrown for errors. */ public function do_download($file, $fullpath) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Filter use global $updraftplus; $remote_files = $this->do_listfiles($file); if (is_wp_error($remote_files)) { throw new Exception('Download error ('.$remote_files->get_error_code().'): '.$remote_files->get_error_message()); } foreach ($remote_files as $file_info) { if ($file_info['name'] == $file) { return $updraftplus->chunked_download($file, $this, $file_info['size'], true, null, 2*1048576); } } $this->log("$file: file not found in listing of remote directory"); return false; } /** * Callback used by by chunked downloading API * * @param String $file - the file (basename) to be downloaded * @param Array $headers - supplied headers * @return String - the data downloaded */ public function chunked_download($file, $headers) { // $curl_options = array(); // if (is_array($headers) && !empty($headers['Range']) && preg_match('/bytes=(.*)$/', $headers['Range'], $matches)) { // $curl_options[CURLOPT_RANGE] = $matches[1]; $opts = $this->options; $storage = $this->get_storage(); $backup_path = empty($opts['backup_path']) ? '' : trailingslashit($opts['backup_path']); $options = array( 'BucketName' => $opts['bucket_name'], 'FileName' => $backup_path.$file, ); if (!empty($headers)) $options['headers'] = $headers; $remote_file = $storage->download($options); return is_string($remote_file) ? $remote_file : false; } /** * Acts as a WordPress options filter * * @param Array $settings - pre-filtered settings * * @return Array filtered settings */ public function options_filter($settings) { if (is_array($settings) && !empty($settings['version']) && !empty($settings['settings'])) { foreach ($settings['settings'] as $instance_id => $instance_settings) { if (!empty($instance_settings['backup_path'])) { $settings['settings'][$instance_id]['backup_path'] = trim($instance_settings['backup_path'], "/ \t\n\r\0\x0B"); } if (!empty($instance_settings['object_lock_duration']) && $instance_settings['object_lock_duration'] > self::MAX_OBJECT_LOCK_DURATION) { $settings['settings'][$instance_id]['object_lock_duration'] = self::MAX_OBJECT_LOCK_DURATION; } } } return $settings; } /** * Delete an indicated file from remote storage * * @param Array $files - the files (basename) to delete * * @return Boolean|Array - success/failure status of the delete operation. Throwing exception is also permitted. */ public function do_delete($files) { $opts = $this->options; $storage = $this->get_storage(); $backup_path = empty($opts['backup_path']) ? '' : trailingslashit($opts['backup_path']); try { if (count($files) > 1) { $multipleFiles = array(); foreach ($files as $file) { $multipleFiles[] = array( 'FileName' => $backup_path.$file, 'BucketName' => $opts['bucket_name'] ); } $result = $storage->deleteMultipleFiles($multipleFiles, $opts['bucket_name'], $backup_path); } else { $fileName = $files[0]; $result = $storage->deleteFile(array( 'FileName' => $backup_path.$fileName, 'BucketName' => $opts['bucket_name'], )); } } catch (UpdraftPlus_Backblaze_NotFoundException $e) { // This exception should only be possible on the single file delete path $this->log("$fileName: file not found (so likely already deleted)"); return true; } return $result; } /** * This method is used to get a list of backup files for the remote storage option * * @param String $match - a string to match when looking for files * * @return Array|WP_Error - returns an array of files (arrays with keys 'name' (basename) and (optional) 'size' (in bytes)) or a WordPress error. Throwing an exception is also allowed. */ public function do_listfiles($match = 'backup_') { $opts = $this->get_options(); $storage = $this->get_storage(); // When listing, paths in the root must not begin with a slash $backup_path = empty($opts['backup_path']) ? '' : trailingslashit($opts['backup_path']); try { $remote_files = $storage->listFiles(array( 'BucketName' => $opts['bucket_name'], 'Prefix' => $backup_path.$match )); } catch (Exception $e) { return new WP_Error('backblaze_list_error', $e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')'); } if (is_wp_error($remote_files)) return $remote_files; $files = array(); foreach ($remote_files as $file) { $file_name = $file->getName(); if ($backup_path && 0 !== strpos($file_name, $backup_path)) continue; $files[] = array( 'name' => substr($file_name, strlen($backup_path)), 'size' => $file->getSize(), // 'fid' => $file->getId(), ); } return $files; } /** * Get a list of parameters required to be present for a credential tests, plus descriptions * * @return Array */ public function get_credentials_test_required_parameters() { return array( 'account_id' => __('Account ID', 'updraftplus'), 'key' => __('Account Key', 'updraftplus') ); } /** * Run a credentials test. Output can be echoed. * * @param String $testfile - basename to use for the test * @param Array $posted_settings - settings to use * * @return Array - 'result' indicating a success/failure status, and 'data' with returned data */ protected function do_credentials_test($testfile, $posted_settings = array()) { $bucket_name = strtolower($posted_settings['bucket_name']); $result = false; $data = null; $storage = $this->get_storage(); try { if (!$this->is_valid_bucket_name($bucket_name)) { echo esc_html(__('Invalid bucket name', 'updraftplus'))."\n"; } else { $buckets = $this->get_bucket_names_array(); $new_bucket_created = false; if (!in_array($bucket_name, $buckets)) { $new_bucket_created = $storage->createPrivateBucket($bucket_name); } if (in_array($bucket_name, $buckets) || $new_bucket_created) { $backup_path = empty($posted_settings['backup_path']) ? '' : trailingslashit($posted_settings['backup_path']); // Now try to write $result = $storage->upload(array( 'BucketName' => $bucket_name, 'FileName' => $backup_path.$testfile, 'Body' => 'This is a test file resulting from pressing the "Test" button in UpdraftPlus, https://updraftplus.com. If it is still here afterwards, then something went wrong deleting it - you should delete it manually.', )); if (is_object($result) && is_callable(array($result, 'getSize')) && $result->getSize() > 1) { $result = true; } } elseif (!$new_bucket_created) { echo esc_html(__('Failure: We could not successfully access or create such a bucket', 'updraftplus').' '.sprintf(__('Please check your access credentials, and if those are correct then try another bucket name (as another %s user may already have taken your name).', 'updraftplus'), 'Backblaze')); } } } catch (Exception $e) { echo esc_html(get_class($e).': '.$e->getMessage().' ('.$e->getCode().', '.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile()).")\n"; } return array('result' => $result, 'data' => $data); } /** * Delete a temporary file use for a credentials test. Output can be echo-ed. * * @param String $testfile - the basename of the file to delete * @param Array $posted_settings - the settings to use * * @return void */ protected function do_credentials_test_deletefile($testfile, $posted_settings) { try { $backup_path = empty($posted_settings['backup_path']) ? '' : trailingslashit($posted_settings['backup_path']); $storage = $this->get_storage(); $storage->deleteFile(array( 'FileName' => $backup_path.$testfile, 'BucketName' => strtolower($posted_settings['bucket_name']), )); } catch (Exception $e) { echo esc_html(__('Delete failed:', 'updraftplus').' '.$e->getMessage().' ('.$e->getCode().', '.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile().')'); } } /** * Retrieve a list of supported features for this storage method * This method should be over-ridden by methods supporting new * features. * * @see UpdraftPlus_BackupModule::get_supported_features() * * @return Array - an array of supported features (any features not * mentioned are assumed to not be supported) */ public function get_supported_features() { // This options format is handled via only accessing options via $this->get_options() return array('multi_options', 'config_templates', 'multi_storage', 'conditional_logic', 'multi_delete'); } /** * Retrieve default options for this remote storage module. * * @return Array - an array of options */ public function get_default_options() { return array( 'account_id' => '', 'key' => '', 'bucket_name' => '', 'backup_path' => '', 'single_bucket_key_id' => '', 'object_lock_duration' => 0 ); } /** * Perform any boot-strapping functions, and return a client instance * * @param Array $opts - instance options * @param Boolean $connect - whether to also set up a connection (if supported by this method) * * @return UpdraftPlus_Backblaze_CurlClient|WP_Error - the storage object. It should also be stored as $this->storage. */ public function do_bootstrap($opts) { $storage = $this->get_storage(); if (!empty($storage) && !is_wp_error($storage)) return $storage; try { if (!is_array($opts)) $opts = $this->get_options(); if (!class_exists('UpdraftPlus_Backblaze_CurlClient')) updraft_try_include_file('includes/Backblaze/CurlClient.php', 'include_once'); if (empty($opts['account_id']) || empty($opts['key'])) return new WP_Error('no_settings', __('No settings were found', 'updraftplus').' (Backblaze)'); $backblaze_options = array( 'ssl_verify' => empty($opts['disableverify']), 'ssl_ca_certs' => empty($opts['useservercerts']) ? UPDRAFTPLUS_DIR.'/includes/cacert.pem' : false ); $storage = new UpdraftPlus_Backblaze_CurlClient($opts['account_id'], $opts['key'], $opts['single_bucket_key_id'], $backblaze_options); $this->set_storage($storage); } catch (Exception $e) { return new WP_Error('blob_service_failed', 'Error when attempting to setup Backblaze access (please check your credentials): '.$e->getMessage().' ('.$e->getCode().', '.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile().')'); } return $storage; } /** * 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['account_id']) && !empty($opts['key'])) return true; return false; } /** * Get the pre configuration template * * @return String - the template */ public function get_pre_configuration_template() { ?>
{{{curl_existence_label}}}

{{configuration_helper_link_text}}

{{input_key_id_label}}:
{{{input_key_id_title}}}
{{input_application_key_label}}: {{input_bucket_key_id_label}}:
{{input_bucket_key_id_title}}
{{input_backup_path_label}}: //
{{{input_backup_path_title}}}
{{input_object_lock_label}}: {{else}} value="0" /> {{/if}}
{{input_object_lock_title}} {{input_object_lock_warning}}{{{read_more_object_lock}}} {{{get_template_test_button_html "Backblaze"}}} UPDRAFTPLUS_URL.'/images/backblaze.png', 'curl_existence_label' => wp_kses($updraftplus_admin->curl_check('Backblaze B2', false, 'backblaze hidden-in-updraftcentral', false), $this->allowed_html_for_content_sanitisation()), 'configuration_helper_link_text' => sprintf(__('For help configuring %s, including screenshots, follow this link.', 'updraftplus'), 'Backblaze'), 'input_key_id_label' => __('Master Application Key ID', 'updraftplus'), 'input_key_id_title' => sprintf(__('Get these settings from %s, or sign up %s.', 'updraftplus'), ''.__('here', 'updraftplus').'', ''.__('here', 'updraftplus').''), 'input_application_key_label' => __('Application key', 'updraftplus'), 'input_application_key_type' => apply_filters('updraftplus_admin_secret_field_type', 'password'), 'input_bucket_key_id_label' => __('Bucket application key ID', 'updraftplus'), 'input_bucket_key_id_title' => __('This is needed if, and only if, your application key was a bucket-specific application key (not a master key)', 'updraftplus'), 'input_backup_path_label' => __('Backup path', 'updraftplus'), 'input_object_lock_max_value' => self::MAX_OBJECT_LOCK_DURATION, 'input_object_lock_label' => __('Object lock duration (days)', 'updraftplus'), 'input_object_lock_title' => __('Object lock is a Backblaze B2 feature that prevents data from being changed or deleted for a given number of days.', 'updraftplus').' '.__('Use this to protect your data from hackers or for regulatory compliance reasons.', 'updraftplus').' '.__('0 days means no lock is applied.', 'updraftplus'), 'read_more_object_lock' => ' '.__('Read more about the Backblaze Object Lock', 'updraftplus').'.', 'input_object_lock_warning' => __('A file which is locked cannot be deleted by any means until the lock time duration has expired.', 'updraftplus'), 'input_backup_path_name_placeholder' => __('Bucket name', 'updraftplus'), 'input_backup_path_title' => ''.__('There are limits upon which path-names are valid.', 'updraftplus').' '.__('Spaces are not allowed.', 'updraftplus').'', 'input_backup_path_some_path_placeholder' => __('some/path', 'updraftplus'), 'input_test_label' => sprintf(__('Test %s Settings', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]), ); return wp_parse_args($properties, $this->get_persistent_variables_and_methods()); } /** * Get bucket name list array for current storage instance * * @return array Which contains bucket names as element values */ protected function get_bucket_names_array() { $bucket_names = array(); $storage = $this->get_storage(); $instance_id = $this->get_instance_id(); if (empty($this->buckets[$instance_id])) $this->buckets[$instance_id] = $storage->listBuckets(); if (!empty($this->buckets[$instance_id]) && is_array($this->buckets[$instance_id])) { foreach ($this->buckets[$instance_id] as $bucket) { $bucket_names[] = $bucket->getName(); } } return $bucket_names; } /** * Checks whether bucket name is valid as per backblaze standards * * @param string $bucket_name Backblaze bucket name * @return boolean If bucket name is valid, it returns true. Otherwise false */ protected function is_valid_bucket_name($bucket_name) { return preg_match('/^(?!b2-)[-0-9a-z]{6,50}$/i', $bucket_name); } /** * Attempts to set an object lock for a file based on the provided options. * * @param array $opts An array of options. * @param object $file The file object on which the object lock will be set. * * @return void */ private function maybe_set_object_lock($opts, $file) { if (!empty($opts['object_lock_duration'])) { // Attempt to set the object lock for the file. $storage = $this->get_storage(); $object_lock_response = $storage->setObjectLock($file->getId(), $file->getName(), $opts['object_lock_duration']); if (is_array($object_lock_response) && !empty($object_lock_response['fileRetention']['retainUntilTimestamp'])) { // Object lock set successfully. Log the information. $retain_date = get_date_from_gmt(date('M d, Y G:i', $object_lock_response['fileRetention']['retainUntilTimestamp'] / 1000), 'M d, Y G:i'); $this->log(sprintf('The file named %s has been successfully locked for %d day(s) and the lock will expire on %s.', $file->getName(), $opts['object_lock_duration'], $retain_date)); } else { // Failed to set object lock. Log an error. $this->log(sprintf(__('Error: unable to set object lock for file: %s', 'updraftplus'), $file->getName()), 'error'); $this->log("Unable to set object lock for file: ".$file->getName()); } } } }