Merge branch 'hotfix/5.5.7'
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,7 @@ return [
|
||||
'adminNotificationsCronIsNotConfigured',
|
||||
'adminNotificationsNewExtensionVersion',
|
||||
'leadCaptureAllowOrigin',
|
||||
'cronDisabled',
|
||||
],
|
||||
'superAdminItems' => [
|
||||
'jobMaxPortion',
|
||||
@@ -196,6 +197,9 @@ return [
|
||||
'daemonProcessTimeout',
|
||||
'daemonMaxProcessNumber',
|
||||
'adminPanelIframeUrl',
|
||||
'cronDisabled',
|
||||
'maintenanceMode',
|
||||
'siteUrl',
|
||||
],
|
||||
'userItems' => [
|
||||
'outboundEmailFromAddress',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -536,6 +536,14 @@
|
||||
"daemonProcessTimeout": {
|
||||
"type": "int",
|
||||
"tooltip": true
|
||||
},
|
||||
"cronDisabled": {
|
||||
"type": "bool",
|
||||
"tooltip": true
|
||||
},
|
||||
"maintenanceMode": {
|
||||
"type": "bool",
|
||||
"tooltip": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+103
-73
@@ -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 {
|
||||
|
||||
@@ -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 += '<span class="text-muted small"> » </span> ' + '<a href="tel:'+number+'" class="small">' + number + '</a>';
|
||||
innerHtml += '<span class="text-muted small"> » </span> ' + '<a href="tel:'+number+'" class="small" data-phone-number="'+number+'" data-action="dial">' + number + '</a>';
|
||||
html = '<div>' + innerHtml + '</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -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 += '<span class="text-muted small"> » </span> ' + '<a href="tel:'+number+'" class="small">' + number + '</a>';
|
||||
innerHtml += '<span class="text-muted small"> » </span> ' + '<a href="tel:'+number+'" class="small" data-phone-number="'+number+'" data-action="dial">' + number + '</a>';
|
||||
html = '<div>' + innerHtml + '</div>';
|
||||
}
|
||||
|
||||
|
||||
@@ -7,3 +7,4 @@
|
||||
{{else}}
|
||||
<div class="plain complex-text hidden">{{complexText value}}</div>
|
||||
{{/unless}}
|
||||
{{#unless isNotEmpty}}{{#if valueIsSet}}{{translate 'None'}}{{/if}}{{/unless}}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
},
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = $('<a href="javascript:" class="text-muted field-info"><span class="fas fa-info-circle"></span></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, "<br />"),
|
||||
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 = $('<a href="javascript:" class="text-muted field-info"><span class="fas fa-info-circle"></span></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, "<br />"),
|
||||
}).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');
|
||||
|
||||
@@ -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 == '<p><br></p>') {
|
||||
code = '';
|
||||
}
|
||||
data[this.name] = code;
|
||||
} else {
|
||||
data[this.name] = this.$element.val();
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 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.
|
||||
************************************************************************/
|
||||
|
||||
namespace tests\integration\Espo\Email;
|
||||
|
||||
class EmailEntityTest extends \tests\integration\Core\BaseTestCase
|
||||
{
|
||||
public function testFromFields()
|
||||
{
|
||||
$entityManager = $this->getContainer()->get('entityManager');
|
||||
|
||||
$email = $entityManager->getEntity('Email');
|
||||
$email->set('fromString', 'Test Hello <test@test.com>');
|
||||
|
||||
$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 <test@test.com>; Man Test <man@test.com>');
|
||||
|
||||
$this->assertEquals('test@test.com', $email->get('replyToAddress'));
|
||||
$this->assertEquals('Test Hello', $email->get('replyToName'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user