Compare commits

..

67 Commits
5.7.2 ... 5.7.6

Author SHA1 Message Date
Yuri Kuznetsov
cd45f07e54 fix account description 2019-10-24 14:34:46 +03:00
Yuri Kuznetsov
a35f9625af fix email reply id 2019-10-24 13:33:07 +03:00
Yuri Kuznetsov
f5245ef3eb email import message id fix 2019-10-24 13:06:11 +03:00
yuri
4ad9ef770b pdf: not latin filename support 2019-10-23 11:05:26 +03:00
yuri
acbc74d858 fix entity plural label 2019-10-22 15:42:41 +03:00
yuri
8da3350523 base plus emails link 2019-10-22 15:40:29 +03:00
yuri
496879dd79 pdf link support 2019-10-22 15:30:49 +03:00
yuri
84cda80fd8 forbid base entity type name 2019-10-22 13:13:13 +03:00
yuri
50a81b1247 date picker disable keyboard navigation 2019-10-22 12:56:57 +03:00
yuri
f0f225f349 propagate remove event for stored main view 2019-10-22 12:25:40 +03:00
yuri
d49fff1289 cleanup 2019-10-22 11:44:26 +03:00
yuri
abaa1b302b email filters from to 2019-10-22 11:38:44 +03:00
yuri
0eaa7825ea email filters layout change 2019-10-22 10:40:34 +03:00
yuri
1028b7c5b3 v 2019-10-22 10:32:36 +03:00
yuri
f319e5219d Merge branch 'hotfix/5.7.6' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.6 2019-10-22 10:05:32 +03:00
Taras Machyshyn
0daae5ced5 Integration tests changes 2019-10-21 16:38:46 +03:00
Taras Machyshyn
abee63d269 FieldManager fix 2019-10-21 16:38:15 +03:00
yuri
be33d90986 all of filter 2019-10-21 15:44:41 +03:00
yuri
3a7cac824d formula comments 2019-10-21 13:44:16 +03:00
yuri
7d616b075f fix formula sumRelated 2019-10-21 12:22:05 +03:00
yuri
b718765138 system requirements tpl fix 2019-10-21 11:46:27 +03:00
yuri
3104be7c99 link multiple default 2019-10-21 10:46:29 +03:00
yuri
a8d182b4cd Merge branch 'hotfix/5.7.5' into stable 2019-10-18 16:14:48 +03:00
yuri
3bfc99c88b entity array clone 2019-10-18 14:46:18 +03:00
yuri
ac3300a5cf fix password labels 2019-10-18 12:58:49 +03:00
yuri
636587a22c Merge branch 'hotfix/5.7.5' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.5 2019-10-18 12:48:48 +03:00
Taras Machyshyn
4ed2c71dfd Integration tests fix 2019-10-18 12:48:30 +03:00
yuri
723229d8e6 Merge branch 'hotfix/5.7.5' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.7.5 2019-10-18 12:12:31 +03:00
yuri
080ab488fa fix json object save 2019-10-18 11:11:55 +03:00
Taras Machyshyn
fd32696c5a Integration tests optimization 2019-10-18 10:40:28 +03:00
Taras Machyshyn
9a56858f12 Label changes 2019-10-18 10:40:20 +03:00
Taras Machyshyn
5923757810 Database indexes changes 2019-10-18 10:40:07 +03:00
yuri
62948a7740 v 2019-10-17 17:21:22 +03:00
yuri
821d57ce00 fix stream 2019-10-17 17:20:47 +03:00
yuri
efd0505ead fix 2019-10-17 16:59:37 +03:00
yuri
91c55965a7 fix 2019-10-17 16:43:16 +03:00
yuri
974305e152 v 2019-10-17 16:42:57 +03:00
yuri
016abaf7b6 fix 2019-10-17 16:16:14 +03:00
yuri
f3ec50dcbd fix 2019-10-17 16:10:39 +03:00
yuri
baeee7caf5 timestamp diff orm functions 2019-10-17 16:07:14 +03:00
yuri
56280224f9 access control disabled 2019-10-17 12:14:47 +03:00
yuri
4b3e341606 fix 2019-10-17 11:38:06 +03:00
yuri
58fad48d5e languageAclDisabled param 2019-10-17 11:03:28 +03:00
yuri
841a73b886 email address lookup fix 2019-10-17 10:17:03 +03:00
yuri
f74ae46ec9 orm additionalSelect 2019-10-16 16:32:00 +03:00
yuri
542d78425c fix email address lookup 2019-10-16 15:39:23 +03:00
yuri
7b241b90a0 fix email search 2019-10-16 11:27:12 +03:00
yuri
de77b1b1d0 v 2019-10-16 10:34:13 +03:00
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
82 changed files with 1710 additions and 325 deletions

View File

@@ -75,7 +75,7 @@ class Email extends \Espo\Core\Controllers\Record
throw new NotFound();
}
if (!$this->getUser()->isAdmin()) {
if ($emailAccount->get('assigniedUserId') !== $this->getUser()->id) {
if ($emailAccount->get('assignedUserId') !== $this->getUser()->id) {
throw new Forbidden();
}
}

View File

@@ -113,11 +113,31 @@ class SumRelatedType extends \Espo\Core\Formula\Functions\Base
$foreignSelectManager->addJoin($foreignLink, $selectParams);
}
$selectParams['groupBy'] = [$foreignLink . '.id'];
if (!empty($selectParams['distinct'])) {
$sqSelectParams = $selectParams;
$selectParams['whereClause'][] = [
$foreignLink . '.id' => $entity->id
];
$sqSelectParams['whereClause'][] = [
$foreignLink . '.id' => $entity->id
];
$sqSelectParams['select'] = ['id'];
unset($sqSelectParams['distinct']);
unset($sqSelectParams['orderBy']);
unset($sqSelectParams['order']);
$selectParams['whereClause'][] = [
'id=s' => [
'entityType' => $foreignEntityType,
'selectParams' => $sqSelectParams,
]
];
} else {
$selectParams['whereClause'][] = [
$foreignLink . '.id' => $entity->id
];
}
$selectParams['groupBy'] = [$foreignLink . '.id'];
$entityManager->getRepository($foreignEntityType)->handleSelectParams($selectParams);

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

