Using callbacks to configure server. Removing logic from server

This commit is contained in:
Daniel Neis Araujo 2013-05-14 00:54:50 -03:00
parent 49c64da4e5
commit 1280e99f51
11 changed files with 422 additions and 1352 deletions

View File

@ -1,348 +0,0 @@
<?php
/**
* \file
* \brief classes related to generating RIF-CS XML response file for ANDS from repository.
* It also serves as an exmaple how class ANDS_RIFCS can be used in a particular case.
*
*/
require_once('ands_rifcs.php');
/**
* \brief For creating RIF-CS metadata to meet the requirement of ANDS.
*
* Class ANDS_RIFCS provides all essential functionalities for creating ANDS RIF-CS records.
* The protected member functions are the backbone functions which can be used for creating any ANDS RIF-CS records.
* At the time of design only data source is database and there is only one set of outputs. Therefore there is only one class has been designed.
* Ideally, there should be a separated class for creating actual records which reflect data source and data models.
*
* Example usage: publish records meet ANDS RIF-CS requirements
*
* \code
* $metadata_node = $outputObj->create_metadata($cur_record);
* $obj_node = new ANDS_TPA($outputObj, $metadata_node, $db);
* try {
* $obj_node->create_obj_node($record[$SQL['set']], $identifier);
* } catch (Exception $e) {
* echo 'Caught exception: ', $e->getMessage(), " when adding $identifier\n";
* }
* \endcode
* \see Code in action can be seen in record_rif.php
*/
class ANDS_TPA extends ANDS_RIFCS {
//! Type: PDO. The database connection of the data source.
//! \see __construct.
private $db;
/**
* Constructor
* The first two parameters are used by its parent class ANDS_RIFCS. The third is its own private property.
*
* \param $ands_response_doc ANDS_Response_XML. A XML Doc acts as the parent node.
* \param $metadata_node DOMElement. The meta node which all subsequent nodes will be added to.
* \param $db Type: PDO. The database connection of the data source.
*/
function __construct($ands_response_doc, $metadata_node, $db) {
parent::__construct($ands_response_doc, $metadata_node);
$this->db = $db;
}
/**
* This is the general entrence of creating actual content. It calls different functions for different type of RIF-CS model.
* When anything goes wrong, e.g. found no record, or $set_name is not recognised, an exception will be thrown.
* And for this implementation, data are stored in a database therefore a PDO is needed. But the source can be any.
*
* \param $set_name Type: string. The name of set is going to be created. Can be one of activity, collection or party.
* \param $key Type: string. The main identifier used in ANDS system. There can be other identifier.
*
* \see create_activity, create_collection, create_party
*/
function create_obj_node($set_name, $key) {
$db = $this->db;
$set_name = strtolower($set_name);
if (in_array($set_name,prepare_set_names())) {
try {
// Get ori_id and which the original table is:
$query = "select ori_table_name, ori_id from oai_headers where oai_identifier = '".$key."'";
$res = exec_pdo_query($db, $query);
$record = $res->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo "$key returned no record.\n";
echo $e->getMessage();
}
$processor = 'create_'.substr($set_name,6);
$this->create_regObject(REG_OBJ_GROUP, $key, MY_URI);
$this->$processor($record['ori_table_name'],$record['ori_id']);
$this->create_identifier_node($key);
$this->create_identifier_node('table='.$record['ori_table_name'].'+id='.$record['ori_id']);
} else {
throw new Exception('Wrong set name was used: '.$set_name);
}
}
/** The processor for creating metadata node of Activity. Called from create_obj_node.
* \param $table_name Type: string. The table name will be used to retrieve data from.
* \param $id_project Type: integer. Internal project id associated to this activity-project.
* \see Function create_obj_node.
*/
private function create_activity($table_name, $id_project) {
$db = $this->db;
# // Get ori_id and which the original table is:
# $query = "select ori_table_name, ori_id from oai_headers where oai_identifier = '".$key."'";
# $res = exec_pdo_query($db, $query);
# $record = $res->fetch(PDO::FETCH_ASSOC);
# // $id_project will e used later, so save it:
# $id_project = $record['ori_id'];
// Get the content using the previously obtained infor:
$query = sprintf("select inter_no,start_date, end_date,pub_descrip from %s where id_project = %s",$table_name,$id_project);
try {
$res = exec_pdo_query($db,$query);
$record = $res->fetch(PDO::FETCH_ASSOC);
} catch (Exception $e) {
echo $e->getMessage();
}
$this->create_rifcs_node('activity','project');
$c = $this->create_name_node();
$this->create_namePart($c,'The Plant Accelerator Project '.$record['inter_no']);
// Test codes for rich format.
# // \n works
# $this->create_description_node(sprintf("Line one:%s,\nLine two:%s.\nThird",'a','b'));
$this->create_description_node(str_replace("\r\n","\n",$record['pub_descrip']));
$this->create_description_node('The experiment was carried out between '.$record['start_date'].' and '.$record['end_date'],'note');
$query = sprintf("select idr,stype from list_prj_ids_v2 where stype in ('dataset','person')",$id_project);
// echo $query;
try {
$res = $db->query($query,PDO::FETCH_ASSOC);
if ($res==false) {
throw new Exception($query."\nIt found nothing.\n");
}
foreach ($res as $record) {
switch ($record['stype']) {
case 'dataset':
$this->create_relatedObject($record['idr'],'hasOutput');
break;
case 'person':
$this->create_relatedObject($record['idr'],'isManagedBy');
break;
}
}
// The Plant Accelerator always participates in Activity
$this->create_relatedObject('0874ad60-ab4d-11df-aebd-0002a5d5c51b','hasParticipant');
} catch (PDOException $e) {
process_pdo_error($query, $e);
}// end of try-catch block
} // end of function create_activity($key, $id_project)
/** The processor for creating metadata node of Collection. Called from create_obj_node.
* \param $table_name Type: string. The table name will be used to retrieve data from.
* \param $id_collect Type: integer. Internal collection id associated to this collection-dataset.
* \see Function create_obj_node.
*/
private function create_collection($table_name, $id_collect) {
$db = $this->db;
try {
$query = sprintf("select plant,variety,start_date,end_date,img_freq,anzsrc from %s where id_collect = %s",$table_name,$id_collect);
$res = exec_pdo_query($db, $query);
$dataset = $res->fetch(PDO::FETCH_ASSOC);
$res = exec_pdo_query($db, $query);
$record = $res->fetch(PDO::FETCH_ASSOC);
$query = 'select id_rep, inter_no, id_project from tpa_project_ids where id_collect = '.$id_collect;
$res = exec_pdo_query($db, $query);
$prj_info = $res->fetch(PDO::FETCH_ASSOC);
$query = 'select email from tpa_person where id_rep = '.$prj_info['id_rep'];
$res = exec_pdo_query($db, $query);
$email = $res->fetch(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
echo $query.' was failed\n';
echo $e->getMessage();
}
$this->create_rifcs_node('collection','dataset');
// Get the project inter_no as the name of this dataset
$c = $this->create_name_node();
$this->create_namePart($c,'Data set of Plant Accelerator Project '.$prj_info['inter_no']);
// locatin node: contact person
$l_node = $this->create_location_node();
$a_node = $this->create_address_node($l_node);
$this->create_e_node($a_node, $email['email']);
// location node: TPA's physical address
$l_node = $this->create_location_node();
$a_node = $this->create_address_node($l_node);
$this->create_physcial_addr_txt($a_node, 'The Plant Accelerator, Hartley Grove, Urrbrae, SA 5064') ;
// Temporal coverage of colletion
$dates = array(array('date'=>$dataset['start_date'],'type'=>'dateFrom'),array('date'=>$dataset['end_date'],'type'=>'dateTo'));
$this->create_coverage_tempo($dates);
// subject
$this->create_subject_node($dataset['aznsrc']);
// relatedOjbects
$query = sprintf("select idr,stype from list_prj_ids_v2(%d) where stype in ('project','person')",$prj_info['id_project']);
try {
$res = $db->query($query,PDO::FETCH_ASSOC);
if ($res==false) {
throw new Exception($query."\nIt found nothing.\n");
}
foreach ($res as $record) {
switch ($record['stype']) {
case 'project':
$this->create_relatedObject($record['idr'],'isOutputOf');
break;
case 'person':
$this->create_relatedObject($record['idr'],'isOwnedBy');
break;
}
}
} catch (PDOException $e) {
process_pdo_error($query, $e);
}// end of try-catch block
// right of accessing
$this->create_description_node('For information on rights and access to this dataset, please contact the owner.','accessRights');
// image data:
$imgs = ''; $ex_conf = '';
$dic = array('im_type_rgb'=>'RGB','im_type_nir'=>'NIR','im_type_fir'=>'FIR','im_type_nir_roots'=>'NIR Roots','im_type_fluo'=>'Fluorescence');
$query = 'select im_type_rgb,im_type_nir,im_type_fir,im_type_nir_roots,im_type_fluo, lines, treatments, replicates, total from ands_collection where id_collect = '. $id_collect;
$res = $db->query($query,PDO::FETCH_ASSOC);
if ($res==false) {
throw new Exception($query."\nIt found nothing.\n");
}
$info = $res->fetch();
foreach ($info as $item => $v) {
switch ($item) {
case 'im_type_rgb':
case 'im_type_nir':
case 'im_type_fir':
case 'im_type_nir_roots':
case 'im_type_fluo':
if (!empty($v)) { $imgs .= $dic[$item].', '; }
break;
default:
if (!empty($v)) { $ex_conf .= ' '.$item.' = '.$v.', '; }
break;
}
}
if (empty($imgs)) $imgs = "Images data of RGB, FIR, NIR, NIR Roots and Fluorescence cameras., ";
$imgs = substr($imgs,0,-2);
if (!empty($ex_conf)) $imgs = $imgs."\n".substr($ex_conf,0,-2);
$this->create_description_node($imgs);
// imaging frequency
$this->create_description_node('Imaging frequency: '.$dataset['img_freq'],'note');
} // end of function create_collection($key,$id_collect)
/** The processor for creating metadata node of Party. Called from create_obj_node. As party-person is different to party-group, there are two sub-functions are called accordingly.
* \param $table_name Type: string. The table name will be used to retrieve data from.
* \param $id_party Type: integer. Internal party id associated to this party.
* \see Function create_obj_node.
*/
private function create_party($table_name, $id_party) {
$db = $this->db;
$query = sprintf("SELECT set_type FROM oai_headers WHERE ori_table_name = '%s' AND ori_id = %s",$table_name,$id_party);
$res = exec_pdo_query($db, $query);
$party_type = $res->fetch(PDO::FETCH_ASSOC);
if (in_array($party_type['set_type'],array('person','group'))) {
$this->create_rifcs_node('party',$party_type['set_type']);
if ($party_type['set_type']=='person') {
$this->create_person($table_name, $id_party);
} elseif ($party_type['set_type']=='group') {
$this->create_group($table_name, $id_party); }
} else {
throw new Exception('Unsupported set_type: '.$party_type['set_type']);
}
} // end of function create_part($key,$id_party)
/** The processor for creating metadata node of Party. Called from create_obj_node. As party-person is different to party-group, there are two sub-functions are called accordingly.
* \param $table_name Type: string. The table name will be used to retrieve data from.
* \param $id_party Type: integer. Internal party id associated to this party-person.
* \see Function create_party.
*/
private function create_person($table_name, $id_party) {
$db = $this->db;
$query = sprintf("SELECT id_org, title, first_name, family_name, tel, fax, email, www, address, post_code, city,state,country,duty FROM %s WHERE id_rep = %s",$table_name, $id_party);
$res = exec_pdo_query($db, $query);
$info = $res->fetch(PDO::FETCH_ASSOC);
$c = $this->create_name_node();
if (!empty($info['title'])) $this->create_namePart($c,$info['title'],'title');
$this->create_namePart($c,$info['family_name'],'family');
$this->create_namePart($c,$info['first_name'],'given');
// locatin node: contact person
$l_node = $this->create_location_node();
$a_node = $this->create_address_node($l_node);
$this->create_e_node($a_node, $info['email']);
if (!empty($info['www'])) $this->create_e_node($a_node, $info['www'],'url');
$this->create_physcial_fone_fax($a_node, $info['tel'],'telephoneNumber');
if (!empty($info['fax'])) $this->create_physcial_fone_fax($a_node, $info['fax'],'faxNumber');
$add_txt = trim($info['address']).', '.$info['city'].' '.$info['state'].' '.$info['post_code'].', '.$info['country'];
// the strlength of ', , ' is 6
if (strlen($add_txt)>6) $this->create_physcial_addr_txt($a_node,$add_txt);
// related objects:
// their group: id_customer is a foreign key of tpa_organisation
$query = sprintf("SELECT get_identifier('tpa_organisation',%s)",$info['id_org']);
$res = exec_pdo_query($db, $query);
$info = $res->fetch(PDO::FETCH_NUM);
$this->create_relatedObject($info[0],'isMemberOf');
// their activities
$query = "SELECT list_persons_objs($id_party,'project')";
$res = exec_pdo_query($db, $query);
$info = $res->fetch(PDO::FETCH_NUM);
foreach ($info as $item) {
$this->create_relatedObject($item,'isManagerOf');
}
// their collections
$query = "SELECT list_persons_objs($id_party,'dataset')";
$res = exec_pdo_query($db, $query);
$info = $res->fetch(PDO::FETCH_NUM);
foreach ($info as $item) {
$this->create_relatedObject($item,'isOwnerOf');
}
}
/** The processor for creating metadata node of Party. Called from create_obj_node. As party-person is different to party-group, there are two sub-functions are called accordingly.
* \param $table_name Type: string. The table name will be used to retrieve data from.
* \param $id_party Type: integer. Internal party id associated to this party-group.
* \see Function create_party.
*/
private function create_group($table_name, $id_party) {
$db = $this->db;
// echo 'table: ',$table_name,' party: ',$id_party,"\n";
$query = sprintf("SELECT customer_name, abn, post_code, address, city, state, country, tel, fax, email, www, description FROM %s WHERE id_org = %s",$table_name, $id_party);
//echo $query;
$res = exec_pdo_query($db, $query);
$info = $res->fetch(PDO::FETCH_ASSOC);
$c = $this->create_name_node();
$this->create_namePart($c,$info['customer_name']);
if (!empty($info['abn'])) $this->create_identifier_node($info['abn'],'abn');
if (!empty($info['description'])) $this->create_description_node($info['description']);
$l_node = $this->create_location_node();
$a_node = $this->create_address_node($l_node);
$this->create_physcial_fone_fax($a_node, $info['tel'],'telephoneNumber');
$this->create_physcial_fone_fax($a_node, $info['fax'],'faxNumber');
$add_txt = trim($info['address']).', '.$info['city'].' '.$info['state'].' '.$info['post_code'].', '.$info['country'];
$this->create_physcial_addr_txt($a_node,$add_txt);
// related objects:
// their members:
$query = "SELECT list_pub_members($id_party)";
$res = exec_pdo_query($db, $query);
$info = $res->fetch(PDO::FETCH_NUM);
foreach ($info as $item) {
$this->create_relatedObject($item,'hasMember');
}
}
} // end of class ANDS_TPA

View File

@ -1,76 +0,0 @@
<?php
//
// DATABASE SETUP
//
$DB_HOST = '';
$DB_USER = '';
$DB_PASSWD = '';
$DB_NAME = '';
/*
* - <b>$SQL</b>: Settings for database and queries from database
*
* - <b>$DSN</b>: DSN for connecting your database. Reference PDO for details.
*
*/
// Data Source Name: This is the universal connection string
// if you use something other than mysql edit accordingly.
// Example for MySQL
// $DSN = "mysql://$DB_USER:$DB_PASSWD@$DB_HOST/$DB_NAME";
// Example for Oracle
// $DSN = "oci8://$DB_USER:$DB_PASSWD@$DB_NAME";
$DSN = "mysql:host={$DB_HOST};dbname={$DB_NAME};port=3306;";
// the charset you store your metadata in your database
// currently only utf-8 and iso8859-1 are supported
$charset = "utf8";
// if entities such as < > ' " in your metadata has already been escaped
// then set this to true (e.g. you store < as &lt; in your DB)
$xmlescaped = false;
// We store multiple entries for one element in a single row
// in the database. SQL['split'] lists the delimiter for these entries.
// If you do not do this, do not define $SQL['split']
// $SQL['split'] = ';';
// the name of the table where your store your metadata's header
$SQL['table'] = 'oai_headers';
// the name of the column where you store the unique identifiers
// pointing to your item.
// this is your internal identifier for the item
$SQL['identifier'] = 'oai_identifier';
$SQL['metadataPrefix'] = 'oai_metadataprefix';
// If you want to expand the internal identifier in some way
// use this (but not for OAI stuff, see next line)
$idPrefix = '';
// this is your external (OAI) identifier for the item
// this will be expanded to
// oai:$repositoryIdentifier:$idPrefix$SQL['identifier']
// should not be changed
//
// Commented out 24/11/10 14:19:09
// $oaiprefix = "oai".$delimiter.$repositoryIdentifier.$delimiter.$idPrefix;
$oaiprefix = "";
// adjust anIdentifier with sample contents an identifier
// $sampleIdentifier = $oaiprefix.'anIdentifier';
// the name of the column where you store your datestamps
$SQL['datestamp'] = 'datestamp';
// the name of the column where you store information whether
// a record has been deleted. Leave it as it is if you do not use
// this feature.
$SQL['deleted'] = 'deleted';
// to be able to quickly retrieve the sets to which one item belongs,
// the setnames are stored for each item
// the name of the column where you store sets
$SQL['set'] = 'oai_set';

View File

@ -1,35 +0,0 @@
<?php
/**
* - <b>$METADATAFORMATS</b>: List of supported metadata formats. It is a two-dimensional array with keys.
* Each supported format is one element of this array at the first dimension.
* The key is the name of a metadata format.
* The exact number of items within each format associated array depends on the nature of a metadata format.
* Most definitions are done here but handlers themselves are defined in separated files because only the names of PHP script are listed here.
* - metadataPrefix
* - schema
* - metadataNamespace
* - myhandler
* - other optional items: record_prefix, record_namespace and etc.
*/
// define all supported metadata formats, has to be an array
//
// myhandler is the name of the file that handles the request for the
// specific metadata format.
// [record_prefix] describes an optional prefix for the metadata
// [record_namespace] describe the namespace for this prefix
$METADATAFORMATS = array (
'rif' => array('metadataPrefix'=>'rif',
'schema'=>'http://services.ands.org.au/sandbox/orca/schemata/registryObjects.xsd',
'metadataNamespace'=>'http://ands.org.au/standards/rif-cs/registryObjects/',
),
'oai_dc' => array('metadataPrefix'=>'oai_dc',
'schema'=>'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
'metadataNamespace'=>'http://www.openarchives.org/OAI/2.0/oai_dc/',
'record_prefix'=>'dc',
'record_namespace' => 'http://purl.org/dc/elements/1.1/'
)
);
if (!is_array($METADATAFORMATS)) { exit("Configuration of METADATAFORMAT has been wrongly set. Correct your ".__FILE__);}

View File

@ -1,28 +0,0 @@
<?php
/*
*
* - <b>$SETS</b>: An array with key words . List of supported SETs.
*
*/
// define all supported sets in your repository
$SETS = array (
array('setSpec'=>'class:activity', 'setName'=>'Activities'),
array('setSpec'=>'class:collection', 'setName'=>'Collections'),
array('setSpec'=>'class:party', 'setName'=>'Parties'),
/*
array('setSpec'=>'math', 'setName'=>'Mathematics') ,
array('setSpec'=>'phys', 'setName'=>'Physics'),
array('setSpec'=>'phdthesis',
'setName'=>'PHD Thesis',
'setDescription'=>'<oai_dc:dc xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/
http://www.openarchives.org/OAI/2.0/oai_dc.xsd">
<dc:description>This set contains metadata describing
electronic music recordings made during the 1950ies</dc:description>
</oai_dc:dc>')
*/
);

122
oai2.php
View File

@ -8,62 +8,15 @@
* @see http://www.openarchives.org/OAI/2.0/openarchivesprotocol.htm
*
* It needs other files:
* - oaidp-config.php : Configuration of provider
* - oaidp-util.php : Utility functions
* - xml_creater.php : XML generating functions
*
* \todo <b>Remember:</b> to define your own classess for generating metadata records.
* In common cases, you have to implement your own code to act fully and correctly.
* For generic usage, you can try the ANDS_Response_XML defined in xml_creater.php.
*/
/**
* Supported attributes associate to verbs.
*/
if (in_array($_SERVER['REQUEST_METHOD'],array('GET','POST'))) {
$args = $_REQUEST;
} else {
$errors[] = oai_error('badRequestMethod', $_SERVER['REQUEST_METHOD']);
}
define('MY_URI', 'dev2.moodle.ufsc.br');
require_once('oaidp-util.php');
// Always using htmlentities() function to encodes the HTML entities submitted by others.
// No one can be trusted.
foreach ($args as $key => $val) {
$checking = htmlspecialchars(stripslashes($val));
if (!is_valid_attrb($checking)) {
$errors[] = oai_error('badArgument', $checking);
} else {$args[$key] = $checking; }
}
if (!empty($errors)) {
oai_exit();
}
$attribs = array ('from', 'identifier', 'metadataPrefix', 'set', 'resumptionToken', 'until');
foreach($attribs as $val) {
unset($$val);
}
require_once('oaidp-config.php');
require_once('config/metadataformats.php');
require_once('config/sets.php');
require_once('config/database.php');
// For generic usage or just trying:
// require_once('xml_creater.php');
// In common cases, you have to implement your own code to act fully and correctly.
require_once('ands_tpa.php');
// Default, there is no compression supported
$compress = FALSE;
if (isset($compression) && is_array($compression)) {
if (in_array('gzip', $compression) && ini_get('output_buffering')) {
$compress = TRUE;
}
}
require_once('xml_creater.php');
require_once('oai2server.php');
/**
@ -94,6 +47,73 @@ $identifyResponse["deletedRecord"] = 'no'; // How your repository handles deleti
// maintained. It MAY reveal a deleted status for records.
$identifyResponse["granularity"] = 'YYYY-MM-DDThh:mm:ssZ';
$repositoryIdentifier = 'dev2.moodle.ufsc.br.';
$oai2 = new OAI2Server($_REQUEST, $identifyResponse,
array(
'ListMetadataFormats' =>
function($identifier = '') {
// throws new OAI2Exception('idDoesNotExist', '', $identifier)
return
array('rif' => array('metadataPrefix'=>'rif',
'schema'=>'http://services.ands.org.au/sandbox/orca/schemata/registryObjects.xsd',
'metadataNamespace'=>'http://ands.org.au/standards/rif-cs/registryObjects/',
),
'oai_dc' => array('metadataPrefix'=>'oai_dc',
'schema'=>'http://www.openarchives.org/OAI/2.0/oai_dc.xsd',
'metadataNamespace'=>'http://www.openarchives.org/OAI/2.0/oai_dc/',
'record_prefix'=>'dc',
'record_namespace' => 'http://purl.org/dc/elements/1.1/'));
},
$oai2 = new OAI2Server($args, $repositoryIdentifier, $identifyResponse);
'ListSets' =>
function($resumptionToken = '') {
return
array (
array('setSpec'=>'class:collection', 'setName'=>'Collections'),
array('setSpec'=>'math', 'setName'=>'Mathematics') ,
array('setSpec'=>'phys', 'setName'=>'Physics'),
array('setSpec'=>'phdthesis', 'setName'=>'PHD Thesis',
'setDescription'=>
'<oai_dc:dc xmlns:oai_dc="http://www.openarchives.org/OAI/2.0/oai_dc/" '.
' xmlns:dc="http://purl.org/dc/elements/1.1/" '.
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '.
' xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai_dc/ '.
' http://www.openarchives.org/OAI/2.0/oai_dc.xsd"> '.
' <dc:description>This set contains metadata describing '.
' electronic music recordings made during the 1950ies</dc:description> '.
' </oai_dc:dc>'));
},
'ListRecords' =>
function($metadataPrefix, $from = '', $until = '', $set = '', $count = false, $deliveredRecords = 0, $maxItems = 0) {
// throws new OAI2Exception('noRecordsMatch')
// throws new OAI2Exception('noSetHierarchy')
if ($count) {
return 10;
}
return array();
},
'GetRecord' =>
function($identifier, $metadataPrefix) {
// throws new OAI2Exception('idDoesNotExist', '', $identifier) if record not found
return array('identifier' => 'dev.testing.pmh',
'datestamp' => date('Y-m-d-H:s'),
'set' => 'class:activity',
'metadata' => array(
'container_name' => 'oai_dc:dc',
'container_attributes' => array(
'xmlns:oai_dc' => "http://www.openarchives.org/OAI/2.0/oai_dc/",
'xmlns:dc' => "http://purl.org/dc/elements/1.1/",
'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
'xsi:schemaLocation' =>
'http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd'
),
'fields' => array(
'dc:title' => 'Testing records',
'dc:author' => 'Neis'
)
));
},
)
);

78
oai2exception.php Normal file
View File

@ -0,0 +1,78 @@
<?php
class OAI2Exception extends Exception {
function __construct($code = 0, $argument = '', $value = '') {
$this->errorTable = array(
'badArgument' => array(
'text' => "Attribute '{$argument}' is not allowed to appear in element 'request'.",
),
'badGranularity' => array(
'text' => "The value '{$value}' of attribute '{$argument}' on element 'request' is not valid with respect to its type, 'UTCdatetimeType'.",
'code' => 'badArgument',
),
'badResumptionToken' => array(
'text' => "The resumptionToken '{$value}' does not exist or has already expired.",
),
'badRequestMethod' => array(
'text' => "The request method '{$argument}' is unknown.",
'code' => 'badVerb',
),
'badVerb' => array(
'text' => "The value '{$argument}' of attribute 'verb' on element 'request' is not valid with respect to its type, 'verbType'",
),
'cannotDisseminateFormat' => array(
'text' => "The metadata format '{$value}' given by {$argument} is not supported by this repository.",
),
'exclusiveArgument' => array(
'text' => 'The usage of resumptionToken as an argument allows no other arguments.',
'code' => 'badArgument',
),
'idDoesNotExist' => array(
'text' => "The value '{$value}' of the identifier does not exist in this repository.",
/*
if (!is_valid_uri($value)) {
'code' = 'badArgument',
'text' .= ' Invalidated URI has been detected.',
}
*/
),
'missingArgument' => array(
'text' => "The required argument '{$argument}' is missing in the request.",
'code' => 'badArgument',
),
'noRecordsMatch' => array(
'text' => 'The combination of the given values results in an empty list.',
),
'noMetadataFormats' => array(
'text' => 'There are no metadata formats available for the specified item.',
),
'noVerb' => array(
'text' => 'The request does not provide any verb.',
'code' => 'badVerb',
),
'noSetHierarchy' => array(
'text' => 'This repository does not support sets.',
),
'sameArgument' => array(
'text' => 'Do not use the same argument more than once.',
'code' => 'badArgument',
),
'sameVerb' => array(
'text' => 'Do not use verb more than once.',
'code' => 'badVerb',
),
'notImp' => array(
'text' => 'Not yet implemented.',
'code' => 'debug',
),
''=> array(
'text' => "Unknown error: code: '{'code'}', argument: '{$argument}', value: '{$value}'",
'code' => 'badArgument',
)
);
parent::__construct($this->errorTable[$code]['text'], $code);
}
}

