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."
+ }
+ }
+ }
+ }
+ }
+}