@@ -115,10 +115,12 @@ class Parser
$braceCounter = 0;
for ($i = 0; $i < strlen($string); $i++) {
$isStringStart = false;
if ($string[$i] === "'" && ($i === 0 || $string[$i - 1] !== "\\")) {
if (!$isString) {
$isString = true;
$isSingleQuote = true;
$isStringStart = true;
} else {
if ($isSingleQuote) {
$isString = false;
@@ -127,6 +129,7 @@ class Parser
} else if ($string[$i] === "\"" && ($i === 0 || $string[$i - 1] !== "\\")) {
if (!$isString) {
$isString = true;
$isStringStart = true;
$isSingleQuote = false;
} else {
if (!$isSingleQuote) {
@@ -137,6 +140,8 @@ class Parser
if ($isString) {
if ($string[$i] === '(' || $string[$i] === ')') {
$modifiedString[$i] = '_';
} else if (!$isStringStart) {
$modifiedString[$i] = ' ';
}
} else {
if ($string[$i] === '(') {
@@ -176,6 +181,16 @@ class Parser
$this->processStrings($expression, $modifiedExpression, $splitterIndexList, true);
$this->stripComments($expression, $modifiedExpression);
foreach ($splitterIndexList as $i => $index) {
if ($expression[$index] !== ';') {
unset($splitterIndexList[$i]);
}
}
$splitterIndexList = array_values($splitterIndexList);
$expressionOutOfBraceList = [];
for ($i = 0; $i < strlen($modifiedExpression); $i++) {
@@ -402,6 +417,43 @@ class Parser
}
}
protected function stripComments(&$expression, &$modifiedExpression)
{
$commentIndexStart = null;
for ($i = 0; $i < strlen($modifiedExpression); $i++) {
if (is_null($commentIndexStart)) {
if ($modifiedExpression[$i] === '/' && $i < strlen($modifiedExpression) - 1 && $modifiedExpression[$i + 1] === '/') {
$commentIndexStart = $i;
}
} else {
if ($modifiedExpression[$i] === "\n" || $i === strlen($modifiedExpression) - 1) {
for ($j = $commentIndexStart; $j <= $i; $j++) {
$modifiedExpression[$j] = ' ';
$expression[$j] = ' ';
}
$commentIndexStart = null;
}
}
}
for ($i = 0; $i < strlen($modifiedExpression) - 1; $i++) {
if (is_null($commentIndexStart)) {
if ($modifiedExpression[$i] === '/' && $modifiedExpression[$i + 1] === '*') {
$commentIndexStart = $i;
}
} else {
if ($modifiedExpression[$i] === '*' && $modifiedExpression[$i + 1] === '/') {
for ($j = $commentIndexStart; $j <= $i + 1; $j++) {
$modifiedExpression[$j] = ' ';
$expression[$j] = ' ';
}
$commentIndexStart = null;
}
}
}
}
protected function parseArgumentListFromFunctionContent($functionContent)
{
$functionContent = trim($functionContent);

View File

@@ -35,26 +35,33 @@ use Espo\Core\Exceptions\Error;
use Espo\Core\Utils\File\Manager as FileManager;
use Espo\Core\Utils\DateTime;
use Espo\Core\Utils\NumberUtil;
use Espo\Core\Utils\Config;
use Espo\Core\Utils\Language;
use Espo\Core\Utils\Metadata;
use Espo\ORM\EntityManager;
require('vendor/zordius/lightncandy/src/lightncandy.php');
class Htmlizer
{
protected $fileManager;
protected $dateTime;
protected $config;
protected $acl;
protected $entityManager;
protected $metadata;
protected $language;
public function __construct(FileManager $fileManager, DateTime $dateTime, NumberUtil $number, $acl = null, $entityManager = null, $metadata = null, $language = null)
public function __construct(
FileManager $fileManager,
DateTime $dateTime,
NumberUtil $number,
$acl = null,
?EntityManager $entityManager = null,
?Metadata $metadata = null,
?Language $language = null,
?Config $config = null
)
{
$this->fileManager = $fileManager;
$this->dateTime = $dateTime;
@@ -63,6 +70,7 @@ class Htmlizer
$this->entityManager = $entityManager;
$this->metadata = $metadata;
$this->language = $language;
$this->config = $config;
}
protected function getAcl()
@@ -92,7 +100,7 @@ class Htmlizer
return $value;
}
protected function getDataFromEntity(Entity $entity, $skipLinks = false, $level = 0)
protected function getDataFromEntity(Entity $entity, $skipLinks = false, $level = 0, ?string $template = null)
{
$data = $entity->toArray();
@@ -118,15 +126,32 @@ class Htmlizer
if (!$skipLinks && $level === 0) {
foreach ($relationList as $relation) {
if (!$entity->hasLinkMultipleField($relation)) continue;
$collection = null;
$collection = $entity->getLinkMultipleCollection($relation);
$data[$relation] = $collection;
if ($entity->hasLinkMultipleField($relation)) {
$toLoad = true;
$collection = $entity->getLinkCollection($relation);
} else {
if (
$template && $entity->getRelationType($relation, ['hasMany', 'manyMany', 'hasChildren']) &&
mb_stripos($template, '{{#each '.$relation.'}}') !== false
) {
$limit = 100;
if ($this->config) {
$limit = $this->config->get('htmlizerLinkLimit') ?? $limit;
}
$collection = $entity->getLinkCollection($relation, ['limit' => $limit]);
}
}
if ($collection) {
$data[$relation] = $collection;
}
}
}
foreach ($data as $key => $value) {
if ($value instanceof \Espo\ORM\EntityCollection) {
if ($value instanceof \Espo\ORM\ICollection) {
$skipAttributeList[] = $key;
$collection = $value;
$list = [];
@@ -280,7 +305,7 @@ class Htmlizer
}
}
return;
}
},
],
'hbhelpers' => [
'ifEqual' => function () {
@@ -300,7 +325,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']() : '';
}
},
]
]);
@@ -319,7 +356,7 @@ class Htmlizer
$this->fileManager->removeFile($fileName);
}
$data = $this->getDataFromEntity($entity, $skipLinks);
$data = $this->getDataFromEntity($entity, $skipLinks, 0, $template);
if (!array_key_exists('today', $data)) {
$data['today'] = $this->dateTime->getTodayString();

View File

@@ -212,14 +212,18 @@ class Importer
if ($parser->checkMessageAttribute($message, 'in-Reply-To') && $parser->getMessageAttribute($message, 'in-Reply-To')) {
$arr = explode(' ', $parser->getMessageAttribute($message, 'in-Reply-To'));
$inReplyTo = $arr[0];
$replied = $this->getEntityManager()->getRepository('Email')->where(array(
'messageId' => $inReplyTo
))->findOne();
if ($replied) {
$email->set('repliedId', $replied->id);
$repliedTeamIdList = $replied->getLinkMultipleIdList('teams');
foreach ($repliedTeamIdList as $repliedTeamId) {
$email->addLinkMultipleId('teams', $repliedTeamId);
if ($inReplyTo) {
if ($inReplyTo[0] !== '<') $inReplyTo = '<' . $inReplyTo . '>';
$replied = $this->getEntityManager()->getRepository('Email')->where(array(
'messageId' => $inReplyTo
))->findOne();
if ($replied) {
$email->set('repliedId', $replied->id);
$repliedTeamIdList = $replied->getLinkMultipleIdList('teams');
foreach ($repliedTeamIdList as $repliedTeamId) {
$email->addLinkMultipleId('teams', $repliedTeamId);
}
}
}
}

View File

@@ -91,7 +91,13 @@ class MailMimeParser
public function getMessageMessageId($message)
{
return $this->getMessageAttribute($message, 'Message-ID');
$messageId = $this->getMessageAttribute($message, 'Message-ID');
if ($messageId && strlen($messageId) && $messageId[0] !== '<') {
$messageId = '<' . $messageId . '>';
}
return $messageId;
}
public function getAddressNameMap($message)

View File

@@ -74,22 +74,33 @@ class Entity extends \Espo\ORM\Entity
}
}
public function getLinkMultipleCollection($field)
public function getLinkCollection(string $link, ?array $selectParams = null)
{
if (!$this->hasLinkMultipleField($field)) return;
if (!$selectParams) $selectParams = [];
$defs = $this->getRelationSelectParams($field);
$relSelectParams = $this->getRelationSelectParams($link);
$columnAttribute = $field . 'Columns';
$selectParams = array_merge($selectParams, $relSelectParams);
$selectParams['returnSthCollection'] = true;
$columnAttribute = $link . 'Columns';
if ($this->hasAttribute($columnAttribute) && $this->getAttributeParam($columnAttribute, 'columns')) {
$defs['additionalColumns'] = $this->getAttributeParam($columnAttribute, 'columns');
$selectParams['additionalColumns'] = $this->getAttributeParam($columnAttribute, 'columns');
}
$collection = $this->get($field, $defs);
$collection = $this->get($link, $selectParams);
return $collection;
}
public function getLinkMultipleCollection(string $link, ?array $selectParams = null)
{
if (!$this->hasLinkMultipleField($link)) return;
return $this->getLinkCollection($link, $selectParams);
}
protected function getRelationSelectParams($link)
{
$field = $link;

View File

@@ -240,4 +240,33 @@ class Tcpdf extends \TCPDF
$this->_out($out);
}
public function Output($name = 'doc.pdf', $dest = 'I')
{
if ($dest === 'I' && !$this->sign && php_sapi_name() != 'cli') {
if ($this->state < 3) {
$this->Close();
}
$name = preg_replace('/[\s]+/', '_', $name);
$name = \Espo\Core\Utils\Util::sanitizeFileName($name);
if (ob_get_contents()) {
$this->Error('Some data has already been output, can\'t send PDF file');
}
header('Content-Type: application/pdf');
if (headers_sent()) {
$this->Error('Some data has already been output to browser, can\'t send PDF file');
}
header('Cache-Control: private, must-revalidate, post-check=0, pre-check=0, max-age=1');
header('Pragma: public');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
header('Content-Disposition: inline; filename="'.$name.'"');
TCPDF_STATIC::sendOutputData($this->getBuffer(), $this->bufferlen);
return '';
}
return parent::Output($name, $dest);
}
}

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
@@ -1728,6 +1732,7 @@ class Base
case 'arrayNoneOf':
case 'arrayIsEmpty':
case 'arrayIsNotEmpty':
case 'arrayAllOf':
if (!$result) break;
$arrayValueAlias = 'arrayFilter' . strval(rand(10000, 99999));
@@ -1759,6 +1764,8 @@ class Base
$arrayValueAlias . '.attribute' => $arrayAttribute
]], $result);
$part[$arrayValueAlias . '.value'] = $value;
$this->setDistinct(true, $result);
} else if ($type === 'arrayNoneOf') {
if (is_null($value) || !$value && !is_array($value)) break;
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
@@ -1768,6 +1775,8 @@ class Base
$arrayValueAlias . '.value=' => $value
]], $result);
$part[$arrayValueAlias . '.id'] = null;
$this->setDistinct(true, $result);
} else if ($type === 'arrayIsEmpty') {
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
$arrayValueAlias . '.entityId:' => $idPart,
@@ -1775,6 +1784,8 @@ class Base
$arrayValueAlias . '.attribute' => $arrayAttribute
]], $result);
$part[$arrayValueAlias . '.id'] = null;
$this->setDistinct(true, $result);
} else if ($type === 'arrayIsNotEmpty') {
$this->addLeftJoin(['ArrayValue', $arrayValueAlias, [
$arrayValueAlias . '.entityId:' => $idPart,
@@ -1782,9 +1793,31 @@ class Base
$arrayValueAlias . '.attribute' => $arrayAttribute
]], $result);
$part[$arrayValueAlias . '.id!='] = null;
}
$this->setDistinct(true, $result);
$this->setDistinct(true, $result);
} else if ($type === 'arrayAllOf') {
if (is_null($value) || !$value && !is_array($value)) break;
if (!is_array($value)) {
$value = [$value];
}
foreach ($value as $arrayValue) {
$part[] = [
$idPart .'=s' => [
'entityType' => 'ArrayValue',
'selectParams' => [
'select' => ['entityId'],
'whereClause' => [
'value' => $arrayValue,
'attribute' => $arrayAttribute,
'entityType' => $arrayEntityType,
],
],
]
];
}
}
}
}
@@ -2215,6 +2248,9 @@ class Base
$result['order'] = null;
}
$result['additionalSelect'] = $result['additionalSelect'] ?? [];
$result['additionalSelect'][] = $fullTextSearchData['where'];
$result['hasFullTextSearch'] = true;
}

