diff --git a/application/Espo/Core/CronManager.php b/application/Espo/Core/CronManager.php index 56daa61a79..322d1f6dcf 100644 --- a/application/Espo/Core/CronManager.php +++ b/application/Espo/Core/CronManager.php @@ -310,7 +310,7 @@ class CronManager } else { $this->runService($job); } - } catch (\Exception $e) { + } catch (\Throwable $e) { $isSuccess = false; if ($e->getCode() === -1) { $job->set('attempts', 0); diff --git a/application/Espo/Core/Utils/Cron/JobTask.php b/application/Espo/Core/Utils/Cron/JobTask.php index 2a588cd7e4..fcdb204b71 100644 --- a/application/Espo/Core/Utils/Cron/JobTask.php +++ b/application/Espo/Core/Utils/Cron/JobTask.php @@ -45,6 +45,10 @@ class JobTask extends \Spatie\Async\Task public function run() { $app = new \Espo\Core\Application(); - $app->runJob($this->jobId); + try { + $app->runJob($this->jobId); + } catch (\Throwable $e) { + $GLOBALS['log']->error("JobTask: Failed job run. Job id: ".$this->jobId.". Error details: ".$e->getMessage()); + } } } diff --git a/application/Espo/Core/defaults/systemConfig.php b/application/Espo/Core/defaults/systemConfig.php index 5b6bae877d..ce6bdfacea 100644 --- a/application/Espo/Core/defaults/systemConfig.php +++ b/application/Espo/Core/defaults/systemConfig.php @@ -183,6 +183,7 @@ return [ 'adminNotificationsCronIsNotConfigured', 'adminNotificationsNewExtensionVersion', 'leadCaptureAllowOrigin', + 'cronDisabled', ], 'superAdminItems' => [ 'jobMaxPortion', @@ -196,6 +197,9 @@ return [ 'daemonProcessTimeout', 'daemonMaxProcessNumber', 'adminPanelIframeUrl', + 'cronDisabled', + 'maintenanceMode', + 'siteUrl', ], 'userItems' => [ 'outboundEmailFromAddress', diff --git a/application/Espo/Entities/Email.php b/application/Espo/Entities/Email.php index a547916030..2598611240 100644 --- a/application/Espo/Entities/Email.php +++ b/application/Espo/Entities/Email.php @@ -51,25 +51,51 @@ class Email extends \Espo\Core\ORM\Entity return $this->has('fromString'); } - protected function _getFromName() - { - if (!$this->has('fromString')) return null; - - return \Espo\Services\Email::parseFromName($this->get('fromString')); - } - protected function _hasFromAddress() { return $this->has('fromString'); } + protected function _hasReplyToName() + { + return $this->has('replyToString'); + } + + protected function _hasReplyToAddress() + { + return $this->has('replyToString'); + } + + protected function _getFromName() + { + if (!$this->has('fromString')) return null; + return \Espo\Services\Email::parseFromName($this->get('fromString')); + } + protected function _getFromAddress() { if (!$this->has('fromString')) return null; - return \Espo\Services\Email::parseFromAddress($this->get('fromString')); } + protected function _getReplyToName() + { + if (!$this->has('replyToString')) return null; + $string = $this->get('replyToString'); + if (!$string) return null; + $string = trim(explode(';', $string)[0]); + return \Espo\Services\Email::parseFromName($string); + } + + protected function _getReplyToAddress() + { + if (!$this->has('replyToString')) return null; + $string = $this->get('replyToString'); + if (!$string) return null; + $string = trim(explode(';', $string)[0]); + return \Espo\Services\Email::parseFromAddress($string); + } + protected function _setIsRead($value) { $this->setValue('isRead', $value !== false); diff --git a/application/Espo/Hooks/Common/Formula.php b/application/Espo/Hooks/Common/Formula.php index f30eef41d0..076462e73d 100644 --- a/application/Espo/Hooks/Common/Formula.php +++ b/application/Espo/Hooks/Common/Formula.php @@ -66,7 +66,7 @@ class Formula extends \Espo\Core\Hooks\Base } } - $customScript = $this->getMetadata()->get(['formula', $entity->getEntityType(), 'beforeSaveCustomScript'], []); + $customScript = $this->getMetadata()->get(['formula', $entity->getEntityType(), 'beforeSaveCustomScript']); if ($customScript) { try { $this->getFormulaManager()->run($customScript, $entity, $variables); diff --git a/application/Espo/Resources/i18n/en_US/Email.json b/application/Espo/Resources/i18n/en_US/Email.json index 97b6a6b414..e5ed7de9d2 100644 --- a/application/Espo/Resources/i18n/en_US/Email.json +++ b/application/Espo/Resources/i18n/en_US/Email.json @@ -49,6 +49,8 @@ "fromName": "From Name", "fromString": "From String", "fromAddress": "From Address", + "replyToName": "Reply-To Name", + "replyToAddress": "Reply-To Address", "isSystem": "Is System" }, "links": { diff --git a/application/Espo/Resources/i18n/en_US/Settings.json b/application/Espo/Resources/i18n/en_US/Settings.json index 4dcbd73f9d..027369c82c 100644 --- a/application/Espo/Resources/i18n/en_US/Settings.json +++ b/application/Espo/Resources/i18n/en_US/Settings.json @@ -114,7 +114,9 @@ "jobPoolConcurrencyNumber": "Jobs Pool Concurrency Number", "daemonInterval": "Daemon Interval", "daemonMaxProcessNumber": "Daemon Max Process Number", - "daemonProcessTimeout": "Daemon Process Timeout" + "daemonProcessTimeout": "Daemon Process Timeout", + "cronDisabled": "Disable Cron", + "maintenanceMode": "Maintenance Mode" }, "options": { "weekStart": { @@ -178,11 +180,15 @@ "jobMaxPortion": "Max number of jobs processed per one execution.", "daemonInterval": "Interval between process cron runs in seconds.", "daemonMaxProcessNumber": "Max number of cron processes run simultaneously.", - "daemonProcessTimeout": "Max execution time (in seconds) allocated for a single cron process." + "daemonProcessTimeout": "Max execution time (in seconds) allocated for a single cron process.", + "cronDisabled": "Cron will not run.", + "maintenanceMode": "Only administrators will have access to the system." }, "labels": { "System": "System", "Locale": "Locale", + "Search": "Search", + "Misc": "Misc", "SMTP": "SMTP", "Configuration": "Configuration", "In-app Notifications": "In-app Notifications", diff --git a/application/Espo/Resources/layouts/Settings/settings.json b/application/Espo/Resources/layouts/Settings/settings.json index 57535a135d..5fd89f386f 100644 --- a/application/Espo/Resources/layouts/Settings/settings.json +++ b/application/Espo/Resources/layouts/Settings/settings.json @@ -3,11 +3,22 @@ "label": "System", "rows": [ [{"name": "useCache"}, {"name": "siteUrl"}], - [{"name": "exportDisabled"}, {"name": "globalSearchEntityList"}], - [{"name": "followCreatedEntities"}, {"name": "b2cMode"}], - [{"name": "aclStrictMode"}, {"name": "aclAllowDeleteCreated"}], - [{"name": "textFilterUseContainsForVarchar"}, {"name": "emailAddressIsOptedOutByDefault"}], - [{"name": "cleanupDeletedRecords"}, false] + [{"name": "b2cMode"}, {"name": "aclStrictMode"}], + [{"name": "maintenanceMode"}, {"name": "cronDisabled"}] + ] + }, + { + "label": "Search", + "rows": [ + [{"name": "textFilterUseContainsForVarchar"}, {"name": "globalSearchEntityList"}] + ] + }, + { + "label": "Misc", + "rows": [ + [{"name": "followCreatedEntities"}, {"name": "emailAddressIsOptedOutByDefault"}], + [{"name": "aclAllowDeleteCreated"}, {"name": "cleanupDeletedRecords"}], + [{"name": "exportDisabled"}, false] ] }, { diff --git a/application/Espo/Resources/metadata/entityDefs/Email.json b/application/Espo/Resources/metadata/entityDefs/Email.json index 5b7f57a592..20bd99e10b 100644 --- a/application/Espo/Resources/metadata/entityDefs/Email.json +++ b/application/Espo/Resources/metadata/entityDefs/Email.json @@ -33,6 +33,20 @@ "replyToString": { "type": "varchar" }, + "replyToName": { + "type": "varchar", + "readOnly": true, + "notStorable": true, + "textFilterDisabled": true, + "layoutFiltersDisabled": true + }, + "replyToAddress": { + "type": "varchar", + "readOnly": true, + "notStorable": true, + "textFilterDisabled": true, + "layoutFiltersDisabled": true + }, "addressNameMap": { "type": "jsonObject", "disabled": true, diff --git a/application/Espo/Resources/metadata/entityDefs/Settings.json b/application/Espo/Resources/metadata/entityDefs/Settings.json index bb4606dbc4..179eb890dd 100644 --- a/application/Espo/Resources/metadata/entityDefs/Settings.json +++ b/application/Espo/Resources/metadata/entityDefs/Settings.json @@ -536,6 +536,14 @@ "daemonProcessTimeout": { "type": "int", "tooltip": true + }, + "cronDisabled": { + "type": "bool", + "tooltip": true + }, + "maintenanceMode": { + "type": "bool", + "tooltip": true } } } diff --git a/application/Espo/Services/EmailNotification.php b/application/Espo/Services/EmailNotification.php index 22c39aa862..1fc2cdedc9 100644 --- a/application/Espo/Services/EmailNotification.php +++ b/application/Espo/Services/EmailNotification.php @@ -120,12 +120,11 @@ class EmailNotification extends \Espo\Core\Services\Base $assignerUser = $this->getEntityManager()->getEntity('User', $assignerUserId); $entity = $this->getEntityManager()->getEntity($entityType, $entityId); - - $this->loadParentNameFields($entity); - if (!$entity) return true; if (!$assignerUser) return true; + $this->loadParentNameFields($entity); + if (!$entity->hasLinkMultipleField('assignedUsers')) { if ($entity->get('assignedUserId') !== $userId) return true; } diff --git a/client/lib/bull.js b/client/lib/bull.js index ee5c22f5ca..e683d8f5bc 100644 --- a/client/lib/bull.js +++ b/client/lib/bull.js @@ -202,13 +202,13 @@ var Bull = Bull || {}; _wait: false, - expectedViews: null, + _waitViewList: null, optionsToPass: null, _nestedViewsFromLayoutLoaded: false, - _readyConditions: null, + _readyConditionList: null, _isRendered: false, @@ -243,12 +243,14 @@ var Bull = Bull || {}; this.nestedViews = {}; this._nestedViewDefs = {}; - if (this.expectedViews == null) { - this.expectedViews = []; + if (this._waitViewList == null) { + this._waitViewList = []; } - if (this._readyConditions == null) { - this._readyConditions = []; + this._waitPromiseCount = 0; + + if (this._readyConditionList == null) { + this._readyConditionList = []; } this.optionsToPass = this.options.optionsToPass || this.optionsToPass || []; @@ -279,6 +281,8 @@ var Bull = Bull || {}; this._layout = this.options._layout || this._layout; this.layoutData = this.options.layoutData || this.layoutData; + this._template = this.templateContent || this._template; + if (this._template != null && this._templator.compilable) { this._templateCompiled = this._templator.compileTemplate(this._template); } @@ -398,38 +402,46 @@ var Bull = Bull || {}; this._isRendered = false; this._isFullyRendered = false; - this._getHtml(function (html) { - if (this._isRenderCanceled) { - this._isRenderCanceled = false; - this._isBeingRendered = false; - return; - } - if (this.$el.size()) { - this.$el.html(html); - } else { - if (this.options.el) { - this.setElement(this.options.el); + return new Promise(function (resolve, reject) { + this._getHtml(function (html) { + if (this._isRenderCanceled) { + this._isRenderCanceled = false; + this._isBeingRendered = false; + reject(); + return; } - this.$el.html(html); - } - this._afterRender(); - if (typeof callback === 'function') { - callback(); - } + if (this.$el.size()) { + this.$el.html(html); + } else { + if (this.options.el) { + this.setElement(this.options.el); + } + this.$el.html(html); + } + this._afterRender(); + if (typeof callback === 'function') { + callback(); + } + resolve(this); + }.bind(this)); }.bind(this)); - }, + /** + * Re-render view. + */ reRender: function (force) { if (this.isRendered()) { - this.render(); + return this.render(); } else if (this.isBeingRendered()) { - this.once('after:render', function () { - this.render(); - }, this); + return new Promise(function (resolve, reject) { + this.once('after:render', function () { + this.render().then(resolve).catch(reject); + }, this); + }.bind(this)); } else { if (force) { - this.render(); + return this.render(); } } }, @@ -455,29 +467,23 @@ var Bull = Bull || {}; afterRender: function () {}, _tryReady: function () { - if (this.isReady) { - return; + if (this.isReady) return; + + if (this._wait) return; + + if (!this._nestedViewsFromLayoutLoaded) return; + + for (var i = 0; i < this._waitViewList.length; i++) { + if (!this.hasView(this._waitViewList[i])) return; } - if (this._wait) { - return; - } - if (!this._nestedViewsFromLayoutLoaded) { - return; - } - for (var i in this.expectedViews) { - if (!this.hasView(this.expectedViews[i])) { - return; - } - } - for (var i in this._readyConditions) { - if (typeof this._readyConditions[i] === 'function') { - if (!this._readyConditions[i]()) { - return; - } + + if (this._waitPromiseCount) return; + + for (var i = 0; i < this._readyConditionList.length; i++) { + if (typeof this._readyConditionList[i] === 'function') { + if (!this._readyConditionList[i]()) return; } else { - if (!this._readyConditions) { - return; - } + if (!this._readyConditionList) return; } } @@ -669,6 +675,7 @@ var Bull = Bull || {}; if (this.model || null) { data.model = this.model; } + data.viewObject = this; this.handleDataBeforeRender(data); this._getTemplate(function (template) { var html = this._renderer.render(template, data); @@ -802,27 +809,30 @@ var Bull = Bull || {}; * @param {Bool} wait True be default. Set false if no need parent view wait for nested view loaded. */ createView: function (key, viewName, options, callback, wait) { - wait = (typeof wait === 'undefined') ? true : wait; - var context = this; - if (wait) { - this.waitForView(key); - } - options = options || {}; - if (!options.el) { - options.el = this.getSelector() + ' [data-view="'+key+'"]'; - } - this._factory.create(viewName, options, function (view) { - var isSet = false; - if (this._isRendered || options.setViewBeforeCallback) { - this.setView(key, view); - isSet = true; + return new Promise(function (resolve) { + wait = (typeof wait === 'undefined') ? true : wait; + var context = this; + if (wait) { + this.waitForView(key); } - if (typeof callback === 'function') { - callback.call(context, view); - } - if (!this._isRendered && !options.setViewBeforeCallback && !isSet) { - this.setView(key, view); + options = options || {}; + if (!options.el) { + options.el = this.getSelector() + ' [data-view="'+key+'"]'; } + this._factory.create(viewName, options, function (view) { + var isSet = false; + if (this._isRendered || options.setViewBeforeCallback) { + this.setView(key, view); + isSet = true; + } + if (typeof callback === 'function') { + callback.call(context, view); + } + resolve(view); + if (!this._isRendered && !options.setViewBeforeCallback && !isSet) { + this.setView(key, view); + } + }.bind(this)); }.bind(this)); }, @@ -897,7 +907,7 @@ var Bull = Bull || {}; * @param {Function} or {Bool} */ addReadyCondition: function (condition) { - this._readyConditions.push(condition); + this._readyConditionList.push(condition); }, /** @@ -905,14 +915,34 @@ var Bull = Bull || {}; * @param {String} key */ waitForView: function (key) { - this.expectedViews.push(key); + this._waitViewList.push(key); }, /** - * Add wait condition is true is passed. Remove wait condition if false. - * @param {Bool} + * Make view wait for promise if Promise is passed as a parameter. + * Add wait condition if true is passed. Remove wait condition if false. + * @param wait Promise | Function | {Bool} */ wait: function (wait) { + if (typeof wait === 'object' && (wait instanceof Promise || typeof wait.then === 'function')) { + this._waitPromiseCount++; + wait.then(function () { + this._waitPromiseCount--; + this._tryReady(); + }.bind(this)); + return; + } + if (typeof wait == 'function') { + this._waitPromiseCount++; + var promise = new Promise(function (resolve) { + resolve(wait.call(this)); + }.bind(this)) + promise.then(function () { + this._waitPromiseCount--; + this._tryReady(); + }.bind(this)); + return promise; + } if (wait) { this._wait = true; } else { diff --git a/client/modules/crm/src/views/call/fields/contacts.js b/client/modules/crm/src/views/call/fields/contacts.js index 691d011bc8..598c96ab0b 100644 --- a/client/modules/crm/src/views/call/fields/contacts.js +++ b/client/modules/crm/src/views/call/fields/contacts.js @@ -45,7 +45,7 @@ Espo.define('crm:views/call/fields/contacts', 'crm:views/meeting/fields/contacts if (key in phoneNumbersMap) { number = phoneNumbersMap[key]; var innerHtml = $(html).html(); - innerHtml += ' » ' + '' + number + ''; + innerHtml += ' » ' + '' + number + ''; html = '
' + innerHtml + '
'; } diff --git a/client/modules/crm/src/views/call/fields/leads.js b/client/modules/crm/src/views/call/fields/leads.js index 8ca21478c6..edd51d84b2 100644 --- a/client/modules/crm/src/views/call/fields/leads.js +++ b/client/modules/crm/src/views/call/fields/leads.js @@ -45,7 +45,7 @@ Espo.define('crm:views/call/fields/leads', 'crm:views/meeting/fields/attendees', if (key in phoneNumbersMap) { number = phoneNumbersMap[key]; var innerHtml = $(html).html(); - innerHtml += ' » ' + '' + number + ''; + innerHtml += ' » ' + '' + number + ''; html = '
' + innerHtml + '
'; } diff --git a/client/res/templates/fields/wysiwyg/detail.tpl b/client/res/templates/fields/wysiwyg/detail.tpl index 19d6883109..4b0b406ce8 100644 --- a/client/res/templates/fields/wysiwyg/detail.tpl +++ b/client/res/templates/fields/wysiwyg/detail.tpl @@ -7,3 +7,4 @@ {{else}} {{/unless}} +{{#unless isNotEmpty}}{{#if valueIsSet}}{{translate 'None'}}{{/if}}{{/unless}} diff --git a/client/src/collection-factory.js b/client/src/collection-factory.js index 0bc89dde24..b48302ab49 100644 --- a/client/src/collection-factory.js +++ b/client/src/collection-factory.js @@ -40,24 +40,26 @@ modelFactory: null, create: function (name, callback, context) { - context = context || this; - - this.modelFactory.getSeed(name, function (seed) { - var orderBy = this.modelFactory.metadata.get(['entityDefs', name, 'collection', 'orderBy']); - var order = this.modelFactory.metadata.get(['entityDefs', name, 'collection', 'order']); - - var className = this.modelFactory.metadata.get(['clientDefs', name, 'collection']) || 'collection'; - - Espo.loader.require(className, function (collectionClass) { - var collection = new collectionClass(null, { - name: name, - orderBy: orderBy, - order: order - }); - collection.model = seed; - collection._user = this.modelFactory.user; - collection.entityType = name; - callback.call(context, collection); + return new Promise(function (resolve) { + context = context || this; + this.modelFactory.getSeed(name, function (seed) { + var orderBy = this.modelFactory.metadata.get(['entityDefs', name, 'collection', 'orderBy']); + var order = this.modelFactory.metadata.get(['entityDefs', name, 'collection', 'order']); + var className = this.modelFactory.metadata.get(['clientDefs', name, 'collection']) || 'collection'; + Espo.loader.require(className, function (collectionClass) { + var collection = new collectionClass(null, { + name: name, + orderBy: orderBy, + order: order + }); + collection.model = seed; + collection._user = this.modelFactory.user; + collection.entityType = name; + if (callback) { + callback.call(context, collection); + } + resolve(collection); + }.bind(this)); }.bind(this)); }.bind(this)); } diff --git a/client/src/model-factory.js b/client/src/model-factory.js index f1af4750b7..d7ba4c5d16 100644 --- a/client/src/model-factory.js +++ b/client/src/model-factory.js @@ -49,10 +49,15 @@ Espo.define('model-factory', [], function () { user: null, create: function (name, callback, context) { - context = context || this; - this.getSeed(name, function (seed) { - var model = new seed(); - callback.call(context, model); + return new Promise(function (resolve) { + context = context || this; + this.getSeed(name, function (seed) { + var model = new seed(); + if (callback) { + callback.call(context, model); + } + resolve(model); + }.bind(this)); }.bind(this)); }, diff --git a/client/src/views/admin/settings.js b/client/src/views/admin/settings.js index 58f9eafbf2..026db37dab 100644 --- a/client/src/views/admin/settings.js +++ b/client/src/views/admin/settings.js @@ -32,11 +32,15 @@ Espo.define('views/admin/settings', 'views/settings/record/edit', function (Dep) layoutName: 'settings', - afterRender: function () { - Dep.prototype.afterRender.call(this); + setup: function () { + Dep.prototype.setup.call(this); - }, + if (this.getHelper().getAppParam('isRestrictedMode') && !this.getUser().isSuperAdmin()) { + this.hideField('cronDisabled'); + this.hideField('maintenanceMode'); + this.setFieldReadOnly('siteUrl'); + } + } }); }); - diff --git a/client/src/views/email/record/compose.js b/client/src/views/email/record/compose.js index e704980990..977972bfb6 100644 --- a/client/src/views/email/record/compose.js +++ b/client/src/views/email/record/compose.js @@ -52,7 +52,11 @@ Espo.define('views/email/record/compose', ['views/record/edit', 'views/email/rec } if (!this.options.signatureDisabled && this.hasSignature()) { - var body = this.prependSignature(this.model.get('body') || '', this.model.get('isHtml')); + var addSignatureMethod = 'prependSignature'; + if (this.options.appendSignature) { + addSignatureMethod = 'appendSignature'; + } + var body = this[addSignatureMethod](this.model.get('body') || '', this.model.get('isHtml')); this.model.set('body', body); } diff --git a/client/src/views/fields/base.js b/client/src/views/fields/base.js index 93fff47397..c7b9a6de00 100644 --- a/client/src/views/fields/base.js +++ b/client/src/views/fields/base.js @@ -314,30 +314,8 @@ Espo.define('views/fields/base', 'view', function (Dep) { }, this); - if ((this.mode == 'detail' || this.mode == 'edit') && this.tooltip) { - var $a; - this.once('after:render', function () { - $a = $(''); - var $label = this.getLabelElement(); - $label.append(' '); - this.getLabelElement().append($a); - $a.popover({ - placement: 'bottom', - container: 'body', - html: true, - content: (this.options.tooltipText || this.translate(this.name, 'tooltips', this.model.name)).replace(/\n/g, "
"), - trigger: 'click', - }).on('shown.bs.popover', function () { - $('body').one('click', function () { - $a.popover('hide'); - }); - }); - }, this); - this.on('remove', function () { - if ($a) { - $a.popover('destroy') - } - }, this); + if ((this.isDetailMode() || this.isEditMode()) && this.tooltip) { + this.initTooltip(); } if (this.mode == 'detail') { @@ -375,6 +353,42 @@ Espo.define('views/fields/base', 'view', function (Dep) { } }, + initTooltip: function () { + var $a; + this.once('after:render', function () { + $a = $(''); + var $label = this.getLabelElement(); + $label.append(' '); + this.getLabelElement().append($a); + + $a.popover({ + placement: 'bottom', + container: 'body', + html: true, + content: (this.options.tooltipText || this.translate(this.name, 'tooltips', this.model.name)).replace(/\n/g, "
"), + }).on('shown.bs.popover', function () { + $('body').off('click.popover-' + this.id); + $('body').on('click.popover-' + this.id , function (e) { + if (e.target.classList.contains('popover-content')) return; + if ($.contains($a.get(0), e.target)) return; + $('body').off('click.popover-' + this.id); + $a.popover('hide'); + }.bind(this)); + }.bind(this)); + + $a.on('click', function () { + $(this).popover('toggle'); + }); + }, this); + + this.on('remove', function () { + if ($a) { + $a.popover('destroy') + } + $('body').off('click.popover-' + this.id); + }, this); + }, + showRequiredSign: function () { var $label = this.getLabelElement(); var $sign = $label.find('span.required-sign'); diff --git a/client/src/views/fields/wysiwyg.js b/client/src/views/fields/wysiwyg.js index 207338c4c5..e1c16ee1cc 100644 --- a/client/src/views/fields/wysiwyg.js +++ b/client/src/views/fields/wysiwyg.js @@ -434,7 +434,11 @@ Espo.define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], fun fetch: function () { var data = {}; if (!this.model.has('isHtml') || this.model.get('isHtml')) { - data[this.name] = this.$summernote.summernote('code'); + var code = this.$summernote.summernote('code'); + if (code == '


') { + code = ''; + } + data[this.name] = code; } else { data[this.name] = this.$element.val(); } diff --git a/client/src/views/import/step1.js b/client/src/views/import/step1.js index 38f7189f33..422f1fc105 100644 --- a/client/src/views/import/step1.js +++ b/client/src/views/import/step1.js @@ -89,12 +89,16 @@ Espo.define('views/import/step1', 'view', function (Dep) { {key: "YYYY.MM.DD", value: '2017.12.27'} ], timeFormatDataList: [ - {key: "HH:mm", value: '23:00'}, {key: "HH:mm:ss", value: '23:00:00'}, + {key: "HH:mm", value: '23:00'}, {key: "hh:mm a", value: '11:00 pm'}, {key: "hh:mma", value: '11:00pm'}, {key: "hh:mm A", value: '11:00 PM'}, - {key: "hh:mmA", value: '11:00PM'} + {key: "hh:mmA", value: '11:00PM'}, + {key: "hh:mm:ss a", value: '11:00:00 pm'}, + {key: "hh:mm:ssa", value: '11:00:00pm'}, + {key: "hh:mm:ss A", value: '11:00:00 PM'}, + {key: "hh:mm:ssA", value: '11:00:00PM'}, ], timezoneList: this.getMetadata().get(['entityDefs', 'Settings', 'fields', 'timeZone', 'options']) }; @@ -107,7 +111,7 @@ Espo.define('views/import/step1', 'view', function (Dep) { delimiter: ',', textQualifier: '"', dateFormat: 'YYYY-MM-DD', - timeFormat: 'HH:mm', + timeFormat: 'HH:mm:ss', timezone: 'UTC', decimalMark: '.', personNameFormat: 'f l', diff --git a/client/src/views/modals/compose-email.js b/client/src/views/modals/compose-email.js index faa8b06a9b..5974b40d45 100644 --- a/client/src/views/modals/compose-email.js +++ b/client/src/views/modals/compose-email.js @@ -88,6 +88,7 @@ Espo.define('views/modals/compose-email', 'views/modals/edit', function (Dep) { selectTemplateDisabled: this.options.selectTemplateDisabled, removeAttachmentsOnSelectTemplate: this.options.removeAttachmentsOnSelectTemplate, signatureDisabled: this.options.signatureDisabled, + appendSignature: this.options.appendSignature, exit: function () {} }; this.createView('edit', viewName, options, callback); diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less index 2ac38441f0..b521a7b655 100644 --- a/frontend/less/espo/custom.less +++ b/frontend/less/espo/custom.less @@ -1565,6 +1565,14 @@ table.less-padding td.cell[data-name="buttons"] > .btn-group { top: 0; } + +.note-editor { + .note-editable { + min-height: 39px; + } +} + + .panel-body.no-padding { padding-left: 0 !important; padding-top: 0 !important; diff --git a/tests/integration/Espo/Email/EmailEntityTest.php b/tests/integration/Espo/Email/EmailEntityTest.php new file mode 100644 index 0000000000..e8600c32a9 --- /dev/null +++ b/tests/integration/Espo/Email/EmailEntityTest.php @@ -0,0 +1,55 @@ +getContainer()->get('entityManager'); + + $email = $entityManager->getEntity('Email'); + $email->set('fromString', 'Test Hello '); + + $this->assertEquals('test@test.com', $email->get('fromAddress')); + $this->assertEquals('Test Hello', $email->get('fromName')); + } + + public function testReplyToFields() + { + $entityManager = $this->getContainer()->get('entityManager'); + + $email = $entityManager->getEntity('Email'); + $email->set('replyToString', 'Test Hello ; Man Test '); + + $this->assertEquals('test@test.com', $email->get('replyToAddress')); + $this->assertEquals('Test Hello', $email->get('replyToName')); + } +}