authMgr ?? new Auth($this->access_key, $this->secret_key); } private function getUploadMgr(): UploadManager { return $this->uploadMgr ?? new UploadManager(); } private function getBucketMgr(): BucketManager { return $this->bucketMgr ?? new BucketManager($this->authMgr); } private function getPathPrefix(): string { $path = ltrim($this->path, '\\/'); if ($path !== '' && !str_ends_with($path, '/')) { $path = $path . '/'; } return $path; } private function applyPathPrefix(string $path): string { $path = ltrim($path, '\\/'); return $this->getPathPrefix() . $path; } private static function parseUrl($url): array { $result = []; // Build arrays of values we need to decode before parsing $entities = [ '%21', '%2A', '%27', '%28', '%29', '%3B', '%3A', '%40', '%26', '%3D', '%24', '%2C', '%2F', '%3F', '%23', '%5B', '%5D', '%5C' ]; $replacements = ['!', '*', "'", '(', ')', ';', ':', '@', '&', '=', '$', ',', '/', '?', '#', '[', ']', '/']; // Create encoded URL with special URL characters decoded so it can be parsed // All other characters will be encoded $encodedURL = str_replace($entities, $replacements, urlencode($url)); // Parse the encoded URL $encodedParts = parse_url($encodedURL); // Now, decode each value of the resulting array if ($encodedParts) { foreach ($encodedParts as $key => $value) { $result[$key] = urldecode(str_replace($replacements, $entities, $value)); } } return $result; } private function normalizeHost($domain): string { if (0 !== stripos($domain, 'https://') && 0 !== stripos($domain, 'http://')) { $domain = "http://{$domain}"; } return rtrim($domain, '/') . '/'; } private function getUrl(string $path): string { $segments = $this->parseUrl($path); $query = empty($segments['query']) ? '' : '?' . $segments['query']; return $this->normalizeHost($this->base_url) . ltrim(implode('/', array_map('rawurlencode', explode('/', $segments['path']))), '/') . $query; } private function privateDownloadUrl(string $path, int $expires = 3600): string { return $this->getAuthMgr()->privateDownloadUrl($this->getUrl($path), $expires); } private function getMetadata($path): FileAttributes|array { $result = $this->getBucketMgr()->stat($this->bucket, $path); $result[0]['key'] = $path; return $this->normalizeFileInfo($result[0]); } private function normalizeFileInfo(array $stats): FileAttributes { return new FileAttributes( $stats['key'], $stats['fsize'] ?? null, null, isset($stats['putTime']) ? floor($stats['putTime'] / 10000000) : null, $stats['mimeType'] ?? null ); } public function fileExists(string $path): bool { [, $error] = $this->getBucketMgr()->stat($this->bucket, $this->applyPathPrefix($path)); return is_null($error); } public function directoryExists(string $path): bool { return $this->fileExists($path); } public function write(string $path, string $contents, Config $config): void { $mime = $config->get('mime', 'application/octet-stream'); /** * @var Error|null $error */ [, $error] = $this->getUploadMgr()->put( $this->getAuthMgr()->uploadToken($this->bucket), $this->applyPathPrefix($path), $contents, null, $mime, $path ); if ($error) { throw UnableToWriteFile::atLocation($path, $error->message()); } } public function writeStream(string $path, $resource, Config $config): void { $data = ''; while (!feof($resource)) { $data .= fread($resource, 1024); } $this->write($path, $data, $config); } public function read(string $path): string { try { $result = file_get_contents($this->privateDownloadUrl($path)); } catch (\Exception $th) { throw UnableToReadFile::fromLocation($path); } if (false === $result) { throw UnableToReadFile::fromLocation($path); } return $result; } public function readStream(string $path) { if (ini_get('allow_url_fopen')) { if ($result = fopen($this->privateDownloadUrl($path), 'r')) { return $result; } } throw UnableToReadFile::fromLocation($path); } public function delete(string $path): void { [, $error] = $this->getBucketMgr()->delete($this->bucket, $this->applyPathPrefix($path)); if (!is_null($error)) { throw UnableToDeleteFile::atLocation($path); } } public function deleteDirectory(string $path): void { $this->delete($path); } public function createDirectory(string $path, Config $config): void { } public function setVisibility(string $path, string $visibility): void { throw UnableToSetVisibility::atLocation($path); } public function visibility(string $path): FileAttributes { throw UnableToRetrieveMetadata::visibility($path); } public function mimeType(string $path): FileAttributes { $meta = $this->getMetadata($path); if ($meta->mimeType() === null) { throw UnableToRetrieveMetadata::mimeType($path); } return $meta; } public function lastModified(string $path): FileAttributes { $meta = $this->getMetadata($path); if ($meta->lastModified() === null) { throw UnableToRetrieveMetadata::lastModified($path); } return $meta; } public function fileSize(string $path): FileAttributes { $meta = $this->getMetadata($path); if ($meta->fileSize() === null) { throw UnableToRetrieveMetadata::fileSize($path); } return $meta; } public function listContents(string $path, bool $deep): iterable { $result = $this->getBucketMgr()->listFiles($this->bucket, $path); foreach ($result[0]['items'] ?? [] as $files) { yield $this->normalizeFileInfo($files); } } public function move(string $source, string $destination, Config $config): void { [, $error] = $this->getBucketMgr()->rename($this->bucket, $source, $destination); if (!is_null($error)) { throw UnableToMoveFile::fromLocationTo($source, $destination); } } public function copy(string $source, string $destination, Config $config): void { [, $error] = $this->getBucketMgr()->copy($this->bucket, $source, $this->bucket, $destination); if (!is_null($error)) { throw UnableToCopyFile::fromLocationTo($source, $destination); } } }