de.propeace.mailinglistsync/Civi/Mailinglistsync/MailingListApi.php
2025-03-28 19:07:48 +01:00

321 lines
9.5 KiB
PHP

<?php
declare(strict_types=1);
namespace Civi\Mailinglistsync;
use Civi\Mailinglistsync\Exceptions\MailinglistException;
/**
* The MailingListApi class provides methods to interact with the mlmmj and
* Dovecot APIs.
*/
class MailingListApi {
// Use the singleton trait
use Singleton;
protected string $mlmmjUrl;
protected string $mlmmjToken;
protected int $mlmmjPort;
protected string $dovecotUrl;
protected string $dovecotToken;
protected int $dovecotPort;
protected function __construct() {
$this->mlmmjUrl = MailingListSettings::get('mlmmj_host');
$this->mlmmjToken = MailingListSettings::get('mlmmj_token');
$this->mlmmjPort = MailingListSettings::get('mlmmj_port');
$this->dovecotUrl = MailingListSettings::get('dovecot_host');
$this->dovecotToken = MailingListSettings::get('dovecot_token');
$this->dovecotPort = MailingListSettings::get('dovecot_port');
}
/**
* Prepares a curl handle for the mlmmj API.
*
* @param null $path
*
* @return \CurlHandle
*/
private function prepareMlmmjCurl($path = NULL): \CurlHandle {
// Build URL
$url = $path
? "{$this->mlmmjUrl}api/{$path}"
: "{$this->mlmmjUrl}api/";
// Set up curl handle
$curl = curl_init();
// Set options
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_PORT => $this->mlmmjPort,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HTTPHEADER => [
'X-MLMMJADMIN-API-AUTH-TOKEN: ' . $this->mlmmjToken,
],
]);
return $curl;
}
/**
* Prepares a curl handle for the Dovecot API.
*
* @param $path
*
* @return \CurlHandle
*/
private function prepareDovecotCurl($path = NULL) {
// Build URL
$url = $path
? "{$this->dovecotUrl}/{$path}"
: "{$this->dovecotUrl}";
// Set up curl handle
$curl = curl_init();
// Set options
curl_setopt_array($curl, [
CURLOPT_URL => $url,
CURLOPT_PORT => $this->dovecotPort,
CURLOPT_RETURNTRANSFER => TRUE,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->dovecotToken,
]
]);
return $curl;
}
/**
* Get a mailing list from mlmmj.
*
* @param string $mailinglistEmail
*
* @return array
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
*/
function getMailingList(string $mailinglistEmail): array {
$curl = $this->prepareMlmmjCurl($mailinglistEmail);
$result = json_decode(curl_exec($curl), TRUE);
// Check result
if (curl_getinfo($curl, CURLINFO_HTTP_CODE) !== 200) {
throw new MailinglistException(
"Could not get mailinglist '$mailinglistEmail'",
MailinglistException::ERROR_CODE_GET_MAILINGLIST_FAILED,
);
}
if ($result['_success'] === FALSE) {
// Return empty array if the account does not exist yet
if ($result['_msg'] === 'NO_SUCH_ACCOUNT') {
return [];
}
// Throw exception if the request failed
else {
throw new MailinglistException(
"Could not get mailinglist '$mailinglistEmail'",
MailinglistException::ERROR_CODE_GET_MAILINGLIST_FAILED,
);
}
}
return $result;
}
/**
* Get the subscribers from mlmmj.
*
* @param string $mailinglistEmail
*
* @return array
*
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
*/
function getMailingListSubscribers(string $mailinglistEmail): array {
$curl = $this->prepareMlmmjCurl($mailinglistEmail . '/subscribers');
$result = json_decode(curl_exec($curl), TRUE);
// Check result
if (!$result['_success']) {
throw new MailinglistException(
"Could not get subscribers for mailinglist '$mailinglistEmail'",
MailinglistException::ERROR_CODE_GET_RECIPIENTS_FAILED,
);
}
return array_map(function($subscriber) {
return $subscriber['mail'];
}, $result['_data']);
}
/**
* Create the email address via dovecot and the mailing list via mlmmj.
*
* @param string $mailinglistEmail
* @param array $options
*
* @return void
*
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
*/
function createMailingList(string $mailinglistEmail, array $options): void {
// Create email address via Dovecot API
$username = explode('@', $mailinglistEmail)[0];
$dovecotCurl = $this->prepareDovecotCurl('list/' . $username);
curl_setopt($dovecotCurl, CURLOPT_CUSTOMREQUEST, 'PUT');
$dovecutResult = json_decode(curl_exec($dovecotCurl), TRUE);
// Check dovecot result (ignore 409 for already existing email addresses)
$statusCode = curl_getinfo($dovecotCurl, CURLINFO_HTTP_CODE);
if ($statusCode !== 201 && $statusCode !== 409) {
$message = "Could not create email address for '$mailinglistEmail'";
if (!empty($dovecutResult['Message'])) {
$message .= ': ' . $dovecutResult['Message'];
}
throw new MailinglistException(
$message,
MailinglistException::ERROR_CODE_DOVECOT_CREATE_EMAIL_ADDRESS_FAILED,
);
}
// Create mailing list via mlmmj API
$mlmmjCurl = $this->prepareMlmmjCurl($mailinglistEmail);
curl_setopt($mlmmjCurl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($mlmmjCurl, CURLOPT_POSTFIELDS, http_build_query($options));
$mlmmjResult = json_decode(curl_exec($mlmmjCurl), TRUE);
// Check mlmmj result
if (!is_array($mlmmjResult) || $mlmmjResult['_success'] !== TRUE) {
$message = "Could not create mailinglist '$mailinglistEmail'";
if (!empty($mlmmjResult['_msg'])) {
$message .= ': ' . $mlmmjResult['_msg'];
}
throw new MailinglistException(
$message,
MailinglistException::ERROR_CODE_CREATE_MAILING_LIST_FAILED,
);
}
}
/**
* Update a mailing list.
*
* @param string $mailinglistEmail
* @param array $options
*
* @return void
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
*/
function updateMailingList(string $mailinglistEmail, array $options): void {
$curl = $this->prepareMlmmjCurl($mailinglistEmail);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($options));
$result = json_decode(curl_exec($curl), TRUE);
// Check result
if (!empty($result['_success']) && $result['_success'] !== TRUE) {
$message = "Could not update mailinglist '$mailinglistEmail'";
if (!empty($result['_msg'])) {
$message .= ': ' . $result['_msg'];
}
throw new MailinglistException(
$message,
MailinglistException::ERROR_CODE_UPDATE_MAILING_LIST_FAILED
);
}
}
/**
* Update the subscribers of a mailing list.
*
* @param string $mailinglistEmail
* @param ?array $recipientsToAdd
* @param ?array $recipientsToRemove
*
* @return void
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
*/
function updateSubscribers(
string $mailinglistEmail,
array $recipientsToAdd = NULL,
array $recipientsToRemove = NULL,
): void {
$query = [];
if ($recipientsToAdd) {
$query['add_subscribers'] = implode(',', $recipientsToAdd);
}
if ($recipientsToRemove) {
$query['remove_subscribers'] = implode(',', $recipientsToRemove);
}
$curl = $this->prepareMlmmjCurl($mailinglistEmail . '/subscribers');
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($query));
$result = json_decode(curl_exec($curl), TRUE);
// Check result
if (!empty($result['_success']) && $result['_success'] !== TRUE) {
throw new MailinglistException(
"Could not update subscribers for mailinglist '$mailinglistEmail'",
MailinglistException::ERROR_CODE_UPDATE_SUBSCRIBERS_FAILED,
);
}
}
/**
* Delete a mailing list and its email address.
*
* @throws \Civi\Mailinglistsync\Exceptions\MailinglistException
*/
function deleteMailingList(string $mailinglistEmail): void {
// Delete mlmmj mailing list
$mlmmjCurl = $this->prepareMlmmjCurl($mailinglistEmail);
curl_setopt($mlmmjCurl, CURLOPT_CUSTOMREQUEST, 'DELETE');
$mlmmjResult = json_decode(curl_exec($mlmmjCurl), TRUE);
// Check mlmmj result
if (!empty($mlmmjResult['_success']) && $mlmmjResult['_success'] !== TRUE) {
$message = "Could not delete mailinglist '$mailinglistEmail'";
if (!empty($mlmmjResult['_msg'])) {
$message .= ': ' . $mlmmjResult['_msg'];
}
throw new MailinglistException(
$message,
MailinglistException::ERROR_CODE_DELETE_MAILING_LIST_FAILED,
);
}
// Delete dovecot email address
$username = explode('@', $mailinglistEmail)[0];
$dovecotCurl = $this->prepareDovecotCurl('list/' . $username);
curl_setopt($dovecotCurl, CURLOPT_CUSTOMREQUEST, 'DELETE');
$dovecotResult = json_decode(curl_exec($dovecotCurl), TRUE);
// Check dovecot result (ignore 404 for non-existing email addresses)
$statusCode = curl_getinfo($dovecotCurl, CURLINFO_HTTP_CODE);
if ($statusCode === 404) {
$message = "Email '$mailinglistEmail' does not exist in Dovecot";
throw new MailinglistException(
$message,
MailinglistException::ERROR_CODE_DELETE_EMAIL_ADDRESS_FAILED,
);
}
if ($statusCode !== 200) {
$message = "Could not delete email address for '$mailinglistEmail'";
if (!empty($dovecotResult['Message'])) {
$message .= ': ' . $dovecotResult['Message'];
}
throw new MailinglistException(
$message,
MailinglistException::ERROR_CODE_DOVECOT_CREATE_EMAIL_ADDRESS_FAILED,
);
}
}
}