Skip to content

Commit

Permalink
pkp#10571 WIP: add support for unrestricted templates
Browse files Browse the repository at this point in the history
  • Loading branch information
taslangraham committed Nov 8, 2024
1 parent bf7381b commit 0ce0834
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 53 deletions.
11 changes: 10 additions & 1 deletion api/v1/emailTemplates/PKPEmailTemplateController.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public function getMany(Request $illuminateRequest): JsonResponse

Hook::call('API::emailTemplates::params', [$collector, $illuminateRequest]);

$emailTemplates = Repo::emailTemplate()->filterTemplatesByUserAccess($collector->getMany()->all(), $request->getUser(), $request->getContext()->getId());
$emailTemplates = collect(Repo::emailTemplate()->filterTemplatesByUserAccess($collector->getMany(), $request->getUser(), $request->getContext()->getId()));

return response()->json([
'itemsMax' => $collector->getCount(),
Expand Down Expand Up @@ -201,6 +201,10 @@ 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());
}
$emailTemplate = Repo::emailTemplate()->getByKey($emailTemplate->getData('contextId'), $emailTemplate->getData('key'));

return response()->json(Repo::emailTemplate()->getSchemaMap()->map($emailTemplate), Response::HTTP_OK);
Expand Down Expand Up @@ -238,6 +242,11 @@ public function edit(Request $illuminateRequest): JsonResponse
$params['contextId'] = $requestContext->getId();
}


// If the user submitted an empty list (meaning all user groups were unchecked), the empty array is converted to null in the request's data.
// Convert back to an empty array.
$params['userGroupIds'] = $params['userGroupIds'] ?: [];

$errors = Repo::emailTemplate()->validate(
$emailTemplate,
$params,
Expand Down
33 changes: 33 additions & 0 deletions classes/components/forms/FieldEmailTemplateUnrestricted.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

/**
* @file classes/components/form/FieldEmailTemplateUnrestricted.php
*
* Copyright (c) 2014-2024 Simon Fraser University
* Copyright (c) 2000-2024 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class FieldEmailTemplateUnrestricted
*
* @ingroup classes_controllers_form
*
* @brief A component to indicate if an email template is unrestricted, i.e accessible to all user groups within the associated mailable
*/

namespace PKP\components\forms;

class FieldEmailTemplateUnrestricted extends Field
{
/** @copydoc Field::$component */
public $component = 'field-email-template-unrestricted';

/**
* @copydoc Field::getConfig()
*/
public function getConfig()
{
$config = parent::getConfig();
$config['label'] = $this->label;
return $config;
}
}
12 changes: 10 additions & 2 deletions classes/components/forms/emailTemplate/EmailTemplateForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

namespace PKP\components\forms\emailTemplate;

use PKP\components\forms\FieldEmailTemplateUnrestricted;
use PKP\components\forms\FieldEmailTemplateUserGroupSettings;
use PKP\components\forms\FieldPreparedContent;
use PKP\components\forms\FieldText;
Expand Down Expand Up @@ -47,9 +48,16 @@ public function __construct(string $action, array $locales)
'isMultilingual' => true,
'toolbar' => 'bold italic superscript subscript | link | blockquote bullist numlist',
'plugins' => 'paste,link,lists',
]))->addField(new FieldEmailTemplateUserGroupSettings('userGroupIds', [
]))->addField(
new FieldEmailTemplateUnrestricted('isUnrestricted'),
[
'type' => 'checkbox'
]
)
->addField(new FieldEmailTemplateUserGroupSettings('userGroupIds', [
'type' => 'checkbox',
'label' => __('workflow.userGroup.allowed'),
'type' => 'checkbox'

]));
}
}
7 changes: 7 additions & 0 deletions classes/decision/steps/Email.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use PKP\facades\Locale;
use PKP\mail\Mailable;
use PKP\user\User;
use Role;
use stdClass;

class Email extends Step
Expand Down Expand Up @@ -124,6 +125,12 @@ protected function getEmailTemplates(): array
{
$request = Application::get()->getRequest();
$context = $request->getContext();
$userRoles = array_map(fn (Role $role) => $role->getId(), $request->getUser()->getRoles($context->getId()));

// Ensure user has access to mailable before proceeding
if(empty(array_intersect($this->mailable::getFromRoleIds(), $userRoles))) {
return [];
}

$emailTemplates = collect();
if ($this->mailable::getEmailTemplateKey()) {
Expand Down
26 changes: 0 additions & 26 deletions classes/emailTemplate/DAO.php
Original file line number Diff line number Diff line change
Expand Up @@ -450,31 +450,5 @@ protected function getUniqueKey(EmailTemplate $emailTemplate): string
}


public function updateTemplateAccessGroups(EmailTemplate $emailTemplate, array $newUserGroupIds, $contextId)
{

// 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();

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,
]
);
}

}
}
56 changes: 48 additions & 8 deletions classes/emailTemplate/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use APP\emailTemplate\DAO;
use APP\facades\Repo;
use Illuminate\Support\Enumerable;
use Illuminate\Support\Facades\DB;
use PKP\context\Context;
use PKP\core\PKPRequest;
Expand Down Expand Up @@ -172,11 +173,12 @@ 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));

