From 7ad6667162894a49b7a3dfb71672650d81f47b33 Mon Sep 17 00:00:00 2001
From: Jaap Jansma <jaap@edeveloper.nl>
Date: Mon, 12 Mar 2018 16:02:22 +0100
Subject: [PATCH] Added action name. Easier to handle parameters this way

---
 CRM/FormProcessor/BAO/FormProcessorAction.php | 33 +++++++++++++++
 CRM/FormProcessor/DAO/FormProcessorAction.php |  7 ++++
 Civi/FormProcessor/DataBag.php                | 10 ++---
 ang/form_processor/ActionDialogCtrl.html      | 19 ++++++++-
 ang/form_processor/ActionDialogCtrl.js        | 34 ++++++++++++++-
 ang/form_processor/FormProcessorEditCtrl.js   | 34 ++-------------
 .../FormProcessorEditCtrl/Details.html        |  4 +-
 .../Converttitletoname.php                    | 34 +++++++++++++++
 api/v3/FormProcessorAction/Create.php         |  5 +++
 api/v3/FormProcessorAction/Validatename.php   | 41 +++++++++++++++++++
 api/v3/FormProcessorInstance/Validatename.php |  4 +-
 form_processor.php                            |  2 +-
 sql/create_civicrm_form_processor.sql         |  1 +
 13 files changed, 185 insertions(+), 43 deletions(-)
 create mode 100644 api/v3/FormProcessorAction/Converttitletoname.php
 create mode 100644 api/v3/FormProcessorAction/Validatename.php

diff --git a/CRM/FormProcessor/BAO/FormProcessorAction.php b/CRM/FormProcessor/BAO/FormProcessorAction.php
index 015df03..5853f4b 100644
--- a/CRM/FormProcessor/BAO/FormProcessorAction.php
+++ b/CRM/FormProcessor/BAO/FormProcessorAction.php
@@ -161,6 +161,39 @@ class CRM_FormProcessor_BAO_FormProcessorAction extends CRM_FormProcessor_DAO_Fo
 			CRM_Utils_Hook::post('delete', 'FormProcessorAction', $action->id, CRM_Core_DAO::$_nullArray);
     }
   }
+
+	/**
+   * Public function to generate name from title
+   *
+   * @param $title
+   * @return string
+   * @access public
+   * @static
+   */
+  public static function buildNameFromTitle($title) {
+    $titleParts = explode(' ', strtolower($title));
+    $nameString = implode('_', $titleParts);
+    return substr($nameString, 0, 80);
+  }
+	
+	/**
+	 * Returns whether the name is valid or not
+	 * 
+	 * @param string $name
+	 * @param int $id optional
+	 * @return bool
+	 * @static
+	 */
+	public static function isNameValid($name, $id=null) {
+		$sql = "SELECT COUNT(*) FROM `civicrm_form_processor_action` WHERE `name` = %1";
+		$params[1] = array($name, 'String');
+		if ($id) {
+			$sql .= " AND `id` != %2";
+			$params[2] = array($id, 'Integer');
+		}
+		$count = CRM_Core_DAO::singleValueQuery($sql, $params);
+		return ($count > 0) ? false : true;
+	}
 	
 	
 }
\ No newline at end of file
diff --git a/CRM/FormProcessor/DAO/FormProcessorAction.php b/CRM/FormProcessor/DAO/FormProcessorAction.php
index fedb335..f7f640a 100644
--- a/CRM/FormProcessor/DAO/FormProcessorAction.php
+++ b/CRM/FormProcessor/DAO/FormProcessorAction.php
@@ -49,6 +49,12 @@ class CRM_FormProcessor_DAO_FormProcessorAction extends CRM_Core_DAO {
           'type' => CRM_Utils_Type::T_INT,
           'required' => true,
         ),
