Commit 28ed3089 authored by jaapjansma's avatar jaapjansma
Browse files

Added date field

parent 271833cd
......@@ -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
......
......@@ -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');
......
......@@ -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;
......
......@@ -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());
......
......@@ -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;
}
......
......@@ -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
}
......@@ -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_';
}
......
......@@ -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}`";
}
......
......@@ -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'));
......
<?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'),
),
);
}
}
......@@ -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);
}
......@@ -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;
}
}
{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}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment