505 lines
16 KiB
PHP
505 lines
16 KiB
PHP
<?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;
|
|
}
|
|
}
|