From 28ed3089cc3894e5ff5599b4d773bbe13b2fea3d Mon Sep 17 00:00:00 2001
From: Jaap Jansma <jaap.jansma@civicoop.org>
Date: Fri, 11 Oct 2019 12:30:46 +0200
Subject: [PATCH] Added date field

---
 CHANGELOG.md                                  |   1 +
 CRM/Dataprocessor/BAO/DataProcessor.php       |   2 +-
 .../DataFlow/AbstractDataFlow.php             |  16 +-
 .../CombinedDataFlow/CombinedSqlDataFlow.php  |   4 +-
 Civi/DataProcessor/DataFlow/SqlDataFlow.php   |   4 +-
 .../DataFlow/SqlTableDataFlow.php             |  15 +-
 .../DataFlow/Utils/Aggregator.php             |  24 +-
 .../DataSpecification/FieldSpecification.php  |  21 ++
 Civi/DataProcessor/Factory.php                |   1 +
 .../DateFieldOutputHandler.php                | 212 ++++++++++++++++++
 .../OutputHandlerAggregate.php                |  11 +
 .../RawFieldOutputHandler.php                 |  12 +
 .../Configuration/DateFieldOutputHandler.tpl  |  23 ++
 13 files changed, 314 insertions(+), 32 deletions(-)
 create mode 100644 Civi/DataProcessor/FieldOutputHandler/DateFieldOutputHandler.php
 create mode 100644 templates/CRM/Dataprocessor/Form/Field/Configuration/DateFieldOutputHandler.tpl

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48fde9a1..c1f7c166 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@
 * Refactored aggregation functionality and added aggregation function field.
 * Fixed issue with updating navigation after editing an output.
 * Added option to expand criteria forms on search forms.
+* Added a Date field.
 
 # Version 1.0.7
 
