<?php
/**
* @file classes/mailable/Repository.php
*
* Copyright (c) 2014-2022 Simon Fraser University
* Copyright (c) 2000-2022 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class Repository
*
* @brief A repository to find and edit Mailables.
*/
namespace PKP\mail;
use APP\core\Application;
use APP\facades\Repo;
use Illuminate\Support\Collection;
use PKP\context\Context;
use PKP\core\PKPString;
use PKP\mail\mailables\DecisionNotifyOtherAuthors;
use PKP\mail\mailables\EditReviewNotify;
use PKP\mail\mailables\ReviewCompleteNotifyEditors;
use PKP\mail\mailables\StatisticsReportNotify;
use PKP\mail\mailables\SubmissionAcknowledgement;
use PKP\mail\mailables\SubmissionAcknowledgementNotAuthor;
use PKP\mail\mailables\SubmissionNeedsEditor;
use PKP\mail\mailables\SubmissionSavedForLater;
use PKP\mail\traits\Configurable;
use PKP\plugins\Hook;
class Repository
{
/**
* Get a list of mailables
*
* @param string $searchPhrase Only include mailables with a name or description matching this search phrase
* @param ?bool $includeDisabled Whether or not to include mailables not used in this context, based on the context settings
*
* @return Collection<int,string> The fully-qualified class name of each mailable
*/
public function getMany(
Context $context,
?string $searchPhrase = null,
?bool $includeDisabled = false,
?bool $includeConfigurableOnly = false
): Collection
{
$mailables = $this->map();
Hook::call('Mailer::Mailables', [$mailables, $context]);
return $mailables
->filter(fn (string $class) => !$searchPhrase || $this->containsSearchPhrase($class, $searchPhrase))
->filter(function (string $class) use ($context, $includeDisabled) {
return $includeDisabled || $this->isMailableEnabled($class, $context);
})
->filter(function (string $class) use ($context, $includeConfigurableOnly) {
return !$includeConfigurableOnly || $this->isMailableConfigurable($class, $context);
});
}
/**
* Simple check if mailable's name and description contains a search phrase
* doesn't look up in associated email templates
*
* @param string $className The fully-qualified class name of the Mailable
*/
protected function containsSearchPhrase(string $className, string $searchPhrase): bool
{
$searchPhrase = PKPString::strtolower($searchPhrase);
/** @var Mailable $className */
return str_contains(PKPString::strtolower($className::getName()), $searchPhrase) ||
str_contains(PKPString::strtolower($className::getDescription()), $searchPhrase);
}
/**
* Get a mailable by its email template key
*
* @return ?string The fully-qualified class name of the mailable
*/
public function get(string $emailTemplateKey, Context $context): ?string
{
/** @var ?string $mailable */
$mailable = $this->getMany($context)
->first(fn (string $mailable) => $mailable::getEmailTemplateKey() === $emailTemplateKey);
if (!$mailable) {
return null;
}
return $mailable;
}
/**
* Get a summary of a mailable's properties
*/
public function summarizeMailable(string $class): array
{
$dataDescriptions = $class::getDataDescriptions();
ksort($dataDescriptions);
return [
'_href' => Application::get()->getRequest()->getDispatcher()->url(
Application::get()->getRequest(),
Application::ROUTE_API,
Application::get()->getRequest()->getContext()->getPath(),
'mailables/' . $class::getEmailTemplateKey(),
),
'dataDescriptions' => $dataDescriptions,
'description' => $class::getDescription(),
'emailTemplateKey' => $class::getEmailTemplateKey(),
'fromRoleIds' => $class::getFromRoleIds(),
'groupIds' => $class::getGroupIds(),
'name' => $class::getName(),
'supportsTemplates' => $class::getSupportsTemplates(),
'toRoleIds' => $class::getToRoleIds(),
];
}
/**
* Get a full description of a mailable's properties, including any
* assigned email templates
*/
public function describeMailable(string $class, int $contextId): array
{
$data = $this->summarizeMailable($class);
if (!$class::getSupportsTemplates()) {
$data['emailTemplates'] = [];
} else {
$templates = Repo::emailTemplate()
->getCollector($contextId)
->alternateTo([$class::getEmailTemplateKey()])
->getMany();
$defaultTemplate = Repo::emailTemplate()->getByKey($contextId, $class::getEmailTemplateKey());
$data['emailTemplates'] = Repo::emailTemplate()
->getSchemaMap()
->summarizeMany(
collect(
array_merge(
[$defaultTemplate],
$templates->values()->toArray()
)
)
)
->values();
}
return $data;
}
/**
* Check if a mailable is enabled on this context
*/
protected function isMailableEnabled(string $class, Context $context): bool
{
if ($class === StatisticsReportNotify::class) {
return (bool) $context->getData('editorialStatsEmail');
} elseif (in_array($class, [SubmissionAcknowledgement::class, SubmissionAcknowledgementNotAuthor::class])) {
$setting = $context->getData('submissionAcknowledgement');
if ($setting === Context::SUBMISSION_ACKNOWLEDGEMENT_ALL_AUTHORS) {
return true;
} elseif ($setting === Context::SUBMISSION_ACKNOWLEDGEMENT_OFF) {
return false;
} elseif ($class === SubmissionAcknowledgementNotAuthor::class) {
return false;
}
return true;
} elseif ($class === DecisionNotifyOtherAuthors::class) {
return $context->getData('notifyAllAuthors');
}
return true;
}
/**
* Check if mailable can be configured
*/
protected function isMailableConfigurable(string $class, Context $context): bool
{
if (!in_array(Configurable::class, class_uses_recursive($class))) {
return false;
}
/**
* Mailables may not have associated email templates due to pkp/pkp-lib#9109 and pkp/pkp-lib#9217,
* don't allow to configure them
*/
if (in_array($class, [
EditReviewNotify::class,
ReviewCompleteNotifyEditors::class,
SubmissionSavedForLater::class,
SubmissionNeedsEditor::class,
])) {
$template = Repo::emailTemplate()->getByKey($context->getId(), $class::getEmailTemplateKey());
if (!$template) {
return false;
}
}
return true;
}
/**
* Get the mailables used in this app
*/
public function map(): Collection
{
return collect([
mailables\AnnouncementNotify::class,
mailables\DecisionAcceptNotifyAuthor::class,
mailables\DecisionBackFromCopyeditingNotifyAuthor::class,
mailables\DecisionBackFromProductionNotifyAuthor::class,
mailables\DecisionCancelReviewRoundNotifyAuthor::class,
mailables\DecisionDeclineNotifyAuthor::class,
mailables\DecisionInitialDeclineNotifyAuthor::class,
mailables\DecisionNewReviewRoundNotifyAuthor::class,
mailables\DecisionNotifyOtherAuthors::class,
mailables\DecisionNotifyReviewer::class,
mailables\DecisionRequestRevisionsNotifyAuthor::class,
mailables\DecisionResubmitNotifyAuthor::class,
mailables\DecisionRevertDeclineNotifyAuthor::class,
mailables\DecisionRevertInitialDeclineNotifyAuthor::class,
mailables\DecisionSendExternalReviewNotifyAuthor::class,
mailables\DecisionSendToProductionNotifyAuthor::class,
mailables\DecisionSkipExternalReviewNotifyAuthor::class,
mailables\DiscussionCopyediting::class,
mailables\DiscussionProduction::class,
mailables\DiscussionReview::class,
mailables\DiscussionSubmission::class,
mailables\EditorAssigned::class,
mailables\EditReviewNotify::class,
mailables\EditorialReminder::class,
mailables\PasswordResetRequested::class,
mailables\PublicationVersionNotify::class,
mailables\RecommendationNotifyEditors::class,
mailables\ReviewAcknowledgement::class,
mailables\ReviewCompleteNotifyEditors::class,
mailables\ReviewConfirm::class,
mailables\ReviewDecline::class,
mailables\ReviewRemind::class,
mailables\ReviewRemindAuto::class,
mailables\ReviewRequest::class,
mailables\ReviewRequestSubsequent::class,
mailables\ReviewResponseRemindAuto::class,
mailables\ReviewerRegister::class,
mailables\ReviewerReinstate::class,
mailables\ReviewerResendRequest::class,
mailables\ReviewerUnassign::class,
mailables\RevisedVersionNotify::class,
mailables\StatisticsReportNotify::class,
mailables\SubmissionAcknowledgement::class,
mailables\SubmissionAcknowledgementNotAuthor::class,
mailables\UserCreated::class,
mailables\ValidateEmailContext::class,
mailables\ValidateEmailSite::class,
]);
}
}
|