Compare commits

...

115 Commits

Author SHA1 Message Date
yuri eadf7835db fix css 2018-08-03 13:16:22 +03:00
yuri c30c4163a6 increase container max size 2018-08-02 16:21:40 +03:00
yuri 8d6db72516 icons fix 2018-08-02 15:08:32 +03:00
yuri 71864056bc navbar panel height 2018-08-02 15:05:00 +03:00
yuri e375105a5f Merge branch 'hotfix/5.3.6' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.6 2018-08-02 13:28:18 +03:00
Taras Machyshyn e066f4c6b0 Merge branch 'hotfix/5.3.6' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.6 2018-08-02 13:27:08 +03:00
Taras Machyshyn b032aa4d8d Upgrades bug fixes 2018-08-02 13:26:46 +03:00
yuri 342a18e0ac fix installer css 2018-08-02 12:59:12 +03:00
yuri e27e9de701 attachment preview resize 2018-08-02 12:37:16 +03:00
yuri b14b199d43 preview resize fix 2018-08-02 12:28:14 +03:00
yuri f77a7c32d5 link multiple preview size param 2018-08-02 12:04:33 +03:00
yuri 9f84d3f233 notifications attachment preview small size 2018-08-02 11:56:00 +03:00
yuri be82244c6b showing attachments in notifications 2018-08-02 11:30:28 +03:00
yuri 7fed72d391 fix image sized download extension 2018-08-02 10:55:39 +03:00
yuri 54d615b64d fix attachment preview in list 2018-08-02 10:46:16 +03:00
yuri 2dbbd1e23d Merge branch 'hotfix/5.3.6' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.6 2018-08-02 10:30:55 +03:00
Taras Machyshyn cba8867389 Bug fixes for MySQL 8 2018-08-01 18:49:40 +03:00
yuri 32d47db15a emailFolderMaxCount 2018-08-01 11:16:50 +03:00
yuri 2c65c9ff9f modal select attributes 2018-08-01 11:13:00 +03:00
yuri fa5d63253b naming fix 2018-08-01 10:46:48 +03:00
yuri b7ae252d3d modal forceSelectAllAttributes 2018-08-01 10:24:41 +03:00
yuri f07b43abda version 2018-07-30 16:05:13 +03:00
yuri 276db37baf required extensions change 2018-07-30 16:03:20 +03:00
yuri 6adad84a29 installer config formatting 2018-07-30 15:52:16 +03:00
yuri f137298c49 add exif extension 2018-07-30 15:50:17 +03:00
yuri f8839518d5 avatar check exif read data exists 2018-07-30 15:48:00 +03:00
yuri 1ae39d1a40 fix campaign revenue empty 2018-07-30 10:20:45 +03:00
yuri 94f920324e fix case compose email 2018-07-30 10:14:17 +03:00
yuri 94c00e2901 orm: revert forcing updating json object 2018-07-27 11:59:22 +03:00
yuri 7de988104b link multiple attachment multiple list view fixes 2018-07-27 11:55:19 +03:00
yuri 03bc90968c fix email sending 2 2018-07-27 11:32:29 +03:00
yuri a5401a22b9 fix email sending 2018-07-27 11:13:49 +03:00
yuri 4c1375d8b2 user buttons links 2018-07-27 10:46:18 +03:00
yuri bab4a3b0d6 fix full text 2018-07-27 10:40:48 +03:00
yuri 33e135f227 fix email fetching 2018-07-27 10:03:26 +03:00
yuri 56b9d8d5c1 fix campaign 2018-07-26 17:56:35 +03:00
yuri 38ec88e302 fix formatting 2018-07-26 17:02:31 +03:00
yuri 10be208d8d fix linkedWith filter 2018-07-26 12:26:25 +03:00
yuri 38d227a948 fix model sync 2018-07-26 10:55:55 +03:00
yuri 1a8e4435e2 use put istead of patch 2018-07-25 15:04:33 +03:00
yuri 12febbdff5 version 2018-07-25 12:44:45 +03:00
yuri ebea168350 field ui fixes 2018-07-25 12:41:48 +03:00
yuri 704519f80b fix setFetched link multiple and link one 2018-07-25 11:38:48 +03:00
yuri 5f1000ddd3 fix audited 2018-07-25 11:04:17 +03:00
yuri 2ee7e6cf4a orm: isUnordered param 2018-07-25 10:57:53 +03:00
yuri 2989975829 audited fix 2018-07-25 10:41:39 +03:00
yuri a4c068427d cleanup 2018-07-24 18:22:54 +03:00
yuri e1a76b9924 orm: isAttributeChanged improvement 2018-07-24 18:22:04 +03:00
yuri 1f2efcd716 link multiple available in list fix 2018-07-23 12:59:04 +03:00
yuri 576dfe068f fix array field setOptionList 2018-07-20 15:51:21 +03:00
yuri 56f8e68599 fix wysiwyg width 2018-07-19 14:56:28 +03:00
yuri e8d75808ef fix email scrolling 2018-07-19 14:43:35 +03:00
yuri bc8bf89d6e css fixes 2018-07-19 12:14:06 +03:00
yuri 91385cc3f0 fix naming 2018-07-19 11:49:43 +03:00
yuri ad21511c72 email list is replied fix 2018-07-19 11:44:40 +03:00
yuri 938a24f102 fix assignment email notificaiotion 2018-07-19 10:59:32 +03:00
yuri 43b041ed6c fix replied icon 2018-07-18 15:37:37 +03:00
yuri 5e3f048795 replied email icon 2018-07-18 14:57:12 +03:00
yuri 3aec3ef6d2 formula higher 2018-07-18 11:37:55 +03:00
yuri 712a450734 formula mb string functions 2018-07-18 11:35:31 +03:00
yuri 5675b6721b sort by list comma fix 2018-07-18 11:25:58 +03:00
yuri a4e23b8adb formula parser fix 2018-07-18 10:29:06 +03:00
yuri 9d49f418c8 orm fix order list 2018-07-18 10:11:36 +03:00
yuri ea9f70dffa global search ability to select text 2018-07-17 17:26:06 +03:00
yuri b624accbe8 version 2018-07-17 17:07:50 +03:00
yuri 12f974635e global search fixes 2018-07-17 17:07:32 +03:00
yuri cfce68eb7e default panel complex fields 2018-07-17 15:38:44 +03:00
yuri db8fbd1ed3 hide default panel if empty 2018-07-17 14:39:54 +03:00
yuri 06395f3ff5 Merge branch 'hotfix/5.3.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.3 2018-07-16 15:48:49 +03:00
Taras Machyshyn 4065ec5477 Test fixes 2018-07-16 15:48:25 +03:00
yuri 31d45a0583 Merge branch 'hotfix/5.3.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.3 2018-07-16 14:20:29 +03:00
yuri 696faa4468 fix wysiwyg toolbar 2018-07-16 14:20:19 +03:00
Taras Machyshyn 625876f123 Merge branch 'hotfix/5.3.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.3 2018-07-16 14:06:54 +03:00
Taras Machyshyn 974cd05276 Integration test for fulltext index 2018-07-16 14:06:37 +03:00
yuri fb5db991a3 fix vars 2018-07-16 13:37:00 +03:00
yuri 3dafb7e922 fix categories 2018-07-16 13:12:09 +03:00
yuri ee640273a1 fix stream attachments 2018-07-16 10:51:55 +03:00
yuri b92de17f72 Merge branch 'hotfix/5.3.3' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.3 2018-07-13 16:55:22 +03:00
Taras Machyshyn abc48ca76f Fulltext bug fixes 2018-07-13 16:52:02 +03:00
yuri 46443aae7e fix full text search 2018-07-13 16:37:32 +03:00
yuri a88c3283d6 full text search fix 2018-07-13 16:33:19 +03:00
yuri 2b9653ee0b replace tabs 2018-07-13 14:53:37 +03:00
yuri 2cf79abdb1 fix list view remove 2018-07-13 14:49:56 +03:00
yuri 20a000a46c fix full text search 2018-07-13 12:33:21 +03:00
ayman-alkom 95bfd5bace fix typo (#968) 2018-07-13 11:51:34 +03:00
yuri da736008f1 version 2018-07-13 11:19:49 +03:00
yuri 1de98d9616 text filter fixes 2018-07-13 11:19:30 +03:00
yuri 31a6143cc3 email template text filter 2018-07-13 11:07:34 +03:00
yuri 8502a84a3f chart mousetick fix 2018-07-12 12:52:05 +03:00
yuri 2234678728 email search tests 2018-07-12 11:31:44 +03:00
yuri 92fedd2c2e Merge branch 'hotfix/5.3.2' of ssh://172.20.0.1/var/git/espo/backend 2018-07-12 10:09:31 +03:00
Taras Machyshyn 0ec450d59d Schema bug fixes 2018-07-12 10:09:01 +03:00
yuri f0a1634f90 version 2018-07-12 10:03:04 +03:00
Taras Machyshyn 5b5f5c8ab7 Schema bug fixes 2018-07-11 15:16:59 +03:00
yuri 19d37e8081 text filter disabling 2018-07-11 11:04:01 +03:00
yuri d8ab10fd75 textFilterDisabled param 2018-07-11 10:37:17 +03:00
yuri 312de11a15 smtp local host name 2018-07-10 15:53:15 +03:00
yuri 47a22042b2 fix select records 2 2018-07-10 13:13:07 +03:00
yuri 31cea9e36e fix select records 2018-07-10 12:26:37 +03:00
yuri 2b983ad880 fix attachment multiple 2018-07-10 12:11:45 +03:00
yuri 21b0c2b2eb attachment multiple in list view 2018-07-10 11:58:11 +03:00
yuri 59b4aa61e4 not sortable by default 2018-07-10 11:36:20 +03:00
yuri 25d91a1e73 Merge branch 'hotfix/5.3.1' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.3.1 2018-07-09 13:36:24 +03:00
yuri 6750d32bd6 skip printing x status reason for pdo exception 2018-07-09 13:30:36 +03:00
yuri 70b8fc9ac9 foreign field: supporting number field 2018-07-09 12:52:55 +03:00
yuri 38a1be5ae3 version 2018-07-09 12:04:26 +03:00
yuri 1d9e9752c8 mass remove memory usage 2018-07-09 12:03:45 +03:00
yuri eb37c1dc47 fix email text filter by email 2018-07-09 11:58:31 +03:00
yuri 1df6ec7f0c mass update improvement memory usage 2018-07-09 11:52:33 +03:00
yuri 1d6ee3e030 clean up 2018-07-09 11:37:39 +03:00
yuri 6eb80b747c login view name in clientDefs 2018-07-09 11:14:28 +03:00
yuri 539c0d22d7 convert lead skip disabled entity types 2018-07-09 10:54:29 +03:00
yuri 8e7d607ad4 fix dynamic logic not has 2018-07-09 10:49:44 +03:00
yuri 59e15eb71f fix select all results modal 2018-07-09 10:48:36 +03:00
Taras Machyshyn c1ba6d5330 Metadata improvements 2018-07-06 17:02:19 +03:00
155 changed files with 1984 additions and 719 deletions
+3 -3
View File
@@ -128,7 +128,7 @@ class Application
$slim->run();
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode(), true);
$container->get('output')->processError($e->getMessage(), $e->getCode(), true, $e);
}
}
@@ -177,7 +177,7 @@ class Application
try {
$auth = $this->createAuth();
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode());
$container->get('output')->processError($e->getMessage(), $e->getCode(), false, $e);
}
$apiAuth = $this->createApiAuth($auth);
@@ -227,7 +227,7 @@ class Application
$result = $controllerManager->process($controllerName, $actionName, $params, $data, $slim->request());
$container->get('output')->render($result);
} catch (\Exception $e) {
$container->get('output')->processError($e->getMessage(), $e->getCode());
$container->get('output')->processError($e->getMessage(), $e->getCode(), false, $e);
}
});
@@ -48,6 +48,6 @@ class ContainsType extends \Espo\Core\Formula\Functions\Base
return false;
}
return strpos($string, $needle) !== false;
return mb_strpos($string, $needle) !== false;
}
}
@@ -53,6 +53,6 @@ class LowerCaseType extends \Espo\Core\Formula\Functions\Base
$value = strval($value);
}
return strtolower($value);
return mb_strtolower($value);
}
}
@@ -1,4 +1,4 @@
<?php
<?php
/************************************************************************
* This file is part of EspoCRM.
*
@@ -52,9 +52,9 @@ class SubstringType extends \Espo\Core\Formula\Functions\Base
if (count($item->value) > 2) {
$length = $this->evaluate($item->value[2]);
return substr($string, $start, $length);
return mb_substr($string, $start, $length);
} else {
return substr($string, $start);
return mb_substr($string, $start);
}
}
}
@@ -53,6 +53,6 @@ class UpperCaseType extends \Espo\Core\Formula\Functions\Base
$value = strval($value);
}
return strtoupper($value);
return mb_strtoupper($value);
}
}
+14 -1
View File
@@ -176,6 +176,8 @@ class Parser
$this->processStrings($expression, $modifiedExpression, $splitterIndexList, true);
$expressionOutOfBraceList = [];
for ($i = 0; $i < strlen($modifiedExpression); $i++) {
if ($modifiedExpression[$i] === '(') {
$braceCounter++;
@@ -186,6 +188,11 @@ class Parser
if ($braceCounter === 0 && $i < strlen($modifiedExpression) - 1) {
$hasExcessBraces = false;
}
if ($braceCounter === 0) {
$expressionOutOfBraceList[] = true;
} else {
$expressionOutOfBraceList[] = false;
}
}
if ($braceCounter !== 0) {
throw new Error('Incorrect round brackets in expression ' . $expression . '.');
@@ -226,7 +233,13 @@ class Parser
foreach ($this->priorityList as $operationList) {
foreach ($operationList as $operator) {
$index = strpos($expression, $operator, 1);
$startFrom = 1;
while (true) {
$index = strpos($expression, $operator, $startFrom);
if ($index === false) break;
if ($expressionOutOfBraceList[$index]) break;
$startFrom = $index + 1;
}
if ($index !== false) {
$possibleRightOperator = null;
if (strlen($operator) === 1) {
+8 -2
View File
@@ -89,8 +89,12 @@ class Sender
$this->transport = new SmtpTransport();
$config = $this->config;
$localHostName = $config->get('smtpLocalHostName', gethostname());
$opts = array(
'name' => 'admin',
'name' => $localHostName,
'host' => $params['server'],
'port' => $params['port'],
'connection_config' => array()
@@ -132,8 +136,10 @@ class Sender
$config = $this->config;
$localHostName = $config->get('smtpLocalHostName', gethostname());
$opts = array(
'name' => 'admin',
'name' => $localHostName,
'host' => $config->get('smtpServer'),
'port' => $config->get('smtpPort'),
'connection_config' => array()
+8 -2
View File
@@ -137,7 +137,7 @@ class Entity extends \Espo\ORM\Entity
}
$this->set($idsAttribute, $ids);
if (!$this->hasFetched($idsAttribute)) {
if (!$this->isNew() && !$this->hasFetched($idsAttribute)) {
$this->setFetched($idsAttribute, $ids);
}
@@ -170,7 +170,13 @@ class Entity extends \Espo\ORM\Entity
$entityName = $entity->get('name');
}
$this->set($field . 'Id', $entityId);
$idAttribute = $field . 'Id';
if (!$this->isNew() && !$this->hasFetched($idAttribute)) {
$this->setFetched($idAttribute, $entityId);
}
$this->set($idAttribute, $entityId);
$this->set($field . 'Name', $entityName);
}
+16 -8
View File
@@ -465,17 +465,21 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$data->$columnName = $foreignEntity->get($columnField);
}
$existingColumnsData->$foreignId = $data;
$entity->setFetched($columnsFieldsName, $existingColumnsData);
if (!$entity->isNew()) {
$entity->setFetched($columnsFieldsName, $existingColumnsData);
}
}
}
}
if ($entity->has($fieldName)) {
$entity->setFetched($fieldName, $existingIds);
}
if ($entity->has($columnsFieldsName) && !empty($columns)) {
$entity->setFetched($columnsFieldsName, $existingColumnsData);
if (!$entity->isNew()) {
if ($entity->has($fieldName)) {
$entity->setFetched($fieldName, $existingIds);
}
if ($entity->has($columnsFieldsName) && !empty($columns)) {
$entity->setFetched($columnsFieldsName, $existingColumnsData);
}
}
foreach ($existingIds as $id) {
@@ -540,13 +544,17 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
$where[$foreignKey] = $entity->id;
$previousForeignEntity = $this->getEntityManager()->getRepository($foreignEntityType)->where($where)->findOne();
if ($previousForeignEntity) {
$entity->setFetched($idFieldName, $previousForeignEntity->id);
if (!$entity->isNew()) {
$entity->setFetched($idFieldName, $previousForeignEntity->id);
}
if ($previousForeignEntity->id !== $entity->get($idFieldName)) {
$previousForeignEntity->set($foreignKey, null);
$this->getEntityManager()->saveEntity($previousForeignEntity);
}
} else {
$entity->setFetched($idFieldName, null);
if (!$entity->isNew()) {
$entity->setFetched($idFieldName, null);
}
}
if ($entity->get($idFieldName)) {
+56 -11
View File
@@ -161,6 +161,9 @@ class Base
if ($desc) {
$list = array_reverse($list);
}
foreach ($list as $i => $listItem) {
$list[$i] = str_replace(',', '_COMMA_', $listItem);
}
$result['orderBy'] = 'LIST:' . $sortBy . ':' . implode(',', $list);
return;
}
@@ -392,7 +395,8 @@ class Base
protected function q($params, &$result)
{
if (isset($params['q']) && $params['q'] !== '') {
$this->textFilter($params['q'], $result);
$textFilter = $params['q'];
$this->textFilter($textFilter, $result);
}
}
@@ -1251,7 +1255,7 @@ class Base
$value = $item['value'];
if (is_null($value)) break;
if (is_null($value) || !$value && !is_array($value)) break;
$relationType = $seed->getRelationType($link);
@@ -1535,7 +1539,8 @@ class Base
if (!$fullTextSearchMinLength) {
$fullTextSearchMinLength = 0;
}
if (mb_strlen($textFilter) >= $fullTextSearchMinLength) {
$textFilterWoWildcards = str_replace('*', '', $textFilter);
if (mb_strlen($textFilterWoWildcards) >= $fullTextSearchMinLength) {
$useFullTextSearch = true;
}
}
@@ -1560,9 +1565,17 @@ class Base
$useFullTextSearch = false;
}
if ($isAuxiliaryUse) {
if (mb_strpos($textFilter, '@') !== false) {
$useFullTextSearch = false;
}
}
if ($useFullTextSearch) {
$textFilter = str_replace(['(', ')'], '', $textFilter);
if (
$isAuxiliaryUse
$isAuxiliaryUse && mb_strpos($textFilter, '*') === false
||
mb_strpos($textFilter, ' ') === false
&&
@@ -1577,6 +1590,19 @@ class Base
$function = 'MATCH_BOOLEAN';
}
$textFilter = str_replace('"*', '"', $textFilter);
$textFilter = str_replace('*"', '"', $textFilter);
while (strpos($textFilter, '**')) {
$textFilter = str_replace('**', '*', $textFilter);
$textFilter = trim($textFilter);
}
while (mb_substr($textFilter, -2) === ' *') {
$textFilter = mb_substr($textFilter, 0, mb_strlen($textFilter) - 2);
$textFilter = trim($textFilter);
}
$fullTextSearchColumnSanitizedList = [];
$query = $this->getEntityManager()->getQuery();
foreach ($fullTextSearchColumnList as $i => $field) {
@@ -1617,15 +1643,34 @@ class Base
$forceFullTextSearch = true;
}
$textFilterForFullTextSearch = $textFilter;
$skipWidlcards = false;
if (!$useFullTextSearch) {
if (mb_strpos($textFilter, '*') !== false) {
$skipWidlcards = true;
$textFilter = str_replace('*', '%', $textFilter);
if (mb_strpos($textFilter, '*') !== false) {
$skipWidlcards = true;
$textFilter = str_replace('*', '%', $textFilter);
} else {
if (!$useFullTextSearch) {
$textFilterForFullTextSearch .= '*';
}
}
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilter, !$useFullTextSearch);
$textFilterForFullTextSearch = str_replace('%', '*', $textFilterForFullTextSearch);
$skipFullTextSearch = false;
if (!$forceFullTextSearch) {
if (mb_strpos($textFilterForFullTextSearch, '*') === 0) {
$skipFullTextSearch = true;
} else if (mb_strpos($textFilterForFullTextSearch, ' *') !== false) {
$skipFullTextSearch = true;
}
}
$fullTextSearchData = null;
if (!$skipFullTextSearch) {
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilterForFullTextSearch, !$useFullTextSearch);
}
$fullTextGroup = [];
@@ -1689,7 +1734,7 @@ class Base
}
if (!$forceFullTextSearch) {
$this->applyAdditionalToTextFilterGroup($textFilter, $group);
$this->applyAdditionalToTextFilterGroup($textFilter, $group, $result);
}
if (!empty($fullTextGroup)) {
@@ -1707,7 +1752,7 @@ class Base
];
}
protected function applyAdditionalToTextFilterGroup($textFilter, &$group)
protected function applyAdditionalToTextFilterGroup($textFilter, &$group, &$result)
{
}
@@ -631,7 +631,13 @@ abstract class Base
protected function systemRebuild()
{
return $this->getContainer()->get('dataManager')->rebuild();
try {
return $this->getContainer()->get('dataManager')->rebuild();
} catch (\Exception $e) {
$GLOBALS['log']->error('Database rebuild failure, details: '.$e->getMessage().'.');
}
return false;
}
/**
+13 -4
View File
@@ -42,6 +42,9 @@ class Output
500 => 'Internal Server Error',
);
protected $ignorePrintXStatusReasonExceptionClassNameList = [
'PDOException'
];
public function __construct(\Espo\Core\Utils\Api\Slim $slim)
{
@@ -69,7 +72,7 @@ class Output
echo $data;
}
public function processError($message = 'Error', $code = 500, $isPrint = false)
public function processError($message = 'Error', $code = 500, $isPrint = false, $exception = null)
{
$currentRoute = $this->getSlim()->router()->getCurrentRoute();
@@ -79,7 +82,7 @@ class Output
$GLOBALS['log']->error('API ['.$this->getSlim()->request()->getMethod().']:'.$currentRoute->getPattern().', Params:'.print_r($currentRoute->getParams(), true).', InputData: '.$inputData.' - '.$message);
}
$this->displayError($message, $code, $isPrint);
$this->displayError($message, $code, $isPrint, $exception);
}
/**
@@ -90,15 +93,21 @@ class Output
*
* @return void
*/
public function displayError($text, $statusCode = 500, $isPrint = false)
public function displayError($text, $statusCode = 500, $isPrint = false, $exception = null)
{
$GLOBALS['log']->error('Display Error: '.$text.', Code: '.$statusCode.' URL: '.$_SERVER['REQUEST_URI']);
ob_clean();
if (!empty( $this->slim)) {
$toPrintXStatusReason = true;
if ($exception && in_array(get_class($exception), $this->ignorePrintXStatusReasonExceptionClassNameList)) {
$toPrintXStatusReason = false;
}
$this->getSlim()->response()->setStatus($statusCode);
$this->getSlim()->response()->headers->set('X-Status-Reason', $text);
if ($toPrintXStatusReason) {
$this->getSlim()->response()->headers->set('X-Status-Reason', $text);
}
if ($isPrint) {
$status = $this->getCodeDesc($statusCode);
@@ -25,16 +25,19 @@
*
* 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\Utils\Database\DBAL\Driver\PDOMySql;
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
{
public function getDatabasePlatform()
{
return new \Espo\Core\Utils\Database\DBAL\Platforms\MySqlPlatform();
}
public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
{
return new \Espo\Core\Utils\Database\DBAL\Schema\MySqlSchemaManager($conn);
}
}
@@ -452,5 +452,17 @@ class MySqlPlatform extends \Doctrine\DBAL\Platforms\MySqlPlatform
return 'MEDIUMTEXT';
}
public function getColumnDeclarationListSQL(array $fields)
{
$queryFields = array();
foreach ($fields as $fieldName => $field) {
$quotedFieldName = $this->espoQuote($fieldName);
$queryFields[] = $this->getColumnDeclarationSQL($quotedFieldName, $field);
}
return implode(', ', $queryFields);
}
//end: ESPO
}
@@ -0,0 +1,82 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Database\DBAL\Schema;
use Doctrine\DBAL\Schema\Index as DBALIndex;
class Index extends \Doctrine\DBAL\Schema\Index
{
public function addFlag($flag)
{
$this->_flags[strtolower($flag)] = true;
return $this;
}
public function hasFlag($flag)
{
return isset($this->_flags[strtolower($flag)]);
}
public function removeFlag($flag)
{
unset($this->_flags[strtolower($flag)]);
}
public function isFullfilledBy(DBALIndex $other)
{
if (count($other->getColumns()) != count($this->getColumns())) {
return false;
}
$sameColumns = $this->spansColumns($other->getColumns());
if ($sameColumns) {
$flags = $this->getFlags();
$otherFlags = $other->getFlags();
if ( ! $this->isUnique() && !$this->isPrimary() && $flags === $otherFlags) {
return true;
} else if ($other->isPrimary() != $this->isPrimary()) {
return false;
} else if ($other->isUnique() != $this->isUnique()) {
return false;
}
if (count($flags) != count($otherFlags) || array_diff($flags, $otherFlags) !== array_diff($otherFlags, $flags)) {
return false;
}
return true;
}
return false;
}
}
@@ -0,0 +1,144 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: http://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Utils\Database\DBAL\Schema;
use Doctrine\DBAL\Events;
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
class MySqlSchemaManager extends \Doctrine\DBAL\Schema\MySqlSchemaManager
{
public function createSchema()
{
$sequences = array();
if ($this->_platform->supportsSequences()) {
$sequences = $this->listSequences();
}
$tables = $this->listTables();
return new Schema($tables, $sequences, $this->createSchemaConfig());
}
public function listTables()
{
$tableNames = $this->listTableNames();
$tables = array();
foreach ($tableNames as $tableName) {
$tables[] = $this->listTableDetails($tableName);
}
return $tables;
}
public function listTableDetails($tableName)
{
$columns = $this->listTableColumns($tableName);
$foreignKeys = array();
if ($this->_platform->supportsForeignKeyConstraints()) {
$foreignKeys = $this->listTableForeignKeys($tableName);
}
$indexes = $this->listTableIndexes($tableName);
return new Table($tableName, $columns, $indexes, $foreignKeys, false, array());
}
public function listTableIndexes($table)
{
$sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
$tableIndexes = $this->_conn->fetchAll($sql);
return $this->_getPortableTableIndexesList($tableIndexes, $table);
}
protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
{
foreach($tableIndexes as $k => $v) {
$v = array_change_key_case($v, CASE_LOWER);
if($v['key_name'] == 'PRIMARY') {
$v['primary'] = true;
} else {
$v['primary'] = false;
}
if (strpos($v['index_type'], 'FULLTEXT') !== false) {
$v['flags'] = array('FULLTEXT');
}
$tableIndexes[$k] = $v;
}
$result = array();
foreach($tableIndexes as $tableIndex) {
$indexName = $keyName = $tableIndex['key_name'];
if ($tableIndex['primary']) {
$keyName = 'primary';
}
$keyName = strtolower($keyName);
if (!isset($result[$keyName])) {
$result[$keyName] = array(
'name' => $indexName,
'columns' => array($tableIndex['column_name']),
'unique' => $tableIndex['non_unique'] ? false : true,
'primary' => $tableIndex['primary'],
'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
);
} else {
$result[$keyName]['columns'][] = $tableIndex['column_name'];
}
}
$eventManager = $this->_platform->getEventManager();
$indexes = array();
foreach($result as $indexKey => $data) {
$index = null;
$defaultPrevented = false;
if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) {
$eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn);
$eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs);
$defaultPrevented = $eventArgs->isDefaultPrevented();
$index = $eventArgs->getIndex();
}
if ( ! $defaultPrevented) {
$index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags']);
}
if ($index) {
$indexes[$indexKey] = $index;
}
}
return $indexes;
}
}
@@ -28,9 +28,9 @@
************************************************************************/
namespace Espo\Core\Utils\Database\DBAL\Schema;
class Schema extends \Doctrine\DBAL\Schema\Schema
{
/**
* Creates a new table.
*
@@ -28,11 +28,12 @@
************************************************************************/
namespace Espo\Core\Utils\Database\DBAL\Schema;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Schema\SchemaException;
class Table extends \Doctrine\DBAL\Schema\Table
{
/**
* @param string $columnName
* @param string $typeName
@@ -49,4 +50,35 @@ class Table extends \Doctrine\DBAL\Schema\Table
return $column;
}
public function addIndex(array $columnNames, $indexName = null, array $flags = array())
{
if($indexName == null) {
$indexName = $this->_generateIdentifierName(
array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
);
}
return $this->_createIndex($columnNames, $indexName, false, false, $flags);
}
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array())
{
if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
throw SchemaException::indexNameInvalid($indexName);
}
foreach ($columnNames as $columnName => $indexColOptions) {
if (is_numeric($columnName) && is_string($indexColOptions)) {
$columnName = $indexColOptions;
}
if ( ! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
}
}
$this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags));
return $this;
}
}
@@ -40,7 +40,8 @@ class AttachmentMultiple extends Base
'type' => 'jsonArray',
'notStorable' => true,
'orderBy' => [['createdAt', 'ASC'], ['name', 'ASC']],
'isLinkMultipleIdList' => true
'isLinkMultipleIdList' => true,
'relation' => $fieldName
),
$fieldName.'Names' => array(
'type' => 'jsonObject',
@@ -33,28 +33,29 @@ class LinkMultiple extends Base
{
protected function load($fieldName, $entityName)
{
$data = array(
$entityName => array (
'fields' => array(
$fieldName.'Ids' => array(
$data = [
$entityName => [
'fields' => [
$fieldName.'Ids' => [
'type' => 'jsonArray',
'notStorable' => true,
'isLinkMultipleIdList' => true,
'relation' => $fieldName
),
$fieldName.'Names' => array(
'relation' => $fieldName,
'isUnordered' => true
],
$fieldName.'Names' => [
'type' => 'jsonObject',
'notStorable' => true,
'isLinkMultipleNameMap' => true
)
)
),
'unset' => array(
$entityName => array(
'fields.'.$fieldName
)
)
);
]
]
],
'unset' => [
$entityName => [
'fields.' . $fieldName
]
]
];
$fieldParams = $this->getFieldParams();
@@ -67,10 +68,10 @@ class LinkMultiple extends Base
$columns = $this->getMetadata()->get("entityDefs.{$entityName}.fields.{$fieldName}.columns");
if (!empty($columns)) {
$data[$entityName]['fields'][$fieldName . 'Columns'] = array(
$data[$entityName]['fields'][$fieldName . 'Columns'] = [
'type' => 'jsonObject',
'notStorable' => true,
);
];
}
return $data;
@@ -181,7 +181,7 @@ class Converter
$schema = $this->getSchema(true);
$indexList = SchemaUtils::getIndexList($ormMeta);
$fieldListExceededIndexMaxLength = SchemaUtils::getFieldListExceededIndexMaxLength($ormMeta, $this->getMaxIndexLength(), $indexList);
$fieldListExceededIndexMaxLength = SchemaUtils::getFieldListExceededIndexMaxLength($ormMeta, $this->getMaxIndexLength());
$tables = array();
foreach ($ormMeta as $entityName => $entityParams) {
@@ -110,7 +110,7 @@ class Utils
$fields = array();
if (!isset($indexList)) {
$indexList = static::getIndexList($ormMeta);
$indexList = static::getIndexList($ormMeta, ['fulltext']);
}
foreach ($indexList as $entityName => $indexes) {
@@ -662,7 +662,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
@@ -727,7 +726,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
@@ -751,7 +749,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
@@ -774,7 +771,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
@@ -848,7 +844,6 @@ class EntityManager
$link => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleField,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleField,
"noLoad" => !$linkMultipleField,
"importDisabled" => !$linkMultipleField,
@@ -873,7 +868,6 @@ class EntityManager
$linkForeign => array(
"type" => "linkMultiple",
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
"layoutListDisabled" => true,
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
"noLoad" => !$linkMultipleFieldForeign,
"importDisabled" => !$linkMultipleFieldForeign,
+16
View File
@@ -358,6 +358,14 @@ class Metadata
*/
public function saveCustom($key1, $key2, $data)
{
if (is_object($data)) {
foreach ($data as $key => $item) {
if ($item == new \stdClass()) {
unset($data->$key);
}
}
}
$filePath = array($this->paths['customPath'], $key1, $key2.'.json');
$changedData = Json::encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
@@ -380,6 +388,14 @@ class Metadata
*/
public function set($key1, $key2, $data)
{
if (is_array($data)) {
foreach ($data as $key => $item) {
if (is_array($item) && empty($item)) {
unset($data[$key]);
}
}
}
$newData = array(
$key1 => array(
$key2 => $data,
+4 -3
View File
@@ -129,7 +129,7 @@ class Image extends \Espo\Core\EntryPoints\Base
}
if (!empty($size)) {
$fileName = $sourceId . '_' . $size . '.jpg';
$fileName = $size . '-' . $attachment->get('name');
} else {
$fileName = $attachment->get('name');
}
@@ -197,9 +197,10 @@ class Image extends \Espo\Core\EntryPoints\Base
break;
}
$targetImage = imagerotate($targetImage, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filePath)['Orientation'] ?: 0], 0);
if (function_exists('exif_read_data')) {
$targetImage = imagerotate($targetImage, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filePath)['Orientation'] ?: 0], 0);
}
return $targetImage;
}
}
@@ -58,18 +58,32 @@ class AssignmentEmailNotification extends \Espo\Core\Hooks\Base
foreach ($userIdList as $userId) {
if (in_array($userId, $fetchedAssignedUserIdList)) continue;
if ($this->getUser()->id === $userId) continue;
if (!$this->isNotSelfAssignment($entity, $userId)) continue;
$this->createJob($entity, $userId);
}
} else {
$userId = $entity->get('assignedUserId');
if (!empty($userId) && $userId != $this->getUser()->id && $entity->isAttributeChanged('assignedUserId')) {
if (!empty($userId) && $entity->isAttributeChanged('assignedUserId') && $this->isNotSelfAssignment($entity, $userId)) {
$this->createJob($entity, $userId);
}
}
}
}
protected function isNotSelfAssignment(Entity $entity, $assignedUserId)
{
if ($entity->hasAttribute('createdById') && $entity->hasAttribute('modifiedById')) {
if ($entity->isNew()) {
$isNotSelfAssignment = $assignedUserId !== $entity->get('createdById');
} else {
$isNotSelfAssignment = $assignedUserId !== $entity->get('modifiedById');
}
} else {
$isNotSelfAssignment = $assignedUserId !== $this->getUser()->id;
}
return $isNotSelfAssignment;
}
protected function createJob(Entity $entity, $userId)
{
$job = $this->getEntityManager()->getEntity('Job');
@@ -82,8 +82,7 @@
"view": "views/fields/teams"
},
"attachments": {
"type": "attachmentMultiple",
"layoutListDisabled": true
"type": "attachmentMultiple"
}
},
"links": {
@@ -66,7 +66,8 @@
"distinct": true
}
},
"trim": true
"trim": true,
"textFilterDisabled": true
},
"description": {
"type": "text"
@@ -130,7 +131,8 @@
"layoutFiltersDisabled": true,
"exportDisabled": true,
"importDisabled": true,
"view": "crm:views/contact/fields/account-role"
"view": "crm:views/contact/fields/account-role",
"textFilterDisabled": true
},
"accountIsInactive": {
"type": "bool",
@@ -34,7 +34,8 @@
},
"order": {
"type": "int",
"disableFormatting": true
"disableFormatting": true,
"textFilterDisabled": true
},
"description": {
"type": "text"
@@ -27,7 +27,8 @@
"order": {
"type": "int",
"required": true,
"disableFormatting": true
"disableFormatting": true,
"textFilterDisabled": true
},
"teams": {
"type": "linkMultiple",
@@ -99,8 +99,7 @@
},
"attachments": {
"type": "attachmentMultiple",
"sourceList": ["Document"],
"layoutListDisabled": true
"sourceList": ["Document"]
}
},
"links": {
@@ -1,22 +1,21 @@
[
{
"route":"/Activities/:scope/:id/:name",
"method":"get",
"params":{
"controller":"Activities",
"action":"list",
"scope":":scope",
"id":":id",
"name":":name"
{
"route": "/Activities/:scope/:id/:name",
"method": "get",
"params": {
"controller": "Activities",
"action": "list",
"scope": ":scope",
"id": ":id",
"name": ":name"
}
},
{
"route": "/Activities",
"method": "get",
"params": {
"controller": "Activities",
"action": "listCalendarEvents"
}
}
},
{
"route":"/Activities",
"method":"get",
"params":{
"controller":"Activities",
"action":"listCalendarEvents"
}
}
]
@@ -33,19 +33,23 @@ class CampaignLogRecord extends \Espo\Core\SelectManagers\Base
{
protected function accessOnlyOwn(&$result)
{
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
$result['whereClause'][] = array(
'campaign.assignedUserId' => $this->getUser()->id
'campaignAccess.assignedUserId' => $this->getUser()->id
);
}
protected function accessOnlyTeam(&$result)
{
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
$teamIdList = $this->user->get('teamsIds');
if (empty($result['customWhere'])) {
$result['customWhere'] = '';
}
if (empty($teamIdList)) {
$result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
$result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
return;
}
$arr = [];
@@ -55,12 +59,12 @@ class CampaignLogRecord extends \Espo\Core\SelectManagers\Base
}
}
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
$result['customWhere'] .= "
AND (
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
OR
campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
)
";
$result['whereClause'][] = array(
@@ -33,19 +33,23 @@ class CampaignTrackingUrl extends \Espo\Core\SelectManagers\Base
{
protected function accessOnlyOwn(&$result)
{
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
$result['whereClause'][] = array(
'campaign.assignedUserId' => $this->getUser()->id
'campaignAccess.assignedUserId' => $this->getUser()->id
);
}
protected function accessOnlyTeam(&$result)
{
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
$teamIdList = $this->user->get('teamsIds');
if (empty($result['customWhere'])) {
$result['customWhere'] = '';
}
if (empty($teamIdList)) {
$result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
$result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
return;
}
$arr = [];
@@ -55,12 +59,12 @@ class CampaignTrackingUrl extends \Espo\Core\SelectManagers\Base
}
}
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
$result['customWhere'] .= "
AND (
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
OR
campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
)
";
$result['whereClause'][] = array(
@@ -45,21 +45,25 @@ class MassEmail extends \Espo\Core\SelectManagers\Base
);
}
protected function acessOnlyOwn(&$result)
protected function accessOnlyOwn(&$result)
{
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
$result['whereClause'][] = array(
'campaign.assignedUserId' => $this->getUser()->id
'campaignAccess.assignedUserId' => $this->getUser()->id
);
}
protected function accessOnlyTeam(&$result)
{
$teamIdList = $this->user->get('teamsIds');
$this->addLeftJoin(['campaign', 'campaignAccess'], $result);
$teamIdList = $this->user->getLinkMultipleIdList('teams');
if (empty($result['customWhere'])) {
$result['customWhere'] = '';
}
if (empty($teamIdList)) {
$result['customWhere'] .= " AND campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
$result['customWhere'] .= " AND campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id);
return;
}
$arr = [];
@@ -69,12 +73,12 @@ class MassEmail extends \Espo\Core\SelectManagers\Base
}
}
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaign.id AND teamsMiddle.deleted = 0";
$result['customJoin'] .= " LEFT JOIN entity_team AS teamsMiddle ON teamsMiddle.entity_type = 'Campaign' AND teamsMiddle.entity_id = campaignAccess.id AND teamsMiddle.deleted = 0";
$result['customWhere'] .= "
AND (
teamsMiddle.team_id IN (" . implode(', ', $arr) . ")
OR
campaign.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
campaignAccess.assigned_user_id = ".$this->getEntityManager()->getPDO()->quote($this->getUser()->id)."
)
";
$result['whereClause'][] = array(
@@ -139,12 +139,14 @@ class Campaign extends \Espo\Services\Record
$sth = $pdo->prepare($sql);
$sth->execute();
$revenue = null;
if ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
$revenue = floatval($row['SUM:amountConverted']);
if ($revenue > 0) {
$entity->set('revenue', $revenue);
if (!$revenue) {
$revenue = null;
}
}
$entity->set('revenue', $revenue);
}
public function logSent($campaignId, $queueItemId = null, Entity $target, Entity $emailOrEmailTemplate = null, $emailAddress, $actionDate = null, $isTest = false)
+25 -19
View File
@@ -718,7 +718,7 @@ abstract class Mapper implements IMapper
public function insert(IEntity $entity)
{
$dataArr = $this->toArray($entity);
$dataArr = $this->toValueMap($entity);
$fieldArr = array();
$valArr = array();
@@ -745,33 +745,35 @@ abstract class Mapper implements IMapper
public function update(IEntity $entity)
{
$dataArr = $this->toArray($entity);
$valueMap = $this->toValueMap($entity);
$setArr = array();
foreach ($dataArr as $field => $value) {
if ($field == 'id') {
$setArr = [];
foreach ($valueMap as $attribute => $value) {
if ($attribute == 'id') {
continue;
}
$type = $entity->fields[$field]['type'];
$type = $entity->getAttributeType($attribute);
if ($type == IEntity::FOREIGN) {
continue;
}
if ($entity->getFetched($field) === $value && $type != IEntity::JSON_ARRAY && $type != IEntity::JSON_OBJECT) {
if (!$entity->isAttributeChanged($attribute) && $type !== IEntity::JSON_OBJECT) {
continue;
}
$value = $this->prepareValueForInsert($type, $value);
$setArr[] = "`" . $this->toDb($field) . "` = " . $this->quote($value);
$setArr[] = "`" . $this->toDb($attribute) . "` = " . $this->quote($value);
}
if (count($setArr) == 0) {
return $entity->id;
}
$setPart = implode(', ', $setArr);
$wherePart = $this->query->getWhere($entity, array('id' => $entity->id, 'deleted' => 0));
$wherePart = $this->query->getWhere($entity, ['id' => $entity->id, 'deleted' => 0]);
$sql = $this->composeUpdateQuery($this->toDb($entity->getEntityType()), $setPart, $wherePart);
@@ -815,21 +817,25 @@ abstract class Mapper implements IMapper
return $this->update($entity);
}
protected function toArray(IEntity $entity, $onlyStorable = true)
protected function toValueMap(IEntity $entity, $onlyStorable = true)
{
$arr = array();
foreach ($entity->fields as $field => $fieldDefs) {
if ($entity->has($field)) {
$data = [];
foreach ($entity->getAttributes() as $attribute => $defs) {
if ($entity->has($attribute)) {
if ($onlyStorable) {
if (!empty($fieldDefs['notStorable']) || !empty($fieldDefs['autoincrement']) || isset($fieldDefs['source']) && $fieldDefs['source'] != 'db')
continue;
if ($fieldDefs['type'] == IEntity::FOREIGN)
continue;
if (
!empty($defs['notStorable'])
||
!empty($defs['autoincrement'])
||
isset($defs['source']) && $defs['source'] != 'db'
) continue;
if ($defs['type'] == IEntity::FOREIGN) continue;
}
$arr[$field] = $entity->get($field);
$data[$attribute] = $entity->get($attribute);
}
}
return $arr;
return $data;
}
protected function fromRow(IEntity $entity, $data)
+7 -1
View File
@@ -576,7 +576,13 @@ abstract class Base
if (strpos($orderBy, 'LIST:') === 0) {
list($l, $field, $list) = explode(':', $orderBy);
$fieldPath = $this->getFieldPathForOrderBy($entity, $field);
$part = "FIELD(" . $fieldPath . ", '" . implode("', '", array_reverse(explode(",", $list))) . "') DESC";
$listQuoted = [];
$list = array_reverse(explode(',', $list));
foreach ($list as $i => $listItem) {
$listItem = str_replace('_COMMA_', ',', $listItem);
$listQuoted[] = $this->quote($listItem);
}
$part = "FIELD(" . $fieldPath . ", " . implode(", ", $listQuoted) . ") DESC";
return $part;
}
+76 -9
View File
@@ -369,25 +369,92 @@ abstract class Entity implements IEntity
return $this->isFetched;
}
public function isFieldChanged($fieldName)
public function isFieldChanged($name)
{
return $this->has($fieldName) && ($this->get($fieldName) != $this->getFetched($fieldName));
return $this->has($name) && ($this->get($name) != $this->getFetched($name));
}
public function isAttributeChanged($fieldName)
public function isAttributeChanged($name)
{
return $this->has($fieldName) && ($this->get($fieldName) != $this->getFetched($fieldName));
if (!$this->has($name)) return false;
if (!$this->hasFetched($name)) {
return true;
}
return !self::areValuesEqual(
$this->getAttributeType($name),
$this->get($name),
$this->getFetched($name),
$this->getAttributeParam($name, 'isUnordered')
);
return $this->get($name) != $this->getFetched($name);
}
public function setFetched($fieldName, $value)
public static function areValuesEqual($type, $v1, $v2, $isUnordered = false)
{
$this->fetchedValuesContainer[$fieldName] = $value;
if ($type === self::JSON_ARRAY) {
if (is_array($v1) && is_array($v2)) {
if ($isUnordered) {
sort($v1);
sort($v2);
}
if ($v1 != $v2) {
return false;
}
foreach ($v1 as $i => $itemValue) {
if (is_object($v1[$i]) && is_object($v2[$i])) {
if (!self::areValuesEqual(self::JSON_OBJECT, $v1[$i], $v2[$i])) {
return false;
}
continue;
}
if ($v1[$i] !== $v2[$i]) {
return false;
}
}
return true;
}
} else if ($type === self::JSON_OBJECT) {
if (is_object($v1) && is_object($v2)) {
if ($v1 != $v2) {
return false;
}
$a1 = get_object_vars($v1);
$a2 = get_object_vars($v2);
foreach ($v1 as $key => $itemValue) {
if (is_object($a1[$key]) && is_object($a2[$key])) {
if (!self::areValuesEqual(self::JSON_OBJECT, $a1[$key], $a2[$key])) {
return false;
}
continue;
}
if (is_array($a1[$key]) && is_array($a2[$key])) {
if (!self::areValuesEqual(self::JSON_ARRAY, $a1[$key], $a2[$key])) {
return false;
}
continue;
}
if ($a1[$key] !== $a2[$key]) {
return false;
}
}
return true;
}
}
return $v1 === $v2;
}
public function getFetched($fieldName)
public function setFetched($name, $value)
{
if (isset($this->fetchedValuesContainer[$fieldName])) {
return $this->fetchedValuesContainer[$fieldName];
$this->fetchedValuesContainer[$name] = $value;
}
public function getFetched($name)
{
if (isset($this->fetchedValuesContainer[$name])) {
return $this->fetchedValuesContainer[$name];
}
return null;
}
@@ -31,33 +31,39 @@
"type": "varchar",
"notStorable": true,
"required": true,
"view": "views/email/fields/from-address-varchar"
"view": "views/email/fields/from-address-varchar",
"textFilterDisabled": true
},
"to": {
"type": "varchar",
"notStorable": true,
"required": true,
"view": "views/email/fields/email-address-varchar"
"view": "views/email/fields/email-address-varchar",
"textFilterDisabled": true
},
"cc": {
"type": "varchar",
"notStorable": true,
"view": "views/email/fields/email-address-varchar"
"view": "views/email/fields/email-address-varchar",
"textFilterDisabled": true
},
"bcc": {
"type": "varchar",
"notStorable": true,
"view": "views/email/fields/email-address-varchar"
"view": "views/email/fields/email-address-varchar",
"textFilterDisabled": true
},
"replyTo": {
"type": "varchar",
"notStorable": true,
"view": "views/email/fields/email-address-varchar"
"view": "views/email/fields/email-address-varchar",
"textFilterDisabled": true
},
"personStringData": {
"type": "varchar",
"notStorable": true,
"disabled": true
"disabled": true,
"view": "views/email/fields/person-string-data"
},
"isRead": {
"type": "bool",
@@ -98,7 +104,8 @@
"folderId": {
"type": "varchar",
"notStorable": true,
"default": false
"default": false,
"textFilterDisabled": true
},
"isUsers": {
"type": "bool",
@@ -137,7 +144,8 @@
"messageIdInternal": {
"type": "varchar",
"maxLength": 300,
"readOnly": true
"readOnly": true,
"textFilterDisabled": true
},
"emailAddress": {
"type": "base",
@@ -146,7 +154,8 @@
},
"fromEmailAddress": {
"type": "link",
"view": "views/email/fields/from-email-address"
"view": "views/email/fields/from-email-address",
"textFilterDisabled": true
},
"toEmailAddresses": {
"type": "linkMultiple"
@@ -85,6 +85,7 @@
},
"collection": {
"sortBy": "createdAt",
"asc": false
"asc": false,
"textFilterFields": ["name", "bodyPlain", "body", "subject"]
}
}
@@ -8,7 +8,8 @@
"order": {
"type": "int",
"minValue": 1,
"required": true
"required": true,
"textFilterDisabled": true
},
"description": {
"type": "text"
File diff suppressed because one or more lines are too long
@@ -96,7 +96,8 @@
},
"trim": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true
"layoutListDisabled": true,
"textFilterDisabled": true
},
"emailAddress": {
"type": "email",
@@ -31,6 +31,7 @@
],
"filter": true,
"notCreatable": false,
"notSortable": true,
"fieldDefs":{
"type":"jsonArray"
},
@@ -15,6 +15,12 @@
"type": "float",
"tooltip": true,
"min": 0
},
{
"name": "previewSize",
"type": "enum",
"default": "medium",
"options": ["", "x-small", "small", "medium", "large"]
}
],
"actualFields":[
@@ -30,10 +36,8 @@
"layoutRelationshipsDisabled": true,
"relationName": "attachments"
},
"notSortable": true,
"filter": true,
"fieldDefs": {
"layoutListDisabled": true
},
"hookClassName": "\\Espo\\Core\\Utils\\FieldManager\\Hooks\\AttachmentMultipleType",
"personalData": true
}
@@ -13,7 +13,7 @@
"required": true
}
],
"fieldTypeList": ["varchar", "enum", "enumInt", "enumFloat", "int", "float", "website", "date", "datetime", "text"],
"fieldTypeList": ["varchar", "enum", "enumInt", "enumFloat", "int", "float", "website", "date", "datetime", "text", "number"],
"filter": true,
"notCreatable": false,
"fieldDefs": {
@@ -1,5 +1,6 @@
{
"notCreatable": true,
"notMergeable": true,
"notSortable": true,
"filter": false
}
@@ -23,8 +23,6 @@
"names"
],
"notCreatable": true,
"filter": true,
"fieldDefs": {
"layoutListDisabled": true
}
"notSortable": true,
"filter": true
}
@@ -14,6 +14,7 @@
],
"filter": false,
"notCreatable": true,
"notSortable": true,
"fieldDefs": {
"notStorable": true
}
@@ -26,6 +26,7 @@
],
"filter": true,
"notCreatable": false,
"notSortable": true,
"fieldDefs":{
"type":"jsonArray"
},
@@ -6,6 +6,7 @@
"default":false
}
],
"notSortable": true,
"notCreatable": true,
"filter": false
}
+213 -206
View File
@@ -1,319 +1,326 @@
[
{
"route":"/",
"method":"get",
"params":"<h1>EspoCRM REST API<\/h1>"
"route": "/",
"method": "get",
"params": "<h1>EspoCRM REST API<\/h1>"
},
{
"route":"/App/user",
"method":"get",
"params":{
"controller":"App",
"action":"user"
"route": "/App/user",
"method": "get",
"params": {
"controller": "App",
"action": "user"
}
},
{
"route":"/Metadata",
"method":"get",
"params":{
"controller":"Metadata"
"route": "/Metadata",
"method": "get",
"params": {
"controller": "Metadata"
}
},
{
"route":"I18n",
"method":"get",
"params":{
"controller":"I18n"
"route": "I18n",
"method": "get",
"params": {
"controller": "I18n"
},
"conditions":{
"auth":false
"conditions": {
"auth": false
}
},
{
"route":"/Settings",
"method":"get",
"params":{
"controller":"Settings"
"route": "/Settings",
"method": "get",
"params": {
"controller": "Settings"
},
"conditions":{
"auth":false
"conditions": {
"auth": false
}
},
{
"route":"/Settings",
"method":"patch",
"params":{
"controller":"Settings"
"route": "/Settings",
"method": "patch",
"params": {
"controller": "Settings"
}
},
{
"route":"User/passwordChangeRequest",
"method":"post",
"params":{
"controller":"User",
"action":"passwordChangeRequest"
"route": "/Settings",
"method": "put",
"params": {
"controller": "Settings"
}
},
{
"route": "User/passwordChangeRequest",
"method": "post",
"params": {
"controller": "User",
"action": "passwordChangeRequest"
},
"conditions":{
"auth":false
"conditions": {
"auth": false
}
},
{
"route":"User/changePasswordByRequest",
"method":"post",
"params":{
"controller":"User",
"action":"changePasswordByRequest"
"route": "User/changePasswordByRequest",
"method": "post",
"params": {
"controller": "User",
"action": "changePasswordByRequest"
},
"conditions":{
"auth":false
"conditions": {
"auth": false
}
},
{
"route":"/Stream",
"method":"get",
"params":{
"controller":"Stream",
"action":"list",
"scope":"User"
"route": "/Stream",
"method": "get",
"params": {
"controller": "Stream",
"action": "list",
"scope": "User"
}
},
{
"route":"/GlobalSearch",
"method":"get",
"params":{
"controller":"GlobalSearch",
"action":"search"
"route": "/GlobalSearch",
"method": "get",
"params": {
"controller": "GlobalSearch",
"action": "search"
}
},
{
"route":"/:controller/action/:action",
"method":"post",
"params":{
"controller":":controller",
"action":":action"
"route": "/:controller/action/:action",
"method": "post",
"params": {
"controller": ":controller",
"action": ":action"
}
},
{
"route":"/:controller/action/:action",
"method":"put",
"params":{
"controller":":controller",
"action":":action"
"route": "/:controller/action/:action",
"method": "put",
"params": {
"controller": ":controller",
"action": ":action"
}
},
{
"route":"/:controller/action/:action",
"method":"get",
"params":{
"controller":":controller",
"action":":action"
"route": "/:controller/action/:action",
"method": "get",
"params": {
"controller": ":controller",
"action": ":action"
}
},
{
"route":"/:controller/layout/:name",
"method":"get",
"params":{
"controller":"Layout",
"scope":":controller"
"route": "/:controller/layout/:name",
"method": "get",
"params": {
"controller": "Layout",
"scope": ":controller"
}
},
{
"route":"/:controller/layout/:name",
"method":"put",
"params":{
"controller":"Layout",
"scope":":controller"
"route": "/:controller/layout/:name",
"method": "put",
"params": {
"controller": "Layout",
"scope": ":controller"
}
},
{
"route":"/:controller/layout/:name",
"method":"patch",
"params":{
"controller":"Layout",
"scope":":controller"
"route": "/:controller/layout/:name",
"method": "patch",
"params": {
"controller": "Layout",
"scope": ":controller"
}
},
{
"route":"/Admin/rebuild",
"method":"post",
"params":{
"controller":"Admin",
"action":"rebuild"
"route": "/Admin/rebuild",
"method": "post",
"params": {
"controller": "Admin",
"action": "rebuild"
}
},
{
"route":"/Admin/clearCache",
"method":"post",
"params":{
"controller":"Admin",
"action":"clearCache"
"route": "/Admin/clearCache",
"method": "post",
"params": {
"controller": "Admin",
"action": "clearCache"
}
},
{
"route":"/Admin/jobs",
"method":"get",
"params":{
"controller":"Admin",
"action":"jobs"
"route": "/Admin/jobs",
"method": "get",
"params": {
"controller": "Admin",
"action": "jobs"
}
},
{
"route":"/Admin/fieldManager/:scope/:name",
"method":"get",
"params":{
"controller":"FieldManager",
"action":"read",
"scope":":scope",
"name":":name"
"route": "/Admin/fieldManager/:scope/:name",
"method": "get",
"params": {
"controller": "FieldManager",
"action": "read",
"scope": ":scope",
"name": ":name"
}
},
{
"route":"/Admin/fieldManager/:scope",
"method":"post",
"params":{
"controller":"FieldManager",
"action":"create",
"scope":":scope"
"route": "/Admin/fieldManager/:scope",
"method": "post",
"params": {
"controller": "FieldManager",
"action": "create",
"scope": ":scope"
}
},
{
"route":"/Admin/fieldManager/:scope/:name",
"method":"put",
"params":{
"controller":"FieldManager",
"action":"update",
"scope":":scope",
"name":":name"
"route": "/Admin/fieldManager/:scope/:name",
"method": "put",
"params": {
"controller": "FieldManager",
"action": "update",
"scope": ":scope",
"name": ":name"
}
},
{
"route":"/Admin/fieldManager/:scope/:name",
"method":"patch",
"params":{
"controller":"FieldManager",
"action":"update",
"scope":":scope",
"name":":name"
"route": "/Admin/fieldManager/:scope/:name",
"method": "patch",
"params": {
"controller": "FieldManager",
"action": "update",
"scope": ":scope",
"name": ":name"
}
},
{
"route":"/Admin/fieldManager/:scope/:name",
"method":"delete",
"params":{
"controller":"FieldManager",
"action":"delete",
"scope":":scope",
"name":":name"
"route": "/Admin/fieldManager/:scope/:name",
"method": "delete",
"params": {
"controller": "FieldManager",
"action": "delete",
"scope": ":scope",
"name": ":name"
}
},
{
"route":"/:controller/:id",
"method":"get",
"params":{
"controller":":controller",
"action":"read",
"id":":id"
"route": "/:controller/:id",
"method": "get",
"params": {
"controller": ":controller",
"action": "read",
"id": ":id"
}
},
{
"route":"/:controller",
"method":"get",
"params":{
"controller":":controller",
"action":"index"
"route": "/:controller",
"method": "get",
"params": {
"controller": ":controller",
"action": "index"
}
},
{
"route":"/:controller",
"method":"post",
"params":{
"controller":":controller",
"action":"create"
"route": "/:controller",
"method": "post",
"params": {
"controller": ":controller",
"action": "create"
}
},
{
"route":"/:controller/:id",
"method":"put",
"params":{
"controller":":controller",
"action":"update",
"id":":id"
"route": "/:controller/:id",
"method": "put",
"params": {
"controller": ":controller",
"action": "update",
"id": ":id"
}
},
{
"route":"/:controller/:id",
"method":"patch",
"params":{
"controller":":controller",
"action":"patch",
"id":":id"
"route": "/:controller/:id",
"method": "patch",
"params": {
"controller": ":controller",
"action": "patch",
"id": ":id"
}
},
{
"route":"/:controller/:id",
"method":"delete",
"params":{
"controller":":controller",
"action":"delete",
"id":":id"
"route": "/:controller/:id",
"method": "delete",
"params": {
"controller": ":controller",
"action": "delete",
"id": ":id"
}
},
{
"route":"/:controller/:id/stream",
"method":"get",
"params":{
"controller":"Stream",
"action":"list",
"id":":id",
"scope":":controller"
"route": "/:controller/:id/stream",
"method": "get",
"params": {
"controller": "Stream",
"action": "list",
"id": ":id",
"scope": ":controller"
}
},
{
"route":"/:controller/:id/subscription",
"method":"put",
"params":{
"controller":":controller",
"id":":id",
"action":"follow"
"route": "/:controller/:id/subscription",
"method": "put",
"params": {
"controller": ":controller",
"id": ":id",
"action": "follow"
}
},
{
"route":"/:controller/:id/subscription",
"method":"delete",
"params":{
"controller":":controller",
"id":":id",
"action":"unfollow"
"route": "/:controller/:id/subscription",
"method": "delete",
"params": {
"controller": ":controller",
"id": ":id",
"action": "unfollow"
}
},
{
"route":"/:controller/:id/:link",
"method":"get",
"params":{
"controller":":controller",
"action":"listLinked",
"id":":id",
"link":":link"
"route": "/:controller/:id/:link",
"method": "get",
"params": {
"controller": ":controller",
"action": "listLinked",
"id": ":id",
"link": ":link"
}
},
{
"route":"/:controller/:id/:link",
"method":"post",
"params":{
"controller":":controller",
"action":"createLink",
"id":":id",
"link":":link"
"route": "/:controller/:id/:link",
"method": "post",
"params": {
"controller": ":controller",
"action": "createLink",
"id": ":id",
"link": ":link"
}
},
{
"route":"/:controller/:id/:link",
"method":"delete",
"params":{
"controller":":controller",
"action":"removeLink",
"id":":id",
"link":":link"
"route": "/:controller/:id/:link",
"method": "delete",
"params": {
"controller": ":controller",
"action": "removeLink",
"id": ":id",
"link": ":link"
}
}
]
+1 -1
View File
@@ -270,7 +270,7 @@ class Email extends \Espo\Core\SelectManagers\Base
);
}
protected function applyAdditionalToTextFilterGroup($textFilter, &$group)
protected function applyAdditionalToTextFilterGroup($textFilter, &$group, &$result)
{
if (strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH) {
$emailAddressId = $this->getEmailAddressIdByValue($textFilter);
@@ -31,24 +31,6 @@ namespace Espo\SelectManagers;
class EmailTemplate extends \Espo\Core\SelectManagers\Base
{
protected function textFilter($textFilter, &$result)
{
$d = array();
$d['name*'] = '' . $textFilter . '%';
$d['subject*'] = '%' . $textFilter . '%';
if (strlen($textFilter) >= self::MIN_LENGTH_FOR_CONTENT_SEARCH) {
$d['bodyPlain*'] = '%' . $textFilter . '%';
$d['body*'] = '%' . $textFilter . '%';
}
$result['whereClause'][] = array(
'OR' => $d
);
}
protected function filterActual(&$result)
{
@@ -58,4 +40,3 @@ class EmailTemplate extends \Espo\Core\SelectManagers\Base
}
}
+16 -4
View File
@@ -173,14 +173,26 @@ class Email extends Record
if (!$smtpParams && $fromAddress === strtolower($this->getConfig()->get('outboundEmailFromAddress'))) {
if (!$this->getConfig()->get('outboundEmailIsShared')) {
throw new Error('Can not use system smtp. System SMTP is not shared.');
throw new Error('Can not use system SMTP. System account is not shared.');
}
$emailSender->setParams(array(
$emailSender->setParams([
'fromName' => $this->getConfig()->get('outboundEmailFromName')
));
]);
}
$params = array();
if (!$smtpParams && !$this->getConfig()->get('outboundEmailIsShared')) {
throw new Error('No SMTP params found for '.$fromAddress.'.');
}
if (!$smtpParams) {
if (in_array($fromAddress, $userAddressList)) {
$emailSender->setParams([
'fromName' => $this->getUser()->get('name')
]);
}
}
$params = [];
$parent = null;
if ($entity->get('parentType') && $entity->get('parentId')) {
+6 -3
View File
@@ -209,12 +209,15 @@ class EmailAccount extends Record
if (empty($fetchData)) {
$fetchData = new \StdClass();
}
$fetchData = clone $fetchData;
if (!property_exists($fetchData, 'lastUID')) {
$fetchData->lastUID = new \StdClass();;
$fetchData->lastUID = new \StdClass();
}
if (!property_exists($fetchData, 'lastDate')) {
$fetchData->lastDate = new \StdClass();;
$fetchData->lastDate = new \StdClass();
}
$fetchData->lastUID = clone $fetchData->lastUID;
$fetchData->lastDate = clone $fetchData->lastDate;
$storage = $this->getStorage($emailAccount);
@@ -385,7 +388,7 @@ class EmailAccount extends Record
$emailAccount = $this->getEntityManager()->getRepository('EmailAccount')->where([
'emailAddress' => $address,
'assignedUserId' => $user->id,
'active' => true
'status' => 'Active'
])->findOne();
return $emailAccount;
+3 -1
View File
@@ -109,9 +109,11 @@ class EmailFolder extends Record
public function listAll()
{
$limit = $this->getConfig()->get('emailFolderMaxCount', 100);
$folderList = $this->getRepository()->where(array(
'assignedUserId' => $this->getUser()->id
))->order('order')->limit(0, 20)->find();
))->order('order')->limit(0, $limit)->find();
$list = new \Espo\ORM\EntityCollection();
+4 -1
View File
@@ -210,12 +210,15 @@ class InboundEmail extends \Espo\Services\Record
if (empty($fetchData)) {
$fetchData = new \StdClass();
}
$fetchData = clone $fetchData;
if (!property_exists($fetchData, 'lastUID')) {
$fetchData->lastUID = new \StdClass();;
}
if (!property_exists($fetchData, 'lastDate')) {
$fetchData->lastDate = new \StdClass();;
$fetchData->lastDate = new \StdClass();
}
$fetchData->lastUID = clone $fetchData->lastUID;
$fetchData->lastDate = clone $fetchData->lastDate;
$imapParams = array(
'host' => $emailAccount->get('host'),
+3 -1
View File
@@ -29,6 +29,8 @@
namespace Espo\Services;
use Espo\Core\Utils\Database\Schema\Utils as SchemaUtils;
class MysqlCharacter extends \Espo\Core\Services\Base
{
protected function init()
@@ -63,7 +65,7 @@ class MysqlCharacter extends \Espo\Core\Services\Base
$sth->execute();
}
$fieldListExceededIndexMaxLength = \Espo\Core\Utils\Database\Schema\Utils::getFieldListExceededIndexMaxLength($ormMeta, $maxIndexLength);
$fieldListExceededIndexMaxLength = SchemaUtils::getFieldListExceededIndexMaxLength($ormMeta, $maxIndexLength);
foreach ($ormMeta as $entityName => $entityParams) {
@@ -218,6 +218,7 @@ class Notification extends \Espo\Services\Record
$note->set('relatedName', $related->get('name'));
}
}
$note->loadLinkMultipleField('attachments');
$entity->set('noteData', $note->toArray());
} else {
unset($collection[$k]);
+23 -19
View File
@@ -1352,9 +1352,17 @@ class Record extends \Espo\Core\Services\Base
$selectParams = $this->getSelectParams($p);
$collection = $repository->find($selectParams);
$this->getEntityManager()->getRepository($this->getEntityType())->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery($this->getEntityType(), $selectParams);
$sth = $this->getEntityManager()->getPdo()->prepare($sql);
$sth->execute();
while ($dataRow = $sth->fetch(\PDO::FETCH_ASSOC)) {
$entity = $this->getEntityManager()->getEntityFactory()->create($this->getEntityType());
$entity->set($dataRow);
$entity->setAsFetched();
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'edit') && $this->checkEntityForMassUpdate($entity, $data)) {
$entity->set($data);
if ($this->checkAssignment($entity)) {
@@ -1427,10 +1435,19 @@ class Record extends \Espo\Core\Services\Base
}
$selectParams = $this->getSelectParams($p);
$skipTextColumns['skipTextColumns'] = true;
$collection = $repository->find($selectParams);
$selectParams['skipTextColumns'] = true;
$this->getEntityManager()->getRepository($this->getEntityType())->handleSelectParams($selectParams);
$sql = $this->getEntityManager()->getQuery()->createSelectQuery($this->getEntityType(), $selectParams);
$sth = $this->getEntityManager()->getPdo()->prepare($sql);
$sth->execute();
while ($dataRow = $sth->fetch(\PDO::FETCH_ASSOC)) {
$entity = $this->getEntityManager()->getEntityFactory()->create($this->getEntityType());
$entity->set($dataRow);
$entity->setAsFetched();
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'delete') && $this->checkEntityForMassRemove($entity)) {
if ($repository->remove($entity)) {
$idsRemoved[] = $entity->id;
@@ -1761,19 +1778,6 @@ class Record extends \Espo\Core\Services\Base
}
}
/*foreach ($collection as $entity) {
$this->loadAdditionalFieldsForExport($entity);
if (method_exists($exportObj, 'loadAdditionalFields')) {
$exportObj->loadAdditionalFields($entity, $fieldList);
}
$row = array();
foreach ($attributeList as $attribute) {
$value = $this->getAttributeFromEntityForExport($entity, $attribute);
$row[$attribute] = $value;
}
$arr[] = $row;
}*/
if (is_null($attributeList)) {
$attributeList = [];
}
@@ -2187,7 +2191,7 @@ class Record extends \Espo\Core\Services\Base
}
// TODO remove in 5.5.0
if (in_array($this->getEntityType(), ['Report', 'Workflow'])) {
if (in_array($this->getEntityType(), ['Report', 'Workflow', 'ReportPanel'])) {
return null;
}
+1 -1
View File
@@ -972,7 +972,7 @@ class Stream extends \Espo\Core\Services\Base
foreach ($auditedFields as $field => $item) {
$updated = false;
foreach ($item['actualList'] as $attribute) {
if ($entity->get($attribute) !== $entity->getFetched($attribute)) {
if ($entity->hasFetched($attribute) && $entity->isAttributeChanged($attribute)) {
$updated = true;
}
}
@@ -64,7 +64,7 @@ Espo.define('crm:views/case/record/panels/activities', 'crm:views/record/panels/
}, function () {resolve([])});
}, this);
} else if (parentModel.get('leadId')) {
this.getModelFactory().create('Lead', function (account) {
this.getModelFactory().create('Lead', function (lead) {
lead.id = parentModel.get('leadId');
lead.fetch().then(function () {
resolve([lead]);
@@ -135,7 +135,7 @@ Espo.define('crm:views/dashlets/opportunities-by-stage', 'crm:views/dashlets/abs
mouse: {
track: true,
relative: true,
position: 's',
position: 'w',
lineColor: this.hoverColor,
trackFormatter: function (obj) {
var label = (obj.series.label || self.translate('None'));
@@ -160,6 +160,7 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
track: true,
relative: true,
lineColor: this.hoverColor,
position: 's',
trackFormatter: function (obj) {
var i = parseInt(obj.x);
var value = '';
@@ -137,7 +137,7 @@ Espo.define('crm:views/dashlets/sales-pipeline', 'crm:views/dashlets/abstract/ch
mouse: {
track: true,
relative: true,
position: 'ne',
position: 'n',
lineColor: this.hoverColor,
trackFormatter: function (obj) {
if (obj.x >= self.chartData.length) {
@@ -81,6 +81,7 @@ Espo.define('crm:views/lead/convert', 'view', function (Dep) {
if (scope == 'Account' && this.getConfig().get('b2cMode')) {
return;
}
if (this.getMetadata().get(['scopes', scope, 'disabled'])) return;
if (this.getAcl().check(scope, 'edit')) {
scopeList.push(scope);
}
+1 -1
View File
@@ -85,7 +85,7 @@
<header>{{translate 'Disabled' scope='Admin'}}</header>
<ul class="disabled connected">
{{#each disabledFields}}
<li draggable="true" data-name="{{name}}">
<li draggable="true" {{#each ../dataAttributeList}}data-{{toDom this}}="{{prop ../this this}}" {{/each}}>
<div class="left">
<label>{{label}}</label>
</div>
@@ -1 +1 @@
<span class="glyphicon glyphicon-envelope action text-muted" data-action="quickView" data-id="{{model.id}}" style="cursor: pointer; margin-left: -7px; top: 2px;" title="{{translate 'View'}}"></span>
<span class="fas fa-envelope action text-muted" data-action="quickView" data-id="{{model.id}}" style="cursor: pointer; margin-left: -7px; top: 2px;" title="{{translate 'View'}}"></span>
@@ -0,0 +1,4 @@
<span class="list-icon-container pull-right"{{#unless isReplied}} style="visibility: hidden;"{{/unless}}>
<span class="glyphicon glyphicon-share-alt small text-muted icon-flip-horizontal" title="{{translate 'isReplied' category='fields' scope='Email'}}"></span>
</span>
<span title="{{value}}">{{value}}</span>
@@ -1,5 +1,7 @@
{{#if hasAttachment}}
<span class="glyphicon glyphicon-paperclip small text-muted pull-right"></span>
<span class="list-icon-container pull-right">
<span class="glyphicon glyphicon-paperclip small text-muted" title="{{translate 'hasAttachment' category='fields' scope='Email'}}"></span>
</span>
{{/if}}
{{#unless isRead}}<strong>{{/unless}}
<a href="#{{model.name}}/view/{{model.id}}" class="link{{#if isImportant}} text-warning{{/if}}" data-id="{{model.id}}" title="{{value}}">{{value}}</a>
+3 -1
View File
@@ -1 +1,3 @@
{{#unless isEmpty}}{{{value}}}{{else}}{{translate 'None'}}{{/unless}}
{{#unless isEmpty}}{{{value}}}{{else}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/unless}}
@@ -0,0 +1 @@
{{#unless isEmpty}}{{{value}}}{{/unless}}
@@ -1,6 +1,9 @@
{{#if value}}
{{{value}}}
{{else}}
{{{translate 'None'}}}
{{/if}}
{{#if valueIsSet}}
{{{translate 'None'}}}
{{else}}
...
{{/if}}
{{/if}}
@@ -0,0 +1,3 @@
{{#if value}}
{{{value}}}
{{/if}}
@@ -1,5 +1,5 @@
{{#if value}}
{{#if isNotEmpty}}
{{value}} {{currencyValue}}
{{else}}
{{translate 'None'}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -1,5 +1,5 @@
{{#if value}}
{{#if isNotEmpty}}
{{currencySymbol}}{{value}}
{{else}}
{{translate 'None'}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -1,5 +1,5 @@
{{#if value}}
{{#if isNotEmpty}}
{{value}}
{{else}}
{{translate 'None'}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -1,5 +1,5 @@
{{#if value}}
{{#if isNotEmpty}}
{{value}} {{currencyValue}}
{{else}}
{{translate 'None'}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -1,5 +1,3 @@
{{#if value}}
{{#if isNotEmpty}}
<span title="{{value}} {{currencyValue}}">{{value}} {{currencyValue}}</span>
{{else}}
{{translate 'None'}}
{{/if}}
@@ -1,5 +1,3 @@
{{#if value}}
<span title="{{currencySymbol}}{{value}}">{{currencySymbol}}{{value}}</span>
{{else}}
{{translate 'None'}}
{{#if isNotEmpty}}
<span title="{{currencySymbol}}{{value}}">{{currencySymbol}}{{value}}</span>
{{/if}}
@@ -1,5 +1,3 @@
{{#if value}}
{{#if isNotEmpty}}
<span title="{{value}} {{currencyValue}}">{{value}} {{currencyValue}}</span>
{{else}}
{{translate 'None'}}
{{/if}}
+1 -1
View File
@@ -1,3 +1,3 @@
{{#if isNotEmpty}}{{value}}{{else}}
{{translate 'None'}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -0,0 +1,4 @@
{{#if value}}
{{{value}}}
{{/if}}
@@ -1,3 +1,3 @@
{{#if isNotEmpty}}{{translateOption salutationValue field='salutationName' scope=scope}} {{firstValue}} {{lastValue}}{{else}}
{{translate 'None'}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
+1 -1
View File
@@ -1,5 +1,5 @@
{{#if value}}
<a href="{{url}}" target="_blank">{{value}}</a>
{{else}}
{{translate 'None'}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -1 +1,3 @@
{{#if isNotEmpty}}{{value}}{{else}}{{translate 'None'}}{{/if}}
{{#if isNotEmpty}}{{value}}{{else}}
{{#if valueIsSet}}{{{translate 'None'}}}{{else}}...{{/if}}
{{/if}}
@@ -1,6 +1,6 @@
{{#unless isPlain}}
{{#if useIframe}}
<iframe frameborder="0" style="width: 100%;" class="hidden" scrolling="no"></iframe>
<iframe frameborder="0" style="width: 100%; overflow-x: hidden; overflow-y: hidden;" class="hidden"></iframe>
{{else}}
<div class="html-container">{{{value}}}</div>
{{/if}}
@@ -1,6 +1,6 @@
<div class="search-container">{{{search}}}</div>
<div class="row">
<div class="row row-list-container">
{{#unless categoriesDisabled}}
<div class="categories-container{{#unless categoriesDisabled}} col-md-3 col-sm-4{{else}} col-md-12{{/unless}}">{{{categories}}}</div>
{{/unless}}
@@ -9,6 +9,6 @@
{{#if createButton}}
<div class="button-container">
<button class="btn btn-default" data-action="create">{{translate 'Create'}}</button>
<button class="btn btn-default" data-action="create">{{createText}}</button>
</div>
{{/if}}
@@ -4,7 +4,7 @@
{{{avatar}}}
</div>
<div class="stream-head-text-container">
<span class="text-muted"><span class="glyphicon glyphicon-envelope action" style="cursor: pointer;" title="{{translate 'View'}}" data-action="quickView" data-id="{{emailId}}" data-scope="Email"></span>
<span class="text-muted"><span class="fas fa-envelope action" style="cursor: pointer;" title="{{translate 'View'}}" data-action="quickView" data-id="{{emailId}}" data-scope="Email"></span>
{{{message}}}
</span>
</div>
@@ -1 +1 @@
<input type="checkbox" class="record-checkbox" data-id="{{model.id}}">
<span class="record-checkbox-container"><input type="checkbox" class="record-checkbox" data-id="{{model.id}}"></span>
@@ -1,3 +1,4 @@
{{#if fieldList.length}}
<div class="row">
{{#each fieldList}}
<div class="cell form-group col-sm-6 col-md-12{{#if hidden}} hidden-cell{{/if}}" data-name="{{name}}">
@@ -8,21 +9,26 @@
</div>
{{/each}}
</div>
{{/if}}
{{#unless complexDateFieldsDisabled}}
<div class="row">
{{#if hasComplexCreated}}
<div class="cell form-group col-sm-6 col-md-12" data-name="complexCreated">
<label class="control-label" data-name="complexCreated"><span class="label-text">{{translate 'Created'}}</span></label>
<div class="field" data-name="complexCreated">
<span data-name="createdAt" class="field">{{{createdAtField}}}</span> <span class="text-muted">&raquo;</span> <span data-name="createdBy" class="field">{{{createdByField}}}</span>
</div>
</div>
{{/if}}
{{#if hasComplexModified}}
<div class="cell form-group col-sm-6 col-md-12" data-name="complexModified">
<label class="control-label" data-name="complexModified"><span class="label-text">{{translate 'Modified'}}</span></label>
<div class="field" data-name="complexModified">
<span data-name="modifiedAt" class="field">{{{modifiedAtField}}}</span> <span class="text-muted">&raquo;</span> <span data-name="modifiedBy" class="field">{{{modifiedByField}}}</span>
</div>
</div>
{{/if}}
</div>
{{/unless}}
@@ -1,3 +1,4 @@
{{#if fieldList.length}}
<div class="row">
{{#each fieldList}}
<div class="cell form-group col-sm-6 col-md-12{{#if hidden}} hidden-cell{{/if}}" data-name="{{name}}">
@@ -8,3 +9,4 @@
</div>
{{/each}}
</div>
{{/if}}
+2 -1
View File
@@ -30,7 +30,8 @@ Espo.define('controllers/base', 'controller', function (Dep) {
return Dep.extend({
login: function () {
this.entire('views/login', {}, function (login) {
var viewName = this.getMetadata().get(['clientDefs', 'App', 'loginView']) || 'views/login';
this.entire(viewName, {}, function (login) {
login.render();
login.on('login', function (data) {
this.trigger('login', data);
+1 -1
View File
@@ -176,7 +176,7 @@ Espo.define('dynamic-logic', [], function () {
} else if (type === 'contains' || type === 'has') {
if (!setValue) return false;
return !!~setValue.indexOf(value);
} else if (type === 'notContains' || type === 'hasNot') {
} else if (type === 'notContains' || type === 'notHas') {
if (!setValue) return true;
return !~setValue.indexOf(value);
} else if (type === 'greaterThan') {
+5
View File
@@ -50,6 +50,11 @@ Espo.define('model', [], function () {
Dep.prototype.initialize.call(this);
},
sync: function (method, model, options) {
if (method === 'patch') options.type = 'PUT';
return Dep.prototype.sync.call(this, method, model, options);
},
set: function (key, val, options) {
if (typeof key === 'object') {
var o = key;
@@ -285,6 +285,9 @@ Espo.define('views/admin/entity-manager/modals/edit-entity', ['views/modal', 'mo
if (this.getMetadata().get(['entityDefs', scope, 'fields', item, 'disabled'])) {
return false;
}
if (this.getMetadata().get(['entityDefs', scope, 'fields', item, 'textFilterDisabled'])) {
return false;
}
return true;
}, this);
@@ -35,7 +35,8 @@ Espo.define('views/admin/entity-manager/record/edit-formula', 'views/record/base
setup: function () {
Dep.prototype.setup.call(this);
this.createField('beforeSaveCustomScript', 'views/fields/formula', {
targetEntityType: this.options.targetEntityType
targetEntityType: this.options.targetEntityType,
height: 500
}, 'edit');
}
@@ -118,6 +118,8 @@ Espo.define('views/admin/field-manager/fields/foreign/field', 'views/fields/enum
this.viewValue = 'views/fields/foreign-datetime';
} else if (type == 'text') {
this.viewValue = 'views/fields/foreign-text';
} else if (type == 'number') {
this.viewValue = 'views/fields/foreign-varchar';
} else {
this.viewValue = null;
}

Some files were not shown because too many files have changed in this diff Show More