/
home
/
sjslayjy
/
public_html
/
theweavenest
/
vendor
/
league
/
flysystem
/
src
/
WebDAV
/
Upload File
HOME
<?php declare(strict_types=1); namespace League\Flysystem\WebDAV; use League\Flysystem\Config; use League\Flysystem\DirectoryAttributes; use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; use League\Flysystem\PathPrefixer; use League\Flysystem\UnableToCheckDirectoryExistence; use League\Flysystem\UnableToCheckFileExistence; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToCreateDirectory; use League\Flysystem\UnableToDeleteDirectory; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToReadFile; use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UnableToWriteFile; use League\Flysystem\UrlGeneration\PublicUrlGenerator; use RuntimeException; use Sabre\DAV\Client; use Sabre\DAV\Xml\Property\ResourceType; use Sabre\HTTP\ClientHttpException; use Sabre\HTTP\Request; use Throwable; use function array_key_exists; use function array_shift; use function dirname; use function explode; use function fclose; use function implode; use function parse_url; use function rawurldecode; class WebDAVAdapter implements FilesystemAdapter, PublicUrlGenerator { public const ON_VISIBILITY_THROW_ERROR = 'throw'; public const ON_VISIBILITY_IGNORE = 'ignore'; public const FIND_PROPERTIES = [ '{DAV:}displayname', '{DAV:}getcontentlength', '{DAV:}getcontenttype', '{DAV:}getlastmodified', '{DAV:}iscollection', '{DAV:}resourcetype', ]; private PathPrefixer $prefixer; public function __construct( private Client $client, string $prefix = '', private string $visibilityHandling = self::ON_VISIBILITY_THROW_ERROR, private bool $manualCopy = false, private bool $manualMove = false, ) { $this->prefixer = new PathPrefixer($prefix); } public function fileExists(string $path): bool { $location = $this->encodePath($this->prefixer->prefixPath($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->encodePath($this->prefixer->prefixPath($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->encodePath($this->prefixer->prefixPath($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->encodePath($this->prefixer->prefixPath($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->encodePath($this->prefixer->prefixPath($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->encodePath($this->prefixer->prefixPath($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->encodePath($this->prefixer->prefixDirectoryPath($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('/', $this->prefixer->prefixDirectoryPath($path)); $directoryParts = []; foreach ($parts as $directory) { if ($directory === '.' || $directory === '') { return; } $directoryParts[] = $directory; $directoryPath = implode('/', $directoryParts); $location = $this->encodePath($directoryPath) . '/'; if ($this->directoryExists($this->prefixer->stripDirectoryPrefix($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->encodePath($this->prefixer->prefixDirectoryPath($path)); $response = $this->client->propFind($location, self::FIND_PROPERTIES, 1); // This is the directory itself, the files are subsequent entries. array_shift($response); foreach ($response as $path => $object) { $path = (string) parse_url(rawurldecode($path), PHP_URL_PATH); $path = $this->prefixer->stripPrefix($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 ($source === $destination) { return; } if ($this->manualMove) { $this->manualMove($source, $destination); return; } $this->createParentDirFor($destination); $location = $this->encodePath($this->prefixer->prefixPath($source)); $newLocation = $this->encodePath($this->prefixer->prefixPath($destination)); try { $response = $this->client->request('MOVE', $location, null, [ 'Destination' => $this->client->getAbsoluteUrl($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 ($source === $destination) { return; } if ($this->manualCopy) { $this->manualCopy($source, $destination); return; } $this->createParentDirFor($destination); $location = $this->encodePath($this->prefixer->prefixPath($source)); $newLocation = $this->encodePath($this->prefixer->prefixPath($destination)); try { $response = $this->client->request('COPY', $location, null, [ 'Destination' => $this->client->getAbsoluteUrl($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 ($this->directoryExists($dirname)) { return; } $this->createDirectory($dirname, new Config()); } private function propFind(string $path, string $section, string $property): mixed { $location = $this->encodePath($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); } } public function publicUrl(string $path, Config $config): string { return $this->client->getAbsoluteUrl($this->encodePath($this->prefixer->prefixPath($path))); } }