diff --git a/application/Espo/Core/HookManager.php b/application/Espo/Core/HookManager.php
index 6f50cc91f7..72149ae8b8 100644
--- a/application/Espo/Core/HookManager.php
+++ b/application/Espo/Core/HookManager.php
@@ -46,10 +46,10 @@ class HookManager
*
* @var array
*/
- protected $ignoredMethods = array(
+ protected $ignoredMethodList = array(
'__construct',
'getDependencyList',
- 'inject',
+ 'inject'
);
protected $paths = array(
@@ -168,7 +168,7 @@ class HookManager
$className = Util::getClassName($hookFilePath);
$classMethods = get_class_methods($className);
- $hookMethods = array_diff($classMethods, $this->ignoredMethods);
+ $hookMethods = array_diff($classMethods, $this->ignoredMethodList);
foreach($hookMethods as $hookName) {
$entityHookData = isset($hookData[$scopeName][$hookName]) ? $hookData[$scopeName][$hookName] : array();
diff --git a/application/Espo/Core/ORM/Repositories/RDB.php b/application/Espo/Core/ORM/Repositories/RDB.php
index c99bcf3375..d95b62aa13 100644
--- a/application/Espo/Core/ORM/Repositories/RDB.php
+++ b/application/Espo/Core/ORM/Repositories/RDB.php
@@ -189,6 +189,13 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$this->getEntityManager()->getHookManager()->process($this->entityType, 'afterRemove', $entity, $options);
}
+ protected function afterMassRelate(Entity $entity, $relationName, array $params = array(), array $options = array())
+ {
+ $options['params'] = $params;
+
+ $this->getEntityManager()->getHookManager()->process($this->entityType, 'afterMassRelate', $entity, $options);
+ }
+
public function remove(Entity $entity, array $options = array())
{
$result = parent::remove($entity, $options);
diff --git a/application/Espo/Core/Utils/Api/Auth.php b/application/Espo/Core/Utils/Api/Auth.php
index 2d735e6616..ec19e8a70f 100644
--- a/application/Espo/Core/Utils/Api/Auth.php
+++ b/application/Espo/Core/Utils/Api/Auth.php
@@ -61,6 +61,13 @@ class Auth extends \Slim\Middleware
list($authUsername, $authPassword) = explode(':', base64_decode($espoAuth));
}
+ if (!isset($authUsername)) {
+ if (!empty($_COOKIE['auth-username']) && !empty($_COOKIE['auth-token'])) {
+ $authUsername = $_COOKIE['auth-username'];
+ $authPassword = $_COOKIE['auth-token'];
+ }
+ }
+
$espoCgiAuth = $req->headers('HTTP_ESPO_CGI_AUTH');
if (empty($espoCgiAuth)) {
$espoCgiAuth = $req->headers('REDIRECT_HTTP_ESPO_CGI_AUTH');
diff --git a/application/Espo/Core/defaults/config.php b/application/Espo/Core/defaults/config.php
index e999f55c68..2bbf79ad5e 100644
--- a/application/Espo/Core/defaults/config.php
+++ b/application/Espo/Core/defaults/config.php
@@ -157,8 +157,9 @@ return array (
'activitiesEntityList' => ['Meeting', 'Call'],
'historyEntityList' => ['Meeting', 'Call', 'Email'],
'lastViewedCount' => 20,
- 'cleanupJobPeriod' => '-1 month',
- 'cleanupActionHistoryPeriod' => '-15 days',
+ 'cleanupJobPeriod' => '1 month',
+ 'cleanupActionHistoryPeriod' => '15 days',
+ 'cleanupAuthTokenPeriod' => '1 month',
'isInstalled' => false
);
diff --git a/application/Espo/Jobs/Cleanup.php b/application/Espo/Jobs/Cleanup.php
index 8b6ec1a63d..8e9f67e05d 100644
--- a/application/Espo/Jobs/Cleanup.php
+++ b/application/Espo/Jobs/Cleanup.php
@@ -33,9 +33,11 @@ use \Espo\Core\Exceptions;
class Cleanup extends \Espo\Core\Jobs\Base
{
- protected $cleanupJobPeriod = '-1 month';
+ protected $cleanupJobPeriod = '1 month';
- protected $cleanupActionHistoryPeriod = '-15 days';
+ protected $cleanupActionHistoryPeriod = '15 days';
+
+ protected $cleanupAuthTokenPeriod = '1 month';
public function run()
{
@@ -46,6 +48,7 @@ class Cleanup extends \Espo\Core\Jobs\Base
$this->cleanupNotes();
$this->cleanupNotifications();
$this->cleanupActionHistory();
+ $this->cleanupAuthToken();
}
protected function cleanupJobs()
@@ -83,7 +86,7 @@ class Cleanup extends \Espo\Core\Jobs\Base
protected function cleanupActionHistory()
{
- $period = $this->getConfig()->get('cleanupActionHistoryPeriod', $this->cleanupActionHistoryPeriod);
+ $period = '-' . $this->getConfig()->get('cleanupActionHistoryPeriod', $this->cleanupActionHistoryPeriod);
$datetime = new \DateTime();
$datetime->modify($period);
@@ -94,9 +97,22 @@ class Cleanup extends \Espo\Core\Jobs\Base
$sth->execute();
}
+ protected function cleanupAuthToken()
+ {
+ $period = '-' . $this->getConfig()->get('cleanupAuthTokenPeriod', $this->cleanupAuthTokenPeriod);
+ $datetime = new \DateTime();
+ $datetime->modify($period);
+
+ $query = "DELETE FROM `auth_token` WHERE DATE(modified_at) < '" . $datetime->format('Y-m-d') . "' AND is_active = 0";
+
+ $pdo = $this->getEntityManager()->getPDO();
+ $sth = $pdo->prepare($query);
+ $sth->execute();
+ }
+
protected function getCleanupJobFromDate()
{
- $period = $this->getConfig()->get('cleanupJobPeriod', $this->cleanupJobPeriod);
+ $period = '-' . $this->getConfig()->get('cleanupJobPeriod', $this->cleanupJobPeriod);
$datetime = new \DateTime();
$datetime->modify($period);
return $datetime->format('Y-m-d');
diff --git a/application/Espo/Modules/Crm/Resources/i18n/da_DK/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/da_DK/KnowledgeBaseArticle.json
index 9dcf12eab4..1b69660756 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/da_DK/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/da_DK/KnowledgeBaseArticle.json
@@ -39,7 +39,6 @@
}
},
"tooltips": {
- "portals": "Vil vises i alle portaler hvis den forbliver tom. Vil kun vises i valgte portaler hvis udfyldt."
},
"presetFilters": {
"published": "Publiseret"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/de_DE/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/de_DE/KnowledgeBaseArticle.json
index e80a13fa40..e68c66d126 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/de_DE/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/de_DE/KnowledgeBaseArticle.json
@@ -39,7 +39,6 @@
}
},
"tooltips": {
- "portals": "Wenn in diesem Feld etwas eingetragen ist, wird dieser Artikel nur in den gewählten Portalen angezeigt. Ist das Feld leer ist der Artikel in allen Portalen zu sehen."
},
"presetFilters": {
"published": "Publiziert"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/en_US/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/en_US/KnowledgeBaseArticle.json
index c3cf30b3b8..51d807ed65 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/en_US/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/en_US/KnowledgeBaseArticle.json
@@ -39,7 +39,7 @@
}
},
"tooltips": {
- "portals": "If not empty then this article will be available only in specified portals. If empty then it will available in all portals."
+ "portals": "Article will be available only in specified portals."
},
"presetFilters": {
"published": "Published"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/es_ES/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/es_ES/KnowledgeBaseArticle.json
index 7e0d2b03ff..0403694f84 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/es_ES/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/es_ES/KnowledgeBaseArticle.json
@@ -39,7 +39,6 @@
}
},
"tooltips": {
- "portals": "Si no está vacía, entonces este artículo estará disponible solo en portales específicos. Si está vacío, entonces estará disponible en todos los portales.\t"
},
"presetFilters": {
"published": "Publicado"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/fr_FR/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/fr_FR/KnowledgeBaseArticle.json
index 20c3af32f7..8d3b7c776d 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/fr_FR/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/fr_FR/KnowledgeBaseArticle.json
@@ -35,7 +35,7 @@
}
},
"tooltips": {
- "portals": "If not empty then this article will be available only in specified portals. If empty then it will available in all portals."
+
},
"presetFilters": {
"published": "Publié"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/id_ID/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/id_ID/KnowledgeBaseArticle.json
index e398c2cb5b..73171b2a90 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/id_ID/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/id_ID/KnowledgeBaseArticle.json
@@ -35,7 +35,6 @@
}
},
"tooltips": {
- "portals": "Jika tidak kosong maka artikel ini akan tersedia hanya dalam portal yang ditentukan. Jika kosong maka akan tersedia di semua portal."
},
"presetFilters": {
"published": "Diterbitkan"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/it_IT/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/it_IT/KnowledgeBaseArticle.json
index 85199a4370..72dcd230ab 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/it_IT/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/it_IT/KnowledgeBaseArticle.json
@@ -37,7 +37,6 @@
}
},
"tooltips": {
- "portals": "Se non è vuoto allora questo articolo sarà disponibile solo nei portali specifici. Se vuoto, allora sarà disponibile in tutti i portali ."
},
"presetFilters": {
"published": "Pubblicato"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/nb_NO/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/nb_NO/KnowledgeBaseArticle.json
index 1a8ad92d20..2147e52710 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/nb_NO/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/nb_NO/KnowledgeBaseArticle.json
@@ -39,7 +39,6 @@
}
},
"tooltips": {
- "portals": "Vil vises i alle portaler hvis den forblir tom. Vil kun vises i valgte portaler hvis utfylt."
},
"presetFilters": {
"published": "Publisert"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/pl_PL/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/pl_PL/KnowledgeBaseArticle.json
index 8912423571..3571b8a91e 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/pl_PL/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/pl_PL/KnowledgeBaseArticle.json
@@ -34,7 +34,6 @@
}
},
"tooltips": {
- "portals": "If not empty then this article will be available only in specified portals. If empty then it will available in all portals."
},
"presetFilters": {
"published": "Published"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/ru_RU/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/ru_RU/KnowledgeBaseArticle.json
index 6fd0f0ba4d..4a623513b8 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/ru_RU/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/ru_RU/KnowledgeBaseArticle.json
@@ -34,7 +34,7 @@
}
},
"tooltips": {
- "portals": "Если не пусто, то эта статья будет доступна только в указанных порталах. Если пусто, то будет доступна во всех порталах."
+ "portals": "Статья будет доступна только в указанных порталах."
},
"presetFilters": {
"published": "Опубликована"
diff --git a/application/Espo/Modules/Crm/Resources/i18n/zh_CN/KnowledgeBaseArticle.json b/application/Espo/Modules/Crm/Resources/i18n/zh_CN/KnowledgeBaseArticle.json
index ffc0fb41f5..f1c84cfc93 100644
--- a/application/Espo/Modules/Crm/Resources/i18n/zh_CN/KnowledgeBaseArticle.json
+++ b/application/Espo/Modules/Crm/Resources/i18n/zh_CN/KnowledgeBaseArticle.json
@@ -37,7 +37,6 @@
}
},
"tooltips": {
- "portals": "如果不是空的那么这篇文章将只能在指定门户可用。如果是空的那么在所有门户她都可用"
},
"presetFilters": {
"published": "公布的"
diff --git a/application/Espo/Modules/Crm/Resources/metadata/scopes/Document.json b/application/Espo/Modules/Crm/Resources/metadata/scopes/Document.json
index 4037e0055b..1fe12fb732 100644
--- a/application/Espo/Modules/Crm/Resources/metadata/scopes/Document.json
+++ b/application/Espo/Modules/Crm/Resources/metadata/scopes/Document.json
@@ -3,7 +3,8 @@
"layouts": true,
"tab": true,
"acl": true,
- "aclPortal": "recordAllAccountOwnNo",
+ "aclPortal": true,
+ "aclPortalLevelList": ["all", "account", "contact", "own", "no"],
"module": "Crm",
"customizable": true,
"importable": false,
diff --git a/application/Espo/Modules/Crm/SelectManagers/KnowledgeBaseArticle.php b/application/Espo/Modules/Crm/SelectManagers/KnowledgeBaseArticle.php
index 4a83c9d07b..9929f737d6 100644
--- a/application/Espo/Modules/Crm/SelectManagers/KnowledgeBaseArticle.php
+++ b/application/Espo/Modules/Crm/SelectManagers/KnowledgeBaseArticle.php
@@ -50,9 +50,6 @@ class KnowledgeBaseArticle extends \Espo\Core\SelectManagers\Base
$this->addOrWhere(array(
array(
'portals.id' => $this->getUser()->get('portalId')
- ),
- array(
- 'portals.id' => null
)
), $result);
}
diff --git a/application/Espo/Modules/Crm/Services/KnowledgeBaseArticle.php b/application/Espo/Modules/Crm/Services/KnowledgeBaseArticle.php
index 7765647232..5ce78fffd7 100644
--- a/application/Espo/Modules/Crm/Services/KnowledgeBaseArticle.php
+++ b/application/Espo/Modules/Crm/Services/KnowledgeBaseArticle.php
@@ -103,45 +103,6 @@ class KnowledgeBaseArticle extends \Espo\Services\Record
);
}
- public function moveToTop($id, $where = null)
- {
- $entity = $this->getEntityManager()->getEntity('KnowledgeBaseArticle', $id);
- if (!$entity) throw new NotFound();
- if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
-
- $currentIndex = $entity->get('order');
-
- if (!is_int($currentIndex)) throw new Error();
-
- if (!$where) {
- $where = array();
- }
-
- $params = array(
- 'where' => $where
- );
-
- $selectManager = $this->getSelectManager();
- $selectParams = $selectManager->buildSelectParams($params, true, true);
-
- $selectParams['whereClause'][] = array(
- 'order<' => $currentIndex
- );
-
- $selectManager->applyOrder('order', false, $selectParams);
-
- $previousEntity = $this->getRepository()->findOne($selectParams);
-
- if (!$previousEntity) return;
-
- $entity->set('order', $previousEntity->get('order'));
- $previousEntity->set('order', $currentIndex);
-
- $this->getEntityManager()->saveEntity($entity);
- $this->getEntityManager()->saveEntity($previousEntity);
- }
-
-
public function moveUp($id, $where = null)
{
$entity = $this->getEntityManager()->getEntity('KnowledgeBaseArticle', $id);
@@ -217,6 +178,43 @@ class KnowledgeBaseArticle extends \Espo\Services\Record
$this->getEntityManager()->saveEntity($entity);
$this->getEntityManager()->saveEntity($nextEntity);
}
+
+ public function moveToTop($id, $where = null)
+ {
+ $entity = $this->getEntityManager()->getEntity('KnowledgeBaseArticle', $id);
+ if (!$entity) throw new NotFound();
+ if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
+
+ $currentIndex = $entity->get('order');
+
+ if (!is_int($currentIndex)) throw new Error();
+
+ if (!$where) {
+ $where = array();
+ }
+
+ $params = array(
+ 'where' => $where
+ );
+
+ $selectManager = $this->getSelectManager();
+ $selectParams = $selectManager->buildSelectParams($params, true, true);
+
+ $selectParams['whereClause'][] = array(
+ 'order<' => $currentIndex
+ );
+
+ $selectManager->applyOrder('order', false, $selectParams);
+
+ $previousEntity = $this->getRepository()->findOne($selectParams);
+
+ if (!$previousEntity) return;
+
+ $entity->set('order', $previousEntity->get('order') - 1);
+
+ $this->getEntityManager()->saveEntity($entity);
+ }
+
public function moveToBottom($id, $where = null)
{
$entity = $this->getEntityManager()->getEntity('KnowledgeBaseArticle', $id);
@@ -248,10 +246,8 @@ class KnowledgeBaseArticle extends \Espo\Services\Record
if (!$nextEntity) return;
- $entity->set('order', $nextEntity->get('order'));
- $nextEntity->set('order', $currentIndex);
+ $entity->set('order', $nextEntity->get('order') + 1);
$this->getEntityManager()->saveEntity($entity);
- $this->getEntityManager()->saveEntity($nextEntity);
}
}
diff --git a/application/Espo/Modules/Crm/Services/MassEmail.php b/application/Espo/Modules/Crm/Services/MassEmail.php
index 9801444792..5dd2d2d63c 100644
--- a/application/Espo/Modules/Crm/Services/MassEmail.php
+++ b/application/Espo/Modules/Crm/Services/MassEmail.php
@@ -337,8 +337,6 @@ class MassEmail extends \Espo\Services\Record
$body = $emailData['body'];
-
-
$optOutUrl = $this->getConfig()->get('siteUrl') . '?entryPoint=unsubscribe&id=' . $queueItem->id;
$optOutLink = ''.$this->getLanguage()->translate('Unsubscribe', 'labels', 'Campaign').'';
@@ -453,6 +451,13 @@ class MassEmail extends \Espo\Services\Record
$header->setId($queueItem->id);
$message->getHeaders()->addHeader($header);
+ $message->getHeaders()->addHeaderLine('Precedence', 'bulk');
+
+ if (!$this->getConfig()->get('massEmailDisableMandatoryOptOutLink')) {
+ $optOutUrl = $this->getConfig()->getSiteUrl() . '?entryPoint=unsubscribe&id=' . $queueItem->id;
+ $message->getHeaders()->addHeaderLine('List-Unsubscribe', '<' . $optOutUrl . '>');
+ }
+
$this->getMailSender()->useGlobal()->send($email, $params, $message, $attachmentList);
$isSent = true;
diff --git a/application/Espo/Modules/Crm/Services/TargetList.php b/application/Espo/Modules/Crm/Services/TargetList.php
index f75c5970fd..b75ad55569 100644
--- a/application/Espo/Modules/Crm/Services/TargetList.php
+++ b/application/Espo/Modules/Crm/Services/TargetList.php
@@ -48,6 +48,14 @@ class TargetList extends \Espo\Services\Record
'User' => 'users'
);
+ protected function init()
+ {
+ parent::init();
+ $this->addDependencyList([
+ 'hookManager'
+ ]);
+ }
+
public function loadAdditionalFields(Entity $entity)
{
parent::loadAdditionalFields($entity);
@@ -178,6 +186,7 @@ class TargetList extends \Espo\Services\Record
}
if ($sql) {
if ($pdo->query($sql)) {
+ $this->getInjection('hookManager')->process('TargetList', 'afterUnlinkAll', $entity, array('link' => $link));
return true;
}
}
diff --git a/application/Espo/ORM/Repositories/RDB.php b/application/Espo/ORM/Repositories/RDB.php
index acd956da5b..f832af56cb 100644
--- a/application/Espo/ORM/Repositories/RDB.php
+++ b/application/Espo/ORM/Repositories/RDB.php
@@ -354,22 +354,26 @@ class RDB extends \Espo\ORM\Repository
protected function beforeRelate(Entity $entity, $relationName, $foreign, $data = null, array $options = array())
{
-
}
protected function afterRelate(Entity $entity, $relationName, $foreign, $data = null, array $options = array())
{
-
}
protected function beforeUnrelate(Entity $entity, $relationName, $foreign, array $options = array())
{
-
}
protected function afterUnrelate(Entity $entity, $relationName, $foreign, array $options = array())
{
+ }
+ protected function beforeMassRelate(Entity $entity, $relationName, array $params = array(), array $options = array())
+ {
+ }
+
+ protected function afterMassRelate(Entity $entity, $relationName, array $params = array(), array $options = array())
+ {
}
public function updateRelation(Entity $entity, $relationName, $foreign, $data)
@@ -391,12 +395,18 @@ class RDB extends \Espo\ORM\Repository
return null;
}
- public function massRelate(Entity $entity, $relationName, array $params = array())
+ public function massRelate(Entity $entity, $relationName, array $params = array(), array $options = array())
{
if (!$entity->id) {
return;
}
- return $this->getMapper()->massRelate($entity, $relationName, $params);
+ $this->beforeMassRelate($entity, $relationName, $params, $options);
+
+ $result = $this->getMapper()->massRelate($entity, $relationName, $params);
+ if ($result) {
+ $this->afterMassRelate($entity, $relationName, $params, $options);
+ }
+ return $result;
}
public function getAll()
diff --git a/application/Espo/Resources/i18n/en_US/DashletOptions.json b/application/Espo/Resources/i18n/en_US/DashletOptions.json
index f11e7f176a..530bf47a1e 100644
--- a/application/Espo/Resources/i18n/en_US/DashletOptions.json
+++ b/application/Espo/Resources/i18n/en_US/DashletOptions.json
@@ -8,7 +8,12 @@
"isDoubleHeight": "Height 2x",
"mode": "Mode",
"enabledScopeList": "What to display",
- "users": "Users"
+ "users": "Users",
+ "entityType": "Entity Type",
+ "primaryFilter": "Primary Filter",
+ "boolFilterList": "Additional Filters",
+ "sortBy": "Order (field)",
+ "sortDirection": "Order (direction)"
},
"options": {
"mode": {
@@ -19,5 +24,8 @@
"agendaDay": "Day (agenda)",
"timeline": "Timeline"
}
+ },
+ "messages": {
+ "selectEntityType": "Select Entity Type in dashlet options."
}
}
diff --git a/application/Espo/Resources/i18n/en_US/Global.json b/application/Espo/Resources/i18n/en_US/Global.json
index 8a24989a1d..2c62a535f2 100644
--- a/application/Espo/Resources/i18n/en_US/Global.json
+++ b/application/Espo/Resources/i18n/en_US/Global.json
@@ -313,7 +313,8 @@
},
"dashlets": {
"Stream": "Stream",
- "Emails": "My Inbox"
+ "Emails": "My Inbox",
+ "Records": "Record List"
},
"notificationMessages": {
"assign": "{entityType} {entity} has been assigned to you",
diff --git a/application/Espo/Resources/metadata/dashlets/Records.json b/application/Espo/Resources/metadata/dashlets/Records.json
new file mode 100644
index 0000000000..aabec551d9
--- /dev/null
+++ b/application/Espo/Resources/metadata/dashlets/Records.json
@@ -0,0 +1,80 @@
+{
+ "options": {
+ "fields": {
+ "title": {
+ "type": "varchar",
+ "required": true
+ },
+ "autorefreshInterval": {
+ "type": "enumFloat",
+ "options": [0, 0.5, 1, 2, 5, 10]
+ },
+ "displayRecords": {
+ "type": "enumInt",
+ "options": [3, 4, 5, 10, 15, 20, 30, 50]
+ },
+ "entityType": {
+ "type": "enum",
+ "view": "views/dashlets/fields/records/entity-type",
+ "translation": "Global.scopeNames"
+ },
+ "primaryFilter": {
+ "type": "enum",
+ "view": "views/dashlets/fields/records/primary-filter"
+ },
+ "boolFilterList": {
+ "type": "multiEnum",
+ "view": "views/dashlets/fields/records/bool-filter-list"
+ },
+ "sortBy": {
+ "type": "enum",
+ "view": "views/dashlets/fields/records/sort-by"
+ },
+ "sortDirection": {
+ "type": "enum",
+ "view": "views/dashlets/fields/records/sort-direction",
+ "options": ["asc", "desc"],
+ "translation": "EntityManager.options.sortDirection"
+ }
+ },
+ "defaults": {
+ "displayRecords": 10,
+ "autorefreshInterval": 0.5,
+ "expandedLayout": {
+ "rows": [
+ [
+ {
+ "name": "name",
+ "link": true
+ }
+ ]
+ ]
+ }
+ },
+ "layout": [
+ {
+ "rows": [
+ [
+ {"name": "title"}
+ ],
+ [
+ {"name": "entityType"},
+ false
+ ],
+ [
+ {"name": "primaryFilter"},
+ {"name": "sortBy"}
+ ],
+ [
+ {"name": "boolFilterList"},
+ {"name": "sortDirection"}
+ ],
+ [
+ {"name": "displayRecords"},
+ {"name": "autorefreshInterval"}
+ ]
+ ]
+ }
+ ]
+ }
+}
diff --git a/client/src/app.js b/client/src/app.js
index 312fc55de2..028f761aa3 100644
--- a/client/src/app.js
+++ b/client/src/app.js
@@ -391,7 +391,7 @@ Espo.define(
this.auth = Base64.encode(data.auth.userName + ':' + data.auth.token);
this.storage.set('user', 'auth', this.auth);
- this.setCookieAuthToken(data.auth.token);
+ this.setCookieAuth(data.auth.userName, data.auth.token);
this.initUserData(data, function () {
this.trigger('auth');
@@ -426,21 +426,24 @@ Espo.define(
this.doAction({action: 'login'});
this.language.clearCache();
- this.unsetCookieAuthToken();
+ this.unsetCookieAuth();
xhr = new XMLHttpRequest;
- xhr.open('GET', this.url + '/', true, 'logout', 'logout');
+ xhr.open('GET', this.url + '/');
+ xhr.setRequestHeader('Authorization', 'Basic ' + Base64.encode('**logout:logout'));
xhr.send('');
xhr.abort();
},
- setCookieAuthToken: function (token) {
+ setCookieAuth: function (username, token) {
var date = new Date();
date.setTime(date.getTime() + (1000 * 24*60*60*1000));
+ document.cookie = 'auth-username='+username+'; expires='+date.toGMTString()+'; path=/';
document.cookie = 'auth-token='+token+'; expires='+date.toGMTString()+'; path=/';
},
- unsetCookieAuthToken: function () {
+ unsetCookieAuth: function () {
+ document.cookie = 'auth-username' + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';
document.cookie = 'auth-token' + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/';
},
@@ -485,14 +488,19 @@ Espo.define(
return;
}
- var arr = Base64.decode(this.auth).split(':');
var xhr = new XMLHttpRequest();
- xhr.open('GET', this.basePath + this.url + '/', true, arr[0], arr[1]);
+
+ xhr.open('GET', this.basePath + this.url + '/');
+ xhr.setRequestHeader('Authorization', 'Basic ' + this.auth);
+
+ xhr.onreadystatechange = function () {
+ if (callback) {
+ callback();
+ }
+ }
+
xhr.send('');
- if (callback) {
- callback();
- }
}.bind(this));
},
diff --git a/client/src/view-helper.js b/client/src/view-helper.js
index 6c5a8d6546..dcb92f7c84 100644
--- a/client/src/view-helper.js
+++ b/client/src/view-helper.js
@@ -43,7 +43,7 @@ Espo.define('view-helper', [], function () {
this.mdReplace = [
'$1',
function (s, string) {
- return '' + string.replace(/\*/g, '*').replace(/\~/g, '~') + '';
+ return '
' + string.replace(/\*/g, '*').replace(/\~/g, '~') + '';
},
function (s, string) {
return '' + string.replace(/\*/g, '*').replace(/\~/g, '~') + '';
diff --git a/client/src/views/dashlets/abstract/record-list.js b/client/src/views/dashlets/abstract/record-list.js
index 6e410986d6..b8a83eddb7 100644
--- a/client/src/views/dashlets/abstract/record-list.js
+++ b/client/src/views/dashlets/abstract/record-list.js
@@ -60,9 +60,20 @@ Espo.define('views/dashlets/abstract/record-list', ['views/dashlets/abstract/bas
return this.getAcl().check(this.scope, 'read');
},
+ getSearchData: function () {
+ return this.getOption('searchData');
+ },
+
afterRender: function () {
this.getCollectionFactory().create(this.scope, function (collection) {
- var searchManager = this.searchManager = new SearchManager(collection, 'list', null, this.getDateTime(), this.getOption('searchData'));
+ var searchData = this.getSearchData();
+
+ var searchManager = this.searchManager = new SearchManager(collection, 'list', null, this.getDateTime(), searchData);
+
+ if (!this.scope) {
+ this.$el.find('.list-container').html(this.translate('selectEntityType', 'messages', 'DashletOptions'));
+ return;
+ }
if (!this.checkAccess()) {
this.$el.find('.list-container').html(this.translate('No Access'));
@@ -76,6 +87,13 @@ Espo.define('views/dashlets/abstract/record-list', ['views/dashlets/abstract/bas
this.collection = collection;
collection.sortBy = this.getOption('sortBy') || this.collection.sortBy;
collection.asc = this.getOption('asc') || this.collection.asc;
+
+ if (this.getOption('sortDirection') === 'asc') {
+ collection.asc = true;
+ } else if (this.getOption('sortDirection') === 'desc') {
+ collection.asc = false;
+ }
+
collection.maxSize = this.getOption('displayRecords');
collection.where = searchManager.getWhere();
@@ -102,7 +120,7 @@ Espo.define('views/dashlets/abstract/record-list', ['views/dashlets/abstract/bas
},
setupActionList: function () {
- if (this.getAcl().checkScope(this.scope, 'create')) {
+ if (this.scope && this.getAcl().checkScope(this.scope, 'create')) {
this.actionList.unshift({
name: 'create',
html: this.translate('Create ' + this.scope, 'labels', this.scope),
@@ -113,6 +131,8 @@ Espo.define('views/dashlets/abstract/record-list', ['views/dashlets/abstract/bas
},
actionRefresh: function () {
+ if (!this.collection) return;
+
this.collection.where = this.searchManager.getWhere();
this.collection.fetch();
},
diff --git a/client/src/views/dashlets/fields/records/bool-filter-list.js b/client/src/views/dashlets/fields/records/bool-filter-list.js
new file mode 100644
index 0000000000..6895cedde2
--- /dev/null
+++ b/client/src/views/dashlets/fields/records/bool-filter-list.js
@@ -0,0 +1,61 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2017 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+Espo.define('views/dashlets/fields/records/bool-filter-list', 'views/fields/multi-enum', function (Dep) {
+
+ return Dep.extend({
+
+ setup: function () {
+ Dep.prototype.setup.call(this);
+
+ this.listenTo(this.model, 'change:entityType', function () {
+ this.setupOptions();
+ this.reRender();
+ }, this);
+ },
+
+ setupOptions: function () {
+ var entityType = this.model.get('entityType');
+ if (!entityType) {
+ this.params.options = [];
+ return;
+ }
+ this.params.options = this.getMetadata().get(['clientDefs', entityType, 'boolFilterList']) || [];
+
+ if (this.getMetadata().get(['scopes', entityType, 'stream']) && this.getAcl().checkScope(entityType, 'stream')) {
+ this.params.options.push('followed');
+ }
+
+ this.translatedOptions = {};
+ this.params.options.forEach(function (item) {
+ this.translatedOptions[item] = this.translate(item, 'boolFilters', entityType);
+ }, this);
+ }
+
+ });
+
+});
diff --git a/client/src/views/dashlets/fields/records/entity-type.js b/client/src/views/dashlets/fields/records/entity-type.js
new file mode 100644
index 0000000000..49fb80eb95
--- /dev/null
+++ b/client/src/views/dashlets/fields/records/entity-type.js
@@ -0,0 +1,76 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2017 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+Espo.define('views/dashlets/fields/records/entity-type', 'views/fields/enum', function (Dep) {
+
+ return Dep.extend({
+
+ setup: function () {
+ Dep.prototype.setup.call(this);
+
+ this.on('change', function () {
+ var o = {
+ primaryFilter: null,
+ boolFilterList: [],
+ title: this.translate('Records', 'dashlets'),
+ sortBy: null,
+ sortDirection: 'asc'
+ };
+ var entityType = this.model.get('entityType');
+ if (entityType) {
+ o.title = this.translate(entityType, 'scopeNamesPlural');
+ o.sortBy = this.getMetadata().get(['entityDefs', entityType, 'collection', 'sortBy']);
+ var asc = this.getMetadata().get(['entityDefs', entityType, 'collection', 'asc']);
+ if (asc) {
+ o.sortDirection = 'asc';
+ } else {
+ o.sortDirection = 'desc';
+ }
+ }
+
+ this.model.set(o);
+ }, this);
+ },
+
+ setupOptions: function () {
+ this.params.options = Object.keys(this.getMetadata().get('scopes')).filter(function (scope) {
+ if (this.getMetadata().get('scopes.' + scope + '.disabled')) return;
+ if (!this.getAcl().checkScope(scope, 'read')) return;
+ if (!this.getMetadata().get(['scopes', scope, 'entity'])) return;
+ if (!this.getMetadata().get(['scopes', scope, 'object'])) return;
+
+ return true;
+ }, this).sort(function (v1, v2) {
+ return this.translate(v1, 'scopeNames').localeCompare(this.translate(v2, 'scopeNames'));
+ }.bind(this));
+
+ this.params.options.unshift('');
+ }
+
+ });
+
+});
diff --git a/client/src/views/dashlets/fields/records/primary-filter.js b/client/src/views/dashlets/fields/records/primary-filter.js
new file mode 100644
index 0000000000..96642794d6
--- /dev/null
+++ b/client/src/views/dashlets/fields/records/primary-filter.js
@@ -0,0 +1,68 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2017 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+Espo.define('views/dashlets/fields/records/primary-filter', 'views/fields/enum', function (Dep) {
+
+ return Dep.extend({
+
+ setup: function () {
+ Dep.prototype.setup.call(this);
+
+ this.listenTo(this.model, 'change:entityType', function () {
+ this.setupOptions();
+ this.reRender();
+ }, this);
+ },
+
+ setupOptions: function () {
+ var entityType = this.model.get('entityType');
+ if (!entityType) {
+ this.params.options = [];
+ return;
+ }
+ var filterList = this.getMetadata().get(['clientDefs', entityType, 'filterList']) || [];
+ this.params.options = [];
+
+ filterList.forEach(function (item) {
+ if (typeof item === 'object' && item.name) {
+ this.params.options.push(item.name);
+ return;
+ }
+ this.params.options.push(item);
+ }, this);
+
+ this.params.options.unshift('all');
+
+ this.translatedOptions = {};
+ this.params.options.forEach(function (item) {
+ this.translatedOptions[item] = this.translate(item, 'presetFilters', entityType);
+ }, this);
+ }
+
+ });
+
+});
diff --git a/client/src/views/dashlets/fields/records/sort-by.js b/client/src/views/dashlets/fields/records/sort-by.js
new file mode 100644
index 0000000000..b7c520f65b
--- /dev/null
+++ b/client/src/views/dashlets/fields/records/sort-by.js
@@ -0,0 +1,71 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2017 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+Espo.define('views/dashlets/fields/records/sort-by', 'views/fields/enum', function (Dep) {
+
+ return Dep.extend({
+
+ setup: function () {
+ Dep.prototype.setup.call(this);
+
+ this.listenTo(this.model, 'change:entityType', function () {
+ this.setupOptions();
+ this.reRender();
+ }, this);
+ },
+
+ setupOptions: function () {
+ var entityType = this.model.get('entityType');
+ var scope = entityType;
+
+ if (!entityType) {
+ this.params.options = [];
+ return;
+ }
+ var fieldDefs = this.getMetadata().get('entityDefs.' + scope + '.fields') || {};
+
+ var orderableFieldList = Object.keys(fieldDefs).filter(function (item) {
+ if (fieldDefs[item].notStorable) {
+ return false;
+ }
+ return true;
+ }, this).sort(function (v1, v2) {
+ return this.translate(v1, 'fields', scope).localeCompare(this.translate(v2, 'fields', scope));
+ }.bind(this));
+
+ var translatedOptions = {};
+ orderableFieldList.forEach(function (item) {
+ translatedOptions[item] = this.translate(item, 'fields', scope);
+ }, this);
+
+ this.params.options = orderableFieldList;
+ this.translatedOptions = translatedOptions;
+ }
+
+ });
+
+});
diff --git a/client/src/views/dashlets/fields/records/sort-direction.js b/client/src/views/dashlets/fields/records/sort-direction.js
new file mode 100644
index 0000000000..eb7fc7f24f
--- /dev/null
+++ b/client/src/views/dashlets/fields/records/sort-direction.js
@@ -0,0 +1,43 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2017 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+Espo.define('views/dashlets/fields/records/sort-direction', 'views/fields/enum', function (Dep) {
+
+ return Dep.extend({
+
+ setup: function () {
+ Dep.prototype.setup.call(this);
+
+ this.listenTo(this.model, 'change:entityType', function () {
+ this.setupOptions();
+ this.reRender();
+ }, this);
+ }
+
+ });
+
+});
diff --git a/client/src/views/dashlets/records.js b/client/src/views/dashlets/records.js
new file mode 100644
index 0000000000..2cecc4a5e5
--- /dev/null
+++ b/client/src/views/dashlets/records.js
@@ -0,0 +1,75 @@
+/************************************************************************
+ * This file is part of EspoCRM.
+ *
+ * EspoCRM - Open Source CRM application.
+ * Copyright (C) 2014-2017 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
+ * Website: http://www.espocrm.com
+ *
+ * EspoCRM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * EspoCRM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with EspoCRM. If not, see http://www.gnu.org/licenses/.
+ *
+ * The interactive user interfaces in modified source and object code versions
+ * of this program must display Appropriate Legal Notices, as required under
+ * Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License version 3,
+ * these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
+ ************************************************************************/
+
+Espo.define('views/dashlets/records', 'views/dashlets/abstract/record-list', function (Dep) {
+
+ return Dep.extend({
+
+ name: 'Records',
+
+ scope: null,
+
+ rowActionsView: 'views/record/row-actions/view-and-edit',
+
+ listView: 'views/email/record/list-expanded',
+
+ init: function () {
+ Dep.prototype.init.call(this);
+ this.scope = this.getOption('entityType');
+ },
+
+ getSearchData: function () {
+ var data = {
+ primary: this.getOption('primaryFilter')
+ };
+
+ var bool = {};
+ (this.getOption('boolFilterList') || []).forEach(function (item) {
+ bool[item] = true;
+ }, this);
+
+ data.bool = bool;
+
+ return data;
+ },
+
+ setupActionList: function () {
+ var scope = this.getOption('entityType');
+ if (scope && this.getAcl().checkScope(scope, 'create')) {
+ this.actionList.unshift({
+ name: 'create',
+ html: this.translate('Create ' + scope, 'labels', scope),
+ iconHtml: '',
+ url: '#' + scope + '/create'
+ });
+ }
+ },
+
+ });
+});
+
diff --git a/client/src/views/email/record/detail.js b/client/src/views/email/record/detail.js
index 7e92719dd7..e166310469 100644
--- a/client/src/views/email/record/detail.js
+++ b/client/src/views/email/record/detail.js
@@ -294,6 +294,28 @@ Espo.define('views/email/record/detail', 'views/record/detail', function (Dep) {
this.save();
},
+ exitAfterDelete: function () {
+ var folderId = ((this.collection || {}).data || {}).folderId || null;
+
+ if (folderId === 'inbox') {
+ folderId = null;
+ }
+
+ var options = {
+ isReturn: true,
+ isReturnThroughLink: false,
+ folder: folderId
+ };
+ var url = '#' + this.scope;
+ var action = null;
+ if (folderId) {
+ action = 'list';
+ url += '/list/folder=' + folderId;
+ }
+ this.getRouter().dispatch(this.scope, action, options);
+ this.getRouter().navigate(url, {trigger: false});
+ }
+
});
});
diff --git a/client/src/views/record/detail.js b/client/src/views/record/detail.js
index 4d60152ed8..3a1ae19650 100644
--- a/client/src/views/record/detail.js
+++ b/client/src/views/record/detail.js
@@ -612,7 +612,7 @@ Espo.define('views/record/detail', ['views/record/base', 'view-record-helper'],
this.recordHelper = new ViewRecordHelper(this.defaultFieldStates, this.defaultFieldStates);
- var collection = this.model.collection;
+ var collection = this.collection = this.model.collection;
if (collection) {
this.listenTo(this.model, 'destroy', function () {
collection.remove(this.model.id);
@@ -1174,6 +1174,13 @@ Espo.define('views/record/detail', ['views/record/base', 'view-record-helper'],
if (this.returnUrl) {
url = this.returnUrl;
} else {
+ if (after) {
+ var methodName = 'exitAfter' + Espo.Utils.upperCaseFirst(after);
+ if (methodName in this) {
+ this[methodName]();
+ return;
+ }
+ }
if (after == 'delete') {
this.getRouter().dispatch(this.scope, null, {
isReturn: true
diff --git a/client/src/views/record/list.js b/client/src/views/record/list.js
index 16d8b928ec..ace534d34c 100644
--- a/client/src/views/record/list.js
+++ b/client/src/views/record/list.js
@@ -271,7 +271,7 @@ Espo.define('views/record/list', 'view', function (Dep) {
this.massActionsDisabled = this.options.massActionsDisabled || this.massActionsDisabled;
- if (this.massActionsDisabled) {
+ if (this.massActionsDisabled && !this.selectable) {
this.checkboxes = false;
}
diff --git a/frontend/less/espo/custom.less b/frontend/less/espo/custom.less
index 9d9d128e85..7615ad60b8 100644
--- a/frontend/less/espo/custom.less
+++ b/frontend/less/espo/custom.less
@@ -747,6 +747,10 @@ td.cell[data-name="buttons"] > .btn-group {
height: ~"calc(100% - 29px)";
}
}
+ .grid-stack-item-content {
+ overflow-x: visible;
+ overflow-y: visible;
+ }
}
.ui-resizable-handle:before {
@@ -1334,6 +1338,10 @@ h5 {
position: relative;
}
+code {
+ font-style: italic;
+}
+
@media screen and (min-width: @screen-sm-min) {
#global-search-panel {
margin-top: 5px;
diff --git a/frontend/less/espo/variables.less b/frontend/less/espo/variables.less
index 5496a6aab0..d0a4e18ca3 100644
--- a/frontend/less/espo/variables.less
+++ b/frontend/less/espo/variables.less
@@ -6,7 +6,6 @@
@font-family-base: @font-family-sans-serif;
@headings-font-family: @font-family-base;
-
@border-radius-base: 0;
@border-radius-large: 0;
@border-radius-small: 0;
@@ -57,6 +56,12 @@
@btn-default-bg: @main-gray;
@btn-default-border: @main-gray;
+@code-bg: @main-gray;
+@code-color: @text-color;
+
+@pre-border-color: @main-gray;
+@pre-bg: @main-gray;
+
@btn-success-border: @btn-success-bg;
@btn-primary-border: @btn-primary-bg;
@btn-danger-border: @btn-danger-bg;