From 25e8a9df0aa871e8f038e64fe436134bab056786 Mon Sep 17 00:00:00 2001
From: Jaap Jansma <jaap.jansma@civicoop.org>
Date: Fri, 11 Oct 2019 13:21:07 +0200
Subject: [PATCH] Added function to clone a data processor.

---
 CHANGELOG.md                                  |   1 +
 CRM/Dataprocessor/Form/CloneDataProcessor.php | 159 ++++++++++++++++++
 CRM/Dataprocessor/Form/DataProcessor.php      |   4 +
 api/v3/DataProcessor.php                      |   3 +
 .../Dataprocessor/Form/CloneDataProcessor.tpl |  64 +++++++
 .../CRM/Dataprocessor/Form/DataProcessor.tpl  |   2 +-
 .../Form/DataProcessorBlocks/Fields.tpl       |  13 +-
 .../Form/ManageDataProcessors.tpl             |  21 ++-
 xml/Menu/dataprocessor.xml                    |   7 +
 9 files changed, 256 insertions(+), 18 deletions(-)
 create mode 100644 CRM/Dataprocessor/Form/CloneDataProcessor.php
 create mode 100644 templates/CRM/Dataprocessor/Form/CloneDataProcessor.tpl

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c1f7c166..2773a6c9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@
 * Fixed issue with updating navigation after editing an output.
 * Added option to expand criteria forms on search forms.
 * Added a Date field.
+* Added function to clone a data processor.
 
 # Version 1.0.7
 
