diff --git a/CHANGELOG.md b/CHANGELOG.md index 162ff7a91dbdc705e7ec65b1e540f1e91c6a61c5..c9e61d00796b7c8d02f63e5408033e62231c4315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Version 1.119 (not yet released) +* Added Fallback Field Output Handler. + # Version 1.118 * Fixed issues with Aggregated Contribution data source diff --git a/Civi/DataProcessor/DataFlow/AbstractDataFlow.php b/Civi/DataProcessor/DataFlow/AbstractDataFlow.php index bb31229c7dd816c7495ee7cce0b4e20320a9b1b3..cd5051ea35ef9dfa3b738606456ed3916993c8b8 100644 --- a/Civi/DataProcessor/DataFlow/AbstractDataFlow.php +++ b/Civi/DataProcessor/DataFlow/AbstractDataFlow.php @@ -178,6 +178,9 @@ abstract class AbstractDataFlow { foreach($this->outputFieldHandlers as $outputFieldHandler) { $formattedRecord[$outputFieldHandler->getOutputFieldSpecification()->alias] = $outputFieldHandler->formatField($record, $formattedRecord); } + foreach($this->outputFieldHandlers as $outputFieldHandler) { + $formattedRecord = $outputFieldHandler->postFormat($formattedRecord); + } return $formattedRecord; } diff --git a/Civi/DataProcessor/Factory.php b/Civi/DataProcessor/Factory.php index 8210c67d3cba368557db6ed2333dd08c850e664f..9ce5a1267696497babebefe0c9e57e9eeb5a2ffb 100644 --- a/Civi/DataProcessor/Factory.php +++ b/Civi/DataProcessor/Factory.php @@ -236,6 +236,7 @@ class Factory { $this->addOutputHandler('sanitize_string', new Definition('Civi\DataProcessor\FieldOutputHandler\SanitizeString'), E::ts('Replaces special characters by their HTML code and newlines by \\n.')); $this->addOutputHandler('conditionally_map_strings', new Definition('Civi\DataProcessor\FieldOutputHandler\ConditionallyMapStrings'), E::ts('Compare the input with up to 5 strings and map the output accordingly.')); $this->addOutputHandler('contact_count_number_of_cases', new Definition('Civi\DataProcessor\FieldOutputHandler\ContactCountNumberOfCases'), E::ts('Count the number of cases related to the given CiviCRM ID.')); + $this->addOutputHandler('fallback', new Definition('Civi\DataProcessor\FieldOutputHandler\FallbackFieldOutputHandler'), E::ts('Fallback field output')); $this->addFileFormat('csv', new Definition('Civi\DataProcessor\FileFormat\CSVFileFormat'), E::ts('CSV')); } diff --git a/Civi/DataProcessor/FieldOutputHandler/AbstractFieldOutputHandler.php b/Civi/DataProcessor/FieldOutputHandler/AbstractFieldOutputHandler.php index b7d944d19773856b6d06c107bc2834d65db30ce7..4bdc8ba8149fa2ff0ea56d93e641448fc26de39b 100644 --- a/Civi/DataProcessor/FieldOutputHandler/AbstractFieldOutputHandler.php +++ b/Civi/DataProcessor/FieldOutputHandler/AbstractFieldOutputHandler.php @@ -159,5 +159,17 @@ abstract class AbstractFieldOutputHandler { return array(); } + /** + * Do post formatting. + * + * Post formatting is additional formatting of the formatted values of a record. + * + * @param \Civi\DataProcessor\FieldOutputHandler\FieldOutput[] $record + * @return \Civi\DataProcessor\FieldOutputHandler\FieldOutput[] + */ + public function postFormat(array $record) { + return $record; + } + } diff --git a/Civi/DataProcessor/FieldOutputHandler/FallbackFieldOutputHandler.php b/Civi/DataProcessor/FieldOutputHandler/FallbackFieldOutputHandler.php new file mode 100644 index 0000000000000000000000000000000000000000..c756c336b20d988dcf8c36cf6c68159c81a16bd9 --- /dev/null +++ b/Civi/DataProcessor/FieldOutputHandler/FallbackFieldOutputHandler.php @@ -0,0 +1,182 @@ +<?php +/** + * Copyright (C) 2025 Jaap Jansma (jaap.jansma@civicoop.org) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +namespace Civi\DataProcessor\FieldOutputHandler; + +use Civi\DataProcessor\DataSpecification\FieldSpecification; +use CRM_Dataprocessor_ExtensionUtil as E; + +class FallbackFieldOutputHandler extends AbstractFieldOutputHandler { + + /** + * @var \Civi\DataProcessor\DataSpecification\FieldSpecification + */ + protected $outputFieldSpec; + + /** + * @var String + */ + protected $field; + + /** + * @var String + */ + protected $fallbackField; + + /** + * Initialize the processor + * + * @param String $alias + * @param String $title + * @param array $configuration + * @param \Civi\DataProcessor\ProcessorType\AbstractProcessorType $processorType + */ + public function initialize($alias, $title, $configuration) { + $this->outputFieldSpec = new FieldSpecification($alias, $this->getType(), $title); + $this->field = $configuration['field']; + $this->fallbackField = $configuration['fallback_field']; + } + + /** + * Returns the data type of this field + * + * @return String + */ + protected function getType() { + return 'String'; + } + + /** + * Returns the formatted value + * + * @param $rawRecord + * @param $formattedRecord + * + * @return \Civi\DataProcessor\FieldOutputHandler\FieldOutput + */ + public function formatField($rawRecord, $formattedRecord) { + return new FieldOutput(); + } + + /** + * @return \Civi\DataProcessor\DataSpecification\FieldSpecification + */ + public function getOutputFieldSpecification() { + return $this->outputFieldSpec; + } + + /** + * Do post formatting. + * + * Post formatting is additional formatting of the formatted values of a + * record. + * + * @param \Civi\DataProcessor\FieldOutputHandler\FieldOutput[] $record + * + * @return \Civi\DataProcessor\FieldOutputHandler\FieldOutput[] + */ + public function postFormat(array $record): array { + $alias = $this->getOutputFieldSpecification()->alias; + $field = $record[$this->field]; + $fallback = $record[$this->fallbackField]; + $record[$alias] = clone $fallback; + if ($field && !$field->isEmpty()) { + $record[$alias] = clone $field; + } + return $record; + } + + /** + * Returns true when this handler has additional configuration. + * + * @return bool + */ + public function hasConfiguration() { + return true; + } + + /** + * 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()) { + $fieldSelect = $this->getFieldOutputHandlers($field['data_processor_id']); + + $form->add('select', 'field', E::ts('Field'), $fieldSelect, true, array( + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge data-processor-field-for-name', + 'placeholder' => E::ts('- select -'), + )); + $form->add('select', 'fallback_field', E::ts('Fallback field'), $fieldSelect, true, array( + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge', + 'placeholder' => E::ts('- select -'), + )); + if (isset($field['configuration'])) { + $configuration = $field['configuration']; + $defaults = array(); + if (isset($configuration['field'])) { + $defaults['field'] = $configuration['field']; + } + if (isset($configuration['fallback_field'])) { + $defaults['fallback_field'] = $configuration['fallback_field']; + } + $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/FallbackFieldOutputHandler.tpl"; + } + + + /** + * Process the submitted values and create a configuration array + * + * @param $submittedValues + * @return array + */ + public function processConfiguration($submittedValues) { + $configuration['field'] = $submittedValues['field']; + $configuration['fallback_field'] = $submittedValues['fallback_field']; + return $configuration; + } + + protected function getFieldOutputHandlers(int $dataProcessorId) { + $return = []; + $dataProcessor = civicrm_api3('DataProcessor', 'getsingle', array('id' => $dataProcessorId)); + $dataProcessorClass = \CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($dataProcessor); + $outputFieldHandlers = $dataProcessorClass->getDataFlow()->getOutputFieldHandlers(); + foreach ($outputFieldHandlers as $outputFieldHandler) { + if ($outputFieldHandler->getOutputFieldSpecification()->alias != $this->getOutputFieldSpecification()->alias) { + $return[$outputFieldHandler->getOutputFieldSpecification()->alias] = $outputFieldHandler->getOutputFieldSpecification()->title; + } + } + return $return; + } + +} diff --git a/Civi/DataProcessor/FieldOutputHandler/FieldOutput.php b/Civi/DataProcessor/FieldOutputHandler/FieldOutput.php index ca22747fd03a5cc3653965c8014cb644da20923f..0db5012a03d55664cb169cb09b94978d196ff6dc 100644 --- a/Civi/DataProcessor/FieldOutputHandler/FieldOutput.php +++ b/Civi/DataProcessor/FieldOutputHandler/FieldOutput.php @@ -12,9 +12,19 @@ class FieldOutput { public $formattedValue; + protected $isEmpty = null; + public function __construct($rawValue=null) { $this->rawValue = $rawValue; $this->formattedValue = $rawValue; } -} \ No newline at end of file + public function isEmpty(): bool { + return $this->rawValue === NULL || $this->rawValue === ''; + } + + public function overrideIsEmpty(bool $isEmpty): void { + $this->isEmpty = $isEmpty; + } + +} diff --git a/templates/CRM/Dataprocessor/Form/Field/Configuration/FallbackFieldOutputHandler.tpl b/templates/CRM/Dataprocessor/Form/Field/Configuration/FallbackFieldOutputHandler.tpl new file mode 100644 index 0000000000000000000000000000000000000000..e2c9132b79c4dc155b32b75738f7e9a77e0b9113 --- /dev/null +++ b/templates/CRM/Dataprocessor/Form/Field/Configuration/FallbackFieldOutputHandler.tpl @@ -0,0 +1,12 @@ +{crmScope extensionKey='dataprocessor'} + <div class="crm-section"> + <div class="label">{$form.field.label}</div> + <div class="content">{$form.field.html}</div> + <div class="clear"></div> + </div> + <div class="crm-section"> + <div class="label">{$form.fallback_field.label}</div> + <div class="content">{$form.fallback_field.html}</div> + <div class="clear"></div> + </div> +{/crmScope}