mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-09 21:07:00 +00:00
Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6a90990bd | ||
|
|
5b2c6abf90 | ||
|
|
2839c134a0 | ||
|
|
2c3ea3b59b | ||
|
|
72c9f5dceb | ||
|
|
9a71643bed | ||
|
|
ffd61a6844 | ||
|
|
c66cded184 | ||
|
|
6c2a23cf10 | ||
|
|
9877d918b4 | ||
|
|
8a5b845b2c | ||
|
|
c2c98db80e | ||
|
|
54dc83aa59 | ||
|
|
20560d5256 | ||
|
|
21285aa51f | ||
|
|
76990ead8a | ||
|
|
48493f78c2 | ||
|
|
b99c06a8a4 | ||
|
|
563331af03 | ||
|
|
09adf27190 | ||
|
|
53c7d2ac85 | ||
|
|
fa0458d220 | ||
|
|
29e0d93e4f | ||
|
|
4f09cb3592 | ||
|
|
187b76d359 | ||
|
|
428f26b02c | ||
|
|
e976e627ad | ||
|
|
48564ff8cb | ||
|
|
f881d7a5c8 | ||
|
|
5b9b345bf8 | ||
|
|
c54e428d24 | ||
|
|
2b69ac4651 | ||
|
|
3cd2f19ddf | ||
|
|
5ed64d99f6 | ||
|
|
20acd516d6 | ||
|
|
d30ef85c66 | ||
|
|
79dcec028f | ||
|
|
17bd2f3324 | ||
|
|
f4b4f6ff89 | ||
|
|
8dd675a275 | ||
|
|
f4c59e70b5 | ||
|
|
8da0ac369c | ||
|
|
a361b124d6 | ||
|
|
3d2d54aafa | ||
|
|
28da462c4e | ||
|
|
94a6c8d525 | ||
|
|
8fbfce086c | ||
|
|
73ac717c4d | ||
|
|
3fb57a1dbe | ||
|
|
af9718951f | ||
|
|
d40b7aef11 | ||
|
|
54ac4b5308 | ||
|
|
3ee28f8d48 | ||
|
|
32f8a93021 | ||
|
|
39624e1ddb | ||
|
|
c010865fe0 | ||
|
|
aaeb905dcf | ||
|
|
ad8b954401 | ||
|
|
ef9f145beb | ||
|
|
f512bbc9fa | ||
|
|
b8f5fe2b21 | ||
|
|
be73390fde | ||
|
|
17cd2bc543 | ||
|
|
74712ba931 | ||
|
|
0d22a238dd | ||
|
|
56e9170a6b | ||
|
|
b333fd6772 | ||
|
|
4eb469eb59 | ||
|
|
4ff0d3c654 | ||
|
|
4f1dd0673e | ||
|
|
caf05b5c26 | ||
|
|
2643ab7a17 | ||
|
|
6caea136a5 | ||
|
|
1d23d65910 | ||
|
|
8cc3de5807 | ||
|
|
9b022df709 | ||
|
|
aba061cb72 | ||
|
|
ae4d725595 | ||
|
|
6a76dc41d1 | ||
|
|
2b649c64a3 | ||
|
|
db2944cb15 | ||
|
|
3d40184373 | ||
|
|
e788c51ff3 | ||
|
|
d7fc389182 | ||
|
|
ecafbe823e | ||
|
|
8abde38ebf | ||
|
|
0fd7f768a9 | ||
|
|
9c38d68b65 | ||
|
|
f0de5dde53 | ||
|
|
2cdbf0ba76 | ||
|
|
34e4f4d7c2 | ||
|
|
61c0b58798 | ||
|
|
b2db0ea683 | ||
|
|
bd0fb02bb2 | ||
|
|
f0aed23416 | ||
|
|
65368519c7 | ||
|
|
53db6f22d6 | ||
|
|
5d47a987f4 | ||
|
|
a1f03e22d4 | ||
|
|
cddaa7b7a1 | ||
|
|
58dee36f1b | ||
|
|
ae24fc07ba | ||
|
|
13ca0db761 | ||
|
|
d2b20154de | ||
|
|
b176d866d0 | ||
|
|
07a9a79626 | ||
|
|
b60da458d5 | ||
|
|
40ffbc550f | ||
|
|
35076148cc | ||
|
|
3175ff9a76 | ||
|
|
81457b1e48 | ||
|
|
e9d5472d72 | ||
|
|
7c4bd371e7 | ||
|
|
98ed7e4dd3 | ||
|
|
8404ef1d83 | ||
|
|
0d99d84adf | ||
|
|
8492962923 | ||
|
|
99627f856f | ||
|
|
b917cc20f6 | ||
|
|
c582e88b7b | ||
|
|
9a00603600 | ||
|
|
550b5e3bb4 | ||
|
|
5ceb57a2cf | ||
|
|
16e96f6454 | ||
|
|
c162ee5337 | ||
|
|
66d34e7035 | ||
|
|
f57f7f72ed | ||
|
|
c911cfe310 | ||
|
|
223c8b53b4 | ||
|
|
add1154014 | ||
|
|
6ede731cfe | ||
|
|
0619cc0e3d | ||
|
|
06a686ad1a | ||
|
|
15aed06f9d | ||
|
|
012936dc7d | ||
|
|
0fb5b4a323 | ||
|
|
dc3cfb4c23 | ||
|
|
450e7894aa | ||
|
|
b8e7dbd147 | ||
|
|
a41c53d7f4 | ||
|
|
419faa2a47 | ||
|
|
1d3b181a1c | ||
|
|
e0d4ec4a28 | ||
|
|
f95d8cbdde | ||
|
|
e22c9151d1 | ||
|
|
f6ae58a05d | ||
|
|
cb3fc1c951 | ||
|
|
7f84fd2b4c | ||
|
|
518a67bf8a | ||
|
|
cc6af53eff |
@@ -9,7 +9,7 @@ Download the latest release from our [website](http://www.espocrm.com).
|
||||
### Requirements
|
||||
|
||||
* PHP 5.6 or above (with pdo, json, gd, openssl, zip, imap, mbstring, curl extensions);
|
||||
* MySQL 5.5.3 or above.
|
||||
* MySQL 5.5.3 or above, or MariaDB.
|
||||
|
||||
For more information about server configuration see [this article](https://www.espocrm.com/documentation/administration/server-configuration/).
|
||||
|
||||
@@ -52,6 +52,12 @@ The build will be created in the `build` directory.
|
||||
|
||||
Before we can merge your pull request you need to accept our CLA [here](https://github.com/espocrm/cla). It's very simple to do.
|
||||
|
||||
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.
|
||||
|
||||
### How to make a translation
|
||||
|
||||
Build po file with command:
|
||||
|
||||
@@ -42,11 +42,13 @@ class EmailAddress extends \Espo\Core\Controllers\Record
|
||||
throw new Forbidden();
|
||||
}
|
||||
$q = $request->get('q');
|
||||
$limit = intval($request->get('limit'));
|
||||
if (empty($limit) || $limit > 30) {
|
||||
$limit = 5;
|
||||
$maxSize = intval($request->get('maxSize'));
|
||||
if (empty($maxSize) || $maxSize > 50) {
|
||||
$maxSize = $this->getConfig()->get('recordsPerPage', 20);
|
||||
}
|
||||
return $this->getRecordService()->searchInAddressBook($q, $limit);
|
||||
|
||||
$onlyActual = $request->get('onlyActual') === 'true';
|
||||
|
||||
return $this->getRecordService()->searchInAddressBook($q, $maxSize, $onlyActual);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,15 +78,18 @@ class Layout extends \Espo\Core\Controllers\Base
|
||||
return $this->actionUpdate($params, $data, $request);
|
||||
}
|
||||
|
||||
public function actionResetToDefault($params, $data, $request)
|
||||
public function postActionResetToDefault($params, $data, $request)
|
||||
{
|
||||
if (!$request->isPost()) {
|
||||
throw new BadRequest();
|
||||
if (!$this->getUser()->isAdmin()) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if (empty($data->scope) || empty($data->name)) {
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$this->getContainer()->get('dataManager')->updateCacheTimestamp();
|
||||
|
||||
return $this->getContainer()->get('layout')->resetToDefault($data->scope, $data->name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,23 +35,12 @@ use \Espo\Core\Exceptions\BadRequest;
|
||||
|
||||
class Settings extends \Espo\Core\Controllers\Base
|
||||
{
|
||||
|
||||
protected function getConfigData()
|
||||
{
|
||||
if ($this->getUser()->id == 'system') {
|
||||
$data = $this->getConfig()->getData();
|
||||
} else {
|
||||
$data = $this->getConfig()->getData($this->getUser()->isAdmin());
|
||||
}
|
||||
$data = $this->getServiceFactory()->create('Settings')->getConfigData();
|
||||
|
||||
$fieldDefs = $this->getMetadata()->get('entityDefs.Settings.fields');
|
||||
|
||||
foreach ($fieldDefs as $field => $d) {
|
||||
if ($d['type'] === 'password') {
|
||||
unset($data[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
$data['jsLibs'] = $this->getMetadata()->get('app.jsLibs');
|
||||
$data->jsLibs = $this->getMetadata()->get('app.jsLibs');
|
||||
|
||||
return $data;
|
||||
}
|
||||
@@ -76,41 +65,7 @@ class Settings extends \Espo\Core\Controllers\Base
|
||||
throw new BadRequest();
|
||||
}
|
||||
|
||||
$ignoreItemList = [];
|
||||
|
||||
$systemOnlyItemList = $this->getConfig()->getSystemOnlyItemList();
|
||||
foreach ($systemOnlyItemList as $item) {
|
||||
$ignoreItemList[] = $item;
|
||||
}
|
||||
|
||||
if ($this->getConfig()->get('restrictedMode') && !$this->getUser()->isSuperAdmin()) {
|
||||
$superAdminOnlyItemList = $this->getConfig()->getSuperAdminOnlyItemList();
|
||||
foreach ($superAdminOnlyItemList as $item) {
|
||||
$ignoreItemList[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ignoreItemList as $item) {
|
||||
unset($data->$item);
|
||||
}
|
||||
|
||||
if (
|
||||
(isset($data->useCache) && $data->useCache !== $this->getConfig()->get('useCache'))
|
||||
||
|
||||
(isset($data->aclStrictMode) && $data->aclStrictMode !== $this->getConfig()->get('aclStrictMode'))
|
||||
) {
|
||||
$this->getContainer()->get('dataManager')->clearCache();
|
||||
}
|
||||
|
||||
$this->getConfig()->setData($data, $this->getUser()->isAdmin());
|
||||
$result = $this->getConfig()->save();
|
||||
if ($result === false) {
|
||||
throw new Error('Cannot save settings');
|
||||
}
|
||||
|
||||
if (isset($data->defaultCurrency) || isset($data->baseCurrency) || isset($data->currencyRates)) {
|
||||
$this->getContainer()->get('dataManager')->rebuildDatabase([]);
|
||||
}
|
||||
$this->getServiceFactory()->create('Settings')->setConfigData($data);
|
||||
|
||||
return $this->getConfigData();
|
||||
}
|
||||
|
||||
@@ -378,7 +378,8 @@ class Container
|
||||
{
|
||||
return new \Espo\Core\Utils\ClientManager(
|
||||
$this->get('config'),
|
||||
$this->get('themeManager')
|
||||
$this->get('themeManager'),
|
||||
$this->get('metadata')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class Record extends Base
|
||||
public function actionRead($params, $data, $request)
|
||||
{
|
||||
$id = $params['id'];
|
||||
$entity = $this->getRecordService()->readEntity($id);
|
||||
$entity = $this->getRecordService()->read($id);
|
||||
|
||||
if (empty($entity)) {
|
||||
throw new NotFound();
|
||||
@@ -95,7 +95,7 @@ class Record extends Base
|
||||
|
||||
$service = $this->getRecordService();
|
||||
|
||||
if ($entity = $service->createEntity($data)) {
|
||||
if ($entity = $service->create($data)) {
|
||||
return $entity->getValueMap();
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ class Record extends Base
|
||||
|
||||
$id = $params['id'];
|
||||
|
||||
if ($entity = $this->getRecordService()->updateEntity($id, $data)) {
|
||||
if ($entity = $this->getRecordService()->update($id, $data)) {
|
||||
return $entity->getValueMap();
|
||||
}
|
||||
|
||||
@@ -140,12 +140,12 @@ class Record extends Base
|
||||
throw new Forbidden("Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
$result = $this->getRecordService()->findEntities($params);
|
||||
$result = $this->getRecordService()->find($params);
|
||||
|
||||
return array(
|
||||
return [
|
||||
'total' => $result['total'],
|
||||
'list' => isset($result['collection']) ? $result['collection']->getValueMapList() : $result['list']
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
public function getActionListKanban($params, $data, $request)
|
||||
@@ -195,7 +195,7 @@ class Record extends Base
|
||||
throw new Forbidden("Max size should should not exceed " . $maxSizeLimit . ". Use offset and limit.");
|
||||
}
|
||||
|
||||
$result = $this->getRecordService()->findLinkedEntities($id, $link, $params);
|
||||
$result = $this->getRecordService()->findLinked($id, $link, $params);
|
||||
|
||||
return array(
|
||||
'total' => $result['total'],
|
||||
@@ -211,7 +211,7 @@ class Record extends Base
|
||||
|
||||
$id = $params['id'];
|
||||
|
||||
if ($this->getRecordService()->deleteEntity($id)) {
|
||||
if ($this->getRecordService()->delete($id)) {
|
||||
return true;
|
||||
}
|
||||
throw new Error();
|
||||
@@ -262,9 +262,9 @@ class Record extends Base
|
||||
$params['format'] = $data->format;
|
||||
}
|
||||
|
||||
return array(
|
||||
return [
|
||||
'id' => $this->getRecordService()->export($params)
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
public function actionMassUpdate($params, $data, $request)
|
||||
@@ -322,7 +322,7 @@ class Record extends Base
|
||||
$params['ids'] = $data->ids;
|
||||
}
|
||||
|
||||
return $this->getRecordService()->massRemove($params);
|
||||
return $this->getRecordService()->massDelete($params);
|
||||
}
|
||||
|
||||
public function actionCreateLink($params, $data, $request)
|
||||
@@ -349,7 +349,7 @@ class Record extends Base
|
||||
$selectData = json_decode(json_encode($data->selectData), true);
|
||||
}
|
||||
|
||||
return $this->getRecordService()->linkEntityMass($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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,15 @@ class CronManager
|
||||
|
||||
private $useProcessPool = false;
|
||||
|
||||
private $asSoonAsPossibleSchedulingList = [
|
||||
'*',
|
||||
'* *',
|
||||
'* * *',
|
||||
'* * * *',
|
||||
'* * * * *',
|
||||
'* * * * * *'
|
||||
];
|
||||
|
||||
const PENDING = 'Pending';
|
||||
|
||||
const READY = 'Ready';
|
||||
@@ -185,7 +194,7 @@ class CronManager
|
||||
|
||||
$this->setLastRunTime(time());
|
||||
|
||||
$this->getCronJobUtil()->markFailedJobs();
|
||||
$this->getCronJobUtil()->markJobsFailed();
|
||||
$this->getCronJobUtil()->updateFailedJobAttempts();
|
||||
$this->createJobsFromScheduledJobs();
|
||||
$this->getCronJobUtil()->removePendingJobDuplicates();
|
||||
@@ -231,6 +240,9 @@ class CronManager
|
||||
if (!$noLock) $this->unlockTables();
|
||||
continue;
|
||||
}
|
||||
|
||||
$job->set('startedAt', date('Y-m-d H:i:s'));
|
||||
|
||||
if ($useProcessPool) {
|
||||
$job->set('status', self::READY);
|
||||
} else {
|
||||
@@ -276,6 +288,10 @@ class CronManager
|
||||
throw new Error("Can't run job {$id} with no status Ready.");
|
||||
}
|
||||
|
||||
if (!$job->get('startedAt')) {
|
||||
$job->set('startedAt', date('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
$job->set('status', self::RUNNING);
|
||||
$job->set('pid', \Espo\Core\Utils\System::getPid());
|
||||
$this->getEntityManager()->saveEntity($job);
|
||||
@@ -388,7 +404,7 @@ class CronManager
|
||||
$createdJobIdList = [];
|
||||
foreach ($activeScheduledJobList as $scheduledJob) {
|
||||
$scheduling = $scheduledJob->get('scheduling');
|
||||
$asSoonAsPossible = $scheduling === '* * * * *';
|
||||
$asSoonAsPossible = in_array($scheduling, $this->asSoonAsPossibleSchedulingList);
|
||||
|
||||
if ($asSoonAsPossible) {
|
||||
$nextDate = date('Y-m-d H:i:s');
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2018 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
|
||||
* Website: http://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Formula\Functions\StringGroup;
|
||||
|
||||
use \Espo\Core\Exceptions\Error;
|
||||
|
||||
class TestType extends \Espo\Core\Formula\Functions\Base
|
||||
{
|
||||
public function process(\StdClass $item)
|
||||
{
|
||||
if (!property_exists($item, 'value') || !is_array($item->value)) {
|
||||
throw new Error('Value for \'String\\Test\' item is not an array.');
|
||||
}
|
||||
if (count($item->value) < 2) {
|
||||
throw new Error('Bad arguments passed to \'String\\Test\'.');
|
||||
}
|
||||
$string = $this->evaluate($item->value[0]);
|
||||
$regexp = $this->evaluate($item->value[1]);
|
||||
|
||||
if (!is_string($string)) {
|
||||
return false;
|
||||
}
|
||||
if (!is_string($regexp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!preg_match($regexp, $string);
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,6 @@ class Htmlizer
|
||||
continue;
|
||||
}
|
||||
if (in_array($attribute, $skipAttributeList)) {
|
||||
unset($data[$attribute]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -474,6 +474,7 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
|
||||
if ($entity->isNew()) {
|
||||
$skipRemove = true;
|
||||
$skipUpdate = true;
|
||||
}
|
||||
|
||||
if ($entity->has($idListAttribute)) {
|
||||
@@ -503,19 +504,21 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable
|
||||
$defs['additionalColumns'] = $columns;
|
||||
}
|
||||
|
||||
$foreignEntityList = $entity->get($name, $defs);
|
||||
if ($foreignEntityList) {
|
||||
foreach ($foreignEntityList as $foreignEntity) {
|
||||
$existingIdList[] = $foreignEntity->id;
|
||||
if (!empty($columns)) {
|
||||
$data = (object)[];
|
||||
foreach ($columns as $columnName => $columnField) {
|
||||
$foreignId = $foreignEntity->id;
|
||||
$data->$columnName = $foreignEntity->get($columnField);
|
||||
}
|
||||
$existingColumnsData->$foreignId = $data;
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($columnsAttribute, $existingColumnsData);
|
||||
if (!$skipRemove && !$skipUpdate) {
|
||||
$foreignEntityList = $entity->get($name, $defs);
|
||||
if ($foreignEntityList) {
|
||||
foreach ($foreignEntityList as $foreignEntity) {
|
||||
$existingIdList[] = $foreignEntity->id;
|
||||
if (!empty($columns)) {
|
||||
$data = (object)[];
|
||||
foreach ($columns as $columnName => $columnField) {
|
||||
$foreignId = $foreignEntity->id;
|
||||
$data->$columnName = $foreignEntity->get($columnField);
|
||||
}
|
||||
$existingColumnsData->$foreignId = $data;
|
||||
if (!$entity->isNew()) {
|
||||
$entity->setFetched($columnsAttribute, $existingColumnsData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,9 +96,12 @@ class Application extends \Espo\Core\Application
|
||||
|
||||
public function runClient()
|
||||
{
|
||||
$this->getContainer()->get('clientManager')->display(null, 'html/portal.html', array(
|
||||
'portalId' => $this->getPortal()->id
|
||||
));
|
||||
$this->getContainer()->get('clientManager')->display(null, null, [
|
||||
'portalId' => $this->getPortal()->id,
|
||||
'applicationId' => $this->getPortal()->id,
|
||||
'apiUrl' => 'api/v1/portal-access/' . $this->getPortal()->id,
|
||||
'appClientClassName' => 'app-portal'
|
||||
]);
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +225,8 @@ class Base
|
||||
$whereClause = $this->convertWhere($where, false, $result);
|
||||
|
||||
$result['whereClause'] = array_merge($result['whereClause'], $whereClause);
|
||||
|
||||
$this->applyLeftJoinsFromWhere($where, $result);
|
||||
}
|
||||
|
||||
public function convertWhere(array $where, $ignoreAdditionaFilterTypes = false, &$result = null)
|
||||
@@ -269,7 +271,7 @@ class Base
|
||||
|
||||
protected function applyLinkedWith($link, $idsValue, &$result)
|
||||
{
|
||||
$part = array();
|
||||
$part = [];
|
||||
|
||||
if (is_array($idsValue) && count($idsValue) == 1) {
|
||||
$idsValue = $idsValue[0];
|
||||
@@ -342,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;
|
||||
}
|
||||
@@ -417,7 +419,7 @@ class Base
|
||||
|
||||
public function getEmptySelectParams()
|
||||
{
|
||||
$result = array();
|
||||
$result = [];
|
||||
$this->prepareResult($result);
|
||||
|
||||
return $result;
|
||||
@@ -435,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'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,9 +488,9 @@ class Base
|
||||
|
||||
protected function accessNo(&$result)
|
||||
{
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'id' => null
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
protected function accessOnlyOwn(&$result)
|
||||
@@ -496,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
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,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');
|
||||
|
||||
@@ -586,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');
|
||||
@@ -621,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
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -650,13 +652,13 @@ class Base
|
||||
}
|
||||
|
||||
if (!empty($d)) {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'OR' => $d
|
||||
);
|
||||
];
|
||||
} else {
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'id' => null
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,7 +692,7 @@ class Base
|
||||
|
||||
public function getAclParams()
|
||||
{
|
||||
$result = array();
|
||||
$result = [];
|
||||
$this->applyAccess($result);
|
||||
return $result;
|
||||
}
|
||||
@@ -841,11 +843,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));
|
||||
@@ -854,10 +856,12 @@ class Base
|
||||
case 'today':
|
||||
$where['type'] = 'between';
|
||||
$dt->setTime(0, 0, 0);
|
||||
$dtTo = clone $dt;
|
||||
$dtTo->modify('+1 day -1 second');
|
||||
$dt->setTimezone(new \DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new \DateTimeZone('UTC'));
|
||||
$from = $dt->format($format);
|
||||
$dt->modify('+1 day -1 second');
|
||||
$to = $dt->format($format);
|
||||
$to = $dtTo->format($format);
|
||||
$where['value'] = [$from, $to];
|
||||
break;
|
||||
case 'past':
|
||||
@@ -942,12 +946,13 @@ class Base
|
||||
break;
|
||||
case 'on':
|
||||
$where['type'] = 'between';
|
||||
|
||||
$dt = new \DateTime($value, new \DateTimeZone($timeZone));
|
||||
$dtTo = clone $dt;
|
||||
$dtTo->modify('+1 day -1 second');
|
||||
$dt->setTimezone(new \DateTimeZone('UTC'));
|
||||
$dtTo->setTimezone(new \DateTimeZone('UTC'));
|
||||
$from = $dt->format($format);
|
||||
$dt->modify('+1 day -1 second');
|
||||
$to = $dt->format($format);
|
||||
$to = $dtTo->format($format);
|
||||
$where['value'] = [$from, $to];
|
||||
break;
|
||||
case 'before':
|
||||
@@ -959,6 +964,7 @@ class Base
|
||||
case 'after':
|
||||
$where['type'] = 'after';
|
||||
$dt = new \DateTime($value, new \DateTimeZone($timeZone));
|
||||
$dt->modify('+1 day -1 second');
|
||||
$dt->setTimezone(new \DateTimeZone('UTC'));
|
||||
$where['value'] = $dt->format($format);
|
||||
break;
|
||||
@@ -1650,9 +1656,9 @@ class Base
|
||||
|
||||
public function addOrWhere($whereClause, &$result)
|
||||
{
|
||||
$result['whereClause'][] = array(
|
||||
$result['whereClause'][] = [
|
||||
'OR' => $whereClause
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
public function getFullTextSearchDataForTextFilter($textFilter, $isAuxiliaryUse = false)
|
||||
@@ -2047,4 +2053,38 @@ class Base
|
||||
|
||||
return $selectParams1;
|
||||
}
|
||||
|
||||
protected function applyLeftJoinsFromWhere($where, &$result)
|
||||
{
|
||||
if (!is_array($where)) return;
|
||||
|
||||
foreach ($where as $item) {
|
||||
$this->applyLeftJoinsFromWhereItem($item, $result);
|
||||
}
|
||||
}
|
||||
|
||||
protected function applyLeftJoinsFromWhereItem($item, &$result)
|
||||
{
|
||||
if (!empty($item['type'])) {
|
||||
if (in_array($item['type'], ['or', 'and', 'not', 'having'])) {
|
||||
if (!array_key_exists('value', $item) || !is_array($item['value'])) return;
|
||||
foreach ($item['value'] as $listItem) {
|
||||
$this->applyLeftJoinsFromWhereItem($listItem, $result);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$attibute = null;
|
||||
if (!empty($item['attribute'])) $attibute = $item['attribute'];
|
||||
if (!$attibute) return;
|
||||
|
||||
$attributeType = $this->getSeed()->getAttributeType($attibute);
|
||||
if ($attributeType === 'foreign') {
|
||||
$relation = $this->getSeed()->getAttributeParam($attibute, 'relation');
|
||||
if ($relation) {
|
||||
$this->addLeftJoin($relation, $result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ abstract class Base implements Injectable
|
||||
'config',
|
||||
'entityManager',
|
||||
'user',
|
||||
'serviceFactory'
|
||||
);
|
||||
|
||||
protected $injections = array();
|
||||
@@ -95,5 +96,9 @@ abstract class Base implements Injectable
|
||||
{
|
||||
return $this->getInjection('user');
|
||||
}
|
||||
}
|
||||
|
||||
protected function getServiceFactory()
|
||||
{
|
||||
return $this->getInjection('serviceFactory');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,6 +583,17 @@ abstract class Base
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getManifestParam($name, $default = null)
|
||||
{
|
||||
$manifest = $this->getManifest();
|
||||
|
||||
if (array_key_exists($name, $manifest)) {
|
||||
return $manifest[$name];
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unzip a package archieve
|
||||
*
|
||||
@@ -726,4 +737,4 @@ abstract class Base
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,14 +109,18 @@ class Install extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$this->afterRunAction();
|
||||
|
||||
$this->clearCache();
|
||||
$this->finalize();
|
||||
|
||||
/* delete unziped files */
|
||||
$this->deletePackageFiles();
|
||||
|
||||
$this->finalize();
|
||||
if ($this->getManifestParam('skipBackup')) {
|
||||
$this->getFileManager()->removeInDir($this->getPath('backupPath'), true);
|
||||
}
|
||||
|
||||
$GLOBALS['log']->debug('Installation process ['.$processId.']: end run.');
|
||||
|
||||
$this->clearCache();
|
||||
}
|
||||
|
||||
protected function restoreFiles()
|
||||
|
||||
@@ -59,16 +59,15 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$backupPath = $this->getPath('backupPath');
|
||||
if (file_exists($backupPath)) {
|
||||
|
||||
/* copy core files */
|
||||
if (!$this->copyFiles()) {
|
||||
$this->throwErrorAndRemovePackage('Cannot copy files.');
|
||||
}
|
||||
}
|
||||
|
||||
/* remove extension files, saved in fileList */
|
||||
if (!$this->deleteFiles('delete', true)) {
|
||||
$this->throwErrorAndRemovePackage('Permission denied to delete files.');
|
||||
}
|
||||
/* remove extension files, saved in fileList */
|
||||
if (!$this->deleteFiles('delete', true)) {
|
||||
$this->throwErrorAndRemovePackage('Permission denied to delete files.');
|
||||
}
|
||||
|
||||
if (!isset($data['skipSystemRebuild']) || !$data['skipSystemRebuild']) {
|
||||
@@ -84,14 +83,14 @@ class Uninstall extends \Espo\Core\Upgrades\Actions\Base
|
||||
|
||||
$this->afterRunAction();
|
||||
|
||||
$this->clearCache();
|
||||
|
||||
/* delete backup files */
|
||||
$this->deletePackageFiles();
|
||||
|
||||
$this->finalize();
|
||||
|
||||
$GLOBALS['log']->debug('Uninstallation process ['.$processId.']: end run.');
|
||||
|
||||
$this->clearCache();
|
||||
}
|
||||
|
||||
protected function restoreFiles()
|
||||
|
||||
@@ -123,7 +123,7 @@ class LDAP extends Espo
|
||||
if ($isPortal) {
|
||||
$useLdapAuthForPortalUser = $this->getUtils()->getOption('portalUserLdapAuth');
|
||||
if (!$useLdapAuthForPortalUser) {
|
||||
return parent::login($username, $password, $authToken, $isPortal);
|
||||
return parent::login($username, $password, $authToken, $params, $request);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,18 +35,19 @@ class ClientManager
|
||||
|
||||
private $config;
|
||||
|
||||
protected $mainHtmlFilePath = 'html/main.html';
|
||||
private $metadata;
|
||||
|
||||
protected $htmlFilePathForDeveloperMode = 'frontend/html/main.html';
|
||||
protected $mainHtmlFilePath = 'html/main.html';
|
||||
|
||||
protected $runScript = "app.start();";
|
||||
|
||||
protected $basePath = '';
|
||||
|
||||
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()
|
||||
@@ -59,6 +60,11 @@ class ClientManager
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
protected function getMetadata()
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
public function setBasePath($basePath)
|
||||
{
|
||||
$this->basePath = $basePath;
|
||||
@@ -77,7 +83,7 @@ class ClientManager
|
||||
return $this->getConfig()->get('cacheTimestamp', 0);
|
||||
}
|
||||
|
||||
public function display($runScript = null, $htmlFilePath = null, $vars = array())
|
||||
public function display($runScript = null, $htmlFilePath = null, $vars = [])
|
||||
{
|
||||
if (is_null($runScript)) {
|
||||
$runScript = $this->runScript;
|
||||
@@ -88,24 +94,48 @@ class ClientManager
|
||||
|
||||
$isDeveloperMode = $this->getConfig()->get('isDeveloperMode');
|
||||
|
||||
$cacheTimestamp = $this->getCacheTimestamp();
|
||||
|
||||
if ($isDeveloperMode) {
|
||||
if (file_exists('frontend/' . $htmlFilePath)) {
|
||||
$htmlFilePath = 'frontend/' . $htmlFilePath;
|
||||
}
|
||||
$useCache = $this->getConfig()->get('useCacheInDeveloperMode');
|
||||
$jsFileList = $this->getMetadata()->get(['app', 'client', 'developerModeScriptList']);
|
||||
$loaderCacheTimestamp = 'null';
|
||||
} else {
|
||||
$useCache = $this->getConfig()->get('useCache');
|
||||
$jsFileList = $this->getMetadata()->get(['app', 'client', 'scriptList']);
|
||||
$loaderCacheTimestamp = $cacheTimestamp;
|
||||
}
|
||||
|
||||
$scriptsHtml = '';
|
||||
foreach ($jsFileList as $jsFile) {
|
||||
$src = $this->basePath . $jsFile . '?r=' . $cacheTimestamp;
|
||||
$scriptsHtml .= ' ' .
|
||||
'<script type="text/javascript" src="'.$src.'" data-base-path="'.$this->basePath.'"></script>' . "\n";
|
||||
}
|
||||
|
||||
$data = [
|
||||
'applicationId' => 'espocrm-application-id',
|
||||
'apiUrl' => 'api/v1',
|
||||
'applicationName' => $this->getConfig()->get('applicationName', 'EspoCRM'),
|
||||
'cacheTimestamp' => $cacheTimestamp,
|
||||
'loaderCacheTimestamp' => $loaderCacheTimestamp,
|
||||
'stylesheet' => $this->getThemeManager()->getStylesheet(),
|
||||
'runScript' => $runScript,
|
||||
'basePath' => $this->basePath,
|
||||
'useCache' => $useCache ? 'true' : 'false',
|
||||
'appClientClassName' => 'app',
|
||||
'scriptsHtml' => $scriptsHtml
|
||||
];
|
||||
|
||||
$html = file_get_contents($htmlFilePath);
|
||||
|
||||
foreach ($vars as $key => $value) {
|
||||
$html = str_replace('{{'.$key.'}}', $value, $html);
|
||||
}
|
||||
$html = str_replace('{{applicationName}}', $this->getConfig()->get('applicationName', 'EspoCRM'), $html);
|
||||
$html = str_replace('{{cacheTimestamp}}', $this->getCacheTimestamp(), $html);
|
||||
$html = str_replace('{{useCache}}', $this->getConfig()->get('useCache') ? 'true' : 'false', $html);
|
||||
$html = str_replace('{{stylesheet}}', $this->getThemeManager()->getStylesheet(), $html);
|
||||
$html = str_replace('{{runScript}}', $runScript , $html);
|
||||
$html = str_replace('{{basePath}}', $this->basePath , $html);
|
||||
if ($isDeveloperMode) {
|
||||
$html = str_replace('{{useCacheInDeveloperMode}}', $this->getConfig()->get('useCacheInDeveloperMode') ? 'true' : 'false', $html);
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (array_key_exists($key, $vars)) continue;
|
||||
$html = str_replace('{{'.$key.'}}', $value, $html);
|
||||
}
|
||||
|
||||
echo $html;
|
||||
|
||||
@@ -31,12 +31,6 @@ namespace Espo\Core\Utils;
|
||||
|
||||
class Config
|
||||
{
|
||||
/**
|
||||
* Path of default config file
|
||||
*
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
private $defaultConfigPath = 'application/Espo/Core/defaults/config.php';
|
||||
|
||||
private $systemConfigPath = 'application/Espo/Core/defaults/systemConfig.php';
|
||||
@@ -45,13 +39,7 @@ class Config
|
||||
|
||||
private $cacheTimestamp = 'cacheTimestamp';
|
||||
|
||||
/**
|
||||
* Array of admin items
|
||||
*
|
||||
* @access protected
|
||||
* @var array
|
||||
*/
|
||||
protected $adminItems = array();
|
||||
protected $adminItems = [];
|
||||
|
||||
protected $associativeArrayAttributeList = [
|
||||
'currencyRates',
|
||||
@@ -61,21 +49,16 @@ class Config
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Contains content of config
|
||||
*
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
private $data;
|
||||
|
||||
private $changedData = array();
|
||||
private $removeData = array();
|
||||
private $changedData = [];
|
||||
|
||||
private $removeData = [];
|
||||
|
||||
private $fileManager;
|
||||
|
||||
|
||||
public function __construct(\Espo\Core\Utils\File\Manager $fileManager) //TODO
|
||||
public function __construct(\Espo\Core\Utils\File\Manager $fileManager)
|
||||
{
|
||||
$this->fileManager = $fileManager;
|
||||
}
|
||||
@@ -228,11 +211,6 @@ class Config
|
||||
return $this->getFileManager()->getPhpContents($this->defaultConfigPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an Object of all configs
|
||||
* @param boolean $reload
|
||||
* @return array()
|
||||
*/
|
||||
protected function loadConfig($reload = false)
|
||||
{
|
||||
if (!$reload && isset($this->data) && !empty($this->data)) {
|
||||
@@ -249,50 +227,25 @@ class Config
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function getAllData()
|
||||
{
|
||||
return (object) $this->loadConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get config acording to restrictions for a user
|
||||
*
|
||||
* @param $isAdmin
|
||||
* @return array
|
||||
*/
|
||||
public function getData($isAdmin = null)
|
||||
{
|
||||
$data = $this->loadConfig();
|
||||
|
||||
$restrictedConfig = $data;
|
||||
foreach ($this->getRestrictedItemList($isAdmin) as $name) {
|
||||
if (isset($restrictedConfig[$name])) {
|
||||
unset($restrictedConfig[$name]);
|
||||
}
|
||||
}
|
||||
|
||||
return $restrictedConfig;
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set JSON data acording to restrictions for a user
|
||||
*
|
||||
* @param $isAdmin
|
||||
* @return bool
|
||||
*/
|
||||
public function setData($data, $isAdmin = null)
|
||||
public function setData($data)
|
||||
{
|
||||
$restrictItems = $this->getRestrictedItemList($isAdmin);
|
||||
|
||||
if (is_object($data)) {
|
||||
$data = get_object_vars($data);
|
||||
}
|
||||
|
||||
$values = array();
|
||||
foreach ($data as $key => $item) {
|
||||
if (!in_array($key, $restrictItems)) {
|
||||
$values[$key] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->set($values);
|
||||
return $this->set($data);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,28 +267,6 @@ class Config
|
||||
return $this->set($timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return object
|
||||
*/
|
||||
protected function getRestrictedItemList($onlySystemItems = null)
|
||||
{
|
||||
$data = $this->loadConfig();
|
||||
|
||||
if ($onlySystemItems) {
|
||||
return $data['systemItems'];
|
||||
}
|
||||
|
||||
if (empty($this->adminItems)) {
|
||||
$this->adminItems = array_merge($data['systemItems'], $data['adminItems']);
|
||||
}
|
||||
|
||||
if ($onlySystemItems === false) {
|
||||
return $this->adminItems;
|
||||
}
|
||||
|
||||
return array_merge($this->adminItems, $data['userItems']);
|
||||
}
|
||||
|
||||
public function getAdminOnlyItemList()
|
||||
{
|
||||
return $this->get('adminItems', []);
|
||||
@@ -351,21 +282,9 @@ class Config
|
||||
return $this->get('systemItems', []);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if an item is allowed to get and save
|
||||
*
|
||||
* @param $name
|
||||
* @param $isAdmin
|
||||
* @return bool
|
||||
*/
|
||||
protected function isAllowed($name, $isAdmin = false)
|
||||
public function getUserOnlyItemList()
|
||||
{
|
||||
if (in_array($name, $this->getRestrictedItemList($isAdmin))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return $this->get('userItems', []);
|
||||
}
|
||||
|
||||
public function getSiteUrl()
|
||||
|
||||
@@ -43,6 +43,10 @@ class Job
|
||||
|
||||
private $cronScheduledJob;
|
||||
|
||||
const NOT_EXISTING_PROCESS_PERIOD = 300;
|
||||
|
||||
const READY_NOT_STARTED_PERIOD = 60;
|
||||
|
||||
public function __construct(Config $config, EntityManager $entityManager)
|
||||
{
|
||||
$this->config = $config;
|
||||
@@ -183,69 +187,131 @@ class Job
|
||||
return $countPending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark pending jobs (all jobs that exceeded jobPeriod)
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function markFailedJobs()
|
||||
public function markJobsFailed()
|
||||
{
|
||||
$this->markFailedJobsByPeriod('jobPeriodForActiveProcess');
|
||||
$this->markFailedJobsByPeriod('jobPeriod');
|
||||
$this->markJobsFailedByNotExistingProcesses();
|
||||
$this->markJobsFailedReadyNotStarted();
|
||||
$this->markJobsFailedByPeriod(true);
|
||||
$this->markJobsFailedByPeriod();
|
||||
}
|
||||
|
||||
protected function markFailedJobsByPeriod($period)
|
||||
protected function markJobsFailedByNotExistingProcesses()
|
||||
{
|
||||
$time = time() - $this->getConfig()->get($period);
|
||||
$timeThreshold = time() - $this->getConfig()->get('jobPeriodForNotExistingProcess', self::NOT_EXISTING_PROCESS_PERIOD);
|
||||
$dateTimeThreshold = date('Y-m-d H:i:s', $timeThreshold);
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$runningJobList = $this->getEntityManager()->getRepository('Job')->select([
|
||||
'id',
|
||||
'scheduledJobId',
|
||||
'executeTime',
|
||||
'targetId',
|
||||
'targetType',
|
||||
'pid',
|
||||
'startedAt'
|
||||
])->where([
|
||||
'status' => CronManager::RUNNING,
|
||||
'startedAt<' => $dateTimeThreshold
|
||||
])->find();
|
||||
|
||||
$select = "
|
||||
SELECT id, scheduled_job_id, execute_time, target_id, target_type, pid FROM `job`
|
||||
WHERE
|
||||
(`status` = '" . CronManager::RUNNING ."' OR `status` = '" . CronManager::READY ."') AND execute_time < '".date('Y-m-d H:i:s', $time)."'
|
||||
";
|
||||
$sth = $pdo->prepare($select);
|
||||
$sth->execute();
|
||||
|
||||
$jobData = array();
|
||||
|
||||
switch ($period) {
|
||||
case 'jobPeriod':
|
||||
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||
if (empty($row['pid']) || !System::isProcessActive($row['pid'])) {
|
||||
$jobData[$row['id']] = $row;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'jobPeriodForActiveProcess':
|
||||
while ($row = $sth->fetch(PDO::FETCH_ASSOC)) {
|
||||
$jobData[$row['id']] = $row;
|
||||
}
|
||||
break;
|
||||
$failedJobList = [];
|
||||
foreach ($runningJobList as $job) {
|
||||
if ($job->get('pid') && !System::isProcessActive($job->get('pid'))) {
|
||||
$failedJobList[] = $job;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($jobData)) {
|
||||
$jobQuotedIdList = [];
|
||||
foreach ($jobData as $jobId => $job) {
|
||||
$jobQuotedIdList[] = $pdo->quote($jobId);
|
||||
}
|
||||
$this->markJobListFailed($failedJobList);
|
||||
}
|
||||
|
||||
$update = "
|
||||
UPDATE job
|
||||
SET `status` = '" . CronManager::FAILED . "', attempts = 0
|
||||
WHERE id IN (".implode(", ", $jobQuotedIdList).")
|
||||
";
|
||||
protected function markJobsFailedReadyNotStarted()
|
||||
{
|
||||
$timeThreshold = time() - $this->getConfig()->get('jobPeriodForReadyNotStarted', SELF::READY_NOT_STARTED_PERIOD);
|
||||
$dateTimeThreshold = date('Y-m-d H:i:s', $timeThreshold);
|
||||
|
||||
$sth = $pdo->prepare($update);
|
||||
$sth->execute();
|
||||
$failedJobList = $this->getEntityManager()->getRepository('Job')->select([
|
||||
'id',
|
||||
'scheduledJobId',
|
||||
'executeTime',
|
||||
'targetId',
|
||||
'targetType',
|
||||
'pid',
|
||||
'startedAt'
|
||||
])->where([
|
||||
'status' => CronManager::READY,
|
||||
'startedAt<' => $dateTimeThreshold
|
||||
])->find();
|
||||
|
||||
$cronScheduledJob = $this->getCronScheduledJob();
|
||||
foreach ($jobData as $jobId => $job) {
|
||||
if (!empty($job['scheduled_job_id'])) {
|
||||
$cronScheduledJob->addLogRecord($job['scheduled_job_id'], CronManager::FAILED, $job['execute_time'], $job['target_id'], $job['target_type']);
|
||||
$this->markJobListFailed($failedJobList);
|
||||
}
|
||||
|
||||
protected function markJobsFailedByPeriod($isForActiveProcesses = false)
|
||||
{
|
||||
$period = 'jobPeriod';
|
||||
if ($isForActiveProcesses) {
|
||||
$period = 'jobPeriodForActiveProcess';
|
||||
}
|
||||
|
||||
$timeThreshold = time() - $this->getConfig()->get($period, 7800);
|
||||
$dateTimeThreshold = date('Y-m-d H:i:s', $timeThreshold);
|
||||
|
||||
$runningJobList = $this->getEntityManager()->getRepository('Job')->select([
|
||||
'id',
|
||||
'scheduledJobId',
|
||||
'executeTime',
|
||||
'targetId',
|
||||
'targetType',
|
||||
'pid',
|
||||
'startedAt'
|
||||
])->where([
|
||||
'status' => CronManager::RUNNING,
|
||||
'executeTime<' => $dateTimeThreshold
|
||||
])->find();
|
||||
|
||||
$failedJobList = [];
|
||||
foreach ($runningJobList as $job) {
|
||||
if (!$isForActiveProcesses) {
|
||||
if (!$job->get('pid') || !System::isProcessActive($job->get('pid'))) {
|
||||
$failedJobList[] = $job;
|
||||
}
|
||||
} else {
|
||||
$failedJobList[] = $job;
|
||||
}
|
||||
}
|
||||
|
||||
$this->markJobListFailed($failedJobList);
|
||||
}
|
||||
|
||||
protected function markJobListFailed($jobList)
|
||||
{
|
||||
if (!count($jobList)) return;
|
||||
|
||||
$jobIdList = [];
|
||||
foreach ($jobList as $job) {
|
||||
$jobIdList[] = $job->id;
|
||||
}
|
||||
|
||||
$quotedIdList = [];
|
||||
foreach ($jobIdList as $id) {
|
||||
$quotedIdList[] = $this->getEntityManager()->getPDO()->quote($id);
|
||||
}
|
||||
|
||||
$sql = "
|
||||
UPDATE job
|
||||
SET `status` = '" . CronManager::FAILED . "', attempts = 0
|
||||
WHERE id IN (".implode(", ", $quotedIdList).")
|
||||
";
|
||||
|
||||
$this->getEntityManager()->getPDO()->query($sql);
|
||||
|
||||
foreach ($jobList as $job) {
|
||||
if ($job->get('scheduledJobId')) {
|
||||
$this->getCronScheduledJob()->addLogRecord(
|
||||
$job->get('scheduledJobId'),
|
||||
CronManager::FAILED,
|
||||
$job->get('startedAt'),
|
||||
$job->get('targetId'),
|
||||
$job->get('targetType')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
|
||||
|
||||
class JsonArrayType extends \Doctrine\DBAL\Types\JsonArrayType
|
||||
class JsonArrayType extends \Doctrine\DBAL\Types\TextType
|
||||
{
|
||||
const JSON_ARRAY = 'jsonArray';
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\FieldTypes;
|
||||
|
||||
class JsonObjectType extends \Doctrine\DBAL\Types\ObjectType
|
||||
class JsonObjectType extends \Doctrine\DBAL\Types\TextType
|
||||
{
|
||||
const JSON_OBJECT = 'jsonObject';
|
||||
|
||||
|
||||
@@ -38,6 +38,10 @@ use Doctrine\DBAL\Schema\Column;
|
||||
|
||||
class MySqlPlatform extends \Doctrine\DBAL\Platforms\MySqlPlatform
|
||||
{
|
||||
/* Espo */
|
||||
const LENGTH_LIMIT_LONGTEXT = 4294967295;
|
||||
/* Espo: end */
|
||||
|
||||
public function getAlterTableSQL(TableDiff $diff)
|
||||
{
|
||||
$columnSql = array();
|
||||
@@ -453,6 +457,29 @@ class MySqlPlatform extends \Doctrine\DBAL\Platforms\MySqlPlatform
|
||||
return 'MEDIUMTEXT';
|
||||
}
|
||||
|
||||
/* Espo: fix a problem of changing text field type */
|
||||
public function getClobTypeLength($type)
|
||||
{
|
||||
switch ($type) {
|
||||
case 'tinytext':
|
||||
return static::LENGTH_LIMIT_TINYTEXT;
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
return static::LENGTH_LIMIT_TEXT;
|
||||
break;
|
||||
|
||||
case 'mediumtext':
|
||||
return static::LENGTH_LIMIT_MEDIUMTEXT;
|
||||
break;
|
||||
|
||||
case 'longtext':
|
||||
return static::LENGTH_LIMIT_LONGTEXT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Espo: end */
|
||||
|
||||
public function getColumnDeclarationListSQL(array $fields)
|
||||
{
|
||||
$queryFields = array();
|
||||
|
||||
@@ -28,12 +28,11 @@
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
|
||||
class Comparator extends \Doctrine\DBAL\Schema\Comparator
|
||||
{
|
||||
|
||||
public function diffColumn(Column $column1, Column $column2)
|
||||
{
|
||||
$changedProperties = array();
|
||||
@@ -80,6 +79,15 @@ class Comparator extends \Doctrine\DBAL\Schema\Comparator
|
||||
}
|
||||
}
|
||||
|
||||
if ($column1->getType() instanceof \Doctrine\DBAL\Types\TextType) {
|
||||
$length1 = $column1->getLength() ?: 16777215/* mediumtext length*/;
|
||||
$length2 = $column2->getLength() ?: 16777215;
|
||||
|
||||
if ($length2 > $length1) {
|
||||
$changedProperties[] = 'length';
|
||||
}
|
||||
}
|
||||
|
||||
if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) {
|
||||
if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) {
|
||||
$changedProperties[] = 'precision';
|
||||
|
||||
@@ -203,6 +203,15 @@ class MySqlSchemaManager extends \Doctrine\DBAL\Schema\MySqlSchemaManager
|
||||
case 'year':
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
/* Espo: fix a problem of changing text field type */
|
||||
case 'tinytext':
|
||||
case 'text':
|
||||
case 'mediumtext':
|
||||
case 'longtext':
|
||||
$length = $this->_platform->getClobTypeLength($dbType);
|
||||
break;
|
||||
/* Espo: end */
|
||||
}
|
||||
|
||||
$length = ((int) $length == 0) ? null : (int) $length;
|
||||
|
||||
@@ -27,29 +27,28 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
return array(
|
||||
return [
|
||||
|
||||
'unset' => array(
|
||||
'unset' => [
|
||||
'__APPEND__',
|
||||
'Preferences'
|
||||
),
|
||||
'Preferences',
|
||||
],
|
||||
'unsetIgnore' => [
|
||||
'__APPEND__',
|
||||
['Preferences', 'fields', 'id'],
|
||||
['Preferences', 'fields', 'data']
|
||||
['Preferences', 'fields', 'data'],
|
||||
],
|
||||
'Preferences' => array(
|
||||
'fields' => array(
|
||||
'id' => array(
|
||||
'Preferences' => [
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'dbType' => 'varchar',
|
||||
'len' => 24,
|
||||
'type' => 'id'
|
||||
),
|
||||
'data' => array(
|
||||
'type' => 'text'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
'type' => 'id',
|
||||
],
|
||||
'data' => [
|
||||
'type' => 'text',
|
||||
'len' => 16777216,
|
||||
]
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
@@ -64,7 +64,7 @@ class EmailFilterManager
|
||||
$emailFilterList = $this->getEntityManager()->getRepository('EmailFilter')->where([
|
||||
'parentId' => $userId,
|
||||
'parentType' => 'User'
|
||||
])->order('LIST:action:Skip;Move to Folder')->find();
|
||||
])->order('LIST:action:Skip,Move to Folder')->find();
|
||||
$this->data[$userId] = $emailFilterList;
|
||||
}
|
||||
foreach ($this->data[$userId] as $emailFilter) {
|
||||
|
||||
@@ -52,7 +52,7 @@ class EntityManager
|
||||
|
||||
private $reservedWordList = ['__halt_compiler', 'abstract', 'and', 'array', 'as', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'final', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'interface', 'isset', 'list', 'namespace', 'new', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'return', 'static', 'switch', 'throw', 'trait', 'try', 'unset', 'use', 'var', 'while', 'xor', 'common'];
|
||||
|
||||
private $linkForbiddenNameList = ['posts', 'stream', 'subscription'];
|
||||
private $linkForbiddenNameList = ['posts', 'stream', 'subscription', 'followers'];
|
||||
|
||||
private $forbiddenEntityTypeNameList = ['PortalUser', 'ApiUser', 'Timeline', 'About', 'Admin'];
|
||||
|
||||
|
||||
@@ -79,6 +79,11 @@ class FieldManager
|
||||
return $this->container->get('defaultLanguage');
|
||||
}
|
||||
|
||||
protected function getFieldManagerUtil()
|
||||
{
|
||||
return $this->container->get('fieldManagerUtil');
|
||||
}
|
||||
|
||||
public function read($scope, $name)
|
||||
{
|
||||
$fieldDefs = $this->getFieldDefs($scope, $name);
|
||||
@@ -267,13 +272,16 @@ class FieldManager
|
||||
$entityDefs = $this->normalizeDefs($scope, $name, $fieldDefs);
|
||||
|
||||
if (!empty($entityDefs)) {
|
||||
$this->saveCustomdDefs($scope, $entityDefs);
|
||||
$result &= $this->saveCustomEntityDefs($scope, $entityDefs);
|
||||
$this->isChanged = true;
|
||||
}
|
||||
|
||||
if ($metadataToBeSaved) {
|
||||
$result &= $this->getMetadata()->save();
|
||||
$this->isChanged = true;
|
||||
}
|
||||
|
||||
if ($this->isChanged) {
|
||||
$this->processHook('afterSave', $type, $scope, $name, $fieldDefs, array('isNew' => $isNew));
|
||||
}
|
||||
|
||||
@@ -431,7 +439,7 @@ class FieldManager
|
||||
}
|
||||
}
|
||||
|
||||
protected function saveCustomdDefs($scope, $newDefs)
|
||||
protected function saveCustomEntityDefs($scope, $newDefs)
|
||||
{
|
||||
$customDefs = $this->getMetadata()->getCustom('entityDefs', $scope, (object) []);
|
||||
|
||||
@@ -492,6 +500,9 @@ class FieldManager
|
||||
'inlineEditDisabled' => [
|
||||
'type' => 'bool',
|
||||
'default' => false
|
||||
],
|
||||
'defaultAttributes' => [
|
||||
'type' => 'jsonObject'
|
||||
]
|
||||
];
|
||||
|
||||
@@ -510,7 +521,7 @@ class FieldManager
|
||||
|
||||
$params = [];
|
||||
foreach ($fieldDefsByType['params'] as $paramData) {
|
||||
$params[ $paramData['name'] ] = $paramData;
|
||||
$params[$paramData['name']] = $paramData;
|
||||
}
|
||||
foreach ($additionalParamList as $paramName => $paramValue) {
|
||||
if (!isset($params[$paramName])) {
|
||||
|
||||
@@ -146,4 +146,43 @@ class FieldManagerUtil
|
||||
|
||||
return $this->fieldByTypeListCache[$scope][$type];
|
||||
}
|
||||
|
||||
private function getFieldTypeAttributeListByType($fieldType, $name, $type)
|
||||
{
|
||||
$defs = $this->getMetadata()->get(['fields', $fieldType]);
|
||||
if (!$defs) return [];
|
||||
|
||||
$attributeList = [];
|
||||
|
||||
if (isset($defs[$type . 'Fields'])) {
|
||||
$list = $defs[$type . 'Fields'];
|
||||
$naming = 'suffix';
|
||||
if (isset($defs['naming'])) {
|
||||
$naming = $defs['naming'];
|
||||
}
|
||||
if ($naming == 'prefix') {
|
||||
foreach ($list as $f) {
|
||||
$attributeList[] = $f . ucfirst($name);
|
||||
}
|
||||
} else {
|
||||
foreach ($list as $f) {
|
||||
$attributeList[] = $name . ucfirst($f);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($type == 'actual') {
|
||||
$attributeList[] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
return $attributeList;
|
||||
}
|
||||
|
||||
public function getFieldTypeAttributeList($fieldType, $name)
|
||||
{
|
||||
return array_merge(
|
||||
$this->getFieldTypeAttributeListByType($fieldType, $name, 'actual'),
|
||||
$this->getFieldTypeAttributeListByType($fieldType, $name, 'notActual')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,6 +466,9 @@ class Manager
|
||||
|
||||
if (file_exists($sourceFile) && is_file($sourceFile)) {
|
||||
$res &= copy($sourceFile, $destFile);
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($destFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,6 +561,9 @@ class Manager
|
||||
}
|
||||
|
||||
if (file_exists($filePath) && is_file($filePath)) {
|
||||
if (function_exists('opcache_invalidate')) {
|
||||
opcache_invalidate($filePath, true);
|
||||
}
|
||||
$result &= unlink($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,8 @@ class Metadata
|
||||
protected $frontendHiddenPathList = [
|
||||
['app', 'formula', 'functionClassNameMap'],
|
||||
['app', 'fileStorage', 'implementationClassNameMap'],
|
||||
['app', 'emailNotifications', 'handlerClassNameMap']
|
||||
['app', 'emailNotifications', 'handlerClassNameMap'],
|
||||
['app', 'client'],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -149,11 +149,6 @@ class System
|
||||
return extension_loaded($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pet process ID
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public static function getPid()
|
||||
{
|
||||
if (function_exists('getmypid')) {
|
||||
@@ -161,27 +156,19 @@ class System
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if process is active
|
||||
*
|
||||
* @param integer $pid
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isProcessActive($pid)
|
||||
{
|
||||
if (empty($pid)) {
|
||||
return false;
|
||||
}
|
||||
if (empty($pid)) return false;
|
||||
|
||||
if (!function_exists('posix_getsid')) {
|
||||
return false;
|
||||
}
|
||||
if (!self::isPosixSupported()) return false;
|
||||
|
||||
if (posix_getsid($pid) === false) {
|
||||
return false;
|
||||
}
|
||||
if (posix_getsid($pid) === false) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function isPosixSupported()
|
||||
{
|
||||
return function_exists('posix_getsid');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,15 @@ return [
|
||||
'internalSmtpUsername',
|
||||
'internalSmtpPassword',
|
||||
'internalSmtpSecurity',
|
||||
'internalOutboundEmailFromAddress'
|
||||
'internalOutboundEmailFromAddress',
|
||||
'requiredPhpVersion',
|
||||
'requiredMysqlVersion',
|
||||
'recommendedMysqlParams',
|
||||
'requiredPhpLibs',
|
||||
'recommendedPhpLibs',
|
||||
'recommendedPhpParams',
|
||||
'requiredMariadbVersion',
|
||||
'recommendedMariadbParams',
|
||||
],
|
||||
'adminItems' => [
|
||||
'devMode',
|
||||
@@ -175,12 +183,6 @@ return [
|
||||
'adminNotificationsCronIsNotConfigured',
|
||||
'adminNotificationsNewExtensionVersion',
|
||||
'leadCaptureAllowOrigin',
|
||||
'requiredPhpVersion',
|
||||
'requiredMysqlVersion',
|
||||
'recommendedMysqlParams',
|
||||
'requiredPhpLibs',
|
||||
'recommendedPhpLibs',
|
||||
'recommendedPhpParams',
|
||||
],
|
||||
'superAdminItems' => [
|
||||
'jobMaxPortion',
|
||||
@@ -198,8 +200,8 @@ return [
|
||||
'userItems' => [
|
||||
'outboundEmailFromAddress',
|
||||
'outboundEmailFromName',
|
||||
'outboundEmailBccAddress',
|
||||
'integrations',
|
||||
'googleMapsApiKey'
|
||||
],
|
||||
'isInstalled' => false,
|
||||
'ldapUserNameAttribute' => 'sAMAccountName',
|
||||
@@ -217,10 +219,12 @@ return [
|
||||
'mbstring',
|
||||
'zip',
|
||||
'gd',
|
||||
'iconv'
|
||||
],
|
||||
'recommendedPhpLibs' => [
|
||||
'curl',
|
||||
'xml',
|
||||
'xmlwriter',
|
||||
'exif',
|
||||
],
|
||||
'recommendedPhpParams' => [
|
||||
|
||||
@@ -38,23 +38,31 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
{
|
||||
public static $authRequired = true;
|
||||
|
||||
protected $allowedFileTypes = array(
|
||||
protected $allowedFileTypes = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
);
|
||||
'image/webp',
|
||||
];
|
||||
|
||||
protected $imageSizes = array(
|
||||
'xxx-small' => array(18, 18),
|
||||
'xx-small' => array(32, 32),
|
||||
'x-small' => array(64, 64),
|
||||
'small' => array(128, 128),
|
||||
'medium' => array(256, 256),
|
||||
'large' => array(512, 512),
|
||||
'x-large' => array(864, 864),
|
||||
'xx-large' => array(1024, 1024),
|
||||
);
|
||||
protected $imageSizes = [
|
||||
'xxx-small' => [18, 18],
|
||||
'xx-small' => [32, 32],
|
||||
'x-small' => [64, 64],
|
||||
'small' => [128, 128],
|
||||
'medium' => [256, 256],
|
||||
'large' => [512, 512],
|
||||
'x-large' => [864, 864],
|
||||
'xx-large' => [1024, 1024],
|
||||
];
|
||||
|
||||
protected $fixOrientationFileTypeList = [
|
||||
'image/jpeg',
|
||||
];
|
||||
|
||||
protected $allowedRelatedTypeList = null;
|
||||
|
||||
protected $allowedFieldList = null;
|
||||
|
||||
public function run()
|
||||
{
|
||||
@@ -68,7 +76,7 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
$size = $_GET['size'];
|
||||
}
|
||||
|
||||
$this->show($id, $size);
|
||||
$this->show($id, $size, false);
|
||||
}
|
||||
|
||||
protected function show($id, $size, $disableAccessCheck = false)
|
||||
@@ -97,6 +105,18 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
throw new Error();
|
||||
}
|
||||
|
||||
if ($this->allowedRelatedTypeList) {
|
||||
if (!in_array($attachment->get('relatedType'), $this->allowedRelatedTypeList)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->allowedFieldList) {
|
||||
if (!in_array($attachment->get('field'), $this->allowedFieldList)) {
|
||||
throw new NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($size)) {
|
||||
if (!empty($this->imageSizes[$size])) {
|
||||
$thumbFilePath = "data/upload/thumbs/{$sourceId}_{$size}";
|
||||
@@ -115,6 +135,9 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
case 'image/gif':
|
||||
imagegif($targetImage);
|
||||
break;
|
||||
case 'image/webp':
|
||||
imagewebp($targetImage);
|
||||
break;
|
||||
}
|
||||
$contents = ob_get_contents();
|
||||
ob_end_clean();
|
||||
@@ -181,7 +204,7 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
switch ($fileType) {
|
||||
case 'image/jpeg':
|
||||
$sourceImage = imagecreatefromjpeg($filePath);
|
||||
imagecopyresampled ($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight);
|
||||
imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight);
|
||||
break;
|
||||
case 'image/png':
|
||||
$sourceImage = imagecreatefrompng($filePath);
|
||||
@@ -195,10 +218,34 @@ class Image extends \Espo\Core\EntryPoints\Base
|
||||
$sourceImage = imagecreatefromgif($filePath);
|
||||
imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight);
|
||||
break;
|
||||
case 'image/webp':
|
||||
$sourceImage = imagecreatefromwebp($filePath);
|
||||
imagecopyresampled($targetImage, $sourceImage, 0, 0, 0, 0, $targetWidth, $targetHeight, $originalWidth, $originalHeight);
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_array($fileType, $this->fixOrientationFileTypeList)) {
|
||||
$targetImage = $this->fixOrientation($targetImage, $filePath);
|
||||
}
|
||||
|
||||
return $targetImage;
|
||||
}
|
||||
|
||||
protected function getOrientation($filePath)
|
||||
{
|
||||
$orientation = 0;
|
||||
if (function_exists('exif_read_data')) {
|
||||
$targetImage = imagerotate($targetImage, array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[@exif_read_data($filePath)['Orientation'] ?: 0], 0);
|
||||
$orientation = @exif_read_data($filePath)['Orientation'];
|
||||
}
|
||||
return $orientation;
|
||||
}
|
||||
|
||||
protected function fixOrientation($targetImage, $filePath)
|
||||
{
|
||||
$orientation = $this->getOrientation($filePath);
|
||||
if ($orientation) {
|
||||
$angle = array_values([0, 0, 0, 180, 0, 0, -90, 0, 90])[$orientation];
|
||||
$targetImage = imagerotate($targetImage, $angle, 0);
|
||||
}
|
||||
|
||||
return $targetImage;
|
||||
|
||||
@@ -38,6 +38,10 @@ class LogoImage extends Image
|
||||
{
|
||||
public static $authRequired = false;
|
||||
|
||||
protected $allowedRelatedTypeList = ['Settings', 'Portal'];
|
||||
|
||||
protected $allowedFieldList = ['companyLogo'];
|
||||
|
||||
public function run()
|
||||
{
|
||||
$this->imageSizes['small-logo'] = array(181, 44);
|
||||
|
||||
@@ -145,7 +145,7 @@ class Invitations
|
||||
$htmlizer = new \Espo\Core\Htmlizer\Htmlizer($this->fileManager, $dateTime, $this->number, null);
|
||||
|
||||
$subject = $htmlizer->render($entity, $subjectTpl, 'invitation-email-subject-' . $entity->getEntityType(), $data, true);
|
||||
$body = $htmlizer->render($entity, $bodyTpl, 'invitation-email-body-' . $entity->getEntityType(), $data, true);
|
||||
$body = $htmlizer->render($entity, $bodyTpl, 'invitation-email-body-' . $entity->getEntityType(), $data, false);
|
||||
|
||||
$email->set('subject', $subject);
|
||||
$email->set('body', $body);
|
||||
|
||||
@@ -153,7 +153,7 @@ class EmailReminder
|
||||
$htmlizer = new \Espo\Core\Htmlizer\Htmlizer($this->fileManager, $dateTime, $this->number, null);
|
||||
|
||||
$subject = $htmlizer->render($entity, $subjectTpl, 'reminder-email-subject-' . $entity->getEntityType(), $data, true);
|
||||
$body = $htmlizer->render($entity, $bodyTpl, 'reminder-email-body-' . $entity->getEntityType(), $data, true);
|
||||
$body = $htmlizer->render($entity, $bodyTpl, 'reminder-email-body-' . $entity->getEntityType(), $data, false);
|
||||
|
||||
$email->set('subject', $subject);
|
||||
$email->set('body', $body);
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"revenueConverted": "Revenue (converted)",
|
||||
"budget": "Budget",
|
||||
"budgetConverted": "Budget (converted)",
|
||||
"budgetCurrency": "Budget Currency",
|
||||
"contactsTemplate": "Contacts Template",
|
||||
"leadsTemplate": "Leads Template",
|
||||
"accountsTemplate": "Accounts Template",
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
"createdAt",
|
||||
"createdBy",
|
||||
"modifiedAt",
|
||||
"billingAddress",
|
||||
"type",
|
||||
"industry",
|
||||
"description",
|
||||
"emailAddress",
|
||||
"industry",
|
||||
"phoneNumber",
|
||||
"targetLists",
|
||||
"type",
|
||||
"billingAddressCountry",
|
||||
"billingAddress",
|
||||
"shippingAddress",
|
||||
"website"
|
||||
]
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"defaults": {
|
||||
"orderBy": "dateStart",
|
||||
"order": "desc",
|
||||
"order": "asc",
|
||||
"displayRecords": 5,
|
||||
"populateAssignedUser": true,
|
||||
"expandedLayout": {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"defaults": {
|
||||
"orderBy": "dateStart",
|
||||
"order": "desc",
|
||||
"order": "asc",
|
||||
"displayRecords": 5,
|
||||
"populateAssignedUser": true,
|
||||
"expandedLayout": {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"dateFilter": {
|
||||
"type": "enum",
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "currentFiscalYear", "currentFiscalQuarter", "ever", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"dateFilter": {
|
||||
"type": "enum",
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "currentFiscalYear", "currentFiscalQuarter", "ever", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"dateFilter": {
|
||||
"type": "enum",
|
||||
"options": ["currentYear", "currentQuarter", "between"],
|
||||
"options": ["currentYear", "currentQuarter", "currentFiscalYear", "currentFiscalQuarter", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"dateFilter": {
|
||||
"type": "enum",
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "ever", "between"],
|
||||
"options": ["currentYear", "currentQuarter", "currentMonth", "currentFiscalYear", "currentFiscalQuarter", "ever", "between"],
|
||||
"default": "currentYear",
|
||||
"translation": "Global.options.dateSearchRanges"
|
||||
},
|
||||
|
||||
@@ -61,6 +61,10 @@
|
||||
"type": "enum",
|
||||
"notStorable": true,
|
||||
"options": ["None", "Accepted", "Tentative", "Declined"],
|
||||
"style": {
|
||||
"Accepted": "success",
|
||||
"Declined": "danger"
|
||||
},
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"where": {
|
||||
|
||||
@@ -56,6 +56,10 @@
|
||||
"type": "enum",
|
||||
"notStorable": true,
|
||||
"options": ["None", "Accepted", "Tentative", "Declined"],
|
||||
"style": {
|
||||
"Accepted": "success",
|
||||
"Declined": "danger"
|
||||
},
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"where": {
|
||||
|
||||
@@ -1042,8 +1042,6 @@ class Activities extends \Espo\Core\Services\Base
|
||||
}
|
||||
|
||||
if ($seed->hasRelation('assignedUsers')) {
|
||||
$selectManager->setDistinct(true, $selectParams);
|
||||
$selectManager->addLeftJoin(['assignedUsers', 'assignedUsers'], $selectParams);
|
||||
$wherePart['assignedUsersMiddle.userId'] = $userId;
|
||||
}
|
||||
|
||||
@@ -1082,6 +1080,7 @@ class Activities extends \Espo\Core\Services\Base
|
||||
}
|
||||
|
||||
if ($seed->hasRelation('assignedUsers')) {
|
||||
$selectManager->setDistinct(true, $selectParams);
|
||||
$selectParams['leftJoins'][] = 'assignedUsers';
|
||||
}
|
||||
|
||||
|
||||
@@ -548,7 +548,7 @@ class MassEmail extends \Espo\Services\Record
|
||||
return $this->campaignService;
|
||||
}
|
||||
|
||||
protected function findLinkedEntitiesQueueItems($id, $params)
|
||||
protected function findLinkedQueueItems($id, $params)
|
||||
{
|
||||
$link = 'queueItems';
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -229,6 +237,12 @@ class Opportunity extends \Espo\Services\Record
|
||||
$result[$row['stage']] = floatval($row['amount']);
|
||||
}
|
||||
|
||||
foreach ($options as $stage) {
|
||||
if (in_array($stage, $stageIgnoreList)) continue;
|
||||
if (array_key_exists($stage, $result)) continue;
|
||||
$result[$stage] = 0.0;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -246,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();
|
||||
|
||||
@@ -291,7 +308,7 @@ class Opportunity extends \Espo\Services\Record
|
||||
|
||||
if ($dt && $dtTo) {
|
||||
$interval = new \DateInterval('P1M');
|
||||
while ($dt->getTimestamp() <= $dtTo->getTimestamp()) {
|
||||
while ($dt->getTimestamp() < $dtTo->getTimestamp()) {
|
||||
$month = $dt->format('Y-m');
|
||||
if (!array_key_exists($month, $result)) {
|
||||
$result[$month] = 0;
|
||||
@@ -305,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');
|
||||
@@ -328,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) {
|
||||
@@ -351,6 +392,37 @@ class Opportunity extends \Espo\Services\Record
|
||||
$dt->modify('first day of this month')->format('Y-m-d'),
|
||||
$dt->add(new \DateInterval('P1M'))->format('Y-m-d')
|
||||
];
|
||||
case 'currentFiscalYear':
|
||||
$dtToday = new \DateTime();
|
||||
$dt = new \DateTime();
|
||||
$fiscalYearShift = $this->getConfig()->get('fiscalYearShift', 0);
|
||||
$dt->modify('first day of January this year')->modify('+' . $fiscalYearShift . ' months');
|
||||
if (intval($dtToday->format('m')) < $fiscalYearShift + 1) {
|
||||
$dt->modify('-1 year');
|
||||
}
|
||||
return [
|
||||
$dt->format('Y-m-d'),
|
||||
$dt->add(new \DateInterval('P1Y'))->format('Y-m-d')
|
||||
];
|
||||
case 'currentFiscalQuarter':
|
||||
$dtToday = new \DateTime();
|
||||
$dt = new \DateTime();
|
||||
$fiscalYearShift = $this->getConfig()->get('fiscalYearShift', 0);
|
||||
$dt->modify('first day of January this year')->modify('+' . $fiscalYearShift . ' months');
|
||||
$month = intval($dtToday->format('m'));
|
||||
$quarterShift = floor(($month - $fiscalYearShift - 1) / 3);
|
||||
if ($quarterShift) {
|
||||
if ($quarterShift >= 0) {
|
||||
$dt->add(new \DateInterval('P'.($quarterShift * 3).'M'));
|
||||
} else {
|
||||
$quarterShift *= -1;
|
||||
$dt->sub(new \DateInterval('P'.($quarterShift * 3).'M'));
|
||||
}
|
||||
}
|
||||
return [
|
||||
$dt->format('Y-m-d'),
|
||||
$dt->add(new \DateInterval('P3M'))->format('Y-m-d')
|
||||
];
|
||||
}
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@ class TargetList extends \Espo\Services\Record
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
|
||||
$count += $this->getEntityManager()->getRepository('Contact')->join(['targetLists'])->where([
|
||||
'targetListsMiddle.targetListId' => $entity->id,
|
||||
'targetListsMiddle.optedOut' => 1
|
||||
@@ -130,14 +129,14 @@ class TargetList extends \Espo\Services\Record
|
||||
$entity->set('optedOutCount', $count);
|
||||
}
|
||||
|
||||
protected function afterCreate(Entity $entity, array $data = array())
|
||||
protected function afterCreateEntity(Entity $entity, $data)
|
||||
{
|
||||
if (array_key_exists('sourceCampaignId', $data) && !empty($data['includingActionList'])) {
|
||||
if (property_exists($data, 'sourceCampaignId') && !empty($data->includingActionList)) {
|
||||
$excludingActionList = [];
|
||||
if (!empty($data['excludingActionList'])) {
|
||||
$excludingActionList = $data['excludingActionList'];
|
||||
if (!empty($data->excludingActionList)) {
|
||||
$excludingActionList = $data->excludingActionList;
|
||||
}
|
||||
$this->populateFromCampaignLog($entity, $data['sourceCampaignId'], $data['includingActionList'], $excludingActionList);
|
||||
$this->populateFromCampaignLog($entity, $data->sourceCampaignId, $data->includingActionList, $excludingActionList);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +243,7 @@ class TargetList extends \Espo\Services\Record
|
||||
}
|
||||
}
|
||||
|
||||
protected function findLinkedEntitiesOptedOut($id, $params)
|
||||
protected function findLinkedOptedOut($id, $params)
|
||||
{
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$query = $this->getEntityManager()->getQuery();
|
||||
@@ -411,4 +410,3 @@ class TargetList extends \Espo\Services\Record
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ class Email extends \Espo\Core\Notificators\Base
|
||||
if ($userIdFrom === $userId) continue;
|
||||
if ($entity->getLinkMultipleColumn('users', 'inTrash', $userId)) continue;
|
||||
|
||||
if ($entity->get('isBeingImported')) {
|
||||
if (!empty($options['isBeingImported'])) {
|
||||
$folderId = $entity->getLinkMultipleColumn('users', 'folderId', $userId);
|
||||
if ($folderId) {
|
||||
if (
|
||||
|
||||
@@ -566,6 +566,14 @@ abstract class Mapper implements IMapper
|
||||
|
||||
$sql .= " ON DUPLICATE KEY UPDATE deleted = '0'";
|
||||
|
||||
if (!empty($data) && is_array($data)) {
|
||||
$setArr = [];
|
||||
foreach ($data as $column => $value) {
|
||||
$setArr[] = $this->toDb($column) . " = " . $this->quote($value);
|
||||
}
|
||||
$sql .= ', ' . implode(', ', $setArr);
|
||||
}
|
||||
|
||||
if ($this->pdo->query($sql)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -35,6 +35,24 @@ use Espo\Core\Utils\Util;
|
||||
|
||||
class Attachment extends \Espo\Core\ORM\Repositories\RDB
|
||||
{
|
||||
protected $imageTypeList = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
];
|
||||
|
||||
protected $imageThumbList = [
|
||||
'xxx-small',
|
||||
'xx-small',
|
||||
'x-small',
|
||||
'small',
|
||||
'medium',
|
||||
'large',
|
||||
'x-large',
|
||||
'xx-large',
|
||||
];
|
||||
|
||||
protected function init()
|
||||
{
|
||||
parent::init();
|
||||
@@ -42,6 +60,11 @@ class Attachment extends \Espo\Core\ORM\Repositories\RDB
|
||||
$this->addDependency('config');
|
||||
}
|
||||
|
||||
protected function getFileManager()
|
||||
{
|
||||
return $this->getInjection('container')->get('fileManager');
|
||||
}
|
||||
|
||||
protected function getFileStorageManager()
|
||||
{
|
||||
return $this->getInjection('container')->get('fileStorageManager');
|
||||
@@ -106,6 +129,20 @@ class Attachment extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
if ($duplicateCount === 0) {
|
||||
$this->getFileStorageManager()->unlink($entity);
|
||||
|
||||
if (in_array($entity->get('type'), $this->imageTypeList)) {
|
||||
$this->removeImageThumbs($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function removeImageThumbs($entity)
|
||||
{
|
||||
foreach ($this->imageThumbList as $suffix) {
|
||||
$filePath = "data/upload/thumbs/".$entity->getSourceId()."_{$suffix}";
|
||||
if ($this->getFileManager()->isFile($filePath)) {
|
||||
$this->getFileManager()->removeFile($filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,22 +47,27 @@ class Email extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
$eaRepository = $this->getEntityManager()->getRepository('EmailAddress');
|
||||
|
||||
$address = $entity->get($type);
|
||||
$addressValue = $entity->get($type);
|
||||
$idList = [];
|
||||
if (
|
||||
(!empty($address) || !filter_var($address, FILTER_VALIDATE_EMAIL))
|
||||
&&
|
||||
$type !== 'replyTo'
|
||||
) {
|
||||
$arr = array_map(function ($e) {
|
||||
return trim($e);
|
||||
}, explode(';', $address));
|
||||
|
||||
$idList = $eaRepository->getIdListFormAddressList($arr);
|
||||
foreach ($idList as $id) {
|
||||
$this->addUserByEmailAddressId($entity, $id, $addAssignedUser);
|
||||
if (!empty($addressValue)) {
|
||||
$addressList = array_map(function ($item) {
|
||||
return trim($item);
|
||||
}, explode(';', $addressValue));
|
||||
|
||||
$addressList = array_filter($addressList, function ($item) {
|
||||
return filter_var($item, FILTER_VALIDATE_EMAIL);
|
||||
});
|
||||
|
||||
$idList = $eaRepository->getIdListFormAddressList($addressList);
|
||||
|
||||
if ($type !== 'replyTo') {
|
||||
foreach ($idList as $id) {
|
||||
$this->addUserByEmailAddressId($entity, $id, $addAssignedUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$entity->setLinkMultipleIdList($type . 'EmailAddresses', $idList);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
public function getIds(array $addressList = [])
|
||||
{
|
||||
$ids = array();
|
||||
$ids = [];
|
||||
if (!empty($addressList)) {
|
||||
$lowerAddressList = [];
|
||||
foreach ($addressList as $address) {
|
||||
@@ -72,8 +72,8 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
]
|
||||
])->find();
|
||||
|
||||
$ids = array();
|
||||
$exist = array();
|
||||
$ids = [];
|
||||
$exist = [];
|
||||
foreach ($eaCollection as $ea) {
|
||||
$ids[] = $ea->id;
|
||||
$exist[] = $ea->get('lower');
|
||||
@@ -96,7 +96,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
|
||||
public function getEmailAddressData(Entity $entity)
|
||||
{
|
||||
$data = array();
|
||||
$data = [];
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$sql = "
|
||||
@@ -105,7 +105,7 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
JOIN email_address ON email_address.id = entity_email_address.email_address_id AND email_address.deleted = 0
|
||||
WHERE
|
||||
entity_email_address.entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_email_address.entity_type = ".$pdo->quote($entity->getEntityName())." AND
|
||||
entity_email_address.entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
entity_email_address.deleted = 0
|
||||
ORDER BY entity_email_address.primary DESC
|
||||
";
|
||||
@@ -249,40 +249,302 @@ class EmailAddress extends \Espo\Core\ORM\Repositories\RDB
|
||||
}
|
||||
}
|
||||
|
||||
public function storeEntityEmailAddress(Entity $entity)
|
||||
public function storeEntityEmailAddressData(Entity $entity)
|
||||
{
|
||||
$emailAddressValue = $entity->get('emailAddress');
|
||||
if (is_string($emailAddressValue)) {
|
||||
$emailAddressValue = trim($emailAddressValue);
|
||||
}
|
||||
$emailAddressData = null;
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
|
||||
if ($entity->has('emailAddressData')) {
|
||||
$emailAddressData = $entity->get('emailAddressData');
|
||||
}
|
||||
$emailAddressValue = $entity->get('emailAddress');
|
||||
if (is_string($emailAddressValue)) {
|
||||
$emailAddressValue = trim($emailAddressValue);
|
||||
}
|
||||
|
||||
$pdo = $this->getEntityManager()->getPDO();
|
||||
$emailAddressData = null;
|
||||
if ($entity->has('emailAddressData')) {
|
||||
$emailAddressData = $entity->get('emailAddressData');
|
||||
}
|
||||
|
||||
if ($emailAddressData !== null && is_array($emailAddressData)) {
|
||||
$previousEmailAddressData = array();
|
||||
if (!$entity->isNew()) {
|
||||
$previousEmailAddressData = $this->getEmailAddressData($entity);
|
||||
if (is_null($emailAddressData)) return;
|
||||
if (!is_array($emailAddressData)) return;
|
||||
|
||||
$keyList = [];
|
||||
$keyPreviousList = [];
|
||||
|
||||
$previousEmailAddressData = [];
|
||||
if (!$entity->isNew()) {
|
||||
$previousEmailAddressData = $this->getEmailAddressData($entity);
|
||||
}
|
||||
|
||||
$hash = (object) [];
|
||||
$hashPrevious = (object) [];
|
||||
|
||||
foreach ($emailAddressData as $row) {
|
||||
$key = trim($row->emailAddress);
|
||||
if (empty($key)) continue;
|
||||
$key = strtolower($key);
|
||||
$hash->$key = [
|
||||
'primary' => !empty($row->primary) ? true : false,
|
||||
'optOut' => !empty($row->optOut) ? true : false,
|
||||
'invalid' => !empty($row->invalid) ? true : false,
|
||||
'emailAddress' => trim($row->emailAddress)
|
||||
];
|
||||
$keyList[] = $key;
|
||||
}
|
||||
|
||||
if (
|
||||
$entity->has('emailAddressIsOptedOut')
|
||||
&&
|
||||
(
|
||||
$entity->isNew()
|
||||
||
|
||||
(
|
||||
$entity->hasFetched('emailAddressIsOptedOut')
|
||||
&&
|
||||
$entity->get('emailAddressIsOptedOut') !== $entity->getFetched('emailAddressIsOptedOut')
|
||||
)
|
||||
)
|
||||
) {
|
||||
if ($emailAddressValue) {
|
||||
$key = strtolower($emailAddressValue);
|
||||
if ($key && isset($hash->$key)) {
|
||||
$hash->$key['optOut'] = $entity->get('emailAddressIsOptedOut');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hash = array();
|
||||
foreach ($emailAddressData as $row) {
|
||||
$key = trim($row->emailAddress);
|
||||
if (!empty($key)) {
|
||||
$key = strtolower($key);
|
||||
$hash[$key] = [
|
||||
'primary' => !empty($row->primary) ? true : false,
|
||||
'optOut' => !empty($row->optOut) ? true : false,
|
||||
'invalid' => !empty($row->invalid) ? true : false,
|
||||
'emailAddress' => trim($row->emailAddress)
|
||||
];
|
||||
foreach ($previousEmailAddressData as $row) {
|
||||
$key = $row->lower;
|
||||
if (empty($key)) continue;
|
||||
$hashPrevious->$key = [
|
||||
'primary' => $row->primary ? true : false,
|
||||
'optOut' => $row->optOut ? true : false,
|
||||
'invalid' => $row->invalid ? true : false,
|
||||
'emailAddress' => $row->emailAddress
|
||||
];
|
||||
$keyPreviousList[] = $key;
|
||||
}
|
||||
|
||||
$primary = false;
|
||||
|
||||
$toCreateList = [];
|
||||
$toUpdateList = [];
|
||||
$toRemoveList = [];
|
||||
|
||||
$revertData = [];
|
||||
|
||||
foreach ($keyList as $key) {
|
||||
$data = $hash->$key;
|
||||
|
||||
$new = true;
|
||||
$changed = false;
|
||||
|
||||
if ($hash->$key['primary']) {
|
||||
$primary = $key;
|
||||
}
|
||||
|
||||
if (property_exists($hashPrevious, $key)) {
|
||||
$new = false;
|
||||
$changed =
|
||||
$hash->$key['optOut'] != $hashPrevious->$key['optOut'] ||
|
||||
$hash->$key['invalid'] != $hashPrevious->$key['invalid'] ||
|
||||
$hash->$key['emailAddress'] !== $hashPrevious->$key['emailAddress'];
|
||||
|
||||
if ($hash->$key['primary']) {
|
||||
if ($hash->$key['primary'] == $hashPrevious->$key['primary']) {
|
||||
$primary = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($new) {
|
||||
$toCreateList[] = $key;
|
||||
}
|
||||
if ($changed) {
|
||||
$toUpdateList[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($keyPreviousList as $key) {
|
||||
if (!property_exists($hash, $key)) {
|
||||
$toRemoveList[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toRemoveList as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if ($emailAddress) {
|
||||
$query = "
|
||||
DELETE FROM entity_email_address
|
||||
WHERE
|
||||
entity_id = ".$pdo->quote($entity->id)." AND
|
||||
entity_type = ".$pdo->quote($entity->getEntityType())." AND
|
||||
email_address_id = ".$pdo->quote($emailAddress->id)."
|
||||
";
|
||||
$sth = $pdo->prepare($query);
|
||||
$sth->execute();
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toUpdateList as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if ($emailAddress) {
|
||||
$skipSave = $this->checkChangeIsForbidden($emailAddress, $entity);
|
||||
if (!$skipSave) {
|
||||
$emailAddress->set([
|
||||
'optOut' => $hash->$address['optOut'],
|
||||
'invalid' => $hash->$address['invalid'],
|
||||
'name' => $hash->$address['emailAddress']
|
||||
]);
|
||||
$this->save($emailAddress);
|
||||
} else {
|
||||
$revertData[$address] = [
|
||||
'optOut' => $emailAddress->get('optOut'),
|
||||
'invalid' => $emailAddress->get('invalid')
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toCreateList as $address) {
|
||||
$emailAddress = $this->getByAddress($address);
|
||||
if (!$emailAddress) {
|
||||
$emailAddress = $this->get();
|
||||
|
||||
$emailAddress->set([
|
||||
'name' => $hash->$address['emailAddress'],
|
||||
'optOut' => $hash->$address['optOut'],
|
||||
'invalid' => $hash->$address['invalid'],
|
||||
]);
|
||||
$this->save($emailAddress);
|
||||
} else {
|
||||
$skipSave = $this->checkChangeIsForbidden($emailAddress, $entity);
|
||||
if (!$skipSave) {
|
||||
if (
|
||||
$emailAddress->get('optOut') != $hash->$address['optOut'] ||
|
||||
$emailAddress->get('invalid') != $hash->$address['invalid'] ||
|
||||
$emailAddress->get('emailAddress') != $hash->$address['emailAddress']
|
||||
) {
|
||||
$emailAddress->set([
|
||||
'optOut' => $hash->$address['optOut'],
|
||||
'invalid' => $hash->$address['invalid'],
|
||||
'name' => $hash->$address['emailAddress']
|
||||
]);
|
||||
$this->save($emailAddress);
|
||||
}
|
||||
} else {
|
||||
$revertData[$address] = [
|
||||
'optOut' => $emailAddress->get('optOut'),
|
||||
'invalid' => $emailAddress->get('invalid')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$query = "
|
||||
INSERT entity_email_address
|
||||
(entity_id, entity_type, email_address_id, `primary`)
|
||||
VALUES
|
||||
(
|
||||
".$pdo->quote($entity->id).",
|
||||
".$pdo->quote($entity->getEntityType()).",
|
||||
".$pdo->quote($emailAddress->id).",
|
||||
".$pdo->quote((int)($address === $primary))."
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE deleted = 0, `primary` = ".$pdo->quote((int)($address === $primary))."
|
||||
";
|
||||
$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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -179,7 +179,9 @@
|
||||
"useNumericFormat": "Use Numeric Format",
|
||||
"strip": "Strip",
|
||||
"minuteStep": "Minutes Step",
|
||||
"inlineEditDisabled": "Disable Inline Edit"
|
||||
"inlineEditDisabled": "Disable Inline Edit",
|
||||
"allowCustomOptions": "Allow Custom Options",
|
||||
"displayAsLabel": "Display as Label"
|
||||
},
|
||||
"messages": {
|
||||
"upgradeVersion": "EspoCRM will be upgraded to version <strong>{version}</strong>. Please be patient as this may take a while.",
|
||||
|
||||
@@ -265,6 +265,7 @@
|
||||
"fieldShouldBeGreater": "{field} shouldn't be less then {value}",
|
||||
"fieldBadPasswordConfirm": "{field} not confirmed properly",
|
||||
"fieldMaxFileSizeError": "File should not exceed {max} Mb",
|
||||
"fieldValueDuplicate": "Duplicate value",
|
||||
"fieldIsUploading": "Uploading in progress",
|
||||
"resetPreferencesDone": "Preferences has been reset to defaults",
|
||||
"confirmation": "Are you sure?",
|
||||
@@ -351,6 +352,7 @@
|
||||
"ids": "IDs",
|
||||
"type": "Type",
|
||||
"names": "Names",
|
||||
"types": "Types",
|
||||
"targetListIsOptedOut": "Is Opted Out (Target List)"
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"status": "Status",
|
||||
"executeTime": "Execute At",
|
||||
"executedAt": "Executed At",
|
||||
"startedAt": "Started At",
|
||||
"attempts": "Attempts Left",
|
||||
"failedAttempts": "Failed Attempts",
|
||||
"serviceName": "Service",
|
||||
|
||||
@@ -208,9 +208,9 @@
|
||||
"posting": "پست کردن ...",
|
||||
"confirmLeaveOutMessage": "آیا مطمئن هستید که می خواهید فرم را ترک کنید؟",
|
||||
"notModified": "شما رکورد را تغییر نداده اید",
|
||||
"fieldIsRequired": "{فیلد} مورد نیاز است",
|
||||
"fieldShouldAfter": "{فیلد} باید پس از {سایرفیلدها} باشد",
|
||||
"fieldShouldBefore": "{فیلد} باید قبل از {سایرفیلدها} باشد",
|
||||
"fieldIsRequired": "{field} مورد نیاز است",
|
||||
"fieldShouldAfter": "{field} باید پس از {سایرفیلدها} باشد",
|
||||
"fieldShouldBefore": "{field} باید قبل از {سایرفیلدها} باشد",
|
||||
"fieldShouldBeBetween": "{field} باید قبل از {otherField} باشد",
|
||||
"fieldBadPasswordConfirm": "{field} درست تایید نشده است",
|
||||
"resetPreferencesDone": "تنظیمات پیش فرض تنظیم مجدد شده است",
|
||||
@@ -228,7 +228,7 @@
|
||||
"noRecordsRemoved": "هیچ رکوردی حذف نشد",
|
||||
"clickToRefresh": "برای تازه کردن کلیک کنید",
|
||||
"writeYourCommentHere": "نظر خود را اینجا بنویسید",
|
||||
"writeMessageToUser": "یک پیام به {کاربر} بنویسید",
|
||||
"writeMessageToUser": "یک پیام به {user} بنویسید",
|
||||
"typeAndPressEnter": "تایپ کنید و وارد کنید",
|
||||
"checkForNewNotifications": "اعلان های جدید را بررسی کنید",
|
||||
"duplicate": "رکوردی که دارید ایجاد میکنید ممکن است هماکنون موجود باشد.",
|
||||
@@ -242,16 +242,16 @@
|
||||
"massFollowResultSingle": " {تعداد}سابقه دنبال می شود",
|
||||
"massUnfollowResultSingle": " {تعداد}سابقه دنبال نمی شود",
|
||||
"massFollowZeroResult": "چیزی به دست نیامده",
|
||||
"fieldShouldBeEmail": "{فیلد} باید یک ایمیل معتبر باشد",
|
||||
"fieldShouldBeEmail": "{field} باید یک ایمیل معتبر باشد",
|
||||
"fieldShouldBeInt": "{field} باید یک عدد صحیح معتبر باشد",
|
||||
"fieldShouldBeDate": "{فیلد} باید یک تاریخ معتبر باشد",
|
||||
"fieldShouldBeDatetime": "{فیلد} باید یک تاریخ / زمان معتبر باشد",
|
||||
"fieldShouldBeDate": "{field} باید یک تاریخ معتبر باشد",
|
||||
"fieldShouldBeDatetime": "{field} باید یک تاریخ / زمان معتبر باشد",
|
||||
"internalPostTitle": "پست تنها توسط کاربران داخلی دیده می شود",
|
||||
"loading": "در حا بارگذاری...",
|
||||
"saving": "در حال ذخیره سازی...",
|
||||
"fieldMaxFileSizeError": "اندازهی فایل نباید بیشتر از {max} مگابایت باشد",
|
||||
"fieldShouldBeLess": "{فیلد} نباید بیشتر باشد {مقدار}",
|
||||
"fieldShouldBeGreater": "{فیلد} نباید کمتر از {مقدار} باشد",
|
||||
"fieldShouldBeLess": "{field} نباید بیشتر باشد {مقدار}",
|
||||
"fieldShouldBeGreater": "{field} نباید کمتر از {مقدار} باشد",
|
||||
"fieldIsUploading": "آپلود در حال انجام است",
|
||||
"erasePersonalDataConfirmation": "زمینه های تأیید شده به طور دائمی پاک خواهند شد. شما مطمئن هستید؟",
|
||||
"massPrintPdfMaxCountError": "می توانید بیش از {maxCount} رکورد چاپ کنید"
|
||||
@@ -315,56 +315,56 @@
|
||||
"Records": "لیست رکورد"
|
||||
},
|
||||
"notificationMessages": {
|
||||
"assign": "{نوع موجودیت} {موجودیت} به شما اختصاص داده شده است",
|
||||
"emailReceived": "ایمیل دریافت شده از {از}",
|
||||
"entityRemoved": "{کاربر} حذف {نوع موجودیت} {موجودیت}"
|
||||
"assign": "{entityType} {entity} به شما اختصاص داده شده است",
|
||||
"emailReceived": "ایمیل دریافت شده از {from}",
|
||||
"entityRemoved": "{user} حذف {entityType} {entity}"
|
||||
},
|
||||
"streamMessages": {
|
||||
"post": "{کاربر} ارسال شده در {نوع موجودیت} {موجودیت}",
|
||||
"attach": "{کاربر} در {نوع موجودیت} {موجودیت} پیوست شده",
|
||||
"status": "{کاربر} به روز {فیلد} از {نوع موجودیت} {موجودیت}",
|
||||
"update": "{کاربر} به روز {نوع موجودیت} {موجودیت}",
|
||||
"postTargetTeam": "{کاربر} ارسال شده به تیم {هدف}",
|
||||
"postTargetTeams": "{کاربر} ارسال شده به تیم ها {هدف}",
|
||||
"postTargetPortal": "{کاربر} ارسال شده به پورتال {هدف}",
|
||||
"postTargetPortals": "{کاربر} ارسال شده به پورتال ها {هدف}",
|
||||
"postTarget": "{کاربر} ارسال شده به {هدف}",
|
||||
"postTargetYou": "{کاربر} برای شما ارسال شده است",
|
||||
"postTargetYouAndOthers": "{کاربر} به {هدف} ارسال شده و شما",
|
||||
"postTargetAll": "{کاربر} به همه ارسال شد",
|
||||
"mentionInPost": "{کاربر} منشن شده {منشن شده} در {نوع موجودیت} {موجودیت}",
|
||||
"mentionYouInPost": "{کاربر} شما را در {نوع موجودیت} {موجودیت} ذکر کرد",
|
||||
"mentionInPostTarget": "{کاربر} منشن شده {منشن شده} در پست",
|
||||
"mentionYouInPostTarget": "{کاربر} شما را در پست به {هدف}",
|
||||
"mentionYouInPostTargetAll": "{کاربر} شما را در پست به همه ذکر کرده است",
|
||||
"mentionYouInPostTargetNoTarget": "{کاربر} در پست شما را ذکر کرد",
|
||||
"create": "{کاربر} ایجاد {نوع موجودیت} {موجودیت}",
|
||||
"createThis": "{کاربر} این {نوع موجودیت} را ایجاد کرد",
|
||||
"createAssignedThis": "{کاربر} این {نوع موجودیت} ایجاد شده به {assignee}",
|
||||
"createAssigned": "{کاربر} ایجاد {نوع موجودیت} {موجودیت} به {assignee} اختصاص داده شده",
|
||||
"assign": "{کاربر} اختصاص داده شده {نوع موجودیت} {موجودیت} به {assignee}",
|
||||
"assignThis": "{کاربر} این {نوع موجودیت} به {assignee}",
|
||||
"postThis": "{کاربر} پست شد",
|
||||
"attachThis": "{کاربر} متصل شده است",
|
||||
"statusThis": "{کاربر} به روز {فیلد}",
|
||||
"updateThis": "{کاربر} این {نوع موجودیت} را به روز کرد",
|
||||
"createRelatedThis": "{کاربر} ایجاد {نوع موجودیت مربوطه} {موجودیت مربوطه} مربوط به این {نوع موجودیت}",
|
||||
"createRelated": "{کاربر} ایجاد {نوع موجودیت مربوطه} {موجودیت مربوطه} مربوط به {نوع موجودیت} {موجودیت}",
|
||||
"relate": "{کاربر} linked {نوع موجودیت مربوطه} {موجودیت مربوطه} با {نوع موجودیت} {موجودیت}",
|
||||
"relateThis": "{کاربر} linked{نوع موجودیت مربوطه} {موجودیت مربوطه} با این {نوع موجودیت}",
|
||||
"emailReceivedFromThis": "ایمیل دریافت شده از {از}",
|
||||
"emailReceivedInitialFromThis": "ایمیل دریافت شده از {از}، این {نوع موجودیت} ایجاد شده است",
|
||||
"post": "{user} ارسال شده در {entityType} {entity}",
|
||||
"attach": "{user} در {entityType} {entity} پیوست شده",
|
||||
"status": "{user} به روز {field} از {entityType} {entity}",
|
||||
"update": "{user} به روز {entityType} {entity}",
|
||||
"postTargetTeam": "{user} ارسال شده به تیم {target}",
|
||||
"postTargetTeams": "{user} ارسال شده به تیم ها {target}",
|
||||
"postTargetPortal": "{user} ارسال شده به پورتال {target}",
|
||||
"postTargetPortals": "{user} ارسال شده به پورتال ها {target}",
|
||||
"postTarget": "{user} ارسال شده به {target}",
|
||||
"postTargetYou": "{user} برای شما ارسال شده است",
|
||||
"postTargetYouAndOthers": "{user} به {target} ارسال شده و شما",
|
||||
"postTargetAll": "{user} به همه ارسال شد",
|
||||
"mentionInPost": "{user} منشن شده {mentioned} در {entityType} {entity}",
|
||||
"mentionYouInPost": "{user} شما را در {entityType} {entity} ذکر کرد",
|
||||
"mentionInPostTarget": "{user} منشن شده {mentioned} در پست",
|
||||
"mentionYouInPostTarget": "{user} شما را در پست به {target}",
|
||||
"mentionYouInPostTargetAll": "{user} شما را در پست به همه ذکر کرده است",
|
||||
"mentionYouInPostTargetNoTarget": "{user} در پست شما را ذکر کرد",
|
||||
"create": "{user} ایجاد {entityType} {entity}",
|
||||
"createThis": "{user} این {entityType} را ایجاد کرد",
|
||||
"createAssignedThis": "{user} این {entityType} ایجاد شده به {assignee}",
|
||||
"createAssigned": "{user} ایجاد {entityType} {entity} به {assignee} اختصاص داده شده",
|
||||
"assign": "{user} اختصاص داده شده {entityType} {entity} به {assignee}",
|
||||
"assignThis": "{user} این {entityType} به {assignee}",
|
||||
"postThis": "{user} پست شد",
|
||||
"attachThis": "{user} متصل شده است",
|
||||
"statusThis": "{user} به روز {field}",
|
||||
"updateThis": "{user} این {entityType} را به روز کرد",
|
||||
"createRelatedThis": "{user} ایجاد {نوع موجودیت مربوطه} {موجودیت مربوطه} مربوط به این {entityType}",
|
||||
"createRelated": "{user} ایجاد {نوع موجودیت مربوطه} {موجودیت مربوطه} مربوط به {entityType} {entity}",
|
||||
"relate": "{user} linked {نوع موجودیت مربوطه} {موجودیت مربوطه} با {entityType} {entity}",
|
||||
"relateThis": "{user} linked{نوع موجودیت مربوطه} {موجودیت مربوطه} با این {entityType}",
|
||||
"emailReceivedFromThis": "ایمیل دریافت شده از {from}",
|
||||
"emailReceivedInitialFromThis": "ایمیل دریافت شده از {from}، این {entityType} ایجاد شده است",
|
||||
"emailReceivedThis": "ایمیل دریافت شد",
|
||||
"emailReceivedInitialThis": "ایمیل دریافت شده، این {نوع موجودیت} ایجاد شده است",
|
||||
"emailReceivedFrom": "ایمیل دریافت شده از {از}، مربوط به {نوع موجودیت} {موجودیت}",
|
||||
"emailReceivedFromInitial": "ایمیل دریافت شده از {از}، {نوع موجودیت} {موجودیت} ایجاد شده است",
|
||||
"emailReceivedInitialFrom": "ایمیل دریافت شده از {از}، {نوع موجودیت} {موجودیت} ایجاد شده است",
|
||||
"emailReceived": "ایمیل دریافت شده مربوط به {نوع موجودیت} {موجودیت}",
|
||||
"emailReceivedInitial": "ایمیل دریافت شد: {نوع موجودیت} {موجودیت} ایجاد شد",
|
||||
"emailSent": "{با} ایمیل فرستاده شده مربوط به {نوع موجودیت} {موجودیت}",
|
||||
"emailReceivedInitialThis": "ایمیل دریافت شده، این {entityType} ایجاد شده است",
|
||||
"emailReceivedFrom": "ایمیل دریافت شده از {from}، مربوط به {entityType} {entity}",
|
||||
"emailReceivedFromInitial": "ایمیل دریافت شده از {from}، {entityType} {entity} ایجاد شده است",
|
||||
"emailReceivedInitialFrom": "ایمیل دریافت شده از {from}، {entityType} {entity} ایجاد شده است",
|
||||
"emailReceived": "ایمیل دریافت شده مربوط به {entityType} {entity}",
|
||||
"emailReceivedInitial": "ایمیل دریافت شد: {entityType} {entity} ایجاد شد",
|
||||
"emailSent": "{با} ایمیل فرستاده شده مربوط به {entityType} {entity}",
|
||||
"emailSentThis": "{با} ارسال ایمیل",
|
||||
"assignThisSelf": "{کاربر} خودش این {نوع موجودیت}",
|
||||
"assignSelf": "{کاربر} خود اختصاص {نوع موجودیت} {موجودیت}"
|
||||
"assignThisSelf": "{user} خودش این {entityType}",
|
||||
"assignSelf": "{user} خود اختصاص {entityType} {entity}"
|
||||
},
|
||||
"lists": {
|
||||
"dayNames": [
|
||||
@@ -596,10 +596,10 @@
|
||||
}
|
||||
},
|
||||
"streamMessagesMale": {
|
||||
"postTargetSelfAndOthers": "{کاربر} به {هدف} ارسال شده و خودش"
|
||||
"postTargetSelfAndOthers": "{user} به {target} ارسال شده و خودش"
|
||||
},
|
||||
"streamMessagesFemale": {
|
||||
"postTargetSelfAndOthers": "{کاربر} به {هدف} ارسال شده و خودش"
|
||||
"postTargetSelfAndOthers": "{user} به {target} ارسال شده و خودش"
|
||||
},
|
||||
"themes": {
|
||||
"EspoRtl": "RTL G-crm"
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
"label":"",
|
||||
"rows":[
|
||||
[{"name":"executeTime"}, {"name": "createdAt"}],
|
||||
[{"name":"executedAt"}, {"name": "modifiedAt"}],
|
||||
[{"name":"attempts"}, {"name": "failedAttempts"}]
|
||||
[{"name":"startedAt"}, {"name": "modifiedAt"}],
|
||||
[{"name":"executedAt"}, false],
|
||||
[{"name":"attempts"}, false],
|
||||
[{"name":"failedAttempts"}, false]
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"status",
|
||||
"createdAt",
|
||||
"executeTime",
|
||||
"startedAt",
|
||||
"executedAt",
|
||||
"queue"
|
||||
]
|
||||
|
||||
26
application/Espo/Resources/metadata/app/client.json
Normal file
26
application/Espo/Resources/metadata/app/client.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"scriptList": [
|
||||
"client/espo.min.js"
|
||||
],
|
||||
"developerModeScriptList": [
|
||||
"client/lib/jquery-2.1.4.min.js",
|
||||
"client/lib/underscore-min.js",
|
||||
"client/lib/es6-promise.min.js",
|
||||
"client/lib/backbone-min.js",
|
||||
"client/lib/handlebars.js",
|
||||
"client/lib/base64.js",
|
||||
"client/lib/jquery-ui.min.js",
|
||||
"client/lib/jquery.ui.touch-punch.min.js",
|
||||
"client/lib/moment.min.js",
|
||||
"client/lib/moment-timezone-with-data.min.js",
|
||||
"client/lib/jquery.timepicker.min.js",
|
||||
"client/lib/jquery.autocomplete.js",
|
||||
"client/lib/bootstrap.min.js",
|
||||
"client/lib/bootstrap-datepicker.js",
|
||||
"client/lib/bull.js",
|
||||
"client/lib/marked.min.js",
|
||||
"client/src/loader.js",
|
||||
"client/src/utils.js",
|
||||
"client/src/exceptions.js"
|
||||
]
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
},
|
||||
{
|
||||
"name": "ifThen",
|
||||
"insertText": "ifThenElse(CONDITION, CONSEQUENT)"
|
||||
"insertText": "ifThen(CONDITION, CONSEQUENT)"
|
||||
},
|
||||
{
|
||||
"name": "string\\concatenate",
|
||||
@@ -16,10 +16,30 @@
|
||||
"name": "string\\substring",
|
||||
"insertText": "string\\substring(STRING, START, LENGTH)"
|
||||
},
|
||||
{
|
||||
"name": "string\\contains",
|
||||
"insertText": "string\\contains(STRING, NEEDLE)"
|
||||
},
|
||||
{
|
||||
"name": "string\\test",
|
||||
"insertText": "string\\test(STRING, REGULAR_EXPRESSION)"
|
||||
},
|
||||
{
|
||||
"name": "string\\length",
|
||||
"insertText": "string\\length(STRING)"
|
||||
},
|
||||
{
|
||||
"name": "string\\trim",
|
||||
"insertText": "string\\trim(STRING)"
|
||||
},
|
||||
{
|
||||
"name": "string\\lowerCase",
|
||||
"insertText": "string\\lowerCase(STRING)"
|
||||
},
|
||||
{
|
||||
"name": "string\\upperCase",
|
||||
"insertText": "string\\upperCase(STRING)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\today",
|
||||
"insertText": "datetime\\today()"
|
||||
@@ -32,6 +52,30 @@
|
||||
"name": "datetime\\format",
|
||||
"insertText": "datetime\\format(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\date",
|
||||
"insertText": "datetime\\date(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\month",
|
||||
"insertText": "datetime\\month(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\year",
|
||||
"insertText": "datetime\\year(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\hour",
|
||||
"insertText": "datetime\\hour(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\minute",
|
||||
"insertText": "datetime\\minute(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\dayOfWeek",
|
||||
"insertText": "datetime\\dayOfWeek(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "datetime\\addMinutes",
|
||||
"insertText": "datetime\\addMinutes(VALUE, MINUTES)"
|
||||
@@ -72,6 +116,14 @@
|
||||
"name": "number\\round",
|
||||
"insertText": "number\\round(VALUE, PRECISION)"
|
||||
},
|
||||
{
|
||||
"name": "number\\floor",
|
||||
"insertText": "number\\floor(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "number\\ceil",
|
||||
"insertText": "number\\ceil(VALUE)"
|
||||
},
|
||||
{
|
||||
"name": "entity\\isNew",
|
||||
"insertText": "entity\\isNew()"
|
||||
@@ -108,6 +160,14 @@
|
||||
"name": "entity\\isRelated",
|
||||
"insertText": "entity\\isRelated(LINK, ID)"
|
||||
},
|
||||
{
|
||||
"name": "entity\\sumRelated",
|
||||
"insertText": "entity\\sumRelated(LINK, FIELD, FILTER)"
|
||||
},
|
||||
{
|
||||
"name": "entity\\countRelated",
|
||||
"insertText": "entity\\countRelated(LINK, FILTER)"
|
||||
},
|
||||
{
|
||||
"name": "env\\userAttribute",
|
||||
"insertText": "env\\userAttribute(ATTRIBUTE)"
|
||||
|
||||
@@ -38,5 +38,10 @@
|
||||
"path": "client/lib/bootstrap-colorpicker.js",
|
||||
"exportsTo": "$",
|
||||
"exportsAs": "colorpicker"
|
||||
},
|
||||
"exif": {
|
||||
"path": "client/lib/exif-js.js",
|
||||
"exportsTo": "window",
|
||||
"exportsAs": "EXIF"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -150,7 +150,8 @@
|
||||
"type": "varchar",
|
||||
"maxLength": 255,
|
||||
"readOnly": true,
|
||||
"index": true
|
||||
"index": true,
|
||||
"textFilterDisabled": true
|
||||
},
|
||||
"messageIdInternal": {
|
||||
"type": "varchar",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"type": "varchar"
|
||||
},
|
||||
"body": {
|
||||
"type": "text",
|
||||
"type": "wysiwyg",
|
||||
"view": "views/fields/wysiwyg",
|
||||
"useIframe": true
|
||||
},
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
},
|
||||
"executeTime": {
|
||||
"type": "datetime",
|
||||
"required": true
|
||||
"required": true,
|
||||
"hasSeconds": true
|
||||
},
|
||||
"number": {
|
||||
"type": "int",
|
||||
@@ -54,8 +55,13 @@
|
||||
"maxLength": 36,
|
||||
"default": null
|
||||
},
|
||||
"startedAt": {
|
||||
"type": "datetime",
|
||||
"hasSeconds": true
|
||||
},
|
||||
"executedAt": {
|
||||
"type": "datetime"
|
||||
"type": "datetime",
|
||||
"hasSeconds": true
|
||||
},
|
||||
"pid": {
|
||||
"type": "int"
|
||||
@@ -76,11 +82,13 @@
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"hasSeconds": true
|
||||
},
|
||||
"modifiedAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"hasSeconds": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
|
||||
@@ -120,7 +120,7 @@
|
||||
"tooltip": true
|
||||
},
|
||||
"signature": {
|
||||
"type": "text",
|
||||
"type": "wysiwyg",
|
||||
"view": "views/fields/wysiwyg"
|
||||
},
|
||||
"defaultReminders": {
|
||||
|
||||
@@ -6,12 +6,18 @@
|
||||
"readOnly": true
|
||||
},
|
||||
"status": {
|
||||
"type": "varchar",
|
||||
"readOnly": true
|
||||
"type": "enum",
|
||||
"readOnly": true,
|
||||
"options": ["Success", "Failed"],
|
||||
"style": {
|
||||
"Success": "success",
|
||||
"Failed": "danger"
|
||||
}
|
||||
},
|
||||
"executionTime": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
"readOnly": true,
|
||||
"hasSeconds": true
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
|
||||
@@ -447,7 +447,8 @@
|
||||
"view": "views/settings/fields/history-entity-list"
|
||||
},
|
||||
"googleMapsApiKey": {
|
||||
"type": "varchar"
|
||||
"type": "varchar",
|
||||
"onlyUser": true
|
||||
},
|
||||
"massEmailDisableMandatoryOptOutLink": {
|
||||
"type": "bool"
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
"trim": true
|
||||
},
|
||||
"body": {
|
||||
"type": "text",
|
||||
"type": "wysiwyg",
|
||||
"view": "views/fields/wysiwyg"
|
||||
},
|
||||
"header": {
|
||||
"type": "text",
|
||||
"type": "wysiwyg",
|
||||
"view": "views/fields/wysiwyg"
|
||||
},
|
||||
"footer": {
|
||||
"type": "text",
|
||||
"type": "wysiwyg",
|
||||
"view": "views/fields/wysiwyg",
|
||||
"tooltip": true
|
||||
},
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"fields":{
|
||||
"currency":{
|
||||
"type":"varchar",
|
||||
"disabled": true,
|
||||
"layoutDetailDisabled": true,
|
||||
"layoutListDisabled": true,
|
||||
"layoutMassUpdateDisabled": true,
|
||||
"maxLength": 6
|
||||
},
|
||||
"converted":{
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
"type": "bool",
|
||||
"name":"useNumericFormat"
|
||||
},
|
||||
{
|
||||
"type": "bool",
|
||||
"name": "hasSeconds",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"type": "enumInt",
|
||||
"name": "minuteStep",
|
||||
|
||||
@@ -4,6 +4,10 @@
|
||||
"name":"required",
|
||||
"type":"bool",
|
||||
"default":false
|
||||
},
|
||||
{
|
||||
"name":"audited",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"actualFields":[
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
"type":"jsonObject",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name":"displayAsLabel",
|
||||
"type":"bool"
|
||||
},
|
||||
{
|
||||
"name":"audited",
|
||||
"type":"bool"
|
||||
|
||||
@@ -11,6 +11,11 @@
|
||||
"type":"varchar",
|
||||
"view": "views/admin/field-manager/fields/foreign/field",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "view",
|
||||
"type":"varchar",
|
||||
"hidden": true
|
||||
}
|
||||
],
|
||||
"fieldTypeList": ["varchar", "enum", "enumInt", "enumFloat", "int", "float", "website", "date", "datetime", "text", "number", "bool"],
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"type":"varchar",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "allowCustomOptions",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"name":"audited",
|
||||
"type":"bool"
|
||||
|
||||
@@ -15,6 +15,10 @@
|
||||
"name":"defaultType",
|
||||
"type":"varchar",
|
||||
"default": "Mobile"
|
||||
},
|
||||
{
|
||||
"name":"audited",
|
||||
"type":"bool"
|
||||
}
|
||||
],
|
||||
"notActualFields":[
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Přihlašovací jméno: {{userName}}</p>
|
||||
<p>Heslo: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Brugernavn: {{userName}}</p>
|
||||
<p>Kodeord: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Benutzername: {{userName}}</p>
|
||||
<p>Passwort: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Username: {{userName}}</p>
|
||||
<p>Password: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Nombre Usuario: {{userName}}</p>
|
||||
<p>Contraseña: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Nombre Usuario: {{userName}}</p>
|
||||
<p>Contraseña: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Nom d'utilisateur: {{userName}}</p>
|
||||
<p>Mot de passe: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Korisničko ime: {{userName}}</p>
|
||||
<p>Lozinka: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Felhasználónév: {{userName}}</p>
|
||||
<p>Jelszó: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Username: {{userName}}</p>
|
||||
<p>Password: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Username: {{userName}}</p>
|
||||
<p>Password: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Vartotojo vardas: {{userName}}</p>
|
||||
<p>Slaptažodis: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Brukernavn: {{userName}}</p>
|
||||
<p>Passord: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Gebruikersnaam: {{userName}}</p>
|
||||
<p>Wachtwoord: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Użytkonik: {{userName}}</p>
|
||||
<p>Hasło: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Usuário: {{userName}}</p>
|
||||
<p>Senha: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
@@ -3,4 +3,4 @@
|
||||
<p>Username: {{userName}}</p>
|
||||
<p>Password: {{password}}</p>
|
||||
|
||||
<p>{{siteUrl}}</p>
|
||||
<p><a href="{{siteUrl}}">{{siteUrl}}</a></p>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user