diff --git a/CRM/Dataprocessor/Form/CloneDataProcessor.php b/CRM/Dataprocessor/Form/CloneDataProcessor.php
new file mode 100644
index 00000000..9ec9a0ac
--- /dev/null
+++ b/CRM/Dataprocessor/Form/CloneDataProcessor.php
@@ -0,0 +1,159 @@
+<?php
+
+use CRM_Dataprocessor_ExtensionUtil as E;
+
+/**
+ * Form controller class
+ *
+ * @see https://wiki.civicrm.org/confluence/display/CRMDOC/QuickForm+Reference
+ */
+class CRM_Dataprocessor_Form_CloneDataProcessor extends CRM_Core_Form {
+
+  private $dataProcessorId;
+
+  private $dataProcessor;
+
+  /**
+   * @var \Civi\DataProcessor\ProcessorType\AbstractProcessorType
+   */
+  private $dataProcessorClass;
+
+  private $currentUrl;
+
+  /**
+   * Function to perform processing before displaying form (overrides parent function)
+   *
+   * @access public
+   */
+  function preProcess() {
+    $this->dataProcessorId = CRM_Utils_Request::retrieve('id', 'Integer', $this, true);
+    $this->dataProcessor = civicrm_api3('DataProcessor', 'getsingle', ['id' => $this->dataProcessorId]);
+    $this->dataProcessorClass = CRM_Dataprocessor_BAO_DataProcessor::dataProcessorToClass($this->dataProcessor, true);
+    $this->currentUrl = CRM_Utils_System::url('civicrm/dataprocessor/form/edit', array('reset' => 1, 'action' => 'update', 'id' => $this->dataProcessorId));
+    $this->assign('data_processor_id', $this->dataProcessorId);
+    $this->assign('dataProcessor', $this->dataProcessor);
+  }
+
+  public function buildQuickForm() {
+    CRM_Utils_System::setTitle(E::ts('Clone data processor: %1', [1=>$this->dataProcessor['title']]));
+    $this->add('hidden', 'id');
+
+    $this->add('text', 'name', E::ts('Name'), array('size' => CRM_Utils_Type::HUGE), FALSE);
+    $this->add('text', 'title', E::ts('Title'), array('size' => CRM_Utils_Type::HUGE), TRUE);
+    $this->add('text', 'description', E::ts('Description'), array('size' => 100, 'maxlength' => 256));
+    $this->add('checkbox', 'is_active', E::ts('Enabled'));
+
+    $this->addButtons(array(
+      array('type' => 'next', 'name' => E::ts('Save'), 'isDefault' => TRUE,),
+      array('type' => 'cancel', 'name' => E::ts('Cancel'))
+    ));
+
+    parent::buildQuickForm();
+  }
+
+  /**
+   * Function to set default values (overrides parent function)
+   *
+   * @return array $defaults
+   * @access public
+   */
+  function setDefaultValues() {
+    $defaults = array();
+    $defaults['id'] = $this->dataProcessorId;
+    if (!empty($this->dataProcessor) && !empty($this->dataProcessorId)) {
+      $defaults['title'] = E::ts('Clone of %1', [1=>$this->dataProcessor['title']]);
+      if (isset($this->dataProcessor['description'])) {
+        $defaults['description'] = $this->dataProcessor['description'];
+      } else {
+        $defaults['description'] = '';
+      }
+      $defaults['is_active'] = $this->dataProcessor['is_active'];
+    }
+    return $defaults;
+  }
+
+  public function postProcess() {
+    $session = CRM_Core_Session::singleton();
+    $values = $this->exportValues();
+    $params['name'] = $values['name'];
+    $params['title'] = $values['title'];
+    $params['description'] = $values['description'];
+    $params['is_active'] = !empty($values['is_active']) ? 1 : 0;
+
+    $result = civicrm_api3('DataProcessor', 'create', $params);
+    $newId = $result['id'];
+
+    $sources = civicrm_api3('DataProcessorSource', 'get', array('data_processor_id' => $this->dataProcessorId, 'options' => array('limit' => 0)));
+    $dataProcessor['data_sources'] = array();
+    foreach($sources['values'] as $i => $datasource) {
+      unset($datasource['id']);
+      unset($datasource['data_processor_id']);
+      $datasource['data_processor_id'] = $newId;
+      civicrm_api3('DataProcessorSource', 'create', $datasource);
+    }
+    $filters = civicrm_api3('DataProcessorFilter', 'get', array('data_processor_id' => $this->dataProcessorId, 'options' => array('limit' => 0)));
+    $dataProcessor['filters']  = array();
+    foreach($filters['values'] as $i => $filter) {
+      unset($filter['id']);
+      unset($filter['data_processor_id']);
+      $filter['data_processor_id'] = $newId;
+      civicrm_api3('DataProcessorFilter', 'create', $filter);
+    }
+    $fields = civicrm_api3('DataProcessorField', 'get', array('data_processor_id' => $this->dataProcessorId, 'options' => array('limit' => 0)));
+    $dataProcessor['fields'] = array();
+    foreach($fields['values'] as $i => $field) {
+      unset($field['id']);
+      unset($field['data_processor_id']);
+      $field['data_processor_id'] = $newId;
+      civicrm_api3('DataProcessorField', 'create', $field);
+    }
+    $outputs = $outputs = civicrm_api3('DataProcessorOutput', 'get', array('data_processor_id' => $this->dataProcessorId, 'options' => array('limit' => 0)));
+    $dataProcessor['outputs'] = array();
+    foreach($outputs['values'] as $i => $output) {
+      unset($output['id']);
+      unset($output['data_processor_id']);
+      $output['data_processor_id'] = $newId;
+      civicrm_api3('DataProcessorOutput', 'create', $output);
+    }
+
+    $redirectUrl = CRM_Utils_System::url('civicrm/dataprocessor/form/edit', array('reset' => 1, 'action' => 'update', 'id' => $result['id']));
+    CRM_Utils_System::redirect($redirectUrl);
+  }
+
+  /**
+   * Function to add validation rules (overrides parent function)
+   *
+   * @access public
+   */
+  function addRules() {
+    if ($this->_action != CRM_Core_Action::DELETE) {
+      $this->addFormRule(array(
+        'CRM_Dataprocessor_Form_DataProcessor',
+        'validateName'
+      ));
+    }
+  }
+
+  /**
+   * Function to validate if rule label already exists
+   *
+   * @param array $fields
+   * @return array|bool
+   * @access static
+   */
+  static function validateName($fields) {
+    /*
+     * if id not empty, edit mode. Check if changed before check if exists
+     */
+    $id = false;
+    if (empty($fields['name'])) {
+      $fields['name'] = CRM_Dataprocessor_BAO_DataProcessor::checkName($fields['title'], $id);
+    }
+    if (!CRM_Dataprocessor_BAO_DataProcessor::isNameValid($fields['name'], $id)) {
+      $errors['name'] = E::ts('There is already a data processor with this name');
+      return $errors;
+    }
+    return TRUE;
+  }
+
+}
diff --git a/CRM/Dataprocessor/Form/DataProcessor.php b/CRM/Dataprocessor/Form/DataProcessor.php
index 41ad721c..edd4f7ad 100644
--- a/CRM/Dataprocessor/Form/DataProcessor.php
+++ b/CRM/Dataprocessor/Form/DataProcessor.php
@@ -146,18 +146,22 @@ class CRM_Dataprocessor_Form_DataProcessor extends CRM_Core_Form {
       $this->add('checkbox', 'is_active', E::ts('Enabled'));
     }
     if ($this->_action == CRM_Core_Action::ADD) {
+      CRM_Utils_System::setTitle(E::ts('Add data processor'));
       $this->addButtons(array(
         array('type' => 'next', 'name' => E::ts('Next'), 'isDefault' => TRUE,),
         array('type' => 'cancel', 'name' => E::ts('Cancel'))));
     } elseif ($this->_action == CRM_Core_Action::DELETE) {
+      CRM_Utils_System::setTitle(E::ts('Delete data processor: %1', [1=>$this->dataProcessor['title']]));
       $this->addButtons(array(
         array('type' => 'next', 'name' => E::ts('Delete'), 'isDefault' => TRUE,),
         array('type' => 'cancel', 'name' => E::ts('Cancel'))));
     } elseif ($this->_action == CRM_Core_Action::EXPORT) {
+      CRM_Utils_System::setTitle(E::ts('Export data processor: %1', [1=>$this->dataProcessor['title']]));
       $this->addButtons(array(
         array('type' => 'cancel', 'name' => E::ts('Go back'), 'isDefault' => TRUE),
       ));
     } else {
+      CRM_Utils_System::setTitle(E::ts('Edit data processor: %1', [1=>$this->dataProcessor['title']]));
       $this->addButtons(array(
         array('type' => 'next', 'name' => E::ts('Save'), 'isDefault' => TRUE,),
         array('type' => 'cancel', 'name' => E::ts('Cancel'))));
diff --git a/api/v3/DataProcessor.php b/api/v3/DataProcessor.php
index 13a9412b..7c0b50c7 100644
--- a/api/v3/DataProcessor.php
+++ b/api/v3/DataProcessor.php
@@ -28,6 +28,9 @@ function civicrm_api3_data_processor_create($params) {
   if (isset($params['id'])) {
     $id = $params['id'];
   }
+  if (!isset($params['id']) && !isset($params['status'])) {
+    $params['status'] = CRM_Dataprocessor_Status::STATUS_IN_DATABASE;
+  }
   if (isset($params['title'])) {
     $params['name'] = CRM_Dataprocessor_BAO_DataProcessor::checkName($params['title'], $id, $params['name']);
   }
diff --git a/templates/CRM/Dataprocessor/Form/CloneDataProcessor.tpl b/templates/CRM/Dataprocessor/Form/CloneDataProcessor.tpl
new file mode 100644
index 00000000..d2a72df3
--- /dev/null
+++ b/templates/CRM/Dataprocessor/Form/CloneDataProcessor.tpl
@@ -0,0 +1,64 @@
+{crmScope extensionKey='dataprocessor'}
+<div class="crm-submit-buttons">
+  {include file="CRM/common/formButtons.tpl" location="top"}
+</div>
+
+<h3>{ts 1=$dataProcessor.title}Clone Data Processor '%1'{/ts}</h3>
+<div class="crm-block crm-form-block crm-data-processor_title-block">
+  <div class="crm-section">
+    <div class="label">{$form.title.label}</div>
+    <div class="content">
+      {$form.title.html}
+      <span class="">
+        {ts}System name:{/ts}&nbsp;
+        <span id="systemName" style="font-style: italic;"></span>
+        <a href="javascript:void(0);" onclick="jQuery('#nameSection').removeClass('hiddenElement'); jQuery(this).parent().addClass('hiddenElement'); return false;">
+          {ts}Change{/ts}
+        </a>
+      </span>
+    </div>
+    <div class="clear"></div>
+  </div>
+  <div id="nameSection" class="crm-section hiddenElement">
+    <div class="label">{$form.name.label}</div>
+    <div class="content">
+      {$form.name.html}
+      <p class="description">{ts}Leave empty to let the system generate a name. The name should consist of lowercase letters, numbers and underscore. E.g team_captains.{/ts}</p>
+    </div>
+    <div class="clear"></div>
+  </div>
+  <div class="crm-section">
+    <div class="label">{$form.description.label}</div>
+    <div class="content">{$form.description.html}</div>
+    <div class="clear"></div>
+  </div>
+  <div class="crm-section">
+    <div class="label">{$form.is_active.label}</div>
+    <div class="content">{$form.is_active.html}</div>
+    <div class="clear"></div>
+  </div>
+</div>
+
+<script type="text/javascript">
+  {literal}
+  CRM.$(function($) {
+    $('#title').on('blur', function() {
+      var title = $('#title').val();
+      if ($('#nameSection').hasClass('hiddenElement')) {
+        CRM.api3('DataProcessor', 'check_name', {
+          'title': title
+        }).done(function (result) {
+          $('#systemName').html(result.name);
+          $('#name').val(result.name);
+        });
+      }
+    });
+  });
+  {/literal}
+</script>
+
+
+<div class="crm-submit-buttons">
+  {include file="CRM/common/formButtons.tpl" location="bottom"}
+</div>
+{/crmScope}
diff --git a/templates/CRM/Dataprocessor/Form/DataProcessor.tpl b/templates/CRM/Dataprocessor/Form/DataProcessor.tpl
index de1a0d67..bb912069 100644
--- a/templates/CRM/Dataprocessor/Form/DataProcessor.tpl
+++ b/templates/CRM/Dataprocessor/Form/DataProcessor.tpl
@@ -12,7 +12,7 @@
 
 {else}
 
-<h3>Data Processor</h3>
+<h3>{ts}Data Processor{/ts}</h3>
 <div class="crm-block crm-form-block crm-data-processor_title-block">
   <div class="crm-section">
     <div class="label">{$form.title.label}</div>
diff --git a/templates/CRM/Dataprocessor/Form/DataProcessorBlocks/Fields.tpl b/templates/CRM/Dataprocessor/Form/DataProcessorBlocks/Fields.tpl
index c5ce6b2b..4eec7f69 100644
--- a/templates/CRM/Dataprocessor/Form/DataProcessorBlocks/Fields.tpl
+++ b/templates/CRM/Dataprocessor/Form/DataProcessorBlocks/Fields.tpl
@@ -12,11 +12,12 @@
                 <td>{$field.title} <br /><span class="description">{$field.name}</span></td>
                 <td style="width: 60px">{if ($field.weight && !is_numeric($field.weight))}{$field.weight}{/if}</td>
                 <td class="right nowrap" style="width: 100px;">
-                        <span class="btn-slide crm-hover-button">{ts}Configure{/ts}
-                        <ul class="panel">
-                            <li><a class="action-item crm-hover-button" href="{crmURL p="civicrm/dataprocessor/form/field" q="reset=1&action=update&data_processor_id=`$field.data_processor_id`&id=`$field.id`"}">{ts}Edit{/ts}</a></li>
-                            <li><a class="action-item crm-hover-button" href="{crmURL p="civicrm/dataprocessor/form/field" q="reset=1&action=delete&data_processor_id=`$field.data_processor_id`&id=`$field.id`"}">{ts}Remove{/ts}</a></li>
-                        </ul>
+                    <span class="btn-slide crm-hover-button">{ts}Configure{/ts}
+                    <ul class="panel">
+                        <li><a class="action-item crm-hover-button" href="{crmURL p="civicrm/dataprocessor/form/field" q="reset=1&action=update&data_processor_id=`$field.data_processor_id`&id=`$field.id`"}">{ts}Edit{/ts}</a></li>
+                        <li><a class="action-item crm-hover-button" href="{crmURL p="civicrm/dataprocessor/form/field" q="reset=1&action=delete&data_processor_id=`$field.data_processor_id`&id=`$field.id`"}">{ts}Remove{/ts}</a></li>
+                    </ul>
+                    </span>
                 </td>
             </tr>
         {/foreach}
@@ -27,4 +28,4 @@
             <i class='crm-i fa-plus-circle'></i> {ts}Add Field{/ts}</a>
     </div>
 </div>
-{/crmScope}
\ No newline at end of file
+{/crmScope}
diff --git a/templates/CRM/Dataprocessor/Form/ManageDataProcessors.tpl b/templates/CRM/Dataprocessor/Form/ManageDataProcessors.tpl
index e4fe7bf1..ad044c1b 100644
--- a/templates/CRM/Dataprocessor/Form/ManageDataProcessors.tpl
+++ b/templates/CRM/Dataprocessor/Form/ManageDataProcessors.tpl
@@ -96,16 +96,15 @@
                                 {/foreach}
                             {/if}
                         </td>
-                        <td>
-                          <span>
-                            <a href="{crmURL p='civicrm/dataprocessor/form/edit' q="reset=1&action=update&id=`$data_processor.id`"}"
-                                     class="action-item crm-hover-button" title="{ts}Edit Data Processor{/ts}">{ts}Edit{/ts}</a>
-                            <a href="{crmURL p='civicrm/dataprocessor/form/edit' q="reset=1&action=export&id=`$data_processor.id`"}"
-                                     class="action-item crm-hover-button" title="{ts}Export Data Processor{/ts}">{ts}Export{/ts}</a>
-                            <a href="{crmURL p='civicrm/dataprocessor/form/edit' q="reset=1&action=delete&id=`$data_processor.id`"}"
-                                 class="action-item crm-hover-button" title="{ts}Delete Data Processor{/ts}">{ts}Delete{/ts}</a>
-                          </span>
-
+                      <td class="right nowrap" style="width: 100px;">
+                        <span class="btn-slide crm-hover-button">{ts}Actions{/ts}
+                        <ul class="panel">
+                          <li><a class="action-item crm-hover-button" href="{crmURL p='civicrm/dataprocessor/form/edit' q="reset=1&action=update&id=`$data_processor.id`"}"title="{ts}Edit Data Processor{/ts}">{ts}Edit{/ts}</a></li>
+                          <li><a class="action-item crm-hover-button" href="{crmURL p='civicrm/dataprocessor/form/clone' q="reset=1&action=add&id=`$data_processor.id`"}" title="{ts}Clone Data Processor{/ts}">{ts}Clone{/ts}</a></li>
+                          <li><a class="action-item crm-hover-button" href="{crmURL p='civicrm/dataprocessor/form/edit' q="reset=1&action=export&id=`$data_processor.id`"}" title="{ts}Export Data Processor{/ts}">{ts}Export{/ts}</a></li>
+                          <li><a class="action-item crm-hover-button" href="{crmURL p='civicrm/dataprocessor/form/edit' q="reset=1&action=delete&id=`$data_processor.id`"}" title="{ts}Delete Data Processor{/ts}">{ts}Delete{/ts}</a></li>
+                        </ul>
+                        </span>
                         </td>
                     </tr>
                 {/foreach}
@@ -115,4 +114,4 @@
         {include file="CRM/common/pager.tpl" location="bottom"}
     </div>
 </div>
-{/crmScope}
\ No newline at end of file
+{/crmScope}
diff --git a/xml/Menu/dataprocessor.xml b/xml/Menu/dataprocessor.xml
index e8f44a8a..7ef77365 100644
--- a/xml/Menu/dataprocessor.xml
+++ b/xml/Menu/dataprocessor.xml
@@ -14,6 +14,13 @@
     <access_arguments>access CiviCRM</access_arguments>
     <access_arguments>administer CiviCRM</access_arguments>
   </item>
+  <item>
+    <path>civicrm/dataprocessor/form/clone</path>
+    <page_callback>CRM_Dataprocessor_Form_CloneDataProcessor</page_callback>
+    <title>Clone DataProcessor</title>
+    <access_arguments>access CiviCRM</access_arguments>
+    <access_arguments>administer CiviCRM</access_arguments>
+  </item>
   <item>
     <path>civicrm/dataprocessor/form/import</path>
     <page_callback>CRM_Dataprocessor_Form_Import</page_callback>
-- 
GitLab