Please note:  this blog post refers to Maestro on Drupal 7.

Maestro was a rewrite/refactor of our Nexflow product. When I wrote the first iteration of the Nexflow engine, I always had an Object Oriented approach in mind. While the original Nexflow engine is object oriented, it lacked a clear and clean way to easily implement new features. Although you could easily write a new task type, it was much more difficult to implement in the engine, requiring editing of core code which would always require extensive engine retesting. Maestro Development Methodology:

Our overriding goal was to create a development environment that was suitable for developers to easily attach their own task types and notification mechanisms in to the system without overwriting or hacking core code. Leveraging the strength of Drupal's core functionality along with some neat OO patterns, we were able to create a base set of modules for Maestro that lets developers add their own task types and/or notification mechanisms via add-on modules.

In order to create a Maestro task module, you need to first understand that there are 2 sides of a Maestro Task. There is the User Interface side and the Engine side.

The UI side is responsible for providing the end-to-end user driven experience in creating a workflow using the visual workflow editor. The user interface provides the hooks for showing a task's general display widget as well as its associated editing panel. Without a UI component, users can't use the task in the workflow designer.

The Engine side is responsible for providing the behind-the-scenes worflow engine capabilities that allow Maestro to carry a process forward through the template created by the UI side. The engine side provides all of the necessary logic to execute and complete a task by the Maestro engine.

Maestro is a Drupal 7 module -- meaning that it will not run on Drupal 6. All development for Maestro in terms of its engine must be done on Drupal 7. This blog post is 100% dedicated to showing you how to create a D7 Maestro task module.

Drupal 7 Modules:

As noted above, Maestro tasks should be added to Maestro via add-on modules thus providing a seamless and easy way to create new task types without requiring any core Maestro engine changes. If you find yourself hacking core code to add a new task type, you're doing something wrong! Stop yourself, grab a coffee, and start over!

We acknowledge that this means that developing a new Maestro task module will require some Drupal 7 module knowledge and expertise. So to alleviate the 1st time developer jitters, the following the steps below show a step-by-step method to create a D7 Maestro Task Type module.

Step 1 - Create the module base:

Choose a name for your module. For this example, we'll call this our "Maestro Sample Task" module. Locate the modules folder under the Maestro installation. Generally this will be found in the /sites/all/modules/maestro/modules directory. Using your favourite editor, create a folder for your module under the Maestro module directory called "maestro_sample_task".

Inside of the folder, create the following files:

  • maestro_sample_task.module

  • maestro_sample_task.info

  • maestro_sample_task.install

Edit the maestro_sample_task.info file and add the following to it:

; $Id:
name = Maestro Sample Task
description = Sample Task for the Maestro Engine
package = Maestro
version = 7.x-1.0-dev
core = 7.x
dependencies[] = maestro
files[] = maestro_sample_task.module
files[] = maestro_sample_task.install


Save the file. Edit your maestro_sample_task.module file and add the following to it:

<?php
// $Id:

That's it! its just a simple placeholder file for now. We will be adding to this file later.

 

Edit the maestro_sample_task.install file and add the following to it:

<?php
// $Id:
function maestro_sample_task_install() {
}

function maestro_sample_task_uninstall(){
}

The .install file will be simply used as a placeholder for the time being. For the purposes of this blog post, that is all we're going to have in the .install file.

At this point, your skeleton Maestro module should be able to be installed and uninstalled from the Drupal Module Admin panel. Although it doesn't do anything useful, the fact that the module actually does install and uninstall is rather important ;-). You should be able to find your module's install grouped with the Maestro package on the modules admin page.

Step 2 - Creating the task's working UI bits:

In order to attach your new task to a workflow, we have to create our Maestro UI components. The Maestro UI components are the elements needed to allow your sample task to show up in the Maestro Visual Designer and have that task saved to Maestro's internal template tables.

The base Maestro UI code is completely Object Oriented in nature. In order for developers to attach a new task to the Maestro UI, you have to extend the functionality. As mentioned earlier, if you find yourself modifying Maestro code, you're probably doing something wrong!

So first things first -- you need to understand WHAT you are extending for the UI. Maestro ships with base task UI code and is found in the maestro_task_interface.class.php file. The very first class definition is for the abstract class named MaestroTaskInterface. It is the MaestroTaskInterface class that you will be extending. The base class provides a wide variety of functionality for the Maestro UI which you will be leveraging for your Task.

Create a file named maestro_sample_task_interface.class.php file in your module's root folder and edit it. Add the following code to it:

<?php
class MaestroTaskInterfaceSample extends MaestroTaskInterface {
  function __construct($task_id=0, $template_id=0) {
    $this->_ask_type = 'Sample';
    $this->_is_interactive = MaestroInteractiveFlag::IS_NOT_INTERACTIVE;
    parent::__construct($task_id, $template_id);
}

  function display() {
    return theme('maestro_task_sample', 
      array(
        'tdid' => $this->_task_id,
        'taskname' => $this->_taskname,
        'ti' => $this
        ));
  }

  function getEditFormContent() {
    $this->_fetchTaskInformation();
    return theme('maestro_task_sample_edit', 
      array(
        'tdid' => $this->_task_id, 
        'td_rec' => $this->_task_data,
        'ta_rec' => $this->_task_assignment_data
        ));
  }

  function save() {
    return parent::save();
  }

}

The first line of code in the file is where we are extending the base MaestroTaskInterface object. This means that as a developer, you have all of the methods and properties from the parent available to you in your class.

So, lets go through what we've got in this code segment now:

We have a constructor for this class that sets the overall task's type and defines whether the task is interactive (that is, has a human interaction element to it) or otherwise. Interactive is a simple bit flag that is either 1 or 0 (using Maestro defined constants) denoting interactive or not respectively. Interactive tasks require interactivity meaning that at some point, a human interaction with the task is required. Non interactive tasks are executed directly by the engine.

For our example, we've set the interactive flag to MaestroInteractiveFlag::IS_NOT_INTERACTIVE, denoting that this is NOT an interactive task. The task will be executed by the engine only. If your task requires human interactivity, you must use the MaestroInteractiveFlag::IS_INTERACTIVE constant.

The display method is responsible for returning the appropriate display themed file. The display method is for showing the task's non-edit mode display. As you can see in the file, we have used the Drupal theme function which means that we will have to implement a maestro_task_sample template for display use.

The getEditFormContent method is used to aggregate any settings you wish on the main edit panel for the task. Again, as you can see by the code, we are calling the Drupal theme function. We must implement the maestro_task_sample_edit template in order for us to visually see anything in the edit panel.

The save method is used to perform any task saving routines that you wish to implement. You dont' have to worry about saving the base task information, that will be done when you include a call to parent::save() in your method. So ensure that you add that call and the parent class will automatically save things like the task name, assignments etc.

As noted above, we need to create 2 template files to support the display and context menu edit functionality. First, create a file named 'theme/structure/tasks/maestro-task-sample.tpl.php'. The maestro-task-sample file will be used for the general block display of your maestro task. This file has to be created in the theme/structure/tasks hierarchy in your module's folder. Add the following content to it:

<?php
// $Id:
?>
<div class="maestro_task">
<div class="t"><div class="b"><div class="r"><div class="l"><div class="bl"><div class="br"><div class="tl-gry"><div class="tr-gry">

<div id="task_title<?php print $tdid; ?>" class="tm-gry maestro_task_title">
<?php print $taskname; ?>
</div>
<div class="maestro_task_body">
<?php print t('Sample'); ?>
</div>

</div></div></div></div></div></div></div></div>
</div>
 

The template (tpl) file above provides a basic display block that the Maestro UI mechanism will use to show it on the template editor page.

There is a key replacement variable in the tpl file. Namely it is the $taskname variable that is being set by the overall task name stored in the database. The task's name is set when you edit the task and THAT overall functionality is provided by Maestro's core.

Now for the second tpl file. The second tpl file is used for creating the edit panel display. You must create a second file named maestro-task-sample-edit.tpl.php in the same theme folder as the file above. Edit it and add the following to it:

<?php
// $Id:
?>
<table>
  <tr>
    <td>
      Add any fields in here you wish to edit.
      Save them via the Task UI's save method to the serialized task_data field.
      Display them in here with the $td_rec->task_data['...field...'] construct.
    </td>
  </tr>
</table>

Finally! We have the edit panel! As noted in the text for the edit, you as the developer need to determine what type of editable data to show. The edit panels for other tasks in Maestro are great examples of how we've pulled data from the task_data serialized field and used it in the edit panel.

Now that you've got the task's Maestro UI class created AND the two corresponding theme files, we can now edit the .module file we created in Step 1 to add in the module's hook_theme() implementation

Add the following lines of code to the .module file:

<?php

include('maestro_sample_task_interface.class.php');

