email insert field

This commit is contained in:
Yuri Kuznetsov
2020-06-15 10:30:41 +03:00
parent d36e5cc0c1
commit b5e9f98fd1
8 changed files with 594 additions and 95 deletions

View File

@@ -230,4 +230,15 @@ class Email extends \Espo\Core\Controllers\Record
}
return $this->getRecordService()->moveToFolderByIdList($idList, $data->folderId);
}
public function getActionGetInsertFieldData($params, $data, $request)
{
if (!$this->getAcl()->checkScope('Email', 'create')) throw new Forbidden();
return $this->getServiceFactory()->create('EmailTemplate')->getInsertFieldData([
'parentId' => $request->get('parentId'),
'parentType' => $request->get('parentType'),
'to' => $request->get('to'),
]);
}
}

View File

@@ -195,4 +195,15 @@ class FieldManagerUtil
{
return $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field, $param]);
}
public function getEntityTypeAttributeList($entityType)
{
$attributeList = [];
foreach ($this->getEntityTypeFieldList($entityType) as $field) {
$attributeList = array_merge($attributeList, $this->getAttributeList($entityType, $field));
}
return $attributeList;
}
}

View File

@@ -84,6 +84,7 @@
"Reply": "Reply",
"Reply to All": "Reply to All",
"Forward": "Forward",
"Insert Field": "Insert Field",
"Original message": "Original message",
"Forwarded message": "Forwarded message",
"Email Accounts": "Personal Email Accounts",

View File

