From be9bcd1c51246aef66823fa3715389b6ddbf49f5 Mon Sep 17 00:00:00 2001
From: Jaap Jansma <jaap.jansma@civicoop.org>
Date: Fri, 28 Feb 2020 10:46:18 +0100
Subject: [PATCH] Added data sources for custom groups which are a multiple
 data set.

---
 CHANGELOG.md                                  |   1 +
 Civi/DataProcessor/Factory/Definition.php     |   6 +-
 .../Source/AbstractCivicrmEntitySource.php    |   5 +-
 .../MultipleCustomGroupSource.php             |  40 ++++++
 .../Contact/MultipleCustomGroupSource.php     | 134 ++++++++++++++++++
 dataprocessor.php                             |   6 +-
 6 files changed, 184 insertions(+), 8 deletions(-)
 create mode 100644 Civi/DataProcessor/Source/CompilerPass/MultipleCustomGroupSource.php
 create mode 100644 Civi/DataProcessor/Source/Contact/MultipleCustomGroupSource.php

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f17e248b..40fb124a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 * Add Recurring Contribution as datasource
 * Added Field Output Handler for Is Active fields based on dates (start date and end date).
 * Refactored the factory.
+* Added data sources for custom groups which are a multiple data set.
 
 # Version 1.2.0
 
