first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-06-08 17:09:23 -04:00
commit df3a033196
17887 changed files with 8637778 additions and 0 deletions
+212
View File
@@ -0,0 +1,212 @@
<?php
/**
* @file classes/plugins/DOIPubIdExportPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class DOIPubIdExportPlugin
*
* @ingroup plugins
*
* @brief Basis class for DOI XML metadata export plugins
*/
namespace APP\plugins;
use APP\facades\Repo;
use APP\issue\Issue;
use APP\journal\Journal;
use APP\submission\Submission;
use APP\template\TemplateManager;
use PKP\core\PKPString;
use PKP\galley\Galley;
use PKP\submission\PKPSubmission;
// Configuration errors.
define('DOI_EXPORT_CONFIG_ERROR_DOIPREFIX', 0x01);
// The name of the setting used to save the registered DOI.
define('DOI_EXPORT_REGISTERED_DOI', 'registeredDoi');
abstract class DOIPubIdExportPlugin extends PubObjectsExportPlugin
{
/**
* @copydoc ImportExportPlugin::display()
*/
public function display($args, $request)
{
switch (array_shift($args)) {
case 'index':
case '':
$templateMgr = TemplateManager::getManager($request);
$templateMgr->display($this->getTemplateResource('index.tpl'));
break;
default:
parent::display($args, $request);
}
}
/**
* Get pub ID type
*
* @return string
*/
public function getPubIdType()
{
return 'doi';
}
/**
* Get pub ID display type
*
* @return string
*/
public function getPubIdDisplayType()
{
return 'DOI';
}
/**
* Mark selected submissions or issues as registered.
*
* @param Journal $context
* @param array $objects Array of published submissions, issues or galleys
*/
public function markRegistered($context, $objects)
{
foreach ($objects as $object) {
$doiId = $object->getData('doiId');
if ($doiId != null) {
Repo::doi()->markRegistered($doiId);
}
}
}
/**
* Saving object's DOI to the object's
* "registeredDoi" setting.
* We prefix the setting with the plugin's
* id so that we do not get name clashes
* when several DOI registration plug-ins
* are active at the same time.
*
* @param Journal $context
* @param Issue|Submission|Galley $object
* @param string $testPrefix
*/
public function saveRegisteredDoi($context, $object, $testPrefix = '10.1234')
{
$registeredDoi = $object->getStoredPubId('doi');
assert(!empty($registeredDoi));
if ($this->isTestMode($context)) {
$registeredDoi = PKPString::regexp_replace('#^[^/]+/#', $testPrefix . '/', $registeredDoi);
}
$object->setData($this->getPluginSettingsPrefix() . '::' . DOI_EXPORT_REGISTERED_DOI, $registeredDoi);
$this->updateObject($object);
}
/**
* Get a list of additional setting names that should be stored with the objects.
*
* @return array
*/
protected function _getObjectAdditionalSettings()
{
return array_merge(parent::_getObjectAdditionalSettings(), [
$this->getPluginSettingsPrefix() . '::' . DOI_EXPORT_REGISTERED_DOI
]);
}
/**
* Get published submissions with a DOI assigned from submission IDs.
*
* @param array $submissionIds
* @param Journal $context
*
* @return array
*/
public function getPublishedSubmissions($submissionIds, $context)
{
$allSubmissionIds = Repo::submission()
->getCollector()
->filterByContextIds([$context->getId()])
->filterByStatus([PKPSubmission::STATUS_PUBLISHED])
->getIds()
->toArray();
$validSubmissionIds = array_intersect($allSubmissionIds, $submissionIds);
$submissions = array_map(function ($submissionId) {
return Repo::submission()->get($submissionId);
}, $validSubmissionIds);
return array_filter($submissions, function ($submission) {
return $submission->getCurrentPublication()->getDoi() !== null;
});
}
/**
* Get published issues with a DOI assigned from issue IDs.
*
* @param array $issueIds
* @param Journal $context
*
* @return array
*/
public function getPublishedIssues($issueIds, $context)
{
return Repo::issue()
->getCollector()
->filterByContextIds([$context->getId()])
->filterByIssueIds($issueIds)
->filterByPublished(true)
->filterByHasDois(true)
->getMany()
->toArray();
}
/**
* Get article galleys with a DOI assigned from galley IDs.
*
* @param array $galleyIds
* @param Journal $context
*
* @return array
*/
public function getArticleGalleys($galleyIds, $context)
{
$allGalleyIds = Repo::galley()
->getCollector()
->filterByContextIds([$context->getId()])
->getIds()
->toArray();
$validGalleyIds = array_intersect($allGalleyIds, $galleyIds);
$galleys = array_map(function ($galleyId) {
return Repo::galley()->get($galleyId);
}, $validGalleyIds);
return array_filter($galleys, function ($galley) {
return $galley->getDoi() !== null;
});
}
/**
* @copydoc ImportExportPlugin::executeCLI()
*/
public function executeCLI($scriptName, &$args)
{
return;
}
/**
* @copydoc ImportExportPlugin::supportsCLI()
*/
public function supportsCLI(): bool
{
return false;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\APP\plugins\DOIPubIdExportPlugin', '\DOIPubIdExportPlugin');
}
@@ -0,0 +1,49 @@
<?php
/**
* @file classes/plugins/IDoiRegistrationAgency.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class IDoiRegistrationAgency
*
* @ingroup plugins
*
* @brief Interface that registration agency plugins must implement to support DOI registrations.
*/
namespace APP\plugins;
use APP\issue\Issue;
use APP\submission\Submission;
use PKP\context\Context;
use PKP\plugins\IPKPDoiRegistrationAgency;
interface IDoiRegistrationAgency extends IPKPDoiRegistrationAgency
{
/**
* @param Submission[] $submissions
*
*/
public function exportSubmissions(array $submissions, Context $context): array;
/**
* @param Submission[] $submissions
*
*/
public function depositSubmissions(array $submissions, Context $context): array;
/**
* @param Issue[] $issues
*
*/
public function exportIssues(array $issues, Context $context): array;
/**
* @param Issue[] $issues
*
*/
public function depositIssues(array $issues, Context $context): array;
}
+415
View File
@@ -0,0 +1,415 @@
<?php
/**
* @file classes/plugins/PubIdPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PubIdPlugin
*
* @ingroup plugins
*
* @brief Public identifiers plugins common functions
*/
namespace APP\plugins;
use APP\core\Application;
use APP\facades\Repo;
use APP\issue\Collector;
use APP\issue\Issue;
use APP\journal\Journal;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use PKP\core\JSONMessage;
use PKP\core\PKPString;
use PKP\submission\Representation;
use PKP\submissionFile\SubmissionFile;
abstract class PubIdPlugin extends \PKP\plugins\PKPPubIdPlugin
{
/**
* @copydoc Plugin::manage()
*/
public function manage($args, $request)
{
$user = $request->getUser();
$router = $request->getRouter();
$context = $router->getContext($request);
$notificationManager = new NotificationManager();
switch ($request->getUserVar('verb')) {
case 'assignPubIds':
if (!$request->checkCSRF()) {
return new JSONMessage(false);
}
return $this->assignPubIds($request, $context);
default:
return parent::manage($args, $request);
}
}
/**
* Handles pubId assignment for any publication, galley, or issue pubIds
* (usen on the plugin setting page, called in the plugin manage function)
*/
protected function assignPubIds($request, $context): JSONMessage
{
$suffixFieldName = $this->getSuffixFieldName();
$suffixGenerationStrategy = $this->getSetting($context->getId(), $suffixFieldName);
if ($suffixGenerationStrategy != 'customId') {
$issueEnabled = $this->isObjectTypeEnabled('Issue', $context->getId());
$publicationEnabled = $this->isObjectTypeEnabled('Publication', $context->getId());
$representationEnabled = $this->isObjectTypeEnabled('Representation', $context->getId());
if ($issueEnabled) {
$issues = Repo::issue()->getCollector()
->filterByContextIds([$context->getId()])
->filterByPublished(true)
->orderBy(Collector::ORDERBY_PUBLISHED_ISSUES)
->getMany();
foreach ($issues as $issue) {
$issuePubId = $issue->getStoredPubId($this->getPubIdType());
if (empty($issuePubId)) {
$issuePubId = $this->getPubId($issue);
Repo::issue()->dao->changePubId($issue->getId(), $this->getPubIdType(), $issuePubId);
}
}
}
if ($publicationEnabled || $representationEnabled) {
$representationDao = Application::getRepresentationDAO();
$submissions = Repo::submission()->getCollector()
->filterByContextIds([$context->getId()])
->filterByStatus([Submission::STATUS_PUBLISHED])
->getMany();
foreach ($submissions as $submission) {
$publications = $submission->getData('publications');
if ($publicationEnabled) {
foreach ($publications as $publication) {
$publicationPubId = $publication->getStoredPubId($this->getPubIdType());
if (empty($publicationPubId)) {
$publicationPubId = $this->getPubId($publication);
Repo::publication()->dao->changePubId(
$publication->getId(),
$this->getPubIdType(),
$publicationPubId
);
}
}
}
if ($representationEnabled) {
foreach ($publications as $publication) {
$representations = Repo::galley()->getCollector()
->filterByPublicationIds([$publication->getId()])
->getMany();
foreach ($representations as $representation) {
$representationPubId = $representation->getStoredPubId($this->getPubIdType());
if (empty($representationPubId)) {
$representationPubId = $this->getPubId($representation);
$representationDao->changePubId(
$representation->getId(),
$this->getPubIdType(),
$representationPubId
);
}
}
}
}
}
}
}
return new JSONMessage(true);
}
//
// Protected template methods from PKPPlubIdPlugin
//
/**
* @copydoc PKPPubIdPlugin::getPubObjectTypes()
*/
public function getPubObjectTypes()
{
$pubObjectTypes = parent::getPubObjectTypes();
$pubObjectTypes['Issue'] = 'APP\issue\Issue';
return $pubObjectTypes;
}
/**
* @copydoc PKPPubIdPlugin::checkDuplicate()
*/
public function checkDuplicate($pubId, $pubObjectType, $excludeId, $contextId)
{
foreach ($this->getPubObjectTypes() as $type => $fqcn) {
if ($type === 'Issue') {
$excludeTypeId = $type === $pubObjectType ? $excludeId : null;
if (Repo::issue()->dao->pubIdExists($this->getPubIdType(), $pubId, $excludeTypeId, $contextId)) {
return false;
}
}
}
return parent::checkDuplicate($pubId, $pubObjectType, $excludeId, $contextId);
}
/**
* Get the public identifier.
*
* @param object $pubObject
* Publication, Representation, SubmissionFile, Issue
*
* @return string
*/
public function getPubId($pubObject)
{
// Get the pub id type
$pubIdType = $this->getPubIdType();
// If we already have an assigned pub id, use it.
$storedPubId = $pubObject->getStoredPubId($pubIdType);
if ($storedPubId) {
return $storedPubId;
}
// Determine the type of the publishing object.
$pubObjectType = $this->getPubObjectType($pubObject);
// Initialize variables for publication objects.
$issue = ($pubObjectType == 'Issue' ? $pubObject : null);
$submission = null;
// Publication is actually handled differently now, but keep it here however for now.
$publication = ($pubObjectType == 'Publication' ? $pubObject : null);
$representation = ($pubObjectType == 'Representation' ? $pubObject : null);
$submissionFile = ($pubObjectType == 'SubmissionFile' ? $pubObject : null);
// Get the context id.
if ($pubObjectType === 'Issue') {
$contextId = $pubObject->getJournalId();
} elseif ($pubObjectType === 'Representation') {
$publication = Repo::publication()->get($pubObject->getData('publicationId'));
$submission = Repo::submission()->get($publication->getData('submissionId'));
$contextId = $submission->getData('contextId');
} elseif (in_array($pubObjectType, ['Publication', 'SubmissionFile'])) {
$submission = Repo::submission()->get($pubObject->getData('submissionId'));
$contextId = $submission->getData('contextId');
}
// Check the context
$context = $this->getContext($contextId);
if (!$context) {
return null;
}
$contextId = $context->getId();
// Check whether pub ids are enabled for the given object type.
$objectTypeEnabled = $this->isObjectTypeEnabled($pubObjectType, $contextId);
if (!$objectTypeEnabled) {
return null;
}
// Retrieve the issue.
if (!$pubObject instanceof Issue) {
assert(!is_null($submission));
$issue = Repo::issue()->getBySubmissionId($submission->getId());
$issue = $issue->getJournalId() == $contextId ? $issue : null;
}
if ($issue && $contextId != $issue->getJournalId()) {
return null;
}
// Retrieve the pub id prefix.
$pubIdPrefix = $this->getSetting($contextId, $this->getPrefixFieldName());
if (empty($pubIdPrefix)) {
return null;
}
// Generate the pub id suffix.
$suffixFieldName = $this->getSuffixFieldName();
$suffixGenerationStrategy = $this->getSetting($contextId, $suffixFieldName);
switch ($suffixGenerationStrategy) {
case 'customId':
$pubIdSuffix = $pubObject->getData($suffixFieldName);
break;
case 'pattern':
$suffixPatternsFieldNames = $this->getSuffixPatternsFieldNames();
$pubIdSuffix = $this->getSetting($contextId, $suffixPatternsFieldNames[$pubObjectType]);
$pubIdSuffix = $this->generateCustomPattern($context, $pubIdSuffix, $pubObject, $issue, $submission, $representation, $submissionFile);
break;
default:
$pubIdSuffix = $this::generateDefaultPattern($context, $issue, $submission, $representation, $submissionFile);
}
if (empty($pubIdSuffix)) {
return null;
}
// Construct the pub id from prefix and suffix.
$pubId = $this->constructPubId($pubIdPrefix, $pubIdSuffix, $contextId);
return $pubId;
}
/**
* Generate the default, semantic-based pub-id pattern suffix
*
* @param Journal $context
* @param ?Issue $issue
* @param Submission $submission
* @param Representation $representation
* @param SubmissionFile $submissionFile
*
*/
public static function generateDefaultPattern($context, $issue = null, $submission = null, $representation = null, $submissionFile = null): string
{
$pubIdSuffix = PKPString::regexp_replace('/[^-._;()\/A-Za-z0-9]/', '', PKPString::strtolower($context->getAcronym($context->getPrimaryLocale())));
if ($issue) {
$pubIdSuffix .= '.v' . $issue->getVolume() . 'i' . $issue->getNumber();
} else {
$pubIdSuffix .= '.v%vi%i';
}
if ($submission) {
$pubIdSuffix .= '.' . $submission->getId();
}
if ($representation) {
$pubIdSuffix .= '.g' . $representation->getId();
}
if ($submissionFile) {
$pubIdSuffix .= '.f' . $submissionFile->getId();
}
return $pubIdSuffix;
}
/**
* Generate the custom, user-defined pub-id pattern suffix
*
* @param Journal $context
* @param string $pubIdSuffix
* @param object $pubObject
* @param Issue $issue
* @param Submission $submission
* @param Representation $representation
* @param SubmissionFile $submissionFile
*
*/
public static function generateCustomPattern($context, $pubIdSuffix, $pubObject, $issue = null, $submission = null, $representation = null, $submissionFile = null): string
{
// %j - journal initials, remove special characters and uncapitalize
$pubIdSuffix = PKPString::regexp_replace('/%j/', PKPString::regexp_replace('/[^-._;()\/A-Za-z0-9]/', '', PKPString::strtolower($context->getAcronym($context->getPrimaryLocale()))), $pubIdSuffix);
// %x - custom identifier
if ($pubObject->getStoredPubId('publisher-id')) {
$pubIdSuffix = PKPString::regexp_replace('/%x/', $pubObject->getStoredPubId('publisher-id'), $pubIdSuffix);
}
if ($issue) {
// %v - volume number
$pubIdSuffix = PKPString::regexp_replace('/%v/', $issue->getVolume(), $pubIdSuffix);
// %i - issue number
$pubIdSuffix = PKPString::regexp_replace('/%i/', $issue->getNumber(), $pubIdSuffix);
// %Y - year
$pubIdSuffix = PKPString::regexp_replace('/%Y/', $issue->getYear(), $pubIdSuffix);
}
if ($submission) {
// %a - article id
$pubIdSuffix = PKPString::regexp_replace('/%a/', $submission->getId(), $pubIdSuffix);
// %p - page number
if ($submission->getPages()) {
$pubIdSuffix = PKPString::regexp_replace('/%p/', $submission->getPages(), $pubIdSuffix);
}
}
if ($representation) {
// %g - galley id
$pubIdSuffix = PKPString::regexp_replace('/%g/', $representation->getId(), $pubIdSuffix);
}
if ($submissionFile) {
// %f - file id
$pubIdSuffix = PKPString::regexp_replace('/%f/', $submissionFile->getId(), $pubIdSuffix);
}
return $pubIdSuffix;
}
//
// Public API
//
/**
* Clear pubIds of all issue objects.
*
* @param Issue $issue
*/
public function clearIssueObjectsPubIds($issue)
{
$publicationPubIdEnabled = $this->isObjectTypeEnabled('Publication', $issue->getJournalId());
$representationPubIdEnabled = $this->isObjectTypeEnabled('Representation', $issue->getJournalId());
$filePubIdEnabled = $this->isObjectTypeEnabled('SubmissionFile', $issue->getJournalId());
if (!$publicationPubIdEnabled && !$representationPubIdEnabled && !$filePubIdEnabled) {
return false;
}
$pubIdType = $this->getPubIdType();
$submissionIds = Repo::submission()
->getCollector()
->filterByContextIds([$issue->getJournalId()])
->filterByIssueIds([$issue->getId()])
->getIds();
foreach ($submissionIds as $submissionId) {
$submission = Repo::submission()->get($submissionId);
if ($publicationPubIdEnabled) { // Does this option have to be enabled here for?
foreach ($submission->getData('publications') as $publication) {
Repo::publication()->dao->deletePubId($publication->getId(), $pubIdType);
}
}
if ($representationPubIdEnabled || $filePubIdEnabled) { // Does this option have to be enabled here for?
foreach ($submission->getData('publications') as $publication) {
$representations = Application::getRepresentationDAO()->getByPublicationId($publication->getId());
foreach ($representations as $representation) {
if ($representationPubIdEnabled) { // Does this option have to be enabled here for?
Application::getRepresentationDAO()->deletePubId($representation->getId(), $pubIdType);
}
if ($filePubIdEnabled) { // Does this option have to be enabled here for?
$articleProofFileIds = Repo::submissionFile()
->getCollector()
->filterByAssoc(
Application::ASSOC_TYPE_REPRESENTATION,
[$representation->getId()]
)->filterByFileStages([SubmissionFile::SUBMISSION_FILE_PROOF])
->getIds();
foreach ($articleProofFileIds as $articleProofFileId) {
Repo::submissionFile()->dao->deletePubId($articleProofFileId, $pubIdType);
}
}
}
unset($representations);
}
}
}
}
/**
* @copydoc PKPPubIdPlugin::getDAOs()
*/
public function getDAOs()
{
return array_merge(parent::getDAOs(), [Repo::issue()->dao]);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\APP\plugins\PubIdPlugin', '\PubIdPlugin');
}
+169
View File
@@ -0,0 +1,169 @@
<?php
/**
* @file classes/plugins/PubObjectCache.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PubObjectCache
*
* @ingroup plugins
*
* @brief A cache for publication objects required during export.
*/
namespace APP\plugins;
use APP\issue\Issue;
use APP\submission\Submission;
use PKP\galley\Galley;
use PKP\submission\Genre;
class PubObjectCache
{
/** @var array */
public $_objectCache = [];
//
// Public API
//
/**
* Add a publishing object to the cache.
*
* @param Issue|Submission|Galley|Genre $object
* @param Submission|null $parent Only required when adding a galley.
*/
public function add($object, $parent)
{
if ($object instanceof Issue) {
$this->_insertInternally($object, 'issues', $object->getId());
}
if ($object instanceof Submission) {
$this->_insertInternally($object, 'articles', $object->getId());
$this->_insertInternally($object, 'articlesByIssue', $object->getCurrentPublication()->getData('issueId'), $object->getId());
}
if ($object instanceof Galley) {
assert($parent instanceof Submission);
$this->_insertInternally($object, 'galleys', $object->getId());
$this->_insertInternally($object, 'galleysByArticle', $object->getData('submissionId'), $object->getId());
$this->_insertInternally($object, 'galleysByIssue', $parent->getCurrentPublication()->getData('issueId'), $object->getId());
}
if ($object instanceof Genre) {
$this->_insertInternally($object, 'genres', $object->getId());
}
}
/**
* Marks the given cache id "complete", i.e. it
* contains all child objects for the given object
* id.
*
* @param string $cacheId
* @param string $objectId
*/
public function markComplete($cacheId, $objectId)
{
assert(is_array($this->_objectCache[$cacheId][$objectId]));
$this->_objectCache[$cacheId][$objectId]['complete'] = true;
// Order objects in the completed cache by ID.
ksort($this->_objectCache[$cacheId][$objectId]);
}
/**
* Retrieve (an) object(s) from the cache.
*
* NB: You must check whether an object is in the cache
* before you try to retrieve it with this method.
*
* @param string $cacheId
* @param int $id1
* @param int $id2
*
*/
public function get($cacheId, $id1, $id2 = null)
{
assert($this->isCached($cacheId, $id1, $id2));
if (is_null($id2)) {
$returner = $this->_objectCache[$cacheId][$id1];
if (is_array($returner)) {
unset($returner['complete']);
}
return $returner;
} else {
return $this->_objectCache[$cacheId][$id1][$id2];
}
}
/**
* Check whether a given object is in the cache.
*
* @param string $cacheId
* @param int $id1
* @param int $id2
*
* @return bool
*/
public function isCached($cacheId, $id1, $id2 = null)
{
if (!isset($this->_objectCache[$cacheId])) {
return false;
}
$id1 = (int)$id1;
if (is_null($id2)) {
if (!isset($this->_objectCache[$cacheId][$id1])) {
return false;
}
if (is_array($this->_objectCache[$cacheId][$id1])) {
return isset($this->_objectCache[$cacheId][$id1]['complete']);
} else {
return true;
}
} else {
$id2 = (int)$id2;
return isset($this->_objectCache[$cacheId][$id1][$id2]);
}
}
//
// Private helper methods
//
/**
* Insert an object into the cache.
*
* @param object $object
* @param string $cacheId
* @param int $id1
* @param int $id2
*/
public function _insertInternally($object, $cacheId, $id1, $id2 = null)
{
if ($this->isCached($cacheId, $id1, $id2)) {
return;
}
if (!isset($this->_objectCache[$cacheId])) {
$this->_objectCache[$cacheId] = [];
}
$id1 = (int)$id1;
if (is_null($id2)) {
$this->_objectCache[$cacheId][$id1] = $object;
} else {
$id2 = (int)$id2;
if (!isset($this->_objectCache[$cacheId][$id1])) {
$this->_objectCache[$cacheId][$id1] = [];
}
$this->_objectCache[$cacheId][$id1][$id2] = $object;
}
}
}
if (!PKP_STRICT_MODE) {
class_alias('\APP\plugins\PubObjectCache', '\PubObjectCache');
}
+935
View File
@@ -0,0 +1,935 @@
<?php
/**
* @file classes/plugins/PubObjectsExportPlugin.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PubObjectsExportPlugin
*
* @ingroup plugins
*
* @brief Basis class for XML metadata export plugins
*/
namespace APP\plugins;
use APP\core\Application;
use APP\core\Request;
use APP\facades\Repo;
use APP\issue\Issue;
use APP\journal\Journal;
use APP\journal\JournalDAO;
use APP\notification\NotificationManager;
use APP\submission\Submission;
use APP\template\TemplateManager;
use PKP\core\EntityDAO;
use PKP\core\JSONMessage;
use PKP\db\DAO;
use PKP\db\DAORegistry;
use PKP\db\SchemaDAO;
use PKP\file\FileManager;
use PKP\filter\FilterDAO;
use PKP\galley\Galley;
use PKP\linkAction\LinkAction;
use PKP\linkAction\request\NullAction;
use PKP\notification\PKPNotification;
use PKP\plugins\Hook;
use PKP\plugins\importexport\PKPImportExportDeployment;
use PKP\plugins\ImportExportPlugin;
use PKP\plugins\PluginRegistry;
use PKP\submission\PKPSubmission;
use PKP\user\User;
// The statuses.
define('EXPORT_STATUS_ANY', '');
define('EXPORT_STATUS_NOT_DEPOSITED', 'notDeposited');
define('EXPORT_STATUS_MARKEDREGISTERED', 'markedRegistered');
define('EXPORT_STATUS_REGISTERED', 'registered');
// The actions.
define('EXPORT_ACTION_EXPORT', 'export');
define('EXPORT_ACTION_MARKREGISTERED', 'markRegistered');
define('EXPORT_ACTION_DEPOSIT', 'deposit');
// Configuration errors.
define('EXPORT_CONFIG_ERROR_SETTINGS', 0x02);
abstract class PubObjectsExportPlugin extends ImportExportPlugin
{
public const EXPORT_ACTION_EXPORT = 'export';
public const EXPORT_ACTION_MARKREGISTERED = 'markRegistered';
public const EXPORT_ACTION_DEPOSIT = 'deposit';
/** @var PubObjectCache */
public $_cache;
/**
* Get the plugin cache
*
* @return PubObjectCache
*/
public function getCache()
{
if (!$this->_cache instanceof PubObjectCache) {
// Instantiate the cache.
$this->_cache = new PubObjectCache();
}
return $this->_cache;
}
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
if (!parent::register($category, $path, $mainContextId)) {
return false;
}
if (Application::isUnderMaintenance()) {
return true;
}
$this->addLocaleData();
Hook::add('AcronPlugin::parseCronTab', [$this, 'callbackParseCronTab']);
foreach ($this->_getDAOs() as $dao) {
if ($dao instanceof SchemaDAO) {
Hook::add('Schema::get::' . $dao->schemaName, [$this, 'addToSchema']);
} elseif ($dao instanceof EntityDAO) {
Hook::add('Schema::get::' . $dao->schema, [$this, 'addToSchema']);
} else {
Hook::add(strtolower_codesafe(get_class($dao)) . '::getAdditionalFieldNames', [&$this, 'getAdditionalFieldNames']);
}
}
return true;
}
/**
* @copydoc Plugin::manage()
*/
public function manage($args, $request)
{
$user = $request->getUser();
$router = $request->getRouter();
$context = $router->getContext($request);
$form = $this->_instantiateSettingsForm($context);
$notificationManager = new NotificationManager();
switch ($request->getUserVar('verb')) {
case 'save':
$form->readInputData();
if ($form->validate()) {
$form->execute();
$notificationManager->createTrivialNotification($user->getId(), PKPNotification::NOTIFICATION_TYPE_SUCCESS);
return new JSONMessage(true);
} else {
return new JSONMessage(true, $form->fetch($request));
}
// no break
case 'index':
$form->initData();
return new JSONMessage(true, $form->fetch($request));
case 'statusMessage':
$statusMessage = $this->getStatusMessage($request);
if ($statusMessage) {
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'statusMessage' => htmlentities($statusMessage),
]);
return new JSONMessage(true, $templateMgr->fetch($this->getTemplateResource('statusMessage.tpl')));
}
}
return parent::manage($args, $request);
}
/**
* @copydoc ImportExportPlugin::display()
*/
public function display($args, $request)
{
parent::display($args, $request);
$context = $request->getContext();
switch (array_shift($args)) {
case 'index':
case '':
// Check for configuration errors:
$configurationErrors = [];
// missing plugin settings
$form = $this->_instantiateSettingsForm($context);
foreach ($form->getFormFields() as $fieldName => $fieldType) {
if ($form->isOptional($fieldName)) {
continue;
}
$pluginSetting = $this->getSetting($context->getId(), $fieldName);
if (empty($pluginSetting)) {
$configurationErrors[] = EXPORT_CONFIG_ERROR_SETTINGS;
break;
}
}
// Add link actions
$actions = $this->getExportActions($context);
$actionNames = array_intersect_key($this->getExportActionNames(), array_flip($actions));
$linkActions = [];
foreach ($actionNames as $action => $actionName) {
$linkActions[] = new LinkAction($action, new NullAction(), $actionName);
}
$templateMgr = TemplateManager::getManager($request);
$templateMgr->assign([
'plugin' => $this,
'actionNames' => $actionNames,
'configurationErrors' => $configurationErrors,
]);
break;
case 'exportSubmissions':
case 'exportIssues':
case 'exportRepresentations':
$this->prepareAndExportPubObjects($request, $context);
}
}
/**
* Gathers relevant pub objects and runs export action
*
* @param Request $request
* @param Journal $context
* @param array $args Optional args for passing in submissionIds from external API calls
*/
public function prepareAndExportPubObjects($request, $context, $args = [])
{
$selectedSubmissions = (array) $request->getUserVar('selectedSubmissions');
$selectedIssues = (array) $request->getUserVar('selectedIssues');
$selectedRepresentations = (array) $request->getUserVar('selectedRepresentations');
$tab = (string) $request->getUserVar('tab');
$noValidation = $request->getUserVar('validation') ? false : true;
if (!empty($args['submissionIds'])) {
$selectedSubmissions = (array) $args['submissionIds'];
}
if (!empty($args['issueIds'])) {
$selectedIssues = (array) $args['issueIds'];
}
if (empty($selectedSubmissions) && empty($selectedIssues) && empty($selectedRepresentations)) {
fatalError(__('plugins.importexport.common.error.noObjectsSelected'));
}
if (!empty($selectedSubmissions)) {
$objects = $this->getPublishedSubmissions($selectedSubmissions, $context);
$filter = $this->getSubmissionFilter();
$objectsFileNamePart = 'articles';
} elseif (!empty($selectedIssues)) {
$objects = $this->getPublishedIssues($selectedIssues, $context);
$filter = $this->getIssueFilter();
$objectsFileNamePart = 'issues';
} elseif (!empty($selectedRepresentations)) {
$objects = $this->getArticleGalleys($selectedRepresentations, $context);
$filter = $this->getRepresentationFilter();
$objectsFileNamePart = 'galleys';
}
// Execute export action
$this->executeExportAction($request, $objects, $filter, $tab, $objectsFileNamePart, $noValidation);
}
/**
* Execute export action.
*
* @param Request $request
* @param array $objects Array of objects to be exported
* @param string $filter Filter to use
* @param string $tab Tab to return to
* @param string $objectsFileNamePart Export file name part for this kind of objects
* @param bool $noValidation If set to true no XML validation will be done
* @param bool $shouldRedirect If set to true, will redirect to `$tab`. Should be true if executed within an ImportExportPlugin.
*/
public function executeExportAction($request, $objects, $filter, $tab, $objectsFileNamePart, $noValidation = null, $shouldRedirect = true)
{
$context = $request->getContext();
$path = ['plugin', $this->getName()];
if ($this->_checkForExportAction(EXPORT_ACTION_EXPORT)) {
assert($filter != null);
$onlyValidateExport = ($request->getUserVar('onlyValidateExport')) ? true : false;
if ($onlyValidateExport) {
$noValidation = false;
}
// Get the XML
$exportXml = $this->exportXML($objects, $filter, $context, $noValidation);
if ($onlyValidateExport) {
if (isset($exportXml)) {
$this->_sendNotification(
$request->getUser(),
'plugins.importexport.common.validation.success',
PKPNotification::NOTIFICATION_TYPE_SUCCESS
);
} else {
$this->_sendNotification(
$request->getUser(),
'plugins.importexport.common.validation.fail',
PKPNotification::NOTIFICATION_TYPE_ERROR
);
}
if ($shouldRedirect) {
$request->redirect(null, null, null, $path, null, $tab);
}
} else {
$fileManager = new FileManager();
$exportFileName = $this->getExportFileName($this->getExportPath(), $objectsFileNamePart, $context, '.xml');
$fileManager->writeFile($exportFileName, $exportXml);
$fileManager->downloadByPath($exportFileName);
$fileManager->deleteByPath($exportFileName);
}
} elseif ($this->_checkForExportAction(EXPORT_ACTION_DEPOSIT)) {
assert($filter != null);
// Get the XML
$exportXml = $this->exportXML($objects, $filter, $context, $noValidation);
// Write the XML to a file.
// export file name example: crossref-20160723-160036-articles-1.xml
$fileManager = new FileManager();
$exportFileName = $this->getExportFileName($this->getExportPath(), $objectsFileNamePart, $context, '.xml');
$fileManager->writeFile($exportFileName, $exportXml);
// Deposit the XML file.
$result = $this->depositXML($objects, $context, $exportFileName);
// send notifications
if ($result === true) {
$this->_sendNotification(
$request->getUser(),
$this->getDepositSuccessNotificationMessageKey(),
PKPNotification::NOTIFICATION_TYPE_SUCCESS
);
} else {
if (is_array($result)) {
foreach ($result as $error) {
assert(is_array($error) && count($error) >= 1);
$this->_sendNotification(
$request->getUser(),
$error[0],
PKPNotification::NOTIFICATION_TYPE_ERROR,
($error[1] ?? null)
);
}
}
}
// Remove all temporary files.
$fileManager->deleteByPath($exportFileName);
if ($shouldRedirect) {
// redirect back to the right tab
$request->redirect(null, null, null, $path, null, $tab);
}
} elseif ($this->_checkForExportAction(EXPORT_ACTION_MARKREGISTERED)) {
$this->markRegistered($context, $objects);
if ($shouldRedirect) {
// redirect back to the right tab
$request->redirect(null, null, null, $path, null, $tab);
}
} else {
$dispatcher = $request->getDispatcher();
$dispatcher->handle404();
}
}
/**
* Get the locale key used in the notification for
* the successful deposit.
*/
public function getDepositSuccessNotificationMessageKey()
{
return 'plugins.importexport.common.register.success';
}
/**
* Deposit XML document.
* This must be implemented in the subclasses, if the action is supported.
*
* @param mixed $objects Array of or single published submission, issue or galley
* @param Journal $context
* @param string $filename Export XML filename
*
* @return bool|array Whether the XML document has been registered
*/
abstract public function depositXML($objects, $context, $filename);
/**
* Get detailed message of the object status i.e. failure messages.
* Parameters needed have to be in the request object.
*
* @param Request $request
*
* @return string Preformatted text that will be displayed in a div element in the modal
*/
public function getStatusMessage($request)
{
return null;
}
/**
* Get the submission filter.
*
* @return string|null
*/
public function getSubmissionFilter()
{
return null;
}
/**
* Get the issue filter.
*
* @return string|null
*/
public function getIssueFilter()
{
return null;
}
/**
* Get the representation filter.
*
* @return string|null
*/
public function getRepresentationFilter()
{
return null;
}
/**
* Get status names for the filter search option.
*
* @return array (string status => string text)
*/
public function getStatusNames()
{
return [
EXPORT_STATUS_ANY => __('plugins.importexport.common.status.any'),
EXPORT_STATUS_NOT_DEPOSITED => __('plugins.importexport.common.status.notDeposited'),
EXPORT_STATUS_MARKEDREGISTERED => __('plugins.importexport.common.status.markedRegistered'),
EXPORT_STATUS_REGISTERED => __('plugins.importexport.common.status.registered'),
];
}
/**
* Get status actions for the display to the user,
* i.e. links to a web site with more information about the status.
*
* @param object $pubObject
*
* @return array (string status => link)
*/
public function getStatusActions($pubObject)
{
return [];
}
/**
* Get actions.
*
* @param Journal $context
*
* @return array
*/
public function getExportActions($context)
{
$actions = [EXPORT_ACTION_EXPORT, EXPORT_ACTION_MARKREGISTERED];
if ($this->getSetting($context->getId(), 'username') && $this->getSetting($context->getId(), 'password')) {
array_unshift($actions, EXPORT_ACTION_DEPOSIT);
}
return $actions;
}
/**
* Get action names.
*
* @return array (string action => string text)
*/
public function getExportActionNames()
{
return [
EXPORT_ACTION_DEPOSIT => __('plugins.importexport.common.action.register'),
EXPORT_ACTION_EXPORT => __('plugins.importexport.common.action.export'),
EXPORT_ACTION_MARKREGISTERED => __('plugins.importexport.common.action.markRegistered'),
];
}
/**
* Return the name of the plugin's deployment class.
*
* @return string
*/
abstract public function getExportDeploymentClassName();
/**
* Get the XML for selected objects.
*
* @param mixed $objects Array of or single published submission, issue or galley
* @param string $filter
* @param Journal $context
* @param bool $noValidation If set to true no XML validation will be done
* @param null|mixed $outputErrors
*
* @return string XML document.
*/
public function exportXML($objects, $filter, $context, $noValidation = null, &$outputErrors = null)
{
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
$exportFilters = $filterDao->getObjectsByGroup($filter);
assert(count($exportFilters) == 1); // Assert only a single serialization filter
$exportFilter = array_shift($exportFilters);
$exportDeployment = $this->_instantiateExportDeployment($context);
$exportFilter->setDeployment($exportDeployment);
if ($noValidation) {
$exportFilter->setNoValidation($noValidation);
}
libxml_use_internal_errors(true);
$exportXml = $exportFilter->execute($objects, true);
$xml = $exportXml->saveXml();
$errors = array_filter(libxml_get_errors(), function ($a) {
return $a->level == LIBXML_ERR_ERROR || $a->level == LIBXML_ERR_FATAL;
});
if (!empty($errors)) {
if ($outputErrors === null) {
$this->displayXMLValidationErrors($errors, $xml);
} else {
$outputErrors = $errors;
}
}
return $xml;
}
/**
* Mark selected submissions or issues as registered.
*
* @param Journal $context
* @param array $objects Array of published submissions, issues or galleys
*/
public function markRegistered($context, $objects)
{
foreach ($objects as $object) {
$object->setData($this->getDepositStatusSettingName(), EXPORT_STATUS_MARKEDREGISTERED);
$this->updateObject($object);
}
}
/**
* Update the given object.
*
* @param Issue|Submission|Galley $object
*/
protected function updateObject($object)
{
// Register a hook for the required additional
// object fields. We do this on a temporary
// basis as the hook adds a performance overhead
// and the field will "stealthily" survive even
// when the DAO does not know about it.
$dao = $object->getDAO();
$dao->update($object);
}
/**
* Add properties for this type of public identifier to the entity's list for
* storage in the database.
* This is used for non-SchemaDAO-backed entities only.
*
* @see PubObjectsExportPlugin::addToSchema()
*
* @param string $hookName
* @param DAO $dao
* @param array $additionalFields
*
* @return false
*/
public function getAdditionalFieldNames($hookName, $dao, &$additionalFields)
{
foreach ($this->_getObjectAdditionalSettings() as $fieldName) {
$additionalFields[] = $fieldName;
}
return false;
}
/**
* Add properties for this type of public identifier to the entity's list for
* storage in the database.
* This is used for SchemaDAO-backed entities only.
*
* @see PKPPubIdPlugin::getAdditionalFieldNames()
*
* @param string $hookName `Schema::get::publication`
* @param array $params
*/
public function addToSchema($hookName, $params)
{
$schema = & $params[0];
foreach ($this->_getObjectAdditionalSettings() as $fieldName) {
$schema->properties->{$fieldName} = (object) [
'type' => 'string',
'apiSummary' => true,
'validation' => ['nullable'],
];
}
return false;
}
/**
* Get a list of additional setting names that should be stored with the objects.
*
* @return array
*/
protected function _getObjectAdditionalSettings()
{
return [$this->getDepositStatusSettingName()];
}
/**
* @copydoc AcronPlugin::parseCronTab()
*/
public function callbackParseCronTab($hookName, $args)
{
$taskFilesPath = & $args[0];
$scheduledTasksPath = "{$this->getPluginPath()}/scheduledTasks.xml";
if (!file_exists($scheduledTasksPath)) {
return false;
}
$taskFilesPath[] = $scheduledTasksPath;
return false;
}
/**
* Retrieve all unregistered articles.
*
* @param Journal $context
*
* @return array
*/
public function getUnregisteredArticles($context)
{
// Retrieve all published submissions that have not yet been registered.
$articles = Repo::submission()->dao->getExportable(
$context->getId(),
null,
null,
null,
null,
$this->getDepositStatusSettingName(),
EXPORT_STATUS_NOT_DEPOSITED,
null
);
return $articles->toArray();
}
/**
* Check whether we are in test mode.
*
* @param Journal $context
*
* @return bool
*/
public function isTestMode($context)
{
return ($this->getSetting($context->getId(), 'testMode') == 1);
}
/**
* Get deposit status setting name.
*
* @return string
*/
public function getDepositStatusSettingName()
{
return $this->getPluginSettingsPrefix() . '::status';
}
/**
* @copydoc PKPImportExportPlugin::usage
*/
public function usage($scriptName)
{
echo __(
'plugins.importexport.' . $this->getPluginSettingsPrefix() . '.cliUsage',
[
'scriptName' => $scriptName,
'pluginName' => $this->getName()
]
) . "\n";
}
/**
* @copydoc PKPImportExportPlugin::executeCLI()
*/
public function executeCLI($scriptName, &$args)
{
$command = array_shift($args);
if (!in_array($command, ['export', 'register'])) {
$this->usage($scriptName);
return;
}
$outputFile = $command == 'export' ? array_shift($args) : null;
$contextPath = array_shift($args);
$objectType = array_shift($args);
$contextDao = DAORegistry::getDAO('JournalDAO'); /** @var JournalDAO $contextDao */
$context = $contextDao->getByPath($contextPath);
if (!$context) {
if ($contextPath != '') {
echo __('plugins.importexport.common.cliError') . "\n";
echo __('plugins.importexport.common.error.unknownContext', ['contextPath' => $contextPath]) . "\n\n";
}
$this->usage($scriptName);
return;
}
PluginRegistry::loadCategory('pubIds', true, $context->getId());
if ($outputFile) {
if ($this->isRelativePath($outputFile)) {
$outputFile = PWD . '/' . $outputFile;
}
$outputDir = dirname($outputFile);
if (!is_writable($outputDir) || (file_exists($outputFile) && !is_writable($outputFile))) {
echo __('plugins.importexport.common.cliError') . "\n";
echo __('plugins.importexport.common.export.error.outputFileNotWritable', ['param' => $outputFile]) . "\n\n";
$this->usage($scriptName);
return;
}
}
switch ($objectType) {
case 'articles':
$objects = $this->getPublishedSubmissions($args, $context);
$filter = $this->getSubmissionFilter();
$objectsFileNamePart = 'articles';
break;
case 'issues':
$objects = $this->getPublishedIssues($args, $context);
$filter = $this->getIssueFilter();
$objectsFileNamePart = 'issues';
break;
case 'galleys':
$objects = $this->getArticleGalleys($args, $context);
$filter = $this->getRepresentationFilter();
$objectsFileNamePart = 'galleys';
break;
default:
$this->usage($scriptName);
return;
}
if (empty($objects)) {
echo __('plugins.importexport.common.cliError') . "\n";
echo __('plugins.importexport.common.error.unknownObjects') . "\n\n";
$this->usage($scriptName);
return;
}
if (!$filter) {
$this->usage($scriptName);
return;
}
$this->executeCLICommand($scriptName, $command, $context, $outputFile, $objects, $filter, $objectsFileNamePart);
return;
}
/**
* Execute the CLI command
*
* @param string $scriptName The name of the command-line script (displayed as usage info)
* @param string $command (export or register)
* @param Journal $context
* @param string $outputFile Path to the file where the exported XML should be saved
* @param array $objects Objects to be exported or registered
* @param string $filter Filter to use
* @param string $objectsFileNamePart Export file name part for this kind of objects
*/
public function executeCLICommand($scriptName, $command, $context, $outputFile, $objects, $filter, $objectsFileNamePart)
{
$exportXml = $this->exportXML($objects, $filter, $context);
if ($command == 'export' && $outputFile) {
file_put_contents($outputFile, $exportXml);
}
if ($command == 'register') {
$fileManager = new FileManager();
$exportFileName = $this->getExportFileName($this->getExportPath(), $objectsFileNamePart, $context, '.xml');
$fileManager->writeFile($exportFileName, $exportXml);
$result = $this->depositXML($objects, $context, $exportFileName);
if ($result === true) {
echo __('plugins.importexport.common.register.success') . "\n";
} else {
echo __('plugins.importexport.common.cliError') . "\n";
if (is_array($result)) {
foreach ($result as $error) {
assert(is_array($error) && count($error) >= 1);
$errorMessage = __($error[0], ['param' => ($error[1] ?? null)]);
echo "*** {$errorMessage}\n";
}
echo "\n";
} else {
echo __('plugins.importexport.common.register.error.mdsError', ['param' => ' - ']) . "\n\n";
}
$this->usage($scriptName);
}
$fileManager->deleteByPath($exportFileName);
}
}
/**
* Get published submissions from submission IDs.
*
* @param array $submissionIds
* @param Journal $context
*
* @return array
*/
public function getPublishedSubmissions($submissionIds, $context)
{
$allSubmissionIds = Repo::submission()
->getCollector()
->filterByContextIds([$context->getId()])
->filterByStatus([PKPSubmission::STATUS_PUBLISHED])
->getIds()
->toArray();
$validSubmissionIds = array_intersect($allSubmissionIds, $submissionIds);
return array_map(function ($submissionId) {
return Repo::submission()->get($submissionId);
}, $validSubmissionIds);
}
/**
* Get published issues from issue IDs.
*
* @param array $issueIds
* @param Journal $context
*
* @return array
*/
public function getPublishedIssues($issueIds, $context)
{
return Repo::issue()
->getCollector()
->filterByContextIds([$context->getId()])
->filterByIssueIds($issueIds)
->filterByPublished(true)
->getMany()
->toArray();
}
/**
* Get article galleys from gallley IDs.
*
* @param array $galleyIds
* @param Journal $context
*
* @return array
*/
public function getArticleGalleys($galleyIds, $context)
{
$allGalleyIds = Repo::galley()
->getCollector()
->filterByContextIds([$context->getId()])
->getIds()
->toArray();
$validGalleyIds = array_intersect($allGalleyIds, $galleyIds);
return array_map(function ($galleyId) {
return Repo::submission()->get($galleyId);
}, $validGalleyIds);
}
/**
* Add a notification.
*
* @param User $user
* @param string $message An i18n key.
* @param int $notificationType One of the NOTIFICATION_TYPE_* constants.
* @param string $param An additional parameter for the message.
*/
public function _sendNotification($user, $message, $notificationType, $param = null)
{
static $notificationManager = null;
$notificationManager ??= new NotificationManager();
$params = is_null($param) ? [] : ['param' => $param];
$notificationManager->createTrivialNotification(
$user->getId(),
$notificationType,
['contents' => __($message, $params)]
);
}
/**
* Instantiate the export deployment.
*
* @param Journal $context
*
* @return PKPImportExportDeployment
*/
public function _instantiateExportDeployment($context)
{
$exportDeploymentClassName = $this->getExportDeploymentClassName();
$exportDeployment = new $exportDeploymentClassName($context, $this);
return $exportDeployment;
}
/**
* Instantiate the settings form.
*
* @param Journal $context
*
* @return \PKP\form\Form
*/
public function _instantiateSettingsForm($context)
{
$settingsFormClassName = $this->getSettingsFormClassName();
return new $settingsFormClassName($this, $context->getId());
}
/**
* Get the DAOs for objects that need to be augmented with additional settings.
*
* @return array
*/
protected function _getDAOs()
{
return [
Repo::publication()->dao,
Repo::submission()->dao,
Application::getRepresentationDAO(),
Repo::issue()->dao,
Repo::submissionFile()->dao
];
}
/**
* Checks for export action type as set user var and as action passed from API call
*
* @param string $exportAction Action to check for
*
* @return bool
*/
protected function _checkForExportAction($exportAction)
{
$request = $this->getRequest();
if ($request->getUserVar($exportAction)) {
return true;
} elseif ($request->getUserVar('action') == $exportAction) {
return true;
}
return false;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\APP\plugins\PubObjectsExportPlugin', '\PubObjectsExportPlugin');
}