Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 291721c1d0 | |||
| d96301d544 | |||
| 54d9674055 | |||
| 4a5a07649a | |||
| cfdb65fad8 | |||
| 16544a4a70 | |||
| 02939d0840 | |||
| bf331c7020 | |||
| e80b55572b | |||
| 51c29f0a6e | |||
| 056191db82 | |||
| 9ace150efa | |||
| 79ae89d172 | |||
| bb703e2060 | |||
| 972d9019e1 | |||
| c99566410a | |||
| 9c56a14fb0 | |||
| b95e58317b | |||
| 6fd9d6e15c | |||
| 518b9fa2ba | |||
| c113b7bd88 | |||
| fb0a302f62 | |||
| 43127379cd |
@@ -218,9 +218,7 @@ class Auth
|
||||
return;
|
||||
}
|
||||
|
||||
$response->setStatus(500);
|
||||
|
||||
$this->log->error("Auth: " . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
|
||||
protected function handleUnauthorized(Response $response, bool $showDialog): void
|
||||
|
||||
@@ -61,17 +61,17 @@ class EspoFileHandler extends MonologStreamHandler
|
||||
{
|
||||
if (!$this->url) {
|
||||
throw new RuntimeException(
|
||||
"Missing a logger file path. Check logger params in `data/config.php`."
|
||||
"Missing a logger file path. Check logger params in config."
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!is_writable($this->url)) {
|
||||
$this->fileManager->checkCreateFile($this->url);
|
||||
}
|
||||
$checkFileResult = $this->fileManager->checkCreateFile($this->url);
|
||||
|
||||
if (!is_writable($this->url)) {
|
||||
return;
|
||||
if (!$checkFileResult) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->fileManager->appendContents(
|
||||
|
||||
@@ -607,7 +607,7 @@ class Manager
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new file if not exists with all folders in the path.
|
||||
* Checks whether a new file can be created. It will also create all needed directories.
|
||||
*/
|
||||
public function checkCreateFile(string $filePath): bool
|
||||
{
|
||||
@@ -630,8 +630,11 @@ class Manager
|
||||
$pathParts = pathinfo($filePath);
|
||||
|
||||
if (!file_exists($pathParts['dirname'])) {
|
||||
$dirPermission = $defaultPermissions['dir'];
|
||||
$dirPermission = is_string($dirPermission) ? base_convert($dirPermission,8,10) : $dirPermission;
|
||||
$dirPermissionOriginal = $defaultPermissions['dir'];
|
||||
|
||||
$dirPermission = is_string($dirPermissionOriginal) ?
|
||||
base_convert($dirPermissionOriginal, 8, 10) :
|
||||
$dirPermissionOriginal;
|
||||
|
||||
if (!$this->mkdir($pathParts['dirname'], $dirPermission, true)) {
|
||||
throw new Error(
|
||||
@@ -640,11 +643,27 @@ class Manager
|
||||
}
|
||||
}
|
||||
|
||||
if (touch($filePath)) {
|
||||
return $this->getPermissionUtils()->setDefaultPermissions($filePath);
|
||||
$touchResult = touch($filePath);
|
||||
|
||||
if (!$touchResult) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
$setPrermissionsResult = $this->getPermissionUtils()->setDefaultPermissions($filePath);
|
||||
|
||||
if (!$setPrermissionsResult) {
|
||||
$this->unlink($filePath);
|
||||
|
||||
/**
|
||||
* Returning true will cause situations when files are created with
|
||||
* a wrong ownership. This is a trade-off for being able to run
|
||||
* Espo under a user that is neither webserver-user nor root. A file
|
||||
* will be created owned by a user running the process.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,6 +31,8 @@ namespace Espo\Entities;
|
||||
|
||||
use Espo\Core\ORM\Entity;
|
||||
|
||||
use Espo\Core\Field\DateTime;
|
||||
|
||||
use stdClass;
|
||||
|
||||
class Note extends Entity
|
||||
@@ -117,6 +119,11 @@ class Note extends Entity
|
||||
return $this->get('post');
|
||||
}
|
||||
|
||||
public function getCreatedAt(): ?DateTime
|
||||
{
|
||||
return $this->getValueObject('createdAt');
|
||||
}
|
||||
|
||||
public function setAclIsProcessed(): void
|
||||
{
|
||||
$this->aclIsProcessed = true;
|
||||
|
||||
@@ -84,7 +84,7 @@ class Pdf implements EntryPoint
|
||||
->setHeader('Pragma', 'public')
|
||||
->setHeader('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT')
|
||||
->setHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT')
|
||||
->setHeader('Content', 'inline; filename="' . basename($fileName) . '"');
|
||||
->setHeader('Content-Disposition', 'inline; filename="' . basename($fileName) . '"');
|
||||
|
||||
if (!$request->getServerParam('HTTP_ACCEPT_ENCODING')) {
|
||||
$response->setHeader('Content-Length', $contents->getLength());
|
||||
|
||||
@@ -46,7 +46,7 @@ class Actual implements Filter
|
||||
public function apply(SelectBuilder $queryBuilder): void
|
||||
{
|
||||
$notActualStatusList = $this->metadata
|
||||
->get(['entityDefs', 'Actual', 'fields', 'status', 'notActualOptions']) ?? [];
|
||||
->get(['entityDefs', 'Lead', 'fields', 'status', 'notActualOptions']) ?? [];
|
||||
|
||||
$queryBuilder->where(
|
||||
Cond::notIn(
|
||||
|
||||
@@ -196,10 +196,6 @@ class Settings
|
||||
foreach ($this->access->getAdminParamList() as $item) {
|
||||
$ignoreItemList[] = $item;
|
||||
}
|
||||
|
||||
foreach ($this->access->getSuperAdminParamList() as $item) {
|
||||
$ignoreItemList[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->config->get('restrictedMode') && !$user->isSuperAdmin()) {
|
||||
|
||||
@@ -108,7 +108,18 @@ class Stream
|
||||
|
||||
private $recordServiceContainer;
|
||||
|
||||
private const NOTE_ACL_PERIOD = '1 hour';
|
||||
/**
|
||||
* When a record is re-assigned, ACL will be recalculated for related notes
|
||||
* created within the period.
|
||||
*/
|
||||
private const NOTE_ACL_PERIOD = '3 days';
|
||||
|
||||
private const NOTE_ACL_LIMIT = 50;
|
||||
|
||||
/**
|
||||
* Not used currently.
|
||||
*/
|
||||
private const NOTE_NOTIFICATION_PERIOD = '1 hour';
|
||||
|
||||
public function __construct(
|
||||
EntityManager $entityManager,
|
||||
@@ -2110,6 +2121,8 @@ class Stream
|
||||
return;
|
||||
}
|
||||
|
||||
$limit = $this->config->get('noteAclLimit', self::NOTE_ACL_LIMIT);
|
||||
|
||||
$noteList = $this->entityManager
|
||||
->getRDBRepository('Note')
|
||||
->where([
|
||||
@@ -2137,6 +2150,8 @@ class Stream
|
||||
'relatedId',
|
||||
'createdAt',
|
||||
])
|
||||
->order('number', 'DESC')
|
||||
->limit(0, $limit)
|
||||
->find();
|
||||
|
||||
$noteOptions = [];
|
||||
@@ -2145,9 +2160,11 @@ class Stream
|
||||
$noteOptions['forceProcessNotifications'] = true;
|
||||
}
|
||||
|
||||
$period = '-' . $this->config->get('noteNotificationPeriod', self::NOTE_ACL_PERIOD);
|
||||
$notificationPeriod = '-' . $this->config->get('noteNotificationPeriod', self::NOTE_NOTIFICATION_PERIOD);
|
||||
$aclPeriod = '-' . $this->config->get('noteAclPeriod', self::NOTE_ACL_PERIOD);
|
||||
|
||||
$threshold = (new DateTime())->modify($period);
|
||||
$notificationThreshold = (new DateTime())->modify($notificationPeriod);
|
||||
$aclThreshold = (new DateTime())->modify($aclPeriod);
|
||||
|
||||
foreach ($noteList as $note) {
|
||||
$this->processNoteAclItem($entity, $note, [
|
||||
@@ -2156,7 +2173,8 @@ class Stream
|
||||
'forceProcessNoteNotifications' => $forceProcessNoteNotifications,
|
||||
'teamIdList' => $teamIdList,
|
||||
'userIdList' => $userIdList,
|
||||
'threshold' => $threshold,
|
||||
'notificationThreshold' => $notificationThreshold,
|
||||
'aclThreshold' => $aclThreshold,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -2170,21 +2188,21 @@ class Stream
|
||||
$teamIdList = $params['teamIdList'];
|
||||
$userIdList = $params['userIdList'];
|
||||
|
||||
$threshold = $params['threshold'];
|
||||
$notificationThreshold = $params['notificationThreshold'];
|
||||
$aclThreshold = $params['aclThreshold'];
|
||||
|
||||
$noteOptions = [
|
||||
'forceProcessNotifications' => $forceProcessNoteNotifications,
|
||||
];
|
||||
$createdAt = $note->getCreatedAt();
|
||||
|
||||
if (!$entity->isNew() && $note->get('createdAt')) {
|
||||
try {
|
||||
$createdAtDt = new DateTime($note->get('createdAt'));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
return;
|
||||
if (!$createdAt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$entity->isNew()) {
|
||||
if ($createdAt->getTimestamp() < $notificationThreshold->getTimestamp()) {
|
||||
$forceProcessNoteNotifications = false;
|
||||
}
|
||||
|
||||
if ($createdAtDt->getTimestamp() < $threshold->getTimestamp()) {
|
||||
if ($createdAt->getTimestamp() < $aclThreshold->getTimestamp()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -2197,7 +2215,9 @@ class Stream
|
||||
$note->set('usersIds', $userIdList);
|
||||
}
|
||||
|
||||
$this->entityManager->saveEntity($note, $noteOptions);
|
||||
$this->entityManager->saveEntity($note, [
|
||||
'forceProcessNotifications' => $forceProcessNoteNotifications,
|
||||
]);
|
||||
}
|
||||
|
||||
public function applyAccessControlToNote(NoteEntity $note, ?User $user = null): void
|
||||
|
||||
@@ -455,6 +455,10 @@ class Export
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$entityDefs->hasField($field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($entityDefs->getField($field)->getParam('exportDisabled')) {
|
||||
unset($fieldList[$i]);
|
||||
}
|
||||
|
||||
@@ -387,7 +387,8 @@ define('view-helper', ['lib!marked', 'lib!dompurify'], function (marked, DOMPuri
|
||||
}
|
||||
|
||||
text = text.replace(
|
||||
/<a href="mailto:(.*)"/gm, '<a href="javascript:" data-email-address="$1" data-action="mailTo"'
|
||||
/<a href="mailto:(.*!\")"/gm,
|
||||
'<a href="javascript:" data-email-address="$1" data-action="mailTo"'
|
||||
);
|
||||
|
||||
return new Handlebars.SafeString(text);
|
||||
|
||||
@@ -38,7 +38,7 @@ define('views/email/list', 'views/list', function (Dep) {
|
||||
|
||||
folderScope: 'EmailFolder',
|
||||
|
||||
currentFolderId: null,
|
||||
selectedFolderId: null,
|
||||
|
||||
defaultFolderId: 'inbox',
|
||||
|
||||
@@ -169,6 +169,8 @@ define('views/email/list', 'views/list', function (Dep) {
|
||||
},
|
||||
|
||||
applyFolder: function () {
|
||||
this.collection.selectedFolderId = this.selectedFolderId;
|
||||
|
||||
if (!this.selectedFolderId) {
|
||||
this.collection.whereFunction = null;
|
||||
|
||||
|
||||
@@ -52,187 +52,202 @@ define('views/email/record/list', 'views/record/list', function (Dep) {
|
||||
this.addMassAction('markAsRead', false, true);
|
||||
this.addMassAction('moveToTrash', false, true);
|
||||
|
||||
this.listenTo(this.collection, 'moving-to-trash', function (id) {
|
||||
this.listenTo(this.collection, 'moving-to-trash', (id) => {
|
||||
var model = this.collection.get(id);
|
||||
|
||||
if (model) {
|
||||
model.set('inTrash', true);
|
||||
}
|
||||
|
||||
if (this.collection.data.folderId !== 'trash' && this.collection.data.folderId !== 'all') {
|
||||
if (this.collection.selectedFolderId !== 'trash' && this.collection.selectedFolderId !== 'all') {
|
||||
this.removeRecordFromList(id);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
|
||||
this.listenTo(this.collection, 'retrieving-from-trash', function (id) {
|
||||
this.listenTo(this.collection, 'retrieving-from-trash', (id) => {
|
||||
var model = this.collection.get(id);
|
||||
|
||||
if (model) {
|
||||
model.set('inTrash', false);
|
||||
}
|
||||
|
||||
if (this.collection.data.folderId === 'trash') {
|
||||
if (this.collection.selectedFolderId === 'trash') {
|
||||
this.removeRecordFromList(id);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
massActionMarkAsRead: function () {
|
||||
var ids = [];
|
||||
|
||||
for (var i in this.checkedList) {
|
||||
ids.push(this.checkedList[i]);
|
||||
}
|
||||
$.ajax({
|
||||
url: 'Email/action/markAsRead',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
ids: ids
|
||||
})
|
||||
});
|
||||
|
||||
ids.forEach(function (id) {
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/markAsRead', {
|
||||
ids: ids,
|
||||
});
|
||||
|
||||
ids.forEach(id => {
|
||||
var model = this.collection.get(id);
|
||||
|
||||
if (model) {
|
||||
model.set('isRead', true);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
massActionMarkAsNotRead: function () {
|
||||
var ids = [];
|
||||
|
||||
for (var i in this.checkedList) {
|
||||
ids.push(this.checkedList[i]);
|
||||
}
|
||||
$.ajax({
|
||||
url: 'Email/action/markAsNotRead',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
ids: ids
|
||||
})
|
||||
});
|
||||
|
||||
ids.forEach(function (id) {
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/markAsNotRead', {
|
||||
ids: ids,
|
||||
});
|
||||
|
||||
ids.forEach(id => {
|
||||
var model = this.collection.get(id);
|
||||
|
||||
if (model) {
|
||||
model.set('isRead', false);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
massActionMarkAsImportant: function () {
|
||||
var ids = [];
|
||||
|
||||
for (var i in this.checkedList) {
|
||||
ids.push(this.checkedList[i]);
|
||||
}
|
||||
$.ajax({
|
||||
url: 'Email/action/markAsImportant',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
ids: ids
|
||||
})
|
||||
});
|
||||
ids.forEach(function (id) {
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/markAsImportant', {
|
||||
ids: ids,
|
||||
});
|
||||
|
||||
ids.forEach(id => {
|
||||
var model = this.collection.get(id);
|
||||
|
||||
if (model) {
|
||||
model.set('isImportant', true);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
massActionMarkAsNotImportant: function () {
|
||||
var ids = [];
|
||||
|
||||
for (var i in this.checkedList) {
|
||||
ids.push(this.checkedList[i]);
|
||||
}
|
||||
$.ajax({
|
||||
url: 'Email/action/markAsNotImportant',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
ids: ids
|
||||
})
|
||||
});
|
||||
ids.forEach(function (id) {
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/markAsNotImportant', {
|
||||
ids: ids,
|
||||
});
|
||||
|
||||
ids.forEach(id => {
|
||||
var model = this.collection.get(id);
|
||||
|
||||
if (model) {
|
||||
model.set('isImportant', false);
|
||||
}
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
massActionMoveToTrash: function () {
|
||||
var ids = [];
|
||||
|
||||
for (var i in this.checkedList) {
|
||||
ids.push(this.checkedList[i]);
|
||||
}
|
||||
|
||||
this.ajaxPostRequest('Email/action/moveToTrash', {
|
||||
ids: ids
|
||||
}).then(function () {
|
||||
Espo.Ui.success(this.translate('Done'));
|
||||
}.bind(this));
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/moveToTrash', {
|
||||
ids: ids
|
||||
})
|
||||
.then(() => {
|
||||
Espo.Ui.success(this.translate('Done'));
|
||||
});
|
||||
|
||||
if (this.collection.data.folderId === 'trash') {
|
||||
if (this.collection.selectedFolderId === 'trash') {
|
||||
return;
|
||||
}
|
||||
|
||||
ids.forEach(function (id) {
|
||||
ids.forEach(id => {
|
||||
this.collection.trigger('moving-to-trash', id);
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
massActionRetrieveFromTrash: function () {
|
||||
var ids = [];
|
||||
|
||||
for (var i in this.checkedList) {
|
||||
ids.push(this.checkedList[i]);
|
||||
}
|
||||
|
||||
this.ajaxPostRequest('Email/action/retrieveFromTrash', {
|
||||
ids: ids
|
||||
}).then(function () {
|
||||
Espo.Ui.success(this.translate('Done'));
|
||||
}.bind(this));
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/retrieveFromTrash', {
|
||||
ids: ids
|
||||
})
|
||||
.then(() => {
|
||||
Espo.Ui.success(this.translate('Done'));
|
||||
});
|
||||
|
||||
if (this.collection.data.folderId !== 'trash') {
|
||||
if (this.collection.selectedFolderId !== 'trash') {
|
||||
return;
|
||||
}
|
||||
|
||||
ids.forEach(function (id) {
|
||||
ids.forEach(id => {
|
||||
this.collection.trigger('retrieving-from-trash', id);
|
||||
}, this);
|
||||
});
|
||||
},
|
||||
|
||||
massActionMoveToFolder: function () {
|
||||
var ids = [];
|
||||
|
||||
for (var i in this.checkedList) {
|
||||
ids.push(this.checkedList[i]);
|
||||
}
|
||||
|
||||
this.createView('dialog', 'views/email-folder/modals/select-folder', {}, function (view) {
|
||||
this.createView('dialog', 'views/email-folder/modals/select-folder', {}, view => {
|
||||
view.render();
|
||||
this.listenToOnce(view, 'select', function (folderId) {
|
||||
|
||||
this.listenToOnce(view, 'select', folderId => {
|
||||
this.clearView('dialog');
|
||||
this.ajaxPostRequest('Email/action/moveToFolder', {
|
||||
ids: ids,
|
||||
folderId: folderId
|
||||
}).then(function () {
|
||||
this.collection.fetch().then(function () {
|
||||
Espo.Ui.success(this.translate('Done'));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/moveToFolder', {
|
||||
ids: ids,
|
||||
folderId: folderId,
|
||||
})
|
||||
.then(() => {
|
||||
this.collection.fetch().then(() => {
|
||||
Espo.Ui.success(this.translate('Done'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
actionMarkAsImportant: function (data) {
|
||||
data = data || {};
|
||||
|
||||
var id = data.id;
|
||||
$.ajax({
|
||||
url: 'Email/action/markAsImportant',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
id: id
|
||||
})
|
||||
});
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/markAsImportant', {
|
||||
id: id,
|
||||
});
|
||||
|
||||
var model = this.collection.get(id);
|
||||
|
||||
if (model) {
|
||||
model.set('isImportant', true);
|
||||
}
|
||||
@@ -240,72 +255,81 @@ define('views/email/record/list', 'views/record/list', function (Dep) {
|
||||
|
||||
actionMarkAsNotImportant: function (data) {
|
||||
data = data || {};
|
||||
var id = data.id;
|
||||
$.ajax({
|
||||
url: 'Email/action/markAsNotImportant',
|
||||
type: 'POST',
|
||||
data: JSON.stringify({
|
||||
id: id
|
||||
})
|
||||
});
|
||||
|
||||
var id = data.id;
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/markAsNotImportant', {
|
||||
id: id,
|
||||
});
|
||||
|
||||
var model = this.collection.get(id);
|
||||
|
||||
if (model) {
|
||||
model.set('isImportant', false);
|
||||
}
|
||||
},
|
||||
|
||||
actionMarkAllAsRead: function () {
|
||||
$.ajax({
|
||||
url: 'Email/action/markAllAsRead',
|
||||
type: 'POST'
|
||||
});
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/markAllAsRead');
|
||||
|
||||
this.collection.forEach(function (model) {
|
||||
this.collection.forEach(model => {
|
||||
model.set('isRead', true);
|
||||
}, this);
|
||||
});
|
||||
|
||||
this.collection.trigger('all-marked-read');
|
||||
},
|
||||
|
||||
actionMoveToTrash: function (data) {
|
||||
var id = data.id;
|
||||
this.ajaxPostRequest('Email/action/moveToTrash', {
|
||||
id: id
|
||||
}).then(function () {
|
||||
Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email'));
|
||||
this.collection.trigger('moving-to-trash', id);
|
||||
}.bind(this));
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/moveToTrash', {
|
||||
id: id
|
||||
})
|
||||
.then(() => {
|
||||
Espo.Ui.warning(this.translate('Moved to Trash', 'labels', 'Email'));
|
||||
|
||||
this.collection.trigger('moving-to-trash', id);
|
||||
});
|
||||
},
|
||||
|
||||
actionRetrieveFromTrash: function (data) {
|
||||
var id = data.id;
|
||||
this.ajaxPostRequest('Email/action/retrieveFromTrash', {
|
||||
id: id
|
||||
}).then(function () {
|
||||
Espo.Ui.warning(this.translate('Retrieved from Trash', 'labels', 'Email'));
|
||||
this.collection.trigger('retrieving-from-trash', id);
|
||||
}.bind(this));
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/retrieveFromTrash', {
|
||||
id: id
|
||||
})
|
||||
.then(() => {
|
||||
Espo.Ui.warning(this.translate('Retrieved from Trash', 'labels', 'Email'));
|
||||
|
||||
this.collection.trigger('retrieving-from-trash', id);
|
||||
});
|
||||
},
|
||||
|
||||
actionMoveToFolder: function (data) {
|
||||
var id = data.id;
|
||||
|
||||
this.createView('dialog', 'views/email-folder/modals/select-folder', {}, function (view) {
|
||||
this.createView('dialog', 'views/email-folder/modals/select-folder', {}, view => {
|
||||
view.render();
|
||||
this.listenToOnce(view, 'select', function (folderId) {
|
||||
|
||||
this.listenToOnce(view, 'select', folderId => {
|
||||
this.clearView('dialog');
|
||||
this.ajaxPostRequest('Email/action/moveToFolder', {
|
||||
id: id,
|
||||
folderId: folderId
|
||||
}).then(function () {
|
||||
this.collection.fetch().then(function () {
|
||||
Espo.Ui.success(this.translate('Done'));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
Espo.Ajax
|
||||
.postRequest('Email/action/moveToFolder', {
|
||||
id: id,
|
||||
folderId: folderId
|
||||
})
|
||||
.then(() => {
|
||||
this.collection.fetch().then(() => {
|
||||
Espo.Ui.success(this.translate('Done'));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
actionSend: function (data) {
|
||||
@@ -314,27 +338,30 @@ define('views/email/record/list', 'views/record/list', function (Dep) {
|
||||
this.confirm({
|
||||
message: this.translate('sendConfirm', 'messages', 'Email'),
|
||||
confirmText: this.translate('Send', 'labels', 'Email'),
|
||||
}).then(
|
||||
function () {
|
||||
var model = this.collection.get(id);
|
||||
if (!model) return;
|
||||
}).then(() => {
|
||||
var model = this.collection.get(id);
|
||||
|
||||
Espo.Ui.notify(this.translate('Sending...', 'labels', 'Email'));
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
model.save({
|
||||
Espo.Ui.notify(this.translate('Sending...', 'labels', 'Email'));
|
||||
|
||||
model
|
||||
.save({
|
||||
status: 'Sending',
|
||||
}).then(
|
||||
function () {
|
||||
Espo.Ui.success(this.translate('emailSent', 'messages', 'Email'));
|
||||
if (this.collection.data.folderId === 'drafts') {
|
||||
this.removeRecordFromList(id);
|
||||
this.uncheckRecord(id, null, true);
|
||||
this.collection.trigger('draft-sent');
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}.bind(this)
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
Espo.Ui.success(this.translate('emailSent', 'messages', 'Email'));
|
||||
|
||||
if (this.collection.selectedFolderId === 'drafts') {
|
||||
this.removeRecordFromList(id);
|
||||
this.uncheckRecord(id, null, true);
|
||||
this.collection.trigger('draft-sent');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -55,18 +55,22 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
maxDecimalPlaces: 3,
|
||||
|
||||
data: function () {
|
||||
var currencyValue = this.model.get(this.currencyFieldName) || this.getPreferences().get('defaultCurrency') || this.getConfig().get('defaultCurrency');
|
||||
var currencyValue = this.model.get(this.currencyFieldName) ||
|
||||
this.getPreferences().get('defaultCurrency') ||
|
||||
this.getConfig().get('defaultCurrency');
|
||||
|
||||
return _.extend({
|
||||
currencyFieldName: this.currencyFieldName,
|
||||
currencyValue: currencyValue,
|
||||
currencyOptions: this.currencyOptions,
|
||||
currencyList: this.currencyList,
|
||||
currencySymbol: this.getMetadata().get(['app', 'currency', 'symbolMap', currencyValue]) || ''
|
||||
currencySymbol: this.getMetadata().get(['app', 'currency', 'symbolMap', currencyValue]) || '',
|
||||
}, Dep.prototype.data.call(this));
|
||||
},
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
this.currencyFieldName = this.name + 'Currency';
|
||||
this.defaultCurrency = this.getConfig().get('defaultCurrency');
|
||||
this.currencyList = this.getConfig().get('currencyList') || [this.defaultCurrency];
|
||||
@@ -90,13 +94,16 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
},
|
||||
|
||||
_getTemplateName: function () {
|
||||
if (this.mode == 'detail' || this.mode == 'list') {
|
||||
var prop
|
||||
if (this.mode == 'list') {
|
||||
if (this.mode === 'detail' || this.mode === 'list') {
|
||||
var prop;
|
||||
|
||||
if (this.mode === 'list') {
|
||||
var prop = 'listTemplate' + this.getCurrencyFormat().toString();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
var prop = 'detailTemplate' + this.getCurrencyFormat().toString();
|
||||
}
|
||||
|
||||
if (this.options.hideCurrency) {
|
||||
prop = 'detailTemplateNoCurrency';
|
||||
}
|
||||
@@ -105,6 +112,7 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
return this[prop];
|
||||
}
|
||||
}
|
||||
|
||||
return Dep.prototype._getTemplateName.call(this);
|
||||
},
|
||||
|
||||
@@ -112,6 +120,7 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
if (this.mode === 'list' || this.mode === 'detail') {
|
||||
return this.formatNumberDetail(value);
|
||||
}
|
||||
|
||||
return this.formatNumberEdit(value);
|
||||
},
|
||||
|
||||
@@ -120,6 +129,7 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
|
||||
if (value !== null) {
|
||||
var parts = value.toString().split(".");
|
||||
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
|
||||
if (parts.length > 1) {
|
||||
@@ -133,6 +143,7 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
|
||||
return parts.join(this.decimalMark);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
|
||||
@@ -142,19 +153,28 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
|
||||
if (currencyDecimalPlaces === 0) {
|
||||
value = Math.round(value);
|
||||
} else if (currencyDecimalPlaces) {
|
||||
value = Math.round(value * Math.pow(10, currencyDecimalPlaces)) / (Math.pow(10, currencyDecimalPlaces));
|
||||
} else {
|
||||
value = Math.round(value * Math.pow(10, this.maxDecimalPlaces)) / (Math.pow(10, this.maxDecimalPlaces));
|
||||
}
|
||||
else if (currencyDecimalPlaces) {
|
||||
value = Math.round(
|
||||
value * Math.pow(10, currencyDecimalPlaces)) / (Math.pow(10, currencyDecimalPlaces)
|
||||
);
|
||||
}
|
||||
else {
|
||||
value = Math.round(
|
||||
value * Math.pow(10, this.maxDecimalPlaces)) / (Math.pow(10, this.maxDecimalPlaces)
|
||||
);
|
||||
}
|
||||
|
||||
var parts = value.toString().split(".");
|
||||
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
|
||||
if (currencyDecimalPlaces === 0) {
|
||||
return parts[0];
|
||||
} else if (currencyDecimalPlaces) {
|
||||
}
|
||||
else if (currencyDecimalPlaces) {
|
||||
var decimalPartLength = 0;
|
||||
|
||||
if (parts.length > 1) {
|
||||
decimalPartLength = parts[1].length;
|
||||
} else {
|
||||
@@ -163,6 +183,7 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
|
||||
if (currencyDecimalPlaces && decimalPartLength < currencyDecimalPlaces) {
|
||||
var limit = currencyDecimalPlaces - decimalPartLength;
|
||||
|
||||
for (var i = 0; i < limit; i++) {
|
||||
parts[1] += '0';
|
||||
}
|
||||
@@ -171,34 +192,40 @@ define('views/fields/currency', 'views/fields/float', function (Dep) {
|
||||
|
||||
return parts.join(this.decimalMark);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
Dep.prototype.afterRender.call(this);
|
||||
if (this.mode == 'edit') {
|
||||
|
||||
if (this.mode === 'edit') {
|
||||
this.$currency = this.$el.find('[data-name="' + this.currencyFieldName + '"]');
|
||||
this.$currency.on('change', function () {
|
||||
|
||||
this.$currency.on('change', () => {
|
||||
this.model.set(this.currencyFieldName, this.$currency.val(), {ui: true});
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var value = this.$element.val();
|
||||
|
||||
value = this.parse(value);
|
||||
|
||||
var data = {};
|
||||
|
||||
var currencyValue = this.$currency.val();
|
||||
|
||||
if (value === null) {
|
||||
currencyValue = null;
|
||||
}
|
||||
|
||||
data[this.name] = value;
|
||||
data[this.currencyFieldName] = currencyValue
|
||||
data[this.currencyFieldName] = currencyValue;
|
||||
|
||||
return data;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,12 +123,16 @@ define('views/fields/duration', 'views/fields/enum', function (Dep) {
|
||||
return this.stringValue;
|
||||
},
|
||||
|
||||
stringifyDuration: function (seconds) {
|
||||
if (!seconds) {
|
||||
stringifyDuration: function (secondsTotal) {
|
||||
if (!secondsTotal) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
var d = seconds;
|
||||
if (secondsTotal < 60) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
var d = secondsTotal;
|
||||
|
||||
var days = Math.floor(d / (86400));
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('views/fields/float', 'views/fields/int', function (Dep) {
|
||||
define('views/fields/float', 'views/fields/int', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -43,7 +43,8 @@ Espo.define('views/fields/float', 'views/fields/int', function (Dep) {
|
||||
|
||||
if (this.getPreferences().has('decimalMark')) {
|
||||
this.decimalMark = this.getPreferences().get('decimalMark');
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (this.getConfig().has('decimalMark')) {
|
||||
this.decimalMark = this.getConfig().get('decimalMark');
|
||||
}
|
||||
@@ -52,6 +53,7 @@ Espo.define('views/fields/float', 'views/fields/int', function (Dep) {
|
||||
|
||||
getValueForDisplay: function () {
|
||||
var value = isNaN(this.model.get(this.name)) ? null : this.model.get(this.name);
|
||||
|
||||
return this.formatNumber(value);
|
||||
},
|
||||
|
||||
@@ -59,11 +61,15 @@ Espo.define('views/fields/float', 'views/fields/int', function (Dep) {
|
||||
if (this.disableFormatting) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value !== null) {
|
||||
var parts = value.toString().split(".");
|
||||
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
|
||||
return parts.join(this.decimalMark);
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
|
||||
@@ -72,29 +78,37 @@ Espo.define('views/fields/float', 'views/fields/int', function (Dep) {
|
||||
|
||||
validateFloat: function () {
|
||||
var value = this.model.get(this.name);
|
||||
|
||||
if (isNaN(value)) {
|
||||
var msg = this.translate('fieldShouldBeFloat', 'messages').replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
parse: function (value) {
|
||||
value = (value !== '') ? value : null;
|
||||
|
||||
if (value !== null) {
|
||||
value = value.split(this.thousandSeparator).join('');
|
||||
value = value.split(this.decimalMark).join('.');
|
||||
value = parseFloat(value);
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var value = this.$element.val();
|
||||
|
||||
value = this.parse(value);
|
||||
|
||||
var data = {};
|
||||
|
||||
data[this.name] = value;
|
||||
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -44,15 +44,27 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
|
||||
thousandSeparator: ',',
|
||||
|
||||
searchTypeList: ['isNotEmpty', 'isEmpty', 'equals', 'notEquals', 'greaterThan', 'lessThan', 'greaterThanOrEquals', 'lessThanOrEquals', 'between'],
|
||||
searchTypeList: [
|
||||
'isNotEmpty',
|
||||
'isEmpty',
|
||||
'equals',
|
||||
'notEquals',
|
||||
'greaterThan',
|
||||
'lessThan',
|
||||
'greaterThanOrEquals',
|
||||
'lessThanOrEquals',
|
||||
'between',
|
||||
],
|
||||
|
||||
setup: function () {
|
||||
Dep.prototype.setup.call(this);
|
||||
|
||||
this.setupMaxLength();
|
||||
|
||||
if (this.getPreferences().has('thousandSeparator')) {
|
||||
this.thousandSeparator = this.getPreferences().get('thousandSeparator');
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (this.getConfig().has('thousandSeparator')) {
|
||||
this.thousandSeparator = this.getConfig().get('thousandSeparator');
|
||||
}
|
||||
@@ -66,21 +78,22 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
afterRender: function () {
|
||||
Dep.prototype.afterRender.call(this);
|
||||
|
||||
if (this.mode == 'search') {
|
||||
if (this.mode === 'search') {
|
||||
var $searchType = this.$el.find('select.search-type');
|
||||
|
||||
this.handleSearchType($searchType.val());
|
||||
|
||||
this.$el.find('select.search-type').on('change', function () {
|
||||
this.$el.find('select.search-type').on('change', () => {
|
||||
this.trigger('change');
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
this.$element.on('input', function () {
|
||||
this.$element.on('input', () => {
|
||||
this.trigger('change');
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
this.$el.find('input.additional').on('input', function () {
|
||||
this.$el.find('input.additional').on('input', () => {
|
||||
this.trigger('change');
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -90,10 +103,12 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
if (this.model.get(this.name) !== null && typeof this.model.get(this.name) !== 'undefined') {
|
||||
data.isNotEmpty = true;
|
||||
}
|
||||
|
||||
data.valueIsSet = this.model.has(this.name);
|
||||
|
||||
if (this.isSearchMode()) {
|
||||
data.value = this.searchParams.value;
|
||||
|
||||
if (this.getSearchType() === 'between') {
|
||||
data.value = this.getSearchParamsData().value1 || this.searchParams.value1;
|
||||
data.value2 = this.getSearchParamsData().value2 || this.searchParams.value2;
|
||||
@@ -105,6 +120,7 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
|
||||
getValueForDisplay: function () {
|
||||
var value = isNaN(this.model.get(this.name)) ? null : this.model.get(this.name);
|
||||
|
||||
return this.formatNumber(value);
|
||||
},
|
||||
|
||||
@@ -112,17 +128,21 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
if (this.disableFormatting) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value !== null) {
|
||||
var stringValue = value.toString();
|
||||
|
||||
stringValue = stringValue.replace(/\B(?=(\d{3})+(?!\d))/g, this.thousandSeparator);
|
||||
|
||||
return stringValue;
|
||||
}
|
||||
|
||||
return '';
|
||||
},
|
||||
|
||||
setupSearch: function () {
|
||||
this.events = _.extend({
|
||||
'change select.search-type': function (e) {
|
||||
'change select.search-type': (e) => {
|
||||
this.handleSearchType($(e.currentTarget).val());
|
||||
},
|
||||
}, this.events || {});
|
||||
@@ -130,15 +150,18 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
|
||||
handleSearchType: function (type) {
|
||||
var $additionalInput = this.$el.find('input.additional');
|
||||
|
||||
var $input = this.$el.find('input[data-name="'+this.name+'"]');
|
||||
|
||||
if (type === 'between') {
|
||||
$additionalInput.removeClass('hidden');
|
||||
$input.removeClass('hidden');
|
||||
} else if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
|
||||
}
|
||||
else if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
|
||||
$additionalInput.addClass('hidden');
|
||||
$input.addClass('hidden');
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
$additionalInput.addClass('hidden');
|
||||
$input.removeClass('hidden');
|
||||
}
|
||||
@@ -177,15 +200,19 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
|
||||
if (typeof max !== 'undefined' && max !== null) {
|
||||
maxValue = this.formatNumber(maxValue);
|
||||
|
||||
this.params.maxLength = maxValue.toString().length;
|
||||
}
|
||||
},
|
||||
|
||||
validateInt: function () {
|
||||
var value = this.model.get(this.name);
|
||||
|
||||
if (isNaN(value)) {
|
||||
var msg = this.translate('fieldShouldBeInt', 'messages').replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
@@ -202,25 +229,34 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
|
||||
if (minValue !== null && maxValue !== null) {
|
||||
if (value < minValue || value > maxValue ) {
|
||||
var msg = this.translate('fieldShouldBeBetween', 'messages').replace('{field}', this.getLabelText())
|
||||
.replace('{min}', minValue)
|
||||
.replace('{max}', maxValue);
|
||||
var msg = this.translate('fieldShouldBeBetween', 'messages')
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{min}', minValue)
|
||||
.replace('{max}', maxValue);
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
if (minValue !== null) {
|
||||
if (value < minValue) {
|
||||
var msg = this.translate('fieldShouldBeGreater', 'messages').replace('{field}', this.getLabelText())
|
||||
.replace('{value}', minValue);
|
||||
var msg = this.translate('fieldShouldBeGreater', 'messages')
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{value}', minValue);
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
return true;
|
||||
}
|
||||
} else if (maxValue !== null) {
|
||||
}
|
||||
else if (maxValue !== null) {
|
||||
if (value > maxValue) {
|
||||
var msg = this.translate('fieldShouldBeLess', 'messages').replace('{field}', this.getLabelText())
|
||||
.replace('{value}', maxValue);
|
||||
var msg = this.translate('fieldShouldBeLess', 'messages')
|
||||
.replace('{field}', this.getLabelText())
|
||||
.replace('{value}', maxValue);
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -230,9 +266,12 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
validateRequired: function () {
|
||||
if (this.isRequired()) {
|
||||
var value = this.model.get(this.name);
|
||||
|
||||
if (value === null || value === false) {
|
||||
var msg = this.translate('fieldIsRequired', 'messages').replace('{field}', this.getLabelText());
|
||||
|
||||
this.showValidationMessage(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -240,22 +279,29 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
|
||||
parse: function (value) {
|
||||
value = (value !== '') ? value : null;
|
||||
|
||||
if (value !== null) {
|
||||
value = value.split(this.thousandSeparator).join('');
|
||||
|
||||
if (value.indexOf('.') !== -1 || value.indexOf(',') !== -1) {
|
||||
value = NaN;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
value = parseInt(value);
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var value = this.$element.val();
|
||||
value = this.parse(value);
|
||||
|
||||
var data = {};
|
||||
|
||||
data[this.name] = value;
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -283,17 +329,20 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
value2: valueTo
|
||||
}
|
||||
};
|
||||
} else if (type == 'isEmpty') {
|
||||
}
|
||||
else if (type === 'isEmpty') {
|
||||
data = {
|
||||
type: 'isNull',
|
||||
typeFront: 'isEmpty'
|
||||
};
|
||||
} else if (type == 'isNotEmpty') {
|
||||
}
|
||||
else if (type === 'isNotEmpty') {
|
||||
data = {
|
||||
type: 'isNotNull',
|
||||
typeFront: 'isNotEmpty'
|
||||
};
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
data = {
|
||||
type: type,
|
||||
value: value,
|
||||
@@ -302,12 +351,13 @@ define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
getSearchType: function () {
|
||||
return this.searchParams.typeFront || this.searchParams.type;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -480,10 +480,19 @@ define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
|
||||
|
||||
fetchSearch: function () {
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
var idList = this.ids || [];
|
||||
|
||||
if (~['anyOf', 'allOf', 'noneOf'].indexOf(type) && !idList.length) {
|
||||
return {
|
||||
type: 'isNotNull',
|
||||
attribute: 'id',
|
||||
data: {
|
||||
type: type,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'anyOf') {
|
||||
var idList = this.ids || [];
|
||||
|
||||
var data = {
|
||||
type: 'linkedWith',
|
||||
value: idList,
|
||||
@@ -493,16 +502,10 @@ define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
|
||||
},
|
||||
};
|
||||
|
||||
if (!idList.length) {
|
||||
data.value = null;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
if (type === 'allOf') {
|
||||
var idList = this.ids || [];
|
||||
|
||||
if (type === 'allOf') {
|
||||
var data = {
|
||||
type: 'linkedWithAll',
|
||||
value: idList,
|
||||
@@ -522,7 +525,7 @@ define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
|
||||
if (type === 'noneOf') {
|
||||
var data = {
|
||||
type: 'notLinkedWith',
|
||||
value: this.ids || [],
|
||||
value: idList,
|
||||
data: {
|
||||
type: type,
|
||||
nameHash: this.nameHash,
|
||||
|
||||
@@ -523,6 +523,16 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
var value = this.$el.find('[data-name="' + this.idName + '"]').val();
|
||||
|
||||
if (~['isOneOf', 'isNotOneOf'].indexOf(type) && !this.searchData.oneOfIdList.length) {
|
||||
return {
|
||||
type: 'isNotNull',
|
||||
attribute: 'id',
|
||||
data: {
|
||||
type: type,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (type === 'isEmpty') {
|
||||
var data = {
|
||||
type: 'isNull',
|
||||
@@ -534,7 +544,8 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
|
||||
return data;
|
||||
}
|
||||
else if (type === 'isNotEmpty') {
|
||||
|
||||
if (type === 'isNotEmpty') {
|
||||
var data = {
|
||||
type: 'isNotNull',
|
||||
attribute: this.idName,
|
||||
@@ -545,7 +556,8 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
|
||||
return data;
|
||||
}
|
||||
else if (type === 'isOneOf') {
|
||||
|
||||
if (type === 'isOneOf') {
|
||||
var data = {
|
||||
type: 'in',
|
||||
attribute: this.idName,
|
||||
@@ -559,7 +571,8 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
|
||||
return data;
|
||||
}
|
||||
else if (type === 'isNotOneOf') {
|
||||
|
||||
if (type === 'isNotOneOf') {
|
||||
var data = {
|
||||
type: 'or',
|
||||
value: [
|
||||
@@ -582,7 +595,8 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
|
||||
return data;
|
||||
}
|
||||
else if (type === 'isNotOneOfAndIsNotEmpty') {
|
||||
|
||||
if (type === 'isNotOneOfAndIsNotEmpty') {
|
||||
var data = {
|
||||
type: 'notIn',
|
||||
attribute: this.idName,
|
||||
@@ -596,7 +610,8 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
|
||||
return data;
|
||||
}
|
||||
else if (type === 'isNot') {
|
||||
|
||||
if (type === 'isNot') {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
@@ -624,7 +639,8 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
};
|
||||
return data;
|
||||
}
|
||||
else if (type === 'isNotAndIsNotEmpty') {
|
||||
|
||||
if (type === 'isNotAndIsNotEmpty') {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
@@ -644,26 +660,25 @@ define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
|
||||
return data;
|
||||
}
|
||||
else {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var nameValue = this.$el.find('[data-name="' + this.nameName + '"]').val();
|
||||
|
||||
var data = {
|
||||
type: 'equals',
|
||||
attribute: this.idName,
|
||||
value: value,
|
||||
data: {
|
||||
type: type,
|
||||
idValue: value,
|
||||
nameValue: nameValue
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var nameValue = this.$el.find('[data-name="' + this.nameName + '"]').val();
|
||||
|
||||
var data = {
|
||||
type: 'equals',
|
||||
attribute: this.idName,
|
||||
value: value,
|
||||
data: {
|
||||
type: type,
|
||||
idValue: value,
|
||||
nameValue: nameValue,
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
getSearchType: function () {
|
||||
|
||||
@@ -45,6 +45,7 @@ define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
|
||||
setup: function () {
|
||||
this.setupOptions();
|
||||
|
||||
if (this.options.customOptionList) {
|
||||
this.setOptionList(this.options.customOptionList);
|
||||
}
|
||||
@@ -57,9 +58,10 @@ define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
if (!this.originalOptionList) {
|
||||
this.originalOptionList = this.params.options || [];
|
||||
}
|
||||
|
||||
this.params.options = Espo.Utils.clone(optionList);
|
||||
|
||||
if (this.mode == 'edit') {
|
||||
if (this.mode === 'edit') {
|
||||
if (this.isRendered()) {
|
||||
this.reRender();
|
||||
}
|
||||
@@ -71,7 +73,7 @@ define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
this.params.options = Espo.Utils.clone(this.originalOptionList);
|
||||
}
|
||||
|
||||
if (this.mode == 'edit') {
|
||||
if (this.mode === 'edit') {
|
||||
if (this.isRendered()) {
|
||||
this.reRender();
|
||||
}
|
||||
@@ -98,6 +100,7 @@ define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
) {
|
||||
data.isNotEmpty = true;
|
||||
}
|
||||
|
||||
data.valueIsSet = this.model.has(this.name);
|
||||
|
||||
if (this.mode === 'search') {
|
||||
@@ -105,82 +108,103 @@ define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
this.searchData.value = this.searchParams.value;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
handleSearchType: function (type) {
|
||||
if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
|
||||
this.$el.find('input.main-element').addClass('hidden');
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.$el.find('input.main-element').removeClass('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
Dep.prototype.afterRender.call(this);
|
||||
if (this.mode == 'search') {
|
||||
|
||||
if (this.mode === 'search') {
|
||||
var type = this.$el.find('select.search-type').val();
|
||||
this.handleSearchType(type);
|
||||
}
|
||||
|
||||
if ((this.mode == 'edit' || this.mode == 'search') && this.params.options && this.params.options.length) {
|
||||
if (
|
||||
(this.mode === 'edit' || this.mode === 'search') &&
|
||||
this.params.options &&
|
||||
this.params.options.length
|
||||
) {
|
||||
this.$element.autocomplete({
|
||||
minChars: 0,
|
||||
lookup: this.params.options,
|
||||
maxHeight: 200,
|
||||
beforeRender: function ($c) {
|
||||
beforeRender: ($c) => {
|
||||
if (this.$element.hasClass('input-sm')) {
|
||||
$c.addClass('small');
|
||||
}
|
||||
}.bind(this),
|
||||
formatResult: function (suggestion) {
|
||||
},
|
||||
formatResult: (suggestion) => {
|
||||
return this.getHelper().escapeString(suggestion.value);
|
||||
}.bind(this),
|
||||
lookupFilter: function (suggestion, query, queryLowerCase) {
|
||||
},
|
||||
lookupFilter: (suggestion, query, queryLowerCase) => {
|
||||
if (suggestion.value.toLowerCase().indexOf(queryLowerCase) === 0) {
|
||||
if (suggestion.value.length === queryLowerCase.length) return false;
|
||||
if (suggestion.value.length === queryLowerCase.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
onSelect: function () {
|
||||
onSelect: () => {
|
||||
this.trigger('change');
|
||||
}.bind(this)
|
||||
},
|
||||
});
|
||||
|
||||
this.$element.attr('autocomplete', 'espo-' + this.name);
|
||||
|
||||
this.$element.on('focus', function () {
|
||||
if (this.$element.val()) return;
|
||||
this.$element.on('focus', () => {
|
||||
if (this.$element.val()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$element.autocomplete('onValueChange');
|
||||
}.bind(this));
|
||||
this.once('render', function () {
|
||||
});
|
||||
|
||||
this.once('render', () => {
|
||||
this.$element.autocomplete('dispose');
|
||||
}, this);
|
||||
this.once('remove', function () {
|
||||
});
|
||||
|
||||
this.once('remove', () => {
|
||||
this.$element.autocomplete('dispose');
|
||||
}, this);
|
||||
});
|
||||
}
|
||||
|
||||
if (this.mode === 'search') {
|
||||
this.$el.find('select.search-type').on('change', function () {
|
||||
this.$el.find('select.search-type').on('change', () => {
|
||||
this.trigger('change');
|
||||
}.bind(this));
|
||||
});
|
||||
|
||||
this.$element.on('input', function () {
|
||||
this.$element.on('input', () => {
|
||||
this.trigger('change');
|
||||
}.bind(this));
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var data = {};
|
||||
|
||||
var value = this.$element.val();
|
||||
|
||||
if (this.params.trim || this.forceTrim) {
|
||||
if (typeof value.trim === 'function') {
|
||||
value = value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
data[this.name] = value || null;
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -190,7 +214,7 @@ define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
var data;
|
||||
|
||||
if (~['isEmpty', 'isNotEmpty'].indexOf(type)) {
|
||||
if (type == 'isEmpty') {
|
||||
if (type === 'isEmpty') {
|
||||
data = {
|
||||
type: 'or',
|
||||
value: [
|
||||
@@ -207,8 +231,9 @@ define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
data: {
|
||||
type: type
|
||||
}
|
||||
}
|
||||
} else {
|
||||
};
|
||||
}
|
||||
else {
|
||||
data = {
|
||||
type: 'and',
|
||||
value: [
|
||||
@@ -228,27 +253,33 @@ define('views/fields/varchar', 'views/fields/base', function (Dep) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
var value = this.$element.val().toString().trim();
|
||||
|
||||
value = value.trim();
|
||||
|
||||
if (value) {
|
||||
data = {
|
||||
value: value,
|
||||
type: type,
|
||||
data: {
|
||||
type: type
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getSearchType: function () {
|
||||
return this.getSearchParamsData().type || this.searchParams.typeFront || this.searchParams.type;
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,6 +106,8 @@ define('views/record/list', 'view', function (Dep) {
|
||||
|
||||
setupHandlerType: 'record/list',
|
||||
|
||||
checkboxesDisabled: false,
|
||||
|
||||
events: {
|
||||
'click a.link': function (e) {
|
||||
e.stopPropagation();
|
||||
@@ -492,6 +494,8 @@ define('views/record/list', 'view', function (Dep) {
|
||||
this.checkboxes = _.isUndefined(this.options.checkboxes) ? this.checkboxes : this.options.checkboxes;
|
||||
this.selectable = _.isUndefined(this.options.selectable) ? this.selectable : this.options.selectable;
|
||||
|
||||
this.checkboxesDisabled = this.options.checkboxes === false;
|
||||
|
||||
this.rowActionsView = _.isUndefined(this.options.rowActionsView) ?
|
||||
this.rowActionsView :
|
||||
this.options.rowActionsView;
|
||||
@@ -1182,6 +1186,10 @@ define('views/record/list', 'view', function (Dep) {
|
||||
if (allResult) {
|
||||
this.checkAllResultMassActionList[method](item);
|
||||
}
|
||||
|
||||
if (!this.checkboxesDisabled) {
|
||||
this.checkboxes = true;
|
||||
}
|
||||
},
|
||||
|
||||
removeAllResultMassAction: function (item) {
|
||||
|
||||
Generated
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "espocrm",
|
||||
"version": "7.0.2",
|
||||
"version": "7.0.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "espocrm",
|
||||
"version": "7.0.2",
|
||||
"version": "7.0.6",
|
||||
"description": "Open-source CRM.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -41,7 +41,12 @@ class BeforeUpgrade
|
||||
$this->processCheckCache();
|
||||
|
||||
// Load to prevent fail if run in a single process.
|
||||
$container->get('entityManager')->getQueryBuilder()->update();
|
||||
$container->get('entityManager')
|
||||
->getQueryBuilder()
|
||||
->update()
|
||||
->in('Test')
|
||||
->set(['test' => 'test'])
|
||||
->build();
|
||||
}
|
||||
|
||||
private function processCheckCache()
|
||||
|
||||
Reference in New Issue
Block a user