585 lines
19 KiB
PHP
585 lines
19 KiB
PHP
<?php /** @noinspection SpellCheckingInspection */
|
|
declare(strict_types=1);
|
|
|
|
namespace Civi\Mailinglistsync;
|
|
|
|
use CRM_Mailinglistsync_ExtensionUtil as E;
|
|
use Civi\Mailinglistsync\Exceptions\MailinglistException;
|
|
|
|
class ADGroupMailingList extends GroupMailingList {
|
|
|
|
const RECIPIENTS_ATTRIBUTE_MAP = [
|
|
// Map recipient attribute names to method names (get/update)
|
|
'sid' => 'Sid',
|
|
'email' => 'Email',
|
|
'first_name' => 'FirstName',
|
|
'last_name' => 'LastName',
|
|
];
|
|
|
|
public const LOCATION_TYPE = 'AD_Mailing_List_Address';
|
|
|
|
protected string $sid;
|
|
|
|
/**
|
|
* Get an AD mailing list by its SID.
|
|
*
|
|
* @params string $sid
|
|
* @param string $sid
|
|
*
|
|
* @return \Civi\Mailinglistsync\ADGroupMailingList|null
|
|
*
|
|
* @throws \CRM_Core_Exception
|
|
* @throws \Civi\API\Exception\UnauthorizedException
|
|
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
|
|
*/
|
|
public static function getBySID(string $sid): ?self {
|
|
$result = static::RELATED_CLASS::get()
|
|
->addSelect('id')
|
|
->addWhere(static::CUSTOM_GROUP_NAME . '.Active_Directory_SID', '=', $sid)
|
|
->execute()
|
|
->first();
|
|
|
|
return $result ? new self($result['id']) : NULL;
|
|
}
|
|
|
|
/**
|
|
* Synchronize recipients with a list of SIDs and e-mail addresses.
|
|
*
|
|
* @param array $recipients
|
|
*
|
|
* @return array A description of the changes made
|
|
*
|
|
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
|
|
*/
|
|
public function syncRecipients(array $recipients): array {
|
|
$adGroupMembers = $this->getRecipients();
|
|
|
|
// Add new recipients
|
|
$recipientsAdded = [];
|
|
foreach ($recipients as $recipient) {
|
|
if (empty($adGroupMembers[$recipient['sid']] ?? NULL)) {
|
|
$contactId = NULL;
|
|
$recipientResult = $recipient + [
|
|
'is_error' => 0,
|
|
];
|
|
|
|
// Check if the contact already exists
|
|
$found = $this::findExistingContact(
|
|
firstName: $recipient['first_name'],
|
|
lastName: $recipient['last_name'],
|
|
email: $recipient['email'],
|
|
sid: $recipient['sid'],
|
|
);
|
|
|
|
// If the contac does not exist, add it
|
|
if ($found === NULL) {
|
|
try {
|
|
$newContact = MailingListRecipient::createContact(
|
|
email: $recipient['email'],
|
|
sid: $recipient['sid'],
|
|
locationType: static::LOCATION_TYPE,
|
|
firstName: $recipient['first_name'] ?? '',
|
|
lastName: $recipient['last_name'] ?? '',
|
|
);
|
|
|
|
$contactId = $newContact->getContactId();
|
|
$recipientResult['contact_id'] = $contactId;
|
|
$recipientResult['contact_created'] = TRUE;
|
|
|
|
// Create contact activity
|
|
try {
|
|
MailingListRecipient::createSyncActivity(
|
|
$contactId,
|
|
'Completed',
|
|
E::ts('Contact created by AD group synchronization'),
|
|
E::ts("This contact has been created by the AD group synchronization process for the group '%1'",
|
|
[1 => $this->getTitle()])
|
|
);
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$error = $e->getLogMessage();
|
|
\Civi::log(E::LONG_NAME)->error($error);
|
|
$recipientResult['is_error'] = 1;
|
|
$recipientResult['activity_error_message'] = $error;
|
|
}
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$recipientResult['is_error'] = 1;
|
|
$recipientResult['error_message'] = $e->getLogMessage();
|
|
}
|
|
}
|
|
|
|
// If the contact exists, use the existing contact
|
|
else {
|
|
$contactId = $found['contact']['id'];
|
|
$recipientResult['contact_id'] = $contactId;
|
|
$resipientResult['found_by'] = $found['found_by'];
|
|
$recipientResult['contact_created'] = FALSE;
|
|
|
|
// If the contact was not found by SID, add the SID to the contact
|
|
if ($found['found_by'] !== 'sid') {
|
|
try {
|
|
$contact = new MailingListRecipient(contact_id: $contactId);
|
|
$contact->updateSid($recipient['sid']);
|
|
$contact->save();
|
|
|
|
$recipientResult['updated'] = [
|
|
'sid' => [
|
|
'is_error' => 0,
|
|
'old' => $found['contact']['sid'],
|
|
'new' => $recipient['sid'],
|
|
],
|
|
];
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$recipientResult['is_error'] = 1;
|
|
$recipientResult['error_message'] = $e->getLogMessage();
|
|
}
|
|
|
|
// Create contact activity
|
|
try {
|
|
MailingListRecipient::createSyncActivity(
|
|
$contactId,
|
|
'Completed',
|
|
E::ts('Contact updated by AD contact synchronization'),
|
|
E::ts("This contact has been updated by the AD contact synchronization process")
|
|
);
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$error = $e->getLogMessage();
|
|
\Civi::log(E::LONG_NAME)->error($error);
|
|
$recipientResult['is_error'] = 1;
|
|
$recipientResult['activity_error_message'] = $error;
|
|
}
|
|
|
|
// Skip adding the contact to the group if it already is a member
|
|
if (in_array(
|
|
$contactId,
|
|
array_column($adGroupMembers, 'contactId')
|
|
)) {
|
|
$recipientsAdded['recipients'][] = $recipientResult;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the contact to the mailing list
|
|
if ($contactId !== NULL) {
|
|
$recipientResult += $this->addRecipient($contactId);
|
|
|
|
// Create contact activity
|
|
try {
|
|
MailingListRecipient::createSyncActivity(
|
|
$contactId,
|
|
'Completed',
|
|
E::ts('Contact added to group'),
|
|
E::ts("This contact has been added to the group '%1' by AD group synchronization.",
|
|
[1 => $this->getTitle()])
|
|
);
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$error = $e->getLogMessage();
|
|
\Civi::log(E::LONG_NAME)->error($error);
|
|
$recipientResult['is_error'] = 1;
|
|
$recipientResult['activity_error_message'] = $error;
|
|
}
|
|
}
|
|
else {
|
|
$recipientResult['is_error'] = 1;
|
|
$recipientResult['error_message'] = 'Contact ID is NULL';
|
|
}
|
|
$recipientsAdded['recipients'][] = $recipientResult;
|
|
}
|
|
}
|
|
$recipientsAdded['count'] = count($recipientsAdded['recipients'] ?? []);
|
|
$recipientsAdded['error_count'] = count(
|
|
array_filter($recipientsAdded['recipients'] ?? [],
|
|
fn($recipient) => (bool) $recipient['is_error'])
|
|
);
|
|
|
|
// Remove recipients that are no longer in the list
|
|
$recipientsRemoved = [];
|
|
foreach ($adGroupMembers as $adGroupMember) {
|
|
/* @var \Civi\Mailinglistsync\MailingListRecipient $adGroupMember */
|
|
if (!in_array($adGroupMember->getSid(), array_column($recipients, 'sid'))) {
|
|
$recipientsRemoved['recipients'][] = $this->removeRecipient($adGroupMember);
|
|
|
|
// Create contact activity
|
|
try {
|
|
MailingListRecipient::createSyncActivity(
|
|
$adGroupMember->getContactId(),
|
|
'Completed',
|
|
E::ts('Contact removed from group'),
|
|
E::ts("This contact has been removed from the group '%1' by AD group synchronization.",
|
|
[1 => $this->getTitle()])
|
|
);
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$error = $e->getLogMessage();
|
|
\Civi::log(E::LONG_NAME)->error($error);
|
|
$recipientResult['is_error'] = 1;
|
|
$recipientResult['activity_error_message'] = $error;
|
|
}
|
|
}
|
|
}
|
|
$recipientsRemoved['count'] = count($recipientsRemoved['recipients'] ?? []);
|
|
$recipientsRemoved['error_count'] = count(
|
|
array_filter($recipientsRemoved['recipients'] ?? [],
|
|
fn($recipient) => (bool) $recipient['is_error'])
|
|
);
|
|
|
|
// Update recipients
|
|
$recipientsUpdated = [];
|
|
|
|
foreach ($recipients as $recipient) {
|
|
$recipientUpdated = $recipient;
|
|
$changed = FALSE;
|
|
|
|
// Find the group member by SID
|
|
$adGroupMemberArray = array_filter(
|
|
$this->getRecipients(),
|
|
fn($adGroupMember) => $adGroupMember->getSid() === $recipient['sid']
|
|
);
|
|
$count = count($adGroupMemberArray);
|
|
if ($count === 0) {
|
|
continue;
|
|
}
|
|
elseif ($count > 1) {
|
|
$recipientUpdated += [
|
|
'is_error' => 1,
|
|
'error_message' => "Multiple recipients found with SID '{$recipient['sid']}'",
|
|
];
|
|
}
|
|
/* @var \Civi\Mailinglistsync\MailingListRecipient $adGroupMember */
|
|
$adGroupMember = array_pop($adGroupMemberArray);
|
|
|
|
// Create new email with AD_Mailing_List_Address location type if necessary
|
|
if (!in_array(static::LOCATION_TYPE, $adGroupMember->getEmailLocationTypes())) {
|
|
try {
|
|
$adGroupMember->createEmail($recipient['email'], static::LOCATION_TYPE);
|
|
$recipientUpdated['new_email_address_created'] = [
|
|
'is_error' => 0,
|
|
'email' => $recipient['email'],
|
|
'location_type' => static::LOCATION_TYPE,
|
|
];
|
|
$changed = TRUE;
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$recipientUpdated['new_email_address_created']['is_error'] = 1;
|
|
$recipientUpdated['new_email_address_created']['error_message'] = $e->getLogMessage();
|
|
}
|
|
$recipientUpdated['is_error'] = $recipientUpdated['is_error'] ||
|
|
(int) $recipientUpdated['new_email_address_created']['is_error'];
|
|
}
|
|
|
|
// Update attributes if they have changed
|
|
$recipientAttributes = self::RECIPIENTS_ATTRIBUTE_MAP;
|
|
unset($recipientAttributes['sid']);
|
|
foreach (self::RECIPIENTS_ATTRIBUTE_MAP as $attrName => $methodName) {
|
|
$changed = self::updateRecipient(
|
|
$recipient,
|
|
$attrName,
|
|
fn() => $adGroupMember->{"get$methodName"}(),
|
|
fn($value) => $adGroupMember->{"update$methodName"}($value),
|
|
$recipientUpdated,
|
|
) || $changed;
|
|
}
|
|
|
|
if ($changed) {
|
|
// Apply changes
|
|
try {
|
|
$adGroupMember->save();
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$recipientUpdated['is_error'] = 1;
|
|
$recipientUpdated['error_message'] = $e->getLogMessage();
|
|
}
|
|
$recipientUpdated['is_error'] = (int) ($recipientUpdated['is_error'] ||
|
|
!empty(array_filter(
|
|
$recipientUpdated['updated'] ?? [],
|
|
fn($update) => (bool) $update['is_error']
|
|
)));
|
|
}
|
|
|
|
if ($changed) {
|
|
$recipientsUpdated['recipients'][] = $recipientUpdated;
|
|
}
|
|
}
|
|
|
|
$recipientsUpdated['count'] = count($recipientsUpdated['recipients'] ?? []);
|
|
$recipientsUpdated['error_count'] = count(
|
|
array_filter($recipientsUpdated['recipients'] ?? [],
|
|
fn($recipient) => (bool) $recipient['is_error'])
|
|
);
|
|
|
|
// Count results
|
|
$resultCount = $recipientsAdded['count']
|
|
+ $recipientsRemoved['count']
|
|
+ $recipientsUpdated['count'];
|
|
$errorCount = $recipientsAdded['error_count']
|
|
+ $recipientsRemoved['error_count']
|
|
+ $recipientsUpdated['error_count'];
|
|
|
|
return [
|
|
'count' => $resultCount,
|
|
'error_count' => $errorCount,
|
|
'is_error' => (int) ($errorCount > 0),
|
|
'added' => $recipientsAdded,
|
|
'removed' => $recipientsRemoved,
|
|
'updated' => $recipientsUpdated,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Updates contacts with values coming from the AD.
|
|
*
|
|
* @param $recipients
|
|
*
|
|
* @return array
|
|
* @throws \Civi\Mailinglistsync\Exceptions\ContactSyncException
|
|
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
|
|
*/
|
|
public
|
|
static function syncContacts($recipients): array {
|
|
$results = [];
|
|
|
|
foreach ($recipients as $contact) {
|
|
$result = [
|
|
'sid' => $contact['sid'] ?? 'unknown',
|
|
'first_name' => $contact['first_name'] ?? 'unknown',
|
|
'last_name' => $contact['last_name'] ?? 'unknown',
|
|
'email' => $contact['email'] ?? 'unknown',
|
|
];
|
|
|
|
// Check for required fields
|
|
foreach (self::RECIPIENTS_ATTRIBUTE_MAP as $attrName => $_) {
|
|
$missing = [];
|
|
if (empty($contact[$attrName])) {
|
|
$missing[] = $attrName;
|
|
}
|
|
}
|
|
if (!empty($missing)) {
|
|
$result += [
|
|
'is_error' => 1,
|
|
'error_message' => E::ts('Missing required attributes: %1', [1 => implode(', ', $missing)]),
|
|
];
|
|
continue;
|
|
}
|
|
|
|
// Try to find an existing contact
|
|
try {
|
|
$contactSearch = self::findExistingContact(
|
|
firstName: $contact['first_name'],
|
|
lastName: $contact['last_name'],
|
|
email: $contact['email'],
|
|
sid: $contact['sid'],
|
|
searchBy: ['sid'],
|
|
);
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$result += [
|
|
'is_error' => 1,
|
|
'error_message' => $e->getLogMessage(),
|
|
];
|
|
continue;
|
|
}
|
|
if ($contactSearch === NULL) {
|
|
$result += [
|
|
'is_error' => 1,
|
|
'error_message' => "No contact found for SID '{$contact['sid']}'",
|
|
];
|
|
}
|
|
|
|
// Update Contact
|
|
else {
|
|
$recipient = new MailingListRecipient(
|
|
sid: $contact['sid'],
|
|
contact_id: $contactSearch['contact']['id'],
|
|
first_name: $contactSearch['contact']['first_name'],
|
|
last_name: $contactSearch['contact']['last_name'],
|
|
email: $contactSearch['contact']['email.email'],
|
|
);
|
|
|
|
// Update attributes if they have changed;
|
|
$changed = FALSE;
|
|
$recipientAttributes = self::RECIPIENTS_ATTRIBUTE_MAP;
|
|
unset($recipientAttributes['sid']);
|
|
foreach (self::RECIPIENTS_ATTRIBUTE_MAP as $attrName => $methodName) {
|
|
$changed = self::updateRecipient(
|
|
$contact,
|
|
$attrName,
|
|
fn() => $recipient->{"get$methodName"}(),
|
|
fn($value) => $recipient->{"update$methodName"}($value),
|
|
$result,
|
|
) || $changed;
|
|
}
|
|
if ($changed) {
|
|
// Apply changes
|
|
try {
|
|
$recipient->save();
|
|
$result += [
|
|
'is_error' => 0,
|
|
];
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$result += [
|
|
'is_error' => 1,
|
|
'error_message' => $e->getLogMessage(),
|
|
];
|
|
}
|
|
}
|
|
|
|
// Count errors in updated attributes
|
|
$result['error_count'] = count(
|
|
array_filter($result['updated'] ?? [],
|
|
fn($update) => (bool) $update['is_error'])
|
|
);
|
|
$result['is_error'] = (int) (($result['is_error'] ?? FALSE) || $result['error_count'] > 0);
|
|
}
|
|
if ((($changed ?? FALSE) === TRUE) || $result['is_error']) {
|
|
$results['updated'][] = $result;
|
|
}
|
|
}
|
|
$results['count'] = count($results['updated'] ?? []);
|
|
$results['error_count'] = array_sum(array_column($results['updated'] ?? [], 'is_error'));
|
|
return ($results['count'] || $results['error_count']) ? $results : [];
|
|
}
|
|
|
|
/**
|
|
* Get a list of recipients indexed by SID.
|
|
*
|
|
* @override GroupMailingList::getRecipients()
|
|
* @return array
|
|
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
|
|
*/
|
|
public
|
|
function getRecipients(): array {
|
|
$recipients = parent::getRecipients();
|
|
$recipientsBySid = [];
|
|
foreach ($recipients as $recipient) {
|
|
$recipientsBySid[$recipient->getSid()] = $recipient;
|
|
}
|
|
return $recipientsBySid;
|
|
}
|
|
|
|
/**
|
|
* Add a recipient to the group related to this mailing list.
|
|
*
|
|
* @param int $contactId
|
|
*
|
|
* @return array|array[]
|
|
*/
|
|
private
|
|
function addRecipient(int $contactId): array {
|
|
$result = [];
|
|
// Add the contact to the group
|
|
try {
|
|
$contactEmail = MailingListRecipient::getContactById($contactId)
|
|
->getEmailId(self::LOCATION_TYPE);
|
|
$this->addContactToGroup($contactId, $contactEmail);
|
|
$result['added'] = TRUE;
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$error = $e->getLogMessage();
|
|
\Civi::log(E::LONG_NAME)->error($error);
|
|
$result['is_error'] = 1;
|
|
$result['group_error_message'] = $error;
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Remove a recipient from the group related to this mailing list.
|
|
*
|
|
* @param \Civi\Mailinglistsync\MailingListRecipient $recipient
|
|
*
|
|
* @return array
|
|
*/
|
|
public
|
|
function removeRecipient(MailingListRecipient $recipient): array {
|
|
$result = [
|
|
'sid' => $recipient->getSid(),
|
|
'contact_id' => $recipient->getContactId(),
|
|
'first_name' => $recipient->getFirstName(),
|
|
'last_name' => $recipient->getLastName(),
|
|
'email' => $recipient->getEmail(),
|
|
];
|
|
try {
|
|
$this->removeContactFromGroup($recipient->getContactId());
|
|
$result['is_error'] = 0;
|
|
|
|
// Create contact activity
|
|
try {
|
|
MailingListRecipient::createSyncActivity(
|
|
$recipient->getContactId(),
|
|
'Completed',
|
|
E::ts('Contact removed from mailing list'),
|
|
E::ts("This contact has been removed from the mailing list '%1'",
|
|
[1 => $this->getTitle()])
|
|
);
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$error = $e->getLogMessage();
|
|
\Civi::log(E::LONG_NAME)->error($error);
|
|
$result['is_error'] = 1;
|
|
$result['activity_error_message'] = $error;
|
|
}
|
|
}
|
|
catch (MailinglistException $e) {
|
|
$error = $e->getLogMessage();
|
|
\Civi::log(E::LONG_NAME)->error($error);
|
|
$result['is_error'] = 1;
|
|
$result['error_message'] = $error;
|
|
}
|
|
finally {
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to update recipient data dynamically.
|
|
* OMG, is this DRY!
|
|
*
|
|
* @param array $new
|
|
* @param string $attributeName
|
|
* @param callable $getter
|
|
* @param callable $setter
|
|
* @param array $changes
|
|
*
|
|
* @return bool TRUE if the recipient was updated, FALSE otherwise
|
|
*/
|
|
private
|
|
static function updateRecipient(
|
|
array $new,
|
|
string $attributeName,
|
|
callable $getter,
|
|
callable $setter,
|
|
array &$changes,
|
|
): bool {
|
|
$updated = FALSE;
|
|
if (strtoupper($new[$attributeName]) !== strtoupper($getter())) {
|
|
$error = NULL;
|
|
$oldValue = $getter();
|
|
try {
|
|
$setter($new[$attributeName]);
|
|
$updated = TRUE;
|
|
}
|
|
catch (MailinglistException $e) {
|
|
\Civi::log(E::LONG_NAME)->error($e->getLogMessage());
|
|
$error = $e->getLogMessage();
|
|
}
|
|
finally {
|
|
$changes['updated'][$attributeName] = [
|
|
'is_error' => (int) ($error !== NULL),
|
|
'old' => $oldValue,
|
|
'new' => $new[$attributeName],
|
|
];
|
|
if ($error !== NULL) {
|
|
$changes['updated'][$attributeName]['error_message'] = $error;
|
|
}
|
|
}
|
|
}
|
|
return $updated;
|
|
}
|
|
|
|
}
|