-
Notifications
You must be signed in to change notification settings - Fork 447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pkp/pkp-lib#10571 limit email template access by user groups #10581
base: main
Are you sure you want to change the base?
Changes from all commits
6766343
cd7630b
7276d1b
fb0751c
56c5cd7
14ff29b
39887f3
5908e11
21d8445
dfdbeb9
ef75acd
b9bc696
10066f6
03a39d4
405e376
098ea45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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. | ||
*/ | ||
|
||
namespace PKP\components\forms; | ||
|
||
class FieldEmailTemplateUnrestricted extends Field | ||
{ | ||
/** @copydoc Field::$component */ | ||
public $component = 'field-email-template-unrestricted'; | ||
public string $subNote = ''; | ||
/** | ||
* @copydoc Field::getConfig() | ||
*/ | ||
public function getConfig() | ||
{ | ||
$config = parent::getConfig(); | ||
$config['subNote'] = $this->subNote; | ||
return $config; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
<?php | ||
|
||
/** | ||
* @file classes/components/form/FieldEmailTemplateUserGroupSettings.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 FieldEmailTemplateUserGroupSettings | ||
* | ||
* @ingroup classes_controllers_form | ||
* | ||
* @brief A component managing user groups assigned to an email template | ||
*/ | ||
|
||
namespace PKP\components\forms; | ||
|
||
class FieldEmailTemplateUserGroupSettings extends Field | ||
{ | ||
/** @copydoc Field::$component */ | ||
public $component = 'field-email-template-user-group-settings'; | ||
|
||
/** | ||
* @copydoc Field::getConfig() | ||
*/ | ||
public function getConfig() | ||
{ | ||
$config = parent::getConfig(); | ||
|
||
return $config; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -128,14 +128,18 @@ protected function getEmailTemplates(): array | |
$emailTemplates = collect(); | ||
if ($this->mailable::getEmailTemplateKey()) { | ||
$emailTemplate = Repo::emailTemplate()->getByKey($context->getId(), $this->mailable::getEmailTemplateKey()); | ||
if ($emailTemplate) { | ||
if ($emailTemplate && Repo::emailTemplate()->isTemplateAccessibleToUser($request->getUser(), $emailTemplate, $context->getId())) { | ||
$emailTemplates->add($emailTemplate); | ||
} | ||
Repo::emailTemplate() | ||
->getCollector($context->getId()) | ||
->alternateTo([$this->mailable::getEmailTemplateKey()]) | ||
->getMany() | ||
->each(fn (EmailTemplate $e) => $emailTemplates->add($e)); | ||
->each(function (EmailTemplate $e) use ($context, $request, $emailTemplates) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a suggestion, but since this isn't an arrow function anymore, I would be more verbose about the variable name, just to make it easier to read at a glance. |
||
if (Repo::emailTemplate()->isTemplateAccessibleToUser($request->getUser(), $e, $context->getId())) { | ||
$emailTemplates->add($e); | ||
} | ||
}); | ||
} | ||
|
||
return Repo::emailTemplate()->getSchemaMap()->mapMany($emailTemplates)->toArray(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -235,13 +235,17 @@ public function getMainEmailTemplatesFilename() | |
* skipping others | ||
* @param bool $skipExisting If true, do not install email templates | ||
* that already exist in the database | ||
* @param bool $recordTemplateGroupAccess - If true, records the templates as unrestricted. | ||
* By default, it is set to false to ensure compatibility with older processes (e.g., migrations) | ||
* where the `email_template_user_group_access` table may not exist at the time of execution. | ||
* | ||
*/ | ||
public function installEmailTemplates( | ||
string $templatesFile, | ||
array $locales = [], | ||
?string $emailKey = null, | ||
bool $skipExisting = false | ||
bool $skipExisting = false, | ||
$recordTemplateGroupAccess = false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I mention this directly below, but I don't think this optional parameter will be necessary. I think the assumption of "unrestricted if no additional data provided" below is enough. |
||
): bool { | ||
$xmlDao = new XMLDAO(); | ||
$data = $xmlDao->parseStruct($templatesFile, ['email']); | ||
|
@@ -281,6 +285,20 @@ public function installEmailTemplates( | |
$this->installAlternateEmailTemplates($contextId, $attrs['key']); | ||
} | ||
} | ||
|
||
if ($recordTemplateGroupAccess) { | ||
// Default to true if `isUnrestricted` is not set. | ||
$isUnrestricted = $attrs['isUnrestricted'] ?? '1'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this check is enough to cover any plugin-related issues or the email template XML not having this flag. I don't think you need the |
||
|
||
if ($isUnrestricted !== '1' && $isUnrestricted !== '0') { | ||
throw new Exception('Invalid value given for the `isUnrestricted` attribute on the ' . $attrs['key'] . ' template.'); | ||
} | ||
|
||
$contextIds = app()->get('context')->getIds(); | ||
foreach ($contextIds as $contextId) { | ||
Repo::emailTemplate()->markTemplateAsUnrestricted($attrs['key'], (bool)$isUnrestricted, $contextId); | ||
} | ||
} | ||
} | ||
return true; | ||
} | ||
|
@@ -381,7 +399,7 @@ public function installAlternateEmailTemplates(int $contextId, ?string $emailKey | |
'Tried to install email template as an alternate to `' . $alternateTo . '`, but no default template exists with this key. Installing ' . $alternateTo . ' email template first', | ||
E_USER_WARNING | ||
); | ||
$this->installEmailTemplates(Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(), [], $alternateTo); | ||
$this->installEmailTemplates(Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(), [], $alternateTo, false, true); | ||
} | ||
|
||
DB::table($this->table)->insert([ | ||
|
@@ -448,4 +466,37 @@ protected function getUniqueKey(EmailTemplate $emailTemplate): string | |
|
||
return $key; | ||
} | ||
|
||
|
||
/** | ||
* Sets email template's unrestricted status to their defaults | ||
*/ | ||
public function setTemplateDefaultUnrestirctedSetting(int $contextId, ?array $emailKeys = null) | ||
{ | ||
$xmlDao = new XMLDAO(); | ||
$data = $xmlDao->parseStruct($this->getMainEmailTemplatesFilename(), ['email']); | ||
|
||
if (!isset($data['email'])) { | ||
return false; | ||
} | ||
|
||
foreach ($data['email'] as $entry) { | ||
$attrs = $entry['attributes']; | ||
|
||
if ($emailKeys !== null && !in_array($attrs['key'], $emailKeys)) { | ||
continue; | ||
} | ||
|
||
// Default to true if `isUnrestricted` is not set. | ||
$isUnrestricted = $attrs['isUnrestricted'] ?? '1'; | ||
|
||
if ($isUnrestricted !== '1' && $isUnrestricted !== '0') { | ||
throw new Exception('Invalid value given for the `isUnrestricted` attribute on the ' . $attrs['key'] . ' template.'); | ||
} | ||
|
||
Repo::emailTemplate()->markTemplateAsUnrestricted($attrs['key'], (bool)$isUnrestricted, $contextId); | ||
} | ||
|
||
return true; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?php | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs file docblock. |
||
|
||
namespace PKP\emailTemplate; | ||
|
||
use Eloquence\Behaviours\HasCamelCasing; | ||
use Eloquence\Database\Model; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless you've gotten advice to the contrary, it's probably best to extend the Laravel model directly through There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an error on my part. It should be extending the |
||
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']; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing space after |
||
|
||
|
||
public function scopeWithEmailKey(Builder $query, ?array $keys): Builder | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would include a comment on what these scopes do for reference (e.g. optionally filter by email key). Same for the two below as well. |
||
{ | ||
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); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a lot more of the setup logic for the Vue components can be done in PHP so that these can both be done with existing
FieldOptions
components. I mentioned this in theui-library
PR as well and we can chat about it more in our next one-on-one (probably easier to share screen and code together than writing it all out here).