hook_civicrm_initiators¶
Summary¶
This hook lists the ways a user can obtain an API key for a record that connects to a remote service, such as starting an OAuth "Authorization Code" grant.
Description¶
Some records hold credentials for a remote service. Rather than asking the user to paste an API key, an extension can offer an initiator: a widget (typically a "Connect to ..." button) that fetches the key for them. The canonical initiator starts an OAuth2 authorization grant, but the hook is not tied to OAuth.
Core calls the hook through Civi\Connect\Initiators::create(), which sorts the results by title and fills in computed properties. Each initiator's render callback is then called to add its markup to a region on the form.
The $context['for'] value identifies the kind of record. Currently supported:
for |
Where | Other $context keys |
|---|---|---|
PaymentProcessor |
"Edit Payment Processor" form, once for the live and once for the test credentials | payment_processor_type, payment_processor_id, is_test |
MailSettings (6.20+) |
"Edit Mail Account" form, for an existing account | mail_settings_id |
The oauth-client extension implements this hook for:
- payment processors whose OAuth provider is tagged
PaymentProcessorType:{TYPE_NAME}. SeeCivi\OAuth\OAuthPaymentProcessorTag. - mail accounts, using any active client whose provider declares a
mailSettingsTemplate. SeeCivi\OAuth\OAuthMailSettingsTag.
Describing an existing connection¶
From CiviCRM 6.20, an initiator can also report on a connection the record already has, so the form can show its status instead of only offering to start a new one. Set is_connected on the initiator that owns the connection, along with any of status_message, status_severity, manage_url and managed_fields (see Parameters).
The "Edit Mail Account" form uses all of these. When an initiator reports a connection, the form shows status_message in an alert coloured by status_severity (default success), links to manage_url, and renders the initiators' widgets under a collapsed "Re-connect" option. When nothing is connected, it renders them as ways to connect. Once connected, an implementation should usually offer only the initiator that owns the connection, as oauth-client does, so re-connecting cannot silently switch to a different account.
A form that supports this reads it back through Civi\Connect\Initiators:
getConnected(): arrayreturns the first initiator withis_connected, or[]if there is none.getManagedFields(): arrayreturns that initiator'smanaged_fields. These are fields the connection supplies at runtime (e.g. a password replaced by an OAuth access token), so the form hides them and leaves their stored values untouched on save.
Availability¶
CiviCRM 6.17+. The connection-status properties were added in 6.20.
Definition¶
hook_civicrm_initiators(array $context, array &$available, ?string &$default)
This event supports a targeted alias, hook_civicrm_initiators::{$context['for']} (e.g. hook_civicrm_initiators::PaymentProcessor), so a Symfony listener can subscribe to one kind of record only.
Parameters¶
array $context: describes the record that needs an API key.for: string, required. The kind of record, e.g.PaymentProcessor.payment_processor_type: string. ForPaymentProcessor, the processor type'sname, e.g.Stripe.payment_processor_id: int|null. ForPaymentProcessor, the record's ID (NULLwhen adding a new processor).is_test: bool. ForPaymentProcessor, whether these are the test credentials.mail_settings_id: int. ForMailSettings, the record's ID.
array &$available: the list of initiators. Add an item keyed by a unique symbolic name, with these properties:title: string. The name shown to the user.render: callable. Adds the widget to the form. Signature:function(CRM_Core_Region $region, array $context, array $initiator): void.is_connected: bool, optional (6.20+). Whether the record is currently connected through this initiator.status_message: string, optional (6.20+). Describes the current connection, e.g. "Connected as foo@example.org".status_severity: string, optional (6.20+). One ofsuccess,warningordanger.manage_url: string, optional (6.20+). The screen that administers this connection.managed_fields: string[], optional (6.20+). Fields the connection supplies at runtime, which the form should hide and not overwrite.- After the hook runs, core also sets
name(the array key) andis_default.
string|null &$default: the key of the suggested initiator, if any.
Returns¶
null
Example¶
use CRM_Myext_ExtensionUtil as E;
function myext_civicrm_initiators(array $context, array &$available, ?string &$default) {
if ($context['for'] !== 'PaymentProcessor' || $context['payment_processor_type'] !== 'MyProcessor') {
return;
}
$available['myprocessor_connect'] = [
'title' => E::ts('My Processor'),
'render' => function (CRM_Core_Region $region, array $context, array $initiator) {
$url = CRM_Utils_System::url('civicrm/myprocessor/connect', [
'id' => $context['payment_processor_id'],
'is_test' => (int) $context['is_test'],
]);
$region->addMarkup(sprintf('<a class="btn btn-xs btn-primary" href="%s">%s</a>',
htmlentities($url), E::ts('Connect to %1', [1 => $initiator['title']])));
},
];
$default ??= 'myprocessor_connect';
}
Or, as a targeted listener:
public static function getSubscribedEvents(): array {
return [
'&hook_civicrm_initiators::PaymentProcessor' => ['onPaymentInitiators', 0],
];
}
public function onPaymentInitiators(array $context, array &$available, &$default): void {
// ...
}
Reporting an existing connection¶
This listener offers a connection for mail accounts and, once a credential exists, reports on it. password is a managed field because the extension supplies it when the mailbox is polled, so the form hides it and saving the form does not overwrite it.
public static function getSubscribedEvents(): array {
return [
'&hook_civicrm_initiators::MailSettings' => ['onMailInitiators', 0],
];
}
public function onMailInitiators(array $context, array &$available, &$default): void {
$token = $this->findToken($context['mail_settings_id']);
$available['mymail'] = [
'title' => E::ts('My Mail Service'),
'render' => [$this, 'renderConnectButton'],
];
if ($token) {
$expired = $token['expires'] < CRM_Utils_Time::time();
$available['mymail'] += [
'is_connected' => TRUE,
'status_severity' => $expired ? 'warning' : 'success',
'status_message' => $expired
? E::ts('The connection has expired. Mail will not be collected until you re-connect.')
: E::ts('Connected as %1.', [1 => $token['owner']]),
'manage_url' => (string) Civi::url('backend://civicrm/mymail/settings'),
'managed_fields' => ['password'],
];
}
}
Tip
The form calls the hook on every page view, so read stored state rather than contacting the remote service.