Hooks indicating user registration?

I’m currently setting up an OJS 3.3.1 instance and the client would like to add some features to the user registration process. My plan was to handle that via a generic plugin that I already set up for other tasks and use a hook for this (hooks worked like a charm with Schema::get::publication and Form::config::before).

But as far as I can see, there are no HookRegistry::call instances in the PKP library that would indicate user registration. I would have expected to find something like User::add but didn’t find anything.

Is that correct?

If intervention with user registration is not possible via hooks, are there other recommended ways to do so?

Hi @mechanicalbutterfly,

The user registration process isn’t yet on the new schema and forms architecture, so the kinds of hooks you found with publications aren’t available there. However, there may be other hooks available. If you can describe what you want to accomplish, I can probably recommend some techniques to do this from a plugin.

Great, thanks for the quick reply.

Every user registering with our OJS instance is supposed to be subscribed to a email newsletter (there’s only one journal running on it, so no need to distinguish between journals). They get the whole legal disclaimer and how to unsubscribe information as part of the registration template.

The newsletter is handled via Mailchimp and our generic plugin will have to send a subscription request to the Mailchimp API at registration.We have added our own service class to handle the details for all of this.

Ideally I now would need to know when a registration is happening and also a way to get the email address for the user who registered so invoke the newsletter service class to do its thing.

If there’s a way to achieve this without hooks, that would be amazing. Otherwise I’ll tell the client that this is not an option yet.

Ok great, that should be doable. Although this is on our “old” form system, there are hooks there too. We’re moving away from this pattern so we haven’t documented them as much.

In this case, you’d want to hook into the execute method of the RegistrationForm. You should be able to do this with the following hook:

HookRegistry::register('registrationform::execute', function($hookName, $args) {
    $form = $args[0];
    $email = $form->user->getData('email');
    // ...
});

If that’s not working for you, you can investigate the code that fires the hook by looking in RegistrationForm::execute() and Form::execute().

This hook fires immediately before the user is inserted into the database. So if you try to get the user from the database, that won’t work. Hopefully you have everything you need in the user object there.

works perfectly well and I can access the email address, which is all I need for our use case.

Thank you so much.

1 Like

If you have the time to post back your fully-working solution (without any secret API keys), I’m sure our community would love to have the example to work from.

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.

Wow, that’s great, thanks @mechanicalbutterfly!

I’m not certain, but I think that you can register a new service in the dependency injection container from your plugin. If I’m right, you would first create your own service provider:

use Pimple\Container;

class MyPluginServiceProvider implements \Pimple\ServiceProviderInterface
{
    public function register(Container $pimple)
    {
        // Newsletter service.
        $pimple['newsletter'] = function() {
            return new NewsletterService();
        };
    }
}

Then register it like this:

Services::register(new MyPluginServiceProvider());

@NateWr

Splendid, it works. Many thanks, that way I don’t have to worry about merge conflicts later.