first commit
This commit is contained in:
@@ -0,0 +1,890 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @defgroup oai OAI
|
||||
* Implements an OAI (Open Archives Initiative) OAI-PMH interface. See
|
||||
* http://www.openarchives.org for information on OAI-PMH.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAI.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAI
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAIDAO
|
||||
*
|
||||
* @brief Class to process and respond to OAI requests.
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
abstract class OAI
|
||||
{
|
||||
public const OAIRECORD_STATUS_DELETED = 0;
|
||||
public const OAIRECORD_STATUS_ALIVE = 1;
|
||||
|
||||
/** @var OAIConfig configuration parameters */
|
||||
public $config;
|
||||
|
||||
/** @var array list of request parameters */
|
||||
public $params;
|
||||
|
||||
/** @var string version of the OAI protocol supported by this class */
|
||||
public $protocolVersion = '2.0';
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* Initializes object and parses user input.
|
||||
*
|
||||
* @param OAIConfig $config repository configuration
|
||||
*/
|
||||
public function __construct($config)
|
||||
{
|
||||
$this->config = $config;
|
||||
|
||||
// Initialize parameters from GET or POST variables
|
||||
$this->params = [];
|
||||
|
||||
if (isset($GLOBALS['HTTP_RAW_POST_DATA']) && !empty($GLOBALS['HTTP_RAW_POST_DATA'])) {
|
||||
OAIUtils::parseStr($GLOBALS['HTTP_RAW_POST_DATA'], $this->params);
|
||||
} elseif (!empty($_SERVER['QUERY_STRING'])) {
|
||||
OAIUtils::parseStr($_SERVER['QUERY_STRING'], $this->params);
|
||||
} else {
|
||||
$this->params = array_merge($_GET, $_POST);
|
||||
}
|
||||
|
||||
// Clean input variables
|
||||
OAIUtils::prepInput($this->params);
|
||||
|
||||
// Encode data with gzip, deflate, or none, depending on browser support
|
||||
if (!ini_get('zlib.output_compression')) {
|
||||
@ob_start('ob_gzhandler');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the requested OAI protocol request
|
||||
* and output the response.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
switch ($this->getParam('verb')) {
|
||||
case 'GetRecord':
|
||||
$this->GetRecord();
|
||||
break;
|
||||
case 'Identify':
|
||||
$this->Identify();
|
||||
break;
|
||||
case 'ListIdentifiers':
|
||||
$this->ListIdentifiers();
|
||||
break;
|
||||
case 'ListMetadataFormats':
|
||||
$this->ListMetadataFormats();
|
||||
break;
|
||||
case 'ListRecords':
|
||||
$this->ListRecords();
|
||||
break;
|
||||
case 'ListSets':
|
||||
$this->ListSets();
|
||||
break;
|
||||
default:
|
||||
$this->error('badVerb', 'Illegal OAI verb');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Abstract implementation-specific functions
|
||||
// (to be overridden in subclass)
|
||||
//
|
||||
|
||||
/**
|
||||
* Return information about the repository.
|
||||
*
|
||||
* @return OAIRepository
|
||||
*/
|
||||
abstract public function repositoryInfo();
|
||||
|
||||
/**
|
||||
* Check if identifier is in the valid format.
|
||||
*
|
||||
* @param string $identifier
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function validIdentifier($identifier)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if identifier exists.
|
||||
*
|
||||
* @param string $identifier
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function identifierExists($identifier)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return OAI record for specified identifier.
|
||||
*
|
||||
* @param string $identifier
|
||||
*
|
||||
* @return OAIRecord (or false, if identifier is invalid)
|
||||
*/
|
||||
abstract public function record($identifier);
|
||||
|
||||
/**
|
||||
* Return set of OAI records.
|
||||
*
|
||||
* @param string $metadataPrefix specified metadata prefix
|
||||
* @param int $from minimum timestamp
|
||||
* @param int $until maximum timestamp
|
||||
* @param string $set specified set
|
||||
* @param int $offset current record offset
|
||||
* @param int $limit maximum number of records to return
|
||||
* @param int $total output parameter, set to total number of records
|
||||
*
|
||||
* @return array OAIRecord
|
||||
*/
|
||||
public function records($metadataPrefix, $from, $until, $set, $offset, $limit, &$total)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return set of OAI identifiers.
|
||||
*
|
||||
* @see getRecords
|
||||
*
|
||||
* @return array OAIIdentifier
|
||||
*/
|
||||
public function identifiers($metadataPrefix, $from, $until, $set, $offset, $limit, &$total)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return set of OAI sets.
|
||||
*
|
||||
* @param int $offset current set offset
|
||||
* @param int $limit Maximum number of sets to return
|
||||
* @param int $total output parameter, set to total number of sets
|
||||
*/
|
||||
public function sets($offset, $limit, &$total)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a resumption token.
|
||||
*
|
||||
* @param string $tokenId
|
||||
*
|
||||
* @return OAIResumptionToken|false
|
||||
*/
|
||||
abstract public function resumptionToken($tokenId);
|
||||
|
||||
/**
|
||||
* Save a resumption token.
|
||||
*
|
||||
* @param int $offset current offset
|
||||
* @param array $params request parameters
|
||||
*
|
||||
* @return OAIResumptionToken the saved token
|
||||
*/
|
||||
abstract public function saveResumptionToken($offset, $params);
|
||||
|
||||
/**
|
||||
* Return array of supported metadata formats.
|
||||
*
|
||||
* @param bool $namesOnly return array of format prefix names only
|
||||
* @param string $identifier return formats for specific identifier
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function metadataFormats($namesOnly = false, $identifier = null)
|
||||
{
|
||||
$formats = [];
|
||||
Hook::call('OAI::metadataFormats', [$namesOnly, $identifier, &$formats]);
|
||||
|
||||
return $formats;
|
||||
}
|
||||
|
||||
//
|
||||
// Protocol request handlers
|
||||
//
|
||||
|
||||
/**
|
||||
* Handle OAI GetRecord request.
|
||||
* Retrieves an individual record from the repository.
|
||||
*/
|
||||
public function GetRecord()
|
||||
{
|
||||
// Validate parameters
|
||||
if (!$this->checkParams(['identifier', 'metadataPrefix'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$identifier = $this->getParam('identifier');
|
||||
$metadataPrefix = $this->getParam('metadataPrefix');
|
||||
|
||||
// Check that identifier is in valid format
|
||||
if ($this->validIdentifier($identifier) === false) {
|
||||
$this->error('badArgument', 'Identifier is not in a valid format');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get metadata for requested identifier
|
||||
if (($record = $this->record($identifier)) === false) {
|
||||
$this->error('idDoesNotExist', 'No matching identifier in this repository');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that the requested metadata format is supported for this identifier
|
||||
if (!in_array($metadataPrefix, $this->metadataFormats(true, $identifier))) {
|
||||
$this->error('cannotDisseminateFormat', 'The requested metadataPrefix is not supported by this repository');
|
||||
return;
|
||||
}
|
||||
|
||||
// Display response
|
||||
$response = "\t<GetRecord>\n" .
|
||||
"\t\t<record>\n" .
|
||||
"\t\t\t<header" . (($record->status == self::OAIRECORD_STATUS_DELETED) ? " status=\"deleted\">\n" : ">\n") .
|
||||
"\t\t\t\t<identifier>" . $record->identifier . "</identifier>\n" .
|
||||
"\t\t\t\t<datestamp>" . $record->datestamp . "</datestamp>\n";
|
||||
// Output set memberships
|
||||
foreach ($record->sets as $setSpec) {
|
||||
$response .= "\t\t\t\t<setSpec>{$setSpec}</setSpec>\n";
|
||||
}
|
||||
$response .= "\t\t\t</header>\n";
|
||||
if (!empty($record->data)) {
|
||||
$response .= "\t\t\t<metadata>\n";
|
||||
// Output metadata
|
||||
$response .= $this->formatMetadata($metadataPrefix, $record);
|
||||
$response .= "\t\t\t</metadata>\n";
|
||||
}
|
||||
$response .= "\t\t</record>\n" .
|
||||
"\t</GetRecord>\n";
|
||||
|
||||
$this->response($response);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle OAI Identify request.
|
||||
* Retrieves information about a repository.
|
||||
*/
|
||||
public function Identify()
|
||||
{
|
||||
// Validate parameters
|
||||
if (!$this->checkParams()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$info = $this->repositoryInfo();
|
||||
|
||||
// Format body of response
|
||||
$response = "\t<Identify>\n" .
|
||||
"\t\t<repositoryName>" . OAIUtils::prepOutput($info->repositoryName) . "</repositoryName>\n" .
|
||||
"\t\t<baseURL>" . $this->config->baseUrl . "</baseURL>\n" .
|
||||
"\t\t<protocolVersion>" . $this->protocolVersion . "</protocolVersion>\n" .
|
||||
"\t\t<adminEmail>" . $info->adminEmail . "</adminEmail>\n" .
|
||||
"\t\t<earliestDatestamp>" . OAIUtils::UTCDate($info->earliestDatestamp) . "</earliestDatestamp>\n" .
|
||||
"\t\t<deletedRecord>persistent</deletedRecord>\n" .
|
||||
"\t\t<granularity>" . $this->config->granularity . "</granularity>\n";
|
||||
if (extension_loaded('zlib')) {
|
||||
// Show compression options if server supports Zlib
|
||||
$response .= "\t\t<compression>gzip</compression>\n" .
|
||||
"\t\t<compression>deflate</compression>\n";
|
||||
}
|
||||
$response .= "\t\t<description>\n" .
|
||||
"\t\t\t<oai-identifier\n" .
|
||||
"\t\t\t\txmlns=\"http://www.openarchives.org/OAI/2.0/oai-identifier\"\n" .
|
||||
"\t\t\t\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" .
|
||||
"\t\t\t\txsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/oai-identifier\n" .
|
||||
"\t\t\t\t\thttp://www.openarchives.org/OAI/2.0/oai-identifier.xsd\">\n" .
|
||||
"\t\t\t\t<scheme>oai</scheme>\n" .
|
||||
"\t\t\t\t<repositoryIdentifier>" . $this->config->repositoryId . "</repositoryIdentifier>\n" .
|
||||
"\t\t\t\t<delimiter>" . $info->delimiter . "</delimiter>\n" .
|
||||
"\t\t\t\t<sampleIdentifier>" . $info->sampleIdentifier . "</sampleIdentifier>\n" .
|
||||
"\t\t\t</oai-identifier>\n" .
|
||||
"\t\t</description>\n";
|
||||
$response .= "\t\t<description>\n" .
|
||||
"\t\t\t<toolkit\n" .
|
||||
"\t\t\t\txmlns=\"http://oai.dlib.vt.edu/OAI/metadata/toolkit\"\n" .
|
||||
"\t\t\t\txsi:schemaLocation=\"http://oai.dlib.vt.edu/OAI/metadata/toolkit\n" .
|
||||
"\t\t\t\t\thttp://oai.dlib.vt.edu/OAI/metadata/toolkit.xsd\">\n" .
|
||||
"\t\t\t\t<title>" . $info->toolkitTitle . "</title>\n" .
|
||||
"\t\t\t\t<author>\n" .
|
||||
"\t\t\t\t\t<name>Public Knowledge Project</name>\n" .
|
||||
"\t\t\t\t\t<email>pkp.contact@gmail.com</email>\n" .
|
||||
"\t\t\t\t</author>\n" .
|
||||
"\t\t\t\t<version>" . $info->toolkitVersion . "</version>\n" .
|
||||
"\t\t\t\t<URL>" . $info->toolkitURL . "</URL>\n" .
|
||||
"\t\t\t</toolkit>\n" .
|
||||
"\t\t</description>\n";
|
||||
$response .= "\t</Identify>\n";
|
||||
|
||||
$this->response($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle OAI ListIdentifiers request.
|
||||
* Retrieves headers of records from the repository.
|
||||
*/
|
||||
public function ListIdentifiers()
|
||||
{
|
||||
$offset = 0;
|
||||
|
||||
// Check for resumption token
|
||||
if ($this->paramExists('resumptionToken')) {
|
||||
// Validate parameters
|
||||
if (!$this->checkParams(['resumptionToken'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get parameters from resumption token
|
||||
if (($token = $this->resumptionToken($this->getParam('resumptionToken'))) === false) {
|
||||
$this->error('badResumptionToken', 'The requested resumptionToken is invalid or has expired');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setParams($token->params);
|
||||
$offset = $token->offset;
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if (!$this->checkParams(['metadataPrefix'], ['from', 'until', 'set'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$metadataPrefix = $this->getParam('metadataPrefix');
|
||||
$set = $this->getParam('set');
|
||||
|
||||
// Check that the requested metadata format is supported
|
||||
if (!in_array($metadataPrefix, $this->metadataFormats(true))) {
|
||||
$this->error('cannotDisseminateFormat', 'The requested metadataPrefix is not supported by this repository');
|
||||
return;
|
||||
}
|
||||
|
||||
// If a set was passed in check if repository supports sets
|
||||
if (isset($set) && $this->config->maxSets == 0) {
|
||||
$this->error('noSetHierarchy', 'This repository does not support sets');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get UNIX timestamps for from and until dates, if applicable
|
||||
if (!$this->extractDateParams($this->getParams(), $from, $until)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store current offset and total records for resumption token, if needed
|
||||
$cursor = $offset;
|
||||
$total = 0;
|
||||
|
||||
// Get list of matching identifiers
|
||||
$records = $this->identifiers($metadataPrefix, $from, $until, $set, $offset, $this->config->maxIdentifiers, $total);
|
||||
if (empty($records)) {
|
||||
$this->error('noRecordsMatch', 'No matching records in this repository');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format body of response
|
||||
$response = "\t<ListIdentifiers>\n";
|
||||
|
||||
// Output identifiers
|
||||
for ($i = 0, $num = count($records); $i < $num; $i++) {
|
||||
$record = $records[$i];
|
||||
$response .= "\t\t<header" . (($record->status == self::OAIRECORD_STATUS_DELETED) ? " status=\"deleted\">\n" : ">\n") .
|
||||
"\t\t\t<identifier>" . $record->identifier . "</identifier>\n" .
|
||||
"\t\t\t<datestamp>" . $record->datestamp . "</datestamp>\n";
|
||||
// Output set memberships
|
||||
foreach ($record->sets as $setSpec) {
|
||||
$response .= "\t\t\t<setSpec>" . OAIUtils::prepOutput($setSpec) . "</setSpec>\n";
|
||||
}
|
||||
$response .= "\t\t</header>\n";
|
||||
}
|
||||
$offset += $num;
|
||||
|
||||
if ($offset != 0 && $offset < $total) {
|
||||
// Partial result, save resumption token
|
||||
$token = $this->saveResumptionToken($offset, $this->getParams());
|
||||
|
||||
$response .= "\t\t<resumptionToken expirationDate=\"" . OAIUtils::UTCDate($token->expire) . "\"\n" .
|
||||
"\t\t\tcompleteListSize=\"{$total}\"\n" .
|
||||
"\t\t\tcursor=\"{$cursor}\">" . $token->id . "</resumptionToken>\n";
|
||||
} elseif (isset($token)) {
|
||||
// Current request completes a previous incomplete list, add empty resumption token
|
||||
$response .= "\t\t<resumptionToken completeListSize=\"{$total}\" cursor=\"{$cursor}\" />\n";
|
||||
}
|
||||
|
||||
$response .= "\t</ListIdentifiers>\n";
|
||||
|
||||
$this->response($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle OAI ListMetadataFormats request.
|
||||
* Retrieves metadata formats supported by the repository.
|
||||
*/
|
||||
public function ListMetadataFormats()
|
||||
{
|
||||
// Validate parameters
|
||||
if (!$this->checkParams([], ['identifier'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get list of metadata formats for selected identifier, or all formats if no identifier was passed
|
||||
if ($this->paramExists('identifier')) {
|
||||
if (!$this->identifierExists($this->getParam('identifier'))) {
|
||||
$this->error('idDoesNotExist', 'No matching identifier in this repository');
|
||||
return;
|
||||
} else {
|
||||
$formats = $this->metadataFormats(false, $this->getParam('identifier'));
|
||||
}
|
||||
} else {
|
||||
$formats = $this->metadataFormats();
|
||||
}
|
||||
|
||||
if (empty($formats) || !is_array($formats)) {
|
||||
$this->error('noMetadataFormats', 'No metadata formats are available');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format body of response
|
||||
$response = "\t<ListMetadataFormats>\n";
|
||||
|
||||
// output metadata formats
|
||||
foreach ($formats as $format) {
|
||||
$response .= "\t\t<metadataFormat>\n" .
|
||||
"\t\t\t<metadataPrefix>" . $format->prefix . "</metadataPrefix>\n" .
|
||||
"\t\t\t<schema>" . $format->schema . "</schema>\n" .
|
||||
"\t\t\t<metadataNamespace>" . $format->namespace . "</metadataNamespace>\n" .
|
||||
"\t\t</metadataFormat>\n";
|
||||
}
|
||||
|
||||
$response .= "\t</ListMetadataFormats>\n";
|
||||
|
||||
$this->response($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle OAI ListRecords request.
|
||||
* Retrieves records from the repository.
|
||||
*/
|
||||
public function ListRecords()
|
||||
{
|
||||
$offset = 0;
|
||||
|
||||
// Check for resumption token
|
||||
if ($this->paramExists('resumptionToken')) {
|
||||
// Validate parameters
|
||||
if (!$this->checkParams(['resumptionToken'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get parameters from resumption token
|
||||
if (($token = $this->resumptionToken($this->getParam('resumptionToken'))) === false) {
|
||||
$this->error('badResumptionToken', 'The requested resumptionToken is invalid or has expired');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setParams($token->params);
|
||||
$offset = $token->offset;
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if (!$this->checkParams(['metadataPrefix'], ['from', 'until', 'set'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$metadataPrefix = $this->getParam('metadataPrefix');
|
||||
$set = $this->getParam('set');
|
||||
|
||||
// Check that the requested metadata format is supported
|
||||
if (!in_array($metadataPrefix, $this->metadataFormats(true))) {
|
||||
$this->error('cannotDisseminateFormat', 'The requested metadataPrefix is not supported by this repository');
|
||||
return;
|
||||
}
|
||||
|
||||
// If a set was passed check if repository supports sets
|
||||
if (isset($set) && $this->config->maxSets == 0) {
|
||||
$this->error('noSetHierarchy', 'This repository does not support sets');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get UNIX timestamps for from and until dates, if applicable
|
||||
if (!$this->extractDateParams($this->getParams(), $from, $until)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store current offset and total records for resumption token, if needed
|
||||
$cursor = $offset;
|
||||
$total = 0;
|
||||
|
||||
// Get list of matching records
|
||||
$records = $this->records($metadataPrefix, $from, $until, $set, $offset, $this->config->maxRecords, $total);
|
||||
if (empty($records)) {
|
||||
$this->error('noRecordsMatch', 'No matching records in this repository');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format body of response
|
||||
$response = "\t<ListRecords>\n";
|
||||
|
||||
// Output records
|
||||
for ($i = 0, $num = count($records); $i < $num; $i++) {
|
||||
$record = $records[$i];
|
||||
$response .= "\t\t<record>\n" .
|
||||
"\t\t\t<header" . (($record->status == self::OAIRECORD_STATUS_DELETED) ? " status=\"deleted\">\n" : ">\n") .
|
||||
"\t\t\t\t<identifier>" . $record->identifier . "</identifier>\n" .
|
||||
"\t\t\t\t<datestamp>" . $record->datestamp . "</datestamp>\n";
|
||||
// Output set memberships
|
||||
foreach ($record->sets as $setSpec) {
|
||||
$response .= "\t\t\t\t<setSpec>" . OAIUtils::prepOutput($setSpec) . "</setSpec>\n";
|
||||
}
|
||||
$response .= "\t\t\t</header>\n";
|
||||
if (!empty($record->data)) {
|
||||
$response .= "\t\t\t<metadata>\n";
|
||||
// Output metadata
|
||||
$response .= $this->formatMetadata($this->getParam('metadataPrefix'), $record);
|
||||
$response .= "\t\t\t</metadata>\n";
|
||||
}
|
||||
$response .= "\t\t</record>\n";
|
||||
}
|
||||
$offset += $num;
|
||||
|
||||
if ($offset != 0 && $offset < $total) {
|
||||
// Partial result, save resumption token
|
||||
$token = $this->saveResumptionToken($offset, $this->getParams());
|
||||
|
||||
$response .= "\t\t<resumptionToken expirationDate=\"" . OAIUtils::UTCDate($token->expire) . "\"\n" .
|
||||
"\t\t\tcompleteListSize=\"{$total}\"\n" .
|
||||
"\t\t\tcursor=\"{$cursor}\">" . $token->id . "</resumptionToken>\n";
|
||||
} elseif (isset($token)) {
|
||||
// Current request completes a previous incomplete list, add empty resumption token
|
||||
$response .= "\t\t<resumptionToken completeListSize=\"{$total}\" cursor=\"{$cursor}\" />\n";
|
||||
}
|
||||
|
||||
$response .= "\t</ListRecords>\n";
|
||||
|
||||
$this->response($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle OAI ListSets request.
|
||||
* Retrieves sets from a repository.
|
||||
*/
|
||||
public function ListSets()
|
||||
{
|
||||
$offset = 0;
|
||||
|
||||
// Check for resumption token
|
||||
if ($this->paramExists('resumptionToken')) {
|
||||
// Validate parameters
|
||||
if (!$this->checkParams(['resumptionToken'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get parameters from resumption token
|
||||
if (($token = $this->resumptionToken($this->getParam('resumptionToken'))) === false) {
|
||||
$this->error('badResumptionToken', 'The requested resumptionToken is invalid or has expired');
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setParams($token->params);
|
||||
$offset = $token->offset;
|
||||
}
|
||||
|
||||
// Validate parameters
|
||||
if (!$this->checkParams()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Store current offset and total sets for resumption token, if needed
|
||||
$cursor = $offset;
|
||||
$total = 0;
|
||||
|
||||
// Get list of matching sets
|
||||
$sets = $this->sets($offset, $this->config->maxRecords, $total);
|
||||
if (empty($sets)) {
|
||||
$this->error('noSetHierarchy', 'This repository does not support sets');
|
||||
return;
|
||||
}
|
||||
|
||||
// Format body of response
|
||||
$response = "\t<ListSets>\n";
|
||||
|
||||
// Output sets
|
||||
for ($i = 0, $num = count($sets); $i < $num; $i++) {
|
||||
$set = $sets[$i];
|
||||
$response .= "\t\t<set>\n" .
|
||||
"\t\t\t<setSpec>" . OAIUtils::prepOutput($set->spec) . "</setSpec>\n" .
|
||||
"\t\t\t<setName>" . OAIUtils::prepOutput($set->name) . "</setName>\n";
|
||||
// output set description, if applicable
|
||||
if (isset($set->description)) {
|
||||
$response .= "\t\t\t<setDescription>\n" .
|
||||
"\t\t\t\t<oai_dc:dc\n" .
|
||||
"\t\t\t\t\txmlns:oai_dc=\"http://www.openarchives.org/OAI/2.0/oai_dc/\"\n" .
|
||||
"\t\t\t\t\txmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n" .
|
||||
"\t\t\t\t\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" .
|
||||
"\t\t\t\t\txsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/oai_dc/\n" .
|
||||
"\t\t\t\t\t\thttp://www.openarchives.org/OAI/2.0/oai_dc.xsd\">\n" .
|
||||
"\t\t\t\t\t<dc:description>" . OAIUtils::prepOutput($set->description) . "</dc:description>\n" .
|
||||
"\t\t\t\t</oai_dc:dc>\n" .
|
||||
"\t\t\t</setDescription>\n";
|
||||
}
|
||||
$response .= "\t\t</set>\n";
|
||||
}
|
||||
$offset += $num;
|
||||
|
||||
if ($offset != 0 && $offset < $total) {
|
||||
// Partial result, set resumption token
|
||||
$token = $this->saveResumptionToken($offset, $this->getParams());
|
||||
|
||||
$response .= "\t\t<resumptionToken expirationDate=\"" . OAIUtils::UTCDate($token->expire) . "\"\n" .
|
||||
"\t\t\tcompleteListSize=\"{$total}\"\n" .
|
||||
"\t\t\tcursor=\"{$cursor}\">" . $token->id . "</resumptionToken>\n";
|
||||
} elseif (isset($token)) {
|
||||
// current request completes a previous incomplete list, add empty resumption token
|
||||
$response .= "\t\t<resumptionToken completeListSize=\"{$total}\" cursor=\"{$cursor}\" />\n";
|
||||
}
|
||||
|
||||
$response .= "\t</ListSets>\n";
|
||||
|
||||
$this->response($response);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private helper functions
|
||||
//
|
||||
|
||||
/**
|
||||
* Display OAI error response.
|
||||
*/
|
||||
public function error($code, $message)
|
||||
{
|
||||
if (in_array($code, ['badVerb', 'badArgument'])) {
|
||||
$printParams = false;
|
||||
} else {
|
||||
$printParams = true;
|
||||
}
|
||||
|
||||
$this->response("\t<error code=\"{$code}\">{$message}</error>", $printParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output OAI response.
|
||||
*
|
||||
* @param string $response text of response message.
|
||||
* @param bool $printParams display request parameters
|
||||
*/
|
||||
public function response($response, $printParams = true)
|
||||
{
|
||||
$request = Application::get()->getRequest();
|
||||
header('Content-Type: text/xml');
|
||||
|
||||
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" .
|
||||
'<?xml-stylesheet type="text/xsl" href="' . $request->getBaseUrl() . "/lib/pkp/xml/oai2.xsl\" ?>\n" .
|
||||
"<OAI-PMH xmlns=\"http://www.openarchives.org/OAI/2.0/\"\n" .
|
||||
"\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" .
|
||||
"\txsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/\n" .
|
||||
"\t\thttp://www.openarchives.org/OAI/2.0/OAI-PMH.xsd\">\n" .
|
||||
"\t<responseDate>" . OAIUtils::UTCDate() . "</responseDate>\n" .
|
||||
"\t<request";
|
||||
|
||||
// print request params, if applicable
|
||||
if ($printParams) {
|
||||
foreach ($this->params as $k => $v) {
|
||||
echo " {$k}=\"" . OAIUtils::prepOutput($v) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
echo '>' . OAIUtils::prepOutput($this->config->baseUrl) . "</request>\n" .
|
||||
$response .
|
||||
"</OAI-PMH>\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the specified parameter.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getParam($name)
|
||||
{
|
||||
return $this->params[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an associative array of all request parameters.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the request parameters.
|
||||
*
|
||||
* @param array $params
|
||||
*/
|
||||
public function setParams($params)
|
||||
{
|
||||
$this->params = $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the requested parameter is set, false if it is not set.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function paramExists($name)
|
||||
{
|
||||
return isset($this->params[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check request parameters.
|
||||
* Outputs error response if an invalid parameter is found.
|
||||
*
|
||||
* @param array $required required parameters for the current request
|
||||
* @param array $optional optional parameters for the current request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function checkParams($required = [], $optional = [])
|
||||
{
|
||||
// Get allowed parameters for current request
|
||||
$requiredParams = array_merge(['verb'], $required);
|
||||
$validParams = array_merge($requiredParams, $optional);
|
||||
|
||||
// Check for missing or duplicate required parameters
|
||||
foreach ($requiredParams as $k) {
|
||||
if (!$this->paramExists($k)) {
|
||||
$this->error('badArgument', "Missing {$k} parameter");
|
||||
return false;
|
||||
} elseif (is_array($this->getParam($k))) {
|
||||
$this->error('badArgument', "Multiple values are not allowed for the {$k} parameter");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicate optional parameters
|
||||
foreach ($optional as $k) {
|
||||
if ($this->paramExists($k) && is_array($this->getParam($k))) {
|
||||
$this->error('badArgument', "Multiple values are not allowed for the {$k} parameter");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for illegal parameters
|
||||
$request = Application::get()->getRequest();
|
||||
foreach ($this->params as $k => $v) {
|
||||
if (!in_array($k, $validParams)) {
|
||||
$this->error('badArgument', "{$k} is an illegal parameter");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns formatted metadata response in specified format.
|
||||
*
|
||||
* @param string $format
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function formatMetadata($format, $record)
|
||||
{
|
||||
$formats = $this->metadataFormats();
|
||||
$metadata = $formats[$format]->toXml($record);
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if from and until parameters have been passed.
|
||||
* If passed, validate and convert to UNIX timestamps.
|
||||
*
|
||||
* @param array $params request parameters
|
||||
* @param int $from from timestamp (output parameter)
|
||||
* @param int $until until timestamp (output parameter)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function extractDateParams($params, &$from, &$until)
|
||||
{
|
||||
if (isset($params['from'])) {
|
||||
$from = OAIUtils::UTCtoTimestamp($params['from'], $this->config->granularity);
|
||||
|
||||
if ($from == 'invalid') {
|
||||
$this->error('badArgument', 'Illegal from parameter');
|
||||
return false;
|
||||
} elseif ($from == 'invalid_granularity') {
|
||||
$this->error('badArgument', 'Illegal granularity for from parameter');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($params['until'])) {
|
||||
$until = OAIUtils::UTCtoTimestamp($params['until'], $this->config->granularity);
|
||||
|
||||
if ($until == 'invalid') {
|
||||
$this->error('badArgument', 'Illegal until parameter');
|
||||
return false;
|
||||
} elseif ($until == 'invalid_granularity') {
|
||||
$this->error('badArgument', 'Illegal granularity for until parameter');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that until value is greater than or equal to from value
|
||||
if (isset($from) && $from > $until) {
|
||||
$this->error('badArgument', 'until parameter must be greater than or equal to from parameter');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that granularities are equal
|
||||
if (isset($from) && strlen($params['from']) != strlen($params['until'])) {
|
||||
$this->error('badArgument', 'until and from parameters must be of the same granularity');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strlen($params['until']) == 10) {
|
||||
// Until date is inclusive
|
||||
$until += 86399;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAI', '\OAI');
|
||||
define('OAIRECORD_STATUS_DELETED', OAI::OAIRECORD_STATUS_DELETED);
|
||||
define('OAIRECORD_STATUS_ALIVE', OAI::OAIRECORD_STATUS_ALIVE);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAIConfig.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAIConfig
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAI
|
||||
*
|
||||
* @brief OAI-PMH server configuration
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
use PKP\config\Config;
|
||||
|
||||
/**
|
||||
* OAI repository configuration.
|
||||
*/
|
||||
class OAIConfig
|
||||
{
|
||||
/** @var string URL to the OAI front-end */
|
||||
public $baseUrl = '';
|
||||
|
||||
/** @var string identifier of the repository */
|
||||
public $repositoryId = 'oai';
|
||||
|
||||
/** @var string record datestamp granularity;
|
||||
* Must be either 'YYYY-MM-DD' or 'YYYY-MM-DDThh:mm:ssZ'
|
||||
*/
|
||||
public $granularity = 'YYYY-MM-DDThh:mm:ssZ';
|
||||
|
||||
/** @var int TTL of resumption tokens */
|
||||
public $tokenLifetime = 86400;
|
||||
|
||||
/** @var int maximum identifiers returned per request */
|
||||
public $maxIdentifiers = 500;
|
||||
|
||||
/** @var int maximum records returned per request */
|
||||
public $maxRecords;
|
||||
|
||||
/** @var int maximum sets returned per request (must be 0 if sets not supported) */
|
||||
public $maxSets = 50;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($baseUrl, $repositoryId)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->repositoryId = $repositoryId;
|
||||
|
||||
$this->maxRecords = Config::getVar('oai', 'oai_max_records');
|
||||
if (!$this->maxRecords) {
|
||||
$this->maxRecords = 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAIConfig', '\OAIConfig');
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAIIdentifier.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAIIdentifier
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAI
|
||||
*
|
||||
* @brief OAI-PMH Identifier
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
/**
|
||||
* OAI identifier.
|
||||
*/
|
||||
class OAIIdentifier
|
||||
{
|
||||
/** @var string unique OAI record identifier */
|
||||
public $identifier;
|
||||
|
||||
/** @var int last-modified *nix timestamp */
|
||||
public $datestamp;
|
||||
|
||||
/** @var array sets this record belongs to */
|
||||
public $sets;
|
||||
|
||||
/** @var string if this record is deleted */
|
||||
public $status;
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAIIdentifier', '\OAIIdentifier');
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAIMetadataFormat.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAIMetadataFormat
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAI
|
||||
*
|
||||
* @brief OAI-PMH metadata format
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
/**
|
||||
* OAI metadata format.
|
||||
* Used to generated metadata XML according to a specified schema.
|
||||
*/
|
||||
class OAIMetadataFormat
|
||||
{
|
||||
/** @var string metadata prefix */
|
||||
public $prefix;
|
||||
|
||||
/** @var string XML schema */
|
||||
public $schema;
|
||||
|
||||
/** @var string XML namespace */
|
||||
public $namespace;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($prefix, $schema, $namespace)
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
$this->schema = $schema;
|
||||
$this->namespace = $namespace;
|
||||
}
|
||||
|
||||
public function getLocalizedData($data, $locale)
|
||||
{
|
||||
foreach ($data as $element) {
|
||||
if (isset($data[$locale])) {
|
||||
return $data[$locale];
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve XML-formatted metadata for the specified record.
|
||||
*
|
||||
* @param OAIRecord $record
|
||||
* @param string $format OAI metadata prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function toXml($record, $format = null)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAIMetadataFormat', '\OAIMetadataFormat');
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAIRecord.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAIRecord
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAI
|
||||
*
|
||||
* @brief OAI-PMH record.
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
/**
|
||||
* OAI record.
|
||||
* Describes metadata for a single record in the repository.
|
||||
*/
|
||||
class OAIRecord extends OAIIdentifier
|
||||
{
|
||||
public $data;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
public function setData($name, &$value)
|
||||
{
|
||||
$this->data[$name] = & $value;
|
||||
}
|
||||
|
||||
public function &getData($name)
|
||||
{
|
||||
if (isset($this->data[$name])) {
|
||||
$returner = & $this->data[$name];
|
||||
} else {
|
||||
$returner = null;
|
||||
}
|
||||
|
||||
return $returner;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAIRecord', '\OAIRecord');
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAIRepository.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAIRepository
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAI
|
||||
*
|
||||
* @brief OAI-PMH Repository
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
/**
|
||||
* OAI repository information.
|
||||
*/
|
||||
class OAIRepository
|
||||
{
|
||||
/** @var string name of the repository */
|
||||
public $repositoryName;
|
||||
|
||||
/** @var string administrative contact email */
|
||||
public $adminEmail;
|
||||
|
||||
/** @var int earliest *nix timestamp in the repository */
|
||||
public $earliestDatestamp;
|
||||
|
||||
/** @var string delimiter in identifier */
|
||||
public $delimiter = ':';
|
||||
|
||||
/** @var string example identifier */
|
||||
public $sampleIdentifier;
|
||||
|
||||
/** @var string toolkit/software title (e.g. Open Journal Systems) */
|
||||
public $toolkitTitle;
|
||||
|
||||
/** @var string toolkit/software version */
|
||||
public $toolkitVersion;
|
||||
|
||||
/** @var string toolkit/software URL */
|
||||
public $toolkitURL;
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAIRepository', '\OAIRepository');
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAIResumptionToken.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAIResumptionToken
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAI
|
||||
*
|
||||
* @brief OAI-PMH Resumption Token
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
/**
|
||||
* OAI resumption token.
|
||||
* Used to resume a record retrieval at the last-retrieved offset.
|
||||
*/
|
||||
class OAIResumptionToken
|
||||
{
|
||||
/** @var string unique token ID */
|
||||
public $id;
|
||||
|
||||
/** @var int record offset */
|
||||
public $offset;
|
||||
|
||||
/** @var array request parameters */
|
||||
public $params;
|
||||
|
||||
/** @var int expiration timestamp */
|
||||
public $expire;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($id, $offset, $params, $expire)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->offset = $offset;
|
||||
$this->params = $params;
|
||||
$this->expire = $expire;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAIResumptionToken', '\OAIResumptionToken');
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAISet.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAISet
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAI
|
||||
*
|
||||
* @brief OAI-PMH set
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
/**
|
||||
* OAI set.
|
||||
* Identifies a set of related records.
|
||||
*/
|
||||
class OAISet
|
||||
{
|
||||
/** @var string unique set specifier */
|
||||
public $spec;
|
||||
|
||||
/** @var string set name */
|
||||
public $name;
|
||||
|
||||
/** @var string set description */
|
||||
public $description;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct($spec, $name, $description)
|
||||
{
|
||||
$this->spec = $spec;
|
||||
$this->name = $name;
|
||||
$this->description = $description;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAISet', '\OAISet');
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/OAIUtils.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class OAI
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAIDAO
|
||||
*
|
||||
* @brief Utility functions used by OAI related classes.
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
use Transliterator;
|
||||
|
||||
class OAIUtils
|
||||
{
|
||||
private static $TO_ASCII;
|
||||
private static $NOT_IN_URI_NON_RESERVED_CHARS = '/[^A-Za-z0-9\-_\.!~*\'()]/';
|
||||
|
||||
public static function init()
|
||||
{
|
||||
self::$TO_ASCII = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a UTC-formatted datestamp from the specified UNIX timestamp.
|
||||
*
|
||||
* @param int $timestamp *nix timestamp (if not used, the current time is used)
|
||||
* @param bool $includeTime include both the time and date
|
||||
*
|
||||
* @return string UTC datestamp
|
||||
*/
|
||||
public static function UTCDate($timestamp = 0, $includeTime = true)
|
||||
{
|
||||
$format = 'Y-m-d';
|
||||
if ($includeTime) {
|
||||
$format .= "\TH:i:s\Z";
|
||||
}
|
||||
|
||||
if ($timestamp == 0) {
|
||||
return gmdate($format);
|
||||
} else {
|
||||
return gmdate($format, $timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a UNIX timestamp from a UTC-formatted datestamp.
|
||||
* Returns the string "invalid" if datestamp is invalid,
|
||||
* or "invalid_granularity" if unsupported granularity.
|
||||
*
|
||||
* @param string $date UTC datestamp
|
||||
* @param string $requiredGranularity Datestamp granularity to require (default: not checked)
|
||||
*
|
||||
* @return int|string timestamp
|
||||
*/
|
||||
public static function UTCtoTimestamp($date, $requiredGranularity = null)
|
||||
{
|
||||
// FIXME Has limited range (see http://php.net/strtotime)
|
||||
if (preg_match("/^\d\d\d\d\-\d\d\-\d\d$/", $date)) {
|
||||
// Match date
|
||||
$time = strtotime("{$date} UTC");
|
||||
return ($time != -1) ? $time : 'invalid';
|
||||
} elseif (preg_match("/^(\d\d\d\d\-\d\d\-\d\d)T(\d\d:\d\d:\d\d)Z$/", $date, $matches)) {
|
||||
// Match datetime
|
||||
// FIXME
|
||||
$date = "{$matches[1]} {$matches[2]}";
|
||||
if ($requiredGranularity && $requiredGranularity != 'YYYY-MM-DDThh:mm:ssZ') {
|
||||
return 'invalid_granularity';
|
||||
} else {
|
||||
$time = strtotime("{$date} UTC");
|
||||
return ($time != -1) ? $time : 'invalid';
|
||||
}
|
||||
} else {
|
||||
return 'invalid';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Clean input variables (by reference).
|
||||
*
|
||||
* @param mixed $data request parameter(s)
|
||||
*/
|
||||
public static function prepInput(&$data)
|
||||
{ // REFERENCE REQUIRED
|
||||
if (!is_array($data)) {
|
||||
$data = urldecode($data);
|
||||
} else {
|
||||
foreach ($data as $k => $v) {
|
||||
if (is_array($data[$k])) {
|
||||
self::prepInput($data[$k]);
|
||||
} else {
|
||||
$data[$k] = urldecode($v);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare variables for output (by reference).
|
||||
* Data is assumed to be UTF-8 encoded (FIXME?)
|
||||
*
|
||||
* @param mixed $data output parameter(s)
|
||||
*
|
||||
* @return mixed cleaned output parameter(s)
|
||||
*/
|
||||
public static function prepOutput(&$data)
|
||||
{ // REFERENCE REQUIRED
|
||||
if (!is_array($data)) {
|
||||
$data = htmlspecialchars($data);
|
||||
} else {
|
||||
foreach ($data as $k => $v) {
|
||||
if (is_array($data[$k])) {
|
||||
self::prepOutput($data[$k]);
|
||||
} else {
|
||||
// FIXME FIXME FIXME
|
||||
$data[$k] = htmlspecialchars($v);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses string $string into an associate array $array.
|
||||
* Acts like parse_str($string, $array) except duplicate
|
||||
* variable names in $string are converted to an array.
|
||||
*
|
||||
* @param array $array of parsed parameters
|
||||
*/
|
||||
public static function parseStr($string, &$array)
|
||||
{
|
||||
$pairs = explode('&', $string);
|
||||
foreach ($pairs as $p) {
|
||||
$vars = explode('=', $p);
|
||||
if (!empty($vars[0]) && isset($vars[1])) {
|
||||
$key = $vars[0];
|
||||
$value = join('=', array_splice($vars, 1));
|
||||
|
||||
if (!isset($array[$key])) {
|
||||
$array[$key] = $value;
|
||||
} elseif (is_array($array[$key])) {
|
||||
array_push($array[$key], $value);
|
||||
} else {
|
||||
$array[$key] = [$array[$key], $value];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valid characters in setSpec [1] are URI unreserved characters [2]
|
||||
* Create a valid string by
|
||||
* - transliterating all non-Latin characters
|
||||
* - stripping all diacritics
|
||||
* - dropping any remaining invalid characters
|
||||
*
|
||||
* [1] https://www.openarchives.org/OAI/2.0/openarchivesprotocol.htm#Set
|
||||
* [2] http://www.ietf.org/rfc/rfc2396.txt
|
||||
*
|
||||
*/
|
||||
public static function toValidSetSpec(string $string): string
|
||||
{
|
||||
$t = self::$TO_ASCII->transliterate($string);
|
||||
return preg_replace(self::$NOT_IN_URI_NON_RESERVED_CHARS, '', $t);
|
||||
}
|
||||
}
|
||||
OAIUtils::init();
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\OAIUtils', '\OAIUtils');
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/oai/PKPOAIDAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2003-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class PKPOAIDAO
|
||||
*
|
||||
* @ingroup oai
|
||||
*
|
||||
* @see OAI
|
||||
*
|
||||
* @brief Base class for DAO operations for the OAI interface.
|
||||
*/
|
||||
|
||||
namespace PKP\oai;
|
||||
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
abstract class PKPOAIDAO extends \PKP\db\DAO
|
||||
{
|
||||
/** @var OAI parent OAI object */
|
||||
public $oai;
|
||||
|
||||
|
||||
/**
|
||||
* Set parent OAI object.
|
||||
*
|
||||
* @param OAI $oai
|
||||
*/
|
||||
public function setOAI($oai)
|
||||
{
|
||||
$this->oai = $oai;
|
||||
}
|
||||
|
||||
//
|
||||
// Resumption tokens
|
||||
//
|
||||
/**
|
||||
* Clear stale resumption tokens.
|
||||
*/
|
||||
public function clearTokens()
|
||||
{
|
||||
$this->update(
|
||||
'DELETE FROM oai_resumption_tokens WHERE expire < ?',
|
||||
[time()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a resumption token.
|
||||
*
|
||||
* @param string $tokenId OAI resumption token
|
||||
*
|
||||
* @return OAIResumptionToken
|
||||
*/
|
||||
public function getToken($tokenId)
|
||||
{
|
||||
$result = $this->retrieve('SELECT * FROM oai_resumption_tokens WHERE token = ?', [$tokenId]);
|
||||
$row = $result->current();
|
||||
return $row ? new OAIResumptionToken($row->token, $row->record_offset, unserialize($row->params), $row->expire) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an OAI resumption token, generating a new ID.
|
||||
*
|
||||
* @param OAIResumptionToken $token
|
||||
*
|
||||
* @return OAIResumptionToken
|
||||
*/
|
||||
public function insertToken($token)
|
||||
{
|
||||
do {
|
||||
// Generate unique token ID
|
||||
$token->id = md5(uniqid(random_int(0, PHP_INT_MAX), true));
|
||||
$result = $this->retrieve(
|
||||
'SELECT COUNT(*) AS row_count FROM oai_resumption_tokens WHERE token = ?',
|
||||
[$token->id]
|
||||
);
|
||||
$row = $result->current();
|
||||
$val = $row->row_count;
|
||||
} while ($val != 0);
|
||||
|
||||
$this->update(
|
||||
'INSERT INTO oai_resumption_tokens (token, record_offset, params, expire)
|
||||
VALUES
|
||||
(?, ?, ?, ?)',
|
||||
[$token->id, $token->offset, serialize($token->params), $token->expire]
|
||||
);
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a data object ID specifies a data object.
|
||||
*
|
||||
* @param int $dataObjectId
|
||||
* @param array $setIds optional Objects ids that specify an OAI set,
|
||||
* in hierarchical order. If passed, will check for the data object id
|
||||
* only inside the specified set.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function recordExists($dataObjectId, $setIds = [])
|
||||
{
|
||||
return $this->getRecord($dataObjectId, $setIds) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return OAI record for specified data object.
|
||||
*
|
||||
* @param int $dataObjectId
|
||||
* @param array $setIds optional Objects ids that specify an OAI set,
|
||||
* in hierarchical order. If passed, will check for the data object id
|
||||
* only inside the specified set.
|
||||
*
|
||||
* @return OAIRecord
|
||||
*/
|
||||
public function getRecord($dataObjectId, $setIds = [])
|
||||
{
|
||||
$result = $this->_getRecordsRecordSetQuery($setIds, null, null, null, $dataObjectId);
|
||||
$row = $result->first();
|
||||
return $row ? $this->_returnRecordFromRow((array) $row) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return set of OAI records matching specified parameters.
|
||||
*
|
||||
* @param array $setIds Objects ids that specify an OAI set,
|
||||
* in hierarchical order. The returned records will be part
|
||||
* of this set.
|
||||
* @param int $from timestamp
|
||||
* @param int $until timestamp
|
||||
* @param string $set setSpec
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param int $total
|
||||
*
|
||||
* @return array OAIRecord
|
||||
*/
|
||||
public function getRecords($setIds, $from, $until, $set, $offset, $limit, &$total)
|
||||
{
|
||||
$query = $this->_getRecordsRecordSetQuery($setIds, $from, $until, $set);
|
||||
$total = $query->count();
|
||||
$results = $query->offset($offset)->limit($limit)->get();
|
||||
|
||||
$records = [];
|
||||
foreach ($results as $row) {
|
||||
$records[] = $this->_returnRecordFromRow((array) $row);
|
||||
}
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return set of OAI identifiers matching specified parameters.
|
||||
*
|
||||
* @param array $setIds Objects ids that specify an OAI set,
|
||||
* in hierarchical order. The returned records will be part
|
||||
* of this set.
|
||||
* @param int $from timestamp
|
||||
* @param int $until timestamp
|
||||
* @param string $set setSpec
|
||||
* @param int $offset
|
||||
* @param int $limit
|
||||
* @param int $total
|
||||
*
|
||||
* @return array OAIIdentifier
|
||||
*/
|
||||
public function getIdentifiers($setIds, $from, $until, $set, $offset, $limit, &$total)
|
||||
{
|
||||
$query = $this->_getRecordsRecordSetQuery($setIds, $from, $until, $set);
|
||||
$total = $query->count();
|
||||
$results = $query->offset($offset)->limit($limit)->get();
|
||||
|
||||
$records = [];
|
||||
foreach ($results as $row) {
|
||||
$records[] = $this->_returnIdentifierFromRow((array) $row);
|
||||
}
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the *nix timestamp of the earliest published submission.
|
||||
*
|
||||
* @param array $setIds optional Objects ids that specify an OAI set,
|
||||
* in hierarchical order. If empty, all records from
|
||||
* all sets will be included.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEarliestDatestamp($setIds = [])
|
||||
{
|
||||
$query = $this->_getRecordsRecordSetQuery($setIds, null, null, null, null, 'last_modified');
|
||||
if ($row = $query->first()) {
|
||||
$record = $this->_returnRecordFromRow((array) $row);
|
||||
return OAIUtils::UTCtoTimestamp($record->datestamp);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods.
|
||||
//
|
||||
/**
|
||||
* Return OAIRecord object from database row.
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return OAIRecord
|
||||
*/
|
||||
public function _returnRecordFromRow($row)
|
||||
{
|
||||
$record = new OAIRecord();
|
||||
$record = $this->_doCommonOAIFromRowOperations($record, $row);
|
||||
|
||||
Hook::call('OAIDAO::_returnRecordFromRow', [&$record, &$row]);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return OAIIdentifier object from database row.
|
||||
*
|
||||
* @param array $row
|
||||
*
|
||||
* @return OAIIdentifier
|
||||
*/
|
||||
public function _returnIdentifierFromRow($row)
|
||||
{
|
||||
$record = new OAIIdentifier();
|
||||
$record = $this->_doCommonOAIFromRowOperations($record, $row);
|
||||
|
||||
Hook::call('OAIDAO::_returnIdentifierFromRow', [&$record, &$row]);
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Common operations for OAIRecord and OAIIdentifier object data set.
|
||||
*
|
||||
* @param OAIRecord|OAIIdentifier $record
|
||||
* @param array $row
|
||||
*
|
||||
* @return OAIRecord|OAIIdentifier
|
||||
*/
|
||||
public function _doCommonOAIFromRowOperations($record, $row)
|
||||
{
|
||||
$record->datestamp = OAIUtils::UTCDate(strtotime($this->datetimeFromDB($row['last_modified'])));
|
||||
|
||||
if (isset($row['tombstone_id'])) {
|
||||
$record->identifier = $row['oai_identifier'];
|
||||
$record->sets = [$row['set_spec']];
|
||||
$record->status = OAI::OAIRECORD_STATUS_DELETED;
|
||||
} else {
|
||||
$record->status = OAI::OAIRECORD_STATUS_ALIVE;
|
||||
$record = $this->setOAIData($record, $row, $record instanceof \PKP\oai\OAIRecord);
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a OAI records record set.
|
||||
*
|
||||
* @param array $setIds Objects ids that specify an OAI set,
|
||||
* in hierarchical order.
|
||||
* @param int|string $from *nix timestamp or ISO datetime string
|
||||
* @param int|string $until *nix timestamp or ISO datetime string
|
||||
* @param string $set
|
||||
* @param int $submissionId optional
|
||||
* @param string $orderBy UNFILTERED
|
||||
*
|
||||
* @return \Illuminate\Database\Query\Builder
|
||||
*/
|
||||
abstract public function _getRecordsRecordSetQuery($setIds, $from, $until, $set, $submissionId = null, $orderBy = 'journal_id, submission_id');
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\oai\PKPOAIDAO', '\PKPOAIDAO');
|
||||
}
|
||||
Reference in New Issue
Block a user