diff --git a/api/v1/_dois/PKPBackendDoiController.php b/api/v1/_dois/PKPBackendDoiController.php index d96facc7471..6a53e97483b 100644 --- a/api/v1/_dois/PKPBackendDoiController.php +++ b/api/v1/_dois/PKPBackendDoiController.php @@ -32,6 +32,8 @@ use PKP\security\authorization\UserRolesRequiredPolicy; use PKP\security\Role; use PKP\submission\GenreDAO; +use PKP\userGroup\UserGroup; + class PKPBackendDoiController extends PKPBaseController { @@ -128,9 +130,10 @@ public function editPublication(Request $illuminateRequest): JsonResponse $publication = Repo::publication()->get($publication->getId()); $submission = Repo::submission()->get($publication->getData('submissionId')); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + + $contextId = $submission->getData('contextId'); + $userGroups = UserGroup::withContextIds($contextId)->get(); + $genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */ $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); diff --git a/api/v1/_submissions/PKPBackendSubmissionsController.php b/api/v1/_submissions/PKPBackendSubmissionsController.php index 3b54238b4a0..3223804cd7c 100644 --- a/api/v1/_submissions/PKPBackendSubmissionsController.php +++ b/api/v1/_submissions/PKPBackendSubmissionsController.php @@ -36,6 +36,8 @@ use PKP\security\Role; use PKP\submission\DashboardView; use PKP\submission\PKPSubmission; +use PKP\userGroup\UserGroup; + abstract class PKPBackendSubmissionsController extends PKPBaseController { @@ -218,9 +220,10 @@ public function getMany(Request $illuminateRequest): JsonResponse $submissions = $collector->getMany(); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $contextId = $context->getId(); + + $userGroups = UserGroup::withContextIds($contextId)->cursor(); + /** @var \PKP\submission\GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -253,9 +256,8 @@ public function assigned(Request $illuminateRequest): JsonResponse ->assignedTo([$user->getId()]) ->getMany(); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $contextId = $context->getId(); + $userGroups = UserGroup::withContextIds($contextId)->cursor(); /** @var \PKP\submission\GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -325,9 +327,8 @@ public function reviews(Request $illuminateRequest): JsonResponse $submissions = $collector->getMany(); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $contextId = $context->getId(); + $userGroups = UserGroup::withContextIds($contextId)->cursor(); /** @var \PKP\submission\GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); diff --git a/api/v1/submissions/AnonymizeData.php b/api/v1/submissions/AnonymizeData.php index 88ba9551def..cecc5313b49 100644 --- a/api/v1/submissions/AnonymizeData.php +++ b/api/v1/submissions/AnonymizeData.php @@ -55,13 +55,12 @@ public function anonymizeReviews(LazyCollection|Submission $submissions, ?LazyCo ->pluck('user_group_id') ->toArray(); - $currentUserGroups = Repo::userGroup()->getCollector() - ->filterByUserGroupIds($currentUserUserGroupIds) - ->getMany(); + $currentUserGroups = UserGroup::withUserGroupIds($currentUserUserGroupIds)->get(); + $isAuthor = $currentUserGroups->contains( fn (UserGroup $userGroup) => - $userGroup->getRoleId() == Role::ROLE_ID_AUTHOR + $userGroup->roleId == Role::ROLE_ID_AUTHOR ); if ($currentUserReviewAssignment->isNotEmpty() || $isAuthor) { diff --git a/api/v1/submissions/PKPSubmissionController.php b/api/v1/submissions/PKPSubmissionController.php index dffbc1bda5f..fb5b446c7e2 100644 --- a/api/v1/submissions/PKPSubmissionController.php +++ b/api/v1/submissions/PKPSubmissionController.php @@ -32,6 +32,7 @@ use Illuminate\Support\Enumerable; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Route; +use Illuminate\Support\LazyCollection; use PKP\components\forms\FormComponent; use PKP\components\forms\publication\PKPCitationsForm; use PKP\components\forms\publication\PKPMetadataForm; @@ -70,6 +71,7 @@ use PKP\submissionFile\SubmissionFile; use PKP\userGroup\UserGroup; + class PKPSubmissionController extends PKPBaseController { use AnonymizeData; @@ -425,9 +427,7 @@ public function getMany(Request $illuminateRequest): JsonResponse $anonymizeReviews = $this->anonymizeReviews($submissions); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds($context->getId())->cursor(); /** @var \PKP\submission\GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -533,9 +533,8 @@ public function get(Request $illuminateRequest): JsonResponse { $submission = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_SUBMISSION); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); + // Anonymize sensitive review assignment data if user is a reviewer or author assigned to the article and review isn't open $reviewAssignments = Repo::reviewAssignment()->getCollector()->filterBySubmissionIds([$submission->getId()])->getMany()->remember(); @@ -599,20 +598,20 @@ public function add(Request $illuminateRequest): JsonResponse } } } + $submitterUserGroups = UserGroup::withContextIds($context->getId()) + ->withRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_AUTHOR]) + ->whereHas('userUserGroups', function ($query) use ($user) { + $query->withUserId($user->getId()); + }) + ->get(); - $submitterUserGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$context->getId()]) - ->filterByUserIds([$user->getId()]) - ->filterByRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_AUTHOR]) - ->getMany(); $userGroupIdPropName = 'userGroupId'; if (isset($params[$userGroupIdPropName])) { $submitAsUserGroup = $submitterUserGroups ->first(function (UserGroup $userGroup) use ($params, $userGroupIdPropName) { - return $userGroup->getId() === $params[$userGroupIdPropName]; + return $userGroup->id === $params[$userGroupIdPropName]; }); if (!$submitAsUserGroup) { $errors[$userGroupIdPropName] = [__('api.submissions.400.invalidSubmitAs')]; @@ -624,14 +623,14 @@ public function add(Request $illuminateRequest): JsonResponse }) ->first(); } else { - $submitAsUserGroup = Repo::userGroup()->getFirstSubmitAsAuthorUserGroup($context->getId()); + $submitAsUserGroup = UserGroup::withContextIds($context->getId())->withRoleIds(Role::ROLE_ID_AUTHOR)->first(); if (!$submitAsUserGroup) { $errors[$userGroupIdPropName] = [__('submission.wizard.notAllowed.description')]; } else { Repo::userGroup()->assignUserToGroup( $user->getId(), - $submitAsUserGroup->getId() - ); + $submitAsUserGroup->id + ); } } @@ -655,32 +654,34 @@ public function add(Request $illuminateRequest): JsonResponse Repo::stageAssignment() ->build( $submission->getId(), - $submitAsUserGroup->getId(), + $submitAsUserGroup->id, $request->getUser()->getId(), - $submitAsUserGroup->getRecommendOnly(), + $submitAsUserGroup->recommendOnly, // Authors can always edit metadata before submitting $submission->getData('submissionProgress') ? true - : $submitAsUserGroup->getPermitMetadataEdit() + : $submitAsUserGroup->permitMetadataEdit ); // Create an author record from the submitter's user account - if ($submitAsUserGroup->getRoleId() === Role::ROLE_ID_AUTHOR) { + if ($submitAsUserGroup->roleId === Role::ROLE_ID_AUTHOR) { $author = Repo::author()->newAuthorFromUser($request->getUser()); $author->setData('publicationId', $publication->getId()); - $author->setUserGroupId($submitAsUserGroup->getId()); + $author->setUserGroupId($submitAsUserGroup->id); $authorId = Repo::author()->add($author); Repo::publication()->edit($publication, ['primaryContactId' => $authorId]); } - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); $genres = $genreDao->getByContextId($submission->getData('contextId'))->toArray(); + if (!$userGroups instanceof LazyCollection) { + $userGroups = $userGroups->lazy(); + } + return response()->json(Repo::submission()->getSchemaMap()->map($submission, $userGroups, $genres), Response::HTTP_OK); } @@ -712,9 +713,7 @@ public function edit(Request $illuminateRequest): JsonResponse $submission = Repo::submission()->get($submission->getId()); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -766,11 +765,7 @@ public function saveForLater(Request $illuminateRequest): JsonResponse Mail::send($mailable); $submission = Repo::submission()->get($submission->getId()); - - $userGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -847,10 +842,10 @@ public function submit(Request $illuminateRequest): JsonResponse Repo::eventLog()->add($eventLog); } - $userGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $contextId = $context->getId(); + $userGroups = UserGroup::withContextIds($contextId)->cursor(); + + /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -872,9 +867,8 @@ public function delete(Request $illuminateRequest): JsonResponse ], Response::HTTP_NOT_FOUND); } - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); + /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -1033,9 +1027,7 @@ public function getPublications(Request $illuminateRequest): JsonResponse $publications = $collector->getMany(); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); $currentUserReviewAssignment = Repo::reviewAssignment()->getCollector() ->filterBySubmissionIds([$submission->getId()]) @@ -1077,9 +1069,7 @@ public function getPublication(Request $illuminateRequest): JsonResponse ], Response::HTTP_FORBIDDEN); } - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -1117,9 +1107,8 @@ public function addPublication(Request $illuminateRequest): JsonResponse $newId = Repo::publication()->add($publication); $publication = Repo::publication()->get($newId); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); + /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -1199,9 +1188,7 @@ public function versionPublication(Request $illuminateRequest): JsonResponse Mail::send($mailable); } - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -1275,9 +1262,7 @@ public function editPublication(Request $illuminateRequest): JsonResponse Repo::publication()->edit($publication, $params); $publication = Repo::publication()->get($publication->getId()); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -1337,9 +1322,8 @@ public function publishPublication(Request $illuminateRequest): JsonResponse $publication = Repo::publication()->get($publication->getId()); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); + /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -1381,9 +1365,8 @@ public function unpublishPublication(Request $illuminateRequest): JsonResponse $publication = Repo::publication()->get($publication->getId()); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); + /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -1425,9 +1408,7 @@ public function deletePublication(Request $illuminateRequest): JsonResponse ], Response::HTTP_FORBIDDEN); } - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::withContextIds($submission->getData('contextId'))->cursor(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -1791,7 +1772,7 @@ public function addDecision(Request $illuminateRequest): JsonResponse protected function getFirstUserGroupInRole(Enumerable $userGroups, int $role): ?UserGroup { - return $userGroups->first(fn (UserGroup $userGroup) => $userGroup->getRoleId() === $role); + return $userGroups->first(fn (UserGroup $userGroup) => $userGroup->roleId === $role); } /** @@ -1907,8 +1888,7 @@ protected function getPublicationLicenseForm(Request $illuminateRequest): JsonRe $submissionLocale = $submission->getData('locale'); $locales = $this->getPublicationFormLocales($context, $submission); - $authorUserGroups = Repo::userGroup()->getByRoleIds([Role::ROLE_ID_AUTHOR], $submission->getData('contextId')); - + $authorUserGroups = UserGroup::query()->withContextIds([$submission->getData('contextId')])->withRoleIds([Role::ROLE_ID_AUTHOR])->cursor(); $publicationLicenseForm = new PKPPublicationLicenseForm($publicationApiUrl, $locales, $publication, $context, $authorUserGroups); return response()->json($this->getLocalizedForm($publicationLicenseForm, $submissionLocale, $locales), Response::HTTP_OK); diff --git a/classes/author/Author.php b/classes/author/Author.php index 2580825406b..8db937368c3 100644 --- a/classes/author/Author.php +++ b/classes/author/Author.php @@ -17,10 +17,9 @@ */ namespace PKP\author; - -use APP\facades\Repo; use PKP\facades\Locale; use PKP\identity\Identity; +use PKP\userGroup\UserGroup; class Author extends Identity { @@ -212,8 +211,8 @@ public function getUserGroup() { //FIXME: should this be queried when fetching Author from DB? - see #5231. static $userGroup; // Frequently we'll fetch the same one repeatedly - if (!$userGroup || $this->getUserGroupId() != $userGroup->getId()) { - $userGroup = Repo::userGroup()->get($this->getUserGroupId()); + if (!$userGroup || $this->getData('userGroupId') != $userGroup->id) { + $userGroup = UserGroup::find($this->getData('userGroupId')); } return $userGroup; } @@ -226,7 +225,8 @@ public function getUserGroup() public function getLocalizedUserGroupName() { $userGroup = $this->getUserGroup(); - return $userGroup->getLocalizedName(); + return $userGroup ? $userGroup->getLocalizedData('name') : null; + } /** diff --git a/classes/author/maps/Schema.php b/classes/author/maps/Schema.php index 85e793acc8e..4ed4eb18add 100644 --- a/classes/author/maps/Schema.php +++ b/classes/author/maps/Schema.php @@ -35,7 +35,7 @@ public function __construct(PKPRequest $request, \PKP\context\Context $context, { parent::__construct($request, $context, $schemaService); - $this->authorUserGroups = Repo::userGroup()->getByRoleIds([Role::ROLE_ID_AUTHOR], $this->context->getId()); + $this->authorUserGroups = UserGroup::withRoleIds(Role::ROLE_ID_AUTHOR)->withContextIds($this->context->getId())->cursor(); } /** @@ -94,8 +94,8 @@ protected function mapByProperties(array $props, Author $item): array switch ($prop) { case 'userGroupName': /** @var UserGroup $userGroup */ - $userGroup = $this->authorUserGroups->first(fn (UserGroup $userGroup) => $userGroup->getId() === $item->getData('userGroupId')); - $output[$prop] = $userGroup ? $userGroup->getName(null) : new stdClass(); + $userGroup = $this->authorUserGroups->first(fn (UserGroup $userGroup) => $userGroup->id === $item->getData('userGroupId')); + $output[$prop] = $userGroup ? $userGroup->name : new stdClass(); break; case 'fullName': $output[$prop] = $item->getFullName(); diff --git a/classes/components/forms/context/PKPAppearanceMastheadForm.php b/classes/components/forms/context/PKPAppearanceMastheadForm.php index b321ec04799..3ffe1d1c4ff 100644 --- a/classes/components/forms/context/PKPAppearanceMastheadForm.php +++ b/classes/components/forms/context/PKPAppearanceMastheadForm.php @@ -15,11 +15,12 @@ namespace PKP\components\forms\context; -use APP\facades\Repo; use PKP\components\forms\FieldHTML; use PKP\components\forms\FieldOptions; use PKP\components\forms\FormComponent; use PKP\security\Role; +use PKP\userGroup\UserGroup; + class PKPAppearanceMastheadForm extends FormComponent { @@ -42,22 +43,22 @@ public function __construct($action, $locales, $context) $savedMastheadUserGroupIdsOrder = (array) $context->getData('mastheadUserGroupIds'); - $collector = Repo::userGroup()->getCollector(); - $allMastheadUserGroups = $collector - ->filterByContextIds([$context->getId()]) - ->filterByMasthead(true) - ->filterExcludeRoles([Role::ROLE_ID_REVIEWER]) - ->orderBy($collector::ORDERBY_ROLE_ID) - ->getMany() - ->toArray(); - - // sort the mashead roles in their saved order - $sortedAllMastheadUserGroups = array_replace(array_intersect_key(array_flip($savedMastheadUserGroupIdsOrder), $allMastheadUserGroups), $allMastheadUserGroups); - + $allMastheadUserGroups = UserGroup::withContextIds($context->getId()) + ->masthead(true) + ->excludeRoleIds(Role::ROLE_ID_REVIEWER) + ->orderByRoleId() + ->get(); + + // Sort the masthead user groups in their saved order + $sortedAllMastheadUserGroups = $allMastheadUserGroups->sortBy(function ($userGroup) use ($savedMastheadUserGroupIdsOrder) { + return array_search($userGroup->id, $savedMastheadUserGroupIdsOrder); + })->values(); + + $mastheadOptions = []; foreach ($sortedAllMastheadUserGroups as $userGroup) { $mastheadOptions[] = [ - 'value' => $userGroup->getId(), - 'label' => $userGroup->getLocalizedName() + 'value' => $userGroup->id, + 'label' => $userGroup->getLocalizedData('name') ]; } @@ -69,9 +70,9 @@ public function __construct($action, $locales, $context) 'options' => $mastheadOptions, 'allowOnlySorting' => true ])) - ->addField(new FieldHTML('reviewer', [ - 'label' => __('user.role.reviewers'), - 'description' => __('manager.setup.editorialMasthead.order.reviewers.description') - ])); + ->addField(new FieldHTML('reviewer', [ + 'label' => __('user.role.reviewers'), + 'description' => __('manager.setup.editorialMasthead.order.reviewers.description') + ])); } } diff --git a/classes/components/forms/context/PKPNotifyUsersForm.php b/classes/components/forms/context/PKPNotifyUsersForm.php index 00be9b088f7..92e06bed3c5 100644 --- a/classes/components/forms/context/PKPNotifyUsersForm.php +++ b/classes/components/forms/context/PKPNotifyUsersForm.php @@ -21,6 +21,7 @@ use PKP\components\forms\FieldRichTextarea; use PKP\components\forms\FieldText; use PKP\components\forms\FormComponent; +use PKP\userGroup\UserGroup; class PKPNotifyUsersForm extends FormComponent { @@ -41,22 +42,21 @@ public function __construct($action, $context) { $this->action = $action; - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); - - $userCountByGroupId = Repo::userGroup()->getUserCountByContextId($context->getId()); + $userGroups = UserGroup::withContextIds($context->getId()) + ->withCount(['userUserGroups as userCount']) + ->get(); $userGroupOptions = []; foreach ($userGroups as $userGroup) { - if (in_array($userGroup->getId(), (array) $context->getData('disableBulkEmailUserGroups'))) { + $userGroupId = $userGroup->id; + if (in_array($userGroupId, (array) $context->getData('disableBulkEmailUserGroups'))) { continue; } $userGroupOptions[] = [ - 'value' => $userGroup->getId(), + 'value' => $userGroupId, 'label' => $userGroup->getLocalizedData('name'), ]; - $this->userGroupCounts[$userGroup->getId()] = $userCountByGroupId->get($userGroup->getId(), 0); + $this->userGroupCounts[$userGroupId] = $userGroup->userCount ?? 0; } $currentUser = Application::get()->getRequest()->getUser(); diff --git a/classes/components/forms/context/PKPRestrictBulkEmailsForm.php b/classes/components/forms/context/PKPRestrictBulkEmailsForm.php index 9e95895af67..76e12276650 100644 --- a/classes/components/forms/context/PKPRestrictBulkEmailsForm.php +++ b/classes/components/forms/context/PKPRestrictBulkEmailsForm.php @@ -38,7 +38,7 @@ public function __construct($action, $context, LazyCollection $userGroups) $userGroupOptions = []; foreach ($userGroups as $userGroup) { $userGroupOptions[] = [ - 'value' => $userGroup->getId(), + 'value' => $userGroup->id, 'label' => htmlspecialchars($userGroup->getLocalizedData('name')), ]; } diff --git a/classes/components/forms/publication/ContributorForm.php b/classes/components/forms/publication/ContributorForm.php index f608d4b9e03..66e9e157153 100644 --- a/classes/components/forms/publication/ContributorForm.php +++ b/classes/components/forms/publication/ContributorForm.php @@ -48,16 +48,13 @@ public function __construct(string $action, array $locales, ?Submission $submiss $this->submission = $submission; $this->context = $context; - $authorUserGroupsOptions = Repo::userGroup() - ->getCollector() - ->filterByRoleIds([Role::ROLE_ID_AUTHOR]) - ->filterByContextIds([$context->getId()]) - ->getMany() + $authorUserGroupsOptions = UserGroup::withRoleIds([Role::ROLE_ID_AUTHOR]) + ->withContextIds([$context->getId()]) + ->get() ->map(fn (UserGroup $authorUserGroup) => [ - 'value' => (int) $authorUserGroup->getId(), - 'label' => $authorUserGroup->getLocalizedName(), + 'value' => (int) $authorUserGroup->usergroupid, + 'label' => $authorUserGroup->getLocalizedData('name'), ]); - $isoCodes = app(IsoCodesFactory::class); $countries = []; foreach ($isoCodes->getCountries() as $country) { diff --git a/classes/components/forms/statistics/users/ReportForm.php b/classes/components/forms/statistics/users/ReportForm.php index f1ea56220d2..e731bd5600a 100644 --- a/classes/components/forms/statistics/users/ReportForm.php +++ b/classes/components/forms/statistics/users/ReportForm.php @@ -20,6 +20,8 @@ use PKP\components\forms\FormComponent; use PKP\context\Context; use PKP\userGroup\UserGroup; +use PKP\userGroup\Repository as UserGroupRepository; + class ReportForm extends FormComponent { @@ -38,21 +40,20 @@ public function __construct(string $action, Context $context) $this->addPage(['id' => 'default', 'submitButton' => ['label' => __('common.export')]]); $this->addGroup(['id' => 'default', 'pageId' => 'default']); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds($context->getId())->get(); + $this->addField(new FieldOptions('userGroupIds', [ 'groupId' => 'default', 'label' => __('user.group'), 'description' => __('manager.export.usersToCsv.description'), - 'options' => $userGroups->values()->map(function (UserGroup $userGroup) { + 'options' => $userGroups->map(function (UserGroup $userGroup) { return [ - 'value' => $userGroup->getId(), - 'label' => htmlspecialchars($userGroup->getLocalizedName()) + 'value' => $userGroup->id, + 'label' => htmlspecialchars($userGroup->getLocalizedData('name')), ]; - }), - 'default' => $userGroups->keys(), + })->values()->toArray(), + 'default' => $userGroups->pluck('id')->toArray(), ])); } } diff --git a/classes/components/forms/submission/StartSubmission.php b/classes/components/forms/submission/StartSubmission.php index e8f812bfd04..94e8e77ec09 100644 --- a/classes/components/forms/submission/StartSubmission.php +++ b/classes/components/forms/submission/StartSubmission.php @@ -157,12 +157,12 @@ protected function addUserGroups(Enumerable $userGroups): void } $options = $userGroups->map(fn (UserGroup $userGroup) => [ - 'value' => $userGroup->getId(), - 'label' => $userGroup->getLocalizedName(), + 'value' => $userGroup->id, + 'label' => $userGroup->getLocalizedData('name'), ]); $hasEditorialRole = $userGroups->contains( - fn (UserGroup $userGroup) => in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN]) + fn (UserGroup $userGroup) => in_array($userGroup->roleId, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN]) ); $description = __('submission.submit.availableUserGroupsDescription'); diff --git a/classes/context/SubEditorsDAO.php b/classes/context/SubEditorsDAO.php index b8b2f1c42b8..5e80e2befce 100644 --- a/classes/context/SubEditorsDAO.php +++ b/classes/context/SubEditorsDAO.php @@ -84,8 +84,9 @@ public function deleteEditor(int $contextId, $assocId, $userId, $assocType) * * @param int[] $assocIds Section or category ids * @param int $assocType Application::ASSOC_TYPE_SECTION or Application::ASSOC_TYPE_CATEGORY + * @param int $contextId * - * @return Collection result rows with userId and userGroupId columns + * @return \Illuminate\Support\Collection result rows with userId and userGroupId properties */ public function getBySubmissionGroupIds(array $assocIds, int $assocType, int $contextId): Collection { @@ -197,10 +198,9 @@ public function assignEditors(Submission $submission, Context $context): Collect // that will cause duplicates to be overwritten $assignments = collect($assignments)->mapWithKeys(fn ($assignment, $key) => [$assignment->userId . '-' . $assignment->userGroupId => $assignment]); - $userGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); + $userGroups = UserGroup::query() + ->withContextIds([$submission->getData('contextId')]) + ->get(); $userGroupIds = $userGroups->keys(); @@ -210,13 +210,13 @@ public function assignEditors(Submission $submission, Context $context): Collect }); foreach ($assignments as $assignment) { - $userGroup = $userGroups->first(fn (UserGroup $userGroup) => $userGroup->getId() == $assignment->userGroupId); + $userGroup = $userGroups->first(fn (UserGroup $userGroup) => $userGroup->id == $assignment->userGroupId); Repo::stageAssignment() ->build( $submission->getId(), $assignment->userGroupId, $assignment->userId, - $userGroup->getRecommendOnly() + $userGroup->recommendOnly, ); } diff --git a/classes/controllers/grid/GridHandler.php b/classes/controllers/grid/GridHandler.php index 50c62ef9184..c97a03b9550 100644 --- a/classes/controllers/grid/GridHandler.php +++ b/classes/controllers/grid/GridHandler.php @@ -399,12 +399,16 @@ public function hasGridDataElements($request) public function setGridDataElements($data) { $this->callFeaturesHook('setGridDataElements', ['grid' => &$this, 'data' => &$data]); - + $this->_data = match (true) { - $data instanceof Enumerable => $this->toAssociativeArray($data), + $data instanceof LazyCollection => $this->toAssociativeArray($data), + + $data instanceof Enumerable => $this->toAssociativeArray(LazyCollection::make($data)), + $data instanceof DAOResultFactory => $data->toAssociativeArray(), $data instanceof ItemIterator => $data->toArray(), - is_iterable($data) => $data + is_iterable($data) => $data, + default => [], }; } diff --git a/classes/core/PKPApplication.php b/classes/core/PKPApplication.php index 5e32022c74e..96029082ce2 100644 --- a/classes/core/PKPApplication.php +++ b/classes/core/PKPApplication.php @@ -154,7 +154,7 @@ class_alias('\PKP\payment\QueuedPayment', '\QueuedPayment'); // QueuedPayment in Hook::addUnsupportedHooks('Mail::send', 'EditorAction::modifyDecisionOptions', 'EditorAction::recordDecision', 'Announcement::getProperties', 'Author::getProperties::values', 'EmailTemplate::getProperties', 'Galley::getProperties::values', 'Issue::getProperties::fullProperties', 'Issue::getProperties::summaryProperties', 'Issue::getProperties::values', 'Publication::getProperties', 'Section::getProperties::fullProperties', 'Section::getProperties::summaryProperties', 'Section::getProperties::values', 'Submission::getProperties::values', 'SubmissionFile::getProperties', 'User::getProperties::fullProperties', 'User::getProperties::reviewerSummaryProperties', 'User::getProperties::summaryProperties', 'User::getProperties::values', 'Announcement::getMany::queryBuilder', 'Announcement::getMany::queryObject', 'Author::getMany::queryBuilder', 'Author::getMany::queryObject', 'EmailTemplate::getMany::queryBuilder', 'EmailTemplate::getMany::queryObject::custom', 'EmailTemplate::getMany::queryObject::default', 'Galley::getMany::queryBuilder', 'Issue::getMany::queryBuilder', 'Publication::getMany::queryBuilder', 'Publication::getMany::queryObject', 'Stats::getOrderedObjects::queryBuilder', 'Stats::getRecords::queryBuilder', 'Stats::queryBuilder', 'Stats::queryObject', 'Submission::getMany::queryBuilder', 'Submission::getMany::queryObject', 'SubmissionFile::getMany::queryBuilder', 'SubmissionFile::getMany::queryObject', 'User::getMany::queryBuilder', 'User::getMany::queryObject', 'User::getReviewers::queryBuilder', 'CategoryDAO::_fromRow', 'IssueDAO::_fromRow', 'IssueDAO::_returnIssueFromRow', 'SectionDAO::_fromRow', 'UserDAO::_returnUserFromRow', 'UserDAO::_returnUserFromRowWithData', 'UserDAO::_returnUserFromRowWithReviewerStats', 'UserGroupDAO::_returnFromRow', 'ReviewerSubmissionDAO::_fromRow', 'API::stats::publication::abstract::params', 'API::stats::publication::galley::params', 'API::stats::publications::abstract::params', 'API::stats::publications::galley::params', 'PKPLocale::installLocale', 'PKPLocale::registerLocaleFile', 'PKPLocale::registerLocaleFile::isValidLocaleFile', 'PKPLocale::translate', 'API::submissions::files::params', 'ArticleGalleyDAO::getLocalizedGalleysByArticle', 'PluginGridHandler::plugin', 'PluginGridHandler::plugin', 'SubmissionFile::assignedFileStages', 'SubmissionHandler::saveSubmit'); // From the 3.4.0 Release Notebook; remove for 3.6.0 development branch Hook::addUnsupportedHooks('AcronPlugin::parseCronTab'); // pkp/pkp-lib#9678 Unavailable since stable-3_5_0; Hook::addUnsupportedHooks('Announcement::delete::before', 'Announcement::delete', 'Announcement::Collector'); // pkp/pkp-lib#10328 Unavailable since stable-3_5_0, use Eloquent Model events instead - + Hook::addUnsupportedHooks('UserGroup::delete::before','UserGroup::delete'); // unavailable since stable-3_6_0, use Eloquent Model events instead // If not in strict mode, globally expose constants on this class. if (!PKP_STRICT_MODE) { foreach ([ diff --git a/classes/core/PKPPageRouter.php b/classes/core/PKPPageRouter.php index 9f14fc29d1f..e9a0cbe2e05 100644 --- a/classes/core/PKPPageRouter.php +++ b/classes/core/PKPPageRouter.php @@ -25,6 +25,9 @@ use PKP\plugins\Hook; use PKP\security\Role; use PKP\security\Validation; +use PKP\userGroup\UserGroup; +use PKP\userGroup\relationships\UserUserGroup; + class PKPPageRouter extends PKPRouter { @@ -402,50 +405,70 @@ public function getHomeUrl(PKPRequest $request): string $userId = $user->getId(); if ($context = $this->getContext($request)) { - // If the user has no roles, or only one role and this is reader, go to "Index" page. - // Else go to "submissions" page - $userGroups = Repo::userGroup()->userUserGroups($userId, $context->getId()); - + // fetch user groups for the user in the current context + $userGroups = UserGroup::query() + ->where('context_id', $context->getId()) + ->whereHas('userUserGroups', function ($query) use ($userId) { + $query->where('user_id', $userId) + ->where(function ($q) { + $q->whereNull('date_end') + ->orWhere('date_end', '>', now()); + }) + ->where(function ($q) { + $q->whereNull('date_start') + ->orWhere('date_start', '<=', now()); + }); + }) + ->get(); + if ($userGroups->isEmpty() - || ($userGroups->count() == 1 && $userGroups->first()->getRoleId() == Role::ROLE_ID_READER) + || ($userGroups->count() == 1 && $userGroups->first()->role_id == Role::ROLE_ID_READER) ) { return $request->url(null, 'index'); } if(Config::getVar('features', 'enable_new_submission_listing')) { - $roleIds = $userGroups->map(function ($group) { - return $group->getRoleId(); - }); - - $roleIdsArray = $roleIds->all(); + $roleIdsArray = $userGroups->pluck('role_id')->all(); - if (count(array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT], $roleIdsArray))) { + if (array_intersect([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_SUB_EDITOR, Role::ROLE_ID_ASSISTANT], $roleIdsArray)) { return $request->url(null, 'dashboard', 'editorial'); } - if(count(array_intersect([ Role::ROLE_ID_REVIEWER], $roleIdsArray))) { + if (in_array(Role::ROLE_ID_REVIEWER, $roleIdsArray)) { return $request->url(null, 'dashboard', 'reviewAssignments'); - } - if(count(array_intersect([ Role::ROLE_ID_AUTHOR], $roleIdsArray))) { + if (in_array(Role::ROLE_ID_AUTHOR, $roleIdsArray)) { return $request->url(null, 'dashboard', 'mySubmissions'); } } return $request->url(null, 'submissions'); } else { - // The user is at the site context, check to see if they are - // only registered in one place w/ one role - $userGroups = Repo::userGroup()->userUserGroups($userId, \PKP\core\PKPApplication::SITE_CONTEXT_ID); + // The user is at the site context + $userGroups = UserGroup::query() + ->where('context_id', \PKP\core\PKPApplication::SITE_CONTEXT_ID) + ->whereHas('userUserGroups', function ($query) use ($userId) { + $query->where('user_id', $userId) + ->where(function ($q) { + $q->whereNull('date_end') + ->orWhere('date_end', '>', now()); + }) + ->where(function ($q) { + $q->whereNull('date_start') + ->orWhere('date_start', '<=', now()); + }); + }) + ->get(); + if ($userGroups->count() == 1) { $firstUserGroup = $userGroups->first(); $contextDao = Application::getContextDAO(); - $context = $contextDao->getById($firstUserGroup->getContextId()); + $context = $contextDao->getById($firstUserGroup->context_id); if (!isset($context)) { $request->redirect(Application::SITE_CONTEXT_PATH, 'index'); } - if ($firstUserGroup->getRoleId() == Role::ROLE_ID_READER) { + if ($firstUserGroup->role_id == Role::ROLE_ID_READER) { $request->redirect(null, 'index'); } } diff --git a/classes/core/SettingsBuilder.php b/classes/core/SettingsBuilder.php index b7b1a408375..f8de1fbeb22 100644 --- a/classes/core/SettingsBuilder.php +++ b/classes/core/SettingsBuilder.php @@ -24,6 +24,7 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use stdClass; +use PKP\facades\Locale; class SettingsBuilder extends Builder { @@ -161,36 +162,36 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' if ($column instanceof ConditionExpression || $column instanceof Closure) { return parent::where($column, $operator, $value, $boolean); } - - $settings = []; - $primaryColumn = false; - - // See Illuminate\Database\Query\Builder::where() + + // Prepare value and operator [$value, $operator] = $this->query->prepareValueAndOperator( $value, $operator, func_num_args() === 2 ); - + $modelSettingsList = $this->model->getSettings(); - + + $settings = []; + $primaryColumn = null; + if (is_string($column)) { if (in_array($column, $modelSettingsList)) { $settings[$column] = $value; } else { - $primaryColumn = $column; + $primaryColumn = $this->getSnakeKey($column); } + } elseif (is_array($column)) { + $settings = array_intersect_key($column, array_flip($modelSettingsList)); + $primaryColumn = array_diff_key($column, $settings); + $primaryColumn = array_map([$this, 'getSnakeKey'], array_keys($primaryColumn)); } - - if (is_array($column)) { - $settings = array_intersect($column, $modelSettingsList); - $primaryColumn = array_diff($column, $modelSettingsList); - } - + if (empty($settings)) { - return parent::where($column, $operator, $value, $boolean); + return parent::where($primaryColumn ?? $column, $operator, $value, $boolean); } - + + // Handle settings $where = []; foreach ($settings as $settingName => $settingValue) { $where = array_merge($where, [ @@ -198,19 +199,24 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' 'setting_value' => $settingValue, ]); } - + $this->query->whereIn( $this->model->getKeyName(), fn (QueryBuilder $query) => $query->select($this->model->getKeyName())->from($this->model->getSettingsTable())->where($where, null, null, $boolean) ); - + if (!empty($primaryColumn)) { parent::where($primaryColumn, $operator, $value, $boolean); } - + return $this; } + + protected function getSnakeKey($key) + { + return Str::snake($key); + } /** * Add a "where in" clause to the query. @@ -224,10 +230,16 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' */ public function whereIn($column, $values, $boolean = 'and', $not = false) { - if ($column instanceof Expression || !in_array($column, $this->model->getSettings())) { + if ($column instanceof Expression) { return parent::whereIn($column, $values, $boolean, $not); } - + + $column = $this->getSnakeKey($column); + + if (!in_array($column, $this->model->getSettings())) { + return parent::whereIn($column, $values, $boolean, $not); + } + $this->query->whereIn( $this->model->getKeyName(), fn (QueryBuilder $query) => @@ -237,9 +249,10 @@ public function whereIn($column, $values, $boolean = 'and', $not = false) ->where('setting_name', $column) ->whereIn('setting_value', $values, $boolean, $not) ); - + return $this; } + /* * Augment model with data from the settings table diff --git a/classes/decision/DecisionType.php b/classes/decision/DecisionType.php index 511714da168..df0cf9474ee 100644 --- a/classes/decision/DecisionType.php +++ b/classes/decision/DecisionType.php @@ -262,7 +262,7 @@ protected function getAssignedAuthorIds(Submission $submission): array ->withRoleIds([Role::ROLE_ID_AUTHOR]) ->withStageIds([$this->getStageId()]) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); } diff --git a/classes/decision/Repository.php b/classes/decision/Repository.php index 79d1f81a6bd..553eb376938 100644 --- a/classes/decision/Repository.php +++ b/classes/decision/Repository.php @@ -428,7 +428,7 @@ protected function updateNotifications(Decision $decision, DecisionType $decisio ->withRoleIds([Role::ROLE_ID_AUTHOR]) ->withStageIds([$decisionType->getStageId()]) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); $notificationMgr->updateNotification( diff --git a/classes/decision/Steps.php b/classes/decision/Steps.php index c8a31c509f0..2a97287a596 100644 --- a/classes/decision/Steps.php +++ b/classes/decision/Steps.php @@ -81,7 +81,7 @@ public function getStageParticipants(int $roleId): array ->withRoleIds([$roleId]) ->withStageIds([$this->decisionType->getStageId()]) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); $users = []; @@ -119,7 +119,7 @@ public function getDecidingEditors(): array ->withRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]) ->withRecommendOnly(false) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); $users = []; diff --git a/classes/install/PKPInstall.php b/classes/install/PKPInstall.php index eb39b49e6c0..72f88cc21ba 100644 --- a/classes/install/PKPInstall.php +++ b/classes/install/PKPInstall.php @@ -43,6 +43,7 @@ use PKP\services\PKPSchemaService; use PKP\site\SiteDAO; use PKP\site\Version; +use PKP\userGroup\UserGroup; class PKPInstall extends Installer { @@ -232,21 +233,31 @@ public function createData() Repo::user()->add($user); // Create an admin user group - $adminUserGroup = Repo::userGroup()->newDataObject(); - $adminUserGroup->setRoleId(Role::ROLE_ID_SITE_ADMIN); - $adminUserGroup->setContextId(\PKP\core\PKPApplication::SITE_CONTEXT_ID); - $adminUserGroup->setDefault(true); + $adminUserGroup = new UserGroup([ + 'roleId' => Role::ROLE_ID_SITE_ADMIN, + 'contextId' => \PKP\core\PKPApplication::SITE_CONTEXT_ID, + 'isDefault' => true, + ]); + + // Prepare multilingual 'name' and 'namePlural' settings + $names = []; + $namePlurals = []; foreach ($this->installedLocales as $locale) { - $name = __('default.groups.name.siteAdmin', [], $locale); - $namePlural = __('default.groups.plural.siteAdmin', [], $locale); - $adminUserGroup->setData('name', $name, $locale); - $adminUserGroup->setData('namePlural', $namePlural, $locale); + $names[$locale] = __('default.groups.name.siteAdmin', [], $locale); + $namePlurals[$locale] = __('default.groups.plural.siteAdmin', [], $locale); } - Repo::userGroup()->add($adminUserGroup); - // Put the installer into this user group - Repo::userGroup()->assignUserToGroup($user->getId(), $adminUserGroup->getId()); + // Set the 'name' and 'namePlural' settings + $adminUserGroup->setData('name', $names); + $adminUserGroup->setData('namePlural', $namePlurals); + + // Save the UserGroup to the database + $adminUserGroup->save(); + // Assign the user to the admin user group + $repository = Repo::userGroup(); + $repository->assignUserToGroup($user->getId(), $adminUserGroup->userGroupId); + // Add initial site data /** @var SiteDAO */ $siteDao = DAORegistry::getDAO('SiteDAO'); diff --git a/classes/invitation/invitations/userRoleAssignment/helpers/UserGroupHelper.php b/classes/invitation/invitations/userRoleAssignment/helpers/UserGroupHelper.php index 9908053974d..0e05778a1ad 100644 --- a/classes/invitation/invitations/userRoleAssignment/helpers/UserGroupHelper.php +++ b/classes/invitation/invitations/userRoleAssignment/helpers/UserGroupHelper.php @@ -32,7 +32,7 @@ public function __construct( public function getUserGroup() { - $this->userGroup = Repo::userGroup()->get($this->userGroupId); + $this->userGroup = UserGroup::find($this->userGroupId); } public static function fromArray(array $data): self diff --git a/classes/invitation/invitations/userRoleAssignment/resources/UserRoleAssignmentInviteResource.php b/classes/invitation/invitations/userRoleAssignment/resources/UserRoleAssignmentInviteResource.php index a6eb4f6a073..9f54445f053 100644 --- a/classes/invitation/invitations/userRoleAssignment/resources/UserRoleAssignmentInviteResource.php +++ b/classes/invitation/invitations/userRoleAssignment/resources/UserRoleAssignmentInviteResource.php @@ -18,6 +18,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\JsonResource; use PKP\user\User; +use PKP\userGroup\UserGroup; class UserRoleAssignmentInviteResource extends JsonResource { @@ -70,11 +71,11 @@ public function toArray(Request $request) protected function transformUserGroups(?array $userGroups) { return collect($userGroups)->map(function ($userGroup) { - $userGroupModel = Repo::userGroup()->get($userGroup['userGroupId']); + $userGroupModel = UserGroup::find($userGroup['userGroupId']); return [ 'userGroupId' => $userGroup['userGroupId'], - 'userGroupName' => $userGroupModel->getName(null), + 'userGroupName' => $userGroupModel ? $userGroupModel->getLocalizedData('name') : null, 'masthead' => $userGroup['masthead'], 'dateStart' => $userGroup['dateStart'], 'dateEnd' => $userGroup['dateEnd'], diff --git a/classes/invitation/invitations/userRoleAssignment/rules/UserGroupExistsRule.php b/classes/invitation/invitations/userRoleAssignment/rules/UserGroupExistsRule.php index 775e1d08445..e7c4db8c939 100644 --- a/classes/invitation/invitations/userRoleAssignment/rules/UserGroupExistsRule.php +++ b/classes/invitation/invitations/userRoleAssignment/rules/UserGroupExistsRule.php @@ -14,8 +14,8 @@ namespace PKP\invitation\invitations\userRoleAssignment\rules; -use APP\facades\Repo; use Illuminate\Contracts\Validation\Rule; +use PKP\userGroup\UserGroup; class UserGroupExistsRule implements Rule { @@ -23,10 +23,9 @@ class UserGroupExistsRule implements Rule public function passes($attribute, $value) { $this->userGroupId = $value; - $userGroup = Repo::userGroup()->get($value); + $userGroup = UserGroup::find($value); return isset($userGroup); } - public function message() { return __('invitation.userRoleAssignment.validation.error.addUserRoles.userGroupNotExisting', [ diff --git a/classes/mail/mailables/UserRoleAssignmentInvitationNotify.php b/classes/mail/mailables/UserRoleAssignmentInvitationNotify.php index bf45a7c6bb6..21456f48312 100644 --- a/classes/mail/mailables/UserRoleAssignmentInvitationNotify.php +++ b/classes/mail/mailables/UserRoleAssignmentInvitationNotify.php @@ -27,7 +27,7 @@ use PKP\mail\traits\Sender; use PKP\security\Role; use PKP\userGroup\relationships\UserUserGroup; -use UserGroup; +use PKP\userGroup\UserGroup; class UserRoleAssignmentInvitationNotify extends Mailable { @@ -94,20 +94,15 @@ private function getAllUserUserGroupSection(array $userUserGroups, ?UserGroup $u $count = 1; foreach ($userUserGroups as $userUserGroup) { - if ($userUserGroup instanceof UserUserGroup) { - $userGroupHelper = UserGroupHelper::fromUserUserGroup($userUserGroup); - } else { - $userGroupHelper = UserGroupHelper::fromArray($userUserGroup); - } - + $userGroupHelper = $userUserGroup instanceof UserUserGroup + ? UserGroupHelper::fromUserUserGroup($userUserGroup) + : UserGroupHelper::fromArray($userUserGroup); + if ($count == 1) { $retString = $title; } - $userGroupToUse = $userGroup; - if (!isset($userGroupToUse)) { - $userGroupToUse = Repo::userGroup()->get($userGroupHelper->userGroupId); - } + $userGroupToUse = $userGroup ?? UserGroup::find($userGroupHelper->userGroupId); $userGroupSection = $this->getUserUserGroupSection($userGroupHelper, $userGroupToUse, $context, $count, $locale); @@ -132,7 +127,7 @@ private function getUserUserGroupSection(UserGroupHelper $userUserGroup, UserGro $sectionMastheadAppear = __('emails.userRoleAssignmentInvitationNotify.userGroupSectionWillNotAppear', [ 'contextName' => $context->getName($locale), - 'sectionName' => $userGroup->getName($locale) + 'sectionName' => $userGroup->getLocalizedData('name', $locale) ] ); @@ -140,7 +135,7 @@ private function getUserUserGroupSection(UserGroupHelper $userUserGroup, UserGro $sectionMastheadAppear = __('emails.userRoleAssignmentInvitationNotify.userGroupSectionWillAppear', [ 'contextName' => $context->getName($locale), - 'sectionName' => $userGroup->getName($locale) + 'sectionName' => $userGroup->getLocalizedData('name', $locale) ] ); } @@ -148,7 +143,7 @@ private function getUserUserGroupSection(UserGroupHelper $userUserGroup, UserGro $userGroupSection = __('emails.userRoleAssignmentInvitationNotify.userGroupSection', [ 'sectionNumber' => $count, - 'sectionName' => $userGroup->getName($locale), + 'sectionName' => $userGroup->getLocalizedData('name', $locale), 'dateStart' => $userUserGroup->dateStart, 'sectionEndingDate' => $sectionEndingDate, 'sectionMastheadAppear' => $sectionMastheadAppear @@ -192,24 +187,35 @@ public function setData(?string $locale = null): void foreach ($this->invitation->getPayload()->userGroupsToRemove as $userUserGroup) { $userGroupHelper = UserGroupHelper::fromArray($userUserGroup); - $userGroup = Repo::userGroup()->get($userGroupHelper->userGroupId); + // Replace the repository call with UserGroup::find() + $userGroup = UserGroup::find($userGroupHelper->userGroupId); + + // Fetch the user-user group relationships $userUserGroups = UserUserGroup::withUserId($user->getId()) - ->withUserGroupId($userGroup->getId()) + ->withUserGroupId($userGroup->id) ->withActive() ->get(); - $userGroupsRemoved = $this->getAllUserUserGroupSection($userUserGroups->toArray(), $userGroup, $context, $locale, $userGroupsRemovedTitle); + // Process the user groups removed + $userGroupsRemoved = $this->getAllUserUserGroupSection( + $userUserGroups->toArray(), + $userGroup, + $context, + $locale, + $userGroupsRemovedTitle + ); } // Existing Roles - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$this->invitation->getContextId()]) - ->filterByUserIds([$user->getId()]) - ->getMany(); + + $userGroups = UserGroup::query() + ->withContextIds([$this->invitation->getContextId()]) + ->withUserIds([$user->getId()]) + ->get(); foreach ($userGroups as $userGroup) { $userUserGroups = UserUserGroup::withUserId($user->getId()) - ->withUserGroupId($userGroup->getId()) + ->withUserGroupId($userGroup->id) ->withActive() ->get(); diff --git a/classes/notification/managerDelegate/PendingRevisionsNotificationManager.php b/classes/notification/managerDelegate/PendingRevisionsNotificationManager.php index dbf3c219052..463ec6d42bd 100644 --- a/classes/notification/managerDelegate/PendingRevisionsNotificationManager.php +++ b/classes/notification/managerDelegate/PendingRevisionsNotificationManager.php @@ -95,7 +95,18 @@ public function getNotificationTitle(Notification $notification): string */ public function updateNotification(PKPRequest $request, ?array $userIds, int $assocType, int $assocId): void { + if (empty($userIds)) { + // no user IDs provided; nothing to update + return; + } + $userId = current($userIds); + + if (!$userId || !is_int($userId) || $userId <= 0) { + // invalid user ID; skip notification creation + return; + } + $submissionId = $assocId; $stageData = $this->_getStageDataByType(); if ($stageData == null) { diff --git a/classes/observers/listeners/RestrictAuthorAssignment.php b/classes/observers/listeners/RestrictAuthorAssignment.php index a3c58ce22bb..079900be931 100644 --- a/classes/observers/listeners/RestrictAuthorAssignment.php +++ b/classes/observers/listeners/RestrictAuthorAssignment.php @@ -21,6 +21,7 @@ use PKP\observers\events\SubmissionSubmitted; use PKP\security\Role; use PKP\stageAssignment\StageAssignment; +use PKP\userGroup\UserGroup; class RestrictAuthorAssignment { @@ -40,12 +41,12 @@ public function handle(SubmissionSubmitted $event) ->get(); foreach ($stageAssignments as $stageAssignment) { - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId, $event->context->getId()); + $userGroup = UserGroup::findById($stageAssignment->userGroupId, $event->context->getId()); if (!$userGroup) { continue; } - $stageAssignment->canChangeMetadata = $userGroup->getPermitMetadataEdit(); + $stageAssignment->canChangeMetadata = $userGroup->permitMetadataEdit; $stageAssignment->save(); } } diff --git a/classes/observers/listeners/SendSubmissionAcknowledgement.php b/classes/observers/listeners/SendSubmissionAcknowledgement.php index cdc1392f2ae..4e18043c474 100644 --- a/classes/observers/listeners/SendSubmissionAcknowledgement.php +++ b/classes/observers/listeners/SendSubmissionAcknowledgement.php @@ -47,7 +47,7 @@ public function handle(SubmissionSubmitted $event) $assignedUserIds = StageAssignment::withSubmissionIds([$event->submission->getId()]) ->withRoleIds([Role::ROLE_ID_AUTHOR]) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); $submitterUsers = Repo::user() diff --git a/classes/observers/listeners/UpdateAuthorStageAssignments.php b/classes/observers/listeners/UpdateAuthorStageAssignments.php index 039c9bd7e9e..ffaf243b27d 100644 --- a/classes/observers/listeners/UpdateAuthorStageAssignments.php +++ b/classes/observers/listeners/UpdateAuthorStageAssignments.php @@ -22,6 +22,7 @@ use PKP\observers\events\SubmissionSubmitted; use PKP\security\Role; use PKP\stageAssignment\StageAssignment; +use PKP\userGroup\UserGroup; class UpdateAuthorStageAssignments { @@ -41,18 +42,16 @@ public function handle(SubmissionSubmitted $event) ->withStageIds([$event->submission->getData('stageId')]) ->get(); - $userGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$event->context->getId()]) - ->filterByRoleIds([Role::ROLE_ID_AUTHOR]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$event->context->getId()]) + ->withRoleIds([Role::ROLE_ID_AUTHOR]) + ->get(); foreach ($stageAssigments as $stageAssignment) { $userGroup = $userGroups->get($stageAssignment->userGroupId); - if (!$userGroup || $stageAssignment->canChangeMetadata === $userGroup->getPermitMetadataEdit()) { + if (!$userGroup || $stageAssignment->canChangeMetadata === $userGroup->permitMetadataEdit) { continue; } - $stageAssignment->canChangeMetadata = $userGroup->getPermitMetadataEdit(); + $stageAssignment->canChangeMetadata = $userGroup->permitMetadataEdit; $stageAssignment->save(); } } diff --git a/classes/orcid/PKPOrcidWork.php b/classes/orcid/PKPOrcidWork.php index 407ddf81bbb..e80bfb61bf3 100644 --- a/classes/orcid/PKPOrcidWork.php +++ b/classes/orcid/PKPOrcidWork.php @@ -258,7 +258,8 @@ private function buildOrcidContributors(array $authors, Context $context, Public ]; $userGroup = $author->getUserGroup(); - $role = self::USER_GROUP_TO_ORCID_ROLE[$userGroup->getName('en')]; + $roleName = $userGroup->getLocalizedData('name', 'en'); + $role = self::USER_GROUP_TO_ORCID_ROLE[$roleName]; if ($role) { $contributor['contributor-attributes']['contributor-role'] = $role; diff --git a/classes/publication/PKPPublication.php b/classes/publication/PKPPublication.php index 849eb63b9fe..f726e88775f 100644 --- a/classes/publication/PKPPublication.php +++ b/classes/publication/PKPPublication.php @@ -190,23 +190,24 @@ public function getAuthorString(\Traversable $userGroups, $includeInBrowseOnly = } if ($includeInBrowseOnly) { - $authors = $authors->filter(function ($author, $key) { - return $author->getData('includeInBrowse'); - }); + $authors = $authors->filter(fn($author) => $author->getData('includeInBrowse')); + } + + // create a mapping of user group ids to user groups for quick lookup + $userGroupMap = []; + foreach ($userGroups as $userGroup) { + $userGroupMap[$userGroup->id] = $userGroup; } $str = ''; $lastUserGroupId = null; foreach ($authors as $author) { + $currentUserGroupId = $author->getUserGroupId(); if (!empty($str)) { - if ($lastUserGroupId != $author->getData('userGroupId')) { - foreach ($userGroups as $userGroup) { - if ($lastUserGroupId === $userGroup->getId()) { - if ($userGroup->getData('showTitle')) { - $str .= ' (' . $userGroup->getLocalizedData('name') . ')'; - } - break; - } + if ($lastUserGroupId !== $currentUserGroupId) { + $lastUserGroup = $userGroupMap[$lastUserGroupId] ?? null; + if ($lastUserGroup && $lastUserGroup->showTitle) { + $str .= ' (' . $lastUserGroup->getLocalizedData('name') . ')'; } $str .= __('common.semicolonListSeparator'); } else { @@ -214,18 +215,14 @@ public function getAuthorString(\Traversable $userGroups, $includeInBrowseOnly = } } $str .= $author->getFullName(); - $lastUserGroupId = $author->getUserGroupId(); + $lastUserGroupId = $currentUserGroupId; } // If there needs to be a trailing user group title, add it if (isset($author)) { - foreach ($userGroups as $userGroup) { - if ($author->getData('userGroupId') === $userGroup->getId()) { - if ($userGroup->getData('showTitle')) { - $str .= ' (' . $userGroup->getLocalizedData('name') . ')'; - } - break; - } + $lastUserGroup = $userGroupMap[$author->getUserGroupId()] ?? null; + if ($lastUserGroup && $lastUserGroup->showTitle) { + $str .= ' (' . $lastUserGroup->getLocalizedData('name') . ')'; } } diff --git a/classes/security/RoleDAO.php b/classes/security/RoleDAO.php index 48cd849bfc1..9dbc7b29ebf 100644 --- a/classes/security/RoleDAO.php +++ b/classes/security/RoleDAO.php @@ -25,6 +25,7 @@ use PKP\core\Core; use PKP\db\DAO; use PKP\db\DAORegistry; +use PKP\userGroup\UserGroup; class RoleDAO extends DAO { @@ -89,14 +90,26 @@ public function getByUserId(int $userId, ?int $contextId = Application::SITE_CON */ public function getByUserIdGroupedByContext(int $userId) { - $roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */ - $userGroups = Repo::userGroup()->userUserGroups($userId); - + $userGroups = UserGroup::query() + ->with('userUserGroups') + ->whereHas('userUserGroups', function ($query) use ($userId) { + $query->where('user_id', $userId) + ->where(function ($q) { + $q->whereNull('date_end') + ->orWhere('date_end', '>', now()); + }) + ->where(function ($q) { + $q->whereNull('date_start') + ->orWhere('date_start', '<=', now()); + }); + }) + ->get(); + $roles = []; foreach ($userGroups as $userGroup) { - $role = $roleDao->newDataObject(); - $role->setRoleId($userGroup->getRoleId()); - $roles[(int) $userGroup->getContextId()][$userGroup->getRoleId()] = $role; + $role = $this->newDataObject(); + $role->setRoleId($userGroup->role_id); + $roles[(int) $userGroup->context_id][$userGroup->role_id] = $role; } return $roles; diff --git a/classes/security/Validation.php b/classes/security/Validation.php index ffa499c611f..1831d88e820 100644 --- a/classes/security/Validation.php +++ b/classes/security/Validation.php @@ -26,6 +26,8 @@ use PKP\site\Site; use PKP\site\SiteDAO; use PKP\user\User; +use PKP\userGroup\UserGroup; +use PKP\security\Role; class Validation { @@ -396,42 +398,76 @@ public static function isSiteAdmin() */ public static function canAdminister($administeredUserId, $administratorUserId) { - $roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */ - // You can administer yourself if ($administeredUserId == $administratorUserId) { return true; } - // You cannot administer administrators - if ($roleDao->userHasRole(\PKP\core\PKPApplication::SITE_CONTEXT_ID, $administeredUserId, Role::ROLE_ID_SITE_ADMIN)) { + $siteContextId = \PKP\core\PKPApplication::SITE_CONTEXT_ID; + + // check if administered user is site admin + $isAdministeredUserSiteAdmin = UserGroup::query() + ->withContextIds($siteContextId) + ->withRoleIds(Role::ROLE_ID_SITE_ADMIN) + ->whereHas('userUserGroups', function ($query) use ($administeredUserId) { + $query->withUserId($administeredUserId) + ->withActive(); + }) + ->exists(); + + if ($isAdministeredUserSiteAdmin) { return false; } - - // Otherwise, administrators can administer everyone - if ($roleDao->userHasRole(\PKP\core\PKPApplication::SITE_CONTEXT_ID, $administratorUserId, Role::ROLE_ID_SITE_ADMIN)) { + + // check if administrator user is site admin + $isAdministratorUserSiteAdmin = UserGroup::query() + ->withContextIds($siteContextId) + ->withRoleIds(Role::ROLE_ID_SITE_ADMIN) + ->whereHas('userUserGroups', function ($query) use ($administratorUserId) { + $query->withUserId($administratorUserId) + ->withActive(); + }) + ->exists(); + + if ($isAdministratorUserSiteAdmin) { return true; } - - // Check for administered user group assignments in other contexts - // that the administrator user doesn't have a manager role in. - $userGroups = Repo::userGroup()->userUserGroups($administeredUserId); - foreach ($userGroups as $userGroup) { - if ($userGroup->getContextId() != \PKP\core\PKPApplication::SITE_CONTEXT_ID && !$roleDao->userHasRole($userGroup->getContextId(), $administratorUserId, Role::ROLE_ID_MANAGER)) { - // Found an assignment: disqualified. - return false; - } + + // Get contexts where administered user has roles + $administeredUserContexts = UserGroup::query() + ->whereHas('userUserGroups', function ($query) use ($administeredUserId) { + $query->withUserId($administeredUserId) + ->withActive(); + }) + ->get() + ->map(fn ($userGroup) => $userGroup->contextId) + ->unique() + ->values() + ->toArray(); + + // get contexts where administrator user has manager role + $administratorManagerContexts = UserGroup::query() + ->withRoleIds(Role::ROLE_ID_MANAGER) + ->whereHas('userUserGroups', function ($query) use ($administratorUserId) { + $query->withUserId($administratorUserId) + ->withActive(); + }) + ->get() + ->map(fn ($userGroup) => $userGroup->contextId) + ->unique() + ->values() + ->toArray(); + + // check for conflicting contexts + $conflictingContexts = array_diff($administeredUserContexts, $administratorManagerContexts); + + if (!empty($conflictingContexts)) { + // found conflicting contexts: disqualified + return false; } // Make sure the administering user has a manager role somewhere - $foundManagerRole = false; - $roles = $roleDao->getByUserId($administratorUserId); - foreach ($roles as $role) { - if ($role->getRoleId() == Role::ROLE_ID_MANAGER) { - $foundManagerRole = true; - } - } - if (!$foundManagerRole) { + if (empty($administratorManagerContexts)) { return false; } @@ -454,63 +490,73 @@ public static function getAdministrationLevel(int $administeredUserId, int $admi if ($administeredUserId == $administratorUserId) { return self::ADMINISTRATION_FULL; } - - $filteredSiteAdminUserGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([\PKP\core\PKPApplication::SITE_CONTEXT_ID]) - ->filterByRoleIds([Role::ROLE_ID_SITE_ADMIN]); - - // You cannot administer administrators - if ($filteredSiteAdminUserGroups->filterByUserIds([$administeredUserId])->getCount() > 0) { + + $siteContextId = \PKP\core\PKPApplication::SITE_CONTEXT_ID; + + // Check if administered user is site admin + $isAdministeredUserSiteAdmin = UserGroup::query() + ->withContextIds($siteContextId) + ->withRoleIds(Role::ROLE_ID_SITE_ADMIN) + ->whereHas('userUserGroups', function ($query) use ($administeredUserId) { + $query->withUserId($administeredUserId) + ->withActive(); + }) + ->exists(); + + if ($isAdministeredUserSiteAdmin) { return self::ADMINISTRATION_PROHIBITED; } - - // Otherwise, administrators can administer everyone - if ($filteredSiteAdminUserGroups->filterByUserIds([$administratorUserId])->getCount() > 0) { + + // Check if administrator user is site admin + $isAdministratorUserSiteAdmin = UserGroup::query() + ->withContextIds($siteContextId) + ->withRoleIds(Role::ROLE_ID_SITE_ADMIN) + ->whereHas('userUserGroups', function ($query) use ($administratorUserId) { + $query->withUserId($administratorUserId) + ->withActive(); + }) + ->exists(); + + if ($isAdministratorUserSiteAdmin) { return self::ADMINISTRATION_FULL; } - // Make sure the administering user has a manager role somewhere - $roleManagerCount = Repo::userGroup() - ->getCollector() - ->filterByUserIds([$administratorUserId]) - ->filterByRoleIds([Role::ROLE_ID_MANAGER]) - ->getCount(); - - if ($roleManagerCount <= 0) { + // Get contexts where administrator has manager role + $administratorManagerContexts = UserGroup::query() + ->withRoleIds(Role::ROLE_ID_MANAGER) + ->whereHas('userUserGroups', function ($query) use ($administratorUserId) { + $query->withUserId($administratorUserId) + ->withActive(); + }) + ->get() + ->map(fn ($userGroup) => $userGroup->contextId) + ->unique() + ->values() + ->toArray(); + + // Ensure the administrator has a manager role somewhere + if (empty($administratorManagerContexts)) { return self::ADMINISTRATION_PROHIBITED; } - - $administeredUserAssignedGroupIds = Repo::userGroup() - ->getCollector() - ->filterByUserIds([$administeredUserId]) - ->getMany() - ->map(fn ($userGroup) => $userGroup->getContextId()) - ->sort() + + // Get contexts where administered user has roles + $administeredUserContexts = UserGroup::query() + ->whereHas('userUserGroups', function ($query) use ($administeredUserId) { + $query->withUserId($administeredUserId) + ->withActive(); + }) + ->get() + ->map(fn ($userGroup) => $userGroup->contextId) + ->unique() + ->values() ->toArray(); - - $administratorUserAssignedGroupIds = Repo::userGroup() - ->getCollector() - ->filterByUserIds([$administratorUserId]) - ->filterByRoleIds([Role::ROLE_ID_MANAGER]) - ->getMany() - ->map(fn ($userGroup) => $userGroup->getContextId()) - ->sort() - ->toArray(); - - // Check for administered user group assignments in other contexts - // that the administrator user doesn't have a manager role in. - if (collect($administeredUserAssignedGroupIds)->diff($administratorUserAssignedGroupIds)->count() > 0) { - // Found an assignment: disqualified. - // But also determine if a partial administrate is allowed - // if the Administrator User is a Journal Manager in the current context - if ($contextId !== null && - Repo::userGroup() - ->getCollector() - ->filterByContextIds([$contextId]) - ->filterByUserIds([$administratorUserId]) - ->filterByRoleIds([Role::ROLE_ID_MANAGER]) - ->getCount()) { + + // Check for conflicting contexts + $conflictingContexts = array_diff($administeredUserContexts, $administratorManagerContexts); + + if (!empty($conflictingContexts)) { + // Check for partial administration + if ($contextId !== null && in_array($contextId, $administratorManagerContexts)) { return self::ADMINISTRATION_PARTIAL; } return self::ADMINISTRATION_PROHIBITED; diff --git a/classes/security/authorization/StageRolePolicy.php b/classes/security/authorization/StageRolePolicy.php index 99929128a1c..69dbd2cdb93 100644 --- a/classes/security/authorization/StageRolePolicy.php +++ b/classes/security/authorization/StageRolePolicy.php @@ -21,6 +21,7 @@ use APP\facades\Repo; use PKP\security\Role; use PKP\stageAssignment\StageAssignment; +use PKP\userGroup\UserGroup; class StageRolePolicy extends AuthorizationPolicy { @@ -79,8 +80,8 @@ public function effect() ->get(); foreach ($stageAssignments as $stageAssignment) { - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId); - if (in_array($userGroup->getRoleId(), $this->_roleIds) && !$stageAssignment->recommendOnly) { + $userGroup = UserGroup::findById($stageAssignment->userGroupId); + if ($userGroup && in_array($userGroup->roleId, $this->_roleIds) && !$stageAssignment->recommendOnly) { return AuthorizationPolicy::AUTHORIZATION_PERMIT; } } @@ -101,8 +102,8 @@ public function effect() $noResults = true; foreach ($stageAssignments as $stageAssignment) { $noResults = false; - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId); - if ($userGroup->getRoleId() == Role::ROLE_ID_MANAGER && !$stageAssignment->recommendOnly) { + $userGroup = UserGroup::find($stageAssignment->userGroupId); + if ($userGroup && $userGroup->roleId == Role::ROLE_ID_MANAGER && !$stageAssignment->recommendOnly) { return AuthorizationPolicy::AUTHORIZATION_PERMIT; } } diff --git a/classes/security/authorization/internal/DecisionAllowedPolicy.php b/classes/security/authorization/internal/DecisionAllowedPolicy.php index 59f90ffe0fa..87651e89263 100644 --- a/classes/security/authorization/internal/DecisionAllowedPolicy.php +++ b/classes/security/authorization/internal/DecisionAllowedPolicy.php @@ -22,6 +22,7 @@ use PKP\security\Role; use PKP\stageAssignment\StageAssignment; use PKP\user\User; +use PKP\userGroup\UserGroup; class DecisionAllowedPolicy extends AuthorizationPolicy { @@ -67,8 +68,8 @@ public function effect() } else { $isAllowed = false; foreach ($stageAssignments as $stageAssignment) { - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId); - if (!in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR])) { + $userGroup = UserGroup::findById($stageAssignment->userGroupId); + if ($userGroup && !in_array($userGroup->roleId, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR])) { continue; } if (Repo::decision()->isRecommendation($decisionType->getDecision()) && $stageAssignment->recommendOnly) { diff --git a/classes/services/PKPContextService.php b/classes/services/PKPContextService.php index fc8ab3fb4bf..cf65fe33eee 100644 --- a/classes/services/PKPContextService.php +++ b/classes/services/PKPContextService.php @@ -48,7 +48,11 @@ use PKP\services\interfaces\EntityReadInterface; use PKP\services\interfaces\EntityWriteInterface; use PKP\submission\GenreDAO; +use PKP\userGroup\Repository as UserGroupRepository; use PKP\validation\ValidatorFactory; +use PKP\userGroup\UserGroup; +use PKP\userGroup\relationships\UserUserGroup; + abstract class PKPContextService implements EntityPropertyInterface, EntityReadInterface, EntityWriteInterface { @@ -547,10 +551,29 @@ public function add($context, $request) $genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */ $genreDao->installDefaults($context->getId(), $context->getData('supportedLocales')); - Repo::userGroup()->installSettings($context->getId(), 'registry/userGroups.xml'); - - $managerUserGroup = Repo::userGroup()->getByRoleIds([Role::ROLE_ID_MANAGER], $context->getId(), true)->firstOrFail(); - Repo::userGroup()->assignUserToGroup($currentUser->getId(), $managerUserGroup->getId()); + $userGroupRepository = app(UserGroupRepository::class); + $userGroupRepository->installSettings($context->getId(), 'registry/userGroups.xml'); + + + $managerUserGroup = UserGroup::withContextIds([$context->getId()]) + ->withRoleIds([Role::ROLE_ID_MANAGER]) + ->isDefault(true) + ->firstOrFail(); + + $assignmentExists = UserUserGroup::query() + ->withUserId($currentUser->getId()) + ->withUserGroupId($managerUserGroup->id) + ->exists(); + + if (!$assignmentExists) { + UserUserGroup::create([ + 'userId' => $currentUser->getId(), + 'userGroupId' => $managerUserGroup->userGroupId, + 'dateStart' => null, + 'dateEnd' => null, + 'masthead' => null, + ]); + } $fileManager = new FileManager(); foreach ($this->installFileDirs as $dir) { @@ -623,7 +646,7 @@ public function delete($context) Repo::reviewAssignment()->deleteByContextId($context->getId()); - Repo::userGroup()->deleteByContextId($context->getId()); + UserGroup::withContextIds($context->getId())->delete(); $genreDao = DAORegistry::getDAO('GenreDAO'); /** @var GenreDAO $genreDao */ $genreDao->deleteByContextId($context->getId()); diff --git a/classes/services/PKPSchemaService.php b/classes/services/PKPSchemaService.php index b276636861e..7e1a8cbe41c 100644 --- a/classes/services/PKPSchemaService.php +++ b/classes/services/PKPSchemaService.php @@ -269,12 +269,12 @@ public function groupPropsByOrigin(string $schemaName, bool $excludeReadOnly = f if (empty($propSchema->origin)) { continue; } - + // Exclude readonly if specified - if ($excludeReadOnly && $propSchema->origin->readOnly) { + if ($excludeReadOnly && !empty($propSchema->readOnly)) { continue; } - + switch($propSchema->origin) { case Schema::ATTRIBUTE_ORIGIN_SETTINGS: $propsByOrigin[Schema::ATTRIBUTE_ORIGIN_SETTINGS][] = $propName; @@ -288,9 +288,10 @@ public function groupPropsByOrigin(string $schemaName, bool $excludeReadOnly = f break; } } - + return $propsByOrigin; } + /** * Sanitize properties according to a schema diff --git a/classes/stageAssignment/Repository.php b/classes/stageAssignment/Repository.php index 01ecd814bbb..23e0d0797ea 100644 --- a/classes/stageAssignment/Repository.php +++ b/classes/stageAssignment/Repository.php @@ -20,6 +20,7 @@ use APP\facades\Repo; use PKP\core\Core; +use PKP\userGroup\UserGroup; class Repository { @@ -29,7 +30,7 @@ class Repository public function build(int $submissionId, int $userGroupId, int $userId, ?bool $recommendOnly = null, ?bool $canChangeMetadata = null): StageAssignment { // Set defaults - $canChangeMetadata ??= Repo::userGroup()->get($userGroupId)->getPermitMetadataEdit(); + $canChangeMetadata ??= UserGroup::find($userGroupId)?->permitMetadataEdit ?? false; $recommendOnly ??= false; return StageAssignment::withSubmissionIds([$submissionId]) diff --git a/classes/stageAssignment/StageAssignment.php b/classes/stageAssignment/StageAssignment.php index 04da0d97002..315d50e9e1c 100644 --- a/classes/stageAssignment/StageAssignment.php +++ b/classes/stageAssignment/StageAssignment.php @@ -27,7 +27,10 @@ use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use PKP\userGroup\relationships\UserGroupStage; +use PKP\userGroup\UserGroup; +use PKP\user\User; class StageAssignment extends Model { @@ -55,6 +58,16 @@ public function userGroupStages(): HasMany return $this->hasMany(UserGroupStage::class, 'user_group_id', 'user_group_id'); } + public function userGroup(): BelongsTo + { + return $this->belongsTo(UserGroup::class, 'user_group_id', 'user_group_id'); + } + + public function user(): BelongsTo + { + return $this->belongsTo(User::class, 'user_id', 'user_id'); + } + // Accessors and Mutators /** diff --git a/classes/submission/Repository.php b/classes/submission/Repository.php index 2538f515656..0adb80d6925 100644 --- a/classes/submission/Repository.php +++ b/classes/submission/Repository.php @@ -42,6 +42,7 @@ use PKP\user\User; use PKP\validation\ValidatorFactory; use PKP\config\Config; +use PKP\userGroup\UserGroup; abstract class Repository { @@ -176,7 +177,11 @@ public function getWorkflowUrlByUserRoles(Submission $submission, ?int $userId = $dispatcher = $request->getDispatcher(); // Check if the user is an author of this submission - $authorUserGroupIds = Repo::userGroup()->getArrayIdByRoleId(Role::ROLE_ID_AUTHOR); + $authorUserGroupIds = UserGroup::withContextIds([$submission->getData('contextId')]) + ->withRoleIds([Role::ROLE_ID_AUTHOR]) + ->get() + ->map(fn($userGroup) => $userGroup->id) + ->toArray(); // Replaces StageAssignmentDAO::getBySubmissionAndStageId $stageAssignments = StageAssignment::withSubmissionIds([$submission->getId()]) @@ -187,6 +192,7 @@ public function getWorkflowUrlByUserRoles(Submission $submission, ?int $userId = foreach ($stageAssignments as $stageAssignment) { if (in_array($stageAssignment->userGroupId, $authorUserGroupIds)) { $authorDashboard = true; + break; } } diff --git a/classes/submission/maps/Schema.php b/classes/submission/maps/Schema.php index 0924f1915f1..049a83cbcc7 100644 --- a/classes/submission/maps/Schema.php +++ b/classes/submission/maps/Schema.php @@ -540,12 +540,12 @@ function (Role $role) { ->withStageIds([$stageId]) ->get(); - foreach ($stageAssignments as $stageAssignment) { - $userGroup = $this->getUserGroup($stageAssignment->userGroupId); - if ($userGroup) { - $currentUserAssignedRoles[] = $userGroup->getRoleId(); + foreach ($stageAssignments as $stageAssignment) { + $userGroup = $this->getUserGroup($stageAssignment->userGroupId); + if ($userGroup) { + $currentUserAssignedRoles[] = $userGroup->roleId; + } } - } // Replaces StageAssignmentDAO::getBySubmissionAndUserIdAndStageId $stageAssignments = StageAssignment::withSubmissionIds([$submission->getId()]) @@ -554,9 +554,9 @@ function (Role $role) { // FIXME - $stageAssignments are just temporarly added until https://github.com/pkp/pkp-lib/issues/10480 is ready foreach ($stageAssignments as $stageAssignment) { - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId); + $userGroup = UserGroup::find($stageAssignment->userGroupId); $stageAssignmentsOverview[] = [ - "roleId" => $userGroup->getRoleId(), + "roleId" => $userGroup?->roleId ?? null, "recommendOnly" => $stageAssignment->recommendOnly, "canChangeMetadata" => $stageAssignment->canChangeMetadata, "userId" => $stageAssignment->userId @@ -670,9 +670,8 @@ protected function getPropertyStageAssignments(Enumerable $stageAssignments): bo protected function getUserGroup(int $userGroupId): ?UserGroup { - /** @var UserGroup $userGroup */ foreach ($this->userGroups as $userGroup) { - if ($userGroup->getId() === $userGroupId) { + if ($userGroup->id === $userGroupId) { return $userGroup; } } diff --git a/classes/submission/reviewer/ReviewerAction.php b/classes/submission/reviewer/ReviewerAction.php index 9b00d21a067..82718cd60e5 100644 --- a/classes/submission/reviewer/ReviewerAction.php +++ b/classes/submission/reviewer/ReviewerAction.php @@ -133,18 +133,20 @@ public function getResponseEmail( // Get editorial contact name // Replaces StageAssignmentDAO::getBySubmissionAndStageId - $stageAssignments = StageAssignment::withSubmissionIds([$submission->getId()]) + // Eager loading 'userGroup' and 'user' relationships + $stageAssignments = StageAssignment::with(['userGroup', 'user']) + ->withSubmissionIds([$submission->getId()]) ->withStageIds([$reviewAssignment->getStageId()]) ->get(); $recipients = []; foreach ($stageAssignments as $stageAssignment) { - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId); - if (!in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR])) { + $userGroup = $stageAssignment->userGroup; + if ($userGroup && !in_array($userGroup->roleId, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR])) { continue; } - $recipients[] = Repo::user()->get($stageAssignment->userId); + $recipients[] = $stageAssignment->user; } // Create dummy user if no one assigned diff --git a/classes/submission/reviewer/form/PKPReviewerReviewStep3Form.php b/classes/submission/reviewer/form/PKPReviewerReviewStep3Form.php index 6250d1cfc81..101221aaf08 100644 --- a/classes/submission/reviewer/form/PKPReviewerReviewStep3Form.php +++ b/classes/submission/reviewer/form/PKPReviewerReviewStep3Form.php @@ -43,6 +43,7 @@ use PKP\submission\reviewAssignment\ReviewAssignment; use PKP\submission\SubmissionComment; use PKP\submission\SubmissionCommentDAO; +use PKP\userGroup\UserGroup; class PKPReviewerReviewStep3Form extends ReviewerReviewForm { @@ -169,72 +170,71 @@ public function execute(...$functionParams) 'recommendation' => (int) $this->getData('recommendation'), // assign the recommendation to the review assignment, if there was one. ]); - // Replaces StageAssignmentDAO::getBySubmissionAndStageId + // Retrieve stage assignments for managers and sub-editors $stageAssignments = StageAssignment::withSubmissionIds([$submission->getId()]) ->withStageIds([$submission->getData('stageId')]) + ->whereHas('userGroup', function ($query) { + $query->withRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]); + }) ->get(); - $receivedList = []; // Avoid sending twice to the same user. - - /** @var NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */ - $notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO'); + $receivedList = []; + // get the NotificationSubscriptionSettingsDAO + $notificationSubscriptionSettingsDao = DAORegistry::getDAO('NotificationSubscriptionSettingsDAO'); /** @var NotificationSubscriptionSettingsDAO $notificationSubscriptionSettingsDao */ + foreach ($stageAssignments as $stageAssignment) { $userId = $stageAssignment->userId; - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId); - - // Never send reviewer comment notification to users other than managers and editors. - if (!in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]) || in_array($userId, $receivedList)) { + if (in_array($userId, $receivedList)) { continue; } - - // Notify editors - $notification = $notificationMgr->createNotification( - Application::get()->getRequest(), + $notificationType = Notification::NOTIFICATION_TYPE_REVIEWER_COMMENT; + $contextId = $context->getId(); + // retrieve blocked email notifications for the user + $blockedEmailNotifications = $notificationSubscriptionSettingsDao->getNotificationSubscriptionSettings( + NotificationSubscriptionSettingsDAO::BLOCKED_EMAIL_NOTIFICATION_KEY, $userId, - Notification::NOTIFICATION_TYPE_REVIEWER_COMMENT, - $submission->getData('contextId'), - PKPApplication::ASSOC_TYPE_REVIEW_ASSIGNMENT, - $reviewAssignment->getId() + $contextId ); - // Check if user is subscribed to this type of notification emails - if (!$notification || in_array( - Notification::NOTIFICATION_TYPE_REVIEWER_COMMENT, - $notificationSubscriptionSettingsDao->getNotificationSubscriptionSettings( - NotificationSubscriptionSettingsDAO::BLOCKED_EMAIL_NOTIFICATION_KEY, + // check if the user has blocked this notification type + if (!in_array($notificationType, $blockedEmailNotifications)) { + // Notify editors + $notification = $notificationMgr->createNotification( + Application::get()->getRequest(), $userId, - (int) $context->getId() - ) - ) - ) { - continue; - } + $notificationType, + $submission->getData('contextId'), + PKPApplication::ASSOC_TYPE_REVIEW_ASSIGNMENT, + $reviewAssignment->getId() + ); + + if ($notification) { + $mailable = new ReviewCompleteNotifyEditors($context, $submission, $reviewAssignment); + $template = Repo::emailTemplate()->getByKey($context->getId(), ReviewCompleteNotifyEditors::getEmailTemplateKey()); + + if (!$template) { + $template = Repo::emailTemplate()->getByKey($context->getId(), 'NOTIFICATION'); + $request = Application::get()->getRequest(); + $mailable->addData([ + 'notificationContents' => $notificationMgr->getNotificationContents($request, $notification), + 'notificationUrl' => $notificationMgr->getNotificationUrl($request, $notification), + ]); + } - $mailable = new ReviewCompleteNotifyEditors($context, $submission, $reviewAssignment); - $template = Repo::emailTemplate()->getByKey($context->getId(), ReviewCompleteNotifyEditors::getEmailTemplateKey()); - - // The template may not exist, see pkp/pkp-lib#9109 - if (!$template) { - $template = Repo::emailTemplate()->getByKey($context->getId(), 'NOTIFICATION'); - $request = Application::get()->getRequest(); - $mailable->addData([ - 'notificationContents' => $notificationMgr->getNotificationContents($request, $notification), - 'notificationUrl' => $notificationMgr->getNotificationUrl($request, $notification), - ]); + $user = Repo::user()->get($userId); + $mailable + ->from($context->getData('contactEmail'), $context->getData('contactName')) + ->recipients([$user]) + ->subject($template->getLocalizedData('subject')) + ->body($template->getLocalizedData('body')) + ->allowUnsubscribe($notification); // include unsubscribe link + + Mail::send($mailable); + Repo::emailLogEntry()->logMailable(SubmissionEmailLogEventType::REVIEW_COMPLETE, $mailable, $submission, $user); + + $receivedList[] = $userId; + } } - - $user = Repo::user()->get($userId); - $mailable - ->from($context->getData('contactEmail'), $context->getData('contactName')) - ->recipients([$user]) - ->subject($template->getLocalizedData('subject')) - ->body($template->getLocalizedData('body')) - ->allowUnsubscribe($notification); - - Mail::send($mailable); - Repo::emailLogEntry()->logMailable(SubmissionEmailLogEventType::REVIEW_COMPLETE, $mailable, $submission, $user); - - $receivedList[] = $userId; } // Remove the task diff --git a/classes/submissionFile/Repository.php b/classes/submissionFile/Repository.php index 5d859a0afc9..4bd6a02bc98 100644 --- a/classes/submissionFile/Repository.php +++ b/classes/submissionFile/Repository.php @@ -311,7 +311,7 @@ public function add(SubmissionFile $submissionFile): int ->withRoleIds([Role::ROLE_ID_AUTHOR]) ->withStageIds([$reviewRound->getStageId()]) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); $notificationMgr = new NotificationManager(); @@ -436,7 +436,7 @@ public function delete(SubmissionFile $submissionFile): void $authorUserIds = StageAssignment::withSubmissionIds([$submissionFile->getData('submissionId')]) ->withRoleIds([Role::ROLE_ID_AUTHOR]) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); $notificationMgr->updateNotification( diff --git a/classes/template/PKPTemplateManager.php b/classes/template/PKPTemplateManager.php index d21963db271..954af9e2448 100644 --- a/classes/template/PKPTemplateManager.php +++ b/classes/template/PKPTemplateManager.php @@ -62,6 +62,7 @@ use PKP\submissionFile\SubmissionFile; use Smarty; use Smarty_Internal_Template; +use PKP\userGroup\UserGroup; require_once('./lib/pkp/lib/vendor/smarty/smarty/libs/plugins/modifier.escape.php'); // Seems to be needed? @@ -1325,11 +1326,24 @@ public function display($template = null, $cache_id = null, $compile_id = null, if (Application::isInstalled()) { $user = $this->_request->getUser(); if ($user) { - $userGroups = Repo::userGroup()->userUserGroups($user->getId()); - + // Fetch user groups where the user is assigned + $userGroups = UserGroup::query() + ->whereHas('userUserGroups', function ($query) use ($user) { + $query->where('user_id', $user->getId()) + ->where(function ($q) { + $q->whereNull('date_end') + ->orWhere('date_end', '>', now()); + }) + ->where(function ($q) { + $q->whereNull('date_start') + ->orWhere('date_start', '<=', now()); + }); + }) + ->get(); + $userRoles = []; foreach ($userGroups as $userGroup) { - $userRoles[] = (int) $userGroup->getRoleId(); + $userRoles[] = (int) $userGroup->role_id; } $currentUser = [ 'csrfToken' => $this->_request->getSession()->token(), diff --git a/classes/user/Report.php b/classes/user/Report.php index 7adfe007c57..f2401583083 100644 --- a/classes/user/Report.php +++ b/classes/user/Report.php @@ -79,7 +79,7 @@ private function _getHeadings(): array __('common.mailingAddress'), __('user.dateRegistered'), __('common.updated'), - ...array_map(fn (UserGroup $userGroup) => $userGroup->getLocalizedName(), $this->_getUserGroups()) + ...array_map(fn (UserGroup $userGroup) => $userGroup->getLocalizedData('name'), $this->_getUserGroups()) ]; } @@ -90,11 +90,23 @@ private function _getHeadings(): array */ private function _getDataRow(User $user): array { - $userGroups = Repo::userGroup()->userUserGroups($user->getId()); - $groups = []; - foreach ($userGroups as $userGroup) { - $groups[$userGroup->getId()] = 0; - } + // fetch user groups where the user is assigned + $userGroups = UserGroup::query() + ->whereHas('userUserGroups', function ($query) use ($user) { + $query->where('user_id', $user->getId()) + ->where(function ($q) { + $q->whereNull('date_end') + ->orWhere('date_end', '>', now()); + }) + ->where(function ($q) { + $q->whereNull('date_start') + ->orWhere('date_start', '<=', now()); + }); + }) + ->get(); + + // get the IDs of the user's groups + $userGroupIds = $userGroups->pluck('user_group_id')->all(); return [ $user->getId(), @@ -106,7 +118,10 @@ private function _getDataRow(User $user): array $user->getMailingAddress(), $user->getDateRegistered(), $user->getLocalizedData('dateProfileUpdated'), - ...array_map(fn (UserGroup $userGroup) => __(isset($groups[$userGroup->getId()]) ? 'common.yes' : 'common.no'), $this->_getUserGroups()) + ...array_map( + fn (UserGroup $userGroup) => __(in_array($userGroup->user_group_id, $userGroupIds) ? 'common.yes' : 'common.no'), + $this->_getUserGroups() + ) ]; } @@ -118,9 +133,8 @@ private function _getDataRow(User $user): array private function _getUserGroups(): array { static $cache; - return $cache ??= Repo::userGroup()->getCollector() - ->filterByContextIds([$this->_request->getContext()->getId()]) - ->getMany() + return $cache ??= UserGroup::withContextIds([$this->_request->getContext()->getId()]) + ->get() ->toArray(); } } diff --git a/classes/user/Repository.php b/classes/user/Repository.php index a1ac4f02ef9..685c8cb47f2 100644 --- a/classes/user/Repository.php +++ b/classes/user/Repository.php @@ -29,6 +29,8 @@ use PKP\security\RoleDAO; use PKP\stageAssignment\StageAssignment; use PKP\submission\SubmissionCommentDAO; +use PKP\userGroup\relationships\UserUserGroup; + class Repository { @@ -225,15 +227,14 @@ public function getAccessibleWorkflowStages(int $userId, int $contextId, Submiss $accessibleWorkflowStages = []; // Replaces StageAssignmentDAO::getBySubmissionAndUserIdAndStageId - $stageAssignments = StageAssignment::with(['userGroupStages']) + $stageAssignments = StageAssignment::with(['userGroup.userGroupStages']) ->withSubmissionIds([$submission->getId()]) ->withUserId($userId) ->get(); - - // Assigned users have access based on their assignment + foreach ($stageAssignments as $stageAssignment) { - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId); - $roleId = $userGroup->getRoleId(); + $userGroup = $stageAssignment->userGroup; + $roleId = $userGroup->roleId; // Check global user roles within the context, e.g., user can be assigned in the role, which was revoked if (!in_array($roleId, $userRoleIds)) { @@ -361,15 +362,33 @@ public function mergeUsers(int $oldUserId, int $newUserId) $subEditorsDao->deleteByUserId($oldUserId); // Transfer old user's roles - $userGroups = Repo::userGroup()->userUserGroups($oldUserId); - foreach ($userGroups as $userGroup) { - if (!Repo::userGroup()->userInGroup($newUserId, $userGroup->getId())) { - $mastheadStatus = Repo::userGroup()->getUserUserGroupMastheadStatus($oldUserId, $userGroup->getId()); - Repo::userGroup()->assignUserToGroup($newUserId, $userGroup->getId(), null, null, $mastheadStatus); + $userUserGroups = UserUserGroup::query() + ->withUserId($oldUserId) + ->get(); + + // Transfer assignments to the new user + foreach ($userUserGroups as $userUserGroup) { + // Check if the new user is already assigned to this user group + $exists = UserUserGroup::query() + ->withUserId($newUserId) + ->withUserGroupId($userUserGroup->userGroupId) + ->exists(); + + if (!$exists) { + UserUserGroup::create([ + 'userId' => $newUserId, + 'userGroupId' => $userUserGroup->userGroupId, + 'dateStart' => $userUserGroup->dateStart, + 'dateEnd' => $userUserGroup->dateEnd, + 'masthead' => $userUserGroup->masthead, + ]); } } - Repo::userGroup()->deleteAssignmentsByUserId($oldUserId); + // Delete all user group assignments for the old user + UserUserGroup::query() + ->withUserId($oldUserId) + ->delete(); // Transfer stage assignments. $stageAssignments = StageAssignment::withUserId($oldUserId)->get(); diff --git a/classes/user/form/RegistrationForm.php b/classes/user/form/RegistrationForm.php index ec31b730d9e..321e33564f9 100644 --- a/classes/user/form/RegistrationForm.php +++ b/classes/user/form/RegistrationForm.php @@ -34,6 +34,7 @@ use PKP\site\Site; use PKP\user\InterestManager; use PKP\user\User; +use PKP\userGroup\UserGroup; class RegistrationForm extends Form { @@ -204,13 +205,16 @@ public function validate($callHooks = true) } if (!Config::getVar('general', 'sitewide_privacy_statement')) { - $contextIds = []; - foreach ($this->getData('userGroupIds') as $userGroupId) { - $userGroup = Repo::userGroup()->get($userGroupId); - $contextIds[] = $userGroup->getContextId(); - } - - $contextIds = array_unique($contextIds); + $userGroupIds = $this->getData('userGroupIds'); + + // Fetch all user groups in a single query + $userGroups = UserGroup::query()->withUserGroupIds($userGroupIds)->get(); + + // Collect context IDs using the 'map' method + $contextIds = $userGroups->map(function ($userGroup) { + return $userGroup->contextId; + })->unique()->toArray(); + if (!empty($contextIds)) { $contextDao = Application::getContextDao(); $privacyConsent = (array) $this->getData('privacyConsent'); diff --git a/classes/user/form/RolesForm.php b/classes/user/form/RolesForm.php index 3989b8d275f..0a8520d3e43 100644 --- a/classes/user/form/RolesForm.php +++ b/classes/user/form/RolesForm.php @@ -21,6 +21,7 @@ use APP\template\TemplateManager; use PKP\user\InterestManager; use PKP\user\User; +use PKP\userGroup\UserGroup; class RolesForm extends BaseProfileForm { @@ -43,10 +44,8 @@ public function fetch($request, $template = null, $display = false) { $templateMgr = TemplateManager::getManager($request); - $userGroupIds = Repo::userGroup()->getCollector() - ->filterByUserIds([$request->getUser()->getId()]) - ->getIds() - ->toArray(); + $userGroupIds = UserGroup::getIdsByUserId($request->getUser()->getId()); + $templateMgr->assign('userGroupIds', $userGroupIds); diff --git a/classes/user/form/UserFormHelper.php b/classes/user/form/UserFormHelper.php index bcca06406ca..cd43b4bd813 100644 --- a/classes/user/form/UserFormHelper.php +++ b/classes/user/form/UserFormHelper.php @@ -106,11 +106,11 @@ public function saveRoleContent($form, $user) $groupFormData = (array) $form->getData($groupData['formElement']); $userGroups = Repo::userGroup()->getByRoleIds([$groupData['roleId']], $context->getId()); foreach ($userGroups as $userGroup) { - if (!$userGroup->getPermitSelfRegistration()) { + if (!$userGroup->permitSelfRegistration) { continue; } - $groupId = $userGroup->getId(); + $groupId = $userGroup->id; $inGroup = Repo::userGroup()->userInGroup($user->getId(), $groupId); if (!$inGroup && array_key_exists($groupId, $groupFormData)) { Repo::userGroup()->assignUserToGroup($user->getId(), $groupId); diff --git a/classes/user/maps/Schema.php b/classes/user/maps/Schema.php index 30e960bc97e..583e5e492e1 100644 --- a/classes/user/maps/Schema.php +++ b/classes/user/maps/Schema.php @@ -22,7 +22,8 @@ use PKP\stageAssignment\StageAssignment; use PKP\user\User; use PKP\workflow\WorkflowStageDAO; -use Submission; +use APP\submission\Submission; +use PKP\userGroup\UserGroup; class Schema extends \PKP\core\maps\Schema { @@ -153,18 +154,26 @@ protected function mapByProperties(array $props, User $user, array $auxiliaryDat case 'groups': $output[$prop] = null; if ($this->context) { - $userGroups = Repo::userGroup()->userUserGroups($user->getId(), $this->context->getId()); + // Fetch user groups where the user is assigned in the current context + $userGroups = UserGroup::query() + ->withContextIds($this->context->getId()) + ->whereHas('userUserGroups', function ($query) use ($user) { + $query->withUserId($user->getId()) + ->withActive(); + }) + ->get(); + $output[$prop] = []; foreach ($userGroups as $userGroup) { $output[$prop][] = [ - 'id' => (int) $userGroup->getId(), - 'name' => $userGroup->getName(null), - 'abbrev' => $userGroup->getAbbrev(null), - 'roleId' => (int) $userGroup->getRoleId(), - 'showTitle' => (bool) $userGroup->getShowTitle(), - 'permitSelfRegistration' => (bool) $userGroup->getPermitSelfRegistration(), - 'permitMetadataEdit' => (bool) $userGroup->getPermitMetadataEdit(), - 'recommendOnly' => (bool) $userGroup->getRecommendOnly(), + 'id' => (int) $userGroup->id, + 'name' => $userGroup->getLocalizedData('name'), + 'abbrev' => $userGroup->getLocalizedData('abbrev'), + 'roleId' => (int) $userGroup->roleId, + 'showTitle' => (bool) $userGroup->showTitle, + 'permitSelfRegistration' => (bool) $userGroup->permitSelfRegistration, + 'permitMetadataEdit' => (bool) $userGroup->permitMetadataEdit, + 'recommendOnly' => (bool) $userGroup->recommendOnly, ]; } } @@ -188,38 +197,47 @@ protected function mapByProperties(array $props, User $user, array $auxiliaryDat } break; case 'stageAssignments': - $submission = $auxiliaryData['submission']; - $stageId = $auxiliaryData['stageId']; + $submission = $auxiliaryData['submission'] ?? null; + $stageId = $auxiliaryData['stageId'] ?? null; - if((!isset($submission) || !isset($stageId)) || (!($submission instanceof Submission) || !is_numeric($auxiliaryData['stageId']))) { + if ( + !($submission instanceof Submission) || + !is_numeric($stageId) + ) { $output['stageAssignments'] = []; break; } // Get User's stage assignments for submission. - // Note: - // - A User can potentially have multiple assignments for a submission. - // - A User can potentially have multiple assignments for a stage of a submission. - $stageAssignments = StageAssignment::withSubmissionIds([$submission->getId()]) - ->withStageIds($stageId ? [$stageId] : []) + $stageAssignments = StageAssignment::with(['userGroup']) + ->withSubmissionIds([$submission->getId()]) + ->withStageIds([$stageId]) ->withUserId($user->getId()) ->withContextId($this->context->getId()) ->get(); $results = []; - foreach ($stageAssignments as $stageAssignment /**@var StageAssignment $stageAssignment*/) { - // Get related user group info for stage assignment - $userGroup = Repo::userGroup()->get($stageAssignment->userGroupId); + foreach ($stageAssignments as $stageAssignment) { + $userGroup = $stageAssignment->userGroup; // Only prepare data for non-reviewer participants - if ($userGroup->getRoleId() !== Role::ROLE_ID_REVIEWER) { + if ($userGroup && $userGroup->roleId !== Role::ROLE_ID_REVIEWER) { $entry = [ 'stageAssignmentId' => $stageAssignment->id, - 'stageAssignmentUserGroup' => $userGroup->getAllData(), + 'stageAssignmentUserGroup' => [ + 'id' => (int) $userGroup->id, + 'name' => $userGroup->getLocalizedData('name'), + 'abbrev' => $userGroup->getLocalizedData('abbrev'), + 'roleId' => (int) $userGroup->roleId, + 'showTitle' => (bool) $userGroup->showTitle, + 'permitSelfRegistration' => (bool) $userGroup->permitSelfRegistration, + 'permitMetadataEdit' => (bool) $userGroup->permitMetadataEdit, + 'recommendOnly' => (bool) $userGroup->recommendOnly, + ], 'stageAssignmentStageId' => $stageId, - 'recommendOnly' => (bool)$stageAssignment->recommendOnly, - 'canChangeMetadata' => (bool)$stageAssignment->canChangeMetadata + 'recommendOnly' => (bool) $stageAssignment->recommendOnly, + 'canChangeMetadata' => (bool) $stageAssignment->canChangeMetadata, ]; $workflowStageDao = DAORegistry::getDAO('WorkflowStageDAO'); /** @var WorkflowStageDAO $workflowStageDao */ @@ -230,22 +248,20 @@ protected function mapByProperties(array $props, User $user, array $auxiliaryDat $results[] = $entry; } - - $output['stageAssignments'] = $results; } + $output['stageAssignments'] = $results; break; default: $output[$prop] = $user->getData($prop); break; } + } + $output = $this->schemaService->addMissingMultilingualValues($this->schema, $output, $this->context->getSupportedFormLocales()); - $output = $this->schemaService->addMissingMultilingualValues($this->schema, $output, $this->context->getSupportedFormLocales()); - - Hook::call('UserSchema::getProperties::values', [$this, &$output, $user, $props]); + Hook::call('UserSchema::getProperties::values', [$this, &$output, $user, $props]); - ksort($output); - } + ksort($output); return $output; } diff --git a/classes/userGroup/Collector.php b/classes/userGroup/Collector.php deleted file mode 100644 index 63ac1c4749a..00000000000 --- a/classes/userGroup/Collector.php +++ /dev/null @@ -1,364 +0,0 @@ -dao = $dao; - } - - public function getCount(): int - { - return $this->dao->getCount($this); - } - - /** - * @return Collection - */ - public function getIds(): Collection - { - return $this->dao->getIds($this); - } - - /** - * @copydoc DAO::getMany() - * - * @return LazyCollection - */ - public function getMany(): LazyCollection - { - return $this->dao->getMany($this); - } - - /** - * Filter by multiple ids - */ - public function filterByUserGroupIds(?array $ids): self - { - $this->userGroupIds = $ids; - return $this; - } - - /** - * Filter by context IDs - */ - public function filterByContextIds(?array $contextIds): self - { - $this->contextIds = $contextIds; - return $this; - } - - /** - * Filter by publication IDs - */ - public function filterByPublicationIds(?array $publicationIds): self - { - $this->publicationIds = $publicationIds; - return $this; - } - - /** - * Filter by roles - */ - public function filterByRoleIds(?array $roleIds): self - { - $this->roleIds = $roleIds; - return $this; - } - - /** - * Exclude roles - */ - public function filterExcludeRoles(?array $excludedRoles): self - { - $this->excludeRoles = $excludedRoles; - return $this; - } - - /** - * Filter by contexts - */ - public function filterByStageIds(?array $stageIds): self - { - $this->stageIds = $stageIds; - return $this; - } - - /** - * Filter by is default - */ - public function filterByIsDefault(?bool $isDefault): self - { - $this->isDefault = $isDefault; - return $this; - } - - /** - * Filter by show title - */ - public function filterByShowTitle(?bool $showTitle): self - { - $this->showTitle = $showTitle; - return $this; - } - - /** - * Filter by permit self registration - */ - public function filterByPermitSelfRegistration(?bool $permitSelfRegistration): self - { - $this->permitSelfRegistration = $permitSelfRegistration; - return $this; - } - - /** - * Filter by permit metadata edit - */ - public function filterByPermitMetadataEdit(?bool $permitMetadataEdit): self - { - $this->permitMetadataEdit = $permitMetadataEdit; - return $this; - } - - /** - * Filter by masthead - */ - public function filterByMasthead(?bool $masthead): self - { - $this->masthead = $masthead; - return $this; - } - - /** - * Filter by permit metadata edit - */ - public function filterByIsRecommendOnly(): self - { - $this->isRecommendOnly = true; - return $this; - } - - /** - * Filter by user ids - */ - public function filterByUserIds(?array $userIds): self - { - $this->userIds = $userIds; - return $this; - } - - /** - * Filter by user's role status - */ - public function filterByUserUserGroupStatus(UserUserGroupStatus $userUserGroupStatus): self - { - $this->userUserGroupStatus = $userUserGroupStatus; - return $this; - } - - /** - * Include orderBy columns to the collector query - */ - public function orderBy(?string $orderBy): self - { - $this->orderBy = $orderBy; - return $this; - } - - /** - * Limit the number of objects retrieved - */ - public function limit(?int $count): self - { - $this->count = $count; - return $this; - } - - /** - * Offset the number of objects retrieved, for example to - * retrieve the second page of contents - */ - public function offset(?int $offset): self - { - $this->offset = $offset; - return $this; - } - - /** - * @copydoc CollectorInterface::getQueryBuilder() - * - * @hook UserGroup::Collector [[&$q, $this]] - */ - public function getQueryBuilder(): Builder - { - $q = DB::table('user_groups as ug') - ->select('ug.*'); - - if (isset($this->userGroupIds)) { - $q->whereIn('ug.user_group_id', $this->userGroupIds); - } - - if (isset($this->userIds)) { - $q->join('user_user_groups as uug', 'ug.user_group_id', '=', 'uug.user_group_id'); - $q->whereIn('uug.user_id', $this->userIds); - $currentDateTime = Core::getCurrentDate(); - if ($this->userUserGroupStatus === UserUserGroupStatus::STATUS_ENDED) { - $q->whereNotNull('uug.date_end') - ->where('uug.date_end', '<=', $currentDateTime); - } elseif ($this->userUserGroupStatus === UserUserGroupStatus::STATUS_ACTIVE) { - $q->where( - fn (Builder $q) => - $q->where('uug.date_start', '<=', $currentDateTime) - ->orWhereNull('uug.date_start') - ) - ->where( - fn (Builder $q) => - $q->where('uug.date_end', '>', $currentDateTime) - ->orWhereNull('uug.date_end') - ); - } - } - - if (isset($this->publicationIds)) { - $q->join('authors as a', 'a.user_group_id', '=', 'ug.user_group_id') - ->whereIn('a.publication_id', $this->publicationIds); - } - - if (isset($this->stageIds)) { - $q->join('user_group_stage as ugs', function ($join) { - $join->on('ug.user_group_id', '=', 'ugs.user_group_id'); - $join->on('ug.context_id', '=', 'ugs.context_id'); - })->whereIn('ugs.stage_id', $this->stageIds); - } - - if (isset($this->contextIds)) { - $q->whereIn(DB::raw('COALESCE(ug.context_id, 0)'), array_map(intval(...), $this->contextIds)); - } - - if (isset($this->roleIds)) { - $q->whereIn('ug.role_id', $this->roleIds); - } - - if (isset($this->excludeRoles)) { - $q->whereNotIn('ug.role_id', $this->excludeRoles); - } - - $q->when($this->isRecommendOnly !== null, function (Builder $q) { - $q->whereIn('ug.user_group_id', function (Builder $q) { - $q->select('user_group_id') - ->from($this->dao->settingsTable) - ->where('setting_name', '=', 'recommendOnly') - ->where('setting_value', $this->isRecommendOnly); - }); - }); - - $q->when($this->isDefault !== null, function (Builder $q) { - $q->where('ug.is_default', $this->isDefault ? 1 : 0); - }); - - $q->when($this->permitSelfRegistration !== null, function (Builder $q) { - $q->where('ug.permit_self_registration', $this->permitSelfRegistration ? 1 : 0); - }); - - $q->when($this->permitMetadataEdit !== null, function (Builder $q) { - $q->where('ug.permit_metadata_edit', $this->permitMetadataEdit ? 1 : 0); - }); - - $q->when($this->masthead !== null, function (Builder $q) { - $q->where('ug.masthead', $this->masthead ? 1 : 0); - }); - - $q->when($this->showTitle !== null, function (Builder $q) { - $q->where('ug.show_title', $this->showTitle ? 1 : 0); - }); - - if (isset($this->count)) { - $q->limit($this->count); - } - - if (isset($this->offset)) { - $q->offset($this->offset); - } - - switch ($this->orderBy) { - case self::ORDERBY_ROLE_ID: - $q->orderBy('ug.role_id', 'asc'); - break; - case self::ORDERBY_ID: - $q->orderBy('ug.user_group_id', 'asc'); - break; - } - - // Add app-specific query statements - Hook::call('UserGroup::Collector', [&$q, $this]); - - return $q; - } -} diff --git a/classes/userGroup/DAO.php b/classes/userGroup/DAO.php deleted file mode 100644 index dcf670c6efd..00000000000 --- a/classes/userGroup/DAO.php +++ /dev/null @@ -1,170 +0,0 @@ - - */ -class DAO extends EntityDAO -{ - use EntityWithParent; - - /** @copydoc EntityDAO::$schema */ - public $schema = PKPSchemaService::SCHEMA_USER_GROUP; - - /** @copydoc EntityDAO::$table */ - public $table = 'user_groups'; - - /** @copydoc EntityDAO::$settingsTable */ - public $settingsTable = 'user_group_settings'; - - /** @copydoc EntityDAO::$primaryKeyColumn */ - public $primaryKeyColumn = 'user_group_id'; - - /** @copydoc EntityDAO::$primaryTableColumns */ - public $primaryTableColumns = [ - 'id' => 'user_group_id', - 'contextId' => 'context_id', - 'roleId' => 'role_id', - 'isDefault' => 'is_default', - 'showTitle' => 'show_title', - 'permitSelfRegistration' => 'permit_self_registration', - 'permitMetadataEdit' => 'permit_metadata_edit', - 'masthead' => 'masthead', - ]; - - /** - * Get the parent object ID column name - */ - public function getParentColumn(): string - { - return 'context_id'; - } - - /** - * Instantiate a new DataObject - */ - public function newDataObject(): UserGroup - { - return app(UserGroup::class); - } - - /** - * Get the total count of rows matching the configured query - */ - public function getCount(Collector $query): int - { - return $query - ->getQueryBuilder() - ->getCountForPagination(); - } - - /** - * Get a list of ids matching the configured query - * - * @return Collection - */ - public function getIds(Collector $query): Collection - { - return $query - ->getQueryBuilder() - ->select('ug.' . $this->primaryKeyColumn) - ->pluck('ug.' . $this->primaryKeyColumn); - } - - /** - * Get a collection of publications matching the configured query - * - * @return LazyCollection - */ - public function getMany(Collector $query): LazyCollection - { - $rows = $query - ->getQueryBuilder() - ->get(); - - return LazyCollection::make(function () use ($rows) { - foreach ($rows as $row) { - yield $row->user_group_id => $this->fromRow($row); - } - }); - } - - /** - * @copydoc EntityDAO::insert() - */ - public function insert(UserGroup $userGroup): int - { - return parent::_insert($userGroup); - } - - /** - * @copydoc EntityDAO::update() - */ - public function update(UserGroup $userGroup) - { - parent::_update($userGroup); - } - - /** - * @copydoc EntityDAO::delete() - */ - public function delete(UserGroup $userGroup) - { - parent::_delete($userGroup); - } - - /** - * Retrieves a keyed Collection (key = user_group_id, value = count) with the amount of active users for each user group - */ - public function getUserCountByContextId(?int $contextId = Application::SITE_CONTEXT_ID_ALL): Collection - { - $currentDateTime = Core::getCurrentDate(); - return DB::table('user_groups', 'ug') - ->join('user_user_groups AS uug', 'uug.user_group_id', '=', 'ug.user_group_id') - ->join('users AS u', 'u.user_id', '=', 'uug.user_id') - ->when($contextId !== Application::SITE_CONTEXT_ID_ALL, fn (Builder $query) => $query->whereRaw('COALESCE(ug.context_id, 0) = ?', [(int) $contextId])) - ->where('u.disabled', '=', 0) - ->where( - fn (Builder $q) => - $q->where('uug.date_start', '<=', $currentDateTime) - ->orWhereNull('uug.date_start') - ) - ->where( - fn (Builder $q) => - $q->where('uug.date_end', '>', $currentDateTime) - ->orWhereNull('uug.date_end') - ) - ->groupBy('ug.user_group_id') - ->select('ug.user_group_id') - ->selectRaw('COUNT(0) AS count') - ->pluck('count', 'user_group_id'); - } -} diff --git a/classes/userGroup/Repository.php b/classes/userGroup/Repository.php index dcaf172669d..62f63ab50bc 100644 --- a/classes/userGroup/Repository.php +++ b/classes/userGroup/Repository.php @@ -13,9 +13,6 @@ namespace PKP\userGroup; -use APP\core\Application; -use APP\core\Request; -use APP\facades\Repo; use Carbon\Carbon; use DateInterval; use Illuminate\Support\Collection; @@ -23,7 +20,6 @@ use Illuminate\Support\LazyCollection; use PKP\core\Core; use PKP\db\DAORegistry; -use PKP\facades\Locale; use PKP\plugins\Hook; use PKP\security\Role; use PKP\services\PKPSchemaService; @@ -34,7 +30,6 @@ use PKP\userGroup\relationships\UserUserGroup; use PKP\validation\ValidatorFactory; use PKP\xml\PKPXMLParser; -use stdClass; class Repository { @@ -46,51 +41,46 @@ class Repository /** @var string Max lifetime for the Editorial Masthead and Editorial History users cache. */ public const MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME = '1 year'; - /** @var DAO */ - public $dao; - /** @var string $schemaMap The name of the class to map this entity to its schema */ public $schemaMap = maps\Schema::class; - /** @var Request */ - protected $request; /** @var PKPSchemaService */ protected $schemaService; - public function __construct(DAO $dao, Request $request, PKPSchemaService $schemaService) + public function __construct(PKPSchemaService $schemaService) { - $this->dao = $dao; - $this->request = $request; $this->schemaService = $schemaService; } - /** @copydoc DAO::newDataObject() */ - public function newDataObject(array $params = []): UserGroup - { - $object = $this->dao->newDataObject(); - if (!empty($params)) { - $object->setAllData($params); - } - return $object; - } - - /** @copydoc DAO::get() */ + /** + * Retrieve UserGroup by id and optional context id. + * + * @param int $id + * @param int|null $contextId + * @return UserGroup|null + */ public function get(int $id, ?int $contextId = null): ?UserGroup { - return $this->dao->get($id, $contextId); + return UserGroup::findById($id, $contextId); } - /** @copydoc DAO::exists() */ + /** + * Check if UserGroup exists by id and context id. + * + * @param int $id + * @param int|null $contextId + * @return bool + */ public function exists(int $id, ?int $contextId = null): bool { - return $this->dao->exists($id, $contextId); - } + $query = UserGroup::query()->where('user_group_id', $id); - /** @copydoc DAO::getCollector() */ - public function getCollector(): Collector - { - return app(Collector::class); + if ($contextId !== null) { + $query->withContextIds([$contextId]); + } + + return $query->exists(); } /** @@ -148,335 +138,351 @@ public function validate($userGroup, $props, $allowedLocales, $primaryLocale) return $errors; } - public function add(UserGroup $userGroup): int - { - $userGroupId = $this->dao->insert($userGroup); - $userGroup = Repo::userGroup()->get($userGroupId); - - Hook::call('UserGroup::add', [$userGroup]); - - // Clear editorial masthead cache if the new role should be added to the masthead - // Because it is a new role, no need to clear editorial history chache - if ($userGroup->getMasthead()) { - self::forgetEditorialMastheadCache($userGroup->getContextId()); - } - return $userGroup->getId(); - } - - public function edit(UserGroup $userGroup, array $params) - { - $newUserGroup = Repo::userGroup()->newDataObject(array_merge($userGroup->_data, $params)); - - Hook::call('UserGroup::edit', [$newUserGroup, $userGroup, $params]); - - $this->dao->update($newUserGroup); - - // Clear editorial masthead and history cache if the role is on the masthead - if ($userGroup->getMasthead()) { - self::forgetEditorialMastheadCache($userGroup->getContextId()); - self::forgetEditorialHistoryCache($userGroup->getContextId()); - } - - Repo::userGroup()->get($newUserGroup->getId()); - } - - public function delete(UserGroup $userGroup) - { - Hook::call('UserGroup::delete::before', [$userGroup]); - - $this->dao->delete($userGroup); - - Hook::call('UserGroup::delete', [$userGroup]); - } - /** - * Delete all user groups assigned to a certain context by contextId - */ - public function deleteByContextId(int $contextId) + * Delete all user groups assigned to a certain context by contextId + * + * @param int $contextId + * @return void + */ + public function deleteByContextId(int $contextId): void { - $userGroupIds = Repo::userGroup()->getCollector() - ->filterByContextIds([$contextId]) - ->getIds(); - - foreach ($userGroupIds as $userGroupId) { - $this->dao->deleteById($userGroupId); - } + UserGroup::query() + ->withContextIds([$contextId]) + ->each(function (UserGroup $userGroup) { + $userGroup->delete(); + }); } /** - * Return all user group ids given a certain role id - * - * @param int $roleId - */ - public function getArrayIdByRoleId($roleId, ?int $contextId = null): array + * Return all user group ids given a certain role ID and context id + * + * @param int $roleId + * @param int|null $contextId + * @return array + */ + public function getArrayIdByRoleId(int $roleId, ?int $contextId = null): array { - $collector = Repo::userGroup()->getCollector() - ->filterByRoleIds([$roleId]); + $query = UserGroup::query() + ->withRoleIds([$roleId]); - if ($contextId) { - $collector->filterByContextIds([$contextId]); + if ($contextId !== null) { + $query->withContextIds([$contextId]); } - return $collector->getIds()->toArray(); + return $query->pluck('user_group_id')->toArray(); } /** - * Return all user group ids given a certain role id - * - * @param ?bool $default Give null for all user groups, else define whether it is default - * - * @return LazyCollection - */ + * Return all user groups given role ids, context id and default flag + * + * @param array $roleIds + * @param int $contextId + * @param bool|null $default + * @return LazyCollection + */ public function getByRoleIds(array $roleIds, int $contextId, ?bool $default = null): LazyCollection { - $collector = Repo::userGroup() - ->getCollector() - ->filterByRoleIds($roleIds) - ->filterByContextIds([$contextId]) - ->filterByIsDefault($default); + $query = UserGroup::query() + ->withRoleIds($roleIds) + ->withContextIds([$contextId]); + + if ($default !== null) { + $query->isDefault($default); + } - return $collector->getMany(); + return $query->cursor(); } /** - * Return all, active or ended user groups ids for a user id - * - * @return LazyCollection - */ - public function userUserGroups(int $userId, ?int $contextId = null, ?UserUserGroupStatus $userUserGroupStatus = UserUserGroupStatus::STATUS_ACTIVE): LazyCollection + * Return all active or ended user groups for a user id and context id (optional) + * + * @param int $userId + * @param int|null $contextId + * @param UserUserGroupStatus $status + * @return LazyCollection + */ + public function userUserGroups(int $userId, ?int $contextId = null, UserUserGroupStatus $status = UserUserGroupStatus::STATUS_ACTIVE): LazyCollection { - $collector = Repo::userGroup() - ->getCollector() - ->filterByUserIds([$userId]) - ->filterByUserUserGroupStatus($userUserGroupStatus); + $query = UserGroup::query() + ->withUserIds([$userId]) + ->withUserUserGroupStatus($status->value); - if ($contextId) { - $collector->filterByContextIds([$contextId]); + if ($contextId !== null) { + $query->withContextIds([$contextId]); } - return $collector->getMany(); + return $query->cursor(); } /** - * Return all context IDs for masthead user groups the given user is or was assigned to - * - * @return Collection of context IDs - */ + * Return all context IDs for masthead user groups the given user is or was assigned to + * + * @param int $userId + * @return Collection + */ public function getUserUserGroupsContextIds(int $userId): Collection { - return Repo::userGroup() - ->getCollector() - ->filterByUserIds([$userId]) - ->filterByUserUserGroupStatus(UserUserGroupStatus::STATUS_ALL) - ->filterByMasthead(true) - ->getQueryBuilder() + return UserGroup::query() + ->withUserIds([$userId]) + ->withUserUserGroupStatus(UserUserGroupStatus::STATUS_ALL->value) + ->masthead(true) ->pluck('context_id') ->unique(); } /** - * return whether a user is in a user group - */ + * Determine whether a user is in a specific user group + * + * @param int $userId + * @param int $userGroupId + * @return bool + */ public function userInGroup(int $userId, int $userGroupId): bool { - return UserUserGroup::withUserId($userId) + return UserUserGroup::query() + ->withUserId($userId) ->withUserGroupId($userGroupId) ->withActive() - ->get() - ->isNotEmpty(); + ->exists(); } /** - * return whether an active user in a user group should be displayed on the masthead - */ - public function userOnMasthead(int $userId, ?int $userGroupId): bool + * Determine whether an active user in a user group should be displayed on the masthead + * + * @param int $userId + * @param int|null $userGroupId + * @return bool + */ + public function userOnMasthead(int $userId, ?int $userGroupId = null): bool { if ($userGroupId) { - $userGroup = Repo::userGroup()->get($userGroupId); - if (!$userGroup->getMasthead()) { + $userGroup = $this->get($userGroupId); + if (!$userGroup || !$userGroup->masthead) { return false; } } - $query = UserUserGroup::withUserId($userId) + + $query = UserUserGroup::query() + ->withUserId($userId) ->withActive() ->withMasthead(); + if ($userGroupId) { $query->withUserGroupId($userGroupId); } - return $query->get()->isNotEmpty(); + + return $query->exists(); } /** - * Get user masthead status for a user group the user is currently active in + * Get UserUserGroup masthead status for a UserGroup the user is currently active in + * + * @param int $userId + * @param int $userGroupId + * @return UserUserGroupMastheadStatus */ public function getUserUserGroupMastheadStatus(int $userId, int $userGroupId): UserUserGroupMastheadStatus { - $masthead = UserUserGroup::withUserId($userId) + $masthead = UserUserGroup::query() + ->withUserId($userId) ->withUserGroupId($userGroupId) ->withActive() - ->pluck('masthead'); - switch ($masthead[0]) { - case 1: - return UserUserGroupMastheadStatus::STATUS_ON; - case 0: - return UserUserGroupMastheadStatus::STATUS_OFF; - case null: - return UserUserGroupMastheadStatus::STATUS_NULL; - } + ->pluck('masthead') + ->first(); + + return match ($masthead) { + true => UserUserGroupMastheadStatus::STATUS_ON, + false => UserUserGroupMastheadStatus::STATUS_OFF, + default => UserUserGroupMastheadStatus::STATUS_NULL, + }; } /** - * return whether a context has a specific user group - */ + * Determine whether a context has a specific UserGroup + * + * @param int $contextId + * @param int $userGroupId + * @return bool + */ public function contextHasGroup(int $contextId, int $userGroupId): bool { - return Repo::userGroup() - ->getCollector() - ->filterByContextIds([$contextId]) - ->filterByUserGroupIds([$userGroupId]) - ->getCount() > 0; + return UserGroup::query() + ->withContextIds([$contextId]) + ->withUserGroupIds([$userGroupId]) + ->exists(); } /** - * Assign a user to a role + * Assign a user to a UserGroup * + * @param int $userId + * @param int $userGroupId * @param string|null $startDate The date in ISO (YYYY-MM-DD HH:MM:SS) format * @param string|null $endDate The date in ISO (YYYY-MM-DD HH:MM:SS) format + * @param UserUserGroupMastheadStatus|null $mastheadStatus + * @return UserUserGroup|null */ - public function assignUserToGroup(int $userId, int $userGroupId, ?string $startDate = null, ?string $endDate = null, ?UserUserGroupMastheadStatus $mastheadStatus = null): ?UserUserGroup - { + public function assignUserToGroup( + int $userId, + int $userGroupId, + ?string $startDate = null, + ?string $endDate = null, + ?UserUserGroupMastheadStatus $mastheadStatus = null + ): ?UserUserGroup { if ($endDate && !Carbon::parse($endDate)->isFuture()) { return null; } $dateStart = $startDate ?? Core::getCurrentDate(); - $userGroup = Repo::userGroup()->get($userGroupId); - // user_user_group's masthead does not inherit from the user_group's masthead, - // it needs to be specified (when accepting an invitations). - switch ($mastheadStatus) { - case UserUserGroupMastheadStatus::STATUS_ON: - $masthead = 1; - break; - case UserUserGroupMastheadStatus::STATUS_OFF: - $masthead = 0; - break; - default: - $masthead = $userGroup->getMasthead() ? 1 : null; + $userGroup = UserGroup::find($userGroupId); + + if (!$userGroup) { + return null; } + + // Determine masthead status + $masthead = match ($mastheadStatus) { + UserUserGroupMastheadStatus::STATUS_ON => true, + UserUserGroupMastheadStatus::STATUS_OFF => false, + default => null, + }; + // Clear editorial masthead cache if a new user is assigned to a masthead role - if ($userGroup->getMasthead()) { - self::forgetEditorialMastheadCache($userGroup->getContextId()); + if ($userGroup->masthead) { + self::forgetEditorialCache($userGroup->contextId); } + return UserUserGroup::create([ - 'userId' => $userId, - 'userGroupId' => $userGroupId, - 'dateStart' => $dateStart, - 'dateEnd' => $endDate, + 'user_id' => $userId, + 'user_group_id' => $userGroupId, + 'date_start' => $dateStart, + 'date_end' => $endDate, 'masthead' => $masthead, ]); } /** - * Remove all user role assignments. This should be used only when merging i.e. fully deleting an user. + * Remove all user role assignments for a user. optionally within a specific UserGroup + * + * This should be used only when merging, i.e., fully deleting a user. + * + * @param int $userId + * @param int|null $userGroupId + * @return bool */ public function deleteAssignmentsByUserId(int $userId, ?int $userGroupId = null): bool { if (!$userGroupId) { $contextIds = $this->getUserUserGroupsContextIds($userId); foreach ($contextIds as $contextId) { - self::forgetEditorialMastheadCache($contextId); + self::forgetEditorialCache($contextId); self::forgetEditorialHistoryCache($contextId); } } - $query = UserUserGroup::withUserId($userId); + $query = UserUserGroup::query()->withUserId($userId); if ($userGroupId) { $query->withUserGroupId($userGroupId); $userGroup = $this->get($userGroupId); - if ($userGroup->getMasthead()) { - self::forgetEditorialMastheadCache($contextId); - self::forgetEditorialHistoryCache($contextId); + if ($userGroup && $userGroup->masthead) { + self::forgetEditorialCache($userGroup->contextId); + self::forgetEditorialHistoryCache($userGroup->contextId); } } - return $query->delete(); + return $query->delete() > 0; } + /** + * End user assignments by setting the end date. + * + * @param int $contextId + * @param int $userId + * @param int|null $userGroupId + * @return void + */ public function endAssignments(int $contextId, int $userId, ?int $userGroupId = null): void { // Clear editorial masthead and history cache if the user was displayed on the masthead for the given role if ($this->userOnMasthead($userId, $userGroupId)) { - self::forgetEditorialMastheadCache($contextId); + self::forgetEditorialCache($contextId); self::forgetEditorialHistoryCache($contextId); } $dateEnd = Core::getCurrentDate(); - $query = UserUserGroup::withContextId($contextId) + $query = UserUserGroup::query() + ->withContextId($contextId) ->withUserId($userId) ->withActive(); + if ($userGroupId) { $query->withUserGroupId($userGroupId); } + $query->update(['date_end' => $dateEnd]); } /** - * Get the user groups assigned to each stage. - * - * @param null|mixed $roleId - * @param null|mixed $count - * - * @return LazyCollection - */ - public function getUserGroupsByStage(int $contextId, $stageId, $roleId = null, $count = null): LazyCollection + * Get the user groups assigned to each stage. + * + * @param int $contextId + * @param int $stageId + * @param int|null $roleId + * @param int|null $count + * @return LazyCollection + */ + public function getUserGroupsByStage(int $contextId, int $stageId, ?int $roleId = null, ?int $count = null): LazyCollection { - $userGroups = $this->getCollector() - ->filterByContextIds([$contextId]) - ->filterByStageIds([$stageId]); + $query = UserGroup::query() + ->withContextIds([$contextId]) + ->withStageIds([$stageId]); - if ($roleId) { - $userGroups->filterByRoleIds([$roleId]); + if ($roleId !== null) { + $query->withRoleIds([$roleId]); } - $userGroups->orderBy(Collector::ORDERBY_ROLE_ID); + $query->orderByRoleId(); + + if ($count !== null) { + $query->limit($count); + } - return $userGroups->getMany(); + return $query->cursor(); } /** - * Remove a user group from a stage - */ + * Remove a user group from a stage + * + * @param int $contextId + * @param int $userGroupId + * @param int $stageId + * @return bool + */ public function removeGroupFromStage(int $contextId, int $userGroupId, int $stageId): bool { - return UserGroupStage::withContextId($contextId) + return UserGroupStage::query() + ->withContextId($contextId) ->withUserGroupId($userGroupId) ->withStageId($stageId) - ->delete(); + ->delete() > 0; } /** * Get all stages assigned to one user group in one context. * * @param int $contextId The context ID. - * @param int $userGroupId The user group ID - * + * @param int $userGroupId The UserGroup ID + * @return Collection */ public function getAssignedStagesByUserGroupId(int $contextId, int $userGroupId): Collection { - return UserGroupStage::withContextId($contextId) + return UserGroupStage::query() + ->withContextId($contextId) ->withUserGroupId($userGroupId) ->pluck('stage_id'); } - /** - * Retrieves a keyed Collection (key = user_group_id, value = count) with the amount of active users for each user group - */ - public function getUserCountByContextId(?int $contextId = null): Collection - { - return $this->dao->getUserCountByContextId($contextId); - } - /** * Get the user group a new author may be assigned to * when they make their first submission, if they are @@ -484,28 +490,27 @@ public function getUserCountByContextId(?int $contextId = null): Collection * * This returns the first user group with ROLE_ID_AUTHOR * that permits self-registration. + * + * @param int $contextId + * @return UserGroup|null */ public function getFirstSubmitAsAuthorUserGroup(int $contextId): ?UserGroup { - return Repo::userGroup() - ->getCollector() - ->filterByContextIds([$contextId]) - ->filterByRoleIds([Role::ROLE_ID_AUTHOR]) - ->filterByPermitSelfRegistration(true) - ->limit(1) - ->getMany() + return UserGroup::query() + ->withContextIds([$contextId]) + ->withRoleIds([Role::ROLE_ID_AUTHOR]) + ->permitSelfRegistration(true) ->first(); } /** * Load the XML file and move the settings to the DB * - * @param int $contextId + * @param int|null $contextId * @param string $filename - * - * @return bool true === success + * @return bool True on success otherwise false */ - public function installSettings(?int $contextId, $filename) + public function installSettings(?int $contextId, string $filename): bool { $xmlParser = new PKPXMLParser(); $tree = $xmlParser->parse($filename); @@ -522,58 +527,60 @@ public function installSettings(?int $contextId, $filename) $roleId = hexdec($setting->getAttribute('roleId')); $nameKey = $setting->getAttribute('name'); $abbrevKey = $setting->getAttribute('abbrev'); - $permitSelfRegistration = $setting->getAttribute('permitSelfRegistration'); - $permitMetadataEdit = $setting->getAttribute('permitMetadataEdit'); - $masthead = $setting->getAttribute('masthead'); + $permitSelfRegistration = $setting->getAttribute('permitSelfRegistration') === 'true'; + $permitMetadataEdit = $setting->getAttribute('permitMetadataEdit') === 'true'; + $masthead = $setting->getAttribute('masthead') === 'true'; // If has manager role then permitMetadataEdit can't be overridden - if (in_array($roleId, [Role::ROLE_ID_MANAGER])) { - $permitMetadataEdit = $setting->getAttribute('permitMetadataEdit'); + if (in_array($roleId, self::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { + $permitMetadataEdit = $setting->getAttribute('permitMetadataEdit') === 'true'; } $defaultStages = explode(',', (string) $setting->getAttribute('stages')); - // create a role associated with this user group - $userGroup = $this->newDataObject(); - $userGroup->setRoleId($roleId); - $userGroup->setContextId($contextId); - $userGroup->setPermitSelfRegistration($permitSelfRegistration ?? false); - $userGroup->setPermitMetadataEdit($permitMetadataEdit ?? false); - $userGroup->setDefault(true); - $userGroup->setShowTitle(true); - $userGroup->setMasthead($masthead ?? false); + // Create a new UserGroup instance and set attributes + $userGroup = new UserGroup([ + 'roleId' => $roleId, + 'contextId' => $contextId, + 'permitSelfRegistration' => $permitSelfRegistration, + 'permitMetadataEdit' => $permitMetadataEdit, + 'isDefault' => true, + 'showTitle' => true, + 'masthead' => $masthead, + ]); + + // Save the UserGroup instance to the database + $userGroup->save(); - // insert the group into the DB - $userGroupId = $this->add($userGroup); + $userGroupId = $userGroup->userGroupId; // Install default groups for each stage - if (is_array($defaultStages)) { // test for groups with no stage assignments - foreach ($defaultStages as $stageId) { - if (!empty($stageId) && $stageId <= WORKFLOW_STAGE_ID_PRODUCTION && $stageId >= WORKFLOW_STAGE_ID_SUBMISSION) { - UserGroupStage::create([ - 'contextId' => $contextId, - 'userGroupId' => $userGroupId, - 'stageId' => $stageId - ]); - } + foreach ($defaultStages as $stageId) { + $stageId = (int) trim($stageId); + if ($stageId >= WORKFLOW_STAGE_ID_SUBMISSION && $stageId <= WORKFLOW_STAGE_ID_PRODUCTION) { + UserGroupStage::create([ + 'context_id' => $contextId, + 'user_group_id' => $userGroupId, + 'stage_id' => $stageId, + ]); } } - // add the i18n keys to the settings table so that they - // can be used when a new locale is added/reloaded - $newUserGroup = $this->get($userGroupId); - $this->edit($newUserGroup, [ + // Update the settings for nameLocaleKey and abbrevLocaleKey directly + $userGroup->fill([ 'nameLocaleKey' => $nameKey, - 'abbrevLocaleKey' => $abbrevKey + 'abbrevLocaleKey' => $abbrevKey, ]); - // install the settings in the current locale for this context + $userGroup->save(); + + // Install the settings in the current locale for this context foreach ($installedLocales as $locale) { $this->installLocale($locale, $contextId); } } - self::forgetEditorialMastheadCache($contextId); + self::forgetEditorialCache($contextId); self::forgetEditorialHistoryCache($contextId); return true; @@ -584,94 +591,109 @@ public function installSettings(?int $contextId, $filename) * * @param string $locale * @param ?int $contextId + * @return void */ - public function installLocale($locale, ?int $contextId = null) + public function installLocale(string $locale, ?int $contextId = null): void { - $userGroupsCollector = $this->getCollector(); + $userGroups = UserGroup::query(); - if (isset($contextId)) { - $userGroupsCollector->filterByContextIds([$contextId]); + if ($contextId !== null) { + $userGroups->withContextIds([$contextId]); } - $userGroups = $userGroupsCollector->getMany(); + $userGroups = $userGroups->get(); foreach ($userGroups as $userGroup) { - $nameKey = $userGroup->getData('nameLocaleKey'); - $userGroup->setData('name', __($nameKey, [], $locale), $locale); - $abbrevKey = $userGroup->getData('abbrevLocaleKey'); - $userGroup->setData('abbrev', __($abbrevKey, [], $locale), $locale); + $nameKey = $userGroup->getSettings('nameLocaleKey') ?? null; + $abbrevKey = $userGroup->getSettings('abbrevLocaleKey') ?? null; + + if ($nameKey) { + $userGroup->setData('name', [$locale => __($nameKey, [], $locale)]); + } + + if ($abbrevKey) { + $userGroup->setData('abbrev', [$locale => __($abbrevKey, [], $locale)]); + } - $this->edit($userGroup, []); + $userGroup->save(); } } /** - * Cache/get cached array of masthead user IDs grouped by masthead role IDs [user_group_id => [user_ids]] + * Cache/get cached array of masthead user IDs grouped by masthead role IDs + * Format: [user_group_id => [user_ids]] * * @param array $mastheadRoles Masthead roles, filtered by the given context ID, and sorted as they should appear on the Editorial Masthead and Editorial History page - * + * @param int $contextId + * @param UserUserGroupStatus $userUserGroupStatus + * @return array */ public function getMastheadUserIdsByRoleIds(array $mastheadRoles, int $contextId, UserUserGroupStatus $userUserGroupStatus = UserUserGroupStatus::STATUS_ACTIVE): array { - $key = __METHOD__; - switch ($userUserGroupStatus) { - case UserUserGroupStatus::STATUS_ACTIVE: - $key .= 'EditorialMasthead'; - break; - case UserUserGroupStatus::STATUS_ENDED: - $key .= 'EditorialHistory'; - break; - } - $key .= $contextId . self::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME; - $expiration = DateInterval::createFromDateString(static::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME); - $allUsersIdsGroupedByUserGroupId = Cache::remember($key, $expiration, function () use ($mastheadRoles, $contextId, $userUserGroupStatus) { - $mastheadRolesIds = array_map( - function (UserGroup $item) use ($contextId) { - if ($item->getContextId() == $contextId) { - return $item->getId(); - } - }, - $mastheadRoles - ); + $statusSuffix = match ($userUserGroupStatus) { + UserUserGroupStatus::STATUS_ACTIVE => 'EditorialMasthead', + UserUserGroupStatus::STATUS_ENDED => 'EditorialHistory', + default => 'EditorialMasthead', + }; + + $cacheKey = __METHOD__ . $statusSuffix . $contextId . self::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME; + $expiration = DateInterval::createFromDateString(self::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME); + + return Cache::remember($cacheKey, $expiration, function () use ($mastheadRoles, $contextId, $userUserGroupStatus) { + // extract UserGroup IDs from mastheadRoles within the context + $mastheadRoleIds = array_map(fn (UserGroup $item) => $item->userGroupId, $mastheadRoles); + // Query that gets all users that are or were active in the given masthead roles // and that have accepted to be displayed on the masthead for the roles. // Sort the results by role ID and user family name. - $usersCollector = Repo::user()->getCollector(); - $usersQuery = $usersCollector - ->filterByContextIds([$contextId]) - ->filterByUserGroupIds($mastheadRolesIds) - ->filterByUserUserGroupStatus($userUserGroupStatus) - ->filterByUserUserGroupMastheadStatus(UserUserGroupMastheadStatus::STATUS_ON) - ->orderBy($usersCollector::ORDERBY_FAMILYNAME, $usersCollector::ORDER_DIR_ASC, [Locale::getLocale(), Application::get()->getRequest()->getSite()->getPrimaryLocale()]) - ->orderByUserGroupIds($mastheadRolesIds) - ->getQueryBuilder() - ->get(); - - // Get unique user IDs grouped by user group ID - $userIdsByUserGroupId = $usersQuery->mapToGroups(function (stdClass $item, int $key) { - return [$item->user_group_id => $item->user_id]; - })->map(function ($item) { - return collect($item)->unique(); - }); - return $userIdsByUserGroupId->toArray(); + $users = UserUserGroup::query() + ->withContextId($contextId) + ->withUserGroupIds($mastheadRoleIds) + ->withUserUserGroupStatus($userUserGroupStatus->value) + ->withUserUserGroupMastheadStatus(UserUserGroupMastheadStatus::STATUS_ON->value) + ->orderBy('user_groups.role_id', 'asc') + ->join('user_groups', 'user_user_groups.user_group_id', '=', 'user_groups.user_group_id') + ->join('users', 'user_user_groups.user_id', '=', 'users.user_id') + ->orderBy('users.family_name', 'asc') + ->get(['user_groups.user_group_id', 'users.user_id']); + + // group unique user ids by UserGroup id + $userIdsByUserGroupId = $users->groupBy('user_group_id')->map(function ($group) { + return $group->pluck('user_id')->unique()->toArray(); + })->toArray(); + + return $userIdsByUserGroupId; }); - return $allUsersIdsGroupedByUserGroupId; } /** - * Clear editorial masthead cache + * Clear editorial masthead cache for a given context + * + * @param int $contextId + * @return void */ - public static function forgetEditorialMastheadCache(int $contextId) + public static function forgetEditorialCache(int $contextId): void { - Cache::forget('PKP\userGroup\Repository::getMastheadUserIdsByRoleIdsEditorialMasthead' . $contextId . self::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME); + $cacheKeyPrefix = 'PKP\userGroup\Repository::getMastheadUserIdsByRoleIds'; + $cacheKeys = [ + "{$cacheKeyPrefix}EditorialMasthead{$contextId}" . self::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME, + "{$cacheKeyPrefix}EditorialHistory{$contextId}" . self::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME, + ]; + + foreach ($cacheKeys as $key) { + Cache::forget($key); + } } /** - * Clear editorial history cache + * Clear editorial history cache for a given context. + * + * @param int $contextId + * @return void */ - public static function forgetEditorialHistoryCache(int $contextId) + public static function forgetEditorialHistoryCache(int $contextId): void { - Cache::forget('PKP\userGroup\Repository::getMastheadUserIdsByRoleIdsEditorialHistory' . $contextId . self::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME); + $cacheKey = "PKP\userGroup\Repository::getMastheadUserIdsByRoleIdsEditorialHistory{$contextId}" . self::MAX_EDITORIAL_MASTHEAD_CACHE_LIFETIME; + Cache::forget($cacheKey); } - } diff --git a/classes/userGroup/UserGroup.php b/classes/userGroup/UserGroup.php index 404180d2c98..4000a80cb5a 100644 --- a/classes/userGroup/UserGroup.php +++ b/classes/userGroup/UserGroup.php @@ -3,263 +3,488 @@ /** * @file classes/userGroup/UserGroup.php * - * Copyright (c) 2014-2021 Simon Fraser University - * Copyright (c) 2000-2021 John Willinsky + * Copyright (c) 2014-2014 Simon Fraser University + * Copyright (c) 2000-2024 John Willinsky * Distributed under the GNU GPL v3. For full terms see the file docs/COPYING. * * @class \PKP\userGroup\UserGroup * - * @see DAO - * - * @brief UserGroup metadata class. + * @brief Eloquent Model for UserGroup */ -namespace PKP\userGroup; + namespace PKP\userGroup; + + use Illuminate\Database\Eloquent\Builder as EloquentBuilder; + use Illuminate\Database\Eloquent\Model; + use Illuminate\Database\Eloquent\Relations\HasMany; + use PKP\core\traits\ModelWithSettings; + use PKP\stageAssignment\StageAssignment; + use PKP\userGroup\relationships\UserUserGroup; + use PKP\userGroup\relationships\UserGroupStage; + use PKP\facades\Locale; + use PKP\plugins\Hook; + use PKP\facades\Repo; + use PKP\services\PKPSchemaService; + use PKP\core\PKPApplication; + use APP\core\Application; -use PKP\core\PKPApplication; -class UserGroup extends \PKP\core\DataObject +class UserGroup extends Model { + + use ModelWithSettings; + + + /** + * The table associated with the model. + * + * @var string + */ + protected $table = 'user_groups'; + + /** + * The primary key associated with the table. + * + * @var string + */ + protected $primaryKey = 'user_group_id'; + /** - * Get the role ID + * Indicates if the model should be timestamped. * - * @return int ROLE_ID_... + * @var bool */ - public function getRoleId() + public $timestamps = false; + + /** + * The attributes that are mass assignable. + * + * @var array + */ + protected $fillable = [ + 'contextId', + 'roleId', + 'isDefault', + 'showTitle', + 'permitSelfRegistration', + 'permitMetadataEdit', + 'masthead', + ]; + + /** + * The attributes that should be cast. + * + * @return array + */ + protected function casts(): array { - return $this->getData('roleId'); + return [ + 'contextId' => 'integer', + 'roleId' => 'integer', + 'isDefault' => 'boolean', + 'showTitle' => 'boolean', + 'permitSelfRegistration' => 'boolean', + 'permitMetadataEdit' => 'boolean', + 'masthead' => 'boolean', + ]; } /** - * Set the role ID - * - * @param int $roleId ROLE_ID_... + * Get the settings table name + */ + public function getSettingsTable(): string + { + return 'user_group_settings'; + } + + /** + * Get the schema name for the model + */ + public static function getSchemaName(): ?string + { + return PKPSchemaService::SCHEMA_USER_GROUP; + } + + /** + * Define multilingual properties */ - public function setRoleId($roleId) + public function getMultilingualProps(): array { - $this->setData('roleId', $roleId); + return [ + 'name', + 'namePlural', + 'abbrev', + ]; } /** - * Get the role path + * Get assigned stage IDs * - * @return string Role path + * @return \Illuminate\Support\Collection */ - public function getPath() + public function getAssignedStageIds() { - return $this->getData('path'); + return $this->userGroupStages()->pluck('stage_id'); } /** - * Set the role path - * $param $path string + * Define the relationship to UserUserGroups */ - public function setPath($path) + public function userUserGroups(): HasMany { - $this->setData('path', $path); + return $this->hasMany(UserUserGroup::class, 'user_group_id', 'user_group_id'); } /** - * Get the context ID + * Define the relationship to UserGroupStages */ - public function getContextId(): ?int + public function userGroupStages(): HasMany + { + return $this->hasMany(UserGroupStage::class, 'user_group_id', 'user_group_id'); + } + + protected function scopeWithContextIds(EloquentBuilder $builder, $contextIds): EloquentBuilder { - return $this->getData('contextId'); + if (empty($contextIds)) { + return $builder; + } + + if (!is_array($contextIds)) { + $contextIds = [$contextIds]; + } + + return $builder->whereIn('context_id', $contextIds); } /** - * Set the context ID + * Scope a query to filter by user group IDs. */ - public function setContextId(?int $contextId): void + protected function scopeWithUserGroupIds(EloquentBuilder $builder, $userGroupIds): EloquentBuilder { - $this->setData('contextId', $contextId); + if (!is_array($userGroupIds)) { + $userGroupIds = [$userGroupIds]; + } + + return $builder->whereIn('user_group_id', $userGroupIds); } /** - * Get the default flag - * - * @return bool + * Scope a query to filter by role IDs. */ - public function getDefault() + protected function scopeWithRoleIds(EloquentBuilder $builder, $roleIds): EloquentBuilder { - return $this->getData('isDefault'); + if (!is_array($roleIds)) { + $roleIds = [$roleIds]; + } + + return $builder->whereIn('role_id', $roleIds); } /** - * Set the default flag - * - * @param bool $isDefault + * Scope a query to exclude certain role IDs. */ - public function setDefault($isDefault) + protected function scopeExcludeRoles(EloquentBuilder $builder, array $excludeRoles): EloquentBuilder { - $this->setData('isDefault', $isDefault); + return $builder->whereNotIn('role_id', $excludeRoles); } /** - * Get the "show title" flag (whether or not the title of the role - * should be included in the list of submission contributor names) - * - * @return bool + * Scope a query to filter by stage IDs. */ - public function getShowTitle() + protected function scopeWithStageIds(EloquentBuilder $builder, $stageIds): EloquentBuilder { - return $this->getData('showTitle'); + if (!is_array($stageIds)) { + $stageIds = [$stageIds]; + } + return $builder->whereHas('userGroupStages', function (EloquentBuilder $q) use ($stageIds) { + $q->whereIn('stage_id', $stageIds); + }); } /** - * Set the "show title" flag - * - * @param bool $showTitle + * Scope a query to exclude role ids. */ - public function setShowTitle($showTitle) + protected function scopeExcludeRoleIds(EloquentBuilder $builder, $roleIds): EloquentBuilder { - $this->setData('showTitle', $showTitle); + if (!is_array($roleIds)) { + $roleIds = [$roleIds]; + } + return $builder->whereNotIn('role_id', $roleIds); } /** - * Get the "permit self-registration" flag (whether or not users may - * self-register for this role, i.e. in the case of external - * reviewers, or whether it should be prohibited, in the case of - * internal reviewers). - * - * @return bool True IFF user self-registration is permitted + * Scope a query to filter by is_default. */ - public function getPermitSelfRegistration() + protected function scopeIsDefault(EloquentBuilder $builder, bool $isDefault): EloquentBuilder { - return $this->getData('permitSelfRegistration'); + return $builder->where('is_default', $isDefault); } /** - * Set the "permit self-registration" flag + * Scope a query to filter by show_title. */ - public function setPermitSelfRegistration(bool $permitSelfRegistration) + protected function scopeShowTitle(EloquentBuilder $builder, bool $showTitle): EloquentBuilder { - $this->setData('permitSelfRegistration', $permitSelfRegistration); + return $builder->where('show_title', $showTitle); } /** - * Get the recommendOnly option (whether or not the manager or the sub-editor role - * can only recommend or also make decisions in the submission review) - * - * @return bool + * Scope a query to filter by permit_self_registration. */ - public function getRecommendOnly() + protected function scopePermitSelfRegistration(EloquentBuilder $builder, bool $permitSelfRegistration): EloquentBuilder { - return $this->getData('recommendOnly'); + return $builder->where('permit_self_registration', $permitSelfRegistration); } /** - * Set the recommendOnly option (whether or not the manager or the sub-editor role - * can only recommend or also make decisions in the submission review) - * - * @param bool $recommendOnly + * Scope a query to filter by permit_metadata_edit. */ - public function setRecommendOnly($recommendOnly) + protected function scopePermitMetadataEdit(EloquentBuilder $builder, bool $permitMetadataEdit): EloquentBuilder { - $this->setData('recommendOnly', $recommendOnly); + return $builder->where('permit_metadata_edit', $permitMetadataEdit); } /** - * Get the localized role name - * - * @return string + * Scope a query to filter by masthead. */ - public function getLocalizedName() + protected function scopeMasthead(EloquentBuilder $builder, bool $masthead): EloquentBuilder { - return $this->getLocalizedData('name'); + return $builder->where('masthead', $masthead); } /** - * Get localized user group name, or array of localized names if $locale is null - * - * @param string|null $locale - * - * @return string|array|null localized name or array of localized names or null + * Scope a query to filter by user IDs. + */ + protected function scopeWithUserIds(EloquentBuilder $builder, array $userIds): EloquentBuilder + { + return $builder->whereHas('userUserGroups', function (EloquentBuilder $q) use ($userIds) { + $q->whereIn('user_id', $userIds); + }); + } + + /** + * Scope a query to filter by recommendOnly setting. + */ + protected function scopeIsRecommendOnly(EloquentBuilder $builder, bool $isRecommendOnly): EloquentBuilder + { + return $builder->where('recommendOnly', $isRecommendOnly); + } + + /** + * Scope a query to filter by UserUserGroupStatus. + */ + protected function scopeWithUserUserGroupStatus(EloquentBuilder $builder, string $status): EloquentBuilder + { + $currentDateTime = now(); + + if ($status === 'active') { + $builder->whereHas('userUserGroups', function (EloquentBuilder $q) use ($currentDateTime) { + $q->where(function (EloquentBuilder $q) use ($currentDateTime) { + $q->where('date_start', '<=', $currentDateTime) + ->orWhereNull('date_start'); + })->where(function (EloquentBuilder $q) use ($currentDateTime) { + $q->where('date_end', '>', $currentDateTime) + ->orWhereNull('date_end'); + }); + }); + } elseif ($status === 'ended') { + $builder->whereHas('userUserGroups', function (EloquentBuilder $q) use ($currentDateTime) { + $q->whereNotNull('date_end') + ->where('date_end', '<=', $currentDateTime); + }); + } + // Implement other statuses if needed + return $builder; + } + + /** + * Scope a query to order by role ID. + */ + protected function scopeOrderByRoleId(EloquentBuilder $builder): EloquentBuilder + { + return $builder->orderBy('role_id', 'asc'); + } + + /** + * Scope a query to order by user group ID. + */ + protected function scopeOrderById(EloquentBuilder $builder): EloquentBuilder + { + return $builder->orderBy('user_group_id', 'asc'); + } + + /** + * Scope a query to include active user count. */ - public function getName($locale) + protected function scopeWithActiveUserCount(EloquentBuilder $builder, ?int $contextId = null): EloquentBuilder { - return $this->getData('name', $locale); + $currentDateTime = now(); + + $builder->select('user_groups.user_group_id') + ->selectRaw('COUNT(user_user_groups.user_id) AS count') + ->join('user_user_groups', 'user_user_groups.user_group_id', '=', 'user_groups.user_group_id') + ->join('users', 'users.user_id', '=', 'user_user_groups.user_id') + ->where('users.disabled', 0) + ->where(function (EloquentBuilder $q) use ($currentDateTime) { + $q->where('user_user_groups.date_start', '<=', $currentDateTime) + ->orWhereNull('user_user_groups.date_start'); + }) + ->where(function (EloquentBuilder $q) use ($currentDateTime) { + $q->where('user_user_groups.date_end', '>', $currentDateTime) + ->orWhereNull('user_user_groups.date_end'); + }) + ->groupBy('user_groups.user_group_id'); + + if ($contextId !== null) { + $builder->where('user_groups.context_id', $contextId); + } + + return $builder; } /** - * Set user group name + * Ensure casts are string values. * - * @param string $name - * @param string $locale + * @param array $casts + * @return array */ - public function setName($name, $locale) + protected function ensureCastsAreStringValues($casts): array { - $this->setData('name', $name, $locale); + return array_map(fn($cast) => (string) $cast, $casts); } + /** - * Get the localized abbreviation + * Scope a query to filter by publication IDs. * - * @return string + * @param EloquentBuilder $builder + * @param array $publicationIds Array of publication IDs to filter by. + * @return EloquentBuilder */ - public function getLocalizedAbbrev() + protected function scopeWithPublicationIds(EloquentBuilder $builder, array $publicationIds): EloquentBuilder { - return $this->getLocalizedData('abbrev'); + return $builder->whereHas('userUserGroups', function (EloquentBuilder $q) use ($publicationIds) { + $q->whereIn('publication_id', $publicationIds); + }); } /** - * Get localized user group abbreviation, or array of localized abbreviations if $locale is null + * Set the name attribute. * - * @param string|null $locale + * @param mixed $value + * @return void + */ + public function setNameAttribute($value): void + { + if (is_string($value)) { + $value = $this->localizeNonLocalizedData($value); + } + $this->setData('name', $value); + } + + /** + * Set the abbrev attribute. * - * @return string|array|null localized abbreviation or array of localized abbreviations or null + * @param mixed $value + * @return void */ - public function getAbbrev($locale) + public function setAbbrevAttribute($value): void { - return $this->getData('abbrev', $locale); + if (is_string($value)) { + $value = $this->localizeNonLocalizedData($value); + } + $this->setData('abbrev', $value); } /** - * Set user group abbreviation + * Localize non-localized data. * - * @param string $abbrev - * @param string $locale + * @param string $value + * @return array */ - public function setAbbrev($abbrev, $locale) + protected function localizeNonLocalizedData(string $value): array { - $this->setData('abbrev', $abbrev, $locale); + return [Locale::getLocale() => $value]; } /** - * Getter for permitMetadataEdit attribute. + * Find a UserGroup by ID and optional context ID. * - * @return bool + * @param int $id + * @param int|null $contextId + * @return self|null */ - public function getPermitMetadataEdit() + public static function findById(int $id, ?int $contextId = null): ?self { - return $this->getData('permitMetadataEdit'); + $query = self::where('user_group_id', $id); + + if ($contextId !== null) { + $query->withContextIds([$contextId]); + } + + return $query->first(); } /** - * Setter for permitMetadataEdit attribute. + * Save the model to the database. + * + * @param array $options + * @return bool */ - public function setPermitMetadataEdit(bool $permitMetadataEdit) + public function save(array $options = []) { - $this->setData('permitMetadataEdit', $permitMetadataEdit); + $isNew = !$this->exists; + + $saved = parent::save($options); + + // Reload the model to ensure all relationships and settings are loaded + $this->refresh(); + + if ($isNew) { + // This is a new record + Hook::call('UserGroup::add', [$this]); + } else { + // This is an update + Hook::call('UserGroup::edit', [$this]); + } + + // Clear editorial masthead cache if the role is on the masthead + if ($this->masthead) { + Repo::userGroup()->forgetEditorialCache($this->contextId); + Repo::userGroup()->forgetEditorialHistoryCache($this->contextId); + } + + return $saved; } /** - * Get the masthead flag + * Booted method to handle model events. + * + * @return void */ - public function getMasthead(): bool + protected static function booted() { - return $this->getData('masthead'); + static::deleting(function ($userGroup) { + // Equivalent to 'UserGroup::delete::before' hook + Hook::call('UserGroup::delete::before', [$userGroup]); + + if ($userGroup->masthead) { + Repo::userGroup()->forgetEditorialCache($userGroup->contextId); + Repo::userGroup()->forgetEditorialHistoryCache($userGroup->contextId); + } + }); + + static::deleted(function ($userGroup) { + Hook::call('UserGroup::delete', [$userGroup]); + }); } /** - * Set the masthead flag + * Define the relationship to StageAssignments. */ - public function setMasthead(bool $masthead) + public function stageAssignments(): HasMany { - $this->setData('masthead', $masthead); + return $this->hasMany(StageAssignment::class, 'user_group_id', 'user_group_id'); } -} -if (!PKP_STRICT_MODE) { - class_alias('\PKP\userGroup\UserGroup', '\UserGroup'); } diff --git a/classes/userGroup/maps/Schema.php b/classes/userGroup/maps/Schema.php index 74be1816d14..10bc0e00993 100644 --- a/classes/userGroup/maps/Schema.php +++ b/classes/userGroup/maps/Schema.php @@ -84,7 +84,7 @@ protected function mapByProperties(array $props, UserGroup $item): array foreach ($props as $prop) { switch ($prop) { default: - $output[$prop] = $item->getData($prop); + $output[$prop] = $item->getAttribute($prop); break; } } diff --git a/classes/userGroup/relationships/UserGroupStage.php b/classes/userGroup/relationships/UserGroupStage.php index d9f27afc4b0..8f6cf802195 100644 --- a/classes/userGroup/relationships/UserGroupStage.php +++ b/classes/userGroup/relationships/UserGroupStage.php @@ -14,10 +14,11 @@ namespace PKP\userGroup\relationships; -use APP\facades\Repo; use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\Casts\Attribute; use Eloquence\Behaviours\HasCamelCasing; +use Illuminate\Database\Eloquent\Relations\BelongsTo; +use PKP\userGroup\UserGroup; + class UserGroupStage extends \Illuminate\Database\Eloquent\Model { @@ -35,12 +36,10 @@ class UserGroupStage extends \Illuminate\Database\Eloquent\Model */ protected $table = 'user_group_stage'; - public function userGroup(): Attribute + + public function userGroup(): BelongsTo { - return Attribute::make( - get: fn ($value, $attributes) => Repo::userGroup()->get($attributes['user_group_id']), - set: fn ($value) => $value->getId() - ); + return $this->belongsTo(UserGroup::class, 'user_group_id', 'user_group_id'); } public function scopeWithStageId(Builder $query, int $stageId): Builder @@ -48,6 +47,11 @@ public function scopeWithStageId(Builder $query, int $stageId): Builder return $query->where('stage_id', $stageId); } + public function scopeWithStageIds(Builder $query, array $stageIds): Builder + { + return $query->whereIn('stage_id', $stageIds); + } + public function scopeWithUserGroupId(Builder $query, int $userGroupId): Builder { return $query->where('user_group_id', $userGroupId); diff --git a/classes/userGroup/relationships/UserUserGroup.php b/classes/userGroup/relationships/UserUserGroup.php index b930efbe271..e54e2f7f11a 100644 --- a/classes/userGroup/relationships/UserUserGroup.php +++ b/classes/userGroup/relationships/UserUserGroup.php @@ -17,8 +17,10 @@ use APP\facades\Repo; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Eloquence\Behaviours\HasCamelCasing; use PKP\core\Core; +use PKP\userGroup\UserGroup; class UserUserGroup extends \Illuminate\Database\Eloquent\Model { @@ -37,12 +39,12 @@ public function user(): Attribute ); } - public function userGroup(): Attribute + /** + * Define the relationship to UserGroup + */ + public function userGroup(): BelongsTo { - return Attribute::make( - get: fn ($value, $attributes) => Repo::userGroup()->get($attributes['user_group_id']), - set: fn ($value) => $value->getId() - ); + return $this->belongsTo(UserGroup::class, 'user_group_id', 'user_group_id'); } public function scopeWithUserId(Builder $query, int $userId): Builder diff --git a/controllers/api/file/PKPManageFileApiHandler.php b/controllers/api/file/PKPManageFileApiHandler.php index 150b24fab23..1e046253b35 100644 --- a/controllers/api/file/PKPManageFileApiHandler.php +++ b/controllers/api/file/PKPManageFileApiHandler.php @@ -223,7 +223,7 @@ public function saveMetadata($args, $request) ->get(); $authorUserIds = $submitterAssignments - ->pluck('userId') + ->pluck('user_id') ->all(); // Update the notifications diff --git a/controllers/grid/queries/form/QueryForm.php b/controllers/grid/queries/form/QueryForm.php index 3f623f6db32..30b49e412f8 100644 --- a/controllers/grid/queries/form/QueryForm.php +++ b/controllers/grid/queries/form/QueryForm.php @@ -36,6 +36,8 @@ use PKP\security\Role; use PKP\stageAssignment\StageAssignment; use PKP\submission\reviewAssignment\ReviewAssignment; +use PKP\userGroup\UserGroup; +use PKP\userGroup\relationships\UserUserGroup; class QueryForm extends Form { @@ -233,6 +235,28 @@ public function initData() return parent::initData(); } + /** + * Get the assigned roles for a user in a submission and stage. + */ + private function getAssignedRoles($submissionId, $stageId, $userId) + { + $assignedRoles = []; + $usersAssignments = StageAssignment::with(['userGroup']) + ->withSubmissionIds([$submissionId]) + ->withStageIds([$stageId]) + ->withUserId($userId) + ->get(); + + foreach ($usersAssignments as $usersAssignment) { + $userGroup = $usersAssignment->userGroup; + if ($userGroup) { + $assignedRoles[] = $userGroup->roleId; + } + } + + return $assignedRoles; + } + /** * Fetch the form. * @@ -288,61 +312,54 @@ public function fetch($request, $template = null, $display = false, $actionArgs $templateMgr->assign('templates', $templateKeySubjectPairs); // Get currently selected participants in the query - - $assignedParticipants = $query->id ? QueryParticipant::withQueryId($query->id)->pluck('user_id')->all() : []; + $assignedParticipants = $query->id + ? QueryParticipant::withQueryId($query->id)->get()->map(fn($qp) => $qp->userId)->all() + : []; // Always include current user, even if not with a stage assignment - $includeUsers[] = $user->getId(); + $includeUsers = [$user->getId()]; $excludeUsers = null; - // When in review stage, include/exclude users depending on the current users role + // When in review stage, include/exclude users depending on the current user's role $reviewAssignments = []; - // Get current users roles - $assignedRoles = (function () use ($query, $user) { - $assignedRoles = []; - // Replaces StageAssignmentDAO::getBySubmissionAndStageId - $usersAssignments = StageAssignment::withSubmissionIds([$query->assocId]) - ->withStageIds([$query->stageId]) - ->withUserId($user->getId()) - ->get(); - - foreach ($usersAssignments as $usersAssignment) { - $userGroup = Repo::userGroup()->get($usersAssignment->userGroupId); - $assignedRoles[] = $userGroup->getRoleId(); - } - return $assignedRoles; - })(); + // Get current user's roles + $assignedRoles = $this->getAssignedRoles($query->assocId, $query->stageId, $user->getId()); if ($query->stageId == WORKFLOW_STAGE_ID_EXTERNAL_REVIEW || $query->stageId == WORKFLOW_STAGE_ID_INTERNAL_REVIEW) { // Get all review assignments for current submission $reviewAssignments = Repo::reviewAssignment()->getCollector()->filterBySubmissionIds([$submission->getId()])->getMany(); // if current user is editor/journal manager/site admin and not have author role , add all reviewers - if (array_intersect($assignedRoles, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]) || (empty($assignedRoles) && ($user->hasRole([Role::ROLE_ID_MANAGER], $context->getId()) || $user->hasRole([Role::ROLE_ID_SITE_ADMIN], PKPApplication::SITE_CONTEXT_ID)))) { + if ( + array_intersect($assignedRoles, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]) || + (empty($assignedRoles) && ($user->hasRole([Role::ROLE_ID_MANAGER], $context->getId()) || $user->hasRole([Role::ROLE_ID_SITE_ADMIN], PKPApplication::SITE_CONTEXT_ID))) + ) { foreach ($reviewAssignments as $reviewAssignment) { $includeUsers[] = $reviewAssignment->getReviewerId(); } } - // if current user is an anonymous reviewer, filter out authors + // if current user is an anonymous reviewer, exclude authors foreach ($reviewAssignments as $reviewAssignment) { if ($reviewAssignment->getReviewerId() == $user->getId()) { if ($reviewAssignment->getReviewMethod() != ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN) { - // Replaces StageAssignmentDAO::getBySubmissionAndRoleId $excludeUsers = StageAssignment::withSubmissionIds([$query->assocId]) ->withRoleIds([Role::ROLE_ID_AUTHOR]) - ->withUserId($user->getId()) ->get() - ->pluck('userId') + ->map(fn($assignment) => $assignment->userId) ->all(); } } } - // if current user is author, add open reviewers who have accepted the request + // If current user is author, include open reviewers who have accepted the request if (array_intersect([Role::ROLE_ID_AUTHOR], $assignedRoles)) { foreach ($reviewAssignments as $reviewAssignment) { - if ($reviewAssignment->getReviewMethod() == ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN && $reviewAssignment->getDateConfirmed() && !$reviewAssignment->getDeclined()) { + if ( + $reviewAssignment->getReviewMethod() == ReviewAssignment::SUBMISSION_REVIEW_METHOD_OPEN && + $reviewAssignment->getDateConfirmed() && + !$reviewAssignment->getDeclined() + ) { $includeUsers[] = $reviewAssignment->getReviewerId(); } } @@ -361,33 +378,38 @@ public function fetch($request, $template = null, $display = false, $actionArgs $usersIterator = $usersIterator->merge($includedUsersIterator); $allParticipants = []; - foreach ($usersIterator as $user) { - $allUserGroups = Repo::userGroup()->userUserGroups($user->getId(), $context->getId()); - + foreach ($usersIterator as $participantUser) { + // fetch user groups where the user is assigned in the current context + $allUserGroups = UserGroup::query() + ->withContextIds($context->getId()) + ->whereHas('userUserGroups', function ($query) use ($participantUser) { + $query->withUserId($participantUser->getId()) + ->withActive(); + }) + ->get(); + $userRoles = []; - // Replaces StageAssignmentDAO::getBySubmissionAndStageId - $userAssignments = StageAssignment::withSubmissionIds([$query->assocId]) - ->withStageIds([$query->stageId]) - ->withUserId($user->getId()) - ->get(); + // get participant's assigned roles + $participantAssignedRoles = $this->getAssignedRoles($query->assocId, $query->stageId, $participantUser->getId()); - foreach ($userAssignments as $userAssignment) { + foreach ($participantAssignedRoles as $roleId) { foreach ($allUserGroups as $userGroup) { - if ($userGroup->getId() == $userAssignment->userGroupId) { - $userRoles[] = $userGroup->getLocalizedName(); + if ($userGroup->roleId == $roleId) { + $userRoles[] = $userGroup->getLocalizedData('name'); } } } + // include reviewer roles if applicable foreach ($reviewAssignments as $assignment) { - if ($assignment->getReviewerId() == $user->getId()) { + if ($assignment->getReviewerId() == $participantUser->getId()) { $userRoles[] = __('user.role.reviewer') . ' (' . __($assignment->getReviewMethodKey()) . ')'; } } if (!count($userRoles)) { $userRoles[] = __('submission.status.unassigned'); } - $allParticipants[$user->getId()] = __('submission.query.participantTitle', [ - 'fullName' => $user->getFullName(), + $allParticipants[$participantUser->getId()] = __('submission.query.participantTitle', [ + 'fullName' => $participantUser->getFullName(), 'userGroup' => join(__('common.commaListSeparator'), $userRoles), ]); } @@ -448,18 +470,7 @@ public function validate($callHooks = true) $participantsToConsider = $blindReviewerCount = 0; foreach ($newParticipantIds as $participantId) { // get participant roles in this workflow stage - $assignedRoles = []; - // Replaces StageAssignmentDAO::getBySubmissionAndStageId - $usersAssignments = StageAssignment::withSubmissionIds([$submissionId]) - ->withStageIds([$stageId]) - ->withUserId($participantId) - ->get(); - - foreach ($usersAssignments as $usersAssignment) { - $userGroup = Repo::userGroup()->get($usersAssignment->userGroupId); - $assignedRoles[] = $userGroup->getRoleId(); - } - + $assignedRoles = $this->getAssignedRoles($submissionId, $stageId, $participantId); if ($stageId == WORKFLOW_STAGE_ID_EXTERNAL_REVIEW || $stageId == WORKFLOW_STAGE_ID_INTERNAL_REVIEW) { // validate the anonymity // get participant review assignments diff --git a/controllers/grid/settings/category/form/CategoryForm.php b/controllers/grid/settings/category/form/CategoryForm.php index cc4317b860d..2bd6bd23d23 100644 --- a/controllers/grid/settings/category/form/CategoryForm.php +++ b/controllers/grid/settings/category/form/CategoryForm.php @@ -260,18 +260,17 @@ public function fetch($request, $template = null, $display = false) // Sort options. $templateMgr->assign('sortOptions', Repo::submission()->getSortSelectOptions()); - $assignableUserGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$request->getContext()->getId()]) - ->filterByRoleIds($this->assignableRoles) - ->filterByStageIds([WORKFLOW_STAGE_ID_SUBMISSION]) - ->getMany() + $assignableUserGroups = UserGroup::query() + ->withContextIds([$request->getContext()->getId()]) + ->withRoleIds($this->assignableRoles) + ->withStageIds([WORKFLOW_STAGE_ID_SUBMISSION]) + ->get() ->map(function (UserGroup $userGroup) use ($request) { return [ 'userGroup' => $userGroup, 'users' => Repo::user() ->getCollector() - ->filterByUserGroupIds([$userGroup->getId()]) + ->filterByUserGroupIds([$userGroup->id]) ->filterByContextIds([$request->getContext()->getId()]) ->getMany() ->mapWithKeys(fn ($user, $key) => [$user->getId() => $user->getFullName()]) diff --git a/controllers/grid/settings/roles/UserGroupGridCellProvider.php b/controllers/grid/settings/roles/UserGroupGridCellProvider.php index 3282a6e16fd..cb198d62a56 100644 --- a/controllers/grid/settings/roles/UserGroupGridCellProvider.php +++ b/controllers/grid/settings/roles/UserGroupGridCellProvider.php @@ -45,26 +45,29 @@ public function getTemplateVarsFromRowColumn($row, $column) $workflowStages = Application::getApplicationStages(); $roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */ - $assignedStages = Repo::userGroup()->getAssignedStagesByUserGroupId($userGroup->getContextId(), $userGroup->getId())->toArray(); + $assignedStages = Repo::userGroup()->getAssignedStagesByUserGroupId($userGroup->contextId, $userGroup->id)->toArray(); switch ($columnId) { case 'name': - return ['label' => $userGroup->getLocalizedName()]; + return ['label' => $userGroup->getLocalizedData('name')]; case 'roleId': - $roleNames = Application::getRoleNames(false, [$userGroup->getRoleId()]); + $roleNames = Application::getRoleNames(false, [$userGroup->roleId]); return ['label' => __(array_shift($roleNames))]; - case in_array($columnId, $workflowStages): - // Set the state of the select element that will - // be used to assign the stage to the user group. - $selectDisabled = false; - if (in_array($columnId, $roleDao->getForbiddenStages($userGroup->getRoleId()))) { - // This stage should not be assigned to the user group. - $selectDisabled = true; - } - - return ['selected' => in_array($columnId, $assignedStages), - 'disabled' => $selectDisabled]; default: + if (in_array($columnId, $workflowStages)) { + // Set the state of the select element that will + // be used to assign the stage to the user group. + $selectDisabled = false; + if (in_array($columnId, $roleDao->getForbiddenStages($userGroup->roleId))) { + // This stage should not be assigned to the user group. + $selectDisabled = true; + } + + return [ + 'selected' => in_array($columnId, $assignedStages), + 'disabled' => $selectDisabled + ]; + } break; } @@ -82,12 +85,12 @@ public function getCellActions($request, $row, $column, $position = GridHandler: if (in_array($columnId, $workflowStages)) { $userGroup = $row->getData(); /** @var UserGroup $userGroup */ - $assignedStages = Repo::userGroup()->getAssignedStagesByUserGroupId($userGroup->getContextId(), $userGroup->getId())->toArray(); + $assignedStages = Repo::userGroup()->getAssignedStagesByUserGroupId($userGroup->contextId, $userGroup->id)->toArray(); $router = $request->getRouter(); $roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */ - if (!in_array($columnId, $roleDao->getForbiddenStages($userGroup->getRoleId()))) { + if (!in_array($columnId, $roleDao->getForbiddenStages($userGroup->roleId))) { if (in_array($columnId, $assignedStages)) { $operation = 'unassignStage'; $actionTitleKey = 'grid.userGroup.unassignStage'; diff --git a/controllers/grid/settings/roles/UserGroupGridHandler.php b/controllers/grid/settings/roles/UserGroupGridHandler.php index 77d7568149e..a18921c9203 100644 --- a/controllers/grid/settings/roles/UserGroupGridHandler.php +++ b/controllers/grid/settings/roles/UserGroupGridHandler.php @@ -81,6 +81,8 @@ public function authorize($request, &$args, $roleAssignments) $this->addPolicy(new ContextAccessPolicy($request, $roleAssignments)); $operation = $request->getRequestedOp(); + $context = $request->getContext(); + $contextId = $context->getId(); $workflowStageRequiredOps = ['assignStage', 'unassignStage']; if (in_array($operation, $workflowStageRequiredOps)) { $this->addPolicy(new WorkflowStageRequiredPolicy($request->getUserVar('stageId'))); @@ -91,7 +93,7 @@ public function authorize($request, &$args, $roleAssignments) // Validate the user group object. $userGroupId = $request->getUserVar('userGroupId'); - $userGroup = Repo::userGroup()->get($userGroupId); + $userGroup = UserGroup::findById($userGroupId); if (!$userGroup) { throw new \Exception('Invalid user group id!'); @@ -183,25 +185,30 @@ protected function loadData($request, $filter) $stageIdFilter = $filter['selectedStageId']; } - $rangeInfo = $this->getGridRangeInfo($request, $this->getId()); + $query = UserGroup::withContextIds($contextId); - if ($stageIdFilter && $stageIdFilter != 0) { - return Repo::userGroup()->getCollector() - ->filterByContextIds([$contextId]) - ->filterByStageIds([$stageIdFilter]) - ->filterByRoleIds([$roleIdFilter]) - ->limit($rangeInfo->getCount()) - ->offset($rangeInfo->getOffset() + max(0, $rangeInfo->getPage() - 1) * $rangeInfo->getCount()) - ->getMany() - ->toArray(); - } elseif ($roleIdFilter && $roleIdFilter != 0) { - return Repo::userGroup()->getByRoleIds([$roleIdFilter], $contextId)->toArray(); - } else { - return Repo::userGroup()->getCollector() - ->filterByContextIds([$contextId]) - ->getMany() - ->toArray(); + if (!empty($roleIdFilter)) { + $query->where('roleId', $roleIdFilter); } + + if (!empty($stageIdFilter)) { + $query->whereHas('userGroupStages', function ($q) use ($stageIdFilter) { + $q->where('stageId', $stageIdFilter); + }); + } + + // pagination + $rangeInfo = $this->getGridRangeInfo($request, $this->getId()); + $perPage = $rangeInfo->getCount(); + $page = max(1, $rangeInfo->getPage()); + $offset = ($page - 1) * $perPage; + + $query->offset($offset)->limit($perPage); + + // results + $userGroups = $query->get(); + + return $userGroups; } /** @@ -346,32 +353,29 @@ public function removeUserGroup($args, $request) $contextId = $this->_getContextId(); $notificationMgr = new NotificationManager(); - $usersAssignedToUserGroupCount = Repo::user()->getCollector() - ->filterByContextIds([$contextId]) - ->filterByUserGroupIds([$userGroup->getId()]) - ->getCount(); + $usersAssignedToUserGroupCount = $userGroup->usersInContext($contextId)->count(); if ($usersAssignedToUserGroupCount == 0) { - if ($userGroup->getData('isDefault')) { + if ($userGroup->isDefault) { // Can't delete default user groups. $notificationMgr->createTrivialNotification( $user->getId(), Notification::NOTIFICATION_TYPE_WARNING, ['contents' => __( 'grid.userGroup.cantRemoveDefaultUserGroup', - ['userGroupName' => $userGroup->getLocalizedName() ] + ['userGroupName' => $userGroup->getLocalizedData('name') ] )] ); } else { // We can delete, no user assigned yet. - Repo::userGroup()->delete($userGroup); + $userGroup->delete(); $notificationMgr->createTrivialNotification( $user->getId(), Notification::NOTIFICATION_TYPE_SUCCESS, ['contents' => __( 'grid.userGroup.removed', - ['userGroupName' => $userGroup->getLocalizedName() ] + ['userGroupName' => $userGroup->getLocalizedData('name') ] )] ); } @@ -383,12 +387,12 @@ public function removeUserGroup($args, $request) Notification::NOTIFICATION_TYPE_WARNING, ['contents' => __( 'grid.userGroup.cantRemoveUserGroup', - ['userGroupName' => $userGroup->getLocalizedName(), 'usersCount' => $usersAssignedToUserGroupCount] + ['userGroupName' => $userGroup->getLocalizedData('name'), 'usersCount' => $usersAssignedToUserGroupCount] )] ); } - $json = \PKP\db\DAO::getDataChangedEvent($userGroup->getId()); + $json = \PKP\db\DAO::getDataChangedEvent($userGroup->id); $json->setGlobalEvent('userGroupUpdated'); return $json; } @@ -441,14 +445,18 @@ private function _toggleAssignment($args, $request) case 'assignStage': UserGroupStage::create([ 'contextId' => $contextId, - 'userGroupId' => $userGroup->getId(), + 'userGroupId' => $userGroup->id, 'stageId' => $stageId ]); $messageKey = 'grid.userGroup.assignedStage'; break; case 'unassignStage': - Repo::userGroup()->removeGroupFromStage($contextId, $userGroup->getId(), $stageId); + UserGroupStage::query() + ->withContextId($contextId) + ->withUserGroupId($userGroup->id) + ->withStageId($stageId) + ->delete(); $messageKey = 'grid.userGroup.unassignedStage'; break; } @@ -463,11 +471,11 @@ private function _toggleAssignment($args, $request) Notification::NOTIFICATION_TYPE_SUCCESS, ['contents' => __( $messageKey, - ['userGroupName' => $userGroup->getLocalizedName(), 'stageName' => __($stageLocaleKeys[$stageId])] + ['userGroupName' => $userGroup->getLocalizedData('name'), 'stageName' => __($stageLocaleKeys[$stageId])] )] ); - return \PKP\db\DAO::getDataChangedEvent($userGroup->getId()); + return \PKP\db\DAO::getDataChangedEvent($userGroup->id); } /** diff --git a/controllers/grid/settings/roles/UserGroupGridRow.php b/controllers/grid/settings/roles/UserGroupGridRow.php index 3e901abf924..609743e618a 100644 --- a/controllers/grid/settings/roles/UserGroupGridRow.php +++ b/controllers/grid/settings/roles/UserGroupGridRow.php @@ -41,7 +41,7 @@ public function initialize($request, $template = null) $rowId = $this->getId(); - $actionArgs = ['userGroupId' => $userGroup->getId()]; + $actionArgs = ['userGroupId' => $userGroup->id]; $this->setRequestArgs($actionArgs); // Only add row actions if this is an existing row. diff --git a/controllers/grid/settings/roles/form/UserGroupForm.php b/controllers/grid/settings/roles/form/UserGroupForm.php index 3a9333ee0a8..dbe30262e3a 100644 --- a/controllers/grid/settings/roles/form/UserGroupForm.php +++ b/controllers/grid/settings/roles/form/UserGroupForm.php @@ -29,6 +29,11 @@ use PKP\stageAssignment\StageAssignment; use PKP\userGroup\relationships\UserGroupStage; use PKP\workflow\WorkflowStageDAO; +use PKP\userGroup\UserGroup; +use PKP\userGroup\relationships\UserUserGroup; +use PKP\userGroup\Repository as UserGroupRepository; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\DB; class UserGroupForm extends Form { @@ -100,7 +105,7 @@ public function getLocaleFieldNames(): array */ public function initData() { - $userGroup = Repo::userGroup()->get($this->getUserGroupId()); + $userGroup = UserGroup::findById($this->getUserGroupId(), $this->getContextId()); $stages = WorkflowStageDAO::getWorkflowStageTranslationKeys(); $this->setData('stages', $stages); $this->setData('assignedStages', []); // sensible default @@ -111,19 +116,19 @@ public function initData() $this->setData('roleForbiddenStagesJSON', $jsonMessage->getString()); if ($userGroup) { - $assignedStages = Repo::userGroup()->getAssignedStagesByUserGroupId($this->getContextId(), $userGroup->getId())->toArray(); + $assignedStages = $userGroup->getAssignedStageIds()->toArray(); $data = [ - 'userGroupId' => $userGroup->getId(), - 'roleId' => $userGroup->getRoleId(), - 'name' => $userGroup->getName(null), //Localized - 'abbrev' => $userGroup->getAbbrev(null), //Localized + 'userGroupId' => $userGroup->id, + 'roleId' => $userGroup->roleId, + 'name' => $userGroup->name, // Localized array + 'abbrev' => $userGroup->abbrev, // Localized array 'assignedStages' => $assignedStages, - 'showTitle' => $userGroup->getShowTitle(), - 'permitSelfRegistration' => $userGroup->getPermitSelfRegistration(), - 'permitMetadataEdit' => $userGroup->getPermitMetadataEdit(), - 'recommendOnly' => $userGroup->getRecommendOnly(), - 'masthead' => $userGroup->getMasthead(), + 'showTitle' => $userGroup->showTitle, + 'permitSelfRegistration' => $userGroup->permitSelfRegistration, + 'permitMetadataEdit' => $userGroup->permitMetadataEdit, + 'recommendOnly' => $userGroup->recommendOnly, + 'masthead' => $userGroup->masthead, ]; foreach ($data as $field => $value) { @@ -158,8 +163,8 @@ public function fetch($request, $template = null, $display = false) $templateMgr->assign('disableRoleSelect', $disableRoleSelect); $templateMgr->assign('selfRegistrationRoleIds', $this->getPermitSelfRegistrationRoles()); $templateMgr->assign('recommendOnlyRoleIds', $this->getRecommendOnlyRoles()); - $templateMgr->assign('notChangeMetadataEditPermissionRoles', Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES); - + $repository = Repo::userGroup(); + $templateMgr->assign('notChangeMetadataEditPermissionRoles', $repository::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES); return parent::fetch($request, $template, $display); } @@ -192,61 +197,82 @@ public function execute(...$functionParams) $request = Application::get()->getRequest(); $userGroupId = $this->getUserGroupId(); - $roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */ + $repository = Repo::userGroup(); + // Check if we are editing an existing user group or creating another one. if ($userGroupId == null) { - $userGroup = Repo::userGroup()->newDataObject(); + // creating a new UserGroup + $userGroup = new UserGroup(); $roleId = $this->getData('roleId'); if ($roleId == Role::ROLE_ID_SITE_ADMIN) { throw new \Exception('Site administrator roles cannot be created here.'); } - $userGroup->setRoleId($roleId); - - $userGroup->setContextId($this->getContextId()); - $userGroup->setDefault(false); - $userGroup->setShowTitle(is_null($this->getData('showTitle')) ? false : $this->getData('showTitle')); - $userGroup->setPermitSelfRegistration($this->getData('permitSelfRegistration') && in_array($userGroup->getRoleId(), $this->getPermitSelfRegistrationRoles())); - $userGroup->setPermitMetadataEdit($this->getData('permitMetadataEdit') && !in_array($this->getData('roleId'), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)); - if (in_array($this->getData('roleId'), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { - $userGroup->setPermitMetadataEdit(true); + $userGroup->roleId = $roleId; + + $userGroup->contextId = $this->getContextId(); + $userGroup->isDefault = false; + $userGroup->showTitle = (bool) $this->getData('showTitle'); + $userGroup->permitSelfRegistration = (bool) $this->getData('permitSelfRegistration') && in_array($userGroup->roleId, $this->getPermitSelfRegistrationRoles()); + + if (in_array($userGroup->roleId, Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { + $userGroup->permitMetadataEdit = true; + } else { + $userGroup->permitMetadataEdit = $this->getData('permitMetadataEdit') ?? false; } - $userGroup->setRecommendOnly($this->getData('recommendOnly') && in_array($userGroup->getRoleId(), $this->getRecommendOnlyRoles())); + $userGroup->recommendOnly = $this->getData('recommendOnly') && in_array($userGroup->roleId, $this->getRecommendOnlyRoles()); + $userGroup->masthead = $this->getData('masthead') ?? false; + + // set localized fields $userGroup = $this->_setUserGroupLocaleFields($userGroup, $request); - $userGroup->setMasthead($this->getData('masthead') ?? false); - $userGroupId = Repo::userGroup()->add($userGroup); + + // save the user group + $userGroup->save(); + $userGroupId = $userGroup->id; } else { - $userGroup = Repo::userGroup()->get($userGroupId); + // editing an existing UserGroup + $userGroup = UserGroup::findById($userGroupId, $this->getContextId()); + + // update localized fields $userGroup = $this->_setUserGroupLocaleFields($userGroup, $request); - $userGroup->setShowTitle(is_null($this->getData('showTitle')) ? false : $this->getData('showTitle')); - $userGroup->setPermitSelfRegistration($this->getData('permitSelfRegistration') && in_array($userGroup->getRoleId(), $this->getPermitSelfRegistrationRoles())); - $userGroup->setPermitMetadataEdit($this->getData('permitMetadataEdit') && !in_array($userGroup->getRoleId(), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)); - if (in_array($userGroup->getRoleId(), Repo::userGroup()::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { - $userGroup->setPermitMetadataEdit(true); + + $userGroup->showTitle = $this->getData('showTitle') ?? false; + $userGroup->permitSelfRegistration = $this->getData('permitSelfRegistration') && in_array($userGroup->roleId, $this->getPermitSelfRegistrationRoles()); + + $previousPermitMetadataEdit = $userGroup->permitMetadataEdit; + + if (in_array($userGroup->roleId, $repository::NOT_CHANGE_METADATA_EDIT_PERMISSION_ROLES)) { + $userGroup->permitMetadataEdit = true; } else { - $permitMetadataEdit = $userGroup->getPermitMetadataEdit(); - - $stageAssignments = StageAssignment::withUserGroupId($userGroupId) + $userGroup->permitMetadataEdit = (bool) $this->getData('permitMetadataEdit'); + } + + // if permitMetadataEdit has changed, update StageAssignments + if ($userGroup->permitMetadataEdit !== $previousPermitMetadataEdit) { + $stageAssignments = StageAssignment::query() + ->withUserGroupId($userGroupId) ->withContextId($this->getContextId()) ->get(); foreach ($stageAssignments as $stageAssignment) { - $stageAssignment->update(['canChangeMetadata' => $permitMetadataEdit]); + $stageAssignment->canChangeMetadata = $userGroup->permitMetadataEdit; + $stageAssignment->save(); } } - $userGroup->setRecommendOnly($this->getData('recommendOnly') && in_array($userGroup->getRoleId(), $this->getRecommendOnlyRoles())); - $userGroup->setMasthead($this->getData('masthead') ?? false); - Repo::userGroup()->edit($userGroup, []); + $userGroup->recommendOnly = $this->getData('recommendOnly') && in_array($userGroup->roleId, $this->getRecommendOnlyRoles()); + $userGroup->masthead = $this->getData('masthead') ?? false; + $userGroup->save(); } // After we have created/edited the user group, we assign/update its stages. $assignedStages = $this->getData('assignedStages'); + // Always set all stages active for some permission levels. - if (in_array($userGroup->getRoleId(), $roleDao->getAlwaysActiveStages())) { + if (in_array($userGroup->roleId, $roleDao->getAlwaysActiveStages())) { $assignedStages = array_keys(WorkflowStageDAO::getWorkflowStageTranslationKeys()); } if ($assignedStages) { @@ -267,30 +293,35 @@ public function execute(...$functionParams) public function _assignStagesToUserGroup($userGroupId, $userAssignedStages) { $contextId = $this->getContextId(); + $roleId = $this->getData('roleId'); + $roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */ + $stageIds = $this->getData('assignedStages') ?? []; + // Current existing workflow stages. $stages = WorkflowStageDAO::getWorkflowStageTranslationKeys(); - - foreach (array_keys($stages) as $stageId) { - Repo::userGroup()->removeGroupFromStage($contextId, $userGroupId, $stageId); - } - + + // Remove all existing stage assignments for this user group + UserGroupStage::query() + ->withContextId($contextId) + ->withUserGroupId($userGroupId) + ->withStageIds($stageIds) + ->delete(); + + // Assign new stages foreach ($userAssignedStages as $stageId) { - // Make sure we don't assign forbidden stages based on - // user groups role id. Override in case of some permission levels. - $roleId = $this->getData('roleId'); - $roleDao = DAORegistry::getDAO('RoleDAO'); /** @var RoleDAO $roleDao */ + // Make sure we don't assign forbidden stages based on user group role id $forbiddenStages = $roleDao->getForbiddenStages($roleId); if (in_array($stageId, $forbiddenStages) && !in_array($roleId, $roleDao->getAlwaysActiveStages())) { continue; } - // Check if is a valid stage. - if (in_array($stageId, array_keys($stages))) { + // Check if it's a valid stage. + if (array_key_exists($stageId, $stages)) { UserGroupStage::create([ 'contextId' => $contextId, 'userGroupId' => $userGroupId, - 'stageId' => $stageId + 'stageId' => $stageId, ]); } else { throw new \Exception('Invalid stage id'); @@ -309,23 +340,23 @@ public function _setUserGroupLocaleFields($userGroup, $request): \PKP\userGroup\ { $router = $request->getRouter(); $context = $router->getContext($request); - $supportedLocales = $context->getSupportedLocaleNames(); - + $supportedLocales = $context->getSupportedLocales(); + $name = $this->getData('name'); + $abbrev = $this->getData('abbrev'); + if (!empty($supportedLocales)) { - foreach ($context->getSupportedLocaleNames() as $localeKey => $localeName) { - $name = $this->getData('name'); - $abbrev = $this->getData('abbrev'); + foreach ($supportedLocales as $localeKey) { if (isset($name[$localeKey])) { - $userGroup->setName($name[$localeKey], $localeKey); + $userGroup->name[$localeKey] = $name[$localeKey]; } if (isset($abbrev[$localeKey])) { - $userGroup->setAbbrev($abbrev[$localeKey], $localeKey); + $userGroup->abbrev[$localeKey] = $abbrev[$localeKey]; } } } else { $localeKey = Locale::getLocale(); - $userGroup->setName($this->getData('name'), $localeKey); - $userGroup->setAbbrev($this->getData('abbrev'), $localeKey); + $userGroup->name[$localeKey] = $name[$localeKey] ?? ''; + $userGroup->abbrev[$localeKey] = $abbrev[$localeKey] ?? ''; } return $userGroup; diff --git a/controllers/grid/settings/sections/form/PKPSectionForm.php b/controllers/grid/settings/sections/form/PKPSectionForm.php index 4cff4a8a3f8..16e77cb309e 100644 --- a/controllers/grid/settings/sections/form/PKPSectionForm.php +++ b/controllers/grid/settings/sections/form/PKPSectionForm.php @@ -112,18 +112,17 @@ public function setSection(Section $section): void */ public function fetch($request, $template = null, $display = false) { - $assignableUserGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$request->getContext()->getId()]) - ->filterByRoleIds($this->assignableRoles) - ->filterByStageIds([WORKFLOW_STAGE_ID_SUBMISSION]) - ->getMany() + $assignableUserGroups = UserGroup::query() + ->withContextIds([$request->getContext()->getId()]) + ->withRoleIds($this->assignableRoles) + ->withStageIds([WORKFLOW_STAGE_ID_SUBMISSION]) + ->get() ->map(function (UserGroup $userGroup) use ($request) { return [ 'userGroup' => $userGroup, 'users' => Repo::user() ->getCollector() - ->filterByUserGroupIds([$userGroup->getId()]) + ->filterByUserGroupIds([$userGroup->id]) ->filterByContextIds([$request->getContext()->getId()]) ->getMany() ->mapWithKeys(fn ($user, $key) => [$user->getId() => $user->getFullName()]) diff --git a/controllers/grid/settings/user/UserGridHandler.php b/controllers/grid/settings/user/UserGridHandler.php index 6817b1f208c..cc256a26bd2 100644 --- a/controllers/grid/settings/user/UserGridHandler.php +++ b/controllers/grid/settings/user/UserGridHandler.php @@ -42,6 +42,7 @@ use PKP\security\Validation; use PKP\user\User; use PKP\userGroup\UserGroup; +use PKP\userGroup\relationships\UserUserGroup; class UserGridHandler extends GridHandler { @@ -159,9 +160,18 @@ public function getTemplateVarsFromRow($row): array $user = $row->getData(); assert($user instanceof User); $contextId = Application::get()->getRequest()->getContext()->getId(); - $userGroupsIterator = Repo::userGroup()->userUserGroups($user->getId(), $contextId); - $roles = $userGroupsIterator->map(fn (UserGroup $userGroup) => $userGroup->getLocalizedName())->join(__('common.commaListSeparator')); - return ['label' => $roles]; + + // fetch user groups where the user is assigned in the current context + $userGroups = UserGroup::query() + ->withContextIds($contextId) + ->whereHas('userUserGroups', function ($query) use ($user) { + $query->withUserId($user->getId()) + ->withActive(); + }) + ->get(); + + $roles = $userGroups->map(fn (UserGroup $userGroup) => $userGroup->getLocalizedData('name'))->join(__('common.commaListSeparator')); + return ['label' => $roles]; } } ); @@ -239,12 +249,11 @@ public function renderFilter($request, $filterData = []) { $context = $request->getContext(); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$context->getId()])->get(); + $userGroupOptions = ['' => __('grid.user.allRoles')]; foreach ($userGroups as $userGroup) { - $userGroupOptions[$userGroup->getId()] = $userGroup->getLocalizedName(); + $userGroupOptions[$userGroup->id] = $userGroup->getLocalizedData('name'); } $userDao = Repo::user()->dao; @@ -539,29 +548,39 @@ public function removeUser($args, $request) if (!$request->checkCSRF()) { return new JSONMessage(false); } - + $context = $request->getContext(); $user = $request->getUser(); - + // Identify the user Id. $userId = $request->getUserVar('rowId'); - - if ($userId !== null && Validation::getAdministrationLevel($userId, $user->getId(), $request->getContext()->getId()) === Validation::ADMINISTRATION_PROHIBITED) { + + if ($userId !== null && Validation::getAdministrationLevel($userId, $user->getId(), $context->getId()) === Validation::ADMINISTRATION_PROHIBITED) { // We don't have administrative rights over this user. return new JSONMessage(false, __('grid.user.cannotAdminister')); } - - // End all active user group assignments for this context. + // Check if this user has any active user group assignments for this context. - $activeUserGroupCount = Repo::userGroup() - ->userUserGroups($userId, $context->getId()) + $activeUserGroupCount = UserGroup::query() + ->withContextIds($context->getId()) + ->whereHas('userUserGroups', function ($query) use ($userId) { + $query->withUserId($userId) + ->withActive(); + }) ->count(); - + if (!$activeUserGroupCount) { return new JSONMessage(false, __('grid.user.userNoRoles')); } else { - Repo::userGroup()->endAssignments($context->getId(), $userId); - + // End all active user group assignments for this context. + UserUserGroup::query() + ->withUserId($userId) + ->withActive() + ->whereHas('userGroup', function ($query) use ($context) { + $query->withContextIds($context->getId()); + }) + ->update(['dateEnd' => now()]); + return \PKP\db\DAO::getDataChangedEvent($userId); } } diff --git a/controllers/grid/settings/user/form/UserForm.php b/controllers/grid/settings/user/form/UserForm.php index bd1eb02a097..a3b3113b1d5 100644 --- a/controllers/grid/settings/user/form/UserForm.php +++ b/controllers/grid/settings/user/form/UserForm.php @@ -20,6 +20,8 @@ use APP\facades\Repo; use APP\template\TemplateManager; use PKP\form\Form; +use PKP\userGroup\UserGroup; +use PKP\userGroup\relationships\UserUserGroup; class UserForm extends Form { @@ -50,11 +52,17 @@ public function initData() $userGroupIds = $masthead = []; if (!is_null($this->userId)) { - $userGroups = Repo::userGroup()->userUserGroups($this->userId); - + // fetch user groups where the user is assigned + $userGroups = UserGroup::query() + ->whereHas('userUserGroups', function ($query) { + $query->withUserId($this->userId) + ->withActive(); + }) + ->get(); + foreach ($userGroups as $userGroup) { - $userGroupIds[] = $userGroup->getId(); - $masthead[$userGroup->getId()] = Repo::userGroup()->userOnMasthead($this->userId, $userGroup->getId()); + $userGroupIds[] = $userGroup->id; + $masthead[$userGroup->id] = Repo::userGroup()->userOnMasthead($this->userId, $userGroup->id); } } @@ -86,12 +94,10 @@ public function display($request = null, $template = null) $allUserGroups = []; - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$contextId]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$contextId])->get(); foreach ($userGroups as $userGroup) { - $allUserGroups[(int) $userGroup->getId()] = $userGroup->getLocalizedName(); + $allUserGroups[(int) $userGroup->id] = $userGroup->getLocalizedData('name'); } $templateMgr->assign([ @@ -121,29 +127,36 @@ public function saveUserGroupAssignments(Request $request): void if ($this->getData('userGroupIds')) { $contextId = $request->getContext()->getId(); - - $oldUserGroupIds = []; - $oldUserGroups = Repo::userGroup()->userUserGroups($this->userId); - foreach ($oldUserGroups as $oldUserGroup) { - $oldUserGroupIds[] = $oldUserGroup->getId(); - } - + + // get current user group IDs + $oldUserGroupIds = UserGroup::query() + ->whereHas('userUserGroups', function ($query) { + $query->withUserId($this->userId) + ->withActive(); + }) + ->pluck('user_group_id') + ->all(); + $userGroupsToEnd = array_diff($oldUserGroupIds, $this->getData('userGroupIds')); collect($userGroupsToEnd) ->each( fn ($userGroupId) => - Repo::userGroup()->contextHasGroup($contextId, $userGroupId) - ? Repo::userGroup()->endAssignments($contextId, $this->userId, $userGroupId) - : null + UserUserGroup::query() + ->withUserId($this->userId) + ->withUserGroupId($userGroupId) + ->withActive() + ->update(['date_end' => now()]) ); $userGroupsToAdd = array_diff($this->getData('userGroupIds'), $oldUserGroupIds); collect($userGroupsToAdd) ->each( fn ($userGroupId) => - Repo::userGroup()->contextHasGroup($contextId, $userGroupId) - ? Repo::userGroup()->assignUserToGroup($this->userId, $userGroupId) - : null + UserUserGroup::create([ + 'userId' => $this->userId, + 'userGroupId' => $userGroupId, + 'dateStart' => now(), + ]) ); } } diff --git a/controllers/grid/users/exportableUsers/ExportableUsersGridHandler.php b/controllers/grid/users/exportableUsers/ExportableUsersGridHandler.php index 2cda973f726..7d690e45c0f 100644 --- a/controllers/grid/users/exportableUsers/ExportableUsersGridHandler.php +++ b/controllers/grid/users/exportableUsers/ExportableUsersGridHandler.php @@ -28,6 +28,7 @@ use PKP\linkAction\request\RedirectConfirmationModal; use PKP\security\authorization\ContextAccessPolicy; use PKP\security\Role; +use PKP\userGroup\UserGroup; class ExportableUsersGridHandler extends GridHandler { @@ -208,13 +209,11 @@ public function renderFilter($request, $filterData = []) { $context = $request->getContext(); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$context->getId()])->get(); $userGroupOptions = ['' => __('grid.user.allRoles')]; foreach ($userGroups as $userGroup) { - $userGroupOptions[$userGroup->getId()] = $userGroup->getLocalizedName(); + $userGroupOptions[$userGroup->id] = $userGroup->getLocalizedData('name'); } $userDao = Repo::user()->dao; $fieldOptions = [ diff --git a/controllers/grid/users/reviewer/form/AdvancedSearchReviewerForm.php b/controllers/grid/users/reviewer/form/AdvancedSearchReviewerForm.php index 3bb20cf6422..24d53eb669f 100644 --- a/controllers/grid/users/reviewer/form/AdvancedSearchReviewerForm.php +++ b/controllers/grid/users/reviewer/form/AdvancedSearchReviewerForm.php @@ -130,7 +130,7 @@ public function fetch($request, $template = null, $display = false) // Replaces StageAssignmentDAO::getBySubmissionAndStageId $warnOnAssignment = StageAssignment::withSubmissionIds([$this->getSubmissionId()]) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); // Get a list of users in the managerial and admin user groups diff --git a/controllers/grid/users/reviewer/form/EnrollExistingReviewerForm.php b/controllers/grid/users/reviewer/form/EnrollExistingReviewerForm.php index c52ea461294..cd5cba92ddf 100644 --- a/controllers/grid/users/reviewer/form/EnrollExistingReviewerForm.php +++ b/controllers/grid/users/reviewer/form/EnrollExistingReviewerForm.php @@ -123,12 +123,12 @@ protected function isValidUserAndGroup(int $userId, int $userGroupId): bool } // User Group refers to the reviewer role - if ($userGroup->getData('roleId') != Role::ROLE_ID_REVIEWER) { + if ($userGroup->roleId != Role::ROLE_ID_REVIEWER) { return false; } // User group refers to the right context - if (intval($userGroup->getData('contextId')) != $context->getId()) { + if ((int) $userGroup->contextId != $context->getId()) { return false; } diff --git a/controllers/grid/users/reviewer/form/ReviewerForm.php b/controllers/grid/users/reviewer/form/ReviewerForm.php index b677a3d2d7d..2f7fe7e9719 100644 --- a/controllers/grid/users/reviewer/form/ReviewerForm.php +++ b/controllers/grid/users/reviewer/form/ReviewerForm.php @@ -280,7 +280,7 @@ public function fetch($request, $template = null, $display = false) $userGroups = []; foreach ($reviewerUserGroups as $userGroup) { - $userGroups[$userGroup->getId()] = $userGroup->getLocalizedName(); + $userGroups[$userGroup->id] = $userGroup->getLocalizedData('name'); } $this->setData('userGroups', $userGroups); diff --git a/controllers/grid/users/stageParticipant/StageParticipantGridCategoryRow.php b/controllers/grid/users/stageParticipant/StageParticipantGridCategoryRow.php index ef2e534f657..ba9c1213098 100644 --- a/controllers/grid/users/stageParticipant/StageParticipantGridCategoryRow.php +++ b/controllers/grid/users/stageParticipant/StageParticipantGridCategoryRow.php @@ -48,7 +48,7 @@ public function __construct($submission, $stageId) public function getCategoryLabel() { $userGroup = $this->getData(); - return $userGroup->getLocalizedName(); + return $userGroup->getLocalizedData('name'); } // diff --git a/controllers/grid/users/stageParticipant/StageParticipantGridHandler.php b/controllers/grid/users/stageParticipant/StageParticipantGridHandler.php index c7e058487f7..a21fb220521 100644 --- a/controllers/grid/users/stageParticipant/StageParticipantGridHandler.php +++ b/controllers/grid/users/stageParticipant/StageParticipantGridHandler.php @@ -203,7 +203,7 @@ public function loadCategoryData($request, &$userGroup, $filter = null) // Replaces StageAssignmentDAO::getBySubmissionAndStageId $stageAssignments = StageAssignment::withSubmissionIds([$submission->getId()]) ->withStageIds([$stageId]) - ->withUserGroupId($userGroup->getId()) + ->withUserGroupId($userGroup->id) ->get(); return $stageAssignments->mapWithKeys(function ($stageAssignment) { @@ -279,13 +279,13 @@ protected function loadData($request, $filter) $this->getStageId() ); foreach ($userGroups as $userGroup) { - if ($userGroup->getRoleId() == Role::ROLE_ID_REVIEWER) { + if ($userGroup->roleId == Role::ROLE_ID_REVIEWER) { continue; } - if (!in_array($userGroup->getId(), $userGroupIds)) { + if (!in_array($userGroup->id, $userGroupIds)) { continue; } - $result[$userGroup->getId()] = $userGroup; + $result[$userGroup->id] = $userGroup; } return $result; } @@ -342,7 +342,7 @@ public function saveParticipant($args, $request) // Check user group role id. $userGroup = Repo::userGroup()->get($userGroupId); - if ($userGroup->getRoleId() == Role::ROLE_ID_MANAGER) { + if ($userGroup->roleId == Role::ROLE_ID_MANAGER) { $notificationMgr->updateNotification( $request, $notificationMgr->getDecisionStageNotifications(), @@ -389,7 +389,7 @@ public function saveParticipant($args, $request) 'dateLogged' => Core::getCurrentDate(), 'userFullName' => $assignedUser->getFullName(), 'username' => $assignedUser->getUsername(), - 'userGroupName' => $userGroup->getData('name') + 'userGroupName' => $userGroup->name, ]); Repo::eventLog()->add($eventLog); @@ -463,7 +463,7 @@ public function deleteParticipant($args, $request) 'dateLogged' => Core::getCurrentDate(), 'userFullName' => $assignedUser->getFullName(), 'username' => $assignedUser->getUsername(), - 'userGroupName' => $userGroup->getData('name') + 'userGroupName' => $userGroup->name, ]); Repo::eventLog()->add($eventLog); @@ -491,7 +491,7 @@ public function fetchUserList($args, $request) $users = $collector->getMany(); $userGroup = Repo::userGroup()->get($userGroupId); - $roleId = $userGroup->getRoleId(); + $roleId = $userGroup->roleId; $sectionId = $submission->getSectionId(); $contextId = $submission->getData('contextId'); diff --git a/controllers/grid/users/stageParticipant/StageParticipantGridRow.php b/controllers/grid/users/stageParticipant/StageParticipantGridRow.php index aad852f8cca..a96f1536266 100644 --- a/controllers/grid/users/stageParticipant/StageParticipantGridRow.php +++ b/controllers/grid/users/stageParticipant/StageParticipantGridRow.php @@ -117,7 +117,7 @@ public function initialize($request, $template = null) $dispatcher = $router->getDispatcher(); $userGroup = Repo::userGroup()->get($userGroupId); - if ($userGroup->getRoleId() == Role::ROLE_ID_AUTHOR) { + if ($userGroup->roleId == Role::ROLE_ID_AUTHOR) { $handler = 'authorDashboard'; $op = 'submission'; } else { diff --git a/controllers/grid/users/stageParticipant/form/AddParticipantForm.php b/controllers/grid/users/stageParticipant/form/AddParticipantForm.php index 95ad8e3bf42..54723bd01b9 100644 --- a/controllers/grid/users/stageParticipant/form/AddParticipantForm.php +++ b/controllers/grid/users/stageParticipant/form/AddParticipantForm.php @@ -100,11 +100,11 @@ protected function _isChangePermitMetadataAllowed(UserGroup $userGroup, int $use { $currentUser = Application::get()->getRequest()->getUser(); - if ($currentUser->getId() === $userId && $userGroup->getRoleId() === Role::ROLE_ID_SUB_EDITOR) { + if ($currentUser->getId() === $userId && $userGroup->roleId === Role::ROLE_ID_SUB_EDITOR) { return false; } - return $userGroup->getRoleId() !== Role::ROLE_ID_MANAGER; + return $userGroup->roleId !== Role::ROLE_ID_MANAGER; } /** @@ -116,11 +116,11 @@ protected function _isChangeRecommendOnlyAllowed(UserGroup $userGroup, int $user { $currentUser = Application::get()->getRequest()->getUser(); - if ($currentUser->getId() === $userId && $userGroup->getRoleId() === Role::ROLE_ID_SUB_EDITOR) { + if ($currentUser->getId() === $userId && $userGroup->roleId === Role::ROLE_ID_SUB_EDITOR) { return false; } - return in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]); + return in_array($userGroup->roleId, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]); } /** @@ -138,10 +138,10 @@ public function fetch($request, $template = null, $display = false) $userGroupOptions = []; foreach ($userGroups as $userGroup) { // Exclude reviewers. - if ($userGroup->getRoleId() == Role::ROLE_ID_REVIEWER) { + if ($userGroup->roleId == Role::ROLE_ID_REVIEWER) { continue; } - $userGroupOptions[$userGroup->getId()] = $userGroup->getLocalizedName(); + $userGroupOptions[$userGroup->id] = $userGroup->getLocalizedData('name'); } $templateMgr = TemplateManager::getManager($request); @@ -150,21 +150,22 @@ public function fetch($request, $template = null, $display = false) 'userGroupOptions' => $userGroupOptions, 'selectedUserGroupId' => array_shift($keys), // assign the first element as selected 'possibleRecommendOnlyUserGroupIds' => $this->_possibleRecommendOnlyUserGroupIds, - 'recommendOnlyUserGroupIds' => Repo::userGroup()->getCollector() - ->filterByContextIds([$request->getContext()->getId()]) - ->filterByIsRecommendOnly() - ->getIds() + 'recommendOnlyUserGroupIds' => UserGroup::query() + ->withContextIds($request->getContext()->getId()) + ->isRecommendOnly(true) + ->pluck('user_group_id') ->toArray(), 'notPossibleEditSubmissionMetadataPermissionChange' => $this->_managerGroupIds, - 'permitMetadataEditUserGroupIds' => Repo::userGroup()->getCollector() - ->filterByContextIds([$request->getContext()->getId()]) - ->filterByPermitMetadataEdit(true) - ->getIds() + 'permitMetadataEditUserGroupIds' => UserGroup::query() + ->withContextIds($request->getContext()->getId()) + ->permitMetadataEdit(true) + ->pluck('user_group_id') ->toArray(), 'submissionId' => $this->getSubmission()->getId(), 'userGroupId' => '', 'userIdSelected' => '', ]); + if ($this->_assignmentId) { $stageAssignment = StageAssignment::find($this->_assignmentId); @@ -177,7 +178,7 @@ public function fetch($request, $template = null, $display = false) $templateMgr->assign([ 'assignmentId' => $this->_assignmentId, 'currentUserName' => $currentUser->getFullName(), - 'currentUserGroup' => $userGroup->getLocalizedName(), + 'currentUserGroup' => $userGroup->getLocalizedData('name'), 'userGroupId' => $stageAssignment->userGroupId, 'userIdSelected' => $stageAssignment->userId, 'currentAssignmentRecommendOnly' => $stageAssignment->recommendOnly, @@ -257,7 +258,7 @@ public function execute(...$functionParams) $canChangeMetadata = $this->_isChangePermitMetadataAllowed($userGroup, $userId) ? (bool) $this->getData('canChangeMetadata') : true; // sanity check - if (UserGroupStage::withStageId($this->getStageId())->withUserGroupId($userGroup->getId())->get()->isNotEmpty()) { + if (UserGroupStage::withStageId($this->getStageId())->withUserGroupId($userGroup->id)->get()->isNotEmpty()) { $updated = false; if ($this->_assignmentId) { @@ -276,7 +277,7 @@ public function execute(...$functionParams) $stageAssignment = Repo::stageAssignment() ->build( $submission->getId(), - $userGroup->getId(), + $userGroup->id, $userId, $recommendOnly, $canChangeMetadata @@ -285,7 +286,7 @@ public function execute(...$functionParams) } parent::execute(...$functionParams); - return [$userGroup->getId(), $userId, $stageAssignment->id]; + return [$userGroup->id, $userId, $stageAssignment->id]; } /** diff --git a/controllers/grid/users/userSelect/UserSelectGridHandler.php b/controllers/grid/users/userSelect/UserSelectGridHandler.php index 1da9cfa9ba6..1210dbd4949 100644 --- a/controllers/grid/users/userSelect/UserSelectGridHandler.php +++ b/controllers/grid/users/userSelect/UserSelectGridHandler.php @@ -76,10 +76,10 @@ public function initialize($request, $args = null) $this->_userGroupOptions = []; foreach ($userGroups as $userGroup) { // Exclude reviewers. - if ($userGroup->getRoleId() == Role::ROLE_ID_REVIEWER) { + if ($userGroup->roleId == Role::ROLE_ID_REVIEWER) { continue; } - $this->_userGroupOptions[$userGroup->getId()] = $userGroup->getLocalizedName(); + $this->_userGroupOptions[$userGroup->id] = $userGroup->getLocalizedData('name'); } $this->setTitle('editor.submission.findAndSelectUser'); @@ -103,12 +103,14 @@ public function initialize($request, $args = null) null, null, $cellProvider, - ['alignment' => GridColumn::COLUMN_ALIGNMENT_LEFT, + [ + 'alignment' => GridColumn::COLUMN_ALIGNMENT_LEFT, 'width' => 30 ] ) ); } + // diff --git a/controllers/modals/submission/ViewSubmissionMetadataHandler.php b/controllers/modals/submission/ViewSubmissionMetadataHandler.php index 1e6cd48669b..4e133a81d55 100644 --- a/controllers/modals/submission/ViewSubmissionMetadataHandler.php +++ b/controllers/modals/submission/ViewSubmissionMetadataHandler.php @@ -22,6 +22,7 @@ use PKP\security\authorization\SubmissionAccessPolicy; use PKP\security\Role; use PKP\submission\reviewAssignment\ReviewAssignment; +use PKP\userGroup\UserGroup; class ViewSubmissionMetadataHandler extends handler { @@ -53,11 +54,9 @@ public function display($args, $request) $context = $request->getContext(); $templateMgr = TemplateManager::getManager($request); $publication = $submission->getCurrentPublication(); - + if ($reviewAssignment->getReviewMethod() != ReviewAssignment::SUBMISSION_REVIEW_METHOD_DOUBLEANONYMOUS) { /* ReviewAssignment::SUBMISSION_REVIEW_METHOD_ANONYMOUS or _OPEN */ - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$context->getId()])->get(); $templateMgr->assign('authors', $publication->getAuthorString($userGroups)); diff --git a/pages/about/AboutContextHandler.php b/pages/about/AboutContextHandler.php index 30ae3c46f6d..0bc051e6098 100644 --- a/pages/about/AboutContextHandler.php +++ b/pages/about/AboutContextHandler.php @@ -28,6 +28,7 @@ use PKP\security\Role; use PKP\userGroup\relationships\enums\UserUserGroupStatus; use PKP\userGroup\relationships\UserUserGroup; +use PKP\userGroup\UserGroup; class AboutContextHandler extends Handler { @@ -59,6 +60,24 @@ public function index($args, $request) $templateMgr->display('frontend/pages/about.tpl'); } + + private function getSortedMastheadUserGroups($context) + { + $mastheadUserGroups = UserGroup::withContextIds([$context->getId()]) + ->masthead(true) + ->excludeRoles([Role::ROLE_ID_REVIEWER]) + ->get(); + + $savedOrder = (array) $context->getData('mastheadUserGroupIds'); + + $sortedUserGroups = $mastheadUserGroups->sortBy(function ($userGroup) use ($savedOrder) { + return array_search($userGroup->id, $savedOrder); + }); + + return $sortedUserGroups; + } + + /** * Display editorial masthead page. * @@ -70,35 +89,28 @@ public function index($args, $request) public function editorialMasthead($args, $request) { $context = $request->getContext(); - - $savedMastheadUserGroupIdsOrder = (array) $context->getData('mastheadUserGroupIds'); - - $collector = Repo::userGroup()->getCollector(); - $allMastheadUserGroups = $collector - ->filterByContextIds([$context->getId()]) - ->filterByMasthead(true) - ->filterExcludeRoles([Role::ROLE_ID_REVIEWER]) - ->orderBy($collector::ORDERBY_ROLE_ID) - ->getMany() - ->toArray(); - - // sort the masthead roles in their saved order for display - $mastheadRoles = array_replace(array_intersect_key(array_flip($savedMastheadUserGroupIdsOrder), $allMastheadUserGroups), $allMastheadUserGroups); - - $allUsersIdsGroupedByUserGroupId = Repo::userGroup()->getMastheadUserIdsByRoleIds($mastheadRoles, $context->getId()); - + + // Get sorted masthead roles using the extracted method + $mastheadRoles = $this->getSortedMastheadUserGroups($context); + + // Get all user IDs grouped by user group ID for the masthead roles + $allUsersIdsGroupedByUserGroupId = Repo::userGroup()->getMastheadUserIdsByRoleIds( + $mastheadRoles, + $context->getId() + ); + $mastheadUsers = []; - foreach ($mastheadRoles as $mastheadUserGroup) { - foreach ($allUsersIdsGroupedByUserGroupId[$mastheadUserGroup->getId()] ?? [] as $userId) { + foreach ($mastheadRoles as $userGroupId => $mastheadUserGroup) { + foreach ($allUsersIdsGroupedByUserGroupId[$userGroupId] ?? [] as $userId) { $user = Repo::user()->get($userId); $userUserGroup = UserUserGroup::withUserId($user->getId()) - ->withUserGroupId($mastheadUserGroup->getId()) + ->withUserGroupId($userGroupId) ->withActive() ->withMasthead() ->first(); if ($userUserGroup) { $startDatetime = new DateTime($userUserGroup->dateStart); - $mastheadUsers[$mastheadUserGroup->getId()][$user->getId()] = [ + $mastheadUsers[$userGroupId][$user->getId()] = [ 'user' => $user, 'dateStart' => $startDatetime->format('Y'), ]; @@ -111,7 +123,11 @@ public function editorialMasthead($args, $request) $usersCollector = Repo::user()->getCollector(); $reviewers = $usersCollector ->filterByUserIds($reviewerIds->toArray()) - ->orderBy($usersCollector::ORDERBY_FAMILYNAME, $usersCollector::ORDER_DIR_ASC, [Locale::getLocale(), Application::get()->getRequest()->getSite()->getPrimaryLocale()]) + ->orderBy( + $usersCollector::ORDERBY_FAMILYNAME, + $usersCollector::ORDER_DIR_ASC, + [Locale::getLocale(), Application::get()->getRequest()->getSite()->getPrimaryLocale()] + ) ->getMany(); Hook::call('AboutContextHandler::editorialMasthead', [$mastheadRoles, $mastheadUsers, $reviewers, $previousYear]); @@ -140,31 +156,25 @@ public function editorialHistory($args, $request) { $context = $request->getContext(); - $savedMastheadUserGroupIdsOrder = (array) $context->getData('mastheadUserGroupIds'); - - $collector = Repo::userGroup()->getCollector(); - $allMastheadUserGroups = $collector - ->filterByContextIds([$context->getId()]) - ->filterByMasthead(true) - ->filterExcludeRoles([Role::ROLE_ID_REVIEWER]) - ->orderBy($collector::ORDERBY_ROLE_ID) - ->getMany() - ->toArray(); - - // sort the masthead roles in their saved order for display - $mastheadRoles = array_replace(array_intersect_key(array_flip($savedMastheadUserGroupIdsOrder), $allMastheadUserGroups), $allMastheadUserGroups); - - $allUsersIdsGroupedByUserGroupId = Repo::userGroup()->getMastheadUserIdsByRoleIds($mastheadRoles, $context->getId(), UserUserGroupStatus::STATUS_ENDED); + // get sorted masthead roles using the extracted method + $mastheadRoles = $this->getSortedMastheadUserGroups($context); + + // get all user IDs grouped by user group ID for the masthead roles with ended status + $allUsersIdsGroupedByUserGroupId = Repo::userGroup()->getMastheadUserIdsByRoleIds( + $mastheadRoles, + $context->getId(), + UserUserGroupStatus::STATUS_ENDED + ); $mastheadUsers = []; - foreach ($mastheadRoles as $mastheadUserGroup) { - foreach ($allUsersIdsGroupedByUserGroupId[$mastheadUserGroup->getId()] ?? [] as $userId) { + foreach ($mastheadRoles as $userGroupId => $mastheadUserGroup) { + foreach ($allUsersIdsGroupedByUserGroupId[$userGroupId] ?? [] as $userId) { $user = Repo::user()->get($userId); $userUserGroups = UserUserGroup::withUserId($user->getId()) - ->withUserGroupId($mastheadUserGroup->getId()) + ->withUserGroupId($userGroupId) ->withEnded() ->withMasthead() - ->sortBy('date_start', 'desc') + ->orderBy('date_start', 'desc') ->get(); $services = []; foreach ($userUserGroups as $userUserGroup) { @@ -176,7 +186,7 @@ public function editorialHistory($args, $request) ]; } if (!empty($services)) { - $mastheadUsers[$mastheadUserGroup->getId()][$user->getId()] = [ + $mastheadUsers[$userGroupId][$user->getId()] = [ 'user' => $user, 'services' => $services ]; diff --git a/pages/admin/AdminHandler.php b/pages/admin/AdminHandler.php index c498c4f6d91..d93b5ca5527 100644 --- a/pages/admin/AdminHandler.php +++ b/pages/admin/AdminHandler.php @@ -53,6 +53,7 @@ use PKP\security\Role; use PKP\site\VersionCheck; use PKP\site\VersionDAO; +use PKP\userGroup\UserGroup; class AdminHandler extends Handler { @@ -322,9 +323,7 @@ public function wizard($args, $request) $bulkEmailsEnabled = in_array($context->getId(), (array) $request->getSite()->getData('enableBulkEmails')); if ($bulkEmailsEnabled) { - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$context->getId()])->get()->toArray(); $restrictBulkEmailsForm = new \PKP\components\forms\context\PKPRestrictBulkEmailsForm($apiUrl, $context, $userGroups); $components[$restrictBulkEmailsForm->id] = $restrictBulkEmailsForm->getConfig(); diff --git a/pages/catalog/PKPCatalogHandler.php b/pages/catalog/PKPCatalogHandler.php index c23fe4ffe57..d2111e8f462 100644 --- a/pages/catalog/PKPCatalogHandler.php +++ b/pages/catalog/PKPCatalogHandler.php @@ -27,6 +27,7 @@ use PKP\file\ContextFileManager; use PKP\security\authorization\ContextRequiredPolicy; use PKP\security\Role; +use PKP\userGroup\UserGroup; class PKPCatalogHandler extends Handler { @@ -105,9 +106,10 @@ public function category($args, $request) 'parentCategory' => $parentCategory, 'subcategories' => iterator_to_array($subcategories), 'publishedSubmissions' => $submissions->toArray(), - 'authorUserGroups' => Repo::userGroup()->getCollector() - ->filterByRoleIds([Role::ROLE_ID_AUTHOR]) - ->filterByContextIds([$context->getId()])->getMany()->remember(), + 'authorUserGroups' => UserGroup::withRoleIds([Role::ROLE_ID_AUTHOR]) + ->withContextIds([$context->getId()]) + ->get() + ->remember(), ]); return $templateMgr->display('frontend/pages/catalogCategory.tpl'); diff --git a/pages/dashboard/DashboardHandler.php b/pages/dashboard/DashboardHandler.php index 0b4ca4eb858..481df105850 100644 --- a/pages/dashboard/DashboardHandler.php +++ b/pages/dashboard/DashboardHandler.php @@ -28,6 +28,7 @@ use PKP\submission\GenreDAO; use PKP\submission\PKPSubmission; use PKP\config\Config; +use PKP\userGroup\UserGroup; define('SUBMISSIONS_LIST_ACTIVE', 'active'); define('SUBMISSIONS_LIST_ARCHIVE', 'archive'); @@ -116,9 +117,8 @@ public function index($args, $request) $itemsMax = $collector->getCount(); $items = $collector->limit(30)->getMany(); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$context->getId()]) + ->lazy(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); diff --git a/pages/decision/DecisionHandler.php b/pages/decision/DecisionHandler.php index def944c3d6e..e6400be7ac0 100644 --- a/pages/decision/DecisionHandler.php +++ b/pages/decision/DecisionHandler.php @@ -123,7 +123,7 @@ public function record($args, $request) ->withRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SUB_EDITOR]) ->withRecommendOnly(false) ->get() - ->pluck('userId') + ->pluck('user_id') ->all(); if (!$assignedEditorIds) { diff --git a/pages/management/ManagementHandler.php b/pages/management/ManagementHandler.php index ea30a86287e..01be98195ef 100644 --- a/pages/management/ManagementHandler.php +++ b/pages/management/ManagementHandler.php @@ -147,25 +147,25 @@ public function context($args, $request) $this->setupTemplate($request); $context = $request->getContext(); $dispatcher = $request->getDispatcher(); - + $apiUrl = $this->getContextApiUrl($request); $publicFileApiUrl = $dispatcher->url($request, PKPApplication::ROUTE_API, $context->getPath(), '_uploadPublicFile'); - + $locales = $this->getSupportedFormLocales($context); - + $contactForm = new PKPContactForm($apiUrl, $locales, $context); $mastheadForm = new MastheadForm($apiUrl, $locales, $context, $publicFileApiUrl); - + $templateMgr->setState([ 'components' => [ PKPContactForm::FORM_CONTACT => $contactForm->getConfig(), MastheadForm::FORM_MASTHEAD => $mastheadForm->getConfig(), ], ]); - + // Interact with the beacon (if enabled) and determine if a new version exists $latestVersion = VersionCheck::checkIfNewVersionExists(); - + // Display a warning message if there is a new version of OJS available if (Config::getVar('general', 'show_upgrade_warning') && $latestVersion) { $currentVersion = VersionCheck::getCurrentDBVersion(); @@ -175,24 +175,22 @@ public function context($args, $request) 'latestVersion' => $latestVersion, ]); - // Get contact information for site administrator - $userGroups = Repo::userGroup()->getByRoleIds([Role::ROLE_ID_SITE_ADMIN], PKPApplication::SITE_CONTEXT_ID); - $adminUserGroup = $userGroups->first(); - - $siteAdmin = Repo::user()->getCollector() - ->filterByUserGroupIds([$adminUserGroup->getId()]) - ->getMany() - ->first(); + $siteAdmins = Repo::user()->getCollector() + ->filterByRoleIds([Role::ROLE_ID_SITE_ADMIN]) + ->getMany(); + + $siteAdmin = $siteAdmins->first(); $templateMgr->assign('siteAdmin', $siteAdmin); } - + $templateMgr->assign('pageTitle', __('manager.setup')); - + $templateMgr->registerClass(PKPContactForm::class, PKPContactForm::class); // FORM_CONTACT $templateMgr->registerClass(PKPMastheadForm::class, PKPMastheadForm::class); // FORM_MASTHEAD - + $templateMgr->display('management/context.tpl'); } + /** * Display website settings diff --git a/pages/submission/PKPSubmissionHandler.php b/pages/submission/PKPSubmissionHandler.php index 3bc2b037e64..4a5afa209aa 100644 --- a/pages/submission/PKPSubmissionHandler.php +++ b/pages/submission/PKPSubmissionHandler.php @@ -43,6 +43,7 @@ use PKP\submission\GenreDAO; use PKP\submissionFile\SubmissionFile; use PKP\user\User; +use PKP\userGroup\UserGroup; abstract class PKPSubmissionHandler extends Handler { @@ -197,10 +198,9 @@ protected function showWizard(array $args, Request $request, Submission $submiss $orderedLocales = $supportedLocales; uksort($orderedLocales, fn ($a, $b) => $b === $submission->getData('locale') ? 1 : -1); - $userGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::query() + ->withContextIds([$context->getId()]) + ->get(); /** @var GenreDAO $genreDao */ $genreDao = DAORegistry::getDAO('GenreDAO'); @@ -219,6 +219,10 @@ protected function showWizard(array $args, Request $request, Submission $submiss $templateMgr = TemplateManager::getManager($request); + if (!$userGroups instanceof LazyCollection) { + $userGroups = $userGroups->lazy(); + } + $templateMgr->setState([ 'categories' => Repo::category()->getBreadcrumbs($categories), 'components' => [ @@ -489,24 +493,23 @@ protected function getContributorsListPanel(Request $request, Submission $submis */ protected function getSubmitUserGroups(Context $context, User $user): LazyCollection { - $userGroups = Repo::userGroup() - ->getCollector() - ->filterByContextIds([$context->getId()]) - ->filterByUserIds([$user->getId()]) - ->filterByRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_AUTHOR]) - ->getMany(); + $userGroups = UserGroup::query() + ->withContextIds([$context->getId()]) + ->withUserIds([$user->getId()]) + ->withRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_AUTHOR]) + ->cursor(); // Users without a submitting role can submit as an // author role that allows self registration - if (!$userGroups->count()) { + if ($userGroups->isEmpty()) { $defaultUserGroup = Repo::userGroup()->getFirstSubmitAsAuthorUserGroup($context->getId()); return LazyCollection::make(function () use ($defaultUserGroup) { if ($defaultUserGroup) { - yield $defaultUserGroup->getId() => $defaultUserGroup; + yield $defaultUserGroup->id => $defaultUserGroup; } }); } - + return $userGroups; } diff --git a/pages/workflow/PKPWorkflowHandler.php b/pages/workflow/PKPWorkflowHandler.php index 59f06dd1fe1..67b6665e1c2 100644 --- a/pages/workflow/PKPWorkflowHandler.php +++ b/pages/workflow/PKPWorkflowHandler.php @@ -56,6 +56,7 @@ use PKP\user\User; use PKP\workflow\WorkflowStageDAO; use PKP\config\Config; +use PKP\userGroup\UserGroup; abstract class PKPWorkflowHandler extends Handler { @@ -165,9 +166,6 @@ public function index($args, $request) $workflowRoles = Application::getWorkflowTypeRoles(); $editorialWorkflowRoles = $workflowRoles[PKPApplication::WORKFLOW_TYPE_EDITORIAL]; - $result = Repo::userGroup()->getCollector() - ->filterByContextIds([$submission->getData('contextId')]) - ->getMany(); $authorUserGroups = Repo::userGroup()->getByRoleIds([Role::ROLE_ID_AUTHOR], $submission->getData('contextId')); $workflowUserGroups = Repo::userGroup()->getByRoleIds($editorialWorkflowRoles, $submission->getData('contextId')); @@ -177,14 +175,14 @@ public function index($args, $request) // they are not assigned in any role and have a manager role in the // context. $currentStageId = $submission->getData('stageId'); - $accessibleWorkflowStages = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_ACCESSIBLE_WORKFLOW_STAGES); $canAccessPublication = false; // View title, metadata, etc. $canEditPublication = Repo::submission()->canEditPublication($submission->getId(), $request->getUser()->getId()); $canAccessProduction = false; // Access to galleys and issue entry $canPublish = false; // Ability to publish, unpublish and create versions $canAccessEditorialHistory = false; // Access to activity log + $accessibleRoles = $this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES) ?? []; // unassigned managers - if (!$accessibleWorkflowStages && array_intersect($this->getAuthorizedContextObject(Application::ASSOC_TYPE_USER_ROLES), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN] ?? [])) { + if (!$accessibleWorkflowStages && array_intersect($accessibleRoles, [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN])) { $canAccessProduction = true; $canPublish = true; $canAccessPublication = true; @@ -194,7 +192,8 @@ public function index($args, $request) $canAccessPublication = true; // Replaces StageAssignmentDAO::getBySubmissionAndUserIdAndStageId - $stageAssignments = StageAssignment::withSubmissionIds([$submission->getId()]) + $stageAssignments = StageAssignment::query() + ->withSubmissionIds([$submission->getId()]) ->withUserId($request->getUser()->getId()) ->withStageIds([WORKFLOW_STAGE_ID_PRODUCTION]) ->get(); @@ -204,14 +203,11 @@ public function index($args, $request) // have been granted access and should be allowed to publish. if ($stageAssignments->isEmpty() && is_array($accessibleWorkflowStages[WORKFLOW_STAGE_ID_PRODUCTION] ?? null)) { $canPublish = (bool) array_intersect([Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER], $accessibleWorkflowStages[WORKFLOW_STAGE_ID_PRODUCTION]); - - // Otherwise, check stage assignments - // "Recommend only" stage assignments can not publish } else { foreach ($stageAssignments as $stageAssignment) { foreach ($workflowUserGroups as $workflowUserGroup) { - if ($stageAssignment->userGroupId == $workflowUserGroup->getId() && - !$stageAssignment->recommendOnly) { + if ($stageAssignment->userGroupId == $workflowUserGroup->usergroupid && + !$stageAssignment->recommendOnly) { $canPublish = true; break; } @@ -580,14 +576,17 @@ public function editorDecisionActions($args, $request) // see if the user is manager, and // if the group is recommendOnly if (!$makeRecommendation && !$makeDecision) { - $userGroups = Repo::userGroup()->userUserGroups($user->getId(), $request->getContext()->getId()); + $userGroups = UserGroup::query() + ->withContextIds([$request->getContext()->getId()]) + ->withUserIds([$user->getId()]) + ->withRoleIds([Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN]) + ->get(); + foreach ($userGroups as $userGroup) { - if (in_array($userGroup->getRoleId(), [Role::ROLE_ID_MANAGER, Role::ROLE_ID_SITE_ADMIN])) { - if (!$userGroup->getRecommendOnly()) { - $makeDecision = true; - } else { - $makeRecommendation = true; - } + if (!$userGroup->recommendOnly) { + $makeDecision = true; + } else { + $makeRecommendation = true; } } } diff --git a/plugins/importexport/native/filter/NativeXmlPKPAuthorFilter.php b/plugins/importexport/native/filter/NativeXmlPKPAuthorFilter.php index 3183fd96a9b..a0fc0db4352 100644 --- a/plugins/importexport/native/filter/NativeXmlPKPAuthorFilter.php +++ b/plugins/importexport/native/filter/NativeXmlPKPAuthorFilter.php @@ -22,6 +22,7 @@ use Exception; use PKP\facades\Locale; use PKP\filter\FilterGroup; +use PKP\userGroup\UserGroup; class NativeXmlPKPAuthorFilter extends NativeImportFilter { @@ -147,14 +148,12 @@ public function handleElement($node) // Identify the user group by name $userGroupName = $node->getAttribute('user_group_ref'); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$context->getId()])->get(); foreach ($userGroups as $userGroup) { if (in_array($userGroupName, $userGroup->getName(null))) { // Found a candidate; stash it. - $author->setUserGroupId($userGroup->getId()); + $author->setUserGroupId($userGroup->id); break; } } diff --git a/plugins/importexport/users/filter/NativeXmlUserGroupFilter.php b/plugins/importexport/users/filter/NativeXmlUserGroupFilter.php index b8934620b9a..8391ada57e9 100644 --- a/plugins/importexport/users/filter/NativeXmlUserGroupFilter.php +++ b/plugins/importexport/users/filter/NativeXmlUserGroupFilter.php @@ -20,6 +20,8 @@ use PKP\filter\FilterGroup; use PKP\userGroup\relationships\UserGroupStage; use PKP\userGroup\UserGroup; +use PKP\userGroup\Repository as UserGroupRepository; +use Illuminate\Support\Facades\App; class NativeXmlUserGroupFilter extends \PKP\plugins\importexport\native\filter\NativeImportFilter { @@ -70,45 +72,58 @@ public function handleElement($node) $context = $deployment->getContext(); // Create the UserGroup object. - $userGroup = Repo::userGroup()->newDataObject(); - $userGroup->setContextId($context->getId()); - + $userGroup = new UserGroup(); + $userGroup->contextId = $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(); - + $userGroups = UserGroup::query() + ->withContextIds($context->getId()) + ->get(); + foreach ($userGroups as $testGroup) { - if (in_array($content[1], $testGroup->getName(null))) { - return $testGroup; // we found one with the same name. + if (in_array($content[1], $testGroup->name)) { + 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); + case 'role_id': + $userGroup->roleId = (int)$n->textContent; break; - case 'is_default': $userGroup->setDefault($n->textContent ?? false); + case 'is_default': + $userGroup->isDefault = filter_var($n->textContent, FILTER_VALIDATE_BOOLEAN); break; - case 'show_title': $userGroup->setShowTitle($n->textContent ?? true); + case 'show_title': + $userGroup->showTitle = filter_var($n->textContent, FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => true]]); break; - case 'name': $userGroup->setName($n->textContent, $n->getAttribute('locale')); + case 'name': + $locale = $n->getAttribute('locale'); + $name = $userGroup->name ?? []; + $name[$locale] = $n->textContent; + $userGroup->name = $name; break; - case 'abbrev': $userGroup->setAbbrev($n->textContent, $n->getAttribute('locale')); + case 'abbrev': + $locale = $n->getAttribute('locale'); + $abbrev = $userGroup->abbrev ?? []; + $abbrev[$locale] = $n->textContent; + $userGroup->abbrev = $abbrev; break; - case 'permit_self_registration': $userGroup->setPermitSelfRegistration($n->textContent ?? false); + case 'permit_self_registration': + $userGroup->permitSelfRegistration = filter_var($n->textContent, FILTER_VALIDATE_BOOLEAN); break; - case 'permit_metadata_edit': $userGroup->setPermitMetadataEdit($n->textContent ?? false); + case 'permit_metadata_edit': + $userGroup->permitMetadataEdit = filter_var($n->textContent, FILTER_VALIDATE_BOOLEAN); break; } } } - - $userGroupId = Repo::userGroup()->add($userGroup); + $userGroup->save(); + $userGroupId = $userGroup->id; $stageNodeList = $node->getElementsByTagNameNS($deployment->getNamespace(), 'stage_assignments'); if ($stageNodeList->length == 1) { diff --git a/plugins/importexport/users/filter/PKPUserUserXmlFilter.php b/plugins/importexport/users/filter/PKPUserUserXmlFilter.php index e4725534866..809d5ee8bfd 100644 --- a/plugins/importexport/users/filter/PKPUserUserXmlFilter.php +++ b/plugins/importexport/users/filter/PKPUserUserXmlFilter.php @@ -23,6 +23,7 @@ use PKP\plugins\importexport\native\filter\NativeExportFilter; use PKP\user\InterestManager; use PKP\user\User; +use PKP\userGroup\UserGroup; class PKPUserUserXmlFilter extends NativeExportFilter { @@ -131,10 +132,9 @@ public function createPKPUserNode($doc, $user) $this->createOptionalNode($doc, $userNode, 'disabled_reason', $user->getDisabledReason()); } - $userGroups = Repo::userGroup()->getCollector() - ->filterByUserIds([$user->getId()]) - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withUserIds([$user->getId()]) + ->withContextIds([$context->getId()]) + ->get(); foreach ($userGroups as $userGroup) { $userNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'user_group_ref', htmlspecialchars($userGroup->getName($context->getPrimaryLocale()), ENT_COMPAT, 'UTF-8'))); @@ -154,9 +154,8 @@ public function addUserGroups($doc, $rootNode) $context = $deployment->getContext(); $userGroupsNode = $doc->createElementNS($deployment->getNamespace(), 'user_groups'); - $userGroups = Repo::userGroup()->getCollector() - ->filterByContextIds([$context->getId()]) - ->getMany(); + $userGroups = UserGroup::withContextIds([$context->getId()]) + ->get(); $filterDao = DAORegistry::getDAO('FilterDAO'); /** @var FilterDAO $filterDao */ $userGroupExportFilters = $filterDao->getObjectsByGroup('usergroup=>user-xml'); diff --git a/plugins/importexport/users/filter/UserGroupNativeXmlFilter.php b/plugins/importexport/users/filter/UserGroupNativeXmlFilter.php index eac5a815f4c..ec35792d241 100644 --- a/plugins/importexport/users/filter/UserGroupNativeXmlFilter.php +++ b/plugins/importexport/users/filter/UserGroupNativeXmlFilter.php @@ -82,17 +82,17 @@ public function createUserGroupNode($doc, $userGroup) $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')); + $userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'role_id', $userGroup->roleId)); + $userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'context_id', $userGroup->contextId)); + $userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'is_default', $userGroup->isDefault ? 'true' : 'false')); + $userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'show_title', $userGroup->showTitle ? 'true' : 'false')); + $userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'permit_selfRegistration', $userGroup->permitSelfRegistration ? 'true' : 'false')); + $userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'permit_metadataEdit', $userGroup->permitMetadataEdit ? 'true' : 'false')); - $this->createLocalizedNodes($doc, $userGroupNode, 'name', $userGroup->getName(null)); - $this->createLocalizedNodes($doc, $userGroupNode, 'abbrev', $userGroup->getAbbrev(null)); + $this->createLocalizedNodes($doc, $userGroupNode, 'name', $userGroup->name); + $this->createLocalizedNodes($doc, $userGroupNode, 'abbrev', $userGroup->abbrev); - $assignedStages = Repo::userGroup()->getAssignedStagesByUserGroupId($context->getId(), $userGroup->getId())->toArray(); + $assignedStages = $userGroup->getAssignedStageIds()->toArray(); $userGroupNode->appendChild($doc->createElementNS($deployment->getNamespace(), 'stage_assignments', htmlspecialchars(join(':', $assignedStages), ENT_COMPAT, 'UTF-8'))); return $userGroupNode; diff --git a/plugins/importexport/users/filter/UserXmlPKPUserFilter.php b/plugins/importexport/users/filter/UserXmlPKPUserFilter.php index 4f1599f0c91..37f17241d4f 100644 --- a/plugins/importexport/users/filter/UserXmlPKPUserFilter.php +++ b/plugins/importexport/users/filter/UserXmlPKPUserFilter.php @@ -28,6 +28,7 @@ use PKP\site\SiteDAO; use PKP\user\InterestManager; use PKP\user\User; +use PKP\userGroup\UserGroup; class UserXmlPKPUserFilter extends \PKP\plugins\importexport\native\filter\NativeImportFilter { @@ -291,9 +292,8 @@ public function parseUser($node) // 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(); + $userGroups = UserGroup::withContextIds([$context->getId()]) + ->get(); // 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 @@ -307,9 +307,10 @@ public function parseUser($node) 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())) { + if (in_array($n->textContent, $userGroup->name) && + !UserGroup::userInGroup($userId, $userGroup->id)) { // Found a candidate; assign user to it. - Repo::userGroup()->assignUserToGroup($userId, $userGroup->getId()); + UserGroup::assignUserToGroup($userId, $userGroup->id); } } } diff --git a/schemas/userGroup.json b/schemas/userGroup.json index ea266ea8553..bc98f009e13 100644 --- a/schemas/userGroup.json +++ b/schemas/userGroup.json @@ -2,59 +2,78 @@ "title": "UserGroup", "description": "A user group assigned to one of the allowed roles.", "properties": { - "abbrev": { - "type": "string", - "description": "The short name of the user group.", - "apiSummary": true, - "multilingual": true - }, - "masthead": { - "type": "boolean" - }, - "name": { - "type": "string", - "description": "The name of the user group.", - "apiSummary": true, - "multilingual": true - }, "id": { - "type": "integer", - "apiSummary": true - }, - "contextId": { - "type": "integer", - "apiSummary": true - }, - "permitSelfRegistration": { - "type": "boolean" - }, - "permitMetadataEdit": { - "type": "boolean" - }, - "recommendOnly": { - "type": "boolean" - }, - "isDefault": { - "type": "boolean" - }, - "roleId": { - "type": "integer", - "validation": [ - "in:16,1,17,65536,4096,4097,1048576,209715" - ] - }, - "showTitle": { - "type": "boolean" - }, - "nameLocaleKey": { - "type": "string", - "description": "A field that contains the the locale key of the group name", - "apiSummary": true - }, - "abbrevLocaleKey": { - "type": "string", - "description": "A field that contains the the locale key of the group abbreviaton", - "apiSummary": true - } - } + "type": "integer", + "origin": "primary", + "apiSummary": true + }, + "contextId": { + "type": "integer", + "origin": "primary", + "apiSummary": true + }, + "roleId": { + "type": "integer", + "origin": "primary", + "validation": [ + "in:16,1,17,65536,4096,4097,1048576,209715" + ] + }, + "isDefault": { + "type": "boolean", + "origin": "primary" + }, + "showTitle": { + "type": "boolean", + "origin": "primary" + }, + "permitSelfRegistration": { + "type": "boolean", + "origin": "primary" + }, + "permitMetadataEdit": { + "type": "boolean", + "origin": "primary" + }, + "masthead": { + "type": "boolean", + "origin": "primary" + }, + "name": { + "type": "string", + "origin": "setting", + "description": "The name of the user group.", + "apiSummary": true, + "multilingual": true + }, + "namePlural": { + "type": "string", + "origin": "setting", + "description": "The plural name of the user group.", + "multilingual": true + }, + "abbrev": { + "type": "string", + "origin": "setting", + "description": "The short name of the user group.", + "apiSummary": true, + "multilingual": true + }, + "nameLocaleKey": { + "type": "string", + "origin": "setting", + "description": "The locale key of the group name.", + "apiSummary": true + }, + "abbrevLocaleKey": { + "type": "string", + "origin": "setting", + "description": "The locale key of the group abbreviation.", + "apiSummary": true + }, + "recommendOnly": { + "type": "boolean", + "origin": "setting" + } + } } diff --git a/templates/controllers/grid/users/author/form/authorForm.tpl b/templates/controllers/grid/users/author/form/authorForm.tpl index b8a7fa861d1..4463e79f704 100644 --- a/templates/controllers/grid/users/author/form/authorForm.tpl +++ b/templates/controllers/grid/users/author/form/authorForm.tpl @@ -46,8 +46,8 @@ {/if} {fbvFormSection id="userGroupId" title="submission.submit.contributorRole" list=true required=true} {foreach from=$authorUserGroups item=$userGroup} - {if $userGroupId == $userGroup->getId()}{assign var="checked" value=true}{else}{assign var="checked" value=false}{/if} - {fbvElement type="radio" id="userGroup"|concat:$userGroup->getId() name="userGroupId" value=$userGroup->getId() checked=$checked label=$userGroup->getLocalizedName() translate=false} + {if $userGroupId == $userGroup->id}{assign var="checked" value=true}{else}{assign var="checked" value=false}{/if} + {fbvElement type="radio" id="userGroup"|concat:$userGroup->id name="userGroupId" value=$userGroup->id checked=$checked label=$userGroup->getLocalizedData('name') translate=false} {/foreach} {/fbvFormSection} {fbvFormSection list="true"} diff --git a/templates/frontend/components/registrationFormContexts.tpl b/templates/frontend/components/registrationFormContexts.tpl index cae256180e1..69b1bfeaf2d 100644 --- a/templates/frontend/components/registrationFormContexts.tpl +++ b/templates/frontend/components/registrationFormContexts.tpl @@ -41,11 +41,11 @@ {translate key="user.register.otherContextRoles"} {foreach from=$readerUserGroups[$contextId] item=userGroup} - {if $userGroup->getPermitSelfRegistration()} - {assign var="userGroupId" value=$userGroup->getId()} + {if $userGroup->permitSelfRegistration} + {assign var="userGroupId" value=$userGroup->id} {if in_array($userGroupId, $userGroupIds)} {assign var=isSelected value=true} @@ -53,11 +53,11 @@ {/if} {/foreach} {foreach from=$reviewerUserGroups[$contextId] item=userGroup} - {if $userGroup->getPermitSelfRegistration()} - {assign var="userGroupId" value=$userGroup->getId()} + {if $userGroup->permitSelfRegistration} + {assign var="userGroupId" value=$userGroup->id} {if in_array($userGroupId, $userGroupIds)} {assign var=isSelected value=true} diff --git a/templates/frontend/pages/userRegister.tpl b/templates/frontend/pages/userRegister.tpl index 96525771865..973bf85c637 100644 --- a/templates/frontend/pages/userRegister.tpl +++ b/templates/frontend/pages/userRegister.tpl @@ -70,7 +70,7 @@ {assign var=contextId value=$currentContext->getId()} {assign var=userCanRegisterReviewer value=0} {foreach from=$reviewerUserGroups[$contextId] item=userGroup} - {if $userGroup->getPermitSelfRegistration()} + {if $userGroup->permitSelfRegistration} {assign var=userCanRegisterReviewer value=$userCanRegisterReviewer+1} {/if} {/foreach} @@ -87,11 +87,11 @@
{foreach from=$reviewerUserGroups[$contextId] item=userGroup} - {if $userGroup->getPermitSelfRegistration()} + {if $userGroup->permitSelfRegistration} {/if} {/foreach} diff --git a/templates/user/userGroupSelfRegistration.tpl b/templates/user/userGroupSelfRegistration.tpl index 883533f4eeb..d1f480f17c0 100644 --- a/templates/user/userGroupSelfRegistration.tpl +++ b/templates/user/userGroupSelfRegistration.tpl @@ -11,35 +11,35 @@ *} {assign var=contextId value=$context->getId()} {foreach from=$readerUserGroups[$contextId] item=userGroup} - {assign var="userGroupId" value=$userGroup->getId()} - {if in_array($userGroup->getId(), $userGroupIds)} + {assign var="userGroupId" value=$userGroup->id} + {if in_array($userGroup->id, $userGroupIds)} {assign var="checked" value=true} {else} {assign var="checked" value=false} {/if} - {if $userGroup->getPermitSelfRegistration()} - {fbvElement type="checkbox" id="readerGroup-$userGroupId" name="readerGroup[$userGroupId]" checked=$checked label=$userGroup->getLocalizedName()|escape translate=false} + {if $userGroup->permitSelfRegistration} + {fbvElement type="checkbox" id="readerGroup-$userGroupId" name="readerGroup[$userGroupId]" checked=$checked label=$userGroup->getLocalizedData('name')|escape translate=false} {/if} {/foreach} {foreach from=$authorUserGroups[$contextId] item=userGroup} - {assign var="userGroupId" value=$userGroup->getId()} - {if in_array($userGroup->getId(), $userGroupIds)} + {assign var="userGroupId" value=$userGroup->id} + {if in_array($userGroup->id, $userGroupIds)} {assign var="checked" value=true} {else} {assign var="checked" value=false} {/if} - {if $userGroup->getPermitSelfRegistration()} - {fbvElement type="checkbox" id="authorGroup-$userGroupId" name="authorGroup[$userGroupId]" checked=$checked label=$userGroup->getLocalizedName()|escape translate=false} + {if $userGroup->permitSelfRegistration} + {fbvElement type="checkbox" id="authorGroup-$userGroupId" name="authorGroup[$userGroupId]" checked=$checked label=$userGroup->getLocalizedData('name')|escape translate=false} {/if} {/foreach} {foreach from=$reviewerUserGroups[$contextId] item=userGroup} - {assign var="userGroupId" value=$userGroup->getId()} - {if in_array($userGroup->getId(), $userGroupIds)} + {assign var="userGroupId" value=$userGroup->id} + {if in_array($userGroup->id, $userGroupIds)} {assign var="checked" value=true} {else} {assign var="checked" value=false} {/if} - {if $userGroup->getPermitSelfRegistration()} - {fbvElement type="checkbox" id="reviewerGroup-$userGroupId" name="reviewerGroup[$userGroupId]" checked=$checked label=$userGroup->getLocalizedName()|escape translate=false} + {if $userGroup->permitSelfRegistration} + {fbvElement type="checkbox" id="reviewerGroup-$userGroupId" name="reviewerGroup[$userGroupId]" checked=$checked label=$userGroup->getLocalizedData('name')|escape translate=false} {/if} {/foreach} diff --git a/tests/classes/security/authorization/PolicyTestCase.php b/tests/classes/security/authorization/PolicyTestCase.php index 25652d2a891..2c41129a609 100644 --- a/tests/classes/security/authorization/PolicyTestCase.php +++ b/tests/classes/security/authorization/PolicyTestCase.php @@ -32,6 +32,8 @@ use PKP\security\Role; use PKP\tests\PKPTestCase; use PKP\user\User; +use PKP\userGroup\UserGroup; + abstract class PolicyTestCase extends PKPTestCase { @@ -103,8 +105,8 @@ public function mockEffect() // Add a user group to the authorized context // of the authorization context manipulation policy. $policy = $this->getAuthorizationContextManipulationPolicy(); - $userGroup = Repo::userGroup()->newDataObject(); - $userGroup->setRoleId(self::ROLE_ID_TEST); + $userGroup = new UserGroup(); + $userGroup->roleId = self::ROLE_ID_TEST; $policy->addAuthorizedContextObject(Application::ASSOC_TYPE_USER_GROUP, $userGroup); // Add user roles array to the authorized context.