← Blog

Custom workflow jobs

Forms Bridge has a variety of workflow jobs out of the box. At the same time, its possible to register custom workflow jobs.

A workflow job is nothing more than a function that gets a submission payload and a bridge instance as input parameters and returns the payload as its output.

The job should always return the payload it has received as its first input. If a job returns a null value the bridge submission will be skipped. If the output of the job is a WP_Error then Forms Bridge will treat this as a runtime error and notify it by email to the admin.

The idea behind the workflow jobs is to perform mutations on the payload or to dispatch some side effects before the submission is sent.

You can configure bridge workflows chaining together jobs to apply mutations to the payload behind the possibilities of the field mappers, make queries to the backend in order to get some data or to prepare the submission, or dispatch changes on the WordPress database.

Some examples

To illustrate the concept we can use the example of an existing workflow job. Lets see what Update mailing contact job from the Odoo ad-don does:

/**
 * It performs a search request in order to find some mailing contact in Odoo by email. If found,
 * then updates its list subscriptions and skip the submission. Otherwise returns the payload
 * without changes and let the bridge to continue with the workflow.
 *
 * @param array            $payload Bridge payload.
 * @param Odoo_Form_Bridge $bridge Bridge object.
 *
 * @return array|null|WP_Error
 */
function forms_bridge_odoo_update_mailing_contact( $payload, $bridge ) {
	// Patch the current bridge and dispatch a search request by email to `mailing.contacts`.
    $response = $bridge
		->patch(
			array(
				'name'     => 'odoo-search-mailing-contact-by-email',
				'template' => null,
				'method'   => 'search',
				'endpoint' => 'mailing.contact',
			)
		)
		->submit( array( array( 'email', '=', $payload['email'] ) ) );

    // If no contact is found the response is a 404 Not Found error.
	if ( ! is_wp_error( $response ) ) {
		$contact_id = $response['data']['result'][0];
		$list_ids   = $payload['list_ids'];

        // Dispatch a `write` operation to update the `list_ids` field of the
        // `mailing.contact` model.
		$response = $bridge
			->patch(
				array(
					'name'     => 'odoo-update-mailing-contact-subscriptions',
					'template' => null,
					'endpoint' => 'mailing.contact',
					'method'   => 'write',
				)
			)
			->submit( array( $contact_id ), array( 'list_ids' => $list_ids ) );

        // If Odoo returns an error, then return the error as the output of the job.
		if ( is_wp_error( $response ) ) {
			return $response;
		}

        // Otherwise, return an empty payload to abort the bridge submission. All work
        // is done!
		return;
	}

    // If no contacts found, then return the payload without mutations.
	return $payload;
}

This job performs a side effect during the workflow execution. It looks for an existing subscriber to the Odoo’s mailing lists and, if found, updates its subscriptions. To do that, it makes use of the patch method of the bridge object to create a copy modifying its configuration. This is the right way to reuse the bridge object inside jobs to perform derived HTTP requests to the backend.

An other example could be the Appointment dates job of the Holded add-on:

/**
 * Given a date string in format 'Y-m-d H:i:s' and a duration as a numeric value, it creates
 * two payload fields:
 *   - startDate: The date value in timestamp format.
 *   - duration: As a float value with 1 as its fallback value.
 *
 * @param array $payload Bridge payload.
 * 
 * @return array|WP_Error
 */
function forms_bridge_holded_appointment_dates( $payload ) {
	$datetime = DateTime::createFromFormat( 'Y-m-d H:i:s', $payload['date'] );
	if ( false === $datetime ) {
		return new WP_Error(
			'invalid-date',
			__( 'Invalid date time value', 'forms-bridge' ),
            array( 'payload' => $payload ),
		);
	}

	$timestamp            = $datetime->getTimestamp();
	$payload['startDate'] = $timestamp;
	$payload['duration']  = floatval( $payload['duration'] ?? 1 );

	return $payload;
}

This is an example of a job that applies advanced mutations to the payload. Jobs can be used to leverage the power of PHP in order to perform any kind of action during the bridge submission.

Register a new job

To register a new job you have three options:

Jobs editor

With the job editor you can create custom workflow jobs or to edit exiting ones. This method allows you to work directly in the admin. Take a look to our tutorial about how to use the jobs editor.

The other methods involves writing code. These methods are useful if you are a developer working on a theme or a plugin and you prefer to write code and use version managers like git or svn.

Theme folders

Forms Bridge will scan the directory of the current theme of the WordPress site and search for folders using the pattern forms-bridge/jobs/${addon} replacing the addon parameter by each of the add-ons enabled.

If some directories are found, Forms Bridge will try to include all PHP and JSON files inside the directories. For example, to include jobs to the Dolibarr add-on you have to create the forms-bridge/jobs/dolibarr inside your theme.

For each included files, Forms Bridge waits an array as the return value of the inclusion. This array should contain the Job configuration.

After a schema validation, this configuration will be registered as a new job and made available to the add-on.

This is how a workflow job script should looks like:

function my_prefix_my_custom_job_method( $payload, $bridge )
{
    $payload['foo'] = 'bar';
    return $payload;
}

return array(
    'title'       => 'Custom Job',
    'description' => 'Workflow job description',
    'method'      => 'my_prefix_my_custom_job_method',
    'input'       => array(),
    'output'      => array( 'foo' => array( 'type' => 'string' ) ),
);

This data will be validated using schema validation methods. To learn more about the workflow schema and schema validation take a look to the Workflow Job Schema entry of the blog.

Keep in mind that if If the array does not match the schema, the registration will be ignored.

Loading hooks

The other available option to register custom workflow jobs is to use the forms_bridge_load_jobs hook. The filter callback will receive an array with job settings and the slug of the target add-on. It’s important to return the list of jobs at the end of the callback to don’t break the filters chain.

function my_prefix_my_custom_job_method( $payload, $bridge )
{
    $payload['foo'] = 'bar';
    return $payload;
}

add_filter( 'forms_bridge_load_jobs', function ( $jobs, $addon ) {
    if ( 'brevo' === $addon ) {
        $jobs[] = array(
             'name'        => 'my-custom-job',
             'title'       => 'Custom Job',
             'description' => 'Workflow job description',
             'method'      => 'my_prefix_my_custom_job_method',
             'input'       => array(),
             'output'      => array( 'foo' => array( 'type' => 'string' ) ),
        );
    }

    return $jobs;
}, 10, 2 );

Place this snippet on the functions.php of your theme and replace the values with your own values.

Registration settings

As you can see in the examples above, to register a job you have to add to the jobs registry a new array containing certain information. This array has the following schema:

  1. name: Internal name of the job. It should be unique to avoid collisions with other jobs (it’s the file name without its extension if you are registering the job using the directories method).
  2. title: A title to be displayed on the job selector of the UI.
  3. description: A description of the job to be displayed on the UI.
  4. method: The name of the job method as string.
  5. input: An array with job input fields interface definition. It should be a JSON Schema definition of the input fields from the payload that the job requires.
  6. output: An array with job output fields interface definition. It should be a JSON Schema definition of the fields that the job will place in the payload.

Using the workflow job

If the registration goes well, you can go to the workflow panel of one of your bridges and click on the + button to add a new job to the workflow. In the job selector you will find your custom job as an available option. Select and click on save at the end. That’s it!