diff --git a/.idea/jsonSchemas.xml b/.idea/jsonSchemas.xml index 46e872e544..241e8f24fc 100644 --- a/.idea/jsonSchemas.xml +++ b/.idea/jsonSchemas.xml @@ -550,6 +550,25 @@ + + + + + + + diff --git a/.vscode/settings.json b/.vscode/settings.json index 5be8a352d8..333c511351 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -256,6 +256,12 @@ ], "url": "./schema/metadata/app/defaultDashboardOptions.json" }, + { + "fileMatch": [ + "*/Resources/metadata/app/emailTemplate.json" + ], + "url": "./schema/metadata/app/emailTemplate.json" + }, { "fileMatch": [ "*/Resources/metadata/app/entityManagerParams.json" diff --git a/application/Espo/Resources/metadata/app/emailTemplate.json b/application/Espo/Resources/metadata/app/emailTemplate.json new file mode 100644 index 0000000000..c25787452e --- /dev/null +++ b/application/Espo/Resources/metadata/app/emailTemplate.json @@ -0,0 +1,16 @@ +{ + "placeholders": { + "today": { + "className": "Espo\\Tools\\EmailTemplate\\Placeholders\\Today", + "order": 0 + }, + "now": { + "className": "Espo\\Tools\\EmailTemplate\\Placeholders\\Now", + "order": 1 + }, + "currentYear": { + "className": "Espo\\Tools\\EmailTemplate\\Placeholders\\CurrentYear", + "order": 2 + } + } +} diff --git a/application/Espo/Resources/metadata/clientDefs/EmailTemplate.json b/application/Espo/Resources/metadata/clientDefs/EmailTemplate.json index 68986d3855..11ef0cdc92 100644 --- a/application/Espo/Resources/metadata/clientDefs/EmailTemplate.json +++ b/application/Espo/Resources/metadata/clientDefs/EmailTemplate.json @@ -31,6 +31,6 @@ "filterList": [ "actual" ], - "placeholderList": ["today", "now", "currentYear", "optOutUrl", "optOutLink"], + "placeholderList": ["optOutUrl", "optOutLink"], "iconClass": "fas fa-envelope-square" } diff --git a/application/Espo/Tools/EmailTemplate/Placeholder.php b/application/Espo/Tools/EmailTemplate/Placeholder.php new file mode 100644 index 0000000000..666f0f5d18 --- /dev/null +++ b/application/Espo/Tools/EmailTemplate/Placeholder.php @@ -0,0 +1,35 @@ +config->get('timeZone'))); + } + catch (Exception $e) { + throw new RuntimeException($e->getMessage()); + } + + return $now->format('Y'); + } +} diff --git a/application/Espo/Tools/EmailTemplate/Placeholders/Now.php b/application/Espo/Tools/EmailTemplate/Placeholders/Now.php new file mode 100644 index 0000000000..b55fc6ff13 --- /dev/null +++ b/application/Espo/Tools/EmailTemplate/Placeholders/Now.php @@ -0,0 +1,49 @@ +dateTime->getNowString(); + } +} diff --git a/application/Espo/Tools/EmailTemplate/Placeholders/Today.php b/application/Espo/Tools/EmailTemplate/Placeholders/Today.php new file mode 100644 index 0000000000..9128ccff2a --- /dev/null +++ b/application/Espo/Tools/EmailTemplate/Placeholders/Today.php @@ -0,0 +1,49 @@ +dateTime->getTodayString(); + } +} diff --git a/application/Espo/Tools/EmailTemplate/PlaceholdersProvider.php b/application/Espo/Tools/EmailTemplate/PlaceholdersProvider.php new file mode 100644 index 0000000000..1c66b22cf5 --- /dev/null +++ b/application/Espo/Tools/EmailTemplate/PlaceholdersProvider.php @@ -0,0 +1,68 @@ +metadata->get("app.emailTemplate.placeholders") ?? []; + + /** @var string[] $list */ + $list = array_keys($defs); + + usort($list, function ($a, $b) use ($defs) { + $o1 = $defs[$a]['order'] ?? 0; + $o2 = $defs[$b]['order'] ?? 0; + + return $o1 - $o2; + }); + + return array_map(function ($name) use ($defs) { + /** @var class-string $className */ + $className = $defs[$name]['className']; + + $placeholder = $this->injectableFactory->create($className); + + return [$name, $placeholder]; + }, $list); + } +} diff --git a/application/Espo/Tools/EmailTemplate/Processor.php b/application/Espo/Tools/EmailTemplate/Processor.php index 584abb967c..024576b8b5 100644 --- a/application/Espo/Tools/EmailTemplate/Processor.php +++ b/application/Espo/Tools/EmailTemplate/Processor.php @@ -43,7 +43,6 @@ use Espo\Core\Entities\Person; use Espo\Core\Htmlizer\HtmlizerFactory as HtmlizerFactory; use Espo\Core\Htmlizer\Htmlizer; use Espo\Core\Acl\GlobalRestriction; -use Espo\Core\Utils\DateTime as DateTimeUtil; use Espo\Entities\EmailTemplate; use Espo\Entities\User; use Espo\Entities\Attachment; @@ -51,9 +50,6 @@ use Espo\Entities\EmailAddress; use Espo\Repositories\EmailAddress as EmailAddressRepository; use Exception; -use DateTime; -use DateTimezone; -use RuntimeException; class Processor { @@ -68,7 +64,7 @@ class Processor private FileStorageManager $fileStorageManager, private User $user, private HtmlizerFactory $htmlizerFactory, - private DateTimeUtil $dateTime + private PlaceholdersProvider $placeholdersProvider ) {} public function process(EmailTemplate $template, Params $params, Data $data): Result @@ -223,23 +219,11 @@ class Processor ); } - /** @noinspection PhpUnusedParameterInspection */ private function processPlaceholders(string $text, Data $data): string { - try { - $now = new DateTime('now', new DateTimezone($this->config->get('timeZone'))); - } - catch (Exception $e) { - throw new RuntimeException($e->getMessage()); - } + foreach ($this->placeholdersProvider->get() as [$key, $placeholder]) { + $value = $placeholder->get($data); - $replaceData = [ - 'today' => $this->dateTime->getTodayString(), - 'now' => $this->dateTime->getNowString(), - 'currentYear' => $now->format('Y'), - ]; - - foreach ($replaceData as $key => $value) { $text = str_replace('{' . $key . '}', $value, $text); } diff --git a/client/src/views/email-template/record/panels/information.js b/client/src/views/email-template/record/panels/information.js index 4c327fe40a..1683cd05d2 100644 --- a/client/src/views/email-template/record/panels/information.js +++ b/client/src/views/email-template/record/panels/information.js @@ -33,7 +33,19 @@ define('views/email-template/record/panels/information', ['views/record/panels/s templateContent: '{{{infoText}}}', data: function () { - let placeholderList = this.getMetadata().get(['clientDefs', 'EmailTemplate', 'placeholderList']) || []; + const list2 = this.getMetadata().get(['clientDefs', 'EmailTemplate', 'placeholderList']) || []; + + const defs = this.getMetadata().get('app.emailTemplate.placeholders') || {}; + + const list1 = Object.keys(defs) + .sort((a, b) => { + const o1 = defs[a].order || 0; + const o2 = defs[b].order || 0; + + return o1 - o2; + }); + + const placeholderList = [...list1, ...list2]; if (!placeholderList.length) { return { @@ -41,9 +53,9 @@ define('views/email-template/record/panels/information', ['views/record/panels/s }; } - let $header = $('

').text(this.translate('Available placeholders', 'labels', 'EmailTemplate') + ':'); + const $header = $('

').text(this.translate('Available placeholders', 'labels', 'EmailTemplate') + ':'); - let $liList = placeholderList.map(item => { + const $liList = placeholderList.map(item => { return $('
  • ').append( $('').text('{' + item + '}'), ' – ', @@ -51,11 +63,11 @@ define('views/email-template/record/panels/information', ['views/record/panels/s ) }); - let $ul = $('
      ').append($liList); + const $ul = $('
        ').append($liList); - let $text = $('') + const $text = $('') .addClass('complex-text') - .append($header, $ul) + .append($header, $ul); return { infoText: $text[0].outerHTML, diff --git a/schema/metadata/app/emailTemplate.json b/schema/metadata/app/emailTemplate.json new file mode 100644 index 0000000000..fef6d27c00 --- /dev/null +++ b/schema/metadata/app/emailTemplate.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://www.espocrm.com/schema/metadata/app/emailTemplates.json", + "title": "app/databasePlatforms", + "description": "Email template definitions.", + "type": "object", + "properties": { + "placeholders": { + "description": "Placeholders.", + "additionalProperties": { + "description": "A placeholder.", + "type": "object", + "properties": { + "className": { + "type": "string", + "description": "A class. Should implement Espo\\Tools\\EmailTemplate\\Placeholder." + }, + "order": { + "type": "integer", + "description": "An order position." + } + } + } + } + } +}