From b2d9d372240a2845886b1450dfe04b9c869cc93d Mon Sep 17 00:00:00 2001
From: Jaap Jansma <jaap.jansma@civicoop.org>
Date: Wed, 1 May 2019 15:52:26 +0200
Subject: [PATCH] added join

---
 CRM/Dataprocessor/BAO/DataProcessorSource.php | 22 ++++-
 CRM/Dataprocessor/Form/DataProcessor.php      | 10 --
 CRM/Dataprocessor/Form/Source.php             | 54 ++++++++++-
 CRM/Dataprocessor/Utils/DataSourceFields.php  |  6 +-
 .../MultipleDataFlows/JoinInterface.php       | 45 +++++++--
 .../DataFlow/MultipleDataFlows/SimpleJoin.php | 91 ++++++++++++++++++-
 .../SimpleNonRequiredJoin.php                 |  7 --
 Civi/DataProcessor/Factory.php                |  4 +-
 .../Form/DataProcessorBlocks/Sources.tpl      | 12 ---
 .../CRM/Dataprocessor/Form/Join/Simple.tpl    | 37 --------
 .../Form/MultipleDataFlows/SimpleJoin.tpl     | 10 ++
 templates/CRM/Dataprocessor/Form/Source.tpl   | 24 ++---
 .../Form/Source/Configuration.tpl             |  2 +-
 .../CRM/Dataprocessor/Form/Source/Join.tpl    | 36 ++++++++
 14 files changed, 257 insertions(+), 103 deletions(-)
 delete mode 100644 templates/CRM/Dataprocessor/Form/Join/Simple.tpl
 create mode 100644 templates/CRM/Dataprocessor/Form/MultipleDataFlows/SimpleJoin.tpl
 create mode 100644 templates/CRM/Dataprocessor/Form/Source/Join.tpl

diff --git a/CRM/Dataprocessor/BAO/DataProcessorSource.php b/CRM/Dataprocessor/BAO/DataProcessorSource.php
index 1b6497c5..bdf8d0c7 100644
--- a/CRM/Dataprocessor/BAO/DataProcessorSource.php
+++ b/CRM/Dataprocessor/BAO/DataProcessorSource.php
@@ -77,13 +77,10 @@ class CRM_Dataprocessor_BAO_DataProcessorSource extends CRM_Dataprocessor_DAO_Da
    */
   public static function getSourceClass($source, \Civi\DataProcessor\ProcessorType\AbstractProcessorType $dataProcessor) {
     $factory = dataprocessor_get_factory();
-    $sourceClass = $factory->getDataSourceByName($source['type']);
-    $sourceClass->setSourceName($source['name']);
-    $sourceClass->setSourceTitle($source['title']);
-    $sourceClass->setConfiguration($source['configuration']);
+    $sourceClass = self::sourceToSourceClass($source);
     $sourceClass->setDataProcessor($dataProcessor);
     $join = null;
-    if ($source['join_type']) {
+    if (isset($source['join_type']) && $source['join_type']) {
       $join = $factory->getJoinByName($source['join_type']);
       $join->setConfiguration($source['join_configuration']);
       $join->setDataProcessor($dataProcessor);
@@ -95,4 +92,19 @@ class CRM_Dataprocessor_BAO_DataProcessorSource extends CRM_Dataprocessor_DAO_Da
     }
     return $sourceClass;
   }
+
+  /**
+   * Converts a Source array to an instanciated source class.
+   *
+   * @param $source
+   * @return \Civi\DataProcessor\Source\SourceInterface
+   */
+  public static function sourceToSourceClass($source) {
+    $factory = dataprocessor_get_factory();
+    $sourceClass = $factory->getDataSourceByName($source['type']);
+    $sourceClass->setSourceName($source['name']);
+    $sourceClass->setSourceTitle($source['title']);
+    $sourceClass->setConfiguration($source['configuration']);
+    return $sourceClass;
+  }
 }
