first commit
This commit is contained in:
@@ -0,0 +1,425 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/user/Repository.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 Repository
|
||||
*
|
||||
* @brief A repository to find and manage users.
|
||||
*/
|
||||
|
||||
namespace PKP\user;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\facades\Repo;
|
||||
use APP\submission\Submission;
|
||||
use Carbon\Carbon;
|
||||
use PKP\context\Context;
|
||||
use PKP\context\SubEditorsDAO;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\file\TemporaryFileDAO;
|
||||
use PKP\log\SubmissionEmailLogDAO;
|
||||
use PKP\log\SubmissionEventLogDAO;
|
||||
use PKP\note\NoteDAO;
|
||||
use PKP\notification\NotificationDAO;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\security\AccessKeyDAO;
|
||||
use PKP\security\Role;
|
||||
use PKP\security\RoleDAO;
|
||||
use PKP\session\SessionDAO;
|
||||
use PKP\stageAssignment\StageAssignmentDAO;
|
||||
use PKP\submission\reviewAssignment\ReviewAssignmentDAO;
|
||||
use PKP\submission\SubmissionCommentDAO;
|
||||
|
||||
class Repository
|
||||
{
|
||||
/** @var DAO $dao */
|
||||
public $dao;
|
||||
|
||||
/** @var string $schemaMap The name of the class to map this entity to its schema */
|
||||
public $schemaMap = maps\Schema::class;
|
||||
|
||||
public function __construct(DAO $dao)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::newDataObject() */
|
||||
public function newDataObject(array $params = []): User
|
||||
{
|
||||
$object = $this->dao->newDataObject();
|
||||
if (!empty($params)) {
|
||||
$object->setAllData($params);
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::get() */
|
||||
public function get(int $id, $allowDisabled = false): ?User
|
||||
{
|
||||
return $this->dao->get($id, $allowDisabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a user by API key.
|
||||
*/
|
||||
public function getByApiKey(string $apiKey): ?User
|
||||
{
|
||||
return $this->getCollector()
|
||||
->filterBySettings(['apiKey' => $apiKey])
|
||||
->getMany()
|
||||
->first();
|
||||
}
|
||||
|
||||
/** @copydoc DAO::get() */
|
||||
public function getByUsername(string $username, bool $allowDisabled = false): ?User
|
||||
{
|
||||
return $this->dao->getByUsername($username, $allowDisabled);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::get() */
|
||||
public function getByEmail(string $email, bool $allowDisabled = false): ?User
|
||||
{
|
||||
return $this->dao->getByEmail($email, $allowDisabled);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::getCollector() */
|
||||
public function getCollector(): Collector
|
||||
{
|
||||
return app(Collector::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the map class for mapping users to their schema
|
||||
*/
|
||||
public function getSchemaMap(): maps\Schema
|
||||
{
|
||||
return app('maps')->withExtensions($this->schemaMap);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::insert() */
|
||||
public function add(User $user): int
|
||||
{
|
||||
$id = $this->dao->insert($user);
|
||||
Hook::call('User::add', [$user]);
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::update() */
|
||||
public function edit(User $user, array $params = [])
|
||||
{
|
||||
$newUser = clone $user;
|
||||
$newUser->setAllData(array_merge($newUser->_data, $params));
|
||||
|
||||
Hook::call('User::edit', [$newUser, $user, $params]);
|
||||
|
||||
$this->dao->update($newUser);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::delete */
|
||||
public function delete(User $user)
|
||||
{
|
||||
Hook::call('User::delete::before', [&$user]);
|
||||
|
||||
$this->dao->delete($user);
|
||||
|
||||
Hook::call('User::delete', [&$user]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the current user view and edit the gossip field for a user
|
||||
*
|
||||
* @param int $userId The user who's gossip field should be accessed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canCurrentUserGossip($userId)
|
||||
{
|
||||
$request = Application::get()->getRequest();
|
||||
$context = $request->getContext();
|
||||
$contextId = $context ? $context->getId() : \PKP\core\PKPApplication::CONTEXT_ID_NONE;
|
||||
$currentUser = $request->getUser();
|
||||
|
||||
// Logged out users can never view gossip fields
|
||||
if (!$currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Users can never view their own gossip fields
|
||||
if ($currentUser->getId() === $userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
|
||||
// Only reviewers have gossip fields
|
||||
if (!$roleDao->userHasRole($contextId, $userId, Role::ROLE_ID_REVIEWER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only admins, editors and subeditors can view gossip fields
|
||||
if (!$roleDao->userHasRole($contextId, $currentUser->getId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this user access the requested workflow stage
|
||||
*
|
||||
* The user must have an assigned role in the specified stage or
|
||||
* be a manager or site admin that has no assigned role in the
|
||||
* submission.
|
||||
*
|
||||
* @param string $stageId One of the WORKFLOW_STAGE_ID_* constants.
|
||||
* @param string $workflowType Accessing the editorial or author workflow? PKPApplication::WORKFLOW_TYPE_*
|
||||
* @param array $userAccessibleStages User's assignments to the workflow stages. Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES
|
||||
* @param array $userRoles User's roles in the context
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canUserAccessStage($stageId, $workflowType, $userAccessibleStages, $userRoles)
|
||||
{
|
||||
$workflowRoles = Application::get()->getWorkflowTypeRoles()[$workflowType];
|
||||
|
||||
if (array_key_exists($stageId, $userAccessibleStages)
|
||||
&& !empty(array_intersect($workflowRoles, $userAccessibleStages[$stageId]))) {
|
||||
return true;
|
||||
}
|
||||
if (empty($userAccessibleStages) && count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN], $userRoles))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve user roles which give access to (certain) submission workflow stages
|
||||
* returns [
|
||||
* stage ID => [role IDs]
|
||||
* ]
|
||||
*
|
||||
*/
|
||||
public function getAccessibleWorkflowStages(int $userId, int $contextId, Submission $submission, ?array $userRoleIds = null): array
|
||||
{
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$stageAssignmentsResult = $stageAssignmentDao->getBySubmissionAndUserIdAndStageId($submission->getId(), $userId);
|
||||
|
||||
if (is_null($userRoleIds)) {
|
||||
$roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */
|
||||
$userRoles = $roleDao->getByUserIdGroupedByContext($userId);
|
||||
|
||||
$userRoleIds = [];
|
||||
if (array_key_exists($contextId, $userRoles)) {
|
||||
$contextRoles = $userRoles[$contextId];
|
||||
|
||||
foreach ($contextRoles as $contextRole) { /** @var Role $userRole */
|
||||
$userRoleIds[] = $contextRole->getRoleId();
|
||||
}
|
||||
}
|
||||
|
||||
// Has admin role?
|
||||
if ($contextId != PKPApplication::CONTEXT_ID_NONE &&
|
||||
array_key_exists(PKPApplication::CONTEXT_ID_NONE, $userRoles) &&
|
||||
in_array(Role::ROLE_ID_SITE_ADMIN, $userRoles[PKPApplication::CONTEXT_ID_NONE])
|
||||
) {
|
||||
$userRoleIds[] = Role::ROLE_ID_SITE_ADMIN;
|
||||
}
|
||||
}
|
||||
|
||||
$accessibleWorkflowStages = [];
|
||||
|
||||
// Assigned users have access based on their assignment
|
||||
while ($stageAssignment = $stageAssignmentsResult->next()) {
|
||||
$userGroup = Repo::userGroup()->get($stageAssignment->getUserGroupId());
|
||||
$roleId = $userGroup->getRoleId();
|
||||
|
||||
// Check global user roles within the context, e.g., user can be assigned in the role, which was revoked
|
||||
if (!in_array($roleId, $userRoleIds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$accessibleWorkflowStages[$stageAssignment->getStageId()][] = $roleId;
|
||||
}
|
||||
|
||||
// Managers and admin have access if not assigned to the submission or are assigned in a revoked role
|
||||
$managerRoles = array_intersect($userRoleIds, [Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER]);
|
||||
if (empty($accessibleWorkflowStages) && !empty($managerRoles)) {
|
||||
$workflowStages = Application::getApplicationStages();
|
||||
foreach ($workflowStages as $stageId) {
|
||||
$accessibleWorkflowStages[$stageId] = $managerRoles;
|
||||
}
|
||||
}
|
||||
|
||||
return $accessibleWorkflowStages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a filtered user report instance
|
||||
*
|
||||
* @param array $args
|
||||
* - @option int[] contextIds Context IDs (required)
|
||||
* - @option int[] userGroupIds List of user groups (all groups by default)
|
||||
*/
|
||||
public function getReport(array $args): Report
|
||||
{
|
||||
$dataSource = $this->getCollector()
|
||||
->filterByUserGroupIds($args['userGroupIds'] ?? null)
|
||||
->filterByContextIds($args['contextIds'] ?? [])
|
||||
->getMany();
|
||||
|
||||
$report = new Report($dataSource);
|
||||
|
||||
Hook::call('User::getReport', [$report]);
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
public function getRolesOverview(Collector $collector)
|
||||
{
|
||||
$result = [
|
||||
[
|
||||
'id' => 'total',
|
||||
'name' => 'stats.allUsers',
|
||||
'value' => $this->dao->getCount($collector),
|
||||
],
|
||||
];
|
||||
|
||||
$roleNames = Application::get()->getRoleNames();
|
||||
|
||||
foreach ($roleNames as $roleId => $roleName) {
|
||||
$result[] = [
|
||||
'id' => $roleId,
|
||||
'name' => $roleName,
|
||||
'value' => $this->dao->getCount($collector->filterByRoleIds([$roleId])),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge user accounts and delete the old user account.
|
||||
*
|
||||
* @param int $oldUserId The user ID to remove
|
||||
* @param int $newUserId The user ID to receive all "assets" (i.e. submissions) from old user
|
||||
*/
|
||||
public function mergeUsers(int $oldUserId, int $newUserId)
|
||||
{
|
||||
// Need both user ids for merge
|
||||
if (empty($oldUserId) || empty($newUserId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Hook::call('UserAction::mergeUsers', [&$oldUserId, &$newUserId]);
|
||||
|
||||
$submissionFiles = Repo::submissionFile()
|
||||
->getCollector()
|
||||
->filterByUploaderUserIds([$oldUserId])
|
||||
->includeDependentFiles()
|
||||
->getMany();
|
||||
|
||||
foreach ($submissionFiles as $submissionFile) {
|
||||
Repo::submissionFile()->edit($submissionFile, ['uploaderUserId' => $newUserId]);
|
||||
}
|
||||
|
||||
$noteDao = DAORegistry::getDAO('NoteDAO'); /** @var NoteDAO $noteDao */
|
||||
$notes = $noteDao->getByUserId($oldUserId);
|
||||
while ($note = $notes->next()) {
|
||||
$note->setUserId($newUserId);
|
||||
$noteDao->updateObject($note);
|
||||
}
|
||||
|
||||
Repo::decision()->dao->reassignDecisions($oldUserId, $newUserId);
|
||||
|
||||
$reviewAssignmentDao = DAORegistry::getDAO('ReviewAssignmentDAO'); /** @var ReviewAssignmentDAO $reviewAssignmentDao */
|
||||
foreach ($reviewAssignmentDao->getByUserId($oldUserId) as $reviewAssignment) {
|
||||
$reviewAssignment->setReviewerId($newUserId);
|
||||
$reviewAssignmentDao->updateObject($reviewAssignment);
|
||||
}
|
||||
|
||||
$submissionEmailLogDao = DAORegistry::getDAO('SubmissionEmailLogDAO'); /** @var SubmissionEmailLogDAO $submissionEmailLogDao */
|
||||
$submissionEmailLogDao->changeUser($oldUserId, $newUserId);
|
||||
Repo::eventLog()->dao->changeUser($oldUserId, $newUserId);
|
||||
|
||||
$submissionCommentDao = DAORegistry::getDAO('SubmissionCommentDAO'); /** @var SubmissionCommentDAO $submissionCommentDao */
|
||||
$submissionComments = $submissionCommentDao->getByUserId($oldUserId);
|
||||
|
||||
while ($submissionComment = $submissionComments->next()) {
|
||||
$submissionComment->setAuthorId($newUserId);
|
||||
$submissionCommentDao->updateObject($submissionComment);
|
||||
}
|
||||
|
||||
$accessKeyDao = DAORegistry::getDAO('AccessKeyDAO'); /** @var AccessKeyDAO $accessKeyDao */
|
||||
$accessKeyDao->transferAccessKeys($oldUserId, $newUserId);
|
||||
|
||||
$notificationDao = DAORegistry::getDAO('NotificationDAO'); /** @var NotificationDAO $notificationDao */
|
||||
$notificationDao->transferNotifications($oldUserId, $newUserId);
|
||||
|
||||
// Delete the old user and associated info.
|
||||
$sessionDao = DAORegistry::getDAO('SessionDAO'); /** @var SessionDAO $sessionDao */
|
||||
$sessionDao->deleteByUserId($oldUserId);
|
||||
$temporaryFileDao = DAORegistry::getDAO('TemporaryFileDAO'); /** @var TemporaryFileDAO $temporaryFileDao */
|
||||
$temporaryFileDao->deleteByUserId($oldUserId);
|
||||
$subEditorsDao = DAORegistry::getDAO('SubEditorsDAO'); /** @var SubEditorsDAO $subEditorsDao */
|
||||
$subEditorsDao->deleteByUserId($oldUserId);
|
||||
|
||||
// Transfer old user's roles
|
||||
$userGroups = Repo::userGroup()->userUserGroups($oldUserId);
|
||||
foreach ($userGroups as $userGroup) {
|
||||
if (!Repo::userGroup()->userInGroup($newUserId, $userGroup->getId())) {
|
||||
Repo::userGroup()->assignUserToGroup($newUserId, $userGroup->getId());
|
||||
}
|
||||
}
|
||||
|
||||
Repo::userGroup()->deleteAssignmentsByUserId($oldUserId);
|
||||
|
||||
// Transfer stage assignments.
|
||||
$stageAssignmentDao = DAORegistry::getDAO('StageAssignmentDAO'); /** @var StageAssignmentDAO $stageAssignmentDao */
|
||||
$stageAssignments = $stageAssignmentDao->getByUserId($oldUserId);
|
||||
while ($stageAssignment = $stageAssignments->next()) {
|
||||
$duplicateAssignments = $stageAssignmentDao->getBySubmissionAndStageId($stageAssignment->getSubmissionId(), null, $stageAssignment->getUserGroupId(), $newUserId);
|
||||
if (!$duplicateAssignments->next()) {
|
||||
// If no similar assignments already exist, transfer this one.
|
||||
$stageAssignment->setUserId($newUserId);
|
||||
$stageAssignmentDao->updateObject($stageAssignment);
|
||||
} else {
|
||||
// There's already a stage assignment for the new user; delete.
|
||||
$stageAssignmentDao->deleteObject($stageAssignment);
|
||||
}
|
||||
}
|
||||
|
||||
$this->delete($this->get($oldUserId, true));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a user object from the Context contact details
|
||||
*/
|
||||
public function getUserFromContextContact(Context $context): User
|
||||
{
|
||||
$contextUser = $this->newDataObject();
|
||||
$supportedLocales = $context->getSupportedFormLocales();
|
||||
$contextUser->setData('email', $context->getData('contactEmail'));
|
||||
$contextUser->setData('givenName', array_fill_keys($supportedLocales, $context->getData('contactName')));
|
||||
return $contextUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete unvalidated expired users
|
||||
*
|
||||
* @param Carbon $dateTillValid The dateTime till before which user will consider expired
|
||||
* @param array $excludableUsersId The users id to exclude form delete operation
|
||||
*
|
||||
* @return int The number rows affected by DB operation
|
||||
*/
|
||||
public function deleteUnvalidatedExpiredUsers(Carbon $dateTillValid, array $excludableUsersId = [])
|
||||
{
|
||||
return $this->dao->deleteUnvalidatedExpiredUsers($dateTillValid, $excludableUsersId);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user