Here are the essentials of the working example.
Custom plugin
For the project, I added a custom plugin to display custom pages, add form fields, add additional JavaScript etc. I stripped all that stuff out in this example, so there’s only the bit of the plugin that calls the newsletter subscription service. This uses the hook @NateWr mentioned above.
<?php
import('lib.pkp.classes.plugins.GenericPlugin');
class CustomPlugin extends GenericPlugin {
/**
* @copydoc Plugin::register()
*/
public function register($category, $path, $mainContextId = NULL) {
$success = parent::register($category, $path, $mainContextId);
if ($success && $this->getEnabled()) {
[...]
// Subscribe new users automatically to newsletter.
HookRegistry::register('registrationform::execute', [$this, 'subscribeNewUserToNewsletter']);
[...]
}
return $success;
}
[...]
/**
* Adds an automatic newsletter subscription for a new user.
*/
public function subscribeNewUserToNewsletter(string $hookName, array $args)
{
$form = $args[0];
$email = $form->user->getData('email');
$service = Services::get('newsletter');
$service->subscribe($email);
}
[...]
}
Newsletter subscription service
Users registering with the platform should be automatically added to a newsletter mailing list handled by Mailchimp. I wrote a service class for this because this is not the only point where this can happen and the service will be reused elsewhere.
Note that most of the class methods exist to get various ids for the subscriber and the list in order to set the GDPR consent. (Users of the platform are informed by a lengthy legal disclaimer on the registration page.)
<?php
namespace APP\Services;
use GuzzleHttp\Client;
import('lib.pkp.classes.config.Config');
class NewsletterService {
protected $audience,
$authData,
$endpoint,
$client,
$marketingPermissionId;
public function __construct() {
$this->endpoint = \Config::getVar('newsletter', 'endpoint');
$this->audience = \Config::getVar('newsletter', 'audience');
$this->marketingPermissionId = \Config::getVar('newsletter', 'marketing_permission_id');
$this->authData = ['anystring', \Config::getVar('newsletter', 'key')];
$this->client = new Client();
\HookRegistry::register('Newsletter::subscribe', [$this, 'subscribe']);
}
protected function getSubscriberId($email): string
{
$fullEndpoint = "{$this->endpoint}/search-members";
$response = $this->client->get(
$fullEndpoint,
[
'auth' => $this->authData,
'http_errors' => FALSE,
'query' => [
'query' => $email
]
]
);
$status = $response->getStatusCode();
if ($status == 200) {
$body = json_decode($response->getBody(), TRUE);
return empty($body) ? '' : $body['exact_matches']['members'][0]['id'];
} else {
return '';
}
}
protected function setEmailMarketingPermission($subscriberId): bool
{
$fullEndpoint = "{$this->endpoint}/lists/{$this->audience}/members/{$subscriberId}";
$response = $this->client->patch(
$fullEndpoint,
[
'auth' => $this->authData,
'http_errors' => FALSE,
'json' => [
'marketing_permissions' => [
[
"marketing_permission_id" => $this->marketingPermissionId,
"enabled" => TRUE
]
],
]
]
);
$status = $response->getStatusCode();
return $status == 200;
}
public function subscribe($email): bool
{
$status = NULL;
$fullEndpoint = "{$this->endpoint}/lists/{$this->audience}/members";
$response = $this->client->post(
$fullEndpoint,
[
'auth' => $this->authData,
'http_errors' => FALSE,
'json' => [
'email_address' => $email,
'status' => 'subscribed'
]
]
);
$status = $response->getStatusCode();
// If status if 400, check the error details.
// If the user is already on the list, we treat this as a success for the frontend.
if ($status == 400) {
$body = json_decode($response->getBody(), TRUE);
return $body['title'] == 'Member Exists';
}
// Something else, dunno, like Mailchimp API completely down?
if ($status != 200) return FALSE;
// Bazinga, we subscribed the user.
// However, we still need to set the GDPR email marketing permission.
// We use this strategy to do that: https://checkmysite.io/blog/passing-gpdr-preferences-via-the-mailchimp-api for details.
$subscriberId = $this->getSubscriberId($email);
if (empty($subscriberId)) return FALSE;
return $this->setEmailMarketingPermission($subscriberId);
}
}
Note that in this case, we know the numeric id for email marketing permission for the Mailchimp audience and have it set in our config file. If you need to work with variable audiences or want to know more about this, check out Passing GDPR preferences via the MailChimp API | check_my_site, from where I adopted this solution.
Registering the service
The service needs to be registered with DIC in order to be available anywhere. This happens in /classes/services/OJSServiceProvider.inc.php
where I added
// Newsletter service.
$pimple['newsletter'] = function() {
return new NewsletterService();
};
(Remember to look out for potential merge conflicts with later OJS updates, though, and resolve them accordingly.)
If this perhaps helps anyone in the future, great.