diff --git a/CRM/Dataprocessor/Form/DataProcessor.php b/CRM/Dataprocessor/Form/DataProcessor.php
index f6ceffdf..1b3442e2 100644
--- a/CRM/Dataprocessor/Form/DataProcessor.php
+++ b/CRM/Dataprocessor/Form/DataProcessor.php
@@ -71,16 +71,6 @@ class CRM_Dataprocessor_Form_DataProcessor extends CRM_Core_Form {
     $sources = $sources['values'];
     CRM_Utils_Weight::addOrder($sources, 'CRM_Dataprocessor_DAO_DataProcessorSource', 'id', $this->currentUrl, 'data_processor_id='.$this->dataProcessorId);
     foreach($sources as $idx => $source) {
-      $sources[$idx]['join_link'] = '';
-      if (isset($source['join_type']) && $source['join_type']) {
-        $joinClass = $factory->getJoinByName($source['join_type']);
-        $sources[$idx]['join_link'] = CRM_Utils_System::url($joinClass->getConfigurationUrl(), array('reset' => 1, 'source_id' => $source['id'], 'data_processor_id' => $this->dataProcessorId));
-      }
-      $sources[$idx]['configuration_link'] = '';
-      $sourceClass = $factory->getDataSourceByName($source['type']);
-      if ($sourceClass->getConfigurationUrl()) {
-        $sources[$idx]['configuration_link'] = CRM_Utils_System::url($sourceClass->getConfigurationUrl(), array('reset' => 1, 'source_id' => $source['id'], 'data_processor_id' => $this->dataProcessorId));
-      }
       if (isset($types[$source['type']])) {
         $sources[$idx]['type_name'] = $types[$source['type']];
       } else {
diff --git a/CRM/Dataprocessor/Form/Source.php b/CRM/Dataprocessor/Form/Source.php
index 36beb629..dd343ab3 100644
--- a/CRM/Dataprocessor/Form/Source.php
+++ b/CRM/Dataprocessor/Form/Source.php
@@ -11,6 +11,11 @@ class CRM_Dataprocessor_Form_Source extends CRM_Core_Form {
 
   private $dataProcessorId;
 
+  /**
+   * @var Civi\DataProcessor\ProcessorType\AbstractProcessorType
+   */
+  private $dataProcessor;
+
   private $id;
 
   private $isFirstDataSource = true;
@@ -22,6 +27,11 @@ class CRM_Dataprocessor_Form_Source extends CRM_Core_Form {
    */
   private $sourceClass;
 
+  /**
+   * @var Civi\DataProcessor\DataFlow\MultipleDataFlows\JoinInterface
+   */
+  private $joinClass;
+
   private $snippet;
 
   /**
@@ -34,6 +44,8 @@ class CRM_Dataprocessor_Form_Source extends CRM_Core_Form {
     if ($this->snippet) {
       $this->assign('suppressForm', TRUE);
       $this->controller->_generateQFKey = FALSE;
+      $block = CRM_Utils_Request::retrieve('block', 'String', $this, FALSE, 'configuration');
+      $this->assign('block', $block);
     }
 
     $factory = dataprocessor_get_factory();
@@ -41,6 +53,9 @@ class CRM_Dataprocessor_Form_Source extends CRM_Core_Form {
     $session = CRM_Core_Session::singleton();
     $this->dataProcessorId = CRM_Utils_Request::retrieve('data_processor_id', 'Integer');
     $this->assign('data_processor_id', $this->dataProcessorId);
+    if ($this->dataProcessorId) {
+      $this->dataProcessor = CRM_Dataprocessor_BAO_DataProcessor::getDataProcessorById($this->dataProcessorId);
+    }
 
     $this->id = CRM_Utils_Request::retrieve('id', 'Integer');
     $this->assign('id', $this->id);
@@ -51,7 +66,7 @@ class CRM_Dataprocessor_Form_Source extends CRM_Core_Form {
     if ($this->id) {
       $this->source = civicrm_api3('DataProcessorSource', 'getsingle', array('id' => $this->id));
       $this->assign('source', $this->source);
-      $this->sourceClass = $factory->getDataSourceByName($this->source['type']);
+      $this->sourceClass = CRM_Dataprocessor_BAO_DataProcessorSource::sourceToSourceClass($this->source);
       $this->assign('has_configuration', $this->sourceClass->hasConfiguration());
 
       $i = 0;
@@ -69,15 +84,30 @@ class CRM_Dataprocessor_Form_Source extends CRM_Core_Form {
     $this->assign('is_first_data_source', $this->isFirstDataSource);
 
     $type = CRM_Utils_Request::retrieve('type', 'String');
-
     if ($type) {
-      $this->sourceClass = $factory->getDataSourceByName($type);
+      $this->source['type'] = $type;
+      $this->sourceClass = CRM_Dataprocessor_BAO_DataProcessorSource::sourceToSourceClass($this->source);
       $this->assign('has_configuration', $this->sourceClass->hasConfiguration());
       if ($this->sourceClass) {
         $this->source['configuration'] = $this->sourceClass->getDefaultConfiguration();
       }
     }
 
+    $join_type = CRM_Utils_Request::retrieve('join_type', 'String');
+    if ($join_type) {
+      $this->source['join_type'] = $join_type;
+    }
+
+    $this->assign('has_join_configuration', false);
+    if (!$this->isFirstDataSource && isset($this->source['join_type']) && $this->source['join_type']) {
+      $this->joinClass = $factory->getJoinByName($this->source['join_type']);
+      $this->assign('has_join_configuration', $this->joinClass->hasConfiguration());
+    }
+
+    if (!isset($this->source['join_configuration']) || !is_array($this->source['join_configuration'])) {
+      $this->source['join_configuration'] = array();
+    }
+
     $title = E::ts('Data Processor Source');
     CRM_Utils_System::setTitle($title);
 
@@ -106,7 +136,23 @@ class CRM_Dataprocessor_Form_Source extends CRM_Core_Form {
 
       if (!$this->isFirstDataSource) {
         $joins = [' - select - '] + $factory->getJoins();
-        $this->add('select', 'join_type', ts('Select Join Type'), $joins, TRUE, ['class' => 'crm-select2']);
+        $this->add('select', 'join_type', ts('Select Join Type'), $joins, TRUE, array(
+          'style' => 'min-width:250px',
+          'class' => 'crm-select2 huge',
+          'placeholder' => E::ts('- select -'),
+        ));
+        if ($this->joinClass && $this->joinClass->hasConfiguration()) {
+          $joinableToSources = array();
+          $dataProcessor = CRM_Dataprocessor_BAO_DataProcessor::getDataProcessorById($this->dataProcessorId);
+          foreach($dataProcessor->getDataSources() as $source) {
+            if ($this->sourceClass && $this->sourceClass->getSourceName() == $source->getSourceName()) {
+              break;
+            }
+            $joinableToSources[] = $source;
+          }
+          $this->joinClass->buildConfigurationForm($this, $this->sourceClass, $joinableToSources, $this->source['join_configuration']);
+          $this->assign('join_configuration_template', $this->joinClass->getConfigurationTemplateFileName());
+        }
       }
 
       if ($this->sourceClass && $this->sourceClass->hasConfiguration()) {
diff --git a/CRM/Dataprocessor/Utils/DataSourceFields.php b/CRM/Dataprocessor/Utils/DataSourceFields.php
index 038f9aa1..5c4ff066 100644
--- a/CRM/Dataprocessor/Utils/DataSourceFields.php
+++ b/CRM/Dataprocessor/Utils/DataSourceFields.php
@@ -19,7 +19,7 @@ class CRM_Dataprocessor_Utils_DataSourceFields {
     $dataProcessor = CRM_Dataprocessor_BAO_DataProcessor::getDataProcessorById($dataProcessorId);
     $fieldSelect = array();
     foreach($dataProcessor->getDataSources() as $dataSource) {
-      $fieldSelect = array_merge($fieldSelect, self::getAvailableFieldsInDataSource($dataSource));
+      $fieldSelect = array_merge($fieldSelect, self::getAvailableFieldsInDataSource($dataSource, $dataSource->getSourceTitle().' :: ', $dataSource->getSourceName().'::'));
     }
     return $fieldSelect;
   }
@@ -31,10 +31,10 @@ class CRM_Dataprocessor_Utils_DataSourceFields {
    * @return array
    * @throws \Exception
    */
-  public static function getAvailableFieldsInDataSource(SourceInterface $dataSource) {
+  public static function getAvailableFieldsInDataSource(SourceInterface $dataSource, $titlePrefix='', $namePrefix='') {
     $fieldSelect = array();
     foreach($dataSource->getAvailableFilterFields()->getFields() as $field) {
-      $fieldSelect[$dataSource->getSourceName().'::'.$field->name] = $dataSource->getSourceTitle().' :: '.$field->title;
+      $fieldSelect[$namePrefix.$field->name] = $titlePrefix.$field->title;
     }
     return $fieldSelect;
   }
diff --git a/Civi/DataProcessor/DataFlow/MultipleDataFlows/JoinInterface.php b/Civi/DataProcessor/DataFlow/MultipleDataFlows/JoinInterface.php
index 7c0de65a..9c2a705e 100644
--- a/Civi/DataProcessor/DataFlow/MultipleDataFlows/JoinInterface.php
+++ b/Civi/DataProcessor/DataFlow/MultipleDataFlows/JoinInterface.php
@@ -8,6 +8,7 @@ namespace Civi\DataProcessor\DataFlow\MultipleDataFlows;
 
 use Civi\DataProcessor\DataFlow\AbstractDataFlow;
 use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
+use Civi\DataProcessor\Source\SourceInterface;
 
 interface JoinInterface{
 
@@ -50,13 +51,6 @@ interface JoinInterface{
    */
   public function setConfiguration($configuration);
 
-  /**
-   * Returns the URL for the configuration form of the join specification
-   *
-   * @return string
-   */
-  public function getConfigurationUrl();
-
   /**
    * Prepares the right data flow based on the data in the left record set.
    *
@@ -67,4 +61,41 @@ interface JoinInterface{
    */
   public function prepareRightDataFlow($left_record_set, AbstractDataFlow $rightDataFlow);
 
+  /**
+   * Returns true when this join has additional configuration
+   *
+   * @return bool
+   */
+  public function hasConfiguration();
+
+  /**
+   * 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());
+
+  /**
+   * When this join has configuration specify the template file name
+   * for the configuration form.
+   *
+   * @return false|string
+   */
+  public function getConfigurationTemplateFileName();
+
+
+  /**
+   * Process the submitted values and create a configuration array
+   *
+   * @param $submittedValues
+   * @param SourceInterface $joinFromSource
+   * @return array
+   */
+  public function processConfiguration($submittedValues, SourceInterface $joinFromSource);
+
 }
\ No newline at end of file
diff --git a/Civi/DataProcessor/DataFlow/MultipleDataFlows/SimpleJoin.php b/Civi/DataProcessor/DataFlow/MultipleDataFlows/SimpleJoin.php
index bfd5045d..f2d8e217 100644
--- a/Civi/DataProcessor/DataFlow/MultipleDataFlows/SimpleJoin.php
+++ b/Civi/DataProcessor/DataFlow/MultipleDataFlows/SimpleJoin.php
@@ -11,6 +11,9 @@ use Civi\DataProcessor\DataFlow\CombinedDataFlow\CombinedSqlDataFlow;
 use Civi\DataProcessor\DataFlow\SqlDataFlow;
 use Civi\DataProcessor\DataFlow\SqlTableDataFlow;
 use Civi\DataProcessor\ProcessorType\AbstractProcessorType;
+use Civi\DataProcessor\Source\SourceInterface;
+
+use CRM_Dataprocessor_ExtensionUtil as E;
 
 class SimpleJoin implements JoinInterface, SqlJoinInterface {
 
@@ -109,10 +112,92 @@ class SimpleJoin implements JoinInterface, SqlJoinInterface {
   }
 
   /**
-   * @return string
+   * Returns true when this join has additional configuration
+   *
+   * @return bool
+   */
+  public function hasConfiguration() {
+    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()) {
+    $leftFields = \CRM_Dataprocessor_Utils_DataSourceFields::getAvailableFieldsInDataSource($joinFromSource, '', '');
+    $form->add('select', 'left_field', ts('Select field'), $leftFields, true, array(
+      'style' => 'min-width:250px',
+      'class' => 'crm-select2 huge',
+      'placeholder' => E::ts('- select -'),
+    ));
+
+    $rightFields = array();
+    foreach($joinableToSources as $joinToSource) {
+      $rightFields = array_merge($rightFields, \CRM_Dataprocessor_Utils_DataSourceFields::getAvailableFieldsInDataSource($joinToSource, $joinToSource->getSourceTitle().' :: ', $joinToSource->getSourceName().'::'));
+    }
+
+    $form->add('select', 'right_field', ts('Select field'), $rightFields, true, array(
+      'style' => 'min-width:250px',
+      'class' => 'crm-select2 huge',
+      'placeholder' => E::ts('- select -'),
+    ));
+
+    // Backwords compatability
+    if ($joinConfiguration['right_prefix'] == $joinFromSource->getSourceName()) {
+      $joinConfigurationBackwardsCompatibility = $joinConfiguration;
+      $joinConfiguration['left_prefix'] = '';
+      $joinConfiguration['left_field'] = $joinConfigurationBackwardsCompatibility['right_field'];
+      $joinConfiguration['right_prefix'] = $joinConfigurationBackwardsCompatibility['left_prefix'];
+      $joinConfiguration['right_field'] = $joinConfigurationBackwardsCompatibility['left_field'];
+    }
+
+    $defaults = array();
+    if (isset($joinConfiguration['left_prefix'])) {
+      $defaults['left_field'] = $joinConfiguration['left_field'];
+    }
+    if (isset($joinConfiguration['right_prefix'])) {
+      $defaults['right_field'] = $joinConfiguration['right_prefix']."::".$joinConfiguration['right_field'];
+    }
+
+    $form->setDefaults($defaults);
+  }
+
+  /**
+   * When this join has configuration specify the template file name
+   * for the configuration form.
+   *
+   * @return false|string
+   */
+  public function getConfigurationTemplateFileName() {
+    return "CRM/Dataprocessor/Form/MultipleDataFlows/SimpleJoin.tpl";
+  }
+
+
+  /**
+   * Process the submitted values and create a configuration array
+   *
+   * @param $submittedValues
+   * @return array
    */
-  public function getConfigurationUrl() {
-    return 'civicrm/dataprocessor/form/joins/simple_join';
+  public function processConfiguration($submittedValues, SourceInterface $joinFromSource) {
+    $left_prefix = $joinFromSource->getSourceName();
+    $left_field = $submittedValues['left_field'];
+    list($right_prefix, $right_field) = explode("::",$submittedValues['right_field'], 2);
+
+    $configuration = array(
+      'left_prefix' => $left_prefix,
+      'left_field' => $left_field,
+      'right_prefix' => $right_prefix,
+      'right_field' => $right_field
+    );
+    return $configuration;
   }
 
   /**
diff --git a/Civi/DataProcessor/DataFlow/MultipleDataFlows/SimpleNonRequiredJoin.php b/Civi/DataProcessor/DataFlow/MultipleDataFlows/SimpleNonRequiredJoin.php
index 4e23a176..76f37bcd 100644
--- a/Civi/DataProcessor/DataFlow/MultipleDataFlows/SimpleNonRequiredJoin.php
+++ b/Civi/DataProcessor/DataFlow/MultipleDataFlows/SimpleNonRequiredJoin.php
@@ -25,13 +25,6 @@ class SimpleNonRequiredJoin  extends  SimpleJoin {
     parent::__construct($left_prefix, $left_field, $right_prefix, $right_field, $type);
   }
 
-  /**
-   * @return string
-   */
-  public function getConfigurationUrl() {
-    return 'civicrm/dataprocessor/form/joins/simple_join';
-  }
-
   /**
    * @param array $configuration
    *
diff --git a/Civi/DataProcessor/Factory.php b/Civi/DataProcessor/Factory.php
index d20880c9..823e2bc5 100644
--- a/Civi/DataProcessor/Factory.php
+++ b/Civi/DataProcessor/Factory.php
@@ -122,8 +122,8 @@ class Factory {
     $this->addOutput('activity_search', 'CRM_DataprocessorSearch_ActivitySearch', E::ts('Activity Search'));
     $this->addOutput('export_csv', 'CRM_DataprocessorOutputExport_CSV', E::ts('CSV Export'));
     $this->addFilter('simple_sql_filter', 'Civi\DataProcessor\FilterHandler\SimpleSqlFilter', E::ts('Field filter'));
-    $this->addjoinType('simple_join', 'Civi\DataProcessor\DataFlow\MultipleDataFlows\SimpleJoin', E::ts('Simple Join'));
-    $this->addjoinType('simple_non_required_join', 'Civi\DataProcessor\DataFlow\MultipleDataFlows\SimpleNonRequiredJoin', E::ts('Simple  (but not required) Join'));
+    $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  (but not required) Join'));
   }
 
   /**
diff --git a/templates/CRM/Dataprocessor/Form/DataProcessorBlocks/Sources.tpl b/templates/CRM/Dataprocessor/Form/DataProcessorBlocks/Sources.tpl
index 357092ae..d2015c85 100644
--- a/templates/CRM/Dataprocessor/Form/DataProcessorBlocks/Sources.tpl
+++ b/templates/CRM/Dataprocessor/Form/DataProcessorBlocks/Sources.tpl
@@ -8,24 +8,12 @@
             <th></th>
             <th></th>
             <th></th>
-            <th></th>
-            <th></th>
         </tr>
         {foreach from=$sources item=source}
             <tr>
                 <td>{$source.type_name}</td>
                 <td>{$source.title}</td>
                 <td>{if ($source.weight && !is_numeric($source.weight))}{$source.weight}{/if}</td>
-                <td>
-                    {if $source.join_link}
-                        <a href="{$source.join_link}">{ts}Join Configuration{/ts}</a>
-                    {/if}
-                </td>
-                <td>
-                    {if $source.configuration_link}
-                        <a href="{$source.configuration_link}">{ts}Configure source{/ts}</a>
-                    {/if}
-                </td>
                 <td>
                     <a href="{crmURL p="civicrm/dataprocessor/form/source" q="reset=1&action=update&data_processor_id=`$source.data_processor_id`&id=`$source.id`"}">{ts}Edit{/ts}</a>
                 </td>
diff --git a/templates/CRM/Dataprocessor/Form/Join/Simple.tpl b/templates/CRM/Dataprocessor/Form/Join/Simple.tpl
deleted file mode 100644
index 8feaf84a..00000000
--- a/templates/CRM/Dataprocessor/Form/Join/Simple.tpl
+++ /dev/null
@@ -1,37 +0,0 @@
-{crmScope extensionKey='dataprocessor'}
-<div class="crm-submit-buttons">
-    {include file="CRM/common/formButtons.tpl" location="top"}
-</div>
-
-    {* block for rule data *}
-    <h3>{ts}Data Processor Sources simple join configuration{/ts}</h3>
-    <div class="crm-block crm-form-block crm-data-processor_simple_join-block">
-        <div class="crm-section">
-            <table class="form_layout">
-                <tr>
-                    <td>
-                        {$form.left_field.label}
-                    </td>
-                    <td></td>
-                    <td>
-                        {$form.right_field.label}
-                    </td>
-                </tr>
-                <tr>
-                    <td>
-                        {$form.left_field.html}
-                    </td>
-                    <td>=</td>
-                    <td>
-                        {$form.right_field.html}
-                    </td>
-                </tr>
-            </table>
-
-        </div>
-    </div>
-
-<div class="crm-submit-buttons">
-    {include file="CRM/common/formButtons.tpl" location="bottom"}
-</div>
-{/crmScope}
\ No newline at end of file
diff --git a/templates/CRM/Dataprocessor/Form/MultipleDataFlows/SimpleJoin.tpl b/templates/CRM/Dataprocessor/Form/MultipleDataFlows/SimpleJoin.tpl
new file mode 100644
index 00000000..5a5f34da
--- /dev/null
+++ b/templates/CRM/Dataprocessor/Form/MultipleDataFlows/SimpleJoin.tpl
@@ -0,0 +1,10 @@
+{crmScope extensionKey='dataprocessor'}
+<div class="crm-section">
+    <div class="label">{ts}Join on field{/ts} <span class="marker">*</span></div>
+    <div class="content">
+                    {$form.left_field.html}
+                    &nbsp;=&nbsp;
+                    {$form.right_field.html}
+    </div>
+</div>
+{/crmScope}
\ No newline at end of file
diff --git a/templates/CRM/Dataprocessor/Form/Source.tpl b/templates/CRM/Dataprocessor/Form/Source.tpl
index cf0d34df..4b0bfd04 100644
--- a/templates/CRM/Dataprocessor/Form/Source.tpl
+++ b/templates/CRM/Dataprocessor/Form/Source.tpl
@@ -39,18 +39,13 @@
             <div class="content">{$form.name.html}</div>
             <div class="clear"></div>
         </div>
-        {if !$is_first_data_source}
-            <div class="crm-section">
-                <div class="label">{$form.join_type.label}</div>
-                <div class="content">{$form.join_type.html}</div>
-                <div class="clear"></div>
-            </div>
-        {/if}
 
         <div id="type_configuration">
             {if ($configuration_template)}
                 {include file=$configuration_template}
             {/if}
+
+            {include file="CRM/Dataprocessor/Form/Source/Join.tpl"}
         </div>
     </div>
 
@@ -60,14 +55,15 @@
 
     <script type="text/javascript">
         {literal}
-        CRM.$(function($) {
-          var id = {/literal}{if ($source)}{$source.id}{else}false{/if}{literal};
-          var data_processor_id = {/literal}{$data_processor_id}{literal};
 
+        CRM.$(function($) {
           $('#type').on('change', function() {
             var type = $('#type').val();
+            var join_type = $('#join_type').val();
+            var id = {/literal}{if ($source)}{$source.id}{else}false{/if}{literal};
+            var data_processor_id = {/literal}{$data_processor_id}{literal};
             if (type) {
-              var dataUrl = CRM.url('civicrm/dataprocessor/form/source', {type: type, 'data_processor_id': data_processor_id, 'id': id});
+              var dataUrl = CRM.url('civicrm/dataprocessor/form/source', {type: type, 'data_processor_id': data_processor_id, 'id': id, 'join_type': join_type});
               CRM.loadPage(dataUrl, {'target': '#type_configuration'});
             }
           });
@@ -90,11 +86,15 @@
         {/literal}
     </script>
 
-{else}
+{elseif ($block == 'joinOnly')}
+    {include file="CRM/Dataprocessor/Form/Source/Join.tpl"}
+{elseif ($block == 'configuration')}
     <div id="type_configuration">
         {if ($configuration_template)}
             {include file=$configuration_template}
         {/if}
+
+        {include file="CRM/Dataprocessor/Form/Source/Join.tpl"}
     </div>
 {/if}
 
diff --git a/templates/CRM/Dataprocessor/Form/Source/Configuration.tpl b/templates/CRM/Dataprocessor/Form/Source/Configuration.tpl
index fd319729..9006dff6 100644
--- a/templates/CRM/Dataprocessor/Form/Source/Configuration.tpl
+++ b/templates/CRM/Dataprocessor/Form/Source/Configuration.tpl
@@ -1,6 +1,6 @@
 {crmScope extensionKey='dataprocessor'}
     {* block for rule data *}
-<div class="crm-accordion-wrapper">
+<div class="crm-accordion-wrapper collapsed">
     <div class="crm-accordion-header">{ts}Filter criteria{/ts}</div>
     <div class="crm-accordion-body">
 
diff --git a/templates/CRM/Dataprocessor/Form/Source/Join.tpl b/templates/CRM/Dataprocessor/Form/Source/Join.tpl
new file mode 100644
index 00000000..5d9f79f6
--- /dev/null
+++ b/templates/CRM/Dataprocessor/Form/Source/Join.tpl
@@ -0,0 +1,36 @@
+{crmScope extensionKey='dataprocessor'}
+<div id="joinBlock">
+{if !$is_first_data_source}
+    <div class="crm-accordion-wrapper">
+        <div class="crm-accordion-header">{ts}Join with other sources{/ts}</div>
+        <div class="crm-accordion-body">
+            <div class="crm-section">
+                <div class="label">{$form.join_type.label}</div>
+                <div class="content">{$form.join_type.html}</div>
+                <div class="clear"></div>
+            </div>
+            {if ($join_configuration_template)}
+                {include file=$join_configuration_template}
+            {/if}
+        </div>
+    </div>
+
+    <script type="text/javascript">
+        {literal}
+        CRM.$(function($) {
+          $('#join_type').on('change', function() {
+            var type = $('#type').val();
+            var join_type = $('#join_type').val();
+            var id = {/literal}{if ($source)}{$source.id}{else}false{/if}{literal};
+            var data_processor_id = {/literal}{$data_processor_id}{literal};
+            if (type) {
+              var dataUrl = CRM.url('civicrm/dataprocessor/form/source', {type: type, 'data_processor_id': data_processor_id, 'id': id, 'join_type': join_type, 'block': 'joinOnly'});
+              CRM.loadPage(dataUrl, {'target': '#joinBlock'});
+            }
+          });
+        });
+        {/literal}
+    </script>
+{/if}
+</div>
+{/crmScope}
\ No newline at end of file
-- 
GitLab