diff --git a/application/Espo/Controllers/App.php b/application/Espo/Controllers/App.php index cf06894f5b..a817e0fc34 100644 --- a/application/Espo/Controllers/App.php +++ b/application/Espo/Controllers/App.php @@ -47,8 +47,21 @@ class App extends \Espo\Core\Controllers\Base $user->loadLinkMultipleField('accounts'); } + $userData = $user->getValues(); + + $emailAddressList = []; + foreach ($user->get('emailAddresses') as $emailAddress) { + if ($emailAddress->get('invalid')) continue; + if ($user->get('emailAddrses') === $emailAddress->get('name')) continue; + $emailAddressList[] = $emailAddress->get('name'); + } + if ($user->get('emailAddrses')) { + array_unshift($emailAddressList, $user->get('emailAddrses')); + } + $userData['emailAddressList'] = $emailAddressList; + return array( - 'user' => $user->getValues(), + 'user' => $userData, 'acl' => $this->getAcl()->getMap(), 'preferences' => $preferences, 'token' => $this->getUser()->get('token') diff --git a/application/Espo/Controllers/Email.php b/application/Espo/Controllers/Email.php index 44a503ebcc..f562a21018 100644 --- a/application/Espo/Controllers/Email.php +++ b/application/Espo/Controllers/Email.php @@ -32,6 +32,7 @@ namespace Espo\Controllers; use \Espo\Core\Exceptions\BadRequest; use \Espo\Core\Exceptions\Forbidden; use \Espo\Core\Exceptions\Error; +use \Espo\Core\Exceptions\NotFound; class Email extends \Espo\Core\Controllers\Record { @@ -51,22 +52,48 @@ class Email extends \Espo\Core\Controllers\Record throw new BadRequest(); } + if (!$this->getAcl()->checkScope('Email')) { + throw new Forbidden(); + } + if (is_null($data['password'])) { if ($data['type'] == 'preferences') { - if (!$this->getUser()->isAdmin() && $data['id'] != $this->getUser()->id) { + if (!$this->getUser()->isAdmin() && $data['id'] !== $this->getUser()->id) { throw new Forbidden(); } $preferences = $this->getEntityManager()->getEntity('Preferences', $data['id']); if (!$preferences) { - throw new Error(); + throw new NotFound(); } - $data['password'] = $this->getContainer()->get('crypt')->decrypt($preferences->get('smtpPassword')); + if (is_null($data['password'])) { + $data['password'] = $this->getContainer()->get('crypt')->decrypt($preferences->get('smtpPassword')); + } + } else if ($data['type'] == 'emailAccount') { + if (!$this->getAcl()->checkScope('EmailAccount')) { + throw new Forbidden(); + } + if (!empty($data['id'])) { + $emailAccount = $this->getEntityManager()->getEntity('EmailAccount', $data['id']); + if (!$emailAccount) { + throw new NotFound(); + } + if (!$this->getUser()->isAdmin()) { + if ($emailAccount->get('assigniedUserId') !== $this->getUser()->id) { + throw new Forbidden(); + } + } + if (is_null($data['password'])) { + $data['password'] = $this->getContainer()->get('crypt')->decrypt($emailAccount->get('smtpPassword')); + } + } } else { if (!$this->getUser()->isAdmin()) { throw new Forbidden(); } - $data['password'] = $this->getConfig()->get('smtpPassword'); + if (is_null($data['password'])) { + $data['password'] = $this->getConfig()->get('smtpPassword'); + } } } diff --git a/application/Espo/Repositories/Email.php b/application/Espo/Repositories/Email.php index fd5d4ac755..81db3e674f 100644 --- a/application/Espo/Repositories/Email.php +++ b/application/Espo/Repositories/Email.php @@ -153,6 +153,7 @@ class Email extends \Espo\Core\ORM\Repositories\RDB if (!empty($ids)) { $entity->set('fromEmailAddressId', $ids[0]); $this->addUserByEmailAddressId($entity, $ids[0], true); + $entity->setLinkMultipleColumn('users', 'isRead', $ids[0], true); if (!$entity->get('sentById')) { $user = $this->getEntityManager()->getRepository('EmailAddress')->getEntityByAddressId($entity->get('fromEmailAddressId'), 'User'); diff --git a/application/Espo/Resources/i18n/en_US/EmailAccount.json b/application/Espo/Resources/i18n/en_US/EmailAccount.json index 81d1ca22f7..f0504dc609 100644 --- a/application/Espo/Resources/i18n/en_US/EmailAccount.json +++ b/application/Espo/Resources/i18n/en_US/EmailAccount.json @@ -13,7 +13,14 @@ "sentFolder": "Sent Folder", "storeSentEmails": "Store Sent Emails", "keepFetchedEmailsUnread": "Keep Fetched Emails Unread", - "emailFolder": "Put in Folder" + "emailFolder": "Put in Folder", + "useSmtp": "Use SMTP", + "smtpHost": "SMTP Host", + "smtpPort": "SMTP Port", + "smtpAuth": "SMTP Auth", + "smtpSecurity": "SMTP Security", + "smtpUsername": "SMTP Username", + "smtpPassword": "SMTP Password" }, "links": { "filters": "Filters", @@ -29,7 +36,9 @@ "Create EmailAccount": "Create Email Account", "IMAP": "IMAP", "Main": "Main", - "Test Connection": "Test Connection" + "Test Connection": "Test Connection", + "Send Test Email": "Send Test Email", + "SMTP": "SMTP" }, "messages": { "couldNotConnectToImap": "Could not connect to IMAP server", diff --git a/application/Espo/Resources/layouts/EmailAccount/detail.json b/application/Espo/Resources/layouts/EmailAccount/detail.json index 57a0604dea..fb6fa58370 100644 --- a/application/Espo/Resources/layouts/EmailAccount/detail.json +++ b/application/Espo/Resources/layouts/EmailAccount/detail.json @@ -34,7 +34,20 @@ false, {"name":"sentFolder"} ], [ - {"name": "testConnection", "customLabel": null, "view": "EmailAccount.Fields.TestConnection"}, false + {"name": "testConnection", "customLabel": null, "view": "views/email-account/fields/test-connection"}, false + ] + ] + }, + { + "label":"SMTP", + "rows": [ + [{"name": "useSmtp"}, false], + [{"name": "smtpHost"}, {"name": "smtpPort"}], + [{"name": "smtpAuth"}, {"name": "smtpSecurity"}], + [{"name": "smtpUsername"}, false], + [{"name": "smtpPassword"}, false], + [ + {"name": "smtpTestSend", "customLabel": null, "view": "views/email-account/fields/test-send"}, false ] ] } diff --git a/application/Espo/Resources/layouts/Preferences/detail.json b/application/Espo/Resources/layouts/Preferences/detail.json index 4efe2383fd..416c29b587 100644 --- a/application/Espo/Resources/layouts/Preferences/detail.json +++ b/application/Espo/Resources/layouts/Preferences/detail.json @@ -19,7 +19,7 @@ ], [{"name": "smtpServer"}, {"name": "smtpPort"}], [{"name": "smtpAuth"}, {"name": "smtpSecurity"}], - [{"name": "smtpUsername"}, {"name": "testSend", "customLabel": null, "view": "Preferences.Fields.TestSend"}], + [{"name": "smtpUsername"}, {"name": "testSend", "customLabel": null, "view": "views/preferences/fields/test-send"}], [{"name": "smtpPassword"}, false] ] }, diff --git a/application/Espo/Resources/metadata/entityDefs/EmailAccount.json b/application/Espo/Resources/metadata/entityDefs/EmailAccount.json index d43679fff6..b27d584671 100644 --- a/application/Espo/Resources/metadata/entityDefs/EmailAccount.json +++ b/application/Espo/Resources/metadata/entityDefs/EmailAccount.json @@ -79,6 +79,33 @@ "type": "link", "readOnly": true }, + "useSmtp": { + "type": "bool" + }, + "smtpHost": { + "type": "varchar" + }, + "smtpPort": { + "type": "int", + "min": 0, + "max": 9999, + "default": 25 + }, + "smtpAuth": { + "type": "bool", + "default": false + }, + "smtpSecurity": { + "type": "enum", + "options": ["", "SSL", "TLS"] + }, + "smtpUsername": { + "type": "varchar", + "required": true + }, + "smtpPassword": { + "type": "password" + }, "createdBy": { "type": "link", "readOnly": true diff --git a/application/Espo/Services/Email.php b/application/Espo/Services/Email.php index e888414048..8dd68c917f 100644 --- a/application/Espo/Services/Email.php +++ b/application/Espo/Services/Email.php @@ -86,19 +86,39 @@ class Email extends Record { $emailSender = $this->getMailSender(); - if (strtolower($this->getUser()->get('emailAddress')) == strtolower($entity->get('from'))) { - $smtpParams = $this->getPreferences()->getSmtpParams(); - if (array_key_exists('password', $smtpParams)) { - $smtpParams['password'] = $this->getCrypt()->decrypt($smtpParams['password']); + $userAddressList = []; + foreach ($this->getUser()->get('emailAddresses') as $ea) { + $userAddressList[] = $ea->get('lower'); + } + + $primaryUserAddress = strtolower($this->getUser()->get('emailAddress')); + $fromAddress = strtolower($entity->get('from')); + + if (empty($fromAddress)) { + throw new Error(); + } + + $smtpParams = null; + if (in_array($fromAddress, $userAddressList)) { + if ($primaryUserAddress === $fromAddress) { + $smtpParams = $this->getPreferences()->getSmtpParams(); + } + if (!$smtpParams) { + $smtpParams = $this->getSmtpParamsFromEmailAccount($entity->get('from'), $this->getUser()->id); } if ($smtpParams) { + if (array_key_exists('password', $smtpParams)) { + $smtpParams['password'] = $this->getCrypt()->decrypt($smtpParams['password']); + } $smtpParams['fromName'] = $this->getUser()->get('name'); $emailSender->useSmtp($smtpParams); } - } else { + } + + if (!$smtpParams && $fromAddress === strtolower($this->getConfig()->get('outboundEmailFromAddress'))) { if (!$this->getConfig()->get('outboundEmailIsShared')) { - throw new Error('Can not use system smtp. outboundEmailIsShared is false.'); + throw new Error('Can not use system smtp. System SMTP is not shared.'); } $emailSender->setParams(array( 'fromName' => $this->getUser()->get('name') @@ -108,7 +128,6 @@ class Email extends Record $params = array(); $parent = null; - if ($entity->get('parentType') && $entity->get('parentId')) { $parent = $this->getEntityManager()->getEntity($entity->get('parentType'), $entity->get('parentId')); if ($parent) { @@ -160,6 +179,31 @@ class Email extends Record $this->getEntityManager()->saveEntity($entity); } + protected function getSmtpParamsFromEmailAccount($address, $userId) + { + $emailAccount = $this->getEntityManager()->getRepository('EmailAccount')->where([ + 'emailAddress' => $address, + 'assignedUserId' => $userId, + 'active' => true, + 'useSmtp' => true + ])->findOne(); + + if (!$emailAccount) return; + + $smtpParams = array(); + $smtpParams['server'] = $emailAccount->get('smtpHost'); + if ($smtpParams['server']) { + $smtpParams['port'] = $emailAccount->get('smtpPort'); + $smtpParams['auth'] = $emailAccount->get('smtpAuth'); + $smtpParams['security'] = $emailAccount->get('smtpSecurity'); + $smtpParams['username'] = $emailAccount->get('smtpUsername'); + $smtpParams['password'] = $emailAccount->get('smtpPassword'); + return $smtpParams; + } + + return; + } + protected function getStreamService() { if (empty($this->streamService)) { diff --git a/application/Espo/Services/EmailAccount.php b/application/Espo/Services/EmailAccount.php index 2dfc916f61..6650795ad9 100644 --- a/application/Espo/Services/EmailAccount.php +++ b/application/Espo/Services/EmailAccount.php @@ -37,7 +37,7 @@ use \Zend\Mail\Storage; class EmailAccount extends Record { - protected $internalAttributeList = ['password']; + protected $internalAttributeList = ['password', 'smtpPassword']; protected $readOnlyAttributeList= ['fetchData']; @@ -65,6 +65,9 @@ class EmailAccount extends Record if (array_key_exists('password', $data)) { $data['password'] = $this->getCrypt()->encrypt($data['password']); } + if (array_key_exists('smtpPassword', $data)) { + $data['smtpPassword'] = $this->getCrypt()->encrypt($data['smtpPassword']); + } } public function getFolders($params) diff --git a/client/src/views/email-account/fields/test-send.js b/client/src/views/email-account/fields/test-send.js new file mode 100644 index 0000000000..94df45e113 --- /dev/null +++ b/client/src/views/email-account/fields/test-send.js @@ -0,0 +1,70 @@ +/************************************************************************ + * This file is part of EspoCRM. + * + * EspoCRM - Open Source CRM application. + * Copyright (C) 2014-2015 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko + * Website: http://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. + ************************************************************************/ + +Espo.define('views/email-account/fields/test-send', 'views/outbound-email/fields/test-send', function (Dep) { + + return Dep.extend({ + + checkAvailability: function () { + if (this.model.get('smtpHost')) { + this.$el.find('button').removeClass('hidden'); + } else { + this.$el.find('button').addClass('hidden'); + } + }, + + afterRender: function () { + this.checkAvailability(); + + this.stopListening(this.model, 'change:smtpHost'); + this.listenTo(this.model, 'change:smtpHost', function () { + this.checkAvailability(); + }, this); + }, + + getSmtpData: function () { + var data = { + 'server': this.model.get('smtpHost'), + 'port': this.model.get('smtpPort'), + 'auth': this.model.get('smtpAuth'), + 'security': this.model.get('smtpSecurity'), + 'username': this.model.get('smtpUsername'), + 'password': this.model.get('smtpPassword') || null, + 'fromName': this.getUser().get('name'), + 'fromAddress': this.model.get('emailAddress'), + 'type': 'emailAccount', + 'id': this.model.id + }; + return data; + }, + + + }); + +}); + diff --git a/client/src/views/email-account/record/detail.js b/client/src/views/email-account/record/detail.js index 4cda3a0008..e98b5a49b2 100644 --- a/client/src/views/email-account/record/detail.js +++ b/client/src/views/email-account/record/detail.js @@ -32,7 +32,9 @@ Espo.define('views/email-account/record/detail', 'views/record/detail', function afterRender: function () { Dep.prototype.afterRender.call(this); + this.initSslFieldListening(); + this.initSmtpFieldsControl(); if (this.wasFetched()) { this.setFieldReadOnly('fetchSince'); @@ -64,7 +66,52 @@ Espo.define('views/email-account/record/detail', 'views/record/detail', function this.model.set('port', '143'); } }, this); - } + }, + + initSmtpFieldsControl: function () { + this.controlSmtpFields(); + this.listenTo(this.model, 'change:useSmtp', this.controlSmtpFields, this); + this.listenTo(this.model, 'change:smtpAuth', this.controlSmtpAuthField, this); + }, + + controlSmtpFields: function () { + if (this.model.get('useSmtp')) { + this.showField('smtpHost'); + this.showField('smtpPort'); + this.showField('smtpAuth'); + this.showField('smtpSecurity'); + this.showField('smtpTestSend'); + + this.setFieldRequired('smtpHost'); + this.setFieldRequired('smtpPort'); + + this.controlSmtpAuthField(); + } else { + this.hideField('smtpHost'); + this.hideField('smtpPort'); + this.hideField('smtpAuth'); + this.hideField('smtpUsername'); + this.hideField('smtpPassword'); + this.hideField('smtpSecurity'); + this.hideField('smtpTestSend'); + + this.setFieldNotRequired('smtpHost'); + this.setFieldNotRequired('smtpPort'); + this.setFieldNotRequired('smtpUsername'); + } + }, + + controlSmtpAuthField: function () { + if (this.model.get('smtpAuth')) { + this.showField('smtpUsername'); + this.showField('smtpPassword'); + this.setFieldRequired('smtpUsername'); + } else { + this.hideField('smtpUsername'); + this.hideField('smtpPassword'); + this.setFieldNotRequired('smtpUsername'); + } + }, }); diff --git a/client/src/views/email-account/record/edit.js b/client/src/views/email-account/record/edit.js index 74692e64b4..8711518c49 100644 --- a/client/src/views/email-account/record/edit.js +++ b/client/src/views/email-account/record/edit.js @@ -34,6 +34,7 @@ Espo.define('views/email-account/record/edit', ['views/record/edit', 'views/emai Dep.prototype.afterRender.call(this); Detail.prototype.initSslFieldListening.call(this); + Detail.prototype.initSmtpFieldsControl.call(this); if (Detail.prototype.wasFetched.call(this)) { this.setFieldReadOnly('fetchSince'); @@ -47,6 +48,14 @@ Espo.define('views/email-account/record/edit', ['views/record/edit', 'views/emai fieldView.render(); } } + }, + + controlSmtpFields: function () { + Detail.prototype.controlSmtpFields.call(this); + }, + + controlSmtpAuthField: function () { + Detail.prototype.controlSmtpAuthField.call(this); } }); diff --git a/client/src/views/email/fields/compose-from-address.js b/client/src/views/email/fields/compose-from-address.js index 4f41682075..5e37e4207f 100644 --- a/client/src/views/email/fields/compose-from-address.js +++ b/client/src/views/email/fields/compose-from-address.js @@ -42,12 +42,20 @@ Espo.define('views/email/fields/compose-from-address', 'views/fields/base', func Dep.prototype.setup.call(this); this.list = []; - if (this.getUser().get('emailAddress') && this.getPreferences().get('smtpServer')) { + /*if (this.getUser().get('emailAddress') && this.getPreferences().get('smtpServer')) { this.list.push(this.getUser().get('emailAddress')); - } + }*/ + + var emailAddressList = this.getUser().get('emailAddressList') || []; + emailAddressList.forEach(function (item) { + this.list.push(item); + }, this); if (this.getConfig().get('outboundEmailIsShared') && this.getConfig().get('outboundEmailFromAddress')) { - this.list.push(this.getConfig().get('outboundEmailFromAddress')); + var address = this.getConfig().get('outboundEmailFromAddress'); + if (!~this.list.indexOf(address)) { + this.list.push(this.getConfig().get('outboundEmailFromAddress')); + } } }, }); diff --git a/client/src/views/outbound-email/fields/test-send.js b/client/src/views/outbound-email/fields/test-send.js index e81c6b5c86..a068d6e4fa 100644 --- a/client/src/views/outbound-email/fields/test-send.js +++ b/client/src/views/outbound-email/fields/test-send.js @@ -26,7 +26,7 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -Espo.define('Views.OutboundEmail.Fields.TestSend', 'Views.Fields.Base', function (Dep) { +Espo.define('views/outbound-email/fields/test-send', 'views/fields/base', function (Dep) { return Dep.extend({ @@ -79,7 +79,7 @@ Espo.define('Views.OutboundEmail.Fields.TestSend', 'Views.Fields.Base', function send: function () { var data = this.getSmtpData(); - this.createView('popup', 'OutboundEmail.Modals.TestSend', { + this.createView('popup', 'views/outbound-email/modals/test-send', { emailAddress: this.getUser().get('emailAddress') }, function (view) { view.render(); diff --git a/client/src/views/preferences/fields/test-send.js b/client/src/views/preferences/fields/test-send.js index 0a8400fa20..78de013757 100644 --- a/client/src/views/preferences/fields/test-send.js +++ b/client/src/views/preferences/fields/test-send.js @@ -26,15 +26,15 @@ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word. ************************************************************************/ -Espo.define('Views.Preferences.Fields.TestSend', 'Views.OutboundEmail.Fields.TestSend', function (Dep) { +Espo.define('views/preferences/fields/test-send', 'views/outbound-email/fields/test-send', function (Dep) { return Dep.extend({ checkAvailability: function () { if (this.model.get('smtpServer')) { - this.$el.find('button').removeClass('disabled'); + this.$el.find('button').removeClass('hidden'); } else { - this.$el.find('button').addClass('disabled'); + this.$el.find('button').addClass('hidden'); } }, @@ -42,7 +42,6 @@ Espo.define('Views.Preferences.Fields.TestSend', 'Views.OutboundEmail.Fields.Tes this.checkAvailability(); this.stopListening(this.model, 'change:smtpServer'); - this.listenTo(this.model, 'change:smtpServer', function () { this.checkAvailability(); }, this);