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
@@ -0,0 +1,114 @@
<?php
/**
* @file plugins/importexport/native/PKPNativeImportExportCLIDeployment.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 PKPNativeImportExportCLIDeployment
*
* @ingroup plugins_importexport_native
*
* @brief CLI Deployment for Import/Export operations
*/
namespace PKP\plugins\importexport\native;
class PKPNativeImportExportCLIDeployment
{
/** @var string The import/export script name */
private $scriptName;
/** @var array The import/export arguments */
public $args;
/** @var array The import/export additional directives */
public $opts;
/** @var string The import/export command */
public $command;
/** @var string The import/export xml file name */
public $xmlFile;
/** @var string The import/export operation context path */
public $contextPath;
/** @var string The import/export operation user name */
public $userName;
/** @var string The export entity */
public $exportEntity;
/**
* Constructor
*/
public function __construct($scriptName, $args)
{
$this->scriptName = $scriptName;
$this->args = $args;
$this->parseCLI();
}
/**
* Parse CLI Command to populate the Deployment's variables
*/
public function parseCLI()
{
$this->opts = $this->parseOpts($this->args, ['no-embed', 'use-file-urls']);
$this->command = array_shift($this->args);
$this->xmlFile = array_shift($this->args);
$this->contextPath = array_shift($this->args);
switch ($this->command) {
case 'import':
$this->userName = array_shift($this->args);
break;
case 'export':
$this->exportEntity = array_shift($this->args);
break;
case 'usage':
break;
default:
throw new \BadMethodCallException(__('plugins.importexport.common.error.unknownCommand', ['command' => $this->command]));
}
}
/**
* Pull out getopt style long options.
*
* @param array $args
* @param array $optCodes
*/
public function parseOpts(&$args, $optCodes)
{
$newArgs = [];
$opts = [];
$sticky = null;
foreach ($args as $arg) {
if ($sticky) {
$opts[$sticky] = $arg;
$sticky = null;
continue;
}
if (substr($arg, 0, 2) != '--') {
$newArgs[] = $arg;
continue;
}
$opt = substr($arg, 2);
if (in_array($opt, $optCodes)) {
$opts[$opt] = true;
continue;
}
if (in_array($opt . ':', $optCodes)) {
$sticky = $opt;
continue;
}
}
$args = $newArgs;
return $opts;
}
}
@@ -0,0 +1,137 @@
<?php
/**
* @file plugins/importexport/native/PKPNativeImportExportCLIToolKit.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 PKPNativeImportExportCLIToolKit
*
* @ingroup plugins_importexport_native
*
* @brief CLI Toolkit class
*/
namespace PKP\plugins\importexport\native;
use Colors\Color;
class PKPNativeImportExportCLIToolKit
{
/**
* Echo a CLI Error Message
*
* @param string $errorMessage
*/
public function echoCLIError($errorMessage)
{
$c = new Color();
echo $c(__('plugins.importexport.common.cliError'))->white()->bold()->highlight('red') . PHP_EOL;
echo $c($errorMessage)->red()->bold() . PHP_EOL;
}
/**
* Echo export results
*
* @param PKPNativeImportExportDeployment $deployment
* @param string $xmlFile
*/
public function getCLIExportResult($deployment, $xmlFile)
{
$c = new Color();
$result = $deployment->processResult;
$foundErrors = $deployment->isProcessFailed();
if (!$foundErrors) {
$xml = $result->saveXml();
file_put_contents($xmlFile, $xml);
echo $c(__('plugins.importexport.native.export.completed'))->green()->bold() . PHP_EOL . PHP_EOL;
} else {
echo $c(__('plugins.importexport.native.processFailed'))->red()->bold() . PHP_EOL . PHP_EOL;
}
}
/**
* Echo import results
*
* @param PKPNativeImportExportDeployment $deployment
*/
public function getCLIImportResult($deployment)
{
$c = new Color();
$result = $deployment->processResult;
$foundErrors = $deployment->isProcessFailed();
$importedRootObjects = $deployment->getImportedRootEntitiesWithNames();
if (!$foundErrors) {
echo $c(__('plugins.importexport.native.importComplete'))->green()->bold() . PHP_EOL . PHP_EOL;
foreach ($importedRootObjects as $contentItemName => $contentItemArrays) {
echo $c($contentItemName)->white()->bold()->highlight('black') . PHP_EOL;
foreach ($contentItemArrays as $contentItemArray) {
foreach ($contentItemArray as $contentItem) {
echo $c('-' . $contentItem->getUIDisplayString())->white()->bold() . PHP_EOL;
}
}
}
} else {
echo $c(__('plugins.importexport.native.processFailed'))->red()->bold() . PHP_EOL . PHP_EOL;
}
}
/**
* Echo import/export possible warnings and errors
*
* @param PKPNativeImportExportDeployment $deployment
*/
public function getCLIProblems($deployment)
{
$result = $deployment->processResult;
$problems = $deployment->getWarningsAndErrors();
$foundErrors = $deployment->isProcessFailed();
$warnings = [];
if (array_key_exists('warnings', $problems)) {
$warnings = $problems['warnings'];
}
$errors = [];
if (array_key_exists('errors', $problems)) {
$errors = $problems['errors'];
}
// Are there any import warnings? Display them.
$this->displayCLIIssues($warnings, __('plugins.importexport.common.warningsEncountered'));
$this->displayCLIIssues($errors, __('plugins.importexport.common.errorsOccured'));
}
/**
* Echo import/export possible warnings and errors
*
* @param array $relatedIssues
* @param string $title
*/
public function displayCLIIssues($relatedIssues, $title)
{
$c = new Color();
if (count($relatedIssues) > 0) {
echo $c($title)->black()->bold()->highlight('light_gray') . PHP_EOL;
$i = 0;
foreach ($relatedIssues as $relatedTypeName => $allRelatedTypes) {
foreach ($allRelatedTypes as $thisTypeId => $thisTypeIds) {
if (count($thisTypeIds) > 0) {
echo ++$i . '.' . $relatedTypeName . PHP_EOL;
foreach ($thisTypeIds as $idRelatedItems) {
foreach ($idRelatedItems as $relatedItemMessage) {
echo '- ' . $relatedItemMessage . PHP_EOL;
}
}
}
}
}
}
}
}
@@ -0,0 +1,90 @@
<?php
/**
* @file plugins/importexport/native/PKPNativeImportExportDeployment.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPNativeImportExportDeployment
*
* @ingroup plugins_importexport_native
*
* @brief Base class configuring the native import/export process to an
* application's specifics.
*/
namespace PKP\plugins\importexport\native;
use PKP\plugins\importexport\PKPImportExportDeployment;
use PKP\submissionFile\SubmissionFile;
class PKPNativeImportExportDeployment extends PKPImportExportDeployment
{
//
// Deployment items for subclasses to override
//
/**
* Get the submission node name
*
* @return string
*/
public function getSubmissionNodeName()
{
return 'submission';
}
/**
* Get the submissions node name
*
* @return string
*/
public function getSubmissionsNodeName()
{
return 'submissions';
}
/**
* Get the namespace URN
*
* @return string
*/
public function getNamespace()
{
return 'http://pkp.sfu.ca';
}
/**
* Get the schema filename.
*
* @return string
*/
public function getSchemaFilename()
{
return 'pkp-native.xsd';
}
/**
* Get the mapping between stage names in XML and their numeric consts
*
* @return array
*/
public function getStageNameStageIdMapping()
{
return [
'submission' => SubmissionFile::SUBMISSION_FILE_SUBMISSION,
'note' => SubmissionFile::SUBMISSION_FILE_NOTE,
'review_file' => SubmissionFile::SUBMISSION_FILE_REVIEW_FILE,
'review_attachment' => SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT,
'final' => SubmissionFile::SUBMISSION_FILE_FINAL,
'copyedit' => SubmissionFile::SUBMISSION_FILE_COPYEDIT,
'proof' => SubmissionFile::SUBMISSION_FILE_PROOF,
'production_ready' => SubmissionFile::SUBMISSION_FILE_PRODUCTION_READY,
'attachment' => SubmissionFile::SUBMISSION_FILE_ATTACHMENT,
'review_revision' => SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION,
'dependent' => SubmissionFile::SUBMISSION_FILE_DEPENDENT,
'query' => SubmissionFile::SUBMISSION_FILE_QUERY,
];
}
}
@@ -0,0 +1,371 @@
<?php
/**
* @file plugins/importexport/native/PKPNativeImportExportPlugin.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 PKPNativeImportExportPlugin
*
* @ingroup plugins_importexport_native
*
* @brief Native XML import/export plugin
*/
namespace PKP\plugins\importexport\native;
use APP\core\Application;
use APP\template\TemplateManager;
use BadMethodCallException;
use Exception;
use PKP\core\JSONMessage;
use PKP\file\TemporaryFileManager;
use PKP\plugins\ImportExportPlugin;
use PKP\plugins\PluginRegistry;
abstract class PKPNativeImportExportPlugin extends ImportExportPlugin
{
/** @var PKPNativeImportExportCLIDeployment CLI Deployment for import/export operations */
protected $cliDeployment = null;
/** @var string Display operation result */
protected $result = null;
/** @var bool Indication that the parent code has managed the display operation */
protected $isResultManaged = false;
/** @var PKPNativeImportExportCLIToolKit The helper for CLI import/export operations */
protected $cliToolkit;
/** @var string Operation type for display method */
protected $opType;
/**
* Constructor
*/
public function __construct()
{
$this->cliToolkit = new PKPNativeImportExportCLIToolKit();
}
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
$success = parent::register($category, $path, $mainContextId);
if (Application::isUnderMaintenance()) {
return $success;
}
if ($success && $this->getEnabled()) {
$this->addLocaleData();
}
return $success;
}
/**
* Get the name of this plugin. The name must be unique within
* its category.
*
* @return string name of plugin
*/
public function getName()
{
return 'NativeImportExportPlugin';
}
/**
* Get the display name.
*
* @return string
*/
public function getDisplayName()
{
return __('plugins.importexport.native.displayName');
}
/**
* Get the display description.
*
* @return string
*/
public function getDescription()
{
return __('plugins.importexport.native.description');
}
/**
* @copydoc ImportExportPlugin::getPluginSettingsPrefix()
*/
public function getPluginSettingsPrefix()
{
return 'native';
}
/**
* @see ImportExportPlugin::display()
*/
public function display($args, $request)
{
$templateMgr = TemplateManager::getManager($request);
parent::display($args, $request);
$context = $request->getContext();
$user = $request->getUser();
$deployment = $this->getAppSpecificDeployment($context, $user);
$this->setDeployment($deployment);
$this->opType = array_shift($args);
switch ($this->opType) {
case 'index':
case '':
$apiUrl = $request->getDispatcher()->url($request, Application::ROUTE_API, $context->getPath(), 'submissions');
$submissionsListPanel = new \APP\components\listPanels\SubmissionsListPanel(
'submissions',
__('common.publications'),
[
'apiUrl' => $apiUrl,
'count' => 100,
'getParams' => new \stdClass(),
'lazyLoad' => true,
]
);
$submissionsConfig = $submissionsListPanel->getConfig();
$submissionsConfig['addUrl'] = '';
$submissionsConfig['filters'] = array_slice($submissionsConfig['filters'], 1);
$templateMgr->setState([
'components' => [
'submissions' => $submissionsConfig,
],
]);
$templateMgr->assign([
'pageComponent' => 'ImportExportPage',
]);
$templateMgr->display($this->getTemplateResource('index.tpl'));
$this->isResultManaged = true;
break;
case 'uploadImportXML':
$temporaryFileManager = new TemporaryFileManager();
$temporaryFile = $temporaryFileManager->handleUpload('uploadedFile', $user->getId());
if ($temporaryFile) {
$json = new JSONMessage(true);
$json->setAdditionalAttributes([
'temporaryFileId' => $temporaryFile->getId()
]);
} else {
$json = new JSONMessage(false, __('common.uploadFailed'));
}
header('Content-Type: application/json');
$this->result = $json->getString();
$this->isResultManaged = true;
break;
case 'importBounce':
$tempFileId = $request->getUserVar('temporaryFileId');
if (empty($tempFileId)) {
$this->result = new JSONMessage(false);
$this->isResultManaged = true;
break;
}
$tab = $this->getBounceTab(
$request,
__('plugins.importexport.native.results'),
'import',
['temporaryFileId' => $tempFileId]
);
$this->result = $tab;
$this->isResultManaged = true;
break;
case 'exportSubmissionsBounce':
$tab = $this->getBounceTab(
$request,
__('plugins.importexport.native.export.submissions.results'),
'exportSubmissions',
['selectedSubmissions' => $request->getUserVar('selectedSubmissions')]
);
$this->result = $tab;
$this->isResultManaged = true;
break;
case 'import':
if (!$request->checkCSRF()) {
throw new Exception('CSRF mismatch!');
}
$temporaryFilePath = $this->getImportedFilePath($request->getUserVar('temporaryFileId'), $user);
[$filter, $xmlString] = $this->getImportFilter($temporaryFilePath);
$result = $this->getImportTemplateResult($filter, $xmlString, $this->getDeployment(), $templateMgr);
$this->result = $result;
$this->isResultManaged = true;
break;
case 'exportSubmissions':
$submissionIds = (array) $request->getUserVar('selectedSubmissions');
$this->getExportSubmissionsDeployment($submissionIds, $this->_childDeployment);
$result = $this->getExportTemplateResult($this->getDeployment(), $templateMgr, 'submissions');
$this->result = $result;
$this->isResultManaged = true;
break;
case 'downloadExportFile':
$exportedFileDatePart = $request->getUserVar('exportedFileDatePart');
$exportedFileContentNamePart = $request->getUserVar('exportedFileContentNamePart');
$downloadSuccess = $this->downloadExportedFile($exportedFileContentNamePart, $exportedFileDatePart, $this->getDeployment());
if (!$downloadSuccess) {
$dispatcher = $request->getDispatcher();
$dispatcher->handle404();
}
$this->isResultManaged = true;
break;
}
}
/**
* Get the XML for a set of submissions.
*
* @param array $submissionIds Array of submission IDs
* @param \PKP\context\Context $context
* @param \PKP\user\User|null $user
* @param array $opts
*
* @return string XML contents representing the supplied submission IDs.
*/
public function exportSubmissions($submissionIds, $context, $user, $opts = [])
{
$appSpecificDeployment = $this->getAppSpecificDeployment($context, null);
$this->setDeployment($appSpecificDeployment);
$this->getExportSubmissionsDeployment($submissionIds, $appSpecificDeployment, $opts);
return $this->exportResultXML($appSpecificDeployment);
}
/**
* @copydoc PKPImportExportPlugin::usage
*/
public function usage($scriptName)
{
echo __('plugins.importexport.native.cliUsage', [
'scriptName' => $scriptName,
'pluginName' => $this->getName()
]) . "\n";
}
/**
* @see PKPImportExportPlugin::executeCLI()
*/
public function executeCLI($scriptName, &$args)
{
try {
$cliDeployment = new PKPNativeImportExportCLIDeployment($scriptName, $args);
} catch (BadMethodCallException $ex) {
$this->cliToolkit->echoCLIError($ex->getMessage());
$this->usage($scriptName);
return true;
}
$this->cliDeployment = $cliDeployment;
$contextDao = Application::getContextDAO();
$contextPath = $cliDeployment->contextPath;
$context = $contextDao->getByPath($contextPath);
if (!$context) {
if ($contextPath != '') {
$this->cliToolkit->echoCLIError(__('plugins.importexport.common.error.unknownContext', ['contextPath' => $contextPath]));
}
$this->usage($scriptName);
return true;
}
PluginRegistry::loadCategory('pubIds', true, $context->getId());
$xmlFile = $cliDeployment->xmlFile;
if ($xmlFile && $this->isRelativePath($xmlFile)) {
$xmlFile = PWD . '/' . $xmlFile;
}
$appSpecificDeployment = $this->getAppSpecificDeployment($context, null);
$this->setDeployment($appSpecificDeployment);
switch ($cliDeployment->command) {
case 'import':
$user = Application::get()->getRequest()->getUser();
if (!$user) {
$this->cliToolkit->echoCLIError(__('plugins.importexport.native.error.unknownUser'));
$this->usage($scriptName);
return true;
}
if (!file_exists($xmlFile)) {
$this->cliToolkit->echoCLIError(__('plugins.importexport.common.export.error.inputFileNotReadable', ['param' => $xmlFile]));
$this->usage($scriptName);
return true;
}
[$filter, $xmlString] = $this->getImportFilter($xmlFile);
$deployment = $this->getDeployment(); /** @var PKPNativeImportExportDeployment $deployment */
$deployment->setUser($user);
$deployment->setImportPath(dirname($xmlFile));
$deployment->import($filter, $xmlString);
$this->cliToolkit->getCLIImportResult($deployment);
$this->cliToolkit->getCLIProblems($deployment);
return true;
case 'export':
$deployment = $this->getDeployment(); /** @var PKPNativeImportExportDeployment $deployment */
$outputDir = dirname($xmlFile);
if (!is_writable($outputDir) || (file_exists($xmlFile) && !is_writable($xmlFile))) {
$this->cliToolkit->echoCLIError(__('plugins.importexport.common.export.error.outputFileNotWritable', ['param' => $xmlFile]));
$this->usage($scriptName);
return true;
}
if ($cliDeployment->xmlFile != '') {
switch ($cliDeployment->exportEntity) {
case $deployment->getSubmissionNodeName():
case $deployment->getSubmissionsNodeName():
$this->getExportSubmissionsDeployment(
$cliDeployment->args,
$deployment,
$cliDeployment->opts
);
$this->cliToolkit->getCLIExportResult($deployment, $xmlFile);
$this->cliToolkit->getCLIProblems($deployment);
return true;
default:
return false;
}
}
return true;
default:
$this->usage($scriptName);
return true;
}
}
}
@@ -0,0 +1,136 @@
<?php
/**
* @file plugins/importexport/native/filter/NativeExportFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class NativeExportFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a DataObject to a Native XML document
*/
namespace PKP\plugins\importexport\native\filter;
use PKP\plugins\importexport\PKPImportExportFilter;
use PKP\xslt\XMLTypeDescription;
class NativeExportFilter extends PKPImportExportFilter
{
/** @var bool If set to true no validation (e.g. XML validation) will be done */
public $_noValidation = null;
public $opts = [];
/**
* Set no validation option
*
* @param bool $noValidation
*/
public function setNoValidation($noValidation)
{
$this->_noValidation = $noValidation;
}
/**
* Get no validation option
*
* @return bool true|null
*/
public function getNoValidation()
{
return $this->_noValidation;
}
//
// Public methods
//
/**
* @copydoc Filter::supports()
*/
public function supports(&$input, &$output)
{
// Validate input
$inputType = & $this->getInputType();
$validInput = $inputType->isCompatible($input);
// If output is null then we're done
if (is_null($output)) {
return $validInput;
}
// Validate output
$outputType = & $this->getOutputType();
if ($outputType instanceof XMLTypeDescription && $this->getNoValidation()) {
$outputType->setValidationStrategy(XMLTypeDescription::XML_TYPE_DESCRIPTION_VALIDATE_NONE);
}
$validOutput = $outputType->isCompatible($output);
return $validInput && $validOutput;
}
//
// Helper functions
//
/**
* Create a set of child nodes of parentNode containing the
* localeKey => value data representing translated content.
*
* @param \DOMDocument $doc
* @param \DOMNode $parentNode
* @param string $name Node name
* @param array $values Array of locale key => value mappings
*/
public function createLocalizedNodes($doc, $parentNode, $name, $values)
{
$deployment = $this->getDeployment();
foreach (is_array($values) ? $values : [] as $locale => $value) {
if ($value === '') { // Skip empty values
continue;
}
$node = $doc->createElementNS($deployment->getNamespace(), $name, htmlspecialchars($value, ENT_COMPAT));
$node->setAttribute('locale', $locale);
$parentNode->appendChild($node);
}
}
/**
* Create an optional node with a name and value.
*
* @param \DOMDocument $doc
* @param \DOMElement $parentNode
* @param string $name
* @param string|null $value
*
* @return ?\DOMElement
*/
public function createOptionalNode($doc, $parentNode, $name, $value)
{
if ($value === '' || $value === null) {
return null;
}
$deployment = $this->getDeployment();
$parentNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), $name, htmlspecialchars($value, ENT_COMPAT, 'UTF-8')));
return $node;
}
/**
* Set xml filtering opts
*
* @param array $opts
*/
public function setOpts($opts)
{
$this->opts = $opts;
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\importexport\native\filter\NativeExportFilter', '\NativeExportFilter');
}
@@ -0,0 +1,137 @@
<?php
/**
* @file plugins/importexport/native/filter/NativeImportFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class NativeImportFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a Native XML document to a DataObject
*/
namespace PKP\plugins\importexport\native\filter;
use Exception;
use PKP\plugins\importexport\PKPImportExportFilter;
class NativeImportFilter extends PKPImportExportFilter
{
//
// Implement template methods from Filter
//
/**
* @see Filter::process()
*
* @param \DOMDocument|string $document
*
* @return array Array of imported documents
*/
public function &process(&$document)
{
// If necessary, convert $document to a DOMDocument.
if (is_string($document)) {
$xmlString = $document;
$document = new \DOMDocument('1.0', 'utf-8');
$document->loadXml($xmlString);
}
assert($document instanceof \DOMDocument);
$importedObjects = [];
if ($document->documentElement->tagName == $this->getPluralElementName()) {
// Multiple element (plural) import
for ($n = $document->documentElement->firstChild; $n !== null; $n = $n->nextSibling) {
if (!($n instanceof \DOMElement)) {
continue;
}
$object = $this->handleElement($n);
if ($object) {
$importedObjects[] = $object;
}
}
} else {
assert($document->documentElement->tagName == $this->getSingularElementName());
// Single element (singular) import
$object = $this->handleElement($document->documentElement);
if ($object) {
$importedObjects[] = $object;
}
}
return $importedObjects;
}
/**
* Return the plural element name
*
* @return string
*/
public function getPluralElementName()
{
assert(false); // Must be overridden by subclasses
}
/**
* Get the singular element name
*
* @return string
*/
public function getSingularElementName()
{
assert(false); // Must be overridden by subclasses
}
/**
* Handle a singular element import
*
* @param \DOMElement $node
* @return object
*/
public function handleElement($node)
{
assert(false); // Must be overridden by subclasses
}
/**
* Parse a localized element
*
* @param \DOMElement $element
*
* @return array Array("locale_KEY", "Localized Text")
*/
public function parseLocalizedContent($element)
{
return [$element->getAttribute('locale'), $element->textContent];
}
/**
* Import node to a given parent node
*
* @param \DOMElement $n The parent node
* @param string $filter The filter to execute it's import function
*/
public function importWithXMLNode($n, $filter = null)
{
$doc = new \DOMDocument('1.0', 'utf-8');
$doc->appendChild($doc->importNode($n, true));
$importFilter = null;
if ($filter) {
$importFilter = PKPImportExportFilter::getFilter($filter, $this->getDeployment());
} elseif (method_exists($this, 'getImportFilter')) {
$importFilter = $this->getImportFilter($n->tagName);
} else {
throw new Exception(__('filter.import.error.couldNotImportNode'));
}
return $importFilter->execute($doc);
}
}
if (!PKP_STRICT_MODE) {
class_alias('\PKP\plugins\importexport\native\filter\NativeImportFilter', '\NativeImportFilter');
}
@@ -0,0 +1,206 @@
<?php
/**
* @file plugins/importexport/native/filter/NativeXmlPKPAuthorFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class NativeXmlPKPAuthorFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a Native XML document to a set of authors
*/
namespace PKP\plugins\importexport\native\filter;
use APP\core\Application;
use APP\facades\Repo;
use APP\publication\Publication;
use Exception;
use PKP\author\Author;
use PKP\facades\Locale;
use PKP\filter\FilterGroup;
class NativeXmlPKPAuthorFilter extends NativeImportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML author import');
parent::__construct($filterGroup);
}
//
// Implement template methods from NativeImportFilter
//
/**
* Return the plural element name
*
* @return string
*/
public function getPluralElementName()
{
return 'authors';
}
/**
* Get the singular element name
*
* @return string
*/
public function getSingularElementName()
{
return 'author';
}
/**
* Handle an author element
*
* @param \DOMElement $node
*
* @return \PKP\author\Author
*/
public function handleElement($node)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
$publication = $deployment->getPublication();
assert($publication instanceof Publication);
// Create the data object
$author = Repo::author()->newDataObject();
$author->setData('publicationId', $publication->getId());
if ($node->getAttribute('primary_contact')) {
$author->setPrimaryContact(true);
}
if ($node->getAttribute('include_in_browse')) {
$author->setIncludeInBrowse(true);
}
if ($node->getAttribute('seq')) {
$author->setSequence($node->getAttribute('seq'));
}
// Handle metadata in subelements
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof \DOMElement) {
switch ($n->tagName) {
case 'givenname':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $publication->getData('locale');
}
$author->setGivenName($n->textContent, $locale);
break;
case 'familyname':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $publication->getData('locale');
}
$author->setFamilyName($n->textContent, $locale);
break;
case 'affiliation':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $publication->getData('locale');
}
$author->setAffiliation($n->textContent, $locale);
break;
case 'country': $author->setCountry($n->textContent);
break;
case 'email': $author->setEmail($n->textContent);
break;
case 'url': $author->setUrl($n->textContent);
break;
case 'orcid': $author->setOrcid($n->textContent);
break;
case 'biography':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $publication->getData('locale');
}
$author->setBiography($n->textContent, $locale);
break;
}
}
}
$authorGivenName = $author->getFullName(true, false, $publication->getData('locale'));
if (empty($authorGivenName)) {
$deployment->addError(
Application::ASSOC_TYPE_SUBMISSION,
$publication->getId(),
__('plugins.importexport.common.error.missingGivenName', [
'authorName' => $author->getLocalizedGivenName(),
'localeName' => Locale::getMetadata($publication->getData('locale'))->getDisplayName()
])
);
}
// Identify the user group by name
$userGroupName = $node->getAttribute('user_group_ref');
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$context->getId()])
->getMany();
foreach ($userGroups as $userGroup) {
if (in_array($userGroupName, $userGroup->getName(null))) {
// Found a candidate; stash it.
$author->setUserGroupId($userGroup->getId());
break;
}
}
if (!$author->getUserGroupId()) {
$authorFullName = $author->getFullName(true, false, $publication->getData('locale'));
$deployment->addError(Application::ASSOC_TYPE_AUTHOR, $publication->getId(), __('plugins.importexport.common.error.unknownUserGroup', ['authorName' => $authorFullName, 'userGroupName' => $userGroupName]));
throw new Exception(__('plugins.importexport.author.exportFailed'));
}
$authorId = Repo::author()->add($author);
$author->setId($authorId);
$importAuthorId = $node->getAttribute('id');
$deployment->setAuthorDBId($importAuthorId, $authorId);
if ($node->getAttribute('id') == $publication->getData('primaryContactId')) {
$publication->setData('primaryContactId', $author->getId());
}
return $author;
}
/**
* Parse an identifier node
*
* @param \DOMElement $element
* @param \PKP\author\Author $author
*/
public function parseIdentifier($element, $author)
{
$deployment = $this->getDeployment();
$publication = $deployment->getPublication();
$advice = $element->getAttribute('advice');
switch ($element->getAttribute('type')) {
case 'internal':
// "update" advice not supported yet.
assert(!$advice || $advice == 'ignore');
if ($element->textContent == $publication->getData('primaryContactId')) {
$publication->setData('primaryContactId', $author->getId());
}
break;
}
}
}
@@ -0,0 +1,347 @@
<?php
/**
* @file plugins/importexport/native/filter/NativeXmlPKPPublicationFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class NativeXmlPKPPublicationFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a Native XML document to a set of publications
*/
namespace PKP\plugins\importexport\native\filter;
use APP\core\Application;
use APP\facades\Repo;
use APP\publication\Publication;
use PKP\citation\CitationDAO;
use PKP\db\DAORegistry;
use PKP\filter\Filter;
use PKP\filter\FilterGroup;
use PKP\plugins\PluginRegistry;
class NativeXmlPKPPublicationFilter extends NativeImportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML publication import');
parent::__construct($filterGroup);
}
//
// Implement template methods from NativeImportFilter
//
/**
* Return the plural element name
*
* @return string
*/
public function getPluralElementName()
{
return 'publications';
}
/**
* Get the singular element name
*
* @return string
*/
public function getSingularElementName()
{
return 'publication';
}
/**
* Handle a singular element import.
*
* @param \DOMElement $node
*/
public function handleElement($node)
{
$deployment = $this->getDeployment();
$submission = $deployment->getSubmission();
$publication = Repo::publication()->newDataObject();
$publication->setData('submissionId', $submission->getId());
$publication->stampModified();
$publication = $this->populateObject($publication, $node);
$publication->setData('version', $node->getAttribute('version'));
$publication->setData('seq', $node->getAttribute('seq'));
$publication->setData('accessStatus', $node->getAttribute('access_status'));
$publication->setData('status', $node->getAttribute('status'));
$publication->setData('urlPath', strlen($urlPath = (string) $node->getAttribute('url_path')) ? $urlPath : null);
$publicationId = Repo::publication()->dao->insert($publication);
$publication = Repo::publication()->get($publicationId);
// Non-persisted temporary ID, will be updated and stored once the authors get parsed
$publication->setData('primaryContactId', $node->getAttribute('primary_contact_id'));
$deployment->setPublication($publication);
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof \DOMElement) {
$this->handleChildElement($n, $publication);
}
}
Repo::publication()->dao->update($publication);
return Repo::publication()->get($publication->getId());
}
/**
* Populate the entity object from the node
*
* @param Publication $publication
* @param \DOMElement $node
*
* @return Publication
*/
public function populateObject($publication, $node)
{
if ($datePublished = $node->getAttribute('date_published')) {
$publication->setData('datePublished', $datePublished);
}
return $publication;
}
/**
* Handle an element whose parent is the publication element.
*
* @param \DOMElement $n
* @param Publication $publication
*/
public function handleChildElement($n, $publication)
{
$setterMappings = $this->_getLocalizedPublicationFields();
$controlledVocabulariesMappings = $this->_getControlledVocabulariesMappings();
[$locale, $value] = $this->parseLocalizedContent($n);
if (empty($locale)) {
$locale = $publication->getData('locale');
}
if (in_array($n->tagName, $setterMappings)) {
$publication->setData($n->tagName, $value, $locale);
} elseif (isset($controlledVocabulariesMappings[$n->tagName])) {
$controlledVocabulariesDao = $submissionKeywordDao = DAORegistry::getDAO($controlledVocabulariesMappings[$n->tagName][0]);
$insertFunction = $controlledVocabulariesMappings[$n->tagName][1];
$controlledVocabulary = [];
for ($nc = $n->firstChild; $nc !== null; $nc = $nc->nextSibling) {
if ($nc instanceof \DOMElement) {
$controlledVocabulary[] = $nc->textContent;
}
}
$controlledVocabulariesValues = [];
$controlledVocabulariesValues[$locale] = $controlledVocabulary;
$controlledVocabulariesDao->$insertFunction($controlledVocabulariesValues, $publication->getId(), false);
$publicationNew = Repo::publication()->get($publication->getId());
$publication->setData($n->tagName, $publicationNew->getData($n->tagName));
} else {
switch ($n->tagName) {
// Otherwise, delegate to specific parsing code
case 'id':
$this->parseIdentifier($n, $publication);
break;
case 'authors':
$this->parseAuthors($n, $publication);
break;
case 'citations':
$this->parseCitations($n, $publication);
break;
case 'copyrightYear':
$publication->setData('copyrightYear', $n->textContent);
break;
case 'licenseUrl':
$publication->setData('licenseUrl', $n->textContent);
break;
default:
$deployment = $this->getDeployment();
$deployment->addWarning(Application::ASSOC_TYPE_PUBLICATION, $publication->getId(), __('plugins.importexport.common.error.unknownElement', ['param' => $n->tagName]));
}
}
}
//
// Element parsing
//
/**
* Parse an identifier node and set up the publication object accordingly
*
* @param \DOMElement $element
* @param Publication $publication
*/
public function parseIdentifier($element, $publication)
{
$deployment = $this->getDeployment();
$submission = $deployment->getSubmission();
$advice = $element->getAttribute('advice');
switch ($element->getAttribute('type')) {
case 'internal':
// "update" advice not supported yet.
assert(!$advice || $advice == 'ignore');
if ($element->textContent == $submission->getData('currentPublicationId')) {
$submission->setData('currentPublicationId', $publication->getId());
Repo::submission()->dao->update($submission);
}
break;
case 'public':
if ($advice == 'update') {
$publication->setData('pub-id::publisher-id', $element->textContent);
}
break;
default:
if ($advice == 'update') {
if ($element->getAttribute('type') == 'doi') {
$doiFound = Repo::doi()->getCollector()->filterByIdentifier($element->textContent)->getMany()->first();
if ($doiFound) {
$publication->setData('doiId', $doiFound->getId());
} else {
$newDoiObject = Repo::doi()->newDataObject(
[
'doi' => $element->textContent,
'contextId' => $submission->getData('contextId')
]
);
$doiId = Repo::doi()->add($newDoiObject);
$publication->setData('doiId', $doiId);
}
} else {
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $deployment->getContext()->getId());
$publication->setData('pub-id::' . $element->getAttribute('type'), $element->textContent);
}
}
}
}
/**
* Parse an authors element
*
* @param \DOMElement $node
* @param Publication $publication
*/
public function parseAuthors($node, $publication)
{
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof \DOMElement) {
assert($n->tagName == 'author');
$this->parseAuthor($n, $publication);
}
}
}
/**
* Parse an author and add it to the submission.
*
* @param \DOMElement $n
* @param Publication $publication
*/
public function parseAuthor($n, $publication)
{
return $this->importWithXMLNode($n, 'native-xml=>author');
}
/**
* Parse a publication citation and add it to the publication.
*
* @param \DOMElement $n
* @param Publication $publication
*/
public function parseCitations($n, $publication)
{
$publicationId = $publication->getId();
$citationsString = '';
foreach ($n->childNodes as $citNode) {
$nodeText = trim($citNode->textContent);
if (empty($nodeText)) {
continue;
}
$citationsString .= $nodeText . "\n";
}
$publication->setData('citationsRaw', $citationsString);
$citationDao = DAORegistry::getDAO('CitationDAO'); /** @var CitationDAO $citationDao */
$citationDao->importCitations($publicationId, $citationsString);
}
//
// Helper functions
//
/**
* Get node name to setter function mapping for localized data.
*
* @return array
*/
public function _getLocalizedPublicationFields()
{
return [
'title',
'prefix',
'subtitle',
'abstract',
'coverage',
'type',
'source',
'rights',
'copyrightHolder',
];
}
/**
* Get node name to DAO and insert function mapping.
*
* @return array
*/
public function _getControlledVocabulariesMappings()
{
return [
'keywords' => ['SubmissionKeywordDAO', 'insertKeywords'],
'agencies' => ['SubmissionAgencyDAO', 'insertAgencies'],
'languages' => ['SubmissionLanguageDAO', 'insertLanguages'],
'disciplines' => ['SubmissionDisciplineDAO', 'insertDisciplines'],
'subjects' => ['SubmissionSubjectDAO', 'insertSubjects'],
];
}
/**
* Get the representation export filter group name
*
* @return string
*/
public function getRepresentationExportFilterGroupName()
{
assert(false); // Subclasses must override
}
/**
* Get the import filter for a given element.
*
* @param string $elementName Name of XML element
*
* @return Filter
*/
public function getImportFilter($elementName)
{
assert(false); // Subclasses should override
}
}
@@ -0,0 +1,130 @@
<?php
/**
* @file plugins/importexport/native/filter/NativeXmlRepresentationFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class NativeXmlRepresentationFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a Native XML document to a set of authors
*/
namespace PKP\plugins\importexport\native\filter;
use APP\core\Application;
use APP\facades\Repo;
use APP\publication\Publication;
use PKP\filter\FilterGroup;
use PKP\plugins\PluginRegistry;
use PKP\submission\Representation;
class NativeXmlRepresentationFilter extends NativeImportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML representation import');
parent::__construct($filterGroup);
}
/**
* Handle a Representation element
*
* @param \DOMElement $node
*
* @return Representation
*/
public function handleElement($node)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
$publication = $deployment->getPublication();
assert($publication instanceof Publication);
// Create the data object
$representationDao = Application::getRepresentationDAO();
$representation = $representationDao->newDataObject(); /** @var Representation $representation */
$representation->setData('publicationId', $publication->getId());
$representation->setData('urlPath', strlen($urlPath = (string) $node->getAttribute('url_path')) ? $urlPath : null);
// Handle metadata in subelements. Look for the 'name' and 'seq' elements.
// All other elements are handled by subclasses.
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof \DOMElement) {
switch ($n->tagName) {
case 'id': $this->parseIdentifier($n, $representation);
break;
case 'name':
$locale = $n->getAttribute('locale') ?: $publication->getData('locale');
$representation->setName($n->textContent, $locale);
break;
case 'seq':
$representation->setSequence($n->textContent);
break;
case 'remote':
$representation->setRemoteURL(($remoteUrl = $n->getAttribute('src')) ? $remoteUrl : null);
break;
}
}
}
return $representation; // database insert is handled by sub class.
}
/**
* Parse an identifier node and set up the representation object accordingly
*
* @param \DOMElement $element
* @param Representation $representation
*/
public function parseIdentifier($element, $representation)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
$advice = $element->getAttribute('advice');
switch ($element->getAttribute('type')) {
case 'internal':
// "update" advice not supported yet.
assert(!$advice || $advice == 'ignore');
break;
case 'public':
if ($advice == 'update') {
$representation->setStoredPubId('publisher-id', $element->textContent);
}
break;
default:
if ($advice == 'update') {
if ($element->getAttribute('type') == 'doi') {
$doiFound = Repo::doi()->getCollector()->filterByIdentifier($element->textContent)->getMany()->first();
if ($doiFound) {
$representation->setData('doiId', $doiFound->getId());
} else {
$newDoiObject = Repo::doi()->newDataObject(
[
'doi' => $element->textContent,
'contextId' => $context->getId()
]
);
$doiId = Repo::doi()->add($newDoiObject);
$representation->setData('doiId', $doiId);
}
} else {
// Load pub id plugins
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $context->getId());
$representation->setStoredPubId($element->getAttribute('type'), $element->textContent);
}
}
}
}
}
@@ -0,0 +1,399 @@
<?php
/**
* @file plugins/importexport/native/filter/NativeXmlSubmissionFileFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class NativeXmlSubmissionFileFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a Native XML document to a submission file
*/
namespace PKP\plugins\importexport\native\filter;
use APP\core\Application;
use APP\core\Services;
use APP\facades\Repo;
use PKP\core\Core;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\file\FileManager;
use PKP\file\TemporaryFileManager;
use PKP\filter\FilterGroup;
use PKP\plugins\PluginRegistry;
use PKP\submission\GenreDAO;
use PKP\submissionFile\SubmissionFile;
class NativeXmlSubmissionFileFilter extends NativeImportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML submission file import');
parent::__construct($filterGroup);
}
//
// Implement template methods from NativeImportFilter
//
/**
* Return the plural element name
*
* @return string
*/
public function getPluralElementName()
{
return 'submission_files';
}
/**
* Get the singular element name
*
* @return string
*/
public function getSingularElementName()
{
return 'submission_file';
}
/**
* Handle a submission file element
*
* @param \DOMElement $node
*
* @return SubmissionFile|null Null if skipping this file
*/
public function handleElement($node)
{
$deployment = $this->getDeployment();
$submission = $deployment->getSubmission();
$context = $deployment->getContext();
$stageName = $node->getAttribute('stage');
$submissionFileIdFromXml = $node->getAttribute('id');
$stageNameIdMapping = $deployment->getStageNameStageIdMapping();
assert(isset($stageNameIdMapping[$stageName]));
$stageId = $stageNameIdMapping[$stageName];
$errorOccurred = false;
$genreId = null;
$genreName = $node->getAttribute('genre');
// Build a cached list of genres by context ID by name
if ($genreName) {
if (!isset($genresByContextId[$context->getId()])) {
$genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */
$genres = $genreDao->getByContextId($context->getId());
while ($genre = $genres->next()) {
foreach ($genre->getName(null) as $locale => $name) {
$genresByContextId[$context->getId()][$name] = $genre;
}
}
}
if (!isset($genresByContextId[$context->getId()][$genreName])) {
$deployment->addError(PKPApplication::ASSOC_TYPE_SUBMISSION_FILE, $submission->getId(), __('plugins.importexport.common.error.unknownGenre', ['param' => $genreName]));
$errorOccurred = true;
} else {
$genre = $genresByContextId[$context->getId()][$genreName];
$genreId = $genre->getId();
}
}
$uploaderUsername = $node->getAttribute('uploader');
$uploaderUserId = null;
if (!$uploaderUsername) {
$user = $deployment->getUser();
} else {
// Determine the user based on the username
$user = Repo::user()->getByUsername($uploaderUsername, true);
}
$uploaderUserId = $user
? (int) $user->getId()
: Application::get()->getRequest()->getUser()->getId();
$submissionFile = Repo::submissionFile()->dao->newDataObject();
$submissionFile->setData('submissionId', $submission->getId());
$submissionFile->setData('locale', $submission->getLocale());
$submissionFile->setData('fileStage', $stageId);
$submissionFile->setData('createdAt', Core::getCurrentDate());
$submissionFile->setData('updatedAt', Core::getCurrentDate());
$submissionFile->setData('dateCreated', $node->getAttribute('date_created'));
$submissionFile->setData('language', $node->getAttribute('language'));
if ($caption = $node->getAttribute('caption')) {
$submissionFile->setData('caption', $caption);
}
if ($copyrightOwner = $node->getAttribute('copyright_owner')) {
$submissionFile->setData('copyrightOwner', $copyrightOwner);
}
if ($credit = $node->getAttribute('credit')) {
$submissionFile->setData('credit', $credit);
}
if (strlen($directSalesPrice = $node->getAttribute('direct_sales_price'))) {
$submissionFile->setData('directSalesPrice', $directSalesPrice);
}
if ($genreId) {
$submissionFile->setData('genreId', $genreId);
}
if ($salesType = $node->getAttribute('sales_type')) {
$submissionFile->setData('salesType', $salesType);
}
if ($sourceSubmissionFileId = $node->getAttribute('source_submission_file_id')) {
$submissionFile->setData('sourceSubmissionFileId', $sourceSubmissionFileId);
}
if ($terms = $node->getAttribute('terms')) {
$submissionFile->setData('terms', $terms);
}
if ($uploaderUserId) {
$submissionFile->setData('uploaderUserId', $uploaderUserId);
}
if ($node->getAttribute('viewable') == 'true') {
$submissionFile->setData('viewable', true);
}
// Handle metadata in sub-elements
$fileIds = [];
$currentFileId = null;
for ($childNode = $node->firstChild; $childNode !== null; $childNode = $childNode->nextSibling) {
if ($childNode instanceof \DOMElement) {
switch ($childNode->tagName) {
case 'id':
$this->parseIdentifier($childNode, $submissionFile);
break;
case 'creator':
case 'description':
case 'name':
case 'publisher':
case 'source':
case 'sponsor':
case 'subject':
[$locale, $value] = $this->parseLocalizedContent($childNode);
$submissionFile->setData($childNode->tagName, $value, $locale);
break;
case 'submission_file_ref':
if ($submissionFile->getData('fileStage') == SubmissionFile::SUBMISSION_FILE_DEPENDENT) {
$oldAssocId = $childNode->getAttribute('id');
$newAssocId = $deployment->getSubmissionFileDBId($oldAssocId);
if ($newAssocId) {
$submissionFile->setData('assocType', PKPApplication::ASSOC_TYPE_SUBMISSION_FILE);
$submissionFile->setData('assocId', $newAssocId);
}
}
break;
case 'file':
// File has already been imported so update file id
$fileId = $deployment->getFileDBId($childNode->getAttribute('id')) ?: $this->handleRevisionElement($childNode);
// Failed to insert the file (error messages are set at the <file> handler)
if (!$fileId) {
break;
}
// If this is the current file revision, set the submission file id
if ($childNode->getAttribute('id') == $node->getAttribute('file_id')) {
$currentFileId = $fileId;
} else { // Otherwise add it to the list of previous revisions
$fileIds[] = $fileId;
}
break;
default:
$deployment->addWarning(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.unknownElement', ['param' => $node->tagName]));
}
}
}
// Quit if there were errors or if the main file could not be inserted
if ($errorOccurred || !$currentFileId) {
return null;
}
// Ensure the current file revision is the last to be processed
$fileIds[] = $currentFileId;
// Consumes the first file ID to insert an initial submission file
$submissionFile->setData('fileId', array_shift($fileIds));
$submissionFile = Repo::submissionFile()->get(Repo::submissionFile()->add($submissionFile));
// Edits the submission file revisions one-by-one so that a useful activity log is built and past revisions can be accessed
foreach ($fileIds as $fileId) {
Repo::submissionFile()->edit($submissionFile, ['fileId' => $fileId]);
}
$deployment->setSubmissionFileDBId($submissionFileIdFromXml, $submissionFile->getId());
// Retrieves the updated submission file
return Repo::submissionFile()->get($submissionFile->getId());
}
/**
* Handle a revision element
*
* @param \DOMElement $node
*
* @return int|null The new file id if successful
*/
public function handleRevisionElement($node)
{
$deployment = $this->getDeployment();
$submission = $deployment->getSubmission();
for ($childNode = $node->firstChild; $childNode !== null; $childNode = $childNode->nextSibling) {
if ($childNode instanceof \DOMElement) {
switch ($childNode->tagName) {
case 'href':
$temporaryFileManager = new TemporaryFileManager();
$temporaryFilename = tempnam($temporaryFileManager->getBasePath(), 'src');
$filesrc = $childNode->getAttribute('src');
$errorFlag = false;
if (preg_match('|\w+://.+|', $filesrc)) {
// process as a URL
$client = Application::get()->getHttpClient();
$response = $client->request('GET', $filesrc);
file_put_contents($temporaryFilename, $response->getBody());
if (!filesize($temporaryFilename)) {
$errorFlag = true;
}
} elseif (substr($filesrc, 0, 1) === '/') {
// local file (absolute path)
if (!copy($filesrc, $temporaryFilename)) {
$errorFlag = true;
}
} elseif (is_readable($deployment->getImportPath() . '/' . $filesrc)) {
// local file (relative path)
$filesrc = $deployment->getImportPath() . '/' . $filesrc;
if (!copy($filesrc, $temporaryFilename)) {
$errorFlag = true;
}
} else {
// unhandled file path
$errorFlag = true;
}
if ($errorFlag) {
$deployment->addError(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.temporaryFileFailed', ['dest' => $temporaryFilename, 'source' => $filesrc]));
$fileManager = new FileManager();
$fileManager->deleteByPath($temporaryFilename);
$temporaryFilename = '';
}
break;
case 'embed':
$temporaryFileManager = new TemporaryFileManager();
$temporaryFilename = tempnam($temporaryFileManager->getBasePath(), 'embed');
if (($e = $childNode->getAttribute('encoding')) != 'base64') {
$deployment->addError(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.unknownEncoding', ['param' => $e]));
} else {
$content = base64_decode($childNode->textContent, true);
$errorFlag = false;
if (!$content) {
$deployment->addError(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.encodingError', ['param' => $e]));
$errorFlag = true;
} elseif (!file_put_contents($temporaryFilename, $content)) {
$deployment->addError(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.temporaryFileFailed', ['dest' => $temporaryFilename, 'source' => 'embed']));
$errorFlag = true;
}
if ($errorFlag) {
$fileManager = new FileManager();
$fileManager->deleteByPath($temporaryFilename);
$temporaryFilename = '';
}
}
break;
}
}
}
$newFileId = null;
if ($temporaryFilename) {
$fileSizeOnDisk = filesize($temporaryFilename);
$expectedFileSize = $node->getAttribute('filesize');
if ($fileSizeOnDisk != $expectedFileSize) {
$deployment->addWarning(Application::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.filesizeMismatch', ['expected' => $expectedFileSize, 'actual' => $fileSizeOnDisk]));
}
clearstatcache(true, $temporaryFilename);
$fileManager = new FileManager();
$submissionDir = Repo::submissionFile()->getSubmissionDir($submission->getData('contextId'), $submission->getId());
$newFileId = Services::get('file')->add(
$temporaryFilename,
$submissionDir . '/' . uniqid() . '.' . $node->getAttribute('extension')
);
$deployment->setFileDBId($node->getAttribute('id'), $newFileId);
$fileManager = new FileManager();
$fileManager->deleteByPath($temporaryFilename);
}
return $newFileId;
}
/**
* Parse an identifier node and set up the representation object accordingly
*
* @param \DOMElement $element
* @param SubmissionFile $submissionFile
*/
public function parseIdentifier($element, $submissionFile)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
$advice = $element->getAttribute('advice');
switch ($element->getAttribute('type')) {
case 'internal':
// "update" advice not supported yet.
assert(!$advice || $advice == 'ignore');
break;
case 'public':
if ($advice == 'update') {
$submissionFile->setStoredPubId('publisher-id', $element->textContent);
}
break;
default:
if ($advice == 'update') {
if ($element->getAttribute('type') == 'doi') {
$doiFound = Repo::doi()->getCollector()->filterByIdentifier($element->textContent)->getMany()->first();
if ($doiFound) {
$submissionFile->setData('doiId', $doiFound->getId());
} else {
$newDoiObject = Repo::doi()->newDataObject(
[
'doi' => $element->textContent,
'contextId' => $context->getId()
]
);
$doiId = Repo::doi()->add($newDoiObject);
$submissionFile->setData('doiId', $doiId);
}
} else {
// Load pub id plugins
PluginRegistry::loadCategory('pubIds', true, $context->getId());
$submissionFile->setStoredPubId($element->getAttribute('type'), $element->textContent);
}
}
}
}
/**
* Instantiate a submission file.
*
* @param string $tagName
*
* @return SubmissionFile
*/
public function instantiateSubmissionFile($tagName)
{
assert(false); // Subclasses should override
}
}
@@ -0,0 +1,241 @@
<?php
/**
* @file plugins/importexport/native/filter/NativeXmlSubmissionFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class NativeXmlSubmissionFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a Native XML document to a set of submissions
*/
namespace PKP\plugins\importexport\native\filter;
use APP\core\Application;
use APP\facades\Repo;
use APP\submission\Submission;
use PKP\core\Core;
use PKP\filter\Filter;
use PKP\filter\FilterGroup;
use PKP\observers\events\BatchMetadataChanged;
use PKP\workflow\WorkflowStageDAO;
class NativeXmlSubmissionFilter extends NativeImportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML submission import');
parent::__construct($filterGroup);
}
//
// Implement template methods from NativeImportFilter
//
/**
* Return the plural element name
*
* @return string
*/
public function getPluralElementName()
{
$deployment = $this->getDeployment();
return $deployment->getSubmissionsNodeName();
}
/**
* Get the singular element name
*
* @return string
*/
public function getSingularElementName()
{
$deployment = $this->getDeployment();
return $deployment->getSubmissionNodeName();
}
/**
* Handle a singular element import.
*
* @param \DOMElement $node
*/
public function handleElement($node)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
// Create and insert the submission (ID needed for other entities)
$submission = Repo::submission()->newDataObject();
$submission->setData('locale', $node->getAttribute('locale') ?: $context->getPrimaryLocale());
$submission->setData('contextId', $context->getId());
$submission->stampLastActivity();
$submission->stampModified();
$submission->setData('status', $node->getAttribute('status'));
$submission->setData('submissionProgress', '');
$submission->setData('stageId', WorkflowStageDAO::getIdFromPath($node->getAttribute('stage')));
// Handle any additional attributes etc.
$submission = $this->populateObject($submission, $node);
$submissionId = Repo::submission()->dao->insert($submission);
$submission = Repo::submission()->get($submissionId);
// Non-persisted temporary ID, will be updated once the publication gets parsed
$submission->setData('currentPublicationId', $node->getAttribute('current_publication_id'));
$deployment->setSubmission($submission);
$deployment->addProcessedObjectId(Application::ASSOC_TYPE_SUBMISSION, $submission->getId());
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof \DOMElement) {
$this->handleChildElement($n, $submission);
}
}
$submission = Repo::submission()->get($submission->getId());
$deployment->addImportedRootEntity(Application::ASSOC_TYPE_SUBMISSION, $submission);
return $submission;
}
/**
* Populate the submission object from the node
*
* @param Submission $submission
* @param \DOMElement $node
*
* @return Submission
*/
public function populateObject($submission, $node)
{
if ($dateSubmitted = $node->getAttribute('date_submitted')) {
$submission->setData('dateSubmitted', Core::getCurrentDate(strtotime($dateSubmitted)));
} else {
$submission->setData('dateSubmitted', Core::getCurrentDate());
}
return $submission;
}
/**
* Handle an element whose parent is the submission element.
*
* @param \DOMElement $n
* @param Submission $submission
*/
public function handleChildElement($n, $submission)
{
switch ($n->tagName) {
case 'id':
$this->parseIdentifier($n, $submission);
break;
case 'submission_file':
$this->parseChild($n, $submission);
break;
case 'publication':
$this->parseChild($n, $submission);
break;
default:
$deployment = $this->getDeployment();
$deployment->addWarning(Application::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.unknownElement', ['param' => $n->tagName]));
}
}
//
// Element parsing
//
/**
* Parse an identifier node and set up the submission object accordingly
*
* @param \DOMElement $element
* @param Submission $submission
*/
public function parseIdentifier($element, $submission)
{
$deployment = $this->getDeployment();
$advice = $element->getAttribute('advice');
switch ($element->getAttribute('type')) {
case 'internal':
// "update" advice not supported yet.
assert(!$advice || $advice == 'ignore');
break;
}
}
/**
* @see Filter::process()
*
* @param \DOMDocument|string $document
*
* @return array Array of imported documents
*/
public function &process(&$document)
{
$importedObjects = & parent::process($document);
$deployment = $this->getDeployment();
// Index imported content
$submissionIds = [];
foreach ($importedObjects as $submission) {
assert($submission instanceof Submission);
$publication = $submission->getCurrentPublication();
if (!isset($publication)) {
$deployment->addError(Application::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.currentPublicationNullOrMissing'));
}
$submissionIds[] = $submission->getId();
}
event(new BatchMetadataChanged($submissionIds));
return $importedObjects;
}
/**
* Parse a submission child and add it to the submission.
*
* @param \DOMElement $n
* @param Submission $submission
*/
public function parseChild($n, $submission)
{
$importFilter = $this->getImportFilter($n->tagName);
assert(isset($importFilter)); // There should be a filter
$submissionChildDoc = new \DOMDocument('1.0', 'utf-8');
$submissionChildDoc->appendChild($submissionChildDoc->importNode($n, true));
$ret = $importFilter->execute($submissionChildDoc);
if ($ret == null) {
$deployment = $this->getDeployment();
$deployment->addError(Application::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.common.error.submissionChildFailed', ['child' => $n->tagName]));
}
}
//
// Helper functions
//
/**
* Get the import filter for a given element.
*
* @param string $elementName Name of XML element
*
* @return Filter
*/
public function getImportFilter($elementName)
{
assert(false); // Subclasses should override
}
}
@@ -0,0 +1,121 @@
<?php
/**
* @file plugins/importexport/native/filter/PKPAuthorNativeXmlFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPAuthorNativeXmlFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a set of authors to a Native XML document
*/
namespace PKP\plugins\importexport\native\filter;
use APP\core\Application;
use APP\facades\Repo;
use Exception;
use PKP\filter\FilterGroup;
class PKPAuthorNativeXmlFilter extends NativeExportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML author export');
parent::__construct($filterGroup);
}
//
// Implement template methods from Filter
//
/**
* @see Filter::process()
*
* @param array $authors Array of authors
*
* @return \DOMDocument
*/
public function &process(&$authors)
{
// Create the XML document
$doc = new \DOMDocument('1.0', 'utf-8');
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$deployment = $this->getDeployment();
// Multiple authors; wrap in a <authors> element
$rootNode = $doc->createElementNS($deployment->getNamespace(), 'authors');
foreach ($authors as $author) {
$rootNode->appendChild($this->createPKPAuthorNode($doc, $author));
}
$doc->appendChild($rootNode);
$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$rootNode->setAttribute('xsi:schemaLocation', $deployment->getNamespace() . ' ' . $deployment->getSchemaFilename());
return $doc;
}
//
// PKPAuthor conversion functions
//
/**
* Create and return an author node.
*
* @param \DOMDocument $doc
* @param \PKP\author\Author $author
*
* @return \DOMElement
*/
public function createPKPAuthorNode($doc, $author)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
// Create the author node
$authorNode = $doc->createElementNS($deployment->getNamespace(), 'author');
if ($author->getPrimaryContact()) {
$authorNode->setAttribute('primary_contact', 'true');
}
if ($author->getIncludeInBrowse()) {
$authorNode->setAttribute('include_in_browse', 'true');
}
$userGroup = Repo::userGroup()->get($author->getUserGroupId());
assert(isset($userGroup));
if (!$userGroup) {
$deployment->addError(Application::ASSOC_TYPE_AUTHOR, $author->getId(), __('plugins.importexport.common.error.userGroupMissing', ['param' => $author->getFullName()]));
throw new Exception(__('plugins.importexport.author.exportFailed'));
}
$authorNode->setAttribute('user_group_ref', $userGroup->getName($context->getPrimaryLocale()));
$authorNode->setAttribute('seq', $author->getSequence());
$authorNode->setAttribute('id', $author->getId());
// Add metadata
$this->createLocalizedNodes($doc, $authorNode, 'givenname', $author->getGivenName(null));
$this->createLocalizedNodes($doc, $authorNode, 'familyname', $author->getFamilyName(null));
$this->createLocalizedNodes($doc, $authorNode, 'affiliation', $author->getAffiliation(null));
$this->createOptionalNode($doc, $authorNode, 'country', $author->getCountry());
$authorNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'email', htmlspecialchars($author->getEmail(), ENT_COMPAT, 'UTF-8')));
$this->createOptionalNode($doc, $authorNode, 'url', $author->getUrl());
$this->createOptionalNode($doc, $authorNode, 'orcid', $author->getOrcid());
$this->createLocalizedNodes($doc, $authorNode, 'biography', $author->getBiography(null));
return $authorNode;
}
}
@@ -0,0 +1,159 @@
<?php
/**
* @file plugins/importexport/native/filter/PKPNativeFilterHelper.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPNativeFilterHelper
*
* @ingroup plugins_importexport_native
*
* @brief Class that provides native import/export filter-related helper methods.
*/
namespace PKP\plugins\importexport\native\filter;
use APP\file\PublicFileManager;
use APP\publication\Publication;
use DOMDocument;
use DOMElement;
use PKP\core\PKPApplication;
class PKPNativeFilterHelper
{
/**
* Create and return an object covers node.
*/
public function createPublicationCoversNode(NativeExportFilter $filter, DOMDocument $doc, Publication $object): ?DOMElement
{
$coverImages = $object->getData('coverImage');
if (empty($coverImages)) {
return null;
}
$deployment = $filter->getDeployment();
$context = $deployment->getContext();
$publicFileManager = new PublicFileManager();
$contextId = $context->getId();
$coversNode = $doc->createElementNS($deployment->getNamespace(), 'covers');
foreach ($coverImages as $locale => $coverImage) {
$coverImageName = $coverImage['uploadName'] ?? '';
$filePath = $publicFileManager->getContextFilesPath($contextId) . '/' . $coverImageName;
if (!file_exists($filePath)) {
$deployment->addWarning(PKPApplication::ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.publicationCoverImageMissing', ['id' => $object->getId(), 'path' => $filePath]));
continue;
}
$coverNode = $doc->createElementNS($deployment->getNamespace(), 'cover');
$coverNode->setAttribute('locale', $locale);
$coverNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'cover_image', htmlspecialchars($coverImageName, ENT_COMPAT, 'UTF-8')));
$coverNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'cover_image_alt_text', htmlspecialchars($coverImage['altText'] ?? '', ENT_COMPAT, 'UTF-8')));
$embedNode = $doc->createElementNS($deployment->getNamespace(), 'embed', base64_encode(file_get_contents($filePath)));
$embedNode->setAttribute('encoding', 'base64');
$coverNode->appendChild($embedNode);
$coversNode->appendChild($coverNode);
}
return $coversNode->firstChild?->parentNode;
}
/**
* Parse out the object covers.
*
* @param NativeImportFilter $filter
* @param \DOMElement $node
* @param Publication $object
*/
public function parsePublicationCovers($filter, $node, $object)
{
$deployment = $filter->getDeployment();
$coverImages = [];
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof DOMElement) {
switch ($n->tagName) {
case 'cover':
$coverImage = $this->parsePublicationCover($filter, $n, $object);
$coverImages[key($coverImage)] = reset($coverImage);
break;
default:
$deployment->addWarning(PKPApplication::ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.unknownElement', ['param' => $n->tagName]));
}
}
}
$object->setData('coverImage', $coverImages);
}
/**
* Parse out the cover and store it in the object.
*
* @param NativeImportFilter $filter
* @param \DOMElement $node
* @param Publication $object
*/
public function parsePublicationCover($filter, $node, $object)
{
$deployment = $filter->getDeployment();
$context = $deployment->getContext();
$locale = $node->getAttribute('locale');
if (empty($locale)) {
$locale = $context->getPrimaryLocale();
}
$coverImagelocale = [];
$coverImage = [];
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof DOMElement) {
switch ($n->tagName) {
case 'cover_image':
$coverImage['uploadName'] = trim(
preg_replace(
"/[^a-z0-9\.\-]+/",
"",
str_replace(
[' ', '_', ':'],
'-',
strtolower($n->textContent)
)
)
);
break;
case 'cover_image_alt_text':
$coverImage['altText'] = $n->textContent;
break;
case 'embed':
if (!isset($coverImage['uploadName'])) {
$deployment->addWarning(PKPApplication::ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.coverImageNameUnspecified'));
break;
}
$publicFileManager = new PublicFileManager();
$filePath = $publicFileManager->getContextFilesPath($context->getId()) . '/' . $coverImage['uploadName'];
$allowedFileTypes = ['gif', 'jpg', 'png', 'webp'];
$extension = pathinfo(strtolower($filePath), PATHINFO_EXTENSION);
if (!in_array($extension, $allowedFileTypes)) {
$deployment->addWarning(PKPApplication::ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.invalidFileExtension'));
break;
}
file_put_contents($filePath, base64_decode($n->textContent));
break;
default:
$deployment->addWarning(PKPApplication::ASSOC_TYPE_PUBLICATION, $object->getId(), __('plugins.importexport.common.error.unknownElement', ['param' => $n->tagName]));
}
}
}
$coverImagelocale[$locale] = $coverImage;
return $coverImagelocale;
}
}
@@ -0,0 +1,353 @@
<?php
/**
* @file plugins/importexport/native/filter/PKPPublicationNativeXmlFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class PKPPublicationNativeXmlFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a Publication to a Native XML document
*/
namespace PKP\plugins\importexport\native\filter;
use APP\core\Application;
use APP\plugins\importexport\native\NativeImportExportDeployment;
use APP\publication\Publication;
use Exception;
use PKP\citation\CitationDAO;
use PKP\db\DAORegistry;
use PKP\filter\FilterGroup;
use PKP\plugins\importexport\PKPImportExportFilter;
use PKP\plugins\PluginRegistry;
use PKP\submission\PKPSubmission;
use PKP\submission\Representation;
use PKP\submission\RepresentationDAOInterface;
class PKPPublicationNativeXmlFilter extends NativeExportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML Publication export');
parent::__construct($filterGroup);
}
//
// Implement template methods from Filter
//
/**
* @see Filter::process()
*
* @param Publication $entity
*
* @return \DOMDocument
*/
public function &process(&$entity)
{
// Create the XML document
$doc = new \DOMDocument('1.0', 'utf-8');
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$deployment = $this->getDeployment();
$rootNode = $this->createEntityNode($doc, $entity);
$doc->appendChild($rootNode);
$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$rootNode->setAttribute('xsi:schemaLocation', $deployment->getNamespace() . ' ' . $deployment->getSchemaFilename());
return $doc;
}
//
// Representation conversion functions
//
/**
* Create and return an entity node.
*
* @param \DOMDocument $doc
* @param Publication $entity
*
* @return \DOMElement
*/
public function createEntityNode($doc, $entity)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
// Create the entity node
$entityNode = $doc->createElementNS($deployment->getNamespace(), 'publication');
$this->addIdentifiers($doc, $entityNode, $entity);
$entityNode->setAttribute('version', $entity->getData('version') ?: 1);
$entityNode->setAttribute('status', $entity->getData('status'));
if ($primaryContactId = $entity->getData('primaryContactId')) {
$entityNode->setAttribute('primary_contact_id', $primaryContactId);
}
$entityNode->setAttribute('url_path', $entity->getData('urlPath'));
if ($entity->getData('status') === PKPSubmission::STATUS_PUBLISHED) {
$entityNode->setAttribute('seq', (int) $entity->getData('seq'));
} else {
$entityNode->setAttribute('seq', '0');
}
if ($entity->getData('accessStatus')) {
$entityNode->setAttribute('access_status', $entity->getData('accessStatus'));
} else {
$entityNode->setAttribute('access_status', '0');
}
if ($datePublished = $entity->getData('datePublished')) {
$entityNode->setAttribute('date_published', date('Y-m-d', strtotime($datePublished)));
}
$this->addMetadata($doc, $entityNode, $entity);
$authors = $entity->getData('authors');
if ($authors && count($authors) > 0) {
$this->addAuthors($doc, $entityNode, $entity);
}
$this->addRepresentations($doc, $entityNode, $entity);
$citationsListNode = $this->createCitationsNode($doc, $deployment, $entity);
if ($citationsListNode->hasChildNodes() || $citationsListNode->hasAttributes()) {
$entityNode->appendChild($citationsListNode);
}
return $entityNode;
}
/**
* Create and add identifier nodes to a submission node.
*
* @param \DOMDocument $doc
* @param \DOMElement $entityNode
* @param Publication $entity
*/
public function addIdentifiers($doc, $entityNode, $entity)
{
$deployment = $this->getDeployment();
// Add internal ID
$entityNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', $entity->getId()));
$node->setAttribute('type', 'internal');
$node->setAttribute('advice', 'ignore');
// Add public ID
if ($pubId = $entity->getStoredPubId('publisher-id')) {
$entityNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', htmlspecialchars($pubId, ENT_COMPAT, 'UTF-8')));
$node->setAttribute('type', 'public');
$node->setAttribute('advice', 'update');
}
// Add pub IDs by plugin
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $deployment->getContext()->getId());
foreach ($pubIdPlugins as $pubIdPlugin) {
$this->addPubIdentifier($doc, $entityNode, $entity, $pubIdPlugin->getPubIdType());
}
// Also add DOI
$this->addPubIdentifier($doc, $entityNode, $entity, 'doi');
}
/**
* Add a single pub ID element for a given plugin to the document.
*
* @param \DOMDocument $doc
* @param \DOMElement $entityNode
* @param Publication $entity
*
* @return ?\DOMElement
*/
public function addPubIdentifier($doc, $entityNode, $entity, $pubIdType)
{
$pubId = $entity->getStoredPubId($pubIdType);
if ($pubId) {
$deployment = $this->getDeployment();
$entityNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', htmlspecialchars($pubId, ENT_COMPAT, 'UTF-8')));
$node->setAttribute('type', $pubIdType);
$node->setAttribute('advice', 'update');
return $node;
}
return null;
}
/**
* Add the publication metadata for a publication to its DOM element.
*
* @param \DOMDocument $doc
* @param \DOMElement $entityNode
* @param Publication $entity
*/
public function addMetadata($doc, $entityNode, $entity)
{
$deployment = $this->getDeployment();
$this->createLocalizedNodes($doc, $entityNode, 'title', $entity->getTitles('html'));
$this->createLocalizedNodes($doc, $entityNode, 'prefix', $entity->getData('prefix'));
$this->createLocalizedNodes($doc, $entityNode, 'subtitle', $entity->getSubTitles('html'));
$this->createLocalizedNodes($doc, $entityNode, 'abstract', $entity->getData('abstract'));
$this->createLocalizedNodes($doc, $entityNode, 'coverage', $entity->getData('coverage'));
$this->createLocalizedNodes($doc, $entityNode, 'type', $entity->getData('type'));
$this->createLocalizedNodes($doc, $entityNode, 'source', $entity->getData('source'));
$this->createLocalizedNodes($doc, $entityNode, 'rights', $entity->getData('rights'));
if ($entity->getData('licenseUrl')) {
$entityNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'licenseUrl', htmlspecialchars($entity->getData('licenseUrl'))));
}
$this->createLocalizedNodes($doc, $entityNode, 'copyrightHolder', $entity->getData('copyrightHolder'));
if ($entity->getData('copyrightYear')) {
$entityNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'copyrightYear', intval($entity->getData('copyrightYear'))));
}
// add controlled vocabularies
// get the supported locale keys
$supportedLocales = $deployment->getContext()->getSupportedFormLocales();
$controlledVocabulariesMapping = $this->_getControlledVocabulariesMappings();
foreach ($controlledVocabulariesMapping as $controlledVocabulariesNodeName => $mappings) {
$dao = DAORegistry::getDAO($mappings[0]);
$getFunction = $mappings[1];
$controlledVocabularyNodeName = $mappings[2];
$controlledVocabulary = $dao->$getFunction($entity->getId(), $supportedLocales);
$this->addControlledVocabulary($doc, $entityNode, $controlledVocabulariesNodeName, $controlledVocabularyNodeName, $controlledVocabulary);
}
}
/**
* Add publication's controlled vocabulary to its DOM element.
*
* @param \DOMDocument $doc
* @param \DOMElement $entityNode
* @param string $controlledVocabulariesNodeName Parent node name
* @param string $controlledVocabularyNodeName Item node name
* @param array $controlledVocabulary Associative array (locale => array of items)
*/
public function addControlledVocabulary($doc, $entityNode, $controlledVocabulariesNodeName, $controlledVocabularyNodeName, $controlledVocabulary)
{
$deployment = $this->getDeployment();
$locales = array_keys($controlledVocabulary);
foreach ($locales as $locale) {
if (!empty($controlledVocabulary[$locale])) {
$controlledVocabulariesNode = $doc->createElementNS($deployment->getNamespace(), $controlledVocabulariesNodeName);
$controlledVocabulariesNode->setAttribute('locale', $locale);
foreach ($controlledVocabulary[$locale] as $controlledVocabularyItem) {
$controlledVocabulariesNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), $controlledVocabularyNodeName, htmlspecialchars($controlledVocabularyItem, ENT_COMPAT, 'UTF-8')));
}
$entityNode->appendChild($controlledVocabulariesNode);
}
}
}
/**
* Add the author metadata for a submission to its DOM element.
*
* @param \DOMDocument $doc
* @param \DOMElement $entityNode
* @param Publication $entity
*/
public function addAuthors($doc, $entityNode, $entity)
{
$currentFilter = PKPImportExportFilter::getFilter('author=>native-xml', $this->getDeployment());
$authors = $entity->getData('authors')->toArray();
$authorsDoc = $currentFilter->execute($authors);
if ($authorsDoc && $authorsDoc->documentElement instanceof \DOMElement) {
$clone = $doc->importNode($authorsDoc->documentElement, true);
$entityNode->appendChild($clone);
} else {
$deployment = $this->getDeployment();
$deployment->addError(Application::ASSOC_TYPE_PUBLICATION, $entity->getId(), __('plugins.importexport.author.exportFailed'));
throw new Exception(__('plugins.importexport.author.exportFailed'));
}
}
/**
* Add the representations of a publication to its DOM element.
*
* @param \DOMDocument $doc
* @param \DOMElement $entityNode
* @param Publication $entity
*/
public function addRepresentations($doc, $entityNode, $entity)
{
$currentFilter = PKPImportExportFilter::getFilter($this->getRepresentationExportFilterGroupName(), $this->getDeployment());
/** @var RepresentationDAOInterface $representationDao */
$representationDao = Application::getRepresentationDAO();
$representations = $representationDao->getByPublicationId($entity->getId());
foreach ($representations as $representation) {
$representationDoc = $currentFilter->execute($representation);
$clone = $doc->importNode($representationDoc->documentElement, true);
$entityNode->appendChild($clone);
}
}
/**
* Get controlled vocabularies parent node name to DAO, get function and item node name mapping.
*
* @return array
*/
public function _getControlledVocabulariesMappings()
{
return [
'keywords' => ['SubmissionKeywordDAO', 'getKeywords', 'keyword'],
'agencies' => ['SubmissionAgencyDAO', 'getAgencies', 'agency'],
'languages' => ['SubmissionLanguageDAO', 'getLanguages', 'language'],
'disciplines' => ['SubmissionDisciplineDAO', 'getDisciplines', 'discipline'],
'subjects' => ['SubmissionSubjectDAO', 'getSubjects', 'subject'],
];
}
//
// Abstract methods to be implemented by subclasses
//
/**
* Get the submission files associated with this representation
*
* @param Representation $representation
*
* @return array
*/
public function getFiles($representation)
{
assert(false); // To be overridden by subclasses
}
/**
* Create and return a Citations node.
*
* @param \DOMDocument $doc
* @param NativeImportExportDeployment $deployment
* @param Publication $publication
*
* @return \DOMElement
*/
private function createCitationsNode($doc, $deployment, $publication)
{
$citationDao = DAORegistry::getDAO('CitationDAO'); /** @var CitationDAO $citationDao */
$nodeCitations = $doc->createElementNS($deployment->getNamespace(), 'citations');
$submissionCitations = $citationDao->getByPublicationId($publication->getId())->toAssociativeArray();
foreach ($submissionCitations as $submissionCitation) {
$rawCitation = $submissionCitation->getRawCitation();
$nodeCitations->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'citation', htmlspecialchars($rawCitation, ENT_COMPAT, 'UTF-8')));
}
return $nodeCitations;
}
}
@@ -0,0 +1,176 @@
<?php
/**
* @file plugins/importexport/native/filter/RepresentationNativeXmlFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class RepresentationNativeXmlFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a representation to a Native XML document
*/
namespace PKP\plugins\importexport\native\filter;
use PKP\filter\FilterGroup;
use PKP\plugins\PluginRegistry;
use PKP\submission\Representation;
class RepresentationNativeXmlFilter extends NativeExportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML representation export');
parent::__construct($filterGroup);
}
//
// Implement template methods from Filter
//
/**
* @see Filter::process()
*
* @param Representation $representation
*
* @return \DOMDocument
*/
public function &process(&$representation)
{
// Create the XML document
$doc = new \DOMDocument('1.0', 'utf-8');
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$deployment = $this->getDeployment();
$rootNode = $this->createRepresentationNode($doc, $representation);
$doc->appendChild($rootNode);
$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$rootNode->setAttribute('xsi:schemaLocation', $deployment->getNamespace() . ' ' . $deployment->getSchemaFilename());
return $doc;
}
//
// Representation conversion functions
//
/**
* Create and return a representation node.
*
* @param \DOMDocument $doc
* @param Representation $representation
*
* @return \DOMElement
*/
public function createRepresentationNode($doc, $representation)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
// Create the representation node
$representationNode = $doc->createElementNS($deployment->getNamespace(), $deployment->getRepresentationNodeName());
$representationNode->setAttribute('locale', $representation->getData('locale'));
$representationNode->setAttribute('url_path', $representation->getData('urlPath'));
$this->addIdentifiers($doc, $representationNode, $representation);
// Add metadata
$this->createLocalizedNodes($doc, $representationNode, 'name', $representation->getName(null));
$sequenceNode = $doc->createElementNS($deployment->getNamespace(), 'seq');
$sequenceNode->appendChild($doc->createTextNode((int) $representation->getSequence()));
$representationNode->appendChild($sequenceNode);
$urlRemote = $representation->getData('urlRemote');
if ($urlRemote) {
$remoteNode = $doc->createElementNS($deployment->getNamespace(), 'remote');
$remoteNode->setAttribute('src', $urlRemote);
$representationNode->appendChild($remoteNode);
} else {
// Add files
foreach ($this->getFiles($representation) as $submissionFile) {
$fileRefNode = $doc->createElementNS($deployment->getNamespace(), 'submission_file_ref');
$fileRefNode->setAttribute('id', $submissionFile->getId());
$representationNode->appendChild($fileRefNode);
}
}
return $representationNode;
}
/**
* Create and add identifier nodes to a representation node.
*
* @param \DOMDocument $doc
* @param \DOMElement $representationNode
* @param Representation $representation
*/
public function addIdentifiers($doc, $representationNode, $representation)
{
$deployment = $this->getDeployment();
// Add internal ID
$representationNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', $representation->getId()));
$node->setAttribute('type', 'internal');
$node->setAttribute('advice', 'ignore');
// Add public ID
if ($pubId = $representation->getStoredPubId('publisher-id')) {
$representationNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', htmlspecialchars($pubId, ENT_COMPAT, 'UTF-8')));
$node->setAttribute('type', 'public');
$node->setAttribute('advice', 'update');
}
// Add pub IDs by plugin
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $deployment->getContext()->getId());
foreach ($pubIdPlugins as $pubIdPlugin) {
$this->addPubIdentifier($doc, $representationNode, $representation, $pubIdPlugin->getPubIdType());
}
// Also add DOI
$this->addPubIdentifier($doc, $representationNode, $representation, 'doi');
}
/**
* Add a single pub ID element for a given plugin to the representation.
*
* @param \DOMDocument $doc
* @param \DOMElement $representationNode
* @param Representation $representation
*
* @return ?\DOMElement
*/
public function addPubIdentifier($doc, $representationNode, $representation, $pubIdType)
{
$pubId = $representation->getStoredPubId($pubIdType);
if ($pubId) {
$deployment = $this->getDeployment();
$representationNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', htmlspecialchars($pubId, ENT_COMPAT, 'UTF-8')));
$node->setAttribute('type', $pubIdType);
$node->setAttribute('advice', 'update');
return $node;
}
return null;
}
//
// Abstract methods to be implemented by subclasses
//
/**
* Get the submission files associated with this representation
*
* @param Representation $representation
*
* @return array
*/
public function getFiles($representation)
{
assert(false); // To be overridden by subclasses
}
}
@@ -0,0 +1,266 @@
<?php
/**
* @file plugins/importexport/native/filter/SubmissionFileNativeXmlFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class SubmissionFileNativeXmlFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a submissionFile to a Native XML document
*/
namespace PKP\plugins\importexport\native\filter;
use APP\core\Application;
use APP\facades\Repo;
use DOMDocument;
use DOMElement;
use PKP\config\Config;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\filter\FilterGroup;
use PKP\plugins\PluginRegistry;
use PKP\submission\GenreDAO;
use PKP\submissionFile\SubmissionFile;
class SubmissionFileNativeXmlFilter extends NativeExportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML submission file export');
parent::__construct($filterGroup);
}
//
// Implement template methods from Filter
//
/**
* @see Filter::process()
*
* @param SubmissionFile $submissionFile
*
* @return ?DOMDocument
*/
public function &process(&$submissionFile)
{
// Create the XML document
$doc = new DOMDocument('1.0', 'utf-8');
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$deployment = $this->getDeployment();
$rootNode = $this->createSubmissionFileNode($doc, $submissionFile);
if (!$rootNode) {
return $rootNode;
}
$doc->appendChild($rootNode);
$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$rootNode->setAttribute('xsi:schemaLocation', $deployment->getNamespace() . ' ' . $deployment->getSchemaFilename());
return $doc;
}
//
// SubmissionFile conversion functions
//
/**
* Create and return a submissionFile node.
*/
public function createSubmissionFileNode(DOMDocument $doc, SubmissionFile $submissionFile): ?DOMElement
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
$stageToName = array_flip($deployment->getStageNameStageIdMapping());
// Quit if the submission file has an invalid file stage
if (!isset($stageToName[$submissionFile->getFileStage()])) {
$deployment->addWarning(PKPApplication::ASSOC_TYPE_SUBMISSION_FILE, $submissionFile->getId(), __('plugins.importexport.native.error.submissionFileInvalidFileStage', ['id' => $submissionFile->getId()]));
return null;
}
$genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */
$genre = $genreDao->getById($submissionFile->getData('genreId'));
$uploaderUser = Repo::user()->get($submissionFile->getData('uploaderUserId'), true);
// Create the submission_file node and set metadata
$submissionFileNode = $doc->createElementNS($deployment->getNamespace(), $this->getSubmissionFileElementName());
$submissionFileNode->setAttribute('id', $submissionFile->getId());
$submissionFileNode->setAttribute('created_at', date('Y-m-d', strtotime($submissionFile->getData('createdAt'))));
$submissionFileNode->setAttribute('date_created', $submissionFile->getData('dateCreated'));
$submissionFileNode->setAttribute('file_id', $submissionFile->getData('fileId'));
$submissionFileNode->setAttribute('stage', $stageToName[$submissionFile->getFileStage()]);
$submissionFileNode->setAttribute('updated_at', date('Y-m-d', strtotime($submissionFile->getData('updatedAt'))));
$submissionFileNode->setAttribute('viewable', $submissionFile->getViewable() ? 'true' : 'false');
if ($caption = $submissionFile->getData('caption')) {
$submissionFileNode->setAttribute('caption', $caption);
}
if ($copyrightOwner = $submissionFile->getData('copyrightOwner')) {
$submissionFileNode->setAttribute('copyright_owner', $copyrightOwner);
}
if ($credit = $submissionFile->getData('credit')) {
$submissionFileNode->setAttribute('credit', $credit);
}
if ($submissionFile->getData('directSalesPrice') != null) {
$submissionFileNode->setAttribute('direct_sales_price', $submissionFile->getData('directSalesPrice'));
}
if ($genre) {
$submissionFileNode->setAttribute('genre', $genre->getName($context->getPrimaryLocale()));
}
if ($language = $submissionFile->getData('language')) {
$submissionFileNode->setAttribute('language', $language);
}
if ($salesType = $submissionFile->getData('salesType')) {
$submissionFileNode->setAttribute('sales_type', $salesType);
}
if ($sourceSubmissionFileId = $submissionFile->getData('sourceSubmissionFileId')) {
$submissionFileNode->setAttribute('source_submission_file_id', $sourceSubmissionFileId);
}
if ($terms = $submissionFile->getData('terms')) {
$submissionFileNode->setAttribute('terms', $terms);
}
if ($uploaderUser) {
$submissionFileNode->setAttribute('uploader', $uploaderUser->getUsername());
}
// Add pub-id plugins
$this->addIdentifiers($doc, $submissionFileNode, $submissionFile);
$this->createLocalizedNodes($doc, $submissionFileNode, 'creator', $submissionFile->getData('creator'));
$this->createLocalizedNodes($doc, $submissionFileNode, 'description', $submissionFile->getData('description'));
$this->createLocalizedNodes($doc, $submissionFileNode, 'name', $submissionFile->getData('name'));
$this->createLocalizedNodes($doc, $submissionFileNode, 'publisher', $submissionFile->getData('publisher'));
$this->createLocalizedNodes($doc, $submissionFileNode, 'source', $submissionFile->getData('source'));
$this->createLocalizedNodes($doc, $submissionFileNode, 'sponsor', $submissionFile->getData('sponsor'));
$this->createLocalizedNodes($doc, $submissionFileNode, 'subject', $submissionFile->getData('subject'));
// If it is a dependent file, add submission_file_ref element
if ($submissionFile->getData('fileStage') == SubmissionFile::SUBMISSION_FILE_DEPENDENT && $submissionFile->getData('assocType') == PKPApplication::ASSOC_TYPE_SUBMISSION_FILE) {
$fileRefNode = $doc->createElementNS($deployment->getNamespace(), 'submission_file_ref');
$fileRefNode->setAttribute('id', $submissionFile->getData('assocId'));
$submissionFileNode->appendChild($fileRefNode);
}
// Create the revision nodes
$revisions = Repo::submissionFile()->getRevisions(($submissionFile->getId()));
$basePath = rtrim(Config::getVar('files', 'files_dir'), '/') . '/';
$hasValidRevision = false;
foreach ($revisions as $revision) {
$localPath = $basePath . $revision->path;
if (!file_exists($localPath)) {
$deployment->addWarning(PKPApplication::ASSOC_TYPE_SUBMISSION_FILE, $submissionFile->getId(), __('plugins.importexport.native.error.submissionFileRevisionMissing', ['id' => $submissionFile->getId(), 'revision' => $revision->revision_id, 'path' => $localPath]));
continue;
}
$hasValidRevision = true;
$revisionNode = $doc->createElementNS($deployment->getNamespace(), 'file');
$revisionNode->setAttribute('id', $revision->fileId);
$revisionNode->setAttribute('filesize', filesize($localPath));
$revisionNode->setAttribute('extension', pathinfo($revision->path, PATHINFO_EXTENSION));
$submissionFileNode->appendChild($revisionNode);
if (!($this->opts['no-embed'] ?? false)) {
$embedNode = $doc->createElementNS($deployment->getNamespace(), 'embed', base64_encode(file_get_contents($localPath)));
$embedNode->setAttribute('encoding', 'base64');
$revisionNode->appendChild($embedNode);
continue;
}
$hrefNode = $doc->createElementNS($deployment->getNamespace(), 'href');
$revisionNode->appendChild($hrefNode);
$hrefNode->setAttribute('mime_type', $revision->mimetype);
if (!($this->opts['use-file-urls'] ?? false)) {
$hrefNode->setAttribute('src', $revision->path);
continue;
}
$baseParams ??= [
'submissionFileId' => $submissionFile->getId(),
'submissionId' => $submissionFile->getData('submissionId'),
'stageId' => Repo::submissionFile()->getWorkflowStageId($submissionFile),
];
$params = $baseParams + ['fileId' => $revision->fileId];
$dispatcher ??= Application::get()->getDispatcher();
$request ??= Application::get()->getRequest();
$url = $dispatcher->url($request, PKPApplication::ROUTE_COMPONENT, $context->getPath(), 'api.file.FileApiHandler', 'downloadFile', null, $params);
$hrefNode->setAttribute('src', $url);
}
// Report if no revision has been added
if (!$hasValidRevision) {
$deployment->addWarning(PKPApplication::ASSOC_TYPE_SUBMISSION_FILE, $submissionFile->getId(), __('plugins.importexport.native.error.submissionFileWithoutRevision', ['id' => $submissionFile->getId()]));
return null;
}
return $submissionFileNode;
}
/**
* Create and add identifier nodes to a submission node.
*
* @param \DOMDocument $doc
* @param \DOMElement $revisionNode
* @param SubmissionFile $submissionFile
*/
public function addIdentifiers($doc, $revisionNode, $submissionFile)
{
$deployment = $this->getDeployment();
// Ommiting the internal ID here because it is in the submission_file attribute
// Add public ID
if ($pubId = $submissionFile->getStoredPubId('publisher-id')) {
$revisionNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', htmlspecialchars($pubId, ENT_COMPAT, 'UTF-8')));
$node->setAttribute('type', 'public');
$node->setAttribute('advice', 'update');
}
// Add pub IDs by plugin
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $deployment->getContext()->getId());
foreach ($pubIdPlugins as $pubIdPlugin) {
$this->addPubIdentifier($doc, $revisionNode, $submissionFile, $pubIdPlugin->getPubIdType());
}
// Also add DOI
$this->addPubIdentifier($doc, $revisionNode, $submissionFile, 'doi');
}
/**
* Add a single pub ID element for a given plugin to the document.
*
* @param \DOMDocument $doc
* @param \DOMElement $revisionNode
* @param SubmissionFile $submissionFile
*
* @return ?\DOMElement
*/
public function addPubIdentifier($doc, $revisionNode, $submissionFile, $pubIdType)
{
$pubId = $submissionFile->getStoredPubId($pubIdType);
if ($pubId) {
$deployment = $this->getDeployment();
$revisionNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', htmlspecialchars($pubId, ENT_COMPAT, 'UTF-8')));
$node->setAttribute('type', $pubIdType);
$node->setAttribute('advice', 'update');
return $node;
}
return null;
}
/**
* Get the submission file element name
*/
public function getSubmissionFileElementName()
{
return 'submission_file';
}
}
@@ -0,0 +1,223 @@
<?php
/**
* @file plugins/importexport/native/filter/SubmissionNativeXmlFilter.php
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class SubmissionNativeXmlFilter
*
* @ingroup plugins_importexport_native
*
* @brief Base class that converts a set of submissions to a Native XML document
*/
namespace PKP\plugins\importexport\native\filter;
use APP\facades\Repo;
use APP\submission\Submission;
use DOMDocument;
use DOMElement;
use PKP\core\PKPApplication;
use PKP\db\DAORegistry;
use PKP\filter\FilterGroup;
use PKP\plugins\importexport\PKPImportExportFilter;
use PKP\submissionFile\SubmissionFile;
use PKP\workflow\WorkflowStageDAO;
class SubmissionNativeXmlFilter extends NativeExportFilter
{
public $_includeSubmissionsNode;
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML submission export');
parent::__construct($filterGroup);
}
//
// Implement template methods from Filter
//
/**
* @see Filter::process()
*
* @param array $submissions Array of submissions
*
* @return DOMDocument
*/
public function &process(&$submissions)
{
// Create the XML document
$doc = new DOMDocument('1.0', 'utf-8');
$doc->preserveWhiteSpace = false;
$doc->formatOutput = true;
$deployment = $this->getDeployment();
if (count($submissions) == 1 && !$this->getIncludeSubmissionsNode()) {
// Only one submission specified; create root node
$rootNode = $this->createSubmissionNode($doc, $submissions[0]);
} else {
// Multiple submissions; wrap in a <submissions> element
$rootNode = $doc->createElementNS($deployment->getNamespace(), $deployment->getSubmissionsNodeName());
foreach ($submissions as $submission) {
$rootNode->appendChild($this->createSubmissionNode($doc, $submission));
}
}
$doc->appendChild($rootNode);
$rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
$rootNode->setAttribute('xsi:schemaLocation', $deployment->getNamespace() . ' ' . $deployment->getSchemaFilename());
return $doc;
}
//
// Submission conversion functions
//
/**
* Create and return a submission node.
*
* @param \DOMDocument $doc
* @param Submission $submission
*
* @return \DOMElement
*/
public function createSubmissionNode($doc, $submission)
{
// Create the root node and attributes
$deployment = $this->getDeployment();
$deployment->setSubmission($submission);
$submissionNode = $doc->createElementNS($deployment->getNamespace(), $deployment->getSubmissionNodeName());
$submissionNode->setAttribute('locale', $submission->getData('locale'));
$submissionNode->setAttribute('date_submitted', date('Y-m-d', strtotime($submission->getData('dateSubmitted'))));
$submissionNode->setAttribute('status', $submission->getData('status'));
$submissionNode->setAttribute('submission_progress', $submission->getData('submissionProgress'));
$submissionNode->setAttribute('current_publication_id', $submission->getData('currentPublicationId'));
$workflowStageDao = DAORegistry::getDAO('WorkflowStageDAO'); /** @var WorkflowStageDAO $workflowStageDao */
$submissionNode->setAttribute('stage', WorkflowStageDAO::getPathFromId($submission->getData('stageId')));
$this->addIdentifiers($doc, $submissionNode, $submission);
$this->addFiles($doc, $submissionNode, $submission);
$this->addPublications($doc, $submissionNode, $submission);
return $submissionNode;
}
/**
* Create and add identifier nodes to a submission node.
*
* @param \DOMDocument $doc
* @param \DOMElement $submissionNode
* @param Submission $submission
*/
public function addIdentifiers($doc, $submissionNode, $submission)
{
$deployment = $this->getDeployment();
// Add internal ID
$submissionNode->appendChild($node = $doc->createElementNS($deployment->getNamespace(), 'id', $submission->getId()));
$node->setAttribute('type', 'internal');
$node->setAttribute('advice', 'ignore');
}
/**
* Add the submission files to its DOM element.
*/
public function addFiles(DOMDocument $doc, DOMElement $submissionNode, Submission $submission): void
{
$submissionFiles = Repo::submissionFile()
->getCollector()
->filterBySubmissionIds([$submission->getId()])
->includeDependentFiles()
->getMany();
$deployment = $this->getDeployment();
foreach ($submissionFiles as $submissionFile) {
// Skip files attached to objects that are not included in the export,
// such as files uploaded to discussions and files uploaded by reviewers
$excludedFileStages = [
SubmissionFile::SUBMISSION_FILE_QUERY,
SubmissionFile::SUBMISSION_FILE_NOTE,
SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT,
SubmissionFile::SUBMISSION_FILE_REVIEW_FILE,
SubmissionFile::SUBMISSION_FILE_REVIEW_ATTACHMENT,
SubmissionFile::SUBMISSION_FILE_REVIEW_REVISION,
SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_FILE,
SubmissionFile::SUBMISSION_FILE_INTERNAL_REVIEW_REVISION
];
if (in_array($submissionFile->getData('fileStage'), $excludedFileStages)) {
$deployment->addWarning(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.native.error.submissionFileSkipped', ['id' => $submissionFile->getId()]));
continue;
}
$currentFilter = PKPImportExportFilter::getFilter('SubmissionFile=>native-xml', $this->getDeployment(), $this->opts);
$submissionFileDoc = $currentFilter->execute($submissionFile, true);
if ($submissionFileDoc) {
$clone = $doc->importNode($submissionFileDoc->documentElement, true);
$submissionNode->appendChild($clone);
}
}
}
/**
* Add the submission files to its DOM element.
*
* @param \DOMDocument $doc
* @param \DOMElement $submissionNode
* @param Submission $submission
*/
public function addPublications($doc, $submissionNode, $submission)
{
$currentFilter = PKPImportExportFilter::getFilter('publication=>native-xml', $this->getDeployment());
$publications = $submission->getData('publications');
foreach ($publications as $publication) {
$publicationDoc = $currentFilter->execute($publication);
if ($publicationDoc && $publicationDoc->documentElement instanceof DOMElement) {
$clone = $doc->importNode($publicationDoc->documentElement, true);
$submissionNode->appendChild($clone);
} else {
$deployment = $this->getDeployment();
$deployment->addError(PKPApplication::ASSOC_TYPE_SUBMISSION, $submission->getId(), __('plugins.importexport.publication.exportFailed'));
throw new \Exception(__('plugins.importexport.publication.exportFailed'));
}
}
}
//
// Abstract methods for subclasses to implement
//
/**
* Sets a flag to always include the <submissions> node, even if there
* may only be one submission.
*
* @param bool $includeSubmissionsNode
*/
public function setIncludeSubmissionsNode($includeSubmissionsNode)
{
$this->_includeSubmissionsNode = $includeSubmissionsNode;
}
/**
* Returns whether to always include the <submissions> node, even if there
* may only be one submission.
*
* @return bool $includeSubmissionsNode
*/
public function getIncludeSubmissionsNode()
{
return $this->_includeSubmissionsNode;
}
}
@@ -0,0 +1,390 @@
<?xml version="1.0"?>
<!--
* plugins/importexport/native/pkp-native.xsd
*
* 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.
*
* Schema describing native XML import/export elements shared across PKP applications
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://pkp.sfu.ca" xmlns:pkp="http://pkp.sfu.ca" elementFormDefault="qualified">
<!-- Bring in the common PKP import/export content -->
<include schemaLocation="../../../xml/importexport.xsd" />
<!-- *********
- * Types *
- ********* -->
<!--
- Basic Types
-->
<!-- Identifies a MIME type -->
<simpleType name="mimeType">
<restriction base="normalizedString" />
</simpleType>
<!-- Identifies a user group -->
<simpleType name="user_group_ref">
<restriction base="normalizedString" />
</simpleType>
<!-- Identifies a user name -->
<simpleType name="username">
<restriction base="normalizedString" />
</simpleType>
<!--
- File-related Types
-->
<!-- Describes a filename -->
<simpleType name="filename">
<restriction base="normalizedString">
</restriction>
</simpleType>
<!-- A remote tag defining an remotely hosted representation -->
<complexType name="remote">
<attribute name="src" type="anyURI" />
</complexType>
<!-- An href tag defining an external URL file resource -->
<complexType name="href">
<attribute name="src" type="anyURI" />
<attribute name="mime_type" type="pkp:mimeType" />
</complexType>
<!-- An embed tag defining an encoded, embedded file resource -->
<complexType name="embed" mixed="true">
<attribute name="encoding" use="required">
<simpleType>
<restriction base="string">
<enumeration value="base64" />
</restriction>
</simpleType>
</attribute>
<attribute name="mime_type" type="pkp:mimeType" />
</complexType>
<!--
- Encapsulate a file, which can be imported from an href,
- or embedded directly in the XML document.
-->
<group name="fileContents">
<choice>
<element name="href" type="pkp:href" />
<element name="embed" type="pkp:embed" />
</choice>
</group>
<complexType name="submission_file">
<sequence>
<element ref="pkp:id" minOccurs="0" maxOccurs="unbounded" />
<element name="creator" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="description" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="name" type="pkp:localizedNode" minOccurs="1" maxOccurs="unbounded" />
<element name="publisher" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="source" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="sponsor" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="subject" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="submission_file_ref" type="pkp:submission_file_ref" minOccurs="0" maxOccurs="1" />
<element name="file" minOccurs="1" maxOccurs="unbounded">
<complexType>
<sequence>
<group ref="pkp:fileContents" />
</sequence>
<attribute name="id" type="int" />
<attribute name="filesize" type="int" />
<attribute name="extension" type="string" />
</complexType>
</element>
</sequence>
<attribute name="caption" type="string" />
<attribute name="copyright_owner" type="string" />
<attribute name="created_at" type="date" />
<attribute name="credit" type="string" />
<attribute name="date_created" type="string" />
<attribute name="direct_sales_price" type="decimal" />
<attribute name="file_id" type="int" />
<attribute name="genre" type="normalizedString" />
<attribute name="id" type="int" />
<attribute name="language" type="string" />
<attribute name="sales_type" type="string" />
<attribute name="source_submission_file_id" type="int" />
<attribute name="stage">
<simpleType>
<restriction base="string">
<enumeration value="public" />
<enumeration value="submission" />
<enumeration value="note" />
<enumeration value="review_file" />
<enumeration value="review_attachment" />
<enumeration value="final" />
<enumeration value="fair_copy" />
<enumeration value="editor" />
<enumeration value="copyedit" />
<enumeration value="proof" />
<enumeration value="production_ready" />
<enumeration value="attachment" />
<enumeration value="query" />
<enumeration value="review_revision" />
<enumeration value="dependent" />
</restriction>
</simpleType>
</attribute>
<attribute name="terms" type="string" />
<attribute name="updated_at" type="date" />
<attribute name="uploader" type="pkp:username" />
<attribute name="viewable" type="boolean" />
</complexType>
<!-- A reference to a submission file that is declared elsewhere -->
<complexType name="submission_file_ref">
<attribute name="id" type="int" />
</complexType>
<!--
- User-related Elements
-->
<!-- A user group -->
<complexType name="user_group">
<sequence>
<element name="role_id" type="int" />
<element name="context_id" type="int" />
<element name="is_default" type="boolean" />
<element name="show_title" type="boolean" />
<element name="permit_self_registration" type="boolean" />
<element name="permit_metadata_edit" type="boolean" />
<element name="name" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="abbrev" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="stage_assignments" type="string" minOccurs="1" maxOccurs="1" />
</sequence>
</complexType>
<!-- Permit "user_group" as a root element -->
<element name="user_group" type="pkp:user_group" />
<!-- A user group -->
<complexType name="user_groups">
<sequence>
<element name="user_group" type="pkp:user_group" maxOccurs="unbounded" />
</sequence>
</complexType>
<!-- Permit "user_groups" as a root element -->
<element name="user_groups" type="pkp:user_groups" />
<!-- An identity (e.g. user, author) -->
<complexType name="identity" abstract="true">
<sequence>
<element name="givenname" type="pkp:localizedNode" minOccurs="1" maxOccurs="unbounded" />
<element name="familyname" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="affiliation" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="country" type="string" minOccurs="0" maxOccurs="1" />
<element name="email" type="string" />
<element name="url" type="anyURI" minOccurs="0" maxOccurs="1" />
<element name="orcid" type="anyURI" minOccurs="0" maxOccurs="1" />
<element name="biography" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
<complexType name="author">
<complexContent>
<extension base="pkp:identity">
<attribute name="primary_contact" type="boolean" default="false" />
<attribute name="user_group_ref" type="pkp:user_group_ref" use="required" />
<attribute name="include_in_browse" type="boolean" default="true" />
<attribute name="seq" type="int" use="required" />
<attribute name="id" type="int" use="required" />
</extension>
</complexContent>
</complexType>
<!--
- Representation-related types
-->
<complexType name="representation">
<sequence>
<element ref="pkp:id" minOccurs="0" maxOccurs="unbounded" />
<element name="name" type="pkp:localizedNode" minOccurs="1" maxOccurs="unbounded" />
<element name="seq" type="int" minOccurs="1" maxOccurs="1" />
<choice>
<element name="submission_file_ref" type="pkp:submission_file_ref" minOccurs="0" maxOccurs="unbounded" />
<element name="remote" type="pkp:remote" minOccurs="0" maxOccurs="1" />
</choice>
</sequence>
<attribute name="locale" type="string" use="optional" />
<attribute name="url_path" type="string" use="optional" />
</complexType>
<complexType name="submission">
<sequence>
<element ref="pkp:id" minOccurs="0" maxOccurs="unbounded" />
<!-- Metadata -->
<element ref="pkp:submission_file" minOccurs="0" maxOccurs="unbounded" />
<element ref="pkp:pkppublication" minOccurs="1" maxOccurs="unbounded" />
</sequence>
<attribute name="status" type="string" use="optional" />
<attribute name="current_publication_id" type="int" use="optional" />
<attribute name="date_submitted" type="date" use="optional" />
<attribute name="submission_progress" type="string" use="optional" />
<attribute name="locale" type="string" use="optional" />
</complexType>
<complexType name="pkppublication">
<sequence>
<element ref="pkp:id" minOccurs="0" maxOccurs="unbounded" />
<!-- Metadata -->
<element ref="pkp:title" minOccurs="1" maxOccurs="unbounded" />
<element name="prefix" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="subtitle" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="abstract" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="coverage" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="type" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="source" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="rights" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="licenseUrl" type="anyURI" minOccurs="0" maxOccurs="1" />
<element name="copyrightHolder" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="copyrightYear" type="int" minOccurs="0" maxOccurs="1" />
<element ref="pkp:keywords" minOccurs="0" maxOccurs="unbounded" />
<element ref="pkp:agencies" minOccurs="0" maxOccurs="unbounded" />
<element ref="pkp:languages" minOccurs="0" maxOccurs="unbounded" />
<element ref="pkp:disciplines" minOccurs="0" maxOccurs="unbounded" />
<element ref="pkp:subjects" minOccurs="0" maxOccurs="unbounded" />
<element ref="pkp:authors" minOccurs="0" maxOccurs="1" />
<element ref="pkp:representation" minOccurs="0" maxOccurs="unbounded" />
<element ref="pkp:citations" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="date_submitted" type="date" use="optional" />
<attribute name="date_published" type="date" use="optional" />
<attribute name="version" type="int" use="optional" />
<attribute name="status" type="int" use="optional" />
<attribute name="primary_contact_id" type="int" use="optional" />
<attribute name="url_path" type="string" use="optional" />
</complexType>
<!-- ************
- * Elements *
- ************ -->
<!--
- Identifier elements
-->
<element name="id">
<complexType mixed="true">
<attribute name="type" type="string" use="optional" />
<attribute name="advice" default="ignore">
<simpleType>
<restriction base="string">
<enumeration value="update" />
<enumeration value="ignore" />
</restriction>
</simpleType>
</attribute>
</complexType>
</element>
<!--
- Metadata element types
-->
<element name="title" type="pkp:localizedNode" />
<!--
- Composite / root elements
-->
<!-- Permit "submissions" as a root element -->
<element name="submissions" abstract="true">
<complexType>
<sequence>
<element ref="pkp:submission" minOccurs="0" maxOccurs="unbounded" />
</sequence>
</complexType>
</element>
<!-- Permit "authors" as a root element to keep the filters happy -->
<element name="authors">
<complexType>
<sequence>
<element name="author" type="pkp:author" minOccurs="1" maxOccurs="unbounded" />
</sequence>
</complexType>
</element>
<!-- Permit "author" as a root element to keep the filters happy -->
<element name="author" type="pkp:author" />
<!--
- Representation-related elements
-->
<!-- Permit "representation" as a root element to keep the filters happy -->
<element name="representation" type="pkp:representation" abstract="true" />
<element name="submission_file" type="pkp:submission_file" />
<!--
- Submission-related elements
-->
<!-- Permit "submission" as a root element -->
<element name="submission" type="pkp:submission" abstract="true" />
<element name="pkppublication" type="pkp:pkppublication" />
<!-- Controlled vocabularies -->
<element name="keywords">
<complexType>
<sequence>
<element name="keyword" type="string" minOccurs="1" maxOccurs="unbounded" />
</sequence>
<attribute name="locale" type="string" />
</complexType>
</element>
<element name="agencies">
<complexType>
<sequence>
<element name="agency" type="string" minOccurs="1" maxOccurs="unbounded" />
</sequence>
<attribute name="locale" type="string" />
</complexType>
</element>
<element name="languages">
<complexType>
<sequence>
<element name="language" type="string" minOccurs="1" maxOccurs="unbounded" />
</sequence>
<attribute name="locale" type="string" />
</complexType>
</element>
<element name="disciplines">
<complexType>
<sequence>
<element name="discipline" type="string" minOccurs="1" maxOccurs="unbounded" />
</sequence>
<attribute name="locale" type="string" />
</complexType>
</element>
<element name="subjects">
<complexType>
<sequence>
<element name="subject" type="string" minOccurs="1" maxOccurs="unbounded" />
</sequence>
<attribute name="locale" type="string" />
</complexType>
</element>
<element name="citations">
<complexType>
<sequence>
<element name="citation" type="string" minOccurs="0" maxOccurs="unbounded" />
</sequence>
<attribute name="locale" type="string" />
</complexType>
</element>
</schema>