Open Journal Systems  2.4.4
 All Classes Namespaces Functions Variables Groups Pages
CitationDAO.inc.php
1 <?php
2 
18 // FIXME: We currently have direct dependencies on specific filter groups.
19 // We have to make this configurable if we want to support different meta-data
20 // standards in the citation assistant (e.g. MODS).
21 define('CITATION_PARSER_FILTER_GROUP', 'plaintext=>nlm30-element-citation');
22 define('CITATION_LOOKUP_FILTER_GROUP', 'nlm30-element-citation=>nlm30-element-citation');
23 
24 import('lib.pkp.classes.citation.Citation');
25 
26 class CitationDAO extends DAO {
30  function CitationDAO() {
31  parent::DAO();
32  }
33 
39  function insertObject(&$citation) {
40  $seq = $citation->getSeq();
41  if (!(is_numeric($seq) && $seq > 0)) {
42  // Find the latest sequence number
43  $result =& $this->retrieve(
44  'SELECT MAX(seq) AS lastseq FROM citations
45  WHERE assoc_type = ? AND assoc_id = ?',
46  array(
47  (integer)$citation->getAssocType(),
48  (integer)$citation->getAssocId(),
49  )
50  );
51 
52  if ($result->RecordCount() != 0) {
53  $row =& $result->GetRowAssoc(false);
54  $seq = $row['lastseq'] + 1;
55  } else {
56  $seq = 1;
57  }
58  $citation->setSeq($seq);
59  }
60 
61  $this->update(
62  sprintf('INSERT INTO citations
63  (assoc_type, assoc_id, citation_state, raw_citation, seq)
64  VALUES
65  (?, ?, ?, ?, ?)'),
66  array(
67  (integer)$citation->getAssocType(),
68  (integer)$citation->getAssocId(),
69  (integer)$citation->getCitationState(),
70  $citation->getRawCitation(),
71  (integer)$seq
72  )
73  );
74  $citation->setId($this->getInsertId());
75  $this->_updateObjectMetadata($citation, false);
76  $this->updateCitationSourceDescriptions($citation);
77  return $citation->getId();
78  }
79 
85  function &getObjectById($citationId) {
86  $result =& $this->retrieve(
87  'SELECT * FROM citations WHERE citation_id = ?', $citationId
88  );
89 
90  $citation = null;
91  if ($result->RecordCount() != 0) {
92  $citation =& $this->_fromRow($result->GetRowAssoc(false));
93  }
94  $result->Close();
95 
96  return $citation;
97  }
98 
108  function importCitations(&$request, $assocType, $assocId, $rawCitationList) {
109  assert(is_numeric($assocType) && is_numeric($assocId));
110  $assocType = (int) $assocType;
111  $assocId = (int) $assocId;
112 
113  // Remove existing citations.
114  $this->deleteObjectsByAssocId($assocType, $assocId);
115 
116  // Tokenize raw citations
117  import('lib.pkp.classes.citation.CitationListTokenizerFilter');
118  $citationTokenizer = new CitationListTokenizerFilter();
119  $citationStrings = $citationTokenizer->execute($rawCitationList);
120 
121  // Instantiate and persist citations
122  $citations = array();
123  if (is_array($citationStrings)) foreach($citationStrings as $seq => $citationString) {
124  $citation = new Citation($citationString);
125 
126  // Initialize the citation with the raw
127  // citation string.
128  $citation->setRawCitation($citationString);
129 
130  // Set the object association
131  $citation->setAssocType($assocType);
132  $citation->setAssocId($assocId);
133 
134  // Set the counter
135  $citation->setSeq($seq+1);
136 
137  $this->insertObject($citation);
138  $citations[$citation->getId()] = $citation;
139  unset($citation);
140  }
141 
142  // Check new citations in parallel.
143  $noOfProcesses = (int)Config::getVar('general', 'citation_checking_max_processes');
144  $processDao =& DAORegistry::getDAO('ProcessDAO');
145  return $processDao->spawnProcesses($request, 'api.citation.CitationApiHandler', 'checkAllCitations', PROCESS_TYPE_CITATION_CHECKING, $noOfProcesses);
146  }
147 
161  function &checkCitation(&$request, &$originalCitation, $filterIds = array()) {
162  assert(is_a($originalCitation, 'Citation'));
163 
164  // Only parse the citation if it has not been parsed before.
165  // Otherwise we risk to overwrite manual user changes.
166  $filteredCitation =& $originalCitation;
167  if ($filteredCitation->getCitationState() < CITATION_PARSED) {
168  // Parse the requested citation
169  $filterCallback = array(&$this, '_instantiateParserFilters');
170  $filteredCitation =& $this->_filterCitation($request, $filteredCitation, $filterCallback, CITATION_PARSED, $filterIds);
171  }
172 
173  // Always re-lookup the citation even if it's been looked-up
174  // before. The user asked us to re-check so there's probably
175  // additional manual information in the citation fields.
176  $filterCallback = array(&$this, '_instantiateLookupFilters');
177  $filteredCitation =& $this->_filterCitation($request, $filteredCitation, $filterCallback, CITATION_LOOKED_UP, $filterIds);
178 
179  // Return the filtered citation.
180  return $filteredCitation;
181  }
182 
194  function checkNextRawCitation(&$request, $lockId) {
195  // NB: We implement an atomic locking strategy to make
196  // sure that no two parallel background processes can claim the
197  // same citation.
198  $rawCitation = null;
199  for ($try = 0; $try < 3; $try++) {
200  // We use three statements (read, write, read) rather than
201  // MySQL's UPDATE ... LIMIT ... to guarantee compatibility
202  // with ANSI SQL.
203 
204  // Get the ID of the next raw citation.
205  $result =& $this->retrieve(
206  'SELECT citation_id
207  FROM citations
208  WHERE citation_state = ?
209  LIMIT 1',
210  CITATION_RAW
211  );
212  if ($result->RecordCount() > 0) {
213  $nextRawCitation = $result->GetRowAssoc(false);
214  $nextRawCitationId = $nextRawCitation['citation_id'];
215  } else {
216  // Nothing to do.
217  $result->Close();
218  return false;
219  }
220  $result->Close();
221  unset($result);
222 
223  // Lock the citation.
224  $this->update(
225  'UPDATE citations
226  SET citation_state = ?, lock_id = ?
227  WHERE citation_id = ? AND citation_state = ?',
228  array(CITATION_CHECKED, $lockId, $nextRawCitationId, CITATION_RAW)
229  );
230 
231  // Make sure that no other concurring process
232  // has claimed this citation before we could
233  // lock it.
234  $result =& $this->retrieve(
235  'SELECT *
236  FROM citations
237  WHERE lock_id = ?',
238  $lockId
239  );
240  if ($result->RecordCount() > 0) {
241  $rawCitation =& $this->_fromRow($result->GetRowAssoc(false));
242  break;
243  }
244  }
245  $result->Close();
246  if (!is_a($rawCitation, 'Citation')) return false;
247 
248  // Check the citation.
249  $filteredCitation =& $this->checkCitation($request, $rawCitation);
250 
251  // Updating the citation will also release the lock.
252  $this->updateObject($filteredCitation);
253 
254  return true;
255  }
256 
268  function &getObjectsByAssocId($assocType, $assocId, $minCitationState = 0, $maxCitationState = CITATION_APPROVED, $rangeInfo = null) {
269  $result =& $this->retrieveRange(
270  'SELECT *
271  FROM citations
272  WHERE assoc_type = ? AND assoc_id = ? AND citation_state >= ? AND citation_state <= ?
273  ORDER BY seq, citation_id',
274  array((int)$assocType, (int)$assocId, (int)$minCitationState, (int)$maxCitationState),
275  $rangeInfo
276  );
277 
278  $returner = new DAOResultFactory($result, $this, '_fromRow', array('id'));
279  return $returner;
280  }
281 
298  function &getCitationFilterInstances($contextId, $filterGroups, $fromFilterIds = array(), $includeOptionalFilters = false) {
299  $filterDao =& DAORegistry::getDAO('FilterDAO'); /* @var $filterDao FilterDAO */
300  $filterList = array();
301 
302  // Retrieve the requested filter group(s).
303  if (is_scalar($filterGroups)) $filterGroups = array($filterGroups);
304  foreach($filterGroups as $filterGroupSymbolic) {
305  $filterList =& array_merge($filterList, $filterDao->getObjectsByGroup($filterGroupSymbolic, $contextId));
306  }
307 
308  // Filter the result list:
309  // 1) If the filter id list is empty and optional filters
310  // should not be included then return only non-optional
311  // (=default) filters.
312  $finalFilterList = array();
313  if (empty($fromFilterIds)) {
314  if ($includeOptionalFilters) {
315  // Return all filters including optional filters.
316  $finalFilterList =& $filterList;
317  } else {
318  // Only return default filters.
319  foreach($filterList as $filter) {
320  if (!$filter->getData('isOptional')) $finalFilterList[] =& $filter;
321  unset($filter);
322  }
323  }
324  // 2) If specific filter ids are given then only filters in that
325  // list will be returned (even if they are non-default filters).
326  } else {
327  foreach($filterList as $filter) {
328  if (in_array($filter->getId(), $fromFilterIds)) $finalFilterList[] =& $filter;
329  unset($filter);
330  }
331  }
332 
333  return $finalFilterList;
334  }
335 
340  function updateObject(&$citation) {
341  // Update the citation and release the lock
342  // on it (if one is present).
343  $returner = $this->update(
344  'UPDATE citations
345  SET assoc_type = ?,
346  assoc_id = ?,
347  citation_state = ?,
348  raw_citation = ?,
349  seq = ?,
350  lock_id = NULL
351  WHERE citation_id = ?',
352  array(
353  (integer)$citation->getAssocType(),
354  (integer)$citation->getAssocId(),
355  (integer)$citation->getCitationState(),
356  $citation->getRawCitation(),
357  (integer)$citation->getSeq(),
358  (integer)$citation->getId()
359  )
360  );
361  $this->_updateObjectMetadata($citation);
362  $this->updateCitationSourceDescriptions($citation);
363  }
364 
370  function deleteObject(&$citation) {
371  return $this->deleteObjectById($citation->getId());
372  }
373 
379  function deleteObjectById($citationId) {
380  assert(!empty($citationId));
381 
382  // Delete citation sources
383  $metadataDescriptionDao =& DAORegistry::getDAO('MetadataDescriptionDAO');
384  $metadataDescriptionDao->deleteObjectsByAssocId(ASSOC_TYPE_CITATION, $citationId);
385 
386  // Delete citation
387  $params = array((int)$citationId);
388  $this->update('DELETE FROM citation_settings WHERE citation_id = ?', $params);
389  return $this->update('DELETE FROM citations WHERE citation_id = ?', $params);
390  }
391 
398  function deleteObjectsByAssocId($assocType, $assocId) {
399  $citations =& $this->getObjectsByAssocId($assocType, $assocId);
400  while (($citation =& $citations->next())) {
401  $this->deleteObjectById($citation->getId());
402  unset($citation);
403  }
404  return true;
405  }
406 
412  function updateCitationSourceDescriptions(&$citation) {
413  $metadataDescriptionDao =& DAORegistry::getDAO('MetadataDescriptionDAO');
414 
415  // Clear all existing citation sources first
416  $citationId = $citation->getId();
417  assert(!empty($citationId));
418  $metadataDescriptionDao->deleteObjectsByAssocId(ASSOC_TYPE_CITATION, $citationId);
419 
420  // Now add the new citation sources
421  foreach ($citation->getSourceDescriptions() as $sourceDescription) {
422  // Make sure that this source description is correctly associated
423  // with the citation so that we can recover it later.
424  assert($sourceDescription->getAssocType() == ASSOC_TYPE_CITATION);
425  $sourceDescription->setAssocId($citationId);
426  $metadataDescriptionDao->insertObject($sourceDescription);
427  }
428  }
429 
436  function &instantiateCitationOutputFilter(&$context) {
437  // The filter is stateless so we can instantiate
438  // it once for all requests.
439  static $citationOutputFilter = null;
440  if (is_null($citationOutputFilter)) {
441  // Retrieve the currently selected citation output
442  // filter from the database.
443  $citationOutputFilterId = $context->getSetting('metaCitationOutputFilterId');
444  $filterDao =& DAORegistry::getDAO('FilterDAO');
445  $citationOutputFilter =& $filterDao->getObjectById($citationOutputFilterId);
446  assert(is_a($citationOutputFilter, 'PersistableFilter'));
447 
448  // We expect a string as output type.
449  $filterGroup =& $citationOutputFilter->getFilterGroup();
450  assert($filterGroup->getOutputType() == 'primitive::string');
451  }
452 
453  return $citationOutputFilter;
454  }
455 
456  //
457  // Protected helper methods
458  //
463  function getInsertId() {
464  return parent::getInsertId('citations', 'citation_id');
465  }
466 
467 
468  //
469  // Private helper methods
470  //
475  function &_newDataObject() {
476  $citation = new Citation();
477  return $citation;
478  }
479 
486  function &_fromRow(&$row) {
487  $citation =& $this->_newDataObject();
488  $citation->setId((integer)$row['citation_id']);
489  $citation->setAssocType((integer)$row['assoc_type']);
490  $citation->setAssocId((integer)$row['assoc_id']);
491  $citation->setCitationState($row['citation_state']);
492  $citation->setRawCitation($row['raw_citation']);
493  $citation->setSeq((integer)$row['seq']);
494 
495  $this->getDataObjectSettings('citation_settings', 'citation_id', $row['citation_id'], $citation);
496 
497  // Add citation source descriptions
498  $sourceDescriptions =& $this->_getCitationSourceDescriptions($citation->getId());
499  while ($sourceDescription =& $sourceDescriptions->next()) {
500  $citation->addSourceDescription($sourceDescription);
501  }
502 
503  return $citation;
504  }
505 
510  function _updateObjectMetadata(&$citation) {
511  // Persist citation meta-data
512  $this->updateDataObjectSettings('citation_settings', $citation,
513  array('citation_id' => $citation->getId()));
514  }
515 
522  function _getCitationSourceDescriptions($citationId) {
523  $metadataDescriptionDao =& DAORegistry::getDAO('MetadataDescriptionDAO');
524  $sourceDescriptions =& $metadataDescriptionDao->getObjectsByAssocId(ASSOC_TYPE_CITATION, $citationId);
525  return $sourceDescriptions;
526  }
527 
540  function &_instantiateParserFilters(&$citation, &$metadataDescription, $contextId, $fromFilterIds) {
541  $displayName = 'Citation Parser Filters'; // Only for internal debugging, no display to user.
542 
543  // Extract the raw citation string from the citation
544  $inputData = $citation->getRawCitation();
545 
546  // Instantiate parser filters.
547  $filterList =& $this->getCitationFilterInstances($contextId, CITATION_PARSER_FILTER_GROUP, $fromFilterIds);
548 
549  $transformationDefinition = compact('displayName', 'inputData', 'filterList');
550  return $transformationDefinition;
551  }
552 
566  function &_instantiateLookupFilters(&$citation, &$metadataDescription, $contextId, $fromFilterIds) {
567  $displayName = 'Citation Lookup Filters'; // Only for internal debugging, no display to user.
568 
569  // Define the input for this transformation.
570  $inputData =& $metadataDescription;
571 
572  // Instantiate lookup filters.
573  $filterList =& $this->getCitationFilterInstances($contextId, CITATION_LOOKUP_FILTER_GROUP, $fromFilterIds);
574 
575  $transformationDefinition = compact('displayName', 'inputData', 'filterList');
576  return $transformationDefinition;
577  }
578 
590  function &_filterCitation(&$request, &$citation, &$filterCallback, $citationStateAfterFiltering, $fromFilterIds = array()) {
591  // Get the context.
592  $router =& $request->getRouter();
593  $context =& $router->getContext($request);
594  assert(is_object($context));
595 
596  // Make sure that the citation implements only one
597  // meta-data schema.
598  $supportedMetadataSchemas =& $citation->getSupportedMetadataSchemas();
599  assert(count($supportedMetadataSchemas) == 1);
600  $metadataSchema =& $supportedMetadataSchemas[0];
601 
602  // Extract the meta-data description from the citation.
603  $originalDescription =& $citation->extractMetadata($metadataSchema);
604 
605  // Let the callback configure the transformation.
606  $transformationDefinition = call_user_func_array($filterCallback, array(&$citation, &$originalDescription, $context->getId(), $fromFilterIds));
607  $filterList =& $transformationDefinition['filterList'];
608  if (empty($filterList)) {
609  // We didn't find any applicable filter.
610  $filteredCitation = $citationMultiplexer = $citationFilterNet = null;
611  } else {
612  // Get the input into the transformation.
613  $muxInputData =& $transformationDefinition['inputData'];
614 
615  // Get the filter group.
616  // NB: The filter group is identical for all filters
617  // in the list. We can simply take the first filter's
618  // group.
619  $filterGroup =& $filterList[0]->getFilterGroup(); /* @var $filterGroup FilterGroup */
620 
621  // The filter group must be adapted to return an array rather
622  // than a scalar value.
623  $filterGroup->setOutputType($filterGroup->getOutputType().'[]');
624 
625  // Instantiate the citation multiplexer filter.
626  import('lib.pkp.classes.filter.GenericMultiplexerFilter');
627  $citationMultiplexer = new GenericMultiplexerFilter($filterGroup, $transformationDefinition['displayName']);
628 
629  // Don't fail just because one of the web services
630  // fails. They are much too unstable to rely on them.
631  $citationMultiplexer->setTolerateFailures(true);
632 
633  // Add sub-filters to the multiplexer.
634  $nullVar = null;
635  foreach($filterList as $citationFilter) {
636  if ($citationFilter->supports($muxInputData, $nullVar)) {
637  $citationMultiplexer->addFilter($citationFilter);
638  unset($citationFilter);
639  }
640  }
641 
642  // Instantiate the citation de-multiplexer filter.
643  // FIXME: This must be configurable if we want to support other
644  // meta-data schemas.
645  import('lib.pkp.plugins.metadata.nlm30.filter.Nlm30CitationDemultiplexerFilter');
646  $citationDemultiplexer = new Nlm30CitationDemultiplexerFilter();
647  $citationDemultiplexer->setOriginalDescription($originalDescription);
648  $citationDemultiplexer->setOriginalRawCitation($citation->getRawCitation());
649  $citationDemultiplexer->setCitationOutputFilter($this->instantiateCitationOutputFilter($context));
650 
651  // Combine multiplexer and de-multiplexer to form the
652  // final citation filter network.
653  import('lib.pkp.classes.filter.GenericSequencerFilter');
654  $citationFilterNet = new GenericSequencerFilter(
656  $filterGroup->getInputType(),
657  'class::lib.pkp.classes.citation.Citation'),
658  'Citation Filter Network');
659  $citationFilterNet->addFilter($citationMultiplexer);
660  $citationFilterNet->addFilter($citationDemultiplexer);
661 
662  // Send the input through the citation filter network.
663  $filteredCitation =& $citationFilterNet->execute($muxInputData);
664  }
665 
666  if (is_null($filteredCitation)) {
667  // Return the original citation if the filters
668  // did not produce any results and add an error message.
669  $filteredCitation =& $citation;
670  if (!empty($transformationDefinition['filterList'])) {
671  $filteredCitation->addError(__('submission.citations.filter.noResultFromFilterError'));
672  }
673  } else {
674  // Copy data from the original citation to the filtered citation.
675  $filteredCitation->setId($citation->getId());
676  $filteredCitation->setSeq($citation->getSeq());
677  $filteredCitation->setRawCitation($citation->getRawCitation());
678  $filteredCitation->setAssocId($citation->getAssocId());
679  $filteredCitation->setAssocType($citation->getAssocType());
680  foreach($citation->getErrors() as $errorMessage) {
681  $filteredCitation->addError($errorMessage);
682  }
683  foreach($citation->getSourceDescriptions() as $sourceDescription) {
684  $filteredCitation->addSourceDescription($sourceDescription);
685  unset($sourceDescription);
686  }
687  }
688 
689  // Set the target citation state.
690  $filteredCitation->setCitationState($citationStateAfterFiltering);
691 
692  if (is_a($citationMultiplexer, 'CompositeFilter')) {
693  // Retrieve the results of intermediate filters and add
694  // them to the citation for inspection by the end user.
695  $lastOutput =& $citationMultiplexer->getLastOutput();
696  if (is_array($lastOutput)) {
697  foreach($lastOutput as $sourceDescription) {
698  $filteredCitation->addSourceDescription($sourceDescription);
699  unset($sourceDescription);
700  }
701  }
702  }
703 
704  if (is_a($citationFilterNet, 'CompositeFilter')) {
705  // Add filtering errors (if any) to the citation's error list.
706  foreach($citationFilterNet->getErrors() as $filterError) {
707  $filteredCitation->addError($filterError);
708  }
709  }
710 
711  return $filteredCitation;
712  }
713 }
714 
715 ?>
_updateObjectMetadata(&$citation)
Operations for retrieving and modifying objects from a database.
Definition: DAO.inc.php:29
A generic filter that is configured with a number of equal type filters. It takes the input argument...
Filter that takes a list of NLM citation descriptions and joins them into a single &quot;best&quot; citation...
deleteObjectById($citationId)
A generic filter that is configured with a number of ordered filters. It takes the input argument of ...
Operations for retrieving and modifying Citation objects.
& getObjectsByAssocId($assocType, $assocId, $minCitationState=0, $maxCitationState=CITATION_APPROVED, $rangeInfo=null)
& retrieve($sql, $params=false, $callHooks=true)
Definition: DAO.inc.php:83
importCitations(&$request, $assocType, $assocId, $rawCitationList)
tempGroup($inputType, $outputType)
deleteObjectsByAssocId($assocType, $assocId)
deleteObject(&$citation)
updateDataObjectSettings($tableName, &$dataObject, $idArray)
Definition: DAO.inc.php:460
Class that takes an unformatted list of citations and returns an array of raw citation strings...
& _fromRow(&$row)
& retrieveRange($sql, $params=false, $dbResultRange=null, $callHooks=true)
Definition: DAO.inc.php:176
updateObject(&$citation)
Wrapper around ADORecordSet providing &quot;factory&quot; features for generating objects from DAOs...
getVar($section, $key, $default=null)
Definition: Config.inc.php:34
& checkCitation(&$request, &$originalCitation, $filterIds=array())
& getDAO($name, $dbconn=null)
& getCitationFilterInstances($contextId, $filterGroups, $fromFilterIds=array(), $includeOptionalFilters=false)
Class representing a citation (bibliographic reference)
insertObject(&$citation)
& _instantiateParserFilters(&$citation, &$metadataDescription, $contextId, $fromFilterIds)
& getObjectById($citationId)
update($sql, $params=false, $callHooks=true, $dieOnError=true)
Definition: DAO.inc.php:211
& instantiateCitationOutputFilter(&$context)
updateCitationSourceDescriptions(&$citation)
checkNextRawCitation(&$request, $lockId)
_getCitationSourceDescriptions($citationId)
& _filterCitation(&$request, &$citation, &$filterCallback, $citationStateAfterFiltering, $fromFilterIds=array())
& _instantiateLookupFilters(&$citation, &$metadataDescription, $contextId, $fromFilterIds)