Support deletions by keeping 0-byte files
This commit is contained in:
parent
e1f59e2c66
commit
a46738633d
|
@ -1,7 +1,7 @@
|
|||
Simple OAI-PMH 2.0 Data Provider
|
||||
================================
|
||||
|
||||
This is a stand-alone and easy to install data provider for the [Open Archives Initiative's Protocol for Metadata Harvesting (OAI-PMH)](http://openarchives.org/pmh/) written in [PHP](http://php.net/). It serves records in any metadata format from a directory of XML files using the filename as identifier and the filemtime as datestamp. Resumption tokens are managed using files. Multiple metadata formats and sets are currently not supported.
|
||||
This is a stand-alone and easy to install data provider for the [Open Archives Initiative's Protocol for Metadata Harvesting (OAI-PMH)](http://openarchives.org/pmh/) written in [PHP](http://php.net/). It serves records in any metadata format from a directory of XML files using the filename as identifier and the filemtime as datestamp. 0-byte files are considered deleted records and handled accordingly. Resumption tokens are managed using files. Multiple metadata formats and sets are currently not supported.
|
||||
|
||||
Just put the records as XML files in the data directory, adjust a few configuration settings and you are ready to go!
|
||||
|
||||
|
@ -16,6 +16,8 @@ Installation
|
|||
|
||||
3. Put the records into the specified data directory. Each record has to be a separate XML file with its identifier as filename (i.e. 12345678.xml).
|
||||
|
||||
3a. Optionally you can maintain deletions by keeping 0-byte files in the data directory for deleted records.
|
||||
|
||||
4. Congratulations! Now you are running an OAI-PMH 2.0 compatible data provider.
|
||||
|
||||
History
|
||||
|
|
12
index.php
12
index.php
|
@ -23,13 +23,15 @@
|
|||
require_once('oai2config.php');
|
||||
require_once('oai2server.php');
|
||||
|
||||
// Get all available records and their respective timestamps
|
||||
// Get all available records and their respective status and timestamps
|
||||
$records = array();
|
||||
$deleted = array();
|
||||
$timestamps = array();
|
||||
|
||||
$files = glob(rtrim($config['dataDirectory'], '/').'/*.xml');
|
||||
foreach($files as $file) {
|
||||
$records[pathinfo($file, PATHINFO_FILENAME)] = $file;
|
||||
$deleted[pathinfo($file, PATHINFO_FILENAME)] = !filesize($file);
|
||||
$timestamps[filemtime($file)][] = pathinfo($file, PATHINFO_FILENAME);
|
||||
};
|
||||
|
||||
|
@ -54,7 +56,7 @@ $identifyResponse = array(
|
|||
'protocolVersion' => '2.0',
|
||||
'adminEmail' => $config['adminEmail'],
|
||||
'earliestDatestamp' => gmdate('Y-m-d\TH:i:s\Z', key($timestamps)),
|
||||
'deletedRecord' => 'no',
|
||||
'deletedRecord' => $config['deletedRecord'],
|
||||
'granularity' => 'YYYY-MM-DDThh:mm:ssZ'
|
||||
);
|
||||
|
||||
|
@ -65,20 +67,21 @@ $oai2 = new OAI2Server(
|
|||
array(
|
||||
'GetRecord' =>
|
||||
function($identifier, $metadataPrefix) {
|
||||
global $records;
|
||||
global $records, $deleted;
|
||||
if (empty($records[$identifier])) {
|
||||
return array();
|
||||
} else {
|
||||
return array(
|
||||
'identifier' => $identifier,
|
||||
'timestamp' => filemtime($records[$identifier]),
|
||||
'deleted' => $deleted[$identifier],
|
||||
'metadata' => $records[$identifier]
|
||||
);
|
||||
}
|
||||
},
|
||||
'ListRecords' =>
|
||||
function($metadataPrefix, $from = null, $until = null, $count = false, $deliveredRecords = 0, $maxItems = 100) {
|
||||
global $records, $timestamps;
|
||||
global $records, $deleted, $timestamps;
|
||||
$resultSet = array();
|
||||
foreach($timestamps as $timestamp => $identifiers) {
|
||||
if ((is_null($from) || $timestamp >= $from) && (is_null($until) || $timestamp <= $until)) {
|
||||
|
@ -86,6 +89,7 @@ $oai2 = new OAI2Server(
|
|||
$resultSet[] = array(
|
||||
'identifier' => $identifier,
|
||||
'timestamp' => filemtime($records[$identifier]),
|
||||
'deleted' => $deleted[$identifier],
|
||||
'metadata' => $records[$identifier]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
/**
|
||||
* This file contains all configuration you need to change according to your preferences
|
||||
* @see http://www.openarchives.org/OAI/2.0/openarchivesprotocol.htm for further explanation
|
||||
*/
|
||||
|
||||
$config = array();
|
||||
|
@ -32,20 +33,31 @@ $config['repositoryName'] = 'Simple OAI 2.0 Data Provider';
|
|||
// Email address for contacting the repository owner
|
||||
$config['adminEmail'] = 'admin@example.org';
|
||||
|
||||
// Do you provide 0-byte files for deleted records?
|
||||
// Possible values:
|
||||
// "no" -> the repository does not maintain information about deletions
|
||||
// "transient" -> the repository maintains information about deletions, but
|
||||
// does not guarantee them to be persistent (default)
|
||||
// "persistent" -> the repository maintains information about deletions with
|
||||
// no time limit
|
||||
$config['deletedRecord'] = 'transient';
|
||||
|
||||
// Metadata format, schema and namespace of your records
|
||||
// (The default is OAI_DC which is also required by the OAI-PMH specification,
|
||||
// but technically you can deliver any XML based data format you want.)
|
||||
$config['metadataFormat'] = 'oai_dc';
|
||||
$config['metadataSchema'] = 'http://www.openarchives.org/OAI/2.0/oai_dc.xsd';
|
||||
$config['metadataNamespace'] = 'http://www.openarchives.org/OAI/2.0/oai_dc/';
|
||||
|
||||
// Directory containing the records
|
||||
// (Make sure the given path is readable)
|
||||
// (Make sure the given path is readable.)
|
||||
$config['dataDirectory'] = 'data/';
|
||||
|
||||
// Maximum number of records to return before giving a resumption token
|
||||
$config['maxRecords'] = 100;
|
||||
|
||||
// Path and prefix for saving resumption tokens
|
||||
// (Make sure the given path is writable)
|
||||
// (Make sure the given path is writable.)
|
||||
$config['tokenPrefix'] = '/tmp/oai2-';
|
||||
|
||||
// Number of seconds a resumption token should be valid
|
||||
|
|
|
@ -32,9 +32,10 @@ class OAI2Server {
|
|||
public $errors = array();
|
||||
private $args = array();
|
||||
private $verb = '';
|
||||
private $deleted_record = 'transient';
|
||||
private $max_records = 100;
|
||||
private $token_prefix = '/tmp/oai2-';
|
||||
private $token_valid = 86400;
|
||||
private $max_records = 100;
|
||||
|
||||
public function __construct($uri, $args, $identifyResponse, $callbacks, $config) {
|
||||
$this->uri = $uri;
|
||||
|
@ -50,9 +51,10 @@ class OAI2Server {
|
|||
$this->listMetadataFormatsCallback = $callbacks['ListMetadataFormats'];
|
||||
$this->listRecordsCallback = $callbacks['ListRecords'];
|
||||
$this->getRecordCallback = $callbacks['GetRecord'];
|
||||
$this->deleted_record = $config['deletedRecord'];
|
||||
$this->max_records = $config['maxRecords'];
|
||||
$this->token_prefix = $config['tokenPrefix'];
|
||||
$this->token_valid = $config['tokenValid'];
|
||||
$this->max_records = $config['maxRecords'];
|
||||
$this->response = new OAI2XMLResponse($this->uri, $this->verb, $this->args);
|
||||
call_user_func(array($this, $this->verb));
|
||||
} else {
|
||||
|
@ -141,8 +143,10 @@ class OAI2Server {
|
|||
try {
|
||||
if ($record = call_user_func($this->getRecordCallback, $this->args['identifier'], $this->args['metadataPrefix'])) {
|
||||
$cur_record = $this->response->addToVerbNode('record');
|
||||
$cur_header = $this->response->createHeader($record['identifier'], $this->formatDatestamp($record['timestamp']), $cur_record);
|
||||
$this->add_metadata($cur_record, $record['metadata']);
|
||||
$cur_header = $this->response->createHeader($record['identifier'], $this->formatDatestamp($record['timestamp']), $record['deleted'], $cur_record);
|
||||
if (!$record['deleted']) {
|
||||
$this->addMetadata($cur_record, $record['metadata']);
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = new OAI2Exception('idDoesNotExist');
|
||||
}
|
||||
|
@ -210,12 +214,10 @@ class OAI2Server {
|
|||
}
|
||||
$records = call_user_func($this->listRecordsCallback, $metadataPrefix, $this->formatTimestamp($from), $this->formatTimestamp($until), false, $deliveredRecords, $maxItems);
|
||||
foreach ($records as $record) {
|
||||
if ($this->verb == 'ListRecords') {
|
||||
$cur_record = $this->response->addToVerbNode('record');
|
||||
$cur_header = $this->response->createHeader($record['identifier'], $this->formatDatestamp($record['timestamp']), $cur_record);
|
||||
$this->add_metadata($cur_record, $record['metadata']);
|
||||
} else { // for ListIdentifiers, only identifiers will be returned.
|
||||
$cur_header = $this->response->createHeader($record['identifier'], $this->formatDatestamp($record['timestamp']));
|
||||
$cur_record = $this->response->addToVerbNode('record');
|
||||
$cur_header = $this->response->createHeader($record['identifier'], $this->formatDatestamp($record['timestamp']), $record['deleted'], $cur_record);
|
||||
if (!$record['deleted'] && $this->verb == 'ListRecords') { // for ListIdentifiers, only identifiers will be returned.
|
||||
$this->addMetadata($cur_record, $record['metadata']);
|
||||
}
|
||||
}
|
||||
// Will we need a new ResumptionToken?
|
||||
|
@ -237,7 +239,7 @@ class OAI2Server {
|
|||
}
|
||||
}
|
||||
|
||||
private function add_metadata($cur_record, $file) {
|
||||
private function addMetadata($cur_record, $file) {
|
||||
$meta_node = $this->response->addChild($cur_record, 'metadata');
|
||||
$fragment = new DOMDocument();
|
||||
$fragment->load($file);
|
||||
|
|
12
oai2xml.php
12
oai2xml.php
|
@ -60,7 +60,7 @@ class OAI2XMLResponse {
|
|||
/**
|
||||
* Add direct child nodes to verb node (OAI-PMH), e.g. response to ListMetadataFormats.
|
||||
* Different verbs can have different required child nodes.
|
||||
* @see create_record, create_header
|
||||
* @see createHeader, importFragment
|
||||
*
|
||||
* @param $nodeName Type: string. The name of appending node.
|
||||
* @param $value Type: string. The content of appending node.
|
||||
|
@ -76,13 +76,14 @@ class OAI2XMLResponse {
|
|||
* Headers are enclosed inside of <record> to the query of ListRecords, ListIdentifiers and etc.
|
||||
*
|
||||
* @param $identifier Type: string. The identifier string for node <identifier>.
|
||||
* @param $timestamp Type: timestamp. Timestapme in UTC format for node <datastamp>.
|
||||
* @param $timestamp Type: timestamp. Timestamp in UTC format for node <datastamp>.
|
||||
* @param $deleted Type: boolean. Deleted status for the record.
|
||||
* @param $add_to_node Type: DOMElement. Default value is null.
|
||||
* In normal cases, $add_to_node is the <record> node created previously.
|
||||
* When it is null, the newly created header node is attatched to $this->verbNode.
|
||||
* Otherwise it will be attatched to the desired node defined in $add_to_node.
|
||||
* Otherwise it will be attached to the desired node defined in $add_to_node.
|
||||
*/
|
||||
function createHeader($identifier, $timestamp, $add_to_node = null) {
|
||||
function createHeader($identifier, $timestamp, $deleted = false, $add_to_node = null) {
|
||||
if(is_null($add_to_node)) {
|
||||
$header_node = $this->addToVerbNode('header');
|
||||
} else {
|
||||
|
@ -90,6 +91,9 @@ class OAI2XMLResponse {
|
|||
}
|
||||
$this->addChild($header_node, 'identifier', $identifier);
|
||||
$this->addChild($header_node, 'datestamp', $timestamp);
|
||||
if($deleted) {
|
||||
$header_node->setAttribute('status', 'deleted');
|
||||
}
|
||||
return $header_node;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue