Approval workflow how-tos

There is a lot that a developer can do with the approval workflows framework, because it was designed to give form plugin developers a lot of room for customisation.

For these how-tos, we’ll walk through an example of creating an approvalform plugin and associated workflow that allows applicants to request access to a course, with automatic enrolment if their application is approved.

We’ll call this example plugin approvalform_enrol, the ‘Course enrolment request form’.

How to start an approvalform plugin

Fundamentally, approvalform plugins define the form fields and labels which are available to workflows based on them. But because they are plugins, they can also have their own front-end components, database tables, report sources, hook watchers, event observers, notifications and more.

Plugins should be placed in the server/mod/approval/form/ directory.

A minimalist approvalform plugin with the name ‘xyz' consists of the following:

  • form.json - this is the JSON form schema which defines all of the available fields and sections

  • version.php - a standard version.php file, with the component name approvalform_xyz

  • classes/xyz.php - class which extends \mod_approval\model\form\approvalform_base, can just be a stub (empty class definition)

  • lang/en/approvalform_xyz.php - language string for the plugin name

Install, enable, and prepare for use by workflows

Once Totara detects the new plugin and installs it via upgrade, it will need to be enabled. Go to Quick-access menu > Plugins > Approval form plugins > Manage approval form plugins and click the eye icon to enable it.

The plugin is now available when creating a new approval workflow form, which can then be used in one or more workflows. Go to Quick-access menu > Approval workflows > Manage approval forms to add a new form based on the new plugin.

Example code

To start, the approvalform_enrol plugin consists of:

form.json (see the JSON schema documentation for details):

{ "title": "Course enrolment request form", "shortname": "enrol", "revision": "Revised November 2023", "version": "2023112400", "language": "en", "component": "approvalform_enrol", "fields": [ { "key": "course_id", "line": "1", "label": "Course ID", "type": "number", "required": true }, { "key": "notes", "line": "2", "label": "Why do you want to take this course?", "type": "editor", "char_length": "50", "required": true } ] }

version.php:

<?php $plugin->version = 2023110200; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2023110200; // Requires this Totara version. $plugin->component = 'approvalform_enrol'; // To check on upgrade, that module sits in correct place

classes/enrol.php:

