mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-10 22:57:01 +00:00
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0e9d3b347 | ||
|
|
5803e3bc85 | ||
|
|
442e62d76c | ||
|
|
086e9a926f | ||
|
|
90a341de2f | ||
|
|
d4ea6d146a | ||
|
|
af991b4008 | ||
|
|
c17017be9b | ||
|
|
e9fcca84db | ||
|
|
6aa7fa589e | ||
|
|
7aab1b611a | ||
|
|
971265eb6b | ||
|
|
f14a699c08 | ||
|
|
2aea0350eb | ||
|
|
18a556bce0 | ||
|
|
783adab8a5 | ||
|
|
1a802b5822 | ||
|
|
99bf56d031 | ||
|
|
34135f0e17 | ||
|
|
c22f23a1c4 | ||
|
|
5ef8588ad1 | ||
|
|
cef5e09a58 | ||
|
|
6933c599ad | ||
|
|
86cb201539 | ||
|
|
619856ad58 | ||
|
|
b539273351 | ||
|
|
dc4c4c3742 | ||
|
|
ae18ce444c | ||
|
|
d1b35594e7 | ||
|
|
79befe9508 | ||
|
|
cc75457e50 | ||
|
|
0fa58aaee6 | ||
|
|
a3f3f7cdaa | ||
|
|
400198f7a3 | ||
|
|
347d3d7fef | ||
|
|
43f8bbc360 | ||
|
|
1ff76bf1a0 | ||
|
|
8d13668595 | ||
|
|
62dcc1f55b | ||
|
|
efb0c354a5 | ||
|
|
09c9faba9c | ||
|
|
c9a6bd2d64 | ||
|
|
5d32485fc5 | ||
|
|
b4aa93a185 | ||
|
|
533faad8c2 | ||
|
|
3a24a45ade | ||
|
|
d6a90990bd | ||
|
|
5b2c6abf90 | ||
|
|
2839c134a0 | ||
|
|
2c3ea3b59b | ||
|
|
72c9f5dceb | ||
|
|
9a71643bed | ||
|
|
ffd61a6844 | ||
|
|
c66cded184 | ||
|
|
6c2a23cf10 | ||
|
|
9877d918b4 | ||
|
|
8a5b845b2c | ||
|
|
c2c98db80e | ||
|
|
54dc83aa59 | ||
|
|
20560d5256 | ||
|
|
21285aa51f | ||
|
|
76990ead8a | ||
|
|
48493f78c2 | ||
|
|
b99c06a8a4 | ||
|
|
563331af03 | ||
|
|
09adf27190 | ||
|
|
53c7d2ac85 | ||
|
|
fa0458d220 | ||
|
|
29e0d93e4f | ||
|
|
4f09cb3592 | ||
|
|
187b76d359 | ||
|
|
428f26b02c | ||
|
|
e976e627ad | ||
|
|
48564ff8cb | ||
|
|
f881d7a5c8 | ||
|
|
5b9b345bf8 | ||
|
|
c54e428d24 | ||
|
|
2b69ac4651 | ||
|
|
3cd2f19ddf | ||
|
|
5ed64d99f6 | ||
|
|
20acd516d6 | ||
|
|
d30ef85c66 | ||
|
|
79dcec028f | ||
|
|
17bd2f3324 | ||
|
|
f4b4f6ff89 | ||
|
|
8dd675a275 | ||
|
|
f4c59e70b5 | ||
|
|
8da0ac369c | ||
|
|
a361b124d6 | ||
|
|
3d2d54aafa | ||
|
|
28da462c4e | ||
|
|
94a6c8d525 | ||
|
|
8fbfce086c | ||
|
|
73ac717c4d | ||
|
|
3fb57a1dbe | ||
|
|
af9718951f | ||
|
|
d40b7aef11 | ||
|
|
54ac4b5308 | ||
|
|
3ee28f8d48 | ||
|
|
32f8a93021 | ||
|
|
39624e1ddb |
@@ -54,9 +54,9 @@ Before we can merge your pull request you need to accept our CLA [here](https://
|
||||
|
||||
Branches:
|
||||
|
||||
* hotfix/* – an upcoming maintenance release; fixes should be pushed to this branch;
|
||||
* master – an upcoming minor or major release; new features should be pushed to this branch;
|
||||
* stable – a last stable release.
|
||||
* hotfix/* – upcoming maintenance release; fixes should be pushed to this branch;
|
||||
* master – develop branch; new features should be pushed to this branch;
|
||||
* stable – last stable release.
|
||||
|
||||
### How to make a translation
|
||||
|
||||
|
||||
@@ -106,6 +106,13 @@ class User extends \Espo\Core\Controllers\Record
|
||||
return $this->getService('User')->passwordChangeRequest($userName, $emailAddress, $url);
|
||||
}
|
||||
|
||||
public function postActionGenerateNewApiKey($params, $data, $request)
|
||||
{
|
||||
if (empty($data->id)) throw new BadRequest();
|
||||
if (!$this->getUser()->isAdmin()) throw new Forbidden();
|
||||
return $this->getRecordService()->generateNewApiKeyForEntity($data->id)->getValueMap();
|
||||
}
|
||||
|
||||
public function actionCreateLink($params, $data, $request)
|
||||
{
|
||||
if (!$this->getUser()->isAdmin()) throw new Forbidden();
|
||||
|
||||
@@ -124,6 +124,11 @@ class Acl
|
||||
return $this->getAclManager()->getScopeForbiddenFieldList($this->getUser(), $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
public function getScopeForbiddenLinkList($scope, $action = 'read', $thresholdLevel = 'no')
|
||||
{
|
||||
return $this->getAclManager()->getScopeForbiddenLinkList($this->getUser(), $scope, $action, $thresholdLevel);
|
||||
}
|
||||
|
||||
public function checkUserPermission($target, $permissionType = 'userPermission')
|
||||
{
|
||||
return $this->getAclManager()->checkUserPermission($this->getUser(), $target, $permissionType);
|
||||
|
||||
@@ -309,6 +309,22 @@ class AclManager
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
public function getScopeForbiddenLinkList(User $user, $scope, $action = 'read', $thresholdLevel = 'no')
|
||||
{
|
||||
$list = [];
|
||||
|
||||
if ($thresholdLevel === 'no') {
|
||||
$list = array_merge(
|
||||
$list,
|
||||
$this->getScopeRestrictedLinkList($scope, $this->getGlobalRestrictionTypeList($user, $action))
|
||||
);
|
||||
$list = array_values($list);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function checkUserPermission(User $user, $target, $permissionType = 'userPermission')
|
||||
{
|
||||
$permission = $this->get($user, $permissionType);
|
||||
|
||||
@@ -378,7 +378,8 @@ class Container
|
||||
{
|
||||
return new \Espo\Core\Utils\ClientManager(
|
||||
$this->get('config'),
|
||||
$this->get('themeManager')
|
||||
$this->get('themeManager'),
|
||||
$this->get('metadata')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class Record extends Base
|
||||
public function actionRead($params, $data, $request)
|
||||
{
|
||||
$id = $params['id'];
|
||||
$entity = $this->getRecordService()->readEntity($id);
|
||||
$entity = $this->getRecordService()->read($id);
|
||||
|
||||
if (empty($entity)) {
|
||||
throw new NotFound();
|
||||
@@ -95,7 +95,7 @@ class Record extends Base
|
||||
|
||||
$service = $this->getRecordService();
|
||||
|
||||
if ($entity = $service->createEntity($data)) {
|
||||
if ($entity = $service->create($data)) {
|
||||
return $entity->getValueMap();
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class Record extends Base
|
||||
|
||||
$id = $params['id'];
|
||||
|
||||
if ($entity = $this->getRecordService()->updateEntity($id, $data)) {
|
||||
if ($entity = $this->getRecordService()->update($id, $data)) {
|
||||
return $entity->getValueMap();
|
||||
}
|
||||
|
||||
@@ -140,12 +140,12 @@ class Record extends Base
|
||||
throw new Forbidden("Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
$result = $this->getRecordService()->findEntities($params);
|
||||
$result = $this->getRecordService()->find($params);
|
||||
|
||||
return array(
|
||||
return [
|
||||
'total' => $result['total'],
|
||||
'list' => isset($result['collection']) ? $result['collection']->getValueMapList() : $result['list']
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
public function getActionListKanban($params, $data, $request)
|
||||
@@ -195,7 +195,7 @@ class Record extends Base
|
||||
throw new Forbidden("Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
$result = $this->getRecordService()->findLinkedEntities($id, $link, $params);
|
||||
$result = $this->getRecordService()->findLinked($id, $link, $params);
|
||||
|
||||
return array(
|
||||
'total' => $result['total'],
|
||||
@@ -211,7 +211,7 @@ class Record extends Base
|
||||
|
||||
$id = $params['id'];
|
||||
|
||||
if ($this->getRecordService()->deleteEntity($id)) {
|
||||
if ($this->getRecordService()->delete($id)) {
|
||||
return true;
|
||||
}
|
||||
throw new Error();
|
||||
@@ -262,9 +262,9 @@ class Record extends Base
|
||||
$params['format'] = $data->format;
|
||||
}
|
||||
|
||||
return array(
|
||||
return [
|
||||
'id' => $this->getRecordService()->export($params)
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
public function actionMassUpdate($params, $data, $request)
|
||||
@@ -349,7 +349,7 @@ class Record extends Base
|
||||
$selectData = json_decode(json_encode($data->selectData), true);
|
||||
}
|
||||
|
||||
return $this->getRecordService()->linkMass($id, $link, $where, $selectData);
|
||||
return $this->getRecordService()->massLink($id, $link, $where, $selectData);
|
||||
} else {
|
||||
$foreignIdList = array();
|
||||
if (isset($data->id)) {
|
||||
@@ -363,7 +363,7 @@ class Record extends Base
|
||||
|
||||
$result = false;
|
||||
foreach ($foreignIdList as $foreignId) {
|
||||
if ($this->getRecordService()->linkEntity($id, $link, $foreignId)) {
|
||||
if ($this->getRecordService()->link($id, $link, $foreignId)) {
|
||||
$result = true;
|
||||
}
|
||||
}
|
||||
@@ -400,7 +400,7 @@ class Record extends Base
|
||||
|
||||
$result = false;
|
||||
foreach ($foreignIdList as $foreignId) {
|
||||
if ($this->getRecordService()->unlinkEntity($id, $link, $foreignId)) {
|
||||
if ($this->getRecordService()->unlink($id, $link, $foreignId)) {
|
||||
$result = $result || true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,10 @@ class SumRelatedType extends \Espo\Core\Formula\Functions\Base
|
||||
|
||||
$selectParams['groupBy'] = [$foreignLink . '.id'];
|
||||
|
||||
$selectParams['whereClause'][] = [
|
||||
$foreignLink . '.id' => $entity->id
|
||||
];
|
||||
|
||||
$entityManager->getRepository($foreignEntityType)->handleSelectParams($selectParams);
|
||||
|
||||
$sql = $entityManager->getQuery()->createSelectQuery($foreignEntityType, $selectParams);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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\RecordGroup;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class CountType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
}
|
||||
|
||||
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) < 3) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$entityType = $this->evaluate($item->value[0]);
|
||||
|
||||
$whereClause = [];
|
||||
|
||||
$i = 1;
|
||||
while ($i < count($item->value) - 1) {
|
||||
$key = $this->evaluate($item->value[$i]);
|
||||
$value = $this->evaluate($item->value[$i + 1]);
|
||||
$whereClause[$key] = $value;
|
||||
$i = $i + 2;
|
||||
}
|
||||
|
||||
return $this->getInjection('entityManager')->getRepository($entityType)->where($whereClause)->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?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\RecordGroup;
|
||||
|
||||
use \Espo\ORM\Entity;
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class ExistsType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
protected function init()
|
||||
{
|
||||
$this->addDependency('entityManager');
|
||||
}
|
||||
|
||||
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) < 3) {
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
$entityType = $this->evaluate($item->value[0]);
|
||||
|
||||
$whereClause = [];
|
||||
|
||||
$i = 1;
|
||||
while ($i < count($item->value) - 1) {
|
||||
$key = $this->evaluate($item->value[$i]);
|
||||
$value = $this->evaluate($item->value[$i + 1]);
|
||||
$whereClause[$key] = $value;
|
||||
$i = $i + 2;
|
||||
}
|
||||
|
||||
return !!$this->getInjection('entityManager')->getRepository($entityType)->where($whereClause)->findOne();
|
||||
}
|
||||
}
|
||||
@@ -269,8 +269,13 @@ class Sender
|
||||
|
||||
if (!empty($attachmentCollection)) {
|
||||
foreach ($attachmentCollection as $a) {
|
||||
$fileName = $this->getEntityManager()->getRepository('Attachment')->getFilePath($a);
|
||||
$attachment = new MimePart(file_get_contents($fileName));
|
||||
if ($a->get('contents')) {
|
||||
$contents = $a->get('contents');
|
||||
} else {
|
||||
$fileName = $this->getEntityManager()->getRepository('Attachment')->getFilePath($a);
|
||||
$contents = file_get_contents($fileName);
|
||||
}
|
||||
$attachment = new MimePart($contents);
|
||||
$attachment->disposition = Mime::DISPOSITION_ATTACHMENT;
|
||||
$attachment->encoding = Mime::ENCODING_BASE64;
|
||||
$attachment->filename ='=?utf-8?B?' . base64_encode($a->get('name')) . '?=';
|
||||
@@ -283,8 +288,13 @@ class Sender
|
||||
|
||||
if (!empty($attachmentInlineCollection)) {
|
||||
foreach ($attachmentInlineCollection as $a) {
|
||||
$fileName = $this->getEntityManager()->getRepository('Attachment')->getFilePath($a);
|
||||
$attachment = new MimePart(file_get_contents($fileName));
|
||||
if ($a->get('contents')) {
|
||||
$contents = $a->get('contents');
|
||||
} else {
|
||||
$fileName = $this->getEntityManager()->getRepository('Attachment')->getFilePath($a);
|
||||
$contents = file_get_contents($fileName);
|
||||
}
|
||||
$attachment = new MimePart($contents);
|
||||
$attachment->disposition = Mime::DISPOSITION_INLINE;
|
||||
$attachment->encoding = Mime::ENCODING_BASE64;
|
||||
$attachment->id = $a->id;
|
||||
|
||||
@@ -271,7 +271,7 @@ class Base
|
||||
|
||||
protected function applyLinkedWith($link, $idsValue, &$result)
|
||||
{
|
||||
$part = array();
|
||||
$part = [];
|
||||
|
||||
if (is_array($idsValue) && count($idsValue) == 1) {
|
||||
$idsValue = $idsValue[0];
|
||||
@@ -344,9 +344,9 @@ class Base
|
||||
JOIN team AS {$aliasName} ON {$aliasName}.deleted = 0 AND {$aliasName}Middle.team_id = {$aliasName}.id
|
||||
";
|
||||
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
$aliasName . 'Middle.teamId' => $idsValue
|
||||
);
|
||||
];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -419,7 +419,7 @@ class Base
|
||||
|
||||
public function getEmptySelectParams()
|
||||
{
|
||||
$result = array();
|
||||
$result = [];
|
||||
$this->prepareResult($result);
|
||||
|
||||
return $result;
|
||||
@@ -437,16 +437,16 @@ class Base
|
||||
$result['leftJoins'] = [];
|
||||
}
|
||||
if (empty($result['whereClause'])) {
|
||||
$result['whereClause'] = array();
|
||||
$result['whereClause'] = [];
|
||||
}
|
||||
if (empty($result['customJoin'])) {
|
||||
$result['customJoin'] = '';
|
||||
}
|
||||
if (empty($result['additionalSelectColumns'])) {
|
||||
$result['additionalSelectColumns'] = array();
|
||||
$result['additionalSelectColumns'] = [];
|
||||
}
|
||||
if (empty($result['joinConditions'])) {
|
||||
$result['joinConditions'] = array();
|
||||
$result['joinConditions'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -488,9 +488,9 @@ class Base
|
||||
|
||||
protected function accessNo(&$result)
|
||||
{
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'id' => null
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function accessOnlyOwn(&$result)
|
||||
@@ -498,23 +498,23 @@ class Base
|
||||
if ($this->hasAssignedUsersField()) {
|
||||
$this->setDistinct(true, $result);
|
||||
$this->addLeftJoin(['assignedUsers', 'assignedUsersAccess'], $result);
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'assignedUsersAccess.id' => $this->getUser()->id
|
||||
);
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->hasAssignedUserField()) {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'assignedUserId' => $this->getUser()->id
|
||||
);
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->hasCreatedByField()) {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'createdById' => $this->getUser()->id
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,44 +529,44 @@ class Base
|
||||
|
||||
if ($this->hasAssignedUsersField()) {
|
||||
$this->addLeftJoin(['assignedUsers', 'assignedUsersAccess'], $result);
|
||||
$result['whereClause'][] = array(
|
||||
'OR' => array(
|
||||
$result['whereClause'][] = [
|
||||
'OR' => [
|
||||
'teamsAccess.id' => $this->getUser()->getLinkMultipleIdList('teams'),
|
||||
'assignedUsersAccess.id' => $this->getUser()->id
|
||||
)
|
||||
);
|
||||
]
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
$d = array(
|
||||
$d = [
|
||||
'teamsAccess.id' => $this->getUser()->getLinkMultipleIdList('teams')
|
||||
);
|
||||
];
|
||||
if ($this->hasAssignedUserField()) {
|
||||
$d['assignedUserId'] = $this->getUser()->id;
|
||||
} else if ($this->hasCreatedByField()) {
|
||||
$d['createdById'] = $this->getUser()->id;
|
||||
}
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'OR' => $d
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function accessPortalOnlyOwn(&$result)
|
||||
{
|
||||
if ($this->getSeed()->hasAttribute('createdById')) {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'createdById' => $this->getUser()->id
|
||||
);
|
||||
];
|
||||
} else {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'id' => null
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
protected function accessPortalOnlyContact(&$result)
|
||||
{
|
||||
$d = array();
|
||||
$d = [];
|
||||
|
||||
$contactId = $this->getUser()->get('contactId');
|
||||
|
||||
@@ -588,27 +588,27 @@ class Base
|
||||
if ($this->getSeed()->hasAttribute('parentId') && $this->getSeed()->hasRelation('parent')) {
|
||||
$contactId = $this->getUser()->get('contactId');
|
||||
if ($contactId) {
|
||||
$d[] = array(
|
||||
$d[] = [
|
||||
'parentType' => 'Contact',
|
||||
'parentId' => $contactId
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($d)) {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'OR' => $d
|
||||
);
|
||||
];
|
||||
} else {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'id' => null
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
protected function accessPortalOnlyAccount(&$result)
|
||||
{
|
||||
$d = array();
|
||||
$d = [];
|
||||
|
||||
$accountIdList = $this->getUser()->getLinkMultipleIdList('accounts');
|
||||
$contactId = $this->getUser()->get('contactId');
|
||||
@@ -623,15 +623,15 @@ class Base
|
||||
$d['accountsAccess.id'] = $accountIdList;
|
||||
}
|
||||
if ($this->getSeed()->hasAttribute('parentId') && $this->getSeed()->hasRelation('parent')) {
|
||||
$d[] = array(
|
||||
$d[] = [
|
||||
'parentType' => 'Account',
|
||||
'parentId' => $accountIdList
|
||||
);
|
||||
];
|
||||
if ($contactId) {
|
||||
$d[] = array(
|
||||
$d[] = [
|
||||
'parentType' => 'Contact',
|
||||
'parentId' => $contactId
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -652,13 +652,13 @@ class Base
|
||||
}
|
||||
|
||||
if (!empty($d)) {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'OR' => $d
|
||||
);
|
||||
];
|
||||
} else {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'id' => null
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,7 +692,7 @@ class Base
|
||||
|
||||
public function getAclParams()
|
||||
{
|
||||
$result = array();
|
||||
$result = [];
|
||||
$this->applyAccess($result);
|
||||
return $result;
|
||||
}
|
||||
@@ -782,13 +782,42 @@ class Base
|
||||
if (isset($w['attribute'])) {
|
||||
$attribute = $w['attribute'];
|
||||
}
|
||||
|
||||
$type = null;
|
||||
if (isset($w['type'])) {
|
||||
$type = $w['type'];
|
||||
}
|
||||
|
||||
$entityType = $this->getEntityType();
|
||||
|
||||
if ($attribute) {
|
||||
if (isset($w['type']) && in_array($w['type'], ['isLinked', 'isNotLinked', 'linkedWith', 'notLinkedWith', 'isUserFromTeams'])) {
|
||||
if (in_array($attribute, $this->getAcl()->getScopeForbiddenFieldList($this->getEntityType()))) {
|
||||
if (strpos($attribute, '.')) {
|
||||
list($link, $attribute) = explode('.', $attribute);
|
||||
if (!$this->getSeed()->hasRelation($link)) {
|
||||
throw new Forbidden("SelectManager::checkWhere: Unknow relation '{$link}' in where.");
|
||||
}
|
||||
$entityType = $this->getSeed($this->getEntityType())->getRelationParam($link, 'entity');
|
||||
if (!$entityType) {
|
||||
throw new Forbidden("SelectManager::checkWhere: Bad relation.");
|
||||
}
|
||||
if (!$this->getAcl()->checkScope($entityType)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
if ($type && in_array($type, ['isLinked', 'isNotLinked', 'linkedWith', 'notLinkedWith', 'isUserFromTeams'])) {
|
||||
if (in_array($attribute, $this->getAcl()->getScopeForbiddenFieldList($entityType))) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
if (
|
||||
$this->getSeed()->hasRelation($attribute)
|
||||
&&
|
||||
in_array($attribute, $this->getAcl()->getScopeForbiddenLinkList($entityType))
|
||||
) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
} else {
|
||||
if (in_array($attribute, $this->getAcl()->getScopeForbiddenAttributeList($this->getEntityType()))) {
|
||||
if (in_array($attribute, $this->getAcl()->getScopeForbiddenAttributeList($entityType))) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
@@ -814,7 +843,7 @@ class Base
|
||||
return $this->userTimeZone;
|
||||
}
|
||||
|
||||
public function convertDateTimeWhere($item)
|
||||
public function transformDateTimeWhereItem($item)
|
||||
{
|
||||
$format = 'Y-m-d H:i:s';
|
||||
|
||||
@@ -843,11 +872,11 @@ class Base
|
||||
}
|
||||
$type = $item['type'];
|
||||
|
||||
if (empty($value) && in_array($type, array('on', 'before', 'after'))) {
|
||||
if (empty($value) && in_array($type, ['on', 'before', 'after'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$where = array();
|
||||
$where = [];
|
||||
$where['attribute'] = $attribute;
|
||||
|
||||
$dt = new \DateTime('now', new \DateTimeZone($timeZone));
|
||||
@@ -968,6 +997,7 @@ class Base
|
||||
$dt->setTimezone(new \DateTimeZone('UTC'));
|
||||
$where['value'] = $dt->format($format);
|
||||
break;
|
||||
|
||||
case 'between':
|
||||
$where['type'] = 'between';
|
||||
if (is_array($value)) {
|
||||
@@ -983,11 +1013,143 @@ class Base
|
||||
$where['value'] = [$from, $to];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'currentMonth':
|
||||
case 'lastMonth':
|
||||
case 'nextMonth':
|
||||
$where['type'] = 'between';
|
||||
$dtFrom = new \DateTime('now', new \DateTimeZone($timeZone));
|
||||
$dtFrom = $dt->modify('first day of this month')->setTime(0, 0, 0);
|
||||
|
||||
if ($type == 'lastMonth') {
|
||||
$dtFrom->modify('-1 month');
|
||||
} else if ($type == 'nextMonth') {
|
||||
$dtFrom->modify('+1 month');
|
||||
}
|
||||
|
||||
$dtTo = clone $dtFrom;
|
||||
$dtTo->modify('+1 month');
|
||||
|
||||
$dtFrom->setTimezone(new \DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new \DateTimeZone('UTC'));
|
||||
|
||||
$where['value'] = [$dtFrom->format($format), $dtTo->format($format)];
|
||||
break;
|
||||
|
||||
case 'currentQuarter':
|
||||
case 'lastQuarter':
|
||||
$where['type'] = 'between';
|
||||
$dt = new \DateTime('now', new \DateTimeZone($timeZone));
|
||||
$quarter = ceil($dt->format('m') / 3);
|
||||
|
||||
$dtFrom = clone $dt;
|
||||
$dtFrom->modify('first day of January this year')->setTime(0, 0, 0);
|
||||
|
||||
if ($type === 'lastQuarter') {
|
||||
$quarter--;
|
||||
if ($quarter == 0) {
|
||||
$quarter = 4;
|
||||
$dtFrom->modify('-1 year');
|
||||
}
|
||||
}
|
||||
|
||||
$dtFrom->add(new \DateInterval('P'.(($quarter - 1) * 3).'M'));
|
||||
$dtTo = clone $dtFrom;
|
||||
$dtTo->add(new \DateInterval('P3M'));
|
||||
$dtFrom->setTimezone(new \DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new \DateTimeZone('UTC'));
|
||||
$where['value'] = [
|
||||
$dtFrom->format($format),
|
||||
$dtTo->format($format)
|
||||
];
|
||||
break;
|
||||
|
||||
case 'currentYear':
|
||||
case 'lastYear':
|
||||
$where['type'] = 'between';
|
||||
$dtFrom = new \DateTime('now', new \DateTimeZone($timeZone));
|
||||
$dtFrom->modify('first day of January this year')->setTime(0, 0, 0);
|
||||
if ($type == 'lastYear') {
|
||||
$dtFrom->modify('-1 year');
|
||||
}
|
||||
$dtTo = clone $dtFrom;
|
||||
$dtTo = $dtTo->modify('+1 year');
|
||||
$dtFrom->setTimezone(new \DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new \DateTimeZone('UTC'));
|
||||
$where['value'] = [
|
||||
$dtFrom->format($format),
|
||||
$dtTo->format($format)
|
||||
];
|
||||
break;
|
||||
|
||||
case 'currentFiscalYear':
|
||||
case 'lastFiscalYear':
|
||||
$where['type'] = 'between';
|
||||
$dtToday = new \DateTime('now', new \DateTimeZone($timeZone));
|
||||
$dt = clone $dtToday;
|
||||
$fiscalYearShift = $this->getConfig()->get('fiscalYearShift', 0);
|
||||
$dt->modify('first day of January this year')->modify('+' . $fiscalYearShift . ' months')->setTime(0, 0, 0);
|
||||
if (intval($dtToday->format('m')) < $fiscalYearShift + 1) {
|
||||
$dt->modify('-1 year');
|
||||
}
|
||||
if ($type === 'lastFiscalYear') {
|
||||
$dt->modify('-1 year');
|
||||
}
|
||||
$dtFrom = clone $dt;
|
||||
$dtTo = clone $dt;
|
||||
$dtTo = $dtTo->modify('+1 year');
|
||||
$dtFrom->setTimezone(new \DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new \DateTimeZone('UTC'));
|
||||
$where['value'] = [
|
||||
$dtFrom->format($format),
|
||||
$dtTo->format($format)
|
||||
];
|
||||
break;
|
||||
|
||||
case 'currentFiscalQuarter':
|
||||
case 'lastFiscalQuarter':
|
||||
$where['type'] = 'between';
|
||||
$dtToday = new \DateTime('now', new \DateTimeZone($timeZone));
|
||||
$dt = clone $dtToday;
|
||||
$fiscalYearShift = $this->getConfig()->get('fiscalYearShift', 0);
|
||||
$dt->modify('first day of January this year')->modify('+' . $fiscalYearShift . ' months')->setTime(0, 0, 0);
|
||||
$month = intval($dtToday->format('m'));
|
||||
$quarterShift = floor(($month - $fiscalYearShift - 1) / 3);
|
||||
if ($quarterShift) {
|
||||
if ($quarterShift >= 0) {
|
||||
$dt->add(new \DateInterval('P'.($quarterShift * 3).'M'));
|
||||
} else {
|
||||
$quarterShift *= -1;
|
||||
$dt->sub(new \DateInterval('P'.($quarterShift * 3).'M'));
|
||||
}
|
||||
}
|
||||
if ($type === 'lastFiscalQuarter') {
|
||||
$dt->modify('-3 months');
|
||||
}
|
||||
$dtFrom = clone $dt;
|
||||
$dtTo = clone $dt;
|
||||
$dtTo = $dtTo->modify('+3 months');
|
||||
$dtFrom->setTimezone(new \DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new \DateTimeZone('UTC'));
|
||||
$where['value'] = [
|
||||
$dtFrom->format($format),
|
||||
$dtTo->format($format)
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
$where['type'] = $type;
|
||||
}
|
||||
$result = $this->getWherePart($where);
|
||||
|
||||
$where['originalType'] = $type;
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function convertDateTimeWhere($item)
|
||||
{
|
||||
$where = $this->transformDateTimeWhereItem($item);
|
||||
$result = $this->getWherePart($where);
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -1027,6 +1189,11 @@ class Base
|
||||
}
|
||||
$value = $item['value'];
|
||||
|
||||
$timeZone = null;
|
||||
if (isset($item['timeZone'])) {
|
||||
$timeZone = $item['timeZone'];
|
||||
}
|
||||
|
||||
if (!empty($item['type'])) {
|
||||
$type = $item['type'];
|
||||
|
||||
@@ -1656,9 +1823,9 @@ class Base
|
||||
|
||||
public function addOrWhere($whereClause, &$result)
|
||||
{
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'OR' => $whereClause
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
public function getFullTextSearchDataForTextFilter($textFilter, $isAuxiliaryUse = false)
|
||||
@@ -2075,13 +2242,13 @@ class Base
|
||||
}
|
||||
}
|
||||
|
||||
$attibute = null;
|
||||
if (!empty($item['attribute'])) $attibute = $item['attribute'];
|
||||
if (!$attibute) return;
|
||||
$attribute = null;
|
||||
if (!empty($item['attribute'])) $attribute = $item['attribute'];
|
||||
if (!$attribute) return;
|
||||
|
||||
$attributeType = $this->getSeed()->getAttributeType($attibute);
|
||||
$attributeType = $this->getSeed()->getAttributeType($attribute);
|
||||
if ($attributeType === 'foreign') {
|
||||
$relation = $this->getSeed()->getAttributeParam($attibute, 'relation');
|
||||
$relation = $this->getSeed()->getAttributeParam($attribute, 'relation');
|
||||
if ($relation) {
|
||||
$this->addLeftJoin($relation, $result);
|
||||
}
|
||||
|
||||
@@ -35,15 +35,15 @@ class Person extends \Espo\Services\Record
|
||||
{
|
||||
protected function getDuplicateWhereClause(Entity $entity, $data)
|
||||
{
|
||||
$data = array(
|
||||
$whereClause = [
|
||||
'OR' => []
|
||||
);
|
||||
];
|
||||
$toCheck = false;
|
||||
if ($entity->get('firstName') || $entity->get('lastName')) {
|
||||
$part = [];
|
||||
$part['firstName'] = $entity->get('firstName');
|
||||
$part['lastName'] = $entity->get('lastName');
|
||||
$data['OR'][] = $part;
|
||||
$whereClause['OR'][] = $part;
|
||||
$toCheck = true;
|
||||
}
|
||||
if (
|
||||
@@ -62,9 +62,9 @@ class Person extends \Espo\Services\Record
|
||||
}
|
||||
}
|
||||
foreach ($list as $emailAddress) {
|
||||
$data['OR'][] = array(
|
||||
$whereClause['OR'][] = [
|
||||
'emailAddress' => $emailAddress
|
||||
);
|
||||
];
|
||||
$toCheck = true;
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class Person extends \Espo\Services\Record
|
||||
return false;
|
||||
}
|
||||
|
||||
return $data;
|
||||
return $whereClause;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,42 +35,19 @@ class ClientManager
|
||||
|
||||
private $config;
|
||||
|
||||
private $metadata;
|
||||
|
||||
protected $mainHtmlFilePath = 'html/main.html';
|
||||
|
||||
protected $runScript = "app.start();";
|
||||
|
||||
protected $basePath = '';
|
||||
|
||||
protected $jsFileList = [
|
||||
'client/espo.min.js'
|
||||
];
|
||||
|
||||
protected $developerModeJsFileList = [
|
||||
'client/lib/jquery-2.1.4.min.js',
|
||||
'client/lib/underscore-min.js',
|
||||
'client/lib/es6-promise.min.js',
|
||||
'client/lib/backbone-min.js',
|
||||
'client/lib/handlebars.js',
|
||||
'client/lib/base64.js',
|
||||
'client/lib/jquery-ui.min.js',
|
||||
'client/lib/jquery.ui.touch-punch.min.js',
|
||||
'client/lib/moment.min.js',
|
||||
'client/lib/moment-timezone-with-data.min.js',
|
||||
'client/lib/jquery.timepicker.min.js',
|
||||
'client/lib/jquery.autocomplete.js',
|
||||
'client/lib/bootstrap.min.js',
|
||||
'client/lib/bootstrap-datepicker.js',
|
||||
'client/lib/bull.js',
|
||||
'client/lib/marked.min.js',
|
||||
'client/src/loader.js',
|
||||
'client/src/utils.js',
|
||||
'client/src/exceptions.js',
|
||||
];
|
||||
|
||||
public function __construct(Config $config, ThemeManager $themeManager)
|
||||
public function __construct(Config $config, ThemeManager $themeManager, Metadata $metadata)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->themeManager = $themeManager;
|
||||
$this->metadata = $metadata;
|
||||
}
|
||||
|
||||
protected function getThemeManager()
|
||||
@@ -83,6 +60,11 @@ class ClientManager
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function getMetadata()
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
public function setBasePath($basePath)
|
||||
{
|
||||
$this->basePath = $basePath;
|
||||
@@ -116,11 +98,11 @@ class ClientManager
|
||||
|
||||
if ($isDeveloperMode) {
|
||||
$useCache = $this->getConfig()->get('useCacheInDeveloperMode');
|
||||
$jsFileList = $this->developerModeJsFileList;
|
||||
$jsFileList = $this->getMetadata()->get(['app', 'client', 'developerModeScriptList']);
|
||||
$loaderCacheTimestamp = 'null';
|
||||
} else {
|
||||
$useCache = $this->getConfig()->get('useCache');
|
||||
$jsFileList = $this->jsFileList;
|
||||
$jsFileList = $this->getMetadata()->get(['app', 'client', 'scriptList']);
|
||||
$loaderCacheTimestamp = $cacheTimestamp;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,10 @@ class DateTime
|
||||
|
||||
protected $timezone;
|
||||
|
||||
public static $systemDateTimeFormat = 'Y-m-d H:i:s';
|
||||
|
||||
public static $systemDateFormat = 'Y-m-d';
|
||||
|
||||
protected $internalDateTimeFormat = 'Y-m-d H:i:s';
|
||||
|
||||
protected $internalDateFormat = 'Y-m-d';
|
||||
|
||||
@@ -79,6 +79,11 @@ class FieldManager
|
||||
return $this->container->get('defaultLanguage');
|
||||
}
|
||||
|
||||
protected function getFieldManagerUtil()
|
||||
{
|
||||
return $this->container->get('fieldManagerUtil');
|
||||
}
|
||||
|
||||
public function read($scope, $name)
|
||||
{
|
||||
$fieldDefs = $this->getFieldDefs($scope, $name);
|
||||
@@ -267,13 +272,16 @@ class FieldManager
|
||||
$entityDefs = $this->normalizeDefs($scope, $name, $fieldDefs);
|
||||
|
||||
if (!empty($entityDefs)) {
|
||||
$this->saveCustomdDefs($scope, $entityDefs);
|
||||
$result &= $this->saveCustomEntityDefs($scope, $entityDefs);
|
||||
$this->isChanged = true;
|
||||
}
|
||||
|
||||
if ($metadataToBeSaved) {
|
||||
$result &= $this->getMetadata()->save();
|
||||
$this->isChanged = true;
|
||||
}
|
||||
|
||||
if ($this->isChanged) {
|
||||
$this->processHook('afterSave', $type, $scope, $name, $fieldDefs, array('isNew' => $isNew));
|
||||
}
|
||||
|
||||
@@ -431,7 +439,7 @@ class FieldManager
|
||||
}
|
||||
}
|
||||
|
||||
protected function saveCustomdDefs($scope, $newDefs)
|
||||
protected function saveCustomEntityDefs($scope, $newDefs)
|
||||
{
|
||||
$customDefs = $this->getMetadata()->getCustom('entityDefs', $scope, (object) []);
|
||||
|
||||
@@ -492,6 +500,9 @@ class FieldManager
|
||||
'inlineEditDisabled' => [
|
||||
'type' => 'bool',
|
||||
'default' => false
|
||||
],
|
||||
'defaultAttributes' => [
|
||||
'type' => 'jsonObject'
|
||||
]
|
||||
];
|
||||
|
||||
@@ -510,7 +521,7 @@ class FieldManager
|
||||
|
||||
$params = [];
|
||||
foreach ($fieldDefsByType['params'] as $paramData) {
|
||||
$params[ $paramData['name'] ] = $paramData;
|
||||
$params[$paramData['name']] = $paramData;
|
||||
}
|
||||
foreach ($additionalParamList as $paramName => $paramValue) {
|
||||
if (!isset($params[$paramName])) {
|
||||
|
||||
@@ -146,4 +146,43 @@ class FieldManagerUtil
|
||||
|
||||
return $this->fieldByTypeListCache[$scope][$type];
|
||||
}
|
||||
|
||||
private function getFieldTypeAttributeListByType($fieldType, $name, $type)
|
||||
{
|
||||
$defs = $this->getMetadata()->get(['fields', $fieldType]);
|
||||
if (!$defs) return [];
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
if (isset($defs[$type . 'Fields'])) {
|
||||
$list = $defs[$type . 'Fields'];
|
||||
$naming = 'suffix';
|
||||
if (isset($defs['naming'])) {
|
||||
$naming = $defs['naming'];
|
||||
}
|
||||
if ($naming == 'prefix') {
|
||||
foreach ($list as $f) {
|
||||
$attributeList[] = $f . ucfirst($name);
|
||||
}
|
||||
} else {
|
||||
foreach ($list as $f) {
|
||||
$attributeList[] = $name . ucfirst($f);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($type == 'actual') {
|
||||
$attributeList[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
public function getFieldTypeAttributeList($fieldType, $name)
|
||||
{
|
||||
return array_merge(
|
||||
$this->getFieldTypeAttributeListByType($fieldType, $name, 'actual'),
|
||||
$this->getFieldTypeAttributeListByType($fieldType, $name, 'notActual')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ class Metadata
|
||||
protected $frontendHiddenPathList = [
|
||||
['app', 'formula', 'functionClassNameMap'],
|
||||
['app', 'fileStorage', 'implementationClassNameMap'],
|
||||
['app', 'emailNotifications', 'handlerClassNameMap']
|
||||
['app', 'emailNotifications', 'handlerClassNameMap'],
|
||||
['app', 'client'],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -208,30 +208,33 @@ class ScheduledJob
|
||||
*/
|
||||
public function isCronConfigured()
|
||||
{
|
||||
$startDate = new \DateTime('-' . $this->checkingCronPeriod, new \DateTimeZone("UTC"));
|
||||
$endDate = new \DateTime('+' . $this->checkingCronPeriod, new \DateTimeZone("UTC"));
|
||||
$r1From = new \DateTime('-' . $this->checkingCronPeriod);
|
||||
$r1To = new \DateTime('+' . $this->checkingCronPeriod);
|
||||
|
||||
$query = "
|
||||
SELECT job.id FROM scheduled_job
|
||||
LEFT JOIN job ON job.scheduled_job_id = scheduled_job.id AND job.deleted = 0
|
||||
WHERE
|
||||
scheduled_job.job = 'Dummy'
|
||||
AND scheduled_job.deleted = 0
|
||||
AND job.execute_time BETWEEN '". $startDate->format('Y-m-d H:i:s') ."' AND '". $endDate->format('Y-m-d H:i:s') ."'
|
||||
AND job.status IN ('Success', 'Failed', 'Pending')
|
||||
ORDER BY job.execute_time DESC
|
||||
";
|
||||
$r2From = new \DateTime('- 1 hour');
|
||||
$r2To = new \DateTime();
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
$format = \Espo\Core\Utils\DateTime::$systemDateTimeFormat;
|
||||
|
||||
$row = $sth->fetch(\PDO::FETCH_ASSOC);
|
||||
$selectParams = [
|
||||
'select' => ['id'],
|
||||
'leftJoins' => ['scheduledJob'],
|
||||
'whereClause' => [
|
||||
'OR' => [
|
||||
[
|
||||
['executedAt>=' => $r2From->format($format)] ,
|
||||
['executedAt<=' => $r2To->format($format)],
|
||||
],
|
||||
[
|
||||
['executeTime>=' => $r1From->format($format)],
|
||||
['executeTime<='=> $r1To->format($format)],
|
||||
'scheduledJob.job' => 'Dummy'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
if (!empty($row['id'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return !!$this->getEntityManager()->getRepository('Job')->findOne($selectParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,23 +38,31 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
{
|
||||
public static $authRequired = true;
|
||||
|
||||
protected $allowedFileTypes = array(
|
||||
protected $allowedFileTypes = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
);
|
||||
'image/webp',
|
||||
];
|
||||
|
||||
protected $imageSizes = array(
|
||||
'xxx-small' => array(18, 18),
|
||||
'xx-small' => array(32, 32),
|
||||
'x-small' => array(64, 64),
|
||||
'small' => array(128, 128),
|
||||
'medium' => array(256, 256),
|
||||
'large' => array(512, 512),
|
||||
'x-large' => array(864, 864),
|
||||
'xx-large' => array(1024, 1024),
|
||||
);
|
||||
protected $imageSizes = [
|
||||
'xxx-small' => [18, 18],
|
||||
'xx-small' => [32, 32],
|
||||
'x-small' => [64, 64],
|
||||
'small' => [128, 128],
|
||||
'medium' => [256, 256],
|
||||
'large' => [512, 512],
|
||||
'x-large' => [864, 864],
|
||||
'xx-large' => [1024, 1024],
|
||||
];
|
||||
|
||||
protected $fixOrientationFileTypeList = [
|
||||
'image/jpeg',
|
||||
];
|
||||
|
||||
protected $allowedRelatedTypeList = null;
|
||||
|
||||
protected $allowedFieldList = null;
|
||||
|
||||
public function run()
|
||||
{
|
||||
@@ -68,7 +76,7 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
$size = $_GET['size'];
|
||||
}
|
||||
|
||||
$this->show($id, $size);
|
||||
$this->show($id, $size, false);
|
||||
}
|
||||
|
||||
protected function show($id, $size, $disableAccessCheck = false)
|
||||
@@ -97,6 +105,18 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if ($this->allowedRelatedTypeList) {
|
||||
if (!in_array($attachment->get('relatedType'), $this->allowedRelatedTypeList)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->allowedFieldList) {
|
||||
if (!in_array($attachment->get('field'), $this->allowedFieldList)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($size)) {
|
||||
if (!empty($this->imageSizes[$size])) {
|
||||
$thumbFilePath = "data/upload/thumbs/{$sourceId}_{$size}";
|
||||
@@ -115,6 +135,9 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
case 'image/gif':
|
||||
imagegif($targetImage);
|
||||
break;
|
||||
case 'image/webp':
|
||||
imagewebp($targetImage);
|
||||
break;
|
||||
}
|
||||
$contents = ob_get_contents();
|
||||
ob_end_clean();
|
||||
@@ -181,7 +204,7 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
switch ($fileType) {
|
||||
case 'image/jpeg':
|
||||
$sourceImage = imagecreatefromjpeg($filePath);
|
||||
imagecopyresampled ($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight);
|
||||
imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight);
|
||||
break;
|
||||
case 'image/png':
|
||||
$sourceImage = imagecreatefrompng($filePath);
|
||||
@@ -195,10 +218,34 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
$sourceImage = imagecreatefromgif($filePath);
|
||||
imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight);
|
||||
break;
|
||||
case 'image/webp':
|
||||
$sourceImage = imagecreatefromwebp($filePath);
|
||||
imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight);
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_array($fileType, $this->fixOrientationFileTypeList)) {
|
||||
$targetImage = $this->fixOrientation($targetImage, $filePath);
|
||||
}
|
||||
|
||||
return $targetImage;
|
||||
}
|
||||
|
||||
protected function getOrientation($filePath)
|
||||
{
|
||||
$orientation = 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);
|
||||
$orientation = @exif_read_data($filePath)['Orientation'];
|
||||
}
|
||||
return $orientation;
|
||||
}
|
||||
|
||||
protected function fixOrientation($targetImage, $filePath)
|
||||
{
|
||||
$orientation = $this->getOrientation($filePath);
|
||||
if ($orientation) {
|
||||
$angle = array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[$orientation];
|
||||
$targetImage = imagerotate($targetImage, $angle, 0);
|
||||
}
|
||||
|
||||
return $targetImage;
|
||||
|
||||
@@ -38,6 +38,10 @@ class LogoImage extends Image
|
||||
{
|
||||
public static $authRequired = false;
|
||||
|
||||
protected $allowedRelatedTypeList = ['Settings', 'Portal'];
|
||||
|
||||
protected $allowedFieldList = ['companyLogo'];
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->imageSizes['small-logo'] = array(181, 44);
|
||||
|
||||
@@ -75,13 +75,13 @@ class Invitations
|
||||
public function sendInvitation(Entity $entity, Entity $invitee, $link)
|
||||
{
|
||||
$uid = $this->getEntityManager()->getEntity('UniqueId');
|
||||
$uid->set('data', array(
|
||||
$uid->set('data', [
|
||||
'eventType' => $entity->getEntityType(),
|
||||
'eventId' => $entity->id,
|
||||
'inviteeId' => $invitee->id,
|
||||
'inviteeType' => $invitee->getEntityType(),
|
||||
'link' => $link
|
||||
));
|
||||
]);
|
||||
|
||||
if ($entity->get('dateEnd')) {
|
||||
$terminateAt = $entity->get('dateEnd');
|
||||
@@ -110,9 +110,9 @@ class Invitations
|
||||
$subjectTpl = $this->templateFileManager->getTemplate('invitation', 'subject', $entity->getEntityType(), 'Crm');
|
||||
$bodyTpl = $this->templateFileManager->getTemplate('invitation', 'body', $entity->getEntityType(), 'Crm');
|
||||
|
||||
$subjectTpl = str_replace(array("\n", "\r"), '', $subjectTpl);
|
||||
$subjectTpl = str_replace(["\n", "\r"], '', $subjectTpl);
|
||||
|
||||
$data = array();
|
||||
$data = [];
|
||||
|
||||
$siteUrl = rtrim($this->getConfig()->get('siteUrl'), '/');
|
||||
$recordUrl = $siteUrl . '/#' . $entity->getEntityType() . '/view/' . $entity->id;
|
||||
@@ -150,24 +150,23 @@ class Invitations
|
||||
$email->set('subject', $subject);
|
||||
$email->set('body', $body);
|
||||
$email->set('isHtml', true);
|
||||
$this->getEntityManager()->saveEntity($email);
|
||||
|
||||
$attachmentName = ucwords($this->language->translate($entity->getEntityType(), 'scopeNames')).'.ics';
|
||||
$attachment = $this->getEntityManager()->getEntity('Attachment');
|
||||
$attachment->set(array(
|
||||
$attachment->set([
|
||||
'name' => $attachmentName,
|
||||
'type' => 'text/calendar',
|
||||
'contents' => $this->getIscContents($entity),
|
||||
));
|
||||
]);
|
||||
|
||||
$email->addAttachment($attachment);
|
||||
$message = new \Zend\Mail\Message();
|
||||
|
||||
$emailSender = $this->mailSender;
|
||||
|
||||
if ($this->smtpParams) {
|
||||
$emailSender->useSmtp($this->smtpParams);
|
||||
}
|
||||
$emailSender->send($email);
|
||||
$emailSender->send($email, [], $message, [$attachment]);
|
||||
|
||||
$this->getEntityManager()->removeEntity($email);
|
||||
}
|
||||
@@ -183,7 +182,7 @@ class Invitations
|
||||
$email = $user->get('emailAddress');
|
||||
}
|
||||
|
||||
$ics = new Ics('//EspoCRM//EspoCRM Calendar//EN', array(
|
||||
$ics = new Ics('//EspoCRM//EspoCRM Calendar//EN', [
|
||||
'startDate' => strtotime($entity->get('dateStart')),
|
||||
'endDate' => strtotime($entity->get('dateEnd')),
|
||||
'uid' => $entity->id,
|
||||
@@ -191,10 +190,8 @@ class Invitations
|
||||
'who' => $who,
|
||||
'email' => $email,
|
||||
'description' => $entity->get('description'),
|
||||
));
|
||||
]);
|
||||
|
||||
return $ics->get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"revenueConverted": "Revenue (converted)",
|
||||
"budget": "Budget",
|
||||
"budgetConverted": "Budget (converted)",
|
||||
"budgetCurrency": "Budget Currency",
|
||||
"contactsTemplate": "Contacts Template",
|
||||
"leadsTemplate": "Leads Template",
|
||||
"accountsTemplate": "Accounts Template",
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
"createdAt",
|
||||
"createdBy",
|
||||
"modifiedAt",
|
||||
"billingAddress",
|
||||
"type",
|
||||
"industry",
|
||||
"description",
|
||||
"emailAddress",
|
||||
"industry",
|
||||
"phoneNumber",
|
||||
"targetLists",
|
||||
"type",
|
||||
"billingAddressCountry",
|
||||
"billingAddress",
|
||||
"shippingAddress",
|
||||
"website"
|
||||
]
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{"name":"name", "width":25, "link":"true"},
|
||||
{"name":"status"},
|
||||
{"name":"emailAddress", "notSortable": true},
|
||||
{"name":"assignedUser"},
|
||||
{"name":"createdAt"}
|
||||
{"name":"name", "link": "true"},
|
||||
{"name":"status", "width": 15},
|
||||
{"name":"emailAddress", "notSortable": true, "width": 20},
|
||||
{"name":"assignedUser", "width": 18},
|
||||
{"name":"createdAt", "width": 13}
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[
|
||||
{"name":"name", "width":32, "link":true},
|
||||
{"name":"status", "width":17},
|
||||
{"name":"emailAddress", "notSortable": true},
|
||||
{"name":"name", "link": true},
|
||||
{"name":"status", "width": 17},
|
||||
{"name":"emailAddress", "notSortable": true, "width": 27},
|
||||
{"name":"createdAt", "width": 18}
|
||||
]
|
||||
|
||||
@@ -52,9 +52,9 @@ class Account extends \Espo\Services\Record
|
||||
if (!$entity->get('name')) {
|
||||
return false;
|
||||
}
|
||||
return array(
|
||||
return [
|
||||
'name' => $entity->get('name')
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function afterMerge(Entity $entity, array $sourceList, $attributes)
|
||||
|
||||
@@ -64,25 +64,29 @@ class Opportunity extends \Espo\Services\Record
|
||||
$stageField = 'lastStage';
|
||||
}
|
||||
|
||||
$selectParams = [
|
||||
'select' => [$stageField, ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => [
|
||||
[$stageField . '!=' => $lostStageList],
|
||||
[$stageField . '!=' => null]
|
||||
],
|
||||
'orderBy' => 'LIST:'.$stageField.':' . implode(',', $options),
|
||||
'groupBy' => [$stageField]
|
||||
$whereClause = [
|
||||
[$stageField . '!=' => $lostStageList],
|
||||
[$stageField . '!=' => null]
|
||||
];
|
||||
|
||||
if ($dateFilter !== 'ever') {
|
||||
$selectParams['whereClause'][] = [
|
||||
$whereClause[] = [
|
||||
'closeDate>=' => $dateFrom,
|
||||
'closeDate<' => $dateTo
|
||||
];
|
||||
}
|
||||
|
||||
$selectParams = [
|
||||
'select' => [$stageField, ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => $whereClause,
|
||||
'orderBy' => 'LIST:'.$stageField.':' . implode(',', $options),
|
||||
'groupBy' => [$stageField]
|
||||
];
|
||||
|
||||
$selectManager->applyAccess($selectParams);
|
||||
|
||||
$this->handleDistinctReportSelectParams($selectParams, $whereClause);
|
||||
|
||||
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
|
||||
|
||||
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
|
||||
@@ -136,26 +140,30 @@ class Opportunity extends \Espo\Services\Record
|
||||
|
||||
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
|
||||
|
||||
$selectParams = [
|
||||
'select' => ['leadSource', ['SUM:amountWeightedConverted', 'amount']],
|
||||
'whereClause' => [
|
||||
'stage!=' => $this->getLostStageList(),
|
||||
['leadSource!=' => ''],
|
||||
['leadSource!=' => null]
|
||||
],
|
||||
'orderBy' => 'LIST:leadSource:' . implode(',', $options),
|
||||
'groupBy' => ['leadSource']
|
||||
$whereClause = [
|
||||
['stage!=' => $this->getLostStageList()],
|
||||
['leadSource!=' => ''],
|
||||
['leadSource!=' => null]
|
||||
];
|
||||
|
||||
if ($dateFilter !== 'ever') {
|
||||
$selectParams['whereClause'][] = [
|
||||
$whereClause[] = [
|
||||
'closeDate>=' => $dateFrom,
|
||||
'closeDate<' => $dateTo
|
||||
];
|
||||
}
|
||||
|
||||
$selectParams = [
|
||||
'select' => ['leadSource', ['SUM:amountWeightedConverted', 'amount']],
|
||||
'whereClause' => $whereClause,
|
||||
'orderBy' => 'LIST:leadSource:' . implode(',', $options),
|
||||
'groupBy' => ['leadSource']
|
||||
];
|
||||
|
||||
$selectManager->applyAccess($selectParams);
|
||||
|
||||
$this->handleDistinctReportSelectParams($selectParams, $whereClause);
|
||||
|
||||
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
|
||||
|
||||
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
|
||||
@@ -189,31 +197,31 @@ class Opportunity extends \Espo\Services\Record
|
||||
|
||||
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
|
||||
|
||||
$selectParams = [
|
||||
'select' => ['stage', ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => [
|
||||
[
|
||||
'stage!=' => $this->getLostStageList()
|
||||
],
|
||||
[
|
||||
'stage!=' => $this->getWonStageList()
|
||||
]
|
||||
],
|
||||
'orderBy' => 'LIST:stage:' . implode(',', $options),
|
||||
'groupBy' => ['stage']
|
||||
$whereClause = [
|
||||
['stage!=' => $this->getLostStageList()],
|
||||
['stage!=' => $this->getWonStageList()]
|
||||
];
|
||||
|
||||
if ($dateFilter !== 'ever') {
|
||||
$selectParams['whereClause'][] = [
|
||||
$whereClause[] = [
|
||||
'closeDate>=' => $dateFrom,
|
||||
'closeDate<' => $dateTo
|
||||
];
|
||||
}
|
||||
|
||||
$selectParams = [
|
||||
'select' => ['stage', ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => $whereClause,
|
||||
'orderBy' => 'LIST:stage:' . implode(',', $options),
|
||||
'groupBy' => ['stage']
|
||||
];
|
||||
|
||||
$stageIgnoreList = array_merge($this->getLostStageList(), $this->getWonStageList());
|
||||
|
||||
$selectManager->applyAccess($selectParams);
|
||||
|
||||
$this->handleDistinctReportSelectParams($selectParams, $whereClause);
|
||||
|
||||
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
|
||||
|
||||
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
|
||||
@@ -252,29 +260,32 @@ class Opportunity extends \Espo\Services\Record
|
||||
|
||||
$selectManager = $this->getSelectManagerFactory()->create('Opportunity');
|
||||
|
||||
$selectParams = [
|
||||
'select' => [['MONTH:closeDate', 'month'], ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => [
|
||||
'stage' => $this->getWonStageList()
|
||||
],
|
||||
'orderBy' => 1,
|
||||
'groupBy' => ['MONTH:closeDate']
|
||||
$whereClause = [
|
||||
['stage' => $this->getWonStageList()]
|
||||
];
|
||||
|
||||
if ($dateFilter !== 'ever') {
|
||||
$selectParams['whereClause'][] = [
|
||||
$whereClause[] = [
|
||||
'closeDate>=' => $dateFrom,
|
||||
'closeDate<' => $dateTo
|
||||
];
|
||||
}
|
||||
|
||||
$selectParams = [
|
||||
'select' => [['MONTH:closeDate', 'month'], ['SUM:amountConverted', 'amount']],
|
||||
'whereClause' => $whereClause,
|
||||
'orderBy' => 1,
|
||||
'groupBy' => ['MONTH:closeDate']
|
||||
];
|
||||
|
||||
$selectManager->applyAccess($selectParams);
|
||||
|
||||
$this->handleDistinctReportSelectParams($selectParams, $whereClause);
|
||||
|
||||
$this->getEntityManager()->getRepository('Opportunity')->handleSelectParams($selectParams);
|
||||
|
||||
$sql = $this->getEntityManager()->getQuery()->createSelectQuery('Opportunity', $selectParams);
|
||||
|
||||
|
||||
$sth = $pdo->prepare($sql);
|
||||
$sth->execute();
|
||||
|
||||
@@ -311,7 +322,7 @@ class Opportunity extends \Espo\Services\Record
|
||||
|
||||
$today = new \DateTime();
|
||||
|
||||
$endPosition = count($keyList) - 1;
|
||||
$endPosition = count($keyList);
|
||||
for ($i = count($keyList) - 1; $i >= 0; $i--) {
|
||||
$key = $keyList[$i];
|
||||
$dt = new \DateTime($key . '-01');
|
||||
@@ -334,6 +345,30 @@ class Opportunity extends \Espo\Services\Record
|
||||
];
|
||||
}
|
||||
|
||||
protected function handleDistinctReportSelectParams(&$selectParams, $whereClause)
|
||||
{
|
||||
if (!empty($selectParams['distinct'])) {
|
||||
$selectParamsSubQuery = $selectParams;
|
||||
|
||||
unset($selectParams['distinct']);
|
||||
$selectParams['leftJoins'] = [];
|
||||
$selectParams['joins'] = [];
|
||||
$selectParams['whereClause'] = $whereClause;
|
||||
|
||||
$selectParamsSubQuery['select'] = ['id'];
|
||||
unset($selectParamsSubQuery['groupBy']);
|
||||
unset($selectParamsSubQuery['orderBy']);
|
||||
unset($selectParamsSubQuery['order']);
|
||||
|
||||
$selectParams['whereClause'][] = [
|
||||
'id=s' => [
|
||||
'entityType' => 'Opportunity',
|
||||
'selectParams' => $selectParamsSubQuery
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDateRangeByFilter($dateFilter)
|
||||
{
|
||||
switch ($dateFilter) {
|
||||
|
||||
@@ -36,6 +36,10 @@ use \Espo\ORM\Entity;
|
||||
|
||||
class Task extends \Espo\Services\Record
|
||||
{
|
||||
protected $selectAttributesDependancyMap = [
|
||||
'dateEnd' => ['status']
|
||||
];
|
||||
|
||||
public function loadAdditionalFields(Entity $entity)
|
||||
{
|
||||
parent::loadAdditionalFields($entity);
|
||||
|
||||
@@ -453,7 +453,7 @@ abstract class Mapper implements IMapper
|
||||
|
||||
$sql = "INSERT INTO `".$relTable."` (".$fieldsPart.") (".$subSql.") ON DUPLICATE KEY UPDATE deleted = '0'";
|
||||
|
||||
if ($this->pdo->query($sql)) {
|
||||
if ($this->runQuery($sql, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -461,6 +461,21 @@ abstract class Mapper implements IMapper
|
||||
}
|
||||
}
|
||||
|
||||
public function runQuery($query, $rerunIfDeadlock = false)
|
||||
{
|
||||
try {
|
||||
return $this->pdo->query($query);
|
||||
} catch (\Exception $e) {
|
||||
if ($rerunIfDeadlock) {
|
||||
if ($e->errorInfo[0] == 40001 && $e->errorInfo[1] == 1213) {
|
||||
return $this->pdo->query($query);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addRelation(IEntity $entity, $relationName, $id = null, $relEntity = null, $data = null)
|
||||
{
|
||||
if (!is_null($relEntity)) {
|
||||
@@ -574,7 +589,7 @@ abstract class Mapper implements IMapper
|
||||
$sql .= ', ' . implode(', ', $setArr);
|
||||
}
|
||||
|
||||
if ($this->pdo->query($sql)) {
|
||||
if ($this->runQuery($sql, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -296,7 +296,7 @@ abstract class Base
|
||||
throw new \Exception("Not allowed function '".$function."'.");
|
||||
}
|
||||
|
||||
if (strpos($function, 'YEAR_') === 0) {
|
||||
if (strpos($function, 'YEAR_') === 0 && $function !== 'YEAR_NUMBER') {
|
||||
$fiscalShift = substr($function, 5);
|
||||
if (is_numeric($fiscalShift)) {
|
||||
$fiscalShift = intval($fiscalShift);
|
||||
@@ -309,7 +309,7 @@ abstract class Base
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($function, 'QUARTER_') === 0) {
|
||||
if (strpos($function, 'QUARTER_') === 0 && $function !== 'QUARTER_NUMBER') {
|
||||
$fiscalShift = substr($function, 8);
|
||||
if (is_numeric($fiscalShift)) {
|
||||
$fiscalShift = intval($fiscalShift);
|
||||
@@ -367,7 +367,6 @@ abstract class Base
|
||||
if ($distinct) {
|
||||
$idPart = $this->toDb($entityType) . ".id";
|
||||
switch ($function) {
|
||||
case 'SUM':
|
||||
case 'COUNT':
|
||||
return $function . "({$part}) * COUNT(DISTINCT {$idPart}) / COUNT({$idPart})";
|
||||
}
|
||||
@@ -1128,7 +1127,7 @@ abstract class Base
|
||||
return preg_replace('/[^A-Za-z0-9_:.]+/', '', $string);
|
||||
}
|
||||
|
||||
protected function getJoins(IEntity $entity, array $joins, $left = false, $joinConditions = array())
|
||||
protected function getJoins(IEntity $entity, array $joins, $isLeft = false, $joinConditions = array())
|
||||
{
|
||||
$joinSqlList = [];
|
||||
foreach ($joins as $item) {
|
||||
@@ -1154,7 +1153,7 @@ abstract class Base
|
||||
foreach ($itemConditions as $left => $right) {
|
||||
$conditions[$left] = $right;
|
||||
}
|
||||
if ($sql = $this->getJoin($entity, $relationName, $left, $conditions, $alias)) {
|
||||
if ($sql = $this->getJoin($entity, $relationName, $isLeft, $conditions, $alias)) {
|
||||
$joinSqlList[] = $sql;
|
||||
}
|
||||
}
|
||||
@@ -1235,9 +1234,9 @@ abstract class Base
|
||||
}
|
||||
}
|
||||
|
||||
protected function getJoin(IEntity $entity, $name, $left = false, $conditions = array(), $alias = null)
|
||||
protected function getJoin(IEntity $entity, $name, $isLeft = false, $conditions = [], $alias = null)
|
||||
{
|
||||
$prefix = ($left) ? 'LEFT ' : '';
|
||||
$prefix = ($isLeft) ? 'LEFT ' : '';
|
||||
|
||||
if (!$entity->hasRelation($name)) {
|
||||
if (!$alias) {
|
||||
|
||||
@@ -249,6 +249,21 @@ class EntityManager
|
||||
return $this->entityFactory;
|
||||
}
|
||||
|
||||
public function runQuery($query, $rerunIfDeadlock = false)
|
||||
{
|
||||
try {
|
||||
return $this->getPDO()->query($query);
|
||||
} catch (\Exception $e) {
|
||||
if ($rerunIfDeadlock) {
|
||||
if ($e->errorInfo[0] == 40001 && $e->errorInfo[1] == 1213) {
|
||||
return $this->getPDO()->query($query);
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -35,6 +35,24 @@ use Espo\Core\Utils\Util;
|
||||
|
||||
class Attachment extends \Espo\Core\ORM\Repositories\RDB
|
||||
{
|
||||
protected $imageTypeList = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
];
|
||||
|
||||
protected $imageThumbList = [
|
||||
'xxx-small',
|
||||
'xx-small',
|
||||
'x-small',
|
||||
'small',
|
||||
'medium',
|
||||
'large',
|
||||
'x-large',
|
||||
'xx-large',
|
||||
];
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
@@ -42,6 +60,11 @@ class Attachment extends \Espo\Core\ORM\Repositories\RDB
|
||||
$this->addDependency('config');
|
||||
}
|
||||
|
||||
protected function getFileManager()
|
||||
{
|
||||
return $this->getInjection('container')->get('fileManager');
|
||||
}
|
||||
|
||||
protected function getFileStorageManager()
|
||||
{
|
||||
return $this->getInjection('container')->get('fileStorageManager');
|
||||
@@ -106,6 +129,20 @@ class Attachment extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
if ($duplicateCount === 0) {
|
||||
$this->getFileStorageManager()->unlink($entity);
|
||||
|
||||
if (in_array($entity->get('type'), $this->imageTypeList)) {
|
||||
$this->removeImageThumbs($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function removeImageThumbs($entity)
|
||||
{
|
||||
foreach ($this->imageThumbList as $suffix) {
|
||||
$filePath = "data/upload/thumbs/".$entity->getSourceId()."_{$suffix}";
|
||||
if ($this->getFileManager()->isFile($filePath)) {
|
||||
$this->getFileManager()->removeFile($filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,22 +47,27 @@ class Email extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
$eaRepository = $this->getEntityManager()->getRepository('EmailAddress');
|
||||
|
||||
$address = $entity->get($type);
|
||||
$addressValue = $entity->get($type);
|
||||
$idList = [];
|
||||
if (
|
||||
(!empty($address) || !filter_var($address, FILTER_VALIDATE_EMAIL))
|
||||
&&
|
||||
$type !== 'replyTo'
|
||||
) {
|
||||
$arr = array_map(function ($e) {
|
||||
return trim($e);
|
||||
}, explode(';', $address));
|
||||
|
||||
$idList = $eaRepository->getIdListFormAddressList($arr);
|
||||
foreach ($idList as $id) {
|
||||
$this->addUserByEmailAddressId($entity, $id, $addAssignedUser);
|
||||
if (!empty($addressValue)) {
|
||||
$addressList = array_map(function ($item) {
|
||||
return trim($item);
|
||||
}, explode(';', $addressValue));
|
||||
|
||||
$addressList = array_filter($addressList, function ($item) {
|
||||
return filter_var($item, FILTER_VALIDATE_EMAIL);
|
||||
});
|
||||
|
||||
$idList = $eaRepository->getIdListFormAddressList($addressList);
|
||||
|
||||
if ($type !== 'replyTo') {
|
||||
foreach ($idList as $id) {
|
||||
$this->addUserByEmailAddressId($entity, $id, $addAssignedUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entity->setLinkMultipleIdList($type . 'EmailAddresses', $idList);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
public function getIds(array $addressList = [])
|
||||
{
|
||||
$ids = array();
|
||||
$ids = [];
|
||||
if (!empty($addressList)) {
|
||||
$lowerAddressList = [];
|
||||
foreach ($addressList as $address) {
|
||||
@@ -72,8 +72,8 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
]
|
||||
])->find();
|
||||
|
||||
$ids = array();
|
||||
$exist = array();
|
||||
$ids = [];
|
||||
$exist = [];
|
||||
foreach ($eaCollection as $ea) {
|
||||
$ids[] = $ea->id;
|
||||
$exist[] = $ea->get('lower');
|
||||
@@ -96,7 +96,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
public function getEmailAddressData(Entity $entity)
|
||||
{
|
||||
$data = array();
|
||||
$data = [];
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$sql = "
|
||||
@@ -105,7 +105,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
JOIN email_address ON email_address.id = entity_email_address.email_address_id AND email_address.deleted = 0
|
||||
WHERE
|
||||
entity_email_address.entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_email_address.entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
entity_email_address.entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
entity_email_address.deleted = 0
|
||||
ORDER BY entity_email_address.primary DESC
|
||||
";
|
||||
@@ -249,40 +249,302 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
}
|
||||
}
|
||||
|
||||
public function storeEntityEmailAddress(Entity $entity)
|
||||
public function storeEntityEmailAddressData(Entity $entity)
|
||||
{
|
||||
$emailAddressValue = $entity->get('emailAddress');
|
||||
if (is_string($emailAddressValue)) {
|
||||
$emailAddressValue = trim($emailAddressValue);
|
||||
}
|
||||
$emailAddressData = null;
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
if ($entity->has('emailAddressData')) {
|
||||
$emailAddressData = $entity->get('emailAddressData');
|
||||
}
|
||||
$emailAddressValue = $entity->get('emailAddress');
|
||||
if (is_string($emailAddressValue)) {
|
||||
$emailAddressValue = trim($emailAddressValue);
|
||||
}
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$emailAddressData = null;
|
||||
if ($entity->has('emailAddressData')) {
|
||||
$emailAddressData = $entity->get('emailAddressData');
|
||||
}
|
||||
|
||||
if ($emailAddressData !== null && is_array($emailAddressData)) {
|
||||
$previousEmailAddressData = array();
|
||||
if (!$entity->isNew()) {
|
||||
$previousEmailAddressData = $this->getEmailAddressData($entity);
|
||||
if (is_null($emailAddressData)) return;
|
||||
if (!is_array($emailAddressData)) return;
|
||||
|
||||
$keyList = [];
|
||||
$keyPreviousList = [];
|
||||
|
||||
$previousEmailAddressData = [];
|
||||
if (!$entity->isNew()) {
|
||||
$previousEmailAddressData = $this->getEmailAddressData($entity);
|
||||
}
|
||||
|
||||
$hash = (object) [];
|
||||
$hashPrevious = (object) [];
|
||||
|
||||
foreach ($emailAddressData as $row) {
|
||||
$key = trim($row->emailAddress);
|
||||
if (empty($key)) continue;
|
||||
$key = strtolower($key);
|
||||
$hash->$key = [
|
||||
'primary' => !empty($row->primary) ? true : false,
|
||||
'optOut' => !empty($row->optOut) ? true : false,
|
||||
'invalid' => !empty($row->invalid) ? true : false,
|
||||
'emailAddress' => trim($row->emailAddress)
|
||||
];
|
||||
$keyList[] = $key;
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->has('emailAddressIsOptedOut')
|
||||
&&
|
||||
(
|
||||
$entity->isNew()
|
||||
||
|
||||
(
|
||||
$entity->hasFetched('emailAddressIsOptedOut')
|
||||
&&
|
||||
$entity->get('emailAddressIsOptedOut') !== $entity->getFetched('emailAddressIsOptedOut')
|
||||
)
|
||||
)
|
||||
) {
|
||||
if ($emailAddressValue) {
|
||||
$key = strtolower($emailAddressValue);
|
||||
if ($key && isset($hash->$key)) {
|
||||
$hash->{$key}['optOut'] = $entity->get('emailAddressIsOptedOut');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hash = array();
|
||||
foreach ($emailAddressData as $row) {
|
||||
$key = trim($row->emailAddress);
|
||||
if (!empty($key)) {
|
||||
$key = strtolower($key);
|
||||
$hash[$key] = [
|
||||
'primary' => !empty($row->primary) ? true : false,
|
||||
'optOut' => !empty($row->optOut) ? true : false,
|
||||
'invalid' => !empty($row->invalid) ? true : false,
|
||||
'emailAddress' => trim($row->emailAddress)
|
||||
];
|
||||
foreach ($previousEmailAddressData as $row) {
|
||||
$key = $row->lower;
|
||||
if (empty($key)) continue;
|
||||
$hashPrevious->$key = [
|
||||
'primary' => $row->primary ? true : false,
|
||||
'optOut' => $row->optOut ? true : false,
|
||||
'invalid' => $row->invalid ? true : false,
|
||||
'emailAddress' => $row->emailAddress
|
||||
];
|
||||
$keyPreviousList[] = $key;
|
||||
}
|
||||
|
||||
$primary = false;
|
||||
|
||||
$toCreateList = [];
|
||||
$toUpdateList = [];
|
||||
$toRemoveList = [];
|
||||
|
||||
$revertData = [];
|
||||
|
||||
foreach ($keyList as $key) {
|
||||
$data = $hash->$key;
|
||||
|
||||
$new = true;
|
||||
$changed = false;
|
||||
|
||||
if ($hash->{$key}['primary']) {
|
||||
$primary = $key;
|
||||
}
|
||||
|
||||
if (property_exists($hashPrevious, $key)) {
|
||||
$new = false;
|
||||
$changed =
|
||||
$hash->{$key}['optOut'] != $hashPrevious->{$key}['optOut'] ||
|
||||
$hash->{$key}['invalid'] != $hashPrevious->{$key}['invalid'] ||
|
||||
$hash->{$key}['emailAddress'] !== $hashPrevious->{$key}['emailAddress'];
|
||||
|
||||
if ($hash->{$key}['primary']) {
|
||||
if ($hash->{$key}['primary'] == $hashPrevious->{$key}['primary']) {
|
||||
$primary = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($new) {
|
||||
$toCreateList[] = $key;
|
||||
}
|
||||
if ($changed) {
|
||||
$toUpdateList[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($keyPreviousList as $key) {
|
||||
if (!property_exists($hash, $key)) {
|
||||
$toRemoveList[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toRemoveList as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if ($emailAddress) {
|
||||
$query = "
|
||||
DELETE FROM entity_email_address
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
email_address_id = ".$pdo->quote($emailAddress->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toUpdateList as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if ($emailAddress) {
|
||||
$skipSave = $this->checkChangeIsForbidden($emailAddress, $entity);
|
||||
if (!$skipSave) {
|
||||
$emailAddress->set([
|
||||
'optOut' => $hash->{$address}['optOut'],
|
||||
'invalid' => $hash->{$address}['invalid'],
|
||||
'name' => $hash->{$address}['emailAddress']
|
||||
]);
|
||||
$this->save($emailAddress);
|
||||
} else {
|
||||
$revertData[$address] = [
|
||||
'optOut' => $emailAddress->get('optOut'),
|
||||
'invalid' => $emailAddress->get('invalid')
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toCreateList as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if (!$emailAddress) {
|
||||
$emailAddress = $this->get();
|
||||
|
||||
$emailAddress->set([
|
||||
'name' => $hash->{$address}['emailAddress'],
|
||||
'optOut' => $hash->{$address}['optOut'],
|
||||
'invalid' => $hash->{$address}['invalid'],
|
||||
]);
|
||||
$this->save($emailAddress);
|
||||
} else {
|
||||
$skipSave = $this->checkChangeIsForbidden($emailAddress, $entity);
|
||||
if (!$skipSave) {
|
||||
if (
|
||||
$emailAddress->get('optOut') != $hash->{$address}['optOut'] ||
|
||||
$emailAddress->get('invalid') != $hash->{$address}['invalid'] ||
|
||||
$emailAddress->get('emailAddress') != $hash->{$address}['emailAddress']
|
||||
) {
|
||||
$emailAddress->set([
|
||||
'optOut' => $hash->{$address}['optOut'],
|
||||
'invalid' => $hash->{$address}['invalid'],
|
||||
'name' => $hash->{$address}['emailAddress']
|
||||
]);
|
||||
$this->save($emailAddress);
|
||||
}
|
||||
} else {
|
||||
$revertData[$address] = [
|
||||
'optOut' => $emailAddress->get('optOut'),
|
||||
'invalid' => $emailAddress->get('invalid')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$query = "
|
||||
INSERT entity_email_address
|
||||
(entity_id, entity_type, email_address_id, `primary`)
|
||||
VALUES
|
||||
(
|
||||
".$pdo->quote($entity->id).",
|
||||
".$pdo->quote($entity->getEntityType()).",
|
||||
".$pdo->quote($emailAddress->id).",
|
||||
".$pdo->quote((int)($address === $primary))."
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($address === $primary))."
|
||||
";
|
||||
|
||||
$this->getEntityManager()->runQuery($query, true);
|
||||
}
|
||||
|
||||
if ($primary) {
|
||||
$emailAddress = $this->getByAddress($primary);
|
||||
if ($emailAddress) {
|
||||
$query = "
|
||||
UPDATE entity_email_address
|
||||
SET `primary` = 0
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
`primary` = 1 AND
|
||||
deleted = 0
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
|
||||
$query = "
|
||||
UPDATE entity_email_address
|
||||
SET `primary` = 1
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
email_address_id = ".$pdo->quote($emailAddress->id)." AND
|
||||
deleted = 0
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($revertData)) {
|
||||
foreach ($emailAddressData as $row) {
|
||||
if (!empty($revertData[$row->emailAddress])) {
|
||||
$row->optOut = $revertData[$row->emailAddress]['optOut'];
|
||||
$row->invalid = $revertData[$row->emailAddress]['invalid'];
|
||||
}
|
||||
}
|
||||
$entity->set('emailAddressData', $emailAddressData);
|
||||
}
|
||||
}
|
||||
|
||||
protected function storeEntityEmailAddressPrimary(Entity $entity)
|
||||
{
|
||||
if (!$entity->has('emailAddress')) return;
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
$emailAddressValue = $entity->get('emailAddress');
|
||||
if (is_string($emailAddressValue)) {
|
||||
$emailAddressValue = trim($emailAddressValue);
|
||||
}
|
||||
|
||||
$entityRepository = $this->getEntityManager()->getRepository($entity->getEntityType());
|
||||
if (!empty($emailAddressValue)) {
|
||||
if ($emailAddressValue != $entity->getFetched('emailAddress')) {
|
||||
|
||||
$emailAddressNew = $this->where(['lower' => strtolower($emailAddressValue)])->findOne();
|
||||
$isNewEmailAddress = false;
|
||||
if (!$emailAddressNew) {
|
||||
$emailAddressNew = $this->get();
|
||||
$emailAddressNew->set('name', $emailAddressValue);
|
||||
if ($entity->has('emailAddressIsOptedOut')) {
|
||||
$emailAddressNew->set('optOut', !!$entity->get('emailAddressIsOptedOut'));
|
||||
}
|
||||
$this->save($emailAddressNew);
|
||||
$isNewEmailAddress = true;
|
||||
}
|
||||
|
||||
$emailAddressValueOld = $entity->getFetched('emailAddress');
|
||||
if (!empty($emailAddressValueOld)) {
|
||||
$emailAddressOld = $this->getByAddress($emailAddressValueOld);
|
||||
if ($emailAddressOld) {
|
||||
$entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld);
|
||||
}
|
||||
}
|
||||
$entityRepository->relate($entity, 'emailAddresses', $emailAddressNew);
|
||||
|
||||
if ($entity->has('emailAddressIsOptedOut')) {
|
||||
$this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut'));
|
||||
}
|
||||
|
||||
$query = "
|
||||
UPDATE entity_email_address
|
||||
SET `primary` = 1
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
email_address_id = ".$pdo->quote($emailAddressNew->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
} else {
|
||||
if (
|
||||
$entity->has('emailAddressIsOptedOut')
|
||||
&&
|
||||
@@ -296,264 +558,33 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
)
|
||||
)
|
||||
) {
|
||||
if ($emailAddressValue) {
|
||||
$key = strtolower($emailAddressValue);
|
||||
if ($key && isset($hash[$key])) {
|
||||
$hash[$key]['optOut'] = $entity->get('emailAddressIsOptedOut');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hashPrev = array();
|
||||
foreach ($previousEmailAddressData as $row) {
|
||||
$key = $row->lower;
|
||||
if (!empty($key)) {
|
||||
$hashPrev[$key] = array(
|
||||
'primary' => $row->primary ? true : false,
|
||||
'optOut' => $row->optOut ? true : false,
|
||||
'invalid' => $row->invalid ? true : false,
|
||||
'emailAddress' => $row->emailAddress
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$primary = false;
|
||||
$toCreate = array();
|
||||
$toUpdate = array();
|
||||
$toRemove = array();
|
||||
|
||||
$revertData = [];
|
||||
|
||||
foreach ($hash as $key => $data) {
|
||||
$new = true;
|
||||
$changed = false;
|
||||
|
||||
if ($hash[$key]['primary']) {
|
||||
$primary = $key;
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $hashPrev)) {
|
||||
$new = false;
|
||||
$changed =
|
||||
$hash[$key]['optOut'] != $hashPrev[$key]['optOut'] ||
|
||||
$hash[$key]['invalid'] != $hashPrev[$key]['invalid'] ||
|
||||
$hash[$key]['emailAddress'] !== $hashPrev[$key]['emailAddress'];
|
||||
|
||||
if ($hash[$key]['primary']) {
|
||||
if ($hash[$key]['primary'] == $hashPrev[$key]['primary']) {
|
||||
$primary = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($new) {
|
||||
$toCreate[] = $key;
|
||||
}
|
||||
if ($changed) {
|
||||
$toUpdate[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($hashPrev as $key => $data) {
|
||||
if (!array_key_exists($key, $hash)) {
|
||||
$toRemove[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toRemove as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if ($emailAddress) {
|
||||
$query = "
|
||||
DELETE FROM entity_email_address
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
email_address_id = ".$pdo->quote($emailAddress->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toUpdate as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if ($emailAddress) {
|
||||
$skipSave = $this->checkChangeIsForbidden($emailAddress, $entity);
|
||||
if (!$skipSave) {
|
||||
$emailAddress->set(array(
|
||||
'optOut' => $hash[$address]['optOut'],
|
||||
'invalid' => $hash[$address]['invalid'],
|
||||
'name' => $hash[$address]['emailAddress']
|
||||
));
|
||||
$this->save($emailAddress);
|
||||
} else {
|
||||
$revertData[$address] = [
|
||||
'optOut' => $emailAddress->get('optOut'),
|
||||
'invalid' => $emailAddress->get('invalid')
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toCreate as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if (!$emailAddress) {
|
||||
$emailAddress = $this->get();
|
||||
|
||||
$emailAddress->set(array(
|
||||
'name' => $hash[$address]['emailAddress'],
|
||||
'optOut' => $hash[$address]['optOut'],
|
||||
'invalid' => $hash[$address]['invalid'],
|
||||
));
|
||||
$this->save($emailAddress);
|
||||
} else {
|
||||
$skipSave = $this->checkChangeIsForbidden($emailAddress, $entity);
|
||||
if (!$skipSave) {
|
||||
if (
|
||||
$emailAddress->get('optOut') != $hash[$address]['optOut'] ||
|
||||
$emailAddress->get('invalid') != $hash[$address]['invalid'] ||
|
||||
$emailAddress->get('emailAddress') != $hash[$address]['emailAddress']
|
||||
) {
|
||||
$emailAddress->set(array(
|
||||
'optOut' => $hash[$address]['optOut'],
|
||||
'invalid' => $hash[$address]['invalid'],
|
||||
'name' => $hash[$address]['emailAddress']
|
||||
));
|
||||
$this->save($emailAddress);
|
||||
}
|
||||
} else {
|
||||
$revertData[$address] = [
|
||||
'optOut' => $emailAddress->get('optOut'),
|
||||
'invalid' => $emailAddress->get('invalid')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$query = "
|
||||
INSERT entity_email_address
|
||||
(entity_id, entity_type, email_address_id, `primary`)
|
||||
VALUES
|
||||
(
|
||||
".$pdo->quote($entity->id).",
|
||||
".$pdo->quote($entity->getEntityName()).",
|
||||
".$pdo->quote($emailAddress->id).",
|
||||
".$pdo->quote((int)($address === $primary))."
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($address === $primary))."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
|
||||
if ($primary) {
|
||||
$emailAddress = $this->getByAddress($primary);
|
||||
if ($emailAddress) {
|
||||
$query = "
|
||||
UPDATE entity_email_address
|
||||
SET `primary` = 0
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
`primary` = 1 AND
|
||||
deleted = 0
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
|
||||
$query = "
|
||||
UPDATE entity_email_address
|
||||
SET `primary` = 1
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
email_address_id = ".$pdo->quote($emailAddress->id)." AND
|
||||
deleted = 0
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($revertData)) {
|
||||
foreach ($emailAddressData as $row) {
|
||||
if (!empty($revertData[$row->emailAddress])) {
|
||||
$row->optOut = $revertData[$row->emailAddress]['optOut'];
|
||||
$row->invalid = $revertData[$row->emailAddress]['invalid'];
|
||||
}
|
||||
}
|
||||
$entity->set('emailAddressData', $emailAddressData);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!$entity->has('emailAddress')) {
|
||||
return;
|
||||
}
|
||||
$entityRepository = $this->getEntityManager()->getRepository($entity->getEntityName());
|
||||
if (!empty($emailAddressValue)) {
|
||||
if ($emailAddressValue != $entity->getFetched('emailAddress')) {
|
||||
|
||||
$emailAddressNew = $this->where(array('lower' => strtolower($emailAddressValue)))->findOne();
|
||||
$isNewEmailAddress = false;
|
||||
if (!$emailAddressNew) {
|
||||
$emailAddressNew = $this->get();
|
||||
$emailAddressNew->set('name', $emailAddressValue);
|
||||
if ($entity->has('emailAddressIsOptedOut')) {
|
||||
$emailAddressNew->set('optOut', !!$entity->get('emailAddressIsOptedOut'));
|
||||
}
|
||||
$this->save($emailAddressNew);
|
||||
$isNewEmailAddress = true;
|
||||
}
|
||||
|
||||
$emailAddressValueOld = $entity->getFetched('emailAddress');
|
||||
if (!empty($emailAddressValueOld)) {
|
||||
$emailAddressOld = $this->getByAddress($emailAddressValueOld);
|
||||
if ($emailAddressOld) {
|
||||
$entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld);
|
||||
}
|
||||
}
|
||||
$entityRepository->relate($entity, 'emailAddresses', $emailAddressNew);
|
||||
|
||||
if ($entity->has('emailAddressIsOptedOut')) {
|
||||
$this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut'));
|
||||
}
|
||||
|
||||
$query = "
|
||||
UPDATE entity_email_address
|
||||
SET `primary` = 1
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
email_address_id = ".$pdo->quote($emailAddressNew->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
} else {
|
||||
if (
|
||||
$entity->has('emailAddressIsOptedOut')
|
||||
&&
|
||||
(
|
||||
$entity->isNew()
|
||||
||
|
||||
(
|
||||
$entity->hasFetched('emailAddressIsOptedOut')
|
||||
&&
|
||||
$entity->get('emailAddressIsOptedOut') !== $entity->getFetched('emailAddressIsOptedOut')
|
||||
)
|
||||
)
|
||||
) {
|
||||
$this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$emailAddressValueOld = $entity->getFetched('emailAddress');
|
||||
if (!empty($emailAddressValueOld)) {
|
||||
$emailAddressOld = $this->getByAddress($emailAddressValueOld);
|
||||
if ($emailAddressOld) {
|
||||
$entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld);
|
||||
}
|
||||
}
|
||||
$this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut'));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$emailAddressValueOld = $entity->getFetched('emailAddress');
|
||||
if (!empty($emailAddressValueOld)) {
|
||||
$emailAddressOld = $this->getByAddress($emailAddressValueOld);
|
||||
if ($emailAddressOld) {
|
||||
$entityRepository->unrelate($entity, 'emailAddresses', $emailAddressOld);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function storeEntityEmailAddress(Entity $entity)
|
||||
{
|
||||
$emailAddressData = null;
|
||||
if ($entity->has('emailAddressData')) {
|
||||
$emailAddressData = $entity->get('emailAddressData');
|
||||
}
|
||||
|
||||
if ($emailAddressData !== null) {
|
||||
$this->storeEntityEmailAddressData($entity);
|
||||
} else if ($entity->has('emailAddress')) {
|
||||
$this->storeEntityEmailAddressPrimary($entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkChangeIsForbidden($entity, $excludeEntity)
|
||||
|
||||
@@ -39,6 +39,8 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
protected $processFieldsAfterRemoveDisabled = true;
|
||||
|
||||
const ERASED_PREFIX = 'ERASED:';
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
@@ -96,7 +98,7 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB
|
||||
JOIN phone_number ON phone_number.id = entity_phone_number.phone_number_id AND phone_number.deleted = 0
|
||||
WHERE
|
||||
entity_phone_number.entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_phone_number.entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
entity_phone_number.entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
entity_phone_number.deleted = 0
|
||||
ORDER BY entity_phone_number.primary DESC
|
||||
";
|
||||
@@ -193,247 +195,270 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB
|
||||
}
|
||||
}
|
||||
|
||||
public function storeEntityPhoneNumber(Entity $entity)
|
||||
protected function storeEntityPhoneNumberData(Entity $entity)
|
||||
{
|
||||
$phoneNumberValue = trim($entity->get('phoneNumber'));
|
||||
$phoneNumberData = null;
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
if ($entity->has('phoneNumberData')) {
|
||||
$phoneNumberData = $entity->get('phoneNumberData');
|
||||
$phoneNumberData = null;
|
||||
if ($entity->has('phoneNumberData')) {
|
||||
$phoneNumberData = $entity->get('phoneNumberData');
|
||||
}
|
||||
|
||||
if (is_null($phoneNumberData)) return;
|
||||
if (!is_array($phoneNumberData)) return;
|
||||
|
||||
$keyList = [];
|
||||
$keyPreviousList = [];
|
||||
|
||||
$previousPhoneNumberData = [];
|
||||
if (!$entity->isNew()) {
|
||||
$previousPhoneNumberData = $this->getPhoneNumberData($entity);
|
||||
}
|
||||
|
||||
$hash = (object) [];
|
||||
$hashPrevious = (object) [];
|
||||
|
||||
foreach ($phoneNumberData as $row) {
|
||||
$key = trim($row->phoneNumber);
|
||||
if (empty($key)) continue;
|
||||
$hash->$key = [
|
||||
'primary' => $row->primary ? true : false,
|
||||
'type' => $row->type
|
||||
];
|
||||
$keyList[] = $key;
|
||||
}
|
||||
|
||||
foreach ($previousPhoneNumberData as $row) {
|
||||
$key = $row->phoneNumber;
|
||||
if (empty($key)) continue;
|
||||
$hashPrevious->$key = [
|
||||
'primary' => $row->primary ? true : false,
|
||||
'type' => $row->type
|
||||
];
|
||||
$keyPreviousList[] = $key;
|
||||
}
|
||||
|
||||
$primary = false;
|
||||
|
||||
$toCreateList = [];
|
||||
$toUpdateList = [];
|
||||
$toRemoveList = [];
|
||||
|
||||
$revertData = [];
|
||||
|
||||
foreach ($keyList as $key) {
|
||||
$data = $hash->$key;
|
||||
|
||||
$new = true;
|
||||
$changed = false;
|
||||
|
||||
if ($hash->{$key}['primary']) {
|
||||
$primary = $key;
|
||||
}
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
if ($phoneNumberData !== null && is_array($phoneNumberData)) {
|
||||
$previousPhoneNumberData = array();
|
||||
if (!$entity->isNew()) {
|
||||
$previousPhoneNumberData = $this->getPhoneNumberData($entity);
|
||||
}
|
||||
|
||||
$hash = array();
|
||||
foreach ($phoneNumberData as $row) {
|
||||
$key = trim($row->phoneNumber);
|
||||
if (!empty($key)) {
|
||||
$hash[$key] = array(
|
||||
'primary' => $row->primary ? true : false,
|
||||
'type' => $row->type
|
||||
);
|
||||
if (property_exists($hashPrevious, $key)) {
|
||||
$new = false;
|
||||
$changed = $hash->{$key}['type'] != $hashPrevious->{$key}['type'];
|
||||
if ($hash->{$key}['primary']) {
|
||||
if ($hash->{$key}['primary'] == $hashPrevious->{$key}['primary']) {
|
||||
$primary = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hashPrev = array();
|
||||
foreach ($previousPhoneNumberData as $row) {
|
||||
$key = $row->phoneNumber;
|
||||
if (!empty($key)) {
|
||||
$hashPrev[$key] = array(
|
||||
'primary' => $row->primary ? true : false,
|
||||
'type' => $row->type
|
||||
);
|
||||
}
|
||||
if ($new) {
|
||||
$toCreateList[] = $key;
|
||||
}
|
||||
if ($changed) {
|
||||
$toUpdateList[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($keyPreviousList as $key) {
|
||||
if (!property_exists($hash, $key)) {
|
||||
$toRemoveList[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toRemoveList as $number) {
|
||||
$phoneNumber = $this->getByNumber($number);
|
||||
if ($phoneNumber) {
|
||||
$query = "
|
||||
DELETE FROM entity_phone_number
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
phone_number_id = ".$pdo->quote($phoneNumber->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toUpdateList as $number) {
|
||||
$phoneNumber = $this->getByNumber($number);
|
||||
if ($phoneNumber) {
|
||||
$skipSave = $this->checkChangeIsForbidden($phoneNumber, $entity);
|
||||
if (!$skipSave) {
|
||||
$phoneNumber->set([
|
||||
'type' => $hash->{$number}['type'],
|
||||
]);
|
||||
$this->save($phoneNumber);
|
||||
} else {
|
||||
$revertData[$number] = [
|
||||
'type' => $phoneNumber->get('type')
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$primary = false;
|
||||
$toCreate = array();
|
||||
$toUpdate = array();
|
||||
$toRemove = array();
|
||||
|
||||
$revertData = [];
|
||||
|
||||
foreach ($hash as $key => $data) {
|
||||
$new = true;
|
||||
$changed = false;
|
||||
|
||||
if ($hash[$key]['primary']) {
|
||||
$primary = $key;
|
||||
}
|
||||
|
||||
if (array_key_exists($key, $hashPrev)) {
|
||||
$new = false;
|
||||
$changed = $hash[$key]['type'] != $hashPrev[$key]['type'];
|
||||
if ($hash[$key]['primary']) {
|
||||
if ($hash[$key]['primary'] == $hashPrev[$key]['primary']) {
|
||||
$primary = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($new) {
|
||||
$toCreate[] = $key;
|
||||
}
|
||||
if ($changed) {
|
||||
$toUpdate[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($hashPrev as $key => $data) {
|
||||
if (!array_key_exists($key, $hash)) {
|
||||
$toRemove[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toRemove as $number) {
|
||||
$phoneNumber = $this->getByNumber($number);
|
||||
if ($phoneNumber) {
|
||||
$query = "
|
||||
DELETE FROM entity_phone_number
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
phone_number_id = ".$pdo->quote($phoneNumber->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toUpdate as $number) {
|
||||
$phoneNumber = $this->getByNumber($number);
|
||||
if ($phoneNumber) {
|
||||
$skipSave = $this->checkChangeIsForbidden($phoneNumber, $entity);
|
||||
if (!$skipSave) {
|
||||
$phoneNumber->set(array(
|
||||
'type' => $hash[$number]['type'],
|
||||
));
|
||||
$this->save($phoneNumber);
|
||||
} else {
|
||||
$revertData[$number] = [
|
||||
'type' => $phoneNumber->get('type')
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toCreate as $number) {
|
||||
$phoneNumber = $this->getByNumber($number);
|
||||
if (!$phoneNumber) {
|
||||
$phoneNumber = $this->get();
|
||||
|
||||
$phoneNumber->set(array(
|
||||
'name' => $number,
|
||||
'type' => $hash[$number]['type'],
|
||||
));
|
||||
$this->save($phoneNumber);
|
||||
} else {
|
||||
$skipSave = $this->checkChangeIsForbidden($phoneNumber, $entity);
|
||||
if (!$skipSave) {
|
||||
if ($phoneNumber->get('type') != $hash[$number]['type']) {
|
||||
$phoneNumber->set(array(
|
||||
'type' => $hash[$number]['type'],
|
||||
));
|
||||
$this->save($phoneNumber);
|
||||
}
|
||||
} else {
|
||||
$revertData[$number] = [
|
||||
'type' => $phoneNumber->get('type')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$query = "
|
||||
INSERT entity_phone_number
|
||||
(entity_id, entity_type, phone_number_id, `primary`)
|
||||
VALUES
|
||||
(
|
||||
".$pdo->quote($entity->id).",
|
||||
".$pdo->quote($entity->getEntityName()).",
|
||||
".$pdo->quote($phoneNumber->id).",
|
||||
".$pdo->quote((int)($number === $primary))."
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($number === $primary))."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
|
||||
if ($primary) {
|
||||
$phoneNumber = $this->getByNumber($primary);
|
||||
if ($phoneNumber) {
|
||||
$query = "
|
||||
UPDATE entity_phone_number
|
||||
SET `primary` = 0
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
`primary` = 1 AND
|
||||
deleted = 0
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
|
||||
$query = "
|
||||
UPDATE entity_phone_number
|
||||
SET `primary` = 1
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
phone_number_id = ".$pdo->quote($phoneNumber->id)." AND
|
||||
deleted = 0
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($revertData)) {
|
||||
foreach ($phoneNumberData as $row) {
|
||||
if (!empty($revertData[$row->phoneNumber])) {
|
||||
$row->type = $revertData[$row->phoneNumber]['type'];
|
||||
}
|
||||
}
|
||||
$entity->set('phoneNumberData', $phoneNumberData);
|
||||
}
|
||||
foreach ($toCreateList as $number) {
|
||||
$phoneNumber = $this->getByNumber($number);
|
||||
if (!$phoneNumber) {
|
||||
$phoneNumber = $this->get();
|
||||
|
||||
$phoneNumber->set([
|
||||
'name' => $number,
|
||||
'type' => $hash->{$number}['type'],
|
||||
]);
|
||||
$this->save($phoneNumber);
|
||||
} else {
|
||||
if (!$entity->has('phoneNumber')) {
|
||||
return;
|
||||
}
|
||||
$entityRepository = $this->getEntityManager()->getRepository($entity->getEntityName());
|
||||
if (!empty($phoneNumberValue)) {
|
||||
if ($phoneNumberValue !== $entity->getFetched('phoneNumber')) {
|
||||
|
||||
$phoneNumberNew = $this->where(array('name' => $phoneNumberValue))->findOne();
|
||||
$isNewPhoneNumber = false;
|
||||
if (!$phoneNumberNew) {
|
||||
$phoneNumberNew = $this->get();
|
||||
$phoneNumberNew->set('name', $phoneNumberValue);
|
||||
$defaultType = $this->getEntityManager()->getEspoMetadata()->get('entityDefs.' . $entity->getEntityName() . '.fields.phoneNumber.defaultType');
|
||||
|
||||
$phoneNumberNew->set('type', $defaultType);
|
||||
|
||||
$this->save($phoneNumberNew);
|
||||
$isNewPhoneNumber = true;
|
||||
}
|
||||
|
||||
$phoneNumberValueOld = $entity->getFetched('phoneNumber');
|
||||
if (!empty($phoneNumberValueOld)) {
|
||||
$phoneNumberOld = $this->getByNumber($phoneNumberValueOld);
|
||||
if ($phoneNumberOld) {
|
||||
$entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld);
|
||||
}
|
||||
}
|
||||
$entityRepository->relate($entity, 'phoneNumbers', $phoneNumberNew);
|
||||
|
||||
$query = "
|
||||
UPDATE entity_phone_number
|
||||
SET `primary` = 1
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
phone_number_id = ".$pdo->quote($phoneNumberNew->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
$skipSave = $this->checkChangeIsForbidden($phoneNumber, $entity);
|
||||
if (!$skipSave) {
|
||||
if ($phoneNumber->get('type') != $hash->{$number}['type']) {
|
||||
$phoneNumber->set([
|
||||
'type' => $hash->{$number}['type'],
|
||||
]);
|
||||
$this->save($phoneNumber);
|
||||
}
|
||||
} else {
|
||||
$phoneNumberValueOld = $entity->getFetched('phoneNumber');
|
||||
if (!empty($phoneNumberValueOld)) {
|
||||
$phoneNumberOld = $this->getByNumber($phoneNumberValueOld);
|
||||
if ($phoneNumberOld) {
|
||||
$entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld);
|
||||
}
|
||||
}
|
||||
$revertData[$number] = [
|
||||
'type' => $phoneNumber->get('type')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$query = "
|
||||
INSERT entity_phone_number
|
||||
(entity_id, entity_type, phone_number_id, `primary`)
|
||||
VALUES
|
||||
(
|
||||
".$pdo->quote($entity->id).",
|
||||
".$pdo->quote($entity->getEntityType()).",
|
||||
".$pdo->quote($phoneNumber->id).",
|
||||
".$pdo->quote((int)($number === $primary))."
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($number === $primary))."
|
||||
";
|
||||
$this->getEntityManager()->runQuery($query, true);
|
||||
}
|
||||
|
||||
if ($primary) {
|
||||
$phoneNumber = $this->getByNumber($primary);
|
||||
if ($phoneNumber) {
|
||||
$query = "
|
||||
UPDATE entity_phone_number
|
||||
SET `primary` = 0
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
`primary` = 1 AND
|
||||
deleted = 0
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
|
||||
$query = "
|
||||
UPDATE entity_phone_number
|
||||
SET `primary` = 1
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
phone_number_id = ".$pdo->quote($phoneNumber->id)." AND
|
||||
deleted = 0
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($revertData)) {
|
||||
foreach ($phoneNumberData as $row) {
|
||||
if (!empty($revertData[$row->phoneNumber])) {
|
||||
$row->type = $revertData[$row->phoneNumber]['type'];
|
||||
}
|
||||
}
|
||||
$entity->set('phoneNumberData', $phoneNumberData);
|
||||
}
|
||||
}
|
||||
|
||||
protected function storeEntityPhoneNumberPrimary(Entity $entity)
|
||||
{
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
if (!$entity->has('phoneNumber')) return;
|
||||
$phoneNumberValue = trim($entity->get('phoneNumber'));
|
||||
|
||||
$entityRepository = $this->getEntityManager()->getRepository($entity->getEntityType());
|
||||
if (!empty($phoneNumberValue)) {
|
||||
if ($phoneNumberValue !== $entity->getFetched('phoneNumber')) {
|
||||
|
||||
$phoneNumberNew = $this->where(['name' => $phoneNumberValue])->findOne();
|
||||
$isNewPhoneNumber = false;
|
||||
if (!$phoneNumberNew) {
|
||||
$phoneNumberNew = $this->get();
|
||||
$phoneNumberNew->set('name', $phoneNumberValue);
|
||||
$defaultType = $this->getEntityManager()->getEspoMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields.phoneNumber.defaultType');
|
||||
|
||||
$phoneNumberNew->set('type', $defaultType);
|
||||
|
||||
$this->save($phoneNumberNew);
|
||||
$isNewPhoneNumber = true;
|
||||
}
|
||||
|
||||
$phoneNumberValueOld = $entity->getFetched('phoneNumber');
|
||||
if (!empty($phoneNumberValueOld)) {
|
||||
$phoneNumberOld = $this->getByNumber($phoneNumberValueOld);
|
||||
if ($phoneNumberOld) {
|
||||
$entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld);
|
||||
}
|
||||
}
|
||||
$entityRepository->relate($entity, 'phoneNumbers', $phoneNumberNew);
|
||||
|
||||
$query = "
|
||||
UPDATE entity_phone_number
|
||||
SET `primary` = 1
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
phone_number_id = ".$pdo->quote($phoneNumberNew->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
} else {
|
||||
$phoneNumberValueOld = $entity->getFetched('phoneNumber');
|
||||
if (!empty($phoneNumberValueOld)) {
|
||||
$phoneNumberOld = $this->getByNumber($phoneNumberValueOld);
|
||||
if ($phoneNumberOld) {
|
||||
$entityRepository->unrelate($entity, 'phoneNumbers', $phoneNumberOld);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function storeEntityPhoneNumber(Entity $entity)
|
||||
{
|
||||
$phoneNumberData = null;
|
||||
if ($entity->has('phoneNumberData')) {
|
||||
$phoneNumberData = $entity->get('phoneNumberData');
|
||||
}
|
||||
|
||||
if ($phoneNumberData !== null) {
|
||||
$this->storeEntityPhoneNumberData($entity);
|
||||
} else if ($entity->has('phoneNumber')) {
|
||||
$this->storeEntityPhoneNumberPrimary($entity);
|
||||
}
|
||||
}
|
||||
|
||||
protected function checkChangeIsForbidden($entity, $excludeEntity)
|
||||
@@ -447,7 +472,7 @@ class PhoneNumber extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
if ($entity->has('name')) {
|
||||
$number = $entity->get('name');
|
||||
if (is_string($number)) {
|
||||
if (is_string($number) && strpos($number, self::ERASED_PREFIX) !== 0) {
|
||||
$numeric = preg_replace('/[^0-9]/', '', $number);
|
||||
} else {
|
||||
$numeric = null;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"subject": "Subject",
|
||||
"attachments": "Attachments",
|
||||
"selectTemplate": "Select Template",
|
||||
"fromEmailAddress": "From Address",
|
||||
"fromEmailAddress": "From Address (link)",
|
||||
"toEmailAddresses": "To Address",
|
||||
"emailAddress": "Email Address",
|
||||
"deliveryDate": "Delivery Date",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"placeholderTexts": {
|
||||
"today": "Today's date",
|
||||
"now": "Current date & time",
|
||||
"currentYear": "Current Year",
|
||||
"optOutUrl": "URL for an unsubsbribe link",
|
||||
"optOutLink": "an unsubscribe link"
|
||||
}
|
||||
|
||||
@@ -352,6 +352,7 @@
|
||||
"ids": "IDs",
|
||||
"type": "Type",
|
||||
"names": "Names",
|
||||
"types": "Types",
|
||||
"targetListIsOptedOut": "Is Opted Out (Target List)"
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -106,6 +106,7 @@
|
||||
"outboundEmailBccAddress": "BCC address for external clients",
|
||||
"cleanupDeletedRecords": "Clean up deleted records",
|
||||
"addressCountryList": "Address Country Autocomplete List",
|
||||
"addressCityList": "Address City Autocomplete List",
|
||||
"fiscalYearShift": "Fiscal Year Start",
|
||||
"jobRunInParallel": "Jobs Run in Parallel",
|
||||
"jobMaxPortion": "Jobs Max Portion",
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
[{"name": "language"}, {"name": "timeZone"}],
|
||||
[{"name": "dateFormat"}, {"name": "weekStart"}],
|
||||
[{"name": "timeFormat"}, {"name": "thousandSeparator"}],
|
||||
[{"name": "addressFormat"}, {"name": "decimalMark"}],
|
||||
[{"name": "addressPreview"}, {"name": "addressCountryList"}],
|
||||
[{"name": "fiscalYearShift"}, false]
|
||||
[{"name": "fiscalYearShift"}, {"name": "decimalMark"}],
|
||||
[{"name": "addressFormat"}, {"name": "addressPreview"}],
|
||||
[{"name": "addressCountryList"}, {"name": "addressCityList"}]
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
26
application/Espo/Resources/metadata/app/client.json
Normal file
26
application/Espo/Resources/metadata/app/client.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"scriptList": [
|
||||
"client/espo.min.js"
|
||||
],
|
||||
"developerModeScriptList": [
|
||||
"client/lib/jquery-2.1.4.min.js",
|
||||
"client/lib/underscore-min.js",
|
||||
"client/lib/es6-promise.min.js",
|
||||
"client/lib/backbone-min.js",
|
||||
"client/lib/handlebars.js",
|
||||
"client/lib/base64.js",
|
||||
"client/lib/jquery-ui.min.js",
|
||||
"client/lib/jquery.ui.touch-punch.min.js",
|
||||
"client/lib/moment.min.js",
|
||||
"client/lib/moment-timezone-with-data.min.js",
|
||||
"client/lib/jquery.timepicker.min.js",
|
||||
"client/lib/jquery.autocomplete.js",
|
||||
"client/lib/bootstrap.min.js",
|
||||
"client/lib/bootstrap-datepicker.js",
|
||||
"client/lib/bull.js",
|
||||
"client/lib/marked.min.js",
|
||||
"client/src/loader.js",
|
||||
"client/src/utils.js",
|
||||
"client/src/exceptions.js"
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
{
|
||||
"name": "ifThen",
|
||||
"insertText": "ifThenElse(CONDITION, CONSEQUENT)"
|
||||
"insertText": "ifThen(CONDITION, CONSEQUENT)"
|
||||
},
|
||||
{
|
||||
"name": "string\\concatenate",
|
||||
@@ -16,10 +16,30 @@
|
||||
"name": "string\\substring",
|
||||
"insertText": "string\\substring(STRING, START, LENGTH)"
|
||||
},
|
||||
{
|
||||
"name": "string\\contains",
|
||||
"insertText": "string\\contains(STRING, NEEDLE)"
|
||||
},
|
||||
{
|
||||
"name": "string\\test",
|
||||
"insertText": "string\\test(STRING, REGULAR_EXPRESSION)"
|
||||
},
|
||||
{
|
||||
"name": "string\\length",
|
||||
"insertText": "string\\length(STRING)"
|
||||
},
|
||||
{
|
||||
"name": "string\\trim",
|
||||
"insertText": "string\\trim(STRING)"
|
||||
},
|
||||
{
|
||||
"name": "string\\lowerCase",
|
||||
"insertText": "string\\lowerCase(STRING)"
|
||||
},
|
||||
{
|
||||
"name": "string\\upperCase",
|
||||
"insertText": "string\\upperCase(STRING)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\today",
|
||||
"insertText": "datetime\\today()"
|
||||
@@ -32,6 +52,30 @@
|
||||
"name": "datetime\\format",
|
||||
"insertText": "datetime\\format(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\date",
|
||||
"insertText": "datetime\\date(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\month",
|
||||
"insertText": "datetime\\month(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\year",
|
||||
"insertText": "datetime\\year(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\hour",
|
||||
"insertText": "datetime\\hour(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\minute",
|
||||
"insertText": "datetime\\minute(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\dayOfWeek",
|
||||
"insertText": "datetime\\dayOfWeek(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\addMinutes",
|
||||
"insertText": "datetime\\addMinutes(VALUE, MINUTES)"
|
||||
@@ -72,6 +116,14 @@
|
||||
"name": "number\\round",
|
||||
"insertText": "number\\round(VALUE, PRECISION)"
|
||||
},
|
||||
{
|
||||
"name": "number\\floor",
|
||||
"insertText": "number\\floor(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "number\\ceil",
|
||||
"insertText": "number\\ceil(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "entity\\isNew",
|
||||
"insertText": "entity\\isNew()"
|
||||
@@ -108,6 +160,14 @@
|
||||
"name": "entity\\isRelated",
|
||||
"insertText": "entity\\isRelated(LINK, ID)"
|
||||
},
|
||||
{
|
||||
"name": "entity\\sumRelated",
|
||||
"insertText": "entity\\sumRelated(LINK, FIELD, FILTER)"
|
||||
},
|
||||
{
|
||||
"name": "entity\\countRelated",
|
||||
"insertText": "entity\\countRelated(LINK, FILTER)"
|
||||
},
|
||||
{
|
||||
"name": "env\\userAttribute",
|
||||
"insertText": "env\\userAttribute(ATTRIBUTE)"
|
||||
|
||||
@@ -38,5 +38,10 @@
|
||||
"path": "client/lib/bootstrap-colorpicker.js",
|
||||
"exportsTo": "$",
|
||||
"exportsAs": "colorpicker"
|
||||
},
|
||||
"exif": {
|
||||
"path": "client/lib/exif-js.js",
|
||||
"exportsTo": "window",
|
||||
"exportsAs": "EXIF"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,6 @@
|
||||
"filterList": [
|
||||
"actual"
|
||||
],
|
||||
"placeholderList": ["today", "now", "optOutUrl", "optOutLink"],
|
||||
"placeholderList": ["today", "now", "currentYear", "optOutUrl", "optOutLink"],
|
||||
"iconClass": "fas fa-envelope-square"
|
||||
}
|
||||
|
||||
@@ -14,18 +14,24 @@
|
||||
{
|
||||
"name": "imported",
|
||||
"label": "Imported",
|
||||
"view": "views/import/record/panels/imported"
|
||||
"view": "views/import/record/panels/imported",
|
||||
"createDisabled": true,
|
||||
"selectDisabled": true
|
||||
},
|
||||
{
|
||||
"name": "duplicates",
|
||||
"label": "Duplicates",
|
||||
"view": "views/import/record/panels/duplicates",
|
||||
"rowActionsView": "views/import/record/row-actions/duplicates"
|
||||
"rowActionsView": "views/import/record/row-actions/duplicates",
|
||||
"createDisabled": true,
|
||||
"selectDisabled": true
|
||||
},
|
||||
{
|
||||
"name": "updated",
|
||||
"label": "Updated",
|
||||
"view": "views/import/record/panels/updated"
|
||||
"view": "views/import/record/panels/updated",
|
||||
"createDisabled": true,
|
||||
"selectDisabled": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
"internal": true,
|
||||
"nonAdminReadOnly": true
|
||||
},
|
||||
"passwordConfirm": {
|
||||
"internal": true,
|
||||
"nonAdminReadOnly": true
|
||||
},
|
||||
"authMethod": {
|
||||
"onlyAdmin": true
|
||||
},
|
||||
|
||||
@@ -505,6 +505,9 @@
|
||||
"addressCountryList": {
|
||||
"type": "multiEnum"
|
||||
},
|
||||
"addressCityList": {
|
||||
"type": "multiEnum"
|
||||
},
|
||||
"jobRunInParallel": {
|
||||
"type": "bool",
|
||||
"tooltip": true
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
},
|
||||
"city":{
|
||||
"type":"varchar",
|
||||
"trim": true
|
||||
"trim": true,
|
||||
"view": "views/fields/address-city",
|
||||
"customizationOptionsDisabled": true
|
||||
},
|
||||
"state":{
|
||||
"type":"varchar",
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"fields":{
|
||||
"currency":{
|
||||
"type":"varchar",
|
||||
"disabled": true,
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutListDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"maxLength": 6
|
||||
},
|
||||
"converted":{
|
||||
|
||||
@@ -44,6 +44,13 @@ class Attachment extends Record
|
||||
|
||||
protected $inlineAttachmentFieldTypeList = ['wysiwyg'];
|
||||
|
||||
protected $imageTypeList = [
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
];
|
||||
|
||||
public function upload($fileData)
|
||||
{
|
||||
if (!$this->getAcl()->checkScope('Attachment', 'create')) {
|
||||
@@ -65,7 +72,7 @@ class Attachment extends Record
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function createEntity($data)
|
||||
public function create($data)
|
||||
{
|
||||
if (!empty($data->file)) {
|
||||
$arr = explode(',', $data->file);
|
||||
@@ -127,7 +134,7 @@ class Attachment extends Record
|
||||
}
|
||||
}
|
||||
|
||||
$entity = parent::createEntity($data);
|
||||
$entity = parent::create($data);
|
||||
|
||||
if (!empty($data->file)) {
|
||||
$entity->clear('contents');
|
||||
@@ -322,7 +329,8 @@ class Attachment extends Record
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'gif' => 'image/gif'
|
||||
'gif' => 'image/gif',
|
||||
'webp' => 'image/webp',
|
||||
];
|
||||
|
||||
$extension = preg_replace('#\?.*#', '', pathinfo($url, \PATHINFO_EXTENSION));
|
||||
@@ -334,7 +342,7 @@ class Attachment extends Record
|
||||
|
||||
if (!$type) return;
|
||||
|
||||
if (!in_array($type, ['image/png', 'image/jpeg', 'image/gif'])) {
|
||||
if (!in_array($type, $this->imageTypeList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -262,9 +262,9 @@ class Email extends Record
|
||||
return $this->streamService;
|
||||
}
|
||||
|
||||
public function createEntity($data)
|
||||
public function create($data)
|
||||
{
|
||||
$entity = parent::createEntity($data);
|
||||
$entity = parent::create($data);
|
||||
|
||||
if ($entity && $entity->get('status') == 'Sending') {
|
||||
$this->send($entity);
|
||||
|
||||
@@ -116,7 +116,7 @@ class EmailAccount extends Record
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
public function createEntity($data)
|
||||
public function create($data)
|
||||
{
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
$count = $this->getEntityManager()->getRepository('EmailAccount')->where(array(
|
||||
@@ -127,7 +127,7 @@ class EmailAccount extends Record
|
||||
}
|
||||
}
|
||||
|
||||
$entity = parent::createEntity($data);
|
||||
$entity = parent::create($data);
|
||||
if ($entity) {
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
$entity->set('assignedUserId', $this->getUser()->id);
|
||||
@@ -301,7 +301,10 @@ class EmailAccount extends Record
|
||||
|
||||
if ($forceByDate && $previousLastUID) {
|
||||
$uid = $storage->getUniqueId($id);
|
||||
if ($uid <= $previousLastUID) continue;
|
||||
if ($uid <= $previousLastUID) {
|
||||
$k++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$fetchOnlyHeader = false;
|
||||
|
||||
@@ -46,16 +46,17 @@ class EmailTemplate extends Record
|
||||
$this->addDependency('fileStorageManager');
|
||||
$this->addDependency('dateTime');
|
||||
$this->addDependency('language');
|
||||
$this->addDependency('number');
|
||||
}
|
||||
|
||||
protected function getFileStorageManager()
|
||||
{
|
||||
return $this->injections['fileStorageManager'];
|
||||
return $this->getInjection('fileStorageManager');
|
||||
}
|
||||
|
||||
protected function getDateTime()
|
||||
{
|
||||
return $this->injections['dateTime'];
|
||||
return $this->getInjection('dateTime');
|
||||
}
|
||||
|
||||
protected function getLanguage()
|
||||
@@ -63,6 +64,11 @@ class EmailTemplate extends Record
|
||||
return $this->getInjection('language');
|
||||
}
|
||||
|
||||
protected function getNumber()
|
||||
{
|
||||
return $this->getInjection('number');
|
||||
}
|
||||
|
||||
public function parseTemplate(Entity $emailTemplate, array $params = [], $copyAttachments = false, $skipAcl = false)
|
||||
{
|
||||
$entityHash = array();
|
||||
@@ -175,7 +181,7 @@ class EmailTemplate extends Record
|
||||
|
||||
protected function parseText($type, Entity $entity, $text, $skipLinks = false, $prefixLink = null, $skipAcl = false)
|
||||
{
|
||||
$fieldList = array_keys($entity->getAttributes());
|
||||
$attributeList = array_keys($entity->getAttributes());
|
||||
|
||||
if ($skipAcl) {
|
||||
$forbiddenAttributeList = [];
|
||||
@@ -191,30 +197,30 @@ class EmailTemplate extends Record
|
||||
$forbiddenLinkList = $this->getAcl()->getScopeRestrictedLinkList($entity->getEntityType(), ['forbidden', 'internal', 'onlyAdmin']);
|
||||
}
|
||||
|
||||
foreach ($fieldList as $field) {
|
||||
if (in_array($field, $forbiddenAttributeList)) continue;
|
||||
foreach ($attributeList as $attribute) {
|
||||
if (in_array($attribute, $forbiddenAttributeList)) continue;
|
||||
|
||||
$value = $entity->get($field);
|
||||
$value = $entity->get($attribute);
|
||||
if (is_object($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fieldType = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() .'.fields.' . $field . '.type');
|
||||
$fieldType = $this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $attribute, 'type']);
|
||||
|
||||
if ($fieldType === 'enum') {
|
||||
$value = $this->getLanguage()->translateOption($value, $field, $entity->getEntityType());
|
||||
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
|
||||
} else if ($fieldType === 'array' || $fieldType === 'multiEnum') {
|
||||
$valueList = [];
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $v) {
|
||||
$valueList[] = $this->getLanguage()->translateOption($v, $field, $entity->getEntityType());
|
||||
$valueList[] = $this->getLanguage()->translateOption($v, $attribute, $entity->getEntityType());
|
||||
}
|
||||
}
|
||||
$value = implode(', ', $valueList);
|
||||
$value = $this->getLanguage()->translateOption($value, $field, $entity->getEntityType());
|
||||
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
|
||||
} else {
|
||||
if (!isset($entity->fields[$field]['type'])) continue;
|
||||
$attributeType = $entity->fields[$field]['type'];
|
||||
if (!isset($entity->fields[$attribute]['type'])) continue;
|
||||
$attributeType = $entity->fields[$attribute]['type'];
|
||||
|
||||
if ($attributeType == 'date') {
|
||||
$value = $this->getDateTime()->convertSystemDate($value);
|
||||
@@ -225,12 +231,22 @@ class EmailTemplate extends Record
|
||||
$value = '';
|
||||
}
|
||||
$value = nl2br($value);
|
||||
} else if ($attributeType == 'float') {
|
||||
if (is_float($value)) {
|
||||
$value = $this->getNumber()->format($value, 2);
|
||||
}
|
||||
} else if ($attributeType == 'int') {
|
||||
if (is_int($value)) {
|
||||
$value = $this->getNumber()->format($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (is_string($value) || $value === null || is_scalar($value) || is_callable([$value, '__toString'])) {
|
||||
$variableName = $field;
|
||||
$variableName = $attribute;
|
||||
if (!is_null($prefixLink)) {
|
||||
$variableName = $prefixLink . '.' . $field;
|
||||
$variableName = $prefixLink . '.' . $attribute;
|
||||
}
|
||||
$text = str_replace('{' . $type . '.' . $variableName . '}', $value, $text);
|
||||
}
|
||||
@@ -260,6 +276,11 @@ class EmailTemplate extends Record
|
||||
$replaceData['today'] = $this->getDateTime()->getTodayString();
|
||||
$replaceData['now'] = $this->getDateTime()->getNowString();
|
||||
|
||||
$timeZone = $this->getConfig()->get('timeZone');
|
||||
$now = new \DateTime('now', new \DateTimezone($timeZone));
|
||||
|
||||
$replaceData['currentYear'] = $now->format('Y');
|
||||
|
||||
foreach ($replaceData as $key => $value) {
|
||||
$text = str_replace('{' . $key . '}', $value, $text);
|
||||
}
|
||||
|
||||
@@ -41,24 +41,6 @@ class InboundEmail extends \Espo\Services\Record
|
||||
|
||||
const PORTION_LIMIT = 20;
|
||||
|
||||
public function createEntity($data)
|
||||
{
|
||||
$entity = parent::createEntity($data);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function getEntity($id = null)
|
||||
{
|
||||
$entity = parent::getEntity($id);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function updateEntity($id, $data)
|
||||
{
|
||||
$entity = parent::updateEntity($id, $data);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
@@ -306,7 +288,10 @@ class InboundEmail extends \Espo\Services\Record
|
||||
|
||||
if ($forceByDate && $previousLastUID) {
|
||||
$uid = $storage->getUniqueId($id);
|
||||
if ($uid <= $previousLastUID) continue;
|
||||
if ($uid <= $previousLastUID) {
|
||||
$k++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$fetchOnlyHeader = false;
|
||||
|
||||
@@ -48,7 +48,7 @@ class Note extends Record
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function createEntity($data)
|
||||
public function create($data)
|
||||
{
|
||||
if (!empty($data->parentType) && !empty($data->parentId)) {
|
||||
$entity = $this->getEntityManager()->getEntity($data->parentType, $data->parentId);
|
||||
@@ -59,7 +59,7 @@ class Note extends Record
|
||||
}
|
||||
}
|
||||
|
||||
return parent::createEntity($data);
|
||||
return parent::create($data);
|
||||
}
|
||||
|
||||
protected function afterCreateEntity(Entity $entity, $data)
|
||||
@@ -82,6 +82,11 @@ class Note extends Record
|
||||
protected function beforeCreateEntity(Entity $entity, $data)
|
||||
{
|
||||
parent::beforeCreateEntity($entity, $data);
|
||||
|
||||
if ($entity->get('type') === 'Post') {
|
||||
$this->handlePostText($entity);
|
||||
}
|
||||
|
||||
$targetType = $entity->get('targetType');
|
||||
|
||||
$entity->clear('isGlobal');
|
||||
@@ -119,6 +124,10 @@ class Note extends Record
|
||||
{
|
||||
parent::beforeUpdateEntity($entity, $data);
|
||||
|
||||
if ($entity->get('type') === 'Post') {
|
||||
$this->handlePostText($entity);
|
||||
}
|
||||
|
||||
$entity->clear('targetType');
|
||||
$entity->clear('usersIds');
|
||||
$entity->clear('teamsIds');
|
||||
@@ -126,6 +135,19 @@ class Note extends Record
|
||||
$entity->clear('isGlobal');
|
||||
}
|
||||
|
||||
protected function handlePostText(Entity $entity)
|
||||
{
|
||||
$post = $entity->get('post');
|
||||
if (empty($post)) return;
|
||||
|
||||
$siteUrl = $this->getConfig()->getSiteUrl();
|
||||
|
||||
$regexp = '/' . preg_quote($siteUrl, '/') . '(\/portal|\/portal\/[a-zA-Z0-9]*)?\/#([A-Z][a-zA-Z0-9]*)\/view\/([a-zA-Z0-9]*)/';
|
||||
$post = preg_replace($regexp, '[\2/\3](#\2/view/\3)', $post);
|
||||
|
||||
$entity->set('post', $post);
|
||||
}
|
||||
|
||||
public function checkAssignment(Entity $entity)
|
||||
{
|
||||
if ($entity->isNew()) {
|
||||
@@ -197,20 +219,20 @@ class Note extends Record
|
||||
return true;
|
||||
}
|
||||
|
||||
public function linkEntity($id, $link, $foreignId)
|
||||
public function link($id, $link, $foreignId)
|
||||
{
|
||||
if ($link === 'teams' || $link === 'users') {
|
||||
throw new Forbidden();
|
||||
}
|
||||
return parant::linkEntity($id, $link, $foreignId);
|
||||
return parant::link($id, $link, $foreignId);
|
||||
}
|
||||
|
||||
public function unlinkEntity($id, $link, $foreignId)
|
||||
public function unlink($id, $link, $foreignId)
|
||||
{
|
||||
if ($link === 'teams' || $link === 'users') {
|
||||
throw new Forbidden();
|
||||
}
|
||||
return parant::unlinkEntity($id, $link, $foreignId);
|
||||
return parant::unlink($id, $link, $foreignId);
|
||||
}
|
||||
|
||||
public function processNoteAclJob($data)
|
||||
|
||||
@@ -117,6 +117,8 @@ class Record extends \Espo\Core\Services\Base
|
||||
|
||||
protected $forceSelectAllAttributes = false;
|
||||
|
||||
protected $selectAttributesDependancyMap = [];
|
||||
|
||||
const MAX_SELECT_TEXT_ATTRIBUTE_LENGTH = 5000;
|
||||
|
||||
const FOLLOWERS_LIMIT = 4;
|
||||
@@ -261,7 +263,12 @@ class Record extends \Espo\Core\Services\Base
|
||||
$this->getEntityManager()->saveEntity($historyRecord);
|
||||
}
|
||||
|
||||
public function readEntity($id)
|
||||
public function readEntity($id) //TODO Remove in 5.8
|
||||
{
|
||||
return $this->read($id);
|
||||
}
|
||||
|
||||
public function read($id)
|
||||
{
|
||||
if (empty($id)) {
|
||||
throw new Error();
|
||||
@@ -313,6 +320,8 @@ class Record extends \Espo\Core\Services\Base
|
||||
if ($this->getUser()->isPortal()) return;
|
||||
if (!$this->getMetadata()->get(['scopes', $entity->getEntityType(), 'stream'])) return;
|
||||
|
||||
if (!$this->getAcl()->check($entity, 'stream')) return;
|
||||
|
||||
$data = $this->getStreamService()->getEntityFollowers($entity, 0, self::FOLLOWERS_LIMIT);
|
||||
if ($data) {
|
||||
$entity->set('followersIds', $data['idList']);
|
||||
@@ -787,7 +796,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
}
|
||||
}
|
||||
|
||||
public function createEntity($data)
|
||||
public function createEntity($data) //TODO Remove in 5.8
|
||||
{
|
||||
return $this->create($data);
|
||||
}
|
||||
@@ -845,7 +854,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
public function updateEntity($id, $data)
|
||||
public function updateEntity($id, $data) //TODO Remove in 5.8
|
||||
{
|
||||
return $this->update($id, $data);
|
||||
}
|
||||
@@ -944,7 +953,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
{
|
||||
}
|
||||
|
||||
public function deleteEntity($id)
|
||||
public function deleteEntity($id) //TODO Remove in 5.8
|
||||
{
|
||||
return $this->delete($id);
|
||||
}
|
||||
@@ -1285,7 +1294,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
];
|
||||
}
|
||||
|
||||
public function linkEntity($id, $link, $foreignId)
|
||||
public function linkEntity($id, $link, $foreignId) //TODO Remove in 5.8
|
||||
{
|
||||
return $this->link($id, $link, $foreignId);
|
||||
}
|
||||
@@ -1320,6 +1329,11 @@ class Record extends \Espo\Core\Services\Base
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$methodName = 'link' . ucfirst($link);
|
||||
if ($link !== 'entity' && $link !== 'entityMass' && method_exists($this, $methodName)) {
|
||||
return $this->$methodName($id, $foreignId);
|
||||
}
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, 'entity');
|
||||
if (!$foreignEntityType) {
|
||||
throw new Error("Entity '{$this->entityType}' has not relation '{$link}'.");
|
||||
@@ -1342,7 +1356,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
return true;
|
||||
}
|
||||
|
||||
public function unlinkEntity($id, $link, $foreignId)
|
||||
public function unlinkEntity($id, $link, $foreignId) //TODO Remove in 5.8
|
||||
{
|
||||
return $this->unlink($id, $link, $foreignId);
|
||||
}
|
||||
@@ -1381,6 +1395,11 @@ class Record extends \Espo\Core\Services\Base
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$methodName = 'unlink' . ucfirst($link);
|
||||
if ($link !== 'entity' && method_exists($this, $methodName)) {
|
||||
return $this->$methodName($id, $foreignId);
|
||||
}
|
||||
|
||||
$foreignEntityType = $entity->getRelationParam($link, 'entity');
|
||||
if (!$foreignEntityType) {
|
||||
throw new Error("Entity '{$this->entityType}' has not relation '{$link}'.");
|
||||
@@ -1403,12 +1422,12 @@ class Record extends \Espo\Core\Services\Base
|
||||
return true;
|
||||
}
|
||||
|
||||
public function linkEntityMass($id, $link, $where, $selectData = null)
|
||||
public function linkEntityMass($id, $link, $where, $selectData = null) //TODO Remove in 5.8
|
||||
{
|
||||
return $this->linkMass($id, $link, $where, $selectData);
|
||||
return $this->massLink($id, $link, $where, $selectData);
|
||||
}
|
||||
|
||||
public function linkMass($id, $link, $where, $selectData = null)
|
||||
public function massLink($id, $link, $where, $selectData = null)
|
||||
{
|
||||
if (empty($id) || empty($link)) {
|
||||
throw new BadRequest;
|
||||
@@ -1438,6 +1457,11 @@ class Record extends \Espo\Core\Services\Base
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$methodName = 'massLink' . ucfirst($link);
|
||||
if (method_exists($this, $methodName)) {
|
||||
return $this->$methodName($id, $where, $selectData);
|
||||
}
|
||||
|
||||
$entityType = $entity->getEntityType();
|
||||
$foreignEntityType = $entity->getRelationParam($link, 'entity');
|
||||
|
||||
@@ -1671,10 +1695,6 @@ class Record extends \Espo\Core\Services\Base
|
||||
{
|
||||
$entity = $this->getRepository()->get($id);
|
||||
|
||||
if (!$this->getAcl()->check($entity, 'read')) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if (empty($userId)) {
|
||||
$userId = $this->getUser()->id;
|
||||
}
|
||||
@@ -1724,7 +1744,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
$idList = $params['ids'];
|
||||
foreach ($idList as $id) {
|
||||
$entity = $this->getEntity($id);
|
||||
if ($entity && $this->getAcl()->check($entity, 'stream')) {
|
||||
if ($entity) {
|
||||
if ($streamService->unfollowEntity($entity, $userId)) {
|
||||
$resultIdList[] = $entity->id;
|
||||
}
|
||||
@@ -1755,7 +1775,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
if ($entity->id) {
|
||||
$where['id!='] = $entity->id;
|
||||
}
|
||||
$duplicateList = $this->getRepository()->where($where)->find();
|
||||
$duplicateList = $this->getRepository()->where($where)->limit(0, 20)->find();
|
||||
if (count($duplicateList)) {
|
||||
$result = array();
|
||||
foreach ($duplicateList as $e) {
|
||||
@@ -2023,7 +2043,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
break;
|
||||
}
|
||||
$value = $entity->get($attribute);
|
||||
return \Espo\Core\Utils\Json::encode($value);
|
||||
return \Espo\Core\Utils\Json::encode($value, \JSON_UNESCAPED_UNICODE);
|
||||
break;
|
||||
case 'jsonArray':
|
||||
if (!empty($defs[$attribute]['isLinkMultipleIdList'])) {
|
||||
@@ -2031,7 +2051,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
}
|
||||
$value = $entity->get($attribute);
|
||||
if (is_array($value)) {
|
||||
return \Espo\Core\Utils\Json::encode($value);
|
||||
return \Espo\Core\Utils\Json::encode($value, \JSON_UNESCAPED_UNICODE);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
@@ -2238,7 +2258,7 @@ class Record extends \Espo\Core\Services\Base
|
||||
{
|
||||
}
|
||||
|
||||
protected function findLinkedEntitiesFollowers($id, $params)
|
||||
protected function findLinkedFollowers($id, $params)
|
||||
{
|
||||
$maxSize = 0;
|
||||
|
||||
@@ -2449,6 +2469,16 @@ class Record extends \Espo\Core\Services\Base
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->selectAttributesDependancyMap as $attribute => $dependantAttributeList) {
|
||||
if (in_array($attribute, $attributeList)) {
|
||||
foreach ($dependantAttributeList as $dependantAttribute) {
|
||||
if (!in_array($dependantAttribute, $attributeList)) {
|
||||
$attributeList[] = $dependantAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
|
||||
@@ -174,21 +174,21 @@ class RecordTree extends Record
|
||||
}
|
||||
}
|
||||
|
||||
public function updateEntity($id, $data)
|
||||
public function update($id, $data)
|
||||
{
|
||||
if (!empty($data->parentId) && $data->parentId == $id) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
return parent::updateEntity($id, $data);
|
||||
return parent::update($id, $data);
|
||||
}
|
||||
|
||||
public function linkEntity($id, $link, $foreignId)
|
||||
public function link($id, $link, $foreignId)
|
||||
{
|
||||
if ($id == $foreignId ) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
return parent::linkEntity($id, $link, $foreignId);
|
||||
return parent::link($id, $link, $foreignId);
|
||||
}
|
||||
|
||||
public function getLastChildrenIdList($parentId = null)
|
||||
|
||||
@@ -86,9 +86,9 @@ class Team extends Record
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function linkMass($id, $link, $where, $selectData = null)
|
||||
public function massLink($id, $link, $where, $selectData = null)
|
||||
{
|
||||
$result = parent::linkMass($id, $link, $where, $selectData);
|
||||
$result = parent::massLink($id, $link, $where, $selectData);
|
||||
|
||||
if ($link === 'users') {
|
||||
$this->getFileManager()->removeInDir('data/cache/application/acl');
|
||||
@@ -97,4 +97,3 @@ class Team extends Record
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -242,7 +242,7 @@ class User extends Record
|
||||
}
|
||||
}
|
||||
|
||||
public function createEntity($data)
|
||||
public function create($data)
|
||||
{
|
||||
$newPassword = null;
|
||||
if (property_exists($data, 'password')) {
|
||||
@@ -250,7 +250,7 @@ class User extends Record
|
||||
$data->password = $this->hashPassword($data->password);
|
||||
}
|
||||
|
||||
$user = parent::createEntity($data);
|
||||
$user = parent::create($data);
|
||||
|
||||
if (!is_null($newPassword) && !empty($data->sendAccessInfo)) {
|
||||
if ($user->isActive()) {
|
||||
@@ -263,7 +263,7 @@ class User extends Record
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function updateEntity($id, $data)
|
||||
public function update($id, $data)
|
||||
{
|
||||
if ($id == 'system') {
|
||||
throw new Forbidden();
|
||||
@@ -280,7 +280,7 @@ class User extends Record
|
||||
unset($data->type);
|
||||
}
|
||||
|
||||
$user = parent::updateEntity($id, $data);
|
||||
$user = parent::update($id, $data);
|
||||
|
||||
if (!is_null($newPassword)) {
|
||||
try {
|
||||
@@ -576,7 +576,7 @@ class User extends Record
|
||||
$this->getMailSender()->send($email);
|
||||
}
|
||||
|
||||
public function deleteEntity($id)
|
||||
public function delete($id)
|
||||
{
|
||||
if ($id == 'system') {
|
||||
throw new Forbidden();
|
||||
@@ -584,7 +584,7 @@ class User extends Record
|
||||
if ($id == $this->getUser()->id) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
return parent::deleteEntity($id);
|
||||
return parent::delete($id);
|
||||
}
|
||||
|
||||
protected function checkEntityForMassRemove(Entity $entity)
|
||||
|
||||
8
client/lib/exif-js.js
Normal file
8
client/lib/exif-js.js
Normal file
File diff suppressed because one or more lines are too long
@@ -212,6 +212,11 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
|
||||
this.fetch(function (data) {
|
||||
this.chartData = this.prepareData(data);
|
||||
|
||||
if (this.isNoData()) {
|
||||
this.showNoData();
|
||||
return;
|
||||
}
|
||||
|
||||
this.adjustContainer();
|
||||
|
||||
setTimeout(function () {
|
||||
@@ -221,6 +226,10 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
|
||||
});
|
||||
},
|
||||
|
||||
isNoData: function () {
|
||||
return false;
|
||||
},
|
||||
|
||||
url: function () {},
|
||||
|
||||
prepareData: function (response) {
|
||||
@@ -239,6 +248,30 @@ Espo.define('crm:views/dashlets/abstract/chart', ['views/dashlets/abstract/base'
|
||||
|
||||
getDateFilter: function () {
|
||||
return this.getOption('dateFilter') || 'currentYear';
|
||||
},
|
||||
|
||||
showNoData: function () {
|
||||
var fontSize = this.getThemeManager().getParam('fontSize') || 14;
|
||||
this.$container.empty();
|
||||
var textFontSize = fontSize * 1.2;
|
||||
|
||||
var $text = $('<span>').html(this.translate('No Data')).addClass('text-muted');
|
||||
|
||||
var $div = $('<div>').css('text-align', 'center')
|
||||
.css('font-size', textFontSize + 'px')
|
||||
.css('display', 'table')
|
||||
.css('width', '100%')
|
||||
.css('height', '100%');
|
||||
|
||||
$text
|
||||
.css('display', 'table-cell')
|
||||
.css('vertical-align', 'middle')
|
||||
.css('padding-bottom', fontSize * 1.5 + 'px');
|
||||
|
||||
|
||||
$div.append($text);
|
||||
|
||||
this.$container.append($div);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -53,6 +53,10 @@ Espo.define('crm:views/dashlets/opportunities-by-lead-source', 'crm:views/dashle
|
||||
return data;
|
||||
},
|
||||
|
||||
isNoData: function () {
|
||||
return !this.chartData.length;
|
||||
},
|
||||
|
||||
setupDefaultOptions: function () {
|
||||
this.defaultOptions['dateFrom'] = this.defaultOptions['dateFrom'] || moment().format('YYYY') + '-01-01';
|
||||
this.defaultOptions['dateTo'] = this.defaultOptions['dateTo'] || moment().format('YYYY') + '-12-31';
|
||||
|
||||
@@ -52,6 +52,10 @@ Espo.define('crm:views/dashlets/sales-by-month', 'crm:views/dashlets/abstract/ch
|
||||
return 0;
|
||||
},
|
||||
|
||||
isNoData: function () {
|
||||
return !this.monthList.length;
|
||||
},
|
||||
|
||||
prepareData: function (response) {
|
||||
var monthList = this.monthList = response.keyList;
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
<li>vis.js by Almende B.V.</li>
|
||||
<li>Ace</li>
|
||||
<li>Marked by Christopher Jeffrey</li>
|
||||
<li>Exif.js by Jacob Seidelin</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1 +1 @@
|
||||
{{#if value}}<span class="fas fa-paperclip small text-soft"></span>{{/if}}
|
||||
{{#if value}}<span class="fas fa-paperclip small text-soft" title="{{translate 'hasAttachment' category='fields' scope='Email'}}"></span>{{/if}}
|
||||
@@ -2,7 +2,7 @@
|
||||
<div>
|
||||
{{#each emailAddressData}}
|
||||
<div class="input-group email-address-block">
|
||||
<input type="email" class="form-control email-address" value="{{emailAddress}}" autocomplete="espo-{{../name}}">
|
||||
<input type="email" class="form-control email-address" value="{{emailAddress}}" autocomplete="espo-{{../name}}" maxlength={{../itemMaxLength}}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-icon email-property{{#if primary}} active{{/if}} hidden" type="button" tabindex="-1" data-action="switchEmailProperty" data-property-type="primary" data-toggle="tooltip" data-placement="top" title="{{translate 'Primary' scope='EmailAddress'}}">
|
||||
<span class="fas fa-star fa-sm{{#unless primary}} text-muted{{/unless}}"></span>
|
||||
@@ -13,7 +13,7 @@
|
||||
<button class="btn btn-default btn-icon email-property{{#if invalid}} active{{/if}}" type="button" tabindex="-1" data-action="switchEmailProperty" data-property-type="invalid" data-toggle="tooltip" data-placement="top" title="{{translate 'Invalid' scope='EmailAddress'}}">
|
||||
<span class="fa fa-exclamation-circle{{#unless invalid}} text-muted{{/unless}}"></span>
|
||||
</button>
|
||||
<button class="btn btn-link btn-icon hidden" style="margin-left: 5px;" type="button" tabindex="-1" data-action="removeEmailAddress" data-property-type="invalid" data-toggle="tooltip" data-placement="top" title="{{translate 'Remove'}}">
|
||||
<button class="btn btn-link btn-icon hidden" type="button" tabindex="-1" data-action="removeEmailAddress" data-property-type="invalid" data-toggle="tooltip" data-placement="top" title="{{translate 'Remove'}}">
|
||||
<span class="fas fa-times"></span>
|
||||
</button>
|
||||
</span>
|
||||
@@ -22,4 +22,3 @@
|
||||
</div>
|
||||
|
||||
<button class="btn btn-default btn-icon" type="button" data-action="addEmailAddress"><span class="fa fa-plus"></span></button>
|
||||
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<span class="input-group-btn">
|
||||
<select data-property-type="type" class="form-control">{{options ../params.typeList type scope=../scope field=../name}}</select>
|
||||
</span>
|
||||
<input type="input" class="form-control phone-number no-margin-shifting" value="{{phoneNumber}}" autocomplete="espo-{{../name}}">
|
||||
<input type="input" class="form-control phone-number no-margin-shifting" value="{{phoneNumber}}" autocomplete="espo-{{../name}}" maxlength={{../itemMaxLength}}>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default btn-icon phone-property{{#if primary}} active{{/if}} hidden" type="button" tabindex="-1" data-action="switchPhoneProperty" data-property-type="primary" data-toggle="tooltip" data-placement="top" title="{{translate 'Primary' scope='PhoneNumber'}}">
|
||||
<span class="fa fa-star fa-sm{{#unless primary}} text-muted{{/unless}}"></span>
|
||||
</button>
|
||||
<button class="btn btn-link btn-icon hidden" style="margin-left: 5px;" type="button" tabindex="-1" data-action="removePhoneNumber" data-property-type="invalid" data-toggle="tooltip" data-placement="top" title="{{translate 'Remove'}}">
|
||||
<button class="btn btn-link btn-icon hidden" type="button" tabindex="-1" data-action="removePhoneNumber" data-property-type="invalid" data-toggle="tooltip" data-placement="top" title="{{translate 'Remove'}}">
|
||||
<span class="fas fa-times"></span>
|
||||
</button>
|
||||
</span>
|
||||
@@ -19,4 +19,3 @@
|
||||
</div>
|
||||
|
||||
<button class="btn btn-default btn-icon" type="button" data-action="addPhoneNumber"><span class="fa fa-plus"></span></button>
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<div class="row">
|
||||
<div class="{{#if isHeaderAdditionalSpace}}col-sm-8{{else}}col-sm-7{{/if}}{{#if isXsSingleRow}} col-xs-6{{/if}}">
|
||||
<div class="{{#if isHeaderAdditionalSpace}}col-sm-8{{else}}col-sm-7{{/if}}{{#if isXsSingleRow}} col-xs-6{{/if}} page-header-column-1">
|
||||
<h3>{{{header}}}</h3>
|
||||
</div>
|
||||
<div class="{{#if isHeaderAdditionalSpace}}col-sm-4{{else}}col-sm-5{{/if}}{{#if isXsSingleRow}} col-xs-6{{/if}}">
|
||||
<div class="{{#if isHeaderAdditionalSpace}}col-sm-4{{else}}col-sm-5{{/if}}{{#if isXsSingleRow}} col-xs-6{{/if}} page-header-column-2">
|
||||
<div class="header-buttons btn-group pull-right">
|
||||
{{#each items.buttons}}
|
||||
<a {{#if link}}href="{{link}}"{{else}}href="javascript:"{{/if}} class="btn btn-{{#if style}}{{style}}{{else}}default{{/if}} action{{#if hidden}} hidden{{/if}}" data-name="{{name}}" data-action="{{action}}"{{#each data}} data-{{@key}}="{{./this}}"{{/each}}{{#if title}} title="{{title}}"{{/if}}>
|
||||
@@ -47,4 +47,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
<p class="credit small">© 2018 <a href="https://www.espocrm.com">EspoCRM</a></p>
|
||||
<p class="credit small">© 2019 <a href="https://www.espocrm.com">EspoCRM</a></p>
|
||||
|
||||
@@ -135,6 +135,13 @@
|
||||
return fieldNames;
|
||||
},
|
||||
|
||||
getScopeFieldAttributeList: function (scope, field) {
|
||||
var type = this.metadata.get(['entityDefs', scope, 'fields', field, 'type']);
|
||||
if (!type) return [];
|
||||
|
||||
return this.getAttributeList(type, field);
|
||||
},
|
||||
|
||||
getAttributeList: function (fieldType, fieldName) {
|
||||
return _.union(this.getActualAttributeList(fieldType, fieldName), this.getNotActualAttributeList(fieldType, fieldName));
|
||||
},
|
||||
|
||||
@@ -217,6 +217,8 @@ Espo.define('view-helper', [], function () {
|
||||
|
||||
text = marked(text);
|
||||
|
||||
text = text.replace(/<a href="mailto:(.*)"/gm, '<a href="javascript:" data-email-address="$1" data-action="mailTo"');
|
||||
|
||||
text = text.replace('[#see-more-text]', ' <a href="javascript:" data-action="seeMoreText">' + self.language.translate('See more')) + '</a>';
|
||||
return new Handlebars.SafeString(text);
|
||||
}.bind(this));
|
||||
|
||||
@@ -103,7 +103,7 @@ Espo.define('views/dashlets/abstract/record-list', ['views/dashlets/abstract/bas
|
||||
}
|
||||
|
||||
if (this.getOption('order') === 'asc') {
|
||||
collection.order = 'order';
|
||||
collection.order = 'asc';
|
||||
} else if (this.getOption('order') === 'desc') {
|
||||
collection.order = 'desc';
|
||||
}
|
||||
|
||||
@@ -137,7 +137,9 @@ Espo.define('views/dashlets/options/base', ['views/modal', 'views/record/detail'
|
||||
var valid = true;
|
||||
this.fieldList.forEach(function (field) {
|
||||
var fieldView = this.getView('record').getFieldView(field);
|
||||
valid = !fieldView.validate() && valid;
|
||||
if (fieldView && fieldView.isEditMode() && !fieldView.disabled && !fieldView.readOnly) {
|
||||
valid = !fieldView.validate() && valid;
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (!valid) {
|
||||
@@ -164,7 +166,7 @@ Espo.define('views/dashlets/options/base', ['views/modal', 'views/record/detail'
|
||||
return {};
|
||||
},
|
||||
|
||||
getFieldView: function () {
|
||||
getFieldView: function (name) {
|
||||
return (this.getFieldViews(true) || {})[name] || null;
|
||||
},
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('views/email-template/fields/insert-field', 'views/fields/base', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
@@ -82,6 +83,14 @@ Espo.define('views/email-template/fields/insert-field', 'views/fields/base', fun
|
||||
var foreignScope = links[link].entity;
|
||||
if (!foreignScope) return;
|
||||
|
||||
if (
|
||||
this.getMetadata().get(['entityAcl', scope, 'links', link, 'onlyAdmin'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', scope, 'links', link, 'forbidden'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', scope, 'links', link, 'internal'])
|
||||
) return;
|
||||
|
||||
var attributeList = this.getScopeAttributeList(foreignScope);
|
||||
|
||||
attributeList.forEach(function (item) {
|
||||
@@ -118,6 +127,14 @@ Espo.define('views/email-template/fields/insert-field', 'views/fields/base', fun
|
||||
if (fieldType === 'linkMultiple') return;
|
||||
if (fieldType === 'attachmentMultiple') return;
|
||||
|
||||
if (
|
||||
this.getMetadata().get(['entityAcl', scope, 'fields', field, 'onlyAdmin'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', scope, 'fields', field, 'forbidden'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', scope, 'fields', field, 'internal'])
|
||||
) return;
|
||||
|
||||
var fieldAttributeList = this.getFieldManager().getAttributeList(fieldType, field);
|
||||
|
||||
fieldAttributeList.forEach(function (attribute) {
|
||||
@@ -165,6 +182,11 @@ Espo.define('views/email-template/fields/insert-field', 'views/fields/base', fun
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
|
||||
label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('name', 'fields') + ')';
|
||||
}
|
||||
} else if (field.indexOf('Type') === field.length - 4) {
|
||||
var baseField = field.substr(0, field.length - 4);
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
|
||||
label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('type', 'fields') + ')';
|
||||
}
|
||||
}
|
||||
|
||||
if (field.indexOf('Ids') === field.length - 3) {
|
||||
@@ -177,6 +199,11 @@ Espo.define('views/email-template/fields/insert-field', 'views/fields/base', fun
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
|
||||
label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('names', 'fields') + ')';
|
||||
}
|
||||
} else if (field.indexOf('Types') === field.length - 5) {
|
||||
var baseField = field.substr(0, field.length - 5);
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
|
||||
label = this.translate(baseField, 'fields', scope) + ' (' + this.translate('types', 'fields') + ')';
|
||||
}
|
||||
}
|
||||
|
||||
if (isForeign) {
|
||||
|
||||
41
client/src/views/fields/address-city.js
Normal file
41
client/src/views/fields/address-city.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/************************************************************************
|
||||
* 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.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('views/fields/address-city', 'views/fields/varchar', function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
setupOptions: function () {
|
||||
var cityList = this.getConfig().get('addressCityList') || [];
|
||||
if (cityList.length) {
|
||||
this.params.options = Espo.Utils.clone(cityList);
|
||||
}
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
@@ -334,19 +334,70 @@ Espo.define('views/fields/address', 'views/fields/base', function (Dep) {
|
||||
this.$country.attr('autocomplete', 'espo-country');
|
||||
}
|
||||
|
||||
this.controlStreetTextareaHeight();
|
||||
this.$street.on('input', function (e) {
|
||||
var numberOfLines = e.currentTarget.value.split('\n').length;
|
||||
var numberOfRows = this.$street.prop('rows');
|
||||
|
||||
if (numberOfRows < numberOfLines) {
|
||||
this.$street.prop('rows', numberOfLines);
|
||||
} else if (numberOfRows > numberOfLines) {
|
||||
this.$street.prop('rows', numberOfLines);
|
||||
}
|
||||
this.controlStreetTextareaHeight();
|
||||
}.bind(this));
|
||||
|
||||
var numberOfLines = this.$street.val().split('\n').length;
|
||||
this.$street.prop('rows', numberOfLines);
|
||||
var cityList = this.getConfig().get('addressCityList') || [];
|
||||
if (cityList.length) {
|
||||
this.$city.autocomplete({
|
||||
minChars: 0,
|
||||
lookup: cityList,
|
||||
maxHeight: 200,
|
||||
formatResult: function (suggestion) {
|
||||
return suggestion.value;
|
||||
},
|
||||
lookupFilter: function (suggestion, query, queryLowerCase) {
|
||||
if (suggestion.value.toLowerCase().indexOf(queryLowerCase) === 0) {
|
||||
if (suggestion.value.length === queryLowerCase.length) return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
onSelect: function () {
|
||||
this.trigger('change');
|
||||
}.bind(this)
|
||||
});
|
||||
this.$city.on('focus', function () {
|
||||
if (this.$city.val()) return;
|
||||
this.$city.autocomplete('onValueChange');
|
||||
}.bind(this));
|
||||
this.once('render', function () {
|
||||
this.$city.autocomplete('dispose');
|
||||
}, this);
|
||||
this.once('remove', function () {
|
||||
this.$city.autocomplete('dispose');
|
||||
}, this);
|
||||
|
||||
this.$city.attr('autocomplete', 'espo-city');
|
||||
}
|
||||
|
||||
this.controlStreetTextareaHeight();
|
||||
this.$street.on('input', function (e) {
|
||||
this.controlStreetTextareaHeight();
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
controlStreetTextareaHeight: function (lastHeight) {
|
||||
var scrollHeight = this.$street.prop('scrollHeight');
|
||||
var clientHeight = this.$street.prop('clientHeight');
|
||||
|
||||
if (typeof lastHeight === 'undefined' && clientHeight === 0) {
|
||||
setTimeout(this.controlStreetTextareaHeight.bind(this), 10);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clientHeight === lastHeight) return;
|
||||
|
||||
if (scrollHeight > clientHeight + 1) {
|
||||
var rows = this.$street.prop('rows');
|
||||
this.$street.attr('rows', rows + 1);
|
||||
this.controlStreetTextareaHeight(clientHeight);
|
||||
}
|
||||
if (this.$street.val().length === 0) {
|
||||
this.$street.attr('rows', 1);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
],
|
||||
|
||||
validations: ['ready', 'required'],
|
||||
@@ -284,11 +285,8 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
|
||||
var preview = name;
|
||||
|
||||
switch (type) {
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/gif':
|
||||
preview = '<img src="' + this.getImageUrl(id, 'small') + '" title="' + name + '">';
|
||||
if (~this.previewTypeList.indexOf(type)) {
|
||||
preview = '<img src="' + this.getImageUrl(id, 'small') + '" title="' + name + '">';
|
||||
}
|
||||
|
||||
return preview;
|
||||
@@ -490,11 +488,8 @@ Espo.define('views/fields/attachment-multiple', 'views/fields/base', function (D
|
||||
},
|
||||
|
||||
isTypeIsImage: function (type) {
|
||||
switch (type) {
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/gif':
|
||||
return true;
|
||||
if (~this.previewTypeList.indexOf(type)) {
|
||||
return true;
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
@@ -247,7 +247,10 @@ Espo.define('views/fields/base', 'view', function (Dep) {
|
||||
this.getFieldManager().getParamList(this.type).forEach(function (d) {
|
||||
var name = d.name;
|
||||
if (!(name in this.params)) {
|
||||
this.params[name] = this.model.getFieldParam(this.name, name) || null;
|
||||
this.params[name] = this.model.getFieldParam(this.name, name);
|
||||
if (typeof this.params[name] === 'undefined') {
|
||||
this.params[name] = null;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
|
||||
|
||||
@@ -136,6 +136,8 @@ Espo.define('views/fields/email', 'views/fields/varchar', function (Dep) {
|
||||
data.valueIsSet = this.model.has(this.name);
|
||||
}
|
||||
|
||||
data.itemMaxLength = this.itemMaxLength;
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -385,8 +387,10 @@ Espo.define('views/fields/email', 'views/fields/varchar', function (Dep) {
|
||||
return;
|
||||
}
|
||||
|
||||
var viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.compose') || 'views/modals/compose-email';
|
||||
|
||||
this.notify('Loading...');
|
||||
this.createView('quickCreate', 'views/modals/compose-email', {
|
||||
this.createView('quickCreate', viewName, {
|
||||
attributes: attributes,
|
||||
}, function (view) {
|
||||
view.render();
|
||||
@@ -405,6 +409,8 @@ Espo.define('views/fields/email', 'views/fields/varchar', function (Dep) {
|
||||
this.erasedPlaceholder = 'ERASED:';
|
||||
|
||||
this.emailAddressOptedOutByDefault = this.getConfig().get('emailAddressIsOptedOutByDefault');
|
||||
|
||||
this.itemMaxLength = this.getMetadata().get(['entityDefs', 'EmailAddress', 'fields', 'name', 'maxLength']) || 255;
|
||||
},
|
||||
|
||||
fetchEmailAddressData: function () {
|
||||
|
||||
@@ -46,6 +46,7 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
],
|
||||
|
||||
defaultType: false,
|
||||
@@ -247,11 +248,8 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
var preview = name;
|
||||
|
||||
switch (type) {
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/gif':
|
||||
preview = '<a data-action="showImagePreview" data-id="' + id + '" href="' + this.getImageUrl(id) + '"><img src="'+this.getBasePath()+'?entryPoint=image&size='+this.previewSize+'&id=' + id + '" class="image-preview"></a>';
|
||||
if (~this.previewTypeList.indexOf(type)) {
|
||||
preview = '<a data-action="showImagePreview" data-id="' + id + '" href="' + this.getImageUrl(id) + '"><img src="'+this.getBasePath()+'?entryPoint=image&size='+this.previewSize+'&id=' + id + '" class="image-preview"></a>';
|
||||
}
|
||||
return preview;
|
||||
},
|
||||
@@ -260,11 +258,8 @@ Espo.define('views/fields/file', 'views/fields/link', function (Dep) {
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
var preview = name;
|
||||
|
||||
switch (type) {
|
||||
case 'image/png':
|
||||
case 'image/jpeg':
|
||||
case 'image/gif':
|
||||
preview = '<img src="' + this.getImageUrl(id, 'small') + '" title="' + name + '">';
|
||||
if (~this.previewTypeList.indexOf(type)) {
|
||||
preview = '<img src="' + this.getImageUrl(id, 'small') + '" title="' + name + '">';
|
||||
}
|
||||
|
||||
return preview;
|
||||
|
||||
@@ -126,6 +126,10 @@ Espo.define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
getMaxValue: function () {
|
||||
var maxValue = this.model.getFieldParam(this.name, 'max') || null;
|
||||
|
||||
if (!maxValue && maxValue !== 0) {
|
||||
maxValue = null;
|
||||
}
|
||||
|
||||
if ('max' in this.params) {
|
||||
maxValue = this.params.max;
|
||||
}
|
||||
@@ -134,7 +138,11 @@ Espo.define('views/fields/int', 'views/fields/base', function (Dep) {
|
||||
},
|
||||
|
||||
getMinValue: function () {
|
||||
var minValue = this.model.getFieldParam(this.name, 'min') || null;
|
||||
var minValue = this.model.getFieldParam(this.name, 'min');
|
||||
|
||||
if (!minValue && minValue !== 0) {
|
||||
minValue = null;
|
||||
}
|
||||
|
||||
if ('min' in this.params) {
|
||||
minValue = this.params.min;
|
||||
|
||||
@@ -57,7 +57,7 @@ Espo.define('views/fields/link-parent', 'views/fields/base', function (Dep) {
|
||||
searchTypeList: ['is', 'isEmpty', 'isNotEmpty'],
|
||||
|
||||
data: function () {
|
||||
var nameValue = this.model.get(this.nameName) ? this.model.get(this.nameName) : this.model.get(this.idName);
|
||||
var nameValue = this.model.get(this.nameName);
|
||||
if (!nameValue && this.model.get(this.idName) && this.model.get(this.typeName)) {
|
||||
nameValue = this.translate(this.model.get(this.typeName), 'scopeNames');
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ Espo.define('views/fields/link', 'views/fields/base', function (Dep) {
|
||||
if (nameValue === null) {
|
||||
nameValue = this.model.get(this.idName);
|
||||
}
|
||||
if (this.mode === 'detail' && !nameValue && this.model.get(this.idName)) {
|
||||
if (this.isReadMode() && !nameValue && this.model.get(this.idName)) {
|
||||
nameValue = this.translate(this.foreignScope, 'scopeNames');
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,8 @@ Espo.define('views/fields/phone', 'views/fields/varchar', function (Dep) {
|
||||
data.valueIsSet = this.model.has(this.name);
|
||||
}
|
||||
|
||||
data.itemMaxLength = this.itemMaxLength;
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -270,6 +272,8 @@ Espo.define('views/fields/phone', 'views/fields/varchar', function (Dep) {
|
||||
}
|
||||
|
||||
this.erasedPlaceholder = 'ERASED:';
|
||||
|
||||
this.itemMaxLength = this.getMetadata().get(['entityDefs', 'PhoneNumber', 'fields', 'name', 'maxLength']);
|
||||
},
|
||||
|
||||
fetchPhoneNumberData: function () {
|
||||
|
||||
@@ -56,7 +56,10 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) {
|
||||
'click a[data-action="seeMoreText"]': function (e) {
|
||||
this.seeMoreText = true;
|
||||
this.reRender();
|
||||
}
|
||||
},
|
||||
'click [data-action="mailTo"]': function (e) {
|
||||
this.mailTo($(e.currentTarget).data('email-address'));
|
||||
},
|
||||
},
|
||||
|
||||
setup: function () {
|
||||
@@ -181,7 +184,7 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) {
|
||||
afterRender: function () {
|
||||
Dep.prototype.afterRender.call(this);
|
||||
|
||||
if (this.mode === 'detail' || this.mode === 'list') {
|
||||
if (this.isReadMode()) {
|
||||
$(window).off('resize.see-more-' + this.cid);
|
||||
|
||||
this.$textContainer = this.$el.find('> .complex-text-container');
|
||||
@@ -190,6 +193,10 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) {
|
||||
|
||||
if (this.isCut()) {
|
||||
this.controlSeeMore();
|
||||
if (this.model.get(this.name) && this.$text.height() === 0) {
|
||||
this.$textContainer.addClass('cut');
|
||||
setTimeout(this.controlSeeMore.bind(this), 50);
|
||||
}
|
||||
|
||||
$(window).on('resize.see-more-' + this.cid, function () {
|
||||
this.controlSeeMore();
|
||||
@@ -283,8 +290,38 @@ Espo.define('views/fields/text', 'views/fields/base', function (Dep) {
|
||||
|
||||
getSearchType: function () {
|
||||
return this.getSearchParamsData().type || this.searchParams.typeFront || this.searchParams.type;
|
||||
}
|
||||
},
|
||||
|
||||
mailTo: function (emailAddress) {
|
||||
var attributes = {
|
||||
status: 'Draft',
|
||||
to: emailAddress
|
||||
};
|
||||
|
||||
if (
|
||||
this.getConfig().get('emailForceUseExternalClient') ||
|
||||
this.getPreferences().get('emailUseExternalClient') ||
|
||||
!this.getAcl().checkScope('Email', 'create')
|
||||
) {
|
||||
require('email-helper', function (EmailHelper) {
|
||||
var emailHelper = new EmailHelper();
|
||||
var link = emailHelper.composeMailToLink(attributes, this.getConfig().get('outboundEmailBccAddress'));
|
||||
document.location.href = link;
|
||||
}.bind(this));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var viewName = this.getMetadata().get('clientDefs.' + this.scope + '.modalViews.compose') || 'views/modals/compose-email';
|
||||
|
||||
this.notify('Loading...');
|
||||
this.createView('quickCreate', viewName, {
|
||||
attributes: attributes,
|
||||
}, function (view) {
|
||||
view.render();
|
||||
view.notify(false);
|
||||
});
|
||||
},
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -325,7 +325,8 @@ Espo.define('views/import/step2', 'view', function (Dep) {
|
||||
defs: {
|
||||
name: name,
|
||||
},
|
||||
mode: 'edit'
|
||||
mode: 'edit',
|
||||
readOnlyDisabled: true
|
||||
}, function (view) {
|
||||
this.additionalFields.push(name);
|
||||
view.render();
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
Espo.define('views/modals/image-preview', 'views/modal', function (Dep) {
|
||||
Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function (Dep) {
|
||||
|
||||
return Dep.extend({
|
||||
|
||||
@@ -40,6 +40,16 @@ Espo.define('views/modals/image-preview', 'views/modal', function (Dep) {
|
||||
|
||||
backdrop: true,
|
||||
|
||||
transformClassList: [
|
||||
'transform-flip',
|
||||
'transform-rotate-180',
|
||||
'transform-flip-and-rotate-180',
|
||||
'transform-flip-and-rotate-270',
|
||||
'transform-rotate-90',
|
||||
'transform-flip-and-rotate-90',
|
||||
'transform-rotate-270',
|
||||
],
|
||||
|
||||
data: function () {
|
||||
return {
|
||||
name: this.options.name,
|
||||
@@ -81,9 +91,46 @@ Espo.define('views/modals/image-preview', 'views/modal', function (Dep) {
|
||||
return url;
|
||||
},
|
||||
|
||||
onImageLoad: function () {
|
||||
console.log(1);
|
||||
},
|
||||
|
||||
afterRender: function () {
|
||||
$container = this.$el.find('.image-container');
|
||||
$img = this.$el.find('.image-container img');
|
||||
$img = this.$img = this.$el.find('.image-container img');
|
||||
|
||||
$img.on('load', function () {
|
||||
var self = this;
|
||||
EXIF.getData($img.get(0), function () {
|
||||
var orientation = EXIF.getTag(this, 'Orientation');
|
||||
switch (orientation) {
|
||||
case 2:
|
||||
$img.addClass('transform-flip');
|
||||
break;
|
||||
case 3:
|
||||
$img.addClass('transform-rotate-180');
|
||||
break;
|
||||
case 4:
|
||||
$img.addClass('transform-rotate-180');
|
||||
$img.addClass('transform-flip');
|
||||
break;
|
||||
case 5:
|
||||
$img.addClass('transform-rotate-270');
|
||||
$img.addClass('transform-flip');
|
||||
break;
|
||||
case 6:
|
||||
$img.addClass('transform-rotate-90');
|
||||
break;
|
||||
case 7:
|
||||
$img.addClass('transform-rotate-90');
|
||||
$img.addClass('transform-flip');
|
||||
break;
|
||||
case 8:
|
||||
$img.addClass('transform-rotate-270');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}.bind(this));
|
||||
|
||||
if (this.navigationEnabled) {
|
||||
$img.css('cursor', 'pointer');
|
||||
@@ -108,6 +155,11 @@ Espo.define('views/modals/image-preview', 'views/modal', function (Dep) {
|
||||
},
|
||||
|
||||
switchToNext: function () {
|
||||
|
||||
this.transformClassList.forEach(function (item) {
|
||||
this.$img.removeClass(item);
|
||||
}, this);
|
||||
|
||||
var index = -1;
|
||||
this.imageList.forEach(function (d, i) {
|
||||
if (d.id === this.options.id) {
|
||||
@@ -127,4 +179,3 @@ Espo.define('views/modals/image-preview', 'views/modal', function (Dep) {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -133,7 +133,9 @@ Espo.define('views/modals/related-list', ['views/modal', 'search-manager'], func
|
||||
|
||||
if (this.model) {
|
||||
this.header += Handlebars.Utils.escapeExpression(this.model.get('name'));
|
||||
this.header += ' » ';
|
||||
if (this.header) {
|
||||
this.header += ' » ';
|
||||
}
|
||||
}
|
||||
this.header += this.options.title || this.getLanguage().translate(this.link, 'links', this.model.name);
|
||||
|
||||
|
||||
@@ -230,32 +230,9 @@ Espo.define('views/note/fields/post', ['views/fields/text', 'lib!Textcomplete'],
|
||||
xhr.errorIsHandled = true;
|
||||
});
|
||||
}
|
||||
} else if (text.indexOf(siteUrl) === 0) {
|
||||
if (/\#[A-Z][a-zA-Z0-9]*\/view\/[a-zA-Z0-9]*$/.test(text)) {
|
||||
var match = /\#([A-Z][a-zA-Z0-9]*)\/view\/([a-zA-Z0-9]*)$/.exec(text)
|
||||
if (match.length === 3) {
|
||||
var entityType = match[1];
|
||||
var id = match[2];
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
var cursorStartPosition = this.$element.prop('selectionStart');
|
||||
var cursorEndPosition = this.$element.prop('selectionEnd');
|
||||
var text = this.$element.val();
|
||||
var textBefore = text.substring(0, cursorStartPosition);
|
||||
var textAfter = text.substring(cursorEndPosition, text.length);
|
||||
|
||||
var textToPaste = '['+entityType+'/'+id+'](#'+entityType+'/view/'+id+')';
|
||||
|
||||
this.$element.val(textBefore + textToPaste + textAfter);
|
||||
|
||||
this.controlTextareaHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -810,6 +810,13 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
}
|
||||
},
|
||||
|
||||
removeAllResultMassAction: function (item) {
|
||||
var index = this.checkAllResultMassActionList.indexOf(item);
|
||||
if (~index) {
|
||||
this.checkAllResultMassActionList.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
addMassAction: function (item, allResult) {
|
||||
this.massActionList.push(item);
|
||||
if (allResult) {
|
||||
@@ -918,6 +925,12 @@ Espo.define('views/record/list', 'view', function (Dep) {
|
||||
this.addMassAction('printPdf');
|
||||
}
|
||||
|
||||
if (this.collection.url !== this.entityType) {
|
||||
this.removeAllResultMassAction('massUpdate');
|
||||
this.removeAllResultMassAction('remove');
|
||||
this.removeAllResultMassAction('export');
|
||||
}
|
||||
|
||||
this.setupMassActionItems();
|
||||
|
||||
if (this.selectable) {
|
||||
|
||||
@@ -74,23 +74,46 @@ Espo.define('views/template/fields/variables', 'views/fields/base', function (De
|
||||
setupAttributeList: function () {
|
||||
var entityType = this.model.get('entityType');
|
||||
|
||||
var attributeList = this.getFieldManager().getEntityAttributes(entityType) || [];
|
||||
var fieldList = this.getFieldManager().getScopeFieldList(entityType);
|
||||
|
||||
var ignoreFieldList = [];
|
||||
fieldList.forEach(function (field) {
|
||||
if (
|
||||
this.getMetadata().get(['entityAcl', entityType, 'fields', field, 'onlyAdmin'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', entityType, 'fields', field, 'forbidden'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', entityType, 'fields', field, 'internal'])
|
||||
||
|
||||
this.getMetadata().get(['entityDefs', entityType, 'fields', field, 'disabled'])
|
||||
) ignoreFieldList.push(field);
|
||||
}, this);
|
||||
|
||||
var attributeList = this.getFieldManager().getScopeAttributeList(entityType) || [];
|
||||
|
||||
var forbiddenList = Espo.Utils.clone(this.getAcl().getScopeForbiddenAttributeList(entityType));
|
||||
|
||||
ignoreFieldList.forEach(function (field) {
|
||||
this.getFieldManager().getScopeFieldAttributeList(entityType, field).forEach(function (attribute) {
|
||||
forbiddenList.push(attribute);
|
||||
});
|
||||
}, this);
|
||||
|
||||
var forbiddenList = this.getAcl().getScopeForbiddenAttributeList(entityType);
|
||||
attributeList = attributeList.filter(function (item) {
|
||||
if (~forbiddenList.indexOf(item)) return;
|
||||
|
||||
var fieldType = this.getMetadata().get(['entityDefs', entityType, 'fields', item, 'type']);
|
||||
if (fieldType === 'map') return;
|
||||
|
||||
if (this.getMetadata().get(['entityDefs', entityType, 'fields', item, 'disabled'])) return;
|
||||
|
||||
return true;
|
||||
}, this);
|
||||
|
||||
|
||||
attributeList.push('id');
|
||||
if (this.getMetadata().get('entityDefs.' + entityType + '.fields.name.type') == 'personName') {
|
||||
attributeList.unshift('name');
|
||||
if (!~attributeList.indexOf('name')) {
|
||||
attributeList.unshift('name');
|
||||
}
|
||||
};
|
||||
attributeList = attributeList.sort(function (v1, v2) {
|
||||
return this.translate(v1, 'fields', entityType).localeCompare(this.translate(v2, 'fields', entityType));
|
||||
@@ -114,8 +137,6 @@ Espo.define('views/template/fields/variables', 'views/fields/base', function (De
|
||||
this.attributeList.unshift('today');
|
||||
}
|
||||
|
||||
attributeList.unshift('');
|
||||
|
||||
var links = this.getMetadata().get('entityDefs.' + entityType + '.links') || {};
|
||||
|
||||
var linkList = Object.keys(links).sort(function (v1, v2) {
|
||||
@@ -128,17 +149,45 @@ Espo.define('views/template/fields/variables', 'views/fields/base', function (De
|
||||
var scope = links[link].entity;
|
||||
if (!scope) return;
|
||||
|
||||
if (
|
||||
this.getMetadata().get(['entityAcl', entityType, 'links', link, 'onlyAdmin'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', entityType, 'links', link, 'forbidden'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', entityType, 'links', link, 'internal'])
|
||||
) return;
|
||||
|
||||
var fieldList = this.getFieldManager().getScopeFieldList(scope);
|
||||
|
||||
var ignoreFieldList = [];
|
||||
fieldList.forEach(function (field) {
|
||||
if (
|
||||
this.getMetadata().get(['entityAcl', scope, 'fields', field, 'onlyAdmin'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', scope, 'fields', field, 'forbidden'])
|
||||
||
|
||||
this.getMetadata().get(['entityAcl', scope, 'fields', field, 'internal'])
|
||||
||
|
||||
this.getMetadata().get(['entityDefs', scope, 'fields', field, 'disabled'])
|
||||
) ignoreFieldList.push(field);
|
||||
}, this);
|
||||
|
||||
var attributeList = this.getFieldManager().getEntityAttributes(scope) || [];
|
||||
|
||||
var forbiddenList = this.getAcl().getScopeForbiddenAttributeList(scope);
|
||||
var forbiddenList = Espo.Utils.clone(this.getAcl().getScopeForbiddenAttributeList(scope));
|
||||
|
||||
ignoreFieldList.forEach(function (field) {
|
||||
this.getFieldManager().getScopeFieldAttributeList(scope, field).forEach(function (attribute) {
|
||||
forbiddenList.push(attribute);
|
||||
});
|
||||
}, this);
|
||||
|
||||
attributeList = attributeList.filter(function (item) {
|
||||
if (~forbiddenList.indexOf(item)) return;
|
||||
|
||||
var fieldType = this.getMetadata().get(['entityDefs', scope, 'fields', item, 'type']);
|
||||
if (fieldType === 'map') return;
|
||||
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', item, 'disabled'])) return;
|
||||
|
||||
return true;
|
||||
}, this);
|
||||
|
||||
@@ -200,6 +249,11 @@ Espo.define('views/template/fields/variables', 'views/fields/base', function (De
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
|
||||
this.translatedOptions[item] = this.translate(baseField, 'fields', scope) + ' (' + this.translate('name', 'fields') + ')';
|
||||
}
|
||||
} else if (field.indexOf('Type') === field.length - 4) {
|
||||
var baseField = field.substr(0, field.length - 4);
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
|
||||
this.translatedOptions[item] = this.translate(baseField, 'fields', scope) + ' (' + this.translate('type', 'fields') + ')';
|
||||
}
|
||||
}
|
||||
|
||||
if (field.indexOf('Ids') === field.length - 3) {
|
||||
@@ -212,6 +266,11 @@ Espo.define('views/template/fields/variables', 'views/fields/base', function (De
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
|
||||
this.translatedOptions[item] = this.translate(baseField, 'fields', scope) + ' (' + this.translate('names', 'fields') + ')';
|
||||
}
|
||||
} else if (field.indexOf('Types') === field.length - 5) {
|
||||
var baseField = field.substr(0, field.length - 5);
|
||||
if (this.getMetadata().get(['entityDefs', scope, 'fields', baseField])) {
|
||||
this.translatedOptions[item] = this.translate(baseField, 'fields', scope) + ' (' + this.translate('types', 'fields') + ')';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -78,6 +78,12 @@ Espo.define('views/user/record/edit', ['views/record/edit', 'views/user/record/d
|
||||
this.hideField('passwordPreview');
|
||||
}
|
||||
}, this);
|
||||
|
||||
|
||||
this.listenTo(this.model, 'after:save', function () {
|
||||
this.model.unset('password', {silent: true});
|
||||
this.model.unset('passwordConfirm', {silent: true});
|
||||
}, this);
|
||||
},
|
||||
|
||||
setupNonAdminFieldsAccess: function () {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@navbar-width: 190px;
|
||||
@navbar-width: 200px;
|
||||
@navbar-minimized-width: 38px;
|
||||
@tob-bar-height: 30px;
|
||||
@top-navbar-bg-color: @main-gray;
|
||||
|
||||
@@ -108,6 +108,10 @@
|
||||
.clearfix();
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px solid @panel-default-border;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
font-size: @font-size-base;
|
||||
border-left-width: 3px;
|
||||
@@ -207,10 +211,14 @@ label {
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin: 10px 0;
|
||||
border-bottom: 0;
|
||||
padding-bottom: 2px;
|
||||
min-height: 33px;
|
||||
margin: 10px 0;
|
||||
border-bottom: 0;
|
||||
padding-bottom: 2px;
|
||||
min-height: 33px;
|
||||
|
||||
.page-header-column-1 {
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header > .row {
|
||||
@@ -921,6 +929,15 @@ table.table > tbody td > .list-row-buttons button {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.table.table-no-overflow {
|
||||
> tbody > tr > td,
|
||||
> tbody > tr > th,
|
||||
> thead > tr > th {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.list-row-buttons span.caret {
|
||||
border-top-color: @gray-light;
|
||||
}
|
||||
@@ -2830,4 +2847,33 @@ a.field-info > span.fa-info-circle {
|
||||
.grid-auto-fill-md {
|
||||
grid-template-columns: repeat(auto-fill, minmax(@grid-column-width-medium, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
.transform-flip {
|
||||
-webkit-transform: scaleX(-1);
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.transform-rotate-90 {
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.transform-rotate-180 {
|
||||
-webkit-transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
-o-transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.transform-rotate-270 {
|
||||
-webkit-transform: rotate(270deg);
|
||||
-moz-transform: rotate(270deg);
|
||||
-o-transform: rotate(270deg);
|
||||
-ms-transform: rotate(270deg);
|
||||
transform: rotate(270deg);
|
||||
}
|
||||
@@ -33,7 +33,7 @@
|
||||
<body>
|
||||
<div class="container content"></div>
|
||||
<footer>
|
||||
<p class="credit small">© 2018 <a href="https://www.espocrm.com" title="Powered by EspoCRM" alt="Visit official EspoCRM website">EspoCRM</a></p>
|
||||
<p class="credit small">© 2019 <a href="https://www.espocrm.com" title="Powered by EspoCRM" alt="Visit official EspoCRM website">EspoCRM</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -28,5 +28,5 @@
|
||||
************************************************************************/
|
||||
|
||||
return array(
|
||||
"INSERT INTO `job` (`id`, `name`, `deleted`, `status`, `execute_time`, `service_name`, `method`, `data`, `attempts`, `failed_attempts`, `created_at`, `modified_at`, `scheduled_job_id`, `target_id`, `target_type`) VALUES ('". uniqid() ."', 'Dummy', '0', 'Pending', '" . gmdate('Y-m-d H:i:s') . "', NULL, 'Dummy', NULL, '3', NULL, '" . gmdate('Y-m-d H:i:s') . "', '" . gmdate('Y-m-d H:i:s') . "', (SELECT id FROM scheduled_job WHERE deleted = 0 AND job = 'Dummy'), NULL, NULL);"
|
||||
"INSERT INTO `job` (`id`, `name`, `deleted`, `status`, `execute_time`, `method_name`, `created_at`, `modified_at`, `scheduled_job_id`) VALUES ('". uniqid() ."', 'Dummy', '0', 'Pending', '" . gmdate('Y-m-d H:i:s') . "', 'Dummy', '" . gmdate('Y-m-d H:i:s') . "', '" . gmdate('Y-m-d H:i:s') . "', (SELECT id FROM scheduled_job WHERE deleted = 0 AND job = 'Dummy'));"
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "espocrm",
|
||||
"version": "5.5.3",
|
||||
"version": "5.5.6",
|
||||
"description": "",
|
||||
"main": "index.php",
|
||||
"repository": {
|
||||
|
||||
Reference in New Issue
Block a user