Skip to content
Open
8 changes: 2 additions & 6 deletions .github/workflows/php.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,8 @@ jobs:
fail-fast: false
matrix:
php:
- '8.0' # from 2020-11 to 2022-11 (2023-11)
- '8.1' # from 2021-11 to 2023-11 (2025-12)
- '8.2' # from 2022-12 to 2024-12 (2026-12)
- '8.3' # from 2023-11 to 2025-12 (2027-12)
- '8.4'
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
Expand Down Expand Up @@ -118,10 +116,8 @@ jobs:
fail-fast: false
matrix:
php:
- '8.0' # from 2020-11 to 2022-11 (2023-11)
- '8.1' # from 2021-11 to 2023-11 (2025-12)
- '8.2' # from 2022-12 to 2024-12 (2026-12)
- '8.3' # from 2023-11 to 2025-12 (2027-12)
- '8.4'
steps:
- uses: actions/checkout@v4
- uses: docker://pipelinecomponents/php-codesniffer
Expand Down
9 changes: 4 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,11 @@
"license": "MIT",
"name": "pdsinterop/solid-crud",
"require": {
"php": "^8.0",
"php": "^8.3",
"ext-mbstring": "*",
"laminas/laminas-diactoros": "^2.14",
"league/flysystem": "^1.0",
"mjrider/flysystem-factory": "^0.7",
"pdsinterop/flysystem-rdf": "^0.6.1",
"laminas/laminas-diactoros": "^3",
"league/flysystem": "^3",
"pdsinterop/flysystem-rdf": "dev-main",
"pietercolpaert/hardf": "^0.3",
"psr/http-factory": "^1.0",
"psr/http-message": "^1.0"
Expand Down
127 changes: 81 additions & 46 deletions src/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
use Laminas\Diactoros\ServerRequest;
use League\Flysystem\FileExistsException;
use League\Flysystem\FileNotFoundException;
use League\Flysystem\FilesystemInterface as Filesystem;
use League\Flysystem\FilesystemOperator as Filesystem;
use League\Flysystem\FilesystemAdapter;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Throwable;
Expand Down Expand Up @@ -61,6 +62,8 @@ class Server
private $requestedPath;
/** @var Filesystem */
private $filesystem;
/** @var FilesystemAdapter */
private $adapter;
/** @var Graph */
private $graph;
/** @var SolidNotificationsInterface */
Expand Down Expand Up @@ -105,12 +108,13 @@ final public function setNotifications(SolidNotificationsInterface $notification
//////////////////////////////// PUBLIC API \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

// @TODO: The Graph should be injected by the caller
final public function __construct(Filesystem $filesystem, Response $response, ?Graph $graph = null)
final public function __construct(Filesystem $filesystem, FilesystemAdapter $adapter, Response $response, ?Graph $graph = null)
{
$this->basePath = '';
$this->baseUrl = '';
$this->lockedPath = false;
$this->filesystem = $filesystem;
$this->adapter = $adapter;
$this->graph = $graph ?? new Graph();
$this->response = $response;
// @TODO: Mention \EasyRdf_Namespace::set('lm', 'https://purl.org/pdsinterop/link-metadata#');
Expand Down Expand Up @@ -208,9 +212,9 @@ private function handle(string $method, string $path, $contents, $request): Resp
}
break;
case 'POST':
$pathExists = $filesystem->has($path);
$pathExists = $filesystem->fileExists($path);
if ($pathExists) {
$mimetype = $filesystem->getMimetype($path);
$mimetype = $filesystem->mimeType($path);
}
if ($path === "/") {
$pathExists = true;
Expand Down Expand Up @@ -268,7 +272,7 @@ private function handle(string $method, string $path, $contents, $request): Resp
$response = $this->handleCreateDirectoryRequest($response, $path);
break;
default:
if ($filesystem->has($path) === true) {
if ($filesystem->fileExists($path) === true) {
$response = $this->handleUpdateRequest($response, $path, $contents);
} else {
$response = $this->handleCreateRequest($response, $path, $contents);
Expand All @@ -289,7 +293,7 @@ private function handleSparqlUpdate(Response $response, string $path, $contents)
$filesystem = $this->filesystem;
$graph = $this->getGraph();

if ($filesystem->has($path) === false) {
if ($filesystem->fileExists($path) === false) {
$data = '';
} else {
// read ttl data
Expand Down Expand Up @@ -349,12 +353,12 @@ private function handleSparqlUpdate(Response $response, string $path, $contents)
$output = $graph->serialise("turtle"); // FIXME: Use enums from namespace Pdsinterop\Rdf\Enum\Format?
// write ttl data

if ($filesystem->has($path) === true) {
$success = $filesystem->update($path, $output);
} else {
$success = $filesystem->write($path, $output);
try {
$filesystem->write($path, $output, []);
$success = true;
} catch (\Exception $e) {
$success = false;
}

$response = $response->withStatus($success ? 201 : 500);

if ($success) {
Expand Down Expand Up @@ -446,7 +450,7 @@ private function handleN3Update(Response $response, string $path, $contents): Re
$graph = $this->getGraph();
$n3Graph = $this->getGraph();

if ($filesystem->has($path) === false) {
if ($filesystem->fileExists($path) === false) {
$data = '';
} else {
// read ttl data
Expand Down Expand Up @@ -497,12 +501,12 @@ private function handleN3Update(Response $response, string $path, $contents): Re
$output = $graph->serialise("turtle"); // FIXME: Use enums from namespace Pdsinterop\Rdf\Enum\Format?
// write ttl data

if ($filesystem->has($path) === true) {
$success = $filesystem->update($path, $output);
} else {
$success = $filesystem->write($path, $output);
try {
$filesystem->write($path, $output, []);
$success = true;
} catch (\Exception $e) {
$success = false;
}

$response = $response->withStatus($success ? 201 : 500);

if ($success) {
Expand All @@ -524,26 +528,27 @@ private function handleN3Update(Response $response, string $path, $contents): Re
private function handleCreateRequest(Response $response, string $path, $contents): Response
{
$filesystem = $this->filesystem;

if ($filesystem->has($path) === true) {
if ($filesystem->fileExists($path) === true) {
$message = vsprintf(self::ERROR_PUT_EXISTING_RESOURCE, [$path]);
$response->getBody()->write($message);
$response = $response->withStatus(400);
} else {
$success = false;

set_error_handler(static function ($severity, $message, $filename, $line) {
throw new \ErrorException($message, 0, $severity, $filename, $line);
});

try {
$success = $filesystem->write($path, $contents);
$filesystem->write($path, $contents, []);
$success = true;
} catch (FileExistsException $e) {
$success = false;
$message = vsprintf(self::ERROR_PUT_EXISTING_RESOURCE, [$path]);
$response->getBody()->write($message);

return $response->withStatus(400);
} catch (Throwable $exception) {
$success = false;
/*/ An error occurred in the underlying flysystem adapter /*/
$message = vsprintf('Could not write to path %s: %s', [$path, $exception->getMessage()]);
$response->getBody()->write($message);
Expand All @@ -552,7 +557,6 @@ private function handleCreateRequest(Response $response, string $path, $contents
} finally {
restore_error_handler();
}

if ($success) {
$this->removeLinkFromMetaFileFor($path);
$response = $response->withHeader("Location", $this->baseUrl . $path);
Expand Down Expand Up @@ -582,12 +586,17 @@ private function parentPath($path)
private function handleCreateDirectoryRequest(Response $response, string $path): Response
{
$filesystem = $this->filesystem;
if ($filesystem->has($path) === true) {
if ($filesystem->fileExists($path) === true) {
$message = vsprintf(self::ERROR_PUT_EXISTING_RESOURCE, [$path]);
$response->getBody()->write($message);
$response = $response->withStatus(400);
} else {
$success = $filesystem->createDir($path);
try {
$filesystem->createDirectory($path);
$success = true;
} catch (\Exception $e) {
$success = false;
}
$response = $response->withStatus($success ? 201 : 500);
if ($success) {
$this->removeLinkFromMetaFileFor($path);
Expand Down Expand Up @@ -617,8 +626,8 @@ private function handleDeleteRequest(Response $response, string $path, $contents
{
$filesystem = $this->filesystem;

if ($filesystem->has($path)) {
$mimetype = $filesystem->getMimetype($path);
if ($filesystem->fileExists($path)) {
$mimetype = $filesystem->mimeType($path);

if ($mimetype === self::MIME_TYPE_DIRECTORY) {
$directoryContents = $filesystem->listContents($path, true);
Expand All @@ -627,15 +636,26 @@ private function handleDeleteRequest(Response $response, string $path, $contents
$message = vsprintf(self::ERROR_CAN_NOT_DELETE_NON_EMPTY_CONTAINER, [$path]);
$response->getBody()->write($message);
} else {
$success = $filesystem->deleteDir($path);
try {
$filesystem->deleteDirectory($path);
$success = true;
} catch (\Exception $e) {
$success = false;
}
if ($success) {
$this->sendNotificationUpdate($path, self::NOTIFICATION_TYPE_DELETE);
}

$status = $success ? 204 : 500;
}
} else {
$success = $filesystem->delete($path);
try {
$filesystem->delete($path);
$success = true;
} catch (\Exception $e) {
$success = false;
}

if ($success) {
$this->sendNotificationUpdate($path, self::NOTIFICATION_TYPE_DELETE);
}
Expand All @@ -656,12 +676,17 @@ private function handleUpdateRequest(Response $response, string $path, string $c
{
$filesystem = $this->filesystem;

if ($filesystem->has($path) === false) {
if ($filesystem->fileExists($path) === false) {
$message = vsprintf(self::ERROR_PUT_NON_EXISTING_RESOURCE, [$path]);
$response->getBody()->write($message);
$response = $response->withStatus(400);
} else {
$success = $filesystem->update($path, $contents);
try {
$filesystem->write($path, $contents, []);
$success = true;
} catch (\Exception $e) {
$success = false;
}
$response = $response->withStatus($success ? 201 : 500);
if ($success) {
$this->removeLinkFromMetaFileFor($path);
Expand Down Expand Up @@ -707,12 +732,13 @@ private function handleReadRequest(Response $response, string $path, $contents,
'<http://www.w3.org/ns/pim/space#Storage>; rel="type"',
]);
$response = $response->withStatus(200);
} elseif(($filesystem->has($path) === false) && (($path == ".meta") || ($path == "/.meta"))) {
} elseif(($filesystem->fileExists($path) === false) && (($path == ".meta") || ($path == "/.meta"))) {
$contents = '';
$response->getBody()->write($contents);
$response = $response->withHeader("Content-type", "text/turtle");
$response = $response->withStatus(200);
} elseif ($filesystem->has($path) === false && $this->hasDescribedBy($path) === false) {
} elseif ($filesystem->fileExists($path) === false) { // FIXME: Check with @potherca how this is supposed to work. Removed the hasDescribedBy for now.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be for a usecase where a resource does not exist, but metadata about the resource does?

I just can't think of a situation where that would be the case... Other than maybe metadata having been written for a folder, without the folder having been avtually created?

I can check the spec to see if there is mention of this usecase, but it currently looks like either overhead or a very very edge case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. Was this perhaps the tombstone for the migrator?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be? Not sure whether to leave this in just in case, or leave it out. 🤔

// && $this->hasDescribedBy($path) === false) {
/*/ The file does not exist and no link-metadata is present /*/
$message = vsprintf(self::ERROR_PATH_DOES_NOT_EXIST, [$path]);
$response->getBody()->write($message);
Expand All @@ -722,33 +748,37 @@ private function handleReadRequest(Response $response, string $path, $contents,
if ($linkMetadataResponse !== null) {
/*/ Link-metadata is present, return the altered response /*/
$response = $linkMetadataResponse;
} elseif ($filesystem->getMimetype($path) === self::MIME_TYPE_DIRECTORY) {
} elseif ($filesystem->mimeType($path) === self::MIME_TYPE_DIRECTORY) {
$contents = $this->listDirectoryAsTurtle($path);
$response->getBody()->write($contents);
$response = $response->withHeader("Content-type", "text/turtle")->withStatus(200);
} elseif ($filesystem->asMime($mime)->has($path)) {
} elseif ((true | $this->adapter->setMimeFormat($mime)) && $filesystem->fileExists($path)) {
/*/ The file does exist and no link-metadata is present /*/
$response = $this->addLinkRelationHeaders($response, $path, $mime);

if (preg_match('/\.(acl|meta|ttl)$/', $path)) {
$mimetype = "text/turtle"; // FIXME: teach flysystem that .acl/.meta/.ttl means text/turtle
} else {
$mimetype = $filesystem->asMime($mime)->getMimetype($path);
$this->adapter->setMimeFormat($mime);
$mimetype = $filesystem->mimeType($path);
}

$fileMimeType = $filesystem->asMime('')->getMimetype($path);
$this->adapter->setMimeFormat('');
$fileMimeType = $filesystem->mimeType($path);
if (in_array($fileMimeType, $acceptedMimeTypes)) {
$contents = $filesystem->asMime('')->read($path);
$this->adapter->setMimeFormat('');
$contents = $filesystem->read($path);
$mimetype = $fileMimeType;
} else {
$contents = $filesystem->asMime($mime)->read($path);
$this->adapter->setMimeFormat($mime);
$contents = $filesystem->read($path);
}

if ($contents !== false) {
$response->getBody()->write($contents);
$response = $response->withHeader("Content-type", $mimetype)->withStatus(200);
} else {
// FIXME: we should not get here if the file does not exist, but here we are. It looks like $filesystem->has("/.meta") always returns true even if the file does not exist;
// FIXME: we should not get here if the file does not exist, but here we are. It looks like $filesystem->fileExists("/.meta") always returns true even if the file does not exist;
if ($path == "/.meta") {
$contents = '';
$response->getBody()->write($contents);
Expand Down Expand Up @@ -920,14 +950,15 @@ private function getMetadata(string $path, $mime) : array
static $metadataCache = [];

if (isset($metadataCache[$path]) === false) {
$filesystem = $this->filesystem;

try {
if ($mime) {
$metadata = $filesystem->asMime($mime)->getMetadata($path);
$this->adapter->setMimeFormat($mime);
$fileAttributes = $this->adapter->fileSize($path);
$this->adapter->setMimeFormat('');
} else {
$metadata = $filesystem->getMetadata($path);
$fileAttributes = $this->adapter->fileSize($path);
}
$metadata = $fileAttributes->extraMetaData();
} catch (FileNotFoundException $e) {
$metadata = [];
}
Expand All @@ -945,6 +976,9 @@ private function hasAcl(string $path, $mime = null): bool

private function hasDescribedBy(string $path, $mime = null): bool
{
if ($this->adapter->fileExists($path) === false) {
return false;
}
return $this->getDescribedByPath($path, $mime) !== '';
}

Expand Down Expand Up @@ -1005,7 +1039,7 @@ private function parseLinkedMetadata(string $path)
$linkMeta = [];

try {
$describedByPath = $this->filesystem->getMetadata($path)['describedby'] ?? '';
$describedByPath = $this->adapter->fileSize($path)->extraMetaData()['describedby'] ?? '';
$describedByContents = $this->filesystem->read($describedByPath);
} catch (FileNotFoundException $e) {
// If, for whatever reason, the file is not present after all, the resource should still be returned (or a 404)
Expand Down Expand Up @@ -1090,7 +1124,7 @@ private function findPath(array $rdfPaths, string $path)
foreach ($rdfPaths as $rdfPath) {
if (
strrpos($path, $rdfPath) === 0
&& $this->filesystem->has($rdfPath)
&& $this->filesystem->fileExists($rdfPath)
) {
// @FIXME: We have no way of knowing if the file is a directory or a file.
// This means that, unless we make a trialing slash `/` required,
Expand Down Expand Up @@ -1151,7 +1185,8 @@ private function removeLinkFromMetaFileFor($path): bool
if ($changed) {
$changedContents = $graph->serialise('turtle');
try {
$result = $this->filesystem->update($describedByPath, $changedContents);
$this->filesystem->write($describedByPath, $changedContents, []);
$result = true;
} catch (FileNotFoundException $exception) {
// $result is already false;
}
Expand Down
Loading
Loading