Open Preprint Systems  3.3.0
CrossRefExportPlugin.inc.php
1 <?php
2 
16 import('classes.plugins.DOIPubIdExportPlugin');
17 
18 // The status of the Crossref DOI.
19 // any, notDeposited, and markedRegistered are reserved
20 define('CROSSREF_STATUS_FAILED', 'failed');
21 
22 define('CROSSREF_API_DEPOSIT_OK', 200);
23 
24 define('CROSSREF_API_URL', 'https://api.crossref.org/v2/deposits');
25 //TESTING
26 define('CROSSREF_API_URL_DEV', 'https://test.crossref.org/v2/deposits');
27 
28 define('CROSSREF_API_STAUTS_URL', 'https://api.crossref.org/servlet/submissionDownload');
29 //TESTING
30 define('CROSSREF_API_STAUTS_URL_DEV', 'https://test.crossref.org/servlet/submissionDownload');
31 
32 // The name of the setting used to save the registered DOI and the URL with the deposit status.
33 define('CROSSREF_DEPOSIT_STATUS', 'depositStatus');
34 
35 
37 
41  function getName() {
42  return 'CrossRefExportPlugin';
43  }
44 
48  function getDisplayName() {
49  return __('plugins.importexport.crossref.displayName');
50  }
51 
55  function getDescription() {
56  return __('plugins.importexport.crossref.description');
57  }
58 
62  function getSubmissionFilter() {
63  return 'preprint=>crossref-xml';
64  }
65 
69  function getStatusNames() {
70  return array_merge(parent::getStatusNames(), array(
71  EXPORT_STATUS_REGISTERED => __('plugins.importexport.crossref.status.registered'),
72  CROSSREF_STATUS_FAILED => __('plugins.importexport.crossref.status.failed'),
73  EXPORT_STATUS_MARKEDREGISTERED => __('plugins.importexport.crossref.status.markedRegistered'),
74  ));
75  }
76 
80  function getStatusActions($pubObject) {
81  $request = Application::get()->getRequest();
82  $dispatcher = $request->getDispatcher();
83  import('lib.pkp.classes.linkAction.request.AjaxModal');
84  return array(
85  CROSSREF_STATUS_FAILED =>
86  new LinkAction(
87  'failureMessage',
88  new AjaxModal(
89  $dispatcher->url(
90  $request, ROUTE_COMPONENT, null,
91  'grid.settings.plugins.settingsPluginGridHandler',
92  'manage', null, array('plugin' => 'CrossRefExportPlugin', 'category' => 'importexport', 'verb' => 'statusMessage',
93  'batchId' => $pubObject->getData($this->getDepositBatchIdSettingName()), 'submissionId' => $pubObject->getId())
94  ),
95  __('plugins.importexport.crossref.status.failed'),
96  'failureMessage'
97  ),
98  __('plugins.importexport.crossref.status.failed')
99  )
100  );
101  }
102 
107  // if the failure occured on request and the message was saved
108  // return that message
109  $submissionId = $request->getUserVar('submissionId');
110  $submissionDao = DAORegistry::getDAO('SubmissionDAO');
111  $article = $submissionDao->getById($submissionId);
112  $failedMsg = $article->getData($this->getFailedMsgSettingName());
113  if (!empty($failedMsg)) {
114  return $failedMsg;
115  }
116  // else check the failure message with Crossref, using the API
117  $context = $request->getContext();
118 
119  import('lib.pkp.classes.helpers.PKPCurlHelper');
120  $curlCh = PKPCurlHelper::getCurlObject();
121 
122  curl_setopt($curlCh, CURLOPT_RETURNTRANSFER, true);
123  curl_setopt($curlCh, CURLOPT_POST, true);
124  curl_setopt($curlCh, CURLOPT_HEADER, 0);
125 
126  // Use a different endpoint for testing and production.
127  $endpoint = ($this->isTestMode($context) ? CROSSREF_API_STAUTS_URL_DEV : CROSSREF_API_STAUTS_URL);
128  curl_setopt($curlCh, CURLOPT_URL, $endpoint);
129  // Set the form post fields
130  $username = $this->getSetting($context->getId(), 'username');
131  $password = $this->getSetting($context->getId(), 'password');
132  $batchId = $request->getUserVar('batchId');
133  $data = array('doi_batch_id' => $batchId, 'type' => 'result', 'usr' => $username, 'pwd' => $password);
134  curl_setopt($curlCh, CURLOPT_POSTFIELDS, $data);
135  $response = curl_exec($curlCh);
136 
137  if ($response === false) {
138  $result = __('plugins.importexport.common.register.error.mdsError', array('param' => 'No response from server.'));
139  } else {
140  $result = $response;
141  }
142  return $result;
143  }
144 
145 
149  function getExportActionNames() {
150  return array(
151  EXPORT_ACTION_DEPOSIT => __('plugins.importexport.crossref.action.register'),
152  EXPORT_ACTION_EXPORT => __('plugins.importexport.crossref.action.export'),
153  EXPORT_ACTION_MARKREGISTERED => __('plugins.importexport.crossref.action.markRegistered'),
154  );
155  }
156 
161  protected function _getObjectAdditionalSettings() {
162  return array_merge(parent::_getObjectAdditionalSettings(), array(
164  $this->getFailedMsgSettingName(),
165  ));
166  }
167 
172  return 'crossref';
173  }
174 
179  return 'CrossRefSettingsForm';
180  }
181 
186  return 'CrossrefExportDeployment';
187  }
188 
192  function executeExportAction($request, $objects, $filter, $tab, $objectsFileNamePart, $noValidation = null) {
193  $context = $request->getContext();
194  $path = array('plugin', $this->getName());
195 
196  import('lib.pkp.classes.file.FileManager');
197  $fileManager = new FileManager();
198  $resultErrors = array();
199 
200  if ($request->getUserVar(EXPORT_ACTION_DEPOSIT)) {
201  assert($filter != null);
202  // Errors occured will be accessible via the status link
203  // thus do not display all errors notifications (for every article),
204  // just one general.
205  // Warnings occured when the registration was successfull will however be
206  // displayed for each article.
207  $errorsOccured = false;
208  // The new Crossref deposit API expects one request per object.
209  // On contrary the export supports bulk/batch object export, thus
210  // also the filter expects an array of objects.
211  // Thus the foreach loop, but every object will be in an one item array for
212  // the export and filter to work.
213  foreach ($objects as $object) {
214  // Get the XML
215  $exportXml = $this->exportXML(array($object), $filter, $context, $noValidation);
216  // Write the XML to a file.
217  // export file name example: crossref-20160723-160036-articles-1-1.xml
218  $objectsFileNamePart = $objectsFileNamePart . '-' . $object->getId();
219  $exportFileName = $this->getExportFileName($this->getExportPath(), $objectsFileNamePart, $context, '.xml');
220  $fileManager->writeFile($exportFileName, $exportXml);
221  // Deposit the XML file.
222  $result = $this->depositXML($object, $context, $exportFileName);
223  if (!$result) {
224  $errorsOccured = true;
225  }
226  if (is_array($result)) {
227  $resultErrors[] = $result;
228  }
229  // Remove all temporary files.
230  $fileManager->deleteByPath($exportFileName);
231  }
232  // send notifications
233  if (empty($resultErrors)) {
234  if ($errorsOccured) {
235  $this->_sendNotification(
236  $request->getUser(),
237  'plugins.importexport.crossref.register.error.mdsError',
238  NOTIFICATION_TYPE_ERROR
239  );
240  } else {
241  $this->_sendNotification(
242  $request->getUser(),
243  $this->getDepositSuccessNotificationMessageKey(),
244  NOTIFICATION_TYPE_SUCCESS
245  );
246  }
247  } else {
248  foreach($resultErrors as $errors) {
249  foreach ($errors as $error) {
250  assert(is_array($error) && count($error) >= 1);
251  $this->_sendNotification(
252  $request->getUser(),
253  $error[0],
254  NOTIFICATION_TYPE_ERROR,
255  (isset($error[1]) ? $error[1] : null)
256  );
257  }
258  }
259  }
260  // redirect back to the right tab
261  $request->redirect(null, null, null, $path, null, $tab);
262  } else {
263  parent::executeExportAction($request, $objects, $filter, $tab, $objectsFileNamePart, $noValidation);
264  }
265  }
266 
276  function depositOnPublish($hookName, $args) {
277  error_log("depositOnPublish");
278  $newPublication = $args[0];
279  $objects[] = Services::get('submission')->get($newPublication->getData('submissionId'));
280  $request = Application::get()->getRequest();
281  $context = $request->getContext();
282  $filter = $this->getSubmissionFilter();
283  $objectsFileNamePart = 'preprints';
284  $noValidation = null;
285 
286  import('lib.pkp.classes.file.FileManager');
287  $fileManager = new FileManager();
288  $resultErrors = array();
289  $errorsOccured = false;
290 
291  foreach ($objects as $object) {
292  $exportXml = $this->exportXML(array($object), $filter, $context, $noValidation);
293  $objectsFileNamePart = $objectsFileNamePart . '-' . $object->getId();
294  $exportFileName = $this->getExportFileName($this->getExportPath(), $objectsFileNamePart, $context, '.xml');
295  $fileManager->writeFile($exportFileName, $exportXml);
296  $result = $this->depositXML($object, $context, $exportFileName);
297  if (!$result) {
298  $errorsOccured = true;
299  }
300  if (is_array($result)) {
301  $resultErrors[] = $result;
302  }
303  $fileManager->deleteByPath($exportFileName);
304  }
305  // send notifications
306  if (empty($resultErrors)) {
307  if ($errorsOccured) {
308  $this->_sendNotification(
309  $request->getUser(),
310  'plugins.importexport.crossref.register.error.mdsError',
311  NOTIFICATION_TYPE_ERROR
312  );
313  } else {
314  $this->_sendNotification(
315  $request->getUser(),
316  $this->getDepositSuccessNotificationMessageKey(),
317  NOTIFICATION_TYPE_SUCCESS
318  );
319  }
320  } else {
321  foreach($resultErrors as $errors) {
322  foreach ($errors as $error) {
323  assert(is_array($error) && count($error) >= 1);
324  $this->_sendNotification(
325  $request->getUser(),
326  $error[0],
327  NOTIFICATION_TYPE_ERROR,
328  (isset($error[1]) ? $error[1] : null)
329  );
330  }
331  }
332  }
333  }
334 
342  function depositXML($objects, $context, $filename) {
343  $status = null;
344 
345  import('lib.pkp.classes.helpers.PKPCurlHelper');
346  $curlCh = PKPCurlHelper::getCurlObject();
347 
348  curl_setopt($curlCh, CURLOPT_RETURNTRANSFER, true);
349  curl_setopt($curlCh, CURLOPT_POST, true);
350  curl_setopt($curlCh, CURLOPT_HEADER, 0);
351 
352  // Use a different endpoint for testing and production.
353  $endpoint = ($this->isTestMode($context) ? CROSSREF_API_URL_DEV : CROSSREF_API_URL);
354  curl_setopt($curlCh, CURLOPT_URL, $endpoint);
355  // Set the form post fields
356  $username = $this->getSetting($context->getId(), 'username');
357  $password = $this->getSetting($context->getId(), 'password');
358  assert(is_readable($filename));
359  if (function_exists('curl_file_create')) {
360  curl_setopt($curlCh, CURLOPT_SAFE_UPLOAD, true);
361  $cfile = new CURLFile($filename);
362  } else {
363  $cfile = "@$filename";
364  }
365  $data = array('operation' => 'doMDUpload', 'usr' => $username, 'pwd' => $password, 'mdFile' => $cfile);
366  curl_setopt($curlCh, CURLOPT_POSTFIELDS, $data);
367  $response = curl_exec($curlCh);
368 
369  $msg = null;
370  if ($response === false) {
371  $result = array(array('plugins.importexport.common.register.error.mdsError', 'No response from server.'));
372  } elseif (curl_getinfo($curlCh, CURLINFO_HTTP_CODE) != CROSSREF_API_DEPOSIT_OK) {
373  // These are the failures that occur immediatelly on request
374  // and can not be accessed later, so we save the falure message in the DB
375  $xmlDoc = new DOMDocument();
376  $xmlDoc->loadXML($response);
377  // Get batch ID
378  $batchIdNode = $xmlDoc->getElementsByTagName('batch_id')->item(0);
379  // Get re message
380  $msg = $response;
381  $status = CROSSREF_STATUS_FAILED;
382  $result = false;
383  } else {
384  // Get DOMDocument from the response XML string
385  $xmlDoc = new DOMDocument();
386  $xmlDoc->loadXML($response);
387  $batchIdNode = $xmlDoc->getElementsByTagName('batch_id')->item(0);
388 
389  // Get the DOI deposit status
390  // If the deposit failed
391  $failureCountNode = $xmlDoc->getElementsByTagName('failure_count')->item(0);
392  $failureCount = (int) $failureCountNode->nodeValue;
393  if ($failureCount > 0) {
394  $status = CROSSREF_STATUS_FAILED;
395  $result = false;
396  } else {
397  // Deposit was received
398  $status = EXPORT_STATUS_REGISTERED;
399  $result = true;
400 
401  // If there were some warnings, display them
402  $warningCountNode = $xmlDoc->getElementsByTagName('warning_count')->item(0);
403  $warningCount = (int) $warningCountNode->nodeValue;
404  if ($warningCount > 0) {
405  $result = array(array('plugins.importexport.crossref.register.success.warning', htmlspecialchars($response)));
406  }
407  // A possibility for other plugins (e.g. reference linking) to work with the response
408  HookRegistry::call('crossrefexportplugin::deposited', array($this, $response, $objects));
409  }
410  }
411  // Update the status
412  if ($status) {
413  $this->updateDepositStatus($context, $objects, $status, $batchIdNode->nodeValue, $msg);
414  $this->updateObject($objects);
415  }
416 
417  curl_close($curlCh);
418  return $result;
419  }
420 
429  function updateDepositStatus($context, $object, $status, $batchId, $failedMsg = null) {
430  assert(is_a($object, 'Submission'));
431  // remove the old failure message, if exists
432  $object->setData($this->getFailedMsgSettingName(), null);
433  $object->setData($this->getDepositStatusSettingName(), $status);
434  $object->setData($this->getDepositBatchIdSettingName(), $batchId);
435  if ($failedMsg) {
436  $object->setData($this->getFailedMsgSettingName(), $failedMsg);
437  }
438  if ($status == EXPORT_STATUS_REGISTERED) {
439  // Save the DOI -- the object will be updated
440  $this->saveRegisteredDoi($context, $object);
441  }
442  }
443 
447  function markRegistered($context, $objects) {
448  foreach ($objects as $object) {
449  // remove the old failure message, if exists
450  $object->setData($this->getFailedMsgSettingName(), null);
451  $object->setData($this->getDepositStatusSettingName(), EXPORT_STATUS_MARKEDREGISTERED);
452  $this->saveRegisteredDoi($context, $object);
453  }
454  }
455 
461  return $this->getPluginSettingsPrefix().'::failedMsg';
462  }
463 
469  return $this->getPluginSettingsPrefix().'::batchId';
470  }
471 
476  return 'plugins.importexport.common.register.success';
477  }
478 
482  function executeCLICommand($scriptName, $command, $context, $outputFile, $objects, $filter, $objectsFileNamePart) {
483  switch ($command) {
484  case 'export':
485  PluginRegistry::loadCategory('generic', true, $context->getId());
486  $exportXml = $this->exportXML($objects, $filter, $context);
487  if ($outputFile) file_put_contents($outputFile, $exportXml);
488  break;
489  case 'register':
490  PluginRegistry::loadCategory('generic', true, $context->getId());
491  import('lib.pkp.classes.file.FileManager');
492  $fileManager = new FileManager();
493  $resultErrors = array();
494  // Errors occured will be accessible via the status link
495  // thus do not display all errors notifications (for every article),
496  // just one general.
497  // Warnings occured when the registration was successfull will however be
498  // displayed for each article.
499  $errorsOccured = false;
500  // The new Crossref deposit API expects one request per object.
501  // On contrary the export supports bulk/batch object export, thus
502  // also the filter expects an array of objects.
503  // Thus the foreach loop, but every object will be in an one item array for
504  // the export and filter to work.
505  foreach ($objects as $object) {
506  // Get the XML
507  $exportXml = $this->exportXML(array($object), $filter, $context);
508  // Write the XML to a file.
509  // export file name example: crossref-20160723-160036-articles-1-1.xml
510  $objectsFileNamePartId = $objectsFileNamePart . '-' . $object->getId();
511  $exportFileName = $this->getExportFileName($this->getExportPath(), $objectsFileNamePartId, $context, '.xml');
512  $fileManager->writeFile($exportFileName, $exportXml);
513  // Deposit the XML file.
514  $result = $this->depositXML($object, $context, $exportFileName);
515  if (!$result) {
516  $errorsOccured = true;
517  }
518  if (is_array($result)) {
519  $resultErrors[] = $result;
520  }
521  // Remove all temporary files.
522  $fileManager->deleteByPath($exportFileName);
523  }
524  // display deposit result status messages
525  if (empty($resultErrors)) {
526  if ($errorsOccured) {
527  echo __('plugins.importexport.crossref.register.error.mdsError') . "\n";
528  } else {
529  echo __('plugins.importexport.common.register.success') . "\n";
530  }
531  } else {
532  echo __('plugins.importexport.common.cliError') . "\n";
533  foreach($resultErrors as $errors) {
534  foreach ($errors as $error) {
535  assert(is_array($error) && count($error) >= 1);
536  $errorMessage = __($error[0], array('param' => (isset($error[1]) ? $error[1] : null)));
537  echo "*** $errorMessage\n";
538  }
539  }
540  echo "\n";
541  $this->usage($scriptName);
542  }
543  break;
544  }
545  }
546 
547 }
548 
549 
CrossRefExportPlugin\getPluginSettingsPrefix
getPluginSettingsPrefix()
Definition: CrossRefExportPlugin.inc.php:171
CrossRefExportPlugin\getDepositSuccessNotificationMessageKey
getDepositSuccessNotificationMessageKey()
Definition: CrossRefExportPlugin.inc.php:475
PubObjectsExportPlugin\getDepositStatusSettingName
getDepositStatusSettingName()
Definition: PubObjectsExportPlugin.inc.php:482
PubObjectsExportPlugin\_sendNotification
_sendNotification($user, $message, $notificationType, $param=null)
Definition: PubObjectsExportPlugin.inc.php:649
CrossRefExportPlugin\updateDepositStatus
updateDepositStatus($context, $object, $status, $batchId, $failedMsg=null)
Definition: CrossRefExportPlugin.inc.php:429
DAORegistry\getDAO
static & getDAO($name, $dbconn=null)
Definition: DAORegistry.inc.php:57
DOIPubIdExportPlugin
Basis class for DOI XML metadata export plugins.
Definition: DOIPubIdExportPlugin.inc.php:24
CrossRefExportPlugin\getName
getName()
Definition: CrossRefExportPlugin.inc.php:41
CrossRefExportPlugin\getDisplayName
getDisplayName()
Definition: CrossRefExportPlugin.inc.php:48
CrossRefExportPlugin\getStatusNames
getStatusNames()
Definition: CrossRefExportPlugin.inc.php:69
CrossRefExportPlugin\getFailedMsgSettingName
getFailedMsgSettingName()
Definition: CrossRefExportPlugin.inc.php:460
CrossRefExportPlugin\getDescription
getDescription()
Definition: CrossRefExportPlugin.inc.php:55
CrossRefExportPlugin\depositOnPublish
depositOnPublish($hookName, $args)
Definition: CrossRefExportPlugin.inc.php:276
PKPCurlHelper\getCurlObject
static getCurlObject($url=null, $useProxySettings=true)
Definition: PKPCurlHelper.inc.php:23
PubObjectsExportPlugin\isTestMode
isTestMode($context)
Definition: PubObjectsExportPlugin.inc.php:474
PubObjectsExportPlugin\exportXML
exportXML($objects, $filter, $context, $noValidation=null)
Definition: PubObjectsExportPlugin.inc.php:346
PluginRegistry\loadCategory
static loadCategory($category, $enabledOnly=false, $mainContextId=null)
Definition: PluginRegistry.inc.php:103
DOIPubIdExportPlugin\saveRegisteredDoi
saveRegisteredDoi($context, $object, $testPrefix='10.1234')
Definition: DOIPubIdExportPlugin.inc.php:95
CrossRefExportPlugin\getSettingsFormClassName
getSettingsFormClassName()
Definition: CrossRefExportPlugin.inc.php:178
ImportExportPlugin\getExportFileName
getExportFileName($basePath, $objectsFileNamePart, $context, $extension='.xml')
Definition: ImportExportPlugin.inc.php:152
Plugin\getSetting
getSetting($contextId, $name)
Definition: Plugin.inc.php:473
CrossRefExportPlugin\getStatusActions
getStatusActions($pubObject)
Definition: CrossRefExportPlugin.inc.php:80
CrossRefExportPlugin\getExportActionNames
getExportActionNames()
Definition: CrossRefExportPlugin.inc.php:149
ImportExportPlugin\getExportPath
getExportPath()
Definition: ImportExportPlugin.inc.php:140
CrossRefExportPlugin\executeExportAction
executeExportAction($request, $objects, $filter, $tab, $objectsFileNamePart, $noValidation=null)
Definition: CrossRefExportPlugin.inc.php:192
AjaxModal
A modal that retrieves its content from via AJAX.
Definition: AjaxModal.inc.php:18
LinkAction
Base class defining an action that can be performed by the user in the user interface.
Definition: LinkAction.inc.php:22
CrossRefExportPlugin\executeCLICommand
executeCLICommand($scriptName, $command, $context, $outputFile, $objects, $filter, $objectsFileNamePart)
Definition: CrossRefExportPlugin.inc.php:482
PubObjectsExportPlugin\usage
usage($scriptName)
Definition: PubObjectsExportPlugin.inc.php:491
CrossRefExportPlugin\getDepositBatchIdSettingName
getDepositBatchIdSettingName()
Definition: CrossRefExportPlugin.inc.php:468
CrossRefExportPlugin
CrossRef/MEDLINE XML metadata export plugin.
Definition: CrossRefExportPlugin.inc.php:36
CrossRefExportPlugin\getSubmissionFilter
getSubmissionFilter()
Definition: CrossRefExportPlugin.inc.php:62
PubObjectsExportPlugin\updateObject
updateObject($object)
Definition: PubObjectsExportPlugin.inc.php:382
Plugin\$request
$request
Definition: Plugin.inc.php:68
CrossRefExportPlugin\_getObjectAdditionalSettings
_getObjectAdditionalSettings()
Definition: CrossRefExportPlugin.inc.php:161
CrossRefExportPlugin\markRegistered
markRegistered($context, $objects)
Definition: CrossRefExportPlugin.inc.php:447
PKPApplication\get
static get()
Definition: PKPApplication.inc.php:235
FileManager
Class defining basic operations for file management.
Definition: FileManager.inc.php:35
HookRegistry\call
static call($hookName, $args=null)
Definition: HookRegistry.inc.php:86
CrossRefExportPlugin\depositXML
depositXML($objects, $context, $filename)
Definition: CrossRefExportPlugin.inc.php:342
PKPServices\get
static get($service)
Definition: PKPServices.inc.php:49
CrossRefExportPlugin\getExportDeploymentClassName
getExportDeploymentClassName()
Definition: CrossRefExportPlugin.inc.php:185
CrossRefExportPlugin\getStatusMessage
getStatusMessage($request)
Definition: CrossRefExportPlugin.inc.php:106