diff --git a/CRM/Dataprocessor/BAO/DataProcessor.php b/CRM/Dataprocessor/BAO/DataProcessor.php
index e8d0768c..31b4e6bb 100644
--- a/CRM/Dataprocessor/BAO/DataProcessor.php
+++ b/CRM/Dataprocessor/BAO/DataProcessor.php
@@ -99,7 +99,7 @@ class CRM_Dataprocessor_BAO_DataProcessor extends CRM_Dataprocessor_DAO_DataProc
           $outputHandler->initialize($field['name'], $field['title'], $field['configuration']);
           $dataProcessorClass->addOutputFieldHandlers($outputHandler);
           if ($outputHandler instanceof OutputHandlerAggregate && $outputHandler->isAggregateField()) {
-            $dataProcessorClass->getDataFlow()->addAggregateField($outputHandler->getAggregateFieldSpec());
+            $dataProcessorClass->getDataFlow()->addAggregateOutputHandler($outputHandler);
           }
         } catch (\Exception $e) {
           CRM_Core_Session::setStatus($e->getMessage(), E::ts("Invalid field"), 'error');
diff --git a/Civi/DataProcessor/DataFlow/AbstractDataFlow.php b/Civi/DataProcessor/DataFlow/AbstractDataFlow.php
index 4fa85a1f..19cd4f0a 100644
--- a/Civi/DataProcessor/DataFlow/AbstractDataFlow.php
+++ b/Civi/DataProcessor/DataFlow/AbstractDataFlow.php
@@ -12,6 +12,7 @@ use \Civi\DataProcessor\DataSpecification\DataSpecification;
 use Civi\DataProcessor\DataSpecification\FieldSpecification;
 use \Civi\DataProcessor\FieldOutputHandler\AbstractFieldOutputHandler;
 use Civi\DataProcessor\DataFlow\Sort\SortSpecification;
+use Civi\DataProcessor\FieldOutputHandler\OutputHandlerAggregate;
 
 abstract class AbstractDataFlow {
 
@@ -46,9 +47,9 @@ abstract class AbstractDataFlow {
   protected $dataSpecification;
 
   /**
-   * @var FieldSpecification[]
+   * @var \Civi\DataProcessor\FieldOutputHandler\OutputHandlerAggregate[]
    */
-  protected $aggregateFields = array();
+  protected $aggregateOutputHandlers = array();
 
   /**
    * @var SortSpecification[]
@@ -240,8 +241,11 @@ abstract class AbstractDataFlow {
     return array();
   }
 
-  public function addAggregateField(FieldSpecification $aggregateField) {
-    $this->aggregateFields[] = $aggregateField;
+  /**
+   * @param \Civi\DataProcessor\DataFlow\OutputHandlerAggregate $aggregateOutputHandler
+   */
+  public function addAggregateOutputHandler(OutputHandlerAggregate $aggregateOutputHandler) {
+    $this->aggregateOutputHandlers[] = $aggregateOutputHandler;
   }
 
   /**
@@ -283,8 +287,8 @@ abstract class AbstractDataFlow {
    * @return array();
    */
   protected function aggregate($records, $fieldNameprefix="") {
-    if (count($this->aggregateFields)) {
-      $aggregator = new Aggregator($records, $this->aggregateFields, $this->dataSpecification);
+    if (count($this->aggregateOutputHandlers)) {
+      $aggregator = new Aggregator($records, $this->aggregateOutputHandlers, $this->dataSpecification);
       $records = $aggregator->aggregateRecords($fieldNameprefix);
     }
     return $records;
diff --git a/Civi/DataProcessor/DataFlow/CombinedDataFlow/CombinedSqlDataFlow.php b/Civi/DataProcessor/DataFlow/CombinedDataFlow/CombinedSqlDataFlow.php
index ac408dd9..defe3703 100644
--- a/Civi/DataProcessor/DataFlow/CombinedDataFlow/CombinedSqlDataFlow.php
+++ b/Civi/DataProcessor/DataFlow/CombinedDataFlow/CombinedSqlDataFlow.php
@@ -133,8 +133,8 @@ class CombinedSqlDataFlow extends SqlDataFlow implements MultipleSourceDataFlows
    */
   public function getFieldsForGroupByStatement() {
     $fields = array();
-    foreach($this->aggregateFields as $field) {
-      $fields[] = "`{$this->primary_table_alias}`.`{$field->name}`";
+    foreach($this->aggregateOutputHandlers as $outputHandler) {
+      $fields[] = $outputHandler->getAggregateFieldSpec()->getSqlGroupByStatement($this->getName());
     }
     foreach($this->sourceDataFlowDescriptions as $sourceDataFlowDescription) {
       $fields = array_merge($fields, $sourceDataFlowDescription->getDataFlow()->getFieldsForGroupByStatement());
diff --git a/Civi/DataProcessor/DataFlow/SqlDataFlow.php b/Civi/DataProcessor/DataFlow/SqlDataFlow.php
index 4f518ba7..4458e572 100644
--- a/Civi/DataProcessor/DataFlow/SqlDataFlow.php
+++ b/Civi/DataProcessor/DataFlow/SqlDataFlow.php
@@ -45,8 +45,8 @@ abstract class SqlDataFlow extends AbstractDataFlow {
    */
   public function getFieldsForGroupByStatement() {
     $fields = array();
-    foreach($this->aggregateFields as $field) {
-      $fields[] = $field->getSqlGroupByStatement($this->getName());
+    foreach($this->aggregateOutputHandlers as $outputHandler) {
+      $fields[] = $outputHandler->getAggregateFieldSpec()->getSqlGroupByStatement($this->getName());
     }
     return $fields;
   }
diff --git a/Civi/DataProcessor/DataFlow/SqlTableDataFlow.php b/Civi/DataProcessor/DataFlow/SqlTableDataFlow.php
index c8e7bcc7..84cd1ca1 100644
--- a/Civi/DataProcessor/DataFlow/SqlTableDataFlow.php
+++ b/Civi/DataProcessor/DataFlow/SqlTableDataFlow.php
@@ -72,19 +72,6 @@ class SqlTableDataFlow extends SqlDataFlow {
     return $fields;
   }
 
-  /**
-   * Returns an array with the fields for in the group by statement in the sql query.
-   *
-   * @return string[]
-   */
-  public function getFieldsForGroupByStatement() {
-    $fields = array();
-    foreach($this->aggregateFields as $field) {
-      $fields[] = "`{$this->table_alias}`.`{$field->name}`";
-    }
-    return $fields;
-  }
-
   /**
    * @return string
    */
@@ -99,4 +86,4 @@ class SqlTableDataFlow extends SqlDataFlow {
     return $this->table_alias;
   }
 
-}
\ No newline at end of file
+}
diff --git a/Civi/DataProcessor/DataFlow/Utils/Aggregator.php b/Civi/DataProcessor/DataFlow/Utils/Aggregator.php
index 941ace68..a3bd595a 100644
--- a/Civi/DataProcessor/DataFlow/Utils/Aggregator.php
+++ b/Civi/DataProcessor/DataFlow/Utils/Aggregator.php
@@ -7,7 +7,9 @@
 namespace Civi\DataProcessor\DataFlow\Utils;
 
 use Civi\DataProcessor\DataSpecification\Aggregatable;
+use Civi\DataProcessor\DataSpecification\DataSpecification;
 use Civi\DataProcessor\DataSpecification\FieldSpecification;
+use Civi\DataProcessor\FieldOutputHandler\OutputHandlerAggregate;
 
 class Aggregator {
 
@@ -17,18 +19,25 @@ class Aggregator {
   protected $records;
 
   /**
-   * @var FieldSpecification[]
+   * @var OutputHandlerAggregate[]
    */
-  protected $aggregateFields = array();
+  protected $aggregateOutputHandlers = array();
 
   /**
    * @var \Civi\DataProcessor\DataSpecification\DataSpecification
    */
   protected $dataSpecification = array();
 
-  public function __construct($records, $aggregateFields, $dataSpecification) {
+  /**
+   * Aggregator constructor.
+   *
+   * @param $records
+   * @param OutputHandlerAggregate[] $aggregateOutputHandlers
+   * @param \Civi\DataProcessor\DataSpecification\DataSpecification $dataSpecification
+   */
+  public function __construct($records, $aggregateOutputHandlers, DataSpecification $dataSpecification) {
     $this->records = $records;
-    $this->aggregateFields = $aggregateFields;
+    $this->aggregateOutputHandlers = $aggregateOutputHandlers;
     $this->dataSpecification = $dataSpecification;
   }
 
@@ -57,10 +66,11 @@ class Aggregator {
 
   protected function getAggregationKeyFromRecord($record, $fieldNameprefix="") {
     $key = '';
-    foreach($this->aggregateFields as $field) {
-      $alias = $field->alias;
+    foreach($this->aggregateOutputHandlers as $outputHandler) {
+      $alias = $outputHandler->getAggregateFieldSpec()->alias;
       if (isset($record[$fieldNameprefix.$alias])) {
-        $key .= $record[$fieldNameprefix.$alias].'_';
+        $value = $outputHandler->formatAggregationValue($record[$fieldNameprefix.$alias]);
+        $key .= $value.'_';
       } else {
         $key .= 'null_';
       }
diff --git a/Civi/DataProcessor/DataSpecification/FieldSpecification.php b/Civi/DataProcessor/DataSpecification/FieldSpecification.php
index 8f539c92..ee90fb61 100644
--- a/Civi/DataProcessor/DataSpecification/FieldSpecification.php
+++ b/Civi/DataProcessor/DataSpecification/FieldSpecification.php
@@ -33,6 +33,11 @@ class FieldSpecification implements SqlFieldSpecification {
    */
   public $options = null;
 
+  /**
+   * @var null|String
+   */
+  protected $sqlValueFormatFunction = null;
+
   public function __construct($name, $type, $title, $options=null, $alias=null) {
     if (empty($alias)) {
       $this->alias = $name;
@@ -49,6 +54,13 @@ class FieldSpecification implements SqlFieldSpecification {
     return $this->options;
   }
 
+  /**
+   * @param $function
+   */
+  public function setMySqlFunction($function) {
+    $this->sqlValueFormatFunction = $function;
+  }
+
   /**
    * Returns the select statement for this field.
    * E.g. COUNT(civicrm_contact.id) AS contact_id_count
@@ -57,6 +69,9 @@ class FieldSpecification implements SqlFieldSpecification {
    * @return string
    */
   public function getSqlSelectStatement($table_alias) {
+    if ($this->sqlValueFormatFunction) {
+      return "{$this->sqlValueFormatFunction} (`{$table_alias}`.`{$this->name}`) AS `{$this->alias}`";
+    }
     return "`{$table_alias}`.`{$this->name}` AS `{$this->alias}`";
   }
 
@@ -68,6 +83,9 @@ class FieldSpecification implements SqlFieldSpecification {
    * @return string
    */
   public function getSqlColumnName($table_alias) {
+    if ($this->sqlValueFormatFunction) {
+      return "{$this->sqlValueFormatFunction} (`{$table_alias}`.`{$this->name}`)";
+    }
     return "`{$table_alias}`.`{$this->name}`";
   }
 
@@ -80,6 +98,9 @@ class FieldSpecification implements SqlFieldSpecification {
    * @return String
    */
   public function getSqlGroupByStatement($table_alias) {
+    if ($this->sqlValueFormatFunction) {
+      return "{$this->sqlValueFormatFunction} (`{$table_alias}`.`{$this->name}`)";
+    }
     return "`{$table_alias}`.`{$this->name}`";
   }
 
diff --git a/Civi/DataProcessor/Factory.php b/Civi/DataProcessor/Factory.php
index b7c5d689..3292f2b4 100644
--- a/Civi/DataProcessor/Factory.php
+++ b/Civi/DataProcessor/Factory.php
@@ -140,6 +140,7 @@ class Factory {
     $this->addjoinType('simple_join', 'Civi\DataProcessor\DataFlow\MultipleDataFlows\SimpleJoin', E::ts('Select fields to join on'));
     $this->addjoinType('simple_non_required_join', 'Civi\DataProcessor\DataFlow\MultipleDataFlows\SimpleNonRequiredJoin', E::ts('Select fields to join on (not required)'));
     $this->addOutputHandler('raw', 'Civi\DataProcessor\FieldOutputHandler\RawFieldOutputHandler', E::ts('Raw field value'));
+    $this->addOutputHandler('date', 'Civi\DataProcessor\FieldOutputHandler\DateFieldOutputHandler', E::ts('Date field value'));
     $this->addOutputHandler('contact_link', 'Civi\DataProcessor\FieldOutputHandler\ContactLinkFieldOutputHandler', E::ts('Link to view contact'));
     $this->addOutputHandler('file_field', 'Civi\DataProcessor\FieldOutputHandler\FileFieldOutputHandler', E::ts('File download link'));
     $this->addOutputHandler('option_label', 'Civi\DataProcessor\FieldOutputHandler\OptionFieldOutputHandler', E::ts('Option label'));
diff --git a/Civi/DataProcessor/FieldOutputHandler/DateFieldOutputHandler.php b/Civi/DataProcessor/FieldOutputHandler/DateFieldOutputHandler.php
new file mode 100644
index 00000000..564a9b36
--- /dev/null
+++ b/Civi/DataProcessor/FieldOutputHandler/DateFieldOutputHandler.php
@@ -0,0 +1,212 @@
+<?php
+/**
+ * @author Jaap Jansma <jaap.jansma@civicoop.org>
+ * @license AGPL-3.0
+ */
+
+namespace Civi\DataProcessor\FieldOutputHandler;
+
+use Civi\DataProcessor\Exception\DataSourceNotFoundException;
+use Civi\DataProcessor\Exception\FieldNotFoundException;
+use CRM_Dataprocessor_ExtensionUtil as E;
+use Civi\DataProcessor\Source\SourceInterface;
+use Civi\DataProcessor\DataSpecification\FieldSpecification;
+
+class DateFieldOutputHandler extends AbstractSimpleFieldOutputHandler implements OutputHandlerAggregate {
+
+  /**
+   * @var bool
+   */
+  protected $isAggregateField = false;
+
+  protected $function = null;
+
+  protected $format = null;
+
+  /**
+   * Initialize the processor
+   *
+   * @param String $alias
+   * @param String $title
+   * @param array $configuration
+   * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $processorType
+   */
+  public function initialize($alias, $title, $configuration) {
+    parent::initialize($alias, $title, $configuration);
+    $this->isAggregateField = isset($configuration['is_aggregate']) ? $configuration['is_aggregate'] : false;
+    $this->format = isset($configuration['format']) ? $configuration['format'] : false;
+    $function = isset($configuration['function']) ? $configuration['function'] : false;
+    $availableFunctions = $this->getFunctions();
+    if (isset($availableFunctions[$function])) {
+      $this->function = $function;
+      $this->inputFieldSpec->setMySqlFunction($availableFunctions[$function]['sql']);
+    }
+  }
+
+  /**
+   * When this handler has additional configuration you can add
+   * the fields on the form with this function.
+   *
+   * @param \CRM_Core_Form $form
+   * @param array $field
+   */
+  public function buildConfigurationForm(\CRM_Core_Form $form, $field=array()) {
+    parent::buildConfigurationForm($form, $field);
+
+    $dateFunctions = array();
+    foreach($this->getFunctions() as $key => $function) {
+      $dateFunctions[$key] = $function['title'];
+    }
+    $form->add('text', 'format', E::ts('Format'), array(
+      'style' => 'min-width:250px',
+      'class' => 'huge',
+    ));
+    $form->add('select', 'function', E::ts('Date function'), $dateFunctions, false, array(
+      'style' => 'min-width:250px',
+      'class' => 'crm-select2 huge',
+      'placeholder' => E::ts('- select -'),
+    ));
+    $form->add('checkbox', 'is_aggregate', E::ts('Aggregate on this field'));
+    if (isset($field['configuration'])) {
+      $configuration = $field['configuration'];
+      $defaults = array();
+      if (isset($configuration['function'])) {
+        $defaults['function'] = $configuration['function'];
+      }
+      if (isset($configuration['format'])) {
+        $defaults['format'] = $configuration['format'];
+      }
+      if (isset($configuration['is_aggregate'])) {
+        $defaults['is_aggregate'] = $configuration['is_aggregate'];
+      }
+      $form->setDefaults($defaults);
+    }
+  }
+
+  /**
+   * When this handler has configuration specify the template file name
+   * for the configuration form.
+   *
+   * @return false|string
+   */
+  public function getConfigurationTemplateFileName() {
+    return "CRM/Dataprocessor/Form/Field/Configuration/DateFieldOutputHandler.tpl";
+  }
+
+
+  /**
+   * Process the submitted values and create a configuration array
+   *
+   * @param $submittedValues
+   * @return array
+   */
+  public function processConfiguration($submittedValues) {
+    $configuration = parent::processConfiguration($submittedValues);
+    $configuration['is_aggregate'] = isset($submittedValues['is_aggregate']) ? $submittedValues['is_aggregate'] : false;
+    $configuration['function'] = isset($submittedValues['function']) ? $submittedValues['function'] : false;
+    $configuration['format'] = isset($submittedValues['format']) ? $submittedValues['format'] : false;
+    return $configuration;
+  }
+
+  /**
+   * Returns the formatted value
+   *
+   * @param $rawRecord
+   * @param $formattedRecord
+   *
+   * @return \Civi\DataProcessor\FieldOutputHandler\FieldOutput
+   */
+  public function formatField($rawRecord, $formattedRecord) {
+    $formattedValue = $rawRecord[$this->inputFieldSpec->alias];
+    if ($this->format) {
+      $date = new \DateTime($formattedValue);
+      $formattedValue = $date->format($this->format);
+    }
+    $output = new FieldOutput($rawRecord[$this->inputFieldSpec->alias]);
+    $output->formattedValue = $formattedValue;
+    return $output;
+  }
+
+  /**
+   * @return \Civi\DataProcessor\DataSpecification\FieldSpecification
+   */
+  public function getAggregateFieldSpec() {
+    return $this->inputFieldSpec;
+  }
+
+  /**
+   * @return bool
+   */
+  public function isAggregateField() {
+    return $this->isAggregateField;
+  }
+
+  /**
+   * Callback function for determining whether this field could be handled by this output handler.
+   *
+   * @param \Civi\DataProcessor\DataSpecification\FieldSpecification $field
+   * @return bool
+   */
+  public function isFieldValid(FieldSpecification $field) {
+    switch ($field->type) {
+      case 'Timestamp':
+      case 'Date':
+        return TRUE;
+        break;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Returns the value. And if needed a formatting could be applied.
+   * E.g. when the value is a date field and you want to aggregate on the month
+   * you can then return the month here.
+   *
+   * @param $value
+   * @return mixed
+   */
+  public function formatAggregationValue($value) {
+    $availableFunctions = $this->getFunctions();
+    if ($this->function && isset($availableFunctions[$this->function]) && $value) {
+      $date = new \DateTime($value);
+      $value = $date->format($availableFunctions[$this->function]['php_date_format']);
+      if (isset($availableFunctions[$this->function]['php_add'])) {
+        $value = $value + $availableFunctions[$this->function]['php_add'];
+      }
+    }
+    return $value;
+  }
+
+  protected function getFunctions() {
+    return array(
+      'dayofmonth' => array(
+        'sql' => 'DAYOFMONTH',
+        'php_date_format' => 'j',
+        'title' => E::ts('Day of month'),
+      ),
+      'dayofweek' => array(
+        'sql' => 'DAYOFWEEK',
+        'php_date_format' => 'N',
+        'php_add' => +1, // +1 as the php date format return 0 for sunday and 6 for saturday. So add one
+        'title' => E::ts('Day of week (1 = Sunday, 7 = Saturday)'),
+      ),
+      'week' => array(
+        'sql' => 'WEEK',
+        'php_date_format' => 'W',
+        'title' => E::ts('Weeknumber'),
+      ),
+      'month' => array(
+        'sql' => 'MONTH',
+        'php_date_format' => 'n',
+        'title' => E::ts('Month'),
+      ),
+      'year' => array(
+        'sql' => 'YEAR',
+        'php_date_format' => 'Y',
+        'title' => E::ts('Year'),
+      ),
+    );
+  }
+
+
+}
diff --git a/Civi/DataProcessor/FieldOutputHandler/OutputHandlerAggregate.php b/Civi/DataProcessor/FieldOutputHandler/OutputHandlerAggregate.php
index 187f723d..7abc605c 100644
--- a/Civi/DataProcessor/FieldOutputHandler/OutputHandlerAggregate.php
+++ b/Civi/DataProcessor/FieldOutputHandler/OutputHandlerAggregate.php
@@ -18,4 +18,15 @@ interface OutputHandlerAggregate {
    */
   public function isAggregateField();
 
+  /**
+   * Returns the value. And if needed a formatting could be applied.
+   * E.g. when the value is a date field and you want to aggregate on the month
+   * you can then return the month here.
+   *
+   * @param $value
+   *
+   * @return mixed
+   */
+  public function formatAggregationValue($value);
+
 }
diff --git a/Civi/DataProcessor/FieldOutputHandler/RawFieldOutputHandler.php b/Civi/DataProcessor/FieldOutputHandler/RawFieldOutputHandler.php
index 6347b7dd..c5e58656 100644
--- a/Civi/DataProcessor/FieldOutputHandler/RawFieldOutputHandler.php
+++ b/Civi/DataProcessor/FieldOutputHandler/RawFieldOutputHandler.php
@@ -89,5 +89,17 @@ class RawFieldOutputHandler extends AbstractSimpleFieldOutputHandler implements
     return $this->isAggregateField;
   }
 
+  /**
+   * Returns the value. And if needed a formatting could be applied.
+   * E.g. when the value is a date field and you want to aggregate on the month
+   * you can then return the month here.
+   *
+   * @param $value
+   * @return mixed
+   */
+  public function formatAggregationValue($value) {
+    return $value;
+  }
+
 
 }
diff --git a/templates/CRM/Dataprocessor/Form/Field/Configuration/DateFieldOutputHandler.tpl b/templates/CRM/Dataprocessor/Form/Field/Configuration/DateFieldOutputHandler.tpl
new file mode 100644
index 00000000..b97dc79c
--- /dev/null
+++ b/templates/CRM/Dataprocessor/Form/Field/Configuration/DateFieldOutputHandler.tpl
@@ -0,0 +1,23 @@
+{crmScope extensionKey='dataprocessor'}
+  {include file="CRM/Dataprocessor/Form/Field/Configuration/SimpleFieldOutputHandler.tpl"}
+  <div class="crm-section">
+    <div class="label">{$form.format.label}</div>
+    <div class="content">
+        {$form.format.html}
+      <p class="description">
+        {ts 1='https://www.php.net/manual/en/function.date.php#format'}See <a href="%1">DateTime format on php.net</a> for help.{/ts}
+      </p>
+    </div>
+    <div class="clear"></div>
+  </div>
+  <div class="crm-section">
+    <div class="label">{$form.function.label}</div>
+    <div class="content">{$form.function.html}</div>
+    <div class="clear"></div>
+  </div>
+  <div class="crm-section">
+    <div class="label">{$form.is_aggregate.label}</div>
+    <div class="content">{$form.is_aggregate.html}</div>
+    <div class="clear"></div>
+  </div>
+{/crmScope}
-- 
GitLab