contactId = $contact_id; $this->firstName = $first_name; $this->lastName = $last_name; $this->email = $email; $this->sid = $sid; $this->updateData = []; } /** * Data to be updated. Use the `save()` method to apply the update. * * @var array */ private array $updateData; /** * Create a new contact and return the recipient. * * @param string $firstName * @param string $lastName * @param string $email * @param string $sid * @param string $locationType * * @return \Civi\Mailinglistsync\MailingListRecipient * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public static function createContact( string $email, string $sid, string $locationType, string $firstName = '', string $lastName = '', ): self { // Try to create a new contact try { $contact = \Civi\Api4\Contact::create(FALSE) ->addValue('contact_type', 'Individual') ->addValue('Active_Directory.SID', $sid) ->addChain( 'Email.create', \Civi\Api4\Email::create(FALSE) ->addValue('contact_id', '$id') ->addValue('email', $email) ->addValue('location_type_id:name', $locationType), ); // If contact has a firstname, add it to the chain if (!empty($firstName)) { $contact->addValue('first_name', $firstName); } // If contact has a lastname, add it to the chain if (!empty($lastName)) { $contact->addValue('last_name', $lastName); } // If contact has no firstname or lastname, add email as display name if (empty($firstName) && empty($lastName)) { $contact->addValue('display_name', $email); } // Execute the query $result = $contact->execute()->first(); } catch (\Exception $e) { throw new MailinglistException( "Failed to create contact: {$e->getMessage()}", MailinglistException::ERROR_CODE_CREATE_CONTACT_FAILED, ); } return new static( sid: $sid, contact_id: $result['id'], first_name: $firstName, last_name: $lastName, email: $email, ); } /** * Get a recipient by its email address. * * @param string $email * * @return MailingListRecipient * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public static function getContactIdEmail(string $email): MailingListRecipient { try { $recipientData = Contact::get() ->addSelect('id', 'first_name', 'last_name', 'email.email', 'Active_Directory.SID') ->addJoin('Email AS email', 'LEFT', ['id', '=', 'email.contact_id']) ->addWhere('email.email', '=', $email) ->addGroupBy('id') ->execute() ->first(); } catch (\Exception $e) { throw new MailinglistException( "Could not get recipient by email '{$email}': {$e->getMessage()}", MailinglistException::ERROR_CODE_GET_RECIPIENTS_FAILED ); } try { $recipient = new MailingListRecipient( sid: $recipientData['Active_Directory.SID'], contact_id: $recipientData['id'], first_name: $recipientData['first_name'], last_name: $recipientData['last_name'], email: $recipientData['email.email'], ); } catch (\Exception $e) { throw new MailinglistException( "Could not create recipient object for contact with id '{$recipientData['id']}': {$e->getMessage()}", MailinglistException::ERROR_CODE_GET_RECIPIENTS_FAILED ); } return $recipient; } /** * Get a list of group mailing lists the contact is subscribed to. * * @return ?array * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public function getMailingLists(): ?array { $mailingLists = []; try { $groups = \Civi\Api4\GroupContact::get() ->addSelect('group_id') ->addJoin( 'Group AS group', 'INNER', ['group_id', '=', 'group.id'], ['group.' . GroupMailingList::CUSTOM_GROUP_NAME . '.Enable_mailing_list', '=', 1], ) ->addWhere('contact_id', '=', $this->getContactId()) ->addWhere('status', '=', 'Added') ->execute() ->getArrayCopy(); } catch (\Exception $e) { throw new MailinglistException( 'Failed to get group mailing lists', MailinglistException::ERROR_CODE_GET_GROUP_MAILING_LISTS_FAILED, ); } foreach ($groups as $group) { $mailingList = new GroupMailingList($group['group_id']); if ($mailingList->isADGroup()) { $mailingList = new ADGroupMailingList($group['group_id']); ; } $mailingLists[] = $mailingList; } return $mailingLists; } /** * Get a list of AD group mailing lists the contact is subscribed to. * * @return ?array * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public function getAdMailingLists(): ?array { $mailingLists = []; try { $groups = \Civi\Api4\GroupContact::get() ->addSelect('group_id') ->addJoin( 'Group AS group', 'INNER', ['group_id', '=', 'group.id'], ['group.' . GroupMailingList::CUSTOM_GROUP_NAME . '.Enable_mailing_list', '=', 1], ['group.' . GroupMailingList::CUSTOM_GROUP_NAME . '.Active_Directory_SID', 'IS NOT EMPTY'], ) ->addWhere('contact_id', '=', $this->getContactId()) ->addWhere('status', '=', 'Added') ->execute() ->getArrayCopy(); } catch (\Exception $e) { throw new MailinglistException( 'Failed to get AD group mailing lists', MailinglistException::ERROR_CODE_GET_AD_GROUP_MAILING_LISTS_FAILED, ); } foreach ($groups as $group) { $mailingLists[] = new ADGroupMailingList($group['group_id']); } return $mailingLists; } /** * Get a list of event mailing lists the contact is subscribed to. * * @return ?array * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public function getEventMailingLists(): ?array { $mailingLists = []; try { $groups = \Civi\Api4\Participant::get() ->addSelect('event_id') ->addJoin( 'Event AS event', 'INNER', ['event_id', '=', 'event.id'], ['event.' . EventMailingList::CUSTOM_GROUP_NAME . '.Enable_mailing_list', '=', 1], ) ->addWhere('contact_id', '=', $this->getContactId()) ->addWhere('status_id', 'IN', EventMailingList::getEnabledParticipantStatus()) ->execute() ->getArrayCopy(); } catch (\Exception $e) { throw new MailinglistException( 'Failed to get event mailing lists', MailinglistException::ERROR_CODE_GET_EVENT_MAILING_LISTS_FAILED, ); } foreach ($groups as $group) { $mailingLists[] = new EventMailingList($group['group_id']); } return $mailingLists; } /** * Get the contact ID. * * @return int|NULL */ public function getContactId(): ?int { return $this->contactId; } /** * Get the first name of the contact. * * @return string|NULL */ public function getFirstName(): ?string { return $this->firstName; } /** * Get the last name of the contact. * * @return string|NULL */ public function getLastName(): ?string { return $this->lastName; } /** * Get the email address of the contact. * * @return string|NULL */ public function getEmail(): ?string { return $this->email; } /** * Get the SID of the contact. * * @return string|NULL */ public function getSid(): ?string { return $this->sid; } /** * Update the first name of the contact. * * @param string $firstName * * @return void */ public function updateFirstName(string $firstName): void { $this->updateData += ['first_name' => $firstName]; } /** * Update the last name of the contact. * * @param string $lastName * * @return void */ public function updateLastName(string $lastName): void { $this->updateData += ['last_name' => $lastName]; } /** * Update the email address of the contact. * * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public function updateEmail(string $email): void { if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new MailinglistException( "Invalid email address '$email'", MailinglistException::ERROR_CODE_INVALID_EMAIL_ADDRESS, ); } $this->updateData += ['email' => $email]; } /** * Update the SID of the contact. * * @param string $sid * * @return void */ public function updateSid(string $sid): void { $this->updateData += [self::CUSTOM_GROUP_NAME . '.SID' => $sid]; } /** * Save the changes marked using the setter methods. * * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public function save(): void { if (empty($this->updateData)) { return; } $contactValues = ['first_name', 'last_name', self::CUSTOM_GROUP_NAME . '.SID']; $contactUpdates = array_intersect_key($this->updateData, array_flip($contactValues)); if (!empty($contactUpdates)) { try { \Civi\Api4\Contact::update() ->setValues($contactUpdates) ->addWhere('id', '=', $this->getContactId()) ->execute(); \Civi::log()->debug( "Updated contact '{$this->getContactId()}' with data: " . json_encode($this->updateData) ); } catch (\Exception $e) { \Civi::log()->error( "Failed to update contact '{$this->getContactId()}': $e" ); throw new MailinglistException( "Failed to update contact: {$e->getMessage()}", MailinglistException::ERROR_CODE_UPDATE_ENTITY_FAILED, ); } } // Update email address if (!empty($this->updateData['email'])) { try { \Civi\Api4\Email::update() ->addValue('email', $this->updateData['email']) ->addWhere('contact_id', '=', $this->getContactId()) ->execute(); \Civi::log()->debug( "Updated email address for contact '{$this->getContactId()}'" ); } catch (\Exception $e) { \Civi::log()->error( "Failed to update email address for contact '{$this->getContactId()}': $e" ); throw new MailinglistException( "Failed to update email address: {$e->getMessage()}", MailinglistException::ERROR_CODE_UPDATE_ENTITY_FAILED, ); } } try { self::createSyncActivity( $this->getContactId(), 'Completed', E::ts('Contact updated'), E::ts('The contact data was updated by the mailing list synchronization: %1', [ 1 => json_encode($this->updateData) ]) ); } catch (\Exception $e) { \Civi::log()->error( "Failed to create activity for contact '{$this->getContactId()}': $e" ); throw new MailinglistException( "Failed to create activity for contact '{$this->getContactId()}': $e", MailinglistException::ERROR_CODE_CREATE_ACTIVITY_FAILED, ); } } /** * Get a list of email location types for the contact. * * @return array * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public function getEmailLocationTypes(): array { try { $result = \Civi\Api4\Email::get() ->addSelect('location_type_id:name') ->addWhere('contact_id', '=', $this->getContactId()) ->execute() ->getArrayCopy(); return array_column($result, 'location_type_id:name'); } catch (\Exception $e) { throw new MailinglistException( 'Failed to get email location types', MailinglistException::ERROR_CODE_GET_EMAIL_LOCATION_TYPES_FAILED, ); } } /** * Create an e-mail address for the contact. * * @param string $email * @param string $locationType * * @return array|NULL * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public function createEmail(string $email, string $locationType): ?array { try { $result = \Civi\Api4\Email::create(FALSE) ->addValue('contact_id', $this->getContactId()) ->addValue('email', $email) ->addValue('location_type_id:name', $locationType) ->execute() ->first(); } catch (\Exception $e) { throw new MailinglistException( 'Failed to create email', MailinglistException::ERROR_CODE_CREATE_EMAIL_ADDRESS_FAILED, ); } $this->email = $email; return $result; } /** * Add a synchronization activity to a contact. * * @param int $targetContactId * @param string $status * @param string $subject * @param string $details * * @return void * @throws \Civi\Mailinglistsync\Exceptions\MailinglistException */ public static function createSyncActivity( int $targetContactId, string $status, string $subject, string $details, ): void { try { \Civi\Api4\Activity::create(FALSE) ->addValue('source_contact_id', getSessionUser()) ->addValue('activity_type_id:name', 'Mailing_List_Synchronization_Activity') ->addValue('subject', $subject) ->addValue('details', $details) ->addValue('status_id:name', ucfirst($status)) ->addValue('target_contact_id', $targetContactId) ->execute()->first(); } catch (\Exception $e) { throw new MailinglistException( "Failed to create contact activity: $e", MailinglistException::ERROR_CODE_CREATE_ACTIVITY_FAILED, ); } } }