diff --git a/application/Espo/Core/Container.php b/application/Espo/Core/Container.php index 6ece88af05..85883dcd72 100644 --- a/application/Espo/Core/Container.php +++ b/application/Espo/Core/Container.php @@ -32,7 +32,7 @@ namespace Espo\Core; class Container { - private $data = array(); + private $data = []; /** @@ -222,6 +222,13 @@ class Container ); } + protected function loadNotificatorFactory() + { + return new \Espo\Core\NotificatorFactory( + $this + ); + } + protected function loadMetadata() { return new \Espo\Core\Utils\Metadata( diff --git a/application/Espo/Core/Injectable.php b/application/Espo/Core/Injectable.php index 0d6d6dcb0a..83347c6612 100644 --- a/application/Espo/Core/Injectable.php +++ b/application/Espo/Core/Injectable.php @@ -31,9 +31,9 @@ namespace Espo\Core; abstract class Injectable implements \Espo\Core\Interfaces\Injectable { - protected $dependencyList = array(); + protected $dependencyList = []; - protected $injections = array(); + protected $injections = []; public function inject($name, $object) { diff --git a/application/Espo/Core/InjectableFactory.php b/application/Espo/Core/InjectableFactory.php index 870e36c51f..132fd2f1a7 100644 --- a/application/Espo/Core/InjectableFactory.php +++ b/application/Espo/Core/InjectableFactory.php @@ -55,4 +55,14 @@ class InjectableFactory } throw new Error("Class '$className' does not exist"); } + + protected function getMetadata() + { + return $this->getContainer()->get('metadata'); + } + + protected function getContainer() + { + return $this->container; + } } diff --git a/application/Espo/Core/Mail/Importer.php b/application/Espo/Core/Mail/Importer.php index ba7ee65be3..72d6cf76bb 100644 --- a/application/Espo/Core/Mail/Importer.php +++ b/application/Espo/Core/Mail/Importer.php @@ -42,11 +42,14 @@ class Importer private $filtersMatcher; - public function __construct($entityManager, $config) + private $notificator = null; + + public function __construct($entityManager, $config, $notificator = null) { $this->entityManager = $entityManager; $this->config = $config; $this->filtersMatcher = new FiltersMatcher(); + $this->notificator = $notificator; } protected function getEntityManager() @@ -64,6 +67,11 @@ class Importer return $this->filtersMatcher; } + protected function getNotificator() + { + return $this->notificator; + } + public function importMessage($parserType = 'ZendMail', $message, $assignedUserId = null, $teamsIdList = [], $userIdList = [], $filterList = [], $fetchOnlyHeader = false, $folderData = null) { $parser = $message->getParser(); @@ -153,10 +161,14 @@ class Importer } } + $duplicate = null; + if ($duplicate = $this->findDuplicate($email)) { - $duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id); - $this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList); - return $duplicate; + if ($duplicate->get('status') != 'Being Imported') { + $duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id); + $this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList); + return $duplicate; + } } if ($parser->checkMessageAttribute($message, 'date')) { @@ -280,11 +292,34 @@ class Importer } } - $this->getEntityManager()->getPdo()->query('LOCK TABLES `email` WRITE'); + if (!$duplicate) { + $this->lockEmailTable(); + if ($duplicate = $this->findDuplicate($email)) { + $this->unlockTables(); + if ($duplicate->get('status') != 'Being Imported') { + $duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id); + $this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList); + return $duplicate; + } + } + } + + if ($duplicate) { + $duplicate->set([ + 'from' => $email->get('from'), + 'to' => $email->get('to'), + 'cc' => $email->get('cc'), + 'bcc' => $email->get('bcc'), + 'replyTo' => $email->get('replyTo'), + 'name' => $email->get('name'), + 'dateSent' => $email->get('dateSent'), + 'body' => $email->get('body'), + 'bodyPlain' => $email->get('name'), + 'parentType' => $email->get('parentType'), + 'parentId' => $email->get('parentId') + ]); + $this->getEntityManager()->getRepository('Email')->fillAccount($duplicate); - if ($duplicate = $this->findDuplicate($email)) { - $this->getEntityManager()->getPdo()->query('UNLOCK TABLES'); - $duplicate = $this->getEntityManager()->getEntity('Email', $duplicate->id); $this->processDuplicate($duplicate, $assignedUserId, $userIdList, $folderData, $teamsIdList); return $duplicate; } @@ -292,13 +327,14 @@ class Importer if (!$email->get('messageId')) { $email->setDummyMessageId(); } + $email->set('status', 'Being Imported'); $this->getEntityManager()->saveEntity($email, [ 'skipAll' => true, 'keepNew' => true ]); - $this->getEntityManager()->getPdo()->query('UNLOCK TABLES'); + $this->unlockTables(); if ($parentFound) { $parentType = $email->get('parentType'); @@ -317,6 +353,8 @@ class Importer } } + $email->set('status', 'Archived'); + $this->getEntityManager()->saveEntity($email, [ 'isBeingImported' => true ]); @@ -332,6 +370,16 @@ class Importer return $email; } + protected function lockEmailTable() + { + $this->getEntityManager()->getPdo()->query('LOCK TABLES `email` WRITE'); + } + + protected function unlockTables() + { + $this->getEntityManager()->getPdo()->query('UNLOCK TABLES'); + } + protected function findParent(Entity $email, $emailAddress) { $contact = $this->getEntityManager()->getRepository('Contact')->where(array( @@ -415,13 +463,24 @@ class Importer $duplicate->set('isBeingImported', true); - $this->getEntityManager()->saveEntity($duplicate, [ + $this->getEntityManager()->getRepository('Email')->applyUsersFilters($duplicate); + + $this->getEntityManager()->getRepository('Email')->processLinkMultipleFieldSave($duplicate, 'users', [ 'skipLinkMultipleRemove' => true, - 'skipLinkMultipleUpdate' => true, - 'isBeingImported' => true, - 'isDuplicate' => true + 'skipLinkMultipleUpdate' => true ]); + $this->getEntityManager()->getRepository('Email')->processLinkMultipleFieldSave($duplicate, 'assignedUsers', [ + 'skipLinkMultipleRemove' => true, + 'skipLinkMultipleUpdate' => true + ]); + + if ($notificator = $this->getNotificator()) { + $notificator->process($duplicate, [ + 'isBeingImported' => true + ]); + } + if (!empty($teamsIdList)) { foreach ($teamsIdList as $teamId) { $this->getEntityManager()->getRepository('Email')->relate($duplicate, 'teams', $teamId); diff --git a/application/Espo/Core/NotificatorFactory.php b/application/Espo/Core/NotificatorFactory.php new file mode 100644 index 0000000000..8995456a0c --- /dev/null +++ b/application/Espo/Core/NotificatorFactory.php @@ -0,0 +1,58 @@ +getMetadata()->getScopeModuleName($entityType); + if ($moduleName) { + $className = '\\Espo\\Modules\\' . $moduleName . '\\Notificators\\' . $normalizedName; + } else { + $className = '\\Espo\\Notificators\\' . $normalizedName; + } + if (!class_exists($className)) { + $className = '\\Espo\\Core\\Notificators\\Base'; + } + } + + return $this->createByClassName($className); + } +} diff --git a/application/Espo/Core/ORM/Repositories/RDB.php b/application/Espo/Core/ORM/Repositories/RDB.php index c716508fa9..8dba605f50 100644 --- a/application/Espo/Core/ORM/Repositories/RDB.php +++ b/application/Espo/Core/ORM/Repositories/RDB.php @@ -454,6 +454,134 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable } } + public function processLinkMultipleFieldSave(Entity $entity, $link, array $options = []) + { + $name = $link; + + $idListAttribute = $link . 'Ids'; + $columnsAttribute = $link . 'Columns'; + + if ($this->getMetadata()->get("entityDefs." . $entity->getEntityType() . ".fields.{$name}.noSave")) { + return; + } + + $skipCreate = false; + $skipRemove = false; + $skipUpdate = false; + if (!empty($options['skipLinkMultipleCreate'])) $skipCreate = true; + if (!empty($options['skipLinkMultipleRemove'])) $skipRemove = true; + if (!empty($options['skipLinkMultipleUpdate'])) $skipUpdate = true; + + if ($entity->isNew()) { + $skipRemove = true; + } + + if ($entity->has($idListAttribute)) { + $specifiedIdList = $entity->get($idListAttribute); + } else if ($entity->has($columnsAttribute)) { + $skipRemove = true; + $specifiedIdList = []; + foreach ($entity->get($columnsAttribute) as $id => $d) { + $specifiedIdList[] = $id; + } + } else { + return; + } + + if (!is_array($specifiedIdList)) return; + + $toRemoveIdList = []; + $existingIdList = []; + $toUpdateIdList = []; + $toCreateIdList = []; + $existingColumnsData = (object)[]; + + $defs = []; + $columns = $this->getMetadata()->get("entityDefs." . $entity->getEntityType() . ".fields.{$name}.columns"); + if (!empty($columns)) { + $columnData = $entity->get($columnsAttribute); + $defs['additionalColumns'] = $columns; + } + + $foreignEntityList = $entity->get($name, $defs); + if ($foreignEntityList) { + foreach ($foreignEntityList as $foreignEntity) { + $existingIdList[] = $foreignEntity->id; + if (!empty($columns)) { + $data = (object)[]; + foreach ($columns as $columnName => $columnField) { + $foreignId = $foreignEntity->id; + $data->$columnName = $foreignEntity->get($columnField); + } + $existingColumnsData->$foreignId = $data; + if (!$entity->isNew()) { + $entity->setFetched($columnsAttribute, $existingColumnsData); + } + } + } + } + + if (!$entity->isNew()) { + if ($entity->has($idListAttribute) && !$entity->hasFetched($idListAttribute)) { + $entity->setFetched($idListAttribute, $existingIdList); + } + if ($entity->has($columnsAttribute) && !empty($columns)) { + $entity->setFetched($columnsAttribute, $existingColumnsData); + } + } + + foreach ($existingIdList as $id) { + if (!in_array($id, $specifiedIdList)) { + if (!$skipRemove) { + $toRemoveIdList[] = $id; + } + } else { + if (!$skipUpdate && !empty($columns)) { + foreach ($columns as $columnName => $columnField) { + if (isset($columnData->$id) && is_object($columnData->$id)) { + if ( + property_exists($columnData->$id, $columnName) + && + ( + !property_exists($existingColumnsData->$id, $columnName) + || + $columnData->$id->$columnName !== $existingColumnsData->$id->$columnName + ) + ) { + $toUpdateIdList[] = $id; + } + } + } + } + } + } + + if (!$skipCreate) { + foreach ($specifiedIdList as $id) { + if (!in_array($id, $existingIdList)) { + $toCreateIdList[] = $id; + } + } + } + + foreach ($toCreateIdList as $id) { + $data = null; + if (!empty($columns) && isset($columnData->$id)) { + $data = $columnData->$id; + } + $this->relate($entity, $name, $id, $data); + } + + foreach ($toRemoveIdList as $id) { + $this->unrelate($entity, $name, $id); + } + + foreach ($toUpdateIdList as $id) { + $data = $columnData->$id; + $this->updateRelation($entity, $name, $id, $data); + } + } + protected function processSpecifiedRelationsSave(Entity $entity, array $options = []) { $relationTypeList = [$entity::HAS_MANY, $entity::MANY_MANY, $entity::HAS_CHILDREN]; @@ -461,128 +589,8 @@ class RDB extends \Espo\ORM\Repositories\RDB implements Injectable if (in_array($defs['type'], $relationTypeList)) { $idListAttribute = $name . 'Ids'; $columnsAttribute = $name . 'Columns'; - if ($entity->has($idListAttribute) || $entity->has($columnsAttribute)) { - if ($this->getMetadata()->get("entityDefs." . $entity->getEntityType() . ".fields.{$name}.noSave")) { - continue; - } - - $skipCreate = false; - $skipRemove = false; - $skipUpdate = false; - if (!empty($options['skipLinkMultipleCreate'])) $skipCreate = true; - if (!empty($options['skipLinkMultipleRemove'])) $skipRemove = true; - if (!empty($options['skipLinkMultipleUpdate'])) $skipUpdate = true; - - if ($entity->isNew()) { - $skipRemove = true; - } - - if ($entity->has($idListAttribute)) { - $specifiedIdList = $entity->get($idListAttribute); - } else if ($entity->has($columnsAttribute)) { - $skipRemove = true; - $specifiedIdList = []; - foreach ($entity->get($columnsAttribute) as $id => $d) { - $specifiedIdList[] = $id; - } - } else { - continue; - } - - if (!is_array($specifiedIdList)) continue; - - $toRemoveIdList = []; - $existingIdList = []; - $toUpdateIdList = []; - $toCreateIdList = []; - $existingColumnsData = (object)[]; - - $defs = []; - $columns = $this->getMetadata()->get("entityDefs." . $entity->getEntityType() . ".fields.{$name}.columns"); - if (!empty($columns)) { - $columnData = $entity->get($columnsAttribute); - $defs['additionalColumns'] = $columns; - } - - $foreignEntityList = $entity->get($name, $defs); - if ($foreignEntityList) { - foreach ($foreignEntityList as $foreignEntity) { - $existingIdList[] = $foreignEntity->id; - if (!empty($columns)) { - $data = (object)[]; - foreach ($columns as $columnName => $columnField) { - $foreignId = $foreignEntity->id; - $data->$columnName = $foreignEntity->get($columnField); - } - $existingColumnsData->$foreignId = $data; - if (!$entity->isNew()) { - $entity->setFetched($columnsAttribute, $existingColumnsData); - } - } - } - } - - if (!$entity->isNew()) { - if ($entity->has($idListAttribute) && !$entity->hasFetched($idListAttribute)) { - $entity->setFetched($idListAttribute, $existingIdList); - } - if ($entity->has($columnsAttribute) && !empty($columns)) { - $entity->setFetched($columnsAttribute, $existingColumnsData); - } - } - - foreach ($existingIdList as $id) { - if (!in_array($id, $specifiedIdList)) { - if (!$skipRemove) { - $toRemoveIdList[] = $id; - } - } else { - if (!$skipUpdate && !empty($columns)) { - foreach ($columns as $columnName => $columnField) { - if (isset($columnData->$id) && is_object($columnData->$id)) { - if ( - property_exists($columnData->$id, $columnName) - && - ( - !property_exists($existingColumnsData->$id, $columnName) - || - $columnData->$id->$columnName !== $existingColumnsData->$id->$columnName - ) - ) { - $toUpdateIdList[] = $id; - } - } - } - } - } - } - - if (!$skipCreate) { - foreach ($specifiedIdList as $id) { - if (!in_array($id, $existingIdList)) { - $toCreateIdList[] = $id; - } - } - } - - foreach ($toCreateIdList as $id) { - $data = null; - if (!empty($columns) && isset($columnData->$id)) { - $data = $columnData->$id; - } - $this->relate($entity, $name, $id, $data); - } - - foreach ($toRemoveIdList as $id) { - $this->unrelate($entity, $name, $id); - } - - foreach ($toUpdateIdList as $id) { - $data = $columnData->$id; - $this->updateRelation($entity, $name, $id, $data); - } - + $this->processLinkMultipleFieldSave($entity, $name, $options); } } else if ($defs['type'] === $entity::HAS_ONE) { if (empty($defs['entity']) || empty($defs['foreignKey'])) continue; diff --git a/application/Espo/Hooks/Common/Notifications.php b/application/Espo/Hooks/Common/Notifications.php index b219320202..96a7a1fec6 100644 --- a/application/Espo/Hooks/Common/Notifications.php +++ b/application/Espo/Hooks/Common/Notifications.php @@ -47,6 +47,11 @@ class Notifications extends \Espo\Core\Hooks\Base return $this->getContainer()->get('serviceFactory'); } + protected function getNotificatorFactory() + { + return $this->getContainer()->get('notificatorFactory'); + } + protected function checkHasStream($entityType) { if (!array_key_exists($entityType, $this->hasStreamCache)) { @@ -58,27 +63,7 @@ class Notifications extends \Espo\Core\Hooks\Base protected function getNotificator($entityType) { if (empty($this->notifatorsHash[$entityType])) { - $normalizedName = Util::normilizeClassName($entityType); - - $className = '\\Espo\\Custom\\Notificators\\' . $normalizedName; - if (!class_exists($className)) { - $moduleName = $this->getMetadata()->getScopeModuleName($entityType); - if ($moduleName) { - $className = '\\Espo\\Modules\\' . $moduleName . '\\Notificators\\' . $normalizedName; - } else { - $className = '\\Espo\\Notificators\\' . $normalizedName; - } - if (!class_exists($className)) { - $className = '\\Espo\\Core\\Notificators\\Base'; - } - } - - $notificator = new $className(); - $dependencies = $notificator->getDependencyList(); - foreach ($dependencies as $name) { - $notificator->inject($name, $this->getContainer()->get($name)); - } - + $notificator = $this->getNotificatorFactory()->create($entityType); $this->notifatorsHash[$entityType] = $notificator; } return $this->notifatorsHash[$entityType]; @@ -150,6 +135,4 @@ class Notifications extends \Espo\Core\Hooks\Base } return $this->streamService; } - } - diff --git a/application/Espo/Repositories/Email.php b/application/Espo/Repositories/Email.php index edc7a94c8a..feeca8676c 100644 --- a/application/Espo/Repositories/Email.php +++ b/application/Espo/Repositories/Email.php @@ -275,30 +275,8 @@ class Email extends \Espo\Core\ORM\Repositories\RDB $entity->setLinkMultipleColumn('users', 'isRead', $entity->get('createdById'), true); } - if (!$entity->isNew() && $entity->isAttributeChanged('parentId')) { - $entity->set('accountId', null); - } - - $parentId = $entity->get('parentId'); - $parentType = $entity->get('parentType'); - if ($parentId && $parentType) { - $parent = $this->getEntityManager()->getEntity($parentType, $parentId); - if ($parent) { - $accountId = null; - if ($parent->getEntityType() == 'Account') { - $accountId = $parent->id; - } - if (!$accountId && $parent->get('accountId') && $parent->getRelationParam('account', 'entity') == 'Account') { - $accountId = $parent->get('accountId'); - } - if ($accountId) { - $account = $this->getEntityManager()->getEntity('Account', $accountId); - if ($account) { - $entity->set('accountId', $accountId); - $entity->set('accountName', $account->get('name')); - } - } - } + if ($entity->isNew() || $entity->isAttributeChanged('parentId')) { + $this->fillAccount($entity); } } @@ -311,17 +289,51 @@ class Email extends \Espo\Core\ORM\Repositories\RDB $this->loadToField($entity); } } - foreach ($entity->getLinkMultipleIdList('users') as $userId) { - $filter = $this->getEmailFilterManager()->getMatchingFilter($entity, $userId); - if ($filter) { - $action = $filter->get('action'); - if ($action === 'Skip') { - $entity->setLinkMultipleColumn('users', 'inTrash', $userId, true); - } else if ($action === 'Move to Folder') { - $folderId = $filter->get('emailFolderId'); - if ($folderId) { - $entity->setLinkMultipleColumn('users', 'folderId', $userId, $folderId); - } + $this->applyUsersFilters($entity); + } + } + + public function fillAccount(Entity $entity) + { + if (!$entity->isNew()) { + $entity->set('accountId', null); + } + + $parentId = $entity->get('parentId'); + $parentType = $entity->get('parentType'); + if ($parentId && $parentType) { + $parent = $this->getEntityManager()->getEntity($parentType, $parentId); + if ($parent) { + $accountId = null; + if ($parent->getEntityType() == 'Account') { + $accountId = $parent->id; + } + if (!$accountId && $parent->get('accountId') && $parent->getRelationParam('account', 'entity') == 'Account') { + $accountId = $parent->get('accountId'); + } + if ($accountId) { + $account = $this->getEntityManager()->getEntity('Account', $accountId); + if ($account) { + $entity->set('accountId', $accountId); + $entity->set('accountName', $account->get('name')); + } + } + } + } + } + + public function applyUsersFilters(Entity $entity) + { + foreach ($entity->getLinkMultipleIdList('users') as $userId) { + $filter = $this->getEmailFilterManager()->getMatchingFilter($entity, $userId); + if ($filter) { + $action = $filter->get('action'); + if ($action === 'Skip') { + $entity->setLinkMultipleColumn('users', 'inTrash', $userId, true); + } else if ($action === 'Move to Folder') { + $folderId = $filter->get('emailFolderId'); + if ($folderId) { + $entity->setLinkMultipleColumn('users', 'folderId', $userId, $folderId); } } } diff --git a/application/Espo/Services/EmailAccount.php b/application/Espo/Services/EmailAccount.php index 5acf5a9cd6..dd55c0bbd1 100644 --- a/application/Espo/Services/EmailAccount.php +++ b/application/Espo/Services/EmailAccount.php @@ -47,6 +47,7 @@ class EmailAccount extends Record { parent::init(); $this->addDependency('crypt'); + $this->addDependency('notificatorFactory'); } protected function getCrypt() @@ -176,7 +177,9 @@ class EmailAccount extends Record throw new Error("Email Account {$emailAccount->id} is not active."); } - $importer = new \Espo\Core\Mail\Importer($this->getEntityManager(), $this->getConfig()); + $notificator = $this->getInjection('notificatorFactory')->create('Email'); + + $importer = new \Espo\Core\Mail\Importer($this->getEntityManager(), $this->getConfig(), $notificator); $maxSize = $this->getConfig()->get('emailMessageMaxSize'); diff --git a/application/Espo/Services/InboundEmail.php b/application/Espo/Services/InboundEmail.php index a3d0c4b632..07f25c97f3 100644 --- a/application/Espo/Services/InboundEmail.php +++ b/application/Espo/Services/InboundEmail.php @@ -76,6 +76,7 @@ class InboundEmail extends \Espo\Services\Record $this->addDependency('mailSender'); $this->addDependency('crypt'); + $this->addDependency('notificatorFactory'); } protected function getMailSender() @@ -159,7 +160,9 @@ class InboundEmail extends \Espo\Services\Record throw new Error("Group Email Account {$emailAccount->id} is not active."); } - $importer = new \Espo\Core\Mail\Importer($this->getEntityManager(), $this->getConfig()); + $notificator = $this->getInjection('notificatorFactory')->create('Email'); + + $importer = new \Espo\Core\Mail\Importer($this->getEntityManager(), $this->getConfig(), $notificator); $maxSize = $this->getConfig()->get('emailMessageMaxSize');