From 16a7c299c1258a87c390d1deeb129d13520718e0 Mon Sep 17 00:00:00 2001 From: Jaap Jansma Date: Thu, 16 Nov 2023 09:51:52 +0100 Subject: [PATCH] Added join types for joining either contribtion or contribution recur on the mandate --- Civi/Sepa/ContainerSpecs.php | 4 + .../Join/AbstractMandateJoin.php | 274 ++++++++++++++++++ .../Join/MandateContributionJoin.php | 37 +++ .../Join/MandateContributionRecurJoin.php | 37 +++ .../Join/MandateContributionJoin.tpl | 19 ++ .../Join/MandateContributionRecurJoin.tpl | 19 ++ 6 files changed, 390 insertions(+) create mode 100644 Civi/Sepa/DataProcessor/Join/AbstractMandateJoin.php create mode 100644 Civi/Sepa/DataProcessor/Join/MandateContributionJoin.php create mode 100644 Civi/Sepa/DataProcessor/Join/MandateContributionRecurJoin.php create mode 100644 templates/CRM/Sepa/Form/DataProcessor/Join/MandateContributionJoin.tpl create mode 100644 templates/CRM/Sepa/Form/DataProcessor/Join/MandateContributionRecurJoin.tpl diff --git a/Civi/Sepa/ContainerSpecs.php b/Civi/Sepa/ContainerSpecs.php index 6b3ea750..3e7c7d1a 100644 --- a/Civi/Sepa/ContainerSpecs.php +++ b/Civi/Sepa/ContainerSpecs.php @@ -76,6 +76,10 @@ public function process(ContainerBuilder $container) { 'sepa_contribution_group', 'Civi\Sepa\DataProcessor\Source\SepaContributionGroup', E::ts('SEPA Contribution Group')]); $dataProcessorFactoryDefinition->addMethodCall('addDataSource', [ 'sepa_mandate_link', 'Civi\Sepa\DataProcessor\Source\SepaMandateLink', E::ts('SEPA Mandate Link')]); + $dataProcessorFactoryDefinition->addMethodCall('addjoinType' ,[ + 'sepa_mandate_contribution_join', 'Civi\Sepa\DataProcessor\Join\MandateContributionJoin', E::ts('Join Sepa Mandate on Contribution')]); + $dataProcessorFactoryDefinition->addMethodCall('addjoinType' ,[ + 'sepa_mandate_contribution_recur_join', 'Civi\Sepa\DataProcessor\Join\MandateContributionRecurJoin', E::ts('Join Sepa Mandate on Contribution Recur')]); } } } diff --git a/Civi/Sepa/DataProcessor/Join/AbstractMandateJoin.php b/Civi/Sepa/DataProcessor/Join/AbstractMandateJoin.php new file mode 100644 index 00000000..f70e74f6 --- /dev/null +++ b/Civi/Sepa/DataProcessor/Join/AbstractMandateJoin.php @@ -0,0 +1,274 @@ +. + */ + +namespace Civi\Sepa\DataProcessor\Join; + +use Civi\DataProcessor\DataFlow\AbstractDataFlow; +use Civi\DataProcessor\DataFlow\MultipleDataFlows\DataFlowDescription; +use Civi\DataProcessor\DataFlow\MultipleDataFlows\JoinInterface; +use Civi\DataProcessor\DataFlow\MultipleDataFlows\SimpleJoin; +use Civi\DataProcessor\DataFlow\SqlDataFlow; +use Civi\DataProcessor\DataFlow\SqlTableDataFlow; +use Civi\DataProcessor\DataSpecification\FieldSpecification; +use Civi\DataProcessor\ProcessorType\AbstractProcessorType; +use Civi\DataProcessor\Source\SourceInterface; +use Civi\DataProcessor\Utils\Sql; +use CRM_Core_Exception; +use CRM_Core_Form; +use CRM_Dataprocessor_Utils_DataSourceFields; +use CRM_Sepa_ExtensionUtil as E; +use Exception; + +abstract class AbstractMandateJoin extends SimpleJoin { + + /** + * Returns the entity table. + * + * @return string + */ + abstract protected function getEntityTable(): string; + + /** + * @var AbstractProcessorType + */ + protected $dataProcessor; + + /** + * @param AbstractProcessorType $dataProcessor + * @return \Civi\DataProcessor\DataFlow\MultipleDataFlows\JoinInterface + * @throws \Exception + */ + public function setDataProcessor(AbstractProcessorType $dataProcessor): JoinInterface { + parent::setDataProcessor($dataProcessor); + $this->dataProcessor = $dataProcessor; + return $this; + } + + + /** + * Returns true when this join has additional configuration + * + * @return bool + */ + public function hasConfiguration(): bool { + return true; + } + + /** + * When this join has additional configuration you can add + * the fields on the form with this function. + * + * @param \CRM_Core_Form $form + * @param SourceInterface $joinFromSource + * @param SourceInterface[] $joinableToSources + * @param array $joinConfiguration + * The current join configuration + */ + public function buildConfigurationForm(CRM_Core_Form $form, SourceInterface $joinFromSource, $joinableToSources, $joinConfiguration=array()) { + $leftFieldCallback = null; + $lookForRightSddEntityIdField = true; + if ($joinFromSource->getDataFlow() instanceof SqlTableDataFlow && $joinFromSource->getDataFlow()->getTable() == 'civicrm_sdd_mandate') { + $leftFieldCallback = [$this, 'filterEntityIdField']; + $lookForRightSddEntityIdField = false; + } + $leftFields = []; + try { + $leftFields = CRM_Dataprocessor_Utils_DataSourceFields::getAvailableFieldsInDataSource($joinFromSource, '', '', $leftFieldCallback); + } + catch (Exception $e) { + } + + try { + $form->add('select', 'left_field', ts('Select field'), $leftFields, TRUE, [ + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge', + 'placeholder' => E::ts('- select -'), + ]); + } + catch (CRM_Core_Exception $e) { + } + + $rightFields = array(); + foreach($joinableToSources as $joinToSource) { + try { + if ($lookForRightSddEntityIdField && $joinToSource->getDataFlow() instanceof SqlTableDataFlow && $joinToSource->getDataFlow() + ->getTable() == 'civicrm_sdd_mandate') { + $rightFields = array_merge($rightFields, CRM_Dataprocessor_Utils_DataSourceFields::getAvailableFieldsInDataSource($joinToSource, $joinToSource->getSourceTitle() . ' :: ', $joinToSource->getSourceName() . '::', [ + $this, + 'filterEntityIdField', + ])); + } + elseif (!$lookForRightSddEntityIdField) { + $rightFields = array_merge($rightFields, CRM_Dataprocessor_Utils_DataSourceFields::getAvailableFieldsInDataSource($joinToSource, $joinToSource->getSourceTitle() . ' :: ', $joinToSource->getSourceName() . '::')); + } + } + catch (Exception $e) { + } + } + + try { + $form->add('select', 'right_field', ts('Select field'), $rightFields, TRUE, [ + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge', + 'placeholder' => E::ts('- select -'), + ]); + } + catch (CRM_Core_Exception $e) { + } + + try { + $form->add('select', 'mandate_join_type', ts('Type'), [ + 'INNER' => E::ts('Required'), + 'LEFT' => E::ts('Not required'), + ], TRUE, [ + 'style' => 'min-width:250px', + 'class' => 'crm-select2 huge', + 'placeholder' => E::ts('- select -'), + ]); + } + catch (CRM_Core_Exception $e) { + } + + $defaults = array(); + if (isset($joinConfiguration['left_field'])) { + $defaults['left_field'] = $joinConfiguration['left_field']; + } + if (isset($joinConfiguration['right_prefix'])) { + $defaults['right_field'] = $joinConfiguration['right_prefix']."::".$joinConfiguration['right_field']; + } + if (!isset($joinConfiguration['mandate_join_type'])) { + $joinConfiguration['mandate_join_type'] = 'LEFT'; + } + $defaults['mandate_join_type'] = $joinConfiguration['mandate_join_type']; + $form->setDefaults($defaults); + } + + /** + * @param \Civi\DataProcessor\DataSpecification\FieldSpecification $field + * + * @return bool + */ + public function filterEntityIdField(FieldSpecification $field): bool { + if ($field->getName() == 'entity_id') { + return true; + } + return false; + } + + /** + * Process the submitted values and create a configuration array + * + * @param $submittedValues + * @param SourceInterface $joinFromSource + * @return array + */ + public function processConfiguration($submittedValues, SourceInterface $joinFromSource): array { + $configuration = parent::processConfiguration($submittedValues, $joinFromSource); + $configuration['mandate_join_type'] = $submittedValues['mandate_join_type']; + return $configuration; + } + + /** + * @param array $configuration + * + * @return \Civi\DataProcessor\DataFlow\MultipleDataFlows\JoinInterface + */ + public function setConfiguration($configuration): JoinInterface { + parent::setConfiguration($configuration); + $this->setType($configuration['mandate_join_type']); + return $this; + } + + /** + * Returns true when this join is compatible with this data flow + * + * @param \Civi\DataProcessor\DataFlow\AbstractDataFlow $dataFlow + * @return bool + */ + public function worksWithDataFlow(AbstractDataFlow $dataFlow): bool { + if (!$dataFlow instanceof SqlDataFlow) { + return false; + } + $this->initialize(); + if ($dataFlow->getTableAlias() == $this->left_table) { + return true; + } + if ($dataFlow->getTableAlias() == $this->right_table) { + return true; + } + return false; + } + + /** + * Returns the SQL join statement + * + * For example: + * INNER JOIN civicrm_contact source_3 ON source_3.id = source_2.contact_id + * OR + * LEFT JOIN civicrm_contact source_3 ON source3.id = source_2.contact_id + * + * @param \Civi\DataProcessor\DataFlow\MultipleDataFlows\DataFlowDescription $sourceDataFlowDescription + * The source data flow description used to genereate the join stament. + * + * @return string + */ + public function getJoinClause(DataFlowDescription $sourceDataFlowDescription): string { + $this->initialize(); + $tablePart = ''; + $joinClause = ''; + $mandateTableAlias = "`$this->left_table`"; + if ($this->right_source->getDataFlow() instanceof SqlTableDataFlow && $this->right_source->getDataFlow()->getTable() == 'civicrm_sdd_mandate') { + $mandateTableAlias = "`$this->right_table`"; + } + if ($sourceDataFlowDescription->getJoinSpecification()) { + $joinClauses = []; + $leftColumnName = "`$this->left_table`.`$this->left_field`"; + if ($this->leftFieldSpec) { + $leftColumnName = $this->leftFieldSpec->getSqlColumnName($this->left_table); + } + $rightColumnName = "`$this->right_table`.`$this->right_field`"; + if ($this->rightFieldSpec) { + $rightColumnName = $this->rightFieldSpec->getSqlColumnName($this->right_table); + } + + $joinClauses[] = "($leftColumnName = $rightColumnName AND $mandateTableAlias.`entity_table` = '" . $this->getEntityTable() . "')"; + $joinClause = "ON (" . implode(" OR ", $joinClauses) . ")"; + } + if ($sourceDataFlowDescription->getDataFlow() instanceof SqlDataFlow) { + $tablePart = $sourceDataFlowDescription->getDataFlow()->getTableStatement(); + } + + $dataFlow = $sourceDataFlowDescription->getDataFlow(); + if ($dataFlow instanceof SqlDataFlow) { + $whereClauses = $dataFlow->getWhereClauses(TRUE, FALSE); + foreach($whereClauses as $whereClause) { + if ($whereClause && $whereClause->isJoinClause()) { + $this->filterClauses[] = $whereClause; + $dataFlow->removeWhereClause($whereClause); + } + } + } + $extraClause = Sql::generateConditionStatement($this->filterClauses); + if (strlen($extraClause)) { + $extraClause = " AND ".$extraClause; + } + + return "$this->type JOIN $tablePart $joinClause $extraClause"; + } + +} diff --git a/Civi/Sepa/DataProcessor/Join/MandateContributionJoin.php b/Civi/Sepa/DataProcessor/Join/MandateContributionJoin.php new file mode 100644 index 00000000..b686354e --- /dev/null +++ b/Civi/Sepa/DataProcessor/Join/MandateContributionJoin.php @@ -0,0 +1,37 @@ +. + */ + +namespace Civi\Sepa\DataProcessor\Join; + +class MandateContributionJoin extends AbstractMandateJoin { + + /** + * When this join has configuration specify the template file name + * for the configuration form. + * + * @return false|string + */ + public function getConfigurationTemplateFileName():? string { + return "CRM/Sepa/Form/DataProcessor/Join/MandateContributionJoin.tpl"; + } + + protected function getEntityTable(): string { + return 'civicrm_contribution'; + } + +} diff --git a/Civi/Sepa/DataProcessor/Join/MandateContributionRecurJoin.php b/Civi/Sepa/DataProcessor/Join/MandateContributionRecurJoin.php new file mode 100644 index 00000000..9c920871 --- /dev/null +++ b/Civi/Sepa/DataProcessor/Join/MandateContributionRecurJoin.php @@ -0,0 +1,37 @@ +. + */ + +namespace Civi\Sepa\DataProcessor\Join; + +class MandateContributionRecurJoin extends AbstractMandateJoin { + + /** + * When this join has configuration specify the template file name + * for the configuration form. + * + * @return false|string + */ + public function getConfigurationTemplateFileName():? string { + return "CRM/Sepa/Form/DataProcessor/Join//MandateContributionRecurJoin.tpl"; + } + + protected function getEntityTable(): string { + return 'civicrm_contribution_recur'; + } + +} diff --git a/templates/CRM/Sepa/Form/DataProcessor/Join/MandateContributionJoin.tpl b/templates/CRM/Sepa/Form/DataProcessor/Join/MandateContributionJoin.tpl new file mode 100644 index 00000000..1d31086e --- /dev/null +++ b/templates/CRM/Sepa/Form/DataProcessor/Join/MandateContributionJoin.tpl @@ -0,0 +1,19 @@ +{crmScope extensionKey='org.project60.sepa'} +

