Customer Account and Contact Hooks

270 visitas Markdown

The fourteen hooks over the customer account and site contact: API keys, the profile, currency, the contact form and the newsletter.

Overview

The traces a customer or visitor leaves on the site live here: API keys, the profile, the currency preference, the contact form and the newsletter.

In most of these hooks the data comes from an unverified source: the visitor filling in the contact form, the address signing up, the bot requesting a page that does not exist. Check it yourself before carrying it to an outside service.

Reference

Following API key permissions changing

actionclient.api_key_updated
AccountApiKeys the new permissions only

Runs after the permissions of an API key change. A widening of permissions is a security event.

Parameters 3
$owner_idintThe owner of the key.
$idintThe id of the key record.
$permsarrayThe new permission set. ? The old set is not passed: to answer "did permissions widen" you must have kept the previous state yourself.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.api_key_updated', 10, function ($owner_id, $id, $perms) {
    // The old set is not passed: keep it yourself to compare.
    Acme::recordScopes($id, $perms);
});

Following an API key being regenerated

actionclient.api_key_regenerated
AccountApiKeys the record stays

Runs after the value of an API key is regenerated. The record stays, the credential changes: calls made with the old value no longer pass.

Parameters 2
$owner_idintThe owner of the key.
$idintThe record id; it is the same after the regeneration.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.api_key_regenerated', 10, function ($owner_id, $id) {
    // The old value no longer passes: refresh your own cache.
    Acme::invalidateCachedKey($id);
});

Following an API key being deleted

actionclient.api_key_deleted
AccountApiKeys the record is gone

Runs after an API key is deleted.

Parameters 2
$owner_idintThe owner of the key.
$idintThe id of the deleted record; the row is gone.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.api_key_deleted', 10, function ($owner_id, $id) {
    Acme::revokeIntegration($id);
});

Following a customer profile being updated

actionclient.profile_updated
AccountProfile the account, not the login

Runs after a customer profile is updated.

Parameters 3
$uidintThe id of the account whose profile changed. ? It is not the id of whoever signed in: a sub-user with the right permission can update another account’s profile. If you record the actor, read it from the session separately.
$data_updatesarrayThe fields written to the main record, new values only: name, phone, language, currency.
$info_updatesarrayWhat was written to the extra record: customer kind, company details, address fields.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.profile_updated', 10, function ($uid, $data_updates, $info_updates) {
    // $uid is the ACCOUNT id, not the login.
    Acme::syncCrm($uid, $data_updates + $info_updates);
});

Following a currency change

actionclient.currency_changed
website empty for a guest

Runs when a visitor changes the site currency. The change is already applied.

Parameters 3
$new_cidintThe new currency.
$old_cidintThe previous currency.
$memberarrayThe session of the signed-in member. It is empty for a guest: this hook runs for visitors who are not signed in too.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.currency_changed', 10, function ($new_cid, $old_cid, $member) {
    // For a guest the member data arrives EMPTY.
    if (!$member) return;

    Acme::rememberCurrency((int) ($member['id'] ?? 0), $new_cid);
});

Following a page that was not found

actionclient.page_not_found
website bot traffic included

Runs when a requested address does not resolve. It is the most direct way to find broken links.

Parameters 1
$urlstringThe full address requested. ? This hook also runs on addresses tried by scanning bots, and on a site those can outnumber human traffic. A listener writing a record on every call fills its own table with noise.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.page_not_found', 10, function ($url) {
    // Bot traffic can outnumber human traffic: filter it.
    if (Acme::looksLikeScanner($url)) return;

    Acme::noteBrokenLink($url);
});

Following a contact form submission

actionclient.contact_submitted
ClientContact comes from a visitor

Runs after a visitor submits the contact form.

Parameters 6
$message_idintThe id of the message record created.
$full_namestringThe sender’s name.
$emailstringThe sender’s address.
$phonestringTheir phone; it can be empty.
$messagestringThe message body, stripped of markup.
$ipstringTheir network address. All of these come from a visitor and none are verified: check them yourself before sending them to an outside service.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.contact_submitted', 10,
    function ($message_id, $full_name, $email, $phone, $message, $ip) {
        // All of these come FROM A VISITOR and are unverified.
        Acme::pushToCrm($message_id, $email, $message);
    });

Following a contact message being answered

