diff --git a/docs/hooks/index.md b/docs/hooks/index.md index 723f4998c244fe00d857a858d7b8e0bab98ca24d..1c97aaae2d5c5eccbcab2e7933e75b939f00fb7d 100644 --- a/docs/hooks/index.md +++ b/docs/hooks/index.md @@ -1,20 +1,20 @@ ## Overview -Hooks are a common way to extend systems. Let's say you want to send a -message to someone in your organization every time a contact is created. An -easy way to do this would be to insert code to send the message in the -CiviCRM core code right where the contact is created. However, as soon as we -upgrade to a newer version all this code will be overwritten. This is where +Hooks are a common way to extend systems. Let's say you want to send a +message to someone in your organization every time a contact is created. An +easy way to do this would be to insert code to send the message in the +CiviCRM core code right where the contact is created. However, as soon as we +upgrade to a newer version all this code will be overwritten. This is where hooks come in to save the day. -At key points in processing - for example saving -something to the database - CiviCRM checks to see whether you've "hooked in" +At key points in processing - for example saving +something to the database - CiviCRM checks to see whether you've "hooked in" some custom code, and runs any valid code it finds. -Hooks allow you to do this by defining a function with a specific name and -adding it to your organisation's CiviCRM installation. The name of the -function indicates the point at which CiviCRM should call it. CiviCRM looks -for appropriate function names and calls the functions whenever it performs +Hooks allow you to do this by defining a function with a specific name and +adding it to your organisation's CiviCRM installation. The name of the +function indicates the point at which CiviCRM should call it. CiviCRM looks +for appropriate function names and calls the functions whenever it performs the indicated operations. Hooks are a powerful way to extend CiviCRM's functionality, incorporate @@ -23,40 +23,43 @@ Many CiviCRM developers find themselves using them in nearly every customization project. !!! tip - A good test for whether or not to use a hook is to ask yourself whether - what you're trying to do can be expressed with a sentence like this: "I want + A good test for whether or not to use a hook is to ask yourself whether + what you're trying to do can be expressed with a sentence like this: "I want X to happen every time someone does Y." -## Hook naming +## Usage -The names of all the hook functions follow a pattern: - -`EXTENSION-NAME_civicrm_HOOK-NAME` +There are two ways to use hooks: the traditional method and the Symfony events method. -The two parts that you'll be changing are: +### Traditional method -1. ==EXTENSION-NAME==: Depending on your installation this can change. In -[Drupal](/hooks/setup/drupal.md) it will be the name of your extension. In -[Joomla](/hooks/setup/joomla.md) it will be "joomla". -2. ==HOOK-NAME==: This is the name of the event you want to hook into, for -example `validateForm`. You'll need to check the reference for a full list of -hooks that are available. +The traditional method of using a hook is to create a function with a specific name such as: -So if you were creating an extension called `superextension` in Drupal and -wanted to do something right after your extension was installed then your -function would be: +```php +function myextension_civicrm_buildForm($formName, &$form) { + // do something +} +``` + +This works well in many cases but has its limitations. For example, if two extensions call the same hook there is no way to determine which runs first. + +For details, see [Tradtional Hooks](/hooks/usage/extension.md) + +### Symfony method + +A newer method that provides greater flexibility is to use Symfony events. + +For example: ```php -<?php -function superextension_civicrm_install() { - // do something here +Civi::dispatcher()->addListener('hook_civicrm_buildForm', "myextension_buildForm", $priority); + +function myextension_buildForm($event) { + // do something } ``` - -!!! tip - To see what the parameters for your new function should be just check the - documentation, in this case - [hook_civicrm_install](/hooks/hook_civicrm_install.md) + +For more details see [Hooks with Symfony](/hooks/usage/symfony.md) ## Targeting Certain Events @@ -66,7 +69,7 @@ object of any type (contact, tag, group, activity, etc.). But usually, you want to launch an action only for a specific type of entity. So a hook generally starts with a test on the type of entity or type of action. -For instance, if you want to act only when an address was edited, start your +For instance, if you want to act only when an address was edited, start your `civicrm_post` hook with: ```php @@ -75,183 +78,33 @@ if ($objectName != "Address" || $op != "edit") { } ``` -## Pitfalls of hooks +## Pitfalls of Hooks Because you have little control over what CiviCRM passes to your hook function, it is very helpful to look inside those objects (especially `$objectRef`) to make sure you're getting what you expect. -A good debugger is indispensable here. See the +A good debugger is indispensable here. See the [page on debugging](/tools/debugging.md) for more information on setting up a debugger for your development environment. !!! warning - From time to time an new release of the CiviCRM can deprecate or change + From time to time a new release of CiviCRM can deprecate or change certain hooks. Keep this in mind when upgrading, and make sure you - check the release notes before upgrading. + check the release notes before upgrading. + +## Packaging Hooks + +Hooks are packaged in CMS-agnostic [extensions](/extensions/index.md). ## Organizing Your Hooks -You may find that some of your hooks target a lot of different cases. Such +You may find that some of your hooks target a lot of different cases. Such hooks can quickly get out of control, and maintaining them can be a nightmare. -You might find it helpful when implementing a hook to delegate certain -operations to different functions instead of lumping it all in together in +You might find it helpful when implementing a hook to delegate certain +operations to different functions instead of lumping it all in together in the main hook. -If you're using [Civix](/extensions/civix.md) to create your extension it will -automatically generate wrapper code for your hook. - -For more information you can checkout the README in this -[zip file][wrapper-zip] for setting up an example Drupal module that -illustrates this technique. - -## Examples of using hooks - -In all of these examples, you'll put the code we provide into your -`myhooks.module` file if using Drupal, or the `civicrmHooks.php` file if using -Joomla!. Be sure to upload the file after each change to the appropriate -location on your server to see the new code take effect. - -Because the majority of users currently use CiviCRM with Drupal we'll assume -you're using Drupal for the rest of the example. But don't worry Joomla! users, -the concept is the same and just requires some tweaks to get it working. Have a -look at the [Joomla help](/hooks/setup/joomla.md) for more instructions. - -### Setting Text on a Form - -To implement `hook_civicrm_buildForm` from within the "myextension" extension -you would add the following function to your main .php or .module file (or a -file always included by that script): - -```php -<?php - -function myextension_civicrm_buildForm($formName, &$form) { - // note that form was passed by reference - $form->assign('intro_text', ts('hello world')); - } -``` - -As long as the extension is enabled, this function will be called every time -CiviCRM builds a form. - -### Sending an Email Message When an Individuals Was Edited - -In order to have CiviCRM tell you when an Individual was edited, define the -`civicrm_pre` hook. This lets you see the incoming edits as well as the values -of the existing record, because you may want to include that information in the -email. - -```php -<?php -function myextension_civicrm_pre( $op, $objectName, $objectId, &$objectRef ) { - // Make sure we just saved an Individual contact and that it was edited - if ($objectName != "Individual" || $op != "edit") { - return; - } - - // send the email - $emailSubject = "An Individual was edited"; - $emailBody = sprintf("Someone edited Individual with ID %d\n", $objectId); - $emailRecipient = 'johndoe@example.org'; - - mail( $emailRecipient, $emailSubject, $emailBody ); -} -``` - -### Validating Form Content - -If you have experience with other hook-based systems, you might think that the -`civicrm_pre` hook is the one to use for validations. But this is not the case -in CiviCRM because, even though the `civicrm_pre` hook is called before the -record is saved to the database, you cannot abort the action from this hook. - -This is where form validation hooks come in. When you return true from a -validation hook CiviCRM saves the new or updated record. When you return an -error array instead, CiviCRM aborts the operation and reports your error to -the user. - -```php -<?php - -function myextension_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors) { - - $errors = array(); - - // check we're targeting the right form - if ($formName != 'My_Contact_Form') { - return true; - } - - $firstName = CRM_Utils_Array::value( 'first_name', $fields ); - - // ensure that firstName is present and valid - if (!$firstName) { - $errors['first_name'] = ts( 'First name is a required field' ); - } elseif (strlen($firstName) > 50) { - $errors['first_name'] = ts( 'First name must be less than 50 characters'); - } - - return empty($errors) ? true : $errors; -} -``` - -### Custom mail merge token - -The CiviMail component lets you customize a bulk email message using mail merge -tokens. For instance, you can begin your message with, "Hi, -{recipient.first_name}!" and when John Doe receives it, he'll see, "Hi, John!" -whereas when Suzy Queue receives it, she'll see, "Hi, Suzy!" - -Besides the built-in tokens, you can use a hook to create new custom tokens. -Let's make a new one that will show the largest contribution each recipient has -given in the past. - -```php -<?php -/** - * Implement this hook so we can add our new token to the list of tokens - * displayed to CiviMail users and set the default - * - * @param array $tokens -*/ -function myextension_civicrm_tokens(&$tokens) { - if (isset($tokens['contribution'])) { - return; - } - - $tokens['contribution'] = array('contribution.max' => 'Max Contribution'); -} - -/** - * @param array $details - * The array to store the token values indexed by contactIDs (unless single) - * @param array $contactIDs - * An array of contactIDs - * @param int $jobID - * The jobID if this is associated with a CiviMail mailing. - * @param array $tokens - * The list of tokens associated with the content - * @param string $className - * The top level className from where the hook is invoked - * - * @return null -*/ -function myextension_civicrm_tokenValues(&$details, $contactIDs, $jobID, $tokens, $className) { - - // validate that we're targeting the right event - if ($className != SomeCustomClass::class) { - return; - } - - // fetch the maximum contribution here - foreach ($contactIDs as $contactID) { - $max = my_function_to_get_the_max($contactID); - $details[$contactID]['contribution.max'] = $max; - } -} -``` - - -[wrapper-zip]: http://wiki.civicrm.org/confluence/download/attachments/86213379/callhooks.zip?version=1&modificationDate=1372586243000&api=v2 +If you're using [Civix](/extensions/civix.md) to create your extension it will +automatically generate wrapper code for your hook. diff --git a/docs/hooks/setup/drupal.md b/docs/hooks/usage/drupal.md similarity index 100% rename from docs/hooks/setup/drupal.md rename to docs/hooks/usage/drupal.md diff --git a/docs/hooks/usage/extension.md b/docs/hooks/usage/extension.md new file mode 100644 index 0000000000000000000000000000000000000000..7169a1e19db2976a62356791d3ebd2f5387e6dce --- /dev/null +++ b/docs/hooks/usage/extension.md @@ -0,0 +1,154 @@ +## Getting started + +The names of all the hook functions follow a pattern: + +`EXTENSION-NAME_civicrm_HOOK-NAME` + +1. Review the [list of hooks](/hooks/list.md) and find the hook that matches your need. +1. Read the documentation page for that hook +1. In your extension, create a function replacing `hook_` with your extension name, and with the same signature. + +So if you were creating an extension called `superextension` and +wanted to do something right after your extension was installed then your +function would be: + +```php +function superextension_civicrm_install() { + // do something here +} +``` + +## Examples of using hooks + +In all of these examples, you'll put the code we provide into your +extension. Be sure to upload the file after each change to the appropriate +location on your server to see the new code take effect. + +### Setting Text on a Form + +To implement `hook_civicrm_buildForm` from within the "myextension" extension +you would add the following function to your main `myextension.php` file (or a +file always included by that script): + +```php +function myextension_civicrm_buildForm($formName, &$form) { + // note that form was passed by reference + $form->assign('intro_text', ts('hello world')); + } +``` + +As long as the extension is enabled, this function will be called every time +CiviCRM builds a form. + +### Sending an Email Message When an Individuals Was Edited + +In order to have CiviCRM tell you when an Individual was edited, define the +`civicrm_pre` hook. This lets you see the incoming edits as well as the values +of the existing record, because you may want to include that information in the +email. + +```php +function myextension_civicrm_pre($op, $objectName, $objectId, &$objectRef) { + // Make sure we just saved an Individual contact and that it was edited + if ($objectName != "Individual" || $op != "edit") { + return; + } + + // send the email + $emailSubject = "An Individual was edited"; + $emailBody = sprintf("Someone edited Individual with ID %d\n", $objectId); + $emailRecipient = 'johndoe@example.org'; + + mail( $emailRecipient, $emailSubject, $emailBody ); +} +``` + +### Validating Form Content + +If you have experience with other hook-based systems, you might think that the +`civicrm_pre` hook is the one to use for validations. But this is not the case +in CiviCRM because, even though the `civicrm_pre` hook is called before the +record is saved to the database, you cannot abort the action from this hook. + +This is where form validation hooks come in. When you return true from a +validation hook CiviCRM saves the new or updated record. When you return an +error array instead, CiviCRM aborts the operation and reports your error to +the user. + +```php +function myextension_civicrm_validateForm($formName, &$fields, &$files, &$form, &$errors) { + + $errors = array(); + + // check we're targeting the right form + if ($formName != 'My_Contact_Form') { + return true; + } + + $firstName = CRM_Utils_Array::value( 'first_name', $fields ); + + // ensure that firstName is present and valid + if (!$firstName) { + $errors['first_name'] = ts( 'First name is a required field' ); + } elseif (strlen($firstName) > 50) { + $errors['first_name'] = ts( 'First name must be less than 50 characters'); + } + + return empty($errors) ? true : $errors; +} +``` + +### Custom mail merge token + +The CiviMail component lets you customize a bulk email message using mail merge +tokens. For instance, you can begin your message with, "Hi, +{recipient.first_name}!" and when John Doe receives it, he'll see, "Hi, John!" +whereas when Suzy Queue receives it, she'll see, "Hi, Suzy!" + +Besides the built-in tokens, you can use a hook to create new custom tokens. +Let's make a new one that will show the largest contribution each recipient has +given in the past. + +```php +/** + * Implement this hook so we can add our new token to the list of tokens + * displayed to CiviMail users and set the default + * + * @param array $tokens +*/ +function myextension_civicrm_tokens(&$tokens) { + if (isset($tokens['contribution'])) { + return; + } + + $tokens['contribution'] = array('contribution.max' => 'Max Contribution'); +} + +/** + * @param array $details + * The array to store the token values indexed by contactIDs (unless single) + * @param array $contactIDs + * An array of contactIDs + * @param int $jobID + * The jobID if this is associated with a CiviMail mailing. + * @param array $tokens + * The list of tokens associated with the content + * @param string $className + * The top level className from where the hook is invoked + * + * @return null +*/ +function myextension_civicrm_tokenValues(&$details, $contactIDs, $jobID, $tokens, $className) { + + // validate that we're targeting the right event + if ($className != SomeCustomClass::class) { + return; + } + + // fetch the maximum contribution here + foreach ($contactIDs as $contactID) { + $max = my_function_to_get_the_max($contactID); + $details[$contactID]['contribution.max'] = $max; + } +} +``` diff --git a/docs/hooks/setup/joomla.md b/docs/hooks/usage/joomla.md similarity index 100% rename from docs/hooks/setup/joomla.md rename to docs/hooks/usage/joomla.md diff --git a/docs/hooks/setup/symfony.md b/docs/hooks/usage/symfony.md similarity index 100% rename from docs/hooks/setup/symfony.md rename to docs/hooks/usage/symfony.md diff --git a/docs/hooks/setup/wordpress.md b/docs/hooks/usage/wordpress.md similarity index 100% rename from docs/hooks/setup/wordpress.md rename to docs/hooks/usage/wordpress.md diff --git a/mkdocs.yml b/mkdocs.yml index 9603009fc023c9bae81dd2544b4df112d3422720..85bc2e68822e481ad6b2edab71dfdb35de48cb28 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,12 +57,13 @@ pages: - APIv3 Interfaces: api/interfaces.md - APIv3 Changes: api/changes.md - Hooks: - - Using Hooks: hooks/index.md - - Setup: - - Hooks with Symfony: hooks/setup/symfony.md - - Hooks with Joomla: hooks/setup/joomla.md - - Hooks with Drupal: hooks/setup/drupal.md - - Hooks with WordPress: hooks/setup/wordpress.md + - Hooks Introduction: hooks/index.md + - Usage: + - Hooks in Extensions: hooks/usage/extension.md + - Hooks in Symfony: hooks/usage/symfony.md + - Hooks in Drupal: hooks/usage/drupal.md + - Hooks in Joomla: hooks/usage/joomla.md + - Hooks in WordPress: hooks/usage/wordpress.md - All Hooks: hooks/list.md - Batch Hooks: - hook_civicrm_batchItems: hooks/hook_civicrm_batchItems.md