Overview

I tried outputting Delete records with the Omeka S OAI-PMH Repository module, so this is a personal note for future reference.

Background

By using the following module, you can build OAI-PMH repository functionality.

https://omeka.org/s/modules/OaiPmhRepository/

However, as far as I could confirm, there did not appear to be a feature for outputting Delete records.

Omeka’s standard features do not seem to include functionality for storing deleted resources.

On the other hand, the following module adds functionality to retain deleted resources.

https://github.com/biblibre/omeka-s-module-Necropolis

By enabling this module, it became possible to record when and by whom a resource was deleted, as shown below.

Application to the OAI-PMH Repository Module

Using the table that stores information about deleted resources created by the above module, I attempt to output Delete records.

I add code to the listResponse function in the following file.

    private function listResponse($verb, $metadataPrefix, $cursor, $set, $from, $until): void
    {
        /**
         * @var \Omeka\Api\Adapter\Manager $apiAdapterManager
         * @var \Doctrine\ORM\EntityManager $entityManager
         */
        $apiAdapterManager = $this->serviceLocator->get('Omeka\ApiAdapterManager');
        $entityManager = $this->serviceLocator->get('Omeka\EntityManager');

        $itemRepository = $entityManager->getRepository(\Omeka\Entity\Item::class);
        $qb = $itemRepository->createQueryBuilder('omeka_root');
        $qb->select('omeka_root');

        $query = new ArrayObject;
        $expr = $qb->expr();

        // Add the following

        if ($set === 'o:deleted') {
            $settings = $this->serviceLocator->get('Omeka\Settings');
            $namespaceId = $settings->get('oaipmhrepository_namespace_id', 'default_namespace');

            // Get deleted records from the necropolis_resource table
            $deletedResourceRepository = $entityManager->getRepository(\Necropolis\Entity\NecropolisResource::class);  // Custom entity

            // Separate media and item retrieval based on oaipmhrepository_expose_media
            $exposeMedia = $settings->get('oaipmhrepository_expose_media', false);  // Default is false (items only)

            if ($exposeMedia) {
                $qb = $deletedResourceRepository->createQueryBuilder('necropolis_resource');
            } else {
                // Get only Items
                $qb = $deletedResourceRepository->createQueryBuilder('necropolis_resource')
                    ->andWhere('necropolis_resource.resourceType = :itemType')
                    ->setParameter('itemType', 'Omeka\Entity\Item');
            }

            // Date filtering
            if ($from) {
                $qb->andWhere($expr->gte('necropolis_resource.deleted', ':from'));
                $qb->setParameter('from', $from);
            }
            if ($until) {
                $qb->andWhere($expr->lte('necropolis_resource.deleted', ':until'));
                $qb->setParameter('until', $until);
            }

            // Result limit and offset
            $qb->setMaxResults($this->_listLimit);
            $qb->setFirstResult($cursor);

            $paginator = new Paginator($qb, false);
            $rows = count($paginator);

            if ($rows == 0) {
                $this->throwError(self::OAI_ERR_NO_RECORDS_MATCH, new Message('No records match the given criteria.')); // @translate
            } else {
                if ($verb == 'ListIdentifiers') {
                    $method = 'appendHeader';
                } elseif ($verb == 'ListRecords') {
                    $method = 'appendRecord';
                }

                $verbElement = $this->document->createElement($verb);
                $this->document->documentElement->appendChild($verbElement);

                foreach ($paginator as $deletedEntity) {
                    // Add deleted resource information to the OAI-PMH response
                    $header = $this->document->createElement('header');
                    $header->setAttribute('status', 'deleted');  // Set as deleted record

                    $identifier = $this->document->createElement('identifier', 'oai:' . $namespaceId . ":" . $deletedEntity->getId());
                    $header->appendChild($identifier);

                    $datestamp = $this->document->createElement('datestamp', $deletedEntity->getDeleted()->format('Y-m-d\TH:i:s\Z'));
                    $header->appendChild($datestamp);

                    $verbElement->appendChild($header);
                }

                // Resumption Token processing
                if ($rows > ($cursor + $this->_listLimit)) {
                    $token = $this->createResumptionToken($verb, $metadataPrefix,
                        $cursor + $this->_listLimit, $set, $from, $until);

                    $tokenElement = $this->document->createElement('resumptionToken', (string) $token->id());
                    $tokenElement->setAttribute('expirationDate', $token->expiration()->format('Y-m-d\TH:i:s\Z'));
                    $tokenElement->setAttribute('completeListSize', (string) $rows);
                    $tokenElement->setAttribute('cursor', (string) $cursor);
                    $verbElement->appendChild($tokenElement);
                } elseif ($cursor != 0) {
                    $tokenElement = $this->document->createElement('resumptionToken');
                    $verbElement->appendChild($tokenElement);
                }
            }

            return;
        }

...

This implementation does not conform to the OAI-PMH standard, but by specifying o:deleted as the set, it can return delete records.

For example, the following. XSL support and other improvements are future tasks.

https://omeka.aws.ldas.jp/oai?verb=ListRecords&metadataPrefix=oai_dc&set=o:deleted&from=2024-01-01

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="/modules/OaiPmhRepository/asset/xsl/oai-pmh-repository.xsl?v=3.4.9"?>
<OAI-PMH xmlns="http://www.openarchives.org/OAI/2.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd">
  <responseDate>2024-10-10T07:50:56Z</responseDate>
  <request verb="ListRecords" metadataPrefix="oai_dc" set="o:deleted" from="2024-01-01">https://omeka.aws.ldas.jp/oai</request>
  <ListRecords>
    <header status="deleted">
      <identifier>oai:omeka.aws.ldas.jp:41</identifier>
      <datestamp>2024-10-10T07:06:01Z</datestamp>
    </header>
  </ListRecords>
</OAI-PMH>

I also confirmed that resumptionToken works correctly.

Summary

By refining the above implementation, it may be possible to output delete records in a way that conforms to the OAI-PMH standard.

There are various points that have not been verified, but I hope this serves as a useful reference.