View File

@@ -72,6 +72,12 @@
"entity": "Task",
"foreign": "parent",
"layoutRelationshipsDisabled": true
},
"emails": {
"type": "hasChildren",
"entity": "Email",
"foreign": "parent",
"layoutRelationshipsDisabled": true
}
},
"collection": {

View File

@@ -171,7 +171,7 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
if (!$this->systemRebuild()) {
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild. Please see the log for more detail.');
}
}

View File

@@ -76,7 +76,7 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
if (!$this->systemRebuild()) {
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild.');
$this->throwErrorAndRemovePackage('Error occurred while EspoCRM rebuild. Please see the log for more detail.');
}
}

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

@@ -30,6 +30,9 @@
namespace Espo\Core\Utils\Database\DBAL\Schema;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Schema\ColumnDiff;
class Comparator extends \Doctrine\DBAL\Schema\Comparator
{
@@ -136,4 +139,130 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
return $changedProperties;
}
public function diffTable(Table $table1, Table $table2)
{
$changes = 0;
$tableDifferences = new TableDiff($table1->getName());
$tableDifferences->fromTable = $table1;
$table1Columns = $table1->getColumns();
$table2Columns = $table2->getColumns();
/* See if all the fields in table 1 exist in table 2 */
foreach ( $table2Columns as $columnName => $column ) {
if ( !$table1->hasColumn($columnName) ) {
$tableDifferences->addedColumns[$columnName] = $column;
$changes++;
}
}
/* See if there are any removed fields in table 2 */
foreach ( $table1Columns as $columnName => $column ) {
if ( !$table2->hasColumn($columnName) ) {
$tableDifferences->removedColumns[$columnName] = $column;
$changes++;
}
}
foreach ( $table1Columns as $columnName => $column ) {
if ( $table2->hasColumn($columnName) ) {
$changedProperties = $this->diffColumn( $column, $table2->getColumn($columnName) );
if (count($changedProperties) ) {
$columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties);
$columnDiff->fromColumn = $column;
$tableDifferences->changedColumns[$column->getName()] = $columnDiff;
$changes++;
}
}
}
$this->detectColumnRenamings($tableDifferences);
$table1Indexes = $table1->getIndexes();
$table2Indexes = $table2->getIndexes();
foreach ($table2Indexes as $index2Name => $index2Definition) {
foreach ($table1Indexes as $index1Name => $index1Definition) {
if ($this->diffIndex($index1Definition, $index2Definition) === false) {
unset($table1Indexes[$index1Name]);
unset($table2Indexes[$index2Name]);
} else {
if ($index1Name == $index2Name) {
/*espo*/ if (isset($table2Indexes[$index2Name])) { /*espo*/
$tableDifferences->changedIndexes[$index2Name] = $table2Indexes[$index2Name];
unset($table1Indexes[$index1Name]);
unset($table2Indexes[$index2Name]);
$changes++;
/*espo*/ } /*espo*/
}
}
}
}
foreach ($table1Indexes as $index1Name => $index1Definition) {
$tableDifferences->removedIndexes[$index1Name] = $index1Definition;
$changes++;
}
foreach ($table2Indexes as $index2Name => $index2Definition) {
$tableDifferences->addedIndexes[$index2Name] = $index2Definition;
$changes++;
}
$fromFkeys = $table1->getForeignKeys();
$toFkeys = $table2->getForeignKeys();
foreach ($fromFkeys as $key1 => $constraint1) {
foreach ($toFkeys as $key2 => $constraint2) {
if($this->diffForeignKey($constraint1, $constraint2) === false) {
unset($fromFkeys[$key1]);
unset($toFkeys[$key2]);
} else {
if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) {
$tableDifferences->changedForeignKeys[] = $constraint2;
$changes++;
unset($fromFkeys[$key1]);
unset($toFkeys[$key2]);
}
}
}
}
foreach ($fromFkeys as $constraint1) {
$tableDifferences->removedForeignKeys[] = $constraint1;
$changes++;
}
foreach ($toFkeys as $constraint2) {
$tableDifferences->addedForeignKeys[] = $constraint2;
$changes++;
}
return $changes ? $tableDifferences : false;
}
private function detectColumnRenamings(TableDiff $tableDifferences)
{
$renameCandidates = array();
foreach ($tableDifferences->addedColumns as $addedColumnName => $addedColumn) {
foreach ($tableDifferences->removedColumns as $removedColumn) {
if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) {
$renameCandidates[$addedColumn->getName()][] = array($removedColumn, $addedColumn, $addedColumnName);
}
}
}
foreach ($renameCandidates as $candidateColumns) {
if (count($candidateColumns) == 1) {
list($removedColumn, $addedColumn) = $candidateColumns[0];
$removedColumnName = strtolower($removedColumn->getName());
$addedColumnName = strtolower($addedColumn->getName());
if ( ! isset($tableDifferences->renamedColumns[$removedColumnName])) {
$tableDifferences->renamedColumns[$removedColumnName] = $addedColumn;
unset($tableDifferences->addedColumns[$addedColumnName]);
unset($tableDifferences->removedColumns[$removedColumnName]);
}
}
}
}
}

View File

