Compare commits

...

64 Commits
5.5.3 ... 5.5.5

Author SHA1 Message Date
yuri
8d13668595 fix ea pn repositories 2019-01-09 14:46:01 +02:00
yuri
62dcc1f55b version 2019-01-09 14:16:08 +02:00
yuri
efb0c354a5 improve select manager checkWhere 2019-01-09 13:49:35 +02:00
yuri
09c9faba9c fix typo 2019-01-09 13:35:39 +02:00
yuri
c9a6bd2d64 select manager check link restricted 2019-01-09 13:35:03 +02:00
yuri
5d32485fc5 email/phone tpl fix 2019-01-09 13:15:20 +02:00
yuri
b4aa93a185 fix sumRelated 2019-01-09 12:30:09 +02:00
yuri
533faad8c2 footer year 2019-01-08 16:40:34 +02:00
yuri
3a24a45ade fix readme 2019-01-08 16:39:31 +02:00
yuri
d6a90990bd fix email fetching by date 2019-01-08 12:12:46 +02:00
yuri
5b2c6abf90 Merge branch 'hotfix/5.5.4' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.5.4 2019-01-08 11:44:15 +02:00
Taras Machyshyn
2839c134a0 FieldManager: defaultAttributes bug fixes 2019-01-08 11:43:58 +02:00
yuri
2c3ea3b59b side menu 200px 2019-01-08 11:29:51 +02:00
yuri
72c9f5dceb fix address street 2019-01-08 11:24:50 +02:00
yuri
9a71643bed field manager util list by type 2019-01-08 11:15:12 +02:00
yuri
ffd61a6844 fix link field 2019-01-08 10:54:15 +02:00
yuri
c66cded184 fix naming 2019-01-08 10:48:05 +02:00
yuri
6c2a23cf10 Merge branch 'hotfix/5.5.4' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.5.4 2019-01-08 10:36:49 +02:00
Taras Machyshyn
9877d918b4 FieldManager: afterSave hook bug fixes 2019-01-08 10:36:10 +02:00
yuri
8a5b845b2c git push github Merge branch 'hotfix/5.5.4' of github.com:espocrm/espocrm into hotfix/5.5.4 2019-01-08 10:31:28 +02:00
yuri
c2c98db80e fix import default read only field 2019-01-08 10:31:15 +02:00
barwi
54dc83aa59 order param fix (#1179)
Thanks
2019-01-05 12:27:39 +02:00
yuri
20560d5256 email template number format 2019-01-04 15:52:57 +02:00
yuri
21285aa51f fix related list header 2019-01-04 14:08:51 +02:00
yuri
76990ead8a import panel fix 2019-01-04 14:06:30 +02:00
yuri
48493f78c2 Merge branch 'hotfix/5.5.4' of ssh://172.20.0.1/var/git/espo/backend into hotfix/5.5.4 2019-01-04 12:07:20 +02:00
Taras Machyshyn
b99c06a8a4 Installation warning fix 2019-01-04 12:06:38 +02:00
yuri
563331af03 et variable label fix 2019-01-04 11:59:18 +02:00
yuri
09adf27190 template variable type 2019-01-04 11:55:27 +02:00
yuri
53c7d2ac85 currency not disabled field 2019-01-04 11:46:47 +02:00
yuri
fa0458d220 template variables fixes 2019-01-04 11:44:59 +02:00
yuri
29e0d93e4f lang 2019-01-04 11:31:21 +02:00
yuri
4f09cb3592 tempalte variables types label 2019-01-04 11:27:51 +02:00
yuri
187b76d359 template variables list filtering 2019-01-04 11:25:16 +02:00
yuri
428f26b02c fix email address storing 2019-01-04 11:03:35 +02:00
yuri
e976e627ad service massLink naming 2019-01-03 15:37:32 +02:00
yuri
48564ff8cb service link $link 2019-01-03 15:17:38 +02:00
yuri
f881d7a5c8 account filters layout change 2019-01-03 14:20:48 +02:00
yuri
5b9b345bf8 fix opp reports left join duplicates 2019-01-03 13:12:17 +02:00
yuri
c54e428d24 cs 2019-01-03 11:42:40 +02:00
yuri
2b69ac4651 fix dashlet options 2019-01-03 11:37:00 +02:00
yuri
3cd2f19ddf fix sales dashlet 2019-01-03 11:36:44 +02:00
yuri
5ed64d99f6 phone number dont store erased numeric 2019-01-03 11:08:54 +02:00
yuri
20acd516d6 orm sum fix 2019-01-03 11:04:07 +02:00
yuri
d30ef85c66 fix orm isLeft 2018-12-28 16:15:59 +02:00
yuri
79dcec028f v 2018-12-28 14:33:00 +02:00
yuri
17bd2f3324 fix text cut 2018-12-28 13:57:03 +02:00
yuri
f4b4f6ff89 load followers acl check 2018-12-28 13:10:16 +02:00
yuri
8dd675a275 fix and refactor email address and phone number repositoties 2018-12-28 12:34:45 +02:00
yuri
f4c59e70b5 email has attachment title 2018-12-27 17:38:04 +02:00
yuri
8da0ac369c address textaread height adjust 2018-12-27 15:29:35 +02:00
yuri
a361b124d6 more functions 2018-12-27 12:17:51 +02:00
yuri
3d2d54aafa add exif to jsLibs 2018-12-26 16:37:14 +02:00
yuri
28da462c4e exif js in about 2018-12-26 16:32:23 +02:00
yuri
94a6c8d525 image preview orientation 2018-12-26 16:29:15 +02:00
yuri
8fbfce086c refactoring image 2018-12-26 16:29:05 +02:00
yuri
73ac717c4d remove thumbs with attachment 2018-12-26 15:20:14 +02:00
yuri
3fb57a1dbe webp images support 2018-12-26 13:48:13 +02:00
yuri
af9718951f record service naming change 2018-12-26 12:54:58 +02:00
yuri
d40b7aef11 charts no data 2018-12-26 12:10:28 +02:00
yuri
54ac4b5308 scriptList in metadata 2018-12-26 11:35:05 +02:00
yuri
3ee28f8d48 cs fix 2018-12-26 11:22:20 +02:00
yuri
32f8a93021 fix image 2018-12-26 11:17:51 +02:00
yuri
39624e1ddb fix logo image access 2018-12-26 11:07:24 +02:00
65 changed files with 1474 additions and 839 deletions

View File

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

View File

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

View File

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

View File

@@ -378,7 +378,8 @@ class Container
{
return new \Espo\Core\Utils\ClientManager(
$this->get('config'),
$this->get('themeManager')
$this->get('themeManager'),
$this->get('metadata')
);
}

View File

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

View File

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

View File

@@ -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();
}
}
@@ -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));
@@ -1656,9 +1685,9 @@ class Base
public function addOrWhere($whereClause, &$result)
{
$result['whereClause'][] = array(
$result['whereClause'][] = [
'OR' => $whereClause
);
];
}
public function getFullTextSearchDataForTextFilter($textFilter, $isAuxiliaryUse = false)
@@ -2075,13 +2104,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);
}

