Domain Forwarding Hooks

2 Aufrufe Markdown

The eight hooks carrying an arriving visitor and post somewhere else, plus the add-on purchase opening those surfaces.

Overview

Forwarding comes in two kinds and both sit behind the same add-on: one carries whoever arrives at the address, the other carries the post.

They differ in number. An address forward is one per domain, while mail forwarding is a list. That difference shows in the parameters: one carries a single record, the other source and target pairs.

Reference

Stopping an address forward

gatedomain.url_forwarding_change
ClientDomains set and clear

Runs before the forward is set at the provider or cleared from it.

Parameters 3
$servicearrayThe domain service record.
$fwdarrayThe forward: protocol (http/https), target (the target without a scheme, trailing slash trimmed), method (301/302). On a clear it arrives as an empty array.
$verbstringsave or cancel.
Return 1
stringA non-empty string stops the action; the text is thrown as the error and the provider is never called.
Listener
Hook::add('gate:domain.url_forwarding_change', 10,
    function ($service, $fwd, $verb) {
        if ($verb === 'cancel') return null;          // leave a clear alone

        // A permanent forward is cached by browsers; refuse an insecure target.
        if (($fwd['protocol'] ?? '') !== 'https' && (int) ($fwd['method'] ?? 0) === 301)
            return 'A permanent forward wants a secure address.';

        return null;
    });

Following an address forward

actiondomain.url_forwarding_changed
ClientDomains on success only

Runs after the forward was set at the provider or cleared from it.

Parameters 3
$servicearrayThe domain service record.
$fwdarrayThe forward that was set. On a clear it is an empty array, so do not read a target from it.
$verbstringThe action that ran.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:domain.url_forwarding_changed', 10,
    function ($service, $fwd, $verb) {
        Audit::note('forwarding', $service['name'] ?? '',
            $verb === 'cancel' ? 'cleared' : ($fwd['target'] ?? ''));
    });

Changing the forward that was read

filterdomain.url_forwarding
Hook::runRefs by reference

Runs after the forward read from the provider was normalised, before it reaches the screen.

Parameters 2
$forwardingarrayrefA single record: active, protocol, method, domain (target without a scheme), url (the full address). active decides the screen's "is there a forward" state and url pre-fills the target box.
$servicearrayThe domain service record.
Return 1
voidThe value changes by reference; the return is not read.
Listener
Hook::add('filter:domain.url_forwarding', 10, function (&$forwarding, $service) {
    // Keep active and url in step: never one filled with the other empty.
    if (empty($forwarding['url'])) $forwarding['active'] = false;
});

Stopping a mail forward

gatedomain.email_forward_save
ClientDomains create and delete

Runs before a mail forwarding rule is created at the provider or deleted from it.

Parameters 4
$servicearrayThe domain service record.
$prefixstringThe local part on the domain. Only the part arrives, not the full address: info means [email protected].
$targetstringThe target box. It can arrive empty on a delete.
$verbstringcreate or delete.
Return 1
stringA non-empty string stops the action; the text is thrown as the error and the provider is never called.
Listener
Hook::add('gate:domain.email_forward_save', 10,
    function ($service, $prefix, $target, $verb) {
        if ($verb === 'delete') return null;

        // Forwarding the control boxes outward opens the door to a takeover.
        if (in_array(strtolower($prefix), ['admin', 'postmaster', 'hostmaster'], true))
            return 'That local part cannot be forwarded.';

        return null;
    });

Following a mail forward

actiondomain.email_forward_saved
ClientDomains on success only

Runs after the rule landed at the provider.

Parameters 4
$servicearrayThe domain service record.
$prefixstringThe source local part.
$targetstringThe target box.
$verbstringThe action that ran.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:domain.email_forward_saved', 10,
    function ($service, $prefix, $target, $verb) {
        MailAudit::rule($verb, $prefix . '@' . ($service['name'] ?? ''), $target);
    });

Changing the forwarding list

filterdomain.email_forwards
Hook::runRefs by reference

Runs after the rules read from the provider were normalised.

Parameters 2
$forwardsarrayrefThe rule list: every row carries identity, prefix, source (prefix@domain) and target. A delete sends those three back, so do not drop them.
$servicearrayThe domain service record.
Return 1
voidThe value changes by reference; the return is not read.
Listener
Hook::add('filter:domain.email_forwards', 10, function (&$forwards, $service) {
    // KEEP identity, prefix and target: a delete targets the rule with them.
    usort($forwards, fn ($a, $b) => strcmp($a['prefix'] ?? '', $b['prefix'] ?? ''));
});

Stopping an add-on purchase

gatedomain.addon_purchase
ClientDomains before the invoice

Runs before the customer buys the add-on, with no record created yet.

Parameters 2
$servicearrayThe domain service record.
$addonKeystringThe add-on being bought: dns-manage, whois-privacy or forwarding. The key uses hyphens; an underscore matches nothing.
Return 1
stringA non-empty string stops the purchase; it reaches the customer as the error and no record is created.
Listener
Hook::add('gate:domain.addon_purchase', 10, function ($service, $addonKey) {
    // Selling a yearly add-on on a name about to expire is not right.
    if (Acme::daysLeft($service) < 30)
        return 'The domain is about to expire; renew it first.';

    return null;
});

Following an add-on order

actiondomain.addon_ordered
ClientDomains the invoice is unpaid

Runs after the order was placed. The add-on is waiting here: it goes live once its invoice is paid.

Parameters 3
$servicearrayThe domain service record.
$addonKeystringThe add-on that was ordered.
$contextarrayThe order context: invoice_id (the unpaid invoice raised), addon_id (the waiting record), addon_name.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:domain.addon_ordered', 10,
    function ($service, $addonKey, $context) {
        // The add-on is NOT live yet: it opens once the invoice is paid.
        Crm::pendingAddon((int) ($context['invoice_id'] ?? 0), $addonKey);
    });

Pitfalls

On a clear the forward arrives empty

While an address forward is being cleared, the hook hands you an empty array as the second parameter. A listener reaching for the old target finds nothing and quietly does the wrong thing. Where you need to know what was cleared, read it at the gate and keep it.

The mail hook hands you the local part

The source parameter is the local part alone, not a full address. You add the domain yourself, taking it from the service record. A listener expecting a full address matches nothing.

The order hook is not the moment the add-on opens

When an add-on order is placed the record is waiting and its invoice unpaid. Opening the surface here means handing over a service nobody paid for. The real opening happens on the payment side hooks.

The add-on key uses hyphens

The keys are dns-manage, whois-privacy and forwarding. Writing one with an underscore matches silently nothing: your listener runs and your condition never holds.

War das hilfreich?

Vielen Dank für Ihre Rückmeldung!

Brauchen Sie weitere Hilfe?

Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.