Compare commits

...

49 Commits
5.7.0 ... 5.7.3

Author SHA1 Message Date
yuri
8199692df7 calendar ui fixes 2019-10-15 16:38:08 +03:00
yuri
1d3a340d3a duplicate returnUrl 2019-10-15 15:19:53 +03:00
yuri
d664f29388 fix 2019-10-15 14:04:00 +03:00
yuri
4d0e1af000 save and continue action 2019-10-15 14:00:55 +03:00
yuri
97ea0c71e1 fix handle action 2019-10-15 14:00:45 +03:00
yuri
d9fbcda231 lanf 2019-10-15 11:57:28 +03:00
yuri
f4b5cfa5b6 add global search test 2019-10-15 11:43:37 +03:00
yuri
4391c7a7ac fix test 2019-10-15 11:43:27 +03:00
yuri
6c14f390f6 fix global search 2019-10-15 11:31:08 +03:00
yuri
4a6829cf10 fix output 2019-10-15 11:15:45 +03:00
yuri
2b6c493be5 fix link parent 2019-10-14 13:24:42 +03:00
yuri
597406f70d fix test 2019-10-14 13:00:31 +03:00
yuri
110e2fbc37 string pos formula function 2019-10-14 12:32:30 +03:00
yuri
3e1fab487a htmlizer ifInArray 2019-10-14 11:30:50 +03:00
yuri
c76e34fe81 fix enum 2019-10-11 15:49:53 +03:00
yuri
eb922103a8 v 2019-10-11 13:02:04 +03:00
yuri
9b946c6a1f compose email layout columns 2019-10-11 13:01:45 +03:00
yuri
e2df819e57 call allow 0 duration 2019-10-11 11:17:21 +03:00
yuri
9b00d50079 fix select manager user from team 2019-10-11 10:50:25 +03:00
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
62 changed files with 947 additions and 211 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

@@ -0,0 +1,51 @@
<?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\StringGroup;
use \Espo\Core\Exceptions\Error;
class PosType extends \Espo\Core\Formula\Functions\Base
{
public function process(\StdClass $item)
{
$args = $item->value ?? [];
if (count($args) < 2) throw new Error("Bad arguments passed to function string\\pos.");
$string = $this->evaluate($args[0]);
$needle = $this->evaluate($args[1]);
if (!is_string($string)) {
return false;
}
return mb_strpos($string, $needle);
}
}

View File

@@ -300,7 +300,19 @@ class Htmlizer
} else {
return $context['inverse'] ? $context['inverse']() : '';
}
}
},
'ifInArray' => function () {
$args = func_get_args();
$context = $args[count($args) - 1];
$array = $args[1] ?? [];
if (in_array($args[0], $array)) {
return $context['fn']();
} else {
return $context['inverse'] ? $context['inverse']() : '';
}
},
]
]);

View File

