Compare commits

..

30 Commits
5.7.0 ... 5.7.2

Author SHA1 Message Date
yuri
814f79d53b Merge branch 'hotfix/5.7.2' of github.com:espocrm/espocrm into hotfix/5.7.2 2019-10-10 15:17:42 +03:00
yuri
0d6482374e Merge branch 'hotfix/5.7.2' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.2 2019-10-10 15:06:34 +03:00
Karthik Bhat K
427e3d466a count(): Parameter must be an array or an object that implements Countable (#1460) 2019-10-10 10:13:27 +03:00
yuri
ae72768f8c v 2019-10-09 11:14:28 +03:00
yuri
bd4e92d892 multi-enum display as list 2019-10-09 11:06:34 +03:00
yuri
afc17ac54c fix filter by foreign array 2019-10-09 10:48:21 +03:00
Taras Machyshyn
52dda066cd Index name consists of a list of fields for relation tables 2019-10-09 10:34:26 +03:00
yuri
29bec8fcd6 fix email storing 2019-10-09 10:15:27 +03:00
yuri
5c4fd344e4 smtp default tls 2019-10-08 17:54:12 +03:00
yuri
e936ad907b outbound email settings fixes 2019-10-08 17:50:45 +03:00
yuri
bc59dab9ec fix lang acl 2019-10-08 17:40:17 +03:00
yuri
48652e35ee fix integrations 2019-10-08 17:04:35 +03:00
yuri
381a51b836 Merge branch 'hotfix/5.7.1' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.1 2019-10-08 15:10:15 +03:00
Taras Machyshyn
a9f36a352d Orm: defined index name for relations 2019-10-08 15:07:55 +03:00
yuri
6c8a0bbb66 orm useIndex change 2019-10-08 13:43:31 +03:00
yuri
b21b69eed5 cs fix 2019-10-08 13:43:09 +03:00
yuri
ef0d3febd8 buttons small fix 2019-10-08 11:54:15 +03:00
yuri
2d6db7570c fix record next/prev 2019-10-08 11:44:47 +03:00
yuri
a7804742e7 fix select manager order 2019-10-08 11:18:50 +03:00
yuri
09301e9d7c fix reminders 2019-10-08 10:45:07 +03:00
yuri
5d10da3b78 orm fix 2019-10-07 14:42:37 +03:00
yuri
dcc4cce872 update reserved words 2019-10-07 14:23:53 +03:00
yuri
02fbc20838 orm join only middle 2019-10-07 13:37:59 +03:00
yuri
6641075043 orm fix 2019-10-07 13:22:30 +03:00
yuri
6624b178c0 fix select manager is not linked filter 2019-10-07 13:08:19 +03:00
yuri
4ec6708cb4 v 2019-10-07 12:41:17 +03:00
yuri
f9752eab02 orm more sanitizing 2019-10-07 12:28:10 +03:00
yuri
3de816e03a formula entity get link column 2019-10-07 12:27:42 +03:00
yuri
b854ebab43 lead capture fix 2019-10-07 11:23:41 +03:00
yuri
ffab7c3e6b external account fix 2019-10-07 10:26:47 +03:00
38 changed files with 465 additions and 134 deletions

View File

@@ -181,7 +181,7 @@ class Application
}
}
$processList = array_values($processList);
if (count($runningCount) >= $maxProcessNumber) {
if ($runningCount >= $maxProcessNumber) {
$toSkip = true;
}
if (!$toSkip) {

View File

@@ -69,7 +69,13 @@ class ClientManager
$externalAccountEntity = $this->clientMap[$hash]['externalAccountEntity'];
$externalAccountEntity->set('accessToken', $data['accessToken']);
$externalAccountEntity->set('tokenType', $data['tokenType']);
$this->getEntityManager()->saveEntity($externalAccountEntity, ['isTokenRenewal' => true]);
$copy = $this->getEntityManager()->getEntity('ExternalAccount', $externalAccountEntity->id);
if ($copy) {
$copy->set('accessToken', $data['accessToken']);
$copy->set('tokenType', $data['tokenType']);
$this->getEntityManager()->saveEntity($copy, ['isTokenRenewal' => true]);
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2019 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Formula\Functions\EntityGroup;
use \Espo\ORM\Entity;
use \Espo\Core\Exceptions\Error;
class GetLinkColumnType extends \Espo\Core\Formula\Functions\Base
{
protected function init()
{
$this->addDependency('entityManager');
}
public function process(\StdClass $item)
{
$args = $item->value ?? [];
if (!is_array($args)) throw new Error();
if (count($args) < 3) throw new Error("Formula: entity\\isRelated: no argument.");
$link = $this->evaluate($args[0]);
$id = $this->evaluate($args[1]);
$column = $this->evaluate($args[2]);
$entityType = $this->getEntity()->getEntityType($entityType);
$repository = $this->getInjection('entityManager')->getRepository($entityType);
return $repository->getRelationColumn($this->getEntity(), $link, $id, $column);
}
}

View File

@@ -819,14 +819,23 @@ class Base
$this->applyAdditional($params, $result);
if (empty($result['orderBy'])) {
$result['orderBy'] = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'orderBy']);
if (empty($result['order']) && !is_array($result['orderBy'])) {
$result['order'] = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'order']) ?? null;
}
return $result;
}
public function applyDefaultOrder(array &$result)
{
$orderBy = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'orderBy']);
$order = $result['order'] ?? null;
if (!$order && !is_array($orderBy)) {
$order = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'order']) ?? null;
}
return $result;
if ($orderBy) {
$this->applyOrder($orderBy, $order, $result);
} else {
$result['order'] = $order;
}
}
public function checkWhere(array $where, bool $checkWherePermission = true, bool $forbidComplexExpressions = false)
@@ -1624,11 +1633,12 @@ class Base
break;
case 'isNotLinked':
if (!$result) break;
$alias = $attribute . 'IsNotLinkedFilter' . strval(rand(10000, 99999));
$part[$alias . '.id'] = null;
$this->setDistinct(true, $result);
$this->addLeftJoin([$attribute, $alias], $result);
$part['id!=s'] = [
'selectParams' => [
'select' => ['id'],
'joins' => [$attribute],
]
];
break;
case 'isLinked':
@@ -1718,6 +1728,8 @@ class Base
case 'arrayNoneOf':
case 'arrayIsEmpty':
case 'arrayIsNotEmpty':
if (!$result) break;
$arrayValueAlias = 'arrayFilter' . strval(rand(10000, 99999));
$arrayAttribute = $attribute;
$arrayEntityType = $this->getEntityType();
@@ -1727,7 +1739,16 @@ class Base
list($arrayAttributeLink, $arrayAttribute) = explode('.', $attribute);
$seed = $this->getSeed();
$arrayEntityType = $seed->getRelationParam($arrayAttributeLink, 'entity');
$idPart = $arrayAttributeLink . '.id';
$arrayLinkAlias = $arrayAttributeLink . 'Filter' . strval(rand(10000, 99999));
$idPart = $arrayLinkAlias . '.id';
$this->addLeftJoin([$arrayAttributeLink, $arrayLinkAlias], $result);
$relationType = $seed->getRelationType($arrayAttributeLink);
if ($relationType === 'manyMany' || $relationType === 'hasMany') {
$this->setDistinct(true, $result);
}
}
if ($type === 'arrayAnyOf') {

View File

@@ -560,14 +560,23 @@ class Converter
protected function applyIndexes(&$ormMetadata, $entityType)
{
if (!isset($ormMetadata[$entityType]['indexes'])) {
return;
if (isset($ormMetadata[$entityType]['indexes'])) {
foreach ($ormMetadata[$entityType]['indexes'] as $indexName => &$indexData) {
if (!isset($indexData['key'])) {
$indexType = SchemaUtils::getIndexTypeByIndexDefs($indexData);
$indexData['key'] = SchemaUtils::generateIndexName($indexName, $indexType);
}
}
}
foreach ($ormMetadata[$entityType]['indexes'] as $indexName => &$indexData) {
if (!isset($indexData['key'])) {
$indexType = SchemaUtils::getIndexTypeByIndexDefs($indexData);
$indexData['key'] = SchemaUtils::generateIndexName($indexName, $indexType);
if (isset($ormMetadata[$entityType]['relations'])) {
foreach ($ormMetadata[$entityType]['relations'] as $relationName => &$relationData) {
if (isset($relationData['indexes'])) {
foreach ($relationData['indexes'] as $indexName => &$indexData) {
$indexType = SchemaUtils::getIndexTypeByIndexDefs($indexData);
$indexData['key'] = SchemaUtils::generateIndexName($indexName, $indexType);
}
}
}
}
}

View File

@@ -31,13 +31,13 @@ namespace Espo\Core\Utils\Database\Orm\Relations;
class EntityTeam extends Base
{
protected function load($linkName, $entityName)
protected function load($linkName, $entityType)
{
$linkParams = $this->getLinkParams();
$foreignEntityName = $this->getForeignEntityName();
return [
$entityName => [
$entityType => [
'relations' => [
$linkName => [
'type' => 'manyMany',
@@ -45,21 +45,21 @@ class EntityTeam extends Base
'relationName' => lcfirst($linkParams['relationName']),
'midKeys' => [
'entityId',
'teamId'
'teamId',
],
'conditions' => [
'entityType' => $entityName
'entityType' => $entityType,
],
'additionalColumns' => [
'entityType' => [
'type' => 'varchar',
'len' => 100
]
]
]
]
'len' => 100,
],
],
],
],
]
];
}
}
}

