Use the delay mechanism to set the activity date in CiviRules

In this blog I will explain how to use the delay mechanism in CiviRules to set date field. In this example we will set the activity date in the action Add Activity. So that we can schedule an activity which should occur at the 3rd tuesday of the month.

CiviRules already has a delay mechanism to delay actions to a point in the future. But not that will specifically delay the action and nothing will be visible till the date the action should be executed.

The code in this blog could be used to set a contribution date in the future, or to set an end date of a membership or any other date field.

I am going to add the delay mechanism to an action to use it to set a certain date. I am also going to add the delay mechanism to the form where the user can enter the parameters for an action.

The first step we are going to do is to add a field for the Activity Date Time. We will call this field activity_date.

buildQuickForm()

The first step is to add the neccessary code to the buildForm of our action.

// CRM/CivirulesActions/Activity/Form/Activity.php

class CRM_CivirulesActions_Activity_Form_Activity extends CRM_CivirulesActions_Form_Form {

  public function buildQuickForm() {
    ....
    $delayList = array('' => ts(' - Do not set an activity date - ')) + CRM_Civirules_Delay_Factory::getOptionList();
    $this->add('select', 'activity_date', ts('Set activity date'), $delayList);
    foreach(CRM_Civirules_Delay_Factory::getAllDelayClasses() as $delay_class) {
      $delay_class->addElements($this, 'activity_date');
    }
    $this->assign('delayClasses', CRM_Civirules_Delay_Factory::getAllDelayClasses());
  }

The code above first retrieves a list with all the classes which could handle delays. Then for each classes it will make sure that the elements are added by the form. Then the list with classes is assigned to the template.

The line “$delayClass->addElements($this, ‘activity_date’); will allow the delay classes to add elements to the form. The second parameter is the prefix for each element name on the form. This prefix is used through the rest of the code to retrieve the data.

The template

After the elements are added to the form we should tweak our template to include the elements.

// templates/CRM/CivirulesActions/Activity/Form/Activity.tpl
...
<h3>{$ruleActionHeader}</h3>
<div class="crm-block crm-form-block crm-civirule-rule_action-block-activity">
...
 
 <div class="crm-section">
   <div class="label">{$form.activity_date.label}</div>
   <div class="content">{$form.activity_date.html}</div>
   <div class="clear"></div>
 </div>
 {foreach from=$delayClasses item=delayClass}
   <div class="crm-section crm-activity_date-class" id="{$delayClass->getName()}">
   <div class="label"></div>
   <div class="content"><strong>{$delayClass->getDescription()}</strong></div>
   <div class="clear"></div>
   {include file=$delayClass->getTemplateFilename() delayPrefix='activity_date'}
   </div>
 {/foreach}

...
</div>
<div class="crm-submit-buttons">
 {include file="CRM/common/formButtons.tpl" location="bottom"}
</div>

{literal}
 <script type="text/javascript">
 cj(function() {
 cj('select#activity_date').change(triggerDelayChange);

 triggerDelayChange();
 });

 function triggerDelayChange() {
 cj('.crm-activity_date-class').css('display', 'none');
 var val = cj('#activity_date').val();
 if (val) {
 cj('#'+val).css('display', 'block');
 }
 }
 </script>
{/literal}

The template consists first of displaying the activity_date select. After that the template for each delayClass is included and the prefix for the field names is also passed to the template.

The second part of the template is javascript to hide all the elements and only show the needed elements which is determined by the selected value in the activity_date select.

You can now see your form in action. There is no validation and saving yet.

Validation of the form

After creating the form and displaying the elements in the template it is time to validate whether the form is valid when it is submitted. We need to add our own formRule which then calls the validate function in the selected delay option (which done by the user by selecting an option for the Activity Date).

// CRM/CivirulesActions/Activity/Form/Activity.php

...

public function addRules() {
 parent::addRules();
 $this->addFormRule(array(
 'CRM_CivirulesActions_Activity_Form_Activity',
 'validateActivityDate'
 ));
}

static function validateActivityDate($fields) {
 $errors = array();
 if (!empty($fields['activity_date'])) {
 $activityDateClass = CRM_Civirules_Delay_Factory::getDelayClassByName($fields['activity_date']);
 $activityDateClass->validate($fields, $errors, 'activity_date');
 }

 if (count($errors)) {
   return $errors;
 }

 return TRUE;
}
...

The code above first adds the validateActivityDate function to the form as a validation rule. The validation function itself will get the class for the selected delay option and then pass the actual validation to the selected delay class. This is done by the line $activityDateClass->validate($fields, $errors, ‘activity_date’);  The last parameter is the prefix for the field names.

Saving the selected the activity date options

Now it is time to make sure the selected options are being saved with the rule. We do that in the postProcess function of the form. How you want to store the selected date for the activity date is up to you but in this example we are storing the selected option by serializing the delayClass.

// CRM/CivirulesActions/Activity/Form/Activity.php

public function postProcess() {
  ...
  $data['activity_date'] = 'null';
 if (!empty($this->_submitValues['activity_date'])) {
 $scheduledDateClass = CRM_Civirules_Delay_Factory::getDelayClassByName($this->_submitValues['activity_date']);
 $scheduledDateClass->setValues($this->_submitValues, 'activity_date');
 $data['activity_date'] = serialize($scheduledDateClass);
 }
 ...
 $this->ruleAction->action_params = serialize($data);
 ...
}

Setting the defaults

When the user edits the civirule the saved options should appear as selected in the form. This is done by setting the default values in the form.

// CRM/CivirulesActions/Activity/Form/Activity.php

public function setDefaultValues() {
...

foreach(CRM_Civirules_Delay_Factory::getAllDelayClasses() as $delay_class) {
   $delay_class->setDefaultValues($defaultValues, 'activity_date');
 }
 $activityDateClass = unserialize($data['activity_date']);
 if ($activityDateClass) {
   $defaults['activity_date'] = get_class($activityDateClass);
   foreach($activityDateClass->getValues('activity_date') as $key => $val) {
     $defaults[$key] = $val;
   }
 }
 ...
return $defaults;
}

Show a user friendly description of the selected activity date

The code below will show to the user which activity date is selected.

// CRM/CivirulesActions/Activity/Add.php
class CRM_CivirulesActions_Activity_Add extends CRM_CivirulesActions_Generic_Api {
...

public function userFriendlyConditionParams() {
  $return = '';
  ...
  if (!empty($params['activity_date'])) {
    $delayClass = unserialize(($params['activity_date']));
    if ($delayClass instanceof CRM_Civirules_Delay_Delay) {
      $return .= '<br>'.ts('Activity date time').': '.$delayClass->getDelayExplanation();
    }
 }

 return $return;

When the action is executed the the parameter for activity_date_time

The last step consists of setting the activity_date_time parameter in the action for creating an activity.

// CRM/CivirulesActions/Activity/Add.php
class CRM_CivirulesActions_Activity_Add extends CRM_CivirulesActions_Generic_Api {
...

protected function alterApiParameters($params, CRM_Civirules_TriggerData_TriggerData $triggerData) {
 $action_params = $this->getActionParameters();
 ...
  if (!empty($action_params['activity_date'])) {
   $delayClass = unserialize(($action_params['activity_date_time']));
   if ($delayClass instanceof CRM_Civirules_Delay_Delay) {
     $activityDate = $delayClass->delayTo(new DateTime(), $triggerData);
    if ($activityDate instanceof DateTime) {
      $params['activity_date_time'] = $activityDate->format('Ymd His');
   }
  }
 }

 return $params;
}

That is all what was needed to add the delay mechanism to the Add Activity Action.