mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-04 16:17:01 +00:00
Compare commits
281 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eadf7835db | ||
|
|
c30c4163a6 | ||
|
|
8d6db72516 | ||
|
|
71864056bc | ||
|
|
e375105a5f | ||
|
|
e066f4c6b0 | ||
|
|
b032aa4d8d | ||
|
|
342a18e0ac | ||
|
|
e27e9de701 | ||
|
|
b14b199d43 | ||
|
|
f77a7c32d5 | ||
|
|
9f84d3f233 | ||
|
|
be82244c6b | ||
|
|
7fed72d391 | ||
|
|
54d615b64d | ||
|
|
2dbbd1e23d | ||
|
|
cba8867389 | ||
|
|
32d47db15a | ||
|
|
2c65c9ff9f | ||
|
|
fa5d63253b | ||
|
|
b7ae252d3d | ||
|
|
f07b43abda | ||
|
|
276db37baf | ||
|
|
6adad84a29 | ||
|
|
f137298c49 | ||
|
|
f8839518d5 | ||
|
|
1ae39d1a40 | ||
|
|
94f920324e | ||
|
|
94c00e2901 | ||
|
|
7de988104b | ||
|
|
03bc90968c | ||
|
|
a5401a22b9 | ||
|
|
4c1375d8b2 | ||
|
|
bab4a3b0d6 | ||
|
|
33e135f227 | ||
|
|
56b9d8d5c1 | ||
|
|
38ec88e302 | ||
|
|
10be208d8d | ||
|
|
38d227a948 | ||
|
|
1a8e4435e2 | ||
|
|
12febbdff5 | ||
|
|
ebea168350 | ||
|
|
704519f80b | ||
|
|
5f1000ddd3 | ||
|
|
2ee7e6cf4a | ||
|
|
2989975829 | ||
|
|
a4c068427d | ||
|
|
e1a76b9924 | ||
|
|
1f2efcd716 | ||
|
|
576dfe068f | ||
|
|
56f8e68599 | ||
|
|
e8d75808ef | ||
|
|
bc8bf89d6e | ||
|
|
91385cc3f0 | ||
|
|
ad21511c72 | ||
|
|
938a24f102 | ||
|
|
43b041ed6c | ||
|
|
5e3f048795 | ||
|
|
3aec3ef6d2 | ||
|
|
712a450734 | ||
|
|
5675b6721b | ||
|
|
a4e23b8adb | ||
|
|
9d49f418c8 | ||
|
|
ea9f70dffa | ||
|
|
b624accbe8 | ||
|
|
12f974635e | ||
|
|
cfce68eb7e | ||
|
|
db8fbd1ed3 | ||
|
|
06395f3ff5 | ||
|
|
4065ec5477 | ||
|
|
31d45a0583 | ||
|
|
696faa4468 | ||
|
|
625876f123 | ||
|
|
974cd05276 | ||
|
|
fb5db991a3 | ||
|
|
3dafb7e922 | ||
|
|
ee640273a1 | ||
|
|
b92de17f72 | ||
|
|
abc48ca76f | ||
|
|
46443aae7e | ||
|
|
a88c3283d6 | ||
|
|
2b9653ee0b | ||
|
|
2cf79abdb1 | ||
|
|
20a000a46c | ||
|
|
95bfd5bace | ||
|
|
da736008f1 | ||
|
|
1de98d9616 | ||
|
|
31a6143cc3 | ||
|
|
8502a84a3f | ||
|
|
2234678728 | ||
|
|
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 | ||
|
|
ade4078f0d | ||
|
|
fdeac68216 | ||
|
|
4c220a5a65 | ||
|
|
f148bd82c5 | ||
|
|
0d983ef34c | ||
|
|
75ad9f5cf2 | ||
|
|
893efe212e | ||
|
|
4a4d2e473f | ||
|
|
9d663ad140 | ||
|
|
f377dfc5b1 | ||
|
|
a4054f5273 | ||
|
|
fcc3ac978f | ||
|
|
2bbeaa8198 | ||
|
|
70308d19ae | ||
|
|
143897cf18 |
@@ -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'
|
||||
]);
|
||||
|
||||
};
|
||||
|
||||
35
application/Espo/Controllers/EmailTemplateCategory.php
Normal file
35
application/Espo/Controllers/EmailTemplateCategory.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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;
|
||||
|
||||
class EmailTemplateCategory extends \Espo\Core\Templates\Controllers\CategoryTree
|
||||
{
|
||||
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,6 @@ class ContainsType extends \Espo\Core\Formula\Functions\Base
|
||||
return false;
|
||||
}
|
||||
|
||||
return strpos($string, $needle) !== false;
|
||||
return mb_strpos($string, $needle) !== false;
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,6 @@ class LowerCaseType extends \Espo\Core\Formula\Functions\Base
|
||||
$value = strval($value);
|
||||
}
|
||||
|
||||
return strtolower($value);
|
||||
return mb_strtolower($value);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
@@ -52,9 +52,9 @@ class SubstringType extends \Espo\Core\Formula\Functions\Base
|
||||
|
||||
if (count($item->value) > 2) {
|
||||
$length = $this->evaluate($item->value[2]);
|
||||
return substr($string, $start, $length);
|
||||
return mb_substr($string, $start, $length);
|
||||
} else {
|
||||
return substr($string, $start);
|
||||
return mb_substr($string, $start);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,6 @@ class UpperCaseType extends \Espo\Core\Formula\Functions\Base
|
||||
$value = strval($value);
|
||||
}
|
||||
|
||||
return strtoupper($value);
|
||||
return mb_strtoupper($value);
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,8 @@ class Parser
|
||||
|
||||
$this->processStrings($expression, $modifiedExpression, $splitterIndexList, true);
|
||||
|
||||
$expressionOutOfBraceList = [];
|
||||
|
||||
for ($i = 0; $i < strlen($modifiedExpression); $i++) {
|
||||
if ($modifiedExpression[$i] === '(') {
|
||||
$braceCounter++;
|
||||
@@ -186,6 +188,11 @@ class Parser
|
||||
if ($braceCounter === 0 && $i < strlen($modifiedExpression) - 1) {
|
||||
$hasExcessBraces = false;
|
||||
}
|
||||
if ($braceCounter === 0) {
|
||||
$expressionOutOfBraceList[] = true;
|
||||
} else {
|
||||
$expressionOutOfBraceList[] = false;
|
||||
}
|
||||
}
|
||||
if ($braceCounter !== 0) {
|
||||
throw new Error('Incorrect round brackets in expression ' . $expression . '.');
|
||||
@@ -226,7 +233,13 @@ class Parser
|
||||
|
||||
foreach ($this->priorityList as $operationList) {
|
||||
foreach ($operationList as $operator) {
|
||||
$index = strpos($expression, $operator, 1);
|
||||
$startFrom = 1;
|
||||
while (true) {
|
||||
$index = strpos($expression, $operator, $startFrom);
|
||||
if ($index === false) break;
|
||||
if ($expressionOutOfBraceList[$index]) break;
|
||||
$startFrom = $index + 1;
|
||||
}
|
||||
if ($index !== false) {
|
||||
$possibleRightOperator = null;
|
||||
if (strlen($operator) === 1) {
|
||||
|
||||
@@ -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')) {
|
||||
@@ -147,7 +137,9 @@ class Entity extends \Espo\ORM\Entity
|
||||
}
|
||||
|
||||
$this->set($idsAttribute, $ids);
|
||||
$this->setFetched($idsAttribute, $ids);
|
||||
if (!$this->isNew() && !$this->hasFetched($idsAttribute)) {
|
||||
$this->setFetched($idsAttribute, $ids);
|
||||
}
|
||||
|
||||
$this->set($field . 'Names', $names);
|
||||
if ($hasType) {
|
||||
@@ -163,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;
|
||||
@@ -172,7 +170,13 @@ class Entity extends \Espo\ORM\Entity
|
||||
$entityName = $entity->get('name');
|
||||
}
|
||||
|
||||
$this->set($field . 'Id', $entityId);
|
||||
$idAttribute = $field . 'Id';
|
||||
|
||||
if (!$this->isNew() && !$this->hasFetched($idAttribute)) {
|
||||
$this->setFetched($idAttribute, $entityId);
|
||||
}
|
||||
|
||||
$this->set($idAttribute, $entityId);
|
||||
$this->set($field . 'Name', $entityName);
|
||||
}
|
||||
|
||||
|
||||
@@ -465,17 +465,21 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
$data->$columnName = $foreignEntity->get($columnField);
|
||||
}
|
||||
$existingColumnsData->$foreignId = $data;
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->has($fieldName)) {
|
||||
$entity->setFetched($fieldName, $existingIds);
|
||||
}
|
||||
if ($entity->has($columnsFieldsName) && !empty($columns)) {
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
if (!$entity->isNew()) {
|
||||
if ($entity->has($fieldName)) {
|
||||
$entity->setFetched($fieldName, $existingIds);
|
||||
}
|
||||
if ($entity->has($columnsFieldsName) && !empty($columns)) {
|
||||
$entity->setFetched($columnsFieldsName, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($existingIds as $id) {
|
||||
@@ -540,13 +544,17 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
$where[$foreignKey] = $entity->id;
|
||||
$previousForeignEntity = $this->getEntityManager()->getRepository($foreignEntityType)->where($where)->findOne();
|
||||
if ($previousForeignEntity) {
|
||||
$entity->setFetched($idFieldName, $previousForeignEntity->id);
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($idFieldName, $previousForeignEntity->id);
|
||||
}
|
||||
if ($previousForeignEntity->id !== $entity->get($idFieldName)) {
|
||||
$previousForeignEntity->set($foreignKey, null);
|
||||
$this->getEntityManager()->saveEntity($previousForeignEntity);
|
||||
}
|
||||
} else {
|
||||
$entity->setFetched($idFieldName, null);
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($idFieldName, null);
|
||||
}
|
||||
}
|
||||
|
||||
if ($entity->get($idFieldName)) {
|
||||
|
||||
@@ -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']);
|
||||
@@ -157,6 +161,9 @@ class Base
|
||||
if ($desc) {
|
||||
$list = array_reverse($list);
|
||||
}
|
||||
foreach ($list as $i => $listItem) {
|
||||
$list[$i] = str_replace(',', '_COMMA_', $listItem);
|
||||
}
|
||||
$result['orderBy'] = 'LIST:' . $sortBy . ':' . implode(',', $list);
|
||||
return;
|
||||
}
|
||||
@@ -203,8 +210,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,8 +394,9 @@ class Base
|
||||
|
||||
protected function q($params, &$result)
|
||||
{
|
||||
if (!empty($params['q'])) {
|
||||
$this->textFilter($params['q'], $result);
|
||||
if (isset($params['q']) && $params['q'] !== '') {
|
||||
$textFilter = $params['q'];
|
||||
$this->textFilter($textFilter, $result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +409,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 +740,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 +1015,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);
|
||||
}
|
||||
}
|
||||
@@ -1247,7 +1255,7 @@ class Base
|
||||
|
||||
$value = $item['value'];
|
||||
|
||||
if (is_null($value)) break;
|
||||
if (is_null($value) || !$value && !is_array($value)) break;
|
||||
|
||||
$relationType = $seed->getRelationType($link);
|
||||
|
||||
@@ -1500,36 +1508,252 @@ 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;
|
||||
}
|
||||
$textFilterWoWildcards = str_replace('*', '', $textFilter);
|
||||
if (mb_strlen($textFilterWoWildcards) >= $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 ($isAuxiliaryUse) {
|
||||
if (mb_strpos($textFilter, '@') !== false) {
|
||||
$useFullTextSearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($useFullTextSearch) {
|
||||
$textFilter = str_replace(['(', ')'], '', $textFilter);
|
||||
|
||||
if (
|
||||
$isAuxiliaryUse && mb_strpos($textFilter, '*') === false
|
||||
||
|
||||
mb_strpos($textFilter, ' ') === false
|
||||
&&
|
||||
mb_strpos($textFilter, '+') === false
|
||||
&&
|
||||
mb_strpos($textFilter, '-') === false
|
||||
&&
|
||||
mb_strpos($textFilter, '*') === false
|
||||
) {
|
||||
$function = 'MATCH_NATURAL_LANGUAGE';
|
||||
} else {
|
||||
$function = 'MATCH_BOOLEAN';
|
||||
}
|
||||
|
||||
$textFilter = str_replace('"*', '"', $textFilter);
|
||||
$textFilter = str_replace('*"', '"', $textFilter);
|
||||
|
||||
while (strpos($textFilter, '**')) {
|
||||
$textFilter = str_replace('**', '*', $textFilter);
|
||||
$textFilter = trim($textFilter);
|
||||
}
|
||||
|
||||
while (mb_substr($textFilter, -2) === ' *') {
|
||||
$textFilter = mb_substr($textFilter, 0, mb_strlen($textFilter) - 2);
|
||||
$textFilter = trim($textFilter);
|
||||
}
|
||||
|
||||
$fullTextSearchColumnSanitizedList = [];
|
||||
$query = $this->getEntityManager()->getQuery();
|
||||
foreach ($fullTextSearchColumnList as $i => $field) {
|
||||
$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
|
||||
);
|
||||
|
||||
$textFilterForFullTextSearch = $textFilter;
|
||||
|
||||
$skipWidlcards = false;
|
||||
|
||||
if (mb_strpos($textFilter, '*') !== false) {
|
||||
$skipWidlcards = true;
|
||||
$textFilter = str_replace('*', '%', $textFilter);
|
||||
} else {
|
||||
if (!$useFullTextSearch) {
|
||||
$textFilterForFullTextSearch .= '*';
|
||||
}
|
||||
}
|
||||
|
||||
$textFilterForFullTextSearch = str_replace('%', '*', $textFilterForFullTextSearch);
|
||||
|
||||
$skipFullTextSearch = false;
|
||||
if (!$forceFullTextSearch) {
|
||||
if (mb_strpos($textFilterForFullTextSearch, '*') === 0) {
|
||||
$skipFullTextSearch = true;
|
||||
} else if (mb_strpos($textFilterForFullTextSearch, ' *') !== false) {
|
||||
$skipFullTextSearch = true;
|
||||
}
|
||||
}
|
||||
|
||||
$fullTextSearchData = null;
|
||||
if (!$skipFullTextSearch) {
|
||||
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilterForFullTextSearch, !$useFullTextSearch);
|
||||
}
|
||||
|
||||
$fullTextGroup = [];
|
||||
|
||||
$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)
|
||||
|
||||
5
application/Espo/Core/Templates/i18n/hu_HU/Base.json
Normal file
5
application/Espo/Core/Templates/i18n/hu_HU/Base.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create {entityType}": "{EntityTypeTranslated} létrehozása"
|
||||
}
|
||||
}
|
||||
10
application/Espo/Core/Templates/i18n/hu_HU/BasePlus.json
Normal file
10
application/Espo/Core/Templates/i18n/hu_HU/BasePlus.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"links": {
|
||||
"meetings": "találkozók",
|
||||
"calls": "felhívja",
|
||||
"tasks": "feladatok"
|
||||
},
|
||||
"labels": {
|
||||
"Create {entityType}": "{EntityTypeTranslated} létrehozása"
|
||||
}
|
||||
}
|
||||
15
application/Espo/Core/Templates/i18n/hu_HU/Company.json
Normal file
15
application/Espo/Core/Templates/i18n/hu_HU/Company.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"fields": {
|
||||
"billingAddress": "számlázási cím",
|
||||
"shippingAddress": "szállítási cím",
|
||||
"website": "Weboldal"
|
||||
},
|
||||
"links": {
|
||||
"meetings": "találkozók",
|
||||
"calls": "felhívja",
|
||||
"tasks": "feladatok"
|
||||
},
|
||||
"labels": {
|
||||
"Create {entityType}": "{EntityTypeTranslated} létrehozása"
|
||||
}
|
||||
}
|
||||
27
application/Espo/Core/Templates/i18n/hu_HU/Event.json
Normal file
27
application/Espo/Core/Templates/i18n/hu_HU/Event.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"fields": {
|
||||
"parent": "Szülő",
|
||||
"dateStart": "Dátum kezdete",
|
||||
"dateEnd": "Dátum vége",
|
||||
"duration": "tartam",
|
||||
"status": "Állapot",
|
||||
"reminders": "Emlékeztetők"
|
||||
},
|
||||
"links": {
|
||||
"parent": "Szülő"
|
||||
},
|
||||
"options": {
|
||||
"status": {
|
||||
"Planned": "Tervezett",
|
||||
"Not Held": "Nem tartott"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"Create {entityType}": "{EntityTypeTranslated} létrehozása",
|
||||
"Schedule {entityType}": "{EntityTypeTranslated} ütemezése"
|
||||
},
|
||||
"presetFilters": {
|
||||
"planned": "Tervezett",
|
||||
"todays": "A mai"
|
||||
}
|
||||
}
|
||||
13
application/Espo/Core/Templates/i18n/hu_HU/Person.json
Normal file
13
application/Espo/Core/Templates/i18n/hu_HU/Person.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"fields": {
|
||||
"address": "Cím"
|
||||
},
|
||||
"links": {
|
||||
"meetings": "találkozók",
|
||||
"calls": "felhívja",
|
||||
"tasks": "feladatok"
|
||||
},
|
||||
"labels": {
|
||||
"Create {entityType}": "{EntityTypeTranslated} létrehozása"
|
||||
}
|
||||
}
|
||||
5
application/Espo/Core/Templates/i18n/sk_SK/Base.json
Normal file
5
application/Espo/Core/Templates/i18n/sk_SK/Base.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create {entityType}": "Vytvoriť {entityTypeTranslated}"
|
||||
}
|
||||
}
|
||||
10
application/Espo/Core/Templates/i18n/sk_SK/BasePlus.json
Normal file
10
application/Espo/Core/Templates/i18n/sk_SK/BasePlus.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"links": {
|
||||
"meetings": "Stretnutia",
|
||||
"calls": "Hovory",
|
||||
"tasks": "Úlohy"
|
||||
},
|
||||
"labels": {
|
||||
"Create {entityType}": "Vytvoriť {entityTypeTranslated}"
|
||||
}
|
||||
}
|
||||
15
application/Espo/Core/Templates/i18n/sk_SK/Company.json
Normal file
15
application/Espo/Core/Templates/i18n/sk_SK/Company.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"fields": {
|
||||
"billingAddress": "Fakturačná adresa",
|
||||
"shippingAddress": "Dodacia adresa",
|
||||
"website": "Webová stránka"
|
||||
},
|
||||
"links": {
|
||||
"meetings": "Stretnutia",
|
||||
"calls": "Hovory",
|
||||
"tasks": "Úlohy"
|
||||
},
|
||||
"labels": {
|
||||
"Create {entityType}": "Vytvoriť {entityTypeTranslated}"
|
||||
}
|
||||
}
|
||||
36
application/Espo/Core/Templates/i18n/sk_SK/Event.json
Normal file
36
application/Espo/Core/Templates/i18n/sk_SK/Event.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"fields": {
|
||||
"parent": "Rodič",
|
||||
"dateStart": "Dátum začiatku",
|
||||
"dateEnd": "Dátum konca",
|
||||
"duration": "Trvanie",
|
||||
"status": "Stav",
|
||||
"reminders": "Pripomienky"
|
||||
},
|
||||
"links": {
|
||||
"parent": "Rodič"
|
||||
},
|
||||
"options": {
|
||||
"status": {
|
||||
"Planned": "Plánovaný",
|
||||
"Held": "Pozdržaný",
|
||||
"Not Held": "Nepozdržaný"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"Create {entityType}": "Vytvoriť {entityTypeTranslated}",
|
||||
"Schedule {entityType}": "Naplánovať {entityTypeTranslated}",
|
||||
"Log {entityType}": "Protokol {entityTypeTranslated}",
|
||||
"Set Held": "Nastav pozdržaný",
|
||||
"Set Not Held": "Nastav nepozdržaný"
|
||||
},
|
||||
"massActions": {
|
||||
"setHeld": "Nastav pozdržaný",
|
||||
"setNotHeld": "Nastav nepozdržaný"
|
||||
},
|
||||
"presetFilters": {
|
||||
"planned": "Plánovaný",
|
||||
"held": "Pozdržaný",
|
||||
"todays": "Dnešné"
|
||||
}
|
||||
}
|
||||
13
application/Espo/Core/Templates/i18n/sk_SK/Person.json
Normal file
13
application/Espo/Core/Templates/i18n/sk_SK/Person.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"fields": {
|
||||
"address": "Adresa"
|
||||
},
|
||||
"links": {
|
||||
"meetings": "Stretnutia",
|
||||
"calls": "Hovory",
|
||||
"tasks": "Úlohy"
|
||||
},
|
||||
"labels": {
|
||||
"Create {entityType}": "Vytvoriť {entityTypeTranslated}"
|
||||
}
|
||||
}
|
||||
@@ -631,7 +631,13 @@ abstract class Base
|
||||
|
||||
protected function systemRebuild()
|
||||
{
|
||||
return $this->getContainer()->get('dataManager')->rebuild();
|
||||
try {
|
||||
return $this->getContainer()->get('dataManager')->rebuild();
|
||||
} catch (\Exception $e) {
|
||||
$GLOBALS['log']->error('Database rebuild failure, details: '.$e->getMessage().'.');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -25,16 +25,19 @@
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Driver\PDOMySql;
|
||||
|
||||
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
|
||||
class Driver extends \Doctrine\DBAL\Driver\PDOMySql\Driver
|
||||
{
|
||||
|
||||
public function getDatabasePlatform()
|
||||
{
|
||||
return new \Espo\Core\Utils\Database\DBAL\Platforms\MySqlPlatform();
|
||||
}
|
||||
|
||||
|
||||
public function getSchemaManager(\Doctrine\DBAL\Connection $conn)
|
||||
{
|
||||
return new \Espo\Core\Utils\Database\DBAL\Schema\MySqlSchemaManager($conn);
|
||||
}
|
||||
}
|
||||
@@ -315,12 +315,154 @@ 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';
|
||||
}
|
||||
|
||||
public function getColumnDeclarationListSQL(array $fields)
|
||||
{
|
||||
$queryFields = array();
|
||||
|
||||
foreach ($fields as $fieldName => $field) {
|
||||
$quotedFieldName = $this->espoQuote($fieldName);
|
||||
$queryFields[] = $this->getColumnDeclarationSQL($quotedFieldName, $field);
|
||||
}
|
||||
|
||||
return implode(', ', $queryFields);
|
||||
}
|
||||
//end: ESPO
|
||||
}
|
||||
82
application/Espo/Core/Utils/Database/DBAL/Schema/Index.php
Normal file
82
application/Espo/Core/Utils/Database/DBAL/Schema/Index.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\Index as DBALIndex;
|
||||
|
||||
class Index extends \Doctrine\DBAL\Schema\Index
|
||||
{
|
||||
public function addFlag($flag)
|
||||
{
|
||||
$this->_flags[strtolower($flag)] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasFlag($flag)
|
||||
{
|
||||
return isset($this->_flags[strtolower($flag)]);
|
||||
}
|
||||
|
||||
public function removeFlag($flag)
|
||||
{
|
||||
unset($this->_flags[strtolower($flag)]);
|
||||
}
|
||||
|
||||
public function isFullfilledBy(DBALIndex $other)
|
||||
{
|
||||
if (count($other->getColumns()) != count($this->getColumns())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sameColumns = $this->spansColumns($other->getColumns());
|
||||
|
||||
if ($sameColumns) {
|
||||
$flags = $this->getFlags();
|
||||
$otherFlags = $other->getFlags();
|
||||
|
||||
if ( ! $this->isUnique() && !$this->isPrimary() && $flags === $otherFlags) {
|
||||
return true;
|
||||
} else if ($other->isPrimary() != $this->isPrimary()) {
|
||||
return false;
|
||||
} else if ($other->isUnique() != $this->isUnique()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($flags) != count($otherFlags) || array_diff($flags, $otherFlags) !== array_diff($otherFlags, $flags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Events;
|
||||
use Doctrine\DBAL\Event\SchemaIndexDefinitionEventArgs;
|
||||
|
||||
class MySqlSchemaManager extends \Doctrine\DBAL\Schema\MySqlSchemaManager
|
||||
{
|
||||
public function createSchema()
|
||||
{
|
||||
$sequences = array();
|
||||
if ($this->_platform->supportsSequences()) {
|
||||
$sequences = $this->listSequences();
|
||||
}
|
||||
$tables = $this->listTables();
|
||||
|
||||
return new Schema($tables, $sequences, $this->createSchemaConfig());
|
||||
}
|
||||
|
||||
public function listTables()
|
||||
{
|
||||
$tableNames = $this->listTableNames();
|
||||
|
||||
$tables = array();
|
||||
foreach ($tableNames as $tableName) {
|
||||
$tables[] = $this->listTableDetails($tableName);
|
||||
}
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
public function listTableDetails($tableName)
|
||||
{
|
||||
$columns = $this->listTableColumns($tableName);
|
||||
$foreignKeys = array();
|
||||
if ($this->_platform->supportsForeignKeyConstraints()) {
|
||||
$foreignKeys = $this->listTableForeignKeys($tableName);
|
||||
}
|
||||
$indexes = $this->listTableIndexes($tableName);
|
||||
|
||||
return new Table($tableName, $columns, $indexes, $foreignKeys, false, array());
|
||||
}
|
||||
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
$sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
|
||||
|
||||
$tableIndexes = $this->_conn->fetchAll($sql);
|
||||
|
||||
return $this->_getPortableTableIndexesList($tableIndexes, $table);
|
||||
}
|
||||
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
|
||||
{
|
||||
foreach($tableIndexes as $k => $v) {
|
||||
$v = array_change_key_case($v, CASE_LOWER);
|
||||
if($v['key_name'] == 'PRIMARY') {
|
||||
$v['primary'] = true;
|
||||
} else {
|
||||
$v['primary'] = false;
|
||||
}
|
||||
if (strpos($v['index_type'], 'FULLTEXT') !== false) {
|
||||
$v['flags'] = array('FULLTEXT');
|
||||
}
|
||||
$tableIndexes[$k] = $v;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach($tableIndexes as $tableIndex) {
|
||||
|
||||
$indexName = $keyName = $tableIndex['key_name'];
|
||||
if ($tableIndex['primary']) {
|
||||
$keyName = 'primary';
|
||||
}
|
||||
$keyName = strtolower($keyName);
|
||||
|
||||
if (!isset($result[$keyName])) {
|
||||
$result[$keyName] = array(
|
||||
'name' => $indexName,
|
||||
'columns' => array($tableIndex['column_name']),
|
||||
'unique' => $tableIndex['non_unique'] ? false : true,
|
||||
'primary' => $tableIndex['primary'],
|
||||
'flags' => isset($tableIndex['flags']) ? $tableIndex['flags'] : array(),
|
||||
);
|
||||
} else {
|
||||
$result[$keyName]['columns'][] = $tableIndex['column_name'];
|
||||
}
|
||||
}
|
||||
|
||||
$eventManager = $this->_platform->getEventManager();
|
||||
|
||||
$indexes = array();
|
||||
foreach($result as $indexKey => $data) {
|
||||
$index = null;
|
||||
$defaultPrevented = false;
|
||||
|
||||
if (null !== $eventManager && $eventManager->hasListeners(Events::onSchemaIndexDefinition)) {
|
||||
$eventArgs = new SchemaIndexDefinitionEventArgs($data, $tableName, $this->_conn);
|
||||
$eventManager->dispatchEvent(Events::onSchemaIndexDefinition, $eventArgs);
|
||||
|
||||
$defaultPrevented = $eventArgs->isDefaultPrevented();
|
||||
$index = $eventArgs->getIndex();
|
||||
}
|
||||
|
||||
if ( ! $defaultPrevented) {
|
||||
$index = new Index($data['name'], $data['columns'], $data['unique'], $data['primary'], $data['flags']);
|
||||
}
|
||||
|
||||
if ($index) {
|
||||
$indexes[$indexKey] = $index;
|
||||
}
|
||||
}
|
||||
|
||||
return $indexes;
|
||||
}
|
||||
}
|
||||
@@ -28,9 +28,9 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
class Schema extends \Doctrine\DBAL\Schema\Schema
|
||||
{
|
||||
|
||||
/**
|
||||
* Creates a new table.
|
||||
*
|
||||
|
||||
@@ -28,11 +28,12 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
class Table extends \Doctrine\DBAL\Schema\Table
|
||||
{
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
* @param string $typeName
|
||||
@@ -49,4 +50,35 @@ class Table extends \Doctrine\DBAL\Schema\Table
|
||||
return $column;
|
||||
}
|
||||
|
||||
public function addIndex(array $columnNames, $indexName = null, array $flags = array())
|
||||
{
|
||||
if($indexName == null) {
|
||||
$indexName = $this->_generateIdentifierName(
|
||||
array_merge(array($this->getName()), $columnNames), "idx", $this->_getMaxIdentifierLength()
|
||||
);
|
||||
}
|
||||
|
||||
return $this->_createIndex($columnNames, $indexName, false, false, $flags);
|
||||
}
|
||||
|
||||
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = array())
|
||||
{
|
||||
if (preg_match('(([^a-zA-Z0-9_]+))', $indexName)) {
|
||||
throw SchemaException::indexNameInvalid($indexName);
|
||||
}
|
||||
|
||||
foreach ($columnNames as $columnName => $indexColOptions) {
|
||||
if (is_numeric($columnName) && is_string($indexColOptions)) {
|
||||
$columnName = $indexColOptions;
|
||||
}
|
||||
|
||||
if ( ! $this->hasColumn($columnName)) {
|
||||
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
|
||||
}
|
||||
}
|
||||
|
||||
$this->_addIndex(new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
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',
|
||||
|
||||
@@ -33,28 +33,29 @@ class LinkMultiple extends Base
|
||||
{
|
||||
protected function load($fieldName, $entityName)
|
||||
{
|
||||
$data = array(
|
||||
$entityName => array (
|
||||
'fields' => array(
|
||||
$fieldName.'Ids' => array(
|
||||
$data = [
|
||||
$entityName => [
|
||||
'fields' => [
|
||||
$fieldName.'Ids' => [
|
||||
'type' => 'jsonArray',
|
||||
'notStorable' => true,
|
||||
'isLinkMultipleIdList' => true,
|
||||
'relation' => $fieldName
|
||||
),
|
||||
$fieldName.'Names' => array(
|
||||
'relation' => $fieldName,
|
||||
'isUnordered' => true
|
||||
],
|
||||
$fieldName.'Names' => [
|
||||
'type' => 'jsonObject',
|
||||
'notStorable' => true,
|
||||
'isLinkMultipleNameMap' => true
|
||||
)
|
||||
)
|
||||
),
|
||||
'unset' => array(
|
||||
$entityName => array(
|
||||
'fields.'.$fieldName
|
||||
)
|
||||
)
|
||||
);
|
||||
]
|
||||
]
|
||||
],
|
||||
'unset' => [
|
||||
$entityName => [
|
||||
'fields.' . $fieldName
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$fieldParams = $this->getFieldParams();
|
||||
|
||||
@@ -67,10 +68,10 @@ class LinkMultiple extends Base
|
||||
|
||||
$columns = $this->getMetadata()->get("entityDefs.{$entityName}.fields.{$fieldName}.columns");
|
||||
if (!empty($columns)) {
|
||||
$data[$entityName]['fields'][$fieldName . 'Columns'] = array(
|
||||
$data[$entityName]['fields'][$fieldName . 'Columns'] = [
|
||||
'type' => 'jsonObject',
|
||||
'notStorable' => true,
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
||||
@@ -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)) {
|
||||
@@ -620,7 +662,6 @@ class EntityManager
|
||||
$link => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleField,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleField,
|
||||
"noLoad" => !$linkMultipleField,
|
||||
"importDisabled" => !$linkMultipleField,
|
||||
@@ -685,7 +726,6 @@ class EntityManager
|
||||
$linkForeign => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
|
||||
"noLoad" => !$linkMultipleFieldForeign,
|
||||
"importDisabled" => !$linkMultipleFieldForeign,
|
||||
@@ -709,7 +749,6 @@ class EntityManager
|
||||
$link => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleField,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleField,
|
||||
"importDisabled" => !$linkMultipleField,
|
||||
"noLoad" => !$linkMultipleField,
|
||||
@@ -732,7 +771,6 @@ class EntityManager
|
||||
$linkForeign => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
|
||||
"importDisabled" => !$linkMultipleFieldForeign,
|
||||
"noLoad" => !$linkMultipleFieldForeign,
|
||||
@@ -806,7 +844,6 @@ class EntityManager
|
||||
$link => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleField,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleField,
|
||||
"noLoad" => !$linkMultipleField,
|
||||
"importDisabled" => !$linkMultipleField,
|
||||
@@ -831,7 +868,6 @@ class EntityManager
|
||||
$linkForeign => array(
|
||||
"type" => "linkMultiple",
|
||||
"layoutDetailDisabled" => !$linkMultipleFieldForeign,
|
||||
"layoutListDisabled" => true,
|
||||
"layoutMassUpdateDisabled" => !$linkMultipleFieldForeign,
|
||||
"noLoad" => !$linkMultipleFieldForeign,
|
||||
"importDisabled" => !$linkMultipleFieldForeign,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -71,6 +71,7 @@ return array (
|
||||
'de_DE',
|
||||
'es_ES',
|
||||
'hr_HR',
|
||||
'hu_HU',
|
||||
'fr_FR',
|
||||
'id_ID',
|
||||
'it_IT',
|
||||
@@ -78,6 +79,7 @@ return array (
|
||||
'nb_NO',
|
||||
'nl_NL',
|
||||
'tr_TR',
|
||||
'sk_SK',
|
||||
'sr_RS',
|
||||
'ro_RO',
|
||||
'ru_RU',
|
||||
@@ -109,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'],
|
||||
@@ -168,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 (
|
||||
|
||||
35
application/Espo/Entities/EmailTemplateCategory.php
Normal file
35
application/Espo/Entities/EmailTemplateCategory.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?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\Entities;
|
||||
|
||||
class EmailTemplateCategory extends \Espo\Core\Templates\Entities\CategoryTree
|
||||
{
|
||||
|
||||
}
|
||||
@@ -129,7 +129,7 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
}
|
||||
|
||||
if (!empty($size)) {
|
||||
$fileName = $sourceId . '_' . $size . '.jpg';
|
||||
$fileName = $size . '-' . $attachment->get('name');
|
||||
} else {
|
||||
$fileName = $attachment->get('name');
|
||||
}
|
||||
@@ -197,9 +197,10 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
break;
|
||||
}
|
||||
|
||||
$targetImage = imagerotate($targetImage, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filePath)['Orientation'] ?: 0], 0);
|
||||
if (function_exists('exif_read_data')) {
|
||||
$targetImage = imagerotate($targetImage, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filePath)['Orientation'] ?: 0], 0);
|
||||
}
|
||||
|
||||
return $targetImage;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,18 +58,32 @@ class AssignmentEmailNotification extends \Espo\Core\Hooks\Base
|
||||
|
||||
foreach ($userIdList as $userId) {
|
||||
if (in_array($userId, $fetchedAssignedUserIdList)) continue;
|
||||
if ($this->getUser()->id === $userId) continue;
|
||||
if (!$this->isNotSelfAssignment($entity, $userId)) continue;
|
||||
$this->createJob($entity, $userId);
|
||||
}
|
||||
} else {
|
||||
$userId = $entity->get('assignedUserId');
|
||||
if (!empty($userId) && $userId != $this->getUser()->id && $entity->isAttributeChanged('assignedUserId')) {
|
||||
if (!empty($userId) && $entity->isAttributeChanged('assignedUserId') && $this->isNotSelfAssignment($entity, $userId)) {
|
||||
$this->createJob($entity, $userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function isNotSelfAssignment(Entity $entity, $assignedUserId)
|
||||
{
|
||||
if ($entity->hasAttribute('createdById') && $entity->hasAttribute('modifiedById')) {
|
||||
if ($entity->isNew()) {
|
||||
$isNotSelfAssignment = $assignedUserId !== $entity->get('createdById');
|
||||
} else {
|
||||
$isNotSelfAssignment = $assignedUserId !== $entity->get('modifiedById');
|
||||
}
|
||||
} else {
|
||||
$isNotSelfAssignment = $assignedUserId !== $this->getUser()->id;
|
||||
}
|
||||
return $isNotSelfAssignment;
|
||||
}
|
||||
|
||||
protected function createJob(Entity $entity, $userId)
|
||||
{
|
||||
$job = $this->getEntityManager()->getEntity('Job');
|
||||
|
||||
@@ -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,7 @@
|
||||
{
|
||||
"layouts": {
|
||||
"detailConvert": "Convertir Referencia",
|
||||
"listForAccount": "Listado (por Cuentas)"
|
||||
"listForAccount": "Listado (por Cuentas)",
|
||||
"listForContact": "Lista (para Contactos)"
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
"time": "hora",
|
||||
"User List": "Lista de Usuarios",
|
||||
"Manage Users": "Usuarios",
|
||||
"View Calendar": "Ver Calendario"
|
||||
"View Calendar": "Ver Calendario",
|
||||
"Create Shared View": "Crear Vista Compartida"
|
||||
}
|
||||
}
|
||||
@@ -1 +1,5 @@
|
||||
{}
|
||||
{
|
||||
"fields": {
|
||||
"futureDays": "Siguientes 'n' Días"
|
||||
}
|
||||
}
|
||||
104
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Account.json
Normal file
104
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Account.json
Normal file
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"fields": {
|
||||
"name": "Név",
|
||||
"website": "Weboldal",
|
||||
"phoneNumber": "Telefon",
|
||||
"billingAddress": "számlázási cím",
|
||||
"shippingAddress": "szállítási cím",
|
||||
"description": "Leírás",
|
||||
"sicCode": "Sic kód",
|
||||
"industry": "Ipar",
|
||||
"type": "típus",
|
||||
"contactRole": "Cím",
|
||||
"campaign": "Kampány",
|
||||
"targetLists": "Céllista",
|
||||
"targetList": "Tűztábla",
|
||||
"originalLead": "Eredeti vezető",
|
||||
"contactIsInactive": "tétlen"
|
||||
},
|
||||
"links": {
|
||||
"contacts": "Kapcsolatok",
|
||||
"opportunities": "lehetőségek",
|
||||
"cases": "Olyan esetek,",
|
||||
"documents": "Dokumentumok",
|
||||
"meetingsPrimary": "Találkozók (bővített)",
|
||||
"callsPrimary": "Hívások (kibővített)",
|
||||
"tasksPrimary": "Feladatok (bővítve)",
|
||||
"emailsPrimary": "E-mailek (bővítve)",
|
||||
"targetLists": "Céllista",
|
||||
"campaignLogRecords": "Kampánynapló",
|
||||
"campaign": "Kampány",
|
||||
"portalUsers": "Portál felhasználók",
|
||||
"originalLead": "Eredeti vezető"
|
||||
},
|
||||
"options": {
|
||||
"type": {
|
||||
"Customer": "Vevő",
|
||||
"Investor": "Befektetői",
|
||||
"Reseller": "viszonteladó"
|
||||
},
|
||||
"industry": {
|
||||
"Agriculture": "Mezőgazdaság",
|
||||
"Advertising": "Hirdető",
|
||||
"Apparel & Accessories": "Ruházat és kiegészítők",
|
||||
"Automotive": "Autóipari",
|
||||
"Banking": "Banki",
|
||||
"Biotechnology": "Biotechnológiai",
|
||||
"Building Materials & Equipment": "Építőanyagok és berendezések",
|
||||
"Chemical": "Kémiai",
|
||||
"Computer": "Számítógép",
|
||||
"Education": "Oktatás",
|
||||
"Electronics": "Elektronika",
|
||||
"Energy": "Energia",
|
||||
"Entertainment & Leisure": "Szórakozás és szabadidő",
|
||||
"Finance": "Pénzügy",
|
||||
"Food & Beverage": "étel és ital",
|
||||
"Grocery": "Élelmiszerbolt",
|
||||
"Healthcare": "Egészségügy",
|
||||
"Insurance": "Biztosítás",
|
||||
"Legal": "Jogi",
|
||||
"Manufacturing": "Gyártás",
|
||||
"Publishing": "Kiadás",
|
||||
"Real Estate": "Ingatlan",
|
||||
"Service": "Szolgáltatás",
|
||||
"Sports": "Sport",
|
||||
"Software": "Szoftver",
|
||||
"Technology": "Technológia",
|
||||
"Telecommunications": "Távközlési",
|
||||
"Television": "Televízió",
|
||||
"Transportation": "Szállítás",
|
||||
"Venture Capital": "Kockázati tőke",
|
||||
"Aerospace": "légtér",
|
||||
"Architecture": "Építészet",
|
||||
"Construction": "Építés",
|
||||
"Defense": "Védelem",
|
||||
"Creative": "Kreatív",
|
||||
"Culture": "Kultúra",
|
||||
"Consulting": "Tanácsadó",
|
||||
"Electric Power": "Elektromos energia",
|
||||
"Hospitality": "Vendégszeretet",
|
||||
"Mass Media": "Tömegmédia",
|
||||
"Mining": "Bányászati",
|
||||
"Music": "Zene",
|
||||
"Marketing": "értékesítés",
|
||||
"Petroleum": "Petróleum",
|
||||
"Retail": "Kiskereskedelem",
|
||||
"Shipping": "Szállítás",
|
||||
"Support": "Támogatás",
|
||||
"Testing, Inspection & Certification": "Vizsgálat, ellenőrzés és tanúsítás",
|
||||
"Wholesale": "nagybani",
|
||||
"Water": "Víz",
|
||||
"Travel": "Utazás"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"Create Account": "Fiók létrehozása",
|
||||
"Copy Billing": "Számlázás másolása",
|
||||
"Set Primary": "Állítsa be az Elsődleges beállítást"
|
||||
},
|
||||
"presetFilters": {
|
||||
"customers": "Az ügyfelek",
|
||||
"partners": "partnerek",
|
||||
"recentlyCreated": "A közelmúltban létrehozott"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"layouts": {
|
||||
"detailConvert": "Átalakító",
|
||||
"listForAccount": "Lista (a fiókhoz)",
|
||||
"listForContact": "Lista (az elérhetőséghez)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"modes": {
|
||||
"month": "Hónap",
|
||||
"week": "Hét",
|
||||
"agendaWeek": "Hét",
|
||||
"day": "Nap",
|
||||
"agendaDay": "Nap",
|
||||
"timeline": "Idővonal"
|
||||
},
|
||||
"labels": {
|
||||
"Today": "Ma",
|
||||
"Create": "Teremt",
|
||||
"Shared": "megosztott",
|
||||
"Add User": "Felhasználó hozzáadása",
|
||||
"current": "jelenlegi",
|
||||
"time": "idő",
|
||||
"User List": "Felhasználói lista",
|
||||
"Manage Users": "Felhasználók kezelése",
|
||||
"View Calendar": "Naptár megtekintése",
|
||||
"Create Shared View": "Megosztott nézet létrehozása"
|
||||
}
|
||||
}
|
||||
42
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Call.json
Normal file
42
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Call.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"fields": {
|
||||
"name": "Név",
|
||||
"parent": "Szülő",
|
||||
"status": "Állapot",
|
||||
"dateStart": "Dátum kezdete",
|
||||
"dateEnd": "Dátum vége",
|
||||
"direction": "Irány",
|
||||
"duration": "tartam",
|
||||
"description": "Leírás",
|
||||
"users": "felhasználók",
|
||||
"contacts": "Kapcsolatok",
|
||||
"leads": "vezet",
|
||||
"reminders": "Emlékeztetők",
|
||||
"account": "számla",
|
||||
"acceptanceStatus": "Átvételi állapot"
|
||||
},
|
||||
"options": {
|
||||
"status": {
|
||||
"Planned": "Tervezett",
|
||||
"Not Held": "Nem tartott"
|
||||
},
|
||||
"direction": {
|
||||
"Outbound": "induló",
|
||||
"Inbound": "Bejövő"
|
||||
},
|
||||
"acceptanceStatus": {
|
||||
"None": "Egyik sem",
|
||||
"Accepted": "Elfogadott",
|
||||
"Declined": "Elutasította",
|
||||
"Tentative": "Kísérleti"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"Create Call": "Hívás létrehozása",
|
||||
"Send Invitations": "Küldjön meghívókat"
|
||||
},
|
||||
"presetFilters": {
|
||||
"planned": "Tervezett",
|
||||
"todays": "A mai"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
{
|
||||
"fields": {
|
||||
"name": "Név",
|
||||
"description": "Leírás",
|
||||
"status": "Állapot",
|
||||
"type": "típus",
|
||||
"startDate": "Kezdő dátum",
|
||||
"endDate": "Befejezés dátuma",
|
||||
"targetLists": "Céllista",
|
||||
"excludingTargetLists": "Céllista kivételével",
|
||||
"sentCount": "Küldött",
|
||||
"openedCount": "Nyitott",
|
||||
"clickedCount": "Kattintott",
|
||||
"optedOutCount": "Elutasított",
|
||||
"bouncedCount": "Visszafordulást",
|
||||
"softBouncedCount": "Lágy visszahúzódott",
|
||||
"leadCreatedCount": "Leads létrehozva",
|
||||
"revenue": "jövedelem",
|
||||
"revenueConverted": "Bevétel (átváltva)",
|
||||
"budget": "Költségvetés",
|
||||
"budgetConverted": "Költségkeret (átváltva)"
|
||||
},
|
||||
"links": {
|
||||
"targetLists": "Céllista",
|
||||
"excludingTargetLists": "Céllista kivételével",
|
||||
"accounts": "Fiókok",
|
||||
"contacts": "Kapcsolatok",
|
||||
"leads": "vezet",
|
||||
"opportunities": "lehetőségek",
|
||||
"campaignLogRecords": "Bejelentkezés",
|
||||
"massEmails": "Tömeges e-mailek",
|
||||
"trackingUrls": "Nyomon követési URL-ek"
|
||||
},
|
||||
"options": {
|
||||
"type": {
|
||||
"Web": "háló",
|
||||
"Television": "Televízió",
|
||||
"Radio": "Rádió",
|
||||
"Newsletter": "Hírlevél",
|
||||
"Mail": "Levél"
|
||||
},
|
||||
"status": {
|
||||
"Planning": "Tervezés",
|
||||
"Active": "Aktív",
|
||||
"Inactive": "tétlen",
|
||||
"Complete": "teljes"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"Create Campaign": "Kampány létrehozása",
|
||||
"Target Lists": "Céllista",
|
||||
"Statistics": "Statisztika",
|
||||
"hard": "kemény",
|
||||
"soft": "puha",
|
||||
"Unsubscribe": "Leiratkozás",
|
||||
"Mass Emails": "Tömeges e-mailek",
|
||||
"Email Templates": "E-mail sablonok",
|
||||
"Unsubscribe again": "Újrairatkozás újra",
|
||||
"Subscribe again": "Feliratkozás újra",
|
||||
"Create Target List": "Céllista létrehozása"
|
||||
},
|
||||
"presetFilters": {
|
||||
"active": "Aktív"
|
||||
},
|
||||
"messages": {
|
||||
"unsubscribed": "Leiratkozott a levelezési listáról.",
|
||||
"subscribedAgain": "Újra feliratkozol."
|
||||
},
|
||||
"tooltips": {
|
||||
"targetLists": "Olyan célok, amelyeknek üzeneteket kell kapniuk.",
|
||||
"excludingTargetLists": "Olyan célzások, amelyeknek nem kell üzeneteket kapnia."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"fields": {
|
||||
"action": "Akció",
|
||||
"actionDate": "Dátum",
|
||||
"data": "Adat",
|
||||
"campaign": "Kampány",
|
||||
"parent": "Cél",
|
||||
"object": "Tárgy",
|
||||
"application": "Alkalmazás",
|
||||
"queueItem": "Sor tétel",
|
||||
"stringData": "String adatok",
|
||||
"stringAdditionalData": "String kiegészítő adatok",
|
||||
"isTest": "A teszt"
|
||||
},
|
||||
"links": {
|
||||
"queueItem": "Sor tétel",
|
||||
"parent": "Szülő",
|
||||
"object": "Tárgy",
|
||||
"campaign": "Kampány"
|
||||
},
|
||||
"options": {
|
||||
"action": {
|
||||
"Sent": "Küldött",
|
||||
"Opened": "Nyitott",
|
||||
"Opted Out": "Elutasított",
|
||||
"Bounced": "Visszafordulást",
|
||||
"Clicked": "Kattintott"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"All": "Minden"
|
||||
},
|
||||
"presetFilters": {
|
||||
"sent": "Küldött",
|
||||
"opened": "Nyitott",
|
||||
"optedOut": "Elutasított",
|
||||
"bounced": "Visszafordulást",
|
||||
"clicked": "Kattintott"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"fields": {
|
||||
"urlToUse": "Kód helyett URL helyett",
|
||||
"campaign": "Kampány"
|
||||
},
|
||||
"links": {
|
||||
"campaign": "Kampány"
|
||||
},
|
||||
"labels": {
|
||||
"Create CampaignTrackingUrl": "Követési URL létrehozása"
|
||||
}
|
||||
}
|
||||
61
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Case.json
Normal file
61
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Case.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"fields": {
|
||||
"name": "Név",
|
||||
"number": "Szám",
|
||||
"status": "Állapot",
|
||||
"account": "számla",
|
||||
"contact": "Kapcsolatba lépni",
|
||||
"contacts": "Kapcsolatok",
|
||||
"priority": "Kiemelten fontos",
|
||||
"type": "típus",
|
||||
"description": "Leírás",
|
||||
"lead": "Vezet",
|
||||
"attachments": "Mellékletek",
|
||||
"inboundEmail": "Csoportos e-mail fiók"
|
||||
},
|
||||
"links": {
|
||||
"account": "számla",
|
||||
"contact": "Kapcsolat (elsődleges)",
|
||||
"Contacts": "Kapcsolatok",
|
||||
"meetings": "találkozók",
|
||||
"calls": "felhívja",
|
||||
"tasks": "feladatok",
|
||||
"emails": "e-mailek",
|
||||
"articles": "Tudásbázis-cikkek",
|
||||
"lead": "Vezet",
|
||||
"attachments": "Mellékletek",
|
||||
"inboundEmail": "Csoportos e-mail fiók"
|
||||
},
|
||||
"options": {
|
||||
"status": {
|
||||
"New": "Új",
|
||||
"Assigned": "Kijelölt",
|
||||
"Pending": "Függőben levő",
|
||||
"Closed": "Zárva",
|
||||
"Rejected": "Elutasítva",
|
||||
"Duplicate": "Másolat"
|
||||
},
|
||||
"priority": {
|
||||
"Low": "Alacsony",
|
||||
"Normal": "Normál",
|
||||
"High": "Magas",
|
||||
"Urgent": "Sürgős"
|
||||
},
|
||||
"type": {
|
||||
"Question": "Kérdés",
|
||||
"Incident": "Incidens",
|
||||
"Problem": "Probléma"
|
||||
}
|
||||
},
|
||||
"labels": {
|
||||
"Create Case": "Létrehozás",
|
||||
"Close": "Bezárás",
|
||||
"Reject": "Elutasít",
|
||||
"Closed": "Zárva",
|
||||
"Rejected": "Elutasítva"
|
||||
},
|
||||
"presetFilters": {
|
||||
"open": "Nyisd ki",
|
||||
"closed": "Zárva"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"fields": {
|
||||
"name": "Név",
|
||||
"title": "Cím",
|
||||
"accountRole": "Cím",
|
||||
"account": "számla",
|
||||
"accounts": "Fiókok",
|
||||
"phoneNumber": "Telefon",
|
||||
"accountType": "Fiók Típus",
|
||||
"doNotCall": "Ne hívj",
|
||||
"address": "Cím",
|
||||
"opportunityRole": "Lehetőség szerep",
|
||||
"description": "Leírás",
|
||||
"campaign": "Kampány",
|
||||
"targetLists": "Céllista",
|
||||
"targetList": "Tűztábla",
|
||||
"portalUser": "Portál felhasználó",
|
||||
"originalLead": "Eredeti vezető",
|
||||
"acceptanceStatus": "Átvételi állapot",
|
||||
"accountIsInactive": "inaktív fiók",
|
||||
"acceptanceStatusMeetings": "Átvételi állapot (ülések)",
|
||||
"acceptanceStatusCalls": "Átvételi állapot (hívások)"
|
||||
},
|
||||
"links": {
|
||||
"opportunities": "lehetőségek",
|
||||
"cases": "Olyan esetek,",
|
||||
"targetLists": "Céllista",
|
||||
"campaignLogRecords": "Kampánynapló",
|
||||
"campaign": "Kampány",
|
||||
"account": "Fiók (elsődleges)",
|
||||
"accounts": "Fiókok",
|
||||
"casesPrimary": "Ügyek (elsődleges)",
|
||||
"portalUser": "Portál felhasználó",
|
||||
"originalLead": "Eredeti vezető",
|
||||
"documents": "Dokumentumok",
|
||||
"tasksPrimary": "Feladatok (bővítve)"
|
||||
},
|
||||
"labels": {
|
||||
"Create Contact": "Kapcsolat létrehozása"
|
||||
},
|
||||
"options": {
|
||||
"opportunityRole": {
|
||||
"": "--Egyik sem--",
|
||||
"Decision Maker": "Döntéshozó",
|
||||
"Evaluator": "Kiértékelő",
|
||||
"Influencer": "Befolyásoló"
|
||||
}
|
||||
},
|
||||
"presetFilters": {
|
||||
"portalUsers": "Portál felhasználók",
|
||||
"notPortalUsers": "Nem Portal felhasználók",
|
||||
"accountActive": "Aktív"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"fields": {
|
||||
"futureDays": "Következő X napok"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create Document": "Dokumentum létrehozása",
|
||||
"Details": "Részletek"
|
||||
},
|
||||
"fields": {
|
||||
"name": "Név",
|
||||
"status": "Állapot",
|
||||
"file": "fájl",
|
||||
"type": "típus",
|
||||
"publishDate": "Megjelenési dátum",
|
||||
"expirationDate": "Lejárati dátum",
|
||||
"description": "Leírás",
|
||||
"accounts": "Fiókok"
|
||||
},
|
||||
"links": {
|
||||
"accounts": "Fiókok",
|
||||
"opportunities": "lehetőségek",
|
||||
"leads": "vezet",
|
||||
"contacts": "Kapcsolatok"
|
||||
},
|
||||
"options": {
|
||||
"status": {
|
||||
"Active": "Aktív",
|
||||
"Draft": "vázlat",
|
||||
"Expired": "Lejárt",
|
||||
"Canceled": "Törölve"
|
||||
},
|
||||
"type": {
|
||||
"": "Egyik sem",
|
||||
"Contract": "Szerződés",
|
||||
"License Agreement": "Licencszerződés"
|
||||
}
|
||||
},
|
||||
"presetFilters": {
|
||||
"active": "Aktív",
|
||||
"draft": "vázlat"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create DocumentFolder": "Dokumentummappa létrehozása",
|
||||
"Manage Categories": "Mappák kezelése",
|
||||
"Documents": "Dokumentumok"
|
||||
},
|
||||
"links": {
|
||||
"documents": "Dokumentumok"
|
||||
}
|
||||
}
|
||||
10
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Email.json
Normal file
10
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Email.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create Lead": "Lead létrehozása",
|
||||
"Create Contact": "Kapcsolat létrehozása",
|
||||
"Create Task": "Feladat létrehozása",
|
||||
"Create Case": "Létrehozás",
|
||||
"Add to Contact": "Hozzáadás a kapcsolatokhoz",
|
||||
"Add to Lead": "Add az ólomhoz"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"fields": {
|
||||
"name": "Név",
|
||||
"status": "Állapot",
|
||||
"target": "Cél",
|
||||
"sentAt": "Dátum elküldve",
|
||||
"attemptCount": "Kísérletek",
|
||||
"emailAddress": "Email cím",
|
||||
"massEmail": "Tömeges e-mail",
|
||||
"isTest": "A teszt"
|
||||
},
|
||||
"links": {
|
||||
"target": "Cél",
|
||||
"massEmail": "Tömeges e-mail"
|
||||
},
|
||||
"options": {
|
||||
"status": {
|
||||
"Pending": "Függőben levő",
|
||||
"Sent": "Küldött",
|
||||
"Failed": "nem sikerült",
|
||||
"Sending": "elküldés"
|
||||
}
|
||||
},
|
||||
"presetFilters": {
|
||||
"pending": "Függőben levő",
|
||||
"sent": "Küldött",
|
||||
"failed": "nem sikerült"
|
||||
}
|
||||
}
|
||||
117
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Global.json
Normal file
117
application/Espo/Modules/Crm/Resources/i18n/hu_HU/Global.json
Normal file
@@ -0,0 +1,117 @@
|
||||
{
|
||||
"links": {
|
||||
"parent": "Szülő",
|
||||
"contacts": "Kapcsolatok",
|
||||
"opportunities": "lehetőségek",
|
||||
"leads": "vezet",
|
||||
"meetings": "találkozók",
|
||||
"calls": "felhívja",
|
||||
"tasks": "feladatok",
|
||||
"emails": "e-mailek",
|
||||
"accounts": "Fiókok",
|
||||
"cases": "Olyan esetek,",
|
||||
"documents": "Dokumentumok",
|
||||
"account": "számla",
|
||||
"opportunity": "Lehetőség",
|
||||
"contact": "Kapcsolatba lépni"
|
||||
},
|
||||
"scopeNames": {
|
||||
"Account": "számla",
|
||||
"Contact": "Kapcsolatba lépni",
|
||||
"Lead": "Vezet",
|
||||
"Target": "Cél",
|
||||
"Opportunity": "Lehetőség",
|
||||
"Meeting": "Találkozó",
|
||||
"Calendar": "Naptár",
|
||||
"Call": "Hívás",
|
||||
"Task": "Feladat",
|
||||
"Case": "Ügy",
|
||||
"Document": "Dokumentum",
|
||||
"DocumentFolder": "Dokumentum mappa",
|
||||
"Campaign": "Kampány",
|
||||
"TargetList": "Tűztábla",
|
||||
"MassEmail": "Tömeges e-mail",
|
||||
"EmailQueueItem": "E-mail soros tétel",
|
||||
"CampaignTrackingUrl": "Követési URL",
|
||||
"Activities": "Tevékenységek",
|
||||
"KnowledgeBaseArticle": "Tudásbázis cikk",
|
||||
"KnowledgeBaseCategory": "Tudásbázis-kategória",
|
||||
"CampaignLogRecord": "Kampánynaplófelvétel"
|
||||
},
|
||||
"scopeNamesPlural": {
|
||||
"Account": "Fiókok",
|
||||
"Contact": "Kapcsolatok",
|
||||
"Lead": "vezet",
|
||||
"Target": "célok",
|
||||
"Opportunity": "lehetőségek",
|
||||
"Meeting": "találkozók",
|
||||
"Calendar": "Naptár",
|
||||
"Call": "felhívja",
|
||||
"Task": "feladatok",
|
||||
"Case": "Olyan esetek,",
|
||||
"Document": "Dokumentumok",
|
||||
"DocumentFolder": "Dokumentummappák",
|
||||
"Campaign": "kampányok",
|
||||
"TargetList": "Céllista",
|
||||
"MassEmail": "Tömeges e-mailek",
|
||||
"EmailQueueItem": "E-mail soros elemek",
|
||||
"CampaignTrackingUrl": "Nyomon követési URL-ek",
|
||||
"Activities": "Tevékenységek",
|
||||
"KnowledgeBaseArticle": "Tudásbázis",
|
||||
"KnowledgeBaseCategory": "Tudásbázis kategóriák",
|
||||
"CampaignLogRecord": "Kampánynaplórekordok"
|
||||
},
|
||||
"dashlets": {
|
||||
"Leads": "Az én vezetem",
|
||||
"Opportunities": "Lehetőségek",
|
||||
"Tasks": "Saját feladatok",
|
||||
"Cases": "Az én ügyeim",
|
||||
"Calendar": "Naptár",
|
||||
"Calls": "Saját hívások",
|
||||
"Meetings": "Találkozásom",
|
||||
"OpportunitiesByStage": "Lehetőségek színpadon",
|
||||
"OpportunitiesByLeadSource": "Lehetőségek a vezető forrásból",
|
||||
"SalesByMonth": "Havi eladások",
|
||||
"SalesPipeline": "Értékesítési csővezeték",
|
||||
"Activities": "Tevékenységem"
|
||||
},
|
||||
"labels": {
|
||||
"Create InboundEmail": "Bejövő e-mail létrehozása",
|
||||
"Activities": "Tevékenységek",
|
||||
"History": "Történelem",
|
||||
"Attendees": "résztvevők",
|
||||
"Schedule Meeting": "Menetrend Találkozó",
|
||||
"Schedule Call": "Hívás ütemezése",
|
||||
"Compose Email": "E-mail írása",
|
||||
"Log Meeting": "Naplózás",
|
||||
"Log Call": "Naplóhívás",
|
||||
"Archive Email": "Archívum e-mailben",
|
||||
"Create Task": "Feladat létrehozása",
|
||||
"Tasks": "feladatok"
|
||||
},
|
||||
"fields": {
|
||||
"billingAddressCity": "Város",
|
||||
"addressCity": "Város",
|
||||
"billingAddressCountry": "Ország",
|
||||
"addressCountry": "Ország",
|
||||
"billingAddressPostalCode": "Irányítószám",
|
||||
"addressPostalCode": "Irányítószám",
|
||||
"billingAddressState": "Állapot",
|
||||
"addressState": "Állapot",
|
||||
"billingAddressStreet": "utca",
|
||||
"addressStreet": "utca",
|
||||
"billingAddressMap": "Térkép",
|
||||
"addressMap": "Térkép",
|
||||
"shippingAddressCity": "Város (Szállítás)",
|
||||
"shippingAddressStreet": "Utca (Szállítás)",
|
||||
"shippingAddressCountry": "Ország (szállítás)",
|
||||
"shippingAddressState": "Állam (szállítás)",
|
||||
"shippingAddressPostalCode": "Irányítószám (Szállítás)",
|
||||
"shippingAddressMap": "Térkép (Szállítás)"
|
||||
},
|
||||
"options": {
|
||||
"reminderTypes": {
|
||||
"Popup": "Felugrik"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user