From 60b9d8bec07bda450f0e7caf52bc9cf8dead8620 Mon Sep 17 00:00:00 2001
From: Jaap Jansma <jaap.jansma@civicoop.org>
Date: Mon, 1 Jul 2019 17:32:13 +0200
Subject: [PATCH] velt issue 4576 WIP

---
 .../Selector/DataProcessorContactSearch.php   | 191 ++++++++++++++++++
 .../DataProcessorContactSearch.php            |  92 +++++++++
 .../Controller/ContactSearch.php              |  16 +-
 .../Form/AbstractSearch.php                   |   1 +
 Civi/DataProcessor/DataFlow/SqlDataFlow.php   |   7 +
 5 files changed, 305 insertions(+), 2 deletions(-)
 create mode 100644 CRM/Contact/Selector/DataProcessorContactSearch.php
 create mode 100644 CRM/Contact/StateMachine/DataProcessorContactSearch.php

diff --git a/CRM/Contact/Selector/DataProcessorContactSearch.php b/CRM/Contact/Selector/DataProcessorContactSearch.php
new file mode 100644
index 00000000..55c29f7d
--- /dev/null
+++ b/CRM/Contact/Selector/DataProcessorContactSearch.php
@@ -0,0 +1,191 @@
+<?php
+/**
+ * @author Jaap Jansma <jaap.jansma@civicoop.org>
+ * @license AGPL-3.0
+ */
+
+class CRM_Contact_Selector_DataProcessorContactSearch {
+  /**
+   * @var \Civi\DataProcessor\ProcessorType\AbstractProcessorType;
+   */
+  protected $dataProcessorClass;
+
+  /**
+   * @var int
+   */
+  protected $dataProcessorId;
+
+  /**
+   * @var array
+   */
+  protected $dataProcessor;
+
+  /**
+   * @var \CRM_Dataprocessor_BAO_Output
+   */
+  protected $dataProcessorOutput;
+
+  public function __construct($customClass, $formValues=null, $params=null, $returnProperties=null) {
+    $this->loadDataProcessor();
+    $sortFields = $this->getSortFields();
+    $this->sort = new CRM_Utils_Sort($sortFields);
+    if (isset($formValues[CRM_Utils_Sort::SORT_ID])) {
+      $this->sort->initSortID($formValues[CRM_Utils_Sort::SORT_ID]);
+    }
+
+    CRM_Dataprocessor_Form_Output_AbstractUIOutputForm::applyFilters($this->dataProcessorClass, $formValues);
+    // Set the sort
+    $sortDirection = 'ASC';
+    if (!empty($this->sort->_vars[$this->sort->getCurrentSortID()])) {
+      $sortField = $this->sort->_vars[$this->sort->getCurrentSortID()];
+      if ($this->sort->getCurrentSortDirection() == CRM_Utils_Sort::DESCENDING) {
+        $sortDirection = 'DESC';
+      }
+      $this->dataProcessorClass->getDataFlow()->addSort($sortField['name'], $sortDirection);
+    }
+  }
+
+  /**
+   * Add the headers for the columns
+   *
+   * @return array
+   *   Array with all possible sort fields.
+   *
+   * @throws \Civi\DataProcessor\DataFlow\InvalidFlowException
+   */
+  protected function getSortFields() {
+    $sortFields = array();
+    $hiddenFields = $this->getHiddenFields();
+    $sortColumnNr = 1;
+    foreach($this->dataProcessorClass->getDataFlow()->getOutputFieldHandlers() as $outputFieldHandler) {
+      $field = $outputFieldHandler->getOutputFieldSpecification();
+      if (!in_array($field->alias, $hiddenFields)) {
+        if ($outputFieldHandler instanceof \Civi\DataProcessor\FieldOutputHandler\OutputHandlerSortable) {
+          $sortFields[$sortColumnNr] = array(
+            'name' => $field->title,
+            'sort' => $field->alias,
+            'direction' => CRM_Utils_Sort::DONTCARE,
+          );
+          $sortColumnNr++;
+        }
+      }
+    }
+    return $sortFields;
+  }
+
+  /**
+   * Generate contact ID query.
+   *
+   * @param array $params
+   * @param $action
+   * @param int $sortID
+   * @param null $displayRelationshipType
+   * @param string $queryOperator
+   *
+   * @return Object
+   */
+  public function contactIDQuery($params, $sortID, $displayRelationshipType = NULL, $queryOperator = 'AND') {
+    $dataFlow = $this->dataProcessorClass->getDataFlow();
+    $cids = array();
+    $contactIdFieldName = $this->dataProcessorOutput['configuration']['contact_id_field'];
+    try {
+      while($record = $dataFlow->nextRecord()) {
+        $cids[] = $record[$contactIdFieldName]->rawValue;
+      }
+    } catch (\Civi\DataProcessor\DataFlow\EndOfFlowException $e) {
+      // do nothing
+    }
+    return CRM_Core_DAO::executeQuery("SELECT id as contact_id from civicrm_contact WHERE id in (".implode(", ", $cids).")");
+  }
+
+  /**
+   * Return the data processor ID
+   *
+   * @return String
+   */
+  protected function getDataProcessorName() {
+    $dataProcessorName = str_replace('civicrm/dataprocessor_contact_search/', '', CRM_Utils_System::getUrlPath());
+    return $dataProcessorName;
+  }
+
+  /**
+   * Retrieve the data processor and the output configuration
+   *
+   * @throws \Exception
+   */
+  protected function loadDataProcessor() {
+    if (!$this->dataProcessorId) {
+      $dataProcessorName = $this->getDataProcessorName();
+      $sql = "
+        SELECT civicrm_data_processor.id as data_processor_id,  civicrm_data_processor_output.id AS output_id
+        FROM civicrm_data_processor 
+        INNER JOIN civicrm_data_processor_output ON civicrm_data_processor.id = civicrm_data_processor_output.data_processor_id
+        WHERE is_active = 1 AND civicrm_data_processor.name = %1 AND civicrm_data_processor_output.type = %2
+      ";
+      $params[1] = [$dataProcessorName, 'String'];
+      $params[2] = [$this->getOutputName(), 'String'];
+      $dao = CRM_Dataprocessor_BAO_DataProcessor::executeQuery($sql, $params, TRUE, 'CRM_Dataprocessor_BAO_DataProcessor');
+      if (!$dao->fetch()) {
+        throw new \Exception('Could not find Data Processor "' . $dataProcessorName.'"');
+      }
+
+      $this->dataProcessor = civicrm_api3('DataProcessor', 'getsingle', array('id' => $dao->data_processor_id));
+      $this->dataProcessorClass = \CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($this->dataProcessor);
+      $this->dataProcessorId = $dao->data_processor_id;
+
+      $this->dataProcessorOutput = civicrm_api3('DataProcessorOutput', 'getsingle', array('id' => $dao->output_id));
+
+      if (!$this->isConfigurationValid()) {
+        throw new \Exception('Invalid configuration found of the data processor "' . $dataProcessorName . '"');
+      }
+    }
+  }
+
+  /**
+   * Checks whether the output has a valid configuration
+   *
+   * @return bool
+   */
+  protected function isConfigurationValid() {
+    if (!isset($this->dataProcessorOutput['configuration']['contact_id_field'])) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns the name of the output for this search
+   *
+   * @return string
+   */
+  protected function getOutputName() {
+    return 'contact_search';
+  }
+
+  /**
+   * Returns whether the ID field is Visible
+   *
+   * @return bool
+   */
+  protected function isIdFieldVisible() {
+    if (isset($this->dataProcessorOutput['configuration']['hide_id_field']) && $this->dataProcessorOutput['configuration']['hide_id_field']) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Returns an array with hidden columns
+   *
+   * @return array
+   */
+  protected function getHiddenFields() {
+    $hiddenFields = array();
+    if (!$this->isIdFieldVisible()) {
+      $hiddenFields[] = $this->getIdFieldName();
+    }
+    return $hiddenFields;
+  }
+
+
+}
\ No newline at end of file
diff --git a/CRM/Contact/StateMachine/DataProcessorContactSearch.php b/CRM/Contact/StateMachine/DataProcessorContactSearch.php
new file mode 100644
index 00000000..8aac209b
--- /dev/null
+++ b/CRM/Contact/StateMachine/DataProcessorContactSearch.php
@@ -0,0 +1,92 @@
+<?php
+/**
+ * @author Jaap Jansma <jaap.jansma@civicoop.org>
+ * @license AGPL-3.0
+ */
+
+class CRM_Contact_StateMachine_DataProcessorContactSearch extends CRM_Core_StateMachine {
+
+  /**
+   * The task that the wizard is currently processing
+   *
+   * @var string
+   */
+  protected $_task;
+
+  /**
+   * Class constructor.
+   *
+   * @param object $controller
+   * @param \const|int $action
+   */
+  public function __construct($controller, $action = CRM_Core_Action::NONE) {
+    parent::__construct($controller, $action);
+
+    $this->_pages = array();
+    $this->_pages['Basic'] = array(
+      'className' => 'CRM_DataprocessorSearch_Form_ContactSearch',
+    );
+    list($task, $result) = $this->taskName($controller);
+    $this->_task = $task;
+    if (is_array($task)) {
+      foreach ($task as $t) {
+        $this->_pages[$t] = NULL;
+      }
+    }
+    else {
+      $this->_pages[$task] = NULL;
+    }
+
+    if ($result) {
+      $this->_pages['CRM_Contact_Form_Task_Result'] = NULL;
+    }
+
+    $this->addSequentialPages($this->_pages, $action);
+  }
+
+  /**
+   * Determine the form name based on the action. This allows us
+   * to avoid using  conditional state machine, much more efficient
+   * and simpler
+   *
+   * @param CRM_Core_Controller $controller
+   *   The controller object.
+   *
+   * @return array
+   *   the name of the form that will handle the task
+   */
+  public function taskName($controller) {
+    // total hack, check POST vars and then session to determine stuff
+    $value = CRM_Utils_Array::value('task', $_POST);
+    if (!isset($value)) {
+      $value = $controller->get('task');
+    }
+    $this->_controller->set('task', $value);
+
+    return CRM_Contact_Task::getTask($value);
+  }
+
+  /**
+   * Return the form name of the task.
+   *
+   * @return string
+   */
+  public function getTaskFormName() {
+    if (is_array($this->_task)) {
+      // return first page
+      return CRM_Utils_String::getClassName($this->_task[0]);
+    }
+    else {
+      return CRM_Utils_String::getClassName($this->_task);
+    }
+  }
+
+  /**
+   * Since this is a state machine for search and we want to come back to the same state
+   * we dont want to issue a reset of the state session when we are done processing a task
+   */
+  public function shouldReset() {
+    return FALSE;
+  }
+
+}
diff --git a/CRM/DataprocessorSearch/Controller/ContactSearch.php b/CRM/DataprocessorSearch/Controller/ContactSearch.php
index 362ed26a..15095c80 100644
--- a/CRM/DataprocessorSearch/Controller/ContactSearch.php
+++ b/CRM/DataprocessorSearch/Controller/ContactSearch.php
@@ -16,6 +16,18 @@
  */
 class CRM_DataprocessorSearch_Controller_ContactSearch extends CRM_Core_Controller {
 
+  protected $dataProcessor;
+
+  /**
+   * Setter function to set the data porcessor
+   *
+   * @param $dataProcessorName
+   * @param $dataProcessor
+   */
+  public function setDataProcessor($dataProcessorName, $dataProcessor) {
+    $this->dataProcessor = $dataProcessor;
+  }
+
   /**
    * Class constructor.
    *
@@ -26,7 +38,7 @@ class CRM_DataprocessorSearch_Controller_ContactSearch extends CRM_Core_Controll
   public function __construct($title = NULL, $modal = TRUE, $action = CRM_Core_Action::NONE) {
     parent::__construct($title, $modal);
 
-    $this->_stateMachine = new CRM_DataprocessorSearch_StateMachine_ContactSearch($this, $action);
+    $this->_stateMachine = new CRM_Contact_StateMachine_DataProcessorContactSearch($this, $action);
 
     // create and instantiate the pages
     $this->addPages($this->_stateMachine, $action);
@@ -72,7 +84,7 @@ class CRM_DataprocessorSearch_Controller_ContactSearch extends CRM_Core_Controll
    * @return mixed
    */
   public function selectorName() {
-    return $this->get('selectorName');
+    return 'CRM_Contact_Selector_DataProcessorContactSearch';
   }
 
 }
diff --git a/CRM/DataprocessorSearch/Form/AbstractSearch.php b/CRM/DataprocessorSearch/Form/AbstractSearch.php
index ceaa55a6..efbf0139 100644
--- a/CRM/DataprocessorSearch/Form/AbstractSearch.php
+++ b/CRM/DataprocessorSearch/Form/AbstractSearch.php
@@ -239,6 +239,7 @@ abstract class CRM_DataprocessorSearch_Form_AbstractSearch extends CRM_Dataproce
     $pagerParams['pageID'] = $pageId;
     $this->pager = new CRM_Utils_Pager($pagerParams);
     $this->assign('pager', $this->pager);
+    $this->controller->set('rowCount', $this->dataProcessorClass->getDataFlow()->recordCount());
 
     $i=0;
     try {
diff --git a/Civi/DataProcessor/DataFlow/SqlDataFlow.php b/Civi/DataProcessor/DataFlow/SqlDataFlow.php
index f5142df0..60ddf3ed 100644
--- a/Civi/DataProcessor/DataFlow/SqlDataFlow.php
+++ b/Civi/DataProcessor/DataFlow/SqlDataFlow.php
@@ -277,4 +277,11 @@ abstract class SqlDataFlow extends AbstractDataFlow {
     );
   }
 
+  /**
+   * @return \CRM_Core_DAO|null
+   */
+  public function getDataObject() {
+    return $this->dao;
+  }
+
 }
\ No newline at end of file
-- 
GitLab