diff --git a/application/Espo/Core/ORM/Entity.php b/application/Espo/Core/ORM/Entity.php
index 262c8f5856..b0fdd88c92 100644
--- a/application/Espo/Core/ORM/Entity.php
+++ b/application/Espo/Core/ORM/Entity.php
@@ -137,7 +137,7 @@ class Entity extends \Espo\ORM\Entity
}
$this->set($idsAttribute, $ids);
- if (!$this->hasFetched($idsAttribute)) {
+ if (!$this->isNew() && !$this->hasFetched($idsAttribute)) {
$this->setFetched($idsAttribute, $ids);
}
@@ -170,7 +170,13 @@ class Entity extends \Espo\ORM\Entity
$entityName = $entity->get('name');
}
- $this->set($field . 'Id', $entityId);
+ $idAttribute = $field . 'Id';
+
+ if (!$this->isNew() && !$this->hasFetched($idAttribute)) {
+ $this->setFetched($idAttribute, $entityId);
+ }
+
+ $this->set($idAttribute, $entityId);
$this->set($field . 'Name', $entityName);
}
diff --git a/application/Espo/Core/ORM/Repositories/RDB.php b/application/Espo/Core/ORM/Repositories/RDB.php
index 8c741c7d94..ae6b912a5d 100644
--- a/application/Espo/Core/ORM/Repositories/RDB.php
+++ b/application/Espo/Core/ORM/Repositories/RDB.php
@@ -465,17 +465,21 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$data->$columnName = $foreignEntity->get($columnField);
}
$existingColumnsData->$foreignId = $data;
- $entity->setFetched($columnsFieldsName, $existingColumnsData);
+ if (!$entity->isNew()) {
+ $entity->setFetched($columnsFieldsName, $existingColumnsData);
+ }
}
}
}
- if ($entity->has($fieldName)) {
- $entity->setFetched($fieldName, $existingIds);
- }
- if ($entity->has($columnsFieldsName) && !empty($columns)) {
- $entity->setFetched($columnsFieldsName, $existingColumnsData);
+ if (!$entity->isNew()) {
+ if ($entity->has($fieldName)) {
+ $entity->setFetched($fieldName, $existingIds);
+ }
+ if ($entity->has($columnsFieldsName) && !empty($columns)) {
+ $entity->setFetched($columnsFieldsName, $existingColumnsData);
+ }
}
foreach ($existingIds as $id) {
@@ -540,13 +544,17 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$where[$foreignKey] = $entity->id;
$previousForeignEntity = $this->getEntityManager()->getRepository($foreignEntityType)->where($where)->findOne();
if ($previousForeignEntity) {
- $entity->setFetched($idFieldName, $previousForeignEntity->id);
+ if (!$entity->isNew()) {
+ $entity->setFetched($idFieldName, $previousForeignEntity->id);
+ }
if ($previousForeignEntity->id !== $entity->get($idFieldName)) {
$previousForeignEntity->set($foreignKey, null);
$this->getEntityManager()->saveEntity($previousForeignEntity);
}
} else {
- $entity->setFetched($idFieldName, null);
+ if (!$entity->isNew()) {
+ $entity->setFetched($idFieldName, null);
+ }
}
if ($entity->get($idFieldName)) {
diff --git a/application/Espo/Core/SelectManagers/Base.php b/application/Espo/Core/SelectManagers/Base.php
index 18d709e0d1..a800584c97 100644
--- a/application/Espo/Core/SelectManagers/Base.php
+++ b/application/Espo/Core/SelectManagers/Base.php
@@ -1255,7 +1255,7 @@ class Base
$value = $item['value'];
- if (is_null($value)) break;
+ if (is_null($value) || !$value && !is_array($value)) break;
$relationType = $seed->getRelationType($link);
@@ -1565,6 +1565,12 @@ class Base
$useFullTextSearch = false;
}
+ if ($isAuxiliaryUse) {
+ if (mb_strpos($textFilter, '@') !== false) {
+ $useFullTextSearch = false;
+ }
+ }
+
if ($useFullTextSearch) {
$textFilter = str_replace(['(', ')'], '', $textFilter);
@@ -1582,10 +1588,11 @@ class Base
$function = 'MATCH_NATURAL_LANGUAGE';
} else {
$function = 'MATCH_BOOLEAN';
-
- $textFilter = str_replace('@', '*', $textFilter);
}
+ $textFilter = str_replace('"*', '"', $textFilter);
+ $textFilter = str_replace('*"', '"', $textFilter);
+
while (strpos($textFilter, '**')) {
$textFilter = str_replace('**', '*', $textFilter);
$textFilter = trim($textFilter);
diff --git a/application/Espo/Core/Utils/Database/Orm/Fields/LinkMultiple.php b/application/Espo/Core/Utils/Database/Orm/Fields/LinkMultiple.php
index 9b4cbcad94..ec0843890e 100644
--- a/application/Espo/Core/Utils/Database/Orm/Fields/LinkMultiple.php
+++ b/application/Espo/Core/Utils/Database/Orm/Fields/LinkMultiple.php
@@ -33,28 +33,29 @@ class LinkMultiple extends Base
{
protected function load($fieldName, $entityName)
{
- $data = array(
- $entityName => array (
- 'fields' => array(
- $fieldName.'Ids' => array(
+ $data = [
+ $entityName => [
+ 'fields' => [
+ $fieldName.'Ids' => [
'type' => 'jsonArray',
'notStorable' => true,
'isLinkMultipleIdList' => true,
- 'relation' => $fieldName
- ),
- $fieldName.'Names' => array(
+ 'relation' => $fieldName,
+ 'isUnordered' => true
+ ],
+ $fieldName.'Names' => [
'type' => 'jsonObject',
'notStorable' => true,
'isLinkMultipleNameMap' => true
- )
- )
- ),
- 'unset' => array(
- $entityName => array(
- 'fields.'.$fieldName
- )
- )
- );
+ ]
+ ]
+ ],
+ 'unset' => [
+ $entityName => [
+ 'fields.' . $fieldName
+ ]
+ ]
+ ];
$fieldParams = $this->getFieldParams();
@@ -67,10 +68,10 @@ class LinkMultiple extends Base
$columns = $this->getMetadata()->get("entityDefs.{$entityName}.fields.{$fieldName}.columns");
if (!empty($columns)) {
- $data[$entityName]['fields'][$fieldName . 'Columns'] = array(
+ $data[$entityName]['fields'][$fieldName . 'Columns'] = [
'type' => 'jsonObject',
'notStorable' => true,
- );
+ ];
}
return $data;
diff --git a/application/Espo/Core/Utils/EntityManager.php b/application/Espo/Core/Utils/EntityManager.php
index 8f6540b5a9..65898b43e7 100644
--- a/application/Espo/Core/Utils/EntityManager.php
+++ b/application/Espo/Core/Utils/EntityManager.php
@@ -662,7 +662,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
- "layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
@@ -727,7 +726,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
- "layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
@@ -751,7 +749,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
- "layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
@@ -774,7 +771,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
- "layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
@@ -848,7 +844,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
- "layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
@@ -873,7 +868,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
- "layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
diff --git a/application/Espo/Modules/Crm/Resources/routes.json b/application/Espo/Modules/Crm/Resources/routes.json
index 35897869f4..935f83736c 100644
--- a/application/Espo/Modules/Crm/Resources/routes.json
+++ b/application/Espo/Modules/Crm/Resources/routes.json
@@ -1,22 +1,21 @@
[
- {
- "route":"/Activities/:scope/:id/:name",
- "method":"get",
- "params":{
- "controller":"Activities",
- "action":"list",
- "scope":":scope",
- "id":":id",
- "name":":name"
+ {
+ "route": "/Activities/:scope/:id/:name",
+ "method": "get",
+ "params": {
+ "controller": "Activities",
+ "action": "list",
+ "scope": ":scope",
+ "id": ":id",
+ "name": ":name"
+ }
+ },
+ {
+ "route": "/Activities",
+ "method": "get",
+ "params": {
+ "controller": "Activities",
+ "action": "listCalendarEvents"
+ }
}
- },
-
- {
- "route":"/Activities",
- "method":"get",
- "params":{
- "controller":"Activities",
- "action":"listCalendarEvents"
- }
- }
]
diff --git a/application/Espo/Modules/Crm/SelectManagers/CampaignLogRecord.php b/application/Espo/Modules/Crm/SelectManagers/CampaignLogRecord.php
index 1cbf3d7e0f..01de4d388c 100644
--- a/application/Espo/Modules/Crm/SelectManagers/CampaignLogRecord.php
+++ b/application/Espo/Modules/Crm/SelectManagers/CampaignLogRecord.php
@@ -33,19 +33,23 @@ class CampaignLogRecord extends \Espo\Core\SelectManagers\Base
{
protected function accessOnlyOwn(&$result)
{
+ $this->addLeftJoin(['campaign', 'campaignAccess'], $result);
+
$result['whereClause'][] = array(
- 'campaign.assignedUserId' => $this->getUser()->id
+ 'campaignAccess.assignedUserId' => $this->getUser()->id
);
}
protected function accessOnlyTeam(&$result)
{
+ $this->addLeftJoin(['campaign', 'campaignAccess'], $result);
+
$teamIdList = $this->user->get('teamsIds');
if (empty($result['customWhere'])) {
$result['customWhere'] = '';
}
if (empty($teamIdList)) {
- $result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
+ $result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
return;
}
$arr = [];
@@ -55,12 +59,12 @@ class CampaignLogRecord extends \Espo\Core\SelectManagers\Base
}
}
- $result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
+ $result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
$result['customWhere'] .= "
AND (
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
OR
- campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
+ campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
)
";
$result['whereClause'][] = array(
diff --git a/application/Espo/Modules/Crm/SelectManagers/CampaignTrackingUrl.php b/application/Espo/Modules/Crm/SelectManagers/CampaignTrackingUrl.php
index 294153903b..128c8a9931 100644
--- a/application/Espo/Modules/Crm/SelectManagers/CampaignTrackingUrl.php
+++ b/application/Espo/Modules/Crm/SelectManagers/CampaignTrackingUrl.php
@@ -33,19 +33,23 @@ class CampaignTrackingUrl extends \Espo\Core\SelectManagers\Base
{
protected function accessOnlyOwn(&$result)
{
+ $this->addLeftJoin(['campaign', 'campaignAccess'], $result);
+
$result['whereClause'][] = array(
- 'campaign.assignedUserId' => $this->getUser()->id
+ 'campaignAccess.assignedUserId' => $this->getUser()->id
);
}
protected function accessOnlyTeam(&$result)
{
+ $this->addLeftJoin(['campaign', 'campaignAccess'], $result);
+
$teamIdList = $this->user->get('teamsIds');
if (empty($result['customWhere'])) {
$result['customWhere'] = '';
}
if (empty($teamIdList)) {
- $result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
+ $result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
return;
}
$arr = [];
@@ -55,12 +59,12 @@ class CampaignTrackingUrl extends \Espo\Core\SelectManagers\Base
}
}
- $result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
+ $result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
$result['customWhere'] .= "
AND (
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
OR
- campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
+ campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
)
";
$result['whereClause'][] = array(
diff --git a/application/Espo/Modules/Crm/SelectManagers/MassEmail.php b/application/Espo/Modules/Crm/SelectManagers/MassEmail.php
index fb6f8b1a09..e53cdd7960 100644
--- a/application/Espo/Modules/Crm/SelectManagers/MassEmail.php
+++ b/application/Espo/Modules/Crm/SelectManagers/MassEmail.php
@@ -45,21 +45,25 @@ class MassEmail extends \Espo\Core\SelectManagers\Base
);
}
- protected function acessOnlyOwn(&$result)
+ protected function accessOnlyOwn(&$result)
{
+ $this->addLeftJoin(['campaign', 'campaignAccess'], $result);
+
$result['whereClause'][] = array(
- 'campaign.assignedUserId' => $this->getUser()->id
+ 'campaignAccess.assignedUserId' => $this->getUser()->id
);
}
protected function accessOnlyTeam(&$result)
{
- $teamIdList = $this->user->get('teamsIds');
+ $this->addLeftJoin(['campaign', 'campaignAccess'], $result);
+
+ $teamIdList = $this->user->getLinkMultipleIdList('teams');
if (empty($result['customWhere'])) {
$result['customWhere'] = '';
}
if (empty($teamIdList)) {
- $result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
+ $result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
return;
}
$arr = [];
@@ -69,12 +73,12 @@ class MassEmail extends \Espo\Core\SelectManagers\Base
}
}
- $result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
+ $result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
$result['customWhere'] .= "
AND (
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
OR
- campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
+ campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
)
";
$result['whereClause'][] = array(
diff --git a/application/Espo/ORM/DB/Mapper.php b/application/Espo/ORM/DB/Mapper.php
index 16f1aece6e..7a4490cd5c 100644
--- a/application/Espo/ORM/DB/Mapper.php
+++ b/application/Espo/ORM/DB/Mapper.php
@@ -718,7 +718,7 @@ abstract class Mapper implements IMapper
public function insert(IEntity $entity)
{
- $dataArr = $this->toArray($entity);
+ $dataArr = $this->toValueMap($entity);
$fieldArr = array();
$valArr = array();
@@ -745,33 +745,35 @@ abstract class Mapper implements IMapper
public function update(IEntity $entity)
{
- $dataArr = $this->toArray($entity);
+ $valueMap = $this->toValueMap($entity);
- $setArr = array();
- foreach ($dataArr as $field => $value) {
- if ($field == 'id') {
+ $setArr = [];
+
+ foreach ($valueMap as $attribute => $value) {
+ if ($attribute == 'id') {
continue;
}
- $type = $entity->fields[$field]['type'];
+ $type = $entity->getAttributeType($attribute);
if ($type == IEntity::FOREIGN) {
continue;
}
- if ($entity->getFetched($field) === $value && $type != IEntity::JSON_ARRAY && $type != IEntity::JSON_OBJECT) {
+ if (!$entity->isAttributeChanged($attribute) && $type !== IEntity::JSON_OBJECT) {
continue;
}
$value = $this->prepareValueForInsert($type, $value);
- $setArr[] = "`" . $this->toDb($field) . "` = " . $this->quote($value);
+ $setArr[] = "`" . $this->toDb($attribute) . "` = " . $this->quote($value);
}
+
if (count($setArr) == 0) {
return $entity->id;
}
$setPart = implode(', ', $setArr);
- $wherePart = $this->query->getWhere($entity, array('id' => $entity->id, 'deleted' => 0));
+ $wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]);
$sql = $this->composeUpdateQuery($this->toDb($entity->getEntityType()), $setPart, $wherePart);
@@ -815,21 +817,25 @@ abstract class Mapper implements IMapper
return $this->update($entity);
}
- protected function toArray(IEntity $entity, $onlyStorable = true)
+ protected function toValueMap(IEntity $entity, $onlyStorable = true)
{
- $arr = array();
- foreach ($entity->fields as $field => $fieldDefs) {
- if ($entity->has($field)) {
+ $data = [];
+ foreach ($entity->getAttributes() as $attribute => $defs) {
+ if ($entity->has($attribute)) {
if ($onlyStorable) {
- if (!empty($fieldDefs['notStorable']) || !empty($fieldDefs['autoincrement']) || isset($fieldDefs['source']) && $fieldDefs['source'] != 'db')
- continue;
- if ($fieldDefs['type'] == IEntity::FOREIGN)
- continue;
+ if (
+ !empty($defs['notStorable'])
+ ||
+ !empty($defs['autoincrement'])
+ ||
+ isset($defs['source']) && $defs['source'] != 'db'
+ ) continue;
+ if ($defs['type'] == IEntity::FOREIGN) continue;
}
- $arr[$field] = $entity->get($field);
+ $data[$attribute] = $entity->get($attribute);
}
}
- return $arr;
+ return $data;
}
protected function fromRow(IEntity $entity, $data)
diff --git a/application/Espo/ORM/Entity.php b/application/Espo/ORM/Entity.php
index 74fbc3bd07..afd2757d3d 100644
--- a/application/Espo/ORM/Entity.php
+++ b/application/Espo/ORM/Entity.php
@@ -369,25 +369,92 @@ abstract class Entity implements IEntity
return $this->isFetched;
}
- public function isFieldChanged($fieldName)
+ public function isFieldChanged($name)
{
- return $this->has($fieldName) && ($this->get($fieldName) != $this->getFetched($fieldName));
+ return $this->has($name) && ($this->get($name) != $this->getFetched($name));
}
- public function isAttributeChanged($fieldName)
+ public function isAttributeChanged($name)
{
- return $this->has($fieldName) && ($this->get($fieldName) != $this->getFetched($fieldName));
+ if (!$this->has($name)) return false;
+
+ if (!$this->hasFetched($name)) {
+ return true;
+ }
+ return !self::areValuesEqual(
+ $this->getAttributeType($name),
+ $this->get($name),
+ $this->getFetched($name),
+ $this->getAttributeParam($name, 'isUnordered')
+ );
+
+ return $this->get($name) != $this->getFetched($name);
}
- public function setFetched($fieldName, $value)
+ public static function areValuesEqual($type, $v1, $v2, $isUnordered = false)
{
- $this->fetchedValuesContainer[$fieldName] = $value;
+ if ($type === self::JSON_ARRAY) {
+ if (is_array($v1) && is_array($v2)) {
+ if ($isUnordered) {
+ sort($v1);
+ sort($v2);
+ }
+ if ($v1 != $v2) {
+ return false;
+ }
+ foreach ($v1 as $i => $itemValue) {
+ if (is_object($v1[$i]) && is_object($v2[$i])) {
+ if (!self::areValuesEqual(self::JSON_OBJECT, $v1[$i], $v2[$i])) {
+ return false;
+ }
+ continue;
+ }
+ if ($v1[$i] !== $v2[$i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ } else if ($type === self::JSON_OBJECT) {
+ if (is_object($v1) && is_object($v2)) {
+ if ($v1 != $v2) {
+ return false;
+ }
+ $a1 = get_object_vars($v1);
+ $a2 = get_object_vars($v2);
+ foreach ($v1 as $key => $itemValue) {
+ if (is_object($a1[$key]) && is_object($a2[$key])) {
+ if (!self::areValuesEqual(self::JSON_OBJECT, $a1[$key], $a2[$key])) {
+ return false;
+ }
+ continue;
+ }
+ if (is_array($a1[$key]) && is_array($a2[$key])) {
+ if (!self::areValuesEqual(self::JSON_ARRAY, $a1[$key], $a2[$key])) {
+ return false;
+ }
+ continue;
+ }
+ if ($a1[$key] !== $a2[$key]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ return $v1 === $v2;
}
- public function getFetched($fieldName)
+ public function setFetched($name, $value)
{
- if (isset($this->fetchedValuesContainer[$fieldName])) {
- return $this->fetchedValuesContainer[$fieldName];
+ $this->fetchedValuesContainer[$name] = $value;
+ }
+
+ public function getFetched($name)
+ {
+ if (isset($this->fetchedValuesContainer[$name])) {
+ return $this->fetchedValuesContainer[$name];
}
return null;
}
diff --git a/application/Espo/Resources/routes.json b/application/Espo/Resources/routes.json
index 045684706f..62d8a70383 100644
--- a/application/Espo/Resources/routes.json
+++ b/application/Espo/Resources/routes.json
@@ -1,319 +1,326 @@
[
{
- "route":"/",
- "method":"get",
- "params":"
EspoCRM REST API<\/h1>"
+ "route": "/",
+ "method": "get",
+ "params": "EspoCRM REST API<\/h1>"
},
{
- "route":"/App/user",
- "method":"get",
- "params":{
- "controller":"App",
- "action":"user"
+ "route": "/App/user",
+ "method": "get",
+ "params": {
+ "controller": "App",
+ "action": "user"
}
},
{
- "route":"/Metadata",
- "method":"get",
- "params":{
- "controller":"Metadata"
+ "route": "/Metadata",
+ "method": "get",
+ "params": {
+ "controller": "Metadata"
}
},
{
- "route":"I18n",
- "method":"get",
- "params":{
- "controller":"I18n"
+ "route": "I18n",
+ "method": "get",
+ "params": {
+ "controller": "I18n"
},
- "conditions":{
- "auth":false
+ "conditions": {
+ "auth": false
}
},
{
- "route":"/Settings",
- "method":"get",
- "params":{
- "controller":"Settings"
+ "route": "/Settings",
+ "method": "get",
+ "params": {
+ "controller": "Settings"
},
- "conditions":{
- "auth":false
+ "conditions": {
+ "auth": false
}
},
{
- "route":"/Settings",
- "method":"patch",
- "params":{
- "controller":"Settings"
+ "route": "/Settings",
+ "method": "patch",
+ "params": {
+ "controller": "Settings"
}
},
{
- "route":"User/passwordChangeRequest",
- "method":"post",
- "params":{
- "controller":"User",
- "action":"passwordChangeRequest"
+ "route": "/Settings",
+ "method": "put",
+ "params": {
+ "controller": "Settings"
+ }
+ },
+ {
+ "route": "User/passwordChangeRequest",
+ "method": "post",
+ "params": {
+ "controller": "User",
+ "action": "passwordChangeRequest"
},
- "conditions":{
- "auth":false
+ "conditions": {
+ "auth": false
}
},
{
- "route":"User/changePasswordByRequest",
- "method":"post",
- "params":{
- "controller":"User",
- "action":"changePasswordByRequest"
+ "route": "User/changePasswordByRequest",
+ "method": "post",
+ "params": {
+ "controller": "User",
+ "action": "changePasswordByRequest"
},
- "conditions":{
- "auth":false
+ "conditions": {
+ "auth": false
}
},
{
- "route":"/Stream",
- "method":"get",
- "params":{
- "controller":"Stream",
- "action":"list",
- "scope":"User"
+ "route": "/Stream",
+ "method": "get",
+ "params": {
+ "controller": "Stream",
+ "action": "list",
+ "scope": "User"
}
},
{
- "route":"/GlobalSearch",
- "method":"get",
- "params":{
- "controller":"GlobalSearch",
- "action":"search"
+ "route": "/GlobalSearch",
+ "method": "get",
+ "params": {
+ "controller": "GlobalSearch",
+ "action": "search"
}
},
{
- "route":"/:controller/action/:action",
- "method":"post",
- "params":{
- "controller":":controller",
- "action":":action"
+ "route": "/:controller/action/:action",
+ "method": "post",
+ "params": {
+ "controller": ":controller",
+ "action": ":action"
}
},
{
- "route":"/:controller/action/:action",
- "method":"put",
- "params":{
- "controller":":controller",
- "action":":action"
+ "route": "/:controller/action/:action",
+ "method": "put",
+ "params": {
+ "controller": ":controller",
+ "action": ":action"
}
},
{
- "route":"/:controller/action/:action",
- "method":"get",
- "params":{
- "controller":":controller",
- "action":":action"
+ "route": "/:controller/action/:action",
+ "method": "get",
+ "params": {
+ "controller": ":controller",
+ "action": ":action"
}
},
{
- "route":"/:controller/layout/:name",
- "method":"get",
- "params":{
- "controller":"Layout",
- "scope":":controller"
+ "route": "/:controller/layout/:name",
+ "method": "get",
+ "params": {
+ "controller": "Layout",
+ "scope": ":controller"
}
},
{
- "route":"/:controller/layout/:name",
- "method":"put",
- "params":{
- "controller":"Layout",
- "scope":":controller"
+ "route": "/:controller/layout/:name",
+ "method": "put",
+ "params": {
+ "controller": "Layout",
+ "scope": ":controller"
}
},
{
- "route":"/:controller/layout/:name",
- "method":"patch",
- "params":{
- "controller":"Layout",
- "scope":":controller"
+ "route": "/:controller/layout/:name",
+ "method": "patch",
+ "params": {
+ "controller": "Layout",
+ "scope": ":controller"
}
},
{
- "route":"/Admin/rebuild",
- "method":"post",
- "params":{
- "controller":"Admin",
- "action":"rebuild"
+ "route": "/Admin/rebuild",
+ "method": "post",
+ "params": {
+ "controller": "Admin",
+ "action": "rebuild"
}
},
{
- "route":"/Admin/clearCache",
- "method":"post",
- "params":{
- "controller":"Admin",
- "action":"clearCache"
+ "route": "/Admin/clearCache",
+ "method": "post",
+ "params": {
+ "controller": "Admin",
+ "action": "clearCache"
}
},
{
- "route":"/Admin/jobs",
- "method":"get",
- "params":{
- "controller":"Admin",
- "action":"jobs"
+ "route": "/Admin/jobs",
+ "method": "get",
+ "params": {
+ "controller": "Admin",
+ "action": "jobs"
}
},
{
- "route":"/Admin/fieldManager/:scope/:name",
- "method":"get",
- "params":{
- "controller":"FieldManager",
- "action":"read",
- "scope":":scope",
- "name":":name"
+ "route": "/Admin/fieldManager/:scope/:name",
+ "method": "get",
+ "params": {
+ "controller": "FieldManager",
+ "action": "read",
+ "scope": ":scope",
+ "name": ":name"
}
},
{
- "route":"/Admin/fieldManager/:scope",
- "method":"post",
- "params":{
- "controller":"FieldManager",
- "action":"create",
- "scope":":scope"
+ "route": "/Admin/fieldManager/:scope",
+ "method": "post",
+ "params": {
+ "controller": "FieldManager",
+ "action": "create",
+ "scope": ":scope"
}
},
{
- "route":"/Admin/fieldManager/:scope/:name",
- "method":"put",
- "params":{
- "controller":"FieldManager",
- "action":"update",
- "scope":":scope",
- "name":":name"
+ "route": "/Admin/fieldManager/:scope/:name",
+ "method": "put",
+ "params": {
+ "controller": "FieldManager",
+ "action": "update",
+ "scope": ":scope",
+ "name": ":name"
}
},
{
- "route":"/Admin/fieldManager/:scope/:name",
- "method":"patch",
- "params":{
- "controller":"FieldManager",
- "action":"update",
- "scope":":scope",
- "name":":name"
+ "route": "/Admin/fieldManager/:scope/:name",
+ "method": "patch",
+ "params": {
+ "controller": "FieldManager",
+ "action": "update",
+ "scope": ":scope",
+ "name": ":name"
}
},
{
- "route":"/Admin/fieldManager/:scope/:name",
- "method":"delete",
- "params":{
- "controller":"FieldManager",
- "action":"delete",
- "scope":":scope",
- "name":":name"
+ "route": "/Admin/fieldManager/:scope/:name",
+ "method": "delete",
+ "params": {
+ "controller": "FieldManager",
+ "action": "delete",
+ "scope": ":scope",
+ "name": ":name"
}
},
{
- "route":"/:controller/:id",
- "method":"get",
- "params":{
- "controller":":controller",
- "action":"read",
- "id":":id"
+ "route": "/:controller/:id",
+ "method": "get",
+ "params": {
+ "controller": ":controller",
+ "action": "read",
+ "id": ":id"
}
},
{
- "route":"/:controller",
- "method":"get",
- "params":{
- "controller":":controller",
- "action":"index"
+ "route": "/:controller",
+ "method": "get",
+ "params": {
+ "controller": ":controller",
+ "action": "index"
}
},
{
- "route":"/:controller",
- "method":"post",
- "params":{
- "controller":":controller",
- "action":"create"
+ "route": "/:controller",
+ "method": "post",
+ "params": {
+ "controller": ":controller",
+ "action": "create"
}
},
{
- "route":"/:controller/:id",
- "method":"put",
- "params":{
- "controller":":controller",
- "action":"update",
- "id":":id"
+ "route": "/:controller/:id",
+ "method": "put",
+ "params": {
+ "controller": ":controller",
+ "action": "update",
+ "id": ":id"
}
},
{
- "route":"/:controller/:id",
- "method":"patch",
- "params":{
- "controller":":controller",
- "action":"patch",
- "id":":id"
+ "route": "/:controller/:id",
+ "method": "patch",
+ "params": {
+ "controller": ":controller",
+ "action": "patch",
+ "id": ":id"
}
},
{
- "route":"/:controller/:id",
- "method":"delete",
- "params":{
- "controller":":controller",
- "action":"delete",
- "id":":id"
+ "route": "/:controller/:id",
+ "method": "delete",
+ "params": {
+ "controller": ":controller",
+ "action": "delete",
+ "id": ":id"
}
},
{
- "route":"/:controller/:id/stream",
- "method":"get",
- "params":{
- "controller":"Stream",
- "action":"list",
- "id":":id",
- "scope":":controller"
+ "route": "/:controller/:id/stream",
+ "method": "get",
+ "params": {
+ "controller": "Stream",
+ "action": "list",
+ "id": ":id",
+ "scope": ":controller"
}
},
{
- "route":"/:controller/:id/subscription",
- "method":"put",
- "params":{
- "controller":":controller",
- "id":":id",
- "action":"follow"
+ "route": "/:controller/:id/subscription",
+ "method": "put",
+ "params": {
+ "controller": ":controller",
+ "id": ":id",
+ "action": "follow"
}
},
{
- "route":"/:controller/:id/subscription",
- "method":"delete",
- "params":{
- "controller":":controller",
- "id":":id",
- "action":"unfollow"
+ "route": "/:controller/:id/subscription",
+ "method": "delete",
+ "params": {
+ "controller": ":controller",
+ "id": ":id",
+ "action": "unfollow"
}
},
{
- "route":"/:controller/:id/:link",
- "method":"get",
- "params":{
- "controller":":controller",
- "action":"listLinked",
- "id":":id",
- "link":":link"
+ "route": "/:controller/:id/:link",
+ "method": "get",
+ "params": {
+ "controller": ":controller",
+ "action": "listLinked",
+ "id": ":id",
+ "link": ":link"
}
},
{
- "route":"/:controller/:id/:link",
- "method":"post",
- "params":{
- "controller":":controller",
- "action":"createLink",
- "id":":id",
- "link":":link"
+ "route": "/:controller/:id/:link",
+ "method": "post",
+ "params": {
+ "controller": ":controller",
+ "action": "createLink",
+ "id": ":id",
+ "link": ":link"
}
},
{
- "route":"/:controller/:id/:link",
- "method":"delete",
- "params":{
- "controller":":controller",
- "action":"removeLink",
- "id":":id",
- "link":":link"
+ "route": "/:controller/:id/:link",
+ "method": "delete",
+ "params": {
+ "controller": ":controller",
+ "action": "removeLink",
+ "id": ":id",
+ "link": ":link"
}
}
]
diff --git a/application/Espo/Services/Email.php b/application/Espo/Services/Email.php
index 829d1d0d3f..835be0589c 100644
--- a/application/Espo/Services/Email.php
+++ b/application/Espo/Services/Email.php
@@ -173,14 +173,26 @@ class Email extends Record
if (!$smtpParams && $fromAddress === strtolower($this->getConfig()->get('outboundEmailFromAddress'))) {
if (!$this->getConfig()->get('outboundEmailIsShared')) {
- throw new Error('Can not use system smtp. System SMTP is not shared.');
+ throw new Error('Can not use system SMTP. System account is not shared.');
}
- $emailSender->setParams(array(
+ $emailSender->setParams([
'fromName' => $this->getConfig()->get('outboundEmailFromName')
- ));
+ ]);
}
- $params = array();
+ if (!$smtpParams && !$this->getConfig()->get('outboundEmailIsShared')) {
+ throw new Error('No SMTP params found for '.$fromAddress.'.');
+ }
+
+ if (!$smtpParams) {
+ if (in_array($fromAddress, $userAddressList)) {
+ $emailSender->setParams([
+ 'fromName' => $this->getUser()->get('name')
+ ]);
+ }
+ }
+
+ $params = [];
$parent = null;
if ($entity->get('parentType') && $entity->get('parentId')) {
diff --git a/application/Espo/Services/EmailAccount.php b/application/Espo/Services/EmailAccount.php
index db31900fc5..5acf5a9cd6 100644
--- a/application/Espo/Services/EmailAccount.php
+++ b/application/Espo/Services/EmailAccount.php
@@ -209,12 +209,15 @@ class EmailAccount extends Record
if (empty($fetchData)) {
$fetchData = new \StdClass();
}
+ $fetchData = clone $fetchData;
if (!property_exists($fetchData, 'lastUID')) {
- $fetchData->lastUID = new \StdClass();;
+ $fetchData->lastUID = new \StdClass();
}
if (!property_exists($fetchData, 'lastDate')) {
- $fetchData->lastDate = new \StdClass();;
+ $fetchData->lastDate = new \StdClass();
}
+ $fetchData->lastUID = clone $fetchData->lastUID;
+ $fetchData->lastDate = clone $fetchData->lastDate;
$storage = $this->getStorage($emailAccount);
@@ -385,7 +388,7 @@ class EmailAccount extends Record
$emailAccount = $this->getEntityManager()->getRepository('EmailAccount')->where([
'emailAddress' => $address,
'assignedUserId' => $user->id,
- 'active' => true
+ 'status' => 'Active'
])->findOne();
return $emailAccount;
diff --git a/application/Espo/Services/InboundEmail.php b/application/Espo/Services/InboundEmail.php
index 59f9688716..a3d0c4b632 100644
--- a/application/Espo/Services/InboundEmail.php
+++ b/application/Espo/Services/InboundEmail.php
@@ -210,12 +210,15 @@ class InboundEmail extends \Espo\Services\Record
if (empty($fetchData)) {
$fetchData = new \StdClass();
}
+ $fetchData = clone $fetchData;
if (!property_exists($fetchData, 'lastUID')) {
$fetchData->lastUID = new \StdClass();;
}
if (!property_exists($fetchData, 'lastDate')) {
- $fetchData->lastDate = new \StdClass();;
+ $fetchData->lastDate = new \StdClass();
}
+ $fetchData->lastUID = clone $fetchData->lastUID;
+ $fetchData->lastDate = clone $fetchData->lastDate;
$imapParams = array(
'host' => $emailAccount->get('host'),
diff --git a/application/Espo/Services/Record.php b/application/Espo/Services/Record.php
index 26ffa9c79b..f347c1eae8 100644
--- a/application/Espo/Services/Record.php
+++ b/application/Espo/Services/Record.php
@@ -2191,7 +2191,7 @@ class Record extends \Espo\Core\Services\Base
}
// TODO remove in 5.5.0
- if (in_array($this->getEntityType(), ['Report', 'Workflow'])) {
+ if (in_array($this->getEntityType(), ['Report', 'Workflow', 'ReportPanel'])) {
return null;
}
diff --git a/application/Espo/Services/Stream.php b/application/Espo/Services/Stream.php
index 4cc58766c8..f4327a28eb 100644
--- a/application/Espo/Services/Stream.php
+++ b/application/Espo/Services/Stream.php
@@ -972,7 +972,7 @@ class Stream extends \Espo\Core\Services\Base
foreach ($auditedFields as $field => $item) {
$updated = false;
foreach ($item['actualList'] as $attribute) {
- if ($entity->get($attribute) !== $entity->getFetched($attribute)) {
+ if ($entity->hasFetched($attribute) && $entity->isAttributeChanged($attribute)) {
$updated = true;
}
}
diff --git a/client/res/templates/fields/array/detail.tpl b/client/res/templates/fields/array/detail.tpl
index 2c88ecc017..485f018e12 100644
--- a/client/res/templates/fields/array/detail.tpl
+++ b/client/res/templates/fields/array/detail.tpl
@@ -1 +1,3 @@
-{{#unless isEmpty}}{{{value}}}{{else}}{{translate 'None'}}{{/unless}}
+{{#unless isEmpty}}{{{value}}}{{else}}
+ {{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
+{{/unless}}
diff --git a/client/res/templates/fields/array/list.tpl b/client/res/templates/fields/array/list.tpl
new file mode 100644
index 0000000000..4bc46255bf
--- /dev/null
+++ b/client/res/templates/fields/array/list.tpl
@@ -0,0 +1 @@
+{{#unless isEmpty}}{{{value}}}{{/unless}}
diff --git a/client/res/templates/fields/attachments-multiple/list.tpl b/client/res/templates/fields/attachments-multiple/list.tpl
new file mode 100644
index 0000000000..310a5e0f15
--- /dev/null
+++ b/client/res/templates/fields/attachments-multiple/list.tpl
@@ -0,0 +1,3 @@
+{{#if value}}
+ {{{value}}}
+{{/if}}
\ No newline at end of file
diff --git a/client/res/templates/fields/currency/detail-1.tpl b/client/res/templates/fields/currency/detail-1.tpl
index 3d1d34e565..6828455061 100644
--- a/client/res/templates/fields/currency/detail-1.tpl
+++ b/client/res/templates/fields/currency/detail-1.tpl
@@ -1,5 +1,5 @@
-{{#if value}}
+{{#if isNotEmpty}}
{{value}} {{currencyValue}}
{{else}}
- {{translate 'None'}}
+ {{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
diff --git a/client/res/templates/fields/currency/detail-2.tpl b/client/res/templates/fields/currency/detail-2.tpl
index 1fbd904fa4..678e6a1f83 100644
--- a/client/res/templates/fields/currency/detail-2.tpl
+++ b/client/res/templates/fields/currency/detail-2.tpl
@@ -1,5 +1,5 @@
-{{#if value}}
+{{#if isNotEmpty}}
{{currencySymbol}}{{value}}
{{else}}
- {{translate 'None'}}
+ {{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
diff --git a/client/res/templates/fields/currency/detail-no-currency.tpl b/client/res/templates/fields/currency/detail-no-currency.tpl
index 3fd57a1a62..c78bc0890d 100644
--- a/client/res/templates/fields/currency/detail-no-currency.tpl
+++ b/client/res/templates/fields/currency/detail-no-currency.tpl
@@ -1,5 +1,5 @@
-{{#if value}}
+{{#if isNotEmpty}}
{{value}}
{{else}}
- {{translate 'None'}}
+ {{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
diff --git a/client/res/templates/fields/currency/detail.tpl b/client/res/templates/fields/currency/detail.tpl
index 3d1d34e565..6828455061 100644
--- a/client/res/templates/fields/currency/detail.tpl
+++ b/client/res/templates/fields/currency/detail.tpl
@@ -1,5 +1,5 @@
-{{#if value}}
+{{#if isNotEmpty}}
{{value}} {{currencyValue}}
{{else}}
- {{translate 'None'}}
+ {{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
diff --git a/client/res/templates/fields/currency/list-1.tpl b/client/res/templates/fields/currency/list-1.tpl
index e1b2948b5c..9669b122be 100644
--- a/client/res/templates/fields/currency/list-1.tpl
+++ b/client/res/templates/fields/currency/list-1.tpl
@@ -1,5 +1,3 @@
-{{#if value}}
+{{#if isNotEmpty}}
{{value}} {{currencyValue}}
-{{else}}
- {{translate 'None'}}
{{/if}}
diff --git a/client/res/templates/fields/currency/list-2.tpl b/client/res/templates/fields/currency/list-2.tpl
index 826723bf51..67304d708e 100644
--- a/client/res/templates/fields/currency/list-2.tpl
+++ b/client/res/templates/fields/currency/list-2.tpl
@@ -1,5 +1,3 @@
-{{#if value}}
- {{currencySymbol}}{{value}}
-{{else}}
- {{translate 'None'}}
+{{#if isNotEmpty}}
+ {{currencySymbol}}{{value}}
{{/if}}
diff --git a/client/res/templates/fields/currency/list.tpl b/client/res/templates/fields/currency/list.tpl
index e1b2948b5c..9669b122be 100644
--- a/client/res/templates/fields/currency/list.tpl
+++ b/client/res/templates/fields/currency/list.tpl
@@ -1,5 +1,3 @@
-{{#if value}}
+{{#if isNotEmpty}}
{{value}} {{currencyValue}}
-{{else}}
- {{translate 'None'}}
{{/if}}
diff --git a/client/res/templates/fields/int/detail.tpl b/client/res/templates/fields/int/detail.tpl
index f6b34e1b68..c8454e9dbd 100644
--- a/client/res/templates/fields/int/detail.tpl
+++ b/client/res/templates/fields/int/detail.tpl
@@ -1,3 +1,3 @@
{{#if isNotEmpty}}{{value}}{{else}}
- {{translate 'None'}}
+ {{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
\ No newline at end of file
diff --git a/client/res/templates/fields/link-multiple/list.tpl b/client/res/templates/fields/link-multiple/list.tpl
new file mode 100644
index 0000000000..660e229523
--- /dev/null
+++ b/client/res/templates/fields/link-multiple/list.tpl
@@ -0,0 +1,4 @@
+{{#if value}}
+ {{{value}}}
+{{/if}}
+
diff --git a/client/res/templates/fields/person-name/detail.tpl b/client/res/templates/fields/person-name/detail.tpl
index 153358eaee..702ccf1880 100644
--- a/client/res/templates/fields/person-name/detail.tpl
+++ b/client/res/templates/fields/person-name/detail.tpl
@@ -1,3 +1,3 @@
{{#if isNotEmpty}}{{translateOption salutationValue field='salutationName' scope=scope}} {{firstValue}} {{lastValue}}{{else}}
-{{translate 'None'}}
+{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
\ No newline at end of file
diff --git a/client/res/templates/fields/url/detail.tpl b/client/res/templates/fields/url/detail.tpl
index 3787369c2b..17178c449a 100644
--- a/client/res/templates/fields/url/detail.tpl
+++ b/client/res/templates/fields/url/detail.tpl
@@ -1,5 +1,5 @@
{{#if value}}
{{value}}
{{else}}
- {{translate 'None'}}
+ {{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
diff --git a/client/res/templates/fields/varchar/detail.tpl b/client/res/templates/fields/varchar/detail.tpl
index dbfa5612db..e8f9641487 100644
--- a/client/res/templates/fields/varchar/detail.tpl
+++ b/client/res/templates/fields/varchar/detail.tpl
@@ -1 +1,3 @@
-{{#if isNotEmpty}}{{value}}{{else}}{{translate 'None'}}{{/if}}
\ No newline at end of file
+{{#if isNotEmpty}}{{value}}{{else}}
+{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
+{{/if}}
\ No newline at end of file
diff --git a/client/src/model.js b/client/src/model.js
index 99c1594d04..0031f057a8 100644
--- a/client/src/model.js
+++ b/client/src/model.js
@@ -50,6 +50,11 @@ Espo.define('model', [], function () {
Dep.prototype.initialize.call(this);
},
+ sync: function (method, model, options) {
+ if (method === 'patch') options.type = 'PUT';
+ return Dep.prototype.sync.call(this, method, model, options);
+ },
+
set: function (key, val, options) {
if (typeof key === 'object') {
var o = key;
diff --git a/client/src/views/fields/address.js b/client/src/views/fields/address.js
index 43f2238058..d0b85a15c7 100644
--- a/client/src/views/fields/address.js
+++ b/client/src/views/fields/address.js
@@ -52,45 +52,41 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) {
var data = Dep.prototype.data.call(this);
data.ucName = Espo.Utils.upperCaseFirst(this.name);
- data.postalCodeValue = this.model.get(this.postalCodeField);
- data.streetValue = this.model.get(this.streetField);
- data.cityValue = this.model.get(this.cityField);
- data.stateValue = this.model.get(this.stateField);
- data.countryValue = this.model.get(this.countryField);
+ this.addressPartList.forEach(function (item) {
+ var value = this.model.get(this[item + 'Field']);
+ data[item + 'Value'] = value;
+ }, this);
if (this.mode == 'detail' || this.mode == 'list') {
data.formattedAddress = this.getFormattedAddress();
}
- data.isEmpty = !(data.postalCodeValue ||
- data.streetValue ||
- data.cityValue ||
- data.stateValue ||
- data.countryValue);
+ var isNotEmpty = false;
return data;
},
setupSearch: function () {
- this.searchData.value = this.getSearchParamsData().value || this.searchParams.additionalValue;
+ this.searchData.value = this.getSearchParamsData().value || this.searchParams.additionalValue;
},
getFormattedAddress: function () {
- var postalCodeValue = this.model.get(this.postalCodeField);
- var streetValue = this.model.get(this.streetField);
- var cityValue = this.model.get(this.cityField);
- var stateValue = this.model.get(this.stateField);
- var countryValue = this.model.get(this.countryField);
+ var isNotEmpty = false;
+ var isSet = false;
+ this.addressAttributeList.forEach(function (attribute) {
+ isNotEmpty = isNotEmpty || this.model.get(attribute);
+ isSet = isSet || this.model.has(attribute);
+ }, this);
- var isEmpty = !(
- postalCodeValue ||
- streetValue ||
- cityValue ||
- stateValue ||
- countryValue
- );
+ var isEmpty = !isNotEmpty;
if (isEmpty) {
+ if (this.mode === 'list') {
+ return '';
+ }
+ if (!isSet) {
+ return this.translate('...');
+ }
return this.translate('None');
}
@@ -320,13 +316,18 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) {
}
},
- init: function () {
- this.postalCodeField = this.options.defs.name + 'PostalCode';
- this.streetField = this.options.defs.name + 'Street';
- this.stateField = this.options.defs.name + 'State';
- this.cityField = this.options.defs.name + 'City';
- this.countryField = this.options.defs.name + 'Country';
- Dep.prototype.init.call(this);
+ setup: function () {
+ Dep.prototype.setup.call(this);
+
+ var actualAttributePartList = this.getMetadata().get(['fields', this.type, 'actualFields']) || [];
+ this.addressAttributeList = [];
+ this.addressPartList = [];
+ actualAttributePartList.forEach(function (item) {
+ var attribute = this.name + Espo.Utils.upperCaseFirst(item);
+ this.addressAttributeList.push(attribute);
+ this.addressPartList.push(item);
+ this[item + 'Field'] = attribute;
+ }, this);
},
validateRequired: function () {
@@ -397,7 +398,7 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) {
type: 'like',
field: this.countryField,
value: value + '%'
- },
+ }
],
data: {
value: value
diff --git a/client/src/views/fields/array.js b/client/src/views/fields/array.js
index 882092b7c6..1cddea531d 100644
--- a/client/src/views/fields/array.js
+++ b/client/src/views/fields/array.js
@@ -32,7 +32,7 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi
type: 'array',
- listTemplate: 'fields/array/detail',
+ listTemplate: 'fields/array/list',
detailTemplate: 'fields/array/detail',
@@ -51,7 +51,8 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi
translatedOptions: this.translatedOptions,
hasOptions: this.params.options ? true : false,
itemHtmlList: itemHtmlList,
- isEmpty: (this.selected || []).length === 0
+ isEmpty: (this.selected || []).length === 0,
+ valueIsSet: this.model.has(this.name)
}, Dep.prototype.data.call(this));
},
@@ -109,7 +110,7 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi
}
if (this.options.customOptionList) {
- this.setOptionList(this.options.customOptionList);
+ this.setOptionList(this.options.customOptionList, true);
}
},
@@ -146,13 +147,21 @@ Espo.define('views/fields/array', ['views/fields/base', 'lib!Selectize'], functi
},
- setOptionList: function (optionList) {
+ setOptionList: function (optionList, silent) {
if (!this.originalOptionList) {
this.originalOptionList = this.params.options;
}
this.params.options = Espo.Utils.clone(optionList);
- if (this.mode == 'edit') {
+ if (this.mode == 'edit' && !silent) {
+ var selectedOptionList = [];
+ this.selected.forEach(function (option) {
+ if (~optionList.indexOf(option)) {
+ selectedOptionList.push(option);
+ }
+ }, this);
+ this.selected = selectedOptionList;
+
if (this.isRendered()) {
this.reRender();
this.trigger('change');
diff --git a/client/src/views/fields/attachment-multiple.js b/client/src/views/fields/attachment-multiple.js
index 9826ee856e..44cc9365e8 100644
--- a/client/src/views/fields/attachment-multiple.js
+++ b/client/src/views/fields/attachment-multiple.js
@@ -32,7 +32,7 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
type: 'attachmentMultiple',
- listTemplate: 'fields/attachments-multiple/detail',
+ listTemplate: 'fields/attachments-multiple/list',
detailTemplate: 'fields/attachments-multiple/detail',
@@ -495,7 +495,7 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
for (var id in nameHash) {
var type = typeHash[id] || false;
var name = nameHash[id];
- if (this.showPreviews && ~this.previewTypeList.indexOf(type)) {
+ if (this.showPreviews && ~this.previewTypeList.indexOf(type) && this.mode !== 'list') {
previews.push('
' + this.getDetailPreview(name, type, id) + '
');
continue;
}
diff --git a/client/src/views/fields/int.js b/client/src/views/fields/int.js
index 2772a7e91a..117fdd5fac 100644
--- a/client/src/views/fields/int.js
+++ b/client/src/views/fields/int.js
@@ -78,6 +78,7 @@ Espo.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);
return data;
},
diff --git a/client/src/views/fields/link-multiple.js b/client/src/views/fields/link-multiple.js
index c952524a30..d097211838 100644
--- a/client/src/views/fields/link-multiple.js
+++ b/client/src/views/fields/link-multiple.js
@@ -32,7 +32,7 @@ Espo.define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
type: 'linkMultiple',
- listTemplate: 'fields/link-multiple/detail',
+ listTemplate: 'fields/link-multiple/list',
detailTemplate: 'fields/link-multiple/detail',
@@ -359,16 +359,19 @@ Espo.define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
var type = this.$el.find('select.search-type').val();
if (type === 'anyOf') {
- var values = this.ids || [];
+ var idList = this.ids || [];
var data = {
type: 'linkedWith',
- value: this.ids || [],
+ value: idList,
nameHash: this.nameHash,
data: {
type: type
}
};
+ if (!idList.length) {
+ data.value = null;
+ }
return data;
} else if (type === 'noneOf') {
var values = this.ids || [];
diff --git a/client/src/views/fields/multi-enum.js b/client/src/views/fields/multi-enum.js
index 19a6614afa..d0426d8ad8 100644
--- a/client/src/views/fields/multi-enum.js
+++ b/client/src/views/fields/multi-enum.js
@@ -32,7 +32,7 @@ Espo.define('views/fields/multi-enum', ['views/fields/array', 'lib!Selectize'],
type: 'multiEnum',
- listTemplate: 'fields//array/detail',
+ listTemplate: 'fields//array/list',
detailTemplate: 'fields/array/detail',
diff --git a/client/src/views/fields/person-name.js b/client/src/views/fields/person-name.js
index 548bc35cf8..6a128077cc 100644
--- a/client/src/views/fields/person-name.js
+++ b/client/src/views/fields/person-name.js
@@ -46,6 +46,8 @@ Espo.define('views/fields/person-name', 'views/fields/varchar', function (Dep) {
data.firstMaxLength = this.model.getFieldParam(this.firstField, 'maxLength');
data.lastMaxLength = this.model.getFieldParam(this.lastField, 'maxLength');
+ data.valueIsSet = this.model.has(this.firstField) || this.model.has(this.lastField);
+
if (this.mode === 'detail') {
data.isNotEmpty = !!data.firstValue || !!data.lastValue || !!data.salutationValue;
} else if (this.mode === 'list' || this.mode === 'listLink') {
diff --git a/client/src/views/fields/url.js b/client/src/views/fields/url.js
index 8642565e72..2179726384 100644
--- a/client/src/views/fields/url.js
+++ b/client/src/views/fields/url.js
@@ -26,15 +26,15 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
-Espo.define('Views.Fields.Url', 'Views.Fields.Varchar', function (Dep) {
+Espo.define('views/fields/url', 'views/fields/varchar', function (Dep) {
return Dep.extend({
type: 'url',
- listTemplate: 'fields.url.list',
+ listTemplate: 'fields/url/list',
- detailTemplate: 'fields.url.detail',
+ detailTemplate: 'fields/url/detail',
setup: function () {
Dep.prototype.setup.call(this);
diff --git a/client/src/views/fields/varchar.js b/client/src/views/fields/varchar.js
index ae5068ce64..57cf864d50 100644
--- a/client/src/views/fields/varchar.js
+++ b/client/src/views/fields/varchar.js
@@ -58,6 +58,8 @@ Espo.define('views/fields/varchar', 'views/fields/base', function (Dep) {
) {
data.isNotEmpty = true;
}
+ data.valueIsSet = this.model.has(this.name);
+
if (this.mode === 'search') {
if (typeof this.searchParams.value === 'string') {
this.searchData.value = this.searchParams.value;
diff --git a/client/src/views/user/detail.js b/client/src/views/user/detail.js
index 45021c6660..f133fd573b 100644
--- a/client/src/views/user/detail.js
+++ b/client/src/views/user/detail.js
@@ -38,7 +38,8 @@ Espo.define('views/user/detail', 'views/detail', function (Dep) {
name: 'preferences',
label: 'Preferences',
style: 'default',
- action: "preferences"
+ action: "preferences",
+ link: '#Preferences/edit/' + this.getUser().id
});
if (!this.model.get('isPortalUser')) {
@@ -47,7 +48,8 @@ Espo.define('views/user/detail', 'views/detail', function (Dep) {
name: 'emailAccounts',
label: "Email Accounts",
style: 'default',
- action: "emailAccounts"
+ action: "emailAccounts",
+ link: '#EmailAccount/list/userId=' + this.model.id + '&userName=' + encodeURIComponent(this.model.get('name'))
});
}
@@ -56,7 +58,8 @@ Espo.define('views/user/detail', 'views/detail', function (Dep) {
name: 'externalAccounts',
label: 'External Accounts',
style: 'default',
- action: "externalAccounts"
+ action: "externalAccounts",
+ link: '#ExternalAccount'
});
}
}
diff --git a/package.json b/package.json
index f898345426..b75bad4785 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "espocrm",
- "version": "5.3.4",
+ "version": "5.3.5",
"description": "",
"main": "index.php",
"repository": {
diff --git a/tests/unit/Espo/ORM/DB/MapperTest.php b/tests/unit/Espo/ORM/DB/MapperTest.php
index f960067e85..c257ff7fd5 100644
--- a/tests/unit/Espo/ORM/DB/MapperTest.php
+++ b/tests/unit/Espo/ORM/DB/MapperTest.php
@@ -35,6 +35,7 @@ use Espo\Entities\Post;
use Espo\Entities\Comment;
use Espo\Entities\Tag;
use Espo\Entities\Note;
+use Espo\Entities\Job;
require_once 'tests/unit/testData/DB/Entities.php';
require_once 'tests/unit/testData/DB/MockPDO.php';
@@ -348,6 +349,34 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
$this->db->update($this->post);
}
+ public function testUpdateArray1()
+ {
+ $query = "UPDATE `job` SET `array` = '[\"2\",\"1\"]' WHERE job.id = '1' AND job.deleted = '0'";
+
+ $this->mockQuery($query, true);
+
+ $job = new \Espo\Entities\Job();
+ $job->id = '1';
+ $job->setFetched('array', ['1', '2']);
+ $job->set('array', ['2', '1']);
+
+ $this->db->update($job);
+ }
+
+ public function testUpdateArray2()
+ {
+ $query = "UPDATE `job` SET `array` = NULL WHERE job.id = '1' AND job.deleted = '0'";
+
+ $this->mockQuery($query, true);
+
+ $job = new \Espo\Entities\Job();
+ $job->id = '1';
+ $job->setFetched('array', ['1', '2']);
+ $job->set('array', null);
+
+ $this->db->update($job);
+ }
+
public function testRemoveRelationHasMany()
{
$query = "UPDATE `comment` SET post_id = NULL WHERE comment.deleted = '0' AND comment.id = '100'";
@@ -448,6 +477,5 @@ class DBMapperTest extends \PHPUnit\Framework\TestCase
)
));
}
+
}
-
-
diff --git a/tests/unit/Espo/ORM/EntityTest.php b/tests/unit/Espo/ORM/EntityTest.php
new file mode 100644
index 0000000000..825902f81f
--- /dev/null
+++ b/tests/unit/Espo/ORM/EntityTest.php
@@ -0,0 +1,201 @@
+setFetched('string', 'test');
+ $this->assertFalse($job->isAttributeChanged('string'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('string', 'test');
+ $job->set('string', 'hello');
+ $this->assertTrue($job->isAttributeChanged('string'));
+
+ $job = new \Espo\Entities\Job();
+ $job->set('string', 'hello');
+ $this->assertTrue($job->isAttributeChanged('string'));
+
+ $job = new \Espo\Entities\Job();
+ $job->set('string', null);
+ $this->assertTrue($job->isAttributeChanged('string'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('array', ['1', '2']);
+ $job->set('array', ['2', '1']);
+ $this->assertTrue($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('array', ['1', '2']);
+ $job->set('array', ['1', '2']);
+ $this->assertFalse($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('array', ['1', '2']);
+ $job->set('array', ['1', 2]);
+ $this->assertTrue($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('array', [
+ (object) ['1' => 'v1']
+ ]);
+ $job->set('array', [
+ (object) ['1' => 'v1']
+ ]);
+ $this->assertFalse($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('array', [
+ (object) ['k1' => 'v1']
+ ]);
+ $job->set('array', [
+ (object) ['k1' => 'v2']
+ ]);
+ $this->assertTrue($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('array', [
+ (object) ['k1' => 'v1']
+ ]);
+ $job->set('array', [
+ (object) ['k1' => 'v1', 'k2' => 'v2'],
+ ]);
+ $this->assertTrue($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('array', ['1', '2']);
+ $job->set('array', ['1', '2', '3']);
+ $this->assertTrue($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->set('array', ['1', '2', '3']);
+ $this->assertTrue($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->set('array', null);
+ $this->assertTrue($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('array', null);
+ $this->assertFalse($job->isAttributeChanged('array'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('arrayUnordered', ['1', '2']);
+ $job->set('arrayUnordered', ['2', '1']);
+ $this->assertFalse($job->isAttributeChanged('arrayUnordered'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('arrayUnordered', ['1', '2']);
+ $job->set('arrayUnordered', ['1', '2']);
+ $this->assertFalse($job->isAttributeChanged('arrayUnordered'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('arrayUnordered', ['1', '2']);
+ $job->set('arrayUnordered', ['1', '2', '3']);
+ $this->assertTrue($job->isAttributeChanged('arrayUnordered'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('arrayUnordered', ['1', '2']);
+ $job->set('arrayUnordered', null);
+ $this->assertTrue($job->isAttributeChanged('arrayUnordered'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('object', (object) ['1' => 'value-1']);
+ $job->set('object', (object) ['1' => 'value-1']);
+ $this->assertFalse($job->isAttributeChanged('object'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('object', (object) ['1' => 'value-1']);
+ $job->set('object', ['1' => 'value-1']);
+ $this->assertTrue($job->isAttributeChanged('object'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('object', (object) ['1' => '1']);
+ $job->set('object', (object) ['1' => 1]);
+ $this->assertTrue($job->isAttributeChanged('object'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('object', (object) [
+ 'k1' => (object) [
+ 'k11' => 'v1'
+ ]
+ ]);
+ $job->set('object', (object) [
+ 'k1' => (object) [
+ 'k11' => 'v2'
+ ]
+ ]);
+ $this->assertTrue($job->isAttributeChanged('object'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('object', (object) [
+ 'k1' => [
+ 'k11' => 'v1'
+ ]
+ ]);
+ $job->set('object', (object) [
+ 'k1' => (object) [
+ 'k11' => 'v1'
+ ]
+ ]);
+ $this->assertTrue($job->isAttributeChanged('object'));
+
+ $job = new \Espo\Entities\Job();
+ $job->setFetched('object', [
+ 'k1' => [
+ 'k11' => 'v1'
+ ]
+ ]);
+ $job->set('object', (object) [
+ 'k1' => (object) [
+ 'k11' => 'v1'
+ ]
+ ]);
+ $this->assertTrue($job->isAttributeChanged('object'));
+ }
+
+}
diff --git a/tests/unit/testData/DB/Entities.php b/tests/unit/testData/DB/Entities.php
index 93117edf1c..e59cbaaef4 100644
--- a/tests/unit/testData/DB/Entities.php
+++ b/tests/unit/testData/DB/Entities.php
@@ -242,3 +242,32 @@ class Article extends TEntity
)
);
}
+
+
+class Job extends TEntity
+{
+ public $fields = array(
+ 'id' => array(
+ 'type' => Entity::ID
+ ),
+ 'string' => array(
+ 'type' => Entity::VARCHAR,
+ 'len' => 50
+ ),
+ 'array' => array(
+ 'type' => Entity::JSON_ARRAY
+ ),
+ 'arrayUnordered' => array(
+ 'type' => Entity::JSON_ARRAY,
+ 'isUnordered' => true
+ ),
+ 'object' => array(
+ 'type' => Entity::JSON_OBJECT
+ ),
+ 'deleted' => array(
+ 'type' => Entity::BOOL,
+ 'default' => 0
+ )
+ );
+}
+