@@ -46,6 +46,7 @@ class EmailTemplate extends Record
$this->addDependency('language');
$this->addDependency('number');
$this->addDependency('htmlizerFactory');
$this->addDependency('fieldManagerUtil');
}
protected function getFileStorageManager()
@@ -85,7 +86,9 @@ class EmailTemplate extends Record
'lower' => $params['emailAddress']
])->findOne();
$entity = $this->getEntityManager()->getRepository('EmailAddress')->getEntityByAddress($params['emailAddress']);
$entity = $this->getEntityManager()->getRepository('EmailAddress')->getEntityByAddress($params['emailAddress'],
null, ['Contact', 'Lead', 'Account', 'User']
);
if ($entity) {
if ($entity instanceof Person) {
@@ -221,60 +224,20 @@ class EmailTemplate extends Record
if (in_array($attribute, $forbiddenAttributeList)) continue;
$value = $entity->get($attribute);
if (is_object($value)) {
continue;
if (is_object($value)) continue;
if (!$entity->getAttributeType($attribute)) continue;
$value = $this->formatAttributeValue($entity, $attribute);
if (is_null($value)) continue;
$variableName = $attribute;
if (!is_null($prefixLink)) {
$variableName = $prefixLink . '.' . $attribute;
}
$fieldType = $this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $attribute, 'type']);
if ($fieldType === 'enum') {
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else if ($fieldType === 'array' || $fieldType === 'multiEnum') {
$valueList = [];
if (is_array($value)) {
foreach ($value as $v) {
$valueList[] = $this->getLanguage()->translateOption($v, $attribute, $entity->getEntityType());
}
}
$value = implode(', ', $valueList);
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else {
$attributeType = $entity->getAttributeType($attribute);
if (!$attributeType) continue;
if ($attributeType == 'date') {
if ($value)
$value = $this->getDateTime()->convertSystemDate($value);
} else if ($attributeType == 'datetime') {
if ($value)
$value = $this->getDateTime()->convertSystemDateTime($value);
} else if ($attributeType == 'text') {
if (!is_string($value)) {
$value = '';
}
$value = nl2br($value);
} else if ($attributeType == 'float') {
if (is_float($value)) {
$decimalPlaces = 2;
if ($fieldType === 'currency') {
$decimalPlaces = $this->getConfig()->get('currencyDecimalPlaces');
}
$value = $this->getNumber()->format($value, $decimalPlaces);
}
} else if ($attributeType == 'int') {
if (is_int($value)) {
$value = $this->getNumber()->format($value);
}
}
}
if (is_string($value) || $value === null || is_scalar($value) || is_callable([$value, '__toString'])) {
$variableName = $attribute;
if (!is_null($prefixLink)) {
$variableName = $prefixLink . '.' . $attribute;
}
$text = str_replace('{' . $type . '.' . $variableName . '}', $value, $text);
}
$text = str_replace('{' . $type . '.' . $variableName . '}', $value, $text);
}
if (!$skipLinks) {
@@ -312,4 +275,148 @@ class EmailTemplate extends Record
return $text;
}
public function formatAttributeValue(Entity $entity, string $attribute) : ?string
{
$value = $entity->get($attribute);
$fieldType = $this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $attribute, 'type']);
$attributeType = $entity->getAttributeType($attribute);
if ($fieldType === 'enum') {
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else if ($fieldType === 'array' || $fieldType === 'multiEnum' || $fieldType === 'checklist') {
$valueList = [];
if (is_array($value)) {
foreach ($value as $v) {
$valueList[] = $this->getLanguage()->translateOption($v, $attribute, $entity->getEntityType());
}
}
$value = implode(', ', $valueList);
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else {
if ($attributeType == 'date') {
if ($value) {
$value = $this->getDateTime()->convertSystemDate($value);
}
} else if ($attributeType == 'datetime') {
if ($value) {
$value = $this->getDateTime()->convertSystemDateTime($value);
}
} else if ($attributeType == 'text') {
if (!is_string($value)) {
$value = '';
}
$value = nl2br($value);
} else if ($attributeType == 'float') {
if (is_float($value)) {
$decimalPlaces = 2;
if ($fieldType === 'currency') {
$decimalPlaces = $this->getConfig()->get('currencyDecimalPlaces');
}
$value = $this->getNumber()->format($value, $decimalPlaces);
}
} else if ($attributeType == 'int') {
if (is_int($value)) {
$value = $this->getNumber()->format($value);
}
}
}
if (!is_string($value) && is_scalar($value) || is_callable([$value, '__toString'])) {
$value = strval($value);
} else if ($value === null) {
$value = '';
}
if (!is_string($value)) return null;
return $value;
}
public function getInsertFieldData(array $params)
{
$to = $params['to'] ?? null;
$parentId = $params['parentId'] ?? null;
$parentType = $params['parentType'] ?? null;
$result = (object) [];
$emailTemplateService = $this->getServiceFactory()->create('EmailTemplate');
$dataList = [];
if ($parentId && $parentType) {
$e = $this->getEntityManager()->getEntity($parentType, $parentId);
if ($e && $this->getAcl()->check($e)) {
$dataList[] = [
'type' => 'parent',
'entity' => $e,
];
}
}
if ($to) {
$e = $this->getEntityManager()->getRepository('EmailAddress')->getEntityByAddress($to, null,
['Contact', 'Lead', 'Account']
);
if ($e && $e->getEntityType() !== 'User' && $this->getAcl()->check($e)) {
$dataList[] = [
'type' => 'to',
'entity' => $e,
];
}
}
$fm = $this->getInjection('fieldManagerUtil');
foreach ($dataList as $item) {
$type = $item['type'];
$e = $item['entity'];
$entityType = $e->getEntityType();
$recordService = $this->getServiceFactory()->create($entityType);
$recordService->prepareEntityForOutput($e);
$ignoreTypeList = ['image', 'file', 'map', 'wysiwyg', 'linkMultiple', 'attachmentMultiple', 'bool'];
foreach ($fm->getEntityTypeFieldList($entityType) as $field) {
$fieldType = $fm->getEntityTypeFieldParam($entityType, $field, 'type');
$fieldAttributeList = $fm->getAttributeList($entityType, $field);
if (
$fm->getEntityTypeFieldParam($entityType, $field, 'disabled') ||
$fm->getEntityTypeFieldParam($entityType, $field, 'directAccessDisabled') ||
$fm->getEntityTypeFieldParam($entityType, $field, 'templatePlaceholderDisabled') ||
in_array($fieldType, $ignoreTypeList)
) {
foreach ($fieldAttributeList as $a) {
$e->clear($a);
}
}
}
$attributeList = $fm->getEntityTypeAttributeList($entityType);
$values = (object) [];
foreach ($attributeList as $a) {
if (!$e->has($a)) continue;
$value = $emailTemplateService->formatAttributeValue($e, $a);
if ($value != '') {
$values->$a = $value;
}
}
$result->$type = (object) [
'entityType' => $e->getEntityType(),
'id' => $e->id,
'values' => $values,
'name' => $e->get('name'),
];
}
return $result;
}
}

View File

@@ -0,0 +1,91 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('field-language', [], function () {
var FieldLanguage = function (metadata, language) {
this.metadata = metadata;
this.language = language;
};
_.extend(FieldLanguage.prototype, {
metadata: null,
language: null,
translateAttribute: function (scope, name) {
var label = this.language.translate(name, 'fields', scope);
if (name.indexOf('Id') === name.length - 2) {
var baseField = name.substr(0, name.length - 2);
if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
label = this.language.translate(baseField, 'fields', scope) +
' (' + this.language.translate('id', 'fields') + ')';
}
} else if (name.indexOf('Name') === name.length - 4) {
var baseField = name.substr(0, name.length - 4);
if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
label = this.language.translate(baseField, 'fields', scope) +
' (' + this.language.translate('name', 'fields') + ')';
}
} else if (name.indexOf('Type') === name.length - 4) {
var baseField = name.substr(0, name.length - 4);
if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
label = this.language.translate(baseField, 'fields', scope) +
' (' + this.language.translate('type', 'fields') + ')';
}
}
if (name.indexOf('Ids') === name.length - 3) {
var baseField = name.substr(0, name.length - 3);
if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
label = this.language.translate(baseField, 'fields', scope) +
' (' + this.language.translate('ids', 'fields') + ')';
}
} else if (name.indexOf('Names') === name.length - 5) {
var baseField = name.substr(0, name.length - 5);
if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
label = this.language.translate(baseField, 'fields', scope) +
' (' + this.language.translate('names', 'fields') + ')';
}
} else if (name.indexOf('Types') === name.length - 5) {
var baseField = name.substr(0, name.length - 5);
if (this.metadata.get(['entityDefs', scope, 'fields', baseField])) {
label = this.language.translate(baseField, 'fields', scope) +
' (' + this.language.translate('types', 'fields') + ')';
}
}
return label;
},
});
return FieldLanguage;
});

