de.propeace.mailinglistsync/Civi/Mailinglistsync/ADGroupMailingList.php
2025-03-04 11:15:09 +01:00

237 lines
7 KiB
PHP

<?php /** @noinspection SpellCheckingInspection */
namespace Civi\Mailinglistsync;
use CRM_Mailinglistsync_ExtensionUtil as E;
use Civi\Mailinglistsync\Exceptions\MailinglistException;
class ADGroupMailingList extends GroupMailingList {
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 {
$id = static::RELATED_CLASS::get()
->addSelect('id')
->addWhere(static::CUSTOM_GROUP_NAME . '.Active_Directory_SID', '=', $sid)
->execute()
->first()['id'];
$groups = \CRM_Contact_BAO_Group::getGroups(['id' => $id]);
$groupId = array_pop($groups);
return $groupId ? new self($groupId) : 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)) {
$recipientsAdded[$recipient['sid']] = $this->addRecipient($recipient);
}
}
// 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[$adGroupMember->getSid()] = $this->removeRecipient($adGroupMember);
}
}
// Update recipients
$recipientsUpdated = [];
$recipientAttributesMap = [
// Map recipient attribute names to method names (get/set)
'first_name' => 'FirstName',
'last_name' => 'LastName',
'email' => 'Email',
];
foreach ($recipients as $recipient) {
$changed = FALSE;
// Find the group member by SID
$adGroupMemberArray = array_filter(
$adGroupMembers,
fn($adGroupMember) => $adGroupMember->getSid() === $recipient['sid']
);
$count = count($adGroupMemberArray);
if ($count === 0) {
continue;
}
elseif ($count > 1) {
throw new MailinglistException(
"Multiple recipients with the same SID",
MailinglistException::ERROR_CODE_MULTIPLE_RECIPIENTS
);
}
/* @var \Civi\Mailinglistsync\MailingListRecipient $adGroupMember */
$adGroupMember = array_pop($adGroupMemberArray);
// Update attributes if they have changed
foreach ($recipientAttributesMap as $attrName => $methodName) {
$changed = self::updateRecipient(
$adGroupMember,
$recipient,
$attrName,
fn() => $adGroupMember->{"get$methodName"}(),
fn($value) => $adGroupMember->{"set$methodName"}($value),
$recipientsUpdated,
) || $changed;
}
// Apply changes
if ($changed) {
$adGroupMember->save();
}
}
// Count errors
$errorCount = count(array_filter(
$recipientsAdded,
fn($recipient) => $recipient['is_error']
));
$errorCount += count(array_filter(
$recipientsRemoved,
fn($recipient) => $recipient['is_error']
));
$errorCount += count(array_filter(
$recipientsUpdated,
fn($recipient) => array_filter($recipient, fn($attr) => $attr['is_error'])
));
return [
'error_count' => $errorCount,
'recipients_added' => $recipientsAdded,
'recipients_removed' => $recipientsRemoved,
'recipients_updated' => $recipientsUpdated,
];
}
/**
* 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();
return array_column($recipients, null, fn($recipient) => $recipient->getSid());
}
private function addRecipient(array $recipient): array {
$result = ['recipient' => $recipient];
try {
// Try to find an existing contact
$contactSearch = $this->findExistingContact(
firstName: $recipient['first_name'],
lastName: $recipient['last_name'],
email: $recipient['email'],
);
if ($contactSearch !== NULL) {
$result['is_error'] = FALSE;
$result['recipient']['contact_id'] = $contactSearch['contact']['id'];
$result['recipient']['found_by'] = $contactSearch['found_by'];
$result['recipient']['contact_created'] = FALSE;
return $result;
}
// Create a new contact if none was found
$newRecipient = MailingListRecipient::createContact(
firstName: $recipient['first_name'],
lastName: $recipient['last_name'],
email: $recipient['email'],
sid: $recipient['sid'],
);
$result['is_error'] = FALSE;
$result['recipient']['contact_id'] = $newRecipient->getContactId();
$result['recipient']['contact_created'] = TRUE;
return $result;
}
catch (MailinglistException $e) {
$error = $e->getLogMessage();
\Civi::log(E::LONG_NAME)->error($error);
$result['is_error'] = TRUE;
$result['error_message'] = $error;
$result['recipient']['contact_created'] = FALSE;
return $result;
}
}
/**
* Helper function to update recipient data dynamically.
* OMG, is this DRY!
*
* @param \Civi\Mailinglistsync\MailingListRecipient $old
* @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(
MailingListRecipient $old,
array $new,
string $attributeName,
callable $getter,
callable $setter,
array &$changes,
): bool {
$updated = FALSE;
if ($new[$attributeName] !== $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[$old['sid']][$attributeName] = [
'is_error' => $error !== NULL,
'old' => $oldValue,
'new' => $new[$attributeName],
];
if ($error !== NULL) {
$changes[$old['sid']][$attributeName]['error'] = $error;
}
}
}
return $updated;
}
}