View File

@ -1,95 +1,75 @@
<?php
/*
* - Actions:
* - identify.php : About the provider
* - listmetadataformats.php : List supported metadata formats
* - listrecords.php : List identifiers and records
* - listsets.php : List sets
* - getrecord.php : Get a record / Your own implementation for providing metadata records.
require_once('oai2exception.php');
require_once('oai2xml.php');
/**
* The content-type the WWW-server delivers back. For debug-puposes, "text/plain"
* is easier to view. On a production site you should use "text/xml".
*/
define('CONTENT_TYPE', 'Content-Type: text/xml');
/** After 24 hours resumptionTokens become invalid. Unit is second. */
define('TOKEN_VALID',24*3600);
/** Where token is saved and path is included */
define('TOKEN_PREFIX','/tmp/oai_pmh-');
class OAI2Server {
public $errors = array();
/*
* @param $delimiter = ':'; should not be changed. Only useful when NODE description is included in the response to Identifier
*/
function __construct($args, $repositoryIdentifier, $identifyResponse,
$delimiter = ':', $sampleIdentifier = false, $show_identifier = false) {
function __construct($args, $identifyResponse, $callbacks) {
$this->args = $args;
$this->repositoryIdentier = $repositoryIdentifier;
$this->identifyResponse = $identifyResponse;
$this->listMetadataFormatsCallback = $callbacks['ListMetadataFormats'];
$this->listSetsCallback = $callbacks['ListSets'];
$this->listRecordsCallback = $callbacks['ListRecords'];
$this->getRecordCallback = $callbacks['GetRecord'];
$this->respond();
}
private function respond() {
if (!isset($this->args['verb']) || empty($this->args['verb'])) {
$this->errors[] = oai_error('noVerb');
$this->errors[] = new OAI2Exception('noVerb');
} else {
switch ($this->args['verb']) {
case 'Identify':
// we never use compression in Identify
$compress = FALSE;
$this->outputObj = $this->identify();
break;
case 'Identify': $this->identify(); break;
case 'ListMetadataFormats':
$this->outputObj = $this->listMetadataFormats();
break;
case 'ListMetadataFormats': $this->listMetadataFormats(); break;
case 'ListSets':
$this->outputObj = $this->listSets();
break;
case 'ListSets': $this->listSets(); break;
case 'ListIdentifiers':
case 'ListRecords':
$this->outputObj = $this->listRecords();
break;
case 'ListRecords': $this->listRecords(); break;
case 'GetRecord':
$this->outputObj = $this->getRecord();
break;
case 'GetRecord': $this->getRecord(); break;
default:
// we never use compression with errors
$compress = FALSE;
$this->errors[] = oai_error('badVerb', $this->args['verb']);
default: $this->errors[] = new OAI2Exception('badVerb', $this->args['verb']);
}
}
if (empty($this->errors)) {
$this->display();
if (isset($this->outputObj)) {
header(CONTENT_TYPE);
$this->outputObj->display();
} else {
exit("Nothing to output. May be a bug.");
}
} else {
$this->errorResponse();
}
}
function errorResponse() {
$e = new ANDS_Error_XML($this->args,$this->errors);
private function errorResponse() {
$e = new OAI2XMLError($this->args,$this->errors);
header(CONTENT_TYPE);
$e->display();
exit();
}
function display() {
if (isset($this->outputObj)) {
if ($compress) {
ob_start('ob_gzhandler');
}
header(CONTENT_TYPE);
$this->outputObj->display();
if ($compress) {
ob_end_flush();
}
} else {
exit("Nothing to output. May be a bug.");
}
}
/**
* Response to Verb Identify
*
@ -97,193 +77,47 @@ class OAI2Server {
*
* http://www.openarchives.org/OAI/2.0/guidelines-oai-identifier.htm for details
*/
public function identify($show_identifier, $repositoryIdentifier, $delimiter, $sampleIdentifier) {
public function identify() {
if (count($this->args) > 1) {
foreach($args as $key => $val) {
if(strcmp($key,"verb")!=0) {
$this->errors[] = oai_error('badArgument', $key, $val);
$this->errors[] = new OAI2Exception('badArgument', $key, $val);
}
}
}
$outputObj = new ANDS_Response_XML($this->args);
$this->outputObj = new ANDS_Response_XML($this->args);
foreach($this->identifyResponse as $key => $val) {
$outputObj->add2_verbNode($key, $val);
$this->outputObj->add2_verbNode($key, $val);
}
// A description MAY be included.
// Use this if you choose to comply with a specific format of unique identifiers
// for items.
// See http://www.openarchives.org/OAI/2.0/guidelines-oai-identifier.htm
// for details
// As they will not be changed, using string for simplicity.
$output = '';
if ($this->show_identifier && $this->repositoryIdentifier && $this->delimiter && $this->sampleIdentifier) {
$output .=
' <description>
<oai-identifier xmlns="http://www.openarchives.org/OAI/2.0/oai-identifier"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/oai-identifier
http://www.openarchives.org/OAI/2.0/oai-identifier.xsd">
<scheme>oai</scheme>
<repositoryIdentifier>'.$repositoryIdentifier.'</repositoryIdentifier>
<delimiter>'.$delimiter.'</delimiter>
<sampleIdentifier>'.$sampleIdentifier.'</sampleIdentifier>
</oai-identifier>
</description>'."\n";
}
// A description MAY be included.
// This example from arXiv.org is used by the e-prints community, please adjust
// see http://www.openarchives.org/OAI/2.0/guidelines-eprints.htm for details
// To include, change 'false' to 'true'.
if (false) {
$output .=
' <description>
<eprints xmlns="http://www.openarchives.org/OAI/1.1/eprints"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/1.1/eprints
http://www.openarchives.org/OAI/1.1/eprints.xsd">
<content>
<text>Author self-archived e-prints</text>
</content>
<metadataPolicy />
<dataPolicy />
<submissionPolicy />
</eprints>
</description>'."\n";
}
// If you want to point harvesters to other repositories, you can list their
// base URLs. Usage of friends container is RECOMMENDED.
// see http://www.openarchives.org/OAI/2.0/guidelines-friends.htm
// for details
// To include, change 'false' to 'true'.
if (false) {
$output .=
' <description>
<friends xmlns="http://www.openarchives.org/OAI/2.0/friends/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/friends/
http://www.openarchives.org/OAI/2.0/friends.xsd">
<baseURL>http://naca.larc.nasa.gov/oai2.0/</baseURL>
<baseURL>http://techreports.larc.nasa.gov/ltrs/oai2.0/</baseURL>
<baseURL>http://physnet.uni-oldenburg.de/oai/oai2.php</baseURL>
<baseURL>http://cogprints.soton.ac.uk/perl/oai</baseURL>
<baseURL>http://ub.uni-duisburg.de:8080/cgi-oai/oai.pl</baseURL>
<baseURL>http://rocky.dlib.vt.edu/~jcdlpix/cgi-bin/OAI1.1/jcdlpix.pl</baseURL>
</friends>
</description>'."\n";
}
// If you want to provide branding information, adjust accordingly.
// Usage of friends container is OPTIONAL.
// see http://www.openarchives.org/OAI/2.0/guidelines-branding.htm
// for details
// To include, change 'false' to 'true'.
if (false) {
$output .=
' <description>
<branding xmlns="http://www.openarchives.org/OAI/2.0/branding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.openarchives.org/OAI/2.0/branding/
http://www.openarchives.org/OAI/2.0/branding.xsd">
<collectionIcon>
<url>http://my.site/icon.png</url>
<link>http://my.site/homepage.html</link>
<title>MySite(tm)</title>
<width>88</width>
<height>31</height>
</collectionIcon>
<metadataRendering
metadataNamespace="http://www.openarchives.org/OAI/2.0/oai_dc/"
mimeType="text/xsl">http://some.where/DCrender.xsl</metadataRendering>
<metadataRendering
metadataNamespace="http://another.place/MARC"
mimeType="text/css">http://another.place/MARCrender.css</metadataRendering>
</branding>
</description>'."\n";
}
if(strlen($output)>10) {
$des = $outputObj->doc->createDocumentFragment();
$des->appendXML($output);
$outputObj->verbNode->appendChild($des);
}
return $outputObj;
}
/**
* Response to Verb ListMetadataFormats
*
* The information of supported metadata formats :
* try database table $SQL['table']
* else try $METADATAFORMATS array from config-metadataformats.php
* The information of supported metadata formats
*/
public function listMetadataFormats() {
global $DSN, $DB_USER, $DB_PASSWD, $METADATAFORMATS, $SQL;
$checkList = array("ops"=>array("identifier"));
$this->checkArgs($checkList);
// Create a PDO object
try {
$db = new PDO($DSN, $DB_USER, $DB_PASSWD);
} catch (PDOException $e) {
exit('Connection failed: ' . $e->getMessage());
}
if (isset($this->args['identifier'])) {
$identifier = $this->args['identifier'];
$query = 'select '.$SQL['metadataPrefix'].' FROM '.$SQL['table']. " WHERE ".$SQL['identifier']." = '".$id."'";
$res = $db->query($query);
if ($res==false) {
if (SHOW_QUERY_ERROR) {
echo __FILE__.','.__LINE__."<br />";
echo "Query: $query<br />\n";
die($db->errorInfo());
} else {
$this->errors[] = oai_error('idDoesNotExist','', $identifier);
}
} else {
$record = $res->fetch();
if($record===false) {
$this->errors[] = oai_error('idDoesNotExist', '', $identifier);
} else {
$mf = explode(",",$record[$SQL['metadataPrefix']]);
if ($formats = call_user_func($this->listMetadataFormatsCallback, $this->args['identifier'])) {
$this->outputObj = new ANDS_Response_XML($this->args);
foreach($formats as $key => $val) {
$cmf = $this->outputObj->add2_verbNode("metadataFormat");
$this->outputObj->addChild($cmf,'metadataPrefix',$key);
$this->outputObj->addChild($cmf,'schema',$val['schema']);
$this->outputObj->addChild($cmf,'metadataNamespace',$val['metadataNamespace']);
}
return $outputObj;
}
$this->errors[] = new OAI2Exception('noMetadataFormats');
} catch (OAI2Exception $e) {
$this->errors[] = $e;
}
//break and clean up on error
if (!empty($this->errors)) {
$this->errorResponse();
}
$outputObj = new ANDS_Response_XML($this->args);
if (isset($mf)) {
foreach($mf as $key) {
$val = $METADATAFORMATS[$key];
$this->addMetedataFormat($outputObj,$key, $val);
}
} elseif (is_array($METADATAFORMATS)) {
foreach($METADATAFORMATS as $key=>$val) {
$this->addMetedataFormat($outputObj,$key, $val);
}
} else { // a very unlikely event
$this->errors[] = oai_error('noMetadataFormats');
$this->errorResponse();
}
return $outputObj;
}
/**
@ -293,36 +127,34 @@ class OAI2Server {
* This variable is filled in config-sets.php
*/
public function listSets() {
global $SETS;
$sets = $SETS;
if (isset($this->args['resumptionToken']) && count($this->args) > 2) {
$this->errors[] = oai_error('exclusiveArgument');
$this->errors[] = new OAI2Exception('exclusiveArgument');
}
$checkList = array("ops"=>array("resumptionToken"));
$this->checkArgs($checkList);
if (is_array($sets)) {
$outputObj = new ANDS_Response_XML($this->args);
if ($sets = call_user_func($this->listSetsCallback)) {
$this->outputObj = new ANDS_Response_XML($this->args);
foreach($sets as $set) {
$setNode = $outputObj->add2_verbNode("set");
$setNode = $this->outputObj->add2_verbNode("set");
foreach($set as $key => $val) {
if($key=='setDescription') {
$desNode = $outputObj->addChild($setNode,$key);
$des = $outputObj->doc->createDocumentFragment();
$desNode = $this->outputObj->addChild($setNode,$key);
$des = $this->outputObj->doc->createDocumentFragment();
$des->appendXML($val);
$desNode->appendChild($des);
} else {
$outputObj->addChild($setNode,$key,$val);
$this->outputObj->addChild($setNode,$key,$val);
}
}
}
} else {
$this->errors[] = oai_error('noSetHierarchy');
oai_exit();
$this->errors[] = new OAI2Exception('noSetHierarchy');
}
return $outputObj;
}
/**
@ -335,62 +167,44 @@ class OAI2Server {
* The reset of information will be extracted from database based those two parameters.
*/
public function getRecord() {
global $METADATAFORMATS, $DSN, $DB_USER, $DB_PASSWD, $SQL;
$checkList = array("required"=>array("metadataPrefix","identifier"));
$this->checkArgs($checkList);
$metadataPrefix = $this->args['metadataPrefix'];
if (!isset($METADATAFORMATS[$metadataPrefix])) {
$this->errors[] = oai_error('cannotDisseminateFormat', 'metadataPrefix', $metadataPrefix);
$metadataFormats = call_user_func($this->listMetadataFormatsCallback);
if (!isset($metadataFormats[$metadataPrefix])) {
$this->errors[] = new OAI2Exception('cannotDisseminateFormat', 'metadataPrefix', $metadataPrefix);
}
// Create a PDO object
try {
$db = new PDO($DSN, $DB_USER, $DB_PASSWD);
} catch (PDOException $e) {
exit('Connection failed: ' . $e->getMessage());
if ($record = call_user_func($this->getRecordCallback, $this->args['identifier'], $metadataPrefix)) {
$identifier = $record['identifier'];
$datestamp = formatDatestamp($record['datestamp']);
$set = $record['set'];
$status_deleted = (isset($record['deleted']) && ($record['deleted'] == 'true') &&
(($this->identifyResponse['deletedRecord'] == 'transient') ||
($this->identifyResponse['deletedRecord'] == 'persistent')));
$this->outputObj = new ANDS_Response_XML($this->args);
$cur_record = $this->outputObj->create_record();
$cur_header = $this->outputObj->create_header($identifier, $datestamp, $set, $cur_record);
if ($status_deleted) {
$cur_header->setAttribute("status","deleted");
} else {
$this->add_metadata($cur_record, $record);
}
}
} catch (OAI2Exception $e) {
$this->errors[] = $e;
}
$identifier = $this->args['identifier'];
$query = selectallQuery($metadataPrefix, $identifier);
$res = $db->query($query);
if ($res===false) {
$this->errors[] = oai_error('idDoesNotExist', '', $identifier);
} elseif (!$res->rowCount()) { // based on PHP manual, it might only work for some DBs
$this->errors[] = oai_error('idDoesNotExist', '', $identifier);
}
if (!empty($this->errors)) {
oai_exit();
}
$record = $res->fetch(PDO::FETCH_ASSOC);
if ($record===false) {
$this->errors[] = oai_error('idDoesNotExist', '', $identifier);
}
$identifier = $record[$SQL['identifier']];;
$datestamp = formatDatestamp($record[$SQL['datestamp']]);
$status_deleted = (isset($record[$SQL['deleted']]) && ($record[$SQL['deleted']] == 'true') &&
($this->identifyResponse['deletedRecord'] == 'transient' || $this->identifyResponse['deletedRecord'] == 'persistent'));
$outputObj = new ANDS_Response_XML($this->args);
$cur_record = $outputObj->create_record();
$cur_header = $outputObj->create_header($identifier, $datestamp,$record[$SQL['set']],$cur_record);
// return the metadata record itself
if ($status_deleted) {
$cur_header->setAttribute("status","deleted");
} else {
call_user_func(array($this, "{$metadataPrefix}_create_metadata"),
$outputObj, $cur_record, $identifier, $record[$SQL['set']], $db);
}
return $outputObj;
}
/**
@ -402,13 +216,10 @@ class OAI2Server {
* Only 'metadataPrefix' is compulsory. All conditions are accessible through global array variable <B>$args</B> by keywords.
*/
public function listRecords() {
global $SQL, $METADATAFORMATS, $DSN, $DB_USER, $DB_PASSWD, $SETS;
$sets = $SETS;
if(isset($this->args['resumptionToken'])) {
if (isset($this->args['resumptionToken'])) {
if (count($this->args) > 2) {
$this->errors[] = oai_error('exclusiveArgument');
$this->errors[] = new OAI2Exception('exclusiveArgument');
}
$checkList = array("ops"=>array("resumptionToken"));
} else {
@ -416,203 +227,103 @@ class OAI2Server {
}
$this->checkArgs($checkList);
$metadataFormats = call_user_func($this->listMetadataFormatsCallback, $this->args);
if (!isset($metadataFormats[$this->args['metadataPrefix']])) {
$this->errors[] = new OAI2Exception('cannotDisseminateFormat', 'metadataPrefix', $this->args['metadataPrefix']);
}
// Resume previous session?
if (isset($this->args['resumptionToken'])) {
if (!file_exists(TOKEN_PREFIX.$this->args['resumptionToken'])) {
$this->errors[] = oai_error('badResumptionToken', '', $this->args['resumptionToken']);
$this->errors[] = new OAI2Exception('badResumptionToken', '', $this->args['resumptionToken']);
} else {
$readings = readResumToken(TOKEN_PREFIX.$this->args['resumptionToken']);
if ($readings == false) {
$this->errors[] = oai_error('badResumptionToken', '', $this->args['resumptionToken']);
} else {
list($deliveredrecords, $extquery, $metadataPrefix) = $readings;
}
}
} else { // no, we start a new session
$deliveredrecords = 0;
$extquery = '';
if ($readings = $this->readResumptionToken(TOKEN_PREFIX.$this->args['resumptionToken'])) {
list($deliveredRecords, $metadataPrefix, $from, $until, $set) = $readings;
} else {
$this->errors[] = new OAI2Exception('badResumptionToken', '', $this->args['resumptionToken']);
}
}
} else {
$deliveredRecords = 0;
$metadataPrefix = $this->args['metadataPrefix'];
if (isset($args['from'])) {
$from = checkDateFormat($this->args['from']);
$extquery .= fromQuery($from);
}
if (isset($args['until'])) {
$until = checkDateFormat($this->args['until']);
$extquery .= untilQuery($until);
}
if (isset($args['set'])) {
if (is_array($sets)) {
$extquery .= setQuery($this->args['set']);
} else {
$this->errors[] = oai_error('noSetHierarchy');
}
}
}
if (!isset($METADATAFORMATS[$metadataPrefix])) {
$this->errors[] = oai_error('cannotDisseminateFormat', 'metadataPrefix', $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 (!empty($this->errors)) {
$this->errorResponse();
}
// Create a PDO object
$maxItems = 1000;
try {
$db = new PDO($DSN, $DB_USER, $DB_PASSWD);
} catch (PDOException $e) {
exit('Connection failed: ' . $e->getMessage());
}
$query = selectallQuery($metadataPrefix) . $extquery;
$records_count = call_user_func($this->listRecordsCallback, $metadataPrefix, $from, $until, $set, true);
$res = $db->prepare($query, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL));
$r = $res->execute();
if ($r===false) {
$this->errors[] = oai_error('noRecordsMatch');
} else {
$r = $res->setFetchMode(PDO::FETCH_ASSOC);
if ($r===false) {
exit("FetchMode is not supported");
}
$num_rows = rowCount($metadataPrefix, $extquery, $db);
if ($num_rows==0) {
$this->errors[] = oai_error('noRecordsMatch');
}
}
$records = call_user_func($this->listRecordsCallback, $metadataPrefix, $from, $until, $set, false, $deliveredRecords, $maxItems);
if (!empty($this->errors)) {
$this->errorResponse();
}
$this->outputObj = new ANDS_Response_XML($this->args);
foreach ($records as $record) {
// Will we need a new ResumptionToken?
if($this->args['verb']=='ListRecords') {
$maxItems = MAXRECORDS;
} elseif($this->args['verb']=='ListIdentifiers') {
$maxItems = MAXIDS;
} else {
exit("Check ".__FILE__." ".__LINE__.", there is something wrong.");
}
$maxrec = min($num_rows - $deliveredrecords, $maxItems);
$identifier = $record['identifier'];
$datestamp = formatDatestamp($record['datestamp']);
$setspec = $record[$SQL['set']];
if ($num_rows - $deliveredrecords > $maxItems) {
$cursor = (int)$deliveredrecords + $maxItems;
$restoken = createResumToken($cursor, $extquery, $metadataPrefix);
$expirationdatetime = gmstrftime('%Y-%m-%dT%TZ', time()+TOKEN_VALID);
} elseif (isset($args['resumptionToken'])) {
// Last delivery, return empty ResumptionToken
$restoken = $args['resumptionToken']; // just used as an indicator
unset($expirationdatetime);
}
$status_deleted = (isset($record['deleted']) && ($record['deleted'] === true) &&
(($this->identifyResponse['deletedRecord'] == 'transient') ||
($this->identifyResponse['deletedRecord'] == 'persistent')));
if (isset($this->args['resumptionToken'])) {
$record = $res->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_ABS, $deliveredrecords);
}
// Record counter
$countrec = 0;
// Publish a batch to $maxrec number of records
$outputObj = new ANDS_Response_XML($this->args);
while ($countrec++ < $maxrec) {
$record = $res->fetch(PDO::FETCH_ASSOC);
if ($record===false) {
if (SHOW_QUERY_ERROR) {
echo __FILE__.",". __LINE__."<br />";
print_r($db->errorInfo());
exit();
if($this->args['verb'] == 'ListRecords') {
$cur_record = $this->outputObj->create_record();
$cur_header = $this->outputObj->create_header($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->outputObj->create_header($identifier, $datestamp,$setspec);
}
if ($status_deleted) {
$cur_header->setAttribute("status","deleted");
}
}
$identifier = $oaiprefix.$record[$SQL['identifier']];
$datestamp = formatDatestamp($record[$SQL['datestamp']]);
$setspec = $record[$SQL['set']];
// Will we need a new ResumptionToken?
if ($records_count - $deliveredRecords > $maxItems) {
$status_deleted = (isset($record[$SQL['deleted']]) && ($record[$SQL['deleted']] === true) &&
($this->identifyResponse['deletedRecord'] == 'transient' || $this->identifyResponse['deletedRecord'] == 'persistent'));
$deliveredRecords += $maxItems;
$restoken = $this->createResumptionToken($deliveredRecords);
if($this->args['verb']=='ListRecords') {
$cur_record = $outputObj->create_record();
$cur_header = $outputObj->create_header($identifier, $datestamp,$setspec,$cur_record);
// return the metadata record itself
if (!$status_deleted) {
call_user_func(array($this, "{$metadataPrefix}_create_metadata"),
$outputObj, $cur_record, $identifier, $setspec, $db);
}
} else { // for ListIdentifiers, only identifiers will be returned.
$cur_header = $outputObj->create_header($identifier, $datestamp,$setspec);
$expirationDatetime = gmstrftime('%Y-%m-%dT%TZ', time()+TOKEN_VALID);
} elseif (isset($args['resumptionToken'])) {
// Last delivery, return empty ResumptionToken
$restoken = null;
$expirationDatetime = null;
}
if ($status_deleted) {
$cur_header->setAttribute("status","deleted");
if (isset($restoken)) {
$this->outputObj->create_resumpToken($restoken,$expirationDatetime,$records_count,$deliveredRecords);
}
}
// ResumptionToken
if (isset($restoken)) {
if(isset($expirationdatetime)) {
$outputObj->create_resumpToken($restoken,$expirationdatetime,$num_rows,$cursor);
} else {
$outputObj->create_resumpToken('',null,$num_rows,$deliveredrecords);
}
}
return $outputObj;
}
/**
* Add a metadata format node to an ANDS_Response_XML
* \param &$outputObj
* type: ANDS_Response_XML. The ANDS_Response_XML object for output.
* \param $key
* type string. The name of new node.
* \param $val
* type: array. Values accessable through keywords 'schema' and 'metadataNamespace'.
*
*/
private function addMetedataFormat(&$outputObj,$key,$val) {
$cmf = $outputObj->add2_verbNode("metadataFormat");
$outputObj->addChild($cmf,'metadataPrefix',$key);
$outputObj->addChild($cmf,'schema',$val['schema']);
$outputObj->addChild($cmf,'metadataNamespace',$val['metadataNamespace']);
}
private function rif_create_metadata($outputObj, $cur_record, $identifier, $setspec, $db) {
$metadata_node = $outputObj->create_metadata($cur_record);
$obj_node = new ANDS_TPA($outputObj, $metadata_node, $db);
try {
$obj_node->create_obj_node($setspec, $identifier);
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), " when adding $identifier\n";
} catch (OAI2Exception $e) {
$this->errors[] = $e;
}
}
private function oai_dc_create_metadata($outputObj, $cur_record, $identifier, $setspec, $db) {
private function add_metadata($cur_record, $record) {
$sql = "SELECT dc_title, dc_creator, dc_subject, dc_description, dc_contributor, dc_publisher,
dc_date , dc_type , dc_format , dc_identifier , dc_source , dc_language,
dc_relation , dc_coverage , dc_rights
FROM oai_records
WHERE oai_set = '{$setspec}'
AND oai_identifier = '{$identifier}'";
$meta_node = $this->outputObj->addChild($cur_record ,"metadata");
$res = exec_pdo_query($db,$sql);
$record = $res->fetch(PDO::FETCH_ASSOC);
$meta_node = $outputObj->addChild($cur_record ,"metadata");
$schema_node = $outputObj->addChild($meta_node, 'oai_dc:dc');
$schema_node->setAttribute('xmlns:oai_dc', "http://www.openarchives.org/OAI/2.0/oai_dc/");
$schema_node->setAttribute('xmlns:dc',"http://purl.org/dc/elements/1.1/");
$schema_node->setAttribute('xmlns:xsi',"http://www.w3.org/2001/XMLSchema-instance");
$schema_node->setAttribute('xsi:schemaLocation',
'http://www.openarchives.org/OAI/2.0/oai_dc/ http://www.openarchives.org/OAI/2.0/oai_dc.xsd');
foreach ($record as $r => $v) {
if (!empty($v)) {
$outputObj->addChild($schema_node, str_replace('_', ':', $r), $v);
}
$schema_node = $this->outputObj->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->outputObj->addChild($schema_node, $name, $value);
}
}
@ -626,7 +337,8 @@ class OAI2Server {
* is needed.
*/
private function checkArgs($checkList) {
global $METADATAFORMATS;
$metadataFormats = call_user_func($this->listMetadataFormatsCallback);
// "verb" has been checked before, no further check is needed
$verb = $this->args["verb"];
@ -637,17 +349,14 @@ class OAI2Server {
if(isset($checkList['required'])) {
for($i = 0; $i < count($checkList["required"]); $i++) {
if(isset($test_args[$checkList['required'][$i]])==false) {
$this->errors[] = oai_error('missingArgument', $checkList["required"][$i]);
if (isset($test_args[$checkList['required'][$i]]) == false) {
$this->errors[] = new OAI2Exception('missingArgument', $checkList["required"][$i]);
} else {
// if metadataPrefix is set, it is in required section
if(isset($test_args['metadataPrefix'])) {
$metadataPrefix = $test_args['metadataPrefix'];
// Check if the format is supported, it has enough infor (an array), last if a handle has been defined.
if (!array_key_exists($metadataPrefix, $METADATAFORMATS) ||
!(is_array($METADATAFORMATS[$metadataPrefix]) ||
!isset($METADATAFORMATS[$metadataPrefix]['myhandler']))) {
$this->errors[] = oai_error('cannotDisseminateFormat', 'metadataPrefix', $metadataPrefix);
if (!isset($metadataFormats[$metadataPrefix])) {
$this->errors[] = new OAI2Exception('cannotDisseminateFormat', 'metadataPrefix', $metadataPrefix);
}
}
unset($test_args[$checkList["required"][$i]]);
@ -655,28 +364,61 @@ class OAI2Server {
}
}
if (!empty($this->errors)) return;
// check to see if there is unwanted
foreach($test_args as $key => $val) {
if(!in_array($key, $checkList["ops"])) {
$this->errors[] = oai_error('badArgument', $key, $val);
$this->errors[] = new OAI2Exception('badArgument', $key, $val);
}
switch ($key) {
case 'from':
case 'until':
if(!checkDateFormat($val)) {
$this->errors[] = oai_error('badGranularity', $key, $val);
$this->errors[] = new OAI2Exception('badGranularity', $key, $val);
}
break;
case 'resumptionToken':
// only check for expairation
// only check for expiration
if((int)$val+TOKEN_VALID < time())
$this->errors[] = oai_error('badResumptionToken');
$this->errors[] = new OAI2Exception('badResumptionToken');
break;
}
}
if (!empty($this->errors)) {
$this->errorResponse();
}
}
private function createResumptionToken($delivered_records) {
list($usec, $sec) = explode(" ", microtime());
$token = ((int)($usec*1000) + (int)($sec*1000));
$fp = fopen (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;
}
}

67
oai2xml.php Normal file
View File

@ -0,0 +1,67 @@
<?php
class OAI2XML {
public $doc; // DOMDocument. Handle of current XML Document object
function __construct($request_args) {
$this->doc = new DOMDocument("1.0","UTF-8");
$oai_node = $this->doc->createElement("OAI-PMH");
$oai_node->setAttribute("xmlns","http://www.openarchives.org/OAI/2.0/");
$oai_node->setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
$oai_node->setAttribute("xsi:schemaLocation","http://www.openarchives.org/OAI/2.0/ http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd");
$this->addChild($oai_node,"responseDate",gmdate("Y-m-d\TH:i:s\Z"));
$this->doc->appendChild($oai_node);
$request = $this->addChild($this->doc->documentElement,"request",MY_URI);
foreach($request_args as $key => $value) {
$request->setAttribute($key,$value);
}
}
function display() {
$pr = new DOMDocument();
$pr->preserveWhiteSpace = false;
$pr->formatOutput = true;
$pr->loadXML($this->doc->saveXML());
echo $pr->saveXML();
}
/**
* Add a child node to a parent node on a XML Doc: a worker function.
*
* @param $mom_node
* Type: DOMNode. The target node.
*
* @param $name
* Type: string. The name of child nade is being added
*
* @param $value
* Type: string. Text for the adding node if it is a text node.
*
* @return DOMElement $added_node
* The newly created node, can be used for further expansion.
* If no further expansion is expected, return value can be igored.
*/
function addChild($mom_node,$name, $value='') {
$added_node = $this->doc->createElement($name,$value);
$added_node = $mom_node->appendChild($added_node);
return $added_node;
}
}
class OAI2XMLError extends OAI2XML {
function __construct($request_args, $errors) {
parent::__construct($request_args);
$oai_node = $this->doc->documentElement;
foreach($errors as $e) {
$node = $this->addChild($oai_node,"error",$e->getMessage());
$node->setAttribute("code",$e->getCode());
}
}
}

View File

@ -1,77 +0,0 @@
<?php
/**
* brief Configuration file of current data provider.
*
* This is the configuration file for the PHP OAI Data Provider.
* The response may may be compressed for better performace:
* - Compression : a compression encoding supported by the repository. The recommended values are those defined for the Content-Encoding header in Section 14.11 of RFC 2616 describing HTTP 1.1. A compression element should not be included for the identity encoding, which is implied.
* The rest of settings will not normally need to be adjusted. Read source code for details.
*/
/**
* Whether to show error message for dubug.
* For installation, testing and debuging set SHOW_QUERY_ERROR to TRUE
* If set to TRUE, application will die and display query and database error message
* as soon as there is a problem. Do not set this to TRUE on a production site,
* since it will show error messages to everybody.
* If set FALSE, will create XML-output, no matter what happens.
*/
// If everything is running ok, you should use this
define('SHOW_QUERY_ERROR',FALSE);
/**
* \property CONTENT_TYPE
* The content-type the WWW-server delivers back. For debug-puposes, "text/plain"
* is easier to view. On a production site you should use "text/xml".
*/
#define('CONTENT_TYPE','Content-Type: text/plain');
// If everything is running ok, you should use this
define('CONTENT_TYPE', 'Content-Type: text/xml');
// For ANDS to harvest of RIF-CS, originatingSource is plantaccelerator.org.au
// $dataSource = "plantaccelerator.org.au";
define('DATASOURCE','dev2.moodle.ufsc.br');
/** Compression methods supported. Optional (multiple). Default: null.
*
* Currently only gzip is supported (you need output buffering turned on,
* and php compiled with libgz).
* The client MUST send "Accept-Encoding: gzip" to actually receive
*/
// $compression = array('gzip');
$compression = null;
// MUST (only one)
// You may choose any name, but for repositories to comply with the oai
// format it has to be unique identifiers for items records.
// see: http://www.openarchives.org/OAI/2.0/guidelines-oai-identifier.htm
// Basically use domainname
// please adjust
// For RIF-CS, especially with ANDS, each registryObject much has a group for the ownership of data.
// For detail please see ANDS guide on its web site. Each data provider should have only one REG_OBJ_GROUP
// for this purpose.
define('REG_OBJ_GROUP','Something agreed on');
/** Maximum mumber of the records to deliver
* (verb is ListRecords)
* If there are more records to deliver
* a ResumptionToken will be generated.
*/
define('MAXRECORDS',10);
/** Maximum mumber of identifiers to deliver
* (verb is ListIdentifiers)
* If there are more identifiers to deliver
* a ResumptionToken will be generated.
*/
define('MAXIDS',40);
/** After 24 hours resumptionTokens become invalid. Unit is second. */
define('TOKEN_VALID',24*3600);
$expirationdatetime = gmstrftime('%Y-%m-%dT%TZ', time()+TOKEN_VALID);
/** Where token is saved and path is included */
define('TOKEN_PREFIX','/tmp/ANDS_DBPD-');
// The shorthand of xml schema namespace, no need to change this
define('XMLSCHEMA', 'http://www.w3.org/2001/XMLSchema-instance');

View File

@ -37,7 +37,6 @@ function formatDatestamp($datestamp) {
function checkDateFormat($date) {
$date = str_replace(array("T","Z")," ",$date);
$time_val = strtotime($date);
if (SHOW_QUERY_ERROR) { echo "timeval: $time_val\n"; }
if(!$time_val) return false;
if(strstr($date,":")) {
return date("Y-m-d H:i:s",$time_val);
@ -45,145 +44,3 @@ function checkDateFormat($date) {
return date("Y-m-d",$time_val);
}
}
/** Retrieve all defined 'setSpec' from configuraiton of $SETS.
* It is used by ANDS_TPA::create_obj_node();
*/
function prepare_set_names() {
global $SETS;
$n = count($SETS);
$a = array_fill(0,$n,'');
for ($i = 0; $i <$n; $i++) {
$a[$i] = $SETS[$i]['setSpec'];
}
return $a;
}
// ResumToken section
/** Generate a string based on the current Unix timestamp in microseconds for creating resumToken file name. */
function get_token() {
list($usec, $sec) = explode(" ", microtime());
return ((int)($usec*1000) + (int)($sec*1000));
}
/** Create a token file.
* It has three parts which is separated by '#': cursor, extension of query, metadataPrefix.
* Called by listrecords.php.
*/
function createResumToken($cursor, $extquery, $metadataPrefix) {
$token = get_token();
$fp = fopen (TOKEN_PREFIX.$token, 'w');
if($fp==false) {
exit("Cannot write. Writer permission needs to be changed.");
}
fputs($fp, "$cursor#");
fputs($fp, "$extquery#");
fputs($fp, "$metadataPrefix#");
fclose($fp);
return $token;
}
/** Read a saved ResumToken */
function readResumToken($resumptionToken) {
$rtVal = false;
$fp = fopen($resumptionToken, 'r');
if ($fp!=false) {
$filetext = fgets($fp, 255);
$textparts = explode('#', $filetext);
fclose($fp);
unlink ($resumptionToken);
$rtVal = array((int)$textparts[0], $textparts[1], $textparts[2]);
}
return $rtVal;
}
// Here are a couple of queries which might need to be adjusted to
// your needs. Normally, if you have correctly named the columns above,
// this does not need to be done.
/** this function should generate a query which will return
* all records
* the useless condition id_column = id_column is just there to ease
* further extensions to the query, please leave it as it is.
*/
function selectallQuery($metadPrefix = "rif", $id = ''){
global $SQL;
$query = "SELECT * FROM ".$SQL['table'] . " WHERE ".$SQL['metadataPrefix']." LIKE '%$metadPrefix%'";
if ($id != '') {
$query .= " AND ".$SQL['identifier']." ='$id'";
}
return $query;
}
/** this function will return identifier and datestamp for all records
* not very useful
*/
function idQuery($metadPrefix = "rif", $id = '') {
global $SQL;
if ($SQL['set'] != '') {
$query = 'select '.$SQL['identifier'].','.$SQL['datestamp'].','.$SQL['set'].' FROM '.$SQL['table']. " WHERE ".$SQL['metadataPrefix']." LIKE '%$metadPrefix%'";
} else {
$query = 'select '.$SQL['identifier'].','.$SQL['datestamp'].' FROM '.$SQL['table']. " WHERE ".$SQL['metadataPrefix']." LIKE '%$metadPrefix%'";
}
if ($id != '') {
$query .= " AND ".$SQL['identifier']." = '$id'";
}
return $query;
}
/** filter for until, appends to the end of SQL query */
function untilQuery($until) {
global $SQL;
return ' AND '.$SQL['datestamp']." <= '$until'";
}
/** filter for from , appends to the end of SQL query */
function fromQuery($from) {
global $SQL;
return ' AND '.$SQL['datestamp']." >= '$from'";
}
/** filter for sets, appends to the end of SQL query */
function setQuery($set) {
global $SQL;
// strip off "class:" which is not saved in database
if(strstr($set,"class:")) $set = substr($set,6);
return ' AND '.$SQL['set']." LIKE '%$set%'";
}
/** for accurately to assess how many records satisfy conditions for all DBs */
function rowCount($metadataPrefix, $extQuery, $db) {
global $SQL;
$n = 0;
$sql = "SELECT COUNT(*) FROM ".$SQL['table'] . " WHERE ".$SQL['metadataPrefix']." LIKE '%$metadataPrefix%'" . $extQuery;
if ($res = $db->query($sql)) {
$n = $res->fetchColumn();
}
return $n;
}
/** A worker function for processing an error when a query was executed
* \param $query string, original query
* \param $e PDOException, the PDOException object
*/
function process_pdo_error($query, $e) {
echo $query.' was failed\n';
echo $e->getMessage();
}
/** When query return no result, throw an Exception of Not found.
* \param $db PDO
* \param $query string
* \return $res PDOStatement
*/
function exec_pdo_query($db, $query) {
$res = $db->query($query);
if ($res===false) {
throw new Exception($query.":\nIt found nothing.\n");
} else return $res;
}

View File

@ -1,145 +1,15 @@
<?php
/**
* \file
* \brief functions and class related to generating XML response file.
* functions and class related to generating XML response file.
*
* Example usage:
*
* \code
* $par_array = array("verb"=>"ListRecords","resumptionToken"=>"9CD1DA87F59C3E960871F4F3C9D093887C17D174");
* // Example 1: Error response
* $error_array[] = oai_error("badVerb","Rubish");
* $error_array[] = oai_error("sameVerb");
* $e = new ANDS_Error_XML($par_array,$error_array);
* $e->display();
*
* // Example 2: Normal response without error codes
* Example: Normal response without error codes
* $par_array = array("verb"=>"ListRecords","resumptionToken"=>"9CD1DA87F59C3E960871F4F3C9D093887C17D174");
* $test = new ANDS_Response_XML($par_array);
* $record_node = $test->create_record();
* $test->create_header("function: identifier string",gmdate("Y-m-d\TH:i:s\Z"),"collection",$record_node);
* $test->create_metadata($record_node);
* $test->display();
* \endcode
*
* \see http://www.openarchives.org/OAI/openarchivesprotocol.html#ErrorConditions
*/
/*
http://www.openarchives.org/OAI/openarchivesprotocol.html#ErrorConditions
badArgument:
The request includes illegal arguments, is missing required arguments, includes a repeated argument, or values for arguments have an illegal syntax. Applied to all verbs.
badResumptionToken:
The value of the resumptionToken argument is invalid or expired. Applied to: ListIdentifiers, ListRecords, ListSets
badVerb:
Value of the verb argument is not a legal OAI-PMH verb, the verb argument is missing, or the verb argument is repeated. N/A
cannotDisseminateFormat:
The metadata format identified by the value given for the metadataPrefix argument is not supported by the item or by the repository. Applied to GetRecord, ListIdentifiers, ListRecords
idDoesNotExist:
The value of the identifier argument is unknown or illegal in this repository. Applied to GetRecord, ListMetadataFormats
noRecordsMatch:
The combination of the values of the from, until, set and metadataPrefix arguments results in an empty list. Applied to ListIdentifiers, ListRecords
noMetadataFormats:
There are no metadata formats available for the specified item. Applied to ListMetadataFormats.
noSetHierarchy:
The repository does not support sets. Applied to ListSets, ListIdentifiers, ListRecords
*/
/** utility funciton to mapping error codes to readable messages */
function oai_error($code, $argument = '', $value = '') {
switch ($code) {
case 'badArgument' :
$text = "Attribute '{$argument}' is not allowed to appear in element 'request'.";
break;
case 'badGranularity' :
$text = "The value '{$value}' of attribute '{$argument}' on element 'request' is not valid with respect to its type, 'UTCdatetimeType'.";
$code = 'badArgument';
break;
case 'badResumptionToken' :
$text = "The resumptionToken '{$value}' does not exist or has already expired.";
break;
case 'badRequestMethod' :
$text = "The request method '{$argument}' is unknown.";
$code = 'badVerb';
break;
case 'badVerb' :
$text = "The value '{$argument}' of attribute 'verb' on element 'request' is not valid with respect to its type, 'verbType'";
break;
case 'cannotDisseminateFormat' :
$text = "The metadata format '{$value}' given by {$argument} is not supported by this repository.";
break;
case 'exclusiveArgument' :
$text = 'The usage of resumptionToken as an argument allows no other arguments.';
$code = 'badArgument';
break;
case 'idDoesNotExist' :
$text = "The value '{$value}' of the identifier does not exist in this repository.";
if (!is_valid_uri($value)) {
$code = 'badArgument';
$text .= ' Invalidated URI has been detected.';
}
break;
case 'missingArgument' :
$text = "The required argument '{$argument}' is missing in the request.";
$code = 'badArgument';
break;
case 'noRecordsMatch' :
$text = 'The combination of the given values results in an empty list.';
break;
case 'noMetadataFormats' :
$text = 'There are no metadata formats available for the specified item.';
break;
case 'noVerb' :
$text = 'The request does not provide any verb.';
$code = 'badVerb';
break;
case 'noSetHierarchy' :
$text = 'This repository does not support sets.';
break;
case 'sameArgument' :
$text = 'Do not use the same argument more than once.';
$code = 'badArgument';
break;
case 'sameVerb' :
$text = 'Do not use verb more than once.';
$code = 'badVerb';
break;
case 'notImp' :
$text = 'Not yet implemented.';
$code = 'debug';
break;
default:
$text = "Unknown error: code: '{$code}', argument: '{$argument}', value: '{$value}'";
$code = 'badArgument';
}
return $code."|".$text;
}
/**
* A wraper of DOMDocument for data provider