actionclient.contact_message_replied
ClientContact after the reply

Runs after an administrator answers a contact message.

Parameters 2
$messagearrayThe original message answered.
$admin_messagestringThe reply text sent.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.contact_message_replied', 10, function ($message, $admin_message) {
    Acme::closeCrmCase((int) ($message['id'] ?? 0));
});

Following a message being reported as spam

actionclient.contact_message_spam_reported
ClientContact two separate flags

Runs after a contact message is reported as spam. The two flags beside it say how far the action went.

Parameters 3
$messagearrayThe message moved to spam, with its address and phone.
$block_emailsintWhether the address and phone went onto the block list.
$report_spamintWhether the network address was blocked. The two flags are independent: one can be set and the other not. A listener treating them as one reports the wrong thing.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.contact_message_spam_reported', 10,
    function ($message, $block_emails, $report_spam) {
        // The two flags are independent.
        if ($report_spam) Acme::shareBadAddress($message['ip'] ?? '');
    });

Stopping a message becoming a ticket

gateclient.contact_message_to_ticket
ClientContact before the conversion

Runs before a contact message is turned into a support ticket.

Parameters 3
$messagearrayThe message to be converted.
$departmentintThe target department.
$staffintThe staff member to assign; a zero means none.
Return 1
string|nullA non-empty text blocks the operation and is shown to the visitor as the error. An empty return lets it carry on.
Listener
Hook::add('gate:client.contact_message_to_ticket', 10,
    function ($message, $department, $staff) {
        if (Acme::blockedSender($message['email'] ?? '')) return 'No ticket can be opened for this sender.';

        return null;
    });

Changing the contact page data

filterclient.contact_page_data
ClientContact added keys reach the template

Runs before the contact page is shown. The offices and support hours are already resolved for the active language.

Parameters 2
$page_dataarrayby linkThe page data. Every key you add becomes a template variable: you can use it directly in your theme.
$langstringby linkThe active language.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:client.contact_page_data', 10, function (&$page_data, &$lang) {
    // A key you add becomes a template variable.
    $page_data['acme_map'] = Acme::mapEmbed($lang);
});

Stopping a newsletter sign-up

gateclient.newsletter_subscribe
ClientNewsletter before the record

Runs before an address joins the newsletter list. The place to keep disposable addresses out.

Parameters 2
$emailstringThe candidate address, lower-cased with its format checked.
$langstringThe site language at the moment of signing up.
Return 1
string|nullA non-empty text blocks the operation and is shown to the visitor as the error. An empty return lets it carry on.
Listener
Hook::add('gate:client.newsletter_subscribe', 10, function ($email, $lang) {
    if (Acme::disposableDomain($email)) return 'This address is not accepted.';

    return null;
});

Following a newsletter sign-up

actionclient.newsletter_subscribed
ClientNewsletter the language is kept too

Runs after an address joins the newsletter list.

Parameters 3
$emailstringThe subscriber address.
$langstringThe language at sign-up. It is kept and decides the language of bulk mailings: get it wrong and the subscriber receives an email they cannot read.
$addedintThe id of the record created.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.newsletter_subscribed', 10, function ($email, $lang, $added) {
    // The language decides the content language of bulk mailings.
    Acme::syncMailingList($email, $lang);
});

Following a newsletter unsubscribe

actionclient.newsletter_unsubscribed
ClientNewsletter through a link

Runs after an address leaves the list. It happens through the link in the email, with no sign-in needed.

Parameters 1
$emailstringThe address removed from the list.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.newsletter_unsubscribed', 10, function ($email) {
    // Remove it from your own list too, or mailings carry on.
    Acme::dropFromMailingList($email);
});

Pitfalls

The id in the profile hook is the account id

The id handed to the update hook belongs to the account being changed, not to whoever made the change. A sub-user with the right permission can update another account’s profile. If you record "who did it", read it from the session separately.

The not-found hook fills with bot traffic

Scanning bots try addresses that do not exist all day, and on a site those requests easily outnumber human ones. A listener writing a record on every call inflates its own table and buries the real broken links. Put a filter in front of it.

¿Le ha resultado útil?

¡Gracias por sus comentarios!

¿Todavía necesita ayuda?

Nuestro equipo de soporte está disponible las 24 horas para ayudarle con lo que no encuentre aquí.