+ {ts}Select the ID of the contribution. This could be either on the contribution source with the field id. Or the any other data source which holds a contribution ID field.{/ts} +

+
+
{ts}Required join{/ts} *
+
{$form.mandate_join_type.html} +

{ts}Required means that both Sepa Mandate Entity ID field and the Contribution ID need to be set. {/ts}

+
+
+
+
{ts}Join on Contribution ID field{/ts} *
+
+ {$form.left_field.html} + = + {$form.right_field.html} +
+
+{/crmScope} diff --git a/templates/CRM/Sepa/Form/DataProcessor/Join/MandateContributionRecurJoin.tpl b/templates/CRM/Sepa/Form/DataProcessor/Join/MandateContributionRecurJoin.tpl new file mode 100644 index 00000000..61b2f96d --- /dev/null +++ b/templates/CRM/Sepa/Form/DataProcessor/Join/MandateContributionRecurJoin.tpl @@ -0,0 +1,19 @@ +{crmScope extensionKey='org.project60.sepa'} +

+ {ts}Select the ID of the contribution recur. This could be either on the contribution recur source with the field id. Or the contribution source and field contribution_recur_id.{/ts} +

+
+
{ts}Required join{/ts} *
+
{$form.mandate_join_type.html} +

{ts}Required means that both Sepa Mandate Entity ID field and the Recurring Contribution ID need to be set. {/ts}

+
+
+
+
{ts}Join on Contribution Recur ID field{/ts} *
+
+ {$form.left_field.html} + = + {$form.right_field.html} +
+
+{/crmScope}