View File

@@ -25,7 +25,8 @@
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/email/fields/body', 'views/fields/wysiwyg', function (Dep) {
define('views/email/fields/body', 'views/fields/wysiwyg', function (Dep) {
return Dep.extend({
@@ -33,7 +34,84 @@ Espo.define('views/email/fields/body', 'views/fields/wysiwyg', function (Dep) {
getAttributeList: function () {
return ['body', 'bodyPlain'];
}
},
setupToolbar: function () {
Dep.prototype.setupToolbar.call(this);
this.toolbar.unshift([
'insert-field',
['insert-field']
]);
this.buttons['insert-field'] = function (context) {
var ui = $.summernote.ui;
var button = ui.button({
contents: '<i class="fas fa-plus"></i>',
tooltip: this.translate('Insert Field', 'labels', 'Email'),
click: function () {
this.showInsertFieldModal();
}.bind(this)
});
return button.render();
}.bind(this);
this.listenTo(this.model, 'change', function (m) {
if (!this.isRendered()) return;
if (m.hasChanged('parentId') || m.hasChanged('to')) {
this.controInsertFieldButton();
}
}, this);
},
afterRender: function () {
Dep.prototype.afterRender.call(this);
this.controInsertFieldButton();
},
controInsertFieldButton: function () {
var $b = this.$el.find('.note-insert-field > button');
if (this.model.get('to') && this.model.get('to').length || this.model.get('parentId')) {
$b.removeAttr('disabled').removeClass('disabled');
} else {
$b.attr('disabled', 'disabled').addClass('disabled');
}
},
showInsertFieldModal: function () {
var to = this.model.get('to');
if (to) {
to = to.split(';')[0].trim();
}
var parentId = this.model.get('parentId');
var parentType = this.model.get('parentType');
Espo.Ui.notify(this.translate('loading', 'messages'));
this.createView('insertFieldDialog', 'views/email/modals/insert-field', {
parentId: parentId,
parentType: parentType,
to: to,
}, function (view) {
view.render();
Espo.Ui.notify();
this.listenToOnce(view, 'insert', function (string) {
if (this.$summernote) {
if (~string.indexOf('\n')) {
string = string.replace(/(?:\r\n|\r|\n)/g, '<br>');
var html = '<p>' + string + '</p>';
this.$summernote.summernote('editor.pasteHTML', html);
} else {
this.$summernote.summernote('editor.insertText', string);
}
}
this.clearView('insertFieldDialog');
}, this);
});
},
});
});

View File

@@ -0,0 +1,197 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
define('views/email/modals/insert-field', ['views/modal', 'field-language'], function (Dep, FieldLanguage) {
return Dep.extend({
backdrop: true,
templateContent: `
{{#each viewObject.dataList}}
<div class="margin-bottom">
<h5>{{label}}: {{translate entityType category='scopeNames'}}</h5>
</div>
<ul class="list-group no-side-margin">
{{#each dataList}}
<li class="list-group-item clearfix">
<a href="javascript:"
data-action="insert" class="text-bold" data-name="{{name}}" data-type="{{../type}}">
{{label}}
</a>
<div class="pull-right"
style="width: 50%; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;">
{{valuePreview}}
</div>
</li>
{{/each}}
</ul>
{{/each}}
{{#unless viewObject.dataList.length}}
{{translate 'No Data'}}
{{/unless}}
`,
events: {
'click [data-action="insert"]': function (e) {
var name = $(e.currentTarget).data('name');
var type = $(e.currentTarget).data('type');
this.insert(type, name);
},
},
setup: function () {
Dep.prototype.setup.call(this);
this.headerHtml = this.translate('Insert Field', 'labels', 'Email');
this.fieldLanguage = new FieldLanguage(this.getMetadata(), this.getLanguage());
this.wait(
Espo.Ajax.getRequest('Email/action/getInsertFieldData', {
parentId: this.options.parentId,
parentType: this.options.parentType,
to: this.options.to,
}).then(
function (fetchedData) {
this.fetchedData = fetchedData;
this.prepareData();
}.bind(this)
)
);
},
prepareData: function () {
this.dataList = [];
var fetchedData = this.fetchedData;
var typeList = ['parent', 'to'];
typeList.forEach(function (type) {
if (!fetchedData[type]) return;
var entityType = fetchedData[type].entityType;
var id = fetchedData[type].id;
for (var it of this.dataList) {
if (it.id === id && it.entityType === entityType) {
return;
}
}
var dataList = this.prepareDisplayValueList(fetchedData[type].entityType, fetchedData[type].values);
if (!dataList.length) return;
this.dataList.push({
type: type,
entityType: entityType,
id: id,
name: fetchedData[type].name,
dataList: dataList,
label: this.translate(type, 'fields', 'Email'),
});
}, this);
},
prepareDisplayValueList: function (scope, values) {
var list = [];
var attributeList = Object.keys(values);
var labels = {};
attributeList.forEach(function (item) {
labels[item] = this.fieldLanguage.translateAttribute(scope, item);
}, this);
attributeList = attributeList.sort(function (v1, v2) {
return labels[v1].localeCompare(labels[v2]);
}.bind(this));
var ignoreAttributeList = ['id', 'modifiedAt', 'modifiedByName'];
var fm = this.getFieldManager();
fm.getEntityTypeFieldList(scope).forEach(function (field) {
var type = this.getMetadata().get(['entityDefs', scope, 'fields', field, 'type']);
if (~['link', 'linkOne', 'image', 'filed', 'linkParent'].indexOf(type)) {
ignoreAttributeList.push(field + 'Id');
}
if (type === 'linkParent') {
ignoreAttributeList.push(field + 'Type');
}
}, this);
attributeList.forEach(function (item) {
if (~ignoreAttributeList.indexOf(item)) return;
var value = values[item];
if (value === null || value === '') return;
if (typeof value == 'boolean') return;
if (Array.isArray(value)) {
for (let v in value) {
if (typeof v !== 'string') return;
}
value = value.split(', ');
};
value = this.getHelper().sanitizeHtml(value);
var valuePreview = value.replace(/<br( \/)?>/gm, ' ');
value = value.replace(/(?:\r\n|\r|\n)/g, '');
value = value.replace(/<br( \/)?>/gm, '\n');
list.push({
name: item,
label: labels[item],
value: value,
valuePreview: valuePreview,
});
}, this);
return list;
},
insert: function (type, name) {
for (var g of this.dataList) {
if (g.type !== type) continue;
for (var i of g.dataList) {
if (i.name !== name) continue;
this.trigger('insert', i.value);
break;
}
break;
}
this.close();
},
});
});

View File

@@ -63,46 +63,7 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
this.useIframe = this.params.useIframe || this.useIframe;
this.toolbar = this.params.toolbar || this.toolbar || [
['style', ['style']],
['style', ['bold', 'italic', 'underline', 'clear']],
['fontsize', ['fontsize']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']],
['table', ['table', 'espoLink', 'espoImage', 'hr']],
['misc', ['codeview', 'fullscreen']]
];
this.buttons = {};
if (!this.params.toolbar) {
if (this.params.attachmentField) {
this.toolbar.push([
'attachment',
['attachment']
]);
var AttachmentButton = function (context) {
var ui = $.summernote.ui;
var button = ui.button({
contents: '<i class="fas fa-paperclip"></i>',
tooltip: this.translate('Attach File'),
click: function () {
this.attachFile();
this.listenToOnce(this.model, 'attachment-uploaded:attachments', function () {
if (this.mode === 'edit') {
Espo.Ui.success(this.translate('Attached'));
}
}, this);
}.bind(this)
});
return button.render();
}.bind(this);
this.buttons['attachment'] = AttachmentButton;
}
}
this.setupToolbar();
this.listenTo(this.model, 'change:isHtml', function (model, value, o) {
if (o.ui && this.mode == 'edit') {
@@ -164,6 +125,48 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
},
},
setupToolbar: function () {
this.buttons = {};
this.toolbar = this.params.toolbar || this.toolbar || [
['style', ['style']],
['style', ['bold', 'italic', 'underline', 'clear']],
['fontsize', ['fontsize']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['height', ['height']],
['table', ['table', 'espoLink', 'espoImage', 'hr']],
['misc', ['codeview', 'fullscreen']]
];
if (!this.params.toolbar) {
if (this.params.attachmentField) {
this.toolbar.push([
'attachment',
['attachment']
]);
var AttachmentButton = function (context) {
var ui = $.summernote.ui;
var button = ui.button({
contents: '<i class="fas fa-paperclip"></i>',
tooltip: this.translate('Attach File'),
click: function () {
this.attachFile();
this.listenToOnce(this.model, 'attachment-uploaded:attachments', function () {
if (this.mode === 'edit') {
Espo.Ui.success(this.translate('Attached'));
}
}, this);
}.bind(this)
});
return button.render();
}.bind(this);
this.buttons['attachment'] = AttachmentButton;
}
}
},
isPlain: function () {
return this.model.has('isHtml') && !this.model.get('isHtml');
},