$userGroupIds = $params['userGroupIds'];
unset($params['userGroupIds']);


Hook::call('EmailTemplate::edit', [$newEmailTemplate, $emailTemplate, $params]);

Expand All @@ -186,9 +188,7 @@ public function edit(EmailTemplate $emailTemplate, array $params, $contextId)
$this->dao->insert($newEmailTemplate);
}

if($userGroupIds) {
$this->dao->updateTemplateAccessGroups($emailTemplate, $userGroupIds, $contextId);
}
$this->updateTemplateAccessGroups($emailTemplate, $userGroupIds, $contextId);
}

/** @copydoc DAO::delete() */
Expand Down Expand Up @@ -268,14 +268,54 @@ public function isTemplateAccessibleToUser(User $user, EmailTemplate $template,
/**
* Filters a list of EmailTemplates to return only those accessible by a specified user.
*
* @param array $templates List of EmailTemplate objects to filter.
* @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.
*/
public function filterTemplatesByUserAccess(array $templates, User $user, int $contextId): \Illuminate\Support\Collection
public function filterTemplatesByUserAccess(Enumerable $templates, User $user, int $contextId): \Illuminate\Support\Collection
{
$filteredTemplates = collect();

foreach ($templates as $template) {
if($this->isTemplateAccessibleToUser($user, $template, $contextId)) {
$filteredTemplates->add($template);
}
}

return $filteredTemplates;
}

/**
* Pass empty array to delete all existing user groups for a template
*/
public function updateTemplateAccessGroups(EmailTemplate $emailTemplate, array $newUserGroupIds, int $contextId): void
{
return collect(array_filter($templates, fn (EmailTemplate $template) => $this->isTemplateAccessibleToUser($user, $template, $contextId)));
$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();

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,
]
);
}

}

}
10 changes: 2 additions & 8 deletions classes/emailTemplate/maps/Schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public function summarizeMany(Enumerable $collection, string $mailableClass = nu
}

/**
* Map schema properties of an Announcement to an assoc array
* Map schema properties of an Email Template to an assoc array
*/
protected function mapByProperties(array $props, EmailTemplate $item, string $mailableClass = null): array
{
Expand All @@ -92,13 +92,7 @@ protected function mapByProperties(array $props, EmailTemplate $item, string $ma
if (!$isUserGroupsAssignable) {
$output['assignedUserGroupIds'] = [];
} else {
// get roles for mailable
$roles = $mailableClass::getFromRoleIds();
// Get the groups for each role
$userGroups = [];
$roleNames = Application::get()->getRoleNames();

// Get the current user groups assigned to the template
// Get the current user groups assigned to the template
$output['assignedUserGroupIds'] = Repo::emailTemplate()->getGroupsAssignedToTemplate($item->getData('key'), Application::get()->getRequest()->getContext()->getId());
}
}
Expand Down
9 changes: 2 additions & 7 deletions classes/mail/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ public function describeMailable(string $class, int $contextId): array
$user = $request->getUser();

// Limit templates to only those accessible to the user's user group(s)
$userAccessibleTemplates = Repo::emailTemplate()->filterTemplatesByUserAccess(array_merge([$defaultTemplate], $templates->values()->toArray()), $user, $contextId);
$userAccessibleTemplates = Repo::emailTemplate()->filterTemplatesByUserAccess(collect(array_merge([$defaultTemplate], $templates->values()->toArray())), $user, $contextId);
$data['emailTemplates'] = Repo::emailTemplate()
->getSchemaMap()
->summarizeMany($userAccessibleTemplates, $class)
Expand Down Expand Up @@ -229,12 +229,7 @@ protected function isMailableConfigurable(string $class, Context $context): bool
*/
public function isGroupsAssignableToTemplates(Mailable|string $mailable): bool
{
return !empty(array_intersect($mailable::getGroupIds(), [
Mailable::GROUP_SUBMISSION,
Mailable::GROUP_REVIEW,
Mailable::GROUP_COPYEDITING,
Mailable::GROUP_PRODUCTION,
]));
return !in_array(Mailable::FROM_SYSTEM, $mailable::getFromRoleIds());
}

/**
Expand Down
2 changes: 1 addition & 1 deletion controllers/grid/queries/form/QueryForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ public function fetch($request, $template = null, $display = false, $actionArgs
->alternateTo([$mailable::getEmailTemplateKey()])
->getMany();

foreach (Repo::emailTemplate()->filterTemplatesByUserAccess($alternateTemplates->all(), $user, $context->getId()) as $alternateTemplate) {
foreach (Repo::emailTemplate()->filterTemplatesByUserAccess($alternateTemplates, $user, $context->getId()) as $alternateTemplate) {
$templateKeySubjectPairs[$alternateTemplate->getData('key')] = Mail::compileParams(
$alternateTemplate->getLocalizedData('name'),
$data
Expand Down
5 changes: 5 additions & 0 deletions schemas/emailTemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@
"validation": [
"nullable"
]
},
"isUnrestricted": {
"type": "boolean",
"description": "Boolean indicating if an email template is available to all user groups within the roles associated with the template's mailable",
"apiSummary": true
}
}
}

0 comments on commit 0ce0834

Please sign in to comment.