diff --git a/app/bundles/EmailBundle/Config/config.php b/app/bundles/EmailBundle/Config/config.php
index 0b72ee7c1f4..2d20671b35d 100644
--- a/app/bundles/EmailBundle/Config/config.php
+++ b/app/bundles/EmailBundle/Config/config.php
@@ -308,6 +308,7 @@
'translator',
'doctrine.orm.entity_manager',
'mautic.stage.model.stage',
+ 'mautic.helper.core_parameters',
],
],
'mautic.form.type.email.utm_tags' => [
diff --git a/app/bundles/EmailBundle/Entity/Email.php b/app/bundles/EmailBundle/Entity/Email.php
index 38cf6505019..6923feb426a 100644
--- a/app/bundles/EmailBundle/Entity/Email.php
+++ b/app/bundles/EmailBundle/Entity/Email.php
@@ -64,6 +64,11 @@ class Email extends FormEntity implements VariantEntityInterface, TranslationEnt
*/
private $subject;
+ /**
+ * @var bool
+ */
+ private $useOwnerAsMailer;
+
/**
* @var string
*/
@@ -265,6 +270,7 @@ public static function loadMetadata(ORM\ClassMetadata $metadata)
$builder->addNullableField('fromName', Type::STRING, 'from_name');
$builder->addNullableField('replyToAddress', Type::STRING, 'reply_to_address');
$builder->addNullableField('bccAddress', Type::STRING, 'bcc_address');
+ $builder->addNullableField('useOwnerAsMailer', Type::BOOLEAN, 'use_owner_as_mailer');
$builder->addNullableField('template', Type::STRING);
$builder->addNullableField('content', Type::TARRAY);
$builder->addNullableField('utmTags', Type::TARRAY, 'utm_tags');
@@ -436,6 +442,7 @@ public static function loadApiMetadata(ApiMetadataDriver $metadata)
'fromName',
'replyToAddress',
'bccAddress',
+ 'useOwnerAsMailer',
'utmTags',
'customHtml',
'plainText',
@@ -678,6 +685,26 @@ public function setSubject($subject)
return $this;
}
+ /**
+ * @return bool
+ */
+ public function getUseOwnerAsMailer()
+ {
+ return $this->useOwnerAsMailer;
+ }
+
+ /**
+ * @param bool $useOwnerAsMailer
+ *
+ * @return $this
+ */
+ public function setUseOwnerAsMailer($useOwnerAsMailer)
+ {
+ $this->useOwnerAsMailer = $useOwnerAsMailer;
+
+ return $this;
+ }
+
/**
* @return mixed
*/
diff --git a/app/bundles/EmailBundle/Form/Type/EmailType.php b/app/bundles/EmailBundle/Form/Type/EmailType.php
index 29efbc7288a..5d90f66af91 100644
--- a/app/bundles/EmailBundle/Form/Type/EmailType.php
+++ b/app/bundles/EmailBundle/Form/Type/EmailType.php
@@ -23,6 +23,7 @@
use Mautic\CoreBundle\Form\Type\SortableListType;
use Mautic\CoreBundle\Form\Type\ThemeListType;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
+use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\EmailBundle\Entity\Email;
use Mautic\FormBundle\Form\Type\FormListType;
use Mautic\LeadBundle\Form\Type\LeadListType;
@@ -62,14 +63,21 @@ class EmailType extends AbstractType
*/
private $stageModel;
+ /**
+ * @var CoreParametersHelper
+ */
+ private $coreParametersHelper;
+
public function __construct(
TranslatorInterface $translator,
EntityManager $entityManager,
- StageModel $stageModel
+ StageModel $stageModel,
+ CoreParametersHelper $coreParametersHelper
) {
- $this->translator = $translator;
- $this->em = $entityManager;
- $this->stageModel = $stageModel;
+ $this->translator = $translator;
+ $this->em = $entityManager;
+ $this->stageModel = $stageModel;
+ $this->coreParametersHelper = $coreParametersHelper;
}
public function buildForm(FormBuilderInterface $builder, array $options)
@@ -160,6 +168,21 @@ public function buildForm(FormBuilderInterface $builder, array $options)
]
);
+ $builder->add(
+ 'useOwnerAsMailer',
+ YesNoButtonGroupType::class,
+ [
+ 'label' => 'mautic.email.use.owner.as.mailer',
+ 'label_attr' => ['class' => 'control-label'],
+ 'attr' => [
+ 'class' => 'form-control',
+ 'tooltip' => 'mautic.email.use.owner.as.mailer.tooltip',
+ ],
+ 'data' => (bool) (is_null($options['data']->getUseOwnerAsMailer()) ? $this->coreParametersHelper->get('mailer_is_owner') : $options['data']->getUseOwnerAsMailer()),
+ 'required' => false,
+ ]
+ );
+
$builder->add(
'utmTags',
EmailUtmTagsType::class,
diff --git a/app/bundles/EmailBundle/Helper/MailHelper.php b/app/bundles/EmailBundle/Helper/MailHelper.php
index eefdafb6a42..4784d65258b 100644
--- a/app/bundles/EmailBundle/Helper/MailHelper.php
+++ b/app/bundles/EmailBundle/Helper/MailHelper.php
@@ -208,13 +208,6 @@ class MailHelper
*/
protected $fatal = false;
- /**
- * Flag whether to use only the globally set From email and name or whether to switch to mailer is owner.
- *
- * @var bool
- */
- protected $useGlobalFrom = false;
-
/**
* Large batch mail sends may result on timeouts with SMTP servers. This will will keep track of the number of sends and restart the connection once met.
*
@@ -323,18 +316,17 @@ public function getSampleMailer($cleanSlate = true)
*
* @param bool $dispatchSendEvent
* @param bool $isQueueFlush (a tokenized/batch send via API such as Mandrill)
- * @param bool $useOwnerAsMailer
*
* @return bool
*/
- public function send($dispatchSendEvent = false, $isQueueFlush = false, $useOwnerAsMailer = true)
+ public function send($dispatchSendEvent = false, $isQueueFlush = false)
{
if ($this->tokenizationEnabled && !empty($this->queuedRecipients) && !$isQueueFlush) {
// This transport uses tokenization and queue()/flushQueue() was not used therefore use them in order
// properly populate metadata for this transport
if ($result = $this->queue($dispatchSendEvent)) {
- $result = $this->flushQueue(['To', 'Cc', 'Bcc'], $useOwnerAsMailer);
+ $result = $this->flushQueue(['To', 'Cc', 'Bcc']);
}
return $result;
@@ -343,15 +335,23 @@ public function send($dispatchSendEvent = false, $isQueueFlush = false, $useOwne
// Set from email
$ownerSignature = false;
if (!$isQueueFlush) {
- if ($useOwnerAsMailer) {
- if ($owner = $this->getContactOwner($this->lead)) {
- $this->setFrom($owner['email'], $owner['first_name'].' '.$owner['last_name'], null);
- $ownerSignature = $this->getContactOwnerSignature($owner);
+ $emailToSend = $this->getEmail();
+ if (!empty($emailToSend)) {
+ if ($emailToSend->getUseOwnerAsMailer()) {
+ $owner = $this->getContactOwner($this->lead);
+ if (!empty($owner)) {
+ $this->setFrom($owner['email'], $owner['first_name'].' '.$owner['last_name']);
+ $ownerSignature = $this->getContactOwnerSignature($owner);
+ } else {
+ $this->setFrom($this->systemFrom, null);
+ }
+ } elseif (!empty($emailToSend->getFromAddress())) {
+ $this->setFrom($emailToSend->getFromAddress(), $emailToSend->getFromName());
} else {
- $this->setFrom($this->from, null, null);
+ $this->setFrom($this->systemFrom, null);
}
- } elseif (!$from = $this->message->getFrom()) {
- $this->setFrom($this->from, null, null);
+ } else {
+ $this->setFrom($this->from, null);
}
} // from is set in flushQueue
@@ -572,12 +572,11 @@ public function queue($dispatchSendEvent = false, $returnMode = self::QUEUE_RESE
/**
* Send batched mail to mailer.
*
- * @param array $resetEmailTypes Array of email types to clear after flusing the queue
- * @param bool $useOwnerAsMailer
+ * @param array $resetEmailTypes Array of email types to clear after flusing the queue
*
* @return bool
*/
- public function flushQueue($resetEmailTypes = ['To', 'Cc', 'Bcc'], $useOwnerAsMailer = true)
+ public function flushQueue($resetEmailTypes = ['To', 'Cc', 'Bcc'])
{
// Assume true unless there was a fatal error configuring the mailer because if tokenizationEnabled is false, the send happened in queue()
$flushed = empty($this->fatal);
@@ -593,10 +592,18 @@ public function flushQueue($resetEmailTypes = ['To', 'Cc', 'Bcc'], $useOwnerAsMa
$this->errors = [];
- if (!$this->useGlobalFrom && $useOwnerAsMailer && 'default' !== $fromKey) {
- $this->setFrom($metadatum['from']['email'], $metadatum['from']['first_name'].' '.$metadatum['from']['last_name'], null);
+ $email = $this->getEmail();
+
+ if (!empty($email)) {
+ if ($email->getUseOwnerAsMailer() && 'default' !== $fromKey) {
+ $this->setFrom($metadatum['from']['email'], $metadatum['from']['first_name'].' '.$metadatum['from']['last_name']);
+ } elseif (!empty($email->getFromAddress())) {
+ $this->setFrom($email->getFromAddress(), $email->getFromName());
+ } else {
+ $this->setFrom($this->systemFrom, null);
+ }
} else {
- $this->setFrom($this->from, null, null);
+ $this->setFrom($this->from, null);
}
foreach ($metadatum['contacts'] as $email => $contact) {
@@ -657,7 +664,6 @@ public function reset($cleanSlate = true)
$this->internalSend = false;
$this->fatal = false;
$this->idHashState = true;
- $this->useGlobalFrom = false;
$this->checkIfTransportNeedsRestart(true);
$this->logger->clear();
@@ -1235,25 +1241,15 @@ public function setReturnPath($address)
*
* @param string|array $fromEmail
* @param string $fromName
- * @param bool|null $isGlobal
*/
- public function setFrom($fromEmail, $fromName = null, $isGlobal = true)
+ public function setFrom($fromEmail, $fromName = null)
{
$fromName = $this->cleanName($fromName);
- if (null !== $isGlobal) {
- if ($isGlobal) {
- if (is_array($fromEmail)) {
- $this->from = $fromEmail;
- } else {
- $this->from = [$fromEmail => $fromName];
- }
- } else {
- // Reset the default to the system from
- $this->from = $this->systemFrom;
- }
-
- $this->useGlobalFrom = $isGlobal;
+ if (is_array($fromEmail)) {
+ $this->from = $fromEmail;
+ } else {
+ $this->from = [$fromEmail => $fromName];
}
try {
@@ -1371,7 +1367,7 @@ public function setEmail(Email $email, $allowBcc = true, $slots = [], $assetAtta
$fromEmail = key($this->from);
}
- $this->setFrom($fromEmail, $fromName, null);
+ $this->setFrom($fromEmail, $fromName);
$this->from = [$fromEmail => $fromName];
} else {
$this->from = $this->systemFrom;
@@ -2055,15 +2051,19 @@ protected function cleanName($name)
protected function getContactOwner(&$contact)
{
$owner = false;
-
- if ($this->factory->getParameter('mailer_is_owner') && is_array($contact) && isset($contact['id'])) {
- if (!isset($contact['owner_id'])) {
- $contact['owner_id'] = 0;
- } elseif (isset($contact['owner_id'])) {
- if (isset(self::$leadOwners[$contact['owner_id']])) {
- $owner = self::$leadOwners[$contact['owner_id']];
- } elseif ($owner = $this->factory->getModel('lead')->getRepository()->getLeadOwner($contact['owner_id'])) {
- self::$leadOwners[$owner['id']] = $owner;
+ $email = $this->getEmail();
+
+ if (!empty($email)) {
+ if ($email->getUseOwnerAsMailer() && is_array($contact) && isset($contact['id'])) {
+ if (!isset($contact['owner_id'])) {
+ $contact['owner_id'] = 0;
+ } elseif (isset($contact['owner_id'])) {
+ $leadModel = $this->factory->getModel('lead');
+ if (isset(self::$leadOwners[$contact['owner_id']])) {
+ $owner = self::$leadOwners[$contact['owner_id']];
+ } elseif ($owner = $leadModel->getRepository()->getLeadOwner($contact['owner_id'])) {
+ self::$leadOwners[$owner['id']] = $owner;
+ }
}
}
}
diff --git a/app/bundles/EmailBundle/Tests/Form/Type/EmailTypeTest.php b/app/bundles/EmailBundle/Tests/Form/Type/EmailTypeTest.php
index 54ee9c89602..3a13a27abab 100644
--- a/app/bundles/EmailBundle/Tests/Form/Type/EmailTypeTest.php
+++ b/app/bundles/EmailBundle/Tests/Form/Type/EmailTypeTest.php
@@ -13,6 +13,7 @@
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
+use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\EmailBundle\Entity\Email;
use Mautic\EmailBundle\Form\Type\EmailType;
use Mautic\StageBundle\Model\StageModel;
@@ -47,18 +48,25 @@ class EmailTypeTest extends \PHPUnit\Framework\TestCase
*/
private $form;
+ /**
+ * @var CoreParametersHelper|MockObject
+ */
+ private $coreParametersHelper;
+
protected function setUp(): void
{
parent::setUp();
- $this->translator = $this->createMock(TranslatorInterface::class);
- $this->entityManager = $this->createMock(EntityManager::class);
- $this->stageModel = $this->createMock(StageModel::class);
- $this->formBuilder = $this->createMock(FormBuilderInterface::class);
- $this->form = new EmailType(
+ $this->translator = $this->createMock(TranslatorInterface::class);
+ $this->entityManager = $this->createMock(EntityManager::class);
+ $this->stageModel = $this->createMock(StageModel::class);
+ $this->formBuilder = $this->createMock(FormBuilderInterface::class);
+ $this->coreParametersHelper = $this->createMock(CoreParametersHelper::class);
+ $this->form = new EmailType(
$this->translator,
$this->entityManager,
- $this->stageModel
+ $this->stageModel,
+ $this->coreParametersHelper
);
$this->formBuilder->method('create')->willReturnSelf();
@@ -70,7 +78,7 @@ public function testBuildForm()
'data' => new Email(),
];
- $this->formBuilder->expects($this->at(46))
+ $this->formBuilder->expects($this->at(47))
->method('add')
->with(
'buttons',
diff --git a/app/bundles/EmailBundle/Tests/Helper/MailHelperTest.php b/app/bundles/EmailBundle/Tests/Helper/MailHelperTest.php
index c3c13779570..979886fcb94 100644
--- a/app/bundles/EmailBundle/Tests/Helper/MailHelperTest.php
+++ b/app/bundles/EmailBundle/Tests/Helper/MailHelperTest.php
@@ -166,6 +166,7 @@ public function testQueuedEmailFromOverride()
$email = new Email();
$email->setFromAddress('override@nowhere.com');
$email->setFromName('Test');
+ $email->setUseOwnerAsMailer(false);
$mailer->setEmail($email);
@@ -233,6 +234,11 @@ public function testQueuedOwnerAsMailer()
$swiftMailer = new \Swift_Mailer($transport);
$mailer = new MailHelper($mockFactory, $swiftMailer, ['nobody@nowhere.com' => 'No Body']);
+
+ $email = new Email();
+ $email->setUseOwnerAsMailer(true);
+ $mailer->setEmail($email);
+
$mailer->enableQueue();
$mailer->setSubject('Hello');
@@ -291,6 +297,11 @@ public function testMailAsOwnerWithEncodedCharactersInName()
$swiftMailer = new \Swift_Mailer($transport);
$mailer = new MailHelper($mockFactory, $swiftMailer, ['nobody@nowhere.com' => 'No Body's Business']);
+ $email = new Email();
+ $email->setUseOwnerAsMailer(true);
+
+ $mailer->setEmail($email);
+ $mailer->enableQueue();
$mailer->enableQueue();
$mailer->setSubject('Hello');
@@ -323,6 +334,11 @@ public function testBatchIsEnabledWithBcTokenInterface()
$swiftMailer = new \Swift_Mailer($transport);
$mailer = new MailHelper($mockFactory, $swiftMailer, ['nobody@nowhere.com' => 'No Body']);
+
+ $email = new Email();
+ $email->setUseOwnerAsMailer(true);
+
+ $mailer->setEmail($email);
$mailer->enableQueue();
$mailer->setSubject('Hello');
@@ -372,6 +388,29 @@ public function testGlobalFromThatAllFromAddressesAreTheSame()
$this->assertEquals(['override@owner.com'], array_unique($fromAddresses));
}
+ public function testStandardEmailFrom()
+ {
+ $mockFactory = $this->getMockFactory(true);
+ $transport = new BatchTransport();
+ $swiftMailer = new \Swift_Mailer($transport);
+ $mailer = new MailHelper($mockFactory, $swiftMailer, ['nobody@nowhere.com' => 'No Body']);
+ $email = new Email();
+
+ $email->setUseOwnerAsMailer(false);
+ $email->setFromAddress('override@nowhere.com');
+ $email->setFromName('Test');
+ $mailer->setEmail($email);
+
+ foreach ($this->contacts as $key => $contact) {
+ $mailer->addTo($contact['email']);
+ $mailer->setLead($contact);
+ $mailer->setBody('{signature}');
+ $mailer->send();
+ $from = key($mailer->message->getFrom());
+ $this->assertEquals('override@nowhere.com', $from);
+ }
+ }
+
public function testStandardOwnerAsMailer()
{
$mockFactory = $this->getMockFactory();
@@ -380,6 +419,11 @@ public function testStandardOwnerAsMailer()
$swiftMailer = new \Swift_Mailer($transport);
$mailer = new MailHelper($mockFactory, $swiftMailer, ['nobody@nowhere.com' => 'No Body']);
+
+ $email = new Email();
+ $mailer->setEmail($email);
+ $email->setUseOwnerAsMailer(true);
+
$mailer->setBody('{signature}');
foreach ($this->contacts as $key => $contact) {
diff --git a/app/bundles/EmailBundle/Translations/en_US/messages.ini b/app/bundles/EmailBundle/Translations/en_US/messages.ini
index 951a08433bb..2a8f213b156 100644
--- a/app/bundles/EmailBundle/Translations/en_US/messages.ini
+++ b/app/bundles/EmailBundle/Translations/en_US/messages.ini
@@ -113,9 +113,9 @@ mautic.email.config.mailer.from.email.tooltip="Set the from email for email sent
mautic.email.config.mailer.from.email="E-mail address to send mail from"
mautic.email.config.mailer.from.name.tooltip="Set the from name for email sent by Mautic"
mautic.email.config.mailer.from.name="Name to send mail as"
-mautic.email.config.mailer.host.tooltip="Set the host for mail server"
-mautic.email.config.mailer.host="Host"
-mautic.email.config.mailer.is.owner.tooltip="If a contact owner is known, force his/her email and name as sender email and name"
+mautic.email.config.mailer.host.tooltip="Set the host for SMTP server"
+mautic.email.config.mailer.host="SMTP host"
+mautic.email.config.mailer.is.owner.tooltip="Set the default mailer is owner setting for all new emails that are created"
mautic.email.config.mailer.is.owner="Mailer is owner"
mautic.email.config.mailer.password.tooltip="Set the password required to authenticate the selected mail service"
mautic.email.config.mailer.password="Password for the selected mail service"
@@ -254,6 +254,8 @@ mautic.email.from_email.tooltip="Set the from address for this email. This will
mautic.email.from_email="From address"
mautic.email.from_name.tooltip="Set the from name for the this email. This will default to the system configuration if left blank."
mautic.email.from_name="From name"
+mautic.email.use.owner.as.mailer.tooltip="If a contact owner is known, force his/her email and name as sender email and name."
+mautic.email.use.owner.as.mailer="Use owner as mailer"
mautic.email.graph.line.stats.failed="Failed"
mautic.email.graph.line.stats.read="Read"
mautic.email.graph.line.stats.sent="Sent"
diff --git a/app/bundles/EmailBundle/Views/Email/form.html.php b/app/bundles/EmailBundle/Views/Email/form.html.php
index 529fea83cb2..1c4a752a9f8 100644
--- a/app/bundles/EmailBundle/Views/Email/form.html.php
+++ b/app/bundles/EmailBundle/Views/Email/form.html.php
@@ -129,6 +129,9 @@
row($form['headers']); ?>
+
+ row($form['useOwnerAsMailer']); ?>
+
diff --git a/app/bundles/LeadBundle/Event/CompanyMergeEvent.php b/app/bundles/LeadBundle/Event/CompanyMergeEvent.php
new file mode 100644
index 00000000000..677a38fc271
--- /dev/null
+++ b/app/bundles/LeadBundle/Event/CompanyMergeEvent.php
@@ -0,0 +1,49 @@
+victor = $victor;
+ $this->loser = $loser;
+ }
+
+ /**
+ * Returns the victor (loser merges into the victor).
+ *
+ * @return Company
+ */
+ public function getVictor()
+ {
+ return $this->victor;
+ }
+
+ /**
+ * Returns the loser (loser merges into the victor).
+ */
+ public function getLoser()
+ {
+ return $this->loser;
+ }
+}
diff --git a/app/bundles/LeadBundle/LeadEvents.php b/app/bundles/LeadBundle/LeadEvents.php
index 3c9d4c6ac91..f62c32850dd 100644
--- a/app/bundles/LeadBundle/LeadEvents.php
+++ b/app/bundles/LeadBundle/LeadEvents.php
@@ -531,6 +531,26 @@ final class LeadEvents
*/
const COMPANY_POST_DELETE = 'mautic.company_post_delete';
+ /**
+ * The mautic.company_pre_merge event is dispatched before two companies are merged.
+ *
+ * The event listener receives a
+ * Mautic\LeadBundle\Event\CompanyMergeEvent instance.
+ *
+ * @var string
+ */
+ const COMPANY_PRE_MERGE = 'mautic.company_pre_merge';
+
+ /**
+ * The mautic.company_post_merge event is dispatched after two companies are merged.
+ *
+ * The event listener receives a
+ * Mautic\LeadBundle\Event\CompanyMergeEvent instance.
+ *
+ * @var string
+ */
+ const COMPANY_POST_MERGE = 'mautic.company_post_merge';
+
/**
* The mautic.list_filters_choices_on_generate event is dispatched when the choices for list filters are generated.
*
diff --git a/app/bundles/LeadBundle/Model/CompanyModel.php b/app/bundles/LeadBundle/Model/CompanyModel.php
index 1af85a60f05..9b5ef12d9b0 100644
--- a/app/bundles/LeadBundle/Model/CompanyModel.php
+++ b/app/bundles/LeadBundle/Model/CompanyModel.php
@@ -24,6 +24,7 @@
use Mautic\LeadBundle\Entity\LeadEventLog;
use Mautic\LeadBundle\Entity\LeadField;
use Mautic\LeadBundle\Event\CompanyEvent;
+use Mautic\LeadBundle\Event\CompanyMergeEvent;
use Mautic\LeadBundle\Event\LeadChangeCompanyEvent;
use Mautic\LeadBundle\Form\Type\CompanyType;
use Mautic\LeadBundle\LeadEvents;
@@ -623,6 +624,10 @@ public function companyMerge($mainCompany, $secCompany)
$mainCompanyId = $mainCompany->getId();
$secCompanyId = $secCompany->getId();
+ // Dispatch pre merge event
+ $event = new CompanyMergeEvent($mainCompany, $secCompany);
+ $this->dispatcher->dispatch(LeadEvents::COMPANY_PRE_MERGE, $event);
+
//if they are the same lead, then just return one
if ($mainCompanyId === $secCompanyId) {
return $mainCompany;
@@ -658,6 +663,9 @@ public function companyMerge($mainCompany, $secCompany)
//save the updated company
$this->saveEntity($mainCompany, false);
+ // Dispatch post merge event
+ $this->dispatcher->dispatch(LeadEvents::COMPANY_POST_MERGE, $event);
+
//delete the old company
$this->deleteEntity($secCompany);
diff --git a/app/migrations/Version20180508202930.php b/app/migrations/Version20180508202930.php
new file mode 100644
index 00000000000..48e1b6950a7
--- /dev/null
+++ b/app/migrations/Version20180508202930.php
@@ -0,0 +1,42 @@
+getTable($this->prefix.'emails')->hasColumn('use_owner_as_mailer')) {
+ throw new SkipMigration('Schema includes this migration');
+ }
+ }
+
+ public function up(Schema $schema): void
+ {
+ $this->abortIf('mysql' != $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.');
+
+ $this->addSql("ALTER TABLE {$this->prefix}emails ADD use_owner_as_mailer TINYINT(4) AFTER email_type;");
+
+ $ownerAsMailerConfigSetting = $this->container->getParameter('mautic.mailer_is_owner') ? 1 : 0;
+
+ $this->addSql("UPDATE {$this->prefix}emails SET use_owner_as_mailer = {$ownerAsMailerConfigSetting};");
+ }
+
+ public function down(Schema $schema): void
+ {
+ $this->abortIf('mysql' != $this->connection->getDatabasePlatform()->getName(), 'Migration can only be executed safely on \'mysql\'.');
+
+ $this->addSql("ALTER TABLE {$this->prefix}emails DROP use_owner_as_mailer;");
+ }
+}
diff --git a/plugins/MauticCrmBundle/Api/PipedriveApi.php b/plugins/MauticCrmBundle/Api/PipedriveApi.php
index 30199b73ecd..cec7ec0af6a 100644
--- a/plugins/MauticCrmBundle/Api/PipedriveApi.php
+++ b/plugins/MauticCrmBundle/Api/PipedriveApi.php
@@ -68,6 +68,15 @@ public function removeCompany($id = null)
return $this->getResponseData($response);
}
+ public function mergeCompany($id = null, $otherId = null)
+ {
+ $params = $this->getRequestParameters(['merge_with_id' => $id]);
+ $url = sprintf('%s/%s/%s', $this->integration->getApiUrl(), self::ORGANIZATIONS_API_ENDPOINT, $otherId);
+ $response = $this->transport->put($url, $params);
+
+ return $this->getResponseData($response);
+ }
+
/**
* @param $data
*/
@@ -101,6 +110,15 @@ public function deleteLead($id)
return $this->getResponseData($response);
}
+ public function mergeLead($id = null, $otherId = null)
+ {
+ $params = $this->getRequestParameters(['merge_with_id' => $id]);
+ $url = sprintf('%s/%s/%s', $this->integration->getApiUrl(), self::PERSONS_API_ENDPOINT, $otherId);
+ $response = $this->transport->put($url, $params);
+
+ return $this->getResponseData($response);
+ }
+
/**
* @param string $email
*
diff --git a/plugins/MauticCrmBundle/Command/ProcessPipedriveDeletionsCommand.php b/plugins/MauticCrmBundle/Command/ProcessPipedriveDeletionsCommand.php
new file mode 100644
index 00000000000..55ceb913bd9
--- /dev/null
+++ b/plugins/MauticCrmBundle/Command/ProcessPipedriveDeletionsCommand.php
@@ -0,0 +1,147 @@
+setName('mautic:integration:pipedrive:process-deletions')
+ ->setDescription('Processes the Pipedrive deletion queue');
+
+ parent::configure();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ $io = new SymfonyStyle($input, $output);
+ $container = $this->getContainer();
+
+ /** @var IntegrationHelper $integrationHelper */
+ $integrationHelper = $container->get('mautic.helper.integration');
+ $integrationObject = $integrationHelper->getIntegrationObject(PipedriveIntegration::INTEGRATION_NAME);
+
+ if (!$integrationObject->getIntegrationSettings()->getIsPublished()) {
+ $io->note('Pipedrive integration id disabled.');
+
+ return;
+ }
+
+ $this->em = $container->get('doctrine.orm.default_entity_manager');
+ $query = $this->em->createQuery('SELECT d FROM MauticPlugin\MauticCrmBundle\Entity\PipedriveDeletion d WHERE d.deletedDate < :olderThan');
+ $query->setParameter('olderThan', new \DateTime('-1 minute'));
+ $deletions = $query->getResult();
+
+ $deleted = 0;
+
+ /** @var PipedriveDeletion $deletion */
+ foreach ($deletions as $deletion) {
+ $integrationEntity = null;
+ $type = $deletion->getObjectType();
+
+ if ('lead' === $type) {
+ $integrationEntity = $this->getLeadIntegrationEntity(['integrationEntityId' => $deletion->getIntegrationEntityId()]);
+ } elseif ('company' === $type) {
+ $integrationEntity = $this->getCompanyIntegrationEntity(['integrationEntityId' => $deletion->getIntegrationEntityId()]);
+ }
+
+ if (!$integrationEntity) {
+ $io->note('Integration entity not found, skipping.');
+ continue;
+ }
+
+ $entityClass = 'company' === $type ? Company::class : Lead::class;
+ /** @var Company|Lead $entity */
+ $entity = $this->em->getRepository($entityClass)->findOneById($integrationEntity->getInternalEntityId());
+
+ if (!$entity) {
+ $name = 'company' === $type ? 'Company' : 'Lead';
+ $io->note($name.' doesn\'t exist.');
+ continue;
+ }
+
+ // prevent listeners from exporting
+ $entity->setEventData('pipedrive.webhook', 1);
+ /** @var ModelFactory $modelFactory */
+ $modelFactory = $container->get('mautic.model.factory');
+ /** @var CompanyModel|LeadModel $model */
+ $modelType = $type;
+
+ if ('company' === $modelType) {
+ $modelType = 'lead.company';
+ }
+
+ $model = $modelFactory->getModel($modelType);
+ $model->deleteEntity($entity);
+
+ if (!empty($entity->deletedId)) {
+ $this->em->remove($integrationEntity);
+ $this->em->remove($deletion);
+ ++$deleted;
+ }
+ }
+
+ $io->success('Deleted '.$deleted.' items. Execution time: '.number_format(microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'], 3));
+ }
+
+ /**
+ * @return IntegrationEntity|object|null
+ */
+ protected function getLeadIntegrationEntity(array $criteria = [])
+ {
+ $criteria['integrationEntity'] = self::PERSON_ENTITY_TYPE;
+ $criteria['internalEntity'] = self::LEAD_ENTITY_TYPE;
+
+ return $this->getIntegrationEntity($criteria);
+ }
+
+ /**
+ * @return IntegrationEntity|object|null
+ */
+ protected function getCompanyIntegrationEntity(array $criteria = [])
+ {
+ $criteria['integrationEntity'] = self::ORGANIZATION_ENTITY_TYPE;
+ $criteria['internalEntity'] = self::COMPANY_ENTITY_TYPE;
+
+ return $this->getIntegrationEntity($criteria);
+ }
+
+ /**
+ * @return IntegrationEntity|object|null
+ */
+ private function getIntegrationEntity(array $criteria = [])
+ {
+ $criteria['integration'] = PipedriveIntegration::INTEGRATION_NAME;
+
+ return $this->em->getRepository(IntegrationEntity::class)->findOneBy($criteria);
+ }
+}
diff --git a/plugins/MauticCrmBundle/Config/config.php b/plugins/MauticCrmBundle/Config/config.php
index 3657fb26a10..d493461f225 100644
--- a/plugins/MauticCrmBundle/Config/config.php
+++ b/plugins/MauticCrmBundle/Config/config.php
@@ -12,7 +12,7 @@
return [
'name' => 'CRM',
'description' => 'Enables integration with Mautic supported CRMs.',
- 'version' => '1.0',
+ 'version' => '1.1',
'author' => 'Mautic',
'routes' => [
'public' => [
@@ -265,6 +265,7 @@
'doctrine.orm.entity_manager',
'mautic.lead.model.lead',
'mautic.lead.model.company',
+ 'mautic.lead.merger',
],
],
'mautic_integration.pipedrive.export.company' => [
diff --git a/plugins/MauticCrmBundle/Controller/PipedriveController.php b/plugins/MauticCrmBundle/Controller/PipedriveController.php
index 33b7e1cd114..8230597b913 100644
--- a/plugins/MauticCrmBundle/Controller/PipedriveController.php
+++ b/plugins/MauticCrmBundle/Controller/PipedriveController.php
@@ -31,10 +31,12 @@ class PipedriveController extends CommonController
const LEAD_ADDED_EVENT = 'added.person';
const LEAD_UPDATE_EVENT = 'updated.person';
const LEAD_DELETE_EVENT = 'deleted.person';
+ const LEAD_MERGED_EVENT = 'merged.person';
const COMPANY_ADD_EVENT = 'added.organization';
const COMPANY_UPDATE_EVENT = 'updated.organization';
const COMPANY_DELETE_EVENT = 'deleted.organization';
+ const COMPANY_MERGED_EVENT = 'merged.organization';
const USER_ADD_EVENT = 'added.user';
const USER_UPDATE_EVENT = 'updated.user';
@@ -73,6 +75,10 @@ public function webhookAction(Request $request)
$leadImport = $this->getLeadImport($pipedriveIntegration);
$leadImport->delete($params['previous']);
break;
+ case self::LEAD_MERGED_EVENT:
+ $leadImport = $this->getLeadImport($pipedriveIntegration);
+ $leadImport->merge($data, $data['merge_what_id']);
+ break;
case self::COMPANY_UPDATE_EVENT:
$companyImport = $this->getCompanyImport($pipedriveIntegration);
$companyImport->update($data);
@@ -81,6 +87,10 @@ public function webhookAction(Request $request)
$companyImport = $this->getCompanyImport($pipedriveIntegration);
$companyImport->delete($params['previous']);
break;
+ case self::COMPANY_MERGED_EVENT:
+ $companyImport = $this->getCompanyImport($pipedriveIntegration);
+ $companyImport->merge($data, $data['merge_what_id']);
+ break;
case self::USER_UPDATE_EVENT:
$ownerImport = $this->getOwnerImport($pipedriveIntegration);
$ownerImport->create($data[0]);
diff --git a/plugins/MauticCrmBundle/Entity/PipedriveDeletion.php b/plugins/MauticCrmBundle/Entity/PipedriveDeletion.php
new file mode 100644
index 00000000000..21a89db07f7
--- /dev/null
+++ b/plugins/MauticCrmBundle/Entity/PipedriveDeletion.php
@@ -0,0 +1,110 @@
+setTable('plugin_crm_pipedrive_deletions');
+
+ $builder->addId();
+ $builder->addNamedField('objectType', 'string', 'object_type');
+ $builder->addNamedField('integrationEntityId', 'integer', 'integration_entity_id');
+ $builder->addNamedField('deletedDate', 'datetime', 'deleted_date');
+
+ $builder->addIndex(['deleted_date'], 'deleted_date');
+ }
+
+ /**
+ * @return mixed
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * @return string
+ */
+ public function getObjectType()
+ {
+ return $this->objectType;
+ }
+
+ /**
+ * @param string $objectType
+ *
+ * @return self
+ */
+ public function setObjectType($objectType)
+ {
+ $this->objectType = $objectType;
+
+ return $this;
+ }
+
+ /**
+ * @return \DateTime
+ */
+ public function getDeletedDate()
+ {
+ $date = new DateTime();
+ return $date->setTimestamp($this->deletedDate);
+ }
+
+ /**
+ * @return self
+ */
+ public function setDeletedDate(DateTime $deletedDate)
+ {
+ $this->deletedDate = $deletedDate->getTimestamp();
+
+ return $this;
+ }
+
+ /**
+ * @return int
+ */
+ public function getIntegrationEntityId()
+ {
+ return $this->integrationEntityId;
+ }
+
+ /**
+ * @param int $integrationEntityId
+ *
+ * @return self
+ */
+ public function setIntegrationEntityId($integrationEntityId)
+ {
+ $this->integrationEntityId = $integrationEntityId;
+
+ return $this;
+ }
+}
diff --git a/plugins/MauticCrmBundle/EventListener/CompanySubscriber.php b/plugins/MauticCrmBundle/EventListener/CompanySubscriber.php
index 38571d8cec6..50edf33152a 100644
--- a/plugins/MauticCrmBundle/EventListener/CompanySubscriber.php
+++ b/plugins/MauticCrmBundle/EventListener/CompanySubscriber.php
@@ -42,8 +42,9 @@ public function __construct(IntegrationHelper $integrationHelper, CompanyExport
public static function getSubscribedEvents()
{
return [
- LeadEvents::COMPANY_POST_SAVE => ['onCompanyPostSave', 0],
- LeadEvents::COMPANY_PRE_DELETE => ['onCompanyPreDelete', 10],
+ LeadEvents::COMPANY_POST_SAVE => ['onCompanyPostSave', 0],
+ LeadEvents::COMPANY_PRE_DELETE => ['onCompanyPreDelete', 10],
+ LeadEvents::COMPANY_POST_MERGE => ['onCompanyPostMerge', 255],
];
}
@@ -60,11 +61,18 @@ public function onCompanyPostSave(Events\CompanyEvent $event)
/** @var PipedriveIntegration $integrationObject */
$integrationObject = $this->integrationHelper->getIntegrationObject(PipedriveIntegration::INTEGRATION_NAME);
- if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive()) {
+
+ if (false === $integrationObject) {
return;
}
$this->companyExport->setIntegration($integrationObject);
+ $operation = $this->companyExport->getOperation($company);
+
+ if (!$integrationObject->shouldImportDataToPipedrive($operation)) {
+ return;
+ }
+
$this->companyExport->pushCompany($company);
}
@@ -81,11 +89,31 @@ public function onCompanyPreDelete(Events\CompanyEvent $event)
/** @var PipedriveIntegration $integrationObject */
$integrationObject = $this->integrationHelper->getIntegrationObject(PipedriveIntegration::INTEGRATION_NAME);
- if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive()) {
+ if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive('update')) {
return;
}
$this->companyExport->setIntegration($integrationObject);
$this->companyExport->delete($company);
}
+
+ public function OnCompanyPostMerge(Events\CompanyMergeEvent $event)
+ {
+ $company = $event->getVictor();
+
+ if ($company->getEventData('pipedrive.webhook')) {
+ return;
+ }
+
+ $otherCompany = $event->getLoser();
+
+ /** @var PipedriveIntegration $integrationObject */
+ $integrationObject = $this->integrationHelper->getIntegrationObject(PipedriveIntegration::INTEGRATION_NAME);
+ if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive()) {
+ return;
+ }
+
+ $this->companyExport->setIntegration($integrationObject);
+ $this->companyExport->merge($company, $otherCompany);
+ }
}
diff --git a/plugins/MauticCrmBundle/EventListener/LeadSubscriber.php b/plugins/MauticCrmBundle/EventListener/LeadSubscriber.php
index 4f02c29dbad..8721e1c652c 100644
--- a/plugins/MauticCrmBundle/EventListener/LeadSubscriber.php
+++ b/plugins/MauticCrmBundle/EventListener/LeadSubscriber.php
@@ -42,9 +42,10 @@ public function __construct(IntegrationHelper $integrationHelper, LeadExport $le
public static function getSubscribedEvents()
{
return [
- LeadEvents::LEAD_POST_SAVE => ['onLeadPostSave', 0],
- LeadEvents::LEAD_PRE_DELETE => ['onLeadPostDelete', 255],
- LeadEvents::LEAD_COMPANY_CHANGE => ['onLeadCompanyChange', 0],
+ LeadEvents::LEAD_POST_SAVE => ['onLeadPostSave', 0],
+ LeadEvents::LEAD_PRE_DELETE => ['onLeadPostDelete', 255],
+ LeadEvents::LEAD_COMPANY_CHANGE => ['onLeadCompanyChange', 0],
+ LeadEvents::LEAD_POST_MERGE => ['onLeadPostMerge', 255],
];
}
@@ -61,10 +62,11 @@ public function onLeadPostSave(Events\LeadEvent $event)
}
/** @var PipedriveIntegration $integrationObject */
$integrationObject = $this->integrationHelper->getIntegrationObject(PipedriveIntegration::INTEGRATION_NAME);
- if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive()) {
+ $this->leadExport->setIntegration($integrationObject);
+ $operation = $this->leadExport->getOperation($lead);
+ if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive($operation)) {
return;
}
- $this->leadExport->setIntegration($integrationObject);
$changes = $lead->getChanges(true);
if (!empty($changes['dateIdentified'])) {
@@ -84,13 +86,33 @@ public function onLeadPostDelete(Events\LeadEvent $event)
/** @var PipedriveIntegration $integrationObject */
$integrationObject = $this->integrationHelper->getIntegrationObject(PipedriveIntegration::INTEGRATION_NAME);
- if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive()) {
+ if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive('update')) {
return;
}
$this->leadExport->setIntegration($integrationObject);
$this->leadExport->delete($lead);
}
+ public function OnLeadPostMerge(Events\LeadMergeEvent $event)
+ {
+ $lead = $event->getVictor();
+
+ if ($lead->getEventData('pipedrive.webhook')) {
+ return;
+ }
+
+ $otherLead = $event->getLoser();
+
+ /** @var PipedriveIntegration $integrationObject */
+ $integrationObject = $this->integrationHelper->getIntegrationObject(PipedriveIntegration::INTEGRATION_NAME);
+ if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive()) {
+ return;
+ }
+
+ $this->leadExport->setIntegration($integrationObject);
+ $this->leadExport->merge($lead, $otherLead);
+ }
+
public function onLeadCompanyChange(Events\LeadChangeCompanyEvent $event)
{
$lead = $event->getLead();
@@ -101,7 +123,7 @@ public function onLeadCompanyChange(Events\LeadChangeCompanyEvent $event)
/** @var PipedriveIntegration $integrationObject */
$integrationObject = $this->integrationHelper->getIntegrationObject(PipedriveIntegration::INTEGRATION_NAME);
- if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive()) {
+ if (false === $integrationObject || !$integrationObject->shouldImportDataToPipedrive('update')) {
return;
}
$this->leadExport->setIntegration($integrationObject);
diff --git a/plugins/MauticCrmBundle/Integration/Pipedrive/Export/CompanyExport.php b/plugins/MauticCrmBundle/Integration/Pipedrive/Export/CompanyExport.php
index cdfa8773993..702695e7f22 100644
--- a/plugins/MauticCrmBundle/Integration/Pipedrive/Export/CompanyExport.php
+++ b/plugins/MauticCrmBundle/Integration/Pipedrive/Export/CompanyExport.php
@@ -120,6 +120,43 @@ public function delete(Company $company)
return false;
}
+ /**
+ * @return bool
+ */
+ public function merge(Company $company, Company $otherCompany, array $mappedData = [])
+ {
+ $integrationEntity = $this->getCompanyIntegrationEntity(['internalEntityId' => $company->getId()]);
+ $created = false;
+
+ if (!$integrationEntity) {
+ $created = $this->create($company, $mappedData);
+ $integrationEntity = $this->getCompanyIntegrationEntity(['internalEntityId' => $company->getId()]);
+ }
+
+ if (!$integrationEntity) {
+ return false;
+ }
+
+ $otherIntegrationEntity = $this->getCompanyIntegrationEntity(['internalEntityId' => $otherCompany->getId()]);
+
+ if (!$otherIntegrationEntity) {
+ return $created ? true : $this->update($integrationEntity, $mappedData);
+ }
+
+ try {
+ $this->getIntegration()->getApiHelper()->mergeCompany($integrationEntity->getIntegrationEntityId(), $otherIntegrationEntity->getIntegrationEntityId());
+
+ $this->em->remove($otherIntegrationEntity);
+ $this->em->flush();
+
+ return true;
+ } catch (\Exception $e) {
+ $this->getIntegration()->logIntegrationError($e);
+ }
+
+ return false;
+ }
+
/**
* @return array
*/
@@ -169,4 +206,15 @@ private function getCompanyIntegrationOwnerId(Company $company)
return $pipedriveOwner->getOwnerId();
}
+
+ public function getOperation(Company $company)
+ {
+ if (!$this->getIntegration()->isCompanySupportEnabled()) {
+ return false; //feature disabled
+ }
+
+ $integrationEntity = $this->getCompanyIntegrationEntity(['internalEntityId' => $company->getId()]);
+
+ return ($integrationEntity) ? 'update' : 'create';
+ }
}
diff --git a/plugins/MauticCrmBundle/Integration/Pipedrive/Export/LeadExport.php b/plugins/MauticCrmBundle/Integration/Pipedrive/Export/LeadExport.php
index b0cf0f9f95d..b361a224381 100644
--- a/plugins/MauticCrmBundle/Integration/Pipedrive/Export/LeadExport.php
+++ b/plugins/MauticCrmBundle/Integration/Pipedrive/Export/LeadExport.php
@@ -127,6 +127,44 @@ public function delete(Lead $lead)
return false;
}
+ /**
+ * @return bool
+ */
+ public function merge(Lead $lead, Lead $otherLead)
+ {
+ $integrationEntity = $this->getLeadIntegrationEntity(['internalEntityId' => $lead->getId()]);
+
+ $created = false;
+
+ if (!$integrationEntity) {
+ $created = $this->create($lead);
+ $integrationEntity = $this->getLeadIntegrationEntity(['internalEntityId' => $lead->getId()]);
+ }
+
+ if (!$integrationEntity) {
+ return false;
+ }
+
+ $otherIntegrationEntity = $this->getLeadIntegrationEntity(['internalEntityId' => $otherLead->getId()]);
+
+ if (!$otherIntegrationEntity) {
+ return $created ? true : $this->update($lead);
+ }
+
+ try {
+ $this->getIntegration()->getApiHelper()->mergeLead($integrationEntity->getIntegrationEntityId(), $otherIntegrationEntity->getIntegrationEntityId());
+
+ $this->em->remove($otherIntegrationEntity);
+ $this->em->flush();
+
+ return true;
+ } catch (\Exception $e) {
+ $this->getIntegration()->logIntegrationError($e);
+ }
+
+ return false;
+ }
+
/**
* @return mixed
*/
@@ -213,4 +251,19 @@ private function convertMauticData($data)
return $data;
}
+
+ public function getOperation(Lead $lead)
+ {
+ // stop for anynomouse
+ if ($lead->isAnonymous() || empty($lead->getEmail())) {
+ return false;
+ }
+
+ $leadId = $lead->getId();
+ /** @var IntegrationEntity $integrationEntity */
+ $integrationEntity = $this->getLeadIntegrationEntity(['internalEntityId' => $leadId]);
+ $personData = $this->getIntegration()->getApiHelper()->findByEmail($lead->getEmail());
+
+ return ($integrationEntity) && !empty($personData) ? 'update' : 'create';
+ }
}
diff --git a/plugins/MauticCrmBundle/Integration/Pipedrive/Import/CompanyImport.php b/plugins/MauticCrmBundle/Integration/Pipedrive/Import/CompanyImport.php
index 0a3d296e528..6b2fab5c5ba 100644
--- a/plugins/MauticCrmBundle/Integration/Pipedrive/Import/CompanyImport.php
+++ b/plugins/MauticCrmBundle/Integration/Pipedrive/Import/CompanyImport.php
@@ -6,6 +6,7 @@
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Helper\IdentifyCompanyHelper;
use Mautic\LeadBundle\Model\CompanyModel;
+use MauticPlugin\MauticCrmBundle\Entity\PipedriveDeletion;
use Symfony\Component\HttpFoundation\Response;
class CompanyImport extends AbstractImport
@@ -131,20 +132,79 @@ public function delete(array $data = [])
throw new \Exception('Company doesn\'t have integration', Response::HTTP_NOT_FOUND);
}
- /** @var Company $company */
- $company = $this->em->getRepository(Company::class)->findOneById($integrationEntity->getInternalEntityId());
+ $integrationSettings = $this->getIntegration()->getIntegrationSettings();
+ $deleteViaCron = ($integrationSettings->getIsPublished() && !empty($integrationSettings->getFeatureSettings()['cronDelete']));
+
+ if ($deleteViaCron) {
+ $deletion = new PipedriveDeletion();
+ $deletion
+ ->setObjectType('company')
+ ->setDeletedDate(new \DateTime())
+ ->setIntegrationEntityId($integrationEntity->getId());
+
+ $this->em->persist($deletion);
+ $this->em->flush();
+ } else {
+ /** @var Company $company */
+ $company = $this->em->getRepository(Company::class)->findOneById($integrationEntity->getInternalEntityId());
+
+ if (!$company) {
+ throw new \Exception('Company doesn\'t exist', Response::HTTP_NOT_FOUND);
+ }
+
+ // prevent listeners from exporting
+ $company->setEventData('pipedrive.webhook', 1);
+ $this->companyModel->deleteEntity($company);
+
+ if (!empty($company->deletedId)) {
+ $this->em->remove($integrationEntity);
+ }
+ }
+ }
- if (!$company) {
- throw new \Exception('Company doesn\'t exists', Response::HTTP_NOT_FOUND);
+ /**
+ * @return bool
+ *
+ * @throws \Doctrine\ORM\OptimisticLockException
+ * @throws \Exception
+ */
+ public function merge(array $data = [], $otherId = null)
+ {
+ if (!$this->getIntegration()->isCompanySupportEnabled()) {
+ return false; //feature disabled
}
+ $otherIntegrationEntity = $this->getCompanyIntegrationEntity(['integrationEntityId' => $otherId]);
+
+ if (!$otherIntegrationEntity) {
+ // Only destination entity exists, so handle it as an update.
+ return $this->update($data);
+ }
+
+ $integrationEntity = $this->getCompanyIntegrationEntity(['integrationEntityId' => $data['id']]);
+
+ if (!$integrationEntity) {
+ // Destination entity doesn't yet exist, so create it first.
+ $this->create($data);
+ $integrationEntity = $this->getCompanyIntegrationEntity(['integrationEntityId' => $data['id']]);
+ }
+
+ /** @var Company $company */
+ $company = $this->companyModel->getEntity($integrationEntity->getInternalEntityId());
+ /** @var Company $otherCompany */
+ $otherCompany = $this->companyModel->getEntity($otherIntegrationEntity->getInternalEntityId());
+
// prevent listeners from exporting
$company->setEventData('pipedrive.webhook', 1);
- $this->companyModel->deleteEntity($company);
- if (!empty($company->deletedId)) {
- $this->em->remove($integrationEntity);
- }
+ $this->companyModel->companyMerge($company, $otherCompany);
+ $this->em->remove($otherIntegrationEntity);
+
+ $integrationEntity->setLastSyncDate(new \DateTime());
+ $this->em->persist($integrationEntity);
+ $this->em->flush();
+
+ return true;
}
/**
diff --git a/plugins/MauticCrmBundle/Integration/Pipedrive/Import/LeadImport.php b/plugins/MauticCrmBundle/Integration/Pipedrive/Import/LeadImport.php
index fcec8a16791..c01df1d5529 100644
--- a/plugins/MauticCrmBundle/Integration/Pipedrive/Import/LeadImport.php
+++ b/plugins/MauticCrmBundle/Integration/Pipedrive/Import/LeadImport.php
@@ -3,10 +3,13 @@
namespace MauticPlugin\MauticCrmBundle\Integration\Pipedrive\Import;
use Doctrine\ORM\EntityManager;
+use Mautic\LeadBundle\Deduplicate\ContactMerger;
+use Mautic\LeadBundle\Deduplicate\Exception\SameContactException;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Model\CompanyModel;
use Mautic\LeadBundle\Model\LeadModel;
+use MauticPlugin\MauticCrmBundle\Entity\PipedriveDeletion;
use Symfony\Component\HttpFoundation\Response;
class LeadImport extends AbstractImport
@@ -21,15 +24,21 @@ class LeadImport extends AbstractImport
*/
private $companyModel;
+ /**
+ * @var ContactMerger
+ */
+ private $contactMerger;
+
/**
* LeadImport constructor.
*/
- public function __construct(EntityManager $em, LeadModel $leadModel, CompanyModel $companyModel)
+ public function __construct(EntityManager $em, LeadModel $leadModel, CompanyModel $companyModel, ContactMerger $contactMerger)
{
parent::__construct($em);
- $this->leadModel = $leadModel;
- $this->companyModel = $companyModel;
+ $this->leadModel = $leadModel;
+ $this->companyModel = $companyModel;
+ $this->contactMerger = $contactMerger;
}
/**
@@ -148,21 +157,82 @@ public function delete(array $data = [])
throw new \Exception('Lead doesn\'t have integration', Response::HTTP_NOT_FOUND);
}
- /** @var Lead $lead */
- $lead = $this->em->getRepository(Lead::class)->findOneById($integrationEntity->getInternalEntityId());
+ $integrationSettings = $this->getIntegration()->getIntegrationSettings();
+ $deleteViaCron = ($integrationSettings->getIsPublished() && !empty($integrationSettings->getFeatureSettings()['cronDelete']));
+
+ if ($deleteViaCron) {
+ $deletion = new PipedriveDeletion();
+ $deletion
+ ->setObjectType('lead')
+ ->setDeletedDate(new \DateTime())
+ ->setIntegrationEntityId($integrationEntity->getId());
+
+ $this->em->persist($deletion);
+ $this->em->flush();
+ } else {
+ /** @var Lead $lead */
+ $lead = $this->em->getRepository(Lead::class)->findOneById($integrationEntity->getInternalEntityId());
+
+ if (!$lead) {
+ throw new \Exception('Lead doesn\'t exists in Mautic', Response::HTTP_NOT_FOUND);
+ }
+
+ // prevent listeners from exporting
+ $lead->setEventData('pipedrive.webhook', 1);
+
+ $this->leadModel->deleteEntity($lead);
- if (!$lead) {
- throw new \Exception('Lead doesn\'t exists in Mautic', Response::HTTP_NOT_FOUND);
+ if (!empty($lead->deletedId)) {
+ $this->em->remove($integrationEntity);
+ }
}
+ }
+
+ /**
+ * @return bool
+ *
+ * @throws \Doctrine\ORM\ORMException
+ * @throws \Doctrine\ORM\OptimisticLockException
+ * @throws \Exception
+ */
+ public function merge(array $data = [], $otherId = null)
+ {
+ $otherIntegrationEntity = $this->getLeadIntegrationEntity(['integrationEntityId' => $otherId]);
+
+ if (!$otherIntegrationEntity) {
+ // Only destination entity exists, so handle it as an update.
+ return $this->update($data);
+ }
+
+ $integrationEntity = $this->getLeadIntegrationEntity(['integrationEntityId' => $data['id']]);
+
+ if (!$integrationEntity) {
+ // Destination entity doesn't yet exist, so create it first.
+ $this->create($data);
+ $integrationEntity = $this->getLeadIntegrationEntity(['integrationEntityId' => $data['id']]);
+ }
+
+ /** @var Lead $lead */
+ $lead = $this->leadModel->getEntity($integrationEntity->getInternalEntityId());
+ /** @var Lead $otherLead */
+ $otherLead = $this->leadModel->getEntity($otherIntegrationEntity->getInternalEntityId());
// prevent listeners from exporting
$lead->setEventData('pipedrive.webhook', 1);
- $this->leadModel->deleteEntity($lead);
-
- if (!empty($lead->deletedId)) {
- $this->em->remove($integrationEntity);
+ try {
+ $lead = $this->contactMerger->merge($lead, $otherLead);
+ $this->update($data);
+ $this->em->remove($otherIntegrationEntity);
+ } catch (SameContactException $exception) {
+ // Ignore
}
+
+ $integrationEntity->setLastSyncDate(new \DateTime());
+ $this->em->persist($integrationEntity);
+ $this->em->flush();
+
+ return true;
}
/**
diff --git a/plugins/MauticCrmBundle/Integration/PipedriveIntegration.php b/plugins/MauticCrmBundle/Integration/PipedriveIntegration.php
index 1e6d5e2a71d..8b086408dc1 100644
--- a/plugins/MauticCrmBundle/Integration/PipedriveIntegration.php
+++ b/plugins/MauticCrmBundle/Integration/PipedriveIntegration.php
@@ -310,7 +310,8 @@ public function appendToForm(&$builder, $data, $formArea)
ChoiceType::class,
[
'choices' => [
- 'mautic.pipedrive.add.edit.contact.import.enabled' => 'enabled',
+ 'mautic.pipedrive.add.edit.contact.import.create' => 'create',
+ 'mautic.pipedrive.add.edit.contact.import.update' => 'update',
],
'expanded' => true,
'multiple' => true,
@@ -320,6 +321,22 @@ public function appendToForm(&$builder, $data, $formArea)
'required' => false,
]
);
+
+ $builder->add(
+ 'cronDelete',
+ ChoiceType::class,
+ [
+ 'choices' => [
+ 'mautic.pipedrive.add.edit.contact.cron_delete.enabled' => 'enabled',
+ ],
+ 'expanded' => true,
+ 'multiple' => true,
+ 'label' => 'mautic.pipedrive.add.edit.contact.cron_delete',
+ 'label_attr' => ['class' => ''],
+ 'placeholder' => false,
+ 'required' => false,
+ ]
+ );
}
}
@@ -375,13 +392,12 @@ public function isCompanySupportEnabled()
/**
* @return bool
*/
- public function shouldImportDataToPipedrive()
+ public function shouldImportDataToPipedrive($operation = 'create')
{
- if (!$this->getIntegrationSettings()->getIsPublished() || empty($this->getIntegrationSettings()->getFeatureSettings()['import'])) {
- return false;
- }
+ $settings = $this->getIntegrationSettings();
+ $features = $settings->getFeatureSettings();
- return true;
+ return $settings->getIsPublished() && !empty($features['import']) && in_array($operation, $features['import'], true);
}
/**
diff --git a/plugins/MauticCrmBundle/MauticCrmBundle.php b/plugins/MauticCrmBundle/MauticCrmBundle.php
index 20f43b1a4ce..2ea7c2f80f1 100644
--- a/plugins/MauticCrmBundle/MauticCrmBundle.php
+++ b/plugins/MauticCrmBundle/MauticCrmBundle.php
@@ -11,6 +11,7 @@
namespace MauticPlugin\MauticCrmBundle;
+use Doctrine\DBAL\Schema\Schema;
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Factory\MauticFactory;
use Mautic\PluginBundle\Bundle\PluginBundleBase;
@@ -32,13 +33,18 @@ public static function onPluginInstall(Plugin $plugin, MauticFactory $factory, $
}
}
+ public static function onPluginUpdate(Plugin $plugin, MauticFactory $factory, $metadata = null, Schema $installedSchema = null)
+ {
+ self::updatePluginSchema(self::getMetadata($factory->getEntityManager(), true), $installedSchema, $factory);
+ }
+
/**
* Fix: plugin installer doesn't find metadata entities for the plugin
* PluginBundle/Controller/PluginController:410.
*
* @return array|null
*/
- private static function getMetadata(EntityManager $em)
+ private static function getMetadata(EntityManager $em, $update = false)
{
$allMetadata = $em->getMetadataFactory()->getAllMetadata();
$currentSchema = $em->getConnection()->getSchemaManager()->createSchema();
@@ -53,7 +59,7 @@ private static function getMetadata(EntityManager $em)
$table = $meta->getTableName();
- if ($currentSchema->hasTable($table)) {
+ if (!$update && $currentSchema->hasTable($table)) {
continue;
}
diff --git a/plugins/MauticCrmBundle/Translations/en_US/messages.ini b/plugins/MauticCrmBundle/Translations/en_US/messages.ini
index 17f4ec4433d..eedc37dd273 100644
--- a/plugins/MauticCrmBundle/Translations/en_US/messages.ini
+++ b/plugins/MauticCrmBundle/Translations/en_US/messages.ini
@@ -78,7 +78,10 @@ mautic.pipedrive.webhook_user="Webhook user"
mautic.pipedrive.webhook_password="Webhook password"
mautic.pipedrive.webhook_callback="Pipedrive webhook URL: "
mautic.pipedrive.add.edit.contact.import="Import contacts to Pipedrive immediately on change in Mautic"
-mautic.pipedrive.add.edit.contact.import.enabled="Enabled"
+mautic.pipedrive.add.edit.contact.import.create="Create immediately on create in Mautic"
+mautic.pipedrive.add.edit.contact.import.update="Update immediately on update in Mautic"
+mautic.pipedrive.add.edit.contact.cron_delete="Handle deletions from Pipedrive via cron"
+mautic.pipedrive.add.edit.contact.cron_delete.enabled="Enabled"
mautic.crm.form.objects_to_pull_from="Choose what %crm% Objects to pull data from"
mautic.zoho.object.lead="Leads"
mautic.zoho.object.contact="Contacts"
@@ -97,4 +100,4 @@ mautic.plugin.integration.campaigns.connectwise.activity.type="Activity Type"
mautic.plugin.integration.campaigns.connectwise.members="Assign to member"
mautic.plugin.config.push.activities="Push contact activities"
mautic.plugin.config.integration.restart="Restart integration"
-mautic.plugin.config.integration.restarted="%integration% restarted"
\ No newline at end of file
+mautic.plugin.config.integration.restarted="%integration% restarted"