function maestro_sample_task_theme() {
  return array(
    'maestro_task_sample' => array(
    'template' => 'theme/structure/tasks/maestro-task-sample',
    'variables' => array('tdid' => NULL)
    ),
    'maestro_task_sample_edit' => array(
    'template' => 'theme/structure/tasks/maestro-task-sample-edit',
    'variables' => array('tdid' => NULL, 'td_rec' => NULL, 'ta_rec' => NULL)
    ),
  );
}

function maestro_sample_task_maestro_get_taskobject_info() {
  $options = array(
    array('display_name' => 'Sample Task',
      'class_name' => 'MaestroTaskInterfaceSample',
      'file_location' => ''
      ),
    );
  return $options;
}

The first line we've added is an include() php directive telling php to include the extended task code we created above. Maestro already includes the base class definitions and adds those references during run time thus you do not have to re-include them.

The maestro_sample_task_theme() function implements Drupal's hook_theme() function so that the display and edit tpl files are accessible from the theme function calls we used in the code above.

Finally, we've created a maestro_sample_task_maestro_get_taskobject_info function which is an implemention of a Maestro hook for adding a task to the right-click menu on the template editor. If you do not implement the maestro_context_menu hook, your task will not show up in the Maestro editing UI. The $options array returns the task's display name, as well as its associated class name. The class_name array key needs to be set to the actual textual name of the Maestro UI class that handles its functionality. We created the MaestroTaskIntrefaceSample class above, and thus that is the value we set class_name to. The file_location array key is used to denote where the class file is actually located. Since we've accomplished this by using an include() php directive at the top of the file, we can safely leave this option blank.

 

Step 3 - Creating the Maestro Engine Hooks:

The Maestro engine is an OO mechanism, meaning that in order for you to write a new task type for it, you must extend the existing base task object. By doing so means that you will have all of the necessary mechanisms at your disposal to integrate with the engine without altering any engine code. Likewise, extending the existing base task object means that the appropriate methods that the engine needs are already defined for you.

Create the file maestro_sample_task_engine.class.php in the root of your module's folder. Add the following code to it:

class MaestroTaskTypeSample extends MaestroTask {

function execute() {
  $msg = 'Execute Task Type: "Sample" - properties: ' . print_r($this->_properties, true);
  watchdog('maestro',$msg);
  $this->setMessage( $msg . print_r($this->_properties, true) . '<br>');
  db_update('maestro_queue')
    ->fields(array('started_date' => time()))
    ->condition('id', $this->_properties->id, '=')
    ->execute();
  $this->executionStatus = TRUE;
  $this->completionStatus = MaestroTaskStatusCodes::STATUS_COMPLETE;
  return $this;
}

function prepareTask() {
  $serializedData = db_query("SELECT task_data FROM {maestro_template_data} WHERE id = :tid",
  array(':tid' => $this->_properties->taskid))->fetchField();
  $taskdata = @unserialize($serializedData);
  return array('handler' => '' ,'serialized_data' => $serializedData);
}

function getTaskConsoleURL() { }

}

The OO code above extends the existing MaestroTask class which provides a slew of defined methods which the engine uses as well as some methods that you as a developer can override or must implement.

The methods which you MUST implement are execute() and prepareTask().

execute() is, well, executed by the engine when the task is sitting in the queue.

prepareTask() is used during task creation time to prepare the task's internal data structures. If there is some data manipulation that you wish to carry out at task creation time, prepareTask is the method in which to do it in.

I've shown the getTaskConsoleURL() method above. This is an example of an overridden method that the base MaestroTask class already provides for you. The parent getTaskConsoleURL method simply returns a # symbol. In our implementation, we just override the method and return nothing as we are creating a non-interactive task. If you require a special Task Console URL for your Task type, you can define it using the getTaskConsoleURL method. Content type, Interactive and manual web tasks both have examples of creating a Task Console URL via the getTaskConsoleURL method.

Now, re-edit the .module file and place an include to the maestro_sample_task_engine.class.php you just created. The top of your .module file should look like this:

<?php

include('maestro_sample_task_interface.class.php');
include('maestro_sample_task_engine.class.php');
include_once('maestro_constants.class.php');

NOTE: Ensure that the rest of the code from Step 2 is still in the .module file!

 

The Final Module

Now that you've completed your module, all you have to do is compress the module and share it with others! The module installs through the regular install routine in Drupal and instantly provides the consumer of the module with the theme files, UI and engine hooks without them having to do any customization or file manipulation.

If your module has specific workflows that should accompany it, you may have to also include a workflow template to show how to use it. This is easily done via the workflow export. We will have a follow-up blog posting talking about how to create a workflow export and import module.