Integrating APIs with Wordpress and Contact Form 7
Tags: wordpress, php, api, form
Published: 2020-03-04 02:49:47

[Contact Form 7](https://contactform7.com) is an invaluable tool for building forms in Wordpress. My team has been using it for quite some time due to its ease of use and flexibility. Some of the features touted on its [plugin page](https://wordpress.org/plugins/contact-form-7/) are:
- Ability to manage multiple contact forms
- Customization of form and mail contents
- AJAX-powered submissions
- reCAPTCHA support
- Akismet spam filtering

While it definitely has some issues we've had to overcome, it is one of the first plugins installed on a new project. One such issue I've personally felt is&mdash;despite generally having decent documentation around its core functionality&mdash;a somewhat lack of insight around integrating with third party services. In this article I would like to outline an approach I've been using to integrate with external APIs during the submission process.

## Hooks
Like all things in Wordpress, Contact Form 7 is built around a series of hooks. These hooks are exposed to us and grant access to seemingly every lifecycle event of a form from admin initialization to submission, tag rendering to validation triggers, and more.

> A list of all provided hooks can be found over on [hookr.io](http://hookr.io/plugins/contact-form-7)

```php
add_action( string $tag, callable $function_to_add, int $priority = 10, int $accepted_args = 1 )
```

If you're unfamiliar with hooks, I'll briefly explain what's happening here. `add_action` is how we can register a listener for specific events that get emitted by plugins or Wordpress itself. It takes up to 4 arguments:

1. tag&mdash;the event we wish to listen for
1. function_to_add&mdash;a reference to the function that will be invoked for the event. This can a function name, inline function, or class method
1. priority&mdash;when the function shold be called
1. accepted_args&mdash;the number of arguments to pass to the function

> More information about actions can be found in the [Codex](https://codex.wordpress.org/Plugin_API/Action_Reference).

### Register listener

The hook we're currently interested in is `wpcf7_before_send_mail` as this represents the last step in the process before mail is actually sent to the recipient. We'll start by registering our listener for this hook:

```php
function on_submit( $form, &$abort, $submission )
{
    // TODO
}

add_action( 'wpcf7_before_send_mail', 'on_submit', 10, 3 );
```
You may have noticed that we've specified that our callback should receive 3 arguments: the form that was submitted, a flag representing failed submission, and the submission itself. Note that `abort` is actually passed by reference so that the calling class can use it to cancel the submission if we indicate it should do so.

Now that we've got the setup out of the way we can start integrating with whichever systems we need to.

## Integrate all the things

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/23rlh1a7i6rnutl6lsua.png)

You may have multiple forms (like in my contrived scenario above) and only want to run the integration on a specific form. The form object passed to our callback function is an instance of `WPCF7_ContactForm`, which means we can examine it to make sure it's the form we're interested in. The simplest way is to compare against the form's ID:

```php
if ( $form->ID() !== 999 ) {
    return;
}
```
Next we can perform the actual integration. Let's assume all we need is a simple POST request to some endpoint that accepts some fields. Like the form above, the submission object is an instance of `WPCF7_Submission` and we can use it to retrieve the data submitted to the form.

```php
$data = $submission->get_posted_data();
```
`$data` is now an array that contains all of the form information we need, including some additional meta data:

```php
array(15) {
    ["_wpcf7"] => string(3) "999"
    ["_wpcf7_version"] => string(5) "5.1.3"
    ["_wpcf7_locale"] => string(5) "en_US"
    ["_wpcf7_unit_tag"] = > string(13) "wpcf7-f761-o1"
    ["_wpcf7_container_post"] => string(1) "0"
    ["email"] => string(12) "jane@doe.com"
    ["name"] => string(8) "Jane Doe"
}
```
We can now extract the information we need and pass it to our API:

```php
$email = sanitize_text_field($data['email']);
$name = sanitize_text_field($data['name']);

$response = wp_safe_remote_post(/* your API endpoint */, [
    'body' => json_encode([
        'email' => $email,
        'name' => $name,
    ]),
]);
```
For the sake of this example, we're going to assume that when there is an issue with the request it returns an error (along with a status code other than 2XX). This should then return an error object that we can check for. If we do encounter an error, we can signal for submission to be aborted.
```php
if ( is_wp_error($response) ) {
    $abort = TRUE;
}
```

### Gotcha
Flagging to abort the submission will only cancel the default CF7 submission processing. If you have other hooks registered they will continue to fire so you need to check if `abort` has been set in each.

```php
if ( $abort === TRUE || $form->ID() !== 999 ) {
    return;
}
```

## A bit of polish
![](https://media.giphy.com/media/EJskj4cmsk9Hi/giphy.gif)

We now have the basic framework in place but we're lacking a bit of that user experience as the user is not properly notified of any issues. If your API returns user-friendly error messages then you can pass those along to the user with `set_response`, which will render your message on the form.

```php
$body = wp_remote_retrieve_body($response);
$result = json_decode($body);

$submission->set_response($result->message);
```

You may also wish to style your form based on its current state. For example, the form could shake momentarily when marked invalid or transition to a confirmation message upon success. To do this, we can set the submission's `status` property. By default CF7 sets its own statuses and applies classes to the form accordingly: if validation fails then the status is set to `validation_failed` with the class of `invalid`.

```html
<form action="..." method="..." class="wpcf7-form invalid">
```

> You can view all of builtin statuses and their classes on the [Plugin Directory](https://plugins.trac.wordpress.org/browser/contact-form-7/trunk/includes/contact-form.php#L368)

You can apply your own classes by setting a custom status along with your message. This will then be applied to the form with a `custom-` prefix and non-alphanumeric characters replaced with hyphens.

```php
$submission->set_status('api_failed'); // custom-api-failed
```

## Wrapping it all up
For the sake of completeness, here is a look at what all of this looks like put together:
```php
function on_submit( $form, &$abort, $submission )
{
    if ( $abort === TRUE || $form->ID() !== 999 ) {
        return;
    }

    $data = $submission->get_posted_data();

    $email = sanitize_text_field($data['email']);
    $name = sanitize_text_field($data['name']);

    $response = wp_safe_remote_post(/* your API endpoint */, [
        'body' => json_encode([
            'email' => $email,
            'name' => $name,
        ]),
    ]);

    if ( is_wp_error($response) ) {
        $abort = TRUE;
        
        $body = wp_remote_retrieve_body($response);
        $result = json_decode($body);

        $submission->set_response($result->error);
        $submission->set_status('api_failed');
    }
}

add_action('wpcf7_before_send_mail', 'on_submit', 10, 3);

```

### Final thoughts
Custom integrations may not be common with a plugin as feature-rich as Contact Form 7 but there is occasionally a need for them in real world projects. Hopefully this provides a decent jumping off point for writing your own. If you have to incorporate many solutions then I would recommend registering multiple functions and incrementing their priority to have them fire one after another.

---

Thank you for taking the time to join me on this adventure! I've recently been delving into Wordpress development and I've found there are some areas which could use more long-form content to really dig in. If there's something you'd like me to cover, or if you have any feedback on this article, leave a comment here or hit me up on [Twitter](https://twitter.com/jorbascrumps).