From f42a6ba99b2e911e3bc2fc1348c3ad946daf7f11 Mon Sep 17 00:00:00 2001 From: Taslan Graham <gtaslan1@gmail.com> Date: Fri, 8 Nov 2024 20:57:59 -0500 Subject: [PATCH] pkp/pkp-lib#10571 Convert queries to eloquent --- .../PKPEmailTemplateController.php | 6 +- .../EmailTemplateAccessGroup.php | 34 ++++ classes/emailTemplate/Repository.php | 150 ++++++++++++------ classes/emailTemplate/maps/Schema.php | 11 +- .../v3_5_0/I10403_EmailTemplateRoleAccess.php | 8 +- 5 files changed, 152 insertions(+), 57 deletions(-) create mode 100644 classes/emailTemplate/EmailTemplateAccessGroup.php diff --git a/api/v1/emailTemplates/PKPEmailTemplateController.php b/api/v1/emailTemplates/PKPEmailTemplateController.php index 82c99283fb7..c8104bd5a9e 100644 --- a/api/v1/emailTemplates/PKPEmailTemplateController.php +++ b/api/v1/emailTemplates/PKPEmailTemplateController.php @@ -205,9 +205,8 @@ public function add(Request $illuminateRequest): JsonResponse $emailTemplate = Repo::emailTemplate()->newDataObject($params); Repo::emailTemplate()->add($emailTemplate); - if($params['userGroupIds']) { - Repo::emailTemplate()->updateTemplateAccessGroups($emailTemplate, $params['userGroupIds'], $requestContext->getId()); - } + Repo::emailTemplate()->setEmailTemplateAccess($emailTemplate, $requestContext->getId(), $params['userGroupIds'], $params['isUnrestricted']); + $emailTemplate = Repo::emailTemplate()->getByKey($emailTemplate->getData('contextId'), $emailTemplate->getData('key')); return response()->json(Repo::emailTemplate()->getSchemaMap()->map($emailTemplate), Response::HTTP_OK); @@ -261,6 +260,7 @@ public function edit(Request $illuminateRequest): JsonResponse } Repo::emailTemplate()->edit($emailTemplate, $params, $requestContext->getId()); + Repo::emailTemplate()->setEmailTemplateAccess($emailTemplate, $requestContext->getId(), $params['userGroupIds'], $params['isUnrestricted']); $emailTemplate = Repo::emailTemplate()->getByKey( // context ID is null if edited for the first time diff --git a/classes/emailTemplate/EmailTemplateAccessGroup.php b/classes/emailTemplate/EmailTemplateAccessGroup.php new file mode 100644 index 00000000000..bb139a53f9a --- /dev/null +++ b/classes/emailTemplate/EmailTemplateAccessGroup.php @@ -0,0 +1,34 @@ +<?php + +namespace PKP\emailTemplate; + +use Eloquence\Behaviours\HasCamelCasing; +use Eloquence\Database\Model; +use Illuminate\Contracts\Database\Eloquent\Builder; + +class EmailTemplateAccessGroup extends Model +{ + use HasCamelCasing; + public $timestamps = false; + protected $primaryKey = 'email_template_user_group_access_id'; + protected $table = 'email_template_user_group_access'; + protected $fillable = ['userGroupId', 'contextId','emailKey']; + + + public function scopeWithEmailKey(Builder $query, ?array $keys): Builder + { + return $query->when(!empty($keys), function ($query) use ($keys) { + return $query->whereIn('email_key', $keys); + }); + } + + public function scopeWithContextId(Builder $query, int $contextId): Builder + { + return $query->where('context_id', $contextId); + } + + public function scopeWithGroupIds(Builder $query, array $ids): Builder + { + return $query->whereIn('user_group_id', $ids); + } +} diff --git a/classes/emailTemplate/Repository.php b/classes/emailTemplate/Repository.php index ba99b90106a..910a618aebc 100644 --- a/classes/emailTemplate/Repository.php +++ b/classes/emailTemplate/Repository.php @@ -15,8 +15,8 @@ use APP\emailTemplate\DAO; use APP\facades\Repo; +use Illuminate\Support\Collection; use Illuminate\Support\Enumerable; -use Illuminate\Support\Facades\DB; use PKP\context\Context; use PKP\core\PKPRequest; use PKP\plugins\Hook; @@ -173,13 +173,9 @@ public function add(EmailTemplate $emailTemplate): string /** @copydoc DAO::update() */ public function edit(EmailTemplate $emailTemplate, array $params, $contextId) { - $userGroupIds = $params['userGroupIds']; - unset($params['userGroupIds']); $newEmailTemplate = clone $emailTemplate; $newEmailTemplate->setAllData(array_merge($newEmailTemplate->_data, $params)); - - Hook::call('EmailTemplate::edit', [$newEmailTemplate, $emailTemplate, $params]); if ($newEmailTemplate->getId()) { @@ -187,8 +183,6 @@ public function edit(EmailTemplate $emailTemplate, array $params, $contextId) } else { $this->dao->insert($newEmailTemplate); } - - $this->updateTemplateAccessGroups($emailTemplate, $userGroupIds, $contextId); } /** @copydoc DAO::delete() */ @@ -234,17 +228,34 @@ public function restoreDefaults($contextId): array } - public function getGroupsAssignedToTemplate(string $key, int $contextId): array + /*** + * Gets the IDs of the user groups assigned to an email template + */ + public function getUserGroupsIdsAssignedToTemplate(string $templateKey, int $contextId): array + { + return EmailTemplateAccessGroup::withEmailKey([$templateKey]) + ->withContextId($contextId) + ->whereNot('user_group_id', null) + ->get() + ->pluck('userGroupId') + ->all(); + } + + /*** + * Checks if an Email Template is unrestricted + */ + public function isTemplateUnrestricted(string $templateKey, int $contextId): bool { - // FIXME - can this be replaced with eloquent? - return DB::table('email_template_role_access') - ->where('email_key', $key) - ->where('context_id', $contextId) - ->pluck('user_group_id') - ->toArray(); + return !!EmailTemplateAccessGroup::withEmailKey([$templateKey]) + ->withContextId($contextId) + ->where('user_group_id', null) + ->first(); } + /** + * Checks if an email template is accessible to a user. A template is accessible if it is assigned to a user group that the user belongs to or if the template is unrestricted + */ public function isTemplateAccessibleToUser(User $user, EmailTemplate $template, int $contextId): bool { if ($user->hasRole([Role::ROLE_ID_SITE_ADMIN, Role::ROLE_ID_MANAGER,], $contextId)) { @@ -252,17 +263,21 @@ public function isTemplateAccessibleToUser(User $user, EmailTemplate $template, } $userUserGroups = Repo::userGroup()->userUserGroups($user->getId(), $contextId)->all(); - $templateUserGroups = $this->getGroupsAssignedToTemplate($template->getData('key'), $contextId); - $userHasAccess = false; + $templateUserGroups = $this->getUserGroupsIdsAssignedToTemplate($template->getData('key'), $contextId); + + // Null entry indicates that template is unrestricted + if(in_array(null, $templateUserGroups)) { + return true; + } + foreach ($userUserGroups as $userGroup) { - if (in_array($userGroup->getId(), $templateUserGroups) || $template->getData('isUnrestricted')) { - $userHasAccess = true; - break; + if (in_array($userGroup->getId(), $templateUserGroups)) { + return true; } } - return $userHasAccess; + return false; } /** @@ -271,9 +286,9 @@ public function isTemplateAccessibleToUser(User $user, EmailTemplate $template, * @param Enumerable $templates List of EmailTemplate objects to filter. * @param User $user The user whose access level is used for filtering. * - * @return \Illuminate\Support\Collection Filtered list of EmailTemplate objects accessible to the user. + * @return Collection Filtered list of EmailTemplate objects accessible to the user. */ - public function filterTemplatesByUserAccess(Enumerable $templates, User $user, int $contextId): \Illuminate\Support\Collection + public function filterTemplatesByUserAccess(Enumerable $templates, User $user, int $contextId): Collection { $filteredTemplates = collect(); @@ -286,36 +301,79 @@ public function filterTemplatesByUserAccess(Enumerable $templates, User $user, i return $filteredTemplates; } - /** - * Pass empty array to delete all existing user groups for a template + /*** + * Internal method used to assign user group IDs to an email template */ - public function updateTemplateAccessGroups(EmailTemplate $emailTemplate, array $newUserGroupIds, int $contextId): void + private function _updateTemplateAccessGroups(EmailTemplate $emailTemplate, array $newUserGroupIds, int $contextId): void { - $isUnrestricted = in_array(null, $newUserGroupIds); - // remove any null values - // Delete old entries for user groups IDs not found in new list of user group IDs - DB::table('email_template_role_access') - ->where('email_key', $emailTemplate->getData('key')) - ->where('context_id', $contextId) - ->whereNotIn('user_group_id', $newUserGroupIds) - ->delete(); + EmailTemplateAccessGroup::withEmailKey([$emailTemplate->getData('key')]) + ->withContextId($contextId) + ->whereNotIn('user_group_id', $newUserGroupIds)->delete(); foreach ($newUserGroupIds as $id) { - DB::table('email_template_role_access') - ->updateOrInsert( - [ // The where conditions (keys that should match) - 'email_key' => $emailTemplate->getData('key'), - 'user_group_id' => $id, - 'context_id' => $contextId - ], - [ // The data to insert or update (values to set) - 'email_key' => $emailTemplate->getData('key'), - 'user_group_id' => $id, - 'context_id' => $contextId, - ] - ); + EmailTemplateAccessGroup::updateOrCreate( + [ + // The where conditions (keys that should match) + 'email_key' => $emailTemplate->getData('key'), + 'user_group_id' => $id, + 'context_id' => $contextId, + ], + [ + // The data to insert or update (values to set) + 'emailKey' => $emailTemplate->getData('key'), + 'userGroupId' => $id, + 'contextId' => $contextId, + ] + ); } + } + /** + * Pass empty array in $userGroupIds to delete all existing user groups for a template + */ + public function setEmailTemplateAccess(EmailTemplate $emailTemplate, int $contextId, ?array $userGroupIds, ?bool $isUnrestricted): void + { + if($userGroupIds !== null) { + $this->_updateTemplateAccessGroups($emailTemplate, $userGroupIds, $contextId); + } + + if($isUnrestricted !== null) { + $this->markTemplateAsUnrestricted($emailTemplate, $isUnrestricted, $contextId); + } + } + + + /** + * Mark an email template as unrestricted or not. + * An unrestricted email template is available to all user groups associated with the Roles linked to the mailable that the template belongs to. + * Mailable roles are stored in the $fromRoleIds property of a mailable + */ + private function markTemplateAsUnrestricted(EmailTemplate $emailTemplate, bool $isUnrestricted, int $contextId): void + { + // Unrestricted emails are represented by an entry with a `null` value for the user group ID + if ($isUnrestricted) { + EmailTemplateAccessGroup::updateOrCreate( + [ + // The where conditions (keys that should match) + 'email_key' => $emailTemplate->getData('key'), + 'user_group_id' => null, + 'context_id' => $contextId, + ], + [ + // The data to insert or update (values to set) + 'emailKey' => $emailTemplate->getData('key'), + 'userGroupId' => null, + 'contextId' => $contextId, + ] + ); + + } else { + // Remove entry with a `null` value for the user group ID to reflect that it is no longer unrestricted + EmailTemplateAccessGroup::withEmailKey([$emailTemplate->getData('key')]) + ->withContextId($contextId) + ->withGroupIds([null]) + ->delete(); + } } } diff --git a/classes/emailTemplate/maps/Schema.php b/classes/emailTemplate/maps/Schema.php index a402ee64afd..0df97f706ae 100644 --- a/classes/emailTemplate/maps/Schema.php +++ b/classes/emailTemplate/maps/Schema.php @@ -84,16 +84,16 @@ protected function mapByProperties(array $props, EmailTemplate $item, string $ma $output = []; $mailableClass = $mailableClass ?? Repo::mailable()->getMailableByEmailTemplate($item); - + $assignedUserGroupsIds = []; // some mailable are not found during some operations such as performing a search for templates. So ensure mailable exist before using - if($mailableClass) { + if ($mailableClass) { $isUserGroupsAssignable = Repo::mailable()->isGroupsAssignableToTemplates($mailableClass); - + $assignedUserGroupsIds = Repo::emailTemplate()->getUserGroupsIdsAssignedToTemplate($item->getData('key'), Application::get()->getRequest()->getContext()->getId()); if (!$isUserGroupsAssignable) { $output['assignedUserGroupIds'] = []; } else { // Get the current user groups assigned to the template - $output['assignedUserGroupIds'] = Repo::emailTemplate()->getGroupsAssignedToTemplate($item->getData('key'), Application::get()->getRequest()->getContext()->getId()); + $output['assignedUserGroupIds'] = $assignedUserGroupsIds; } } @@ -108,6 +108,9 @@ protected function mapByProperties(array $props, EmailTemplate $item, string $ma 'emailTemplates/' . $item->getData('key') ); break; + case 'isUnrestricted': + $output['isUnrestricted'] = Repo::emailTemplate()->isTemplateUnrestricted($item->getData('key'), Application::get()->getRequest()->getContext()->getId()); + break; default: $output[$prop] = $item->getData($prop); break; diff --git a/classes/migration/upgrade/v3_5_0/I10403_EmailTemplateRoleAccess.php b/classes/migration/upgrade/v3_5_0/I10403_EmailTemplateRoleAccess.php index 8662b51de88..cbfc1186e74 100644 --- a/classes/migration/upgrade/v3_5_0/I10403_EmailTemplateRoleAccess.php +++ b/classes/migration/upgrade/v3_5_0/I10403_EmailTemplateRoleAccess.php @@ -14,11 +14,11 @@ class I10403_EmailTemplateRoleAccess extends Migration public function up(): void { - Schema::create('email_template_role_access', function (Blueprint $table) { - $table->bigInteger('email_template_role_access_id')->autoIncrement(); + Schema::create('email_template_user_group_access', function (Blueprint $table) { + $table->bigInteger('email_template_user_group_access_id')->autoIncrement(); $table->string('email_key', 255); $table->bigInteger('context_id'); - $table->bigInteger('user_group_id'); + $table->bigInteger('user_group_id')->nullable(); $table->foreign('context_id')->references('journal_id')->on('journals')->onDelete('cascade'); $table->foreign('user_group_id')->references('user_group_id')->on('user_groups')->onDelete('cascade'); @@ -30,6 +30,6 @@ public function up(): void */ public function down(): void { - Schema::drop('email_template_role_access'); + Schema::drop('email_template_user_group_access'); } }