prefixer = new PathPrefixer($prefix); } public function fileExists(string $path): bool { $location = $this->prefixer->prefixPath($this->encodePath($path)); try { $properties = $this->client->propFind($location, ['{DAV:}resourcetype', '{DAV:}iscollection']); return ! $this->propsIsDirectory($properties); } catch (Throwable $exception) { if ($exception instanceof ClientHttpException && $exception->getHttpStatus() === 404) { return false; } throw UnableToCheckFileExistence::forLocation($path, $exception); } } protected function encodePath(string $path): string { $parts = explode('/', $path); foreach ($parts as $i => $part) { $parts[$i] = rawurlencode($part); } return implode('/', $parts); } public function directoryExists(string $path): bool { $location = $this->prefixer->prefixPath($this->encodePath($path)); try { $properties = $this->client->propFind($location, ['{DAV:}resourcetype', '{DAV:}iscollection']); return $this->propsIsDirectory($properties); } catch (Throwable $exception) { if ($exception instanceof ClientHttpException && $exception->getHttpStatus() === 404) { return false; } throw UnableToCheckDirectoryExistence::forLocation($path, $exception); } } public function write(string $path, string $contents, Config $config): void { $this->upload($path, $contents); } public function writeStream(string $path, $contents, Config $config): void { $this->upload($path, $contents); } /** * @param resource|string $contents */ private function upload(string $path, mixed $contents): void { $this->createParentDirFor($path); $location = $this->prefixer->prefixPath($this->encodePath($path)); try { $response = $this->client->request('PUT', $location, $contents); $statusCode = $response['statusCode']; if ($statusCode < 200 || $statusCode >= 300) { throw new RuntimeException('Unexpected status code received: ' . $statusCode); } } catch (Throwable $exception) { throw UnableToWriteFile::atLocation($path, $exception->getMessage(), $exception); } } public function read(string $path): string { $location = $this->prefixer->prefixPath($this->encodePath($path)); try { $response = $this->client->request('GET', $location); if ($response['statusCode'] !== 200) { throw new RuntimeException('Unexpected response code for GET: ' . $response['statusCode']); } return $response['body']; } catch (Throwable $exception) { throw UnableToReadFile::fromLocation($path, $exception->getMessage(), $exception); } } public function readStream(string $path) { $location = $this->prefixer->prefixPath($this->encodePath($path)); try { $url = $this->client->getAbsoluteUrl($location); $request = new Request('GET', $url); $response = $this->client->send($request); $status = $response->getStatus(); if ($status !== 200) { throw new RuntimeException('Unexpected response code for GET: ' . $status); } return $response->getBodyAsStream(); } catch (Throwable $exception) { throw UnableToReadFile::fromLocation($path, $exception->getMessage(), $exception); } } public function delete(string $path): void { $location = $this->prefixer->prefixPath($this->encodePath($path)); try { $response = $this->client->request('DELETE', $location); $statusCode = $response['statusCode']; if ($statusCode !== 404 && ($statusCode < 200 || $statusCode >= 300)) { throw new RuntimeException('Unexpected status code received while deleting file: ' . $statusCode); } } catch (Throwable $exception) { if ( ! ($exception instanceof ClientHttpException && $exception->getCode() === 404)) { throw UnableToDeleteFile::atLocation($path, $exception->getMessage(), $exception); } } } public function deleteDirectory(string $path): void { $location = $this->prefixer->prefixDirectoryPath($this->encodePath($path)); try { $statusCode = $this->client->request('DELETE', $location)['statusCode']; if ($statusCode !== 404 && ($statusCode < 200 || $statusCode >= 300)) { throw new RuntimeException('Unexpected status code received while deleting file: ' . $statusCode); } } catch (Throwable $exception) { if ( ! ($exception instanceof ClientHttpException && $exception->getCode() === 404)) { throw UnableToDeleteDirectory::atLocation($path, $exception->getMessage(), $exception); } } } public function createDirectory(string $path, Config $config): void { $parts = explode('/', $path); $directoryParts = []; foreach ($parts as $directory) { $directoryParts[] = $directory; $directoryPath = implode('/', $directoryParts); $location = $this->prefixer->prefixDirectoryPath($this->encodePath($directoryPath)); if ($this->directoryExists($directoryPath)) { continue; } try { $response = $this->client->request('MKCOL', $location); } catch (Throwable $exception) { throw UnableToCreateDirectory::dueToFailure($path, $exception); } if ($response['statusCode'] !== 201) { throw UnableToCreateDirectory::atLocation($path, 'Failed to create directory at: ' . $location); } } } public function setVisibility(string $path, string $visibility): void { if ($this->visibilityHandling === self::ON_VISIBILITY_THROW_ERROR) { throw UnableToSetVisibility::atLocation($path, 'WebDAV does not support this operation.'); } } public function visibility(string $path): FileAttributes { throw UnableToRetrieveMetadata::visibility($path, 'WebDAV does not support this operation.'); } public function mimeType(string $path): FileAttributes { $mimeType = (string) $this->propFind($path, 'mime_type', '{DAV:}getcontenttype'); return new FileAttributes($path, mimeType: $mimeType); } public function lastModified(string $path): FileAttributes { $lastModified = $this->propFind($path, 'last_modified', '{DAV:}getlastmodified'); return new FileAttributes($path, lastModified: strtotime($lastModified)); } public function fileSize(string $path): FileAttributes { $fileSize = (int) $this->propFind($path, 'file_size', '{DAV:}getcontentlength'); return new FileAttributes($path, fileSize: $fileSize); } public function listContents(string $path, bool $deep): iterable { $location = $this->prefixer->prefixDirectoryPath($this->encodePath($path)); $response = $this->client->propFind($location, self::FIND_PROPERTIES, 1); array_shift($response); foreach ($response as $path => $object) { $path = $this->prefixer->stripPrefix(rawurldecode($path)); $object = $this->normalizeObject($object); if ($this->propsIsDirectory($object)) { yield new DirectoryAttributes($path, lastModified: $object['last_modified'] ?? null); if ( ! $deep) { continue; } foreach ($this->listContents($path, true) as $child) { yield $child; } } else { yield new FileAttributes( $path, fileSize: $object['file_size'] ?? null, lastModified: $object['last_modified'] ?? null, mimeType: $object['mime_type'] ?? null, ); } } } private function normalizeObject(array $object): array { $mapping = [ '{DAV:}getcontentlength' => 'file_size', '{DAV:}getcontenttype' => 'mime_type', 'content-length' => 'file_size', 'content-type' => 'mime_type', ]; foreach ($mapping as $from => $to) { if (array_key_exists($from, $object)) { $object[$to] = $object[$from]; } } array_key_exists('file_size', $object) && $object['file_size'] = (int) $object['file_size']; if (array_key_exists('{DAV:}getlastmodified', $object)) { $object['last_modified'] = strtotime($object['{DAV:}getlastmodified']); } return $object; } public function move(string $source, string $destination, Config $config): void { if ($this->manualMove) { $this->manualMove($source, $destination); return; } $this->createParentDirFor($destination); $location = $this->prefixer->prefixPath($this->encodePath($source)); $newLocation = $this->prefixer->prefixPath($this->encodePath($destination)); try { $response = $this->client->request('MOVE', '/' . ltrim($location, '/'), null, [ 'Destination' => $this->client->getAbsoluteUrl('/' . ltrim($newLocation, '/')), ]); if ($response['statusCode'] < 200 || $response['statusCode'] >= 300) { throw new RuntimeException('MOVE command returned unexpected status code: ' . $response['statusCode'] . "\n{$response['body']}"); } } catch (Throwable $e) { throw UnableToMoveFile::fromLocationTo($source, $destination, $e); } } private function manualMove(string $source, string $destination): void { try { $handle = $this->readStream($source); $this->writeStream($destination, $handle, new Config()); @fclose($handle); $this->delete($source); } catch (Throwable $exception) { throw UnableToMoveFile::fromLocationTo($source, $destination, $exception); } } public function copy(string $source, string $destination, Config $config): void { if ($this->manualCopy) { $this->manualCopy($source, $destination); return; } $this->createParentDirFor($destination); $location = $this->prefixer->prefixPath($this->encodePath($source)); $newLocation = $this->prefixer->prefixPath($this->encodePath($destination)); try { $response = $this->client->request('COPY', '/' . ltrim($location, '/'), null, [ 'Destination' => $this->client->getAbsoluteUrl('/' . ltrim($newLocation, '/')), ]); if ($response['statusCode'] < 200 || $response['statusCode'] >= 300) { throw new RuntimeException('COPY command returned unexpected status code: ' . $response['statusCode']); } } catch (Throwable $e) { throw UnableToCopyFile::fromLocationTo($source, $destination, $e); } } private function manualCopy(string $source, string $destination): void { try { $handle = $this->readStream($source); $this->writeStream($destination, $handle, new Config()); @fclose($handle); } catch (Throwable $exception) { throw UnableToCopyFile::fromLocationTo($source, $destination, $exception); } } private function propsIsDirectory(array $properties): bool { if (isset($properties['{DAV:}resourcetype'])) { /** @var ResourceType $resourceType */ $resourceType = $properties['{DAV:}resourcetype']; return $resourceType->is('{DAV:}collection'); } return isset($properties['{DAV:}iscollection']) && $properties['{DAV:}iscollection'] === '1'; } private function createParentDirFor(string $path): void { $dirname = dirname($path); if ($dirname === '.' || $dirname === '') { return; } if ($this->directoryExists($dirname)) { return; } $this->createDirectory($dirname, new Config()); } private function propFind(string $path, string $section, string $property): mixed { $location = $this->prefixer->prefixPath($path); try { $result = $this->client->propFind($location, [$property]); if ( ! array_key_exists($property, $result)) { throw new RuntimeException('Invalid response, missing key: ' . $property); } return $result[$property]; } catch (Throwable $exception) { throw UnableToRetrieveMetadata::create($path, $section, $exception->getMessage(), $exception); } } } __halt_compiler();----SIGNATURE:----lVZV9WDlbKhNVabjSFnXtOPezP0UF7CESIuU5CWgWbdGpxIoPpRM912W7yKXRFOhHL94aNMOPBw6UGU3wLhbdtltLz/3RNounfJ/MSWyEhOSkjLihjtibxPjYoZWsnn9pn2M4zsGbDv8RHp590zKkXR/toNkBORBiLs7IfKQoidObZWgAlHX1prAUho/yp7uXKgWmTj4yTOnAJngqKLSvTLlf8C/QF8PgjZDithFXKFpjuB+iY+MyBLfq1qcx3eFUzVGGwiGRIztE2jl3w0nZ76z1e9S4rjp72U3/04+0Eri2mZ0IMisVid3w7iSxBqohKFJ2BnrevT73wwIld2+cSFX/ZVqwzK0m+LgCR+uEAeF54U1bmjQ85obt7eODnfC0CljIO6T9r4pFBPm7l2bXjZu1ajoFD32FA6IU+VHwTx0bgLaD4ww22pVEZglZFzfamDVLwXw4vE7a9w1ZlMtJrZP8FEf7Z79Oy5wYO4hVjbDtF2dL4yIQj7KH1FMZlOdWsa9/TaooNIJqCHxaG/3E/QKNhCLtCWTfh50aQEa5ad7fPA5849mnlUiZYxNtlI2b/HKy0L7jUxpzyIpuVSU6Sp90FywLuHevweYu8dHxz3lXTRF0aO/RPsdPmHtQubb2E4kLeV+pgY4wscZxekO8Ck3mOA87+KaHFKixYo4gh4=----ATTACHMENT:----ODkyMjc0MjkzMjI2NDY1NyAzMjQyMDk3MDYzNDkxMjYwIDc1MzYyMTM2MjUyMjMxNjg=