View File

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

View File

@@ -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])) {

View File

@@ -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')
);
}
}

View File

@@ -66,7 +66,8 @@ class Metadata
protected $frontendHiddenPathList = [
['app', 'formula', 'functionClassNameMap'],
['app', 'fileStorage', 'implementationClassNameMap'],
['app', 'emailNotifications', 'handlerClassNameMap']
['app', 'emailNotifications', 'handlerClassNameMap'],
['app', 'client'],
];
/**

View File

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

View File

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

View File

@@ -21,6 +21,7 @@
"revenueConverted": "Revenue (converted)",
"budget": "Budget",
"budgetConverted": "Budget (converted)",
"budgetCurrency": "Budget Currency",
"contactsTemplate": "Contacts Template",
"leadsTemplate": "Leads Template",
"accountsTemplate": "Accounts Template",

View File

@@ -4,13 +4,14 @@
"createdAt",
"createdBy",
"modifiedAt",
"billingAddress",
"type",
"industry",
"description",
"emailAddress",
"industry",
"phoneNumber",
"targetLists",
"type",
"billingAddressCountry",
"billingAddress",
"shippingAddress",
"website"
]

View File

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

View File

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

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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))."
";
$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->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)

View File

@@ -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,271 @@ 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))."
";
$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->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 +473,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;

View File

@@ -352,6 +352,7 @@
"ids": "IDs",
"type": "Type",
"names": "Names",
"types": "Types",
"targetListIsOptedOut": "Is Opted Out (Target List)"
},
"links": {

View 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"
]
}

View File

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

View File

@@ -38,5 +38,10 @@
"path": "client/lib/bootstrap-colorpicker.js",
"exportsTo": "$",
"exportsAs": "colorpicker"
},
"exif": {
"path": "client/lib/exif-js.js",
"exportsTo": "window",
"exportsAs": "EXIF"
}
}

View File

@@ -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
}
]
},

View File

@@ -29,7 +29,9 @@
"fields":{
"currency":{
"type":"varchar",
"disabled": true,
"layoutDetailDisabled": true,
"layoutListDisabled": true,
"layoutMassUpdateDisabled": true,
"maxLength": 6
},
"converted":{

View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

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

View File

@@ -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)
@@ -197,20 +197,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)

View File

@@ -261,7 +261,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 +318,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 +794,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 +852,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 +951,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 +1292,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 +1327,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 +1354,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 +1393,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 +1420,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 +1455,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 +1693,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 +1742,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;
}
@@ -2238,7 +2256,7 @@ class Record extends \Espo\Core\Services\Base
{
}
protected function findLinkedEntitiesFollowers($id, $params)
protected function findLinkedFollowers($id, $params)
{
$maxSize = 0;

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -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);
}
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@
<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>

View File

@@ -1 +1 @@
<p class="credit small">&copy; 2018 <a href="https://www.espocrm.com">EspoCRM</a></p>
<p class="credit small">&copy; 2019 <a href="https://www.espocrm.com">EspoCRM</a></p>

View File

@@ -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));
},

View File

@@ -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';
}

View File

@@ -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;
},

View File

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

View File

@@ -334,19 +334,31 @@ 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);
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);
}
},

View File

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

View File

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

View File

@@ -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');
}

View File

@@ -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');
}

View File

@@ -181,7 +181,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 +190,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();

View File

@@ -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();

View File

@@ -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) {
});
});

View File

@@ -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 += ' &raquo ';
if (this.header) {
this.header += ' &raquo ';
}
}
this.header += this.options.title || this.getLanguage().translate(this.link, 'links', this.model.name);

View File

@@ -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') + ')';
}
}

View File

@@ -1,4 +1,4 @@
@navbar-width: 190px;
@navbar-width: 200px;
@navbar-minimized-width: 38px;
@tob-bar-height: 30px;
@top-navbar-bg-color: @main-gray;

View File

@@ -2830,4 +2830,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);
}

View File

@@ -33,7 +33,7 @@
<body>
<div class="container content"></div>
<footer>
<p class="credit small">&copy; 2018 <a href="https://www.espocrm.com" title="Powered by EspoCRM" alt="Visit official EspoCRM website">EspoCRM</a></p>
<p class="credit small">&copy; 2019 <a href="https://www.espocrm.com" title="Powered by EspoCRM" alt="Visit official EspoCRM website">EspoCRM</a></p>
</footer>
</body>
</html>

View File

@@ -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'));"
);

View File

@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "5.5.3",
"version": "5.5.5",
"description": "",
"main": "index.php",
"repository": {