![]() Server : Apache System : Linux server2.corals.io 4.18.0-348.2.1.el8_5.x86_64 #1 SMP Mon Nov 15 09:17:08 EST 2021 x86_64 User : corals ( 1002) PHP Version : 7.4.33 Disable Function : exec,passthru,shell_exec,system Directory : /home/corals/mautic.corals.io/app/bundles/LeadBundle/Entity/ |
<?php namespace Mautic\LeadBundle\Entity; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Criteria; use Doctrine\ORM\Mapping as ORM; use Mautic\ApiBundle\Serializer\Driver\ApiMetadataDriver; use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder; use Mautic\CoreBundle\Entity\FormEntity; use Mautic\CoreBundle\Entity\IpAddress; use Mautic\LeadBundle\DataObject\LeadManipulator; use Mautic\LeadBundle\Form\Validator\Constraints\UniqueCustomField; use Mautic\LeadBundle\Model\FieldModel; use Mautic\NotificationBundle\Entity\PushID; use Mautic\PointBundle\Entity\Group; use Mautic\PointBundle\Entity\GroupContactScore; use Mautic\StageBundle\Entity\Stage; use Mautic\UserBundle\Entity\User; use Symfony\Component\Validator\Mapping\ClassMetadata; class Lead extends FormEntity implements CustomFieldEntityInterface, IdentifierFieldEntityInterface { use CustomFieldEntityTrait; public const FIELD_ALIAS = ''; public const POINTS_ADD = 'plus'; public const POINTS_SUBTRACT = 'minus'; public const POINTS_MULTIPLY = 'times'; public const POINTS_DIVIDE = 'divide'; public const POINTS_SET = 'set'; public const DEFAULT_ALIAS = 'l'; /** * Used to determine social identity. * * @var array */ private $availableSocialFields = []; /** * @var string */ private $id; private $title; private $firstname; private $lastname; private $company; private $position; private $email; private $phone; private $mobile; private $address1; private $address2; private $city; private $state; private $zipcode; /** * @var string|null */ private $timezone; private $country; /** * @var User|null */ private $owner; /** * @var int */ private $points = 0; /** * @var array */ private $pointChanges = []; /** * @var int|null */ private $updatedPoints; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\PointsChangeLog> */ private $pointsChangeLog; /** * @var null */ private $actualPoints; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\CompanyChangeLog> */ private $companyChangeLog; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\DoNotContact> */ private $doNotContact; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\CoreBundle\Entity\IpAddress> */ private $ipAddresses; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\NotificationBundle\Entity\PushID> */ private $pushIds; /** * @var ArrayCollection<int, LeadEventLog> */ private $eventLog; /** * @var \DateTimeInterface */ private $lastActive; /** * @var array */ private $internal = []; /** * @var array */ private $socialCache = []; /** * Used to populate trigger color. * * @var string */ private $color; /** * @var LeadManipulator */ private $manipulator; /** * @var bool */ private $newlyCreated = false; /** * @var \DateTimeInterface */ private $dateIdentified; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\LeadNote> */ private $notes; /** * @var string|null */ private $preferredProfileImage = 'gravatar'; /** * @var bool */ public $imported = false; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\Tag> */ private $tags; /** * @var Stage|null */ private $stage; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\StagesChangeLog> */ private $stageChangeLog; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\UtmTag> */ private $utmtags; /** * @var \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\FrequencyRule> */ private $frequencyRules; /** * @var ArrayCollection<int,GroupContactScore> */ private $groupScores; private $primaryCompany; /** * Used to determine order of preferred channels. * * @var array */ private $channelRules = []; public function __construct() { $this->ipAddresses = new ArrayCollection(); $this->pushIds = new ArrayCollection(); $this->eventLog = new ArrayCollection(); $this->doNotContact = new ArrayCollection(); $this->pointsChangeLog = new ArrayCollection(); $this->tags = new ArrayCollection(); $this->stageChangeLog = new ArrayCollection(); $this->frequencyRules = new ArrayCollection(); $this->companyChangeLog = new ArrayCollection(); $this->groupScores = new ArrayCollection(); } public static function loadMetadata(ORM\ClassMetadata $metadata): void { $builder = new ClassMetadataBuilder($metadata); $builder->setTable('leads') ->setCustomRepositoryClass(LeadRepository::class) ->addLifecycleEvent('checkDateIdentified', 'preUpdate') ->addLifecycleEvent('checkDateIdentified', 'prePersist') ->addLifecycleEvent('checkAttributionDate', 'preUpdate') ->addLifecycleEvent('checkAttributionDate', 'prePersist') ->addLifecycleEvent('checkDateAdded', 'prePersist') ->addIndex(['date_added'], 'lead_date_added') ->addIndex(['date_identified'], 'date_identified'); $builder->addBigIntIdField(); $builder->createManyToOne('owner', User::class) ->fetchLazy() ->addJoinColumn('owner_id', 'id', true, false, 'SET NULL') ->build(); $builder->createField('points', 'integer') ->build(); $builder->createOneToMany('pointsChangeLog', 'PointsChangeLog') ->orphanRemoval() ->setOrderBy(['dateAdded' => 'DESC']) ->mappedBy('lead') ->cascadeAll() ->fetchExtraLazy() ->build(); $builder->createOneToMany('companyChangeLog', 'CompanyChangeLog') ->orphanRemoval() ->setOrderBy(['dateAdded' => 'DESC']) ->mappedBy('lead') ->cascadeAll() ->fetchExtraLazy() ->build(); $builder->createOneToMany('doNotContact', DoNotContact::class) ->orphanRemoval() ->mappedBy('lead') ->cascadePersist() ->cascadeDetach() ->cascadeMerge() ->fetchExtraLazy() ->build(); $builder->createManyToMany('ipAddresses', IpAddress::class) ->setJoinTable('lead_ips_xref') ->addInverseJoinColumn('ip_id', 'id', true, false, 'CASCADE') ->addJoinColumn('lead_id', 'id', false, false, 'CASCADE') ->setIndexBy('ipAddress') ->cascadeDetach() ->cascadeMerge() ->cascadePersist() ->build(); $builder->createOneToMany('pushIds', PushID::class) ->orphanRemoval() ->mappedBy('lead') ->cascadeAll() ->fetchExtraLazy() ->build(); $builder->createOneToMany('eventLog', LeadEventLog::class) ->mappedBy('lead') ->cascadePersist() ->cascadeMerge() ->cascadeDetach() ->fetchExtraLazy() ->build(); $builder->createField('lastActive', 'datetime') ->columnName('last_active') ->nullable() ->build(); $builder->createField('internal', 'array') ->nullable() ->build(); $builder->createField('socialCache', 'array') ->columnName('social_cache') ->nullable() ->build(); $builder->createField('dateIdentified', 'datetime') ->columnName('date_identified') ->nullable() ->build(); $builder->createOneToMany('notes', 'LeadNote') ->orphanRemoval() ->setOrderBy(['dateAdded' => 'DESC']) ->mappedBy('lead') ->cascadeDetach() ->cascadeMerge() ->fetchExtraLazy() ->build(); $builder->createField('preferredProfileImage', 'string') ->columnName('preferred_profile_image') ->nullable() ->build(); $builder->createManyToMany('tags', Tag::class) ->setJoinTable('lead_tags_xref') ->addInverseJoinColumn('tag_id', 'id', false) ->addJoinColumn('lead_id', 'id', false, false, 'CASCADE') ->setOrderBy(['tag' => 'ASC']) ->setIndexBy('tag') ->fetchLazy() ->cascadeMerge() ->cascadePersist() ->cascadeDetach() ->build(); $builder->createManyToOne('stage', Stage::class) ->cascadePersist() ->cascadeMerge() ->cascadeDetach() ->addJoinColumn('stage_id', 'id', true, false, 'SET NULL') ->build(); $builder->createOneToMany('stageChangeLog', 'StagesChangeLog') ->orphanRemoval() ->setOrderBy(['dateAdded' => 'DESC']) ->mappedBy('lead') ->cascadeAll() ->fetchExtraLazy() ->build(); $builder->createOneToMany('utmtags', UtmTag::class) ->orphanRemoval() ->mappedBy('lead') ->cascadeAll() ->fetchExtraLazy() ->build(); $builder->createOneToMany('frequencyRules', FrequencyRule::class) ->orphanRemoval() ->setIndexBy('channel') ->setOrderBy(['dateAdded' => 'DESC']) ->mappedBy('lead') ->cascadeAll() ->fetchExtraLazy() ->build(); $builder->createOneToMany('groupScores', GroupContactScore::class) ->mappedBy('contact') ->cascadeAll() ->fetchExtraLazy() ->build(); self::loadFixedFieldMetadata( $builder, [ 'title', 'firstname', 'lastname', 'company', 'position', 'email', 'phone', 'mobile', 'address1', 'address2', 'city', 'state', 'zipcode', 'timezone', 'country', ], FieldModel::$coreFields ); } /** * Prepares the metadata for API usage. */ public static function loadApiMetadata(ApiMetadataDriver $metadata): void { $metadata->setRoot('lead') ->setGroupPrefix('leadBasic') ->addListProperties( [ 'id', 'points', 'color', 'title', 'firstname', 'lastname', 'company', 'position', 'email', 'phone', 'mobile', 'address1', 'address2', 'city', 'state', 'zipcode', 'timezone', 'country', ] ) ->setGroupPrefix('lead') ->addListProperties( [ 'id', 'points', 'color', 'fields', ] ) ->addProperties( [ 'lastActive', 'owner', 'ipAddresses', 'tags', 'utmtags', 'stage', 'dateIdentified', 'preferredProfileImage', 'doNotContact', 'frequencyRules', ] ) ->build(); } public static function loadValidatorMetadata(ClassMetadata $metadata): void { $metadata->addConstraint(new UniqueCustomField(['object' => 'lead'])); } public static function getDefaultIdentifierFields(): array { return [ 'firstname', 'lastname', 'company', 'email', ]; } /** * @param string $prop * @param mixed $val * @param mixed|null $oldValue */ protected function isChanged($prop, $val, $oldValue = null) { $getter = 'get'.ucfirst($prop); $current = $oldValue ?? $this->$getter(); if ('owner' == $prop) { if ($current && !$val) { $this->changes['owner'] = [$current->getId(), $val]; } elseif (!$current && $val) { $this->changes['owner'] = [$current, $val->getId()]; } elseif ($current && $val && $current->getId() != $val->getId()) { $this->changes['owner'] = [$current->getId(), $val->getId()]; } } elseif ('ipAddresses' == $prop) { $this->changes['ipAddresses'] = ['', $val->getIpAddress()]; // Kept for BC. Not a good way to track changes on a collection if (empty($this->changes['ipAddressList'])) { $this->changes['ipAddressList'] = []; } $this->changes['ipAddressList'][$val->getIpAddress()] = $val; } elseif ('tags' == $prop) { if ($val instanceof Tag) { $this->changes['tags']['added'][] = $val->getTag(); } else { $this->changes['tags']['removed'][] = $val; } } elseif ('utmtags' == $prop) { if ($val instanceof UtmTag) { if ($val->getUtmContent()) { $this->changes['utmtags'] = ['utm_content', $val->getUtmContent()]; } if ($val->getUtmMedium()) { $this->changes['utmtags'] = ['utm_medium', $val->getUtmMedium()]; } if ($val->getUtmCampaign()) { $this->changes['utmtags'] = ['utm_campaign', $val->getUtmCampaign()]; } if ($val->getUtmTerm()) { $this->changes['utmtags'] = ['utm_term', $val->getUtmTerm()]; } if ($val->getUtmSource()) { $this->changes['utmtags'] = ['utm_source', $val->getUtmSource()]; } } } elseif ('frequencyRules' == $prop) { if (!isset($this->changes['frequencyRules'])) { $this->changes['frequencyRules'] = []; } if ($val instanceof FrequencyRule) { $channel = $val->getChannel(); $this->changes['frequencyRules'][$channel] = $val->getChanges(); } else { $this->changes['frequencyRules']['removed'][] = $val; } } elseif ('stage' == $prop) { if ($current && !$val) { $this->changes['stage'] = [$current->getId(), $val]; } elseif (!$current && $val) { $this->changes['stage'] = [$current, $val->getId()]; } elseif ($current && $val && $current->getId() != $val->getId()) { $this->changes['stage'] = [$current->getId(), $val->getId()]; } } elseif ('points' == $prop && $current != $val) { $this->changes['points'] = [$current, $val]; } else { parent::isChanged($prop, $val); } } public function convertToArray(): array { return get_object_vars($this); } /** * Set id. * * @param int $id * * @return Lead */ public function setId($id) { $this->id = (string) $id; return $this; } /** * Get id. */ public function getId(): int { return (int) $this->id; } /** * Set owner. * * @return Lead */ public function setOwner(User $owner = null) { $this->isChanged('owner', $owner); $this->owner = $owner; return $this; } /** * @return User|null */ public function getOwner() { return $this->owner; } /** * Returns the user to be used for permissions. * * @return User|int */ public function getPermissionUser() { return $this->getOwner() ?? $this->getCreatedBy(); } /** * Add ipAddress. * * @return Lead */ public function addIpAddress(IpAddress $ipAddress) { if (!$ipAddress->isTrackable()) { return $this; } $ip = $ipAddress->getIpAddress(); if (!isset($this->ipAddresses[$ip])) { $this->isChanged('ipAddresses', $ipAddress); $this->ipAddresses[$ip] = $ipAddress; } return $this; } /** * Remove ipAddress. */ public function removeIpAddress(IpAddress $ipAddress): void { $this->ipAddresses->removeElement($ipAddress); } /** * Get ipAddresses. * * @return Collection */ public function getIpAddresses() { return $this->ipAddresses; } /** * Get full name. * * @param bool $lastFirst * * @return string */ public function getName($lastFirst = false) { $firstName = $this->getFirstname(); $lastName = $this->getLastname(); $fullName = ''; if ($lastFirst && $firstName && $lastName) { $fullName = $lastName.', '.$firstName; } elseif ($firstName && $lastName) { $fullName = $firstName.' '.$lastName; } elseif ($firstName) { $fullName = $firstName; } elseif ($lastName) { $fullName = $lastName; } return $fullName; } /** * Get preferred locale. * * @return string */ public function getPreferredLocale() { if (isset($this->updatedFields['preferred_locale'])) { return $this->updatedFields['preferred_locale']; } if (!empty($this->fields['core']['preferred_locale']['value'])) { return $this->fields['core']['preferred_locale']['value']; } return ''; } /** * Get the primary identifier for the lead. * * @param bool $lastFirst * * @return string */ public function getPrimaryIdentifier($lastFirst = false) { if ($name = $this->getName($lastFirst)) { return $name; } elseif ($this->getCompany()) { return $this->getCompany(); } elseif ($this->getEmail()) { return $this->getEmail(); } elseif ($socialIdentity = $this->getFirstSocialIdentity()) { return $socialIdentity; } elseif (count($ips = $this->getIpAddresses())) { return $ips->first()->getIpAddress(); } else { return 'mautic.lead.lead.anonymous'; } } /** * Get the secondary identifier for the lead; mainly company. * * @return string */ public function getSecondaryIdentifier() { if ($this->getCompany()) { return $this->getCompany(); } return ''; } /** * Get the location for the lead. */ public function getLocation(): string { $location = ''; if ($this->getCity()) { $location .= $this->getCity().', '; } if ($this->getState()) { $location .= $this->getState().', '; } if ($this->getCountry()) { $location .= $this->getCountry().', '; } return rtrim($location, ', '); } /** * Point changes are tracked and will be persisted as a direct DB query to avoid PHP memory overwrites with concurrent requests * The risk in this is that the $changes['points'] may not be accurate but at least no points are lost. * * @param int $points * @param string $operator * * @return Lead */ public function adjustPoints($points, $operator = self::POINTS_ADD) { if (!$points = (int) $points) { return $this; } // Use $updatedPoints in an attempt to keep track in the $changes log although this may not be accurate if the DB updates the points rather // than PHP memory if (null == $this->updatedPoints) { $this->updatedPoints = $this->points; } $oldPoints = $this->updatedPoints; switch ($operator) { case self::POINTS_ADD: $this->updatedPoints += $points; $operator = '+'; break; case self::POINTS_SUBTRACT: $this->updatedPoints -= $points; $operator = '-'; break; case self::POINTS_MULTIPLY: $this->updatedPoints *= $points; $operator = '*'; break; case self::POINTS_DIVIDE: $this->updatedPoints /= $points; $operator = '/'; break; default: throw new \UnexpectedValueException('Invalid operator'); } // Keep track of point changes to make a direct DB query // Ignoring Aunt Sally here (PEMDAS) if (!isset($this->pointChanges[$operator])) { $this->pointChanges[$operator] = 0; } $this->pointChanges[$operator] += $points; $this->isChanged('points', (int) $this->updatedPoints, (int) $oldPoints); return $this; } /** * @return array */ public function getPointChanges() { return $this->pointChanges; } /** * Set points. * * @param int $points * * @return Lead */ public function setPoints($points) { $this->isChanged('points', $points); $this->points = (int) $points; // Something is setting points directly so reset points updated by database $this->resetPointChanges(); return $this; } /** * Get points. * * @return int */ public function getPoints() { if (null !== $this->actualPoints) { return $this->actualPoints; } elseif (null !== $this->updatedPoints) { return $this->updatedPoints; } return $this->points; } /** * Set by the repository method when points are updated and requeried directly on the DB side. */ public function setActualPoints($points): void { $this->actualPoints = (int) $points; $this->pointChanges = []; } /** * Reset point changes. * * @return $this */ public function resetPointChanges() { $this->actualPoints = null; $this->pointChanges = []; $this->updatedPoints = null; return $this; } /** * Creates a points change entry. */ public function addPointsChangeLogEntry(string $type, string $name, string $action, int $pointChanges, IpAddress $ip, Group $group = null): void { if (0 === $pointChanges) { // No need to record no change return; } // Create a new points change event $event = new PointsChangeLog(); $event->setType($type); $event->setEventName($name); $event->setActionName($action); $event->setDateAdded(new \DateTime()); $event->setDelta($pointChanges); $event->setIpAddress($ip); $event->setLead($this); if ($group) { $event->setGroup($group); } $this->addPointsChangeLog($event); } /** * Add pointsChangeLog. * * @return Lead */ public function addPointsChangeLog(PointsChangeLog $pointsChangeLog) { $this->pointsChangeLog[] = $pointsChangeLog; return $this; } /** * Creates a points change entry. */ public function stageChangeLogEntry($stage, $name, $action): void { // create a new points change event $event = new StagesChangeLog(); $event->setStage($stage); $event->setEventName($name); $event->setActionName($action); $event->setDateAdded(new \DateTime()); $event->setLead($this); $this->stageChangeLog($event); } /** * Add StagesChangeLog. * * @return Lead */ public function stageChangeLog(StagesChangeLog $stageChangeLog) { $this->stageChangeLog[] = $stageChangeLog; return $this; } /** * @return \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\StagesChangeLog> */ public function getStageChangeLog() { return $this->stageChangeLog; } /** * Remove pointsChangeLog. */ public function removePointsChangeLog(PointsChangeLog $pointsChangeLog): void { $this->pointsChangeLog->removeElement($pointsChangeLog); } /** * Get pointsChangeLog. * * @return Collection */ public function getPointsChangeLog() { return $this->pointsChangeLog; } public function addCompanyChangeLogEntry($type, $name, $action, $company = null): ?CompanyChangeLog { if (!$company) { // No need to record a null delta return null; } // Create a new company change event $event = new CompanyChangeLog(); $event->setType($type); $event->setEventName($name); $event->setActionName($action); $event->setDateAdded(new \DateTime()); $event->setCompany($company); $event->setLead($this); $this->addCompanyChangeLog($event); return $event; } /** * Add Company ChangeLog. * * @return Lead */ public function addCompanyChangeLog(CompanyChangeLog $companyChangeLog) { $this->companyChangeLog[] = $companyChangeLog; return $this; } /** * @return \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\CompanyChangeLog> */ public function getCompanyChangeLog() { return $this->companyChangeLog; } /** * @param bool $enabled * @param bool $mobile * * @return $this */ public function addPushIDEntry($identifier, $enabled = true, $mobile = false) { $entity = new PushID(); /** @var PushID $id */ foreach ($this->pushIds as $id) { if ($id->getPushID() === $identifier) { if ($id->isEnabled() === $enabled) { return $this; } else { $entity = $id; $this->removePushID($id); } } } $entity->setPushID($identifier); $entity->setLead($this); $entity->setEnabled($enabled); $entity->setMobile($mobile); $this->addPushID($entity); $this->isChanged('pushIds', $this->pushIds); return $this; } /** * @return $this */ public function addPushID(PushID $pushID) { $this->pushIds[] = $pushID; return $this; } public function removePushID(PushID $pushID): void { $this->pushIds->removeElement($pushID); } /** * @return \Doctrine\Common\Collections\Collection<int, \Mautic\NotificationBundle\Entity\PushID> */ public function getPushIDs() { return $this->pushIds; } /** * @return $this */ public function addEventLog(LeadEventLog $log) { $this->eventLog[] = $log; $log->setLead($this); return $this; } public function removeEventLog(LeadEventLog $eventLog): void { $this->eventLog->removeElement($eventLog); } public function getLastEventLogByAction(string $action): ?LeadEventLog { $criteria = Criteria::create() ->where(Criteria::expr()->eq('action', $action)) ->orderBy(['dateAdded' => Criteria::DESC]) ->setFirstResult(0) ->setMaxResults(1); return $this->eventLog->matching($criteria)->first() ?: null; } /** * @return $this */ public function addDoNotContactEntry(DoNotContact $doNotContact) { $this->changes['dnc_channel_status'][$doNotContact->getChannel()] = [ 'reason' => $doNotContact->getReason(), 'comments' => $doNotContact->getComments(), ]; $this->doNotContact[$doNotContact->getChannel()] = $doNotContact; return $this; } public function removeDoNotContactEntry(DoNotContact $doNotContact): void { $this->changes['dnc_channel_status'][$doNotContact->getChannel()] = [ 'reason' => DoNotContact::IS_CONTACTABLE, 'old_reason' => $doNotContact->getReason(), 'comments' => $doNotContact->getComments(), ]; $this->doNotContact->removeElement($doNotContact); } /** * @return \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\DoNotContact> */ public function getDoNotContact(): Collection { return $this->doNotContact; } /** * Set internal storage. */ public function setInternal($internal): void { $this->internal = $internal; } /** * Get internal storage. * * @return mixed */ public function getInternal() { return $this->internal; } /** * Set social cache. */ public function setSocialCache($cache): void { $this->socialCache = $cache; } /** * Get social cache. * * @return mixed */ public function getSocialCache() { return $this->socialCache; } /** * @return mixed */ public function getColor() { return $this->color; } /** * @param mixed $color */ public function setColor($color): void { $this->color = $color; } public function isAnonymous(): bool { return !($this->getName() || $this->getFirstname() || $this->getLastname() || $this->getCompany() || $this->getEmail() || $this->getFirstSocialIdentity() ); } public function wasAnonymous(): bool { return null == $this->dateIdentified && false === $this->isAnonymous(); } /** * @return bool */ protected function getFirstSocialIdentity() { if (isset($this->fields['social'])) { foreach ($this->fields['social'] as $social) { if (!empty($social['value'])) { return $social['value']; } } } elseif (!empty($this->updatedFields)) { foreach ($this->availableSocialFields as $social) { if (!empty($this->updatedFields[$social])) { return $this->updatedFields[$social]; } } } return false; } /** * @return self */ public function setManipulator(LeadManipulator $manipulator = null) { $this->manipulator = $manipulator; return $this; } /** * @return LeadManipulator|null */ public function getManipulator() { return $this->manipulator; } /** * @return bool */ public function isNewlyCreated() { return $this->newlyCreated; } /** * @param bool $newlyCreated Created */ public function setNewlyCreated($newlyCreated): void { $this->newlyCreated = $newlyCreated; } /** * @return mixed */ public function getNotes() { return $this->notes; } /** * @param string $source */ public function setPreferredProfileImage($source): void { $this->preferredProfileImage = $source; } /** * @return string */ public function getPreferredProfileImage() { return $this->preferredProfileImage; } /** * @return mixed */ public function getDateIdentified() { return $this->dateIdentified; } /** * @param mixed $dateIdentified */ public function setDateIdentified($dateIdentified): void { $this->isChanged('dateIdentified', $dateIdentified); $this->dateIdentified = $dateIdentified; } /** * @return mixed */ public function getLastActive() { return $this->lastActive; } /** * @param mixed $lastActive */ public function setLastActive($lastActive): void { $this->changes['dateLastActive'] = [$this->lastActive, $lastActive]; $this->lastActive = $lastActive; } public function setAvailableSocialFields(array $availableSocialFields): void { $this->availableSocialFields = $availableSocialFields; } /** * Add tag. * * @return Lead */ public function addTag(Tag $tag) { $this->isChanged('tags', $tag); $this->tags[$tag->getTag()] = $tag; return $this; } /** * Remove tag. */ public function removeTag(Tag $tag): void { $this->isChanged('tags', $tag->getTag()); $this->tags->removeElement($tag); } /** * Get tags. * * @return mixed */ public function getTags() { return $this->tags; } /** * Set tags. * * @return $this */ public function setTags($tags) { $this->tags = $tags; return $this; } /** * Get utm tags. * * @return mixed */ public function getUtmTags() { return $this->utmtags; } /** * Set utm tags. * * @return $this */ public function setUtmTags($utmTags) { $this->isChanged('utmtags', $utmTags); $this->utmtags[] = $utmTags; return $this; } public function removeUtmTagEntry(UtmTag $utmTag): void { $this->changes['utmtags'] = ['removed', 'UtmTagID:'.$utmTag->getId()]; $this->utmtags->removeElement($utmTag); } /** * Set stage. * * @return Stage */ public function setStage(Stage $stage = null) { $this->isChanged('stage', $stage); $this->stage = $stage; return $this; } /** * Get stage. * * @return Stage|null */ public function getStage() { return $this->stage; } /** * Set frequency rules. * * @param FrequencyRule[] $frequencyRules * * @return Lead */ public function setFrequencyRules($frequencyRules) { $this->frequencyRules = new ArrayCollection($frequencyRules); return $this; } /** * Get frequency rules. * * @return \Doctrine\Common\Collections\Collection<int, \Mautic\LeadBundle\Entity\FrequencyRule> */ public function getFrequencyRules() { return $this->frequencyRules; } /** * Remove frequencyRule. */ public function removeFrequencyRule(FrequencyRule $frequencyRule): void { $this->isChanged('frequencyRules', $frequencyRule->getId(), false); $this->frequencyRules->removeElement($frequencyRule); } /** * Add frequency rule. */ public function addFrequencyRule(FrequencyRule $frequencyRule): void { $this->isChanged('frequencyRules', $frequencyRule, false); $this->frequencyRules[] = $frequencyRule; } /** * Get attribution value. */ public function getAttribution(): float { return (float) $this->getFieldValue('attribution'); } /** * If there is an attribution amount but no date, insert today's date. */ public function checkAttributionDate(): void { $attribution = $this->getFieldValue('attribution'); $attributionDate = $this->getFieldValue('attribution_date'); if (!empty($attribution) && empty($attributionDate)) { $this->addUpdatedField('attribution_date', (new \DateTime())->format('Y-m-d')); } elseif (empty($attribution) && !empty($attributionDate)) { $this->addUpdatedField('attribution_date', null); } } /** * Set date identified. */ public function checkDateIdentified(): void { if ($this->wasAnonymous()) { $this->dateIdentified = new \DateTime(); $this->changes['dateIdentified'] = ['', $this->dateIdentified]; } } /** * Set date added if not already set. */ public function checkDateAdded(): void { if (null === $this->getDateAdded()) { $this->setDateAdded(new \DateTime()); } } /** * @return mixed */ public function getPrimaryCompany() { return $this->primaryCompany; } /** * @param mixed $primaryCompany * * @return Lead */ public function setPrimaryCompany($primaryCompany) { $this->primaryCompany = $primaryCompany; return $this; } /** * @return mixed */ public function getTitle() { return $this->title; } /** * @param mixed $title * * @return Lead */ public function setTitle($title) { $this->isChanged('title', $title); $this->title = $title; return $this; } /** * @return mixed */ public function getFirstname() { return $this->firstname; } /** * @param mixed $firstname * * @return Lead */ public function setFirstname($firstname) { $this->isChanged('firstname', $firstname); $this->firstname = $firstname; return $this; } /** * @return mixed */ public function getLastname() { return $this->lastname; } /** * @param mixed $lastname * * @return Lead */ public function setLastname($lastname) { $this->isChanged('lastname', $lastname); $this->lastname = $lastname; return $this; } /** * @return mixed */ public function getPosition() { return $this->position; } /** * @param mixed $position * * @return Lead */ public function setPosition($position) { $this->isChanged('position', $position); $this->position = $position; return $this; } /** * @return mixed */ public function getPhone() { return $this->phone; } /** * @param mixed $phone * * @return Lead */ public function setPhone($phone) { $this->isChanged('phone', $phone); $this->phone = $phone; return $this; } /** * @return mixed */ public function getMobile() { return $this->mobile; } /** * @param mixed $mobile * * @return Lead */ public function setMobile($mobile) { $this->isChanged('mobile', $mobile); $this->mobile = $mobile; return $this; } /** * @return string|null */ public function getLeadPhoneNumber() { return $this->getMobile() ?: $this->getPhone(); } /** * @return mixed */ public function getAddress1() { return $this->address1; } /** * @param mixed $address1 * * @return Lead */ public function setAddress1($address1) { $this->isChanged('address1', $address1); $this->address1 = $address1; return $this; } /** * @return mixed */ public function getAddress2() { return $this->address2; } /** * @param mixed $address2 * * @return Lead */ public function setAddress2($address2) { $this->isChanged('address2', $address2); $this->address2 = $address2; return $this; } /** * @return mixed */ public function getCity() { return $this->city; } /** * @param mixed $city * * @return Lead */ public function setCity($city) { $this->isChanged('city', $city); $this->city = $city; return $this; } /** * @return mixed */ public function getState() { return $this->state; } /** * @param mixed $state * * @return Lead */ public function setState($state) { $this->isChanged('state', $state); $this->state = $state; return $this; } /** * @return mixed */ public function getZipcode() { return $this->zipcode; } /** * @param mixed $zipcode * * @return Lead */ public function setZipcode($zipcode) { $this->isChanged('zipcode', $zipcode); $this->zipcode = $zipcode; return $this; } /** * @return string */ public function getTimezone() { return $this->timezone; } /** * @param string $timezone * * @return Lead */ public function setTimezone($timezone) { $this->isChanged('timezone', $timezone); $this->timezone = $timezone; return $this; } /** * @return mixed */ public function getCountry() { return $this->country; } /** * @param mixed $country * * @return Lead */ public function setCountry($country) { $this->isChanged('country', $country); $this->country = $country; return $this; } /** * @return mixed */ public function getCompany() { return $this->company; } /** * @param mixed $company * * @return Lead */ public function setCompany($company) { $this->isChanged('company', $company); $this->company = $company; return $this; } /** * @return mixed */ public function getEmail() { return $this->email; } /** * @param mixed $email * * @return Lead */ public function setEmail($email) { $this->isChanged('email', $email); $this->email = $email; return $this; } /** * Returns array of rules with preferred channels first. * * @return mixed */ public function getChannelRules() { if (null === $this->channelRules) { $frequencyRules = $this->getFrequencyRules()->toArray(); $dnc = $this->getDoNotContact(); $dncChannels = []; /** @var DoNotContact $record */ foreach ($dnc as $record) { $dncChannels[$record->getChannel()] = $record->getReason(); } $this->channelRules = self::generateChannelRules($frequencyRules, $dncChannels); } return $this->channelRules; } /** * @return $this */ public function setChannelRules(array $rules) { $this->channelRules = $rules; return $this; } /** * Used mostly when batching to generate preferred channels without hydrating associations one at a time. * * @return array<mixed, array<'dnc'|'frequency', mixed>> */ public static function generateChannelRules(array $frequencyRules, array $dncRules): array { $rules = []; $dncFrequencyRules = []; foreach ($frequencyRules as $rule) { if ($rule instanceof FrequencyRule) { $ruleArray = [ 'channel' => $rule->getChannel(), 'pause_from_date' => $rule->getPauseFromDate(), 'pause_to_date' => $rule->getPauseToDate(), 'preferred_channel' => $rule->getPreferredChannel(), 'frequency_time' => $rule->getFrequencyTime(), 'frequency_number' => $rule->getFrequencyNumber(), ]; if (array_key_exists($rule->getChannel(), $dncRules)) { $dncFrequencyRules[$rule->getChannel()] = $ruleArray; } else { $rules[$rule->getChannel()] = $ruleArray; } } else { // Already an array break; } } if (count($rules)) { $frequencyRules = $rules; } /* @var FrequencyRule $rule */ usort( $frequencyRules, function ($a, $b): int { if ($a['pause_from_date'] && $a['pause_to_date']) { $now = new \DateTime(); if ($now >= $a['pause_from_date'] && $now <= $a['pause_to_date']) { // A is paused so give lower preference return 1; } } if ($a['preferred_channel'] === $b['preferred_channel']) { if (!$a['frequency_time'] || !$b['frequency_time'] || !$a['frequency_number'] || !$b['frequency_number']) { return 0; } // Order by which ever can be sent more frequent if ($a['frequency_time'] === $b['frequency_time']) { if ($a['frequency_number'] === $b['frequency_number']) { return 0; } return ($a['frequency_number'] > $b['frequency_number']) ? -1 : 1; } else { $convertToMonth = fn ($number, $unit) => match ($unit) { FrequencyRule::TIME_MONTH => (int) $number, FrequencyRule::TIME_WEEK => $number * 4, FrequencyRule::TIME_DAY => $number * 30, default => $number, }; $aFrequency = $convertToMonth($a['frequency_number'], $a['frequency_time']); $bFrequency = $convertToMonth($b['frequency_number'], $b['frequency_time']); return $bFrequency <=> $aFrequency; } } return ($a['preferred_channel'] > $b['preferred_channel']) ? -1 : 1; } ); $rules = []; foreach ($frequencyRules as $rule) { $rules[$rule['channel']] = [ 'frequency' => $rule, 'dnc' => DoNotContact::IS_CONTACTABLE, ]; } if (count($dncRules)) { foreach ($dncRules as $channel => $reason) { $rules[$channel] = [ 'frequency' => $dncFrequencyRules[$channel] ?? null, 'dnc' => $reason, ]; } } return $rules; } /** * @return ArrayCollection<int,GroupContactScore> */ public function getGroupScores(): Collection { return $this->groupScores; } public function getGroupScore(Group $group): ?GroupContactScore { foreach ($this->groupScores as $groupScore) { if ($groupScore->getGroup() === $group) { return $groupScore; } } return null; } /** * @param ArrayCollection<int,GroupContactScore> $groupScores */ public function setGroupScores($groupScores): void { $this->groupScores = $groupScores; } public function addGroupScore(GroupContactScore $groupContactScore): Lead { $this->groupScores[] = $groupContactScore; return $this; } public function removeGroupScore(GroupContactScore $groupContactScore): void { $this->groupScores->removeElement($groupContactScore); } }