getOptions()->get('access_key'); } /** * @param string $key * * @return void */ private function setAccessKey(string $key):void { $this->getOptions()->set('access_key', trim($key)); } /** * @return string */ public function getSecretKey():string { return $this->getOptions()->get('secret_key'); } /** * @param string $key * * @return void */ private function setSecretKey(string $key):void { $this->getOptions()->set('secret_key', trim($key)); } /** * @return string */ public function getRegion():string { return $this->getOptions()->get('region'); } /** * @param string $region * * @return void */ private function setRegion(string $region):void { $this->getOptions()->set('region', $region); } /** * @return string */ public function getBucket():string { return $this->getOptions()->get('bucket'); } /** * @param string $bucket * * @return void */ private function setBucket(string $bucket):void { $this->getOptions()->set('bucket', $bucket); } /** * @return string */ public function getEndpoint():string { return $this->getOptions()->get('endpoint'); } /** * @param string $endpoint * * @return void */ private function setEndpoint(string $endpoint):void { $this->getOptions()->set('endpoint', $endpoint); } /** * @return bool */ public function getVerifySSL():bool { return !!$this->getOptions()->get('verifyssl', false); } /** * @param bool $verifyssl * * @return void */ private function setVerifySSL(bool $verifyssl):void { $this->getOptions()->set('verifyssl', !!$verifyssl); } /** * @return int */ public function getRetries():int { return $this->getOptions()->get('retries', 3); } /** * @param int $retries * * @return void */ private function setRetries(int $retries):void { $this->getOptions()->set('retries', $retries); } /** * @return int */ public function getKeepAliveTimeout():int { return $this->getOptions()->get('keepalive_timeout', 60); } /** * @param int $timeout * * @return void */ private function setKeepAliveTimeout(int $timeout):void { $this->getOptions()->set('keepalive_timeout', $timeout); } private function setSelectedVendor(string $vendor):void { $this->getOptions()->set('selected_vendor', $vendor); } public function getSelectedVendor():string { return $this->getOptions()->get('selected_vendor'); } private function setQuickAccessCode(string $code):void { $this->getOptions()->set('quick_access_code', $code); } public function getQuickAccessCode():string { return $this->getOptions()->get('quick_access_code'); } /** * @return int */ public function getKeepAliveRequests():int { return $this->getOptions()->get('keepalive_requests', 100); } /** * @param int $requests * * @return void */ private function setKeepAliveRequests(int $requests):void { $this->getOptions()->set('keepalive_requests', $requests); } /** * @return stdClass */ public function getExtraFields(): stdClass { return $this->getOptions()->get('extrafields', new stdClass()); } /** * @param stdClass $fields * * @return void */ private function setExtraFields( stdClass $fields):void { $this->getOptions()->set('extrafields', $fields); } /** * @return string */ private function _getParsedEndpoint():string { $endpoint = $this->getEndpoint(); if(!$this->getExtraFields()) return $endpoint; foreach($this->getExtraFields() as $key => $value) $endpoint = str_replace('{' . $key . '}', $value, $endpoint); return $endpoint; } /** * @return ClientManager */ public function getClient():ClientManager { if(!$this->_client) { $this->_client = new ClientManager($this->getLogController()); $this->_client->setAccessKey($this->getAccessKey()); $this->_client->setSecretKey($this->getSecretKey()); $this->_client->setRegion($this->getRegion()); $this->_client->setBucket($this->getBucket()); $this->_client->setEndpoint($this->_getParsedEndpoint()); $this->_client->setVerifySSL($this->getVerifySSL()); $this->_client->setKeepAliveTimeout($this->getKeepAliveTimeout()); $this->_client->setKeepAliveRequests($this->getKeepAliveRequests()); $this->_client->setRetries($this->getRetries()); $this->_client->setChunkSize($this->getChunkSize()); } return $this->_client; } /** * @return void * @throws ConnectionException */ public function connect():void { try { $this->getClient()->listObjects('', 1); } catch(ClientException $e) { throw new ConnectionException($e->getMessage(), $e->getCode(), $e); } } /** * @return void */ public function disconnect():void {} /** * @return void * @throws RegistrationException */ public function register():void { try { $this->getClient()->createObject($this->getRealPath('/.writecheck')); $this->getClient()->deleteObject($this->getRealPath('/.writecheck')); } catch( ClientException $e) { throw new RegistrationException($e->getMessage(), $e->getCode(), $e); } } /** * @return void */ public function unregister():void {} /** * @return void * @throws FieldsValidationException */ public function validateFields():void { if(!$this->getPath()) throw new FieldsValidationException("No path provided"); if(!str_starts_with($this->getPath(), '/')) throw new FieldsValidationException("Path must start with \"/\""); if(!preg_match("/^[\/a-zA-Z0-9\-_.]+$/", $this->getPath())) throw new FieldsValidationException("Invalid path provided (Allowed characters A-Z a-z 0-9 _ - . and /)"); if(!$this->getAccessKey()) throw new FieldsValidationException("No access key provided"); if(!$this->getSecretKey()) throw new FieldsValidationException("No secret key provided"); if(!$this->getBucket()) throw new FieldsValidationException("No bucket provided"); if(!preg_match("/^[a-z0-9][a-z0-9\-.]{2,62}$/", $this->getBucket())) throw new FieldsValidationException("Invalid bucket provided. Bucket name must be lowercase and start with a letter or number. They can be between 3 and 63 characters long and may contain dashes and periods."); if(!$this->getEndpoint()) throw new FieldsValidationException("No endpoint provided"); if($this->getRetries() > 10 || $this->getRetries() < 0) throw new FieldsValidationException("Invalid retries provided. Minimum 0 and Maximum 10"); if($this->getChunkSize() < self::MIN_CHUNK_SIZE) throw new FieldsValidationException("Invalid chunk size provided. Minimum 5MB"); if($this->getKeepAliveTimeout() > 600 || $this->getKeepAliveTimeout() < 0) throw new FieldsValidationException("Invalid keep alive timeout provided. Minimum 0 seconds (disabled) and Maximum 600 seconds"); if($this->getKeepAliveRequests() > 1000 || $this->getKeepAliveRequests() < 0) throw new FieldsValidationException("Invalid keep alive requests provided. Minimum 0 requests (Determined by the remote vendor) and Maximum 1000 requests"); if(Wordpress::strContains($this->getEndpoint(), '{region}') && !$this->getRegion()) throw new FieldsValidationException("No region provided"); } /** * @param object $data * * @return void */ public function setData(object $data):void { if(isset($data->selected_vendor)) $this->setSelectedVendor($data->selected_vendor); if(isset($data->quick_access_code)) $this->setQuickAccessCode($data->quick_access_code); if ($this->getSelectedVendor() === 'jetstorage' && $this->getQuickAccessCode()) { if (($decoded = base64_decode($this->getQuickAccessCode(), true)) === false) throw new FieldsValidationException("Invalid Base64 in access code"); if (($uncompressed = @gzdecode($decoded)) === false) throw new FieldsValidationException("Failed to decompress access code"); if (!is_object($quick_access = json_decode($uncompressed))) throw new FieldsValidationException("Access code does not contain valid JSON"); foreach (['access_key', 'secret_key', 'region', 'bucket'] as $field) if (!property_exists($quick_access, $field) || empty($quick_access->$field)) throw new FieldsValidationException("Missing field '$field' in access code"); $this->setAccessKey($quick_access->access_key); $this->setSecretKey($quick_access->secret_key); $this->setRegion($quick_access->region); $this->setBucket($quick_access->bucket); $this->setEndpoint($quick_access->region.'.storage.jetbackup.com'); } else { if(isset($data->access_key)) $this->setAccessKey($data->access_key); if(isset($data->secret_key)) $this->setSecretKey($data->secret_key); if(isset($data->region)) $this->setRegion($data->region); if(isset($data->bucket)) $this->setBucket($data->bucket); if(isset($data->endpoint)) $this->setEndpoint($data->endpoint); } if(isset($data->verifyssl)) $this->setVerifySSL(!!$data->verifyssl); if(isset($data->retries)) $this->setRetries(intval($data->retries)); if(isset($data->extrafields)) $this->setExtraFields((object) $data->extrafields); if(isset($data->keepalive_timeout)) $this->setKeepAliveTimeout(intval($data->keepalive_timeout)); if(isset($data->keepalive_requests)) $this->setKeepAliveRequests(intval($data->keepalive_requests)); } /** * @return array */ public function getData(): array { return $this->getOptions()->getData(); } /** * @param string $directory * @param string|null $data * * @return bool */ public function dirExists(string $directory, ?string $data=null): bool { return true; } /** * @param string $file * @param string|null $data * * @return bool * @throws IOException */ public function fileExists(string $file, ?string $data=null): bool { try { $this->getLogController()->logDebug("[fileExists] file exist for $file"); return !$this->getClient()->getObject($this->getRealPath($file))->isDir(); } catch(ClientException $e) { if($e->getCode() == 404) return false; throw new IOException($e->getMessage()); } } /** * @param string $directory * @param bool $recursive * @param string|null $data * * @return string|null */ public function createDir( string $directory, bool $recursive, ?string $data=null):?string { return null; } /** * @param string $directory * @param string|null $data * * @return void * @throws IOException */ public function removeDir( string $directory, ?string $data=null):void { if(!str_ends_with($directory, '/')) $directory .= '/'; $this->getLogController()->logDebug("[removeDir] Remove directory $directory"); $checked = []; $queue = [$directory]; while($queue) { $dir = array_pop($queue); $iterator = $this->listDir($dir); if(!$iterator->hasNext() || isset($checked[$dir])) { $this->removeFile($dir); unset($checked[$dir]); continue; } $queue[] = $dir; $checked[$dir] = true; while($iterator->hasNext()) { $file = $iterator->getNext(); if( $file->getType() == iDestinationFile::TYPE_DIRECTORY) { $dir = $file->getFullPath() . ((!str_ends_with($file->getFullPath(), '/')) ? '/' : ''); $queue[] = $dir; continue; } $this->removeFile($file->getFullPath()); } } } /** * @param string $file * @param string|null $data * * @return void * @throws IOException */ public function removeFile(string $file, ?string $data=null):void { try { $this->getLogController()->logDebug("[removeFile] Remove file $file"); $this->getClient()->deleteObject($this->getRealPath($file)); } catch(ClientException $e) { if($e->getCode() == 404) return; throw new IOException($e->getMessage(), $e->getCode(), $e); } } /** * @param string $source * @param string $destination * @param string|null $data * * @return void * @throws IOException */ public function copyFileToLocal(string $source, string $destination, ?string $data=null):void { try { $this->getLogController()->logDebug("[copyFileToLocal] Downloading file $source -> $destination"); $this->getClient()->getObject($this->getRealPath($source), $destination); } catch(ClientException $e) { throw new IOException($e->getMessage(), $e->getCode(), $e); } } /** * @param string $source * @param string|null $data * * @return DestinationChunkedDownload */ public function copyFileToLocalChunked(string $source, string $destination, ?string $data=null):DestinationChunkedDownload { $this->getLogController()->logDebug("[copyFileToLocalChunked] Downloading chunked file $source -> $destination"); return new ChunkedDownload($this->getClient(), $this->getRealPath($source), $destination); } /** * @param string $source * @param string $destination * @param string|null $data * * @return string|null * @throws IOException */ public function copyFileToRemote(string $source, string $destination, ?string $data=null):?string { try { $this->getLogController()->logDebug("[copyFileToRemote] Copy file $source -> $destination"); $this->getClient()->putObject($source, $this->getRealPath($destination)); } catch(ClientException $e) { throw new IOException($e->getMessage(), $e->getCode(), $e); } return null; } /** * @param string $destination * * @return DestinationChunkedUpload */ public function copyFileToRemoteChunked(string $source, string $destination, ?string $data=null):DestinationChunkedUpload { $this->getLogController()->logDebug("[copyFileToRemoteChunked] Copy Chunked file $source -> $destination"); return new ChunkedUpload($this->getClient(), $this->getRealPath($destination)); } /** * @throws IOException */ public function listDir(string $directory, ?string $data=null):DestinationDirIterator { $this->getLogController()->logDebug("[listDir] Requested listDir for $directory"); return new DirIterator($this, $directory); } /** * @return iDestinationDiskUsage|null */ public function getDiskInfo():?iDestinationDiskUsage { return null; } /** * @param string $file * * @return iDestinationFile|null * @throws IOException */ public function getFileStat(string $file):?iDestinationFile { try { $this->getLogController()->logDebug("[getFileStat] Requested stat for $file"); if(!($object = $this->getClient()->getObject($this->getRealPath($file)))) return null; $dirname = ''; if(Wordpress::strContains($object->getKey(), '/')) { $remove = preg_replace("/^\/+/", "", $this->getRealPath('/')); $dirname = substr($object->getKey(), strlen($remove), strrpos($object->getKey(), '/')); } $file = new DestinationFile(); $file->setName(basename($object->getKey())); $file->setPath('/' . preg_replace("/^\/+/", "", $dirname)); $file->setModifyTime($object->getMtime()); $file->setSize($object->isDir() ? 4096 : $object->getSize()); $file->setType($object->isDir() ? DestinationFile::TYPE_DIRECTORY : DestinationFile::TYPE_FILE); return $file; } catch(ClientException $e) { throw new IOException($e->getMessage(), $e->getCode(), $e); } } public function protectedFields():array { return ['secret_key']; } }