diff --git a/src/Flysystem/Adapter/FedoraAdapter.php b/src/Flysystem/Adapter/FedoraAdapter.php index 8fd91ca3..03900753 100644 --- a/src/Flysystem/Adapter/FedoraAdapter.php +++ b/src/Flysystem/Adapter/FedoraAdapter.php @@ -283,8 +283,14 @@ class FedoraAdapter implements AdapterInterface { */ public function delete($path) { $response = $this->fedora->deleteResource($path); - $code = $response->getStatusCode(); + if ($code == 204) { + // Deleted so check for a tombstone as well. + $tomb_code = $this->deleteTombstone($path); + if (!is_null($tomb_code)) { + return $tomb_code; + } + } return in_array($code, [204, 404]); } @@ -321,4 +327,36 @@ class FedoraAdapter implements AdapterInterface { return $this->getMetadata($dirname); } + /** + * Delete a tombstone for a path if it exists. + * + * @param string $path + * The original deleted resource path. + * + * @return bool|null + * NULL if no tombstone, TRUE if tombstone deleted, FALSE otherwise. + */ + private function deleteTombstone($path) { + $response = $this->fedora->getResourceHeaders($path); + $return = NULL; + if ($response->getStatusCode() == 410) { + $return = FALSE; + $link_headers = Psr7\parse_header($response->getHeader('Link')); + if ($link_headers) { + $tombstones = array_filter($link_headers, function ($o) { + return (isset($o['rel']) && $o['rel'] == 'hasTombstone'); + }); + foreach ($tombstones as $tombstone) { + // Trim <> from URL. + $url = rtrim(ltrim($tombstone[0], '<'), '>'); + $response = $this->fedora->deleteResource($url); + if ($response->getStatusCode() == 204) { + $return = TRUE; + } + } + } + } + return $return; + } + } diff --git a/tests/src/Kernel/FedoraAdapterTest.php b/tests/src/Kernel/FedoraAdapterTest.php index d16cd798..abff8eea 100644 --- a/tests/src/Kernel/FedoraAdapterTest.php +++ b/tests/src/Kernel/FedoraAdapterTest.php @@ -185,6 +185,60 @@ class FedoraAdapterTest extends IslandoraKernelTestBase { return new FedoraAdapter($api, $mime_guesser); } + /** + * Mocks up an adapter for Delete requests with a tombstone. + */ + protected function createAdapterForDeleteWithTombstone() { + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(204); + + $head_prophecy = $this->prophesize(Response::class); + $head_prophecy->getStatusCode()->willReturn(410); + $head_prophecy->getHeader('Link')->willReturn('; rel="hasTombstone"'); + + $tombstone_prophecy = $this->prophesize(Response::class); + $tombstone_prophecy->getStatusCode()->willReturn(204); + + $fedora_prophecy->deleteResource('')->willReturn($prophecy->reveal()); + $fedora_prophecy->getResourceHeaders('')->willReturn($head_prophecy->reveal()); + $fedora_prophecy->deleteResource('some-path-to-a-tombstone')->willReturn($tombstone_prophecy->reveal()); + + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + + /** + * Mocks up an adapter for Delete requests with a tombstone which fail. + */ + protected function createAdapterForDeleteWithTombstoneFail() { + $fedora_prophecy = $this->prophesize(IFedoraApi::class); + + $prophecy = $this->prophesize(Response::class); + $prophecy->getStatusCode()->willReturn(204); + + $head_prophecy = $this->prophesize(Response::class); + $head_prophecy->getStatusCode()->willReturn(410); + $head_prophecy->getHeader('Link')->willReturn('; rel="hasTombstone"'); + + $tombstone_prophecy = $this->prophesize(Response::class); + $tombstone_prophecy->getStatusCode()->willReturn(500); + + $fedora_prophecy->deleteResource('')->willReturn($prophecy->reveal()); + $fedora_prophecy->getResourceHeaders('')->willReturn($head_prophecy->reveal()); + $fedora_prophecy->deleteResource('some-path-to-a-tombstone')->willReturn($tombstone_prophecy->reveal()); + + $api = $fedora_prophecy->reveal(); + + $mime_guesser = $this->prophesize(MimeTypeGuesserInterface::class)->reveal(); + + return new FedoraAdapter($api, $mime_guesser); + } + /** * Asserts the stucture/contents of a metadata response for a file. */ @@ -459,6 +513,24 @@ class FedoraAdapterTest extends IslandoraKernelTestBase { $this->assertTrue($adapter->deleteDir('') == TRUE, "deleteDir() must return TRUE on 204 or 404"); } + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::delete + */ + public function testDeleteWithTombstone() { + $adapter = $this->createAdapterForDeleteWithTombstone(); + + $this->assertTrue($adapter->delete(''), 'delete() must return TRUE on 204 or 404 reponse after deleting the tombstone.'); + } + + /** + * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::delete + */ + public function testDeleteWithTombstoneFail() { + $adapter = $this->createAdapterForDeleteWithTombstoneFail(); + + $this->assertFalse($adapter->delete(''), 'delete() must return FALSE on non-(204 or 404) response after deleting the tombstone.'); + } + /** * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::rename * @covers \Drupal\islandora\Flysystem\Adapter\FedoraAdapter::copy