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,469 @@
<?php
/**
* @file plugins/generic/usageEvent/PKPUsageEventPlugin.php
*
* Copyright (c) 2013-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 PKPUsageEventPlugin
*
* @ingroup plugins_generic_usageEvent
*
* @brief Base class for usage event plugin. Provide usage events to
* other statistics plugins.
*/
namespace PKP\plugins\generic\usageEvent;
use APP\core\Application;
use APP\core\PageRouter;
use APP\template\TemplateManager;
use PKP\config\Config;
use PKP\context\Context;
use PKP\core\Core;
use PKP\core\DataObject;
use PKP\core\PKPRequest;
use PKP\db\DAORegistry;
use PKP\plugins\GenericPlugin;
use PKP\plugins\Hook;
use PKP\plugins\PluginRegistry;
use PKP\security\Role;
use PKP\security\RoleDAO;
use PKP\template\PKPTemplateManager;
// User classification types.
define('USAGE_EVENT_PLUGIN_CLASSIFICATION_BOT', 'bot');
define('USAGE_EVENT_PLUGIN_CLASSIFICATION_ADMIN', 'administrative');
abstract class PKPUsageEventPlugin extends GenericPlugin
{
//
// Implement methods from PKPPlugin.
//
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
$success = parent::register($category, $path, $mainContextId);
if ($success) {
$eventHooks = $this->getEventHooks();
foreach ($eventHooks as $hook) {
Hook::add($hook, [$this, 'getUsageEvent']);
}
}
return $success;
}
/**
* @copydoc LazyLoadPlugin::getName()
*/
public function getName()
{
return 'usageeventplugin';
}
/**
* @copydoc Plugin::getInstallSitePluginSettingsFile()
*/
public function getInstallSitePluginSettingsFile()
{
return PKP_LIB_PATH . "/{$this->getPluginPath()}/settings.xml";
}
/**
* @copydoc Plugin::getDisplayName()
*/
public function getDisplayName()
{
return __('plugins.generic.usageEvent.displayName');
}
/**
* @copydoc Plugin::getDescription()
*/
public function getDescription()
{
return __('plugins.generic.usageEvent.description');
}
/**
* @copydoc LazyLoadPlugin::getEnabled()
*
* @param null|mixed $contextId
*/
public function getEnabled($contextId = null)
{
return true;
}
/**
* @copydoc Plugin::isSitePlugin()
*/
public function isSitePlugin()
{
return true;
}
/**
* @copydoc Plugin::getCanEnable()
*/
public function getCanEnable()
{
return false;
}
/**
* @copydoc Plugin::getCanDisable()
*/
public function getCanDisable()
{
return false;
}
//
// Public methods.
//
/**
* Get the unique site id.
*
* @return mixed string or null
*/
public function getUniqueSiteId()
{
return Application::get()->getUUID();
}
//
// Hook implementations.
//
/**
* Get usage event and pass it to the registered plugins, if any.
*/
public function getUsageEvent($hookName, $args)
{
// Check if we have a registration to receive the usage event.
if (Hook::getHooks('UsageEventPlugin::getUsageEvent')) {
$usageEvent = $this->buildUsageEvent($hookName, $args);
Hook::call('UsageEventPlugin::getUsageEvent', array_merge([$hookName, $usageEvent], $args));
}
return false;
}
//
// Protected methods.
//
/**
* Get all hooks that must be used to
* generate usage events.
*
* @return array
*/
protected function getEventHooks()
{
return [
'TemplateManager::display',
'FileManager::downloadFileFinished'
];
}
/**
* Get all hooks that define the
* finished file download.
*
* @return array
*/
protected function getDownloadFinishedEventHooks()
{
return [
'FileManager::downloadFileFinished'
];
}
/**
* Build an usage event.
*
* @param string $hookName
* @param array $args
*
* @return false|?array
*/
protected function buildUsageEvent($hookName, $args)
{
// Finished downloading a file?
if (in_array($hookName, $this->getDownloadFinishedEventHooks())) {
// The usage event for this request is already build and
// passed to any other registered hook.
return null;
}
$application = Application::get();
$request = $application->getRequest();
$router = $request->getRouter(); /** @var PageRouter $router */
$templateMgr = $args[0]; /** @var TemplateManager $templateMgr */
// We are just interested in page requests.
if (!($router instanceof PageRouter)) {
return false;
}
// Check whether we are in journal context.
$context = $router->getContext($request);
if (!$context) {
return false;
}
// Prepare request information.
[$pubObject, $downloadSuccess, $assocType, $idParams, $canonicalUrlPage, $canonicalUrlOp, $canonicalUrlParams] =
$this->getUsageEventData($hookName, $args, $request, $router, $templateMgr, $context);
if (!$pubObject) {
return false;
}
// Timestamp.
$time = Core::getCurrentDate();
// Actual document size, MIME type.
$htmlPageAssocTypes = $this->getHtmlPageAssocTypes();
if (in_array($assocType, $htmlPageAssocTypes)) {
// HTML pages with no file downloads.
$mimeType = 'text/html';
} elseif ($pubObject instanceof \APP\issue\IssueGalley) {
$mimeType = $pubObject->getFileType();
} else {
// Files.
$path = $pubObject->getData('path');
$mimeType = $pubObject->getData('mimetype');
}
$canonicalUrl = $router->url(
$request,
null,
$canonicalUrlPage,
$canonicalUrlOp,
$canonicalUrlParams
);
// Make sure we log the server name and not aliases.
$configBaseUrl = Config::getVar('general', 'base_url');
$requestBaseUrl = $request->getBaseUrl();
if ($requestBaseUrl !== $configBaseUrl) {
// Make sure it's not an url override (no alias on that case).
if (!in_array($requestBaseUrl, Config::getContextBaseUrls()) &&
$requestBaseUrl !== Config::getVar('general', 'base_url[index]')) {
// Alias found, replace it by base_url from config file.
// Make sure we use the correct base url override value for the context, if any.
$baseUrlReplacement = Config::getVar('general', 'base_url[' . $context->getPath() . ']');
if (!$baseUrlReplacement) {
$baseUrlReplacement = $configBaseUrl;
}
$canonicalUrl = str_replace($requestBaseUrl, $baseUrlReplacement, $canonicalUrl);
}
}
// Public identifiers.
// 1) A unique system internal ID that will help us to easily attribute
// statistics to a specific publication object.
array_unshift($idParams, 'c' . $context->getId());
$siteId = $this->getUniqueSiteId();
array_unshift($idParams, $siteId);
$applicationName = $application->getName();
$applicationId = $applicationName . ':' . implode('-', $idParams);
$idKey = 'other::' . $applicationName;
$identifiers = [$idKey => $applicationId];
// 2) Standardized public identifiers, e.g. DOI, URN, etc.
if ($this->isPubIdObjectType($pubObject)) {
$pubIdPlugins = PluginRegistry::loadCategory('pubIds', true, $context->getId());
if (!empty($pubIdPlugins)) {
foreach ($pubIdPlugins as $pubIdPlugin) {
if (!$pubIdPlugin->getEnabled()) {
continue;
}
$pubId = $pubObject->getStoredPubId($pubIdPlugin->getPubIdType());
if ($pubId) {
$identifiers[$pubIdPlugin->getPubIdType()] = $pubId;
}
}
}
// Handle DOIs separately
if ($context->areDoisEnabled()) {
$pubId = $pubObject->getStoredPubId('doi');
if ($pubId) {
$identifiers['doi'] = $pubId;
}
}
}
// Service URI.
$serviceUri = $router->url($request, $context->getPath());
// IP and Host.
$ip = $request->getRemoteAddr();
$host = null;
if (isset($_SERVER['REMOTE_HOST'])) {
// We do NOT actively look up the remote host to
// avoid the performance penalty. We only set the remote
// host if we get it "for free".
$host = $_SERVER['REMOTE_HOST'];
}
// HTTP user agent.
$userAgent = $request->getUserAgent();
// HTTP referrer.
$referrer = ($_SERVER['HTTP_REFERER'] ?? null);
// User and roles.
$user = $request->getUser();
$roles = [];
if ($user) {
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
$rolesByContext = $roleDao->getByUserIdGroupedByContext($user->getId());
foreach ([\PKP\core\PKPApplication::CONTEXT_SITE, $context->getId()] as $workingContext) {
if (isset($rolesByContext[$workingContext])) {
foreach ($rolesByContext[$workingContext] as $roleId => $role) {
$roles[] = $roleId;
}
}
}
}
// Try a simple classification of the request.
$classification = null;
if (!empty($roles)) {
// Access by editors, authors, etc.
$internalRoles = array_diff($roles, [Role::ROLE_ID_READER]);
if (!empty($internalRoles)) {
$classification = USAGE_EVENT_PLUGIN_CLASSIFICATION_ADMIN;
}
}
if ($request->isBot()) {
// The bot classification overwrites other classifications.
$classification = USAGE_EVENT_PLUGIN_CLASSIFICATION_BOT;
}
// TODO: Classify LOCKSS or similar as 'internal' access.
/*
* Comparison of our event log format with Apache log parameters...
*
* 1) default parameters:
* %h: remote hostname or IP => $ip, $host
* %l: remote logname (identd) => not supported, see $user, $roles instead
* %u: remote user => not supported, see $user, $roles instead
* %t: request time => $time
* %r: query => derived objects: $pubObject, $assocType, $canonicalUrl, $identifiers, $serviceUri, $classification
* %s: status => not supported (always 200 in our case)
*
* 2) other common parameters
* %O: bytes sent => not supported (cannot be reliably determined from within PHP)
* %X: connection status => $downloadSuccess (not reliable!)
* %{ContentType}o: => $mimeType
* %{User-agent}i: => $userAgent
* %{Referer}i: => $referrer
*
* Several items, e.g. time etc., may differ from what Apache
* would actually log. But the differences do not matter for our use
* cases.
*/
// Collect all information into an array.
$usageEvent = compact(
'time',
'pubObject',
'assocType',
'canonicalUrl',
'mimeType',
'identifiers',
'downloadSuccess',
'serviceUri',
'ip',
'host',
'user',
'roles',
'userAgent',
'referrer',
'classification'
);
return $usageEvent;
}
/**
* Get usage event details based on the passed hook.
* Subclasses should extend to implement application specifics.
*
* @param string $hookName
* @param array $hookArgs
* @param PKPRequest $request
* @param PageRouter $router
* @param PKPTemplateManager $templateMgr
* @param Context $context
*
* @return array With the following data:
* DataObject the published object, boolean download success, integer used published object assoc type,
* string used published object id foreign keys lookup (all parent associated objects id,
* preceded with a single letter to identify the object), string canonical url page,
* string canonical url operation, array with canonical url parameters.
*
* @see PKPUsageEventPlugin::buildUsageEvent()
*/
protected function getUsageEventData($hookName, $hookArgs, $request, $router, $templateMgr, $context)
{
$nullVar = null;
$pubObject = $nullVar;
$downloadSuccess = false;
$canonicalUrlPage = $canonicalUrlOp = $assocType = null;
$canonicalUrlParams = $idParams = [];
if ($hookName == 'TemplateManager::display') {
$page = $router->getRequestedPage($request);
$op = $router->getRequestedOp($request);
// First check for a context index page view.
if (($page == 'index' || empty($page)) && $op == 'index') {
$pubObject = $templateMgr->getTemplateVars('currentContext');
if ($pubObject instanceof Context) {
$assocType = Application::getContextAssocType();
$canonicalUrlOp = '';
$canonicalUrlPage = 'index';
$downloadSuccess = true;
}
}
}
return [$pubObject, $downloadSuccess, $assocType, $idParams, $canonicalUrlPage, $canonicalUrlOp, $canonicalUrlParams];
}
//
// Abstract protected methods.
//
/**
* Get all assoc types that have their usage event
* produced by html page access.
*
* @return array
*/
abstract protected function getHtmlPageAssocTypes();
/**
* Whether or not the passed object is of a type that can have
* different public identifiers, like DOI, URN, etc.
*
* @param DataObject $pubObject
*
* @return bool
*/
abstract protected function isPubIdObjectType($pubObject);
}
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:23+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:23+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "حدث الاستعمال"
msgid "plugins.generic.usageEvent.description"
msgstr "ينشئ متلقياً يوفر حدث الاستعمال بصيغة معينة."
@@ -0,0 +1,21 @@
# Osman Durmaz <osmandurmaz@hotmail.de>, 2023.
msgid ""
msgstr ""
"PO-Revision-Date: 2023-06-02 19:20+0000\n"
"Last-Translator: Osman Durmaz <osmandurmaz@hotmail.de>\n"
"Language-Team: Azerbaijani <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/az/>\n"
"Language: az\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.13.1\n"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Müəyyən edilmiş bir formatda istifadə fəaliyyəti təqdim edən bir link "
"yaradar."
msgid "plugins.generic.usageEvent.displayName"
msgstr "İstifadə fəaliyyəti"
@@ -0,0 +1,21 @@
# Cyril Kamburov <cc@intermedia.bg>, 2021.
msgid ""
msgstr ""
"PO-Revision-Date: 2021-10-13 20:55+0000\n"
"Last-Translator: Cyril Kamburov <cc@intermedia.bg>\n"
"Language-Team: Bulgarian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/bg_BG/>\n"
"Language: bg_BG\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Модул (плъгин) за събития при употреба"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Създава кука (точка на обработка), която предоставя събитие за използване в "
"определен формат."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2021-01-21 12:53+0000\n"
"Last-Translator: Kazimir Hrastek <kazimir3385@gmail.com>\n"
"Language-Team: Bosnian <http://translate.pkp.sfu.ca/projects/pkp-lib/generic-"
"usageEvent/bs_BA/>\n"
"Language: bs_BA\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Događaj upotrebe"
msgid "plugins.generic.usageEvent.description"
msgstr "Stvara kuku koja pruža događaj korištenja u definisanom formatu."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:24+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:24+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Esdeveniment d’ús"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Crea un enllaç que proporciona un esdeveniment d’ús en un format definit."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2020-04-21 15:38+0000\n"
"Last-Translator: Hewa Salam Khalid <hewa.salam@koyauniversity.org>\n"
"Language-Team: Kurdish <http://translate.pkp.sfu.ca/projects/pkp-lib/generic-"
"usageEvent/ku_IQ/>\n"
"Language: ku_IQ\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "ڕوداوی بەکارهێنان"
msgid "plugins.generic.usageEvent.description"
msgstr ".قولاپێک دروست بکە کە هاوکاری ڕێکخستنی ڕوداو بە شێوازی پێناسەکراو بکات"
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:23+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:23+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Událost využívání"
msgid "plugins.generic.usageEvent.description"
msgstr "Vytvoří hook, který poskytuje událost využití v definovaném formátu."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:24+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:24+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Brugshændelse"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Opretter en 'hook' (overstyringsfunktion), der leverer brugshændelse i et "
"defineret format."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-30T07:05:58-07:00\n"
"PO-Revision-Date: 2019-09-30T07:05:58-07:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Nutzungsereignis"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Legt einen Hook an, der ein Nutzungsereignis in einem definierten Format "
"liefert."
@@ -0,0 +1,8 @@
# Weblate Admin <alec@smecher.bc.ca>, 2023.
msgid ""
msgstr ""
"Language: dsb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Weblate\n"
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:24+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:24+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Συμβάν Χρήσης"
msgid "plugins.generic.usageEvent.description"
msgstr "Αυτό το Πρόσθετο παρέχει συμβάντα χρήσης σε προκαθορισμένη μορφή."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-30T07:05:58-07:00\n"
"PO-Revision-Date: 2019-09-30T07:05:58-07:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Usage event"
msgid "plugins.generic.usageEvent.description"
msgstr "Creates a hook that provides usage event in a defined format."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:24+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:24+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Caso de uso"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Crea un \"hook\" que proporciona el uso de eventos en un formato definido."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:24+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:24+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "رخداد کارکرد"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"این افزونه این امکان را فراهم می سازد تا رخداد کارکرد را در فرمت تعریف شده "
"تولید کرد."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:24+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:24+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Käyttötapahtuma"
msgid "plugins.generic.usageEvent.description"
msgstr "Luo koukun, joka tarjoaa käyttötapahtuman annetussa muodossa."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-30T07:05:58-07:00\n"
"PO-Revision-Date: 2019-09-30T07:05:58-07:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Journal d'événement"
msgid "plugins.generic.usageEvent.description"
msgstr "Créer un lien vers le journal d'événement dans un format particulier."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2020-08-27 15:48+0000\n"
"Last-Translator: Paul Heckler <paul.d.heckler@gmail.com>\n"
"Language-Team: French <http://translate.pkp.sfu.ca/projects/pkp-lib/generic-"
"usageEvent/fr/>\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Évènement d'utilisation"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Créé un connecteur qui fournit les évènements d'utilisation dans un format "
"défini."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2021-02-05 14:53+0000\n"
"Last-Translator: Real Academia Galega <reacagal@gmail.com>\n"
"Language-Team: Galician <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/gl_ES/>\n"
"Language: gl_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Caso de uso"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Crea un \"hook\" que proporciona o uso de eventos nun formato definido."
@@ -0,0 +1,20 @@
# Maja Jurić <maja-juric@windowslive.com>, 2023.
msgid ""
msgstr ""
"PO-Revision-Date: 2023-02-08 15:40+0000\n"
"Last-Translator: Maja Jurić <maja-juric@windowslive.com>\n"
"Language-Team: Croatian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/hr/>\n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.13.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Događaj korištenja"
msgid "plugins.generic.usageEvent.description"
msgstr "Stvara kuku koja vraća događaj korištenja u definiranom formatu."
@@ -0,0 +1,8 @@
# Weblate Admin <alec@smecher.bc.ca>, 2023.
msgid ""
msgstr ""
"Language: hsb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Weblate\n"
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-02-13T21:07:39+00:00\n"
"PO-Revision-Date: 2020-02-13T21:07:39+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Használati esemény"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Olyan kampót (hook) hoz létre, amely meghatározott formátumban biztosít "
"használati eseményt."
@@ -0,0 +1,21 @@
# Hovhannes Harutyunyan <hharutyunyan@gmail.com>, 2022.
msgid ""
msgstr ""
"PO-Revision-Date: 2022-01-13 14:25+0000\n"
"Last-Translator: Hovhannes Harutyunyan <hharutyunyan@gmail.com>\n"
"Language-Team: Armenian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/hy_AM/>\n"
"Language: hy_AM\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Օգտագործման իրադարձություն"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Ստեղծում է կեռիկ, որն ապահովում է օգտագործման իրադարձությունը սահմանված "
"ձևաչափով:"
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2020-02-12 08:50+0000\n"
"Last-Translator: Ramli Baharuddin <ramli.baharuddin@relawanjurnal.id>\n"
"Language-Team: Indonesian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/id_ID/>\n"
"Language: id_ID\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Aktivitas pemakaian"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Buat hook yang menyajikan aktivitas pemakaian sesuai format yang ditentukan."
@@ -0,0 +1,14 @@
# Kolbrun Reynisdottir <kolla@probus.is>, 2022.
msgid ""
msgstr ""
"Language: is_IS\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Weblate\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr ""
msgid "plugins.generic.usageEvent.description"
msgstr ""
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2021-03-08 12:54+0000\n"
"Last-Translator: Bjorn-Ole Kamm <pkp_trans@b-ok.de>\n"
"Language-Team: Japanese <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/ja_JP/>\n"
"Language: ja_JP\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "使用状況イベント"
msgid "plugins.generic.usageEvent.description"
msgstr "定義された形式で使用状況イベントを提供するフックを作成します。"
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2021-04-07 15:28+0000\n"
"Last-Translator: Dimitri Gogelia <dimitri.gogelia@iliauni.edu.ge>\n"
"Language-Team: Georgian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/ka_GE/>\n"
"Language: ka_GE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "პლაგინი „გამოყენების მოვლენა“"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"ქმნის დამმუშავებელს, რომელიც უზრუნველყოფს გამოყენების მოვლენას განსაზღვრულ "
"ფორმატში."
@@ -0,0 +1,3 @@
# Mahmut VURAL <mahmut.vural@outlook.com>, 2024.
msgid ""
msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit"
@@ -0,0 +1,20 @@
# Ieva Tiltina <pastala@gmail.com>, 2023.
msgid ""
msgstr ""
"PO-Revision-Date: 2023-09-22 13:06+0000\n"
"Last-Translator: Ieva Tiltina <pastala@gmail.com>\n"
"Language-Team: Latvian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/lv/>\n"
"Language: lv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n % 10 == 0 || n % 100 >= 11 && n % 100 <= "
"19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n"
"X-Generator: Weblate 4.13.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Lietošanas notikums"
msgid "plugins.generic.usageEvent.description"
msgstr "Izveido aizķeri, kas nodrošina lietošanas notikumu noteiktā formātā."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2020-07-29 18:46+0000\n"
"Last-Translator: Jovan Jonovski <jonovski@hotmail.com>\n"
"Language-Team: Macedonian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/mk/>\n"
"Language: mk\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n==1 || n%10==1 ? 0 : 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Искористеност"
msgid "plugins.generic.usageEvent.description"
msgstr "Создава јадица што обезбедува настан за употреба во дефиниран формат."
@@ -0,0 +1,21 @@
# Studiorimau <studiorimau@gmail.com>, 2021.
msgid ""
msgstr ""
"PO-Revision-Date: 2021-10-29 07:29+0000\n"
"Last-Translator: Studiorimau <studiorimau@gmail.com>\n"
"Language-Team: Malay <http://translate.pkp.sfu.ca/projects/pkp-lib/generic-"
"usageEvent/ms/>\n"
"Language: ms\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Aktiviti penggunaan"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Mencipta hook yang menyediakan aktiviti penggunaan dalam format yang "
"ditentukan."
@@ -0,0 +1,22 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-03-20T15:57:55+00:00\n"
"PO-Revision-Date: 2021-01-26 10:17+0000\n"
"Last-Translator: FRITT, University of Oslo Library <fritt-"
"info@journals.uio.no>\n"
"Language-Team: Norwegian Bokmål <http://translate.pkp.sfu.ca/projects/"
"pkp-lib/generic-usageEvent/nb_NO/>\n"
"Language: nb_NO\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Brukshendelse"
msgid "plugins.generic.usageEvent.description"
msgstr "Lager en hake som leverer brukshendelse i et definert format."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:24+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:24+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Gebruiksgebeurtenis"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Maakt een hook die een gebruiksgebeurtenis rapporteert in een gedefinieerd "
"formaat."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:24+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:24+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Zdarzenie użycia"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Utwórz zaczepienie, które dostarczy zdarzenie użycia w zdefiniowanym "
"formacie."
@@ -0,0 +1,21 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-30T11:54:39-07:00\n"
"PO-Revision-Date: 2020-06-06 02:37+0000\n"
"Last-Translator: Diego José Macêdo <diegojmacedo@gmail.com>\n"
"Language-Team: Portuguese (Brazil) <http://translate.pkp.sfu.ca/projects/"
"pkp-lib/generic-usageEvent/pt_BR/>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Evento de uso"
msgid "plugins.generic.usageEvent.description"
msgstr "Cria um conector que oferece eventos de uso em um formato definido."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:25+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:25+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Eventos de Uso"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Cria um mecanismo que providencia eventos de utilização num formado definido."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:25+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:25+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Модуль «Событие использования»"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Создает обработчик, который выдает событие использования сайта в "
"определенном формате."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:25+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:25+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Dogodek uporabe"
msgid "plugins.generic.usageEvent.description"
msgstr "Ustvari proženje dogodka uporabe v definirani obliki."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2020-05-27 12:39+0000\n"
"Last-Translator: Viveka Svensson <viveka.svensson@lnu.se>\n"
"Language-Team: Swedish <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/sv/>\n"
"Language: sv_SE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Användningshändelse"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Skapar en hook som matar ut användningshändelser i ett definerat format."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:25+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:25+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Kullanım Etkinliği"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Tanımlanmış bir biçimde kullanım etkinliği sağlayan bir bağlantı oluşturur."
@@ -0,0 +1,22 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:25+00:00\n"
"PO-Revision-Date: 2020-04-18 18:17+0000\n"
"Last-Translator: Fylypovych Georgii <red.ukr@gmail.com>\n"
"Language-Team: Ukrainian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/uk/>\n"
"Language: uk_UA\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<="
"4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Плагін \"Подія використання\""
msgid "plugins.generic.usageEvent.description"
msgstr "Створює обробник, який видає подію використання сайту у визначеному форматі."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2020-08-14 13:48+0000\n"
"Last-Translator: Tran Ngoc Trung <khuchatthienduong@gmail.com>\n"
"Language-Team: Vietnamese <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"generic-usageEvent/vi_VN/>\n"
"Language: vi_VN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr "Sự kiện sử dụng"
msgid "plugins.generic.usageEvent.description"
msgstr ""
"Tạo một cái móc mà cung cấp sự kiện sử dụng trong một định dạng xác định."
@@ -0,0 +1,14 @@
# Emma U <emmaupkp@gmail.com>, 2023.
msgid ""
msgstr ""
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Weblate\n"
msgid "plugins.generic.usageEvent.displayName"
msgstr ""
msgid "plugins.generic.usageEvent.description"
msgstr ""
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plugin_settings SYSTEM "../../../dtd/pluginSettings.dtd">
<!--
* plugins/generic/usageEvent/settings.xml
*
* Copyright (c) 2013-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* Default plugin settings.
*
-->
<plugin_settings>
<setting type="bool">
<name>enabled</name>
<value>true</value>
</setting>
</plugin_settings>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE version SYSTEM "../../../dtd/pluginVersion.dtd">
<!--
* plugins/generic/usageEvent/version.xml
*
* Copyright (c) 2013-2021 Simon Fraser University
* Copyright (c) 2003-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* Plugin version information.
-->
<version>
<application>usageEvent</application>
<type>plugins.generic</type>
<release>1.0.0.0</release>
<date>2013-04-15</date>
<sitewide>1</sitewide>
<class>UsageEventPlugin</class>
</version>
@@ -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>
@@ -0,0 +1,83 @@
<?php
/**
* @file plugins/importexport/users/PKPUserImportExportDeployment.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 PKPUserImportExportDeployment
*
* @ingroup plugins_importexport_user
*
* @brief Class configuring the user import/export process to this
* application's specifics.
*/
namespace PKP\plugins\importexport\users;
use APP\core\Application;
use PKP\context\Context;
use PKP\plugins\importexport\PKPImportExportDeployment;
use PKP\site\Site;
use PKP\user\User;
class PKPUserImportExportDeployment extends PKPImportExportDeployment
{
/** @var Site */
public $_site;
/**
* Constructor
*
* @param Context $context
* @param ?User $user
*/
public function __construct($context, $user)
{
parent::__construct($context, $user);
$site = Application::get()->getRequest()->getSite();
$this->setSite($site);
}
/**
* Set the site.
*
* @param Site $site
*/
public function setSite($site)
{
$this->_site = $site;
}
/**
* Get the site.
*
* @return Site
*/
public function getSite()
{
return $this->_site;
}
/**
* Get the schema filename.
*
* @return string
*/
public function getSchemaFilename()
{
return 'pkp-users.xsd';
}
/**
* Get the namespace URN
*
* @return string
*/
public function getNamespace()
{
return 'http://pkp.sfu.ca';
}
}
@@ -0,0 +1,304 @@
<?php
/**
* @file plugins/importexport/users/PKPUserImportExportPlugin.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 PKPUserImportExportPlugin
*
* @ingroup plugins_importexport_users
*
* @brief User XML import/export plugin
*/
namespace PKP\plugins\importexport\users;
use APP\facades\Repo;
use APP\template\TemplateManager;
use Exception;
use PKP\context\Context;
use PKP\core\JSONMessage;
use PKP\core\PKPRequest;
use PKP\db\DAORegistry;
use PKP\file\FileManager;
use PKP\file\TemporaryFileDAO;
use PKP\file\TemporaryFileManager;
use PKP\filter\Filter;
use PKP\filter\FilterDAO;
use PKP\plugins\ImportExportPlugin;
use PKP\user\User;
abstract class PKPUserImportExportPlugin extends ImportExportPlugin
{
/**
* @copydoc Plugin::register()
*
* @param null|mixed $mainContextId
*/
public function register($category, $path, $mainContextId = null)
{
$success = parent::register($category, $path, $mainContextId);
$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 'UserImportExportPlugin';
}
/**
* Get the display name.
*
* @return string
*/
public function getDisplayName()
{
return __('plugins.importexport.users.displayName');
}
/**
* Get the display description.
*
* @return string
*/
public function getDescription()
{
return __('plugins.importexport.users.description');
}
/**
* @copydoc ImportExportPlugin::getPluginSettingsPrefix()
*/
public function getPluginSettingsPrefix()
{
return 'users';
}
/**
* Display the plugin.
*
* @param array $args
* @param PKPRequest $request
*/
public function display($args, $request)
{
$templateMgr = TemplateManager::getManager($request);
$context = $request->getContext();
parent::display($args, $request);
$templateMgr->assign('plugin', $this);
switch (array_shift($args)) {
case 'index':
case '':
$templateMgr->display($this->getTemplateResource('index.tpl'));
break;
case 'uploadImportXML':
$user = $request->getUser();
$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');
return $json->getString();
case 'importBounce':
if (!$request->checkCSRF()) {
throw new Exception('CSRF mismatch!');
}
$json = new JSONMessage(true);
$json->setEvent('addTab', [
'title' => __('plugins.importexport.users.results'),
'url' => $request->url(null, null, null, ['plugin', $this->getName(), 'import'], ['temporaryFileId' => $request->getUserVar('temporaryFileId'), 'csrfToken' => $request->getSession()->getCSRFToken()]),
]);
header('Content-Type: application/json');
return $json->getString();
case 'import':
if (!$request->checkCSRF()) {
throw new Exception('CSRF mismatch!');
}
$temporaryFileId = $request->getUserVar('temporaryFileId');
$temporaryFileDao = DAORegistry::getDAO('TemporaryFileDAO'); /** @var TemporaryFileDAO $temporaryFileDao */
$user = $request->getUser();
$temporaryFile = $temporaryFileDao->getTemporaryFile($temporaryFileId, $user->getId());
if (!$temporaryFile) {
$json = new JSONMessage(true, __('plugins.importexport.users.uploadFile'));
header('Content-Type: application/json');
return $json->getString();
}
$temporaryFilePath = $temporaryFile->getFilePath();
libxml_use_internal_errors(true);
$filter = $this->getUserImportExportFilter($context, $user);
$users = $this->importUsers(file_get_contents($temporaryFilePath), $context, $user, $filter);
$validationErrors = array_filter(libxml_get_errors(), function ($a) {
return $a->level == LIBXML_ERR_ERROR || $a->level == LIBXML_ERR_FATAL;
});
$templateMgr->assign('validationErrors', $validationErrors);
libxml_clear_errors();
if ($filter->hasErrors()) {
$templateMgr->assign('filterErrors', $filter->getErrors());
}
$templateMgr->assign('users', $users);
$json = new JSONMessage(true, $templateMgr->fetch($this->getTemplateResource('results.tpl')));
header('Content-Type: application/json');
return $json->getString();
case 'export':
$filter = $this->getUserImportExportFilter($request->getContext(), $request->getUser(), false);
$exportXml = $this->exportUsers(
(array) $request->getUserVar('selectedUsers'),
$request->getContext(),
$request->getUser(),
$filter
);
$fileManager = new FileManager();
$exportFileName = $this->getExportFileName($this->getExportPath(), 'users', $context, '.xml');
$fileManager->writeFile($exportFileName, $exportXml);
$fileManager->downloadByPath($exportFileName);
$fileManager->deleteByPath($exportFileName);
break;
case 'exportAllUsers':
$filter = $this->getUserImportExportFilter($request->getContext(), $request->getUser(), false);
$exportXml = $this->exportAllUsers(
$request->getContext(),
$request->getUser(),
$filter
);
$fileManager = new FileManager();
$exportFileName = $this->getExportFileName($this->getExportPath(), 'users', $context, '.xml');
$fileManager->writeFile($exportFileName, $exportXml);
$fileManager->downloadByPath($exportFileName);
$fileManager->deleteByPath($exportFileName);
break;
default:
$dispatcher = $request->getDispatcher();
$dispatcher->handle404();
}
}
/**
* Get the XML for all of users.
*
* @param Context $context
* @param ?User $user
* @param Filter $filter byRef parameter - import/export filter used
*
* @return string XML contents representing the supplied user IDs.
*/
public function exportAllUsers($context, $user, &$filter = null)
{
$users = Repo::user()->getCollector()
->filterByContextIds([$context->getId()])
->getMany();
if (!$filter) {
$filter = $this->getUserImportExportFilter($context, $user, false);
}
return $this->exportUsers($users->toArray(), $context, $user, $filter);
}
/**
* Get the XML for a set of users.
*
* @param array $ids mixed Array of users or user IDs
* @param Context $context
* @param ?User $user
* @param Filter $filter byRef parameter - import/export filter used
*
* @return string XML contents representing the supplied user IDs.
*/
public function exportUsers($ids, $context, $user, &$filter = null)
{
$xml = '';
if (!$filter) {
$filter = $this->getUserImportExportFilter($context, $user, false);
}
$users = [];
foreach ($ids as $id) {
if ($id instanceof User) {
$users[] = $id;
} else {
$user = Repo::user()->get($id, true);
if ($user) {
$users[] = $user;
}
}
}
$userXml = $filter->execute($users);
if ($userXml) {
$xml = $userXml->saveXml();
} else {
fatalError('Could not convert users.');
}
return $xml;
}
/**
* Get the XML for a set of users.
*
* @param string $importXml XML contents to import
* @param Context $context
* @param ?User $user
* @param Filter $filter byRef parameter - import/export filter used
*
* @return array Set of imported users
*/
public function importUsers($importXml, $context, $user, &$filter = null)
{
if (!$filter) {
$filter = $this->getUserImportExportFilter($context, $user);
}
return $filter->execute($importXml);
}
/**
* Return user filter for import purposes
*
* @param Context $context
* @param ?User $user
* @param bool $isImport return Import Filter if true - export if false
*
* @return Filter
*/
public function getUserImportExportFilter($context, $user, $isImport = true)
{
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
if ($isImport) {
$userFilters = $filterDao->getObjectsByGroup('user-xml=>user');
} else {
$userFilters = $filterDao->getObjectsByGroup('user=>user-xml');
}
assert(count($userFilters) == 1); // Assert only a single unserialization filter
$filter = array_shift($userFilters);
$filter->setDeployment(new PKPUserImportExportDeployment($context, $user));
return $filter;
}
}
@@ -0,0 +1,133 @@
<?php
/**
* @file plugins/importexport/users/filter/NativeXmlUserGroupFilter.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 NativeXmlUserGroupFilter
*
* @ingroup plugins_importexport_users
*
* @brief Base class that converts a Native XML document to a set of user groups
*/
namespace PKP\plugins\importexport\users\filter;
use APP\facades\Repo;
use PKP\filter\FilterGroup;
use PKP\userGroup\relationships\UserGroupStage;
use PKP\userGroup\UserGroup;
class NativeXmlUserGroupFilter extends \PKP\plugins\importexport\native\filter\NativeImportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML user group import');
parent::__construct($filterGroup);
}
//
// Implement template methods from NativeImportFilter
//
/**
* Return the plural element name
*
* @return string
*/
public function getPluralElementName()
{
return 'user_groups';
}
/**
* Get the singular element name
*
* @return string
*/
public function getSingularElementName()
{
return 'user_group';
}
/**
* Handle a user_group element
*
* @param \DOMElement $node
*
* @return UserGroup Array of UserGroup objects
*/
public function handleElement($node)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
// Create the UserGroup object.
$userGroup = Repo::userGroup()->newDataObject();
$userGroup->setContextId($context->getId());
// Extract the name node element to see if this user group exists already.
$nodeList = $node->getElementsByTagNameNS($deployment->getNamespace(), 'name');
if ($nodeList->length > 0) {
$content = $this->parseLocalizedContent($nodeList->item(0)); // $content[1] contains the localized name.
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$context->getId()])
->getMany();
foreach ($userGroups as $testGroup) {
if (in_array($content[1], $testGroup->getName(null))) {
return $testGroup; // we found one with the same name.
}
}
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof \DOMElement) {
switch ($n->tagName) {
case 'role_id': $userGroup->setRoleId($n->textContent);
break;
case 'is_default': $userGroup->setDefault($n->textContent ?? false);
break;
case 'show_title': $userGroup->setShowTitle($n->textContent ?? true);
break;
case 'name': $userGroup->setName($n->textContent, $n->getAttribute('locale'));
break;
case 'abbrev': $userGroup->setAbbrev($n->textContent, $n->getAttribute('locale'));
break;
case 'permit_self_registration': $userGroup->setPermitSelfRegistration($n->textContent ?? false);
break;
case 'permit_metadata_edit': $userGroup->setPermitMetadataEdit($n->textContent ?? false);
break;
}
}
}
$userGroupId = Repo::userGroup()->add($userGroup);
$stageNodeList = $node->getElementsByTagNameNS($deployment->getNamespace(), 'stage_assignments');
if ($stageNodeList->length == 1) {
$n = $stageNodeList->item(0);
$assignedStages = preg_split('/:/', $n->textContent);
foreach ($assignedStages as $stage) {
if ($stage >= WORKFLOW_STAGE_ID_SUBMISSION && $stage <= WORKFLOW_STAGE_ID_PRODUCTION) {
UserGroupStage::create([
'contextId' => $context->getId(),
'userGroupId' => $userGroupId,
'stageId' => $stage
]);
}
}
}
return $userGroup;
} else {
fatalError('unable to find "name" userGroup node element. Check import XML document structure for validity.');
}
}
}
@@ -0,0 +1,174 @@
<?php
/**
* @file plugins/importexport/users/filter/PKPUserUserXmlFilter.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 PKPUserUserXmlFilter
*
* @brief Base class that converts a set of users to a User XML document
*/
namespace PKP\plugins\importexport\users\filter;
use APP\facades\Repo;
use DOMDocument;
use PKP\config\Config;
use PKP\db\DAORegistry;
use PKP\filter\FilterDAO;
use PKP\filter\FilterGroup;
use PKP\plugins\importexport\native\filter\NativeExportFilter;
use PKP\user\InterestManager;
use PKP\user\User;
class PKPUserUserXmlFilter extends NativeExportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('User XML user export');
parent::__construct($filterGroup);
}
//
// Implement template methods from Filter
//
/**
* @see Filter::process()
*
* @param array $users Array of users
*
* @return DOMDocument
*/
public function &process(&$users)
{
// Create the XML document
$doc = new DOMDocument('1.0', 'utf-8');
$deployment = $this->getDeployment();
$rootNode = $doc->createElementNS($deployment->getNamespace(), 'PKPUsers');
$this->addUserGroups($doc, $rootNode);
// Multiple users; wrap in a <users> element
$usersNode = $doc->createElementNS($deployment->getNamespace(), 'users');
foreach ($users as $user) {
$usersNode->appendChild($this->createPKPUserNode($doc, $user));
}
$rootNode->appendChild($usersNode);
$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;
}
//
// \PKP\author\Author conversion functions
//
/**
* Create and return a user node.
*
* @param DOMDocument $doc
* @param User $user
*
* @return \DOMElement
*/
public function createPKPUserNode($doc, $user)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
// Create the user node
$userNode = $doc->createElementNS($deployment->getNamespace(), 'user');
// Add metadata
$this->createLocalizedNodes($doc, $userNode, 'givenname', $user->getGivenName(null));
$this->createLocalizedNodes($doc, $userNode, 'familyname', $user->getFamilyName(null));
$this->createLocalizedNodes($doc, $userNode, 'affiliation', $user->getAffiliation(null));
$this->createOptionalNode($doc, $userNode, 'country', $user->getCountry());
$userNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'email', htmlspecialchars($user->getEmail(), ENT_COMPAT, 'UTF-8')));
$this->createOptionalNode($doc, $userNode, 'url', $user->getUrl());
$this->createOptionalNode($doc, $userNode, 'orcid', $user->getOrcid());
if (is_array($user->getBiography(null))) {
$this->createLocalizedNodes($doc, $userNode, 'biography', $user->getBiography(null));
}
$userNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'username', htmlspecialchars($user->getUsername(), ENT_COMPAT, 'UTF-8')));
$this->createOptionalNode($doc, $userNode, 'gossip', $user->getGossip());
if (is_array($user->getSignature(null))) {
$this->createLocalizedNodes($doc, $userNode, 'signature', $user->getSignature(null));
}
$passwordNode = $doc->createElementNS($deployment->getNamespace(), 'password');
$passwordNode->setAttribute('is_disabled', $user->getDisabled() ? 'true' : 'false');
$passwordNode->setAttribute('must_change', $user->getMustChangePassword() ? 'true' : 'false');
$passwordNode->setAttribute('encryption', Config::getVar('security', 'encryption'));
$passwordNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'value', htmlspecialchars($user->getPassword(), ENT_COMPAT, 'UTF-8')));
$userNode->appendChild($passwordNode);
$this->createOptionalNode($doc, $userNode, 'date_registered', $user->getDateRegistered());
$this->createOptionalNode($doc, $userNode, 'date_last_login', $user->getDateLastLogin());
$this->createOptionalNode($doc, $userNode, 'date_last_email', $user->getDateLastEmail());
$this->createOptionalNode($doc, $userNode, 'date_validated', $user->getDateValidated());
$this->createOptionalNode($doc, $userNode, 'inline_help', $user->getInlineHelp() ? 'true' : 'false');
$this->createOptionalNode($doc, $userNode, 'auth_string', $user->getAuthStr());
$this->createOptionalNode($doc, $userNode, 'phone', $user->getPhone());
$this->createOptionalNode($doc, $userNode, 'mailing_address', $user->getMailingAddress());
$this->createOptionalNode($doc, $userNode, 'billing_address', $user->getBillingAddress());
$this->createOptionalNode($doc, $userNode, 'locales', join(':', $user->getLocales()));
if ($user->getDisabled()) {
$this->createOptionalNode($doc, $userNode, 'disabled_reason', $user->getDisabledReason());
}
$userGroups = Repo::userGroup()->getCollector()
->filterByUserIds([$user->getId()])
->filterByContextIds([$context->getId()])
->getMany();
foreach ($userGroups as $userGroup) {
$userNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'user_group_ref', htmlspecialchars($userGroup->getName($context->getPrimaryLocale()), ENT_COMPAT, 'UTF-8')));
}
// Add Reviewing Interests, if any.
$interestManager = new InterestManager();
$interests = $interestManager->getInterestsString($user);
$this->createOptionalNode($doc, $userNode, 'review_interests', $interests);
return $userNode;
}
public function addUserGroups($doc, $rootNode)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
$userGroupsNode = $doc->createElementNS($deployment->getNamespace(), 'user_groups');
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$context->getId()])
->getMany();
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
$userGroupExportFilters = $filterDao->getObjectsByGroup('usergroup=>user-xml');
assert(count($userGroupExportFilters) == 1); // Assert only a single serialization filter
$exportFilter = array_shift($userGroupExportFilters);
$exportFilter->setDeployment($this->getDeployment());
$userGroupsArray = $userGroups->toArray();
$userGroupsDoc = $exportFilter->execute($userGroupsArray);
if ($userGroupsDoc->documentElement instanceof \DOMElement) {
$clone = $doc->importNode($userGroupsDoc->documentElement, true);
$rootNode->appendChild($clone);
}
}
}
@@ -0,0 +1,100 @@
<?php
/**
* @file plugins/importexport/users/filter/UserGroupNativeXmlFilter.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 UserGroupNativeXmlFilter
*
* @ingroup plugins_importexport_users
*
* @brief Base class that converts a set of user groups to a Native XML document
*/
namespace PKP\plugins\importexport\users\filter;
use APP\facades\Repo;
use DOMDocument;
use PKP\filter\FilterGroup;
use PKP\userGroup\UserGroup;
class UserGroupNativeXmlFilter extends \PKP\plugins\importexport\native\filter\NativeExportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('Native XML user group export');
parent::__construct($filterGroup);
}
//
// Implement template methods from Filter
//
/**
* @see Filter::process()
*
* @param array $userGroups Array of user groups
*
* @return DOMDocument
*/
public function &process(&$userGroups)
{
// Create the XML document
$doc = new DOMDocument('1.0', 'utf-8');
$deployment = $this->getDeployment();
// Multiple authors; wrap in a <authors> element
$rootNode = $doc->createElementNS($deployment->getNamespace(), 'user_groups');
foreach ($userGroups as $userGroup) {
$rootNode->appendChild($this->createUserGroupNode($doc, $userGroup));
}
$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;
}
//
// UserGroup conversion functions
//
/**
* Create and return a user group node.
*
* @param DOMDocument $doc
* @param UserGroup $userGroup
*
* @return \DOMElement
*/
public function createUserGroupNode($doc, $userGroup)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
// Create the user_group node
$userGroupNode = $doc->createElementNS($deployment->getNamespace(), 'user_group');
// Add metadata
$userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'role_id', $userGroup->getRoleId()));
$userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'context_id', $userGroup->getContextId()));
$userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'is_default', $userGroup->getDefault() ? 'true' : 'false'));
$userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'show_title', $userGroup->getShowTitle() ? 'true' : 'false'));
$userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'permit_self_registration', $userGroup->getPermitSelfRegistration() ? 'true' : 'false'));
$userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'permit_metadata_edit', $userGroup->getPermitMetadataEdit() ? 'true' : 'false'));
$this->createLocalizedNodes($doc, $userGroupNode, 'name', $userGroup->getName(null));
$this->createLocalizedNodes($doc, $userGroupNode, 'abbrev', $userGroup->getAbbrev(null));
$assignedStages = Repo::userGroup()->getAssignedStagesByUserGroupId($context->getId(), $userGroup->getId())->toArray();
$userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'stage_assignments', htmlspecialchars(join(':', $assignedStages), ENT_COMPAT, 'UTF-8')));
return $userGroupNode;
}
}
@@ -0,0 +1,393 @@
<?php
/**
* @file plugins/importexport/users/filter/UserXmlPKPUserFilter.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 UserXmlPKPUserFilter
*
* @ingroup plugins_importexport_users
*
* @brief Base class that converts a User XML document to a set of users
*/
namespace PKP\plugins\importexport\users\filter;
use APP\core\Application;
use APP\facades\Repo;
use Illuminate\Support\Facades\Mail;
use PKP\db\DAORegistry;
use PKP\filter\FilterDAO;
use PKP\filter\FilterGroup;
use PKP\mail\mailables\UserCreated;
use PKP\plugins\importexport\users\PKPUserImportExportDeployment;
use PKP\security\Validation;
use PKP\site\SiteDAO;
use PKP\user\InterestManager;
use PKP\user\User;
class UserXmlPKPUserFilter extends \PKP\plugins\importexport\native\filter\NativeImportFilter
{
/**
* Constructor
*
* @param FilterGroup $filterGroup
*/
public function __construct($filterGroup)
{
$this->setDisplayName('User XML user import');
parent::__construct($filterGroup);
}
//
// Implement template methods from NativeImportFilter
//
/**
* Return the plural element name
*
* @return string
*/
public function getPluralElementName()
{
return 'PKPUsers';
}
/**
* Handle a user_groups element
*
* @param \DOMElement $node
*
* @return array Array of UserGroup objects
*/
public function parseUserGroup($node)
{
$filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */
$importFilters = $filterDao->getObjectsByGroup('user-xml=>usergroup');
assert(count($importFilters) == 1); // Assert only a single unserialization filter
$importFilter = array_shift($importFilters);
$importFilter->setDeployment($this->getDeployment());
$userGroupDoc = new \DOMDocument('1.0', 'utf-8');
$userGroupDoc->appendChild($userGroupDoc->importNode($node, true));
return $importFilter->execute($userGroupDoc);
}
/**
* Handle a users element
*
* @param \DOMElement $node
*
* @return array Array of User objects
*/
public function parseUser($node)
{
/** @var PKPUserImportExportDeployment */
$deployment = $this->getDeployment();
$context = $deployment->getContext();
$site = $deployment->getSite();
// Create the data object
$user = Repo::user()->newDataObject();
// Password encryption
$encryption = null;
// Handle metadata in subelements
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof \DOMElement) {
switch ($n->tagName) {
case 'username': $user->setUsername($n->textContent);
break;
case 'givenname':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $site->getPrimaryLocale();
}
$user->setGivenName($n->textContent, $locale);
break;
case 'familyname':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $site->getPrimaryLocale();
}
$user->setFamilyName($n->textContent, $locale);
break;
case 'affiliation':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $site->getPrimaryLocale();
}
$user->setAffiliation($n->textContent, $locale);
break;
case 'country': $user->setCountry($n->textContent);
break;
case 'email': $user->setEmail($n->textContent);
break;
case 'url': $user->setUrl($n->textContent);
break;
case 'orcid': $user->setOrcid($n->textContent);
break;
case 'phone': $user->setPhone($n->textContent);
break;
case 'billing_address': $user->setBillingAddress($n->textContent);
break;
case 'mailing_address': $user->setMailingAddress($n->textContent);
break;
case 'biography':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $site->getPrimaryLocale();
}
$user->setBiography($n->textContent, $locale);
break;
case 'gossip': $user->setGossip($n->textContent);
break;
case 'signature':
$locale = $n->getAttribute('locale');
if (empty($locale)) {
$locale = $site->getPrimaryLocale();
}
$user->setSignature($n->textContent, $locale);
break;
case 'date_registered': $user->setDateRegistered($n->textContent);
break;
case 'date_last_login': $user->setDateLastLogin($n->textContent);
break;
case 'date_last_email': $user->setDateLastEmail($n->textContent);
break;
case 'date_validated': $user->setDateValidated($n->textContent);
break;
case 'inline_help':$n->textContent == 'true' ? $user->setInlineHelp(true) : $user->setInlineHelp(false) ;
break;
case 'auth_string': $user->setAuthStr($n->textContent);
break;
case 'disabled_reason': $user->setDisabledReason($n->textContent);
break;
case 'locales': $user->setLocales(preg_split('/:/', $n->textContent));
break;
case 'password':
if ($n->getAttribute('must_change') == 'true') {
$user->setMustChangePassword(true);
}
if ($n->getAttribute('is_disabled') == 'true') {
$user->setDisabled(true);
}
if ($n->getAttribute('encryption')) {
$encryption = $n->getAttribute('encryption');
}
$passwordValueNodeList = $n->getElementsByTagNameNS($deployment->getNamespace(), 'value');
if ($passwordValueNodeList->length == 1) {
$password = $passwordValueNodeList->item(0);
$user->setPassword($password->textContent);
} else {
$this->addError(__('plugins.importexport.user.error.userHasNoPassword', ['username' => $user->getUsername()]));
}
break;
}
}
}
// Password Import Validation
$password = $this->importUserPasswordValidation($user, $encryption);
$userByUsername = Repo::user()->getByUsername($user->getUsername(), true);
$userByEmail = Repo::user()->getByEmail($user->getEmail(), true);
// username and email are both required and unique, so either
// both exist for one and the same user, or both do not exist
if ($userByUsername && $userByEmail && $userByUsername->getId() == $userByEmail->getId()) {
$user = $userByUsername;
$userId = $user->getId();
} elseif (!$userByUsername && !$userByEmail) {
// if user names do not exists in the site primary locale
// copy one of the existing for the default/required site primary locale
if (empty($user->getGivenName($site->getPrimaryLocale()))) {
// get all user given names, family names and affiliations
$userGivenNames = $user->getGivenName(null);
$userFamilyNames = $user->getFamilyName(null);
$userAffiliations = $user->getAffiliation(null);
// get just not empty user given names, family names and affiliations
$notEmptyGivenNames = $notEmptyFamilyNames = $notEmptyAffiliations = [];
$notEmptyGivenNames = array_filter($userGivenNames, function ($a) {
return !empty($a);
});
// if all given names are empty, import fails
if (empty($notEmptyGivenNames)) {
fatalError('User given name is empty.');
}
if (!empty($userFamilyNames)) {
$notEmptyFamilyNames = array_filter($userFamilyNames, function ($a) {
return !empty($a);
});
}
if (!empty($userAffiliations)) {
$notEmptyAffiliations = array_filter($userAffiliations, function ($a) {
return !empty($a);
});
}
// see if both, given and family name, exist in the same locale
$commonLocales = array_intersect_key($notEmptyGivenNames, $notEmptyFamilyNames);
if (empty($commonLocales)) {
// if not: copy only the given name
$firstLocale = reset(array_keys($notEmptyGivenNames));
$user->setGivenName($notEmptyGivenNames[$firstLocale], $site->getPrimaryLocale());
} else {
// else: take the first common locale for given and family name
$firstLocale = reset(array_keys($commonLocales));
// see if there is affiliation in a common locale
$affiliationCommonLocales = array_intersect_key($notEmptyAffiliations, $commonLocales);
if (!empty($affiliationCommonLocales)) {
// take the first common locale to all, given name, family name and affiliation
$firstLocale = reset(array_keys($affiliationCommonLocales));
// copy affiliation
if (empty($notEmptyAffiliations[$site->getPrimaryLocale()])) {
$user->setAffiliation($notEmptyAffiliations[$firstLocale], $site->getPrimaryLocale());
}
}
//copy given and family name
$user->setGivenName($notEmptyGivenNames[$firstLocale], $site->getPrimaryLocale());
if (empty($notEmptyFamilyNames[$site->getPrimaryLocale()])) {
$user->setFamilyName($notEmptyFamilyNames[$firstLocale], $site->getPrimaryLocale());
}
}
}
$userId = Repo::user()->add($user);
// Insert reviewing interests, now that there is a userId.
$interestNodeList = $node->getElementsByTagNameNS($deployment->getNamespace(), 'review_interests');
if ($interestNodeList->length == 1) {
$n = $interestNodeList->item(0);
if ($n) {
$interests = preg_split('/,\s*/', $n->textContent);
$interestManager = new InterestManager();
$interestManager->setInterestsForUser($user, $interests);
}
}
// send USER_REGISTER e-mail only if it is a new inserted/registered user
// else, if the user already exists, its metadata will not be change (just groups will be re-assigned below)
if ($password) {
$template = Repo::emailTemplate()->getByKey($context->getId(), UserCreated::getEmailTemplateKey());
$sender = Application::get()->getRequest()->getUser();
$mailable = new UserCreated($context, $password);
$mailable
->recipients($user)
->sender($sender)
->replyTo($context->getData('contactEmail'), $context->getData('contactName'))
->subject($template->getLocalizedData('subject'))
->body($template->getLocalizedData('body'));
Mail::send($mailable);
}
} else {
// the username and the email do not match to the one and the same existing user
$this->addError(__('plugins.importexport.user.error.usernameEmailMismatch', ['username' => $user->getUsername(), 'email' => $user->getEmail()]));
}
// We can only assign a user to a user group if persisted to the database by $userId
if ($userId) {
$userGroups = Repo::userGroup()->getCollector()
->filterByContextIds([$context->getId()])
->getMany();
// Extract user groups from the User XML and assign the user to those (existing) groups.
// Note: It is possible for a user to exist with no user group assignments so there is
// no fatalError() as is the case with \PKP\author\Author import.
$userGroupNodeList = $node->getElementsByTagNameNS($deployment->getNamespace(), 'user_group_ref');
if ($userGroupNodeList->length > 0) {
for ($i = 0 ; $i < $userGroupNodeList->length ; $i++) {
$n = $userGroupNodeList->item($i);
/** @var \PKP\userGroup\UserGroup $userGroup */
foreach ($userGroups as $userGroup) {
// if the given user associated group name in within tag 'user_group_ref' is in the list of $userGroup name local list
// and the user is not already assigned to that group
if (in_array($n->textContent, $userGroup->getName(null)) && !Repo::userGroup()->userInGroup($userId, $userGroup->getId())) {
// Found a candidate; assign user to it.
Repo::userGroup()->assignUserToGroup($userId, $userGroup->getId());
}
}
}
}
}
return $user;
}
/**
* Handle a singular element import.
*
* @param \DOMElement $node
*/
public function handleElement($node)
{
$deployment = $this->getDeployment();
$context = $deployment->getContext();
for ($n = $node->firstChild; $n !== null; $n = $n->nextSibling) {
if ($n instanceof \DOMElement) {
$this->handleChildElement($n);
}
}
}
/**
* Handle an element whose parent is the submission element.
*
* @param \DOMElement $n
*/
public function handleChildElement($n)
{
switch ($n->tagName) {
case 'user_group':
$this->parseUserGroup($n);
break;
case 'user':
$this->parseUser($n);
break;
default:
fatalError('Unknown element ' . $n->tagName);
}
}
/**
* Validation process for imported passwords
*
* @param User $userToImport ByRef. The user that is being imported.
* @param string $encryption null, sha1, md5 (or any other encryption algorithm defined)
*
* @return string if a new password is generated, the function returns it.
*/
public function importUserPasswordValidation($userToImport, $encryption)
{
$passwordHash = $userToImport->getPassword();
$password = null;
if (!$encryption) {
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
$site = $siteDao->getSite();
if (strlen($passwordHash) >= $site->getMinPasswordLength()) {
$userToImport->setPassword(Validation::encryptCredentials($userToImport->getUsername(), $passwordHash));
} else {
$this->addError(__('plugins.importexport.user.error.plainPasswordNotValid', ['username' => $userToImport->getUsername()]));
}
} else {
if (password_needs_rehash($passwordHash, PASSWORD_BCRYPT)) {
$password = Validation::generatePassword();
$userToImport->setPassword(Validation::encryptCredentials($userToImport->getUsername(), $password));
$userToImport->setMustChangePassword(true);
$this->addError(__('plugins.importexport.user.error.passwordHasBeenChanged', ['username' => $userToImport->getUsername()]));
} else {
$userToImport->setPassword($passwordHash);
}
}
return $password;
}
}
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filterConfig SYSTEM "../../../../dtd/filterConfig.dtd">
<!--
* filterConfig.xml
*
* 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.
*
* Filter Configuration.
-->
<filterConfig>
<filterGroups>
<filterGroup
symbolic="user=>user-xml"
displayName="plugins.importexport.users.displayName"
description="plugins.importexport.users.description"
inputType="class::lib.pkp.classes.user.User[]"
outputType="xml::schema(lib/pkp/plugins/importexport/users/pkp-users.xsd)" />
<!-- Native XML Users input -->
<filterGroup
symbolic="user-xml=>user"
displayName="plugins.importexport.users.displayName"
description="plugins.importexport.users.description"
inputType="xml::schema(lib/pkp/plugins/importexport/users/pkp-users.xsd)"
outputType="class::classes.users.User[]" />
<!-- Native XML usergroup output -->
<filterGroup
symbolic="usergroup=>user-xml"
displayName="plugins.importexport.users.displayName"
description="plugins.importexport.users.description"
inputType="class::lib.pkp.classes.security.UserGroup[]"
outputType="xml::schema(lib/pkp/plugins/importexport/users/pkp-users.xsd)" />
<!-- Native XML usergroup input -->
<filterGroup
symbolic="user-xml=>usergroup"
displayName="plugins.importexport.native.displayName"
description="plugins.importexport.native.description"
inputType="xml::schema(lib/pkp/plugins/importexport/users/pkp-users.xsd)"
outputType="class::lib.pkp.classes.security.UserGroup[]" />
</filterGroups>
<filters>
<!-- Native XML users output -->
<filter
inGroup="user=>user-xml"
class="PKP\plugins\importexport\users\filter\PKPUserUserXmlFilter"
isTemplate="0" />
<!-- Native XML users input -->
<filter
inGroup="user-xml=>user"
class="PKP\plugins\importexport\users\filter\UserXmlPKPUserFilter"
isTemplate="0" />
<!-- Native XML usergroup output -->
<filter
inGroup="usergroup=>user-xml"
class="PKP\plugins\importexport\users\filter\UserGroupNativeXmlFilter"
isTemplate="0" />
<!-- Native XML usergroup input -->
<filter
inGroup="user-xml=>usergroup"
class="PKP\plugins\importexport\users\filter\NativeXmlUserGroupFilter"
isTemplate="0" />
</filters>
</filterConfig>
@@ -0,0 +1,20 @@
<?php
/**
* @defgroup plugins_importexport_users User import/export plugin
*/
/**
* @file plugins/importexport/users/index.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.
*
* @ingroup plugins_importexport_users
*
* @brief Wrapper for XML user import/export plugin.
*
*/
return new \APP\plugins\importexport\users\UserImportExportPlugin();
@@ -0,0 +1,84 @@
<?xml version="1.0"?>
<!--
* plugins/importexport/users/pkp-users.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 specific to OMP
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://pkp.sfu.ca" xmlns:pkp="http://pkp.sfu.ca" elementFormDefault="qualified">
<!--
- Base the user import/export on the User database model.
-->
<include schemaLocation="../../../plugins/importexport/native/pkp-native.xsd" />
<!--
- User-related Elements
-->
<!-- A password complex type -->
<complexType name="password">
<sequence>
<element name="value" type="string" minOccurs="1" maxOccurs="1" />
</sequence>
<attribute name="is_disabled" type="boolean" default="false" />
<attribute name="must_change" type="boolean" default="false" />
<attribute name="encryption" type="string" />
</complexType>
<!-- Permit "users" as a root element -->
<element name="users">
<complexType>
<sequence>
<element name="user" type="pkp:user" minOccurs="1" maxOccurs="unbounded" />
</sequence>
</complexType>
</element>
<!-- A user -->
<complexType name="user">
<complexContent>
<extension base="pkp:identity">
<sequence>
<element name="username" type="string" minOccurs="1" maxOccurs="1" />
<element name="gossip" type="string" minOccurs="0" maxOccurs="1" />
<element name="signature" type="pkp:localizedNode" minOccurs="0" maxOccurs="unbounded" />
<element name="password" type="pkp:password" minOccurs="1" maxOccurs="1" />
<element name="date_registered" type="string" minOccurs="0" maxOccurs="1" />
<element name="date_last_login" type="string" minOccurs="0" maxOccurs="1" />
<element name="date_last_email" type="string" minOccurs="0" maxOccurs="1" />
<element name="date_validated" type="string" minOccurs="0" maxOccurs="1" />
<element name="inline_help" type="string" minOccurs="0" maxOccurs="1" />
<element name="auth_string" type="string" minOccurs="0" maxOccurs="1" />
<element name="phone" type="string" minOccurs="0" maxOccurs="1" />
<element name="mailing_address" type="string" minOccurs="0" maxOccurs="1" />
<element name="billing_address" type="string" minOccurs="0" maxOccurs="1" />
<element name="locales" type="string" minOccurs="0" maxOccurs="1" />
<element name="disabled_reason" type="string" minOccurs="0" maxOccurs="1" />
<element name="user_group_ref" type="string" maxOccurs="unbounded" />
<element name="review_interests" type="string" minOccurs="0" maxOccurs="1" />
</sequence>
</extension>
</complexContent>
</complexType>
<!-- Permit "user" as a root element -->
<element name="user" type="pkp:user" />
<!--
- Composite / root elements
-->
<element name="PKPUsers">
<complexType>
<sequence>
<element ref="pkp:user_groups" maxOccurs="1" />
<element ref="pkp:users" maxOccurs="1" />
</sequence>
</complexType>
</element>
</schema>
@@ -0,0 +1,69 @@
<?php
/**
* @defgroup plugins_metadata_dc11 Dublin Core 1.1 Metadata Format
*/
/**
* @file plugins/metadata/dc11/PKPDc11MetadataPlugin.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 PKPDc11MetadataPlugin
*
* @ingroup plugins_metadata_dc11
*
* @brief Abstract base class for Dublin Core version 1.1 metadata plugins
*/
namespace PKP\plugins\metadata\dc11;
use PKP\plugins\MetadataPlugin;
class PKPDc11MetadataPlugin extends MetadataPlugin
{
//
// Override protected template methods from Plugin
//
/**
* @copydoc Plugin::getName()
*/
public function getName()
{
return 'Dc11MetadataPlugin';
}
/**
* @copydoc Plugin::getDisplayName()
*/
public function getDisplayName()
{
return __('plugins.metadata.dc11.displayName');
}
/**
* @copydoc Plugin::getDescription()
*/
public function getDescription()
{
return __('plugins.metadata.dc11.description');
}
/**
* @copydoc MetadataPlugin::supportsFormat()
*/
public function supportsFormat($format)
{
return $format === 'dc11';
}
/**
* @copydoc MetadataPlugin::getSchemaObject()
*/
public function getSchemaObject($format)
{
assert($this->supportsFormat($format));
return new \APP\plugins\metadata\dc11\schema\Dc11Schema();
}
}
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:25+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:25+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "البيانات الوصفية Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr "يعاون مخططات Dublin Core version 1.1 ومهايئات تطبيقاتها."
@@ -0,0 +1,19 @@
# Osman Durmaz <osmandurmaz@hotmail.de>, 2023.
msgid ""
msgstr ""
"PO-Revision-Date: 2023-06-02 19:20+0000\n"
"Last-Translator: Osman Durmaz <osmandurmaz@hotmail.de>\n"
"Language-Team: Azerbaijani <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/az/>\n"
"Language: az\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.13.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 üst datası"
msgid "plugins.metadata.dc11.description"
msgstr "Dublin Core version 1.1 sxemləri və tətbiq adapterləri təqdim edir."
@@ -0,0 +1,20 @@
# Cyril Kamburov <cc@intermedia.bg>, 2021.
msgid ""
msgstr ""
"PO-Revision-Date: 2021-10-13 21:00+0000\n"
"Last-Translator: Cyril Kamburov <cc@intermedia.bg>\n"
"Language-Team: Bulgarian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/bg_BG/>\n"
"Language: bg_BG\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Метаданни Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Допринася за схемите на Dublin Core версия 1.1 и адаптерите за приложения."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:26+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:26+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Metadades Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Contribueix amb els esquemes i adaptadors daplicació de Dublin Core version "
"1.1."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2020-04-17 18:37+0000\n"
"Last-Translator: Hewa Salam Khalid <hewa.salam@koyauniversity.org>\n"
"Language-Team: Kurdish <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/ku_IQ/>\n"
"Language: ku_IQ\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 زانیاریی ناسێنەری"
msgid "plugins.metadata.dc11.description"
msgstr "هاوکاری لەگەڵ ڕێنماییەکانی Dublin Core version 1.1 و ئەپلیکەیشنەکانی."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:25+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:25+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Metadata Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr "Využívá Dublin Core verzi 1.1 schémat a aplikačních adaptérů."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:26+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:26+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 meta-data"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Understøtter Dublin Core version 1.1 database- og applikationstilpasninger."
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-30T07:05:58-07:00\n"
"PO-Revision-Date: 2019-09-30T07:05:58-07:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 Metadaten"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Stellt Schemata und Anwendungsschnittstellen für Dublin Core version 1.1 "
"bereit."
@@ -0,0 +1,8 @@
# Weblate Admin <alec@smecher.bc.ca>, 2023.
msgid ""
msgstr ""
"Language: dsb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Weblate\n"
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:26+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:26+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Μεταδεδομένα Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Αυτό το Πρόσθετο παρέχει σχήματα και προσαρμογείς σύμφωνα με το πρότυπο "
"Dublin Core 1.1."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-30T07:05:58-07:00\n"
"PO-Revision-Date: 2019-09-30T07:05:58-07:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 meta-data"
msgid "plugins.metadata.dc11.description"
msgstr "Contributes Dublin Core version 1.1 schemas and application adapters."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:26+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:26+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Metadatos Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Proporciona plantillas Dublin Core 1.1 y compatibilidad de aplicaciones."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:26+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:26+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "فراداده هسته دوبلین "
msgid "plugins.metadata.dc11.description"
msgstr "شماتیک و برنامههسته دوبلین نسخه 1.1 "
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:26+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:26+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 metadata"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Lisää Dublin Core version 1.1 mukaiset skeemat ja järjestelmäsovittimet."
@@ -0,0 +1,23 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-09-30T07:05:58-07:00\n"
"PO-Revision-Date: 2020-02-01 08:35+0000\n"
"Last-Translator: Marie-Hélène Vézina [UdeMontréal] <marie-"
"helene.vezina@umontreal.ca>\n"
"Language-Team: French (Canada) <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/fr_CA/>\n"
"Language: fr_CA\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Métadonnées Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Permet d'utiliser les éléments et adaptateurs du Dublin Core version 1.1"
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2020-08-31 17:48+0000\n"
"Last-Translator: Paul Heckler <paul.d.heckler@gmail.com>\n"
"Language-Team: French <http://translate.pkp.sfu.ca/projects/pkp-lib/metadata-"
"dc11/fr/>\n"
"Language: fr_FR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Métadonnées Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Fournit des schémas et adaptateurs d'application Dublin Core version 1.1."
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2021-02-10 21:53+0000\n"
"Last-Translator: Real Academia Galega <reacagal@gmail.com>\n"
"Language-Team: Galician <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/gl_ES/>\n"
"Language: gl_ES\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Metadatos Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Proporciona esquemas Dublin Core 1.1 e compatibilidade das aplicacións."
@@ -0,0 +1,21 @@
# Jula Dakić <juladakic@gmail.com>, 2023.
# Maja Jurić <maja-juric@windowslive.com>, 2023.
msgid ""
msgstr ""
"PO-Revision-Date: 2023-02-08 15:40+0000\n"
"Last-Translator: Maja Jurić <maja-juric@windowslive.com>\n"
"Language-Team: Croatian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/hr/>\n"
"Language: hr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
"X-Generator: Weblate 4.13.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 metapodaci"
msgid "plugins.metadata.dc11.description"
msgstr "Doprinosi Dublin Core verziji 1.1 shemama i aplikacijskim adapterima."
@@ -0,0 +1,8 @@
# Weblate Admin <alec@smecher.bc.ca>, 2023.
msgid ""
msgstr ""
"Language: hsb\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Weblate\n"
@@ -0,0 +1,20 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-02-13T21:07:39+00:00\n"
"PO-Revision-Date: 2020-02-13T21:07:39+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 meta-data"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Segíti a Dublin Core 1.1 sémák és alkalmazás adapterek közötti "
"együttműködést."
@@ -0,0 +1,21 @@
# Hovhannes Harutyunyan <hharutyunyan@gmail.com>, 2022.
# Tigran Zargaryan <tigran@flib.sci.am>, 2022.
msgid ""
msgstr ""
"PO-Revision-Date: 2022-02-22 10:26+0000\n"
"Last-Translator: Tigran Zargaryan <tigran@flib.sci.am>\n"
"Language-Team: Armenian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/hy_AM/>\n"
"Language: hy_AM\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 մետատվյալներ"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Նպաստում է Dublin Core տարբերակ 1.1 սխեմաներին և հավելվածների ադապտերներին:"
@@ -0,0 +1,19 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:26+00:00\n"
"PO-Revision-Date: 2019-11-19T11:06:26+00:00\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Metadata Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr ""
"Berkontribusi terhadap skema dan adapter aplikasi Dublin Core versi 1.1."
@@ -0,0 +1,19 @@
# Kolbrun Reynisdottir <kolla@probus.is>, 2022.
msgid ""
msgstr ""
"PO-Revision-Date: 2022-01-29 19:02+0000\n"
"Last-Translator: Kolbrun Reynisdottir <kolla@probus.is>\n"
"Language-Team: Icelandic <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/is_IS/>\n"
"Language: is_IS\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n % 10 != 1 || n % 100 == 11;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 meta-data"
msgid "plugins.metadata.dc11.description"
msgstr "Contributes Dublin Core version 1.1 schemas and application adapters."
@@ -0,0 +1,21 @@
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-11-19T11:06:26+00:00\n"
"PO-Revision-Date: 2019-12-29 20:34+0000\n"
"Last-Translator: Lucia Steele <lucia.steele@aboutscience.eu>\n"
"Language-Team: Italian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/it/>\n"
"Language: it_IT\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Meadati Dublin Core 1.1"
msgid "plugins.metadata.dc11.description"
msgstr "Fornisce schemi e adattatori di applicazioni Dublin Core 1.1."
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2021-03-08 12:54+0000\n"
"Last-Translator: Bjorn-Ole Kamm <pkp_trans@b-ok.de>\n"
"Language-Team: Japanese <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/ja_JP/>\n"
"Language: ja_JP\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 メタデーター"
msgid "plugins.metadata.dc11.description"
msgstr "Dublin Core バージョン 1.1 のスキーマとアプリケーションアダプタを提供します。"
@@ -0,0 +1,18 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2021-04-07 15:28+0000\n"
"Last-Translator: Dimitri Gogelia <dimitri.gogelia@iliauni.edu.ge>\n"
"Language-Team: Georgian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/ka_GE/>\n"
"Language: ka_GE\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 3.9.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 მეტა-მონაცემები"
msgid "plugins.metadata.dc11.description"
msgstr "ხელს უწყობს Dublin Core version 1.1 სქემებსა და პროგრამების ადაპტერებს."
@@ -0,0 +1,20 @@
# Ieva Tiltina <pastala@gmail.com>, 2023.
msgid ""
msgstr ""
"PO-Revision-Date: 2023-09-22 13:06+0000\n"
"Last-Translator: Ieva Tiltina <pastala@gmail.com>\n"
"Language-Team: Latvian <http://translate.pkp.sfu.ca/projects/pkp-lib/"
"metadata-dc11/lv/>\n"
"Language: lv\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n % 10 == 0 || n % 100 >= 11 && n % 100 <= "
"19) ? 0 : ((n % 10 == 1 && n % 100 != 11) ? 1 : 2);\n"
"X-Generator: Weblate 4.13.1\n"
msgid "plugins.metadata.dc11.displayName"
msgstr "Dublin Core 1.1 metadati"
msgid "plugins.metadata.dc11.description"
msgstr "Sniedz Dublin Core 1.1 versijas shēmas un lietojumprogrammu adapterus."

Some files were not shown because too many files have changed in this diff Show More