View File

@@ -340,7 +340,8 @@ class Converter
}
if (!empty($uniqueIndex)) {
$table->addUniqueIndex($uniqueIndex, SchemaUtils::generateIndexName($columnName, 'unique'));
$uniqueIndexName = implode('_', $uniqueIndex);
$table->addUniqueIndex($uniqueIndex, SchemaUtils::generateIndexName($uniqueIndexName, 'unique'));
}
//END: add unique indexes

View File

@@ -69,7 +69,7 @@ class Utils
if (isset($entityParams['indexes']) && is_array($entityParams['indexes'])) {
foreach ($entityParams['indexes'] as $indexName => $indexParams) {
$indexType = static::getIndexTypeByIndexDefs($indexParams);
$tableIndexName = static::generateIndexName($indexName, $indexType);
$tableIndexName = isset($indexParams['key']) ? $indexParams['key'] : static::generateIndexName($indexName, $indexType);
if (isset($indexParams['flags']) && is_array($indexParams['flags'])) {
$skipIndex = false;

View File

@@ -50,7 +50,7 @@ class EntityManager
private $container;
private $reservedWordList = ['__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor', 'common'];
private $reservedWordList = ['__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor', 'common', 'fn'];
private $linkForbiddenNameList = ['posts', 'stream', 'subscription', 'followers', 'action', 'null', 'false', 'true'];

View File

@@ -57,9 +57,9 @@ return [
'outboundEmailFromName' => 'EspoCRM',
'outboundEmailFromAddress' => '',
'smtpServer' => '',
'smtpPort' => 25,
'smtpPort' => 587,
'smtpAuth' => true,
'smtpSecurity' => '',
'smtpSecurity' => 'TLS',
'smtpUsername' => '',
'smtpPassword' => '',
'languageList' => [

View File

@@ -31,7 +31,7 @@ namespace Espo\Entities;
class Integration extends \Espo\Core\ORM\Entity
{
public function get($name, $params = array())
public function get($name, $params = [])
{
if ($name == 'id') {
return $this->id;
@@ -100,6 +100,13 @@ class Integration extends \Espo\Core\ORM\Entity
}
}
public function isAttributeChanged($name)
{
if ($name === 'data') return true;
return parent::isAttributeChanged($name);
}
public function populateFromArray(array $arr, $onlyAccessible = true, $reset = false)
{
if ($reset) {

View File

@@ -438,6 +438,59 @@ abstract class Mapper implements IMapper
}
}
public function getRelationColumn(IEntity $entity, string $relationName, string $id, string $column)
{
$type = $entity->getRelationType($entityType, $relationName);
if (!$type === IEntity::MANY_MANY) return null;
$relDefs = $entity->relations[$relationName];
$relTable = $this->toDb($relDefs['relationName']);
$keySet = $this->query->getKeys($entity, $relationName);
$key = $keySet['key'];
$foreignKey = $keySet['foreignKey'];
$nearKey = $keySet['nearKey'];
$distantKey = $keySet['distantKey'];
$additionalColumns = $entity->getRelationParam($relationName, 'additionalColumns') ?? [];
if (!isset($additionalColumns[$column])) return null;
$columnType = $additionalColumns[$column]['type'] ?? 'string';
$columnAlias = $this->query->sanitizeSelectAlias($column);
$sql =
"SELECT " . $this->toDb($this->query->sanitize($column)) . " AS `{$columnAlias}` FROM `{$relTable}` " .
"WHERE ";
$wherePart =
$this->toDb($nearKey) . " = " . $this->pdo->quote($entity->id) . " ".
"AND " . $this->toDb($distantKey) . " = " . $this->pdo->quote($id) . " AND deleted = 0";
$sql .= $wherePart;
$ps = $this->pdo->query($sql);
if ($ps) {
foreach ($ps as $row) {
$value = $row[$columnAlias];
if ($columnType == 'bool') {
$value = boolval($value);
} else if ($columnType == 'int') {
$value = intval($value);
} else if ($columnType == 'float') {
$value = floatval($value);
}
return $value;
}
}
return null;
}
public function massRelate(IEntity $entity, $relationName, array $params = [])
{
if (!$entity) {

View File

@@ -57,7 +57,7 @@ abstract class Base
'customHaving',
'skipTextColumns',
'maxTextColumnsLength',
'useIndexList',
'useIndex',
];
protected static $sqlOperators = [
@@ -310,13 +310,15 @@ abstract class Base
if (!empty($params['additionalColumns']) && is_array($params['additionalColumns']) && !empty($params['relationName'])) {
foreach ($params['additionalColumns'] as $column => $field) {
$selectPart .= ", `" . $this->toDb($this->sanitize($params['relationName'])) . "`." . $this->toDb($this->sanitize($column)) . " AS `{$field}`";
$itemAlias = $this->sanitizeSelectAlias($field);
$selectPart .= ", `" . $this->toDb($this->sanitize($params['relationName'])) . "`." . $this->toDb($this->sanitize($column)) . " AS `{$itemAlias}`";
}
}
if (!empty($params['additionalSelectColumns']) && is_array($params['additionalSelectColumns'])) {
foreach ($params['additionalSelectColumns'] as $column => $field) {
$selectPart .= ", " . $column . " AS `{$field}`";
$itemAlias = $this->sanitizeSelectAlias($field);
$selectPart .= ", " . $column . " AS `{$itemAlias}`";
}
}
@@ -384,9 +386,14 @@ abstract class Base
}
$indexKeyList = null;
if (!empty($params['useIndexList']) && $this->metadata) {
$indexList = $params['useIndex'] ?? null;
if (!empty($indexList) && $this->metadata) {
$indexKeyList = [];
foreach ($params['useIndexList'] as $indexName) {
if (is_string($indexList)) {
$indexList = [$indexList];
}
foreach ($indexList as $indexName) {
$indexKey = $this->metadata->get($entityType, ['indexes', $indexName, 'key']);
if ($indexKey) {
$indexKeyList[] = $indexKey;
@@ -864,7 +871,7 @@ abstract class Base
$part = str_replace('{alias}', $alias, $part);
}
} else {
$part = $this->toDb($entity->getEntityType()) . '.' . $this->toDb($attribute);
$part = $this->toDb($entity->getEntityType()) . '.' . $this->toDb($this->sanitize($attribute));
if ($type === 'orderBy') {
$part .= ' {direction}';
}
@@ -1019,6 +1026,8 @@ abstract class Base
if (!$alias) {
$alias = $this->getAlias($entity, $relationName);
} else {
$alias = $this->sanitizeSelectAlias($alias);
}
if ($alias) {
@@ -1613,14 +1622,19 @@ abstract class Base
protected function getJoins(IEntity $entity, array $joins, $isLeft = false, $joinConditions = [])
{
$joinSqlList = [];
foreach ($joins as $item) {
$itemConditions = [];
$params = [];
if (is_array($item)) {
$relationName = $item[0];
if (count($item) > 1) {
$alias = $item[1];
$alias = $item[1] ?? $relationName;
if (count($item) > 2) {
$itemConditions = $item[2];
$itemConditions = $item[2] ?? [];
}
if (count($item) > 3) {
$params = $item[3] ?? [];
}
} else {
$alias = $relationName;
@@ -1636,7 +1650,7 @@ abstract class Base
foreach ($itemConditions as $left => $right) {
$conditions[$left] = $right;
}
if ($sql = $this->getJoin($entity, $relationName, $isLeft, $conditions, $alias)) {
if ($sql = $this->getJoin($entity, $relationName, $isLeft, $conditions, $alias, $params)) {
$joinSqlList[] = $sql;
}
}
@@ -1752,13 +1766,15 @@ abstract class Base
}
}
protected function getJoin(IEntity $entity, $name, $isLeft = false, $conditions = [], $alias = null)
protected function getJoin(IEntity $entity, $name, $isLeft = false, $conditions = [], $alias = null, array $params = [])
{
$prefix = ($isLeft) ? 'LEFT ' : '';
if (!$entity->hasRelation($name)) {
if (!$alias) {
$alias = $this->sanitize($name);
} else {
$alias = $this->sanitizeSelectAlias($alias);
}
$table = $this->toDb($this->sanitize($name));
@@ -1808,8 +1824,34 @@ abstract class Base
$midAlias = $alias . 'Middle';
$indexKeyList = null;
$indexList = $params['useIndex'] ?? null;
if ($indexList && $this->metadata) {
$indexKeyList = [];
if (is_string($indexList)) {
$indexList = [$indexList];
}
foreach ($indexList as $indexName) {
$indexKey = $this->metadata->get($entity->getEntityType(), ['relations', $relationName, 'indexes', $indexName, 'key']);
if ($indexKey) {
$indexKeyList[] = $indexKey;
}
}
}
$indexPart = '';
if ($indexKeyList && count($indexKeyList)) {
$sanitizedIndexList = [];
foreach ($indexKeyList as $indexKey) {
$sanitizedIndexList[] = '`' . $this->sanitizeIndexName($indexKey) . '`';
}
$indexPart = " USE INDEX (".implode(', ', $sanitizedIndexList).")";
}
$sql =
"{$prefix}JOIN `{$relTable}` AS `{$midAlias}` ON {$this->toDb($entity->getEntityType())}." . $this->toDb($key) . " = {$midAlias}." . $this->toDb($nearKey)
"{$prefix}JOIN `{$relTable}` AS `{$midAlias}`{$indexPart} ON {$this->toDb($entity->getEntityType())}." . $this->toDb($key) . " = {$midAlias}." . $this->toDb($nearKey)
. " AND "
. "{$midAlias}.deleted = " . $this->pdo->quote(0);
@@ -1821,9 +1863,13 @@ abstract class Base
$sql .= " AND " . implode(" AND ", $joinSqlList);
}
$sql .= " {$prefix}JOIN `{$distantTable}` AS `{$alias}` ON {$alias}." . $this->toDb($foreignKey) . " = {$midAlias}." . $this->toDb($distantKey)
. " AND "
. "{$alias}.deleted = " . $this->pdo->quote(0) . "";
$onlyMiddle = $params['onlyMiddle'] ?? false;
if (!$onlyMiddle) {
$sql .= " {$prefix}JOIN `{$distantTable}` AS `{$alias}` ON {$alias}." . $this->toDb($foreignKey) . " = {$midAlias}." . $this->toDb($distantKey)
. " AND "
. "{$alias}.deleted = " . $this->pdo->quote(0) . "";
}
return $sql;

View File

@@ -396,6 +396,11 @@ class RDB extends \Espo\ORM\Repository
return $result;
}
public function getRelationColumn(Entity $entity, string $relationName, string $foreignId, string $column)
{
return $this->getMapper()->getRelationColumn($entity, $relationName, $foreignId, $column);
}
protected function beforeRelate(Entity $entity, $relationName, $foreign, $data = null, array $options = [])
{
}

View File

@@ -188,6 +188,7 @@
"inlineEditDisabled": "Disable Inline Edit",
"allowCustomOptions": "Allow Custom Options",
"displayAsLabel": "Display as Label",
"displayAsList": "Display as List",
"maxCount": "Max Item Count",
"accept": "Accept",
"displayRawText": "Display raw text (no markdown)"

View File

@@ -92,7 +92,7 @@
"type": "int",
"min": 0,
"max": 9999,
"default": 25
"default": 587
},
"smtpAuth": {
"type": "bool",
@@ -100,6 +100,7 @@
},
"smtpSecurity": {
"type": "enum",
"default": "TLS",
"options": ["", "SSL", "TLS"]
},
"smtpUsername": {

View File

@@ -100,7 +100,7 @@
"type": "int",
"min": 0,
"max": 9999,
"default": 25
"default": 587
},
"smtpAuth": {
"type": "bool",
@@ -108,6 +108,7 @@
},
"smtpSecurity": {
"type": "enum",
"default": "TLS",
"options": ["", "SSL", "TLS"]
},
"smtpUsername": {

View File

@@ -101,7 +101,8 @@
"type": "int",
"min": 0,
"max": 9999,
"default": 25
"required": true,
"default": 587
},
"smtpAuth": {
"type": "bool",
@@ -109,6 +110,7 @@
},
"smtpSecurity": {
"type": "enum",
"default": "TLS",
"options": ["", "SSL", "TLS"]
},
"smtpUsername": {

View File

@@ -37,6 +37,10 @@
"name":"displayAsLabel",
"type":"bool"
},
{
"name": "displayAsList",
"type":"bool"
},
{
"name":"audited",
"type":"bool"

View File

@@ -71,12 +71,12 @@ class Email extends \Espo\Core\SelectManagers\Base
$skipIndex = true;
}
if (!$skipIndex) {
$result['useIndexList'] = ['dateSent'];
$result['useIndex'] = 'dateSent';
}
}
if ($folderId === 'drafts') {
$result['useIndexList'] = ['createdById'];
$result['useIndex'] = 'createdById';
}
if ($folderId !== 'drafts') {

View File

@@ -253,7 +253,7 @@ class Email extends Record
}
}
$message = null;
$message = new \Zend\Mail\Message();
$this->validateEmailAddresses($entity);

View File

@@ -102,6 +102,8 @@ class Language extends \Espo\Core\Services\Base
unset($data['Global']['scopeNames'][$scope]);
unset($data['Global']['scopeNamesPlural'][$scope]);
} else {
if (in_array($scope, ['EmailAccount', 'InboundEmail'])) continue;
foreach ($this->getAcl()->getScopeForbiddenFieldList($scope) as $field) {
if (isset($data[$scope]['fields'])) unset($data[$scope]['fields'][$field]);
if (isset($data[$scope]['options'])) unset($data[$scope]['options'][$field]);

View File

@@ -327,12 +327,16 @@ class LeadCapture extends Record
}
}
$isContactOptedIn = false;
if ($leadCapture->get('subscribeToTargetList') && $leadCapture->get('targetListId')) {
$isAlreadyOptedIn = false;
if ($contact) {
if ($leadCapture->get('subscribeContactToTargetList')) {
$isAlreadyOptedIn = $this->isTargetOptedIn($contact, $leadCapture->get('targetListId'));
$isContactOptedIn = $isAlreadyOptedIn;
if (!$isAlreadyOptedIn) {
$this->getEntityManager()->getRepository('Contact')->relate($contact, 'targetLists', $leadCapture->get('targetListId'), [
'optedOut' => false,
@@ -351,15 +355,6 @@ class LeadCapture extends Record
'leadCaptureId' => $leadCapture->id,
]);
}
$this->getInjection('hookManager')->process('LeadCapture', 'afterLeadCapture', $leadCapture, [], [
'targetId' => $contact->id,
'targetType' => 'Contact',
]);
$this->getInjection('hookManager')->process('Contact', 'afterLeadCapture', $contact, [], [
'leadCaptureId' => $leadCapture->id,
]);
}
}
}
@@ -376,6 +371,16 @@ class LeadCapture extends Record
}
}
if ($contact && (!$isContactOptedIn || !$leadCapture->get('subscribeToTargetList'))) {
$this->getInjection('hookManager')->process('LeadCapture', 'afterLeadCapture', $leadCapture, [], [
'targetId' => $contact->id,
'targetType' => 'Contact',
]);
$this->getInjection('hookManager')->process('Contact', 'afterLeadCapture', $contact, [], [
'leadCaptureId' => $leadCapture->id,
]);
}
$isNew = !$duplicate && !$contact;
if (!$contact || !$leadCapture->get('subscribeContactToTargetList')) {
@@ -408,12 +413,13 @@ class LeadCapture extends Record
'leadCaptureId' => $leadCapture->id,
]);
}
}
if ($toRelateLead || !$leadCapture->get('subscribeToTargetList')) {
$this->getInjection('hookManager')->process('LeadCapture', 'afterLeadCapture', $leadCapture, [], [
'targetId' => $targetLead->id,
'targetType' => 'Lead',
]);
$this->getInjection('hookManager')->process('Lead', 'afterLeadCapture', $targetLead, [], [
'leadCaptureId' => $leadCapture->id,
]);

View File

@@ -64,6 +64,7 @@ class Metadata extends \Espo\Core\Services\Base
if (!$this->getUser()->isAdmin()) {
$scopeList = array_keys($this->getMetadata()->get(['scopes'], []));
foreach ($scopeList as $scope) {
if (in_array($scope, ['Reminder'])) continue;
if (!$this->getAcl()->check($scope)) {
unset($data->entityDefs->$scope);
unset($data->clientDefs->$scope);

View File

@@ -1064,7 +1064,13 @@ class Record extends \Espo\Core\Services\Base
protected function getSelectParams($params)
{
$selectParams = $this->getSelectManager($this->entityType)->getSelectParams($params, true, true, true);
$selectManager = $this->getSelectManager($this->entityType);
$selectParams = $selectManager->getSelectParams($params, true, true, true);
if (empty($selectParams['orderBy'])) {
$selectManager->applyDefaultOrder($selectParams);
}
return $selectParams;
}
@@ -1345,7 +1351,13 @@ class Record extends \Espo\Core\Services\Base
}
}
$selectParams = $this->getSelectManager($foreignEntityType)->getSelectParams($params, !$skipAcl, true);
$selectManager = $this->getSelectManager($foreignEntityType);
$selectParams = $selectManager->getSelectParams($params, !$skipAcl, true);
if (empty($selectParams['orderBy'])) {
$selectManager->applyDefaultOrder($selectParams);
}
if (array_key_exists($link, $this->linkSelectParams)) {
$selectParams = array_merge($selectParams, $this->linkSelectParams[$link]);

View File

@@ -194,9 +194,7 @@ class RecordTree extends Record
public function getLastChildrenIdList($parentId = null)
{
$selectParams = $this->getSelectManager($this->entityType)->getSelectParams([], true, true);
$selectParams['whereClause'][] = array(
'parentId' => $parentId
);
$selectParams['whereClause'][] = ['parentId' => $parentId];
$idList = [];
@@ -205,12 +203,12 @@ class RecordTree extends Record
$includingRecords = true;
}
$collection = $this->getRepository()->find($selectParams);
$collection = $this->getRepository()->select(['id'])->find($selectParams);
foreach ($collection as $entity) {
$selectParams2 = $this->getSelectManager($this->entityType)->getSelectParams([], true, true);
$selectParams2['whereClause'][] = array(
'parentId' => $entity->id
);
$selectParams2['whereClause'][] = ['parentId' => $entity->id];
if (!$this->getRepository()->count($selectParams2)) {
$idList[] = $entity->id;
} else {

View File

@@ -386,7 +386,7 @@ class Stream extends \Espo\Core\Services\Base
'orderBy' => 'number',
'order' => 'DESC',
'limit' => $sqLimit,
'useIndexList' => ['createdByNumber'],
'useIndex' => 'createdByNumber',
];
if ($user->isPortal()) {
@@ -484,7 +484,7 @@ class Stream extends \Espo\Core\Services\Base
'orderBy' => 'number',
'order' => 'DESC',
'limit' => $sqLimit,
'useIndexList' => ['createdByNumber'],
'useIndex' => 'createdByNumber',
];
if ($user->isPortal()) {

View File

@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('crm:views/meeting/fields/reminders', 'views/fields/base', function (Dep) {
define('crm:views/meeting/fields/reminders', 'views/fields/base', function (Dep) {
return Dep.extend({
@@ -76,8 +76,8 @@ Espo.define('crm:views/meeting/fields/reminders', 'views/fields/base', function
this.reminderList = this.model.get(this.name) || [];
}, this);
this.typeList = this.getMetadata().get('entityDefs.Reminder.fields.type.options');
this.secondsList = this.getMetadata().get('entityDefs.Reminder.fields.seconds.options');
this.typeList = this.getMetadata().get('entityDefs.Reminder.fields.type.options') || [];
this.secondsList = this.getMetadata().get('entityDefs.Reminder.fields.seconds.options') || [];
},
afterRender: function () {

View File

@@ -26,29 +26,73 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/admin/outbound-emails', 'views/settings/record/edit', function (Dep) {
define('views/admin/outbound-emails', 'views/settings/record/edit', function (Dep) {
return Dep.extend({
layoutName: 'outboundEmails',
dependencyDefs: {
'smtpAuth': {
map: {
true: [
{
action: 'show',
fields: ['smtpUsername', 'smtpPassword']
}
]
},
default: [
{
action: 'hide',
fields: ['smtpUsername', 'smtpPassword']
dynamicLogicDefs: {
fields: {
smtpUsername: {
visible: {
conditionGroup: [
{
type: 'isNotEmpty',
attribute: 'smtpServer',
},
{
type: 'isTrue',
attribute: 'smtpAuth',
}
]
}
]
}
},
smtpPassword: {
visible: {
conditionGroup: [
{
type: 'isNotEmpty',
attribute: 'smtpServer',
},
{
type: 'isTrue',
attribute: 'smtpAuth',
}
]
}
},
smtpPort: {
visible: {
conditionGroup: [
{
type: 'isNotEmpty',
attribute: 'smtpServer',
},
]
}
},
smtpSecurity: {
visible: {
conditionGroup: [
{
type: 'isNotEmpty',
attribute: 'smtpServer',
},
]
}
},
smtpAuth: {
visible: {
conditionGroup: [
{
type: 'isNotEmpty',
attribute: 'smtpServer',
},
]
}
},
},
},
setup: function () {

View File

@@ -136,36 +136,29 @@ define('views/detail', 'views/main', function (Dep) {
},
actionFollow: function () {
$el = this.$el.find('[data-action="follow"]');
$el.addClass('disabled');
$.ajax({
url: this.model.name + '/' + this.model.id + '/subscription',
type: 'PUT',
success: function () {
$el.remove();
this.disableMenuItem('follow');
Espo.Ajax.putRequest(this.model.name + '/' + this.model.id + '/subscription')
.then(function () {
this.removeMenuItem('follow', true);
this.model.set('isFollowed', true);
}.bind(this),
error: function () {
$el.removeClass('disabled');
}.bind(this)
});
}.bind(this))
.fail(function () {
this.enableMenuItem('follow');
}.bind(this));
},
actionUnfollow: function () {
$el = this.$el.find('[data-action="unfollow"]');
$el.addClass('disabled');
$.ajax({
url: this.model.name + '/' + this.model.id + '/subscription',
type: 'DELETE',
success: function () {
$el.remove();
this.model.set('isFollowed', false);
}.bind(this),
error: function () {
$el.removeClass('disabled');
}.bind(this)
});
this.disableMenuItem('unfollow');
Espo.Ajax.deleteRequest(this.model.name + '/' + this.model.id + '/subscription')
.then(function () {
this.removeMenuItem('unfollow', true);
this.model.set('isFollowed', false);
}.bind(this))
.fail(function () {
this.enableMenuItem('unfollow');
}.bind(this));
},
getHeader: function () {

View File

@@ -395,7 +395,12 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
if (this.displayAsList) {
if (!list.length) return '';
return '<div>' + list.join('</div><div>') + '</div>';
var itemClassName = 'multi-enum-item-container';
if (this.displayAsLabel) {
itemClassName += ' multi-enum-item-label-container';
}
return '<div class="'+itemClassName+'">' +
list.join('</div><div class="'+itemClassName+'">') + '</div>';
} else if (this.displayAsLabel) {
return list.join(' ');
} else {

View File

@@ -191,6 +191,10 @@ define('views/main', 'view', function (Dep) {
if (!doNotReRender && this.isRendered()) {
this.getView('header').reRender();
}
if (doNotReRender && this.isRendered()) {
this.$el.find('.header .header-buttons [data-name="'+name+'"]').remove();
}
},
disableMenuItem: function (name) {

View File

@@ -394,14 +394,13 @@ Espo.define('views/modals/detail', 'views/modal', function (Dep) {
} else {
var initialCount = collection.length;
this.listenToOnce(collection, 'sync', function () {
var model = collection.at(indexOfRecord);
this.switchToModelByIndex(indexOfRecord);
}, this);
collection.fetch({
more: true,
remove: false,
});
}).then(function () {
var model = collection.at(indexOfRecord);
this.switchToModelByIndex(indexOfRecord);
}.bind(this));
}
},

View File

@@ -975,6 +975,8 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
},
actionPrevious: function () {
this.model.abortLastFetch();
var collection;
if (!this.model.collection) {
collection = this.collection;
@@ -992,6 +994,8 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
},
actionNext: function () {
this.model.abortLastFetch();
var collection;
if (!this.model.collection) {
collection = this.collection;
@@ -1011,14 +1015,13 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
} else {
var initialCount = collection.length;
this.listenToOnce(collection, 'sync', function () {
var model = collection.at(indexOfRecord);
this.switchToModelByIndex(indexOfRecord);
}, this);
collection.fetch({
more: true,
remove: false,
});
}).then(function () {
var model = collection.at(indexOfRecord);
this.switchToModelByIndex(indexOfRecord);
}.bind(this));
}
},

View File

@@ -1041,6 +1041,13 @@ ul.dropdown-menu > li.checkbox:last-child {
margin-bottom: 0;
}
}
.multi-enum-item-label-container {
margin-bottom: 4px;
}
.multi-enum-item-label-container:last-child {
margin-bottom: 0;
}
}
.filter > .form-group .field {

View File

@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "5.7.0",
"version": "5.7.2",
"description": "",
"main": "index.php",
"repository": {

View File

@@ -320,4 +320,30 @@ class FormulaTest extends \tests\integration\Core\BaseTestCase
$this->assertTrue($result1 !== $result);
}
public function testEntityGetLinkColumn()
{
$fm = $this->getContainer()->get('formulaManager');
$em = $this->getContainer()->get('entityManager');
$lead = $em->createEntity('Lead', []);
$targetList = $em->createEntity('TargetList', []);
$em->getRepository('Lead')->relate($lead, 'targetLists', $targetList->id, [
'optedOut' => true,
]);
$script = "entity\\getLinkColumn('targetLists', '{$targetList->id}', 'optedOut')";
$result = $fm->run($script, $lead);
$this->assertTrue($result);
$em->getRepository('Lead')->relate($lead, 'targetLists', $targetList->id, [
'optedOut' => false,
]);
$result = $fm->run($script, $lead);
$this->assertFalse($result);
}
}

View File

@@ -308,6 +308,22 @@ class QueryTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($expectedSql, $sql);
}
public function testJoinOnlyMiddle()
{
$sql = $this->query->createSelectQuery('Post', [
'select' => ['id'],
'leftJoins' => [['tags', null, null, ['onlyMiddle' => true]]]
]);
$expectedSql =
"SELECT post.id AS `id` FROM `post` " .
"LEFT JOIN `post_tag` AS `tagsMiddle` ON post.id = tagsMiddle.post_id AND tagsMiddle.deleted = '0' " .
"WHERE post.deleted = '0'";
$this->assertEquals($expectedSql, $sql);
}
public function testWhereNotValue1()
{
$sql = $this->query->createSelectQuery('Post', [