Compare commits

..

79 Commits

Author SHA1 Message Date
Yuri Kuznetsov b0420b8b93 fix email folder 2020-10-28 13:19:49 +02:00
Yuri Kuznetsov b47018cbab hide email menu items in portal 2020-10-28 11:58:44 +02:00
Yuri Kuznetsov ea69ab6eaf v 2020-10-28 11:12:51 +02:00
Eymen Elkum 92887401b2 address view remove duplicated codes (#1823) 2020-10-28 11:10:52 +02:00
Yuri Kuznetsov df0bda6324 Merge branch 'hotfix/6.0.3' of github.com:espocrm/espocrm into hotfix/6.0.3 2020-10-28 11:05:37 +02:00
Yuri Kuznetsov 9d67808496 fix tester 2020-10-28 11:02:24 +02:00
Yuri Kuznetsov cf33a98f20 fix activities and select manager 2020-10-27 17:04:49 +02:00
Yuri Kuznetsov afbda344ec fix export specific fields 2020-10-27 14:25:47 +02:00
Yuri Kuznetsov 1079597584 fix 2fa 2020-10-27 14:13:00 +02:00
Yuri Kuznetsov af9b411a4c diff all patch versions 2020-10-27 12:07:39 +02:00
Yuri Kuznetsov cd49d951d8 v 2020-10-27 11:59:55 +02:00
Yuri Kuznetsov 7d63c114aa fix export all fields 2020-10-27 11:06:52 +02:00
Yuri Kuznetsov af6f05ba07 formula fix logical operator priority 2020-10-27 10:31:42 +02:00
Yuri Kuznetsov 46f8eb0cdb formula fix logical not 2020-10-27 10:23:03 +02:00
Pomazan Bogdan c543bb5a5b Update Admin.json (#1814) 2020-10-26 15:36:46 +02:00
Yuri Kuznetsov d1997089e4 v 2020-10-26 13:06:11 +02:00
Yuri Kuznetsov 57dd0177f9 fix export 2020-10-25 18:56:57 +02:00
Yuri Kuznetsov ba76e5ee3d fix language manager 2020-10-25 18:54:34 +02:00
Yuri Kuznetsov 76c93b842c case close reject actions only if available in status list 2020-10-23 10:25:26 +03:00
Yuri Kuznetsov e73bff5ddf v 2020-10-22 12:14:40 +03:00
Yuri Kuznetsov 7d5c0d754e fix upgrade script 2020-10-22 11:47:59 +03:00
Taras Machyshyn e08a74b129 Bug fixes 2020-10-22 11:30:00 +03:00
Taras Machyshyn be07aafaf4 Fix 2020-10-21 15:33:42 +03:00
Taras Machyshyn e2bf27e524 Fix 2020-10-21 12:00:47 +03:00
Yuri Kuznetsov b0157adbe2 cs fix 2020-10-21 09:40:34 +03:00
Yuri Kuznetsov 239593afb5 fix mass actions 2020-10-21 09:31:42 +03:00
Yuri Kuznetsov b97d764588 fix error msg 2020-10-19 15:30:36 +03:00
Yuri Kuznetsov bf8e7af984 export fixes 2020-10-19 15:19:24 +03:00
Yuri Kuznetsov bf68c475e5 email queue items filters email 2020-10-19 13:37:06 +03:00
Yuri Kuznetsov 29624239ba search queue items 2020-10-19 13:35:43 +03:00
Yuri Kuznetsov 4cb3c4918b campaign log search by email address 2020-10-19 12:59:36 +03:00
Yuri Kuznetsov 26fa0d65e1 fix campaign log search 2020-10-19 12:57:17 +03:00
Yuri Kuznetsov 958baed3df fix email invalid on bounced 2020-10-19 12:35:48 +03:00
Yuri Kuznetsov 40d6fb7565 fix export 2020-10-15 19:40:37 +03:00
Yuri Kuznetsov a7551b7dbf fix request getContentType 2020-10-15 17:35:58 +03:00
Yuri Kuznetsov 9ed33c4dc4 remove self assign in portal 2020-10-14 16:23:02 +03:00
Yuri Kuznetsov 6b18fbe8f7 select manager fix 2020-10-14 15:48:52 +03:00
Yuri Kuznetsov 53b9880ea8 entry point changes 2020-10-14 15:15:09 +03:00
Yuri Kuznetsov 62538664df Merge branch 'master' of github.com:espocrm/espocrm 2020-10-14 13:17:56 +03:00
Yuri Kuznetsov f678fb16cc ouauth callback changes 2020-10-14 13:17:46 +03:00
Yuri Kuznetsov fe09aa1cd9 cs fix 2020-10-13 11:01:52 +03:00
Yuri Kuznetsov 710decc610 fix htmlizer and email template no id 2020-10-13 10:47:37 +03:00
Yuri Kuznetsov 5d481d3528 email auto reply error log 2020-10-13 10:47:27 +03:00
Yuri Kuznetsov 8bcd3b72c5 hook fix 2020-10-13 09:56:13 +03:00
Yuri Kuznetsov 0a0da810e9 application state changes 2020-10-13 09:56:04 +03:00
Yuri Kuznetsov e630c046fe Merge branch 'hotfix/5.9.5' 2020-10-12 14:01:49 +03:00
Yuri Kuznetsov 2857b5c53d fix import button 2020-10-12 14:00:25 +03:00
Yuri Kuznetsov 0965c61f55 fix reminder submition 2020-10-12 10:14:17 +03:00
Yuri Kuznetsov 9dd318b176 fix label 2020-10-10 16:08:58 +03:00
Yuri Kuznetsov f8beca720c label fixes 2020-10-10 16:06:57 +03:00
Yuri Kuznetsov d8f58dcc1d upgrade cleanup 2020-10-09 14:58:34 +03:00
Yuri Kuznetsov 32aaaa350e v 2020-10-09 14:37:50 +03:00
Yuri Kuznetsov 0f6d1c6817 fix diff: ignore upgrades 2020-10-09 13:56:41 +03:00
Yuri Kuznetsov 8c5a342a5a fix diff 2020-10-09 13:53:07 +03:00
Yuri Kuznetsov 257401898b upgrade cleanup files and fix msg 2020-10-09 12:24:24 +03:00
Yuri Kuznetsov e7ff3f903a fix email modal 2020-10-08 14:05:28 +03:00
Taras Machyshyn 6f82638b5d Upgrade script 2020-10-07 17:46:08 +03:00
Taras Machyshyn 0625ce51c4 Upgrade script 2020-10-07 17:26:11 +03:00
Taras Machyshyn f5670436d0 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-10-07 17:19:05 +03:00
Taras Machyshyn 5d20a56ac8 Upgrade script 2020-10-07 17:18:34 +03:00
Yuri Kuznetsov 020cf9713a Merge branch 'master' of https://github.com/espocrm/espocrm 2020-10-07 16:49:33 +03:00
Yuri Kuznetsov fe1361fc9c portal action list restriction 2020-10-07 16:49:01 +03:00
Taras Machyshyn 36621be496 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-10-07 15:51:31 +03:00
Taras Machyshyn 25690030d1 Installation fixes 2020-10-07 15:50:57 +03:00
Yuri Kuznetsov fcd6be3a0c fix password recovery 2020-10-07 15:39:03 +03:00
Yuri Kuznetsov ad8dfa487c fix convert lead 2020-10-07 15:13:39 +03:00
Yuri Kuznetsov 7f6f54628d cs fix 2020-10-07 15:04:42 +03:00
Taras Machyshyn 8ea0b072c6 Installation fixes 2020-10-07 14:22:21 +03:00
Yuri Kuznetsov 0df6248226 getPhpSafeContents for unix 2020-10-07 13:12:55 +03:00
Yuri Kuznetsov 26a132658e date after before validation autocomplete 2020-10-07 12:42:51 +03:00
Yuri Kuznetsov d02529ee86 Merge branch 'hotfix/5.9.5' 2020-10-07 11:02:01 +03:00
Yuri Kuznetsov 0cec0fef51 fix image orientation 2020-10-07 11:01:32 +03:00
Yuri Kuznetsov e56f6ab75d fix image orientation 2020-10-07 10:55:39 +03:00
Yuri Kuznetsov a4980aa8f2 cs fix 2020-10-07 10:53:21 +03:00
Yuri Kuznetsov fd219b49be calendar modal fix 2020-10-06 13:44:35 +03:00
Yuri Kuznetsov b90579aadc cs fix 2020-10-06 13:44:18 +03:00
Yuri Kuznetsov 0b39d7f6b6 cs fix 2020-10-06 12:13:17 +03:00
Yuri Kuznetsov a44ce06162 Merge branch 'master' of https://github.com/espocrm/espocrm 2020-10-06 12:02:35 +03:00
Yuri Kuznetsov f1eb2ab5d5 cs fix 2020-10-06 12:00:48 +03:00
69 changed files with 1181 additions and 413 deletions
+8 -2
View File
@@ -138,7 +138,13 @@ class RequestWrapper implements ApiRequest
public function getContentType() : ?string
{
return $this->getHeader('Content-Type');
if (!$this->hasHeader('Content-Type')) {
return null;
}
$contentType = $this->request->getHeader('Content-Type')[0];
return strtolower($contentType);
}
public function getBodyContents() : ?string
@@ -163,7 +169,7 @@ class RequestWrapper implements ApiRequest
{
$contents = $this->getBodyContents();
if (strtolower($this->getContentType()) === 'application/json' && $contents) {
if ($this->getContentType() === 'application/json' && $contents) {
$this->parsedBody = json_decode($contents);
if (is_array($this->parsedBody)) {
@@ -29,9 +29,8 @@
namespace Espo\Core\ApplicationRunners;
use Espo\Core\Exceptions\Error;
use Espo\Core\{
Exceptions\Error,
InjectableFactory,
EntryPointManager,
ApplicationUser,
@@ -39,7 +38,6 @@ use Espo\Core\{
Portal\Application as PortalApplication,
Utils\Route,
Utils\ClientManager,
Authentication\Authentication,
Api\Auth as ApiAuth,
Api\ErrorOutput as ApiErrorOutput,
@@ -48,18 +46,12 @@ use Espo\Core\{
};
use Slim\{
App as SlimApp,
Factory\AppFactory as SlimAppFactory,
};
use Psr\Http\{
Message\ResponseInterface as Psr7Response,
Message\ServerRequestInterface as Psr7Request,
Server\RequestHandlerInterface as Psr7RequestHandler,
ResponseEmitter,
Factory\ServerRequestCreatorFactory,
Psr7\Response,
};
use StdClass;
use Exception;
/**
@@ -113,30 +105,19 @@ class EntryPoint implements ApplicationRunner
}
}
$slim = SlimAppFactory::create();
$request = (ServerRequestCreatorFactory::create())->createServerRequestFromGlobals();
$slim->setBasePath(Route::detectBasePath());
if ($request->getMethod() !== 'GET') {
throw new Error("Only GET request allowed for entry points.");
}
$slim->add(
function (Psr7Request $request, Psr7RequestHandler $handler) use (
$entryPoint, $data, $authRequired, $authNotStrict, $slim
) : Psr7Response {
$requestWrapped = new RequestWrapper($request, $slim->getBasePath());
$responseWrapped = new ResponseWrapper($handler->handle($request));
$requestWrapped = new RequestWrapper($request, Route::detectBasePath());
$this->processRequest($entryPoint, $requestWrapped, $responseWrapped, $data, $authRequired, $authNotStrict);
$responseWrapped = new ResponseWrapper(new Response());
return $responseWrapped->getResponse();
}
);
$this->processRequest($entryPoint, $requestWrapped, $responseWrapped, $data, $authRequired, $authNotStrict);
$route = Route::detectEntryPointRoute();
$slim->get($route, function (Psr7Request $request, Psr7Response $response) : Psr7Response {
return $response;
});
$slim->run();
(new ResponseEmitter())->emit($responseWrapped->getResponse());
}
protected function processRequest(
+36 -9
View File
@@ -55,11 +55,13 @@ class ApplicationState
}
/**
* Get portal ID (if an applicaition is portal).
* Get a portal ID (if an applicaition is portal).
*/
public function getPortalId() : string
{
if (!$this->isPortal()) throw new Error("Can't get portal ID for non-portal application.");
if (!$this->isPortal()) {
throw new Error("Can't get portal ID for non-portal application.");
}
return $this->getPortal()->id;
}
@@ -69,7 +71,9 @@ class ApplicationState
*/
public function getPortal() : PortalEntity
{
if (!$this->isPortal()) throw new Error("Can't get portal for non-portal application.");
if (!$this->isPortal()) {
throw new Error("Can't get portal for non-portal application.");
}
return $this->container->get('portal');
}
@@ -83,21 +87,38 @@ class ApplicationState
}
/**
* Get current logged user. If no auth is applied, then system user will be returned.
* Get a current logged user. If no auth is applied, then the system user will be returned.
*/
public function getUser() : UserEntity
{
if (!$this->hasUser()) throw new Error("User is not yet available.");
if (!$this->hasUser()) {
throw new Error("User is not yet available.");
}
return $this->container->get('user');
}
/**
* Get an ID of a current logged user. If no auth is applied, then the system user will be returned.
*/
public function getUserId() : string
{
return $this->getUser()->id;
}
/**
* Whether a user is logged.
*/
public function isLogged() : bool
{
if (!$this->container->has('user')) return false;
if ($this->getUser()->isSystem()) return false;
if (!$this->container->has('user')) {
return false;
}
if ($this->getUser()->isSystem()) {
return false;
}
return true;
}
@@ -106,7 +127,10 @@ class ApplicationState
*/
public function isAdmin() : bool
{
if (!$this->isLogged()) return false;
if (!$this->isLogged()) {
return false;
}
return $this->getUser()->isAdmin();
}
@@ -116,7 +140,10 @@ class ApplicationState
*/
public function isApi() : bool
{
if (!$this->isLogged()) return false;
if (!$this->isLogged()) {
return false;
}
return $this->getUser()->isApi();
}
}
@@ -368,7 +368,7 @@ class Authentication
if ($code) {
if (!$impl->verifyCode($loggedUser, $code)) {
Result::fail('Code not verified');
return Result::fail('Code not verified');
}
return $result;
@@ -38,6 +38,6 @@ class NotType extends BaseFunction
{
public function process(ArgumentList $args)
{
return !$this->evaluate($args);
return !$this->evaluate($args[0]);
}
}
+2 -1
View File
@@ -40,7 +40,8 @@ class Parser
{
protected $priorityList = [
['='],
['||', '&&'],
['||'],
['&&'],
['==', '!=', '>', '<', '>=', '<='],
['+', '-'],
['*', '/', '%'],
+1 -1
View File
@@ -120,7 +120,7 @@ class Htmlizer
$relationList = $entity->getRelationList();
if (!$skipLinks && $level === 0 && $this->entityManager) {
if (!$skipLinks && $level === 0 && $this->entityManager && $entity->id) {
foreach ($relationList as $relation) {
$collection = null;
+1 -1
View File
@@ -274,7 +274,7 @@ class Recovery
'isSystem' => true,
]);
if (!$this->emailAddress->hasSystemSmtp()) {
if (!$this->emailSender->hasSystemSmtp()) {
$sender->withSmtpParams([
'server' => $config->get('internalSmtpServer'),
'port' => $config->get('internalSmtpPort'),
+35 -10
View File
@@ -2368,7 +2368,9 @@ class SelectManager
protected function textFilter($textFilter, array &$result, $noFullText = false)
{
$fieldDefs = $this->getSeed()->getAttributes();
$fieldList = $this->getTextFilterFieldList();
$group = [];
$textFilterContainsMinLength = $this->getConfig()->get('textFilterContainsMinLength', self::MIN_LENGTH_FOR_CONTENT_SEARCH);
@@ -2401,6 +2403,7 @@ class SelectManager
$textFilterForFullTextSearch = str_replace('%', '*', $textFilterForFullTextSearch);
$skipFullTextSearch = false;
if (!$forceFullTextSearch) {
if (mb_strpos($textFilterForFullTextSearch, '*') === 0) {
$skipFullTextSearch = true;
@@ -2409,9 +2412,12 @@ class SelectManager
}
}
if ($noFullText) $skipFullTextSearch = true;
if ($noFullText) {
$skipFullTextSearch = true;
}
$fullTextSearchData = null;
if (!$skipFullTextSearch) {
$fullTextSearchData = $this->getFullTextSearchDataForTextFilter($textFilterForFullTextSearch, !$useFullTextSearch);
}
@@ -2419,6 +2425,7 @@ class SelectManager
$fullTextGroup = [];
$fullTextSearchFieldList = [];
if ($fullTextSearchData) {
if ($this->fullTextRelevanceThreshold) {
$fullTextGroup[] = [$fullTextSearchData['where'] . '>=' => $this->fullTextRelevanceThreshold];
@@ -2434,12 +2441,15 @@ class SelectManager
$orderTypeMap = [
'combined' => self::FT_ORDER_COMBINTED,
'relavance' => self::FT_ORDER_RELEVANCE,
'relevance' => self::FT_ORDER_RELEVANCE,
'original' => self::FT_ORDER_ORIGINAL,
];
$mOrderType = $this->getMetadata()->get(['entityDefs', $this->entityType, 'collection', 'fullTextSearchOrderType']);
if ($mOrderType) $fullTextOrderType = $orderTypeMap[$mOrderType];
if ($mOrderType) {
$fullTextOrderType = $orderTypeMap[$mOrderType];
}
if (!isset($result['orderBy']) || $fullTextOrderType === self::FT_ORDER_RELEVANCE) {
$result['orderBy'] = [[$relevanceExpression, 'desc']];
@@ -2468,9 +2478,14 @@ class SelectManager
foreach ($fieldList as $field) {
if ($useFullTextSearch) {
if (in_array($field, $fullTextSearchFieldList)) continue;
if (in_array($field, $fullTextSearchFieldList)) {
continue;
}
}
if ($forceFullTextSearch) {
continue;
}
if ($forceFullTextSearch) continue;
$seed = $this->getSeed();
@@ -2478,12 +2493,16 @@ class SelectManager
if (strpos($field, '.') !== false) {
list($link, $foreignField) = explode('.', $field);
$foreignEntityType = $seed->getRelationParam($link, 'entity');
$seed = $this->getEntityManager()->getEntity($foreignEntityType);
$this->addLeftJoin($link, $result);
if ($seed->getRelationParam($link, 'type') === $seed::HAS_MANY) {
$this->setDistinct(true, $result);
}
$attributeType = $seed->getAttributeType($foreignField);
} else {
$attributeType = $seed->getAttributeType($field);
@@ -2493,6 +2512,7 @@ class SelectManager
if (is_numeric($textFilter)) {
$group[$field] = intval($textFilter);
}
continue;
}
@@ -2666,11 +2686,6 @@ class SelectManager
$selectParams1['havingClause'][] = $selectParams2['havingClause'];
}
if (!empty($selectParams2['leftJoins'])) {
foreach ($selectParams2['leftJoins'] as $item) {
$this->addLeftJoin($item, $selectParams1);
}
}
if (!empty($selectParams2['joins'])) {
foreach ($selectParams2['joins'] as $item) {
@@ -2678,6 +2693,16 @@ class SelectManager
}
}
if (!empty($selectParams2['leftJoins'])) {
foreach ($selectParams2['leftJoins'] as $item) {
if ($this->hasJoin($item, $selectParams1)) {
continue;
}
$this->addLeftJoin($item, $selectParams1);
}
}
if (isset($selectParams2['select'])) {
$selectParams1['select'] = $selectParams2['select'];
}
@@ -169,6 +169,7 @@ class AdminNotificationManager
$extension = $this->getEntityManager()->getRepository('Extension')
->select(['version'])
->where([
'name' => $extensionName,
'isInstalled' => true,
])
->order('createdAt', true)
+1 -5
View File
@@ -203,7 +203,7 @@ class Manager
/**
* Get array or StdClass data from PHP file.
* For Windows: If a file is not yet written, it will wait until it's ready.
* If a file is not yet written, it will wait until it's ready.
*
* @return array|StdClass
*/
@@ -226,10 +226,6 @@ class Manager
return $data;
}
if (stripos(\PHP_OS, 'WIN') !== 0) {
break;
}
usleep(self::GET_SAFE_CONTENTS_RETRY_INTERVAL * 1000000);
$counter ++;
@@ -34,13 +34,15 @@ use Espo\Core\EntryPoints\{
NoAuth,
};
use Espo\Core\Api\Request;
class OauthCallback implements EntryPoint
{
use NoAuth;
public function run()
public function run(Request $request)
{
echo "EspoCRM rocks! If this window is not closed automatically, it's probable that URL you use to access ".
echo "If this window is not closed automatically, it's probable that URL you use to access ".
"EspoCRM doesn't match URL specified at Administration > Settings > Site URL.";
}
}
@@ -34,17 +34,20 @@ use Espo\ORM\Entity;
use Espo\Core\{
Utils\Config,
ORM\EntityManager,
ApplicationState,
};
class AssignmentEmailNotification
{
protected $config;
protected $entityManager;
protected $applicationState;
public function __construct(Config $config, EntityManager $entityManager)
public function __construct(Config $config, EntityManager $entityManager, ApplicationState $applicationState)
{
$this->config = $config;
$this->entityManager = $entityManager;
$this->applicationState = $applicationState;
}
public function afterSave(Entity $entity, array $options = [])
@@ -67,17 +70,25 @@ class AssignmentEmailNotification
if ($entity->has('assignedUsersIds')) {
$userIdList = $entity->getLinkMultipleIdList('assignedUsers');
$fetchedAssignedUserIdList = $entity->getFetched('assignedUsersIds');
if (!is_array($fetchedAssignedUserIdList)) {
$fetchedAssignedUserIdList = [];
}
foreach ($userIdList as $userId) {
if (in_array($userId, $fetchedAssignedUserIdList)) continue;
if (!$this->isNotSelfAssignment($entity, $userId)) continue;
if (in_array($userId, $fetchedAssignedUserIdList)) {
continue;
}
if (!$this->isNotSelfAssignment($entity, $userId)) {
continue;
}
$this->createJob($entity, $userId);
}
} else {
$userId = $entity->get('assignedUserId');
if (!empty($userId) &&
$entity->isAttributeChanged('assignedUserId') && $this->isNotSelfAssignment($entity, $userId)
) {
@@ -96,26 +107,29 @@ class AssignmentEmailNotification
$isNotSelfAssignment = $assignedUserId !== $entity->get('modifiedById');
}
} else {
$isNotSelfAssignment = $assignedUserId !== $this->getUser()->id;
$isNotSelfAssignment = $assignedUserId !== $this->applicationState->getUserId();
}
return $isNotSelfAssignment;
}
protected function createJob(Entity $entity, $userId)
{
$job = $this->entityManager->getEntity('Job');
$job->set([
'serviceName' => 'EmailNotification',
'methodName' => 'notifyAboutAssignmentJob',
'data' => [
'userId' => $userId,
'assignerUserId' => $this->getUser()->id,
'assignerUserId' => $this->applicationState->getUserId(),
'entityId' => $entity->id,
'entityType' => $entity->getEntityType(),
],
'executeTime' => date('Y-m-d H:i:s'),
'queue' => 'e0',
]);
$this->entityManager->saveEntity($job);
}
}
@@ -36,6 +36,9 @@ use Espo\Core\{
Jobs\Job,
};
use Throwable;
use DateTime;
class SubmitPopupReminders implements Job
{
const REMINDER_PAST_HOURS = 24;
@@ -53,21 +56,27 @@ class SubmitPopupReminders implements Job
public function run()
{
if (!$this->config->get('useWebSocket')) return;
if (!$this->config->get('useWebSocket')) {
return;
}
$dt = new \DateTime();
$dt = new DateTime();
$now = $dt->format('Y-m-d H:i:s');
$pastHours = $this->config->get('reminderPastHours', self::REMINDER_PAST_HOURS);
$nowShifted = $dt->modify('-' . $pastHours . ' hours')->format('Y-m-d H:i:s');
$reminderList = $this->entityManager->getRepository('Reminder')->where([
'type' => 'Popup',
'remindAt<=' => $now,
'startAt>' => $nowShifted,
'isSubmitted' => false,
])->find();
$reminderList = $this->entityManager
->getRepository('Reminder')
->where([
'type' => 'Popup',
'remindAt<=' => $now,
'startAt>' => $nowShifted,
'isSubmitted' => false,
])
->find();
$submitData = [];
@@ -78,6 +87,7 @@ class SubmitPopupReminders implements Job
if (!$userId || !$entityType || !$entityId) {
$this->deleteReminder($reminder);
continue;
}
@@ -85,19 +95,24 @@ class SubmitPopupReminders implements Job
if (!$entity) {
$this->deleteReminder($reminder);
continue;
}
if ($entity->hasLinkMultipleField('users')) {
$entity->loadLinkMultipleField('users', ['status' => 'acceptanceStatus']);
$status = $entity->getLinkMultipleColumn('users', 'status', $userId);
if ($status === 'Declined') {
$this->deleteReminder($reminder);
continue;
}
}
$dateAttribute = 'dateStart';
if ($entityType === 'Task') {
$dateAttribute = 'dateEnd';
}
@@ -125,10 +140,10 @@ class SubmitPopupReminders implements Job
foreach ($submitData as $userId => $list) {
try {
$this->getContainer()->get('webSocketSubmission')->submit('popupNotifications.event', $userId, (object) [
$this->webSocketSubmission->submit('popupNotifications.event', $userId, (object) [
'list' => $list
]);
} catch (\Throwable $e) {
} catch (Throwable $e) {
$GLOBALS['log']->error('Job SubmitPopupReminders: [' . $e->getCode() . '] ' .$e->getMessage());
}
}
@@ -38,7 +38,8 @@
},
"labels": {
"Create Task": "Create Task",
"Complete": "Complete"
"Complete": "Complete",
"overdue": "overdue"
},
"presetFilters": {
"actual": "Actual",
@@ -2,5 +2,6 @@
"massEmail",
"status",
"target",
"sentAt"
"sentAt",
"emailAddress"
]
@@ -78,6 +78,7 @@
}
},
"collection": {
"textFilterFields": ["queueItem.id", "queueItem.emailAddress"],
"orderBy": "createdAt",
"order": "desc"
},
@@ -45,6 +45,7 @@
}
},
"collection": {
"textFilterFields": ["id", "emailAddress"],
"orderBy": "createdAt",
"order": "desc"
}
@@ -4,6 +4,7 @@
"tab": false,
"acl": "recordAllTeamNo",
"aclPortal": "recordAllNo",
"aclPortalActionList": ["read"],
"module": "Crm",
"customizable": true,
"importable": false,
@@ -4,6 +4,7 @@
"tab": true,
"acl": "recordAllTeamNo",
"aclPortal": "recordAllNo",
"aclPortalActionList": ["read"],
"module": "Crm",
"customizable": true,
"importable": true,
@@ -4,6 +4,7 @@
"tab": false,
"acl": "recordAllTeamNo",
"aclPortal": "recordAllNo",
"aclPortalActionList": ["read"],
"module": "Crm",
"customizable": true,
"importable": false,
@@ -873,7 +873,7 @@ class Activities implements
$service->handleListParams($params);
$selectParams = $selectManager->getSelectParams($params, false, true);
$selectParams = $selectManager->getSelectParams($params, false, true, true);
$offset = $selectParams['offset'];
$limit = $selectParams['limit'];
+157 -81
View File
@@ -31,14 +31,17 @@ namespace Espo\Modules\Crm\Services;
use Espo\Core\Exceptions\Error;
use Espo\Core\Exceptions\Forbidden;
use Espo\Core\Exceptions\ConflictSilent;
use Espo\ORM\Entity;
use Espo\Modules\Crm\Entities\Lead as LeadEntity;
use Espo\Core\Templates\Services\Person as PersonService;
use Espo\Core\Di;
class Lead extends \Espo\Core\Templates\Services\Person implements
class Lead extends PersonService implements
Di\FieldUtilAware
{
@@ -52,31 +55,36 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
{
if (!empty($data->emailId)) {
$email = $this->getEntityManager()->getEntity('Email', $data->emailId);
if ($email && !$email->get('parentId') && $this->getAcl()->check($email)) {
$email->set(array(
$email->set([
'parentType' => 'Lead',
'parentId' => $entity->id
));
]);
$this->getEntityManager()->saveEntity($email);
}
}
if ($entity->get('campaignId')) {
$campaign = $this->getEntityManager()->getEntity('Campaign', $entity->get('campaignId'));
if ($campaign) {
$log = $this->getEntityManager()->getEntity('CampaignLogRecord');
$log->set(array(
'action' => 'Lead Created',
'actionDate' => date('Y-m-d H:i:s'),
'parentType' => 'Lead',
'parentId' => $entity->id,
'campaignId' => $campaign->id
));
$this->getEntityManager()->saveEntity($log);
}
$campaign = $this->getEntityManager()->getEntity('Campaign', $entity->get('campaignId'));
if ($campaign) {
$log = $this->getEntityManager()->getEntity('CampaignLogRecord');
$log->set([
'action' => 'Lead Created',
'actionDate' => date('Y-m-d H:i:s'),
'parentType' => 'Lead',
'parentId' => $entity->id,
'campaignId' => $campaign->id,
]);
$this->getEntityManager()->saveEntity($log);
}
}
}
public function getConvertAttributes($id)
public function getConvertAttributes(string $id)
{
$lead = $this->getEntity($id);
@@ -84,7 +92,7 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
throw new Forbidden();
}
$data = array();
$data = [];
$entityList = $this->getMetadata()->get('entityDefs.Lead.convertEntityList', []);
@@ -93,22 +101,30 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
$convertFieldsDefs = $this->getMetadata()->get('entityDefs.Lead.convertFields', array());
foreach ($entityList as $entityType) {
if (!$this->getAcl()->checkScope($entityType, 'edit')) continue;
if (!$this->getAcl()->checkScope($entityType, 'edit')) {
continue;
}
$attributes = array();
$attributes = [];
$target = $this->getEntityManager()->getEntity($entityType);
$fieldMap = array();
$fieldMap = [];
$fieldList = array_keys($this->getMetadata()->get('entityDefs.Lead.fields', []));
$fieldList = array_keys($this->getMetadata()->get('entityDefs.Lead.fields', array()));
foreach ($fieldList as $field) {
if (!$this->getMetadata()->get('entityDefs.'.$entityType.'.fields.' . $field)) continue;
if (!$this->getMetadata()->get('entityDefs.'.$entityType.'.fields.' . $field)) {
continue;
}
if (
$this->getMetadata()->get(['entityDefs', $entityType, 'fields', $field, 'type'])
!==
$this->getMetadata()->get(['entityDefs', 'Lead', 'fields', $field, 'type'])
) continue;
) {
continue;
}
$fieldMap[$field] = $field;
}
@@ -121,55 +137,69 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
foreach ($fieldMap as $field => $leadField) {
$type = $this->getMetadata()->get(['entityDefs', 'Lead', 'fields', $field, 'type']);
if (in_array($type, ['file', 'image'])) {
$attachment = $lead->get($field);
if ($attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$idAttribute = $field . 'Id';
$nameAttribute = $field . 'Name';
if ($attachment) {
$attributes[$idAttribute] = $attachment->id;
$attributes[$nameAttribute] = $attachment->get('name');
}
}
continue;
} else if (in_array($type, ['attachmentMultiple'])) {
}
else if (in_array($type, ['attachmentMultiple'])) {
$attachmentList = $lead->get($field);
if (count($attachmentList)) {
$idList = [];
$nameHash = (object) [];
$typeHash = (object) [];
foreach ($attachmentList as $attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
if ($attachment) {
$idList[] = $attachment->id;
$nameHash->{$attachment->id} = $attachment->get('name');
$typeHash->{$attachment->id} = $attachment->get('type');
}
}
$attributes[$field . 'Ids'] = $idList;
$attributes[$field . 'Names'] = $nameHash;
$attributes[$field . 'Types'] = $typeHash;
}
continue;
}
$leadAttributeList = $this->fieldUtil->getAttributeList('Lead', $leadField);
$attributeList = $this->fieldUtil->getAttributeList($entityType, $field);
foreach ($attributeList as $i => $attribute) {
if (in_array($attribute, $ignoreAttributeList)) continue;
if (in_array($attribute, $ignoreAttributeList)) {
continue;
}
$leadAttribute = $leadAttributeList[$i];
if (!$lead->has($leadAttribute)) continue;
if (!$lead->has($leadAttribute)) {
continue;
}
$attributes[$attribute] = $lead->get($leadAttribute);
}
}
$data[$entityType] = $attributes;
}
return $data;
@@ -197,7 +227,10 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
$account->set(get_object_vars($recordsData->Account));
if ($duplicateCheck) {
$rDuplicateList = $this->getServiceFactory()->create('Account')->findDuplicates($account, $recordsData->Account);
$rDuplicateList = $this->getServiceFactory()
->create('Account')
->findDuplicates($account, $recordsData->Account);
if ($rDuplicateList) {
foreach ($rDuplicateList as $e) {
$item = $e->getValueMap();
@@ -210,6 +243,7 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!$skipSave) {
$entityManager->saveEntity($account);
$lead->set('createdAccountId', $account->id);
}
}
@@ -217,12 +251,16 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!empty($recordsData->Contact)) {
$contact = $entityManager->getEntity('Contact');
$contact->set(get_object_vars($recordsData->Contact));
if (isset($account)) {
$contact->set('accountId', $account->id);
}
if ($duplicateCheck) {
$rDuplicateList = $this->getServiceFactory()->create('Contact')->findDuplicates($contact, $recordsData->Contact);
$rDuplicateList = $this->getServiceFactory()
->create('Contact')
->findDuplicates($contact, $recordsData->Contact);
if ($rDuplicateList) {
foreach ($rDuplicateList as $e) {
$item = $e->getValueMap();
@@ -235,6 +273,7 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!$skipSave) {
$entityManager->saveEntity($contact);
$lead->set('createdContactId', $contact->id);
}
}
@@ -242,15 +281,18 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!empty($recordsData->Opportunity)) {
$opportunity = $entityManager->getEntity('Opportunity');
$opportunity->set(get_object_vars($recordsData->Opportunity));
if (isset($account)) {
$opportunity->set('accountId', $account->id);
}
if (isset($contact)) {
$opportunity->set('contactId', $contact->id);
}
if ($duplicateCheck) {
$rDuplicateList = $this->getServiceFactory()->create('Opportunity')
$rDuplicateList = $this->getServiceFactory()
->create('Opportunity')
->findDuplicates($opportunity, $recordsData->Opportunity);
if ($rDuplicateList) {
@@ -265,9 +307,11 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
if (!$skipSave) {
$entityManager->saveEntity($opportunity);
if (isset($contact)) {
$entityManager->getRepository('Contact')->relate($contact, 'opportunities', $opportunity);
}
$lead->set('createdOpportunityId', $opportunity->id);
}
}
@@ -277,78 +321,110 @@ class Lead extends \Espo\Core\Templates\Services\Person implements
'reason' => 'duplicate',
'duplicates' => $duplicateList,
];
throw new \Espo\Core\Exceptions\ConflictSilent(json_encode($reason));
throw new ConflictSilent(json_encode($reason));
}
$lead->set('status', 'Converted');
$entityManager->saveEntity($lead);
if ($meetings = $lead->get('meetings')) {
foreach ($meetings as $meeting) {
if (!empty($contact)) {
$entityManager->getRepository('Meeting')->relate($meeting, 'contacts', $contact);
}
$leadRepisotory = $entityManager->getRepository('Lead');
if (!empty($opportunity)) {
$meeting->set('parentId', $opportunity->id);
$meeting->set('parentType', 'Opportunity');
$entityManager->saveEntity($meeting);
} else if (!empty($account)) {
$meeting->set('parentId', $account->id);
$meeting->set('parentType', 'Account');
$entityManager->saveEntity($meeting);
}
$meetings = $leadRepisotory
->getRelation($lead, 'meetings')
->select(['id', 'parentId', 'parentType'])
->find();
foreach ($meetings as $meeting) {
if (!empty($contact)) {
$entityManager->getRepository('Meeting')->relate($meeting, 'contacts', $contact);
}
}
if ($calls = $lead->get('calls')) {
foreach ($calls as $call) {
if (!empty($contact)) {
$entityManager->getRepository('Call')->relate($call, 'contacts', $contact);
}
if (!empty($opportunity)) {
$call->set('parentId', $opportunity->id);
$call->set('parentType', 'Opportunity');
$entityManager->saveEntity($call);
} else if (!empty($account)) {
$call->set('parentId', $account->id);
$call->set('parentType', 'Account');
$entityManager->saveEntity($call);
}
if (!empty($opportunity)) {
$meeting->set('parentId', $opportunity->id);
$meeting->set('parentType', 'Opportunity');
$entityManager->saveEntity($meeting);
}
}
if ($emails = $lead->get('emails')) {
foreach ($emails as $email) {
if (!empty($opportunity)) {
$email->set('parentId', $opportunity->id);
$email->set('parentType', 'Opportunity');
$entityManager->saveEntity($email);
} else if (!empty($account)) {
$email->set('parentId', $account->id);
$email->set('parentType', 'Account');
$entityManager->saveEntity($email);
}
else if (!empty($account)) {
$meeting->set('parentId', $account->id);
$meeting->set('parentType', 'Account');
$entityManager->saveEntity($meeting);
}
}
if ($documents = $lead->get('documents')) {
foreach ($documents as $document) {
if (!empty($account)) {
$entityManager->getRepository('Document')->relate($document, 'accounts', $account);
}
if (!empty($opportunity)) {
$entityManager->getRepository('Document')->relate($document, 'opportunities', $opportunity);
}
$calls = $leadRepisotory
->getRelation($lead, 'calls')
->select(['id', 'parentId', 'parentType'])
->find();
foreach ($calls as $call) {
if (!empty($contact)) {
$entityManager->getRepository('Call')->relate($call, 'contacts', $contact);
}
if (!empty($opportunity)) {
$call->set('parentId', $opportunity->id);
$call->set('parentType', 'Opportunity');
$entityManager->saveEntity($call);
}
else if (!empty($account)) {
$call->set('parentId', $account->id);
$call->set('parentType', 'Account');
$entityManager->saveEntity($call);
}
}
$emails = $leadRepisotory
->getRelation($lead, 'emails')
->select(['id', 'parentId', 'parentType'])
->find();
foreach ($emails as $email) {
if (!empty($opportunity)) {
$email->set('parentId', $opportunity->id);
$email->set('parentType', 'Opportunity');
$entityManager->saveEntity($email);
}
else if (!empty($account)) {
$email->set('parentId', $account->id);
$email->set('parentType', 'Account');
$entityManager->saveEntity($email);
}
}
$documents = $leadRepisotory
->getRelation($lead, 'documents')
->select(['id'])
->find();
foreach ($documents as $document) {
if (!empty($account)) {
$entityManager->getRepository('Document')->relate($document, 'accounts', $account);
}
if (!empty($opportunity)) {
$entityManager->getRepository('Document')->relate($document, 'opportunities', $opportunity);
}
}
$streamService = $this->getStreamService();
if ($streamService->checkIsFollowed($lead, $this->getUser()->id)) {
if (!empty($opportunity)) {
$streamService->followEntity($opportunity, $this->getUser()->id);
}
if (!empty($account)) {
$streamService->followEntity($account, $this->getUser()->id);
}
if (!empty($contact)) {
$streamService->followEntity($contact, $this->getUser()->id);
}
+1 -1
View File
@@ -175,7 +175,7 @@ class BaseEntity implements Entity
// @todo Remove this.
if ($this->hasRelation($name) && $this->id && $this->entityManager) {
trigger_error(
"Accessing related records with Entity::get is deprecated. Use \$entityManager->getRelation(...)->find()",
"Accessing related records with Entity::get is deprecated. Use \$repository->getRelation(...)->find()",
E_USER_DEPRECATED
);
@@ -169,7 +169,7 @@
"Cancel": "Отменить",
"Apply": "Применить",
"Unlink": "Убрать ссылку",
"Mass Update": "Обновить все",
"Mass Update": "Массовое обновление",
"Export": "Экспортировать",
"No Data": "Нет данных",
"No Access": "Нет доступа",
@@ -185,7 +185,7 @@
"uninstallConfirmation": "Ви впевнені, що хочете видалити розширення?",
"cronIsNotConfigured": "Заплановані завдання не працюють. Отже, вхідні електронні листи, сповіщення та нагадування не працюють. Будь ласка, дотримуйтесь інструкцій (https://www.espocrm.com/documentation/administration/server-configuration/#user-content-setup-a-crontab), щоб налаштувати cron job.",
"newExtensionVersionIsAvailable": "Нова {extensionName} версія {latestVersion} доступна.",
"upgradeVersion": "EspoCRM буде оновлено до версії **{версія}**. Будьте терплячі, оскільки це може зайняти деякий час.",
"upgradeVersion": "EspoCRM буде оновлено до версії **{version}**. Будьте терплячі, оскільки це може зайняти деякий час.",
"upgradeDone": "EspoCRM оновлено до версії **{version}**.",
"downloadUpgradePackage": "Завантажте апгрейд [звідси]({url})",
"upgradeInfo": "Перегляньте [документацію]({url}) про те, як оновити EspoCRM.\n",
@@ -257,4 +257,4 @@
"noteStatus": "Повідомлення про оновлення статусу",
"passwordChangeLink": "Посилання на зміну паролю"
}
}
}
@@ -65,7 +65,10 @@
"parent": false
},
"EmailFolder": {
"assignedUser": false
"assignedUser": {
"read": "yes",
"edit": "no"
}
},
"Email": {
"inboundEmails": false,
@@ -53,11 +53,21 @@
{
"label":"Folders",
"link":"#EmailFolder",
"configCheck": "!emailFoldersDisabled"
"configCheck": "!emailFoldersDisabled",
"accessDataList": [
{
"inPortalDisabled": true
}
]
},
{
"label":"Filters",
"link":"#EmailFilter"
"link":"#EmailFilter",
"accessDataList": [
{
"inPortalDisabled": true
}
]
}
]
},
@@ -42,11 +42,13 @@
},
{
"name":"after",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"name":"before",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"type": "bool",
@@ -39,11 +39,13 @@
},
{
"name":"after",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"name":"before",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"type": "bool",
@@ -39,11 +39,13 @@
},
{
"name":"after",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"name":"before",
"type":"varchar"
"type":"varchar",
"view": "views/admin/field-manager/fields/date/after-before"
},
{
"type": "bool",
@@ -5,6 +5,7 @@
"acl": "recordAllTeamNo",
"aclPortal": true,
"aclPortalLevelList": ["all", "no"],
"aclPortalActionList": ["read"],
"customizable": true,
"importable": true,
"disabled": false
@@ -162,8 +162,6 @@ class Email extends \Espo\Core\Select\SelectManager
$result['whereClause'][] = [
'usersMiddle.userId' => $this->getUser()->id
];
$this->addUsersColumns($result);
}
protected function boolFilterOnlyMy(&$result)
+31 -6
View File
@@ -133,6 +133,7 @@ class EmailTemplate extends Record implements
if (!empty($params['relatedId']) && !empty($params['relatedType'])) {
$related = $this->getEntityManager()->getEntity($params['relatedType'], $params['relatedId']);
if ($related) {
$entityHash[$related->getEntityType()] = $related;
}
@@ -155,6 +156,7 @@ class EmailTemplate extends Record implements
if ($handlebarsInSubject) {
$subject = $htmlizer->render($parent, $subject);
}
if ($handlebarsInBody) {
$body = $htmlizer->render($parent, $body);
}
@@ -244,7 +246,9 @@ class EmailTemplate extends Record implements
}
foreach ($attributeList as $attribute) {
if (in_array($attribute, $forbiddenAttributeList)) continue;
if (in_array($attribute, $forbiddenAttributeList)) {
continue;
}
$value = $entity->get($attribute);
@@ -252,13 +256,18 @@ class EmailTemplate extends Record implements
continue;
}
if (!$entity->getAttributeType($attribute)) continue;
if (!$entity->getAttributeType($attribute)) {
continue;
}
$value = $this->formatAttributeValue($entity, $attribute);
if (is_null($value)) continue;
if (is_null($value)) {
continue;
}
$variableName = $attribute;
if (!is_null($prefixLink)) {
$variableName = $prefixLink . '.' . $attribute;
}
@@ -266,7 +275,7 @@ class EmailTemplate extends Record implements
$text = str_replace('{' . $type . '.' . $variableName . '}', $value, $text);
}
if (!$skipLinks) {
if (!$skipLinks && $entity->id) {
$relationDefs = $entity->getRelations();
foreach ($entity->getRelationList() as $relation) {
@@ -301,10 +310,12 @@ class EmailTemplate extends Record implements
}
$replaceData = [];
$replaceData['today'] = $this->getDateTime()->getTodayString();
$replaceData['now'] = $this->getDateTime()->getNowString();
$timeZone = $this->getConfig()->get('timeZone');
$now = new DateTime('now', new DateTimezone($timeZone));
$replaceData['currentYear'] = $now->format('Y');
@@ -328,11 +339,13 @@ class EmailTemplate extends Record implements
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else if ($fieldType === 'array' || $fieldType === 'multiEnum' || $fieldType === 'checklist') {
$valueList = [];
if (is_array($value)) {
foreach ($value as $v) {
$valueList[] = $this->getLanguage()->translateOption($v, $attribute, $entity->getEntityType());
}
}
$value = implode(', ', $valueList);
$value = $this->getLanguage()->translateOption($value, $attribute, $entity->getEntityType());
} else {
@@ -348,13 +361,16 @@ class EmailTemplate extends Record implements
if (!is_string($value)) {
$value = '';
}
$value = nl2br($value);
} else if ($attributeType == 'float') {
if (is_float($value)) {
$decimalPlaces = 2;
if ($fieldType === 'currency') {
$decimalPlaces = $this->getConfig()->get('currencyDecimalPlaces');
}
$value = $this->getNumber()->format($value, $decimalPlaces);
}
} else if ($attributeType == 'int') {
@@ -370,7 +386,9 @@ class EmailTemplate extends Record implements
$value = '';
}
if (!is_string($value)) return null;
if (!is_string($value)) {
return null;
}
return $value;
}
@@ -389,6 +407,7 @@ class EmailTemplate extends Record implements
if ($parentId && $parentType) {
$e = $this->getEntityManager()->getEntity($parentType, $parentId);
if ($e && $this->getAcl()->check($e)) {
$dataList[] = [
'type' => 'parent',
@@ -426,6 +445,7 @@ class EmailTemplate extends Record implements
foreach ($fm->getEntityTypeFieldList($entityType) as $field) {
$fieldType = $fm->getEntityTypeFieldParam($entityType, $field, 'type');
$fieldAttributeList = $fm->getAttributeList($entityType, $field);
if (
$fm->getEntityTypeFieldParam($entityType, $field, 'disabled') ||
$fm->getEntityTypeFieldParam($entityType, $field, 'directAccessDisabled') ||
@@ -441,9 +461,14 @@ class EmailTemplate extends Record implements
$attributeList = $fm->getEntityTypeAttributeList($entityType);
$values = (object) [];
foreach ($attributeList as $a) {
if (!$e->has($a)) continue;
if (!$e->has($a)) {
continue;
}
$value = $emailTemplateService->formatAttributeValue($e, $a);
if ($value != '') {
$values->$a = $value;
}
+188 -71
View File
@@ -43,10 +43,16 @@ use Espo\Core\{
Mail\Parsers\MailMimeParser,
};
use Espo\Services\{
Email as EmailService,
Record as RecordService,
};
use Espo\Entities\{
Team,
InboundEmail as InboundEmailEntity,
User,
Email as EmailEntity,
};
use Espo\Core\Di;
@@ -57,7 +63,7 @@ use Throwable;
use DateTime;
use DateTimeZone;
class InboundEmail extends \Espo\Services\Record implements
class InboundEmail extends RecordService implements
Di\CryptAware,
Di\EmailSenderAware,
@@ -83,9 +89,11 @@ class InboundEmail extends \Espo\Services\Record implements
protected function handleInput($data)
{
parent::handleInput($data);
if (property_exists($data, 'password')) {
$data->password = $this->getCrypt()->encrypt($data->password);
}
if (property_exists($data, 'smtpPassword')) {
$data->smtpPassword = $this->getCrypt()->encrypt($data->smtpPassword);
}
@@ -106,6 +114,7 @@ class InboundEmail extends \Espo\Services\Record implements
{
if (!empty($params['id'])) {
$account = $this->getEntityManager()->getEntity('InboundEmail', $params['id']);
if ($account) {
$params['password'] = $this->getCrypt()->decrypt($account->get('password'));
$params['imapHandler'] = $account->get('imapHandler');
@@ -117,9 +126,11 @@ class InboundEmail extends \Espo\Services\Record implements
$storage = $this->createStorage($params);
$folders = new RecursiveIteratorIterator($storage->getFolders(), RecursiveIteratorIterator::SELF_FIRST);
foreach ($folders as $name => $folder) {
$foldersArr[] = mb_convert_encoding($folder->getGlobalName(), 'UTF-8', 'UTF7-IMAP');
}
return $foldersArr;
}
@@ -127,6 +138,7 @@ class InboundEmail extends \Espo\Services\Record implements
{
if (!empty($params['id'])) {
$account = $this->getEntityManager()->getEntity('InboundEmail', $params['id']);
if ($account) {
$params['imapHandler'] = $account->get('imapHandler');
}
@@ -137,6 +149,7 @@ class InboundEmail extends \Espo\Services\Record implements
if ($storage->getFolders()) {
return true;
}
throw new Error();
}
@@ -153,25 +166,30 @@ class InboundEmail extends \Espo\Services\Record implements
$maxSize = $this->getConfig()->get('emailMessageMaxSize');
$teamId = $emailAccount->get('teamId');
$userId = null;
if ($emailAccount->get('assignToUserId')) {
$userId = $emailAccount->get('assignToUserId');
}
$userIdList = [];
$teamIdList = $emailAccount->getLinkMultipleIdList('teams');
if (!empty($teamIdList)) {
if ($emailAccount->get('addAllTeamUsers')) {
$userList = $this->getEntityManager()->getRepository('User')->find([
'select' => ['id'],
'whereClause' => [
$userList = $this->getEntityManager()
->getRepository('User')
->select(['id'])
->distinct()
->join('teams')
->where([
'isActive' => true,
'teamsMiddle.teamId' => $teamIdList
],
'distinct' => true,
'joins' => ['teams'],
]);
'teamsMiddle.teamId' => $teamIdList,
])
->find();
foreach ($userList as $user) {
$userIdList[] = $user->id;
}
@@ -182,18 +200,21 @@ class InboundEmail extends \Espo\Services\Record implements
$teamIdList[] = $teamId;
}
$filterCollection = $this->getEntityManager()->getRepository('EmailFilter')->where([
'action' => 'Skip',
'OR' => [
[
'parentType' => $emailAccount->getEntityType(),
'parentId' => $emailAccount->id
$filterCollection = $this->getEntityManager()
->getRepository('EmailFilter')
->where([
'action' => 'Skip',
'OR' => [
[
'parentType' => $emailAccount->getEntityType(),
'parentId' => $emailAccount->id,
],
[
'parentId' => null
],
],
[
'parentId' => null
]
]
])->find();
])
->find();
$fetchData = $emailAccount->get('fetchData');
@@ -239,6 +260,7 @@ class InboundEmail extends \Espo\Services\Record implements
$GLOBALS['log']->error(
'InboundEmail '.$emailAccount->id.' (Select Folder) [' . $e->getCode() . '] ' .$e->getMessage()
);
continue;
}
@@ -266,6 +288,7 @@ class InboundEmail extends \Espo\Services\Record implements
$idList = $storage->getIdsFromUID($lastUID);
} else {
$fetchSince = $emailAccount->get('fetchSince');
if ($lastDate) {
$fetchSince = $lastDate;
}
@@ -296,8 +319,10 @@ class InboundEmail extends \Espo\Services\Record implements
if ($forceByDate && $previousLastUID) {
$uid = $storage->getUniqueId($id);
if ($uid <= $previousLastUID) {
$k++;
continue;
}
}
@@ -379,6 +404,7 @@ class InboundEmail extends \Espo\Services\Record implements
} else {
if ($emailAccount->get('reply')) {
$user = $this->getEntityManager()->getEntity('User', $userId);
$this->autoReply($emailAccount, $email, $user);
}
}
@@ -394,15 +420,18 @@ class InboundEmail extends \Espo\Services\Record implements
if ($email && $email->get('dateSent')) {
$dt = null;
try {
$dt = new DateTime($email->get('dateSent'));
} catch (Exception $e) {}
if ($dt) {
$nowDt = new DateTime();
if ($dt->getTimestamp() >= $nowDt->getTimestamp()) {
$dt = $nowDt;
}
$dateSent = $dt->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i:s');
$lastDate = $dateSent;
}
@@ -416,6 +445,7 @@ class InboundEmail extends \Espo\Services\Record implements
if ($forceByDate) {
$nowDt = new DateTime();
$lastDate = $nowDt->format('Y-m-d H:i:s');
}
@@ -425,8 +455,10 @@ class InboundEmail extends \Espo\Services\Record implements
if ($forceByDate) {
if ($previousLastUID) {
$idList = $storage->getIdsFromUID($previousLastUID);
if (count($idList)) {
$uid1 = $storage->getUniqueId($idList[0]);
if ($uid1 && $uid1 > $previousLastUID) {
unset($fetchData->byDate->$folder);
}
@@ -453,6 +485,7 @@ class InboundEmail extends \Espo\Services\Record implements
$filterCollection, $fetchOnlyHeader, $folderData = null
) {
$email = null;
try {
$email = $importer->importMessage(
$message, $userId, $teamIdList, $userIdList, $filterCollection, $fetchOnlyHeader, $folderData
@@ -475,8 +508,10 @@ class InboundEmail extends \Espo\Services\Record implements
{
if ($email->get('parentType') && $email->get('parentId')) {
$parent = $this->getEntityManager()->getEntity($email->get('parentType'), $email->get('parentId'));
if ($parent) {
$this->getServiceFactory()->create('Stream')->noteEmailReceived($parent, $email);
return;
}
}
@@ -490,6 +525,7 @@ class InboundEmail extends \Espo\Services\Record implements
$userIdList = $case->getLinkMultipleIdList('assignedUsers');
} else {
$assignedUserId = $case->get('assignedUserId');
if ($assignedUserId) {
$userIdList[] = $assignedUserId;
}
@@ -515,48 +551,58 @@ class InboundEmail extends \Espo\Services\Record implements
{
if ($email->get('parentType') == 'Case' && $email->get('parentId')) {
$case = $this->getEntityManager()->getEntity('Case', $email->get('parentId'));
if ($case) {
$this->processCaseToEmailFields($case, $email);
if (!$email->isFetched()) {
$this->getServiceFactory()->create('Stream')->noteEmailReceived($case, $email);
}
}
return;
}
if (preg_match('/\[#([0-9]+)[^0-9]*\]/', $email->get('name'), $m)) {
$caseNumber = $m[1];
$case = $this->getEntityManager()->getRepository('Case')->where([
'number' => $caseNumber
])->findOne();
$case = $this->getEntityManager()
->getRepository('Case')
->where([
'number' => $caseNumber,
])
->findOne();
if ($case) {
$email->set('parentType', 'Case');
$email->set('parentId', $case->id);
$this->processCaseToEmailFields($case, $email);
if (!$email->isFetched()) {
$this->getServiceFactory()->create('Stream')->noteEmailReceived($case, $email);
}
}
} else {
$params = [
'caseDistribution' => $inboundEmail->get('caseDistribution'),
'teamId' => $inboundEmail->get('teamId'),
'userId' => $inboundEmail->get('assignToUserId'),
'targetUserPosition' => $inboundEmail->get('targetUserPosition'),
'inboundEmailId' => $inboundEmail->id
];
$case = $this->emailToCase($email, $params);
return;
}
$user = $this->getEntityManager()->getEntity('User', $case->get('assignedUserId'));
$params = [
'caseDistribution' => $inboundEmail->get('caseDistribution'),
'teamId' => $inboundEmail->get('teamId'),
'userId' => $inboundEmail->get('assignToUserId'),
'targetUserPosition' => $inboundEmail->get('targetUserPosition'),
'inboundEmailId' => $inboundEmail->id,
];
$this->getServiceFactory()->create('Stream')->noteEmailReceived($case, $email, true);
$case = $this->emailToCase($email, $params);
if ($inboundEmail->get('reply')) {
$this->autoReply($inboundEmail, $email, $case, $user);
}
$user = $this->getEntityManager()->getEntity('User', $case->get('assignedUserId'));
$this->getServiceFactory()->create('Stream')->noteEmailReceived($case, $email, true);
if ($inboundEmail->get('reply')) {
$this->autoReply($inboundEmail, $email, $case, $user);
}
}
@@ -581,6 +627,7 @@ class InboundEmail extends \Espo\Services\Record implements
protected function assignLeastBusy(Entity $case, Team $team, $targetUserPosition)
{
$className = 'Espo\\Custom\\Business\\Distribution\\CaseObj\\LeastBusy';
if (!class_exists($className)) {
$className = 'Espo\\Modules\\Crm\\Business\\Distribution\\CaseObj\\LeastBusy';
}
@@ -595,10 +642,12 @@ class InboundEmail extends \Espo\Services\Record implements
}
}
protected function emailToCase(\Espo\Entities\Email $email, array $params = [])
protected function emailToCase(EmailEntity $email, array $params = [])
{
$case = $this->getEntityManager()->getEntity('Case');
$case->populateDefaults();
$case->set('name', $email->get('name'));
$bodyPlain = $email->getBodyPlain();
@@ -616,8 +665,13 @@ class InboundEmail extends \Espo\Services\Record implements
foreach ($attachmentIdList as $attachmentId) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->get($attachmentId);
if (!$attachment) continue;
if (!$attachment) {
continue;
}
$copiedAttachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$copiedAttachmentIdList[] = $copiedAttachment->id;
}
@@ -626,6 +680,7 @@ class InboundEmail extends \Espo\Services\Record implements
}
$userId = null;
if (!empty($params['userId'])) {
$userId = $params['userId'];
}
@@ -635,19 +690,23 @@ class InboundEmail extends \Espo\Services\Record implements
}
$teamId = false;
if (!empty($params['teamId'])) {
$teamId = $params['teamId'];
}
if ($teamId) {
$case->set('teamsIds', [$teamId]);
}
$caseDistribution = '';
if (!empty($params['caseDistribution'])) {
$caseDistribution = $params['caseDistribution'];
}
$targetUserPosition = null;
if (!empty($params['targetUserPosition'])) {
$targetUserPosition = $params['targetUserPosition'];
}
@@ -660,21 +719,26 @@ class InboundEmail extends \Espo\Services\Record implements
$case->set('status', 'Assigned');
}
break;
case 'Round-Robin':
if ($teamId) {
$team = $this->getEntityManager()->getEntity('Team', $teamId);
if ($team) {
$this->assignRoundRobin($case, $team, $targetUserPosition);
}
}
break;
case 'Least-Busy':
if ($teamId) {
$team = $this->getEntityManager()->getEntity('Team', $teamId);
if ($team) {
$this->assignLeastBusy($case, $team, $targetUserPosition);
}
}
break;
}
@@ -686,18 +750,26 @@ class InboundEmail extends \Espo\Services\Record implements
$case->set('accountId', $email->get('accountId'));
}
$contact = $this->getEntityManager()->getRepository('Contact')->join('emailAddresses', 'emailAddressesMultiple')->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])->findOne();
$contact = $this->getEntityManager()
->getRepository('Contact')
->join('emailAddresses', 'emailAddressesMultiple')
->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])
->findOne();
if ($contact) {
$case->set('contactId', $contact->id);
} else {
if (!$case->get('accountId')) {
$lead = $this->getEntityManager()->getRepository('Lead')
->join('emailAddresses', 'emailAddressesMultiple')->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])->findOne();
$lead = $this->getEntityManager()
->getRepository('Lead')
->join('emailAddresses', 'emailAddressesMultiple')
->where([
'emailAddressesMultiple.id' => $email->get('fromEmailAddressId')
])
->findOne();
if ($lead) {
$case->set('leadId', $lead->id);
}
@@ -726,16 +798,22 @@ class InboundEmail extends \Espo\Services\Record implements
}
$d = new DateTime();
$d->modify('-3 hours');
$threshold = $d->format('Y-m-d H:i:s');
$emailAddress = $this->getEntityManager()->getRepository('EmailAddress')->getByAddress($email->get('from'));
$sent = $this->getEntityManager()->getRepository('Email')->where([
'toEmailAddresses.id' => $emailAddress->id,
'dateSent>' => $threshold,
'status' => 'Sent'
])->join('toEmailAddresses')->findOne();
$sent = $this->getEntityManager()
->getRepository('Email')
->where([
'toEmailAddresses.id' => $emailAddress->id,
'dateSent>' => $threshold,
'status' => 'Sent',
])
->join('toEmailAddresses')
->findOne();
if ($sent) {
return false;
@@ -743,17 +821,23 @@ class InboundEmail extends \Espo\Services\Record implements
try {
$replyEmailTemplateId = $inboundEmail->get('replyEmailTemplateId');
if ($replyEmailTemplateId) {
$entityHash = [];
if ($case) {
$entityHash['Case'] = $case;
if ($case->get('contactId')) {
$contact = $this->getEntityManager()->getEntity('Contact', $case->get('contactId'));
}
}
if (empty($contact)) {
$contact = $this->getEntityManager()->getEntity('Contact');
$fromName = \Espo\Services\Email::parseFromName($email->get('fromString'));
$fromName = EmailService::parseFromName($email->get('fromString'));
if (!empty($fromName)) {
$contact->set('name', $fromName);
}
@@ -771,11 +855,13 @@ class InboundEmail extends \Espo\Services\Record implements
$replyData = $emailTemplateService->parse($replyEmailTemplateId, ['entityHash' => $entityHash], true);
$subject = $replyData['subject'];
if ($case) {
$subject = '[#' . $case->get('number'). '] ' . $subject;
}
$reply = $this->getEntityManager()->getEntity('Email');
$reply->set('to', $email->get('from'));
$reply->set('subject', $subject);
$reply->set('body', $replyData['body']);
@@ -829,8 +915,9 @@ class InboundEmail extends \Espo\Services\Record implements
return true;
}
} catch (Exception $e) {}
} catch (Exception $e) {
$GLOBALS['log']->error("Inbound Email: Auto-reply error: " . $e->getMessage());
}
}
protected function getSmtpParamsFromInboundEmail(InboundEmailEntity $emailAccount)
@@ -844,9 +931,11 @@ class InboundEmail extends \Espo\Services\Record implements
$smtpParams['security'] = $emailAccount->get('smtpSecurity');
$smtpParams['username'] = $emailAccount->get('smtpUsername');
$smtpParams['password'] = $emailAccount->get('smtpPassword');
if (array_key_exists('password', $smtpParams)) {
$smtpParams['password'] = $this->getCrypt()->decrypt($smtpParams['password']);
}
return $smtpParams;
}
@@ -858,6 +947,7 @@ class InboundEmail extends \Espo\Services\Record implements
$content = $message->getRawContent();
$isHard = false;
if (preg_match('/permanent[ ]*[error|failure]/', $content)) {
$isHard = true;
}
@@ -876,15 +966,21 @@ class InboundEmail extends \Espo\Services\Record implements
}
}
if (!$queueItemId) return false;
if (!$queueItemId) {
return false;
}
$queueItem = $this->getEntityManager()->getEntity('EmailQueueItem', $queueItemId);
if (!$queueItem) return false;
if (!$queueItem) {
return false;
}
$massEmailId = $queueItem->get('massEmailId');
$massEmail = $this->getEntityManager()->getEntity('MassEmail', $massEmailId);
$campaignId = null;
if ($massEmail) {
$campaignId = $massEmail->get('campaignId');
}
@@ -897,13 +993,19 @@ class InboundEmail extends \Espo\Services\Record implements
if ($isHard && $emailAddress) {
$emailAddressEntity = $this->getEntityManager()->getRepository('EmailAddress')->getByAddress($emailAddress);
$emailAddressEntity->set('invalid', true);
$this->getEntityManager()->saveEntity($emailAddressEntity);
if ($emailAddressEntity) {
$emailAddressEntity->set('invalid', true);
$this->getEntityManager()->saveEntity($emailAddressEntity);
}
}
if ($campaignId && $target && $target->id) {
$this->getCampaignService()
->logBounced($campaignId, $queueItemId, $target, $emailAddress, $isHard, null, $queueItem->get('isTest'));
->logBounced(
$campaignId, $queueItemId, $target, $emailAddress, $isHard, null, $queueItem->get('isTest')
);
}
return true;
@@ -918,14 +1020,17 @@ class InboundEmail extends \Espo\Services\Record implements
return $this->campaignService;
}
public function findAccountForSending(string $emailAddress) : ?\Espo\Entities\InboundEmail
public function findAccountForSending(string $emailAddress) : ?InboundEmailEntity
{
$inboundEmail = $this->getEntityManager()->getRepository('InboundEmail')->where([
'status' => 'Active',
'useSmtp' => true,
'smtpHost!=' => null,
'emailAddress' => $emailAddress,
])->findOne();
$inboundEmail = $this->getEntityManager()
->getRepository('InboundEmail')
->where([
'status' => 'Active',
'useSmtp' => true,
'smtpHost!=' => null,
'emailAddress' => $emailAddress,
])
->findOne();
return $inboundEmail;
}
@@ -933,6 +1038,7 @@ class InboundEmail extends \Espo\Services\Record implements
public function findSharedAccountForUser(User $user, $emailAddress)
{
$groupEmailAccountPermission = $this->getAclManager()->get($user, 'groupEmailAccountPermission');
$teamIdList = $user->getLinkMultipleIdList('teams');
$inboundEmail = null;
@@ -941,17 +1047,20 @@ class InboundEmail extends \Espo\Services\Record implements
if ($groupEmailAccountPermission && $groupEmailAccountPermission !== 'no') {
if ($groupEmailAccountPermission === 'team') {
if (!count($teamIdList)) return;
if (!count($teamIdList)) {
return;
}
$selectParams = [
'whereClause' => [
'status' => 'Active',
'useSmtp' => true,
'smtpIsShared' => true,
'teamsMiddle.teamId' => $teamIdList,
'emailAddress' => $emailAddress
'emailAddress' => $emailAddress,
],
'joins' => ['teams'],
'distinct' => true
'distinct' => true,
];
} else if ($groupEmailAccountPermission === 'all') {
$selectParams = [
@@ -959,7 +1068,7 @@ class InboundEmail extends \Espo\Services\Record implements
'status' => 'Active',
'useSmtp' => true,
'smtpIsShared' => true,
'emailAddress' => $emailAddress
'emailAddress' => $emailAddress,
]
];
}
@@ -967,6 +1076,7 @@ class InboundEmail extends \Espo\Services\Record implements
$inboundEmail = $this->getEntityManager()->getRepository('InboundEmail')->findOne($selectParams);
}
return $inboundEmail;
}
@@ -1034,9 +1144,11 @@ class InboundEmail extends \Espo\Services\Record implements
$storage = $this->getStorage($emailAccount);
$folder = $emailAccount->get('sentFolder');
if (empty($folder)) {
throw new Error("No sent folder for Email Account: " . $emailAccount->id . ".");
}
$storage->appendMessage($message->toString(), $folder);
}
@@ -1044,6 +1156,7 @@ class InboundEmail extends \Espo\Services\Record implements
{
$smtpParams = [];
$smtpParams['server'] = $emailAccount->get('smtpHost');
if ($smtpParams['server']) {
$smtpParams['port'] = $emailAccount->get('smtpPort');
$smtpParams['auth'] = $emailAccount->get('smtpAuth');
@@ -1058,9 +1171,11 @@ class InboundEmail extends \Espo\Services\Record implements
if ($emailAccount->get('fromName')) {
$smtpParams['fromName'] = $emailAccount->get('fromName');
}
if ($emailAccount->get('emailAddress')) {
$smtpParams['fromAddress'] = $emailAccount->get('emailAddress');
}
if (array_key_exists('password', $smtpParams) && is_string($smtpParams['password'])) {
$smtpParams['password'] = $this->getCrypt()->decrypt($smtpParams['password']);
}
@@ -1077,7 +1192,9 @@ class InboundEmail extends \Espo\Services\Record implements
{
$handlerClassName = $emailAccount->get('smtpHandler');
if (!$handlerClassName) return;
if (!$handlerClassName) {
return;
}
try {
$handler = $this->injectableFactory->create($handlerClassName);
+177 -42
View File
@@ -59,6 +59,7 @@ use Espo\Core\{
use Espo\Tools\Export\Export as ExportTool;
use StdClass;
use Exception;
use Espo\Core\Di;
@@ -1245,13 +1246,19 @@ class Record implements Crud,
public function delete(string $id)
{
if (empty($id)) throw new BadRequest("ID is empty.");
if (empty($id)) {
throw new BadRequest("ID is empty.");
}
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound("Record {$id} not found.");
if (!$entity) {
throw new NotFound("Record {$id} not found.");
}
if (!$this->getAcl()->check($entity, 'delete')) throw new ForbiddenSilent("No delete access.");
if (!$this->getAcl()->check($entity, 'delete')) {
throw new ForbiddenSilent("No delete access.");
}
$this->beforeDeleteEntity($entity);
@@ -1292,6 +1299,7 @@ class Record implements Crud,
}
$maxSize = 0;
if ($disableCount) {
if (!empty($params['maxSize'])) {
$maxSize = $params['maxSize'];
@@ -1376,10 +1384,16 @@ class Record implements Crud,
];
foreach ($statusList as $status) {
if (in_array($status, $statusIgnoreList)) continue;
if (!$status) continue;
if (in_array($status, $statusIgnoreList)) {
continue;
}
if (!$status) {
continue;
}
$selectParamsSub = $selectParams;
$selectParamsSub['whereClause'][] = [
$statusField => $status
];
@@ -1454,12 +1468,19 @@ class Record implements Crud,
public function restoreDeleted(string $id)
{
if (!$this->getUser()->isAdmin()) throw new Forbidden();
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$entity = $this->getEntityEvenDeleted($id);
if (!$entity) throw new NotFound();
if (!$entity->get('deleted')) throw new Forbidden();
if (!$entity) {
throw new NotFound();
}
if (!$entity->get('deleted')) {
throw new Forbidden();
}
$this->getRepository()->restoreDeleted($entity->id);
@@ -1475,6 +1496,7 @@ class Record implements Crud,
return $this->getConfig()->get('maxSelectTextAttributeLengthForList', self::MAX_SELECT_TEXT_ATTRIBUTE_LENGTH);
}
}
return null;
}
@@ -1487,12 +1509,15 @@ class Record implements Crud,
public function findLinked(string $id, string $link, array $params) : RecordCollection
{
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
if (empty($link)) {
throw new Error();
}
@@ -1515,6 +1540,7 @@ class Record implements Crud,
}
$methodName = 'findLinkedEntities' . ucfirst($link);
if (method_exists($this, $methodName)) {
return $this->$methodName($id, $params);
}
@@ -1638,35 +1664,45 @@ class Record implements Crud,
}
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if ($this->noEditAccessRequiredForLink) {
if (!$this->getAcl()->check($entity, 'read')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
} else {
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
}
$methodName = 'link' . ucfirst($link);
if ($link !== 'entity' && $link !== 'entityMass' && method_exists($this, $methodName)) {
return $this->$methodName($id, $foreignId);
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) {
throw new Error("Entity '{$this->entityType}' has not relation '{$link}'.");
}
$foreignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $foreignId);
if (!$foreignEntity) {
throw new NotFound();
}
$accessActionRequired = 'edit';
if (in_array($link, $this->noEditAccessRequiredLinkList)) {
$accessActionRequired = 'read';
}
if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) {
throw new Forbidden();
}
@@ -1712,30 +1748,39 @@ class Record implements Crud,
}
if ($this->noEditAccessRequiredForLink) {
if (!$this->getAcl()->check($entity, 'read')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
} else {
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
}
$methodName = 'unlink' . ucfirst($link);
if ($link !== 'entity' && method_exists($this, $methodName)) {
return $this->$methodName($id, $foreignId);
}
$foreignEntityType = $entity->getRelationParam($link, 'entity');
if (!$foreignEntityType) {
throw new Error("Entity '{$this->entityType}' has not relation '{$link}'.");
}
$foreignEntity = $this->getEntityManager()->getEntity($foreignEntityType, $foreignId);
if (!$foreignEntity) {
throw new NotFound();
}
$accessActionRequired = 'edit';
if (in_array($link, $this->noEditAccessRequiredLinkList)) {
$accessActionRequired = 'read';
}
if (!$this->getAcl()->check($foreignEntity, $accessActionRequired)) {
throw new Forbidden();
}
@@ -1756,6 +1801,7 @@ class Record implements Crud,
if (!$this->getMetadata()->get(['scopes', $this->entityType, 'stream'])) throw new NotFound();
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound();
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'stream')) throw new Forbidden();
@@ -1776,6 +1822,7 @@ class Record implements Crud,
if (!$this->getMetadata()->get(['scopes', $this->entityType, 'stream'])) throw new NotFound();
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFound();
if (!$this->getAcl()->check($entity, 'edit')) throw new Forbidden();
if (!$this->getAcl()->check($entity, 'stream')) throw new Forbidden();
@@ -1810,14 +1857,17 @@ class Record implements Crud,
}
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'edit')) {
throw new Forbidden();
}
$methodName = 'massLink' . ucfirst($link);
if (method_exists($this, $methodName)) {
return $this->$methodName($id, $where, $selectData);
}
@@ -1874,7 +1924,9 @@ class Record implements Crud,
public function massUpdate(array $params, StdClass $data)
{
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') throw new Forbidden();
if ($this->getAcl()->get('massUpdatePermission') !== 'yes') {
throw new Forbidden();
}
$resultIdList = [];
$repository = $this->getRepository();
@@ -1891,11 +1943,13 @@ class Record implements Crud,
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'edit') && $this->checkEntityForMassUpdate($entity, $data)) {
$entity->set($data);
try {
$this->processValidation($entity, $data);
} catch (\Exception $e) {
} catch (Exception $e) {
continue;
}
if ($this->checkAssignment($entity)) {
$repository->save($entity, ['massUpdate' => true, 'skipStreamNotesAcl' => true]);
$resultIdList[] = $entity->id;
@@ -1944,8 +1998,11 @@ class Record implements Crud,
foreach ($collection as $entity) {
if ($this->getAcl()->check($entity, 'delete') && $this->checkEntityForMassRemove($entity)) {
$repository->remove($entity);
$resultIdList[] = $entity->id;
$count++;
$this->processActionHistoryRecord('delete', $entity);
}
}
@@ -1960,7 +2017,9 @@ class Record implements Crud,
public function massRecalculateFormula(array $params)
{
if (!$this->getUser()->isAdmin()) throw new Forbidden();
if (!$this->getUser()->isAdmin()) {
throw new Forbidden();
}
$count = 0;
@@ -1981,7 +2040,10 @@ class Record implements Crud,
public function follow(string $id, ?string $userId = null)
{
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFoundSilent();
if (!$entity) {
throw new NotFoundSilent();
}
if (!$this->getAcl()->check($entity, 'stream')) {
throw new Forbidden();
@@ -1997,7 +2059,10 @@ class Record implements Crud,
public function unfollow(string $id, ?string $userId = null)
{
$entity = $this->getRepository()->get($id);
if (!$entity) throw new NotFoundSilent();
if (!$entity) {
throw new NotFoundSilent();
}
if (empty($userId)) {
$userId = $this->getUser()->id;
@@ -2021,7 +2086,10 @@ class Record implements Crud,
$collection = $this->getRepository()->sth()->find($selectParams);
foreach ($collection as $entity) {
if (!$this->getAcl()->check($entity, 'stream') || !$this->getAcl()->check($entity, 'read')) continue;
if (!$this->getAcl()->check($entity, 'stream') || !$this->getAcl()->check($entity, 'read')) {
continue;
}
if ($streamService->followEntity($entity, $userId)) {
$resultIdList[] = $entity->id;
}
@@ -2031,7 +2099,9 @@ class Record implements Crud,
'count' => count($resultIdList),
];
if (isset($params['ids'])) $result['ids'] = $resultIdList;
if (isset($params['ids'])) {
$result['ids'] = $resultIdList;
}
return $result;
}
@@ -2042,7 +2112,9 @@ class Record implements Crud,
$streamService = $this->getStreamService();
if (empty($userId)) $userId = $this->getUser()->id;
if (empty($userId)) {
$userId = $this->getUser()->id;
}
$selectParams = $this->convertMassActionSelectParams($params);
@@ -2066,24 +2138,36 @@ class Record implements Crud,
protected function convertMassActionSelectParams($params)
{
if (array_key_exists('ids', $params)) {
if (!is_array($params['ids'])) throw new BadRequest();
$selectParams = $this->getSelectParams([]);
$selectParams['whereClause'][] = [
'id' => $params['ids']
];
} else if (array_key_exists('where', $params)) {
$p = ['where' => $params['where']];
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$p[$k] = $v;
}
if (!is_array($params['ids'])) {
throw new BadRequest();
}
$selectParams = $this->getSelectParams($p);
} else {
throw new BadRequest();
$selectParams = $this->getSelectParams([]);
$selectParams['whereClause'][] = [
'id' => $params['ids'],
];
return $selectParams;
}
return $selectParams;
if (array_key_exists('where', $params)) {
$searchParams = [
'where' => $params['where'],
];
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$searchParams[$k] = $v;
}
}
unset($searchParams['select']);
return $this->getSelectParams($searchParams);
}
throw new BadRequest();
}
protected function getDuplicateWhereClause(Entity $entity, $data)
@@ -2099,7 +2183,12 @@ class Record implements Crud,
if ($entity->id) {
$where['id!='] = $entity->id;
}
$duplicate = $this->getRepository()->select(['id'])->where($where)->findOne();
$duplicate = $this->getRepository()
->select(['id'])
->where($where)
->findOne();
if ($duplicate) {
return true;
}
@@ -2133,7 +2222,9 @@ class Record implements Crud,
$limit = self::FIND_DUPLICATES_LIMIT;
$duplicateList = $this->getRepository()->limit(0, $limit)->find($selectParams);
$duplicateList = $this->getRepository()
->limit(0, $limit)
->find($selectParams);
if (count($duplicateList)) {
return $duplicateList;
@@ -2168,14 +2259,17 @@ class Record implements Crud,
foreach ($this->internalAttributeList as $attribute) {
$entity->clear($attribute);
}
foreach ($this->forbiddenAttributeList as $attribute) {
$entity->clear($attribute);
}
if (!$this->getUser()->isAdmin()) {
foreach ($this->onlyAdminAttributeList as $attribute) {
$entity->clear($attribute);
}
}
foreach ($this->getAcl()->getScopeForbiddenAttributeList($entity->getEntityType(), 'read') as $attribute) {
$entity->clear($attribute);
}
@@ -2216,14 +2310,16 @@ class Record implements Crud,
$this->beforeMerge($entity, $sourceList, $attributes);
$fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields', array());
$fieldDefs = $this->getMetadata()->get('entityDefs.' . $entity->getEntityType() . '.fields', []);
$hasPhoneNumber = false;
if (!empty($fieldDefs['phoneNumber']) && $fieldDefs['phoneNumber']['type'] == 'phone') {
$hasPhoneNumber = true;
}
$hasEmailAddress = false;
if (!empty($fieldDefs['emailAddress']) && $fieldDefs['emailAddress']['type'] == 'email') {
$hasEmailAddress = true;
}
@@ -2239,6 +2335,7 @@ class Record implements Crud,
if ($hasEmailAddress) {
$emailAddressToRelateList = [];
$emailAddressList = $repository->findRelated($entity, 'emailAddresses');
foreach ($emailAddressList as $emailAddress) {
$emailAddressToRelateList[] = $emailAddress;
}
@@ -2266,12 +2363,14 @@ class Record implements Crud,
if ($hasPhoneNumber) {
$phoneNumberList = $repository->findRelated($source, 'phoneNumbers');
foreach ($phoneNumberList as $phoneNumber) {
$phoneNumberToRelateList[] = $phoneNumber;
}
}
if ($hasEmailAddress) {
$emailAddressList = $repository->findRelated($source, 'emailAddresses');
foreach ($emailAddressList as $emailAddress) {
$emailAddressToRelateList[] = $emailAddress;
}
@@ -2280,10 +2379,12 @@ class Record implements Crud,
$mergeLinkList = [];
$linksDefs = $this->getMetadata()->get(['entityDefs', $this->getEntityType(), 'links']);
foreach ($linksDefs as $link => $d) {
if (!empty($d['notMergeable'])) {
continue;
}
if (!empty($d['type']) && in_array($d['type'], ['hasMany', 'hasChildren'])) {
$mergeLinkList[] = $link;
}
@@ -2292,6 +2393,7 @@ class Record implements Crud,
foreach ($sourceList as $source) {
foreach ($mergeLinkList as $link) {
$linkedList = $repository->findRelated($source, $link);
foreach ($linkedList as $linked) {
$repository->relate($entity, $link, $linked);
}
@@ -2306,10 +2408,13 @@ class Record implements Crud,
if ($hasEmailAddress) {
$emailAddressData = [];
foreach ($emailAddressToRelateList as $i => $emailAddress) {
$o = (object) [];
$o->emailAddress = $emailAddress->get('name');
$o->primary = false;
if (empty($attributes->emailAddress)) {
if ($i === 0) {
$o->primary = true;
@@ -2317,8 +2422,10 @@ class Record implements Crud,
} else {
$o->primary = $o->emailAddress === $attributes->emailAddress;
}
$o->optOut = $emailAddress->get('optOut');
$o->invalid = $emailAddress->get('invalid');
$emailAddressData[] = $o;
}
$attributes->emailAddressData = $emailAddressData;
@@ -2326,10 +2433,12 @@ class Record implements Crud,
if ($hasPhoneNumber) {
$phoneNumberData = [];
foreach ($phoneNumberToRelateList as $i => $phoneNumber) {
$o = (object) [];
$o->phoneNumber = $phoneNumber->get('name');
$o->primary = false;
if (empty($attributes->phoneNumber)) {
if ($i === 0) {
$o->primary = true;
@@ -2337,7 +2446,9 @@ class Record implements Crud,
} else {
$o->primary = $o->phoneNumber === $attributes->phoneNumber;
}
$o->type = $phoneNumber->get('type');
$phoneNumberData[] = $o;
}
$attributes->phoneNumberData = $phoneNumberData;
@@ -2364,9 +2475,11 @@ class Record implements Crud,
protected function findLinkedFollowers($id, $params)
{
$entity = $this->getRepository()->get($id);
if (!$entity) {
throw new NotFound();
}
if (!$this->getAcl()->check($entity, 'read')) {
throw new Forbidden();
}
@@ -2396,32 +2509,47 @@ class Record implements Crud,
foreach ($fields as $field => $item) {
if (!empty($item['duplicateIgnore']) || in_array($field, $this->duplicateIgnoreFieldList)) {
$attributeToIgnoreList = $fieldManager->getAttributeList($this->entityType, $field);
foreach ($attributeToIgnoreList as $attribute) {
unset($attributes->$attribute);
}
continue;
}
if (empty($item['type'])) {
continue;
}
if (empty($item['type'])) continue;
$type = $item['type'];
if (in_array($type, ['file', 'image'])) {
$attachment = $entity->get($field);
if ($attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$attachment = $this->getEntityManager()
->getRepository('Attachment')
->getCopiedAttachment($attachment);
$idAttribute = $field . 'Id';
if ($attachment) {
$attributes->$idAttribute = $attachment->id;
}
}
} else if (in_array($type, ['attachmentMultiple'])) {
$attachmentList = $entity->get($field);
if (count($attachmentList)) {
$idList = [];
$nameHash = (object) [];
$typeHash = (object) [];
foreach ($attachmentList as $attachment) {
$attachment = $this->getEntityManager()->getRepository('Attachment')->getCopiedAttachment($attachment);
$attachment = $this->getEntityManager()
->getRepository('Attachment')
->getCopiedAttachment($attachment);
if ($attachment) {
$idList[] = $attachment->id;
$nameHash->{$attachment->id} = $attachment->get('name');
@@ -2435,8 +2563,12 @@ class Record implements Crud,
} else if ($type === 'linkMultiple') {
$foreignLink = $entity->getRelationParam($field, 'foreign');
$foreignEntityType = $entity->getRelationParam($field, 'entity');
if ($foreignEntityType && $foreignLink) {
$foreignRelationType = $this->getMetadata()->get(['entityDefs', $foreignEntityType, 'links', $foreignLink, 'type']);
$foreignRelationType = $this->getMetadata()->get(
['entityDefs', $foreignEntityType, 'links', $foreignLink, 'type']
);
if ($foreignRelationType !== 'hasMany') {
unset($attributes->{$field . 'Ids'});
unset($attributes->{$field . 'Names'});
@@ -2461,7 +2593,9 @@ class Record implements Crud,
$duplicatingEntityId = $data->_duplicatingEntityId;
if (!$duplicatingEntityId) return;
$duplicatingEntity = $this->getEntityManager()->getEntity($entity->getEntityType(), $duplicatingEntityId);
if (!$duplicatingEntity) return;
if (!$this->getAcl()->check($duplicatingEntity, 'read')) return;
@@ -2474,6 +2608,7 @@ class Record implements Crud,
foreach ($this->duplicatingLinkList as $link) {
$linkedList = $repository->findRelated($duplicatingEntity, $link);
foreach ($linkedList as $linked) {
$repository->relate($entity, $link, $linked);
}
+20 -12
View File
@@ -131,6 +131,8 @@ class Export
$collection = null;
$exportAllFields = !array_key_exists('fieldList', $params);
if (array_key_exists('collection', $params)) {
$collection = $params['collection'];
} else {
@@ -138,26 +140,33 @@ class Export
if (array_key_exists('ids', $params)) {
$ids = $params['ids'];
$where = [
[
'type' => 'in',
'field' => 'id',
'value' => $ids
'value' => $ids,
]
];
$selectParams = $selectManager->getSelectParams(['where' => $where], true, true);
$selectParams = $selectManager->getSelectParams(['where' => $where], true, true, true);
}
else if (array_key_exists('where', $params)) {
$where = $params['where'];
$p = [];
$p['where'] = $where;
$searchParams = [];
$searchParams['where'] = $where;
if (!empty($params['selectData']) && is_array($params['selectData'])) {
foreach ($params['selectData'] as $k => $v) {
$p[$k] = $v;
$searchParams[$k] = $v;
}
}
$selectParams = $this->getSelectParams($p);
unset($searchParams['select']);
$selectParams = $selectManager->getSelectParams($searchParams, true, true, true);
}
else {
throw new BadRequest();
@@ -172,14 +181,15 @@ class Export
$select = Select::fromRaw($selectParams);
$collection = $this->entityManager->getRepository($this->entityType)
$collection = $this->entityManager
->getRepository($this->entityType)
->clone($select)
->sth()
->find();
}
$attributeListToSkip = [
'deleted'
'deleted',
];
foreach ($this->skipAttributeList as $attribute) {
@@ -208,15 +218,12 @@ class Export
}
}
if (!array_key_exists('fieldList', $params)) {
$exportAllFields = true;
if ($exportAllFields) {
$fieldDefs = $this->metadata->get(['entityDefs', $this->entityType, 'fields'], []);
$fieldList = array_keys($fieldDefs);
array_unshift($fieldList, 'id');
} else {
$exportAllFields = false;
$fieldList = $params['fieldList'];
}
@@ -225,6 +232,7 @@ class Export
unset($fieldList[$i]);
}
}
$fieldList = array_values($fieldList);
if (method_exists($exportObj, 'filterFieldList')) {
@@ -56,7 +56,11 @@ class Csv
public function loadAdditionalFields(Entity $entity, $fieldList)
{
foreach ($fieldList as $field) {
if ($this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
$fieldType = $this->metadata->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
if (
$fieldType === 'linkMultiple' || $fieldType === 'attachmentMultiple'
) {
if (!$entity->has($field . 'Ids')) {
$entity->loadLinkMultipleField($field);
}
@@ -113,7 +113,9 @@ class Xlsx
}
}
foreach ($fieldList as $field) {
if ($this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']) === 'linkMultiple') {
$fieldType = $this->getMetadata()->get(['entityDefs', $entity->getEntityType(), 'fields', $field, 'type']);
if ($fieldType === 'linkMultiple' || $fieldType === 'attachmentMultiple') {
if (!$entity->has($field . 'Ids')) {
$entity->loadLinkMultipleField($field);
}
@@ -442,7 +444,7 @@ class Xlsx
}
$sheet->setCellValue("$col$rowNumber", $value);
}
} else if ($type == 'linkMultiple') {
} else if ($type == 'linkMultiple' || $type == 'attachmentMultiple') {
if (array_key_exists($name . 'Ids', $row) && array_key_exists($name . 'Names', $row)) {
$nameList = [];
foreach ($row[$name . 'Ids'] as $relatedId) {
@@ -37,11 +37,12 @@ use Espo\Core\{
Utils\Language,
};
class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\FileManagerAware
class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\FileManagerAware, Di\DataCacheAware
{
use Di\DefaultLanguageSetter;
use Di\MetadataSetter;
use Di\FileManagerSetter;
use Di\DataCacheSetter;
protected $ignoreList = [
'Global.sets',
@@ -71,7 +72,7 @@ class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\File
public function getScopeData($language, $scope)
{
$languageObj = new Language($language, $this->fileManager, $this->metadata);
$languageObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache);
$data = $languageObj->get($scope);
@@ -179,8 +180,8 @@ class LabelManager implements Di\DefaultLanguageAware, Di\MetadataAware, Di\File
public function saveLabels($language, $scope, $labels)
{
$languageObj = new Language($language, $this->fileManager, $this->metadata);
$languageOriginalObj = new Language($language, $this->fileManager, $this->metadata, false, true);
$languageObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache);
$languageOriginalObj = new Language($language, $this->fileManager, $this->metadata, $this->dataCache, false, true);
$returnDataHash = [];
@@ -9,4 +9,4 @@
</div>
{{/if}}
<div class="edit-container">{{{edit}}}</div>
<div class="edit-container record">{{{edit}}}</div>
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep) {
define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep) {
return Dep.extend({
@@ -42,25 +42,32 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
return {
scopeList: this.scopeList,
scope: this.scope,
isNew: !(this.id)
isNew: !(this.id),
};
},
events: {
'change .scope-switcher input[name="scope"]': function () {
this.notify('Loading...');
var scope = $('.scope-switcher input[name="scope"]:checked').val();
this.scope = scope;
this.getModelFactory().create(this.scope, function (model) {
model.populateDefaults();
var attributes = this.getView('edit').fetch();
attributes = _.extend(attributes, this.getView('edit').model.toJSON());
model.set(attributes);
this.model = model;
this.createRecordView(model, function (view) {
view.render();
view.notify(false);
});
this.handleAccess(model);
}.bind(this));
},
@@ -75,11 +82,13 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
if (this.options.allDay) {
var allDayScopeList = this.getMetadata().get('clientDefs.Calendar.allDayScopeList') || [];
if (~allDayScopeList.indexOf(this.scope)) {
this.model.set('dateStart', null);
this.model.set('dateEnd', null);
this.model.set('dateStartDate', null);
this.model.set('dateEndDate', this.options.dateEndDate);
if (this.options.dateEndDate !== this.options.dateStartDate) {
this.model.set('dateStartDate', this.options.dateStartDate)
}
@@ -102,7 +111,9 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
this.dateIsChanged = true;
}
}, this);
this.listenTo(this.model, 'change:dateEnd', function (m, value, o) {
this.listenTo(
this.model, 'change:dateEnd', function (m, value, o) {
if (o.ui || o.updatedByDuration) {
this.dateIsChanged = true;
}
@@ -115,6 +126,7 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
if (this.id && !this.getAcl().checkModel(model, 'edit') || !this.id && !this.getAcl().checkModel(model, 'create')) {
this.hideButton('save');
this.hideButton('fullForm');
this.$el.find('button[data-name="save"]').addClass('hidden');
this.$el.find('button[data-name="fullForm"]').addClass('hidden');
} else {
@@ -131,8 +143,10 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
afterRender: function () {
Dep.prototype.afterRender.call(this);
if (this.hasView('edit')) {
var model = this.getView('edit').model;
if (model) {
this.handleAccess(model);
}
@@ -145,6 +159,7 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
if (!this.options.id && !this.options.scope) {
var scopeList = [];
this.scopeList.forEach(function (scope) {
if (this.getAcl().check(scope, 'create')) {
if (~this.enabledScopeList.indexOf(scope)) {
@@ -152,6 +167,7 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
}
}
}, this);
this.scopeList = scopeList;
var calendarDefaultEntity = scopeList[0];
@@ -187,6 +203,7 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
this.confirm(this.translate('removeRecordConfirmation', 'messages'), function () {
var $buttons = this.dialog.$el.find('.modal-footer button');
$buttons.addClass('disabled');
model.destroy({
success: function () {
this.trigger('after:destroy', model);
@@ -194,10 +211,9 @@ Espo.define('crm:views/calendar/modals/edit', 'views/modals/edit', function (Dep
}.bind(this),
error: function () {
$buttons.removeClass('disabled');
}
},
});
}, this);
}
},
});
});
@@ -34,19 +34,30 @@ define('crm:views/case/record/detail', 'views/record/detail', function (Dep) {
setupActionItems: function () {
Dep.prototype.setupActionItems.call(this);
if (
this.getAcl().checkModel(this.model, 'edit') &&
!~['Closed', 'Rejected', 'Duplicate'].indexOf(this.model.get('status')) &&
this.getAcl().checkField(this.entityType, 'status', 'edit')
) {
this.dropdownItemList.push({
'label': 'Close',
'name': 'close',
});
this.dropdownItemList.push({
'label': 'Reject',
'name': 'reject',
});
var statusList = this.getMetadata().get(
['entityDefs', 'Case', 'fields', 'status', 'options']
) || [];
if (~statusList.indexOf('Closed')) {
this.dropdownItemList.push({
'label': 'Close',
'name': 'close',
});
}
if (~statusList.indexOf('Rejected')) {
this.dropdownItemList.push({
'label': 'Reject',
'name': 'reject',
});
}
}
},
@@ -32,7 +32,8 @@ Espo.define('crm:views/task/fields/is-overdue', 'views/fields/base', function (D
readOnly: true,
_template: '{{#if isOverdue}}<span class="label label-danger">{{translate "overdue"}}</span>{{/if}}',
_template: '{{#if isOverdue}}<span class="label label-danger">' +
'{{translate "overdue" scope="Task"}}</span>{{/if}}',
data: function () {
var isOverdue = false;
+1 -1
View File
@@ -4,7 +4,7 @@
<div class="button-container">
<button class="btn btn-default pull-right hidden" data-action="reset">{{translate 'Reset'}}</button>
<div class="btn-group">
<button class="btn btn-default dropdown-toggle select-field" data-toggle="dropdown" tabindex="-1">{{translate 'Select Field'}} <span class="caret"></span></button>
<button class="btn btn-default dropdown-toggle select-field" data-toggle="dropdown" tabindex="-1">{{translate 'Add Field'}} <span class="caret"></span></button>
<ul class="dropdown-menu pull-left filter-list">
{{#each ../fieldList}}
<li data-name="{{./this}}"><a href="javascript:" data-name="{{./this}}" data-action="add-field">{{translate this scope=../../entityType category='fields'}}</a></li>
+17 -1
View File
@@ -142,7 +142,10 @@ define('ui', [], function () {
if (this.removeOnClose) {
this.$el.on('hidden.bs.modal', function (e) {
if (this.$el.get(0) == e.target) {
if (this.skipRemove) return;
if (this.skipRemove) {
return;
}
this.remove();
}
}.bind(this));
@@ -324,11 +327,22 @@ define('ui', [], function () {
$('body > .popover').addClass('hidden');
};
Dialog.prototype.hide = function () {
this.$el.find('.modal-content').addClass('hidden');
};
Dialog.prototype.hideWithBackdrop = function () {
var $modalBackdrop = $('.modal-backdrop');
$modalBackdrop.last().addClass('hidden');
$($modalBackdrop.get($modalBackdrop.length - 2)).removeClass('hidden');
var $modalConainer = $('.modal-container');
$($modalConainer.get($modalConainer.length - 2)).removeClass('overlaid');
this.skipRemove = true;
setTimeout(function () {
this.skipRemove = false;
}.bind(this), 50);
@@ -337,6 +351,7 @@ define('ui', [], function () {
this.$el.find('.modal-content').addClass('hidden');
};
Dialog.prototype.close = function () {
var $modalBackdrop = $('.modal-backdrop');
$modalBackdrop.last().removeClass('hidden');
@@ -347,6 +362,7 @@ define('ui', [], function () {
this.$el.modal('hide');
$(this).trigger('dialog:close');
};
Dialog.prototype.remove = function () {
this.onRemove();
this.$el.remove();
@@ -88,6 +88,7 @@ define('views/admin/field-manager/edit', ['view', 'model'], function (Dep, Model
this.model.set('tooltipText', this.getLanguage().translate(this.field, 'tooltips', this.scope));
}
} else {
this.model.scope = this.scope;
this.model.set('type', this.type);
}
@@ -0,0 +1,57 @@
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2020 Yuri Kuznetsov, Taras Machyshyn, Oleksiy Avramenko
* Website: https://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.
************************************************************************/
define('views/admin/field-manager/fields/date/after-before', 'views/fields/varchar', function (Dep) {
return Dep.extend({
setupOptions: function () {
Dep.prototype.setupOptions.call(this);
if (!this.model.scope) {
return;
}
var list = this.getFieldManager().getEntityTypeFieldList(
this.model.scope,
{
typeList: ['date', 'datetime', 'datetimeOptional'],
}
);
if (this.model.get('name')) {
list = list.filter(function (item) {
return item !== this.model.get('name');
}, this);
}
this.params.options = list;
},
});
});
+15 -5
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/admin/integrations/index', 'view', function (Dep) {
define('views/admin/integrations/index', 'view', function (Dep) {
return Dep.extend({
@@ -51,12 +51,13 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
},
setup: function () {
this.integrationList = Object.keys(this.getMetadata().get('integrations') || {});;
this.integrationList = Object.keys(this.getMetadata().get('integrations') || {});
this.integration = this.options.integration || null;
this.on('after:render', function () {
this.renderHeader();
if (!this.integration) {
this.renderDefaultPage();
} else {
@@ -70,34 +71,45 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
this.getRouter().navigate('#Admin/integrations/name=' + integration, {trigger: false});
var viewName = this.getMetadata().get('integrations.' + integration + '.view') || 'views/admin/integrations/' + Espo.Utils.camelCaseToHyphen(this.getMetadata().get('integrations.' + integration + '.authMethod'));
var viewName = this.getMetadata().get('integrations.' + integration + '.view') ||
'views/admin/integrations/' +
Espo.Utils.camelCaseToHyphen(this.getMetadata().get('integrations.' + integration + '.authMethod'));
this.notify('Loading...');
this.createView('content', viewName, {
el: '#integration-content',
integration: integration,
}, function (view) {
this.renderHeader();
view.render();
this.notify(false);
$(window).scrollTop(0);
}.bind(this));
},
renderDefaultPage: function () {
$('#integration-header').html('').hide();
if (this.integrationList.length) {
var msg = this.translate('selectIntegration', 'messages', 'Integration');
} else {
var msg = '<p class="lead">' + this.translate('noIntegrations', 'messages', 'Integration') + '</p>';
}
$('#integration-content').html(msg);
},
renderHeader: function () {
if (!this.integration) {
$('#integration-header').html('');
return;
}
$('#integration-header').show().html(this.translate(this.integration, 'titles', 'Integration'));
},
@@ -106,5 +118,3 @@ Espo.define('views/admin/integrations/index', 'view', function (Dep) {
},
});
});
+17 -8
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/export/modals/export', ['views/modal', 'model'], function (Dep, Model) {
define('views/export/modals/export', ['views/modal', 'model'], function (Dep, Model) {
return Dep.extend({
@@ -44,11 +44,11 @@ Espo.define('views/export/modals/export', ['views/modal', 'model'], function (De
{
name: 'export',
label: 'Export',
style: 'danger'
style: 'danger',
},
{
name: 'cancel',
label: 'Cancel'
label: 'Cancel',
}
];
@@ -73,44 +73,53 @@ Espo.define('views/export/modals/export', ['views/modal', 'model'], function (De
this.createView('record', 'views/export/record/record', {
scope: this.scope,
model: this.model,
el: this.getSelector() + ' .record'
el: this.getSelector() + ' .record',
});
},
actionExport: function () {
var data = this.getView('record').fetch();
this.model.set(data);
if (this.getView('record').validate()) return;
if (this.getView('record').validate()) {
return;
}
var returnData = {
exportAllFields: data.exportAllFields,
format: data.format
format: data.format,
};
if (!data.exportAllFields) {
var attributeList = [];
data.fieldList.forEach(function (item) {
if (item === 'id') {
attributeList.push('id');
return;
}
var type = this.getMetadata().get(['entityDefs', this.scope, 'fields', item, 'type']);
if (type) {;
if (type) {
this.getFieldManager().getAttributeList(type, item).forEach(function (attribute) {
attributeList.push(attribute);
}, this);
}
if (~item.indexOf('_')) {
attributeList.push(item);
}
}, this);
returnData.attributeList = attributeList;
returnData.fieldList = data.fieldList;
}
this.trigger('proceed', returnData);
this.close();
}
},
});
});
+22 -10
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
define('views/export/record/record', 'views/record/base', function (Dep) {
return Dep.extend({
@@ -48,10 +48,10 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
fieldList = fieldList.filter(function (item) {
var defs = this.getMetadata().get(['entityDefs', this.scope, 'fields', item]) || {};
if (defs.disabled) return;
if (defs.exportDisabled) return;
if (defs.type === 'map') return;
if (defs.type === 'attachmentMultiple') return;
return true;
}, this);
@@ -61,32 +61,43 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
fieldList.unshift('id');
var translatedOptions = {};
fieldList.forEach(function (item) {
translatedOptions[item] = this.getLanguage().translate(item, 'fields', this.scope);
}, this);
this.createField('exportAllFields', 'views/fields/bool', {
});
this.createField('exportAllFields', 'views/fields/bool', {});
var setFieldList = this.model.get('fieldList') || [];
setFieldList.forEach(function (item) {
if (~fieldList.indexOf(item)) return;
if (!~item.indexOf('_')) return;
if (~fieldList.indexOf(item)) {
return;
}
if (!~item.indexOf('_')) {
return;
}
var arr = item.split('_');
fieldList.push(item);
var foreignScope = this.getMetadata().get(['entityDefs', this.scope, 'links', arr[0], 'entity']);
if (!foreignScope) return;
translatedOptions[item] = this.getLanguage().translate(arr[0], 'links', this.scope) + '.' + this.getLanguage().translate(arr[1], 'fields', foreignScope);
if (!foreignScope) {
return;
}
translatedOptions[item] = this.getLanguage().translate(arr[0], 'links', this.scope) + '.' +
this.getLanguage().translate(arr[1], 'fields', foreignScope);
}, this);
this.createField('fieldList', 'views/fields/multi-enum', {
required: true,
translatedOptions: translatedOptions,
options: fieldList
options: fieldList,
});
var formatList =
@@ -98,6 +109,7 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
});
this.controlAllFields();
this.listenTo(this.model, 'change:exportAllFields', function () {
this.controlAllFields();
}, this);
@@ -109,7 +121,7 @@ Espo.define('views/export/record/record', 'views/record/base', function (Dep) {
} else {
this.hideField('fieldList');
}
}
},
});
});
-10
View File
@@ -356,11 +356,6 @@ define('views/fields/address', 'views/fields/base', function (Dep) {
this.$country.attr('autocomplete', 'espo-country');
}
this.controlStreetTextareaHeight();
this.$street.on('input', function (e) {
this.controlStreetTextareaHeight();
}.bind(this));
var cityList = this.getConfig().get('addressCityList') || [];
if (cityList.length) {
this.$city.autocomplete({
@@ -394,11 +389,6 @@ define('views/fields/address', 'views/fields/base', function (Dep) {
this.$city.attr('autocomplete', 'espo-city');
}
this.controlStreetTextareaHeight();
this.$street.on('input', function (e) {
this.controlStreetTextareaHeight();
}.bind(this));
var stateList = this.getConfig().get('addressStateList') || [];
if (stateList.length) {
this.$state.autocomplete({
+20 -1
View File
@@ -624,6 +624,10 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
var self = options.espoView;
var lang = options.langInfo;
if (!self) {
return;
}
context.memo('button.espoImage', function () {
var button = ui.button({
contents: options.espoImage.icon,
@@ -673,6 +677,10 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
var self = options.espoView;
var lang = options.langInfo;
if (!self) {
return;
}
this.show = function () {
var linkInfo = context.invoke('editor.getLinkInfo');
@@ -705,6 +713,10 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
var self = options.espoView;
var lang = options.langInfo;
if (!self) {
return;
}
var isMacLike = /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform);
context.memo('button.espoLink', function () {
@@ -721,7 +733,10 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
this.initialize = function () {};
this.destroy = function () {
if (!self) return;
if (!self) {
return;
}
self.clearView('dialogInsertLink');
}
@@ -757,6 +772,10 @@ define('views/fields/wysiwyg', ['views/fields/text', 'lib!Summernote'], function
var self = options.espoView;
var lang = options.langInfo;
if (!self) {
return;
}
this.$window = $(window);
this.$scrollbar = $('html, body');
+4 -4
View File
@@ -388,13 +388,13 @@ define('views/import/step2', 'view', function (Dep) {
},
disableButtons: function () {
this.$el.find('button[data-action="next"]').addClass('disabled');
this.$el.find('button[data-action="back"]').addClass('disabled');
this.$el.find('button[data-action="next"]').addClass('disabled').attr('disabled', 'disabled');
this.$el.find('button[data-action="back"]').addClass('disabled').attr('disabled', 'disabled');
},
enableButtons: function () {
this.$el.find('button[data-action="next"]').removeClass('disabled');
this.$el.find('button[data-action="back"]').removeClass('disabled');
this.$el.find('button[data-action="next"]').removeClass('disabled').removeAttr('disabled');
this.$el.find('button[data-action="back"]').removeClass('disabled').removeAttr('disabled');
},
fetch: function (skipValidation) {
+41 -2
View File
@@ -70,8 +70,13 @@ define('views/modals/compose-email', 'views/modals/edit', function (Dep) {
this.once('after:render', function () {
this.actionClose();
}, this);
return;
}
this.once('remove', function () {
this.dialogIsHidden = false;
}, this);
},
createRecordView: function (model, callback) {
@@ -99,18 +104,34 @@ define('views/modals/compose-email', 'views/modals/edit', function (Dep) {
var model = editView.model;
var afterSend = function () {
this.dialogIsHidden = false;
this.trigger('after:save', model);
this.trigger('after:send', model);
dialog.close();
this.stopListening(editView, 'before:save', beforeSave);
this.stopListening(editView, 'error:save', errorSave);
this.remove();
}.bind(this);
var beforeSave = function () {
dialog.hide();
this.dialogIsHidden = true;
dialog.hideWithBackdrop();
editView.setConfirmLeaveOut(false);
};
if (!this.forceRemoveIsInitiated) {
this.initiateForceRemove();
}
}.bind(this);
var errorSave = function () {
this.dialogIsHidden = false;
if (this.isRendered()) {
dialog.show();
}
@@ -169,5 +190,23 @@ define('views/modals/compose-email', 'views/modals/edit', function (Dep) {
editView.saveDraft();
},
initiateForceRemove: function () {
this.forceRemoveIsInitiated = true;
var parentView = this.getParentView();
if (!parentView) {
return true;
}
parentView.once('remove', function () {
if (!this.dialogIsHidden) {
return;
}
this.remove();
}, this);
},
});
});
+17 -3
View File
@@ -26,7 +26,7 @@
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function (Dep) {
define('views/modals/image-preview', ['views/modal', 'lib!exif'], function (Dep) {
return Dep.extend({
@@ -72,25 +72,29 @@ Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function
getImageUrl: function () {
var url = this.getBasePath() + '?entryPoint=image&id=' + this.options.id;
if (this.size) {
url += '&size=' + this.size;
}
if (this.getUser().get('portalId')) {
url += '&portalId=' + this.getUser().get('portalId');
}
return url;
},
getOriginalImageUrl: function () {
var url = this.getBasePath() + '?entryPoint=image&id=' + this.options.id;
if (this.getUser().get('portalId')) {
url += '&portalId=' + this.getUser().get('portalId');
}
return url;
},
onImageLoad: function () {
console.log(1);
},
afterRender: function () {
@@ -102,7 +106,12 @@ Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function
var imgEl = $img.get(0);
EXIF.getData(imgEl, function () {
if ($img.css('image-orientation') === 'from-image') {
return;
}
var orientation = EXIF.getTag(this, 'Orientation');
switch (orientation) {
case 2:
$img.addClass('transform-flip');
@@ -138,6 +147,7 @@ Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function
if (this.navigationEnabled) {
$img.css('cursor', 'pointer');
$img.click(function () {
this.switchToNext();
}.bind(this));
@@ -145,10 +155,12 @@ Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function
var manageSize = function () {
var width = $container.width();
$img.css('maxWidth', width);
}.bind(this);
$(window).off('resize.image-review');
$(window).on('resize.image-review', function () {
manageSize();
});
@@ -159,12 +171,12 @@ Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function
},
switchToNext: function () {
this.transformClassList.forEach(function (item) {
this.$img.removeClass(item);
}, this);
var index = -1;
this.imageList.forEach(function (d, i) {
if (d.id === this.options.id) {
index = i;
@@ -172,12 +184,14 @@ Espo.define('views/modals/image-preview', ['views/modal', 'lib!exif'], function
}, this);
index++;
if (index > this.imageList.length - 1) {
index = 0;
}
this.options.id = this.imageList[index].id
this.options.name = this.imageList[index].name;
this.reRender();
},
+2
View File
@@ -246,6 +246,8 @@ define('views/record/detail', ['views/record/base', 'view-record-helper'], funct
this.getAcl().check(this.entityType, 'edit')
&&
!~this.getAcl().getScopeForbiddenFieldList(this.entityType).indexOf('assignedUser')
&&
!this.getUser().isPortal()
) {
if (this.model.has('assignedUserId')) {
this.dropdownItemList.push({
+7 -3
View File
@@ -31,6 +31,11 @@ if (substr(php_sapi_name(), 0, 3) != 'cli') exit;
include "bootstrap.php";
use Espo\Core\{
Application,
ApplicationRunners\Rebuild,
};
$arg = isset($_SERVER['argv'][1]) ? trim($_SERVER['argv'][1]) : '';
if (empty($arg)) {
@@ -46,7 +51,7 @@ if (!isset($pathInfo['extension']) || $pathInfo['extension'] !== 'zip' || !is_fi
die("Unsupported package.\n");
}
$app = new \Espo\Core\Application();
$app = new Application();
$app->setupSystemUser();
$config = $app->getContainer()->get('config');
@@ -67,8 +72,7 @@ try {
}
try {
$app = new \Espo\Core\Application();
$app->runRebuild();
(new Application())->run(Rebuild::class);
} catch (\Exception $e) {}
echo "Extension installation is complete.\n";
+23 -13
View File
@@ -410,32 +410,42 @@ class Installer
{
$this->auth();
$user = array(
$result = $this->createRecord('User', [
'id' => '1',
'userName' => $userName,
'password' => $this->getPasswordHash()->hash($password),
'lastName' => 'Admin',
'type' => 'admin',
);
]);
$result = $this->createRecord('User', $user);
return $result;
}
protected function saveAdminPreferences($preferences)
{
$data = [
$this->saveAdminPreferences([
'dateFormat' => '',
'timeFormat' => '',
'timeZone' => '',
'weekStart' => -1,
'defaultCurrency' => '',
'language' => '',
'thousandSeparator' => $preferences['thousandSeparator'] ?? ',',
'decimalMark' => $preferences['decimalMark'] ?? '.',
];
'thousandSeparator' => $this->getConfig()->get('thousandSeparator', ','),
'decimalMark' => $this->getConfig()->get('decimalMark', '.'),
]);
return $result;
}
protected function saveAdminPreferences($preferences)
{
$permittedSettingList = array(
'dateFormat',
'timeFormat',
'timeZone',
'weekStart',
'defaultCurrency',
'thousandSeparator',
'decimalMark',
'language',
);
$data = array_intersect_key($preferences, array_flip($permittedSettingList));
if (empty($data)) {
return true;
}
+3 -3
View File
@@ -100,7 +100,7 @@
{$langs['fields']['Thousand Separator']}
</label>
<div class="field field-thousandSeparator">
<input type="text" class="main-element form-control" name="thousandSeparator" value="{$fields['thousandSeparator'].value}">
<input type="text" class="main-element form-control" name="thousandSeparator" value="{$fields['thousandSeparator'].value}" maxlength="1">
</div>
</div>
@@ -108,8 +108,8 @@
<label class="field-label-decimalMark control-label">
{$langs['fields']['Decimal Mark']} *</label>
<div class="field field-decimalMark">
<input type="text" class="main-element form-control" name="decimalMark" value="{$fields['decimalMark'].value}">
</div>
<input type="text" class="main-element form-control" name="decimalMark" value="{$fields['decimalMark'].value}" maxlength="1">
</div>
</div>
</div>
+22 -5
View File
@@ -45,15 +45,21 @@ class Diff
}
getTagList () {
var dirInitial = process.cwd();
process.chdir(this.espoPath);
var tagsString = cp.execSync('git tag -l --sort=-v:refname').toString();
var tagList = tagsString.trim().split("\n");
process.chdir(dirInitial);
return tagList;
}
getPreviousVersionList () {
var dirInitial = process.cwd();
var version = (require(this.espoPath + '/package.json') || {}).version;
process.chdir(this.espoPath);
@@ -87,11 +93,18 @@ class Diff
if (!~tag.indexOf('beta') && !~tag.indexOf('alpha')) {
versionFromList.push(tag);
break;
var patchVersionNumberI = tag.split('.')[2];
if (patchVersionNumberI === '0') {
break;
}
}
}
}
process.chdir(dirInitial);
return versionFromList;
}
@@ -225,6 +238,9 @@ class Diff
}
}
var deletedFileList = this.getDeletedFileList(versionFrom);
var tagList = this.getTagList();
process.chdir(buildPath);
var fileList = [];
@@ -251,8 +267,6 @@ class Diff
fs.writeFileSync(diffBeforeUpgradeFolderPath, beforeUpgradeFileList.join('\n'));
}
var deletedFileList = this.getDeletedFileList(versionFrom);
if (beforeUpgradeFileList.length) {
cp.execSync(
'xargs -a ' + diffBeforeUpgradeFolderPath + ' cp -p --parents -t ' + upgradePath + '/beforeUpgradeFiles'
@@ -265,8 +279,6 @@ class Diff
}
}
var tagList = this.getTagList();
execute('xargs -a ' + diffFilePath + ' cp -p --parents -t ' + upgradePath + '/files' , function (stdout) {
var d = new Date();
@@ -442,6 +454,8 @@ class Diff
}
getDeletedFileList (versionFrom) {
var dirInitial = process.cwd();
process.chdir(this.espoPath);
var deletedFileList = this.getRepositoryDeletedFileList(versionFrom);
@@ -460,6 +474,7 @@ class Diff
deletedFileList = deletedFileList.filter(function (item) {
if (
item.indexOf('tests/') === 0 ||
item.indexOf('upgrades/') === 0 ||
item.indexOf('frontend/less') === 0
) {
return false;
@@ -468,6 +483,8 @@ class Diff
return true;
});
process.chdir(dirInitial);
return deletedFileList;
}
+13 -2
View File
@@ -29,5 +29,16 @@
include "bootstrap.php";
$app = new \Espo\Core\Application();
$app->runEntryPoint('OauthCallback');
use Espo\Core\{
Application,
ApplicationRunners\EntryPoint,
};
$app = new Application();
$app->run(
EntryPoint::class,
(object) [
'entryPoint' => 'oauthCallback',
]
);
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "6.0.0-beta3",
"version": "6.0.3",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "espocrm",
"version": "6.0.0-beta3",
"version": "6.0.3",
"description": "",
"main": "index.php",
"repository": {
+1 -1
View File
@@ -146,7 +146,7 @@ class Tester
{
$configData = $this->getTestConfigData();
if ($configData[$optionName] === $data) {
if (array_key_exists($optionName, $configData) && $configData[$optionName] === $data) {
return true;
}
@@ -434,4 +434,22 @@ class EvaluatorTest extends \PHPUnit\Framework\TestCase
$this->assertEquals('test', $result);
}
public function testNegate1()
{
$expression = "!string\contains('hello', 'test')";
$result = $this->evaluator->process($expression);
$this->assertTrue($result);
}
public function testLogicalProority()
{
$expression = "0 && 0 || 1";
$result = $this->evaluator->process($expression);
$this->assertTrue($result);
}
}
+7 -3
View File
@@ -31,6 +31,11 @@ if (substr(php_sapi_name(), 0, 3) != 'cli') exit;
include "bootstrap.php";
use Espo\Core\{
Application,
ApplicationRunners\Rebuild,
};
$arg = isset($_SERVER['argv'][1]) ? trim($_SERVER['argv'][1]) : '';
if ($arg == 'version' || $arg == '-v') {
@@ -51,7 +56,7 @@ if (!isset($pathInfo['extension']) || $pathInfo['extension'] !== 'zip' || !is_fi
die("Unsupported package.\n");
}
$app = new \Espo\Core\Application();
$app = new Application();
$app->setupSystemUser();
$config = $app->getContainer()->get('config');
@@ -73,8 +78,7 @@ try {
}
try {
$app = new \Espo\Core\Application();
$app->runRebuild();
(new Application())->run(Rebuild::class);
} catch (\Exception $e) {}
echo "Upgrade is complete. Current version is " . $config->get('version') . ". \n";
+27
View File
@@ -34,6 +34,8 @@ class AfterUpgrade
$this->container = $container;
$this->processEmailAccountsUpdate();
$this->cleanupFiles();
}
protected function processEmailAccountsUpdate()
@@ -46,4 +48,29 @@ class AfterUpgrade
$pdo->exec($q1);
$pdo->exec($q2);
}
protected function cleanupFiles()
{
$fileManager = $this->container->get('fileManager');
$fileList = [
'application/Espo/Core/Loaders/ClientManager.php',
'client/res/templates/record/panels/default-side.tpl',
'application/Espo/Resources/templates/noteEmailRecieved',
];
foreach ($fileList as $file) {
if (is_dir($file)) {
$fileManager->removeInDir($file, true);
continue;
}
if (!file_exists($file)) {
continue;
}
unlink($file);
}
}
}
+23 -4
View File
@@ -86,9 +86,28 @@ class BeforeUpgrade
if ($extension) {
$version = $extension->get('version');
if (version_compare($version, '1.5.0', '<')) {
if (version_compare($version, '1.4.0', '<')) {
$message =
"EspoCRM 6.0.0 is not compatible with Real Estate extension of a version lower than 1.5.0. " .
"EspoCRM 6.0.0 is not compatible with Real Estate extension of a version lower than 1.4.0. " .
"Please upgrade the extension or uninstall it. Then run the upgrade command again.";
throw new Error($message);
}
}
$extension = $em->getRepository('Extension')
->where([
'name' => 'VoIP Integration',
'isInstalled' => true,
])
->findOne();
if ($extension) {
$version = $extension->get('version');
if (version_compare($version, '1.15.0', '<')) {
$message =
"EspoCRM 6.0.0 is not compatible with VoIP Integration extension of a version lower than 1.15.0. " .
"Please upgrade the extension or uninstall it. Then run the upgrade command again.";
throw new Error($message);
@@ -116,8 +135,8 @@ class BeforeUpgrade
$message =
"In v6.0 we have dropped a support of MyISAM engine for DB tables. " .
"You have the following tables that use MyISAM: {$tableListString}.{$lineBreak}" .
"Please change the engine to InnoDB for these tables then run upgrade again.{$lineBreak}" .
"You have the following tables that use MyISAM: {$tableListString}.{$lineBreak}{$lineBreak}" .
"Please change the engine to InnoDB for these tables then run upgrade again.{$lineBreak}{$lineBreak}" .
"See: {$linkString}.";
throw new Error($message);