first commit
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/emailTemplate/Collector.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class Collector
|
||||
*
|
||||
* @brief A helper class to configure a Query Builder to get a collection of email templates
|
||||
*/
|
||||
|
||||
namespace PKP\emailTemplate;
|
||||
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use PKP\core\interfaces\CollectorInterface;
|
||||
use PKP\plugins\Hook;
|
||||
|
||||
/**
|
||||
* @template T of EmailTemplate
|
||||
*/
|
||||
class Collector implements CollectorInterface
|
||||
{
|
||||
public DAO $dao;
|
||||
|
||||
/**
|
||||
* Retrieve all matches from query builder limited by those
|
||||
* which are custom templates or have been modified from the
|
||||
* default.
|
||||
*/
|
||||
public ?bool $isModified = null;
|
||||
public int $contextId;
|
||||
public ?array $keys = null;
|
||||
public ?string $searchPhrase = null;
|
||||
public ?int $count = null;
|
||||
public ?int $offset = null;
|
||||
public ?array $alternateTo = null;
|
||||
|
||||
public const EMAIL_TEMPLATE_STAGE_DEFAULT = 0;
|
||||
|
||||
public function __construct(DAO $dao, int $contextId)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
$this->contextId = $contextId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc DAO::getMany()
|
||||
* @return LazyCollection<int,T>
|
||||
*/
|
||||
public function getMany(): LazyCollection
|
||||
{
|
||||
return $this->dao->getMany($this);
|
||||
}
|
||||
|
||||
public function getCount(): int
|
||||
{
|
||||
return $this->dao->getCount($this);
|
||||
}
|
||||
|
||||
public function isModified(?bool $isModified): self
|
||||
{
|
||||
$this->isModified = $isModified;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set email keys filter
|
||||
*/
|
||||
public function filterByKeys(?array $keys): self
|
||||
{
|
||||
$this->keys = $keys;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set query search phrase
|
||||
*/
|
||||
public function searchPhrase(?string $phrase): self
|
||||
{
|
||||
$this->searchPhrase = $phrase;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter results by custom templates that are alternates of another other email
|
||||
*
|
||||
* @param string[] $emailTemplateKeys One or more default email template keys
|
||||
*/
|
||||
public function alternateTo(?array $emailTemplateKeys): self
|
||||
{
|
||||
$this->alternateTo = $emailTemplateKeys;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method performs a UNION on the default and custom template
|
||||
* tables, and returns the final SQL string and merged bindings.
|
||||
*
|
||||
* Use a UNION to ensure the query will match rows in email_templates and
|
||||
* email_templates_default. This ensures that custom templates which have
|
||||
* no default in email_templates_default are still returned. These templates
|
||||
* should not be returned when a role filter is used.
|
||||
*/
|
||||
public function getQueryBuilder(): Builder
|
||||
{
|
||||
$q = $this->isModified === true || !is_null($this->alternateTo)
|
||||
? $this->getCustomQueryBuilder()
|
||||
: $this->getDefaultQueryBuilder()->union($this->getCustomQueryBuilder());
|
||||
|
||||
$q
|
||||
->when(!is_null($this->count), function (Builder $q) {
|
||||
return $q->limit($this->count);
|
||||
})
|
||||
|
||||
->when(!is_null($this->count) && !is_null($this->offset), function (Builder $q) {
|
||||
return $q->offset($this->offset);
|
||||
});
|
||||
|
||||
$q->orderBy('email_key');
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* If you try to execute the query returned by this method, it will
|
||||
* cause an error in PostgreSQL.
|
||||
*
|
||||
* Call `->distinct()` on the query builder returned by this method
|
||||
* before executing it, or use `->union()` to join it with
|
||||
* `self::getCustomQueryBuilder()` (see self::getQueryBuilder()).
|
||||
*
|
||||
* @see self::getCompiledQuery()
|
||||
*
|
||||
*/
|
||||
protected function getDefaultQueryBuilder(): Builder
|
||||
{
|
||||
$q = DB::table('email_templates_default_data as etddata')
|
||||
->select('email_key')
|
||||
->selectRaw('NULL as email_id')
|
||||
->selectRaw($this->contextId . ' as context_id')
|
||||
->selectRaw('NULL as alternate_to')
|
||||
|
||||
->whereNotIn('etddata.email_key', function (Builder $q) {
|
||||
$q->select('et.email_key')
|
||||
->from('email_templates as et')
|
||||
->where('et.context_id', $this->contextId);
|
||||
})
|
||||
|
||||
->when(!is_null($this->keys), function (Builder $q) {
|
||||
return $q->whereIn('email_key', $this->keys);
|
||||
})
|
||||
|
||||
// search phrase
|
||||
->when(!is_null($this->searchPhrase), function (Builder $q) {
|
||||
$words = explode(' ', $this->searchPhrase);
|
||||
$likePattern = DB::raw("CONCAT('%', LOWER(?), '%')");
|
||||
foreach ($words as $word) {
|
||||
$q->where(function (Builder $q) use ($word, $likePattern) {
|
||||
$q->where(DB::raw('LOWER(etddata.subject)'), 'LIKE', $likePattern)->addBinding($word)
|
||||
->orWhere(DB::raw('LOWER(etddata.body)'), 'LIKE', $likePattern)->addBinding($word)
|
||||
->orWhere(DB::raw('LOWER(etddata.email_key)'), 'LIKE', $likePattern)->addBinding($word);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add app-specific query statements
|
||||
Hook::call('EmailTemplate::Collector::default', [$q, $this]);
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute query builder for custom email templates
|
||||
* and email templates that have been modified from
|
||||
* the default.
|
||||
*
|
||||
* @see self::getCompiledQuery()
|
||||
*
|
||||
*/
|
||||
protected function getCustomQueryBuilder(): Builder
|
||||
{
|
||||
$q = DB::table($this->dao->table . ' as et')
|
||||
->select([
|
||||
'et.email_key',
|
||||
'et.email_id',
|
||||
'et.context_id',
|
||||
'et.alternate_to',
|
||||
])
|
||||
|
||||
->where('et.context_id', $this->contextId)
|
||||
|
||||
->when(!is_null($this->keys), function (Builder $q) {
|
||||
return $q->whereIn('et.email_key', $this->keys);
|
||||
})
|
||||
|
||||
->when(!is_null($this->alternateTo), function (Builder $q) {
|
||||
return $q->whereIn('et.alternate_to', $this->alternateTo);
|
||||
})
|
||||
|
||||
->when(!is_null($this->searchPhrase), function (Builder $q) {
|
||||
$words = explode(' ', $this->searchPhrase);
|
||||
$likePattern = DB::raw("CONCAT('%', LOWER(?), '%')");
|
||||
foreach ($words as $word) {
|
||||
$q->where(function (Builder $q) use ($word, $likePattern) {
|
||||
$q->whereIn('et.email_id', function ($q) use ($word, $likePattern) {
|
||||
return $q->select('ets.email_id')
|
||||
->from('email_templates_settings as ets')
|
||||
->where(function ($q) use ($word, $likePattern) {
|
||||
$q->where('ets.setting_name', 'subject');
|
||||
$q->where(DB::raw('LOWER(ets.setting_value)'), 'LIKE', $likePattern)->addBinding($word);
|
||||
})
|
||||
->orWhere(function ($q) use ($word, $likePattern) {
|
||||
$q->where('ets.setting_name', 'body');
|
||||
$q->where(DB::raw('LOWER(ets.setting_value)'), 'LIKE', $likePattern)->addBinding($word);
|
||||
});
|
||||
})
|
||||
->orWhere(DB::raw('LOWER(et.email_key)'), 'LIKE', $likePattern)->addBinding($word);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add app-specific query statements
|
||||
Hook::call('EmailTemplate::Collector::custom', [$q, $this]);
|
||||
|
||||
return $q;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/emailTemplate/DAO.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class DAO
|
||||
*
|
||||
* @brief Read and write email templates to the database.
|
||||
*/
|
||||
|
||||
namespace PKP\emailTemplate;
|
||||
|
||||
use APP\core\Application;
|
||||
use APP\core\Services;
|
||||
use APP\facades\Repo;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\LazyCollection;
|
||||
use PKP\core\EntityDAO;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\db\DAORegistry;
|
||||
use PKP\db\XMLDAO;
|
||||
use PKP\facades\Locale;
|
||||
use PKP\site\Site;
|
||||
use PKP\site\SiteDAO;
|
||||
use Stringy\Stringy;
|
||||
|
||||
/**
|
||||
* @template T of EmailTemplate
|
||||
* @extends EntityDAO<T>
|
||||
*/
|
||||
class DAO extends EntityDAO
|
||||
{
|
||||
/** @copydoc EntityDAO::$schema */
|
||||
public $schema = \PKP\services\PKPSchemaService::SCHEMA_EMAIL_TEMPLATE;
|
||||
|
||||
/** @copydoc EntityDAO::$table */
|
||||
public $table = 'email_templates';
|
||||
|
||||
/** @copydoc EntityDAO::$settingsTable */
|
||||
public $settingsTable = 'email_templates_settings';
|
||||
|
||||
public $defaultTable = 'email_templates_default_data';
|
||||
|
||||
/** @copydoc EntityDAO::$primaryKeyColumn */
|
||||
public $primaryKeyColumn = 'email_id';
|
||||
|
||||
/** @copydoc EntityDAO::$primaryTableColumns */
|
||||
public $primaryTableColumns = [
|
||||
'id' => 'email_id',
|
||||
'key' => 'email_key',
|
||||
'alternateTo' => 'alternate_to',
|
||||
'contextId' => 'context_id',
|
||||
'enabled' => 'enabled',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the parent object ID column name
|
||||
*/
|
||||
public function getParentColumn(): string
|
||||
{
|
||||
return 'context_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a new DataObject
|
||||
*/
|
||||
public function newDataObject(): EmailTemplate
|
||||
{
|
||||
return app(EmailTemplate::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc EntityDAO::insert()
|
||||
*
|
||||
* Custom email templates will need to generate a unique key,
|
||||
* but the key will be set when this template is a customization
|
||||
* of a default template.
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return string The email template key
|
||||
*/
|
||||
public function insert(EmailTemplate $object): string
|
||||
{
|
||||
if (!$object->getData('key')) {
|
||||
$object->setData('key', $this->getUniqueKey($object));
|
||||
}
|
||||
parent::_insert($object);
|
||||
return $object->getData('key');
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc EntityDAO::update()
|
||||
*/
|
||||
public function update(EmailTemplate $object)
|
||||
{
|
||||
parent::_update($object);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc EntityDAO::delete()
|
||||
*/
|
||||
public function delete(EmailTemplate $emailTemplate)
|
||||
{
|
||||
parent::_delete($emailTemplate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection of Email Templates matching the configured query
|
||||
* @return LazyCollection<int,T>
|
||||
*/
|
||||
public function getMany(Collector $query): LazyCollection
|
||||
{
|
||||
$rows = $query
|
||||
->getQueryBuilder()
|
||||
->get();
|
||||
|
||||
return LazyCollection::make(function () use ($rows) {
|
||||
foreach ($rows as $row) {
|
||||
yield $this->fromRow($row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single email template that matches the given key
|
||||
*/
|
||||
public function getByKey(int $contextId, string $key): ?EmailTemplate
|
||||
{
|
||||
$results = Repo::emailTemplate()->getCollector($contextId)
|
||||
->filterByKeys([$key])
|
||||
->getMany();
|
||||
|
||||
return $results->isNotEmpty() ? $results->first() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of announcements matching the configured query
|
||||
*/
|
||||
public function getCount(Collector $query): int
|
||||
{
|
||||
return $query
|
||||
->getQueryBuilder()
|
||||
->get()
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve template together with data from the email_template_default_data
|
||||
*
|
||||
* @copydoc EntityDAO::fromRow()
|
||||
*/
|
||||
public function fromRow(object $row): EmailTemplate
|
||||
{
|
||||
/** @var EmailTemplate $emailTemplate */
|
||||
$emailTemplate = parent::fromRow($row);
|
||||
$schema = $this->schemaService->get($this->schema);
|
||||
$contextDao = Application::getContextDAO();
|
||||
|
||||
$supportedLocalesJson = $row->context_id === PKPApplication::CONTEXT_SITE ?
|
||||
DB::table('site')->first()->supported_locales :
|
||||
DB::table($contextDao->settingsTableName)
|
||||
->where($contextDao->primaryKeyColumn, $row->context_id)
|
||||
->where('setting_name', 'supportedLocales')
|
||||
->value('setting_value');
|
||||
|
||||
$rows = DB::table($this->defaultTable)
|
||||
->where('email_key', '=', $emailTemplate->getData('key'))
|
||||
->whereIn('locale', json_decode($supportedLocalesJson, true))
|
||||
->get();
|
||||
|
||||
$props = ['name', 'subject', 'body'];
|
||||
|
||||
$rows->each(function ($row) use ($emailTemplate, $schema, $props) {
|
||||
foreach ($props as $prop) {
|
||||
// Don't allow default data to override custom template data
|
||||
if ($emailTemplate->getData($prop, $row->locale)) {
|
||||
continue;
|
||||
}
|
||||
$emailTemplate->setData(
|
||||
$prop,
|
||||
$this->convertFromDB(
|
||||
$row->{$prop},
|
||||
$schema->properties->{$prop}->type
|
||||
),
|
||||
$row->locale
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return $emailTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all email templates for a specific locale.
|
||||
*/
|
||||
public function deleteEmailTemplatesByLocale(string $locale)
|
||||
{
|
||||
DB::table($this->settingsTable)->where('locale', $locale)->delete();
|
||||
DB::table($this->defaultTable)->where('locale', $locale)->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a template exists with the given email key for a journal/
|
||||
* conference/...
|
||||
*
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function defaultTemplateIsInstalled(string $key)
|
||||
{
|
||||
return DB::table($this->defaultTable)->where('email_key', $key)->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the main email template path and filename.
|
||||
*
|
||||
* TODO add to the Repository
|
||||
*/
|
||||
public function getMainEmailTemplatesFilename()
|
||||
{
|
||||
return 'registry/emailTemplates.xml';
|
||||
}
|
||||
|
||||
/**
|
||||
* Install email templates from an XML file.
|
||||
*
|
||||
* @param string $templatesFile Filename to install
|
||||
* @param array $locales List of locales to install data for
|
||||
* @param string|null $emailKey Optional name of single email key to install,
|
||||
* skipping others
|
||||
* @param bool $skipExisting If true, do not install email templates
|
||||
* that already exist in the database
|
||||
*
|
||||
*/
|
||||
public function installEmailTemplates(
|
||||
string $templatesFile,
|
||||
array $locales = [],
|
||||
?string $emailKey = null,
|
||||
bool $skipExisting = false
|
||||
): bool {
|
||||
$xmlDao = new XMLDAO();
|
||||
$data = $xmlDao->parseStruct($templatesFile, ['email']);
|
||||
if (!isset($data['email'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// if locales is empty, it will use the site's installed locales
|
||||
$locales = array_filter(array_map('trim', $locales));
|
||||
if (empty($locales)) {
|
||||
$siteDao = DAORegistry::getDAO('SiteDAO'); /** @var SiteDAO $siteDao */
|
||||
$site = $siteDao->getSite(); /** @var Site $site */
|
||||
$locales = $site->getInstalledLocales();
|
||||
}
|
||||
|
||||
// filter out any invalid locales that is not supported by site
|
||||
$allLocales = array_keys(Locale::getLocales());
|
||||
if (!empty($invalidLocales = array_diff($locales, $allLocales))) {
|
||||
$locales = array_diff($locales, $invalidLocales);
|
||||
}
|
||||
|
||||
foreach ($data['email'] as $entry) {
|
||||
$attrs = $entry['attributes'];
|
||||
if ($emailKey && $emailKey != $attrs['key']) {
|
||||
continue;
|
||||
}
|
||||
if ($skipExisting && $this->defaultTemplateIsInstalled($attrs['key'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add localized data
|
||||
$this->installEmailTemplateLocaleData($templatesFile, $locales, $attrs['key']);
|
||||
|
||||
if (isset($attrs['alternateTo'])) {
|
||||
$contextIds = Services::get('context')->getIds();
|
||||
foreach ($contextIds as $contextId) {
|
||||
$this->installAlternateEmailTemplates($contextId, $attrs['key']);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install email template contents from an XML file.
|
||||
*
|
||||
* @param string $templatesFile Filename to install
|
||||
* @param array $locales List of locales to install data for
|
||||
* @param string|null $emailKey Optional name of single email key to install,
|
||||
* skipping others
|
||||
*
|
||||
*/
|
||||
public function installEmailTemplateLocaleData(
|
||||
string $templatesFile,
|
||||
array $locales = [],
|
||||
?string $emailKey = null
|
||||
): bool {
|
||||
$xmlDao = new XMLDAO();
|
||||
$data = $xmlDao->parseStruct($templatesFile, ['email']);
|
||||
if (!isset($data['email'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($data['email'] as $entry) {
|
||||
$attrs = $entry['attributes'];
|
||||
if ($emailKey && $emailKey != $attrs['key']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $attrs['name'] ?? null;
|
||||
$subject = $attrs['subject'] ?? null;
|
||||
$body = $attrs['body'] ?? null;
|
||||
if ($name && $subject && $body) {
|
||||
foreach ($locales as $locale) {
|
||||
DB::table($this->defaultTable)
|
||||
->where('email_key', $attrs['key'])
|
||||
->where('locale', $locale)
|
||||
->delete();
|
||||
|
||||
$previous = Locale::getMissingKeyHandler();
|
||||
Locale::setMissingKeyHandler(fn (string $key): string => '');
|
||||
$translatedName = $name ? __($name, [], $locale) : $attrs['key'];
|
||||
$translatedSubject = __($subject, [], $locale);
|
||||
$translatedBody = __($body, [], $locale);
|
||||
Locale::setMissingKeyHandler($previous);
|
||||
if ($translatedSubject !== null && $translatedBody !== null) {
|
||||
DB::table($this->defaultTable)->insert([
|
||||
'email_key' => $attrs['key'],
|
||||
'locale' => $locale,
|
||||
'name' => $translatedName,
|
||||
'subject' => $this->renameApplicationVariables($translatedSubject),
|
||||
'body' => $this->renameApplicationVariables($translatedBody),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs the "extra" email templates for a context
|
||||
*
|
||||
* These are default email templates that are not the default email
|
||||
* template for a particular mailable. They are extra templates listed
|
||||
* alongside the default template for this mailable.
|
||||
*
|
||||
* These templates are defined by the presence of the `alternateTo`
|
||||
* attribute in the email templates XML file. For them to appear in the
|
||||
* UI, they must have an entry in the `email_templates` database.
|
||||
*/
|
||||
public function installAlternateEmailTemplates(int $contextId, ?string $emailKey = null): void
|
||||
{
|
||||
$xmlDao = new XMLDAO();
|
||||
$data = $xmlDao->parseStruct(Repo::emailTemplate()->dao->getMainEmailTemplatesFilename(), ['email']);
|
||||
if (!isset($data['email'])) {
|
||||
throw new Exception('Unable to install email templates.');
|
||||
}
|
||||
|
||||
foreach ($data['email'] as $entry) {
|
||||
$attrs = $entry['attributes'];
|
||||
$alternateTo = $attrs['alternateTo'] ?? null;
|
||||
|
||||
if ($emailKey && $emailKey != $attrs['key']) {
|
||||
continue;
|
||||
}
|
||||
if (!$alternateTo) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$exists = DB::table($this->defaultTable)
|
||||
->where('email_key', $alternateTo)
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
trigger_error(
|
||||
'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);
|
||||
}
|
||||
|
||||
DB::table($this->table)->insert([
|
||||
'email_key' => $attrs['key'],
|
||||
'context_id' => $contextId,
|
||||
'alternate_to' => $attrs['alternateTo'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install email template localized data from an XML file.
|
||||
*
|
||||
* @deprecated Since OJS/OMP 3.2, this data should be supplied via the non-localized email template list and PO files. (pkp/pkp-lib#5461)
|
||||
*
|
||||
* @param string $templateDataFile Filename to install
|
||||
* @param string $locale Locale of template(s) to install
|
||||
* @param string|null $emailKey If specified, the key of the single template
|
||||
* to install (otherwise all are installed)
|
||||
*
|
||||
* @return array|boolean
|
||||
*/
|
||||
public function installEmailTemplateData(
|
||||
string $templateDataFile,
|
||||
string $locale,
|
||||
?string $emailKey = null
|
||||
): bool {
|
||||
$xmlDao = new XMLDAO();
|
||||
$data = $xmlDao->parse($templateDataFile);
|
||||
if (!$data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($data->getChildren() as $emailNode) {
|
||||
$subject = $emailNode->getChildValue('subject');
|
||||
$body = $emailNode->getChildValue('body');
|
||||
|
||||
// Translate variable contents
|
||||
foreach ([&$subject, &$body] as &$var) {
|
||||
$var = preg_replace_callback('{{translate key="([^"]+)"}}', fn ($matches) => __($matches[1], [], $locale), $var);
|
||||
}
|
||||
|
||||
if ($emailKey && $emailKey != $emailNode->getAttribute('key')) {
|
||||
continue;
|
||||
}
|
||||
DB::table($this->defaultTable)
|
||||
->where('email_key', $emailNode->getAttribute('key'))
|
||||
->where('locale', $locale)
|
||||
->delete();
|
||||
|
||||
DB::table($this->defaultTable)->insert([
|
||||
'email_key' => $emailNode->getAttribute('key'),
|
||||
'locale' => $locale,
|
||||
'subject' => $subject,
|
||||
'body' => $body,
|
||||
]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $localizedData email template's localized subject or body
|
||||
*/
|
||||
protected function renameApplicationVariables(string $localizedData): string
|
||||
{
|
||||
$map = $this->variablesToRename();
|
||||
if (empty($map)) {
|
||||
return $localizedData;
|
||||
}
|
||||
|
||||
$variables = [];
|
||||
$replacements = [];
|
||||
foreach ($map as $key => $value) {
|
||||
$variables[] = '/\{\$' . $key . '\}/';
|
||||
$replacements[] = '{$' . $value . '}';
|
||||
}
|
||||
|
||||
return preg_replace($variables, $replacements, $localizedData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this function on an application level to rename app-specific template variables
|
||||
*
|
||||
* Example: ['contextName' => 'journalName']
|
||||
*/
|
||||
protected function variablesToRename(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a unique key for an email template
|
||||
*
|
||||
* Use this to generate a unique key before adding a template to the
|
||||
* database.
|
||||
*/
|
||||
protected function getUniqueKey(EmailTemplate $emailTemplate): string
|
||||
{
|
||||
$key = Stringy::create($emailTemplate->getLocalizedData('name'))
|
||||
->slugify()
|
||||
->regexReplace('[^a-z0-9\-\_.]', '')
|
||||
->truncate(30)
|
||||
->toString();
|
||||
|
||||
if (!$key) {
|
||||
$key = uniqid();
|
||||
}
|
||||
|
||||
$emailTemplate = $this->getByKey($emailTemplate->getData('contextId'), $key);
|
||||
|
||||
$i = 0;
|
||||
while ($emailTemplate) {
|
||||
$key = $i ? (substr($key, 0, -1) . $i) : ($key . $i);
|
||||
$emailTemplate = $this->getByKey($emailTemplate->getData('contextId'), $key);
|
||||
$i++;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/emailTemplate/EmailTemplate.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class EmailTemplate
|
||||
*
|
||||
* @ingroup mail
|
||||
*
|
||||
* @see EmailTemplateDAO
|
||||
*
|
||||
* @brief Describes basic email template properties.
|
||||
*/
|
||||
|
||||
namespace PKP\emailTemplate;
|
||||
|
||||
class EmailTemplate extends \PKP\core\DataObject
|
||||
{
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
|
||||
/**
|
||||
* Get ID of journal/conference/...
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAssocId()
|
||||
{
|
||||
return $this->getData('contextId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ID of journal/conference/...
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @param int $assocId
|
||||
*/
|
||||
public function setAssocId($assocId)
|
||||
{
|
||||
$this->setData('contextId', $assocId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not this is a custom email template
|
||||
* (ie one that was created by the journal/conference/... manager and
|
||||
* is not part of the system upon installation)
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*/
|
||||
public function isCustomTemplate()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sender role ID.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*/
|
||||
public function getFromRoleId()
|
||||
{
|
||||
return $this->getData('fromRoleId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sender role ID.
|
||||
*
|
||||
* @param int $fromRoleId
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*/
|
||||
public function setFromRoleId($fromRoleId)
|
||||
{
|
||||
$this->setData('fromRoleId', $fromRoleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recipient role ID.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*/
|
||||
public function getToRoleId()
|
||||
{
|
||||
return $this->getData('toRoleId');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set recipient role ID.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @param int $toRoleId
|
||||
*/
|
||||
public function setToRoleId($toRoleId)
|
||||
{
|
||||
$this->setData('toRoleId', $toRoleId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ID of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEmailId()
|
||||
{
|
||||
return $this->getData('id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ID of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @param int $emailId
|
||||
*/
|
||||
public function setEmailId($emailId)
|
||||
{
|
||||
$this->setData('id', $emailId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get key of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getEmailKey()
|
||||
{
|
||||
return $this->getData('key');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set key of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function setEmailKey($key)
|
||||
{
|
||||
$this->setData('key', $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enabled setting of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getEnabled()
|
||||
{
|
||||
return $this->getData('enabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the enabled setting of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @param bool $enabled
|
||||
*/
|
||||
public function setEnabled($enabled)
|
||||
{
|
||||
$this->setData('enabled', $enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if email template is allowed to be disabled.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanDisable()
|
||||
{
|
||||
return $this->getData('canDisable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether or not email template is allowed to be disabled.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @param bool $canDisable
|
||||
*/
|
||||
public function setCanDisable($canDisable)
|
||||
{
|
||||
$this->setData('canDisable', $canDisable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subject of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSubject()
|
||||
{
|
||||
return $this->getData('subject');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set subject of email.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @param string $subject
|
||||
*/
|
||||
public function setSubject($subject)
|
||||
{
|
||||
$this->setData('subject', $subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get body of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBody()
|
||||
{
|
||||
return $this->getData('body');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set body of email template.
|
||||
*
|
||||
* @deprecated 3.2
|
||||
*
|
||||
* @param string $body
|
||||
*/
|
||||
public function setBody($body)
|
||||
{
|
||||
$this->setData('body', $body);
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\emailTemplate\EmailTemplate', '\EmailTemplate');
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/emailTemplate/Repository.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class Repository
|
||||
*
|
||||
* @brief A repository to find and manage email templates.
|
||||
*/
|
||||
|
||||
namespace PKP\emailTemplate;
|
||||
|
||||
use APP\core\Services;
|
||||
use APP\emailTemplate\DAO;
|
||||
use APP\facades\Repo;
|
||||
use PKP\context\Context;
|
||||
use PKP\core\PKPRequest;
|
||||
use PKP\plugins\Hook;
|
||||
use PKP\services\PKPSchemaService;
|
||||
use PKP\validation\ValidatorFactory;
|
||||
|
||||
class Repository
|
||||
{
|
||||
public DAO $dao;
|
||||
|
||||
// The name of the class to map this entity to its schema
|
||||
public string $schemaMap = maps\Schema::class;
|
||||
|
||||
protected PKPRequest $request;
|
||||
|
||||
protected PKPSchemaService $schemaService;
|
||||
|
||||
public function __construct(DAO $dao, PKPRequest $request, PKPSchemaService $schemaService)
|
||||
{
|
||||
$this->dao = $dao;
|
||||
$this->request = $request;
|
||||
$this->schemaService = $schemaService;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::newDataObject() */
|
||||
public function newDataObject(array $params = []): Emailtemplate
|
||||
{
|
||||
$object = $this->dao->newDataObject();
|
||||
if (!empty($params)) {
|
||||
$object->setAllData($params);
|
||||
}
|
||||
return $object;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::getByKey() */
|
||||
public function getByKey(int $contextId, string $key): ?EmailTemplate
|
||||
{
|
||||
return $this->dao->getByKey($contextId, $key);
|
||||
}
|
||||
|
||||
/** @copydoc DAO::getCollector() */
|
||||
public function getCollector(int $contextId): Collector
|
||||
{
|
||||
return app(Collector::class, ['contextId' => $contextId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the map class for mapping
|
||||
* announcements to their schema
|
||||
*/
|
||||
public function getSchemaMap(): maps\Schema
|
||||
{
|
||||
return app('maps')->withExtensions($this->schemaMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate properties for an email template
|
||||
*
|
||||
* Perform validation checks on data used to add or edit an email template.
|
||||
*
|
||||
* @param array $props A key/value array with the new data to validate
|
||||
*
|
||||
* @return array A key/value array with validation errors. Empty if no errors
|
||||
*/
|
||||
public function validate(?EmailTemplate $object, array $props, Context $context): array
|
||||
{
|
||||
$primaryLocale = $context->getData('primaryLocale');
|
||||
$allowedLocales = $context->getData('supportedFormLocales');
|
||||
|
||||
$errors = [];
|
||||
$validator = ValidatorFactory::make(
|
||||
$props,
|
||||
$this->schemaService->getValidationRules(PKPSchemaService::SCHEMA_EMAIL_TEMPLATE, $allowedLocales)
|
||||
);
|
||||
|
||||
// Check required fields
|
||||
ValidatorFactory::required(
|
||||
$validator,
|
||||
$object,
|
||||
$this->schemaService->getRequiredProps(PKPSchemaService::SCHEMA_EMAIL_TEMPLATE),
|
||||
$this->schemaService->getMultilingualProps(PKPSchemaService::SCHEMA_EMAIL_TEMPLATE),
|
||||
$allowedLocales,
|
||||
$primaryLocale
|
||||
);
|
||||
|
||||
if (isset($props['contextId'])) {
|
||||
$validator->after(function ($validator) use ($props, $context) {
|
||||
if (!Services::get('context')->exists($props['contextId'])) {
|
||||
$validator->errors()->add('contextId', __('api.contexts.404.contextNotFound'));
|
||||
}
|
||||
if ($context->getId() !== $props['contextId']) {
|
||||
$validator->errors()->add('contextId', __('api.emailTemplates.400.invalidContext'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// An email template can only be an alternate to a mailable's default email template
|
||||
if (isset($props['alternateTo'])) {
|
||||
$validator->after(function ($validator) use ($props, $context) {
|
||||
$mailableExists = Repo::mailable()
|
||||
->getMany($context)
|
||||
->contains(fn (string $mailable) => $mailable::getEmailTemplateKey() === $props['alternateTo']);
|
||||
|
||||
if (!$mailableExists) {
|
||||
$validator->errors()->add('alternateTo', __('api.emailTemplates.400.invalidAlternateTo'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Check for input from disallowed locales
|
||||
ValidatorFactory::allowedLocales($validator, $this->schemaService->getMultilingualProps($this->dao->schema), $allowedLocales);
|
||||
|
||||
if ($validator->fails()) {
|
||||
$errors = $this->schemaService->formatValidationErrors($validator->errors());
|
||||
}
|
||||
|
||||
Hook::call('EmailTemplate::validate', [&$errors, $object, $props, $allowedLocales, $primaryLocale]);
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new email template
|
||||
*/
|
||||
public function add(EmailTemplate $emailTemplate): string
|
||||
{
|
||||
$key = $this->dao->insert($emailTemplate);
|
||||
|
||||
Hook::call('EmailTemplate::add', [$emailTemplate]);
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
/** @copydoc DAO::update() */
|
||||
public function edit(EmailTemplate $emailTemplate, array $params)
|
||||
{
|
||||
$newEmailTemplate = clone $emailTemplate;
|
||||
$newEmailTemplate->setAllData(array_merge($newEmailTemplate->_data, $params));
|
||||
|
||||
Hook::call('EmailTemplate::edit', [$newEmailTemplate, $emailTemplate, $params]);
|
||||
|
||||
if ($newEmailTemplate->getId()) {
|
||||
$this->dao->update($newEmailTemplate);
|
||||
} else {
|
||||
$this->dao->insert($newEmailTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
/** @copydoc DAO::delete() */
|
||||
public function delete(EmailTemplate $emailTemplate)
|
||||
{
|
||||
Hook::call('EmailTemplate::delete::before', [&$emailTemplate]);
|
||||
$this->dao->delete($emailTemplate);
|
||||
Hook::call('EmailTemplate::delete', [&$emailTemplate]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a collection of email templates
|
||||
*/
|
||||
public function deleteMany(Collector $collector)
|
||||
{
|
||||
foreach ($collector->getMany() as $emailTemplate) {
|
||||
$this->delete($emailTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all custom templates and template modifications. Resets the
|
||||
* email template settings to their installed defaults.
|
||||
*
|
||||
* @return array List of keys that were deleted or reset
|
||||
*/
|
||||
public function restoreDefaults($contextId): array
|
||||
{
|
||||
$results = $this->getCollector($contextId)
|
||||
->isModified(true)
|
||||
->getMany();
|
||||
|
||||
$deletedKeys = [];
|
||||
$results->each(function ($emailTemplate) use ($deletedKeys) {
|
||||
$deletedKeys[] = $emailTemplate->getData('key');
|
||||
$this->delete($emailTemplate);
|
||||
});
|
||||
$this->dao->installAlternateEmailTemplates($contextId);
|
||||
Hook::call('EmailTemplate::restoreDefaults', [&$deletedKeys, $contextId]);
|
||||
return $deletedKeys;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* @file classes/emailTemplate/maps/Schema.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class Schema
|
||||
*
|
||||
* @brief Map email templates to the properties defined in the email template schema
|
||||
*/
|
||||
|
||||
namespace PKP\emailTemplate\maps;
|
||||
|
||||
use Illuminate\Support\Enumerable;
|
||||
use PKP\core\PKPApplication;
|
||||
use PKP\emailTemplate\EmailTemplate;
|
||||
use PKP\services\PKPSchemaService;
|
||||
|
||||
class Schema extends \PKP\core\maps\Schema
|
||||
{
|
||||
/** @copydoc \PKP\core\maps\Schema::$collection */
|
||||
public Enumerable $collection;
|
||||
|
||||
/** @copydoc \PKP\core\maps\Schema::$schema */
|
||||
public string $schema = PKPSchemaService::SCHEMA_EMAIL_TEMPLATE;
|
||||
|
||||
/**
|
||||
* Map an email template
|
||||
*
|
||||
* Includes all properties in the email template schema.
|
||||
*/
|
||||
public function map(EmailTemplate $item): array
|
||||
{
|
||||
return $this->mapByProperties($this->getProps(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize an email template
|
||||
*
|
||||
* Includes properties with the apiSummary flag in the email template schema.
|
||||
*/
|
||||
public function summarize(EmailTemplate $item): array
|
||||
{
|
||||
return $this->mapByProperties($this->getSummaryProps(), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a collection of email templates
|
||||
*
|
||||
* @see self::map
|
||||
*/
|
||||
public function mapMany(Enumerable $collection): Enumerable
|
||||
{
|
||||
$this->collection = $collection;
|
||||
return $collection->map(function ($item) {
|
||||
return $this->map($item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize a collection of email templates
|
||||
*
|
||||
* @see self::summarize
|
||||
*/
|
||||
public function summarizeMany(Enumerable $collection): Enumerable
|
||||
{
|
||||
$this->collection = $collection;
|
||||
return $collection->map(function ($item) {
|
||||
return $this->summarize($item);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Map schema properties of an Announcement to an assoc array
|
||||
*/
|
||||
protected function mapByProperties(array $props, EmailTemplate $item): array
|
||||
{
|
||||
$output = [];
|
||||
foreach ($props as $prop) {
|
||||
switch ($prop) {
|
||||
case '_href':
|
||||
$output[$prop] = $this->request->getDispatcher()->url(
|
||||
$this->request,
|
||||
PKPApplication::ROUTE_API,
|
||||
$this->context->getData('urlPath'),
|
||||
'emailTemplates/' . $item->getData('key')
|
||||
);
|
||||
break;
|
||||
default:
|
||||
$output[$prop] = $item->getData($prop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$output = $this->schemaService->addMissingMultilingualValues($this->schema, $output, $this->context->getSupportedFormLocales());
|
||||
|
||||
ksort($output);
|
||||
|
||||
return $this->withExtensions($output, $item);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user