diff --git a/application/Espo/Services/EmailTemplate.php b/application/Espo/Services/EmailTemplate.php
index 58be8b929e..4325ec0900 100644
--- a/application/Espo/Services/EmailTemplate.php
+++ b/application/Espo/Services/EmailTemplate.php
@@ -165,10 +165,15 @@ class EmailTemplate extends Record
return $this->parseTemplate($emailTemplate, $params, $copyAttachments);
}
- protected function parseText($type, Entity $entity, $text)
+ protected function parseText($type, Entity $entity, $text, $skipLinks = false, $prefixLink = null)
{
- $fieldList = array_keys($entity->getFields());
+ $fieldList = array_keys($entity->getAttributes());
+
+ $forbidenAttributeList = $this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read');
+
foreach ($fieldList as $field) {
+ if (in_array($field, $forbidenAttributeList)) continue;
+
$value = $entity->get($field);
if (is_object($value)) {
continue;
@@ -195,9 +200,34 @@ class EmailTemplate extends Record
}
}
if (is_string($value) || $value === null || is_scalar($value) || is_callable([$value, '__toString'])) {
- $text = str_replace('{' . $type . '.' . $field . '}', $value, $text);
+ $variableName = $field;
+ if (!is_null($prefixLink)) {
+ $variableName = $prefixLink . '.' . $field;
+ }
+ $text = str_replace('{' . $type . '.' . $variableName . '}', $value, $text);
}
}
+
+ if (!$skipLinks) {
+ $relationDefs = $entity->getRelations();
+ foreach ($entity->getRelationList() as $relation) {
+ if (
+ !empty($relationDefs[$relation]['type'])
+ &&
+ ($entity->getRelationType($relation) === 'belongsTo' || $entity->getRelationType($relation) === 'belongsToParent')
+ ) {
+ $relatedEntity = $entity->get($relation);
+ if (!$relatedEntity) continue;
+ if ($this->getAcl()) {
+ if (!$this->getAcl()->check($relatedEntity, 'read')) continue;
+ }
+
+ $text = $this->parseText($type, $relatedEntity, $text, true, $relation);
+ }
+ }
+ }
+
+
return $text;
}
}
diff --git a/client/src/acl-manager.js b/client/src/acl-manager.js
index 62b2e5a523..cd27ee10aa 100644
--- a/client/src/acl-manager.js
+++ b/client/src/acl-manager.js
@@ -61,6 +61,7 @@ Espo.define('acl-manager', ['acl'], function (Acl) {
this.implementationHash = {};
this.forbiddenFieldsCache = {};
this.implementationClassMap = {};
+ this.forbiddenAttributesCache = {};
},
getImplementation: function (scope) {
@@ -220,6 +221,37 @@ Espo.define('acl-manager', ['acl'], function (Acl) {
this.forbiddenFieldsCache[key] = fieldList;
return fieldList;
+ },
+
+ getScopeForbiddenAttributeList: function (scope, action, thresholdLevel) {
+ action = action || 'read';
+ thresholdLevel = thresholdLevel || 'no';
+
+ var key = scope + '_' + action + '_' + thresholdLevel;
+ if (key in this.forbiddenAttributesCache) {
+ return this.forbiddenAttributesCache[key];
+ }
+
+ var levelList = this.fieldLevelList.slice(this.fieldLevelList.indexOf(thresholdLevel));
+
+ var fieldTableQuickAccess = this.data.fieldTableQuickAccess || {};
+ var scopeData = fieldTableQuickAccess[scope] || {};
+
+ var attributesData = scopeData.attributes || {};
+ var actionData = attributesData[action] || {};
+
+ var attributeList = [];
+ levelList.forEach(function (level) {
+ var list = actionData[level] || [];
+ list.forEach(function (attribute) {
+ if (~attributeList.indexOf(attribute)) return;
+ attributeList.push(attribute);
+ }, this);
+ }, this);
+
+ this.forbiddenAttributesCache[key] = attributeList;
+
+ return attributeList;
}
});
diff --git a/client/src/views/email-template/fields/insert-field.js b/client/src/views/email-template/fields/insert-field.js
index 966891b5e1..bf4120e3b7 100644
--- a/client/src/views/email-template/fields/insert-field.js
+++ b/client/src/views/email-template/fields/insert-field.js
@@ -56,19 +56,74 @@ Espo.define('views/email-template/fields/insert-field', 'views/fields/base', fun
return (defs[scope].entity && (defs[scope].tab || defs[scope].object));
});
+ this.translatedOptions = {};
+
var entityFields = {};
entityList.forEach(function (scope) {
+ this.translatedOptions[scope] = {};
+
var list = this.getFieldManager().getEntityAttributes(scope) || [];
+
+ var forbiddenList = this.getAcl().getScopeForbiddenAttributeList(scope);
+ list = list.filter(function (item) {
+ if (~forbiddenList.indexOf(item)) return;
+ return true;
+ }, this);
+
list.push('id');
if (this.getMetadata().get('entityDefs.' + scope + '.fields.name.type') == 'personName') {
list.unshift('name');
+ this.translatedOptions[scope]['name'] = this.translate('name', 'fields', scope);
};
entityFields[scope] = list.sort(function (v1, v2) {
return this.translate(v1, 'fields', scope).localeCompare(this.translate(v2, 'fields', scope));
}.bind(this));
+
+ entityFields[scope].forEach(function (item) {
+ this.translatedOptions[scope][item] = this.translate(item, 'fields', scope);
+ }, this);
+
+ var links = this.getMetadata().get('entityDefs.' + scope + '.links') || {};
+
+ var linkList = Object.keys(links).sort(function (v1, v2) {
+ return this.translate(v1, 'links', scope).localeCompare(this.translate(v2, 'links', scope));
+ }.bind(this));
+
+ linkList.forEach(function (link) {
+ var type = links[link].type
+ if (type != 'belongsTo') return;
+ var foreignScope = links[link].entity;
+ if (!foreignScope) return;
+
+ var attributeList = this.getFieldManager().getEntityAttributes(foreignScope) || [];
+
+ var forbiddenList = this.getAcl().getScopeForbiddenAttributeList(scope);
+ attributeList = attributeList.filter(function (item) {
+ if (~forbiddenList.indexOf(item)) return;
+ return true;
+ }, this);
+
+ attributeList.push('id');
+ if (this.getMetadata().get('entityDefs.' + foreignScope + '.fields.name.type') == 'personName') {
+ attributeList.unshift('name');
+ };
+
+ attributeList.sort(function (v1, v2) {
+ return this.translate(v1, 'fields', foreignScope).localeCompare(this.translate(v2, 'fields', foreignScope));
+ }.bind(this));
+
+ attributeList.forEach(function (item) {
+ entityFields[scope].push(link + '.' + item);
+
+ this.translatedOptions[scope][link + '.' + item] =
+ this.translate(link, 'links', scope) + '.' + this.translate(item, 'fields', foreignScope);
+ }, this);
+ }, this);
+
}, this);
entityFields['Person'] = ['name', 'firstName', 'lastName', 'salutationName', 'emailAddress', 'assignedUserName'];
+ this.translatedOptions['Person'] = {};
this.entityList = entityList;
this.entityFields = entityFields;
@@ -109,13 +164,21 @@ Espo.define('views/email-template/fields/insert-field', 'views/fields/base', fun
var entityType = this.$entityType.val();
var fieldList = this.entityFields[entityType];
- this.$field.empty();
+ this.$field.html('');
fieldList.forEach(function (field) {
- this.$field.append('');
+ this.$field.append('');
}, this);
},
+ translateItem: function (entityType, item) {
+ if (this.translatedOptions[entityType][item]) {
+ return this.translatedOptions[entityType][item];
+ } else {
+ return this.translate(item, 'fields');
+ }
+ },
+
insert: function (entityType, field) {
this.trigger('insert-field', {
entityType: entityType,
diff --git a/client/src/views/template/fields/variables.js b/client/src/views/template/fields/variables.js
index 381fd58e22..71c88deca7 100644
--- a/client/src/views/template/fields/variables.js
+++ b/client/src/views/template/fields/variables.js
@@ -69,6 +69,13 @@ Espo.define('views/template/fields/variables', 'views/fields/base', function (De
var entityType = this.model.get('entityType');
var attributeList = this.getFieldManager().getEntityAttributes(entityType) || [];
+
+ var forbiddenList = this.getAcl().getScopeForbiddenAttributeList(entityType);
+ attributeList = attributeList.filter(function (item) {
+ if (~forbiddenList.indexOf(item)) return;
+ return true;
+ }, this);
+
attributeList.push('id');
if (this.getMetadata().get('entityDefs.' + entityType + '.fields.name.type') == 'personName') {
attributeList.unshift('name');
@@ -100,6 +107,12 @@ Espo.define('views/template/fields/variables', 'views/fields/base', function (De
var attributeList = this.getFieldManager().getEntityAttributes(scope) || [];
+ var forbiddenList = this.getAcl().getScopeForbiddenAttributeList(scope);
+ attributeList = attributeList.filter(function (item) {
+ if (~forbiddenList.indexOf(item)) return;
+ return true;
+ }, this);
+
attributeList.push('id');
if (this.getMetadata().get('entityDefs.' + scope + '.fields.name.type') == 'personName') {
attributeList.unshift('name');