@@ -332,19 +332,6 @@ class Converter
$table->setPrimaryKey(array("id"));
//add unique indexes
if (!empty($relationParams['conditions'])) {
foreach ($relationParams['conditions'] as $fieldName => $fieldParams) {
$uniqueIndex[] = Util::toUnderScore($fieldName);
}
}
if (!empty($uniqueIndex)) {
$uniqueIndexName = implode('_', $uniqueIndex);
$table->addUniqueIndex($uniqueIndex, SchemaUtils::generateIndexName($uniqueIndexName, 'unique'));
}
//END: add unique indexes
//add defined indexes
if (!empty($relationParams['indexes'])) {
$normalizedIndexes = SchemaUtils::getIndexList([
@@ -358,6 +345,19 @@ class Converter
}
//END: add defined indexes
//add unique indexes
if (!empty($relationParams['conditions'])) {
foreach ($relationParams['conditions'] as $fieldName => $fieldParams) {
$uniqueIndex[] = Util::toUnderScore($fieldName);
}
}
if (!empty($uniqueIndex)) {
$uniqueIndexName = implode('_', $uniqueIndex);
$table->addUniqueIndex($uniqueIndex, SchemaUtils::generateIndexName($uniqueIndexName, 'unique'));
}
//END: add unique indexes
return $table;
}

View File

@@ -54,7 +54,18 @@ class EntityManager
private $linkForbiddenNameList = ['posts', 'stream', 'subscription', 'followers', 'action', 'null', 'false', 'true'];
private $forbiddenEntityTypeNameList = ['Common', 'PortalUser', 'ApiUser', 'Timeline', 'About', 'Admin', 'Null', 'False', 'True'];
private $forbiddenEntityTypeNameList = [
'Common',
'PortalUser',
'ApiUser',
'Timeline',
'About',
'Admin',
'Null',
'False',
'True',
'Base',
];
public function __construct(Metadata $metadata, Language $language, File\Manager $fileManager, Config $config, Container $container = null)
{

View File

@@ -427,7 +427,11 @@ class FieldManager
protected function getFieldDefs($scope, $name, $default = null)
{
return $this->getMetadata()->get('entityDefs'.'.'.$scope.'.fields.'.$name, $default);
$defs = $this->getMetadata()->getObjects(['entityDefs', $scope, 'fields', $name], $default);
if (is_object($defs)) {
return get_object_vars($defs);
}
return $defs;
}
protected function getCustomFieldDefs($scope, $name, $default = null)
@@ -532,7 +536,7 @@ class FieldManager
}
$actualCustomFieldDefs = $this->getCustomFieldDefs($scope, $name, []);
$actualFieldDefs = $this->getFieldDefs($scope, $name, []);
$actualFieldDefs = $this->getFieldDefs($scope, $name, (object) []);
$permittedParamList = array_keys($params);
$filteredFieldDefs = !empty($actualCustomFieldDefs) ? $actualCustomFieldDefs : [];

View File

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

View File

@@ -163,6 +163,13 @@ abstract class Base
'IN',
'NOT_IN',
'BINARY',
'UNIX_TIMESTAMP',
'TIMESTAMPDIFF_DAY',
'TIMESTAMPDIFF_MONTH',
'TIMESTAMPDIFF_YEAR',
'TIMESTAMPDIFF_WEEK',
'TIMESTAMPDIFF_HOUR',
'TIMESTAMPDIFF_MINUTE',
];
protected $multipleArgumentsFunctionList = [
@@ -188,6 +195,12 @@ abstract class Base
'MUL',
'DIV',
'MOD',
'TIMESTAMPDIFF_DAY',
'TIMESTAMPDIFF_MONTH',
'TIMESTAMPDIFF_YEAR',
'TIMESTAMPDIFF_WEEK',
'TIMESTAMPDIFF_HOUR',
'TIMESTAMPDIFF_MINUTE',
];
protected $comparisonFunctionList = [
@@ -553,6 +566,18 @@ abstract class Base
break;
case 'NOT':
return 'NOT ' . $part;
case 'TIMESTAMPDIFF_YEAR':
return 'TIMESTAMPDIFF(YEAR, ' . implode(', ', $argumentPartList) . ')';
case 'TIMESTAMPDIFF_MONTH':
return 'TIMESTAMPDIFF(MONTH, ' . implode(', ', $argumentPartList) . ')';
case 'TIMESTAMPDIFF_WEEK':
return 'TIMESTAMPDIFF(WEEK, ' . implode(', ', $argumentPartList) . ')';
case 'TIMESTAMPDIFF_DAY':
return 'TIMESTAMPDIFF(DAY, ' . implode(', ', $argumentPartList) . ')';
case 'TIMESTAMPDIFF_HOUR':
return 'TIMESTAMPDIFF(HOUR, ' . implode(', ', $argumentPartList) . ')';
case 'TIMESTAMPDIFF_MINUTE':
return 'TIMESTAMPDIFF(MINUTE, ' . implode(', ', $argumentPartList) . ')';
}
if ($distinct) {
@@ -929,15 +954,22 @@ abstract class Base
$attributeList = array_keys($entity->fields);
} else {
$attributeList = $itemList;
foreach ($attributeList as $i => $attribute) {
if (is_string($attribute)) {
if (strpos($attribute, ':')) {
$attributeList[$i] = [
$attribute,
$attribute
];
continue;
}
}
if ($params && isset($params['additionalSelect'])) {
foreach ($params['additionalSelect'] as $item) {
$attributeList[] = $item;
}
}
foreach ($attributeList as $i => $attribute) {
if (is_string($attribute)) {
if (strpos($attribute, ':')) {
$attributeList[$i] = [
$attribute,
$attribute
];
continue;
}
}
}

View File

@@ -448,6 +448,15 @@ abstract class Entity implements IEntity
public function setFetched($name, $value)
{
if ($value) {
$type = $this->getAttributeType($name);
if ($type === self::JSON_OBJECT) {
$value = self::cloneObject($value);
} else if ($type === self::JSON_ARRAY) {
$value = self::cloneArray($value);
}
}
$this->fetchedValuesContainer[$name] = $value;
}
@@ -478,12 +487,16 @@ abstract class Entity implements IEntity
public function updateFetchedValues()
{
$this->fetchedValuesContainer = $this->valuesContainer;
foreach ($this->fetchedValuesContainer as $attribute => $value) {
$this->setFetched($attribute, $value);
}
}
public function setAsFetched()
{
$this->isFetched = true;
$this->fetchedValuesContainer = $this->valuesContainer;
$this->updateFetchedValues();
}
public function setAsNotFetched()
@@ -520,4 +533,45 @@ abstract class Entity implements IEntity
{
return $this->entityManager;
}
protected function cloneArray($value)
{
if (is_array($value)) {
$copy = [];
foreach ($value as $v) {
if (is_object($v)) {
$v = clone $v;
}
$copy[] = $v;
}
return $copy;
}
return $value;
}
protected function cloneObject($value)
{
if (is_array($value)) {
$copy = [];
foreach ($value as $v) {
$copy[] = self::cloneObject($v);
}
return $copy;
}
if (is_object($value)) {
$copy = (object) [];
foreach (get_object_vars($value) as $k => $v) {
$key = $k;
if (!is_string($key)) {
$key = strval($key);
}
$copy->$key = self::cloneObject($v);
}
return $copy;
}
return $value;
}
}

View File

@@ -29,7 +29,7 @@
namespace Espo\ORM;
class EntityCollection implements \Iterator, \Countable, \ArrayAccess, \SeekableIterator
class EntityCollection implements \Iterator, \Countable, \ArrayAccess, \SeekableIterator, ICollection
{
private $entityFactory = null;

View File

@@ -0,0 +1,36 @@
<?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\ORM;
interface ICollection
{
}

View File

@@ -29,7 +29,7 @@
namespace Espo\ORM;
class SthCollection implements \IteratorAggregate
class SthCollection implements \IteratorAggregate, ICollection
{
protected $entityManager = null;

View File

@@ -608,6 +608,7 @@
"isNot": "Is Not",
"isNotOneOf": "None Of",
"anyOf": "Any Of",
"allOf": "All Of",
"noneOf": "None Of"
},
"varcharSearchRanges": {

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

@@ -2,8 +2,9 @@
"account",
"dateSent",
"emailAddress",
"from",
"to",
"isNotRead",
"isImportant",
"isNotReplied",
"status",
"parent",

View File

@@ -44,7 +44,7 @@
"url": "views/fields/foreign-url",
"date": "views/fields/foreign-date",
"datetime": "views/fields/foreign-datetime",
"text": "views/fields/views/fields/foreign-text",
"text": "views/fields/foreign-text",
"number": "views/fields/foreign-varchar",
"bool": "views/fields/foreign-bool",
"email": "views/fields/foreign-email",

View File

@@ -14,6 +14,11 @@
{
"name":"readOnly",
"type":"bool"
},
{
"name": "default",
"type": "linkMultiple",
"view": "views/admin/field-manager/fields/link-multiple/default"
}
],
"actualFields":[

View File

@@ -50,11 +50,15 @@ class Email extends \Espo\Core\SelectManagers\Base
$this->applyFolder($folderId, $result);
}
if (empty($params['textFilter']) && !empty($result['orderBy']) && $result['orderBy'] === 'dateSent') {
$textFilter = $params['textFilter'] ?? null;
if (!$textFilter && !empty($result['orderBy']) && $result['orderBy'] === 'dateSent') {
$skipIndex = false;
if (isset($params['where'])) {
foreach ($params['where'] as $item) {
if ($item['type'] === 'textFilter') {
$type = $item['type'] ?? null;
$value = $item['value'] ?? null;
if ($type === 'textFilter') {
$skipIndex = true;
break;
} else {
@@ -330,12 +334,21 @@ class Email extends \Espo\Core\SelectManagers\Base
protected function applyAdditionalToTextFilterGroup(string $textFilter, array &$group, array &$result)
{
if (strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH) {
if (
strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH
&&
strpos($textFilter, '@') !== false
&&
empty($result['hasFullTextSearch'])
) {
$emailAddressId = $this->getEmailAddressIdByValue($textFilter);
if ($emailAddressId) {
$this->leftJoinEmailAddress($result);
$group = [];
$group['fromEmailAddressId'] = $emailAddressId;
$group['emailEmailAddress.emailAddressId'] = $emailAddressId;
} else {
$group = [];
}
}
}
@@ -358,103 +371,159 @@ class Email extends \Espo\Core\SelectManagers\Base
protected function leftJoinEmailAddress(&$result)
{
if (empty($result['customJoin'])) {
$result['customJoin'] = '';
}
if (stripos($result['customJoin'], 'emailEmailAddress') === false) {
$result['customJoin'] .= "
LEFT JOIN email_email_address AS `emailEmailAddress`
ON
emailEmailAddress.email_id = email.id AND
emailEmailAddress.deleted = 0
";
}
if ($this->hasLeftJoin('emailEmailAddress', $result)) return;
$this->setDistinct(true, $result);
$this->addLeftJoin([
'EmailEmailAddress',
'emailEmailAddress',
[
'emailId:' => 'id',
'deleted' => false,
]
], $result);
}
public function whereEmailAddress(string $value, array &$result)
protected function getWherePartEmailAddressEquals($value, array &$result)
{
$orItem = [];
if (!$value) {
return ['id' => null];
}
$emailAddressId = $this->getEmailAddressIdByValue($value);
if ($emailAddressId) {
$this->leftJoinEmailAddress($result);
$orItem['fromEmailAddressId'] = $emailAddressId;
$orItem['emailEmailAddress.emailAddressId'] = $emailAddressId;
$result['whereClause'][] = [
'OR' => $orItem
];
} else {
if (empty($result['customWhere'])) {
$result['customWhere'] = '';
}
$result['customWhere'] .= ' AND 0';
if (!$emailAddressId) {
return ['id' => null];
}
$this->setDistinct(true, $result);
$alias = 'emailEmailAddress' . strval(rand(10000, 99999));
$this->addLeftJoin([
'EmailEmailAddress',
$alias,
[
'emailId:' => 'id',
'deleted' => false,
]
], $result);
return [
'OR' => [
'fromEmailAddressId' => $emailAddressId,
$alias . '.emailAddressId' => $emailAddressId,
],
];
}
protected function getWherePartFromEquals($value, array &$result)
{
if (!$value) {
return ['id' => null];
}
$emailAddressId = $this->getEmailAddressIdByValue($value);
if (!$emailAddressId) {
return ['id' => null];
}
return [
'fromEmailAddressId' => $emailAddressId,
];
}
protected function getWherePartToEquals($value, array &$result)
{
if (!$value) {
return ['id' => null];
}
$emailAddressId = $this->getEmailAddressIdByValue($value);
if (!$emailAddressId) {
return ['id' => null];
}
$alias = 'emailEmailAddress' . strval(rand(10000, 99999));
$this->addLeftJoin([
'EmailEmailAddress',
$alias,
[
'emailId:' => 'id',
'deleted' => false,
]
], $result);
return [
$alias . '.emailAddressId' => $emailAddressId,
$alias . '.addressType' => 'to',
];
}
protected function getWherePartIsNotRepliedIsTrue()
{
return array(
return [
'isReplied' => false
);
];
}
protected function getWherePartIsNotRepliedIsFalse()
{
return array(
return [
'isReplied' => true
);
];
}
public function getWherePartIsNotReadIsTrue()
{
return array(
return [
'usersMiddle.isRead' => false,
'OR' => array(
'OR' => [
'sentById' => null,
'sentById!=' => $this->getUser()->id
)
);
]
];
}
protected function getWherePartIsNotReadIsFalse()
{
return array(
return [
'usersMiddle.isRead' => true
);
];
}
protected function getWherePartIsReadIsTrue()
{
return array(
return [
'usersMiddle.isRead' => true
);
];
}
protected function getWherePartIsReadIsFalse()
{
return array(
return [
'usersMiddle.isRead' => false,
'OR' => array(
'OR' => [
'sentById' => null,
'sentById!=' => $this->getUser()->id
)
);
]
];
}
protected function getWherePartIsImportantIsTrue()
{
return array(
return [
'usersMiddle.isImportant' => true
);
];
}
protected function getWherePartIsImportantIsFalse()
{
return array(
return [
'usersMiddle.isImportant' => false
);
];
}
}

View File

@@ -44,7 +44,7 @@ class EmailFilter extends \Espo\Core\SelectManagers\Base
'assignedUserId' => $this->getUser()->id
])->find();
foreach ($emailAccountList as $emailAccount) {
$idList = $emailAccount->id;
$idList[] = $emailAccount->id;
}
if (count($idList)) {

View File

@@ -775,31 +775,6 @@ class Email extends Record
$this->getEntityManager()->getRepository('Email')->loadNameHash($entity, $fieldList);
}
protected function getSelectParams($params)
{
$searchByEmailAddress = false;
if (!empty($params['where']) && is_array($params['where'])) {
foreach ($params['where'] as $i => $p) {
if (!empty($p['attribute']) && $p['attribute'] == 'emailAddress') {
$searchByEmailAddress = true;
$emailAddress = $p['value'];
unset($params['where'][$i]);
}
}
}
$selectManager = $this->getSelectManager($this->getEntityType());
$selectParams = $selectManager->getSelectParams($params, true);
if ($searchByEmailAddress) {
$selectManager->whereEmailAddress($emailAddress, $selectParams);
}
return $selectParams;
}
public function copyAttachments($emailId, $parentType, $parentId)
{
return $this->getCopiedAttachments($emailId, $parentType, $parentId);

View File

@@ -67,8 +67,15 @@ class EmailAddress extends Record
$selectManager = $this->getSelectManagerFactory()->create($entityType);
$selectManager->applyAccess($selectParams);
$select = ['id', 'emailAddress', 'name'];
if ($this->getMetadata()->get(['entityDefs', $entityType, 'fields', 'name', 'type']) === 'personName') {
$select[] = 'firstName';
$select[] = 'lastName';
}
$collection = $this->getEntityManager()->getRepository($entityType)
->select(['id', 'emailAddress', 'name'])
->select($select)
->find($selectParams);
foreach ($collection as $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;
@@ -104,6 +112,8 @@ class GlobalSearch extends \Espo\Core\Services\Base
$selectParams['useFullTextSearch'] = true;
$selectManager->applyTextFilter($query, $selectParams);
unset($selectParams['additionalSelect']);
$itemSql = $this->getEntityManager()->getQuery()->createSelectQuery($entityType, $selectParams);
$unionPartList[] = "(\n" . $itemSql . "\n)";

View File

@@ -97,6 +97,8 @@ class Language extends \Espo\Core\Services\Base
$scopeList = array_keys($this->getMetadata()->get(['scopes'], []));
foreach ($scopeList as $scope) {
if ($this->getMetadata()->get(['entityAcl', $scope, 'languageAclDisabled'])) continue;
if (!$this->getAcl()->check($scope)) {
unset($data[$scope]);
unset($data['Global']['scopeNames'][$scope]);
@@ -132,11 +134,15 @@ class Language extends \Espo\Core\Services\Base
],
];
}
$data['User']['fields']['password'] = $languageObj->translate('password', 'fields', 'User');
$data['User']['fields']['passwordConfirm'] = $languageObj->translate('passwordConfirm', 'fields', 'User');
}
$data['User']['fields'] = $data['User']['fields'] ?? [];
$data['User']['fields']['password'] = $languageObj->translate('password', 'fields', 'User');
$data['User']['fields']['passwordConfirm'] = $languageObj->translate('passwordConfirm', 'fields', 'User');
$data['User']['fields']['newPassword'] = $languageObj->translate('newPassword', 'fields', 'User');
$data['User']['fields']['newPasswordConfirm'] = $languageObj->translate('newPasswordConfirm', 'fields', 'User');
return $data;
}
}

View File

@@ -307,7 +307,8 @@ class Pdf extends \Espo\Core\Services\Base
$this->getAcl(),
$this->getInjection('entityManager'),
$this->getInjection('metadata'),
$this->getInjection('defaultLanguage')
$this->getInjection('defaultLanguage'),
$this->getInjection('config')
);
}
}

View File

@@ -564,6 +564,7 @@ class Stream extends \Espo\Core\Services\Base
'orderBy' => 'number',
'order' => 'DESC',
'limit' => $sqLimit,
'_name' => 'selfPost',
];
$selectParamsList[] = [
@@ -593,6 +594,7 @@ class Stream extends \Espo\Core\Services\Base
'orderBy' => 'number',
'order' => 'DESC',
'limit' => $sqLimit,
'_name' => 'globalPost',
];
}
@@ -679,18 +681,22 @@ class Stream extends \Espo\Core\Services\Base
}
}
if ($skipOwn) {
$whereClause[] = [
'createdById!=' => $this->getUser()->id,
];
}
$sqlPartList = [];
foreach ($selectParamsList as $i => $selectParams) {
if (empty($selectParams['whereClause'])) {
$selectParams['whereClause'] = [];
}
$selectParams['whereClause'][] = $whereClause;
if ($skipOwn) {
$itemName = $selectParams['_name'] ?? null;
if ($itemName !== 'selfPost' && $itemName !== 'globalPost') {
$selectParams['whereClause'][] = [
'createdById!=' => $this->getUser()->id,
];
}
}
$sqlPartList[] = "(\n" . $this->getEntityManager()->getQuery()->createSelectQuery('Note', $selectParams) . "\n)";
}