<?php namespace approvalform_enrol; use mod_approval\model\form\approvalform_base; /** * Class enrol provides an interface to the approvalform_enrol sub-plugin. * * @package approvalform_simple */ class enrol extends approvalform_base { // Stub }

lang/en/approvalform_enrol.php:

Create a default enrolment workflow

To create a default enrolment workflow, run this PHP script in your directory roots:

How to change a form label, or make it a lang string

If the label for a form field is not quite right, you have two options for changing it:

  • Hard-coded change to the form.json schema

  • Use a language string key as the label, by changing it to <key>_label, where <key> is the language string key.

The hard-coded approach is simple, but requires a developer if it needs to be changed in future. It also only supports forms in a single language.

Using a language string key as the label allows the label to be translated into multiple languages, and allows admins to change the label themselves by editing the language string.

Help text can also be a language string - see the How to add help text to form fields.

In either case, once the form schema has been changed, its internal version number needs to be increased. Any approval forms based on the plugin will then need to be refreshed to pick up the new schema.

Example code

Updated form.json and language string definitions to use language strings for form labels.

form.json updated:

lang/en/approvalform_enrol.php updated:

How to add help text to form fields

There are multiple options for adding help text to a form field (help text appears when the i icon is clicked).

  • Hard-coded change to the form.json schema, add a 'help' property to any field.

  • Use a language string key as the help text, by changing it to <key>_help, where <key> is the language string key.

  • Use the help_html property instead, when you need to include HTML markup. Normally help text is plain text, and special characters are escaped.

As always, once the form schema has been changed, its internal version number needs to be increased. Any approval forms based on the plugin will then need to be refreshed to pick up the new schema.

Example code

form.json updated:

lang/en/approvalform_enrol.php updated:

How to modify form schema dynamically

If you need to modify the form schema before it is sent to the front end to be rendered, for example to control the range of years in a date picker, or the set of available choices in a select field, you can do that by implementing adjust_form_schema_for_application() in your approvalform class.

This is used internally to adjust editor fields for the current user, so ensure that you call parent::adjust_form_schema_for_application() at some point in your implementation.

Pseudo-code:

The form_schema in $form_schema will be a subset of the form.json schema, made up of the collection of form_views configured for the workflow at the current stage – hence the need for the conditional that checks whether the schema has the desired field.

Example code

We need to get the course ID from a $_GET var, so that the applicant doesn’t have to fill it in themselves.

classes/enrol.php updated:

How to set the application title from a form field

Similar to how an approvalform plugin can modify form schema dynamically, it can also modify the application data (the form response), by implementing observe_form_data_for_application() in your approvalform class.

Note that observe_form_data_for_application() gets called whenever application data is processed - both on the way into the database (when posted by the applicant) and on the way out to the page (when the submitted application is viewed). Care should be taken to only execute actions once, and only when they need to be actioned.

Example code

We want to give each application the same name as the requested course.

classes/enrol.php updated:

How to use a field value to trigger an action on event

Very often as an application moves through a workflow, we want to make something happen in the system. Currently this is only possible by observing an application event, or writing a scheduled task that looks for applications in a particular state.

Available application events include:

  1. Application completed - mod_approval\event\application_completed

  2. Existing approvals invalidated due to rejection or withdrawal - mod_approval\event\approvals_invalidated

  3. Application approved - mod_approval\event\level_approved

  4. Application rejected - mod_approval\event\level_rejected

  5. Application entered new level - mod_approval\event\level_started

  6. Application fully-approved at stage - mod_approval\event\stage_all_approved

  7. Application ended current stage - mod_approval\event\stage_ended

  8. Application entered new stage - mod_approval\event\stage_started

  9. Application submitted - mod_approval\event\stage_submitted

  10. Application withdrawn - mod_approval\event\stage_withdrawn

The Application completed event does not imply anything about whether the application was approved or not. It is triggered any time an application enters an ‘end' stage and cannot progress any further. Use Application fully-approved at stage to respond to approval.

As with all Totara events, care must be taken to ensure that you are only observing the particular events you want to observe. The event object ID is the application ID, and from there you can use the application model to discover the overall context of the application.

Event observer pseudo-code:

Note that detecting true application state can be tricky with advanced workflow configurations. This is because of the configurability of workflows:

  • An application can reach an end state (i.e. be 'completed') on any transition, including form submission

  • You may need to do something at a particular stage, but stage names are admin-editable

  • You may need to check a particular form field value, but the admin can configure the workflow so that the field is not required, or not even available

Please contact Totara Support if you have particular issues with detecting application state, or suggestions for improvement.

Example code

Our example approvalform plugin needs to enrol the applicant on their selected course once the application is approved at all levels.

Note that we’re just using manual enrolments here for simplicity. Ideally we would create an enrolment plugin specifically for use with approvalform_enrol-based workflows.

db/event.php:

classes/observer/application_event.php:

How to capture key application data in a local table

Approval workflows response data is stored as a JSON blob, so it can be difficult to access, filter, or sort applications based on the answers to specific form fields.

To solve this problem, approvalform plugins can create their own responses table, with fields for application_id and any form keys they wish to access or report on. Then use \approvalform_base::observe_form_data_for_application() to save the tracked form response fields to the table.

Once application response fields are stored in a table, it can be used in report sources and business logic to supplement the information in the ttr_approval_application table.

Example code

Our enrolment workflow needs to track the course_id provided by each application.

db/install.xml:

classes\entity\response.php:

classes\enrol.php updated:

How to trigger a transition via scheduled task

Some workflows require time to pass before the application transitions to a new stage. This can be accomplished with a 'waiting' stage, and a scheduled task that makes the transition occur when a condition is met.

Or perhaps you want to automatically transition applications to an 'abandoned' end state after a certain amount of time, or if an approval deadline passes. Again, this is accomplished with a scheduled task.

Task pseudo-code:

Loading the correct applications is made much easier if response fields or other metadata is tracked in a local database table, as described in the How to capture key application data in a local table section.

Example code

For our course enrolment use case, we want to automatically withdraw applications that have not been completed before the course start date is reached.

lang/en/approvalform_enrol.php updated:

db/tasks.php:

classes\task\withdraw_late_applications.php:

Â