@@ -356,12 +356,16 @@ class Base
if ($relationType == 'belongsTo') {
$key = $seed->getRelationParam($link, 'key');
$aliasName = 'usersTeams' . ucfirst($link);
$aliasName = 'usersTeams' . ucfirst($link) . strval(rand(10000, 99999));
$result['customJoin'] .= "
JOIN team_user AS {$aliasName}Middle ON {$aliasName}Middle.user_id = ".$query->toDb($seed->getEntityType()).".".$query->toDb($key)." AND {$aliasName}Middle.deleted = 0
JOIN team AS {$aliasName} ON {$aliasName}.deleted = 0 AND {$aliasName}Middle.team_id = {$aliasName}.id
";
$this->addLeftJoin([
'TeamUser',
$aliasName . 'Middle',
[
$aliasName . 'Middle.userId:' => $key,
$aliasName . 'Middle.deleted' => false,
]
], $result);
$result['whereClause'][] = [
$aliasName . 'Middle.teamId' => $idsValue
@@ -819,14 +823,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 +1637,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 +1732,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 +1743,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

@@ -71,7 +71,7 @@ class Output
echo $data;
}
public function processError(string $message = 'Error', int $statusCode = 500, bool $toPrint = false, $exception = null)
public function processError(string $message = 'Error', $statusCode = 500, bool $toPrint = false, $exception = null)
{
$currentRoute = $this->getSlim()->router()->getCurrentRoute();
@@ -99,7 +99,7 @@ class Output
$this->displayError($message, $statusCode, $toPrint, $exception);
}
public function displayError(string $text, int $statusCode = 500, bool $toPrint = false, $exception = null)
public function displayError(string $text, $statusCode = 500, bool $toPrint = false, $exception = null)
{
$logLevel = 'error';
$messageLineFile = null;

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

@@ -23,6 +23,7 @@
"dateEnd": {
"type": "datetime",
"required": true,
"view": "crm:views/call/fields/date-end",
"after": "dateStart"
},
"duration": {

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

@@ -56,7 +56,20 @@
"Portal Users": "Usuarios del portal",
"Action History": "Histórico",
"Label Manager": "Etiquetas",
"Permissions": "Permisos"
"Auth Log": "Registros de autenticación",
"Lead Capture": "Captura de Posible cliente",
"Attachments": "Adjuntos",
"API Users": "Usuarios de API",
"Template Manager": "Gestor de plantillas",
"System Requirements": "Requerimientos del sistema",
"PHP Settings": "Configuraciones PHP",
"Database Settings": "Configuraciones de la Base de Datos",
"Permissions": "Permisos",
"Success": "Éxito",
"Fail": "Falló",
"is recommended": "es recomendado",
"extension is missing": "no se encuentra la extensión",
"PDF Templates": "Plantillas PDF"
},
"layouts": {
"list": "Lista",
@@ -159,8 +172,7 @@
"installExtension": "La extensión {name} {version} está lista para ser instalada.",
"upgradeBackup": "Recomendamos hacer una copia de seguridad de los archivos y datos de EspoCRM antes de actualizar.",
"thousandSeparatorEqualsDecimalMark": "El símbolo de separador de miles no puede ser el mismo que el de punto decimal.",
"userHasNoEmailAddress": "El usuario no tiene dirección de correo electrónico.",
"newVersionIsAvailable": "La nueva versión de EspoCRM {latestVersion} está disponible."
"userHasNoEmailAddress": "El usuario no tiene dirección de correo electrónico."
},
"descriptions": {
"settings": "Ajustes generales del sistema.",
@@ -191,7 +203,17 @@
"entityManager": "Crear y editar entidades personalizadas. Administrar campos y relaciones.",
"emailFilters": "Filtros para los correos entrantes.",
"actionHistory": "Registro de las acciones del usuario.",
"labelManager": "Personaliza las etiquetas de las aplicaciones."
"labelManager": "Personaliza las etiquetas de las aplicaciones.",
"authLog": "Historial de acceso.",
"leadCapture": "Puntos de acceso al API para Web-to-Lead.",
"attachments": "Todos los archivos adjuntos almacenados en el sistema.",
"templateManager": "Personalice las plantillas de mensajes.",
"systemRequirements": "Requerimientos del sistema para EspoCRM.",
"apiUsers": "Usuarios separados para propósitos de integraciones.",
"jobs": "Los trabajos que ejecutan tareas en segundo plano.",
"pdfTemplates": "Plantillas para impresión en PDF.",
"webhooks": "Administre webhooks.",
"dashboardTemplates": "Implemente paneles para los usuarios."
},
"options": {
"previewSize": {

View File

@@ -328,14 +328,14 @@
},
"notificationMessages": {
"assign": "{entityType} {entity} ha sido asignado a usted",
"emailReceived": "Correo recibido de: <strong>{from}</strong>.",
"entityRemoved": "{user} ha eliminado: <strong>[{entityType}]</strong> {entity}"
"emailReceived": "Correo recibido de: {from}",
"entityRemoved": "{user} ha eliminado: {entityType} {entity}"
},
"streamMessages": {
"post": "{user} ha publicado en: <strong>[{entityType}]</strong> {entity}",
"attach": "{user} ha añadido un archivo adjunto en: <strong>[{entityType}]</strong> {entity}",
"status": "{user} ha actualizado el campo <strong>{field}</strong> en: <strong>[{entityType}]</strong> {entity}",
"update": "{user} ha actualizado: <strong>[{entityType}]</strong> {entity}",
"post": "{user} ha publicado en: {entityType} {entity}",
"attach": "{user} ha añadido un archivo adjunto en: {entityType} {entity}",
"status": "{user} ha actualizado el campo {field} en: {entityType} {entity}",
"update": "{user} ha actualizado: {entityType} {entity}",
"postTargetTeam": "{user} ha publicado en el equipo {target}",
"postTargetTeams": "{user} ha publicado en los equipos {target}",
"postTargetPortal": "{user} ha publicado en el portal {target}",
@@ -344,47 +344,47 @@
"postTargetYou": "{user} ha publicado pora usted",
"postTargetYouAndOthers": "{user} ha publicado para {target} y para usted",
"postTargetAll": "{user} ha publicado para todos",
"mentionInPost": "{user} ha mencionado a {mentioned} en: <strong>[{entityType}]</strong> {entity}",
"mentionYouInPost": "{user} te ha mencionado en: <strong>[{entityType}]</strong> {entity}",
"mentionInPost": "{user} ha mencionado a {mentioned} en: {entityType} {entity}",
"mentionYouInPost": "{user} te ha mencionado en: {entityType} {entity}",
"mentionInPostTarget": "{user} ha mencionado a {mentioned} en la publicación",
"mentionYouInPostTarget": "{user} te ha mencionado en la publicación para {target}",
"mentionYouInPostTargetAll": "{user} te ha mencionado en una publicación para todos",
"mentionYouInPostTargetNoTarget": "{user} te ha mencionado en una publicación",
"create": "{user} ha creado: <strong>[{entityType}]</strong> {entity}",
"createThis": "{user} ha creado: <strong>{entityType}</strong>",
"create": "{user} ha creado: {entityType} {entity}",
"createThis": "{user} ha creado: {entityType}",
"createAssignedThis": "{user} ha creado y se lo ha asignado a {assignee}: {entityType}",
"createAssigned": "{user} ha creado y se lo ha asignado a {assignee}: <strong>[{entityType}]</strong> {entity}",
"assign": "{user} ha asignado: <strong>[{entityType}]</strong> {entity} a {assignee}",
"createAssigned": "{user} ha creado y se lo ha asignado a {assignee}: {entityType} {entity}",
"assign": "{user} ha asignado: {entityType} {entity} a {assignee}",
"assignThis": "{user} ha asignado {entityType} a {assignee}",
"postThis": "{user} ha publicado",
"attachThis": "{user} ha adjuntado",
"statusThis": "{user} ha actualizado el campo <strong>{field}</strong>",
"statusThis": "{user} ha actualizado el campo {field}",
"updateThis": "{user} ha actualizado: {entityType}",
"createRelatedThis": "{user} ha creado: <strong>[{relatedEntityType}]</strong> {relatedEntity}, enlazado a {entityType}",
"createRelated": "{user} ha creado: <strong>[{relatedEntityType}]</strong> {relatedEntity} enlazado a <strong>[{entityType}]</strong> {entity}",
"relate": "{user} ha enlazado <strong>[{relatedEntityType}]</strong> {relatedEntity} a <strong>[{entityType}] {entity}",
"createRelatedThis": "{user} ha creado: {relatedEntityType} {relatedEntity}, enlazado a {entityType}",
"createRelated": "{user} ha creado: {relatedEntityType} {relatedEntity} enlazado a {entityType} {entity}",
"relate": "{user} ha enlazado {relatedEntityType} {relatedEntity} a {entityType} {entity}",
"relateThis": "{user} vinculado {relatedEntityType} {relatedEntity} con este {entityType}",
"emailReceivedFromThis": "Correo recibido de: <strong>{from}</strong>.",
"emailReceivedFromThis": "Correo recibido de: {from}",
"emailReceivedInitialFromThis": "Correo recibido de {from}, se ha creado: {entityType}",
"emailReceivedThis": "Correo recibido",
"emailReceivedInitialThis": "Correo recibido, se ha creado: {entityType}",
"emailReceivedFrom": "Correo recibido de {from}, relacionado a: <strong>[{entityType}]</strong> {entity}",
"emailReceivedFromInitial": "Correo recibido de {from}, se ha creado: <strong>[{entityType}]<strong>\n {entityType}",
"emailReceivedInitialFrom": "Correo recibido de {from}, se ha creado: <strong>[{entityType}]<strong>\n {entityType}",
"emailReceivedFrom": "Correo recibido de {from}, relacionado a: {entityType} {entity}",
"emailReceivedFromInitial": "Correo recibido de {from}, se ha creado: {entityType} {entityType}",
"emailReceivedInitialFrom": "Correo recibido de {from}, se ha creado: {entityType} {entityType}",
"emailReceived": "El correo {email} ha sido recibido para el {entityType} {entity}",
"emailReceivedInitial": "Correo recibido, se ha creado: <strong>[{entityType}]<strong>\n {entityType}",
"emailSent": "{by} ha enviado un correo relacionado a: <strong>[{entityType}]</strong> {entity}",
"emailReceivedInitial": "Correo recibido, se ha creado: {entityType} {entityType}",
"emailSent": "{by} ha enviado un correo relacionado a: {entityType} {entity}",
"emailSentThis": "{by} ha enviado un correo",
"postTargetSelf": "{user} se ha enviado un mensaje a sí mismo",
"postTargetSelfAndOthers": "{user} ha publicado para {target} y para sí mismo",
"createAssignedYou": "{user} ha creado y te lo ha asignado: <strong>[{entityType}]</strong> {entity}",
"createAssignedYou": "{user} ha creado y te lo ha asignado: {entityType} {entity}",
"createAssignedThisSelf": "{user} ha creado y se ha asignado a sí mismo: {entityType}",
"createAssignedSelf": "{user} ha creado y se ha asignado a sí mismo: <strong>[{entityType}]</strong> {entity}",
"assignYou": "{user} te ha asignado <strong>[{entityType}]<strong> {entity} a ti",
"createAssignedSelf": "{user} ha creado y se ha asignado a sí mismo: {entityType} {entity}",
"assignYou": "{user} te ha asignado {entityType} {entity} a ti",
"assignThisVoid": "{user} ha desasignado: {entityType}",
"assignVoid": "{user} ha desasignado: <strong>[{entityType}]</strong> {entity}",
"assignVoid": "{user} ha desasignado: {entityType} {entity}",
"assignThisSelf": "{user} se ha asignado así mismo: {entityType}",
"assignSelf": "{user} se ha asignado así mismo: <strong>[{entityType}]</strong> {entity}"
"assignSelf": "{user} se ha asignado así mismo: {entityType} {entity}"
},
"lists": {
"monthNames": [

View File

@@ -147,7 +147,6 @@
"upgradeBackup": "We adviseren om eerst een backup te maken van uw EspoCRM bestanden en data, alvorens te upgraden.",
"thousandSeparatorEqualsDecimalMark": "Het duizendtal scheidingsteken mag niet hetzelfde zijn als het decimaalteken.",
"userHasNoEmailAddress": "Gebruiker heeft geen emailadres.",
"newVersionIsAvailable": "Nieuwe EspoCRM-versie {latestVersion} is beschikbaar.",
"uninstallConfirmation": "Weet je zeker dat je de extensie wilt verwijderen?",
"cronIsNotConfigured": "Geplande taken zijn niet actief. Vandaar dat inkomende e-mails, meldingen en herinneringen niet werken. Volg de [instructies] (https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab) om cron-taak in te stellen.",
"newExtensionVersionIsAvailable": "Nieuwe versie {extensionName} {latestVersion} is beschikbaar.",

View File

@@ -184,10 +184,9 @@
"upgradeBackup": "Перед обновлением рекомендуется сделать резервную копию ваших файлов и данных EspoCRM.",
"thousandSeparatorEqualsDecimalMark": "Разделитель тысячных не может быть таким же, как разделитель десятичных.",
"userHasNoEmailAddress": "У пользователя нет адреса эл. почты.",
"newVersionIsAvailable": "Новая версия EspoCRM {latestVersion} доступна.",
"uninstallConfirmation": "Вы действительно хотите удалить расширение?",
"cronIsNotConfigured": "Запланированные задания не выполняются. Следовательно, входящие письма, уведомления и напоминания не работают. Пожалуйста, следуйте инструкциям [https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab) для установки cron job.",
"newExtensionVersionIsAvailable": "Новая {extensionName} версия {lastVersion} доступна."
"newExtensionVersionIsAvailable": "Новая {extensionName} версия {latestVersion} доступна."
},
"descriptions": {
"settings": "Системные настройки.",

View File

@@ -1,18 +1,21 @@
[
{
"label":"",
"rows":[
"columns": [
[
{
"name":"from",
"view": "views/email/fields/compose-from-address"
},
{"name":"cc"}
{"name":"to"}
],
[
{"name":"to"},
{"name":"cc"},
{"name":"bcc"}
],
]
]
},
{
"rows": [
[
{
"name": "parent"
@@ -22,9 +25,9 @@
"view":"views/email/fields/select-template"
}
],
[{"name":"subject","fullWidth":true}],
[{"name":"body","fullWidth":true}],
[{"name":"attachments"},{"name":"isHtml"}]
[{"name":"subject","fullWidth": true}],
[{"name":"body","fullWidth": true}],
[{"name":"attachments"}, {"name":"isHtml"} ]
]
}
]

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

@@ -97,6 +97,14 @@ class GlobalSearch extends \Espo\Core\Services\Base
$selectParams['orderBy'] = [['name']];
}
if ($this->getMetadata()->get(['entityDefs', $entityType, 'fields', 'name', 'type']) === 'personName') {
$selectParams['select'][] = 'firstName';
$selectParams['select'][] = 'lastName';
} else {
$selectParams['select'][] = ['VALUE:', 'firstName'];
$selectParams['select'][] = ['VALUE:', 'lastName'];
}
$selectParams['offset'] = 0;
$selectParams['limit'] = $offset + $maxSize + 1;

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

@@ -3,18 +3,20 @@
{{#if header}}
<div class="row button-container">
<div class="col-sm-4 col-xs-6">
<div class="col-sm-4 col-xs-5">
<div class="btn-group range-switch-group">
<button class="btn btn-text btn-icon" data-action="prev"><span class="fas fa-chevron-left"></span></button>
<button class="btn btn-text btn-icon" data-action="next"><span class="fas fa-chevron-right"></span></button>
</div>
<button class="btn btn-text strong" data-action="today">{{translate 'Today' scope='Calendar'}}</button>
<button class="btn btn-text strong" data-action="today" title="{{todayLabel}}">
<span class="hidden-sm hidden-xs">{{todayLabel}}</span><span class="visible-sm visible-xs">{{todayLabelShort}}</span>
</button>
<button class="btn btn-text{{#unless isCustomView}} hidden{{/unless}} btn-icon" data-action="editCustomView" title="{{translate 'Edit'}}"><span class="fas fa-pencil-alt fa-sm"></span></button>
</div>
<div class="date-title col-sm-4 col-xs-6"><h4><span style="cursor: pointer;" data-action="refresh" title="{{translate 'Refresh'}}"></span></h4></div>
<div class="date-title col-sm-4 col-xs-7"><h4><span style="cursor: pointer;" data-action="refresh" title="{{translate 'Refresh'}}"></span></h4></div>
<div class="col-sm-4 col-xs-12">
<div class="btn-group pull-right mode-buttons">

View File

@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], function (Dep, FullCalendar) {
define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], function (Dep, FullCalendar) {
return Dep.extend({
@@ -56,7 +56,7 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
titleFormat: {
month: 'MMMM YYYY',
week: 'MMMM D, YYYY',
week: 'MMMM YYYY',
day: 'dddd, MMMM D, YYYY'
},
@@ -66,6 +66,8 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
header: this.header,
isCustomViewAvailable: this.isCustomViewAvailable,
isCustomView: this.isCustomView,
todayLabel: this.translate('Today', 'labels', 'Calendar'),
todayLabelShort: this.translate('Today', 'labels', 'Calendar').substr(0, 2),
};
},
@@ -286,7 +288,7 @@ Espo.define('crm:views/calendar/calendar', ['view', 'lib!full-calendar'], functi
var title;
if (viewName == 'week') {
title = $.fullCalendar.formatRange(view.start, view.end, this.titleFormat[viewName], ' - ');
title = $.fullCalendar.formatRange(view.start, view.end, this.titleFormat[viewName], ' ');
} else {
title = view.intervalStart.format(this.titleFormat[viewName]);
}

View File

@@ -0,0 +1,50 @@
/************************************************************************
* 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.
************************************************************************/
define('crm:views/call/fields/date-end', 'views/fields/datetime', function (Dep) {
return Dep.extend({
validateAfter: function () {
var field = this.model.getFieldParam(this.name, 'after');
if (field) {
var value = this.model.get(this.name);
var otherValue = this.model.get(field);
if (value && otherValue) {
if (moment(value).unix() < moment(otherValue).unix()) {
var msg = this.translate('fieldShouldAfter', 'messages').replace('{field}', this.getLabelText())
.replace('{otherField}', this.translate(field, 'fields', this.model.name));
this.showValidationMessage(msg);
return true;
}
}
}
},
});
});

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

@@ -16,7 +16,11 @@
<div class="panel-heading"><h4 class="panel-title"><%= panelLabelString %></h4></div>
<% } %>
<div class="panel-body panel-body-form">
<% _.each(panel.rows, function (row, rowNumber) { %>
<% var rows = panel.rows || [] %>
<% var columns = panel.columns || [] %>
<% _.each(rows, function (row, rowNumber) { %>
<div class="row">
<% var columnCount = row.length; %>
<% _.each(row, function (cell, cellNumber) { %>
@@ -90,6 +94,78 @@
<% }); %>
</div>
<% }); %>
<%
var columnCount = columns.length;
if (columnCount) {
%>
<div class="row">
<%
}
%>
<% _.each(columns, function (column, columnNumber) { %>
<%
var spanClass;
if (!columnCount) return;
if (columnCount === 1 || column.fullWidth) {
spanClass = 'col-sm-12';
} else if (columnCount === 2) {
if (column.span === 2) {
spanClass = 'col-sm-12';
} else {
spanClass = 'col-sm-6';
}
} else if (columnCount === 3) {
if (column.span === 2) {
spanClass = 'col-sm-8';
} else if (column.span === 3) {
spanClass = 'col-sm-12';
} else {
spanClass = 'col-sm-4';
}
} else if (columnCount === 4) {
if (column.span === 2) {
spanClass = 'col-sm-6';
} else if (column.span === 3) {
spanClass = 'col-sm-9';
} else if (column.span === 4) {
spanClass = 'col-sm-12';
} else {
spanClass = 'col-md-3 col-sm-6';
}
} else {
spanClass = 'col-sm-12';
}
%>
<div class="column <%= spanClass %>">
<% _.each(column, function (cell, cellNumber) { %>
<div class="cell form-group<% if (cell.field) { %>{{#if hiddenFields.<%= cell.field %>}} hidden-cell{{/if}}<% } %>" data-name="<%= cell.field %>">
<% if (!cell.noLabel) { %><label class="control-label<% if (cell.field) { %>{{#if hiddenFields.<%= cell.field %>}} hidden{{/if}}<% } %>" data-name="<%= cell.field %>"><span class="label-text"><%
if ('customLabel' in cell) {
print (cell.customLabel);
} else {
print ("{{translate \""+cell.field+"\" scope=\""+model.name+"\" category='fields'}}");
}
%></span></label><% } %>
<div class="field<% if (cell.field) { %>{{#if hiddenFields.<%= cell.field %>}} hidden{{/if}}<% } %>" data-name="<%= cell.field %>"><%
if ('customCode' in cell) {
print (cell.customCode);
} else {
print ("{{{this."+cell.name+"}}}");
}
%></div>
</div>
<% }); %>
</div>
<% }); %>
<%
if (columnCount) {
%>
</div>
<%
}
%>
</div>
</div>
<% }); %>

View File

@@ -1,4 +1,5 @@
<div class="input-group input-group-link-parent">
{{#if foreignScopeList.length}}
<span class="input-group-btn">
<select class="form-control" data-name="{{typeName}}">
{{options foreignScopeList foreignScope category='scopeNames'}}
@@ -9,5 +10,8 @@
<button data-action="selectLink" class="btn btn-default btn-icon" type="button" tabindex="-1" title="{{translate 'Select'}}"><i class="fas fa-angle-up"></i></button>
<button data-action="clearLink" class="btn btn-default btn-icon" type="button" tabindex="-1"><i class="fas fa-times"></i></button>
</span>
{{else}}
{{translate 'None'}}
{{/if}}
</div>
<input type="hidden" data-name="{{idName}}" value="{{idValue}}">

View File

@@ -58,7 +58,15 @@ define('utils', [], function () {
if ($dropdown.length) {
var $dropdownToggle = $dropdown.parent().find('[data-toggle="dropdown"]');
if ($dropdownToggle.length) {
var isDisabled = false;
if ($dropdownToggle.attr('disabled')) {
isDisabled = true;
$dropdownToggle.removeAttr('disabled').removeClass('disabled');
}
$dropdownToggle.dropdown('toggle');
if (isDisabled) {
$dropdownToggle.attr('disabled', 'disabled').addClass('disabled');
}
}
}
}

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 () {
@@ -352,11 +345,10 @@ define('views/detail', 'views/main', function (Dep) {
this.getRouter().dispatch(this.scope, 'create', {
attributes: attributes,
returnUrl: this.getRouter().getCurrentUrl(),
});
this.getRouter().navigate(url, {trigger: false});
}.bind(this));
},
});

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

@@ -25,7 +25,8 @@
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/fields/duration', 'views/fields/enum', function (Dep) {
define('views/fields/duration', 'views/fields/enum', function (Dep) {
return Dep.extend({
@@ -111,7 +112,7 @@ Espo.define('views/fields/duration', 'views/fields/enum', function (Dep) {
stringifyDuration: function (seconds) {
if (!seconds) {
return '';
return '0';
}
var d = seconds;
var days = Math.floor(d / (86400));
@@ -242,7 +243,7 @@ Espo.define('views/fields/duration', 'views/fields/enum', function (Dep) {
updateDuration: function () {
var seconds = this.seconds;
if (seconds <= 0) {
if (seconds < 0) {
if (this.mode == 'edit') {
this.$duration.val('');
} else {

View File

@@ -110,7 +110,7 @@ define('views/fields/enum', ['views/fields/base', 'lib!Selectize'], function (De
}
if (this.params.isSorted && this.translatedOptions) {
this.params.options = Espo.Utils.clone(this.params.options);
this.params.options = Espo.Utils.clone(this.params.options) || [];
this.params.options = this.params.options.sort(function (v1, v2) {
return (this.translatedOptions[v1] || v1).localeCompare(this.translatedOptions[v2] || v2);
}.bind(this));

View File

@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/fields/link-parent', 'views/fields/base', function (Dep) {
define('views/fields/link-parent', 'views/fields/base', function (Dep) {
return Dep.extend({
@@ -65,6 +65,7 @@ Espo.define('views/fields/link-parent', 'views/fields/base', function (Dep) {
if ((this.mode == 'detail' || this.mode == 'list' && this.displayScopeColorInListMode) && this.foreignScope) {
iconHtml = this.getHelper().getScopeColorIconHtml(this.foreignScope);
}
return _.extend({
idName: this.idName,
nameName: this.nameName,
@@ -103,11 +104,6 @@ Espo.define('views/fields/link-parent', 'views/fields/base', function (Dep) {
if (!this.getMetadata().get(['scopes', item, 'disabled'])) return true;
}, this);
if (this.mode == 'edit' && this.foreignScopeList.length == 0) {
throw new Error('Bad parent link defenition. Model list is empty.');
}
this.foreignScope = this.model.get(this.typeName) || this.foreignScopeList[0];
this.listenTo(this.model, 'change:' + this.typeName, function () {

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

@@ -118,6 +118,8 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
convertCurrencyAction: true,
saveAndContinueEditingAction: false,
events: {
'click .button-container .action': function (e) {
Espo.Utils.handleAction(this, e);
@@ -163,6 +165,10 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
$(window).scrollTop(0);
},
actionSaveAndContinueEditing: function () {
this.save(null, true);
},
actionSelfAssign: function () {
var attributes = {
assignedUserId: this.getUser().id,
@@ -348,6 +354,13 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
}, this);
}, this);
}
if (this.saveAndContinueEditingAction) {
this.dropdownEditItemList.push({
name: 'saveAndContinueEditing',
label: 'Save & Continue Editing',
});
}
}
},
@@ -975,6 +988,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 +1007,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 +1028,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));
}
},
@@ -1350,11 +1366,18 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
}
}
for (var i in simplifiedLayout[p].rows) {
var lType = 'rows';
if (simplifiedLayout[p].columns) {
lType = 'columns';
panel.columns = [];
}
for (var i in simplifiedLayout[p][lType]) {
var row = [];
for (var j in simplifiedLayout[p].rows[i]) {
var cellDefs = simplifiedLayout[p].rows[i][j];
for (var j in simplifiedLayout[p][lType][i]) {
var cellDefs = simplifiedLayout[p][lType][i][j];
if (cellDefs == false) {
row.push(false);
@@ -1398,7 +1421,7 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
var fullWidth = cellDefs.fullWidth || false;
if (!fullWidth) {
if (simplifiedLayout[p].rows[i].length == 1) {
if (simplifiedLayout[p][lType][i].length == 1) {
fullWidth = true;
}
}
@@ -1452,7 +1475,7 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
row.push(cell);
}
panel.rows.push(row);
panel[lType].push(row);
}
layout.push(panel);
}

View File

@@ -81,7 +81,18 @@ define('views/record/edit', 'views/record/detail', function (Dep) {
this.populateDefaults();
}
Dep.prototype.setupBeforeFinal.call(this);
}
},
setupActionItems: function () {
Dep.prototype.setupActionItems.call(this);
if (this.saveAndContinueEditingAction) {
this.dropdownItemList.push({
name: 'saveAndContinueEditing',
label: 'Save & Continue Editing',
});
}
},
});
});

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 {
@@ -3167,7 +3174,10 @@ table.table-admin-panel {
margin-top: 6px;
h4 {
margin-top: 0;
font-size: 15px;
}
white-space: nowrap;
overflow: hidden;
}
}

View File

@@ -99,7 +99,7 @@
"modRewriteInstruction": {
"apache": {
"windows": "<br> <pre>1. Encontrar el archivo httpd.conf (generalmente lo encontrará en una carpeta llamada conf, config o o algo similar a esas líneas)<br>\n2. Dentro del archivo httpd.conf descomentamos la línea LoadModule rewrite_module modules/mod_rewrite.so (eliminar el '#' que está al comienzo de la línea)<br>\n3. También encuentre que la línea ClearModuleList no esté comentada después busque y asegurese que la línea AddModule mod_rewrite.c no está comentada tampoco.\n</pre>",
"linux": "<br> <br>1. Habilita \"mod_rewrite\". Para hacerlo, ejecute esos comandos en un Terminal: <pre>{APACHE1}</pre><br> 2. Habilita el soporte de .htaccess. Agregue/edite los ajustes de configuración del Servidor (/etc/apache/apache2.conf, /etc/httpd/conf/httpd.conf): <pre>{APACHE2}</pre>\nLuego ejecuta este comando en un Terminal: <pre>{APACHE3}</pre> <br>3. Intente agregar la ruta RewriteBase, abra un archivo {API_PATH} .htaccess y reemplace la siguiente línea: <pre>{APACHE4}</ pre> a <pre>{APACHE5}</ pre> <br> Para obtener más información, visite la guía <a href=\"https://www.espocrm.com/documentation/administration/apache-server-configuration/\" target=\"_blank\"> configuración del servidor Apache para EspoCRM </a>. <br> <br>"
"linux": "<br> <br>1. Habilita \"mod_rewrite\". Para hacerlo, ejecute esos comandos en un Terminal: <pre>{APACHE1}</pre><br> 2. Habilita el soporte de .htaccess. Agregue/edite los ajustes de configuración del Servidor (/etc/apache/apache2.conf, /etc/httpd/conf/httpd.conf): <pre>{APACHE2}</pre>\nLuego ejecuta este comando en un Terminal: <pre>{APACHE3}</pre> <br>3. Intente agregar la ruta RewriteBase, abra un archivo {API_PATH} .htaccess y reemplace la siguiente línea: <pre>{APACHE4}</pre> a <pre>{APACHE5}</pre> <br> Para obtener más información, visite la guía <a href=\"https://www.espocrm.com/documentation/administration/apache-server-configuration/\" target=\"_blank\"> configuración del servidor Apache para EspoCRM </a>. <br> <br>"
},
"nginx": {
"linux": "<br>\n<pre>\n{NGINX}\n</pre> <br> Para obtener más información, visite la guía <a href=\"https://www.espocrm.com/documentation/administration/nginx-server-configuration/\" target=\"_blank\"> configuración del servidor Nginx para EspoCRM </a>. <br> <br> <br>",

View File

@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "5.7.0",
"version": "5.7.3",
"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

@@ -0,0 +1,93 @@
<?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 tests\integration\Espo\GlobalSearch;
class GlobalSearchTest extends \tests\integration\Core\BaseTestCase
{
public function testSearch1()
{
$app = $this->createApplication();
$em = $app->getContainer()->get('entityManager');
$team = $em->createEntity('Team', [
'name' => 'test',
]);
$contact = $em->createEntity('Contact', [
'lastName' => '1',
'teamsIds' => [$team->id],
]);
$account = $em->createEntity('Account', [
'name' => '1',
'teamsIds' => [$team->id],
]);
$account = $em->createEntity('Account', [
'name' => '2',
'teamsIds' => [$team->id],
]);
$account = $em->createEntity('Account', [
'name' => '1',
]);
$this->createUser([
'userName' => 'tester',
'teamsIds' => [$team->id],
], [
'data' => [
'Account' => [
'create' => 'no',
'read' => 'team',
'edit' => 'no',
'delete' => 'no',
'stream' => 'no',
],
'Contact' => [
'create' => 'no',
'read' => 'team',
'edit' => 'no',
'delete' => 'no',
'stream' => 'no',
],
],
]);
$this->auth('tester');
$app = $this->createApplication(true);
$service = $app->getContainer()->get('serviceFactory')->create('GlobalSearch');
$result = $service->find('1', 0, 10);
$this->assertEquals(2, count($result['list']));
}
}

View File

@@ -27,7 +27,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace tests\integration\Espo\Attachment;
namespace tests\integration\Espo\LeadCapture;
class LeadCaptureTest extends \tests\integration\Core\BaseTestCase
{

View File

@@ -2816,6 +2816,47 @@ class FormulaTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('12', $actual);
}
function testPos()
{
$item = json_decode('
{
"type": "string\\\\pos",
"value": [
{
"type": "value",
"value": "1234"
},
{
"type": "value",
"value": 23
}
]
}
');
$actual = $this->formula->process($item, $this->entity);
$this->assertEquals(1, $actual);
$item = json_decode('
{
"type": "string\\\\pos",
"value": [
{
"type": "value",
"value": "1234"
},
{
"type": "value",
"value": 54
}
]
}
');
$actual = $this->formula->process($item, $this->entity);
$this->assertFalse($actual);
}
function testBundle()
{
$item = json_decode('

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', [