mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-05 03:27:00 +00:00
Compare commits
176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92fedd2c2e | ||
|
|
0ec450d59d | ||
|
|
f0a1634f90 | ||
|
|
5b5f5c8ab7 | ||
|
|
19d37e8081 | ||
|
|
d8ab10fd75 | ||
|
|
312de11a15 | ||
|
|
47a22042b2 | ||
|
|
31cea9e36e | ||
|
|
2b983ad880 | ||
|
|
21b0c2b2eb | ||
|
|
59b4aa61e4 | ||
|
|
25d91a1e73 | ||
|
|
6750d32bd6 | ||
|
|
70b8fc9ac9 | ||
|
|
38a1be5ae3 | ||
|
|
1d9e9752c8 | ||
|
|
eb37c1dc47 | ||
|
|
1df6ec7f0c | ||
|
|
1d6ee3e030 | ||
|
|
6eb80b747c | ||
|
|
539c0d22d7 | ||
|
|
8e7d607ad4 | ||
|
|
59e15eb71f | ||
|
|
c1ba6d5330 | ||
|
|
410aec734a | ||
|
|
6078b17d38 | ||
|
|
3b36d607ac | ||
|
|
156cd85474 | ||
|
|
0e29798e2b | ||
|
|
19dbe81c79 | ||
|
|
62838961bb | ||
|
|
ee84162470 | ||
|
|
7a76dcce2c | ||
|
|
0c21ed2e31 | ||
|
|
39daa763ab | ||
|
|
f0c9690152 | ||
|
|
450091e71f | ||
|
|
3a5c64b877 | ||
|
|
949d96db7a | ||
|
|
c974ce8864 | ||
|
|
caab8e9bbb | ||
|
|
12469ee6f9 | ||
|
|
dcd90b6f70 | ||
|
|
f92b3c3d16 | ||
|
|
f3b41783c5 | ||
|
|
3ec33c6054 | ||
|
|
eb7c0da40c | ||
|
|
2817c0027e | ||
|
|
c7457b95d1 | ||
|
|
05244d598c | ||
|
|
f55e0b2cb0 | ||
|
|
ed6256da2c | ||
|
|
0398137ba7 | ||
|
|
a6448a2769 | ||
|
|
71cf0d01f8 | ||
|
|
d2a6d7ee99 | ||
|
|
a07bc15f00 | ||
|
|
52ebd35785 | ||
|
|
636d24a117 | ||
|
|
8afbfaeb31 | ||
|
|
117084f835 | ||
|
|
eae92d8638 | ||
|
|
31f5df9db4 | ||
|
|
00419a4cfc | ||
|
|
3f36e5b2e8 | ||
|
|
cc2abc961e | ||
|
|
bea0398776 | ||
|
|
fd4c55ba9d | ||
|
|
fef5edce28 | ||
|
|
6423e4cb68 | ||
|
|
2acaf1f7ff | ||
|
|
fe31b078f6 | ||
|
|
e3d81f4a61 | ||
|
|
fb1d1d8fc5 | ||
|
|
cabef5906c | ||
|
|
2dfffe00d0 | ||
|
|
78390efe45 | ||
|
|
8ae0d2da88 | ||
|
|
ecb3273883 | ||
|
|
256b94f877 | ||
|
|
422f02b5c9 | ||
|
|
7e4c31db1a | ||
|
|
c9918c07b8 | ||
|
|
ed438b1a31 | ||
|
|
83843cfe46 | ||
|
|
462de7b025 | ||
|
|
01dbc183f8 | ||
|
|
8817c82996 | ||
|
|
3493addec5 | ||
|
|
d41a588bb5 | ||
|
|
7953705e30 | ||
|
|
763a1ad96f | ||
|
|
b58d958f51 | ||
|
|
0b8d43f734 | ||
|
|
87518f33a9 | ||
|
|
d83530bbf6 | ||
|
|
517e1bab7c | ||
|
|
cd22552e4a | ||
|
|
abc394512c | ||
|
|
fa0cb01660 | ||
|
|
e6d509bd0b | ||
|
|
775641aee1 | ||
|
|
3c51c6bc77 | ||
|
|
9ddd7b1d32 | ||
|
|
6020a01a62 | ||
|
|
1ad9ee10f6 | ||
|
|
59863b8a91 | ||
|
|
1a083c247c | ||
|
|
cd7ca31212 | ||
|
|
9be9ff3d68 | ||
|
|
e3b1ead830 | ||
|
|
b6041592ea | ||
|
|
0fa8b3da0b | ||
|
|
7dd0fe07ac | ||
|
|
2d1770f439 | ||
|
|
6be192514a | ||
|
|
63fc42f8cf | ||
|
|
6f8a593f09 | ||
|
|
b00e8f8900 | ||
|
|
4e226ebcb7 | ||
|
|
30909c497b | ||
|
|
efd5ccfa96 | ||
|
|
88d159d4c6 | ||
|
|
29788c353b | ||
|
|
7897272f65 | ||
|
|
5cabc76782 | ||
|
|
b0ef416a4f | ||
|
|
0de0768bfb | ||
|
|
e4ac128a2e | ||
|
|
2307f21d04 | ||
|
|
89d706c94f | ||
|
|
23350a0ffe | ||
|
|
65d047f831 | ||
|
|
dc40045de6 | ||
|
|
50d91ea6d8 | ||
|
|
3a8865e382 | ||
|
|
fddcef284f | ||
|
|
c0854250e4 | ||
|
|
71c9501354 | ||
|
|
a85d0f91a2 | ||
|
|
977514f5ef | ||
|
|
bf9ad953a1 | ||
|
|
5b1d96f649 | ||
|
|
bf4ac0c9f3 | ||
|
|
3c8b2534eb | ||
|
|
16c9f46583 | ||
|
|
ef9609b710 | ||
|
|
a4c15992a9 | ||
|
|
14d1173a0c | ||
|
|
1e7acbdbd2 | ||
|
|
35e729b25c | ||
|
|
4ee5ea78e3 | ||
|
|
fe971f9f67 | ||
|
|
a828523f26 | ||
|
|
af9ca6788e | ||
|
|
36d1c3af63 | ||
|
|
1853e98209 | ||
|
|
f526d43798 | ||
|
|
ae8c76cecb | ||
|
|
2b32c94543 | ||
|
|
b6f5909df1 | ||
|
|
ef35bbbb63 | ||
|
|
64f2cc6c7e | ||
|
|
a16635eb26 | ||
|
|
6e614f0a7d | ||
|
|
859f4eab0a | ||
|
|
104e0b9079 | ||
|
|
612abbf5c0 | ||
|
|
2dfbf71806 | ||
|
|
c43f4d129d | ||
|
|
8d47a48f62 | ||
|
|
9a333e6e38 | ||
|
|
f6e0ef8cc6 | ||
|
|
36a45717f1 | ||
|
|
86f63d72e1 |
@@ -118,6 +118,9 @@ module.exports = function (grunt) {
|
||||
clean: {
|
||||
start: ['build/*'],
|
||||
final: ['build/tmp'],
|
||||
beforeFinal: {
|
||||
src: ['build/tmp/custom/Espo/Custom/*', '!build/tmp/custom/Espo/Custom/.htaccess']
|
||||
}
|
||||
},
|
||||
less: lessData,
|
||||
uglify: {
|
||||
@@ -290,9 +293,10 @@ module.exports = function (grunt) {
|
||||
'copy:frontendLib',
|
||||
'copy:backend',
|
||||
'replace',
|
||||
'clean:beforeFinal',
|
||||
'copy:final',
|
||||
'chmod',
|
||||
'clean:final',
|
||||
'clean:final'
|
||||
]);
|
||||
|
||||
};
|
||||
|
||||
@@ -89,6 +89,9 @@ class EntityManager extends \Espo\Core\Controllers\Base
|
||||
if (!empty($data['iconClass'])) {
|
||||
$params['iconClass'] = $data['iconClass'];
|
||||
}
|
||||
if (isset($data['fullTextSearch'])) {
|
||||
$params['fullTestSearch'] = $data['fullTextSearch'];
|
||||
}
|
||||
|
||||
$params['kanbanViewMode'] = !empty($data['kanbanViewMode']);
|
||||
if (!empty($data['kanbanStatusIgnoreList'])) {
|
||||
|
||||
60
application/Espo/Controllers/Pdf.php
Normal file
60
application/Espo/Controllers/Pdf.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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\Controllers;
|
||||
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\Core\Exceptions\BadRequest;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class Pdf extends \Espo\Core\Controllers\Base
|
||||
{
|
||||
public function postActionMassPrint($params, $data)
|
||||
{
|
||||
if (empty($data->idList) || !is_array($data->idList)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
if (empty($data->entityType)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
if (empty($data->templateId)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
if (!$this->getAcl()->checkScope('Template')) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
if (!$this->getAcl()->checkScope($data->entityType)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $this->getServiceFactory()->create('Pdf')->massGenerate($data->entityType, $data->idList, $data->templateId, true)
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -182,9 +182,15 @@ class AclManager
|
||||
|
||||
$impl = $this->getImplementation($scope);
|
||||
|
||||
$methodName = 'checkEntity' . ucfirst($action);
|
||||
if (method_exists($impl, $methodName)) {
|
||||
return $impl->$methodName($user, $entity, $data);
|
||||
if (!$action) {
|
||||
$action = 'read';
|
||||
}
|
||||
|
||||
if ($action) {
|
||||
$methodName = 'checkEntity' . ucfirst($action);
|
||||
if (method_exists($impl, $methodName)) {
|
||||
return $impl->$methodName($user, $entity, $data);
|
||||
}
|
||||
}
|
||||
|
||||
return $impl->checkEntity($user, $entity, $data, $action);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ class Container
|
||||
return new \Espo\Core\Utils\Metadata\OrmMetadata(
|
||||
$this->get('metadata'),
|
||||
$this->get('fileManager'),
|
||||
$this->get('config')->get('useCache')
|
||||
$this->get('config')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -133,11 +133,12 @@ class Record extends Base
|
||||
$q = $request->get('q');
|
||||
$textFilter = $request->get('textFilter');
|
||||
|
||||
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
|
||||
if (empty($maxSize)) {
|
||||
$maxSize = self::MAX_SIZE_LIMIT;
|
||||
$maxSize = $maxSizeLimit;
|
||||
}
|
||||
if (!empty($maxSize) && $maxSize > self::MAX_SIZE_LIMIT) {
|
||||
throw new Forbidden("Max should should not exceed " . self::MAX_SIZE_LIMIT . ". Use pagination (offset, limit).");
|
||||
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
|
||||
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
$params = array(
|
||||
@@ -174,11 +175,12 @@ class Record extends Base
|
||||
$q = $request->get('q');
|
||||
$textFilter = $request->get('textFilter');
|
||||
|
||||
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
|
||||
if (empty($maxSize)) {
|
||||
$maxSize = self::MAX_SIZE_LIMIT;
|
||||
$maxSize = $maxSizeLimit;
|
||||
}
|
||||
if (!empty($maxSize) && $maxSize > self::MAX_SIZE_LIMIT) {
|
||||
throw new Forbidden("Max should should not exceed " . self::MAX_SIZE_LIMIT . ". Use pagination (offset, limit).");
|
||||
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
|
||||
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
$params = array(
|
||||
@@ -213,6 +215,10 @@ class Record extends Base
|
||||
if ($request->get('filterList')) {
|
||||
$params['filterList'] = $request->get('filterList');
|
||||
}
|
||||
|
||||
if ($request->get('select')) {
|
||||
$params['select'] = explode(',', $request->get('select'));
|
||||
}
|
||||
}
|
||||
|
||||
public function actionListLinked($params, $data, $request)
|
||||
@@ -228,11 +234,12 @@ class Record extends Base
|
||||
$q = $request->get('q');
|
||||
$textFilter = $request->get('textFilter');
|
||||
|
||||
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
|
||||
if (empty($maxSize)) {
|
||||
$maxSize = self::MAX_SIZE_LIMIT;
|
||||
$maxSize = $maxSizeLimit;
|
||||
}
|
||||
if (!empty($maxSize) && $maxSize > self::MAX_SIZE_LIMIT) {
|
||||
throw new Forbidden();
|
||||
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
|
||||
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
$params = array(
|
||||
|
||||
@@ -53,6 +53,8 @@ class DataManager
|
||||
*/
|
||||
public function rebuild($entityList = null)
|
||||
{
|
||||
$this->populateConfigParameters();
|
||||
|
||||
$result = $this->clearCache();
|
||||
|
||||
$result &= $this->rebuildMetadata();
|
||||
@@ -172,5 +174,25 @@ class DataManager
|
||||
$this->getContainer()->get('config')->save();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function populateConfigParameters()
|
||||
{
|
||||
$config = $this->getContainer()->get('config');
|
||||
|
||||
$pdo = $this->getContainer()->get('entityManager')->getPDO();
|
||||
$query = "SHOW VARIABLES LIKE 'ft_min_word_len'";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
|
||||
$fullTextSearchMinLength = null;
|
||||
if ($row = $sth->fetch(\PDO::FETCH_ASSOC)) {
|
||||
if (isset($row['Value'])) {
|
||||
$fullTextSearchMinLength = intval($row['Value']);
|
||||
}
|
||||
}
|
||||
|
||||
$config->set('fullTextSearchMinLength', $fullTextSearchMinLength);
|
||||
|
||||
$config->save();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,18 +62,26 @@ class Xlsx extends \Espo\Core\Injectable
|
||||
public function loadAdditionalFields(Entity $entity, $fieldList)
|
||||
{
|
||||
foreach ($entity->getRelationList() as $link) {
|
||||
if ($entity->getRelationType($link) === 'belongsToParent') {
|
||||
if (in_array($link, $fieldList)) {
|
||||
$parent = $entity->get($link);
|
||||
if ($parent instanceof Entity) {
|
||||
$entity->set($link . 'Name', $parent->get('name'));
|
||||
if (in_array($link, $fieldList)) {
|
||||
if ($entity->getRelationType($link) === 'belongsToParent') {
|
||||
if (!$entity->get($link . 'Name')) {
|
||||
$entity->loadParentNameField($link);
|
||||
}
|
||||
}
|
||||
} else if ($entity->getRelationType($link) === 'belongsTo' && $entity->getRelationParam($link, 'noJoin') && $entity->hasField($link . 'Name')) {
|
||||
if (in_array($link, $fieldList)) {
|
||||
$related = $entity->get($link);
|
||||
if ($related instanceof Entity) {
|
||||
$entity->set($link . 'Name', $related->get('name'));
|
||||
} else if (
|
||||
(
|
||||
(
|
||||
$entity->getRelationType($link) === 'belongsTo'
|
||||
&&
|
||||
$entity->getRelationParam($link, 'noJoin')
|
||||
)
|
||||
||
|
||||
$entity->getRelationType($link) === 'hasOne'
|
||||
)
|
||||
&&
|
||||
$entity->hasAttribute($link . 'Name')
|
||||
) {
|
||||
if (!$entity->get($link . 'Name') || !$entity->get($link . 'Id')) {
|
||||
$entity->loadLinkField($link);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -469,7 +477,7 @@ class Xlsx extends \Espo\Core\Injectable
|
||||
|
||||
} else {
|
||||
if (array_key_exists($name, $row)) {
|
||||
$sheet->setCellValue("$col$rowNumber", $row[$name]);
|
||||
$sheet->setCellValueExplicit("$col$rowNumber", $row[$name], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
<?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\Formula\Functions\EntityGroup;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class CountRelatedType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
$this->addDependency('selectManagerFactory');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
if (!property_exists($item, 'value')) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (!is_array($item->value)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (count($item->value) < 1) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$link = $this->evaluate($item->value[0]);
|
||||
|
||||
if (empty($link)) {
|
||||
throw new Error("No link passed to countRelated function.");
|
||||
}
|
||||
|
||||
$filter = null;
|
||||
if (count($item->value) > 1) {
|
||||
$filter = $this->evaluate($item->value[1]);
|
||||
}
|
||||
|
||||
$entity = $this->getEntity();
|
||||
|
||||
$entityManager = $this->getInjection('entityManager');
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, 'entity');
|
||||
|
||||
if (empty($foreignEntityType)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$foreignSelectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
|
||||
|
||||
$selectParams = $foreignSelectManager->getEmptySelectParams();
|
||||
|
||||
if ($filter) {
|
||||
$foreignSelectManager->applyFilter($filter, $selectParams);
|
||||
}
|
||||
|
||||
return $entityManager->getRepository($entity->getEntityType())->countRelated($entity, $link, $selectParams);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?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\Formula\Functions\EntityGroup;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class SumRelatedType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
$this->addDependency('selectManagerFactory');
|
||||
}
|
||||
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
if (!property_exists($item, 'value')) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (!is_array($item->value)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if (count($item->value) < 2) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$link = $this->evaluate($item->value[0]);
|
||||
|
||||
if (empty($link)) {
|
||||
throw new Error("No link passed to sumRelated function.");
|
||||
}
|
||||
|
||||
$field = $this->evaluate($item->value[1]);
|
||||
|
||||
if (empty($field)) {
|
||||
throw new Error("No field passed to sumRelated function.");
|
||||
}
|
||||
|
||||
$filter = null;
|
||||
if (count($item->value) > 2) {
|
||||
$filter = $this->evaluate($item->value[2]);
|
||||
}
|
||||
|
||||
$entity = $this->getEntity();
|
||||
|
||||
$entityManager = $this->getInjection('entityManager');
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, 'entity');
|
||||
|
||||
if (empty($foreignEntityType)) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$foreignSelectManager = $this->getInjection('selectManagerFactory')->create($foreignEntityType);
|
||||
|
||||
$foreignLink = $entity->getRelationParam($link, 'foreign');
|
||||
|
||||
if (empty($foreignLink)) {
|
||||
throw new Error("No foreign link for link {$link}.");
|
||||
}
|
||||
|
||||
$selectParams = $foreignSelectManager->getEmptySelectParams();
|
||||
|
||||
if ($filter) {
|
||||
$foreignSelectManager->applyFilter($filter, $selectParams);
|
||||
}
|
||||
|
||||
$selectParams['select'] = [[$foreignLink . '.id', 'foreignId'], 'SUM:' . $field];
|
||||
|
||||
$foreignSelectManager->addJoin($foreignLink, $selectParams);
|
||||
|
||||
$selectParams['groupBy'] = [$foreignLink . '.id'];
|
||||
|
||||
$entityManager->getRepository($foreignEntityType)->handleSelectParams($selectParams);
|
||||
|
||||
$sql = $entityManager->getQuery()->createSelectQuery($foreignEntityType, $selectParams);
|
||||
|
||||
$pdo = $entityManager->getPDO();
|
||||
$sth = $pdo->prepare($sql);
|
||||
$sth->execute();
|
||||
$rowList = $sth->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
if (empty($rowList)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $rowList[0]['SUM:' . $field];
|
||||
}
|
||||
}
|
||||
@@ -100,10 +100,10 @@ class Htmlizer
|
||||
$forbidenAttributeList = $this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read');
|
||||
}
|
||||
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
if (in_array($field, $forbidenAttributeList)) continue;
|
||||
|
||||
|
||||
$type = $entity->getAttributeType($field);
|
||||
|
||||
if ($type == Entity::DATETIME) {
|
||||
@@ -219,6 +219,14 @@ class Htmlizer
|
||||
return number_format($number, $decimals, $decimalPoint, $thousandsSeparator);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
'var' => function ($context, $options) {
|
||||
if ($context && isset($context[0]) && isset($context[1])) {
|
||||
if (isset($context[1][$context[0]])) {
|
||||
return $context[1][$context[0]];
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
],
|
||||
'hbhelpers' => [
|
||||
|
||||
@@ -205,6 +205,10 @@ class Importer
|
||||
))->findOne();
|
||||
if ($replied) {
|
||||
$email->set('repliedId', $replied->id);
|
||||
$repliedTeamIdList = $replied->getLinkMultipleIdList('teams');
|
||||
foreach ($repliedTeamIdList as $repliedTeamId) {
|
||||
$email->addLinkMultipleId('teams', $repliedTeamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,6 +300,23 @@ class Importer
|
||||
|
||||
$this->getEntityManager()->getPdo()->query('UNLOCK TABLES');
|
||||
|
||||
if ($parentFound) {
|
||||
$parentType = $email->get('parentType');
|
||||
$parentId = $email->get('parentId');
|
||||
$emailKeepParentTeamsEntityList = $this->getConfig()->get('emailKeepParentTeamsEntityList', []);
|
||||
if ($parentId && in_array($parentType, $emailKeepParentTeamsEntityList)) {
|
||||
if ($this->getEntityManager()->hasRepository($parentType)) {
|
||||
$parent = $this->getEntityManager()->getEntity($parentType, $parentId);
|
||||
if ($parent) {
|
||||
$parentTeamIdList = $parent->getLinkMultipleIdList('teams');
|
||||
foreach ($parentTeamIdList as $parentTeamId) {
|
||||
$email->addLinkMultipleId('teams', $parentTeamId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->getEntityManager()->saveEntity($email);
|
||||
|
||||
foreach ($inlineAttachmentList as $attachment) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -53,11 +53,6 @@ class Entity extends \Espo\ORM\Entity
|
||||
$repository = $this->entityManager->getRepository($parentType);
|
||||
|
||||
$select = ['id', 'name'];
|
||||
if ($parentType === 'Lead') {
|
||||
$select[] = 'accountName';
|
||||
$select[] = 'emailAddress';
|
||||
$select[] = 'phoneNumber';
|
||||
}
|
||||
$foreignEntity = $repository->select($select)->where(['id' => $parentId])->findOne();
|
||||
if ($foreignEntity) {
|
||||
$this->set($field . 'Name', $foreignEntity->get('name'));
|
||||
@@ -109,11 +104,6 @@ class Entity extends \Espo\ORM\Entity
|
||||
}
|
||||
|
||||
$defs['select'] = ['id', 'name'];
|
||||
if ($foreignEntityType === 'Lead') {
|
||||
$defs['select'][] = 'accountName';
|
||||
$defs['select'][] = 'emailAddress';
|
||||
$defs['select'][] = 'phoneNumber';
|
||||
}
|
||||
|
||||
$hasType = false;
|
||||
if ($this->hasField($field . 'Types')) {
|
||||
@@ -165,7 +155,13 @@ class Entity extends \Espo\ORM\Entity
|
||||
if (!$this->hasRelation($field) || !$this->hasAttribute($field . 'Id')) return;
|
||||
if ($this->getRelationType($field) !== 'hasOne' && $this->getRelationType($field) !== 'belongsTo') return;
|
||||
|
||||
$entity = $this->get($field);
|
||||
$relatedEntityType = $this->getRelationParam($field, 'entity');
|
||||
|
||||
$select = ['id', 'name'];
|
||||
|
||||
$entity = $this->get($field, [
|
||||
'select' => $select
|
||||
]);
|
||||
|
||||
$entityId = null;
|
||||
$entityName = null;
|
||||
|
||||
@@ -40,6 +40,13 @@ class Tcpdf extends \TCPDF
|
||||
|
||||
protected $footerPosition = 15;
|
||||
|
||||
protected $useGroupNumbers = false;
|
||||
|
||||
public function setUseGroupNumbers($value)
|
||||
{
|
||||
$this->useGroupNumbers = $value;
|
||||
}
|
||||
|
||||
public function setFooterHtml($html)
|
||||
{
|
||||
$this->footerHtml = $html;
|
||||
@@ -58,7 +65,16 @@ class Tcpdf extends \TCPDF
|
||||
|
||||
$this->SetY((-1) * $this->footerPosition);
|
||||
|
||||
$html = str_replace('{pageNumber}', '{:pnp:}', $this->footerHtml);
|
||||
$html = $this->footerHtml;
|
||||
|
||||
if ($this->useGroupNumbers) {
|
||||
$html = str_replace('{pageNumber}', '{:png:}', $html);
|
||||
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
|
||||
} else {
|
||||
$html = str_replace('{pageNumber}', '{:pnp:}', $html);
|
||||
$html = str_replace('{pageAbsoluteNumber}', '{:pnp:}', $html);
|
||||
}
|
||||
|
||||
$this->writeHTMLCell(0, 0, '', '', $html, 0, 1, 0, '', 0, false, 'T');
|
||||
|
||||
$this->SetAutoPageBreak($autoPageBreak, $breakMargin);
|
||||
@@ -98,6 +114,9 @@ class Tcpdf extends \TCPDF
|
||||
++$pagegroupnum;
|
||||
$pnga = TCPDF_STATIC::formatPageNumber($pagegroupnum);
|
||||
$pngu = TCPDF_FONTS::UTF8ToUTF16BE($pnga, false, $this->isunicode, $this->CurrentFont);
|
||||
|
||||
$pnga = $pngu;
|
||||
|
||||
$png_num_chars = $this->GetNumChars($pnga);
|
||||
// replace page numbers
|
||||
$replace = array();
|
||||
|
||||
@@ -64,6 +64,10 @@ class Base
|
||||
|
||||
const MIN_LENGTH_FOR_CONTENT_SEARCH = 4;
|
||||
|
||||
const MIN_LENGTH_FOR_FULL_TEXT_SEARCH = 4;
|
||||
|
||||
protected $fullTextSearchDataCacheHash = [];
|
||||
|
||||
public function __construct($entityManager, \Espo\Entities\User $user, Acl $acl, AclManager $aclManager, Metadata $metadata, Config $config, InjectableFactory $injectableFactory)
|
||||
{
|
||||
$this->entityManager = $entityManager;
|
||||
@@ -146,7 +150,7 @@ class Base
|
||||
} else {
|
||||
$orderPart = 'DESC';
|
||||
}
|
||||
$result['orderBy'] = [[$sortBy . 'Country', $orderPart], [$sortBy . 'City', $orderPart], [$sortBy . 'Street', $orderPart]];
|
||||
$result['orderBy'] = [[$sortBy . 'Country', $orderPart], [$sortBy . 'City', $orderPart], [$sortBy . '_eet', $orderPart]];
|
||||
return;
|
||||
} else if ($type === 'enum') {
|
||||
$list = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $sortBy, 'options']);
|
||||
@@ -203,8 +207,8 @@ class Base
|
||||
}
|
||||
$this->applyBoolFilter($filter, $result);
|
||||
}
|
||||
} else if ($item['type'] == 'textFilter' && !empty($item['value'])) {
|
||||
if (!empty($item['value'])) {
|
||||
} else if ($item['type'] == 'textFilter') {
|
||||
if (isset($item['value']) || $item['value'] !== '') {
|
||||
$this->textFilter($item['value'], $result);
|
||||
}
|
||||
} else if ($item['type'] == 'primary' && !empty($item['value'])) {
|
||||
@@ -387,7 +391,7 @@ class Base
|
||||
|
||||
protected function q($params, &$result)
|
||||
{
|
||||
if (!empty($params['q'])) {
|
||||
if (isset($params['q']) && $params['q'] !== '') {
|
||||
$this->textFilter($params['q'], $result);
|
||||
}
|
||||
}
|
||||
@@ -401,7 +405,7 @@ class Base
|
||||
public function manageTextFilter($textFilter, &$result)
|
||||
{
|
||||
$this->prepareResult($result);
|
||||
$this->q(array('q' => $textFilter), $result);
|
||||
$this->q(['q' => $textFilter], $result);
|
||||
}
|
||||
|
||||
public function getEmptySelectParams()
|
||||
@@ -732,7 +736,7 @@ class Base
|
||||
$this->where($params['where'], $result);
|
||||
}
|
||||
|
||||
if (!empty($params['textFilter'])) {
|
||||
if (isset($params['textFilter']) && $params['textFilter'] !== '') {
|
||||
$this->textFilter($params['textFilter'], $result);
|
||||
}
|
||||
|
||||
@@ -1007,7 +1011,7 @@ class Base
|
||||
foreach ($item['value'] as $i) {
|
||||
$a = $this->getWherePart($i, $result);
|
||||
foreach ($a as $left => $right) {
|
||||
if (!empty($right) || is_null($right) || $right === '') {
|
||||
if (!empty($right) || is_null($right) || $right === '' || $right === 0 || $right === false) {
|
||||
$arr[] = array($left => $right);
|
||||
}
|
||||
}
|
||||
@@ -1500,36 +1504,211 @@ class Base
|
||||
);
|
||||
}
|
||||
|
||||
public function getFullTextSearchDataForTextFilter($textFilter, $isAuxiliaryUse = false)
|
||||
{
|
||||
if (array_key_exists($textFilter, $this->fullTextSearchDataCacheHash)) {
|
||||
return $this->fullTextSearchDataCacheHash[$textFilter];
|
||||
}
|
||||
|
||||
if ($this->getConfig()->get('fullTextSearchDisabled')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = null;
|
||||
|
||||
$fieldList = $this->getTextFilterFieldList();
|
||||
|
||||
if ($isAuxiliaryUse) {
|
||||
$textFilter = str_replace('%', '', $textFilter);
|
||||
}
|
||||
|
||||
$fullTextSearchColumnList = $this->getEntityManager()->getOrmMetadata()->get($this->getEntityType(), ['fullTextSearchColumnList']);
|
||||
|
||||
$useFullTextSearch = false;
|
||||
|
||||
if (
|
||||
$this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'collection', 'fullTextSearch'])
|
||||
&&
|
||||
!empty($fullTextSearchColumnList)
|
||||
) {
|
||||
$fullTextSearchMinLength = $this->getConfig()->get('fullTextSearchMinLength', self::MIN_LENGTH_FOR_FULL_TEXT_SEARCH);
|
||||
if (!$fullTextSearchMinLength) {
|
||||
$fullTextSearchMinLength = 0;
|
||||
}
|
||||
if (mb_strlen($textFilter) >= $fullTextSearchMinLength) {
|
||||
$useFullTextSearch = true;
|
||||
}
|
||||
}
|
||||
|
||||
$fullTextSearchFieldList = [];
|
||||
|
||||
if ($useFullTextSearch) {
|
||||
foreach ($fieldList as $field) {
|
||||
$defs = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'fields', $field], []);
|
||||
if (empty($defs['type'])) continue;
|
||||
$fieldType = $defs['type'];
|
||||
if (!empty($defs['notStorable'])) continue;
|
||||
if (!$this->getMetadata()->get(['fields', $fieldType, 'fullTextSearch'])) continue;
|
||||
$fullTextSearchFieldList[] = $field;
|
||||
}
|
||||
if (!count($fullTextSearchFieldList)) {
|
||||
$useFullTextSearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($fullTextSearchColumnList)) {
|
||||
$useFullTextSearch = false;
|
||||
}
|
||||
|
||||
if ($useFullTextSearch) {
|
||||
if (
|
||||
$isAuxiliaryUse
|
||||
||
|
||||
mb_strpos($textFilter, ' ') === false
|
||||
&&
|
||||
mb_strpos($textFilter, '+') === false
|
||||
&&
|
||||
mb_strpos($textFilter, '-') === false
|
||||
&&
|
||||
mb_strpos($textFilter, '*') === false
|
||||
) {
|
||||
$function = 'MATCH_NATURAL_LANGUAGE';
|
||||
} else {
|
||||
$function = 'MATCH_BOOLEAN';
|
||||
}
|
||||
|
||||
$fullTextSearchColumnSanitizedList = [];
|
||||
$query = $this->getEntityManager()->getQuery();
|
||||
foreach ($fullTextSearchColumnList as $i => $field) {
|
||||
$fullTextSearchColumnSanitizedList[$i] = $query->sanitize($query->toDb($field));
|
||||
}
|
||||
|
||||
$where = $function . ':' . implode(',', $fullTextSearchColumnSanitizedList) . ':' . $textFilter;
|
||||
|
||||
$result = [
|
||||
'where' => $where,
|
||||
'fieldList' => $fullTextSearchFieldList,
|
||||
'columnList' => $fullTextSearchColumnList
|
||||
];
|
||||
}
|
||||
|
||||
$this->fullTextSearchDataCacheHash[$textFilter] = $result;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function textFilter($textFilter, &$result)
|
||||
{
|
||||
$fieldDefs = $this->getSeed()->getAttributes();
|
||||
$fieldList = $this->getTextFilterFieldList();
|
||||
$d = array();
|
||||
$group = [];
|
||||
|
||||
$textFilterContainsMinLength = $this->getConfig()->get('textFilterContainsMinLength', self::MIN_LENGTH_FOR_CONTENT_SEARCH);
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
if (
|
||||
strlen($textFilter) >= $textFilterContainsMinLength
|
||||
&&
|
||||
(
|
||||
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'text'
|
||||
||
|
||||
!empty($this->textFilterUseContainsAttributeList[$field])
|
||||
||
|
||||
!empty($fieldDefs[$field]['type']) && $fieldDefs[$field]['type'] == 'varchar' &&
|
||||
$this->getConfig()->get('textFilterUseContainsForVarchar')
|
||||
)
|
||||
) {
|
||||
$expression = '%' . $textFilter . '%';
|
||||
} else {
|
||||
$expression = $textFilter . '%';
|
||||
}
|
||||
$d[$field . '*'] = $expression;
|
||||
$fullTextSearchData = null;
|
||||
|
||||
$forceFullTextSearch = false;
|
||||
|
||||
$useFullTextSearch = !empty($result['useFullTextSearch']);
|
||||
|
||||
if (mb_strpos($textFilter, 'ft:') === 0) {
|
||||
$textFilter = mb_substr($textFilter, 3);
|
||||
$useFullTextSearch = true;
|
||||
$forceFullTextSearch = true;
|
||||
}
|
||||
$result['whereClause'][] = array(
|
||||
'OR' => $d
|
||||
);
|
||||
|
||||
$skipWidlcards = false;
|
||||
if (!$useFullTextSearch) {
|
||||
if (mb_strpos($textFilter, '*') !== false) {
|
||||
$skipWidlcards = true;
|
||||
$textFilter = str_replace('*', '%', $textFilter);
|
||||
}
|
||||
}
|
||||
|
||||
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilter, !$useFullTextSearch);
|
||||
|
||||
$fullTextGroup = [];
|
||||
|
||||
$fullTextSearchFieldList = [];
|
||||
if ($fullTextSearchData) {
|
||||
$fullTextGroup[] = $fullTextSearchData['where'];
|
||||
$fullTextSearchFieldList = $fullTextSearchData['fieldList'];
|
||||
}
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
if ($useFullTextSearch) {
|
||||
if (in_array($field, $fullTextSearchFieldList)) continue;
|
||||
}
|
||||
if ($forceFullTextSearch) continue;
|
||||
|
||||
$attributeType = null;
|
||||
if (!empty($fieldDefs[$field]['type'])) {
|
||||
$attributeType = $fieldDefs[$field]['type'];
|
||||
}
|
||||
|
||||
if ($attributeType === 'int') {
|
||||
if (is_numeric($textFilter)) {
|
||||
$group[$field] = intval($textFilter);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$skipWidlcards) {
|
||||
if (
|
||||
mb_strlen($textFilter) >= $textFilterContainsMinLength
|
||||
&&
|
||||
(
|
||||
$attributeType == 'text'
|
||||
||
|
||||
in_array($field, $this->textFilterUseContainsAttributeList)
|
||||
||
|
||||
$attributeType == 'varchar' && $this->getConfig()->get('textFilterUseContainsForVarchar')
|
||||
)
|
||||
) {
|
||||
$expression = '%' . $textFilter . '%';
|
||||
} else {
|
||||
$expression = $textFilter . '%';
|
||||
}
|
||||
} else {
|
||||
$expression = $textFilter;
|
||||
}
|
||||
|
||||
if ($fullTextSearchData) {
|
||||
if (!$useFullTextSearch) {
|
||||
if (in_array($field, $fullTextSearchFieldList)) {
|
||||
if (!array_key_exists('OR', $fullTextGroup)) {
|
||||
$fullTextGroup['OR'] = [];
|
||||
}
|
||||
$fullTextGroup['OR'][$field . '*'] = $expression;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$group[$field . '*'] = $expression;
|
||||
}
|
||||
|
||||
if (!$forceFullTextSearch) {
|
||||
$this->applyAdditionalToTextFilterGroup($textFilter, $group, $result);
|
||||
}
|
||||
|
||||
if (!empty($fullTextGroup)) {
|
||||
$group['AND'] = $fullTextGroup;
|
||||
}
|
||||
|
||||
if (count($group) === 0) {
|
||||
$result['whereClause'][] = [
|
||||
'id' => null
|
||||
];
|
||||
}
|
||||
|
||||
$result['whereClause'][] = [
|
||||
'OR' => $group
|
||||
];
|
||||
}
|
||||
|
||||
protected function applyAdditionalToTextFilterGroup($textFilter, &$group, &$result)
|
||||
{
|
||||
}
|
||||
|
||||
public function applyAccess(&$result)
|
||||
|
||||
@@ -149,6 +149,11 @@ class Install extends \Espo\Core\Upgrades\Actions\Base\Install
|
||||
'fileList' => $fileList,
|
||||
'description' => $manifest['description'],
|
||||
);
|
||||
|
||||
if (!empty($manifest['checkVersionUrl'])) {
|
||||
$data['checkVersionUrl'] = $manifest['checkVersionUrl'];
|
||||
}
|
||||
|
||||
$extensionEntity->set($data);
|
||||
|
||||
return $entityManager->saveEntity($extensionEntity);
|
||||
|
||||
@@ -95,7 +95,13 @@ class AdminNotificationManager
|
||||
$extensionsNeedingUpgrade = $this->getExtensionsNeedingUpgrade();
|
||||
if (!empty($extensionsNeedingUpgrade)) {
|
||||
foreach ($extensionsNeedingUpgrade as $extensionName => $extensionDetails) {
|
||||
$message = $this->getLanguage()->translate('newExtensionVersionIsAvailable', 'messages', 'Admin');
|
||||
$label = 'new' . Util::toCamelCase($extensionName, ' ', true) .'VersionIsAvailable';
|
||||
|
||||
$message = $this->getLanguage()->get(['Admin', 'messages', $label]);
|
||||
if (!$message) {
|
||||
$message = $this->getLanguage()->translate('newExtensionVersionIsAvailable', 'messages', 'Admin');
|
||||
}
|
||||
|
||||
$notificationList[] = array(
|
||||
'id' => 'newExtensionVersionIsAvailable' . Util::toCamelCase($extensionName, ' ', true),
|
||||
'type' => 'newExtensionVersionIsAvailable',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -251,6 +251,8 @@ class LDAP extends Base
|
||||
$data[$fieldName] = $fieldValue;
|
||||
}
|
||||
|
||||
$this->getAuth()->useNoAuth();
|
||||
|
||||
$user = $this->getEntityManager()->getEntity('User');
|
||||
$user->set($data);
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@
|
||||
|
||||
namespace Espo\Core\Utils\Database;
|
||||
|
||||
use Espo\Core\Utils\Util,
|
||||
Espo\ORM\Entity;
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class Converter
|
||||
{
|
||||
@@ -38,15 +38,18 @@ class Converter
|
||||
|
||||
private $fileManager;
|
||||
|
||||
private $config;
|
||||
|
||||
private $schemaConverter;
|
||||
|
||||
private $schemaFromMetadata = null;
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager)
|
||||
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Config $config = null)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->fileManager = $fileManager;
|
||||
$this->ormConverter = new Orm\Converter($this->metadata, $this->fileManager);
|
||||
$this->config = $config;
|
||||
$this->ormConverter = new Orm\Converter($this->metadata, $this->fileManager, $this->config);
|
||||
}
|
||||
|
||||
protected function getMetadata()
|
||||
|
||||
@@ -315,12 +315,142 @@ class MySqlPlatform extends \Doctrine\DBAL\Platforms\MySqlPlatform
|
||||
$options['collate'] = 'utf8mb4_unicode_ci';
|
||||
}
|
||||
|
||||
return parent::_getCreateTableSQL($tableName, $columns, $options);
|
||||
$queryFields = $this->getColumnDeclarationListSQL($columns);
|
||||
|
||||
if (isset($options['uniqueConstraints']) && ! empty($options['uniqueConstraints'])) {
|
||||
foreach ($options['uniqueConstraints'] as $index => $definition) {
|
||||
$queryFields .= ', ' . $this->getUniqueConstraintDeclarationSQL($index, $definition);
|
||||
}
|
||||
}
|
||||
|
||||
// add all indexes
|
||||
if (isset($options['indexes']) && ! empty($options['indexes'])) {
|
||||
foreach($options['indexes'] as $index => $definition) {
|
||||
$queryFields .= ', ' . $this->getIndexDeclarationSQL($index, $definition);
|
||||
}
|
||||
}
|
||||
|
||||
// attach all primary keys
|
||||
if (isset($options['primary']) && ! empty($options['primary'])) {
|
||||
$keyColumns = array_unique(array_values($options['primary']));
|
||||
$queryFields .= ', PRIMARY KEY(' . implode(', ', $keyColumns) . ')';
|
||||
}
|
||||
|
||||
$query = 'CREATE ';
|
||||
|
||||
if (!empty($options['temporary'])) {
|
||||
$query .= 'TEMPORARY ';
|
||||
}
|
||||
|
||||
$query .= 'TABLE ' . $this->espoQuote($tableName) . ' (' . $queryFields . ') ';
|
||||
$query .= $this->buildTableOptions($options);
|
||||
$query .= $this->buildPartitionOptions($options);
|
||||
|
||||
$sql[] = $query;
|
||||
|
||||
if (isset($options['foreignKeys'])) {
|
||||
foreach ((array) $options['foreignKeys'] as $definition) {
|
||||
$sql[] = $this->getCreateForeignKeySQL($definition, $tableName);
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
public function getColumnCollationDeclarationSQL($collation)
|
||||
{
|
||||
return $this->getCollationFieldDeclaration($collation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SQL for table options
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function buildTableOptions(array $options)
|
||||
{
|
||||
if (isset($options['table_options'])) {
|
||||
return $options['table_options'];
|
||||
}
|
||||
|
||||
$tableOptions = array();
|
||||
|
||||
// Charset
|
||||
if ( ! isset($options['charset'])) {
|
||||
$options['charset'] = 'utf8';
|
||||
}
|
||||
|
||||
$tableOptions[] = sprintf('DEFAULT CHARACTER SET %s', $options['charset']);
|
||||
|
||||
// Collate
|
||||
if ( ! isset($options['collate'])) {
|
||||
$options['collate'] = 'utf8_unicode_ci';
|
||||
}
|
||||
|
||||
$tableOptions[] = sprintf('COLLATE %s', $options['collate']);
|
||||
|
||||
// Engine
|
||||
if ( ! isset($options['engine'])) {
|
||||
$options['engine'] = 'InnoDB';
|
||||
}
|
||||
|
||||
$tableOptions[] = sprintf('ENGINE = %s', $options['engine']);
|
||||
|
||||
// Auto increment
|
||||
if (isset($options['auto_increment'])) {
|
||||
$tableOptions[] = sprintf('AUTO_INCREMENT = %s', $options['auto_increment']);
|
||||
}
|
||||
|
||||
// Comment
|
||||
if (isset($options['comment'])) {
|
||||
$comment = trim($options['comment'], " '");
|
||||
|
||||
$tableOptions[] = sprintf("COMMENT = '%s' ", str_replace("'", "''", $comment));
|
||||
}
|
||||
|
||||
// Row format
|
||||
if (isset($options['row_format'])) {
|
||||
$tableOptions[] = sprintf('ROW_FORMAT = %s', $options['row_format']);
|
||||
}
|
||||
|
||||
return implode(' ', $tableOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build SQL for partition options.
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function buildPartitionOptions(array $options)
|
||||
{
|
||||
return (isset($options['partition_options']))
|
||||
? ' ' . $options['partition_options']
|
||||
: '';
|
||||
}
|
||||
|
||||
public function getClobTypeDeclarationSQL(array $field)
|
||||
{
|
||||
if ( ! empty($field['length']) && is_numeric($field['length'])) {
|
||||
$length = $field['length'];
|
||||
|
||||
if ($length <= static::LENGTH_LIMIT_TINYTEXT) {
|
||||
return 'TINYTEXT';
|
||||
}
|
||||
|
||||
if ($length <= static::LENGTH_LIMIT_TEXT) {
|
||||
return 'TEXT';
|
||||
}
|
||||
|
||||
if ($length > static::LENGTH_LIMIT_MEDIUMTEXT) {
|
||||
return 'LONGTEXT';
|
||||
}
|
||||
}
|
||||
|
||||
return 'MEDIUMTEXT';
|
||||
}
|
||||
//end: ESPO
|
||||
}
|
||||
188
application/Espo/Core/Utils/Database/Helper.php
Normal file
188
application/Espo/Core/Utils/Database/Helper.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?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;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class Helper
|
||||
{
|
||||
private $config;
|
||||
|
||||
private $connection;
|
||||
|
||||
protected $drivers = array(
|
||||
'mysqli' => '\Espo\Core\Utils\Database\DBAL\Driver\Mysqli\Driver',
|
||||
'pdo_mysql' => '\Espo\Core\Utils\Database\DBAL\Driver\PDOMySql\Driver',
|
||||
);
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Config $config = null)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function getDbalConnection()
|
||||
{
|
||||
if (!isset($this->connection)) {
|
||||
if (!$this->getConfig()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$connectionParams = $this->getConfig()->get('database');
|
||||
|
||||
if (empty($connectionParams['dbname']) || empty($connectionParams['user'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$connectionParams['driverClass'] = $this->drivers[ $connectionParams['driver'] ];
|
||||
unset($connectionParams['driver']);
|
||||
|
||||
$dbalConfig = new \Doctrine\DBAL\Configuration();
|
||||
$this->connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $dbalConfig);
|
||||
}
|
||||
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum index length. If $tableName is empty get a value for all database tables
|
||||
*
|
||||
* @param string|null $tableName
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxIndexLength($tableName = null, $default = 1000)
|
||||
{
|
||||
$mysqlEngine = $this->getMysqlEngine($tableName);
|
||||
if (!$mysqlEngine) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
switch ($mysqlEngine) {
|
||||
case 'InnoDB':
|
||||
$mysqlVersion = $this->getMysqlVersion();
|
||||
|
||||
if (version_compare($mysqlVersion, '10.0.0') >= 0) {
|
||||
return 767; //InnoDB, MariaDB
|
||||
}
|
||||
|
||||
if (version_compare($mysqlVersion, '5.7.0') >= 0) {
|
||||
return 3072; //InnoDB, MySQL 5.7+
|
||||
}
|
||||
|
||||
return 767; //InnoDB
|
||||
break;
|
||||
}
|
||||
|
||||
return 1000; //MyISAM
|
||||
}
|
||||
|
||||
public function getTableMaxIndexLength($tableName, $default = 1000)
|
||||
{
|
||||
return $this->getMaxIndexLength($tableName, $default);
|
||||
}
|
||||
|
||||
protected function getMysqlVersion()
|
||||
{
|
||||
$connection = $this->getDbalConnection();
|
||||
if (!$connection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $connection->fetchColumn("select version()");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table/database tables engine. If $tableName is empty get a value for all database tables
|
||||
*
|
||||
* @param string|null $tableName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getMysqlEngine($tableName = null, $default = null)
|
||||
{
|
||||
$connection = $this->getDbalConnection();
|
||||
if (!$connection) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$query = "SHOW TABLE STATUS WHERE Engine = 'MyISAM'";
|
||||
if (!empty($tableName)) {
|
||||
$query = "SHOW TABLE STATUS WHERE Engine = 'MyISAM' AND Name = '" . $tableName . "'";
|
||||
}
|
||||
|
||||
$result = $connection->fetchColumn($query);
|
||||
|
||||
if (!empty($result)) {
|
||||
return 'MyISAM';
|
||||
}
|
||||
|
||||
return 'InnoDB';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if full text supports. If $tableName is empty get a value for all database tables
|
||||
*
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSupportsFulltext($tableName = null, $default = false)
|
||||
{
|
||||
$mysqlEngine = $this->getMysqlEngine($tableName);
|
||||
if (!$mysqlEngine) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
switch ($mysqlEngine) {
|
||||
case 'InnoDB':
|
||||
$mysqlVersion = $this->getMysqlVersion();
|
||||
|
||||
if (version_compare($mysqlVersion, '5.6.4') >= 0) {
|
||||
return true; //InnoDB, MySQL 5.6.4+
|
||||
}
|
||||
|
||||
return false; //InnoDB
|
||||
break;
|
||||
}
|
||||
|
||||
return true; //MyISAM
|
||||
}
|
||||
|
||||
public function isTableSupportsFulltext($tableName, $default = false)
|
||||
{
|
||||
return $this->isSupportsFulltext($tableName, $default);
|
||||
}
|
||||
}
|
||||
@@ -28,20 +28,28 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Orm;
|
||||
use Espo\Core\Utils\Util,
|
||||
Espo\ORM\Entity;
|
||||
|
||||
use Espo\Core\Utils\Util;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class Converter
|
||||
{
|
||||
private $metadata;
|
||||
|
||||
private $fileManager;
|
||||
|
||||
private $config;
|
||||
|
||||
private $metadataHelper;
|
||||
|
||||
private $databaseHelper;
|
||||
|
||||
private $relationManager;
|
||||
|
||||
private $entityDefs;
|
||||
|
||||
protected $defaultFieldType = 'varchar';
|
||||
|
||||
protected $defaultNaming = 'postfix';
|
||||
|
||||
protected $defaultLength = array(
|
||||
@@ -99,14 +107,16 @@ class Converter
|
||||
'additionalTables',
|
||||
);
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager)
|
||||
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Config $config = null)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->fileManager = $fileManager; //need to featue with ormHooks. Ex. isFollowed field
|
||||
$this->config = $config;
|
||||
|
||||
$this->relationManager = new RelationManager($this->metadata);
|
||||
|
||||
$this->metadataHelper = new \Espo\Core\Utils\Metadata\Helper($this->metadata);
|
||||
$this->databaseHelper = new \Espo\Core\Utils\Database\Helper($this->config);
|
||||
}
|
||||
|
||||
protected function getMetadata()
|
||||
@@ -114,6 +124,11 @@ class Converter
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function getEntityDefs($reload = false)
|
||||
{
|
||||
if (empty($this->entityDefs) || $reload) {
|
||||
@@ -138,6 +153,11 @@ class Converter
|
||||
return $this->metadataHelper;
|
||||
}
|
||||
|
||||
protected function getDatabaseHelper()
|
||||
{
|
||||
return $this->databaseHelper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orm metadata convertation process
|
||||
*
|
||||
@@ -186,6 +206,8 @@ class Converter
|
||||
|
||||
$ormMetadata = Util::merge($ormMetadata, $convertedLinks);
|
||||
|
||||
$this->applyFullTextSearch($ormMetadata, $entityName);
|
||||
|
||||
if (!empty($entityMetadata['collection']) && is_array($entityMetadata['collection'])) {
|
||||
$collectionDefs = $entityMetadata['collection'];
|
||||
$ormMetadata[$entityName]['collection'] = array();
|
||||
@@ -467,4 +489,47 @@ class Converter
|
||||
return $values;
|
||||
}
|
||||
|
||||
protected function applyFullTextSearch(&$ormMetadata, $entityType)
|
||||
{
|
||||
if (!$this->getDatabaseHelper()->isTableSupportsFulltext(Util::toUnderScore($entityType))) return;
|
||||
if (!$this->getMetadata()->get(['entityDefs', $entityType, 'collection', 'fullTextSearch'])) return;
|
||||
|
||||
$fieldList = $this->getMetadata()->get(['entityDefs', $entityType, 'collection', 'textFilterFields'], ['name']);
|
||||
|
||||
$fullTextSearchColumnList = [];
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
$defs = $this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field], []);
|
||||
if (empty($defs['type'])) continue;
|
||||
$fieldType = $defs['type'];
|
||||
if (!empty($defs['notStorable'])) continue;
|
||||
if (!$this->getMetadata()->get(['fields', $fieldType, 'fullTextSearch'])) continue;
|
||||
|
||||
$partList = $this->getMetadata()->get(['fields', $fieldType, 'fullTextSearchColumnList']);
|
||||
if ($partList) {
|
||||
if ($this->getMetadata()->get(['fields', $fieldType, 'naming']) === 'prefix') {
|
||||
foreach ($partList as $part) {
|
||||
$fullTextSearchColumnList[] = $part . ucfirst($field);
|
||||
}
|
||||
} else {
|
||||
foreach ($partList as $part) {
|
||||
$fullTextSearchColumnList[] = $field . ucfirst($part);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$fullTextSearchColumnList[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($fullTextSearchColumnList)) {
|
||||
$ormMetadata[$entityType]['fullTextSearchColumnList'] = $fullTextSearchColumnList;
|
||||
if (!array_key_exists('indexes', $ormMetadata[$entityType])) {
|
||||
$ormMetadata[$entityType]['indexes'] = [];
|
||||
}
|
||||
$ormMetadata[$entityType]['indexes']['system_fullTextSearch'] = [
|
||||
'columns' => $fullTextSearchColumnList,
|
||||
'flags' => ['fulltext']
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -46,40 +46,40 @@ class Email extends Base
|
||||
JOIN email_address ON email_address.id = entity_email_address.email_address_id
|
||||
WHERE
|
||||
entity_email_address.deleted = 0 AND entity_email_address.entity_type = '{$entityName}' AND
|
||||
email_address.deleted = 0 AND email_address.name LIKE {value}
|
||||
email_address.deleted = 0 AND email_address.lower LIKE {value}
|
||||
)",
|
||||
'=' => array(
|
||||
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
|
||||
'sql' => 'emailAddressesMultiple.name = {value}',
|
||||
'sql' => 'emailAddressesMultiple.lower = {value}',
|
||||
'distinct' => true
|
||||
),
|
||||
'<>' => array(
|
||||
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
|
||||
'sql' => 'emailAddressesMultiple.name <> {value}',
|
||||
'sql' => 'emailAddressesMultiple.lower <> {value}',
|
||||
'distinct' => true
|
||||
),
|
||||
'IN' => array(
|
||||
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
|
||||
'sql' => 'emailAddressesMultiple.name IN {value}',
|
||||
'sql' => 'emailAddressesMultiple.lower IN {value}',
|
||||
'distinct' => true
|
||||
),
|
||||
'NOT IN' => array(
|
||||
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
|
||||
'sql' => 'emailAddressesMultiple.name NOT IN {value}',
|
||||
'sql' => 'emailAddressesMultiple.lower NOT IN {value}',
|
||||
'distinct' => true
|
||||
),
|
||||
'IS NULL' => array(
|
||||
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
|
||||
'sql' => 'emailAddressesMultiple.name IS NULL',
|
||||
'sql' => 'emailAddressesMultiple.lower IS NULL',
|
||||
'distinct' => true
|
||||
),
|
||||
'IS NOT NULL' => array(
|
||||
'leftJoins' => [['emailAddresses', 'emailAddressesMultiple']],
|
||||
'sql' => 'emailAddressesMultiple.name IS NOT NULL',
|
||||
'sql' => 'emailAddressesMultiple.lower IS NOT NULL',
|
||||
'distinct' => true
|
||||
)
|
||||
),
|
||||
'orderBy' => 'emailAddresses.name {direction}',
|
||||
'orderBy' => 'emailAddresses.lower {direction}',
|
||||
),
|
||||
$fieldName .'Data' => array(
|
||||
'type' => 'text',
|
||||
|
||||
@@ -43,11 +43,11 @@ class HasMany extends Base
|
||||
$entityName => array (
|
||||
'fields' => array(
|
||||
$linkName.'Ids' => array(
|
||||
'type' => 'varchar',
|
||||
'type' => 'jsonArray',
|
||||
'notStorable' => true,
|
||||
),
|
||||
$linkName.'Names' => array(
|
||||
'type' => 'varchar',
|
||||
'type' => 'jsonObject',
|
||||
'notStorable' => true,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -50,11 +50,11 @@ class ManyMany extends Base
|
||||
$entityName => array(
|
||||
'fields' => array(
|
||||
$linkName.'Ids' => array(
|
||||
'type' => 'varchar',
|
||||
'type' => 'jsonArray',
|
||||
'notStorable' => true,
|
||||
),
|
||||
$linkName.'Names' => array(
|
||||
'type' => 'varchar',
|
||||
'type' => 'jsonObject',
|
||||
'notStorable' => true,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -81,11 +81,12 @@ class Converter
|
||||
|
||||
protected $maxIndexLength;
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Database\Schema\Schema $databaseSchema)
|
||||
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\Utils\Database\Schema\Schema $databaseSchema, \Espo\Core\Utils\Config $config = null)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->fileManager = $fileManager;
|
||||
$this->databaseSchema = $databaseSchema;
|
||||
$this->config = $config;
|
||||
|
||||
$this->typeList = array_keys(\Doctrine\DBAL\Types\Type::getTypesMap());
|
||||
}
|
||||
@@ -100,6 +101,11 @@ class Converter
|
||||
return $this->fileManager;
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema
|
||||
*
|
||||
@@ -124,7 +130,7 @@ class Converter
|
||||
protected function getMaxIndexLength()
|
||||
{
|
||||
if (!isset($this->maxIndexLength)) {
|
||||
$this->maxIndexLength = $this->getDatabaseSchema()->getMaxIndexLength();
|
||||
$this->maxIndexLength = $this->getDatabaseSchema()->getDatabaseHelper()->getMaxIndexLength();
|
||||
}
|
||||
|
||||
return $this->maxIndexLength;
|
||||
@@ -238,8 +244,10 @@ class Converter
|
||||
$tables[$entityName]->setPrimaryKey($primaryColumns);
|
||||
|
||||
if (!empty($indexList[$entityName])) {
|
||||
foreach($indexList[$entityName] as $indexName => $indexColumnList) {
|
||||
$tables[$entityName]->addIndex($indexColumnList, $indexName);
|
||||
foreach($indexList[$entityName] as $indexName => $indexParams) {
|
||||
$indexColumnList = $indexParams['columns'];
|
||||
$indexFlagList = isset($indexParams['flags']) ? $indexParams['flags'] : array();
|
||||
$tables[$entityName]->addIndex($indexColumnList, $indexName, $indexFlagList);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,12 +48,7 @@ class Schema
|
||||
|
||||
private $converter;
|
||||
|
||||
private $connection;
|
||||
|
||||
protected $drivers = array(
|
||||
'mysqli' => '\Espo\Core\Utils\Database\DBAL\Driver\Mysqli\Driver',
|
||||
'pdo_mysql' => '\Espo\Core\Utils\Database\DBAL\Driver\PDOMySql\Driver',
|
||||
);
|
||||
private $databaseHelper;
|
||||
|
||||
protected $fieldTypePaths = array(
|
||||
'application/Espo/Core/Utils/Database/DBAL/FieldTypes',
|
||||
@@ -79,8 +74,6 @@ class Schema
|
||||
*/
|
||||
protected $rebuildActionClasses = null;
|
||||
|
||||
|
||||
|
||||
public function __construct(\Espo\Core\Utils\Config $config, \Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, \Espo\Core\ORM\EntityManager $entityManager, \Espo\Core\Utils\File\ClassParser $classParser, \Espo\Core\Utils\Metadata\OrmMetadata $ormMetadata)
|
||||
{
|
||||
$this->config = $config;
|
||||
@@ -89,17 +82,17 @@ class Schema
|
||||
$this->entityManager = $entityManager;
|
||||
$this->classParser = $classParser;
|
||||
|
||||
$this->databaseHelper = new \Espo\Core\Utils\Database\Helper($this->config);
|
||||
|
||||
$this->comparator = new \Espo\Core\Utils\Database\DBAL\Schema\Comparator();
|
||||
$this->initFieldTypes();
|
||||
|
||||
$this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager);
|
||||
|
||||
$this->schemaConverter = new Converter($this->metadata, $this->fileManager, $this);
|
||||
$this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager, $this->config);
|
||||
$this->schemaConverter = new Converter($this->metadata, $this->fileManager, $this, $this->config);
|
||||
|
||||
$this->ormMetadata = $ormMetadata;
|
||||
}
|
||||
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
@@ -140,26 +133,16 @@ class Schema
|
||||
return $this->getConnection()->getDatabasePlatform();
|
||||
}
|
||||
|
||||
public function getDatabaseHelper()
|
||||
{
|
||||
return $this->databaseHelper;
|
||||
}
|
||||
|
||||
public function getConnection()
|
||||
{
|
||||
if (isset($this->connection)) {
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
$dbalConfig = new \Doctrine\DBAL\Configuration();
|
||||
|
||||
$connectionParams = $this->getConfig()->get('database');
|
||||
|
||||
$connectionParams['driverClass'] = $this->drivers[ $connectionParams['driver'] ];
|
||||
unset($connectionParams['driver']);
|
||||
|
||||
$this->connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $dbalConfig);
|
||||
|
||||
return $this->connection;
|
||||
return $this->getDatabaseHelper()->getDbalConnection();
|
||||
}
|
||||
|
||||
|
||||
protected function initFieldTypes()
|
||||
{
|
||||
foreach($this->fieldTypePaths as $path) {
|
||||
@@ -187,8 +170,6 @@ class Schema
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Rebuild database schema
|
||||
*/
|
||||
@@ -224,7 +205,6 @@ class Schema
|
||||
return (bool) $result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get current database schema
|
||||
*
|
||||
@@ -248,7 +228,6 @@ class Schema
|
||||
//return $schema->toSql($this->getPlatform()); //it can return with DROP TABLE
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Get SQL queries to get from one to another schema
|
||||
*
|
||||
@@ -261,8 +240,6 @@ class Schema
|
||||
return $this->toSql($schemaDiff); //$schemaDiff->toSql($this->getPlatform());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Init Rebuild Actions, get all classes and create them
|
||||
* @return void
|
||||
@@ -311,46 +288,4 @@ class Schema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getMaxIndexLength()
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
$mysqlEngine = $this->getMysqlEngine();
|
||||
|
||||
switch ($mysqlEngine) {
|
||||
case 'InnoDB':
|
||||
$mysqlVersion = $this->getMysqlVersion();
|
||||
|
||||
if (version_compare($mysqlVersion, '10.0.0') >= 0) {
|
||||
return 767; //InnoDB, MariaDB
|
||||
}
|
||||
|
||||
if (version_compare($mysqlVersion, '5.7.0') >= 0) {
|
||||
return 3072; //InnoDB, MySQL 5.7+
|
||||
}
|
||||
|
||||
return 767; //InnoDB
|
||||
break;
|
||||
}
|
||||
|
||||
return 1000; //MyISAM
|
||||
}
|
||||
|
||||
protected function getMysqlVersion()
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
return $connection->fetchColumn("select version()");
|
||||
}
|
||||
|
||||
protected function getMysqlEngine()
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
$result = $connection->fetchColumn("SHOW TABLE STATUS WHERE Engine = 'MyISAM'");
|
||||
|
||||
if (!empty($result)) {
|
||||
return 'MyISAM';
|
||||
}
|
||||
|
||||
return 'InnoDB';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ use Espo\Core\Utils\Util;
|
||||
|
||||
class Utils
|
||||
{
|
||||
public static function getIndexList(array $ormMeta)
|
||||
public static function getIndexList(array $ormMeta, array $ignoreFlags = [])
|
||||
{
|
||||
$indexList = array();
|
||||
|
||||
@@ -53,19 +53,37 @@ class Utils
|
||||
|
||||
if ($keyValue === true) {
|
||||
$tableIndexName = static::generateIndexName($columnName);
|
||||
$indexList[$entityName][$tableIndexName] = array($columnName);
|
||||
$indexList[$entityName][$tableIndexName]['columns'] = array($columnName);
|
||||
} else if (is_string($keyValue)) {
|
||||
$tableIndexName = static::generateIndexName($keyValue);
|
||||
$indexList[$entityName][$tableIndexName][] = $columnName;
|
||||
$indexList[$entityName][$tableIndexName]['columns'][] = $columnName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($entityParams['indexes']) && is_array($entityParams['indexes'])) {
|
||||
foreach ($entityParams['indexes'] as $indexName => $indexParams) {
|
||||
$tableIndexName = static::generateIndexName($indexName);
|
||||
|
||||
if (isset($indexParams['flags']) && is_array($indexParams['flags'])) {
|
||||
|
||||
$skipIndex = false;
|
||||
foreach ($ignoreFlags as $ignoreFlag) {
|
||||
if (($flagKey = array_search($ignoreFlag, $indexParams['flags'])) !== false) {
|
||||
unset($indexParams['flags'][$flagKey]);
|
||||
$skipIndex = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($skipIndex && empty($indexParams['flags'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indexList[$entityName][$tableIndexName]['flags'] = $indexParams['flags'];
|
||||
}
|
||||
|
||||
if (is_array($indexParams['columns'])) {
|
||||
$tableIndexName = static::generateIndexName($indexName);
|
||||
$indexList[$entityName][$tableIndexName] = Util::toUnderScore($indexParams['columns']);
|
||||
$indexList[$entityName][$tableIndexName]['columns'] = Util::toUnderScore($indexParams['columns']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +101,7 @@ class Utils
|
||||
return substr(implode('_', $nameList), 0, $maxLength);
|
||||
}
|
||||
|
||||
public static function getFieldListExceededIndexMaxLength(array $ormMeta, $indexMaxLength = 1000, $characterLength = 4)
|
||||
public static function getFieldListExceededIndexMaxLength(array $ormMeta, $indexMaxLength = 1000, array $indexList = null, $characterLength = 4)
|
||||
{
|
||||
$permittedFieldTypeList = [
|
||||
'varchar',
|
||||
@@ -91,10 +109,14 @@ class Utils
|
||||
|
||||
$fields = array();
|
||||
|
||||
$indexList = static::getIndexList($ormMeta);
|
||||
if (!isset($indexList)) {
|
||||
$indexList = static::getIndexList($ormMeta, ['fulltext']);
|
||||
}
|
||||
|
||||
foreach ($indexList as $entityName => $indexes) {
|
||||
foreach ($indexes as $indexName => $columnList) {
|
||||
foreach ($indexes as $indexName => $indexParams) {
|
||||
$columnList = $indexParams['columns'];
|
||||
|
||||
$indexLength = 0;
|
||||
foreach ($columnList as $columnName) {
|
||||
$fieldName = Util::toCamelCase($columnName);
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?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\Schema\rebuildActions;
|
||||
|
||||
class FulltextIndex extends \Espo\Core\Utils\Database\Schema\BaseRebuildActions
|
||||
{
|
||||
public function beforeRebuild()
|
||||
{
|
||||
$currentSchema = $this->getCurrentSchema();
|
||||
$tables = $currentSchema->getTables();
|
||||
|
||||
if (empty($tables)) return;
|
||||
|
||||
$databaseHelper = new \Espo\Core\Utils\Database\Helper($this->getConfig());
|
||||
$connection = $databaseHelper->getDbalConnection();
|
||||
|
||||
$metadataSchema = $this->getMetadataSchema();
|
||||
$tables = $metadataSchema->getTables();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$tableName = $table->getName();
|
||||
$indexes = $table->getIndexes();
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
if (!$index->hasFlag('fulltext')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columns = $index->getColumns();
|
||||
foreach ($columns as $columnName) {
|
||||
|
||||
$query = "SHOW FULL COLUMNS FROM `". $tableName ."` WHERE Field = '" . $columnName . "'";
|
||||
|
||||
try {
|
||||
$row = $connection->fetchAssoc($query);
|
||||
} catch (\Exception $e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (strtoupper($row['Type'])) {
|
||||
case 'LONGTEXT':
|
||||
$alterQuery = "ALTER TABLE `". $tableName ."` MODIFY `". $columnName ."` MEDIUMTEXT COLLATE ". $row['Collation'] ."";
|
||||
$GLOBALS['log']->info('SCHEMA, Execute Query: ' . $alterQuery);
|
||||
$connection->executeQuery($alterQuery);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,28 @@ class EntityManager
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function checkRelationshipExists($name)
|
||||
{
|
||||
$name = ucfirst($name);
|
||||
|
||||
$scopeList = array_keys($this->getMetadata()->get(['scopes'], []));
|
||||
|
||||
foreach ($scopeList as $entityType) {
|
||||
$relationsDefs = $this->getEntityManager()->getMetadata()->get($entityType, 'relations');
|
||||
if (empty($relationsDefs)) continue;
|
||||
foreach ($relationsDefs as $link => $item) {
|
||||
if (empty($item['type'])) continue;
|
||||
if (empty($item['relationName'])) continue;
|
||||
if ($item['type'] === 'manyMany') {
|
||||
if (ucfirst($item['relationName']) === $name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function create($name, $type, $params = [], $replaceData = [])
|
||||
{
|
||||
$name = ucfirst($name);
|
||||
@@ -172,6 +194,10 @@ class EntityManager
|
||||
throw new Conflict('Entity name \''.$name.'\' is not allowed.');
|
||||
}
|
||||
|
||||
if ($this->checkRelationshipExists($name)) {
|
||||
throw new Conflict('Relationship with the same name \''.$name.'\' exists.');
|
||||
}
|
||||
|
||||
$normalizedName = Util::normilizeClassName($name);
|
||||
|
||||
$templateNamespace = "\Espo\Core\Templates";
|
||||
@@ -403,6 +429,16 @@ class EntityManager
|
||||
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
|
||||
}
|
||||
|
||||
|
||||
if (isset($data['fullTextSearch'])) {
|
||||
$entityDefsData = [
|
||||
'collection' => [
|
||||
'fullTextSearch' => !!$data['fullTextSearch']
|
||||
]
|
||||
];
|
||||
$this->getMetadata()->set('entityDefs', $name, $entityDefsData);
|
||||
}
|
||||
|
||||
if (array_key_exists('kanbanStatusIgnoreList', $data)) {
|
||||
$scopeData['kanbanStatusIgnoreList'] = $data['kanbanStatusIgnoreList'];
|
||||
$this->getMetadata()->set('scopes', $name, $scopeData);
|
||||
@@ -546,6 +582,12 @@ class EntityManager
|
||||
} else {
|
||||
$relationName = lcfirst($entity) . $entityForeign;
|
||||
}
|
||||
if ($this->getMetadata()->get(['scopes', ucfirst($relationName)])) {
|
||||
throw new Conflict("Entity with the same name '{$relationName}' exists.");
|
||||
}
|
||||
if ($this->checkRelationshipExists($relationName)) {
|
||||
throw new Conflict("Relationship with the same name '{$relationName}' exists.");
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($link) || empty($linkForeign)) {
|
||||
|
||||
@@ -66,7 +66,11 @@ class FieldManagerUtil
|
||||
}
|
||||
if ($naming == 'prefix') {
|
||||
foreach ($list as $f) {
|
||||
$fieldList[] = $f . ucfirst($name);
|
||||
if ($f === '') {
|
||||
$fieldList[] = $name;
|
||||
} else {
|
||||
$fieldList[] = $f . ucfirst($name);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
foreach ($list as $f) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -41,19 +41,28 @@ class OrmMetadata
|
||||
|
||||
protected $fileManager;
|
||||
|
||||
protected $config;
|
||||
|
||||
protected $useCache;
|
||||
|
||||
public function __construct($metadata, $fileManager, $useCache = false)
|
||||
public function __construct(\Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager, $config)
|
||||
{
|
||||
$this->metadata = $metadata;
|
||||
$this->fileManager = $fileManager;
|
||||
$this->useCache = $useCache;
|
||||
|
||||
$this->useCache = false;
|
||||
if ($config instanceof \Espo\Core\Utils\Config) {
|
||||
$this->config = $config;
|
||||
$this->useCache = $this->config->get('useCache', false);
|
||||
} elseif (is_bool($config)) {
|
||||
$this->useCache = $config;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getConverter()
|
||||
{
|
||||
if (!isset($this->converter)) {
|
||||
$this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager);
|
||||
$this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager, $this->config);
|
||||
}
|
||||
|
||||
return $this->converter;
|
||||
@@ -64,6 +73,11 @@ class OrmMetadata
|
||||
return $this->fileManager;
|
||||
}
|
||||
|
||||
protected function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
public function clearData()
|
||||
{
|
||||
$this->ormData = null;
|
||||
|
||||
@@ -86,10 +86,12 @@ class Util
|
||||
return $name;
|
||||
}
|
||||
|
||||
if($capitaliseFirstChar) {
|
||||
$name[0] = strtoupper($name[0]);
|
||||
$name = lcfirst($name);
|
||||
if ($capitaliseFirstChar) {
|
||||
$name = ucfirst($name);
|
||||
}
|
||||
return preg_replace_callback('/'.$symbol.'([a-z])/', 'static::toCamelCaseConversion', $name);
|
||||
|
||||
return preg_replace_callback('/'.$symbol.'([a-zA-Z])/', 'static::toCamelCaseConversion', $name);
|
||||
}
|
||||
|
||||
protected static function toCamelCaseConversion($matches)
|
||||
|
||||
@@ -111,6 +111,7 @@ return array (
|
||||
'adminNotifications' => true,
|
||||
'adminNotificationsNewVersion' => true,
|
||||
'adminNotificationsCronIsNotConfigured' => true,
|
||||
'adminNotificationsNewExtensionVersion' => true,
|
||||
'assignmentEmailNotifications' => false,
|
||||
'assignmentEmailNotificationsEntityList' => ['Lead', 'Opportunity', 'Task', 'Case'],
|
||||
'assignmentNotificationsEntityList' => ['Meeting', 'Call', 'Task', 'Email'],
|
||||
@@ -170,6 +171,9 @@ return array (
|
||||
'inlineAttachmentUploadMaxSize' => 20,
|
||||
'textFilterUseContainsForVarchar' => false,
|
||||
'tabColorsDisabled' => false,
|
||||
'massPrintPdfMaxCount' => 50,
|
||||
'emailKeepParentTeamsEntityList' => ['Case'],
|
||||
'recordListMaxSizeLimit' => 200,
|
||||
'isInstalled' => false
|
||||
);
|
||||
|
||||
|
||||
@@ -154,7 +154,11 @@ return array ( 'defaultPermissions' =>
|
||||
'ldapUserTeamsIds',
|
||||
'ldapUserTeamsNames',
|
||||
'cleanupJobPeriod',
|
||||
'cleanupActionHistoryPeriod'
|
||||
'cleanupActionHistoryPeriod',
|
||||
'adminNotifications',
|
||||
'adminNotificationsNewVersion',
|
||||
'adminNotificationsCronIsNotConfigured',
|
||||
'adminNotificationsNewExtensionVersion'
|
||||
),
|
||||
'userItems' =>
|
||||
array (
|
||||
|
||||
@@ -76,8 +76,8 @@ class CurrencyConverted extends \Espo\Core\Hooks\Base
|
||||
$targetValue = $value;
|
||||
} else {
|
||||
$targetValue = $value;
|
||||
$targetValue = $targetValue * (isset($rates[$defaultCurrency]) ? $rates[$defaultCurrency] : 1.0);
|
||||
$targetValue = $targetValue / (isset($rates[$currency]) ? $rates[$currency] : 1.0);
|
||||
$targetValue = $targetValue / (isset($rates[$baseCurrency]) ? $rates[$baseCurrency] : 1.0);
|
||||
$targetValue = $targetValue * (isset($rates[$currency]) ? $rates[$currency] : 1.0);
|
||||
$targetValue = round($targetValue, 2);
|
||||
}
|
||||
$entity->set($fieldName, $targetValue);
|
||||
|
||||
54
application/Espo/Jobs/CheckNewExtensionVersion.php
Normal file
54
application/Espo/Jobs/CheckNewExtensionVersion.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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\Jobs;
|
||||
|
||||
use Espo\Core\Exceptions;
|
||||
|
||||
class CheckNewExtensionVersion extends CheckNewVersion
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
if (!$this->getConfig()->get('adminNotifications') || !$this->getConfig()->get('adminNotificationsNewExtensionVersion')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$job = $this->getEntityManager()->getEntity('Job');
|
||||
$job->set(array(
|
||||
'name' => 'Check for new versions of installed extensions (job)',
|
||||
'serviceName' => 'AdminNotifications',
|
||||
'methodName' => 'jobCheckNewExtensionVersion',
|
||||
'executeTime' => $this->getRunTime()
|
||||
));
|
||||
|
||||
$this->getEntityManager()->saveEntity($job);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +191,7 @@ class Cleanup extends \Espo\Core\Jobs\Base
|
||||
$collection = $this->getEntityManager()->getRepository('Attachment')->where(array(
|
||||
'OR' => array(
|
||||
array(
|
||||
'role' => ['Export File']
|
||||
'role' => ['Export File', 'Mail Merge', 'Mass Pdf']
|
||||
)
|
||||
),
|
||||
'createdAt<' => $datetime->format('Y-m-d H:i:s')
|
||||
|
||||
@@ -37,7 +37,7 @@ class Activities extends \Espo\Core\Controllers\Base
|
||||
{
|
||||
protected $maxCalendarRange = 123;
|
||||
|
||||
protected $maxSizeLimit = 200;
|
||||
const MAX_SIZE_LIMIT = 200;
|
||||
|
||||
public function actionListCalendarEvents($params, $data, $request)
|
||||
{
|
||||
@@ -113,11 +113,12 @@ class Activities extends \Espo\Core\Controllers\Base
|
||||
|
||||
$futureDays = intval($request->get('futureDays'));
|
||||
|
||||
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
|
||||
if (empty($maxSize)) {
|
||||
$maxSize = $this->maxSizeLimit;
|
||||
$maxSize = $maxSizeLimit;
|
||||
}
|
||||
if ($maxSize > $this->maxSizeLimit) {
|
||||
throw new Forbidden("Max should should not exceed " . $this->maxSizeLimit . ". Use pagination (offset, limit).");
|
||||
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
|
||||
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
return $service->getUpcomingActivities($userId, array(
|
||||
@@ -175,11 +176,12 @@ class Activities extends \Espo\Core\Controllers\Base
|
||||
$sortBy = $request->get('sortBy');
|
||||
$where = $request->get('where');
|
||||
|
||||
$maxSizeLimit = $this->getConfig()->get('recordListMaxSizeLimit', self::MAX_SIZE_LIMIT);
|
||||
if (empty($maxSize)) {
|
||||
$maxSize = $this->maxSizeLimit;
|
||||
$maxSize = $maxSizeLimit;
|
||||
}
|
||||
if ($maxSize > $this->maxSizeLimit) {
|
||||
throw new Forbidden("Max should should not exceed " . $this->maxSizeLimit . ". Use pagination (offset, limit).");
|
||||
if (!empty($maxSize) && $maxSize > $maxSizeLimit) {
|
||||
throw new Forbidden("Max should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
$scope = null;
|
||||
|
||||
@@ -29,7 +29,27 @@
|
||||
|
||||
namespace Espo\Modules\Crm\Controllers;
|
||||
|
||||
use \Espo\Core\Exceptions\Error,
|
||||
\Espo\Core\Exceptions\Forbidden,
|
||||
\Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
class Campaign extends \Espo\Core\Controllers\Record
|
||||
{
|
||||
public function postActionGenerateMailMergePdf($params, $data, $request)
|
||||
{
|
||||
if (empty($data->campaignId)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
if (empty($data->link)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if (!$this->getAcl()->checkScope('Campaign', 'read')) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $this->getRecordService()->generateMailMergePdf($data->campaignId, $data->link, true)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,9 @@ class Opportunity extends \Espo\Core\Controllers\Record
|
||||
$dateFrom = $request->get('dateFrom');
|
||||
$dateTo = $request->get('dateTo');
|
||||
$dateFilter = $request->get('dateFilter');
|
||||
$useLastStage = $request->get('useLastStage') === 'true';
|
||||
|
||||
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo);
|
||||
return $this->getService('Opportunity')->reportSalesPipeline($dateFilter, $dateFrom, $dateTo, $useLastStage);
|
||||
}
|
||||
|
||||
public function postActionMassConvertCurrency($params, $data, $request)
|
||||
|
||||
@@ -63,6 +63,9 @@ class TargetList extends \Espo\Core\Controllers\Record
|
||||
if (empty($data->targetId)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
$data->id = strval($data->id);
|
||||
$data->targetId = strval($data->targetId);
|
||||
|
||||
return $this->getRecordService()->optOut($data->id, $data->targetType, $data->targetId);
|
||||
}
|
||||
|
||||
@@ -77,6 +80,9 @@ class TargetList extends \Espo\Core\Controllers\Record
|
||||
if (empty($data->targetId)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
$data->id = strval($data->id);
|
||||
$data->targetId = strval($data->targetId);
|
||||
|
||||
return $this->getRecordService()->cancelOptOut($data->id, $data->targetType, $data->targetId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,8 @@ class EventConfirmation extends \Espo\Core\EntryPoints\Base
|
||||
";
|
||||
|
||||
$this->getClientManager()->display($runScript);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error();
|
||||
|
||||
@@ -56,7 +56,7 @@ class CaseObj extends \Espo\Core\ORM\Repositories\RDB
|
||||
if ($entity->getFetched('contactId')) {
|
||||
$previousPortalUser = $this->getEntityManager()->getRepository('User')->where(array(
|
||||
'contactId' => $entity->getFetched('contactId'),
|
||||
'isPortal' => true
|
||||
'isPortalUser' => true
|
||||
))->findOne();
|
||||
if ($previousPortalUser) {
|
||||
$this->getInjection('serviceFactory')->create('Stream')->unfollowEntity($entity, $previousPortalUser->id);
|
||||
@@ -70,9 +70,10 @@ class CaseObj extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
$portalUser = $this->getEntityManager()->getRepository('User')->where(array(
|
||||
'contactId' => $contactId,
|
||||
'isPortal' => true,
|
||||
'isPortalUser' => true,
|
||||
'isActive' => true
|
||||
))->findOne();
|
||||
|
||||
if ($portalUser) {
|
||||
$this->getInjection('serviceFactory')->create('Stream')->followEntity($entity, $portalUser->id);
|
||||
}
|
||||
@@ -97,4 +98,3 @@ class CaseObj extends \Espo\Core\ORM\Repositories\RDB
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,5 +41,19 @@ class Lead extends \Espo\Core\ORM\Repositories\RDB
|
||||
$this->relate($entity, 'targetLists', $entity->get('targetListId'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function handleSelectParams(&$params)
|
||||
{
|
||||
parent::handleSelectParams($params);
|
||||
if (array_key_exists('select', $params)) {
|
||||
if (in_array('name', $params['select'])) {
|
||||
$additionalAttributeList = ['emailAddress', 'phoneNumber', 'accountName'];
|
||||
foreach ($additionalAttributeList as $attribute) {
|
||||
if (!in_array($attribute, $params['select'])) {
|
||||
$params['select'][] = $attribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,53 @@ class Opportunity extends \Espo\Core\ORM\Repositories\RDB
|
||||
}
|
||||
}
|
||||
|
||||
if (!$entity->isAttributeChanged('lastStage') && $entity->isAttributeChanged('stage')) {
|
||||
$probability = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap', $entity->get('stage')], 0);
|
||||
$probabilityMap = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap'], []);
|
||||
|
||||
if (!$probability) {
|
||||
$stageList = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
|
||||
if ($entity->isNew()) {
|
||||
if (count($stageList)) {
|
||||
$min = 100;
|
||||
$minStage = null;
|
||||
foreach ($stageList as $stage) {
|
||||
if (!empty($probabilityMap[$stage]) && $probabilityMap[$stage] !== 100) {
|
||||
if ($probabilityMap[$stage] < $min) {
|
||||
$min = $probabilityMap[$stage];
|
||||
$minStage = $stage;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($minStage) {
|
||||
$entity->set('lastStage', $minStage);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$lastStageProbability = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap', $entity->get('lastStage')], 0);
|
||||
if ($lastStageProbability === 100) {
|
||||
if (count($stageList)) {
|
||||
$max = 0;
|
||||
$maxStage = null;
|
||||
foreach ($stageList as $stage) {
|
||||
if (!empty($probabilityMap[$stage]) && $probabilityMap[$stage] !== 100) {
|
||||
if ($probabilityMap[$stage] > $max) {
|
||||
$max = $probabilityMap[$stage];
|
||||
$maxStage = $stage;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($maxStage) {
|
||||
$entity->set('lastStage', $maxStage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$entity->set('lastStage', $entity->get('stage'));
|
||||
}
|
||||
}
|
||||
|
||||
parent::beforeSave($entity, $options);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,12 @@
|
||||
"revenue": "Revenue",
|
||||
"revenueConverted": "Revenue (converted)",
|
||||
"budget": "Budget",
|
||||
"budgetConverted": "Budget (converted)"
|
||||
"budgetConverted": "Budget (converted)",
|
||||
"contactsTemplate": "Contacts Template",
|
||||
"leadsTemplate": "Leads Template",
|
||||
"accountsTemplate": "Accounts Template",
|
||||
"usersTemplate": "Users Template",
|
||||
"mailMergeOnlyWithAddress": "Skip records w/o filled address"
|
||||
},
|
||||
"links": {
|
||||
"targetLists": "Target Lists",
|
||||
@@ -30,7 +35,11 @@
|
||||
"opportunities": "Opportunities",
|
||||
"campaignLogRecords": "Log",
|
||||
"massEmails": "Mass Emails",
|
||||
"trackingUrls": "Tracking URLs"
|
||||
"trackingUrls": "Tracking URLs",
|
||||
"contactsTemplate": "Contacts Template",
|
||||
"leadsTemplate": "Leads Template",
|
||||
"accountsTemplate": "Accounts Template",
|
||||
"usersTemplate": "Users Template"
|
||||
},
|
||||
"options": {
|
||||
"type": {
|
||||
@@ -59,7 +68,9 @@
|
||||
"Email Templates": "Email Templates",
|
||||
"Unsubscribe again": "Unsubscribe again",
|
||||
"Subscribe again": "Subscribe again",
|
||||
"Create Target List": "Create Target List"
|
||||
"Create Target List": "Create Target List",
|
||||
"Mail Merge": "Mail Merge",
|
||||
"Generate Mail Merge PDF": "Generate Mail Merge PDF"
|
||||
},
|
||||
"presetFilters": {
|
||||
"active": "Active"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"fields": {
|
||||
"futureDays": "Next X Days"
|
||||
"futureDays": "Next X Days",
|
||||
"useLastStage": "Group by last reached stage"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
"campaign": "Campaign",
|
||||
"originalLead": "Original Lead",
|
||||
"amountCurrency": "Amount Currency",
|
||||
"contactRole": "Contact Role"
|
||||
"contactRole": "Contact Role",
|
||||
"lastStage": "Last Stage"
|
||||
},
|
||||
"links": {
|
||||
"contacts": "Contacts",
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"entryCount": "Entry Count",
|
||||
"optedOutCount": "Opted Out Count",
|
||||
"campaigns": "Campaigns",
|
||||
"endDate": "End Date",
|
||||
"targetLists": "Target Lists",
|
||||
"includingActionList": "Including",
|
||||
"excludingActionList": "Excluding"
|
||||
"excludingActionList": "Excluding",
|
||||
"targetStatus": "Target Status",
|
||||
"isOptedOut": "Is Opted Out"
|
||||
},
|
||||
"links": {
|
||||
"accounts": "Accounts",
|
||||
@@ -23,6 +26,10 @@
|
||||
"Television": "Television",
|
||||
"Radio": "Radio",
|
||||
"Newsletter": "Newsletter"
|
||||
},
|
||||
"targetStatus": {
|
||||
"Opted Out": "Opted Out",
|
||||
"Listed": "Listed"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{
|
||||
"label":"Overview",
|
||||
"label": "Overview",
|
||||
"rows":[
|
||||
[{"name":"name"}, {"name":"status"}],
|
||||
[{"name":"type"}, {"name":"startDate"}],
|
||||
@@ -8,5 +8,13 @@
|
||||
[{"name":"targetLists"}, {"name":"excludingTargetLists"}],
|
||||
[{"name":"description", "fullWidth": true}]
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Mail Merge",
|
||||
"name": "mailMerge",
|
||||
"rows":[
|
||||
[{"name":"contactsTemplate"}, {"name":"leadsTemplate"}],
|
||||
[{"name":"accountsTemplate"}, {"name":"mailMergeOnlyWithAddress"}]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"label":"Overview",
|
||||
"rows":[
|
||||
[{"name":"name"}, {"name":"entryCount"}],
|
||||
[false, {"name":"optedOutCount"}],
|
||||
[{"name":"description", "fullWidth": true}]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"rows": [
|
||||
[{"name":"name", "fullWidth": true}],
|
||||
[{"name":"entryCount", "fullWidth": true}],
|
||||
[{"name":"optedOutCount", "fullWidth": true}],
|
||||
[{"name":"description", "fullWidth": true}]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
[
|
||||
{"name":"name", "link":true},
|
||||
{"name":"entryCount", "width": 25, "notSortable": true}
|
||||
{"name":"targetStatus", "width": 25, "notSortable": true}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
"contacts",
|
||||
"leads",
|
||||
"users",
|
||||
"accounts"
|
||||
"accounts",
|
||||
"users"
|
||||
]
|
||||
|
||||
@@ -61,7 +61,9 @@
|
||||
"create": false
|
||||
},
|
||||
"targetLists": {
|
||||
"layout": "listForTarget"
|
||||
"rowActionsView": "crm:views/record/row-actions/relationship-target",
|
||||
"layout": "listForTarget",
|
||||
"view": "crm:views/record/panels/target-lists"
|
||||
}
|
||||
},
|
||||
"filterList": [
|
||||
|
||||
@@ -56,36 +56,130 @@
|
||||
"filterList": [
|
||||
"active"
|
||||
],
|
||||
"formDependency": {
|
||||
"type": {
|
||||
"map": {
|
||||
"Email": [
|
||||
{
|
||||
"action": "show",
|
||||
"fields": ["targetLists", "excludingTargetLists"]
|
||||
}
|
||||
],
|
||||
"Newsletter": [
|
||||
{
|
||||
"action": "show",
|
||||
"fields": ["targetLists", "excludingTargetLists"]
|
||||
}
|
||||
],
|
||||
"Mail": [
|
||||
{
|
||||
"action": "show",
|
||||
"fields": ["targetLists", "excludingTargetLists"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"default": [
|
||||
{
|
||||
"action": "hide",
|
||||
"fields": ["targetLists", "excludingTargetLists"]
|
||||
"dynamicLogic": {
|
||||
"fields": {
|
||||
"targetLists": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "or",
|
||||
"value": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Email"
|
||||
},
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Newsletter"
|
||||
},
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Mail"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"excludingTargetLists": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "or",
|
||||
"value": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Email"
|
||||
},
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Newsletter"
|
||||
},
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Mail"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"contactsTemplate": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Mail"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"leadsTemplate": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Mail"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"accountsTemplate": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Mail"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"usersTemplate": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Mail"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"mailMergeOnlyWithAddress": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Mail"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
"panels": {
|
||||
"mailMerge": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "type",
|
||||
"value": "Mail"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"boolFilterList": ["onlyMy"],
|
||||
"iconClass": "fas fa-chart-line"
|
||||
}
|
||||
|
||||
@@ -85,8 +85,9 @@
|
||||
},
|
||||
"targetLists": {
|
||||
"create": false,
|
||||
"rowActionsView": "views/record/row-actions/relationship-unlink-only",
|
||||
"layout": "listForTarget"
|
||||
"rowActionsView": "crm:views/record/row-actions/relationship-target",
|
||||
"layout": "listForTarget",
|
||||
"view": "crm:views/record/panels/target-lists"
|
||||
}
|
||||
},
|
||||
"boolFilterList": [
|
||||
|
||||
@@ -6,28 +6,6 @@
|
||||
"recordViews":{
|
||||
"detail":"crm:views/lead/record/detail"
|
||||
},
|
||||
"formDependency":{
|
||||
"status":{
|
||||
"map":{
|
||||
"Converted":[
|
||||
{
|
||||
"action":"show",
|
||||
"panels":[
|
||||
"convertedTo"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"default":[
|
||||
{
|
||||
"action":"hide",
|
||||
"panels":[
|
||||
"convertedTo"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sidePanels":{
|
||||
"detail":[
|
||||
{
|
||||
@@ -112,14 +90,15 @@
|
||||
},
|
||||
"relationshipPanels":{
|
||||
"campaignLogRecords":{
|
||||
"rowActionsView":"Record.RowActions.Empty",
|
||||
"rowActionsView":"views/record/row-actions/empty",
|
||||
"select":false,
|
||||
"create":false
|
||||
},
|
||||
"targetLists":{
|
||||
"create":false,
|
||||
"rowActionsView":"views/record/row-actions/relationship-unlink-only",
|
||||
"layout": "listForTarget"
|
||||
"create": false,
|
||||
"rowActionsView": "crm:views/record/row-actions/relationship-target",
|
||||
"layout": "listForTarget",
|
||||
"view": "crm:views/record/panels/target-lists"
|
||||
}
|
||||
},
|
||||
"filterList":[
|
||||
@@ -154,6 +133,19 @@
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"panels": {
|
||||
"convertedTo": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "status",
|
||||
"value": "Converted"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"color": "#d6a2c9",
|
||||
|
||||
@@ -73,6 +73,21 @@
|
||||
}
|
||||
},
|
||||
"kanbanViewMode": true,
|
||||
"dynamicLogic": {
|
||||
"fields": {
|
||||
"lastStage": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "equals",
|
||||
"attribute": "stage",
|
||||
"value": "Closed Lost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"color": "#9fc77e",
|
||||
"iconClass": "fas fa-dollar-sign"
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"sortBy": "dateStart",
|
||||
"asc": true,
|
||||
"displayRecords": 5,
|
||||
"populateAssignedUser": true,
|
||||
"expandedLayout": {
|
||||
"rows": [
|
||||
[
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"sortBy": "createdAt",
|
||||
"asc": false,
|
||||
"displayRecords": 5,
|
||||
"populateAssignedUser": true,
|
||||
"expandedLayout": {
|
||||
"rows": [
|
||||
[
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"sortBy": "createdAt",
|
||||
"asc": false,
|
||||
"displayRecords": 5,
|
||||
"populateAssignedUser": true,
|
||||
"expandedLayout": {
|
||||
"rows": [
|
||||
[
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"sortBy": "dateStart",
|
||||
"asc": true,
|
||||
"displayRecords": 5,
|
||||
"populateAssignedUser": true,
|
||||
"expandedLayout": {
|
||||
"rows": [
|
||||
[
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"sortBy": "closeDate",
|
||||
"asc": true,
|
||||
"displayRecords": 5,
|
||||
"populateAssignedUser": true,
|
||||
"expandedLayout": {
|
||||
"rows": [
|
||||
[
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
},
|
||||
"useLastStage": {
|
||||
"type": "bool"
|
||||
}
|
||||
},
|
||||
"layout": [
|
||||
@@ -31,7 +34,7 @@
|
||||
],
|
||||
[
|
||||
{"name": "dateFilter"},
|
||||
false
|
||||
{"name": "useLastStage"}
|
||||
],
|
||||
[
|
||||
{"name": "dateFrom"},
|
||||
|
||||
@@ -206,6 +206,12 @@
|
||||
"layoutListDisabled": true,
|
||||
"readOnly": true,
|
||||
"view": "views/fields/link-one"
|
||||
},
|
||||
"targetListIsOptedOut": {
|
||||
"type": "bool",
|
||||
"notStorable": true,
|
||||
"readOnly": true,
|
||||
"disabled": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -142,6 +142,30 @@
|
||||
},
|
||||
"budget": {
|
||||
"type": "currency"
|
||||
},
|
||||
"contactsTemplate": {
|
||||
"type": "link",
|
||||
"view": "crm:views/campaign/fields/template",
|
||||
"targetEntityType": "Contact"
|
||||
},
|
||||
"leadsTemplate": {
|
||||
"type": "link",
|
||||
"view": "crm:views/campaign/fields/template",
|
||||
"targetEntityType": "Lead"
|
||||
},
|
||||
"accountsTemplate": {
|
||||
"type": "link",
|
||||
"view": "crm:views/campaign/fields/template",
|
||||
"targetEntityType": "Account"
|
||||
},
|
||||
"usersTemplate": {
|
||||
"type": "link",
|
||||
"view": "crm:views/campaign/fields/template",
|
||||
"targetEntityType": "User"
|
||||
},
|
||||
"mailMergeOnlyWithAddress": {
|
||||
"type": "bool",
|
||||
"default": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
@@ -209,6 +233,26 @@
|
||||
"entity": "MassEmail",
|
||||
"foreign": "campaign",
|
||||
"layoutRelationshipsDisabled": true
|
||||
},
|
||||
"contactsTemplate": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Template",
|
||||
"noJoin": true
|
||||
},
|
||||
"leadsTemplate": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Template",
|
||||
"noJoin": true
|
||||
},
|
||||
"accountsTemplate": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Template",
|
||||
"noJoin": true
|
||||
},
|
||||
"usersTemplate": {
|
||||
"type": "belongsTo",
|
||||
"entity": "Template",
|
||||
"noJoin": true
|
||||
}
|
||||
},
|
||||
"collection": {
|
||||
|
||||
@@ -82,8 +82,7 @@
|
||||
"view": "views/fields/teams"
|
||||
},
|
||||
"attachments": {
|
||||
"type": "attachmentMultiple",
|
||||
"layoutListDisabled": true
|
||||
"type": "attachmentMultiple"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
@@ -166,7 +165,8 @@
|
||||
},
|
||||
"collection": {
|
||||
"sortBy": "number",
|
||||
"asc": false
|
||||
"asc": false,
|
||||
"textFilterFields": ["name", "number"]
|
||||
},
|
||||
"indexes": {
|
||||
"status": {
|
||||
|
||||
@@ -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",
|
||||
@@ -289,6 +291,12 @@
|
||||
"layoutListDisabled": true,
|
||||
"readOnly": true,
|
||||
"view": "views/fields/link-one"
|
||||
},
|
||||
"targetListIsOptedOut": {
|
||||
"type": "bool",
|
||||
"notStorable": true,
|
||||
"readOnly": true,
|
||||
"disabled": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -194,6 +194,12 @@
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"layoutFiltersDisabled": true,
|
||||
"entity": "TargetList"
|
||||
},
|
||||
"targetListIsOptedOut": {
|
||||
"type": "bool",
|
||||
"notStorable": true,
|
||||
"readOnly": true,
|
||||
"disabled": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -62,6 +62,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"lastStage": {
|
||||
"type": "enum",
|
||||
"view": "crm:views/opportunity/fields/last-stage",
|
||||
"customizationOptionsDisabled": true,
|
||||
"customizationDefaultDisabled": true
|
||||
},
|
||||
"probability": {
|
||||
"type": "int",
|
||||
"required": true,
|
||||
@@ -247,6 +253,9 @@
|
||||
"stage": {
|
||||
"columns": ["stage", "deleted"]
|
||||
},
|
||||
"lastStage": {
|
||||
"columns": ["lastStage"]
|
||||
},
|
||||
"assignedUser": {
|
||||
"columns": ["assignedUserId", "deleted"]
|
||||
},
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
"entryCount": {
|
||||
"type": "int",
|
||||
"readOnly": true,
|
||||
"notStorable": true
|
||||
"notStorable": true,
|
||||
"layoutFiltersDisabled": true,
|
||||
"layoutMassUpdateDisabled": true
|
||||
},
|
||||
"optedOutCount": {
|
||||
"type": "int",
|
||||
"readOnly": true,
|
||||
"notStorable": true,
|
||||
"layoutListDisabled": true,
|
||||
"layoutFiltersDisabled": true,
|
||||
"layoutMassUpdateDisabled": true
|
||||
},
|
||||
"description": {
|
||||
"type": "text"
|
||||
@@ -60,6 +70,28 @@
|
||||
"layoutLinkDisabled": true,
|
||||
"notStorable": true,
|
||||
"disabled": true
|
||||
},
|
||||
"targetStatus": {
|
||||
"type": "enum",
|
||||
"options": ["Listed", "Opted Out"],
|
||||
"notStorable": true,
|
||||
"readOnly": true,
|
||||
"layoutListDisabled": true,
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"exportDisabled": true,
|
||||
"importDisabled": true,
|
||||
"view": "crm:views/target-list/fields/target-status"
|
||||
},
|
||||
"isOptedOut": {
|
||||
"type": "bool",
|
||||
"notStorable": true,
|
||||
"readOnly": true,
|
||||
"layoutListDisabled": true,
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"exportDisabled": true,
|
||||
"importDisabled": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -99,8 +99,7 @@
|
||||
},
|
||||
"attachments": {
|
||||
"type": "attachmentMultiple",
|
||||
"sourceList": ["Document"],
|
||||
"layoutListDisabled": true
|
||||
"sourceList": ["Document"]
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
"type": "hasMany",
|
||||
"entity": "TargetList",
|
||||
"foreign": "users"
|
||||
},
|
||||
"targetListIsOptedOut": {
|
||||
"type": "bool",
|
||||
"notStorable": true,
|
||||
"readOnly": true,
|
||||
"disabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,23 +34,47 @@ class Opportunity extends \Espo\Core\SelectManagers\Base
|
||||
protected function filterOpen(&$result)
|
||||
{
|
||||
$result['whereClause'][] = array(
|
||||
'stage!=' => ['Closed Won', 'Closed Lost']
|
||||
'stage!=' => array_merge($this->getWonStageList(), $this->getLostStageList())
|
||||
);
|
||||
}
|
||||
|
||||
protected function filterWon(&$result)
|
||||
{
|
||||
$result['whereClause'][] = array(
|
||||
'stage=' => 'Closed Won'
|
||||
'stage=' => $this->getWonStageList()
|
||||
);
|
||||
}
|
||||
|
||||
protected function filterLost(&$result)
|
||||
{
|
||||
$result['whereClause'][] = array(
|
||||
'stage=' => 'Closed Lost'
|
||||
'stage=' => $this->getLostStageList()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
protected function getLostStageList()
|
||||
{
|
||||
$lostStageList = [];
|
||||
$probabilityMap = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap'], []);
|
||||
$stageList = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
|
||||
foreach ($stageList as $stage) {
|
||||
if (empty($probabilityMap[$stage])) {
|
||||
$lostStageList[] = $stage;
|
||||
}
|
||||
}
|
||||
return $lostStageList;
|
||||
}
|
||||
|
||||
protected function getWonStageList()
|
||||
{
|
||||
$wonStageList = [];
|
||||
$probabilityMap = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap'], []);
|
||||
$stageList = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
|
||||
foreach ($stageList as $stage) {
|
||||
if (!empty($probabilityMap[$stage]) && $probabilityMap[$stage] == 100) {
|
||||
$wonStageList[] = $stage;
|
||||
}
|
||||
}
|
||||
return $wonStageList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,11 @@ class Account extends \Espo\Services\Record
|
||||
'role' => 'accountRole',
|
||||
'isInactive' => 'accountIsInactive'
|
||||
)
|
||||
),
|
||||
'targetLists' => array(
|
||||
'additionalColumns' => array(
|
||||
'optedOut' => 'isOptedOut'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -114,7 +114,8 @@ class Activities extends \Espo\Core\Services\Base
|
||||
'parentType',
|
||||
'parentId',
|
||||
'status',
|
||||
'createdAt'
|
||||
'createdAt',
|
||||
['VALUE:', 'hasAttachment']
|
||||
],
|
||||
'leftJoins' => [['users', 'usersLeft']],
|
||||
'whereClause' => array(
|
||||
@@ -165,7 +166,8 @@ class Activities extends \Espo\Core\Services\Base
|
||||
'parentType',
|
||||
'parentId',
|
||||
'status',
|
||||
'createdAt'
|
||||
'createdAt',
|
||||
['VALUE:', 'hasAttachment']
|
||||
],
|
||||
'leftJoins' => [['users', 'usersLeft']],
|
||||
'whereClause' => array(
|
||||
@@ -223,7 +225,8 @@ class Activities extends \Espo\Core\Services\Base
|
||||
'parentType',
|
||||
'parentId',
|
||||
'status',
|
||||
'createdAt'
|
||||
'createdAt',
|
||||
'hasAttachment'
|
||||
],
|
||||
'leftJoins' => [['users', 'usersLeft']],
|
||||
'whereClause' => array(
|
||||
@@ -269,7 +272,8 @@ class Activities extends \Espo\Core\Services\Base
|
||||
'parentType',
|
||||
'parentId',
|
||||
'status',
|
||||
'createdAt'
|
||||
'createdAt',
|
||||
['VALUE:', 'hasAttachment']
|
||||
],
|
||||
'whereClause' => array(),
|
||||
'customJoin' => ''
|
||||
@@ -375,7 +379,8 @@ class Activities extends \Espo\Core\Services\Base
|
||||
'parentType',
|
||||
'parentId',
|
||||
'status',
|
||||
'createdAt'
|
||||
'createdAt',
|
||||
['VALUE:', 'hasAttachment']
|
||||
],
|
||||
'whereClause' => array()
|
||||
);
|
||||
@@ -480,7 +485,8 @@ class Activities extends \Espo\Core\Services\Base
|
||||
'parentType',
|
||||
'parentId',
|
||||
'status',
|
||||
'createdAt'
|
||||
'createdAt',
|
||||
'hasAttachment'
|
||||
],
|
||||
'whereClause' => array(),
|
||||
'customJoin' => ''
|
||||
@@ -627,15 +633,21 @@ class Activities extends \Espo\Core\Services\Base
|
||||
|
||||
$sth->execute();
|
||||
|
||||
$rows = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
$rowList = $sth->fetchAll(PDO::FETCH_ASSOC);
|
||||
|
||||
$list = array();
|
||||
foreach ($rows as $row) {
|
||||
$boolAttributeList = ['hasAttachment'];
|
||||
|
||||
$list = [];
|
||||
foreach ($rowList as $row) {
|
||||
foreach ($boolAttributeList as $attribute) {
|
||||
if (!array_key_exists($attribute, $row)) continue;
|
||||
$row[$attribute] = $row[$attribute] == '1' ? true : false;
|
||||
}
|
||||
$list[] = $row;
|
||||
}
|
||||
|
||||
return array(
|
||||
'list' => $rows,
|
||||
'list' => $list,
|
||||
'total' => $totalCount
|
||||
);
|
||||
}
|
||||
@@ -1004,7 +1016,8 @@ class Activities extends \Espo\Core\Services\Base
|
||||
($seed->hasAttribute('parentType') ? ['parentType', 'parentType'] : ['VALUE:', 'parentType']),
|
||||
($seed->hasAttribute('parentId') ? ['parentId', 'parentId'] : ['VALUE:', 'parentId']),
|
||||
'status',
|
||||
'createdAt'
|
||||
'createdAt',
|
||||
['VALUE:', 'hasAttachment']
|
||||
];
|
||||
|
||||
$selectParams = $selectManager->getEmptySelectParams();
|
||||
|
||||
@@ -31,8 +31,25 @@ namespace Espo\Modules\Crm\Services;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
|
||||
use \Espo\Core\Exceptions\Error,
|
||||
\Espo\Core\Exceptions\Forbidden,
|
||||
\Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
class Campaign extends \Espo\Services\Record
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
$this->addDependency('container');
|
||||
}
|
||||
|
||||
protected $entityTypeAddressFieldListMap = [
|
||||
'Account' => ['billingAddress', 'shippingAddress'],
|
||||
'Contact' => ['address'],
|
||||
'Lead' => ['address'],
|
||||
'User' => []
|
||||
];
|
||||
|
||||
public function loadAdditionalFields(Entity $entity)
|
||||
{
|
||||
parent::loadAdditionalFields($entity);
|
||||
@@ -47,8 +64,7 @@ class Campaign extends \Espo\Services\Record
|
||||
$openedCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where(array(
|
||||
'campaignId' => $entity->id,
|
||||
'action' => 'Opened',
|
||||
'isTest' => false,
|
||||
'groupBy' => ['queueItemId']
|
||||
'isTest' => false
|
||||
))->count();
|
||||
$entity->set('openedCount', $openedCount);
|
||||
|
||||
@@ -61,8 +77,7 @@ class Campaign extends \Espo\Services\Record
|
||||
$clickedCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where(array(
|
||||
'campaignId' => $entity->id,
|
||||
'action' => 'Clicked',
|
||||
'isTest' => false,
|
||||
'groupBy' => ['queueItemId']
|
||||
'isTest' => false
|
||||
))->count();
|
||||
$entity->set('clickedCount', $clickedCount);
|
||||
|
||||
@@ -75,8 +90,7 @@ class Campaign extends \Espo\Services\Record
|
||||
$optedOutCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where(array(
|
||||
'campaignId' => $entity->id,
|
||||
'action' => 'Opted Out',
|
||||
'isTest' => false,
|
||||
'groupBy' => ['queueItemId']
|
||||
'isTest' => false
|
||||
))->count();
|
||||
$entity->set('optedOutCount', $optedOutCount);
|
||||
|
||||
@@ -89,8 +103,7 @@ class Campaign extends \Espo\Services\Record
|
||||
$bouncedCount = $this->getEntityManager()->getRepository('CampaignLogRecord')->where(array(
|
||||
'campaignId' => $entity->id,
|
||||
'action' => 'Bounced',
|
||||
'isTest' => false,
|
||||
'groupBy' => ['queueItemId']
|
||||
'isTest' => false
|
||||
))->count();
|
||||
$entity->set('bouncedCount', $bouncedCount);
|
||||
|
||||
@@ -283,5 +296,105 @@ class Campaign extends \Espo\Services\Record
|
||||
$this->getEntityManager()->saveEntity($logRecord);
|
||||
}
|
||||
|
||||
}
|
||||
public function generateMailMergePdf($campaignId, $link, $checkAcl = false)
|
||||
{
|
||||
$campaign = $this->getEntityManager()->getEntity('Campaign', $campaignId);
|
||||
|
||||
if ($checkAcl && !$this->getAcl()->check($campaign, 'read')) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if ($checkAcl) {
|
||||
$targetEntityType = $campaign->getRelationParam($link, 'entity');
|
||||
if (!$this->getAcl()->check($targetEntityType, 'read')) {
|
||||
throw new Forbidden("Could not mail merge campaign because access to target enity type is forbidden.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($link, ['accounts', 'contacts', 'leads', 'users'])) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
if ($campaign->get('type') !== 'Mail') {
|
||||
throw new Error("Could not mail merge campaign not of Mail type.");
|
||||
}
|
||||
|
||||
if (
|
||||
!$campaign->get($link . 'TemplateId')
|
||||
) {
|
||||
throw new Error("Could not mail merge campaign w/o specified template.");
|
||||
}
|
||||
|
||||
$template = $this->getEntityManager()->getEntity('Template', $campaign->get($link . 'TemplateId'));
|
||||
if (!$template) {
|
||||
throw new Error("Template not found");
|
||||
}
|
||||
if ($template->get('entityType') !== $targetEntityType) {
|
||||
throw new Error("Template is not of proper entity type.");
|
||||
}
|
||||
|
||||
$campaign->loadLinkMultipleField('targetLists');
|
||||
$campaign->loadLinkMultipleField('excludingTargetLists');
|
||||
|
||||
if (count($campaign->getLinkMultipleIdList('targetLists')) === 0) {
|
||||
throw new Error("Could not mail merge campaign w/o any specified target list.");
|
||||
}
|
||||
|
||||
$metTargetHash = [];
|
||||
$targetEntityList = [];
|
||||
|
||||
$excludingTargetListList = $campaign->get('excludingTargetLists');
|
||||
foreach ($excludingTargetListList as $excludingTargetList) {
|
||||
foreach ($excludingTargetList->get($link) as $excludingTarget) {
|
||||
$hashId = $excludingTarget->getEntityType() . '-'. $excludingTarget->id;
|
||||
$metTargetHash[$hashId] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$addressFieldList = $this->entityTypeAddressFieldListMap[$targetEntityType];
|
||||
|
||||
$targetListCollection = $campaign->get('targetLists');
|
||||
foreach ($targetListCollection as $targetList) {
|
||||
if (!$campaign->get($link . 'TemplateId')) continue;
|
||||
$entityList = $targetList->get($link, [
|
||||
'additionalColumnsConditions' => [
|
||||
'optedOut' => false
|
||||
]
|
||||
]);
|
||||
foreach ($entityList as $e) {
|
||||
$hashId = $e->getEntityType() . '-'. $e->id;
|
||||
if (!empty($metTargetHash[$hashId])) {
|
||||
continue;
|
||||
}
|
||||
$metTargetHash[$hashId] = true;
|
||||
|
||||
if ($campaign->get('mailMergeOnlyWithAddress')) {
|
||||
if (empty($addressFieldList)) continue;
|
||||
$hasAddress = false;
|
||||
foreach ($addressFieldList as $addressField) {
|
||||
if ($e->get($addressField . 'Street') || $e->get($addressField . 'PostalCode')) {
|
||||
$hasAddress = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$hasAddress) continue;
|
||||
}
|
||||
|
||||
$targetEntityList[] = $e;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($targetEntityList)) {
|
||||
throw new Error("No targets available for mail merge.");
|
||||
}
|
||||
|
||||
$filename = $campaign->get('name') . ' - ' . $this->getDefaultLanguage()->translate($targetEntityType, 'scopeNamesPlural');
|
||||
|
||||
return $this->getServiceFactory()->create('Pdf')->generateMailMerge($targetEntityType, $targetEntityList, $template, $filename, $campaign->id);
|
||||
}
|
||||
|
||||
protected function getDefaultLanguage()
|
||||
{
|
||||
return $this->getInjection('container')->get('defaultLanguage');
|
||||
}
|
||||
}
|
||||
|
||||
38
application/Espo/Modules/Crm/Services/CampaignLogRecord.php
Normal file
38
application/Espo/Modules/Crm/Services/CampaignLogRecord.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\Modules\Crm\Services;
|
||||
|
||||
use \Espo\Core\Exceptions\Forbidden;
|
||||
use \Espo\ORM\Entity;
|
||||
|
||||
class CampaignLogRecord extends \Espo\Services\Record
|
||||
{
|
||||
protected $forceSelectAllAttributes = true;
|
||||
}
|
||||
@@ -34,6 +34,8 @@ use \Espo\ORM\Entity;
|
||||
|
||||
class CampaignTrackingUrl extends \Espo\Services\Record
|
||||
{
|
||||
protected $mandatorySelectAttributeList = ['campaignId'];
|
||||
|
||||
protected function beforeCreateEntity(Entity $entity, $data)
|
||||
{
|
||||
parent::beforeCreateEntity($entity, $data);
|
||||
|
||||
@@ -43,6 +43,19 @@ class Contact extends \Espo\Core\Templates\Services\Person
|
||||
'title'
|
||||
];
|
||||
|
||||
protected $linkSelectParams = array(
|
||||
'targetLists' => array(
|
||||
'additionalColumns' => array(
|
||||
'optedOut' => 'isOptedOut'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
protected $mandatorySelectAttributeList = [
|
||||
'accountId',
|
||||
'accountName'
|
||||
];
|
||||
|
||||
protected function afterCreateEntity(Entity $entity, $data)
|
||||
{
|
||||
if (!empty($data->emailId)) {
|
||||
|
||||
@@ -43,6 +43,14 @@ class Lead extends \Espo\Core\Templates\Services\Person
|
||||
$this->addDependency('container');
|
||||
}
|
||||
|
||||
protected $linkSelectParams = array(
|
||||
'targetLists' => array(
|
||||
'additionalColumns' => array(
|
||||
'optedOut' => 'isOptedOut'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
protected function getFieldManager()
|
||||
{
|
||||
return $this->getInjection('container')->get('fieldManager');
|
||||
|
||||
@@ -45,6 +45,8 @@ class MassEmail extends \Espo\Services\Record
|
||||
|
||||
private $emailTemplateService = null;
|
||||
|
||||
protected $mandatorySelectAttributeList = ['campaignId'];
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
@@ -36,7 +36,12 @@ use \Espo\Core\Exceptions\Forbidden;
|
||||
|
||||
class Opportunity extends \Espo\Services\Record
|
||||
{
|
||||
public function reportSalesPipeline($dateFilter, $dateFrom = null, $dateTo = null)
|
||||
protected $mandatorySelectAttributeList = [
|
||||
'accountId',
|
||||
'accountName'
|
||||
];
|
||||
|
||||
public function reportSalesPipeline($dateFilter, $dateFrom = null, $dateTo = null, $useLastStage = false)
|
||||
{
|
||||
if (in_array('amount', $this->getAcl()->getScopeForbiddenAttributeList('Opportunity'))) {
|
||||
throw new Forbidden();
|
||||
@@ -46,19 +51,27 @@ class Opportunity extends \Espo\Services\Record
|
||||
list($dateFrom, $dateTo) = $this->getDateRangeByFilter($dateFilter);
|
||||
}
|
||||
|
||||
$lostStageList = $this->getLostStageList();
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$options = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
|
||||
|
||||
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
|
||||
|
||||
$stageField = 'stage';
|
||||
if ($useLastStage) {
|
||||
$stageField = 'lastStage';
|
||||
}
|
||||
|
||||
$selectParams = [
|
||||
'select' => ['stage', ['SUM:amountConverted', 'amount']],
|
||||
'select' => [$stageField, ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => [
|
||||
'stage!=' => 'Closed Lost'
|
||||
[$stageField . '!=' => $lostStageList],
|
||||
[$stageField . '!=' => null]
|
||||
],
|
||||
'orderBy' => 'LIST:stage:' . implode(',', $options),
|
||||
'groupBy' => ['stage']
|
||||
'orderBy' => 'LIST:'.$stageField.':' . implode(',', $options),
|
||||
'groupBy' => [$stageField]
|
||||
];
|
||||
|
||||
if ($dateFilter !== 'ever') {
|
||||
@@ -81,7 +94,7 @@ class Opportunity extends \Espo\Services\Record
|
||||
|
||||
$result = array();
|
||||
foreach ($rows as $row) {
|
||||
$result[$row['stage']] = floatval($row['amount']);
|
||||
$result[$row[$stageField]] = floatval($row['amount']);
|
||||
}
|
||||
|
||||
return $result;
|
||||
@@ -109,7 +122,7 @@ class Opportunity extends \Espo\Services\Record
|
||||
$selectParams = [
|
||||
'select' => ['leadSource', ['SUM:amountWeightedConverted', 'amount']],
|
||||
'whereClause' => [
|
||||
'stage!=' => 'Closed Lost',
|
||||
'stage!=' => $this->getLostStageList(),
|
||||
['leadSource!=' => ''],
|
||||
['leadSource!=' => null]
|
||||
],
|
||||
@@ -162,8 +175,12 @@ class Opportunity extends \Espo\Services\Record
|
||||
$selectParams = [
|
||||
'select' => ['stage', ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => [
|
||||
'stage!=' => 'Closed Lost',
|
||||
'stage!=' => 'Closed Won'
|
||||
[
|
||||
'stage!=' => $this->getLostStageList()
|
||||
],
|
||||
[
|
||||
'stage!=' => $this->getWonStageList()
|
||||
]
|
||||
],
|
||||
'orderBy' => 'LIST:stage:' . implode(',', $options),
|
||||
'groupBy' => ['stage']
|
||||
@@ -176,7 +193,7 @@ class Opportunity extends \Espo\Services\Record
|
||||
];
|
||||
}
|
||||
|
||||
$stageIgnoreList = ['Closed Lost', 'Closed Won'];
|
||||
$stageIgnoreList = array_merge($this->getLostStageList(), $this->getWonStageList());
|
||||
|
||||
$selectManager->applyAccess($selectParams);
|
||||
|
||||
@@ -215,7 +232,7 @@ class Opportunity extends \Espo\Services\Record
|
||||
$selectParams = [
|
||||
'select' => [['MONTH:closeDate', 'month'], ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => [
|
||||
'stage' => 'Closed Won'
|
||||
'stage' => $this->getWonStageList()
|
||||
],
|
||||
'orderBy' => 1,
|
||||
'groupBy' => ['MONTH:closeDate']
|
||||
@@ -396,4 +413,30 @@ class Opportunity extends \Espo\Services\Record
|
||||
'count' => $count
|
||||
);
|
||||
}
|
||||
|
||||
protected function getLostStageList()
|
||||
{
|
||||
$lostStageList = [];
|
||||
$probabilityMap = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap'], []);
|
||||
$stageList = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
|
||||
foreach ($stageList as $stage) {
|
||||
if (empty($probabilityMap[$stage])) {
|
||||
$lostStageList[] = $stage;
|
||||
}
|
||||
}
|
||||
return $lostStageList;
|
||||
}
|
||||
|
||||
protected function getWonStageList()
|
||||
{
|
||||
$wonStageList = [];
|
||||
$probabilityMap = $this->getMetadata()->get(['entityDefs', 'Opportunity', 'fields', 'stage', 'probabilityMap'], []);
|
||||
$stageList = $this->getMetadata()->get('entityDefs.Opportunity.fields.stage.options', []);
|
||||
foreach ($stageList as $stage) {
|
||||
if (!empty($probabilityMap[$stage]) && $probabilityMap[$stage] == 100) {
|
||||
$wonStageList[] = $stage;
|
||||
}
|
||||
}
|
||||
return $wonStageList;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,29 @@ class TargetList extends \Espo\Services\Record
|
||||
'User' => 'users'
|
||||
);
|
||||
|
||||
protected $linkSelectParams = [
|
||||
'accounts' => [
|
||||
'additionalColumns' => [
|
||||
'optedOut' => 'targetListIsOptedOut'
|
||||
]
|
||||
],
|
||||
'contacts' => [
|
||||
'additionalColumns' => [
|
||||
'optedOut' => 'targetListIsOptedOut'
|
||||
]
|
||||
],
|
||||
'leads' => [
|
||||
'additionalColumns' => [
|
||||
'optedOut' => 'targetListIsOptedOut'
|
||||
]
|
||||
],
|
||||
'users' => [
|
||||
'additionalColumns' => [
|
||||
'optedOut' => 'targetListIsOptedOut'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
@@ -60,6 +83,7 @@ class TargetList extends \Espo\Services\Record
|
||||
{
|
||||
parent::loadAdditionalFields($entity);
|
||||
$this->loadEntryCountField($entity);
|
||||
$this->loadOptedOutCountField($entity);
|
||||
}
|
||||
|
||||
public function loadAdditionalFieldsForList(Entity $entity)
|
||||
@@ -78,6 +102,34 @@ class TargetList extends \Espo\Services\Record
|
||||
$entity->set('entryCount', $count);
|
||||
}
|
||||
|
||||
protected function loadOptedOutCountField(Entity $entity)
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
|
||||
$count += $this->getEntityManager()->getRepository('Contact')->join(['targetLists'])->where([
|
||||
'targetListsMiddle.targetListId' => $entity->id,
|
||||
'targetListsMiddle.optedOut' => 1
|
||||
])->count();
|
||||
|
||||
$count += $this->getEntityManager()->getRepository('Lead')->join(['targetLists'])->where([
|
||||
'targetListsMiddle.targetListId' => $entity->id,
|
||||
'targetListsMiddle.optedOut' => 1
|
||||
])->count();
|
||||
|
||||
$count += $this->getEntityManager()->getRepository('Account')->join(['targetLists'])->where([
|
||||
'targetListsMiddle.targetListId' => $entity->id,
|
||||
'targetListsMiddle.optedOut' => 1
|
||||
])->count();
|
||||
|
||||
$count += $this->getEntityManager()->getRepository('User')->join(['targetLists'])->where([
|
||||
'targetListsMiddle.targetListId' => $entity->id,
|
||||
'targetListsMiddle.optedOut' => 1
|
||||
])->count();
|
||||
|
||||
$entity->set('optedOutCount', $count);
|
||||
}
|
||||
|
||||
protected function afterCreate(Entity $entity, array $data = array())
|
||||
{
|
||||
if (array_key_exists('sourceCampaignId', $data) && !empty($data['includingActionList'])) {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\ORM\DB;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\IEntity;
|
||||
use Espo\ORM\EntityFactory;
|
||||
|
||||
@@ -107,6 +107,14 @@ abstract class Base
|
||||
'LENGTH'
|
||||
];
|
||||
|
||||
protected $matchFunctionList = ['MATCH_BOOLEAN', 'MATCH_NATURAL_LANGUAGE', 'MATCH_QUERY_EXPANSION'];
|
||||
|
||||
protected $matchFunctionMap = [
|
||||
'MATCH_BOOLEAN' => 'IN BOOLEAN MODE',
|
||||
'MATCH_NATURAL_LANGUAGE' => 'IN NATURAL LANGUAGE MODE',
|
||||
'MATCH_QUERY_EXPANSION' => 'WITH QUERY EXPANSION'
|
||||
];
|
||||
|
||||
protected $entityFactory;
|
||||
|
||||
protected $pdo;
|
||||
@@ -308,6 +316,45 @@ abstract class Base
|
||||
return $function . '(' . $part . ')';
|
||||
}
|
||||
|
||||
protected function convertMatchExpression($entity, $expression)
|
||||
{
|
||||
$delimiterPosition = strpos($expression, ':');
|
||||
if ($delimiterPosition === false) {
|
||||
throw new \Exception("Bad MATCH usage.");
|
||||
}
|
||||
|
||||
$function = substr($expression, 0, $delimiterPosition);
|
||||
$rest = substr($expression, $delimiterPosition + 1);
|
||||
|
||||
if (empty($rest)) {
|
||||
throw new \Exception("Empty MATCH parameters.");
|
||||
}
|
||||
|
||||
$delimiterPosition = strpos($rest, ':');
|
||||
if ($delimiterPosition === false) {
|
||||
throw new \Exception("Bad MATCH usage.");
|
||||
}
|
||||
|
||||
$columns = substr($rest, 0, $delimiterPosition);
|
||||
$query = mb_substr($rest, $delimiterPosition + 1);
|
||||
|
||||
$columnList = explode(',', $columns);
|
||||
|
||||
$tableName = $this->toDb($entity->getEntityType());
|
||||
|
||||
foreach ($columnList as $i => $column) {
|
||||
$columnList[$i] = $tableName . '.' . $this->sanitize($column);
|
||||
}
|
||||
|
||||
$query = $this->quote($query);
|
||||
|
||||
if (!in_array($function, $this->matchFunctionList)) return;
|
||||
$modePart = ' ' . $this->matchFunctionMap[$function];
|
||||
|
||||
$result = "MATCH (" . implode(',', $columnList) . ") AGAINST (" . $query . "" . $modePart . ")";
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function convertComplexExpression($entity, $field, $distinct = false)
|
||||
{
|
||||
@@ -317,7 +364,14 @@ abstract class Base
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
if (strpos($field, ':')) {
|
||||
list($function, $field) = explode(':', $field);
|
||||
$dilimeterPosition = strpos($field, ':');
|
||||
$function = substr($field, 0, $dilimeterPosition);
|
||||
|
||||
if (in_array($function, $this->matchFunctionList)) {
|
||||
return $this->convertMatchExpression($entity, $field);
|
||||
}
|
||||
|
||||
$field = substr($field, $dilimeterPosition + 1);
|
||||
}
|
||||
if (!empty($function)) {
|
||||
$function = preg_replace('/[^A-Za-z0-9_]+/', '', $function);
|
||||
@@ -724,6 +778,13 @@ abstract class Base
|
||||
foreach ($whereClause as $field => $value) {
|
||||
|
||||
if (is_int($field)) {
|
||||
if (is_string($value)) {
|
||||
if (strpos($value, 'MATCH_') === 0) {
|
||||
$rightPart = $this->convertMatchExpression($entity, $value);
|
||||
$whereParts[] = $rightPart;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$field = 'AND';
|
||||
}
|
||||
|
||||
@@ -776,6 +837,7 @@ abstract class Base
|
||||
if (empty($isComplex)) {
|
||||
|
||||
if (!isset($entity->fields[$field])) {
|
||||
$whereParts[] = '0';
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -908,7 +970,7 @@ abstract class Base
|
||||
}
|
||||
} else {
|
||||
$internalPart = $this->getWhere($entity, $value, $field, $params, $level + 1);
|
||||
if ($internalPart) {
|
||||
if ($internalPart || $internalPart === '0') {
|
||||
$whereParts[] = "(" . $internalPart . ")";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,6 +206,11 @@ class EntityManager
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
public function getOrmMetadata()
|
||||
{
|
||||
return $this->getMetadata();
|
||||
}
|
||||
|
||||
public function getPDO()
|
||||
{
|
||||
if (empty($this->pdo)) {
|
||||
@@ -231,6 +236,11 @@ class EntityManager
|
||||
return $collection;
|
||||
}
|
||||
|
||||
public function getEntityFactory()
|
||||
{
|
||||
return $this->entityFactory;
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -192,10 +192,11 @@ class RDB extends \Espo\ORM\Repository
|
||||
|
||||
public function find(array $params = array())
|
||||
{
|
||||
$params = $this->getSelectParams($params);
|
||||
|
||||
if (empty($params['skipAdditionalSelectParams'])) {
|
||||
$this->handleSelectParams($params);
|
||||
}
|
||||
$params = $this->getSelectParams($params);
|
||||
|
||||
$dataArr = $this->getMapper()->select($this->seed, $params);
|
||||
|
||||
|
||||
@@ -49,7 +49,11 @@ class Email extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
$address = $entity->get($type);
|
||||
$idList = [];
|
||||
if (!empty($address) || !filter_var($address, FILTER_VALIDATE_EMAIL)) {
|
||||
if (
|
||||
(!empty($address) || !filter_var($address, FILTER_VALIDATE_EMAIL))
|
||||
&&
|
||||
$type !== 'replyTo'
|
||||
) {
|
||||
$arr = array_map(function ($e) {
|
||||
return trim($e);
|
||||
}, explode(';', $address));
|
||||
|
||||
@@ -162,7 +162,8 @@
|
||||
"readOnly": "Read-only",
|
||||
"maxFileSize": "Max File Size (Mb)",
|
||||
"isPersonalData": "Is Personal Data",
|
||||
"useIframe": "Use Iframe"
|
||||
"useIframe": "Use Iframe",
|
||||
"useNumericFormat": "Use Numeric Format"
|
||||
},
|
||||
"messages": {
|
||||
"upgradeVersion": "EspoCRM will be upgraded to version <strong>{version}</strong>. Please be patient as this may take a while.",
|
||||
@@ -177,9 +178,9 @@
|
||||
"selectExtensionPackage": "Select extension package",
|
||||
"extensionInstalled": "Extension {name} {version} has been installed.",
|
||||
"installExtension": "Extension {name} {version} is ready for an installation.",
|
||||
"cronIsNotConfigured": "Scheduled jobs are not running. Hence inbound emails, notifications and reminders are not working. Please follow the <a target=\"_blank\" href=\"https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab\">instructions</a> to setup cron job.",
|
||||
"cronIsNotConfigured": "Scheduled jobs are not running. Hence inbound emails, notifications and reminders are not working. Please follow the [instructions](https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab) to setup cron job.",
|
||||
"newVersionIsAvailable": "New EspoCRM version {latestVersion} is available.",
|
||||
"newExtensionVersionIsAvailable": "New release {latestVersion} of {extensionName} is available.",
|
||||
"newExtensionVersionIsAvailable": "New {extensionName} version {latestVersion} is available.",
|
||||
"uninstallConfirmation": "Are you sure you want to uninstall the extension?"
|
||||
},
|
||||
"descriptions": {
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"EmailTemplates": "Email Templates"
|
||||
},
|
||||
"fields": {
|
||||
"order": "Order"
|
||||
"order": "Order",
|
||||
"childList": "Child List"
|
||||
},
|
||||
"links": {
|
||||
"emailTemplates": "Email Templates"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user