diff --git a/Civi/DataProcessor/Factory/Definition.php b/Civi/DataProcessor/Factory/Definition.php
index d3d99570..91f2928d 100644
--- a/Civi/DataProcessor/Factory/Definition.php
+++ b/Civi/DataProcessor/Factory/Definition.php
@@ -32,8 +32,10 @@ class Definition {
    */
   public function get() {
     $reflectionClass = new \ReflectionClass($this->class);
-    if ($reflectionClass->getConstructor()) {
-      return $reflectionClass->newInstance($this->arguments);
+    if ($this->arguments && $reflectionClass->getConstructor()) {
+      return $reflectionClass->newInstanceArgs($this->arguments);
+    } elseif ($reflectionClass->getConstructor()) {
+      return $reflectionClass->newInstance();
     } else {
       return $reflectionClass->newInstanceWithoutConstructor();
     }
diff --git a/Civi/DataProcessor/Source/AbstractCivicrmEntitySource.php b/Civi/DataProcessor/Source/AbstractCivicrmEntitySource.php
index 9f617637..79fe06ff 100644
--- a/Civi/DataProcessor/Source/AbstractCivicrmEntitySource.php
+++ b/Civi/DataProcessor/Source/AbstractCivicrmEntitySource.php
@@ -123,7 +123,7 @@ abstract class AbstractCivicrmEntitySource extends AbstractSource {
    * @return \Civi\DataProcessor\DataFlow\SqlDataFlow
    */
   protected function getEntityDataFlow() {
-    return new SqlTableDataFlow($this->getTable(), $this->getSourceName(), $this->getSourceTitle());
+    return new SqlTableDataFlow($this->getTable(), $this->getSourceName());
   }
 
   /**
@@ -187,9 +187,6 @@ abstract class AbstractCivicrmEntitySource extends AbstractSource {
       $spec = $this->getAvailableFields()->getFieldSpecificationByName($filter_field_alias);
     }
 
-
-
-
     if ($spec) {
       if ($spec instanceof CustomFieldSpecification) {
         $customGroupDataFlow = $this->ensureCustomGroup($spec->customGroupTableName, $spec->customGroupName);
diff --git a/Civi/DataProcessor/Source/CompilerPass/MultipleCustomGroupSource.php b/Civi/DataProcessor/Source/CompilerPass/MultipleCustomGroupSource.php
new file mode 100644
index 00000000..2fddc4e5
--- /dev/null
+++ b/Civi/DataProcessor/Source/CompilerPass/MultipleCustomGroupSource.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Jaap Jansma <jaap.jansma@civicoop.org>
+ * @license AGPL-3.0
+ */
+
+namespace Civi\DataProcessor\Source\CompilerPass;
+
+use CRM_Dataprocessor_ExtensionUtil as E;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+
+class MultipleCustomGroupSource implements CompilerPassInterface {
+
+  /**
+   * You can modify the container here before it is dumped to PHP code.
+   */
+  public function process(ContainerBuilder $container) {
+    if (!$container->hasDefinition('data_processor_factory')) {
+      return;
+    }
+    $factoryDefinition = $container->getDefinition('data_processor_factory');
+
+    try {
+      $dao = \CRM_Core_DAO::executeQuery("SELECT * FROM civicrm_custom_group WHERE is_multiple = '1' and is_active = '1'");
+      while($dao->fetch()) {
+        $arguments = [
+          '\Civi\DataProcessor\Source\Contact\MultipleCustomGroupSource',
+          [$dao->name, $dao->title, $dao->table_name],
+        ];
+        $definition = new Definition('Civi\DataProcessor\Factory\Definition', $arguments);
+        $factoryDefinition->addMethodCall('addDataSource', array('multi_custom_group_'.$dao->name, $definition, E::ts('Custom group: %1', [1=>$dao->title])));
+      }
+    } catch (\CiviCRM_API3_Exception $e) {
+      // Do nothing
+    }
+  }
+
+}
diff --git a/Civi/DataProcessor/Source/Contact/MultipleCustomGroupSource.php b/Civi/DataProcessor/Source/Contact/MultipleCustomGroupSource.php
new file mode 100644
index 00000000..d3e1073a
--- /dev/null
+++ b/Civi/DataProcessor/Source/Contact/MultipleCustomGroupSource.php
@@ -0,0 +1,134 @@
+<?php
+/**
+ * @author Jaap Jansma <jaap.jansma@civicoop.org>
+ * @license AGPL-3.0
+ */
+
+namespace Civi\DataProcessor\Source\Contact;
+
+use Civi\DataProcessor\DataFlow\SqlTableDataFlow;
+use Civi\DataProcessor\DataSpecification\DataSpecification;
+use Civi\DataProcessor\DataSpecification\FieldExistsException;
+use Civi\DataProcessor\DataSpecification\FieldSpecification;
+use Civi\DataProcessor\Source\AbstractSource;
+use Civi\DataProcessor\DataSpecification\CustomFieldSpecification;
+
+use CRM_Dataprocessor_ExtensionUtil as E;
+
+
+class MultipleCustomGroupSource extends AbstractSource {
+
+  /**
+   * @var string
+   */
+  protected $custom_group_name;
+
+  /**
+   * @var string
+   */
+  protected $custom_group_title;
+
+  /**
+   * @var string
+   */
+  protected $custom_group_table_name;
+
+  /**
+   * @var \Civi\DataProcessor\DataSpecification\DataSpecification
+   */
+  protected $availableFields;
+
+  /**
+   * @var \Civi\DataProcessor\DataSpecification\DataSpecification
+   */
+  protected $availableFilterFields;
+
+  public function __construct($custom_group_name, $custom_group_title, $custom_group_table_name) {
+    parent::__construct();
+    $this->custom_group_name = $custom_group_name;
+    $this->custom_group_title = $custom_group_title;
+    $this->custom_group_table_name = $custom_group_table_name;
+  }
+
+  /**
+   * Initialize the join
+   *
+   * @return void
+   */
+  public function initialize() {
+    if (!$this->dataFlow) {
+      $this->dataFlow = new SqlTableDataFlow($this->custom_group_table_name, $this->getSourceName());
+    }
+  }
+
+  /**
+   * @return \Civi\DataProcessor\DataSpecification\DataSpecification
+   * @throws \Exception
+   */
+  public function getAvailableFields() {
+    if (!$this->availableFields) {
+      $this->availableFields = new DataSpecification();
+      $this->availableFields->addFieldSpecification('entity_id', new FieldSpecification('entity_id','Integer', E::ts('Contact ID'), null, $this->getSourceName().'_entity_id'));
+      $this->loadCustomGroupsAndFields($this->availableFields, false);
+    }
+    return $this->availableFields;
+  }
+
+  /**
+   * @return \Civi\DataProcessor\DataSpecification\DataSpecification
+   * @throws \Exception
+   */
+  public function getAvailableFilterFields() {
+    if (!$this->availableFilterFields) {
+      $this->availableFilterFields = new DataSpecification();
+      $this->availableFilterFields->addFieldSpecification('entity_id', new FieldSpecification('entity_id','Integer', E::ts('Contact ID'), null, $this->getSourceName().'_entity_id'));
+      $this->loadCustomGroupsAndFields($this->availableFilterFields, true);
+    }
+    return $this->availableFilterFields;
+  }
+
+  /**
+   * Add custom fields to the available fields section
+   *
+   * @param DataSpecification $dataSpecification
+   * @param bool $onlySearchAbleFields
+   * @param $entity
+   * @throws \Civi\DataProcessor\DataSpecification\FieldExistsException
+   * @throws \Exception
+   */
+  protected function loadCustomGroupsAndFields(DataSpecification $dataSpecification, $onlySearchAbleFields) {
+    $params['options']['limit'] = 0;
+    $params['custom_group_id'] = $this->custom_group_name;
+    $params['is_active'] = 1;
+    if ($onlySearchAbleFields) {
+      $params['is_searchable'] = 1;
+    }
+    $customFields = civicrm_api3('CustomField', 'get', $params);
+    foreach ($customFields['values'] as $field) {
+      $alias = $this->getSourceName() . '_' . $field['name'];
+      $customFieldSpec = new CustomFieldSpecification(
+        $this->custom_group_name, $this->custom_group_table_name, $this->custom_group_title,
+        $field,
+        $alias
+      );
+      $dataSpecification->addFieldSpecification($customFieldSpec->name, $customFieldSpec);
+    }
+  }
+
+  /**
+   * Ensures a field is in the data source
+   *
+   * @param \Civi\DataProcessor\DataSpecification\FieldSpecification $fieldSpecification
+   * @throws \Exception
+   */
+  public function ensureFieldInSource(FieldSpecification $fieldSpecification) {
+    try {
+      $this->dataFlow->getDataSpecification()->addFieldSpecification($fieldSpecification->alias, $fieldSpecification);
+    } catch (FieldExistsException $e) {
+      // Do nothing.
+    }
+  }
+
+
+
+}
diff --git a/dataprocessor.php b/dataprocessor.php
index 998e190a..170f6696 100644
--- a/dataprocessor.php
+++ b/dataprocessor.php
@@ -29,6 +29,8 @@ function dataprocessor_civicrm_container(ContainerBuilder $container) {
   $apiKernelDefinition = $container->getDefinition('civi_api_kernel');
   $apiProviderDefinition = new Definition('Civi\DataProcessor\Output\Api');
   $apiKernelDefinition->addMethodCall('registerApiProvider', array($apiProviderDefinition));
+
+  $container->addCompilerPass(new \Civi\DataProcessor\Source\CompilerPass\MultipleCustomGroupSource());
 }
 
 /**
@@ -51,8 +53,8 @@ function dataprocessor_civicrm_alterAPIPermissions($entity, $action, &$params, &
   $actionCamelCase = _civicrm_api_get_camel_name($api_action);
   $dao = CRM_Core_DAO::executeQuery("
     SELECT *
-    FROM civicrm_data_processor_output o 
-    INNER JOIN civicrm_data_processor p ON o.data_processor_id = p.id 
+    FROM civicrm_data_processor_output o
+    INNER JOIN civicrm_data_processor p ON o.data_processor_id = p.id
     WHERE p.is_active = 1
     AND (LOWER(api_entity) = LOWER(%1) OR LOWER(api_entity) = LOWER(%2))
     AND (
-- 
GitLab