Spaces:
No application file
No application file
| namespace Mautic\LeadBundle\Model; | |
| use Doctrine\Common\Collections\ArrayCollection; | |
| use Doctrine\ORM\PersistentCollection; | |
| use Mautic\CoreBundle\Model\MauticModelInterface; | |
| use Mautic\LeadBundle\Entity\DoNotContact as DNC; | |
| use Mautic\LeadBundle\Entity\DoNotContactRepository; | |
| use Mautic\LeadBundle\Entity\Lead; | |
| class DoNotContact implements MauticModelInterface | |
| { | |
| public function __construct( | |
| protected LeadModel $leadModel, | |
| protected DoNotContactRepository $dncRepo | |
| ) { | |
| } | |
| /** | |
| * Remove a Lead's DNC entry based on channel. | |
| * | |
| * @param int $contactId | |
| * @param string $channel | |
| * @param bool|true $persist | |
| * @param int|null $reason | |
| */ | |
| public function removeDncForContact($contactId, $channel, $persist = true, $reason = null): bool | |
| { | |
| $contact = $this->leadModel->getEntity($contactId); | |
| /** @var DNC $dnc */ | |
| foreach ($contact->getDoNotContact() as $dnc) { | |
| if ($dnc->getChannel() === $channel) { | |
| // Skip if reason doesn't match | |
| // Some integrations (Sugar CRM) can use both reasons (unsubscribed, bounced) | |
| if ($reason && $dnc->getReason() != $reason) { | |
| continue; | |
| } | |
| $contact->removeDoNotContactEntry($dnc); | |
| if ($persist) { | |
| $this->leadModel->saveEntity($contact); | |
| } | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Create a DNC entry for a lead. | |
| * | |
| * @param Lead|int|null $contactId | |
| * @param string|mixed[] $channel If an array with an ID, use the structure ['email' => 123] | |
| * @param string $comments | |
| * @param int $reason Must be a class constant from the DoNotContact class | |
| * @param bool $persist | |
| * @param bool $checkCurrentStatus | |
| * @param bool $allowUnsubscribeOverride | |
| * | |
| * @return bool|DNC If a DNC entry is added or updated, returns the DoNotContact object. If a DNC is already present | |
| * and has the specified reason, nothing is done and this returns false | |
| */ | |
| public function addDncForContact( | |
| $contactId, | |
| $channel, | |
| $reason = DNC::BOUNCED, | |
| $comments = '', | |
| $persist = true, | |
| $checkCurrentStatus = true, | |
| $allowUnsubscribeOverride = false | |
| ) { | |
| $dnc = null; | |
| $contact = $this->leadModel->getEntity($contactId); | |
| if (null === $contact) { | |
| // Contact not found, nothing to do | |
| return false; | |
| } | |
| // if !$checkCurrentStatus, assume is contactable due to already being validated | |
| $isContactable = ($checkCurrentStatus) ? $this->isContactable($contact, $channel) : DNC::IS_CONTACTABLE; | |
| /** @var ArrayCollection<int, DNC> $dncEntities */ | |
| $dncEntities = new ArrayCollection(); | |
| // If they don't have a DNC entry yet | |
| if (DNC::IS_CONTACTABLE === $isContactable) { | |
| $dnc = $dncEntities[] = $this->createDncRecord($contact, $channel, $reason, $comments); | |
| } elseif ($isContactable !== $reason) { | |
| // Or if the given reason is different than the stated reason | |
| $dncEntities = $contact->getDoNotContact(); | |
| foreach ($dncEntities as $dnc) { | |
| // Only update if the contact did not unsubscribe themselves or if the code forces it | |
| $allowOverride = ($allowUnsubscribeOverride || DNC::UNSUBSCRIBED !== $dnc->getReason()); | |
| // Only update if the contact did not unsubscribe themselves | |
| if ($allowOverride && $dnc->getChannel() === $channel) { | |
| // Note the outdated entry for listeners | |
| $contact->removeDoNotContactEntry($dnc); | |
| // Update the entry with the latest | |
| $this->updateDncRecord($dnc, $contact, $channel, $reason, $comments); | |
| break; | |
| } | |
| } | |
| } | |
| if (null !== $dnc && $persist) { | |
| // Use model saveEntity to trigger events for DNC change | |
| $this->leadModel->saveEntity($contact); | |
| $this->dncRepo->detachEntities($dncEntities->toArray()); | |
| // need to force a collection to load items in the next call. | |
| $collection = $contact->getDoNotContact(); | |
| if ($collection instanceof PersistentCollection) { | |
| $collection->setInitialized(false); | |
| } | |
| } | |
| return $dnc; | |
| } | |
| /** | |
| * @param string $channel | |
| * | |
| * @return int | |
| * | |
| * @see DNC This method can return boolean false, so be | |
| * sure to always compare the return value against | |
| * the class constants of DoNotContact | |
| */ | |
| public function isContactable(Lead $contact, $channel) | |
| { | |
| if (is_array($channel)) { | |
| $channel = key($channel); | |
| } | |
| $dncEntries = $this->dncRepo->getEntriesByLeadAndChannel($contact, $channel); | |
| // If the lead has no entries in the DNC table, we're good to go | |
| if (empty($dncEntries)) { | |
| return DNC::IS_CONTACTABLE; | |
| } | |
| foreach ($dncEntries as $dnc) { | |
| if (DNC::IS_CONTACTABLE !== $dnc->getReason()) { | |
| return $dnc->getReason(); | |
| } | |
| } | |
| return DNC::IS_CONTACTABLE; | |
| } | |
| public function createDncRecord(Lead $contact, $channel, $reason, $comments = null): DNC | |
| { | |
| $dnc = new DNC(); | |
| if (is_array($channel)) { | |
| $channelId = reset($channel); | |
| $channel = key($channel); | |
| $dnc->setChannelId((int) $channelId); | |
| } | |
| $dnc->setChannel($channel); | |
| $dnc->setReason($reason); | |
| $dnc->setLead($contact); | |
| $dnc->setDateAdded(new \DateTime()); | |
| $dnc->setComments($comments); | |
| $contact->addDoNotContactEntry($dnc); | |
| return $dnc; | |
| } | |
| public function updateDncRecord(DNC $dnc, Lead $contact, $channel, $reason, $comments = null): void | |
| { | |
| // Update the DNC entry | |
| $dnc->setChannel($channel); | |
| $dnc->setReason($reason); | |
| $dnc->setLead($contact); | |
| $dnc->setDateAdded(new \DateTime()); | |
| $dnc->setComments($comments); | |
| // Re-add the entry to the lead | |
| $contact->addDoNotContactEntry($dnc); | |
| } | |
| /** | |
| * @return DoNotContactRepository | |
| */ | |
| public function getDncRepo() | |
| { | |
| return $this->dncRepo; | |
| } | |
| } | |