+        'name' => array(
+          'name' => 'name',
+          'title' => E::ts('Name'),
+          'type' => CRM_Utils_Type::T_STRING,
+          'maxlength' => 80,
+        ) ,
         'title' => array(
           'name' => 'title',
           'title' => E::ts('Title'),
@@ -90,6 +96,7 @@ class CRM_FormProcessor_DAO_FormProcessorAction extends CRM_Core_DAO {
         'id' => 'id', 
         'form_processor_instance_id' => 'form_processor_instance_id',
         'weight' => 'weight',
+        'name' => 'name',
         'title' => 'title',
         'type' => 'type',
         'configuration' => 'configuration',
diff --git a/Civi/FormProcessor/DataBag.php b/Civi/FormProcessor/DataBag.php
index 194dc50..86b76ff 100644
--- a/Civi/FormProcessor/DataBag.php
+++ b/Civi/FormProcessor/DataBag.php
@@ -56,7 +56,7 @@ class DataBag {
 	 */
 	public function setActionData(\CRM_FormProcessor_BAO_FormProcessorAction $action, $field, $value) {
 		$this->actions[$action->id] = $action;
-		$this->actionData[$action->id][$field] = $value;
+		$this->actionData[$action->name][$field] = $value;
 		return $this; 
 	}
 	
@@ -67,7 +67,7 @@ class DataBag {
 	 * @return string
 	 */
 	public function getActionData(\CRM_FormProcessor_BAO_FormProcessorAction $action) {
-		return $this->actionData[$action->id];
+		return $this->actionData[$action->name];
 	}
 	
 	/**
@@ -89,7 +89,7 @@ class DataBag {
 	public function setActionDataFromActionProviderParameterBag(\CRM_FormProcessor_BAO_FormProcessorAction $action, $parameterBag) {
 		$this->actions[$action->id] = $action;
 		foreach($parameterBag as $field => $value) {
-			$this->actionData[$action->id][$field] = $value;
+			$this->actionData[$action->name][$field] = $value;
 		}
 		return $this;
 	}
@@ -132,8 +132,8 @@ class DataBag {
 			$aliases[] = 'input.'.$input->name;
 		}
 		foreach($this->actions as $action) {
-			foreach($this->actionData[$action->id] as $field => $value) {
-				$aliases[] = 'action.'.$action->id.'.'.$field;	
+			foreach($this->actionData[$action->name] as $field => $value) {
+				$aliases[] = 'action.'.$action->name.'.'.$field;	
 			}
 		}
 		return $aliases;
diff --git a/ang/form_processor/ActionDialogCtrl.html b/ang/form_processor/ActionDialogCtrl.html
index 59b2caa..d7ec64d 100644
--- a/ang/form_processor/ActionDialogCtrl.html
+++ b/ang/form_processor/ActionDialogCtrl.html
@@ -6,7 +6,7 @@
     		<div>{{action.type.title}}</div>
   		</div>
     	
-    	<div crm-ui-field="{name: 'ActionForm.title', title: ts('Title')}">
+    	<div crm-ui-field="{name: 'ActionForm.title', title: ts('Title'), required: true}">
       <input
         crm-ui-id="addActionForm.title"
         type="text"
@@ -18,6 +18,23 @@
         />
 	    </div>
 	    
+	    <div crm-ui-field="{name: 'ActionForm.name', title: ts('Name'), required: true}">
+      <input
+        crm-ui-id="addActionForm.name"
+        type="text"
+        name="name"
+        ng-model="action.name"
+        ng-disabled="locks.name"
+        required
+        class="big crm-form-text"/>
+
+      	<a crm-ui-lock binding="locks.name"></a>
+      	
+      	<div ng-show="!isValidName(action.name) || !isNameValid">
+        	<em>{{ts('WARNING: The name includes invalid characters or is already in use by another action.')}}</em>
+      	</div>
+	    </div>
+	    
 	    <crm-ap-action-configuration 
 	    	configuration="action.configuration" 
 	    	action="action.type"
diff --git a/ang/form_processor/ActionDialogCtrl.js b/ang/form_processor/ActionDialogCtrl.js
index f56c60a..d10527d 100644
--- a/ang/form_processor/ActionDialogCtrl.js
+++ b/ang/form_processor/ActionDialogCtrl.js
@@ -1,10 +1,42 @@
 (function(angular, $, _) {
 
-  angular.module('form_processor').controller('ActionDialogCtrl', function InputDialogCtrl($scope, dialogService) {
+  angular.module('form_processor').controller('ActionDialogCtrl', function InputDialogCtrl($scope, dialogService, crmApi) {
     $scope.ts = CRM.ts(null);
     
     $scope.action = angular.copy($scope.model.action);
     
+    $scope.locks = {name: true};
+    $scope.isNameValid = false;
+    
+    $scope.$watch('action.name', function(newName, oldName) {
+    	// Watch for changes in the name field
+    	$scope.isNameValid = false;
+    	if (newName) {
+	      crmApi('FormProcessorAction', 'validatename', {'name': newName,'id': $scope.action.id})
+	      .then(function(data) {
+	      	if (data.is_valid) {
+	      		$scope.isNameValid = true;
+	      	}
+	      });
+     }
+    }, true);
+    
+    $scope.$watch('action.title', function(newTitle, oldTitle) {
+    	// Watch for changes in the name field
+    	if ($scope.locks.name && $scope.action.id <= 0) {
+    		crmApi('FormProcessorAction', 'converttitletoname', {'title': newTitle})
+      	.then(function(data) {
+      		if (data.name) {
+      			$scope.action.name = data.name;		
+      		}
+    		});
+    	}
+    }, true);
+    
+    $scope.isValidName = function(name) {
+    	return (!name) || name.match(/^[a-z0-9_]+$/) ? true : false;
+    };
+    
     $scope.saveClick = function() {
     	$scope.model.action = $scope.action;
     	dialogService.close('ActionDialog', $scope.model);
diff --git a/ang/form_processor/FormProcessorEditCtrl.js b/ang/form_processor/FormProcessorEditCtrl.js
index b19c0b3..0d40165 100644
--- a/ang/form_processor/FormProcessorEditCtrl.js
+++ b/ang/form_processor/FormProcessorEditCtrl.js
@@ -141,7 +141,6 @@
         	}
         });
         
-        var newActions = [];
         var actionWeight = 0;
         angular.forEach($scope.formProcessor.actions, function(action, key) {
         	var old_id = action.id;
@@ -155,37 +154,10 @@
         	apiCalls.push(crmApi('FormProcessorAction', 'create', action, true).then(function(action_result) {
         		action.id = action_result.id;
         		action.type = action_type;
-        		if (old_id < 0) {
-        			newActions[old_id] = action.id;          		
-        		}
       		}));
         	actionWeight++;
         });
         
-        // Update the mapping
-        $q.all(apiCalls).then(function(result){
-    			// We have saved the last action. 
-    			// Lets fix the mapping of the actions so that the mapping with an id < 0 
-    			// will be resolved to their new id
-       		angular.forEach($scope.formProcessor.actions, function(action, key) {
-       			var actionChanged = false;
-       			angular.forEach(action.mapping, function (field, parameter) {
-       				var splitted_field = field.split('.');
-       				if (splitted_field[0] == 'action' && splitted_field[1] < 0) {
-       					action.mapping[parameter] = 'action.'+newActions[splitted_field[1]]+'.'+splitted_field[2];
-       					actionChanged = true;
-       				}
-       			});
-       			if (actionChanged) {
-       				var action_type = angular.copy(action.type);
-       				action.type = action.type.name;
-       				crmApi('FormProcessorAction', 'create', action, true).then(function(action_result) {
-       					action.type = action_type;
-       				});
-       			}
-       		});
-        });
-        
         $scope.editFormProcessorForm.$setPristine();
         
         if (goBack) {
@@ -264,6 +236,7 @@
     		id: $scope.new_id,
     		type: angular.copy(actionType),
       	'title': '',
+      	'name': '',
   			'configuration': angular.copy(actionType.default_configuration),
   			'mapping': {}
       };
@@ -280,7 +253,7 @@
       });
       
       $scope.updateFields($scope.formProcessor, action);
-      index = $scope.formProcessor.inputs.indexOf(action);
+      index = $scope.formProcessor.actions.indexOf(action);
       var model = {
       	formProcessor: $scope.formProcessor,
       	action: action
@@ -315,7 +288,6 @@
       dialogService.open('OutputHandlerDialog', '~/form_processor/OutputHandlerDialogCtrl.html', model, options)
       .then(function(data) {
       	$scope.editFormProcessorForm.$setDirty();
-      	console.log(data);
       	$scope.formProcessor.output_handler = data.outputHandler;
 			});
     };
@@ -338,7 +310,7 @@
 				if (!skipAfter) {
 					angular.forEach(action.type.output_spec, function(output_field, output_field_key) {
 						var field = {
-  						'name': 'action.'+action.id+'.'+output_field.name,
+  						'name': 'action.'+action.name+'.'+output_field.name,
   						'label': 'Action::'+action.title+'::'+output_field.title
   					};
   					formProcessor.fields.push(field);
diff --git a/ang/form_processor/FormProcessorEditCtrl/Details.html b/ang/form_processor/FormProcessorEditCtrl/Details.html
index f78991d..63a106e 100644
--- a/ang/form_processor/FormProcessorEditCtrl/Details.html
+++ b/ang/form_processor/FormProcessorEditCtrl/Details.html
@@ -1,6 +1,6 @@
 <div class="crm-block" ng-form="formProcessorDetailForm" crm-ui-id-scope>
   <div class="crm-group">
-    <div crm-ui-field="{name: 'formProcessorDetailForm.title', title: ts('Title')}">
+    <div crm-ui-field="{name: 'formProcessorDetailForm.title', title: ts('Title'), required: true}">
       <input
         crm-ui-id="formProcessorDetailForm.title"
         type="text"
@@ -10,7 +10,7 @@
         required
         />
     </div>
-    <div crm-ui-field="{name: 'formProcessorDetailForm.name', title: ts('Name')}">
+    <div crm-ui-field="{name: 'formProcessorDetailForm.name', title: ts('Name'), required: true}">
       <input
         crm-ui-id="formProcessorDetailForm.name"
         type="text"
diff --git a/api/v3/FormProcessorAction/Converttitletoname.php b/api/v3/FormProcessorAction/Converttitletoname.php
new file mode 100644
index 0000000..34d924f
--- /dev/null
+++ b/api/v3/FormProcessorAction/Converttitletoname.php
@@ -0,0 +1,34 @@
+<?php
+
+use CRM_FormProcessor_ExtensionUtil as E;
+
+/**
+ * FormProcessorAction.Converttitletoname API
+ *
+ * @param array $params
+ * @return array API result descriptor
+ * @see civicrm_api3_create_success
+ * @see civicrm_api3_create_error
+ * @throws API_Exception
+ */
+function civicrm_api3_form_processor_action_converttitletoname($params) {
+	$title = $params['title'];
+  $returnValues['name'] = CRM_FormProcessor_BAO_FormProcessorAction::buildNameFromTitle($title);
+  return $returnValues;
+}
+
+/**
+ * FormProcessor.Convgerttitletoname API specification (optional)
+ * This is used for documentation and validation.
+ *
+ * @param array $spec description of fields supported by this API call
+ * @return void
+ * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
+ */
+function _civicrm_api3_form_processor_action_converttitletoname_spec(&$spec) {
+	$spec['title'] = array(
+		'title' => E::ts('Title'),
+		'type' => CRM_Utils_Type::T_STRING,
+		'api.required' => true
+	);
+}
\ No newline at end of file
diff --git a/api/v3/FormProcessorAction/Create.php b/api/v3/FormProcessorAction/Create.php
index b280fce..b3261c4 100644
--- a/api/v3/FormProcessorAction/Create.php
+++ b/api/v3/FormProcessorAction/Create.php
@@ -27,6 +27,11 @@ function _civicrm_api3_form_processor_action_create_spec(&$spec) {
 		'type' => CRM_Utils_Type::T_INT,
 		'api.required' => true,
 	);
+	$spec['name'] = array(
+		'title' => E::ts('Name'),
+		'type' => CRM_Utils_Type::T_STRING,
+		'api.required' => true
+	);
 	$spec['title'] = array(
 		'title' => E::ts('Title'),
 		'type' => CRM_Utils_Type::T_STRING,
diff --git a/api/v3/FormProcessorAction/Validatename.php b/api/v3/FormProcessorAction/Validatename.php
new file mode 100644
index 0000000..518b53d
--- /dev/null
+++ b/api/v3/FormProcessorAction/Validatename.php
@@ -0,0 +1,41 @@
+<?php
+
+use CRM_FormProcessor_ExtensionUtil as E;
+
+/**
+ * FormProcessorAction.Validatename API
+ *
+ * @param array $params
+ * @return array API result descriptor
+ * @see civicrm_api3_create_success
+ * @see civicrm_api3_create_error
+ * @throws API_Exception
+ */
+function civicrm_api3_form_processor_action_validatename($params) {
+	$name = $params['name'];
+	$id = isset($params['id']) ? $params['id'] : null;
+  $returnValues['name'] = $name;
+  $returnValues['is_valid'] = CRM_FormProcessor_BAO_FormProcessorAction::isNameValid($name, $id);
+  return $returnValues;
+}
+
+/**
+ * FormProcessor.Validatename API specification (optional)
+ * This is used for documentation and validation.
+ *
+ * @param array $spec description of fields supported by this API call
+ * @return void
+ * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
+ */
+function _civicrm_api3_form_processor_action_validatename_spec(&$spec) {
+	$spec['id'] = array(
+		'title' => E::ts('ID'),
+		'type' => CRM_Utils_Type::T_INT,
+		'api.required' => false
+	);
+	$spec['name'] = array(
+		'title' => E::ts('Name'),
+		'type' => CRM_Utils_Type::T_STRING,
+		'api.required' => true
+	);
+}
\ No newline at end of file
diff --git a/api/v3/FormProcessorInstance/Validatename.php b/api/v3/FormProcessorInstance/Validatename.php
index d048705..6ded749 100644
--- a/api/v3/FormProcessorInstance/Validatename.php
+++ b/api/v3/FormProcessorInstance/Validatename.php
@@ -3,7 +3,7 @@
 use CRM_FormProcessor_ExtensionUtil as E;
 
 /**
- * FormProcessor.Validatename API
+ * FormProcessorInstance.Validatename API
  *
  * @param array $params
  * @return array API result descriptor
@@ -20,7 +20,7 @@ function civicrm_api3_form_processor_instance_validatename($params) {
 }
 
 /**
- * FormProcessor.Validatename API specification (optional)
+ * FormProcessorInstance.Validatename API specification (optional)
  * This is used for documentation and validation.
  *
  * @param array $spec description of fields supported by this API call
diff --git a/form_processor.php b/form_processor.php
index ac5b817..f1d633a 100644
--- a/form_processor.php
+++ b/form_processor.php
@@ -136,7 +136,7 @@ function form_processor_civicrm_navigationMenu(&$params) {
       'child' => array(
         $newNavId + 1 => array(
           'attributes' => array(
-            'label' => E::ts('Form processors,'),
+            'label' => E::ts('Form processors'),
             'name' => 'form_processors',
             'url' => 'civicrm/admin/automation/formprocessor/#/formprocessors',
             'permission' => 'administer CiviCRM',
diff --git a/sql/create_civicrm_form_processor.sql b/sql/create_civicrm_form_processor.sql
index 14e7a43..5062f63 100644
--- a/sql/create_civicrm_form_processor.sql
+++ b/sql/create_civicrm_form_processor.sql
@@ -38,6 +38,7 @@ CREATE TABLE IF NOT EXISTS `civicrm_form_processor_action` (
   `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
   `form_processor_instance_id` INT UNSIGNED NOT NULL,
   `weight` INT UNSIGNED NOT NULL,
+  `name` VARCHAR(80) NOT NULL,
   `title` VARCHAR(80) NOT NULL,
   `type` VARCHAR(80) NOT NULL,
   `configuration` TEXT NULL,
-- 
GitLab