CWIS Developer Documentation
OAIServer.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: OAIServer.php
4 #
5 # Part of the ScoutLib application support library
6 # Copyright 2009-2016 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu
8 #
9 
10 class OAIServer
11 {
12 
13  # ---- PUBLIC INTERFACE --------------------------------------------------
14 
15  # object constructor
16 
24  public function __construct(
25  $RepDescr, &$ItemFactory, $SetsSupported = FALSE, $OaisqSupported = FALSE)
26  {
27  # save repository description
28  $this->RepDescr = $RepDescr;
29 
30  # save supported option settings
31  $this->SetsSupported = $SetsSupported;
32  $this->OaisqSupported = $OaisqSupported;
33 
34  # normalize repository description values
35  $this->RepDescr["IDPrefix"] =
36  preg_replace("/[^0-9a-z]/i", "", $this->RepDescr["IDPrefix"]);
37 
38  # save item factory
39  $this->ItemFactory =& $ItemFactory;
40 
41  # load OAI request type and arguments
42  $this->LoadArguments();
43 
44  # set default indent size
45  $this->IndentSize = 4;
46 
47  # start with empty list of formats
48  $this->FormatDescrs = array();
49  }
50 
63  public function AddFormat($Name, $TagName, $SchemaNamespace, $SchemaDefinition,
64  $SchemaVersion, $NamespaceList, $ElementList, $QualifierList,
65  $DefaultMap)
66  {
67  # find highest current format ID
68  $HighestFormatId = 0;
69  foreach ($this->FormatDescrs as $FormatName => $FormatDescr)
70  {
71  if ($FormatDescr["FormatId"] > $HighestFormatId)
72  {
73  $HighestFormatId = $FormatDescr["FormatId"];
74  }
75  }
76 
77  # set new format ID to next value
78  $this->FormatDescrs[$Name]["FormatId"] = $HighestFormatId + 1;
79 
80  # store values
81  $this->FormatDescrs[$Name]["TagName"] = $TagName;
82  $this->FormatDescrs[$Name]["SchemaNamespace"] = $SchemaNamespace;
83  $this->FormatDescrs[$Name]["SchemaDefinition"] = $SchemaDefinition;
84  $this->FormatDescrs[$Name]["SchemaVersion"] = $SchemaVersion;
85  $this->FormatDescrs[$Name]["ElementList"] = $ElementList;
86  $this->FormatDescrs[$Name]["QualifierList"] = $QualifierList;
87  $this->FormatDescrs[$Name]["NamespaceList"] = $NamespaceList;
88  $this->FormatDescrs[$Name]["DefaultMap"] = $DefaultMap;
89 
90  # start out with empty mappings list
91  if (!isset($this->FieldMappings[$Name]))
92  {
93  $this->FieldMappings[$Name] = array();
94  }
95  }
96 
101  public function FormatList()
102  {
103  $FList = array();
104  foreach ($this->FormatDescrs as $FormatName => $FormatDescr)
105  {
106  $FList[$FormatDescr["FormatId"]] = $FormatName;
107  }
108  return $FList;
109  }
110 
116  public function FormatElementList($FormatName)
117  {
118  return $this->FormatDescrs[$FormatName]["ElementList"];
119  }
120 
121 
127  public function FormatQualifierList($FormatName)
128  {
129  return $this->FormatDescrs[$FormatName]["QualifierList"];
130  }
131 
138  public function GetFieldMapping($FormatName, $LocalFieldName)
139  {
140  # return stored value
141  if (isset($this->FieldMappings[$FormatName][$LocalFieldName]))
142  {
143  return $this->FieldMappings[$FormatName][$LocalFieldName];
144  }
145  else
146  {
147  return NULL;
148  }
149  }
150 
157  public function SetFieldMapping($FormatName, $LocalFieldName, $OAIFieldName)
158  {
159  $this->FieldMappings[$FormatName][$LocalFieldName][]= $OAIFieldName;
160  }
161 
168  public function GetQualifierMapping($FormatName, $LocalQualifierName)
169  {
170  # return stored value
171  if (isset($this->QualifierMappings[$FormatName][$LocalQualifierName]))
172  {
173  return $this->QualifierMappings[$FormatName][$LocalQualifierName];
174  }
175  else
176  {
177  return NULL;
178  }
179  }
180 
187  public function SetQualifierMapping(
188  $FormatName, $LocalQualifierName, $OAIQualifierName)
189  {
190  $this->QualifierMappings[$FormatName][$LocalQualifierName] =
191  $OAIQualifierName;
192  }
193 
198  public function GetResponse()
199  {
200  # call appropriate method based on request type
201  switch (strtoupper($this->Args["verb"]))
202  {
203  case "IDENTIFY":
204  $Response = $this->ProcessIdentify();
205  break;
206 
207  case "GETRECORD":
208  $Response = $this->ProcessGetRecord();
209  break;
210 
211  case "LISTIDENTIFIERS":
212  $Response = $this->ProcessListRecords(FALSE);
213  break;
214 
215  case "LISTRECORDS":
216  $Response = $this->ProcessListRecords(TRUE);
217  break;
218 
219  case "LISTMETADATAFORMATS":
220  $Response = $this->ProcessListMetadataFormats();
221  break;
222 
223  case "LISTSETS":
224  $Response = $this->ProcessListSets();
225  break;
226 
227  default:
228  # return "bad argument" response
229  $Response = $this->GetResponseBeginTags();
230  $Response .= $this->GetRequestTag();
231  $Response .= $this->GetErrorTag(
232  "badVerb", "Bad or unknown request type.");
233  $Response .= $this->GetResponseEndTags();
234  break;
235  }
236 
237  # return generated response to caller
238  return $Response;
239  }
240 
241 
242  # ---- PRIVATE INTERFACE -------------------------------------------------
243 
244  private $Args;
245  private $RepDescr;
246  private $ItemFactory;
247  private $FormatDescrs;
248  private $FormatFields;
249  private $FieldMappings;
250  private $QualifierMappings;
251  private $IndentSize;
252  private $SetsSupported;
253  private $OaisqSupported;
254 
255 
256  # ---- response generation methods
257 
262  private function ProcessIdentify()
263  {
264  # initialize response
265  $Response = $this->GetResponseBeginTags();
266 
267  # add request info tag
268  $Response .= $this->GetRequestTag("Identify");
269 
270  # open response type tag
271  $Response .= $this->FormatTag("Identify");
272 
273  # add repository info tags
274  $Response .= $this->FormatTag("repositoryName", $this->RepDescr["Name"]);
275  $Response .= $this->FormatTag("baseURL", $this->RepDescr["BaseURL"]);
276  $Response .= $this->FormatTag("protocolVersion", "2.0");
277  foreach ($this->RepDescr["AdminEmail"] as $AdminEmail)
278  {
279  $Response .= $this->FormatTag("adminEmail", $AdminEmail);
280  }
281  $Response .= $this->FormatTag(
282  "earliestDatestamp", $this->RepDescr["EarliestDate"]);
283  $Response .= $this->FormatTag(
284  "deletedRecord", "no");
285  $Response .= $this->FormatTag(
286  "granularity",
287  (strtoupper($this->RepDescr["DateGranularity"]) == "DATETIME")
288  ? "YYYY-MM-DDThh:mm:ssZ" : "YYYY-MM-DD");
289 
290  # add repository description section
291  $Response .= $this->FormatTag("description");
292  $Attribs = array(
293  "xmlns" => "http://www.openarchives.org/OAI/2.0/oai-identifier",
294  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
295  "xsi:schemaLocation" =>
296  "http://www.openarchives.org/OAI/2.0/oai-identifier "
297  ."http://www.openarchives.org/OAI/2.0/oai-identifier.xsd",
298  );
299  $Response .= $this->FormatTag(
300  "oai-identifier", NULL, $Attribs);
301  $Response .= $this->FormatTag(
302  "scheme", "oai");
303  $Response .= $this->FormatTag(
304  "repositoryIdentifier", $this->RepDescr["IDDomain"]);
305  $Response .= $this->FormatTag(
306  "delimiter", ":");
307  $Response .= $this->FormatTag(
308  "sampleIdentifier", $this->EncodeIdentifier("12345"));
309  $Response .= $this->FormatTag();
310  $Response .= $this->FormatTag();
311 
312  # close response type tag
313  $Response .= $this->FormatTag();
314 
315  # close out response
316  $Response .= $this->GetResponseEndTags();
317 
318  # return response to caller
319  return $Response;
320  }
321 
326  private function ProcessGetRecord()
327  {
328  # initialize response
329  $Response = $this->GetResponseBeginTags();
330 
331  # if arguments were bad
332  if (isset($this->Args["identifier"]))
333  {
334  $ItemId = $this->DecodeIdentifier($this->Args["identifier"]);
335  }
336  else
337  {
338  $ItemId = NULL;
339  }
340  if (isset($this->Args["metadataPrefix"]))
341  {
342  $MetadataFormat = $this->Args["metadataPrefix"];
343  }
344  else
345  {
346  $MetadataFormat = NULL;
347  }
348  if (($ItemId == NULL) || ($MetadataFormat == NULL) ||
349  !is_array($this->FieldMappings[$MetadataFormat]))
350  {
351  # add request info tag with no attributes
352  $Response .= $this->GetRequestTag("GetRecord");
353 
354  # add error tag
355  $Response .= $this->GetErrorTag("badArgument", "Bad argument found.");
356  }
357  else
358  {
359  # add request info tag
360  $ReqArgList = array("identifier", "metadataPrefix");
361  $Response .= $this->GetRequestTag("GetRecord", $ReqArgList);
362 
363  # attempt to load item corresponding to record
364  $Item = $this->ItemFactory->GetItem($ItemId);
365 
366  # if no item found
367  if ($Item == NULL)
368  {
369  # add error tag
370  $Response .= $this->GetErrorTag(
371  "idDoesNotExist", "No item found for specified ID.");
372  }
373  else
374  {
375  # open response type tag
376  $Response .= $this->FormatTag("GetRecord");
377 
378  # add tags for record
379  $Response .= $this->GetRecordTags($Item, $MetadataFormat);
380 
381  # close response type tag
382  $Response .= $this->FormatTag();
383  }
384  }
385 
386  # close out response
387  $Response .= $this->GetResponseEndTags();
388 
389  # return response to caller
390  return $Response;
391  }
392 
399  private function ProcessListRecords($IncludeMetadata)
400  {
401  # set request type
402  if ($IncludeMetadata)
403  {
404  $Request = "ListRecords";
405  }
406  else
407  {
408  $Request = "ListIdentifiers";
409  }
410 
411  # initialize response
412  $Response = $this->GetResponseBeginTags();
413 
414  # if resumption token supplied
415  if (isset($this->Args["resumptionToken"]))
416  {
417  # set expected argument lists
418  $ReqArgList = array("resumptionToken");
419  $OptArgList = NULL;
420 
421  # parse into list parameters
422  $Args = $this->DecodeResumptionToken($this->Args["resumptionToken"]);
423  }
424  else
425  {
426  # set expected argument lists
427  $ReqArgList = array("metadataPrefix");
428  $OptArgList = array("from", "until", "set");
429 
430  # get list parameters from incoming arguments
431  $Args = $this->Args;
432 
433  # set list starting point to beginning
434  $Args["ListStartPoint"] = 0;
435  }
436 
437  # if resumption token was supplied and was bad
438  if ($Args == NULL)
439  {
440  # add request info tag
441  $Response .= $this->GetRequestTag($Request, $ReqArgList, $OptArgList);
442 
443  # add error tag indicating bad resumption token
444  $Response .= $this->GetErrorTag(
445  "badResumptionToken", "Bad resumption token.");
446 
447  # if other parameter also supplied
448  if (count($this->Args) > 2)
449  {
450  # add error tag indicating exclusive argument error
451  $Response .= $this->GetErrorTag(
452  "badArgument", "Resumption token is exclusive argument.");
453  }
454  }
455  # else if resumption token supplied and other arguments also supplied
456  elseif (isset($this->Args["resumptionToken"]) && (count($this->Args) > 2))
457  {
458  # add error tag indicating exclusive argument error
459  $Response .= $this->GetRequestTag();
460  $Response .= $this->GetErrorTag(
461  "badArgument", "Resumption token is exclusive argument.");
462  }
463  # else if metadata format was not specified
464  elseif (empty($Args["metadataPrefix"]))
465  {
466  # add request info tag with no attributes
467  $Response .= $this->GetRequestTag($Request);
468 
469  # add error tag indicating bad argument
470  $Response .= $this->GetErrorTag(
471  "badArgument", "No metadata format specified.");
472  }
473  # else if from or until date is specified but bad
474  elseif ((isset($Args["from"]) && $this->DateIsInvalid($Args["from"]))
475  || (isset($Args["until"]) && $this->DateIsInvalid($Args["until"])))
476  {
477  # add request info tag with no attributes
478  $Response .= $this->GetRequestTag($Request);
479 
480  # add error tag indicating bad argument
481  $Response .= $this->GetErrorTag("badArgument", "Bad date format.");
482  }
483  else
484  {
485  # add request info tag
486  $Response .= $this->GetRequestTag($Request, $ReqArgList, $OptArgList);
487 
488  # if set requested and we do not support sets
489  if (isset($Args["set"]) && ($this->SetsSupported != TRUE))
490  {
491  # add error tag indicating that we don't support sets
492  $Response .= $this->GetErrorTag(
493  "noSetHierarchy",
494  "This repository does not support sets.");
495  }
496  # else if requested metadata format is not supported
497  elseif (empty($this->FormatDescrs[$Args["metadataPrefix"]]))
498  {
499  # add error tag indicating that format is not supported
500  $Response .= $this->GetErrorTag(
501  "cannotDisseminateFormat",
502  "Metadata format \"".$Args["metadataPrefix"]
503  ."\" not supported by this repository.");
504  }
505  else
506  {
507  # if set requested
508  if (isset($Args["set"]))
509  {
510  # if OAI-SQ supported and set represents OAI-SQ query
511  if ($this->OaisqSupported && $this->IsOaisqQuery($Args["set"]))
512  {
513  # parse OAI-SQ search parameters out of set name
514  $SearchParams = $this->ParseOaisqQuery(
515  $Args["set"], $Args["metadataPrefix"]);
516 
517  # if search parameters found
518  if (count($SearchParams))
519  {
520  # perform search for items that match OAI-SQ request
521  $ItemIds = $this->ItemFactory->SearchForItems(
522  $SearchParams,
523  (isset($Args["from"]) ? $Args["from"] : NULL),
524  (isset($Args["until"]) ? $Args["until"] : NULL));
525  }
526  else
527  {
528  # no items match
529  $ItemIds = array();
530  }
531  }
532  else
533  {
534  # get list of items in set that matches incoming criteria
535  $ItemIds = $this->ItemFactory->GetItemsInSet(
536  $Args["set"],
537  (isset($Args["from"]) ? $Args["from"] : NULL),
538  (isset($Args["until"]) ? $Args["until"] : NULL));
539  }
540  }
541  else
542  {
543  # get list of items that matches incoming criteria
544  $ItemIds = $this->ItemFactory->GetItems(
545  (isset($Args["from"]) ? $Args["from"] : NULL),
546  (isset($Args["until"]) ? $Args["until"] : NULL));
547  }
548 
549  # if no items found
550  if (count($ItemIds) == 0)
551  {
552  # add error tag indicating that no records found that match spec
553  $Response .= $this->GetErrorTag(
554  "noRecordsMatch",
555  "No records were found that match the specified parameters.");
556  }
557  else
558  {
559  # open response type tag
560  $Response .= $this->FormatTag($Request);
561 
562  # initialize count of processed items
563  $ListIndex = 0;
564 
565  # for each item
566  foreach ($ItemIds as $ItemId)
567  {
568  # if item is within range
569  if ($ListIndex >= $Args["ListStartPoint"])
570  {
571  # retrieve item
572  $Item = $this->ItemFactory->GetItem($ItemId);
573 
574  # add record for item
575  $Response .= $this->GetRecordTags(
576  $Item, $Args["metadataPrefix"], $IncludeMetadata);
577  }
578 
579  # increment count of processed items
580  $ListIndex++;
581 
582  # stop processing if we have processed max number of items
583  $MaxItemsPerPass = 20;
584  if (($ListIndex - $Args["ListStartPoint"]) >= $MaxItemsPerPass)
585  {
586  break;
587  }
588  }
589 
590  # if items left unprocessed
591  if ($ListIndex < count($ItemIds))
592  {
593  # add resumption token tag
594  $Token = $this->EncodeResumptionToken(
595  (isset($Args["from"]) ? $Args["from"] : NULL),
596  (isset($Args["until"]) ? $Args["until"] : NULL),
597  (isset($Args["metadataPrefix"]) ?
598  $Args["metadataPrefix"] : NULL),
599  (isset($Args["set"]) ? $Args["set"] : NULL),
600  $ListIndex);
601  $Response .= $this->FormatTag("resumptionToken", $Token);
602  }
603  else
604  {
605  # if we started with a resumption token tag
606  if (isset($this->Args["resumptionToken"]))
607  {
608  # add empty resumption token tag to indicate end of set
609  $Response .= $this->FormatTag("resumptionToken", "");
610  }
611  }
612 
613  # close response type tag
614  $Response .= $this->FormatTag();
615  }
616  }
617  }
618 
619  # close out response
620  $Response .= $this->GetResponseEndTags();
621 
622  # return response to caller
623  return $Response;
624  }
625 
630  private function ProcessListMetadataFormats()
631  {
632  # initialize response
633  $Response = $this->GetResponseBeginTags();
634 
635  # if arguments were bad
636  $Arg = isset($this->Args["identifier"]) ? $this->Args["identifier"] : NULL;
637  $ItemId = $this->DecodeIdentifier($Arg);
638  if (isset($this->Args["identifier"]) && ($ItemId == NULL))
639  {
640  # add error tag
641  $Response .= $this->GetRequestTag();
642  $Response .= $this->GetErrorTag(
643  "idDoesNotExist", "Identifier unknown or illegal.");
644  }
645  else
646  {
647  # add request info tag
648  $OptArgList = array("identifier");
649  $Response .= $this->GetRequestTag("ListMetadataFormats", NULL, $OptArgList);
650 
651  # open response type tag
652  $Response .= $this->FormatTag("ListMetadataFormats");
653 
654  # for each supported format
655  foreach ($this->FormatDescrs as $FormatName => $FormatDescr)
656  {
657  # open format tag
658  $Response .= $this->FormatTag("metadataFormat");
659 
660  # add tags describing format
661  $Response .= $this->FormatTag("metadataPrefix", $FormatName);
662  if (isset($FormatDescr["SchemaDefinition"]))
663  {
664  $Response .= $this->FormatTag("schema",
665  $FormatDescr["SchemaDefinition"]);
666  }
667  if (isset($FormatDescr["SchemaNamespace"]))
668  {
669  $Response .= $this->FormatTag("metadataNamespace",
670  $FormatDescr["SchemaNamespace"]);
671  }
672 
673  # close format tag
674  $Response .= $this->FormatTag();
675  }
676 
677  # close response type tag
678  $Response .= $this->FormatTag();
679  }
680 
681  # close out response
682  $Response .= $this->GetResponseEndTags();
683 
684  # return response to caller
685  return $Response;
686  }
687 
692  private function ProcessListSets()
693  {
694  # initialize response
695  $Response = $this->GetResponseBeginTags();
696 
697  # add request info tag
698  $OptArgList = array("resumptionToken");
699  $Response .= $this->GetRequestTag("ListSets", NULL, $OptArgList);
700 
701  # retrieve list of supported sets
702  $SetList = $this->SetsSupported ? $this->ItemFactory->GetListOfSets() : array();
703 
704  # if sets not supported or we have no sets
705  if ((!$this->SetsSupported) || (!count($SetList) && !$this->OaisqSupported))
706  {
707  # add error tag indicating that we do not support sets
708  $Response .= $this->GetErrorTag(
709  "noSetHierarchy", "This repository does not support sets.");
710  }
711  else
712  {
713  # open response type tag
714  $Response .= $this->FormatTag("ListSets");
715 
716  # if OAI-SQ is enabled
717  if ($this->OaisqSupported)
718  {
719  # add OAI-SQ to list of sets
720  $SetList["OAI-SQ"] = "OAI-SQ";
721  $SetList["OAI-SQ-F"] = "OAI-SQ-F";
722  }
723 
724  # for each supported set
725  foreach ($SetList as $SetName => $SetSpec)
726  {
727  # open set tag
728  $Response .= $this->FormatTag("set");
729 
730  # add set spec and set name
731  $Response .= $this->FormatTag("setSpec", $SetSpec);
732  $Response .= $this->FormatTag("setName", $SetName);
733 
734  # close set tag
735  $Response .= $this->FormatTag();
736  }
737 
738  # close response type tag
739  $Response .= $this->FormatTag();
740  }
741 
742  # close out response
743  $Response .= $this->GetResponseEndTags();
744 
745  # return response to caller
746  return $Response;
747  }
748 
749 
750  # ---- common private methods
751 
756  private function GetResponseBeginTags()
757  {
758  # start with XML declaration
759  $Tags = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?".">\n";
760 
761  # add OAI-PMH root element begin tag
762  $Tags .= "<OAI-PMH xmlns=\"http://www.openarchives.org/OAI/2.0/\"\n"
763  ." xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
764  ." xsi:schemaLocation=\"http://www.openarchives.org/OAI/2.0/\n"
765  ." http://www.openarchives.org/OAI/2.0/OAI-PMH.xsd\">\n";
766 
767  # add response timestamp
768  $Tags .= " <responseDate>".date("Y-m-d\\TH:i:s\\Z")."</responseDate>\n";
769 
770  # return tags to caller
771  return $Tags;
772  }
773 
778  private function GetResponseEndTags()
779  {
780  # close out OAI-PMH root element
781  $Tags = "</OAI-PMH>\n";
782 
783  # return tags to caller
784  return $Tags;
785  }
786 
794  private function GetRequestTag(
795  $RequestType = NULL, $ReqArgList = NULL, $OptArgList = NULL)
796  {
797  # build attribute array
798  $AttributeList = array();
799  if ($RequestType !== NULL)
800  {
801  $AttributeList["verb"] = $RequestType;
802  }
803  if ($ReqArgList != NULL)
804  {
805  foreach ($ReqArgList as $ArgName)
806  {
807  if (isset($this->Args[$ArgName]))
808  {
809  $AttributeList[$ArgName] = $this->Args[$ArgName];
810  }
811  }
812  }
813  if ($OptArgList != NULL)
814  {
815  foreach ($OptArgList as $ArgName)
816  {
817  if (isset($this->Args[$ArgName]))
818  {
819  $AttributeList[$ArgName] = $this->Args[$ArgName];
820  }
821  }
822  }
823 
824  # generate formatted tag
825  $Tag = $this->FormatTag("request",
826  $this->RepDescr["BaseURL"],
827  $AttributeList);
828 
829  # return tag to caller
830  return $Tag;
831  }
832 
839  private function GetErrorTag($ErrorCode, $ErrorMessage)
840  {
841  return $this->FormatTag("error", $ErrorMessage, array("code" => $ErrorCode));
842  }
843 
852  private function GetRecordTags($Item, $MetadataFormat, $IncludeMetadata = TRUE)
853  {
854  # if more than identifiers requested
855  if ($IncludeMetadata)
856  {
857  # open record tag
858  $Tags = $this->FormatTag("record");
859  }
860  else
861  {
862  # just initialize tag string with empty value
863  $Tags = "";
864  }
865 
866  # add header with identifier, datestamp, and set tags
867  $Tags .= $this->FormatTag("header");
868  $Tags .= $this->FormatTag("identifier",
869  $this->EncodeIdentifier($Item->GetId()));
870  $Tags .= $this->FormatTag("datestamp", $Item->GetDatestamp());
871  $Sets = $Item->GetSets();
872  foreach ($Sets as $Set)
873  {
874  $Tags .= $this->FormatTag("setSpec", $Set);
875  }
876  $Tags .= $this->FormatTag();
877 
878  # if more than identifiers requested
879  if ($IncludeMetadata)
880  {
881  # open metadata tag
882  $Tags .= $this->FormatTag("metadata");
883 
884  # set up attributes for metadata format tag
885  $MFAttribs["xsi:schemaLocation"] =
886  $this->FormatDescrs[$MetadataFormat]["SchemaNamespace"]." \n"
887  .$this->FormatDescrs[$MetadataFormat]["SchemaDefinition"];
888  $MFAttribs["xmlns"] = $this->FormatDescrs[$MetadataFormat]["SchemaNamespace"];
889  if (strlen($this->FormatDescrs[$MetadataFormat]["SchemaVersion"]) > 0)
890  {
891  $MFAttribs["schemaVersion"] =
892  $this->FormatDescrs[$MetadataFormat]["SchemaVersion"];
893  }
894  $MFAttribs["xmlns:xsi"] = "http://www.w3.org/2001/XMLSchema-instance";
895  foreach ($this->FormatDescrs[$MetadataFormat]["NamespaceList"] as
896  $NamespaceName => $NamespaceURI)
897  {
898  $MFAttribs["xmlns:".$NamespaceName] = $NamespaceURI;
899  }
900 
901  # open metadata format tag
902  $Tags .= $this->FormatTag($this->FormatDescrs[$MetadataFormat]["TagName"],
903  NULL, $MFAttribs);
904 
905  # for each field mapping for this metadata format
906  foreach ($this->FieldMappings[$MetadataFormat] as
907  $LocalFieldName => $OAIFieldNames)
908  {
909  foreach ($OAIFieldNames as $OAIFieldName)
910  {
911  # if field looks like it has been mapped
912  if (strlen($OAIFieldName) > 0 && strlen($LocalFieldName) > 0 )
913  {
914  $Tags .= $this->FormatItemContent(
915  $Item, $MetadataFormat, $LocalFieldName, $OAIFieldName);
916  }
917  }
918  }
919 
920  # close metadata format tag
921  $Tags .= $this->FormatTag();
922 
923  # close metadata tag
924  $Tags .= $this->FormatTag();
925 
926  # if there is additional search info about this item
927  $SearchInfo = $Item->GetSearchInfo();
928  if (count($SearchInfo))
929  {
930  # open about and search info tags
931  $Tags .= $this->FormatTag("about");
932  $Attribs = array(
933  "xmlns" => "http://scout.wisc.edu/XML/searchInfo/",
934  "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
935  "xsi:schemaLocation" => "http://scout.wisc.edu/XML/searchInfo/ "
936  ."http://scout.wisc.edu/XML/searchInfo.xsd",
937  );
938  $Tags .= $this->FormatTag("searchInfo", NULL, $Attribs);
939 
940  # for each piece of additional info
941  foreach ($SearchInfo as $InfoName => $InfoValue)
942  {
943  # add tag for info
944  $Tags .= $this->FormatTag($InfoName,
945  utf8_encode(htmlspecialchars(
946  preg_replace("/[\\x00-\\x1F]+/", "", $InfoValue))));
947  }
948 
949  # close about and search info tags
950  $Tags .= $this->FormatTag();
951  $Tags .= $this->FormatTag();
952  }
953  }
954 
955  # if more than identifiers requested
956  if ($IncludeMetadata)
957  {
958  # close record tag
959  $Tags .= $this->FormatTag();
960  }
961 
962  # return tags to caller
963  return $Tags;
964  }
965 
971  private function EncodeIdentifier($ItemId)
972  {
973  # return encoded value to caller
974  return "oai:".$this->RepDescr["IDDomain"]
975  .":".$this->RepDescr["IDPrefix"]."-".$ItemId;
976  }
977 
984  private function DecodeIdentifier($Identifier)
985  {
986  # assume that decode will fail
987  $Id = NULL;
988 
989  # split ID into component pieces
990  $Pieces = explode(":", $Identifier);
991 
992  # if pieces look okay
993  if (($Pieces[0] == "oai") && ($Pieces[1] == $this->RepDescr["IDDomain"]))
994  {
995  # split final piece
996  $Pieces = explode("-", $Pieces[2]);
997 
998  # if identifier prefix looks okay
999  if ($Pieces[0] == $this->RepDescr["IDPrefix"])
1000  {
1001  # decoded value is final piece
1002  $Id = $Pieces[1];
1003  }
1004  }
1005 
1006  # return decoded value to caller
1007  return $Id;
1008  }
1009 
1018  private function EncodeResumptionToken(
1019  $StartingDate, $EndingDate, $MetadataFormat, $SetSpec, $ListStartPoint)
1020  {
1021  # concatenate values to create token
1022  $Token = $StartingDate."-_-".$EndingDate."-_-".$MetadataFormat."-_-"
1023  .$SetSpec."-_-".$ListStartPoint;
1024 
1025  # return token to caller
1026  return $Token;
1027  }
1028 
1034  private function DecodeResumptionToken($ResumptionToken)
1035  {
1036  # split into component pieces
1037  $Pieces = preg_split("/-_-/", $ResumptionToken);
1038 
1039  # if we were unable to split token
1040  if (count($Pieces) != 5)
1041  {
1042  # return NULL list
1043  $Args = NULL;
1044  }
1045  else
1046  {
1047  # assign component pieces to list parameters
1048  if (strlen($Pieces[0]) > 0) { $Args["from"] = $Pieces[0]; }
1049  if (strlen($Pieces[1]) > 0) { $Args["until"] = $Pieces[1]; }
1050  if (strlen($Pieces[2]) > 0) { $Args["metadataPrefix"] = $Pieces[2]; }
1051  if (strlen($Pieces[3]) > 0) { $Args["set"] = $Pieces[3]; }
1052  if (strlen($Pieces[4]) > 0) { $Args["ListStartPoint"] = $Pieces[4]; }
1053  }
1054 
1055  # return list parameter array to caller
1056  return $Args;
1057  }
1058 
1064  private function DateIsInvalid($Date)
1065  {
1066  # if date is null or matches required format
1067  if (empty($Date) || preg_match("/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/", $Date))
1068  {
1069  # date is okay
1070  return FALSE;
1071  }
1072  else
1073  {
1074  # date is not okay
1075  return TRUE;
1076  }
1077  }
1078 
1088  private function FormatTag(
1089  $Name = NULL, $Content = NULL, $Attributes = NULL, $NewIndentLevel = NULL)
1090  {
1091  static $IndentLevel = 1;
1092  static $OpenTagStack = array();
1093 
1094  # reset indent level if requested
1095  if ($NewIndentLevel !== NULL)
1096  {
1097  $IndentLevel = $NewIndentLevel;
1098  }
1099 
1100  # if tag name supplied
1101  if ($Name !== NULL)
1102  {
1103  # start out with appropriate indent
1104  $Tag = str_repeat(" ", ($IndentLevel * $this->IndentSize));
1105 
1106  # open begin tag
1107  $Tag .= "<".$Name;
1108 
1109  # if attributes supplied
1110  if ($Attributes !== NULL)
1111  {
1112  # add attributes
1113  foreach ($Attributes as $AttributeName => $AttributeValue)
1114  {
1115  $Tag .= " ".$AttributeName."=\"".$AttributeValue."\"";
1116  }
1117  }
1118 
1119  # if content supplied
1120  if ($Content !== NULL)
1121  {
1122  # close begin tag
1123  $Tag .= ">";
1124 
1125  # add content
1126  $Tag .= htmlspecialchars($Content);
1127 
1128  # add end tag
1129  $Tag .= "</".$Name.">\n";
1130  }
1131  else
1132  {
1133  # close begin tag
1134  $Tag .= ">\n";
1135 
1136  # increase indent level
1137  $IndentLevel++;
1138 
1139  # add tag to open tag stack
1140  array_push($OpenTagStack, $Name);
1141  }
1142  }
1143  else
1144  {
1145  # decrease indent level
1146  if ($IndentLevel > 0) { $IndentLevel--; }
1147 
1148  # pop last entry off of open tag stack
1149  $LastName = array_pop($OpenTagStack);
1150 
1151  # start out with appropriate indent
1152  $Tag = str_repeat(" ", ($IndentLevel * $this->IndentSize));
1153 
1154  # add end tag to match last open tag
1155  $Tag .= "</".$LastName.">\n";
1156  }
1157 
1158  # return formatted tag to caller
1159  return $Tag;
1160  }
1161 
1170  private function FormatItemContent(
1171  $Item, $MetadataFormat, $LocalFieldName, $OAIFieldName)
1172  {
1173  # retrieve content for field
1174  $Content = $Item->GetValue($LocalFieldName);
1175 
1176  # retrieve qualifiers for content
1177  $Qualifier = $Item->GetQualifier($LocalFieldName);
1178 
1179  # get qualifier maps, if any exists for our format
1180  $QualifierMaps = isset($this->QualifierMappings[$MetadataFormat]) ?
1181  $this->QualifierMappings[$MetadataFormat] :
1182  array();
1183 
1184  # get defaults, if any exist for our format
1185  $DefaultMaps = isset($this->FormatDescrs[$MetadataFormat]["DefaultMap"]) ?
1186  $this->FormatDescrs[$MetadataFormat]["DefaultMap"] :
1187  array();
1188 
1189  $Tags = "";
1190  # if content is array
1191  if (is_array($Content))
1192  {
1193  # for each element of array
1194  foreach ($Content as $ContentIndex => $ContentValue)
1195  {
1196  # if element has content
1197  if (strlen($ContentValue) > 0)
1198  {
1199  # determine if we have a mapped qualifier for this item
1200  $ContentAttribs = NULL;
1201  if (isset($Qualifier[$ContentIndex]) &&
1202  strlen($Qualifier[$ContentIndex]) &&
1203  isset($QualifierMaps[$Qualifier[$ContentIndex]]) &&
1204  strlen($QualifierMaps[$Qualifier[$ContentIndex]]) )
1205  {
1206  # if so, add the appropriate attribute
1207  $ContentAttribs["xsi:type"] =
1208  $QualifierMaps[$Qualifier[$ContentIndex]];
1209  }
1210 
1211  # generate tags for this field, append them to our list
1212  $Tags .= $this->FormatTag($OAIFieldName,
1213  utf8_encode(htmlspecialchars(preg_replace(
1214  "/[\\x00-\\x1F]+/", "", $ContentValue))),
1215  $ContentAttribs);
1216  }
1217  }
1218  }
1219  else
1220  {
1221  # check for a default value, fill it in if there was one
1222  if (strlen($Content)==0 && isset($DefaultMap[$OAIFieldName]))
1223  {
1224  $Content = $DefaultMap[$OAIFieldName];
1225  }
1226 
1227  # if field has content
1228  if (strlen($Content) > 0)
1229  {
1230  # generate tag for field
1231  $ContentAttribs = NULL;
1232  if (strlen($Qualifier) > 0 &&
1233  isset($QualifierMaps[$Qualifier]) &&
1234  strlen($QualifierMaps[$Qualifier]) )
1235  {
1236  $ContentAttribs["xsi:type"] =
1237  $QualifierMaps[$Qualifier];
1238 
1239  }
1240 
1241  $Tags .= $this->FormatTag($OAIFieldName,
1242  utf8_encode(htmlspecialchars(preg_replace(
1243  "/[\\x00-\\x1F]+/", "", $Content))),
1244  $ContentAttribs);
1245  }
1246  }
1247 
1248  return $Tags;
1249  }
1250 
1251 
1255  private function LoadArguments()
1256  {
1257  # if request type available via POST variables
1258  if (isset($_POST["verb"]))
1259  {
1260  # retrieve arguments from POST variables
1261  $this->Args = $_POST;
1262  }
1263  # else if request type available via GET variables
1264  elseif (isset($_GET["verb"]))
1265  {
1266  # retrieve arguments from GET variables
1267  $this->Args = $_GET;
1268  }
1269  else
1270  {
1271  # ERROR OUT
1272  return;
1273  }
1274 
1275  # clear out ApplicationFramework page specifier if set
1276  if (isset($this->Args["P"])) { unset($this->Args["P"]); }
1277  }
1278 
1279  # ---- methods to support OAI-SQ
1280 
1286  private function IsOaisqQuery($SetString)
1287  {
1288  return ((strpos($SetString, "OAI-SQ|") === 0)
1289  || (strpos($SetString, "OAI-SQ!") === 0)
1290  || (strpos($SetString, "OAI-SQ-F|") === 0)
1291  || (strpos($SetString, "OAI-SQ-F!") === 0)
1292  ) ? TRUE : FALSE;
1293  }
1294 
1301  private function TranslateOaisqEscapes($Pieces)
1302  {
1303  # for each piece
1304  $N = count($Pieces);
1305  for ($Index = 0; $Index < $N; $Index++)
1306  {
1307  # replace escaped chars with equivalents
1308  $Pieces[$Index] = preg_replace_callback(
1309  "/~[a-fA-F0-9]{2,2}/",
1310  create_function(
1311  '$Matches',
1312  'for ($Index = 0; $Index < count($Matches); $Index++)'
1313  .'{'
1314  .' $Replacements = chr(intval(substr($Matches[$Index],1,2),16));'
1315  .'}'
1316  .'return $Replacements;'
1317  ),
1318  $Pieces[$Index]);
1319  }
1320 
1321  # return translated array of pieces to caller
1322  return $Pieces;
1323  }
1324 
1332  private function ParseOaisqQuery($SetString, $FormatName)
1333  {
1334  # if OAI-SQ fielded search requested
1335  if (strpos($SetString, "OAI-SQ-F") === 0)
1336  {
1337  # split set string into field names and values
1338  $Pieces = explode(substr($SetString, 8, 1), $SetString);
1339 
1340  # discard first piece (OAI-SQ designator)
1341  array_shift($Pieces);
1342 
1343  # if set string contains escaped characters
1344  if (preg_match("/~[a-fA-F0-9]{2,2}/", $SetString))
1345  {
1346  $Pieces = $this->TranslateOaisqEscapes($Pieces);
1347  }
1348 
1349  # for every two pieces
1350  $SearchParams = array();
1351  $NumPairedPieces = round(count($Pieces) / 2) * 2;
1352  for ($Index = 0; $Index < $NumPairedPieces; $Index += 2)
1353  {
1354  # retrieve local field mapping
1355  foreach ($this->FieldMappings[$FormatName] as
1356  $LocalFieldName => $OAIFieldNames)
1357  {
1358  if (array_search($Pieces[$Index], $OAIFieldNames) !== FALSE)
1359  {
1360  $SearchParams[$LocalFieldName] = $Pieces[$Index + 1];
1361  }
1362  }
1363  }
1364  }
1365  else
1366  {
1367  # split set string to trim off query designator
1368  $Pieces = explode(substr($SetString, 6, 1), $SetString, 2);
1369 
1370  # if set string contains escaped characters
1371  if (preg_match("/~[a-fA-F0-9]{2,2}/", $SetString))
1372  {
1373  $Pieces = $this->TranslateOaisqEscapes($Pieces);
1374  }
1375 
1376  # remainder of set string is keyword search string
1377  $SearchParams["X-KEYWORD-X"] = $Pieces[1];
1378  }
1379 
1380  # return array of search parameters to caller
1381  return $SearchParams;
1382  }
1383 }
__construct($RepDescr, &$ItemFactory, $SetsSupported=FALSE, $OaisqSupported=FALSE)
Construct an OAI server object.
Definition: OAIServer.php:24
FormatElementList($FormatName)
Get list of elements for a specified format.
Definition: OAIServer.php:116
FormatQualifierList($FormatName)
Get the list of qualifiers for a specified format.
Definition: OAIServer.php:127
FormatList()
Get the list of formats.
Definition: OAIServer.php:101
GetResponse()
Get OAI response.
Definition: OAIServer.php:198
GetItem($ItemId)
Retrieve item by item ID.
GetFieldMapping($FormatName, $LocalFieldName)
Get mapped name for a field.
Definition: OAIServer.php:138
GetQualifierMapping($FormatName, $LocalQualifierName)
Get mapping for a qualifier.
Definition: OAIServer.php:168
AddFormat($Name, $TagName, $SchemaNamespace, $SchemaDefinition, $SchemaVersion, $NamespaceList, $ElementList, $QualifierList, $DefaultMap)
Add a new metadata format.
Definition: OAIServer.php:63
SetQualifierMapping($FormatName, $LocalQualifierName, $OAIQualifierName)
Set mapping for a qualifier.
Definition: OAIServer.php:187
GetItems($SqlCondition=NULL)
Retrieve items.
Common factory class for item manipulation.
Definition: ItemFactory.php:17
SetFieldMapping($FormatName, $LocalFieldName, $OAIFieldName)
Set mapping for a field.
Definition: OAIServer.php:157