Commit 6acd174a authored by jaapjansma's avatar jaapjansma
Browse files

made event token output and tokens now also work after a participant search

parent bf791c78
......@@ -9,6 +9,7 @@ namespace Civi\DataProcessorTokenOutput;
use CRM_DataprocessorTokenOutput_ExtensionUtil as E;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
class CompilerPass implements CompilerPassInterface {
......@@ -19,16 +20,31 @@ class CompilerPass implements CompilerPassInterface {
if (!$container->hasDefinition('data_processor_factory')) {
return;
}
$factoryDefinition = $container->getDefinition('data_processor_factory');
$tokenFactoryDefinition = new Definition('Civi\DataProcessorTokenOutput\TokenFactory');
$factoryDefinition->addMethodCall('addOutput', array(
'token', 'Civi\DataProcessorTokenOutput\Output\Token', E::ts('Tokens')
'token', 'Civi\DataProcessorTokenOutput\Output\ContactToken', E::ts('Tokens')
));
$tokenFactoryDefinition->addMethodCall('addTokenOutputType', ['token']);
$factoryDefinition->addMethodCall('addOutput', array(
'household_token', 'Civi\DataProcessorTokenOutput\Output\HouseholdToken', E::ts('Household Tokens')
));
$tokenFactoryDefinition->addMethodCall('addTokenOutputType', ['household_token']);
$factoryDefinition->addMethodCall('addOutput', array(
'participant_token', 'Civi\DataProcessorTokenOutput\Output\ParticipantToken', E::ts('Participants Tokens')
));
$tokenFactoryDefinition->addMethodCall('addTokenOutputType', ['participant_token']);
$factoryDefinition->addMethodCall('addOutput', array(
'event_token', 'Civi\DataProcessorTokenOutput\Output\EventToken', E::ts('Event Tokens')
));
$tokenFactoryDefinition->addMethodCall('addTokenOutputType', ['event_token']);
$container->setDefinition('data_processor_token_factory', $tokenFactoryDefinition);
}
......
......@@ -16,7 +16,37 @@ use Civi\DataProcessor\Output\OutputInterface;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
use CRM_DataprocessorTokenOutput_ExtensionUtil as E;
class Token implements OutputInterface, TokenOutput {
abstract class AbstractToken implements OutputInterface, TokenOutput {
/**
* Returns the title of the ID field.
*
* @return String
*/
abstract protected function getIdFieldTitle();
/**
* Function to set a different contact ID to be used in the data processor.
* This function is here so child classes can override it.
*
* @param int $contactId
* @param array $values
* @param array $configuration
*
* @return int|null
*/
abstract protected function getIdForToken($contactId, $values, $configuration);
/**
* @return string
*/
protected function getIdFieldName() {
return 'id_field';
}
protected function getHelpText() {
return '';
}
/**
* Returns true when this output has additional configuration
......@@ -47,7 +77,8 @@ class Token implements OutputInterface, TokenOutput {
$sortFields['desc_'.$field->alias] = E::ts('Descending sort %1', array(1=>$field->title));
}
$form->add('select', 'contact_id_field', E::ts('Contact ID field'), $fieldSelect, true, array(
$form->assign('id_field_name', $this->getIdFieldName());
$form->add('select', $this->getIdFieldName(), $this->getIdFieldTitle(), $fieldSelect, true, array(
'style' => 'min-width:250px',
'class' => 'crm-select2 huge',
'placeholder' => E::ts('- select -'),
......@@ -66,7 +97,7 @@ class Token implements OutputInterface, TokenOutput {
'multiple' => false,
'placeholder' => E::ts('- select -'),
));
$form->assign('help_text', $this->getHelpText());
$defaults = array();
if ($output) {
......@@ -74,8 +105,8 @@ class Token implements OutputInterface, TokenOutput {
if (isset($output['configuration']['help_text'])) {
$defaults['help_text'] = $output['configuration']['help_text'];
}
if (isset($output['configuration']['contact_id_field'])) {
$defaults['contact_id_field'] = $output['configuration']['contact_id_field'];
if (isset($output['configuration'][$this->getIdFieldName()])) {
$defaults[$this->getIdFieldName()] = $output['configuration'][$this->getIdFieldName()];
}
if (isset($output['configuration']['hidden_fields'])) {
$defaults['hidden_fields'] = $output['configuration']['hidden_fields'];
......@@ -111,7 +142,7 @@ class Token implements OutputInterface, TokenOutput {
* @return array $output
*/
public function processConfiguration($submittedValues, &$output) {
$configuration['contact_id_field'] = $submittedValues['contact_id_field'];
$configuration[$this->getIdFieldName()] = $submittedValues[$this->getIdFieldName()];
$configuration['hidden_fields'] = $submittedValues['hidden_fields'];
$configuration['sort'] = $submittedValues['sortfield'];
return $configuration;
......@@ -176,47 +207,82 @@ class Token implements OutputInterface, TokenOutput {
}
try {
$availableFields = $dataProcessorClass->getDataFlow()->getOutputFieldHandlers();
$allFields = $dataProcessorClass->getDataFlow()->getOutputFieldHandlers();
} catch (\Exception $e) {
return; // No fields available.
}
$contactIdToUse = $this->getContactIdForToken($contact_id, $configuration);
if (!$contactIdToUse) {
$availableFields = [];
foreach ($allFields as $outputFieldHandler) {
$field = $outputFieldHandler->getOutputFieldSpecification();
if (!in_array($field->alias, $hiddenFields)) {
$availableFields[] = $field;
}
}
$idToUse = $this->getIdForToken($contact_id, $values, $configuration);
if (!$idToUse) {
return;
}
$this->initializeDataProcessorClass($contactIdToUse, $dataProcessorClass, $configuration);
$this->initializeDataProcessorClass($idToUse, $dataProcessorClass, $configuration);
$this->process($dataProcessorClass, $values, $availableFields, $name);
}
/**
* Convert the data from the data processor to tokens.
*
* @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass
* @param array $values
* @param \Civi\DataProcessor\DataSpecification\FieldSpecification[] $availableFields
* @param string $name
*
* @throws \Civi\DataProcessor\DataFlow\InvalidFlowException
*/
protected function process(AbstractProcessorType $dataProcessorClass, &$values, $availableFields, $name) {
try {
$record = $dataProcessorClass->getDataFlow()->nextRecord();
} catch (EndOfFlowException $e) {
return; // Do nothing there is no data.
}
foreach($availableFields as $outputFieldHandler) {
$field = $outputFieldHandler->getOutputFieldSpecification();
if (!in_array($field->alias, $hiddenFields)) {
$values[$name.'.'.$field->alias] = $record[$field->alias]->formattedValue;
}
foreach($availableFields as $field) {
$values[$name.'.'.$field->alias] = $record[$field->alias]->formattedValue;
}
}
protected function initializeDataProcessorClass($contact_id, AbstractProcessorType $dataProcessorClass, $configuration) {
list($datasource_name, $field_name) = explode('::', $configuration['contact_id_field'], 2);
/**
* Initialize the data processor.
*
* @param int|array $idsToUse
* @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessorClass
* @param array $configuration
*
* @throws \Civi\DataProcessor\DataFlow\InvalidFlowException
* @throws \Civi\DataProcessor\Exception\DataSourceNotFoundException
* @throws \Civi\DataProcessor\Exception\FieldNotFoundException
*/
protected function initializeDataProcessorClass($idsToUse, AbstractProcessorType $dataProcessorClass, $configuration) {
list($datasource_name, $field_name) = explode('::', $configuration[$this->getIdFieldName()], 2);
$dataSource = $dataProcessorClass->getDataSourceByName($datasource_name);
if (!$dataSource) {
throw new DataSourceNotFoundException(E::ts("Requires data source '%1' which could not be found. Did you rename or deleted the data source?", array(1=>$datasource_name)));
throw new DataSourceNotFoundException(E::ts("Requires data source '%1' which could not be found. Did you rename or deleted the data source?", [1 => $datasource_name]));
}
$fieldSpecification = $dataSource->getAvailableFilterFields()->getFieldSpecificationByAlias($field_name);
$fieldSpecification = $dataSource->getAvailableFilterFields()
->getFieldSpecificationByAlias($field_name);
if (!$fieldSpecification) {
throw new FieldNotFoundException(E::ts("Requires a field with the name '%1' in the data source '%2'. Did you change the data source type?", array(
throw new FieldNotFoundException(E::ts("Requires a field with the name '%1' in the data source '%2'. Did you change the data source type?", [
1 => $field_name,
2 => $datasource_name
)));
]));
}
$dataFlow = $dataSource->ensureField($fieldSpecification);
if ($dataFlow && $dataFlow instanceof SqlDataFlow) {
$whereClause = new SqlDataFlow\SimpleWhereClause($dataFlow->getName(), $fieldSpecification->name, '=', $contact_id, $fieldSpecification->type);
$operator = '=';
if (is_array($idsToUse)) {
$operator = 'IN';
}
$whereClause = new SqlDataFlow\SimpleWhereClause($dataFlow->getName(), $fieldSpecification->name, $operator, $idsToUse, $fieldSpecification->type);
$dataFlow->addWhereClause($whereClause);
}
......@@ -231,18 +297,5 @@ class Token implements OutputInterface, TokenOutput {
$dataProcessorClass->getDataFlow()->setOffset(0);
}
/**
* Function to set a different contact ID to be used in the data processor.
* This function is here so child classes can override it.
*
* @param $contactId
* @param $configuration
*
* @return mixed
*/
protected function getContactIdForToken($contactId, $configuration) {
return $contactId;
}
}
<?php
/**
* @author Jaap Jansma <jaap.jansma@civicoop.org>
* @license AGPL-3.0
*/
namespace Civi\DataProcessorTokenOutput\Output;
use Civi\DataProcessor\DataFlow\EndOfFlowException;
use Civi\DataProcessor\DataFlow\Sort\SortCompareFactory;
use Civi\DataProcessor\DataFlow\SqlDataFlow;
use Civi\DataProcessor\Exception\DataSourceNotFoundException;
use Civi\DataProcessor\Exception\FieldNotFoundException;
use Civi\DataProcessor\Output\OutputInterface;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
use CRM_DataprocessorTokenOutput_ExtensionUtil as E;
class ContactToken extends AbstractToken {
/**
* Returns the title of the ID field.
*
* @return String
*/
protected function getIdFieldTitle() {
return E::ts('Contact ID Field');
}
/**
* @return string
*/
protected function getIdFieldName() {
return 'contact_id_field';
}
/**
* Function to set a different contact ID to be used in the data processor.
* This function is here so child classes can override it.
*
* @param int $contactId
* @param array $values
* @param array $configuration
*
* @return int|null
*/
protected function getIdForToken($contactId, $values, $configuration) {
return $contactId;
}
}
<?php
/**
* @author Jaap Jansma <jaap.jansma@civicoop.org>
* @license AGPL-3.0
*/
namespace Civi\DataProcessorTokenOutput\Output;
use Civi\DataProcessor\DataFlow\EndOfFlowException;
use Civi\DataProcessor\DataFlow\Sort\SortCompareFactory;
use Civi\DataProcessor\DataFlow\SqlDataFlow;
use Civi\DataProcessor\Exception\DataSourceNotFoundException;
use Civi\DataProcessor\Exception\FieldNotFoundException;
use Civi\DataProcessor\Output\OutputInterface;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
use CRM_DataprocessorTokenOutput_ExtensionUtil as E;
class EventToken extends AbstractToken {
/**
* Array containing all event ids
*
* This property is set when executed from an participant search action.
*
* @var array
*/
private static $_eventIds = null;
/**
* Function to set a different contact ID to be used in the data processor.
* This function is here so child classes can override it.
*
* @param $contactId
* @param $configuration
*
* @return mixed
*/
protected function getIdForToken($contactId, $values, $configuration) {
if (isset($values['extra_data']['event']['id'])) {
// Coming from CiviRules
return $values['extra_data']['event']['id'];
}
if (isset($values['extra_data']['participant']['id'])) {
// Coming from CiviRules
$participant_id = $values['extra_data']['participant']['id'];
try {
$event_id = civicrm_api3('Participant', 'getvalue', [
'id' => $participant_id,
'return' => 'event_id'
]);
} catch (\CiviCRM_API3_Exception $e) {
return false;
}
return $event_id;
} elseif (is_array(self::$_eventIds) && count(self::$_eventIds)) {
foreach(self::$_eventIds as $key => $val) {
if ($val['contact_id'] == $contactId) {
unset(self::$_eventIds[$key]);
return $val['event_id'];
}
}
return array_shift(self::$_eventIds);
}
return false;
}
/**
* Returns the title of the ID field.
*
* @return String
*/
protected function getIdFieldTitle() {
return E::ts('Event ID Field');
}
/**
* @return string
*/
protected function getIdFieldName() {
return 'event_id_field';
}
/**
* Extract the event ids from the event task forms.
* We need this as soon as the task is a send letter or send e-mail from the action
* after a search for participants.
*
* We can then use those event ids during the processing of the tokens.
*
* @param $form
*/
public static function extractParticipantIdsFromEventFormTasks(\CRM_Core_Form $form) {
if ($form instanceof \CRM_Event_Form_Task) {
$participantIds = $form->getVar('_participantIds');
self::$_eventIds = array();
$IDs = implode(',', $participantIds);
$query = "SELECT contact_id, event_id, id FROM civicrm_participant WHERE id IN ($IDs)";
$dao = \CRM_Core_DAO::executeQuery($query);
while ($dao->fetch()) {
$key = array_search($dao->id, $participantIds);
if ($key !== false) {
self::$_eventIds[$key] = ['contact_id' => $dao->contact_id, 'event_id' => $dao->event_id];
}
}
ksort(self::$_eventIds);
}
}
}
......@@ -16,7 +16,23 @@ use Civi\DataProcessor\Output\OutputInterface;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
use CRM_DataprocessorTokenOutput_ExtensionUtil as E;
class HouseholdToken extends Token {
class HouseholdToken extends AbstractToken {
/**
* Returns the title of the ID field.
*
* @return String
*/
protected function getIdFieldTitle() {
return E::ts('Contact ID Field');
}
/**
* @return string
*/
protected function getIdFieldName() {
return 'contact_id_field';
}
/**
* When this output type has additional configuration you can add
......@@ -88,22 +104,27 @@ class HouseholdToken extends Token {
return $configuration;
}
/**
* Function to set a different contact ID to be used in the data processor.
* This function is here so child classes can override it.
*
* @param $contactId
* @param $configuration
* @param int $contactId
* @param array $values
* @param array $configuration
*
* @return mixed
* @return int|null
*/
protected function getContactIdForToken($contactId, $configuration) {
$householdContactId = false;
$contact = civicrm_api3('Contact', 'getsingle', array('id' => $contactId));
protected function getIdForToken($contactId, $values, $configuration) {
$householdContactId = FALSE;
try {
$contact = civicrm_api3('Contact', 'getsingle', ['id' => $contactId]);
} catch (\CiviCRM_API3_Exception $e) {
return null;
}
if ($contact['contact_type'] == 'Individual') {
$householdContactId = $this->retrieveHousehold($contactId, $configuration['relationship_types']);
} elseif ($contact['contact_type'] == 'Household') {
}
elseif ($contact['contact_type'] == 'Household') {
$householdContactId = $contactId;
}
return $householdContactId;
......
......@@ -16,236 +16,65 @@ use Civi\DataProcessor\Output\OutputInterface;
use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
use CRM_DataprocessorTokenOutput_ExtensionUtil as E;
class ParticipantToken implements OutputInterface, TokenOutput {
class ParticipantToken extends AbstractToken {
/**
* Returns true when this output has additional configuration
* Array containing all the related participant ids
*
* @return bool
*/
public function hasConfiguration() {
return true;
}
/**
* When this output type has additional configuration you can add
* the fields on the form with this function.
* This property is set when executed from an participant search action.
*
* @param \CRM_Core_Form $form
* @param array $output
* @var array
*/
public function buildConfigurationForm(\CRM_Core_Form $form, $output = []) {
$fieldSelect = \CRM_Dataprocessor_Utils_DataSourceFields::getAvailableFilterFieldsInDataSources($output['data_processor_id']);
$dataProcessor = civicrm_api3('DataProcessor', 'getsingle', array('id' => $output['data_processor_id']));
$dataProcessorClass = \CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($dataProcessor);
$fields = array();
$sortFields = array();
foreach($dataProcessorClass->getDataFlow()->getOutputFieldHandlers() as $outputFieldHandler) {
$field = $outputFieldHandler->getOutputFieldSpecification();
$fields[$field->alias] = $field->title;
$sortFields['asc_'.$field->alias] = E::ts('Ascending sort %1', array(1=>$field->title));
$sortFields['desc_'.$field->alias] = E::ts('Descending sort %1', array(1=>$field->title));
}
$form->add('select', 'participant_id_field', E::ts('Participant ID field'), $fieldSelect, true, array(
'style' => 'min-width:250px',
'class' => 'crm-select2 huge',
'placeholder' => E::ts('- select -'),
));
$form->add('select', 'hidden_fields', E::ts('Hidden fields'), $fields, false, array(
'style' => 'min-width:250px',
'class' => 'crm-select2 huge',
'multiple' => true,
'placeholder' => E::ts('- select -'),
));
$form->add('select', 'sortfield', E::ts('Sort'), $sortFields, false, array(
'style' => 'min-width:250px',
'class' => 'crm-select2 huge',
'multiple' => false,
'placeholder' => E::ts('- select -'),
));
$defaults = array();
if ($output) {
if (isset($output['configuration']) && is_array($output['configuration'])) {
if (isset($output['configuration']['help_text'])) {
$defaults['help_text'] = $output['configuration']['help_text'];
}
if (isset($output['configuration']['participant_id_field'])) {
$defaults['participant_id_field'] = $output['configuration']['participant_id_field'];
}
if (isset($output['configuration']['hidden_fields'])) {
$defaults['hidden_fields'] = $output['configuration']['hidden_fields'];
}
if (isset($output['configuration']['sort'])) {
$defaults['sortfield'] = $output['configuration']['sort'];
}
}
}
if (!isset($defaults['default_limit'])) {
$sortFieldNames = array_keys($sortFields);
$defaults['sort'] = reset($sortFieldNames); // Get the first field.
}
$form->setDefaults($defaults);
}
private static $_participantIds = null;
/**
* When this output type has configuration specify the template file name
* for the configuration form.
*
* @return false|string
*/
public function getConfigurationTemplateFileName() {
return "CRM/DataprocessorTokenOutput/Form/ParticipantTokenConfiguration.tpl";
}
/**
* Process the submitted values and create a configuration array
*
* @param $submittedValues
* @param array $output
*
* @return array $output
*/
public function processConfiguration($submittedValues, &$output) {
$configuration['participant_id_field'] = $submittedValues['participant_id_field'];
$configuration['hidden_fields'] = $submittedValues['hidden_fields'];
$configuration['sort'] = $submittedValues['sortfield'];
return $configuration;
}
/**
* This function is called prior to removing an output
* Function to set a different contact ID to be used in the data processor.
* This function is here so child classes can override it.
*
* @param array $output
* @param $contactId
* @param $configuration
*
* @return void
* @return mixed
*/
public function deleteOutput($output) {
// Do nothing.
protected function getIdForToken($contactId, $values, $configuration) {
if (isset($values['extra_data']['participant']['id'])) {
// Coming from CiviRules
return $values['extra_data']['participant']['id'];
} elseif (is_array(self::$_participantIds) && count(self::$_participantIds)) {
return array_shift(self::$_participantIds);
}
return false;
}