simple-oai-pmh/oai2server.php

364 lines
14 KiB
PHP

<?php
require_once('oai2exception.php');
require_once('oai2xml.php');
/**
* This is an implementation of OAI Data Provider version 2.0.
* @see http://www.openarchives.org/OAI/2.0/openarchivesprotocol.htm
*/
class OAI2Server {
public $errors = array();
private $args = array();
private $verb = '';
private $token_prefix = '/tmp/oai_pmh-';
private $token_valid = 86400;
function __construct($uri, $args, $identifyResponse, $callbacks) {
$this->uri = $uri;
if (!isset($args['verb']) || empty($args['verb'])) {
$this->errors[] = new OAI2Exception('badVerb');
} else {
$verbs = array('Identify', 'ListMetadataFormats', 'ListSets', 'ListIdentifiers', 'ListRecords', 'GetRecord');
if (in_array($args['verb'], $verbs)) {
$this->verb = $args['verb'];
unset($args['verb']);
$this->args = $args;
$this->identifyResponse = $identifyResponse;
$this->listMetadataFormatsCallback = $callbacks['ListMetadataFormats'];
$this->listSetsCallback = $callbacks['ListSets'];
$this->listRecordsCallback = $callbacks['ListRecords'];
$this->getRecordCallback = $callbacks['GetRecord'];
$this->response = new OAI2XMLResponse($this->uri, $this->verb, $this->args);
call_user_func(array($this, $this->verb));
} else {
$this->errors[] = new OAI2Exception('badVerb', $args['verb']);
}
}
}
public function response() {
if (empty($this->errors)) {
return $this->response->doc;
} else {
$errorResponse = new OAI2XMLResponse($this->uri, $this->verb, $this->args);
$oai_node = $errorResponse->doc->documentElement;
foreach($this->errors as $e) {
$node = $errorResponse->addChild($oai_node,"error",$e->getMessage());
$node->setAttribute("code",$e->getOAI2Code());
}
return $errorResponse->doc;
}
}
public function Identify() {
if (count($this->args) > 0) {
foreach($this->args as $key => $val) {
$this->errors[] = new OAI2Exception('badArgument', $key, $val);
}
} else {
foreach($this->identifyResponse as $key => $val) {
$this->response->addToVerbNode($key, $val);
}
}
}
public function ListMetadataFormats() {
foreach ($this->args as $argument => $value) {
if ($argument != 'identifier') {
$this->errors[] = new OAI2Exception('badArgument', $argument, $value);
}
}
if (isset($this->args['identifier'])) {
$identifier = $this->args['identifier'];
} else {
$identifier = '';
}
if (empty($this->errors)) {
try {
if ($formats = call_user_func($this->listMetadataFormatsCallback, $identifier)) {
foreach($formats as $key => $val) {
$cmf = $this->response->addToVerbNode("metadataFormat");
$this->response->addChild($cmf,'metadataPrefix',$key);
$this->response->addChild($cmf,'schema',$val['schema']);
$this->response->addChild($cmf,'metadataNamespace',$val['metadataNamespace']);
}
} else {
$this->errors[] = new OAI2Exception('noMetadataFormats');
}
} catch (OAI2Exception $e) {
$this->errors[] = $e;
}
}
}
public function ListSets() {
if (isset($this->args['resumptionToken'])) {
if (count($this->args) > 1) {
$this->errors[] = new OAI2Exception('exclusiveArgument');
} else {
if ((int)$val+$this->token_valid < time()) {
$this->errors[] = new OAI2Exception('badResumptionToken');
}
}
$resumptionToken = $this->args['resumptionToken'];
} else {
$resumptionToken = null;
}
if (empty($this->errors)) {
if ($sets = call_user_func($this->listSetsCallback, $resumptionToken)) {
foreach($sets as $set) {
$setNode = $this->response->addToVerbNode("set");
foreach($set as $key => $val) {
if($key=='setDescription') {
$desNode = $this->response->addChild($setNode,$key);
$des = $this->response->doc->createDocumentFragment();
$des->appendXML($val);
$desNode->appendChild($des);
} else {
$this->response->addChild($setNode,$key,$val);
}
}
}
} else {
$this->errors[] = new OAI2Exception('noSetHierarchy');
}
}
}
public function GetRecord() {
if (!isset($this->args['metadataPrefix'])) {
$this->errors[] = new OAI2Exception('missingArgument', 'metadataPrefix');
} else {
$metadataFormats = call_user_func($this->listMetadataFormatsCallback);
if (!isset($metadataFormats[$this->args['metadataPrefix']])) {
$this->errors[] = new OAI2Exception('cannotDisseminateFormat', 'metadataPrefix', $this->args['metadataPrefix']);
}
}
if (!isset($this->args['identifier'])) {
$this->errors[] = new OAI2Exception('missingArgument', 'identifier');
}
if (empty($this->errors)) {
try {
if ($record = call_user_func($this->getRecordCallback, $this->args['identifier'], $this->args['metadataPrefix'])) {
$identifier = $record['identifier'];
$datestamp = $this->formatDatestamp($record['datestamp']);
$set = $record['set'];
$status_deleted = (isset($record['deleted']) && ($record['deleted'] == 'true') &&
(($this->identifyResponse['deletedRecord'] == 'transient') ||
($this->identifyResponse['deletedRecord'] == 'persistent')));
$cur_record = $this->response->addToVerbNode('record');
$cur_header = $this->response->createHeader($identifier, $datestamp, $set, $cur_record);
if ($status_deleted) {
$cur_header->setAttribute("status","deleted");
} else {
$this->add_metadata($cur_record, $record);
}
} else {
$this->errors[] = new OAI2Exception('idDoesNotExist', 'identifier', $identifier);
}
} catch (OAI2Exception $e) {
$this->errors[] = $e;
}
}
}
public function ListIdentifiers() {
return $this->ListRecords();
}
public function ListRecords() {
$maxItems = 1000;
$deliveredRecords = 0;
$metadataPrefix = $this->args['metadataPrefix'];
$from = isset($this->args['from']) ? $this->args['from'] : '';
$until = isset($this->args['until']) ? $this->args['until'] : '';
$set = isset($this->args['set']) ? $this->args['set'] : '';
if (isset($this->args['resumptionToken'])) {
if (count($this->args) > 1) {
$this->errors[] = new OAI2Exception('exclusiveArgument');
} else {
if ((int)$val+$this->token_valid < time()) {
$this->errors[] = new OAI2Exception('badResumptionToken');
} else {
if (!file_exists($this->token_prefix.$this->args['resumptionToken'])) {
$this->errors[] = new OAI2Exception('badResumptionToken', '', $this->args['resumptionToken']);
} else {
if ($readings = $this->readResumptionToken($this->token_prefix.$this->args['resumptionToken'])) {
list($deliveredRecords, $metadataPrefix, $from, $until, $set) = $readings;
} else {
$this->errors[] = new OAI2Exception('badResumptionToken', '', $this->args['resumptionToken']);
}
}
}
}
} else {
if (!isset($this->args['metadataPrefix'])) {
$this->errors[] = new OAI2Exception('missingArgument', 'metadataPrefix');
} else {
$metadataFormats = call_user_func($this->listMetadataFormatsCallback);
if (!isset($metadataFormats[$this->args['metadataPrefix']])) {
$this->errors[] = new OAI2Exception('cannotDisseminateFormat', 'metadataPrefix', $this->args['metadataPrefix']);
}
}
if (isset($this->args['from'])) {
if(!$this->checkDateFormat($this->args['from'])) {
$this->errors[] = new OAI2Exception('badGranularity', 'from', $this->args['from']);
}
}
if (isset($this->args['until'])) {
if(!$this->checkDateFormat($this->args['until'])) {
$this->errors[] = new OAI2Exception('badGranularity', 'until', $this->args['until']);
}
}
}
if (empty($this->errors)) {
try {
$records_count = call_user_func($this->listRecordsCallback, $metadataPrefix, $from, $until, $set, true);
$records = call_user_func($this->listRecordsCallback, $metadataPrefix, $from, $until, $set, false, $deliveredRecords, $maxItems);
foreach ($records as $record) {
$identifier = $record['identifier'];
$datestamp = $this->formatDatestamp($record['datestamp']);
$setspec = $record['set'];
$status_deleted = (isset($record['deleted']) && ($record['deleted'] === true) &&
(($this->identifyResponse['deletedRecord'] == 'transient') ||
($this->identifyResponse['deletedRecord'] == 'persistent')));
if($this->verb == 'ListRecords') {
$cur_record = $this->response->addToVerbNode('record');
$cur_header = $this->response->createHeader($identifier, $datestamp,$setspec,$cur_record);
if (!$status_deleted) {
$this->add_metadata($cur_record, $record);
}
} else { // for ListIdentifiers, only identifiers will be returned.
$cur_header = $this->response->createHeader($identifier, $datestamp,$setspec);
}
if ($status_deleted) {
$cur_header->setAttribute("status","deleted");
}
}
// Will we need a new ResumptionToken?
if ($records_count - $deliveredRecords > $maxItems) {
$deliveredRecords += $maxItems;
$restoken = $this->createResumptionToken($deliveredRecords);
$expirationDatetime = gmstrftime('%Y-%m-%dT%TZ', time()+$this->token_valid);
} elseif (isset($args['resumptionToken'])) {
// Last delivery, return empty ResumptionToken
$restoken = null;
$expirationDatetime = null;
}
if (isset($restoken)) {
$this->response->createResumptionToken($restoken,$expirationDatetime,$records_count,$deliveredRecords);
}
} catch (OAI2Exception $e) {
$this->errors[] = $e;
}
}
}
private function add_metadata($cur_record, $record) {
$meta_node = $this->response->addChild($cur_record ,"metadata");
$schema_node = $this->response->addChild($meta_node, $record['metadata']['container_name']);
foreach ($record['metadata']['container_attributes'] as $name => $value) {
$schema_node->setAttribute($name, $value);
}
foreach ($record['metadata']['fields'] as $name => $value) {
$this->response->addChild($schema_node, $name, $value);
}
}
private function createResumptionToken($delivered_records) {
list($usec, $sec) = explode(" ", microtime());
$token = ((int)($usec*1000) + (int)($sec*1000));
$fp = fopen ($this->token_prefix.$token, 'w');
if($fp==false) {
exit("Cannot write. Writer permission needs to be changed.");
}
fputs($fp, "$delivered_records#");
fputs($fp, "$metadataPrefix#");
fputs($fp, "{$this->args['from']}#");
fputs($fp, "{$this->args['until']}#");
fputs($fp, "{$this->args['set']}#");
fclose($fp);
return $token;
}
private function readResumptionToken($resumptionToken) {
$rtVal = false;
$fp = fopen($resumptionToken, 'r');
if ($fp != false) {
$filetext = fgets($fp, 255);
$textparts = explode('#', $filetext);
fclose($fp);
unlink($resumptionToken);
$rtVal = array_values($textparts);
}
return $rtVal;
}
/**
* All datestamps used in this system are GMT even
* return value from database has no TZ information
*/
private function formatDatestamp($datestamp) {
return date("Y-m-d\TH:i:s\Z",strtotime($datestamp));
}
/**
* The database uses datastamp without time-zone information.
* It needs to clean all time-zone informaion from time string and reformat it
*/
private function checkDateFormat($date) {
$date = str_replace(array("T","Z")," ",$date);
$time_val = strtotime($date);
if(!$time_val) return false;
if(strstr($date,":")) {
return date("Y-m-d H:i:s",$time_val);
} else {
return date("Y-m-d",$time_val);
}
}
}