[ Mini Kiebo ]
Server: Windows NT DESKTOP-5B8S0D4 6.2 build 9200 (Windows 8 Professional Edition) i586
Path:
D:
/
Backup
/
14082024
/
Data
/
htdocs
/
htdocs
/
jurnal-kesmas
/
lib
/
pkp
/
classes
/
oai
/
[
Home
]
File: OAI.php
<?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); }