View File

@@ -999,8 +999,16 @@ var Bull = Bull || {};
this.$el = $(el).eq(0);
this.el = this.$el[0];
}
},
propagateEvent: function () {
this.trigger.apply(this, arguments);
for (var key in this.nestedViews) {
var view = this.nestedViews[key];
view.propagateEvent.apply(view, arguments);
}
},
});
}).call(this, Bull, Backbone, _);

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

@@ -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

@@ -77,7 +77,7 @@
<div class="row">
<div class="col-md-12">
<div class="pull-right">
<a target="_blank" href="https://www.espocrm.com/documentation/administration/server-configuration/" style="font-weight:bold;">Configuration Instructions</a>
<a target="_blank" href="https://www.espocrm.com/documentation/administration/server-configuration/" style="font-weight:600;">Configuration Instructions</a>
</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

@@ -157,7 +157,13 @@ define('controller', [], function () {
storeMainView: function (key, view) {
this.set('storedMainView-' + key, view);
view.once('remove', function () {
this.listenTo(view, 'remove', function (o) {
o = o || {};
if (o.ignoreCleaning) return;
this.stopListening(view, 'remove');
this.clearStoredMainView(key);
}, this);
},
@@ -278,6 +284,10 @@ define('controller', [], function () {
if (master.currentViewKey) {
this.set('storedScrollTop-' + master.currentViewKey, $(window).scrollTop());
if (this.hasStoredMainView(master.currentViewKey)) {
var mainView = master.getView('main');
if (mainView) {
mainView.propagateEvent('remove', {ignoreCleaning: true});
}
master.unchainView('main');
}
}

View File

@@ -306,7 +306,7 @@ define('controllers/record', 'controller', function (Dep) {
var collectionName = this.entityType || this.name;
if (usePreviouslyFetched) {
if (collectionName in this.collectionMap) {
var collection = this.collectionMap[collectionName];// = this.collectionMap[collectionName].clone();
var collection = this.collectionMap[collectionName];
callback.call(context, collection);
return;
}

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

@@ -392,6 +392,8 @@ define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'model']
toPlural: function (string) {
if (string.slice(-1) == 'y') {
return string.substr(0, string.length - 1) + 'ies';
} else if (string.slice(-1) == 's') {
return string + 'es';
} else {
return string + 's';
}

View File

@@ -0,0 +1,79 @@
/************************************************************************
* 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('views/admin/field-manager/fields/link-multiple/default', 'views/fields/link-multiple', function (Dep) {
return Dep.extend({
data: function () {
var defaultAttributes = this.model.get('defaultAttributes') || {};
var nameHash = defaultAttributes[this.options.field + 'Names'] || {};
var idValues = defaultAttributes[this.options.field + 'Ids'] || [];
var data = Dep.prototype.data.call(this);
data.nameHash = nameHash;
data.idValues = idValues;
return data;
},
setup: function () {
Dep.prototype.setup.call(this);
this.foreignScope = this.getMetadata().get(['entityDefs', this.options.scope, 'links', this.options.field, 'entity']);
},
fetch: function () {
var data = Dep.prototype.fetch.call(this);
var defaultAttributes = {};
defaultAttributes[this.options.field + 'Ids'] = data[this.idsName];
defaultAttributes[this.options.field + 'Names'] = data[this.nameHashName];
if (data[this.idsName] === null || data[this.idsName].length === 0) {
defaultAttributes = null;
}
return {
defaultAttributes: defaultAttributes
};
},
copyValuesFromModel: function () {
var defaultAttributes = this.model.get('defaultAttributes') || {};
var idValues = defaultAttributes[this.options.field + 'Ids'] || [];
var nameHash = defaultAttributes[this.options.field + 'Names'] || {};
this.ids = idValues;
this.nameHash = nameHash;
},
});
});

View File

@@ -345,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

@@ -26,7 +26,10 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/email/fields/email-address-varchar', ['views/fields/varchar', 'views/email/fields/from-address-varchar'], function (Dep, From) {
define(
'views/email/fields/email-address-varchar',
['views/fields/base', 'views/email/fields/from-address-varchar', 'views/email/fields/email-address'],
function (Dep, From, EmailAddress) {
return Dep.extend({
@@ -48,6 +51,8 @@ Espo.define('views/email/fields/email-address-varchar', ['views/fields/varchar',
this.deleteAddress(address);
},
'keyup input': function (e) {
if (this.mode === 'search') return;
if (e.keyCode == 188 || e.keyCode == 186 || e.keyCode == 13) {
var $input = $(e.currentTarget);
var address = $input.val().replace(',', '').replace(';', '').trim();
@@ -61,6 +66,8 @@ Espo.define('views/email/fields/email-address-varchar', ['views/fields/varchar',
}
},
'change input': function (e) {
if (this.mode === 'search') return;
var $input = $(e.currentTarget);
var address = $input.val().replace(',','').replace(';','').trim();
if (~address.indexOf('@')) {
@@ -193,6 +200,10 @@ Espo.define('views/email/fields/email-address-varchar', ['views/fields/varchar',
this.$input.autocomplete('dispose');
}, this);
}
if (this.mode == 'search' && this.getAcl().check('Email', 'create')) {
EmailAddress.prototype.initSearchAutocomplete.call(this);
}
},
checkEmailAddressInString: function (string) {
@@ -275,6 +286,20 @@ Espo.define('views/email/fields/email-address-varchar', ['views/fields/varchar',
return data;
},
fetchSearch: function () {
var value = this.$element.val().trim();
if (value) {
var data = {
type: 'equals',
value: value,
}
return data;
}
return false;
},
getValueForDisplay: function () {
if (this.mode == 'detail') {
var names = [];

View File

@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/email/fields/email-address', ['views/fields/base'], function (Dep) {
define('views/email/fields/email-address', ['views/fields/base'], function (Dep) {
return Dep.extend({
@@ -42,51 +42,56 @@ Espo.define('views/email/fields/email-address', ['views/fields/base'], function
this.$input = this.$el.find('input');
if (this.mode == 'search') {
this.$input.autocomplete({
serviceUrl: function (q) {
return 'EmailAddress/action/searchInAddressBook?maxSize=' + this.getAutocompleteMaxCount();
}.bind(this),
paramName: 'q',
minChars: 1,
autoSelectFirst: true,
triggerSelectOnValidInput: false,
formatResult: function (suggestion) {
return this.getHelper().escapeString(suggestion.name) + ' &#60;' + this.getHelper().escapeString(suggestion.id) + '&#62;';
}.bind(this),
transformResult: function (response) {
var response = JSON.parse(response);
var list = [];
response.forEach(function(item) {
list.push({
id: item.emailAddress,
name: item.entityName,
emailAddress: item.emailAddress,
entityId: item.entityId,
entityName: item.entityName,
entityType: item.entityType,
data: item.emailAddress,
value: item.emailAddress
});
}, this);
return {
suggestions: list
};
}.bind(this),
onSelect: function (s) {
this.$input.val(s.emailAddress);
}.bind(this)
});
this.once('render', function () {
this.$input.autocomplete('dispose');
}, this);
this.once('remove', function () {
this.$input.autocomplete('dispose');
}, this);
if (this.mode == 'search' && this.getAcl().check('Email', 'create')) {
this.initSearchAutocomplete();
}
},
initSearchAutocomplete: function () {
this.$input = this.$input || this.$el.find('input');
this.$input.autocomplete({
serviceUrl: function (q) {
return 'EmailAddress/action/searchInAddressBook?maxSize=' + this.getAutocompleteMaxCount();
}.bind(this),
paramName: 'q',
minChars: 1,
autoSelectFirst: true,
triggerSelectOnValidInput: false,
formatResult: function (suggestion) {
return this.getHelper().escapeString(suggestion.name) + ' &#60;' + this.getHelper().escapeString(suggestion.id) + '&#62;';
}.bind(this),
transformResult: function (response) {
var response = JSON.parse(response);
var list = [];
response.forEach(function(item) {
list.push({
id: item.emailAddress,
name: item.entityName,
emailAddress: item.emailAddress,
entityId: item.entityId,
entityName: item.entityName,
entityType: item.entityType,
data: item.emailAddress,
value: item.emailAddress,
});
}, this);
return {
suggestions: list
};
}.bind(this),
onSelect: function (s) {
this.$input.val(s.emailAddress);
}.bind(this)
});
this.once('render', function () {
this.$input.autocomplete('dispose');
}, this);
this.once('remove', function () {
this.$input.autocomplete('dispose');
}, this);
},
fetchSearch: function () {
var value = this.$element.val();
@@ -101,8 +106,7 @@ Espo.define('views/email/fields/email-address', ['views/fields/base'], function
return data;
}
return false;
}
},
});
});

View File

@@ -25,7 +25,11 @@
* 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/email/fields/from-address-varchar', 'views/fields/varchar', function (Dep) {
define(
'views/email/fields/from-address-varchar',
['views/fields/base', 'views/email/fields/email-address'],
function (Dep, EmailAddress) {
return Dep.extend({
@@ -36,6 +40,7 @@ Espo.define('views/email/fields/from-address-varchar', 'views/fields/varchar', f
Dep.prototype.setup.call(this);
this.on('render', function () {
if (this.mode === 'search') return;
this.initAddressList();
}, this);
},
@@ -74,6 +79,18 @@ Espo.define('views/email/fields/from-address-varchar', 'views/fields/varchar', f
return data;
},
afterRender: function () {
Dep.prototype.afterRender.call(this);
if (this.mode == 'search' && this.getAcl().check('Email', 'create')) {
EmailAddress.prototype.initSearchAutocomplete.call(this);
}
},
getAutocompleteMaxCount: function () {
return EmailAddress.prototype.getAutocompleteMaxCount.call(this);
},
initAddressList: function () {
this.nameHash = {};
this.typeHash = this.model.get('typeHash') || {};
@@ -329,8 +346,21 @@ Espo.define('views/email/fields/from-address-varchar', 'views/fields/varchar', f
}, this);
}.bind(this));
}
},
fetchSearch: function () {
var value = this.$element.val().trim();
if (value) {
var data = {
type: 'equals',
value: value,
}
return data;
}
return false;
},
});
});

View File

@@ -40,7 +40,7 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
searchTemplate: 'fields/array/search',
searchTypeList: ['anyOf', 'noneOf', 'isEmpty', 'isNotEmpty'],
searchTypeList: ['anyOf', 'noneOf', 'allOf', 'isEmpty', 'isNotEmpty'],
maxItemLength: null,
@@ -149,7 +149,7 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
handleSearchType: function (type) {
var $inputContainer = this.$el.find('div.input-container');
if (~['anyOf', 'noneOf'].indexOf(type)) {
if (~['anyOf', 'noneOf', 'allOf'].indexOf(type)) {
$inputContainer.removeClass('hidden');
} else {
$inputContainer.addClass('hidden');
@@ -481,7 +481,7 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
var arr = [];
var arrFront = [];
if (~['anyOf', 'noneOf'].indexOf(type)) {
if (~['anyOf', 'noneOf', 'allOf'].indexOf(type)) {
var valueList = this.$element.val().split(':,:');
if (valueList.length == 1 && valueList[0] == '') {
valueList = [];
@@ -515,6 +515,21 @@ define('views/fields/array', ['views/fields/base', 'lib!Selectize'], function (D
return data;
}
if (type === 'allOf') {
var data = {
type: 'arrayAllOf',
value: valueList,
data: {
type: 'allOf',
valueList: valueList
}
};
if (!valueList.length) {
data.value = null;
}
return data;
}
if (type === 'isEmpty') {
var data = {
type: 'arrayIsEmpty',

View File

@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/fields/date', 'views/fields/base', function (Dep) {
define('views/fields/date', 'views/fields/base', function (Dep) {
return Dep.extend({
@@ -161,6 +161,7 @@ Espo.define('views/fields/date', 'views/fields/base', function (Dep) {
weekStart: this.getDateTime().weekStart,
autoclose: true,
todayHighlight: true,
keyboardNavigation: false,
};
var language = this.getConfig().get('language');

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

@@ -100,13 +100,10 @@ Espo.define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
this.nameHash = Espo.Utils.clone(nameHash);
this.ids = Espo.Utils.clone(idList);
} else {
this.ids = Espo.Utils.clone(this.model.get(this.idsName) || []);
this.nameHash = Espo.Utils.clone(this.model.get(this.nameHashName) || {});
}
this.copyValuesFromModel(); }
this.listenTo(this.model, 'change:' + this.idsName, function () {
this.ids = Espo.Utils.clone(this.model.get(this.idsName) || []);
this.nameHash = Espo.Utils.clone(this.model.get(this.nameHashName) || {});
this.copyValuesFromModel();
}, this);
this.sortable = this.sortable || this.params.sortable;
@@ -151,6 +148,11 @@ Espo.define('views/fields/link-multiple', 'views/fields/base', function (Dep) {
}
},
copyValuesFromModel: function () {
this.ids = Espo.Utils.clone(this.model.get(this.idsName) || []);
this.nameHash = Espo.Utils.clone(this.model.get(this.nameHashName) || {});
},
handleSearchType: function (type) {
if (~['anyOf', 'noneOf'].indexOf(type)) {
this.$el.find('div.link-group-container').removeClass('hidden');

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

@@ -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',
});
}
}
},
@@ -859,6 +872,8 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
this.portalLayoutDisabled = this.options.portalLayoutDisabled || this.portalLayoutDisabled;
this.dynamicLogicDefs = this.options.dynamicLogicDefs || this.dynamicLogicDefs;
this.accessControlDisabled = this.options.accessControlDisabled || this.accessControlDisabled;
this.setupActionItems();
this.setupBeforeFinal();
@@ -869,7 +884,9 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
},
setupBeforeFinal: function () {
this.manageAccess();
if (!this.accessControlDisabled) {
this.manageAccess();
}
this.attributes = this.model.getClonedAttributes();
@@ -1353,11 +1370,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);
@@ -1401,7 +1425,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;
}
}
@@ -1455,7 +1479,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

@@ -38,5 +38,7 @@ define('views/record/edit-for-modal', 'views/record/edit', function (Dep) {
isWide: true,
accessControlDisabled: true,
});
});

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

@@ -3174,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.2",
"version": "5.7.6",
"description": "",
"main": "index.php",
"repository": {

View File

@@ -189,4 +189,9 @@ abstract class BaseTestCase extends \PHPUnit\Framework\TestCase
{
$this->espoTester->setData($data);
}
protected function enableFullReset()
{
$this->espoTester->setParam('fullReset', true);
}
}

View File

@@ -109,6 +109,29 @@ class Tester
return $returns;
}
public function setParam($name, $value)
{
$this->params[$name] = $value;
}
protected function getTestConfigData()
{
if (!file_exists($this->configPath)) {
die('Config for integration tests ['. $this->configPath .'] is not found');
}
return include($this->configPath);
}
protected function saveTestConfigData($optionName, $data)
{
$configData = $this->getTestConfigData();
$configData[$optionName] = $data;
$fileManager = new \Espo\Core\Utils\File\Manager();
return $fileManager->putPhpContents($this->configPath, $configData);
}
public function auth($userName, $password = null, $portalId = null, $authenticationMethod = null)
{
$this->userName = $userName;
@@ -172,6 +195,10 @@ class Tester
chdir($baseDir);
set_include_path($baseDir);
if ($this->getParam('fullReset')) {
$this->saveTestConfigData('lastModifiedTime', null);
}
}
protected function install()
@@ -179,9 +206,9 @@ class Tester
$mainApplication = new \Espo\Core\Application();
$fileManager = $mainApplication->getContainer()->get('fileManager');
$latestEspo = Utils::getLatestBuildedPath($this->buildedPath);
$latestEspoDir = Utils::getLatestBuildedPath($this->buildedPath);
$configData = include($this->configPath);
$configData = $this->getTestConfigData();
$configData['siteUrl'] = $mainApplication->getContainer()->get('config')->get('siteUrl') . '/' . $this->installPath;
$this->params['siteUrl'] = $configData['siteUrl'];
@@ -193,11 +220,9 @@ class Tester
die("Permission denied for directory [".$this->installPath."].\n");
}
//remove and copy Espo files
//reset DB, remove and copy Espo files
Utils::checkCreateDatabase($configData['database']);
Utils::dropTables($configData['database']);
$fileManager->removeInDir($this->installPath);
$tt = $fileManager->copy($latestEspo, $this->installPath, true);
$this->reset($fileManager, $latestEspoDir);
Utils::fixUndefinedVariables();
@@ -219,6 +244,59 @@ class Tester
$installer->setSuccess();
}
protected function reset($fileManager, $latestEspoDir)
{
$configData = $this->getTestConfigData();
$fullReset = false;
$modifiedTime = filemtime($latestEspoDir . '/application');
if (!isset($configData['lastModifiedTime']) || $configData['lastModifiedTime'] != $modifiedTime) {
$fullReset = true;
$this->saveTestConfigData('lastModifiedTime', $modifiedTime);
}
if ($fullReset) {
Utils::dropTables($configData['database']);
$fileManager->removeInDir($this->installPath);
$fileManager->copy($latestEspoDir, $this->installPath, true);
return true;
}
//Utils::truncateTables($configData['database']);
Utils::dropTables($configData['database']);
$fileManager->removeInDir($this->installPath . '/data');
$fileManager->removeInDir($this->installPath . '/custom/Espo/Custom');
$fileManager->removeInDir($this->installPath . '/client/custom');
$fileManager->unlink($this->installPath . '/install/config.php');
return true;
}
protected function cleanDirectory($fileManager, $path, array $ignoreList = [])
{
if (!file_exists($path)) {
return true;
}
$list = $fileManager->getFileList($path);
foreach ($list as $itemName) {
if (in_array($itemName, $ignoreList)) continue;
$itemPath = $path . '/' . $itemName;
if (is_file($itemPath)) {
$fileManager->unlink($itemPath);
} else {
$fileManager->removeInDir($itemPath, true);
}
}
return true;
}
protected function loadData()
{
$applyChanges = false;

View File

@@ -147,6 +147,16 @@ class Utils
}
}
public static function truncateTables(array $options)
{
$pdo = static::createPdoConnection($options);
$result = $pdo->query("show tables");
while ($row = $result->fetch(\PDO::FETCH_NUM)) {
$pdo->query("TRUNCATE TABLE `".$row[0]."`;");
}
}
public static function createPdoConnection(array $params)
{
$platform = !empty($params['platform']) ? strtolower($params['platform']) : 'mysql';

View File

@@ -95,6 +95,37 @@ class FormulaTest extends \tests\integration\Core\BaseTestCase
$this->assertEquals(40, $result);
}
public function testSumRelated()
{
$em = $this->getContainer()->get('entityManager');
$fm = $this->getContainer()->get('formulaManager');
$contact1 = $em->createEntity('Contact', [
'lastName' => '1',
]);
$contact2 = $em->createEntity('Contact', [
'lastName' => '2',
]);
$opportunity1 = $em->createEntity('Opportunity', [
'name' => '1',
'amount' => 1,
'stage' => 'Closed Won',
'contactsIds' => [$contact1->id, $contact2->id],
]);
$opportunity2 = $em->createEntity('Opportunity', [
'name' => '2',
'amount' => 1,
'stage' => 'Closed Won',
'contactsIds' => [$contact1->id, $contact2->id],
]);
$script = "entity\sumRelated('opportunities', 'amountConverted', 'won')";
$result = $fm->run($script, $contact1);
$this->assertEquals(2, $result);
}
public function testRecordExists()
{
$em = $this->getContainer()->get('entityManager');

View File

@@ -49,7 +49,8 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
$this->assertStringMatchesFormat('%x', $extensionId);
$this->assertFileExists('data/upload/extensions/' . $extensionId . 'z');
$this->assertFileExists('data/upload/extensions/' . $extensionId); //directory
//$this->assertDirectoryExists('data/upload/extensions/' . $extensionId);
$this->enableFullReset();
return $extensionId;
}
@@ -74,6 +75,8 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
$this->assertFileNotExists('extension.php');
$this->assertFileNotExists('upgrade.php');
$this->enableFullReset();
return $extensionId;
}
@@ -97,6 +100,8 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
$this->assertFileExists('extension.php');
$this->assertFileExists('upgrade.php');
$this->enableFullReset();
return $extensionId;
}
@@ -119,5 +124,7 @@ class GeneralTest extends \tests\integration\Core\BaseTestCase
$this->assertFileExists('vendor/zendframework'); //directory
$this->assertFileExists('extension.php');
$this->assertFileExists('upgrade.php');
$this->enableFullReset();
}
}

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

@@ -510,7 +510,7 @@ class ParserTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($expected, $actual);
}
function parseNewLine()
function testParseNewLine()
{
$expression = " \n \"test\n\thello\" ";
$expected = (object) [
@@ -521,6 +521,117 @@ class ParserTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($expected, $actual);
}
function testParseComment1()
{
$expression = "// \"test\"\n \"//test\" ";
$expected = (object) [
'type' => 'value',
'value' => "//test"
];
$actual = $this->parser->parse($expression);
$this->assertEquals($expected, $actual);
}
function testParseComment2()
{
$expression = "\"test\" // test\n ";
$expected = (object) [
'type' => 'value',
'value' => "test",
];
$actual = $this->parser->parse($expression);
$this->assertEquals($expected, $actual);
}
function testParseComment3()
{
$expression = "\"test\" /* test\n */";
$expected = (object) [
'type' => 'value',
'value' => "test",
];
$actual = $this->parser->parse($expression);
$this->assertEquals($expected, $actual);
}
function testParseComment4()
{
$expression = "/* test\n */ \"/*test*/\"";
$expected = (object) [
'type' => 'value',
'value' => "/*test*/",
];
$actual = $this->parser->parse($expression);
$this->assertEquals($expected, $actual);
}
function testParseComment5()
{
$expression = "\"test\" /* test\n */ /* test */ ";
$expected = (object) [
'type' => 'value',
'value' => "test",
];
$actual = $this->parser->parse($expression);
$this->assertEquals($expected, $actual);
}
function testParseComment6()
{
$expression = "/* test; */ test1 = 1 + 1; test2 = 2 * 2;";
$expected = (object) [
'type' => 'bundle',
'value' => [
(object) [
'type' => 'setAttribute',
'value' => [
(object) [
'type' => 'value',
'value' => 'test1',
],
(object) [
'type' => 'numeric\summation',
'value' => [
(object) [
'type' => 'value',
'value' => 1,
],
(object) [
'type' => 'value',
'value' => 1,
]
],
],
],
],
(object) [
'type' => 'setAttribute',
'value' => [
(object) [
'type' => 'value',
'value' => 'test2',
],
(object) [
'type' => 'numeric\multiplication',
'value' => [
(object) [
'type' => 'value',
'value' => 2,
],
(object) [
'type' => 'value',
'value' => 2,
]
],
],
],
],
],
];
$actual = $this->parser->parse($expression);
$this->assertEquals($expected, $actual);
}
function testParseFunction()
{
$expression = "numeric\\summation (10, parent.amountConverted, 0.1)";

View File

@@ -144,10 +144,6 @@ class ImporterTest extends \PHPUnit\Framework\TestCase
function testImport2()
{
if (extension_loaded('mailparse')) {
$this->assertTrue(true);
return;
}
$entityManager = $this->entityManager;
$config = $this->config;

View File

@@ -74,14 +74,14 @@ class FieldManagerTest extends \PHPUnit\Framework\TestCase
{
$this->expectException('\Espo\Core\Exceptions\Conflict');
$data = array(
$data = (object) [
"type" => "varchar",
"maxLength" => "50",
);
];
$this->objects['metadata']
->expects($this->once())
->method('get')
->method('getObjects')
->will($this->returnValue($data));
$this->object->create('CustomEntity', 'varName', $data);
@@ -95,15 +95,14 @@ class FieldManagerTest extends \PHPUnit\Framework\TestCase
"label" => "Modified Name",
);
$existingData = array(
$existingData = (object) [
"type" => "varchar",
"maxLength" => 50,
"label" => "Name",
);
];
$map = array(
['entityDefs.Account.fields.name', [], $existingData],
[['entityDefs', 'Account', 'fields', 'name', 'type'], null, $existingData['type']],
[['entityDefs', 'Account', 'fields', 'name', 'type'], null, $data['type']],
['fields.varchar', null, null],
[['fields', 'varchar', 'hookClassName'], null, null],
);
@@ -118,6 +117,11 @@ class FieldManagerTest extends \PHPUnit\Framework\TestCase
->method('get')
->will($this->returnValueMap($map));
$this->objects['metadata']
->expects($this->exactly(2))
->method('getObjects')
->will($this->returnValue($existingData));
$this->objects['metadataHelper']
->expects($this->once())
->method('getFieldDefsByType')
@@ -177,7 +181,6 @@ class FieldManagerTest extends \PHPUnit\Framework\TestCase
);
$map = array(
['entityDefs.Account.fields.name', [], $data],
[['entityDefs', 'Account', 'fields', 'name', 'type'], null, $data['type']],
['fields.varchar', null, null],
[['fields', 'varchar', 'hookClassName'], null, null],
@@ -240,9 +243,18 @@ class FieldManagerTest extends \PHPUnit\Framework\TestCase
$this->objects['metadata']
->expects($this->exactly(2))
->method('getObjects')
->will($this->returnValue((object) $data));
$this->objects['metadata']
->expects($this->exactly(1))
->method('getCustom')
->will($this->returnValue((object) []));
$this->objects['metadata']
->expects($this->never())
->method('saveCustom');
$this->object->update('Account', 'name', $data);
}
@@ -289,7 +301,6 @@ class FieldManagerTest extends \PHPUnit\Framework\TestCase
);
$map = array(
['entityDefs.CustomEntity.fields.varName', [], $data],
['entityDefs.CustomEntity.fields.varName.type', null, $data['type']],
[['entityDefs', 'CustomEntity', 'fields', 'varName'], null, $data],
['fields.varchar', null, null],
@@ -301,6 +312,11 @@ class FieldManagerTest extends \PHPUnit\Framework\TestCase
->method('get')
->will($this->returnValueMap($map));
$this->objects['metadata']
->expects($this->exactly(2))
->method('getObjects')
->will($this->returnValue((object) $data));
$this->objects['metadata']
->expects($this->once())
->method('saveCustom')
@@ -374,8 +390,8 @@ class FieldManagerTest extends \PHPUnit\Framework\TestCase
$this->objects['metadata']
->expects($this->at(0))
->method('get')
->will($this->returnValue($data));
->method('getObjects')
->will($this->returnValue((object) $data));
$this->objects['language']
->expects($this->once())

View File

@@ -753,6 +753,17 @@ class QueryTest extends \PHPUnit\Framework\TestCase
$this->assertEquals($expectedSql, $sql);
}
public function testFunction17()
{
$sql = $this->query->createSelectQuery('Comment', [
'select' => [["TIMESTAMPDIFF_YEAR:('2016-10-10', '2018-10-10')", 'test']],
'withDeleted' => true
]);
$expectedSql =
"SELECT TIMESTAMPDIFF(YEAR, '2016-10-10', '2018-10-10') AS `test` FROM `comment`";
$this->assertEquals($expectedSql, $sql);
}
public function testFunctionTZ1()
{
$sql = $this->query->createSelectQuery('Comment', [

View File

@@ -98,14 +98,24 @@ class EntityTest extends \PHPUnit\Framework\TestCase
$this->assertTrue($job->isAttributeChanged('array'));
$job = new \Espo\Entities\Job();
$job->setFetched('array', [
$job->set('array', [
(object) ['k1' => 'v1']
]);
$job->setAsFetched();
$job->set('array', [
(object) ['k1' => 'v1', 'k2' => 'v2'],
]);
$this->assertTrue($job->isAttributeChanged('array'));
$job = new \Espo\Entities\Job();
$v = [
(object) ['k1' => 'v1']
];
$job->setFetched('array', $v);
$v[0]->k2 = 'v2';
$job->set('array', $v);
$this->assertTrue($job->isAttributeChanged('array'));
$job = new \Espo\Entities\Job();
$job->setFetched('array', ['1', '2']);
$job->set('array', ['1', '2', '3']);
@@ -144,13 +154,13 @@ class EntityTest extends \PHPUnit\Framework\TestCase
$this->assertTrue($job->isAttributeChanged('arrayUnordered'));
$job = new \Espo\Entities\Job();
$job->setFetched('object', (object) ['1' => 'value-1']);
$job->set('object', (object) ['1' => 'value-1']);
$job->setFetched('object', (object) ['a1' => 'value-1']);
$job->set('object', (object) ['a1' => 'value-1']);
$this->assertFalse($job->isAttributeChanged('object'));
$job = new \Espo\Entities\Job();
$job->setFetched('object', (object) ['1' => 'value-1']);
$job->set('object', ['1' => 'value-1']);
$job->setFetched('object', (object) ['a1' => 'value-1']);
$job->set('object', ['a1' => 'value-1']);
$this->assertTrue($job->isAttributeChanged('object'));
$job = new \Espo\Entities\Job();
@@ -197,5 +207,4 @@ class EntityTest extends \PHPUnit\Framework\TestCase
]);
$this->assertTrue($job->isAttributeChanged('object'));
}
}