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,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>