From 1b08482b2f28424c5f312a162f0f78afcd9b0f4e Mon Sep 17 00:00:00 2001
From: Jaap Jansma <jaap@edeveloper.nl>
Date: Thu, 1 Mar 2018 21:39:56 +0100
Subject: [PATCH] update added validation rules and refactor

---
 CRM/FormProcessor/BAO/FormProcessorAction.php |  10 +-
 CRM/FormProcessor/BAO/FormProcessorInput.php  |  13 +-
 .../BAO/FormProcessorInputValidation.php      | 151 ++++++++++++++++++
 ...rocessor.php => FormProcessorInstance.php} |  58 +++----
 CRM/FormProcessor/DAO/FormProcessorAction.php |   8 +-
 CRM/FormProcessor/DAO/FormProcessorInput.php  |  10 +-
 .../DAO/FormProcessorInputValidation.php      | 104 ++++++++++++
 ...rocessor.php => FormProcessorInstance.php} |   6 +-
 Civi/FormProcessor/API/Provider.php           |   7 +-
 .../Validation/AbstractValidator.php          | 127 +++++++++++++++
 .../Validation/EmailValidator.php             |  42 +++++
 Civi/FormProcessor/Validation/Factory.php     |  69 ++++++++
 ang/form_processor.ang.php                    |   2 +
 ang/form_processor/ActionDialogCtrl.html      |  12 +-
 ang/form_processor/ActionDialogCtrl.js        |   3 +
 ang/form_processor/FormProcessorEditCtrl.html |   9 +-
 ang/form_processor/FormProcessorEditCtrl.js   |  77 +++++++--
 .../FormProcessorEditCtrl/Details.html        |   2 +-
 .../FormProcessorEditCtrl/InputTable.html     |   8 +
 ang/form_processor/FormProcessorListCtrl.js   |   6 +-
 ang/form_processor/InputDialogCtrl.html       |  46 +++++-
 ang/form_processor/InputDialogCtrl.js         |  24 ++-
 ...mFormProcessorValidationConfiguration.html |  15 ++
 ...crmFormProcessorValidationConfiguration.js |  16 ++
 api/v3/FormProcessorAction/Create.php         |   6 +-
 api/v3/FormProcessorInput/Create.php          |   6 +-
 api/v3/FormProcessorInput/Get.php             |   3 +
 .../FormProcessorInputValidation/Create.php   |  52 ++++++
 .../FormProcessorInputValidation/Delete.php   |  37 +++++
 api/v3/FormProcessorInputValidation/Get.php   |  33 ++++
 .../Create.php                                |  10 +-
 .../Delete.php                                |   6 +-
 .../Get.php                                   |  13 +-
 .../Validatename.php                          |   6 +-
 form_processor.php                            |  19 ++-
 sql/create_civicrm_form_processor.sql         |  16 +-
 sql/uninstall.sql                             |   6 +-
 tests/phpunit/api/v3/ApiPermissionTest.php    |  53 ++++--
 38 files changed, 955 insertions(+), 136 deletions(-)
 create mode 100644 CRM/FormProcessor/BAO/FormProcessorInputValidation.php
 rename CRM/FormProcessor/BAO/{FormProcessor.php => FormProcessorInstance.php} (55%)
 create mode 100644 CRM/FormProcessor/DAO/FormProcessorInputValidation.php
 rename CRM/FormProcessor/DAO/{FormProcessor.php => FormProcessorInstance.php} (94%)
 create mode 100644 Civi/FormProcessor/Validation/AbstractValidator.php
 create mode 100644 Civi/FormProcessor/Validation/EmailValidator.php
 create mode 100644 Civi/FormProcessor/Validation/Factory.php
 create mode 100644 ang/form_processor/crmFormProcessorValidationConfiguration.html
 create mode 100644 ang/form_processor/crmFormProcessorValidationConfiguration.js
 create mode 100644 api/v3/FormProcessorInputValidation/Create.php
 create mode 100644 api/v3/FormProcessorInputValidation/Delete.php
 create mode 100644 api/v3/FormProcessorInputValidation/Get.php
 rename api/v3/{FormProcessor => FormProcessorInstance}/Create.php (86%)
 rename api/v3/{FormProcessor => FormProcessorInstance}/Delete.php (79%)
 rename api/v3/{FormProcessor => FormProcessorInstance}/Get.php (66%)
 rename api/v3/{FormProcessor => FormProcessorInstance}/Validatename.php (84%)

diff --git a/CRM/FormProcessor/BAO/FormProcessorAction.php b/CRM/FormProcessor/BAO/FormProcessorAction.php
index 8c192bd..015df03 100644
--- a/CRM/FormProcessor/BAO/FormProcessorAction.php
+++ b/CRM/FormProcessor/BAO/FormProcessorAction.php
@@ -31,7 +31,7 @@ class CRM_FormProcessor_BAO_FormProcessorAction extends CRM_FormProcessor_DAO_Fo
     while ($action->fetch()) {
       $row = array();
       self::storeValues($action, $row);
-      if (!empty($row['form_processor_id'])) {
+      if (!empty($row['form_processor_instance_id'])) {
       	$provider = form_processor_get_action_provider();
 				$action_type = $provider->getActionByName($row['type']);
 				
@@ -143,15 +143,15 @@ class CRM_FormProcessor_BAO_FormProcessorAction extends CRM_FormProcessor_DAO_Fo
   }
 
 	/**
-   * Function to delete all actions with a form processor id
+   * Function to delete all actions with a form processor instance id
    *
-   * @param int $formProcessorId
+   * @param int $formProcessorInstanceId
    * @access public
    * @static
    */
-  public static function deleteWithFormProcessorId($formProcessorId) {
+  public static function deleteWithFormProcessorInstanceId($formProcessorInstanceId) {
     $action = new CRM_FormProcessor_BAO_FormProcessorAction();
-    $action->form_processor_id = $formProcessorId;
+    $action->form_processor_instance_id = $formProcessorInstanceId;
     $action->find(FALSE);
     while ($action->fetch()) {
       CRM_Utils_Hook::pre('delete', 'FormProcessorAction', $action->id, CRM_Core_DAO::$_nullArray);
diff --git a/CRM/FormProcessor/BAO/FormProcessorInput.php b/CRM/FormProcessor/BAO/FormProcessorInput.php
index 67702ff..0844a98 100644
--- a/CRM/FormProcessor/BAO/FormProcessorInput.php
+++ b/CRM/FormProcessor/BAO/FormProcessorInput.php
@@ -30,7 +30,7 @@ class CRM_FormProcessor_BAO_FormProcessorInput extends CRM_FormProcessor_DAO_For
     while ($input->fetch()) {
       $row = array();
       self::storeValues($input, $row);
-      if (!empty($row['form_processor_id'])) {
+      if (!empty($row['form_processor_instance_id'])) {
       	$type = \Civi::service('form_processor_type_factory')->getTypeByName($row['type']);
 				if ($type) {
 					$configuration = $type->getDefaultConfiguration();
@@ -47,6 +47,9 @@ class CRM_FormProcessor_BAO_FormProcessorInput extends CRM_FormProcessor_DAO_For
 					
 					$row['type'] = $type;
 				}
+				
+				$row['validators'] = array_values(CRM_FormProcessor_BAO_FormProcessorInputValidation::getValues(array('form_processor_input_id' => $input->id)));
+				
         $result[$row['id']] = $row;
       } else {
         //invalid input because no there is no form processor
@@ -128,15 +131,15 @@ class CRM_FormProcessor_BAO_FormProcessorInput extends CRM_FormProcessor_DAO_For
   }
 
 	/**
-   * Function to delete all inputs with a form processor id
+   * Function to delete all inputs with a form processor instance id
    *
-   * @param int $formProcessorId
+   * @param int $formProcessorInstanceId
    * @access public
    * @static
    */
-  public static function deleteWithFormProcessorId($formProcessorId) {
+  public static function deleteWithFormProcessorInstanceId($formProcessorInstanceId) {
     $input = new CRM_FormProcessor_BAO_FormProcessorInput();
-    $input->form_processor_id = $formProcessorId;
+    $input->form_processor_instance_id = $formProcessorInstanceId;
     $input->find(FALSE);
     while ($input->fetch()) {
       CRM_Utils_Hook::pre('delete', 'FormProcessorInput', $input->id, CRM_Core_DAO::$_nullArray);
diff --git a/CRM/FormProcessor/BAO/FormProcessorInputValidation.php b/CRM/FormProcessor/BAO/FormProcessorInputValidation.php
new file mode 100644
index 0000000..99ee591
--- /dev/null
+++ b/CRM/FormProcessor/BAO/FormProcessorInputValidation.php
@@ -0,0 +1,151 @@
+<?php
+
+use CRM_FormProcessor_ExtensionUtil as E;
+
+/**
+ * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org>
+ * @license http://www.gnu.org/licenses/agpl-3.0.html
+ */
+class CRM_FormProcessor_BAO_FormProcessorInputValidation extends CRM_FormProcessor_DAO_FormProcessorInputValidation {
+  
+	 /**
+   * Function to get values
+   * 
+   * @return array $result found rows with data
+   * @access public
+   * @static
+   */
+  public static function getValues($params) {
+    $result = array();
+    $inputValidation = new CRM_FormProcessor_BAO_FormProcessorInputValidation();
+    if (!empty($params)) {
+      $fields = self::fields();
+      foreach ($params as $key => $value) {
+        if (isset($fields[$key])) {
+          $inputValidation->$key = $value;
+        }
+      }
+    }
+    $inputValidation->find();
+    while ($inputValidation->fetch()) {
+      $row = array();
+      self::storeValues($inputValidation, $row);
+      if (!empty($row['form_processor_input_id'])) {
+      	$validator = \Civi::service('form_processor_validation_factory')->getValidatorByName($row['validator']);
+				if ($validator) {
+					$configuration = $validator->getDefaultConfiguration();
+					if (isset($row['configuration']) && is_string($row['configuration'])) {
+						$row['configuration'] = json_decode($row['configuration'], true);
+					}
+					if (empty($row['configuration']) || !is_array($row['configuration'])) {
+						$row['configuration'] = array();
+					}
+					foreach($row['configuration'] as $name => $value) {
+						$configuration->set($name, $value);
+					}
+					$validator->setConfiguration($configuration);
+					
+					$row['validator'] = $validator;
+				}
+        $result[$row['id']] = $row;
+      } else {
+        //invalid input because no there is no form processor
+        CRM_FormProcessor_BAO_FormProcessorInputValidation::deleteWithId($row['id']);
+      }
+    }
+    return $result;
+  }
+
+  /**
+   * Function to add or update form processor input validation
+   * 
+   * @param array $params 
+   * @return array $result
+   * @access public
+   * @throws Exception when params is empty
+   * @static
+   */
+  public static function add($params) {
+    $result = array();
+    if (empty($params)) {
+      throw new Exception('Params can not be empty when adding or updating a form processor input validation');
+    }
+		
+		if (!empty($params['id'])) {
+      CRM_Utils_Hook::pre('edit', 'FormProcessorInputValidation', $params['id'], $params);
+    }
+    else {
+      CRM_Utils_Hook::pre('create', 'FormProcessorInputValidation', NULL, $params);
+    }
+		
+    $inputValidation = new CRM_FormProcessor_BAO_FormProcessorInputValidation();
+    $fields = self::fields();
+    foreach ($params as $key => $value) {
+      if (isset($fields[$key])) {
+        $inputValidation->$key = $value;
+      }
+    }
+		if (is_array($inputValidation->configuration)) {
+			$inputValidation->configuration = json_encode($inputValidation->configuration);
+		}
+    $inputValidation->save();
+    self::storeValues($inputValidation, $result);
+		
+		if (!empty($params['id'])) {
+      CRM_Utils_Hook::post('edit', 'FormProcessorInputValidation', $inputValidation->id, $inputValidation);
+    }
+    else {
+      CRM_Utils_Hook::post('create', 'FormProcessorInputValidation', $inputValidation->id, $inputValidation);
+    }
+		
+    return $result;
+  }
+
+  /**
+   * Function to delete a form processor input with id
+   * 
+   * @param int $id
+   * @throws Exception when $id is empty
+   * @access public
+   * @static
+   */
+  public static function deleteWithId($id) {
+    if (empty($id)) {
+      throw new Exception('id can not be empty when attempting to delete a form processor input validation');
+    }
+		
+		CRM_Utils_Hook::pre('delete', 'FormProcessorInputValidation', $id, CRM_Core_DAO::$_nullArray);
+		
+    $inputValidation = new CRM_FormProcessor_BAO_FormProcessorInputValidation();
+    $inputValidation->id = $id;
+    if ($inputValidation->find(true)) {
+      $inputValidation->delete();
+    }
+		
+		CRM_Utils_Hook::post('delete', 'FormProcessorInputValidation', $id, CRM_Core_DAO::$_nullArray);
+		
+    return;
+  }
+
+	/**
+   * Function to delete all inputs with a form processor input id
+   *
+   * @param int $formProcessorInputId
+   * @access public
+   * @static
+   */
+  public static function deleteWithFormProcessorInputId($formProcessorInputId) {
+    $inputValidation = new CRM_FormProcessor_BAO_FormProcessorInputValidation();
+    $inputValidation->form_processor_input_id = $formProcessorInputId;
+    $inputValidation->find(FALSE);
+    while ($inputValidation->fetch()) {
+      CRM_Utils_Hook::pre('delete', 'FormProcessorInputValidation', $inputValidation->id, CRM_Core_DAO::$_nullArray);
+		
+      $inputValidation->delete();
+			
+			CRM_Utils_Hook::post('delete', 'FormProcessorInputValidation', $inputValidation->id, CRM_Core_DAO::$_nullArray);
+    }
+  }
+	
+	
+}
\ No newline at end of file
diff --git a/CRM/FormProcessor/BAO/FormProcessor.php b/CRM/FormProcessor/BAO/FormProcessorInstance.php
similarity index 55%
rename from CRM/FormProcessor/BAO/FormProcessor.php
rename to CRM/FormProcessor/BAO/FormProcessorInstance.php
index b355b1d..cdf59a6 100644
--- a/CRM/FormProcessor/BAO/FormProcessor.php
+++ b/CRM/FormProcessor/BAO/FormProcessorInstance.php
@@ -4,7 +4,7 @@
  * @license http://www.gnu.org/licenses/agpl-3.0.html
  */
  
- class CRM_FormProcessor_BAO_FormProcessor extends CRM_FormProcessor_DAO_FormProcessor {
+ class CRM_FormProcessor_BAO_FormProcessorInstance extends CRM_FormProcessor_DAO_FormProcessorInstance {
  	
 	/**
    * Function to get values
@@ -15,21 +15,21 @@
    */
   public static function getValues($params) {
     $result = array();
-    $formProcessor = new CRM_FormProcessor_BAO_FormProcessor();
+    $formProcessorInstance = new CRM_FormProcessor_BAO_FormProcessorInstance();
     if (!empty($params)) {
       $fields = self::fields();
       foreach ($params as $key => $value) {
         if (isset($fields[$key])) {
-          $formProcessor->$key = $value;
+          $formProcessorInstance->$key = $value;
         }
       }
     }
-    $formProcessor->find();
-    while ($formProcessor->fetch()) {
+    $formProcessorInstance->find();
+    while ($formProcessorInstance->fetch()) {
       $row = array();
-      self::storeValues($formProcessor, $row); 
-			$row['inputs'] = array_values(CRM_FormProcessor_BAO_FormProcessorInput::getValues(array('form_processor_id' => $formProcessor->id)));
-			$row['actions'] = array_values(CRM_FormProcessor_BAO_FormProcessorAction::getValues(array('form_processor_id' => $formProcessor->id)));
+      self::storeValues($formProcessorInstance, $row); 
+			$row['inputs'] = array_values(CRM_FormProcessor_BAO_FormProcessorInput::getValues(array('form_processor_instance_id' => $formProcessorInstance->id)));
+			$row['actions'] = array_values(CRM_FormProcessor_BAO_FormProcessorAction::getValues(array('form_processor_instance_id' => $formProcessorInstance->id)));
 			
       $result[$row['id']] = $row;
     }
@@ -37,7 +37,7 @@
   }
  	
 	/**
-   * Function to add or update a FormProcessor
+   * Function to add or update a FormProcessorInstance
    * 
    * @param array $params 
    * @return array $result
@@ -52,39 +52,39 @@
     }
 
     if (!empty($params['id'])) {
-      CRM_Utils_Hook::pre('edit', 'FormProcessor', $params['id'], $params);
+      CRM_Utils_Hook::pre('edit', 'FormProcessorInstance', $params['id'], $params);
     }
     else {
-      CRM_Utils_Hook::pre('create', 'FormProcessor', NULL, $params);
+      CRM_Utils_Hook::pre('create', 'FormProcessorInstance', NULL, $params);
     }
 
-    $formProcessor = new CRM_FormProcessor_BAO_FormProcessor();
+    $formProcessorInstance = new CRM_FormProcessor_BAO_FormProcessorInstance();
     $fields = self::fields();
     foreach ($params as $key => $value) {
       if (isset($fields[$key])) {
-        $formProcessor->$key = $value;
+        $formProcessorInstance->$key = $value;
       }
     }
-    if (!isset($formProcessor->name) || empty($formProcessor->name)) {
-      if (isset($formProcessor->title)) {
-        $formProcessor->name = CRM_FormProcessor_BAO_FormProcessor::buildNameFromLabel($formProcessor->title);
+    if (!isset($formProcessorInstance->name) || empty($formProcessorInstance->name)) {
+      if (isset($formProcessorInstance->title)) {
+        $formProcessorInstance->name = CRM_FormProcessor_BAO_FormProcessorInstance::buildNameFromLabel($formProcessorInstance->title);
       }
     }
-    $formProcessor->save();
-    self::storeValues($formProcessor, $result);
+    $formProcessorInstance->save();
+    self::storeValues($formProcessorInstance, $result);
 
     if (!empty($params['id'])) {
-      CRM_Utils_Hook::post('edit', 'FormProcessor', $formProcessor->id, $formProcessor);
+      CRM_Utils_Hook::post('edit', 'FormProcessorInstance', $formProcessorInstance->id, $formProcessorInstance);
     }
     else {
-      CRM_Utils_Hook::post('create', 'FormProcessor', $formProcessor->id, $formProcessor);
+      CRM_Utils_Hook::post('create', 'FormProcessorInstance', $formProcessorInstance->id, $formProcessorInstance);
     }
 
     return $result;
   }
 
 	/**
-   * Function to delete a FormProcessor with id
+   * Function to delete a FormProcessorInstance with id
    * 
    * @param int $id
    * @throws Exception when $id is empty
@@ -96,18 +96,18 @@
       throw new Exception('id can not be empty when attempting to delete a form processor');
     }
 
-    CRM_Utils_Hook::pre('delete', 'FormProcessor', $id, CRM_Core_DAO::$_nullArray);
+    CRM_Utils_Hook::pre('delete', 'FormProcessorInstance', $id, CRM_Core_DAO::$_nullArray);
 
 		// First delete all inputs
-		CRM_FormProcessor_BAO_FormProcessorInput::deleteWithFormProcessorId($id);
+		CRM_FormProcessor_BAO_FormProcessorInput::deleteWithFormProcessorInstanceId($id);
 		// And all actions
-		CRM_FormProcessor_BAO_FormProcessorAction::deleteWithFormProcessorId($id);
+		CRM_FormProcessor_BAO_FormProcessorAction::deleteWithFormProcessorInstanceId($id);
 
-    $rule = new CRM_FormProcessor_BAO_FormProcessor();
-    $rule->id = $id;
-    $rule->delete();
+    $formProcessorInstance = new CRM_FormProcessor_BAO_FormProcessorInstance();
+    $formProcessorInstance->id = $id;
+    $formProcessorInstance->delete();
 
-    CRM_Utils_Hook::post('delete', 'FormProcessor', $id, CRM_Core_DAO::$_nullArray);
+    CRM_Utils_Hook::post('delete', 'FormProcessorInstance', $id, CRM_Core_DAO::$_nullArray);
 
     return;
   }
@@ -135,7 +135,7 @@
 	 * @static
 	 */
 	public static function isNameValid($name, $id=null) {
-		$sql = "SELECT COUNT(*) FROM `civicrm_form_processor` WHERE `name` = %1";
+		$sql = "SELECT COUNT(*) FROM `civicrm_form_processor_instance` WHERE `name` = %1";
 		$params[1] = array($name, 'String');
 		if ($id) {
 			$sql .= " AND `id` != %2";
diff --git a/CRM/FormProcessor/DAO/FormProcessorAction.php b/CRM/FormProcessor/DAO/FormProcessorAction.php
index aeef77e..fedb335 100644
--- a/CRM/FormProcessor/DAO/FormProcessorAction.php
+++ b/CRM/FormProcessor/DAO/FormProcessorAction.php
@@ -36,12 +36,12 @@ class CRM_FormProcessor_DAO_FormProcessorAction extends CRM_Core_DAO {
           'type' => CRM_Utils_Type::T_INT,
           'required' => true
         ),
-        'form_processor_id' => array(
-          'name' => 'form_processor_id',
+        'form_processor_instance_id' => array(
+          'name' => 'form_processor_instance_id',
           'title' => E::ts('Form Processor ID'),
           'type' => CRM_Utils_Type::T_INT,
           'required' => true,
-          'FKApiName' => 'FormProcessor',
+          'FKApiName' => 'FormProcessorInstance',
         ),
         'weight' => array(
           'name' => 'weight',
@@ -88,7 +88,7 @@ class CRM_FormProcessor_DAO_FormProcessorAction extends CRM_Core_DAO {
     if (!(self::$_fieldKeys)) {
       self::$_fieldKeys = array(
         'id' => 'id', 
-        'form_processor_id' => 'form_processor_id',
+        'form_processor_instance_id' => 'form_processor_instance_id',
         'weight' => 'weight',
         'title' => 'title',
         'type' => 'type',
diff --git a/CRM/FormProcessor/DAO/FormProcessorInput.php b/CRM/FormProcessor/DAO/FormProcessorInput.php
index 8fc23fc..b183c1a 100644
--- a/CRM/FormProcessor/DAO/FormProcessorInput.php
+++ b/CRM/FormProcessor/DAO/FormProcessorInput.php
@@ -36,12 +36,12 @@ class CRM_FormProcessor_DAO_FormProcessorInput extends CRM_Core_DAO {
           'type' => CRM_Utils_Type::T_INT,
           'required' => true
         ),
-        'form_processor_id' => array(
-          'name' => 'form_processor_id',
-          'title' => E::ts('Form Processor ID'),
+        'form_processor_instance_id' => array(
+          'name' => 'form_processor_instance_id',
+          'title' => E::ts('Form Processor Instance ID'),
           'type' => CRM_Utils_Type::T_INT,
           'required' => true,
-          'FKApiName' => 'FormProcessor',
+          'FKApiName' => 'FormProcessorInstance',
         ),
         'name' => array(
           'name' => 'name',
@@ -89,7 +89,7 @@ class CRM_FormProcessor_DAO_FormProcessorInput extends CRM_Core_DAO {
     if (!(self::$_fieldKeys)) {
       self::$_fieldKeys = array(
         'id' => 'id', 
-        'form_processor_id' => 'form_processor_id',
+        'form_processor_instance_id' => 'form_processor_instance_id',
         'name' => 'name',
         'type' => 'type',
         'is_required' => 'is_required',
diff --git a/CRM/FormProcessor/DAO/FormProcessorInputValidation.php b/CRM/FormProcessor/DAO/FormProcessorInputValidation.php
new file mode 100644
index 0000000..21b131e
--- /dev/null
+++ b/CRM/FormProcessor/DAO/FormProcessorInputValidation.php
@@ -0,0 +1,104 @@
+<?php
+
+use CRM_FormProcessor_ExtensionUtil as E;
+
+/**
+ * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org>
+ * @license http://www.gnu.org/licenses/agpl-3.0.html
+ */
+class CRM_FormProcessor_DAO_FormProcessorInputValidation extends CRM_Core_DAO {
+  /**
+   * static instance to hold the field values
+   *
+   * @var array
+   * @static
+   */
+  static $_fields = null;
+  static $_export = null;
+  /**
+   * empty definition for virtual function
+   */
+  static function getTableName() {
+    return 'civicrm_form_processor_input_validation';
+  }
+  /**
+   * returns all the column names of this table
+   *
+   * @access public
+   * @return array
+   */
+  public static function &fields() {
+    if (!(self::$_fields)) {
+      self::$_fields = array(
+        'id' => array(
+          'name' => 'id',
+          'title' => E::ts('ID'),
+          'type' => CRM_Utils_Type::T_INT,
+          'required' => true
+        ),
+        'form_processor_input_id' => array(
+          'name' => 'form_processor_input_id',
+          'title' => E::ts('Form Processor Input ID'),
+          'type' => CRM_Utils_Type::T_INT,
+          'required' => true,
+          'FKApiName' => 'FormProcessorInput',
+        ),
+        'validator' => array(
+          'name' => 'validator',
+          'title' => E::ts('Validator'),
+          'type' => CRM_Utils_Type::T_STRING,
+          'maxlength' => 80,
+          'required' => true,
+        ),
+        'configuration' => array(
+          'name' => 'configuration',
+          'title' => E::ts('Configuration'),
+          'type' => CRM_Utils_Type::T_TEXT,
+        ),
+      );
+    }
+    return self::$_fields;
+  }
+  /**
+   * Returns an array containing, for each field, the array key used for that
+   * field in self::$_fields.
+   *
+   * @access public
+   * @return array
+   */
+  public static function &fieldKeys() {
+    if (!(self::$_fieldKeys)) {
+      self::$_fieldKeys = array(
+        'id' => 'id', 
+        'form_processor_input_id' => 'form_processor_input_id',
+        'validator' => 'validator',
+        'configuration' => 'configuration',
+      );
+    }
+    return self::$_fieldKeys;
+  }
+  /**
+   * returns the list of fields that can be exported
+   *
+   * @access public
+   * return array
+   * @static
+   */
+  static function &export($prefix = false)
+  {
+    if (!(self::$_export)) {
+      self::$_export = array();
+      $fields = self::fields();
+      foreach($fields as $name => $field) {
+        if (CRM_Utils_Array::value('export', $field)) {
+          if ($prefix) {
+            self::$_export['form_processor_input'] = & $fields[$name];
+          } else {
+            self::$_export[$name] = & $fields[$name];
+          }
+        }
+      }
+    }
+    return self::$_export;
+  }
+}
\ No newline at end of file
diff --git a/CRM/FormProcessor/DAO/FormProcessor.php b/CRM/FormProcessor/DAO/FormProcessorInstance.php
similarity index 94%
rename from CRM/FormProcessor/DAO/FormProcessor.php
rename to CRM/FormProcessor/DAO/FormProcessorInstance.php
index 4acef1a..1df1a4f 100644
--- a/CRM/FormProcessor/DAO/FormProcessor.php
+++ b/CRM/FormProcessor/DAO/FormProcessorInstance.php
@@ -6,7 +6,7 @@ use CRM_FormProcessor_ExtensionUtil as E;
  * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org>
  * @license http://www.gnu.org/licenses/agpl-3.0.html
  */
-class CRM_FormProcessor_DAO_FormProcessor extends CRM_Core_DAO {
+class CRM_FormProcessor_DAO_FormProcessorInstance extends CRM_Core_DAO {
   /**
    * static instance to hold the field values
    *
@@ -19,7 +19,7 @@ class CRM_FormProcessor_DAO_FormProcessor extends CRM_Core_DAO {
    * empty definition for virtual function
    */
   static function getTableName() {
-    return 'civicrm_form_processor';
+    return 'civicrm_form_processor_instance';
   }
   /**
    * returns all the column names of this table
@@ -118,7 +118,7 @@ class CRM_FormProcessor_DAO_FormProcessor extends CRM_Core_DAO {
       foreach($fields as $name => $field) {
         if (CRM_Utils_Array::value('export', $field)) {
           if ($prefix) {
-            self::$_export['form_processor'] = & $fields[$name];
+            self::$_export['form_processor_instance'] = & $fields[$name];
           } else {
             self::$_export[$name] = & $fields[$name];
           }
diff --git a/Civi/FormProcessor/API/Provider.php b/Civi/FormProcessor/API/Provider.php
index e61d20b..95260d8 100644
--- a/Civi/FormProcessor/API/Provider.php
+++ b/Civi/FormProcessor/API/Provider.php
@@ -48,7 +48,7 @@
   	$params = $apiRequest['params'];
   	  	
   	// Find the form processor
-  	$formProcessors = \CRM_FormProcessor_BAO_FormProcessor::getValues(array('name' => $apiRequest['action']));
+  	$formProcessors = \CRM_FormProcessor_BAO_FormProcessorInstance::getValues(array('name' => $apiRequest['action']));
 		if (count($formProcessors) != 1) {
 			throw new \API_Exception('Could not find a form processor');
 		}
@@ -96,7 +96,7 @@
    */
   public function getEntityNames($version) {
   	return array(
-  		'FormProcessorExecutor'
+  		'FormProcessor'
 		);
   }
 
@@ -109,8 +109,7 @@
    */
   public function getActionNames($version, $entity) {		
   	$params['is_active'] = 1;
-  	$form_processors = \CRM_FormProcessor_BAO_FormProcessor::getValues($params);
-		$actions = array();
+  	$form_processors = \CRM_FormProcessor_BAO_FormProcessorInstance::getValues($params);
 		foreach($form_processors as $form_processor) {
 			$actions[] = $form_processor['name'];
 		}
diff --git a/Civi/FormProcessor/Validation/AbstractValidator.php b/Civi/FormProcessor/Validation/AbstractValidator.php
new file mode 100644
index 0000000..8fc6aec
--- /dev/null
+++ b/Civi/FormProcessor/Validation/AbstractValidator.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org>
+ * @license http://www.gnu.org/licenses/agpl-3.0.html
+ */
+ 
+ namespace Civi\FormProcessor\Validation;
+ 
+ use \Civi\FormProcessor\Config\ConfigurationBag;
+ use \Civi\FormProcessor\Config\Specification;
+ use \Civi\FormProcessor\Config\SpecificationBag;
+ 
+ use CRM_FormProcessor_ExtensionUtil as E;
+ 
+ abstract class AbstractValidator implements \JsonSerializable {
+ 	
+	/**
+	 * @var ConfigurationBag
+	 */
+	protected $configuration;
+	
+	/**
+	 * @var ConfigurationBag
+	 */
+	protected $defaultConfiguration;
+ 	
+	/**
+	 * Returns the name of the validator.
+	 * 
+	 * @return string
+	 */
+	abstract public function getName();
+	
+	/**
+	 * Returns the label of the validator.
+	 * 
+	 * @return string
+	 */
+	abstract public function getLabel();
+	
+	/**
+	 * Validate the input.
+	 * 
+	 * @param mixed $input
+	 * @return bool
+	 */
+	abstract public function validate($input);
+	
+	public function __construct() {
+		$this->configuration = new ConfigurationBag();
+		$this->defaultConfiguration = new ConfigurationBag();
+
+		foreach($this->getConfigurationSpecification() as $spec) {
+			if ($spec->getDefaultValue()) {
+				$this->configuration->set($spec->getName(), $spec->getDefaultValue());
+				$this->defaultConfiguration->set($spec->getName(), $spec->getDefaultValue());
+			}
+		}
+	}
+	
+	/**
+	 * Get the configuration specification
+	 * 
+	 * @return SpecificationBag
+	 */
+	public function getConfigurationSpecification() {
+		return new SpecificationBag();
+	}
+	
+	/**
+	 * @return bool;
+	 */
+	public function validateConfiguration() {
+		if ($this->configuration === null) {
+			return false;
+		}
+		return SpecificationBag::validate($this->configuration, $this->getConfigurationSpecification());
+	}
+	
+	/**
+	 * @return ConfigurationBag
+	 */
+	public function getConfiguration() {
+		return $this->configuration;
+	}
+	
+	/**
+	 * @return ConfigurationBag
+	 */
+	public function getDefaultConfiguration() {
+		return $this->defaultConfiguration;
+	}
+	
+	
+	/**
+	 * Returns the invalid message.
+	 * 
+	 * @return string
+	 */
+	public function getInvalidMessage() {
+		return E::ts('Invalid %1', array(1=>$this->getLabel()));
+	}
+	
+	/**
+	 * @param ConfigurationBag $configuration
+	 */
+	public function setConfiguration(ConfigurationBag $configuration) {
+		$this->configuration = $configuration;
+		return $this;
+	}
+	
+	public function toArray() {
+		return array(
+			'name' => $this->getName(),
+			'label' => $this->getLabel(),
+			'invalid_message' => $this->getInvalidMessage(),
+			'configuration_spec' => $this->getConfigurationSpecification()->toArray(),
+			'configuration' => $this->getConfiguration()->toArray(),
+		);
+	}
+	
+	public function jsonSerialize() {
+		return $this->toArray();
+	}
+	
+ }
diff --git a/Civi/FormProcessor/Validation/EmailValidator.php b/Civi/FormProcessor/Validation/EmailValidator.php
new file mode 100644
index 0000000..413ac49
--- /dev/null
+++ b/Civi/FormProcessor/Validation/EmailValidator.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org>
+ * @license http://www.gnu.org/licenses/agpl-3.0.html
+ */
+ 
+ namespace Civi\FormProcessor\Validation;
+ 
+ use \Civi\FormProcessor\Validation\AbstractValidator;
+ 
+ use CRM_FormProcessor_ExtensionUtil as E;
+ 
+ class EmailValidator extends AbstractValidator {
+ 	
+	const PATTERN_LOOSE = '/^.+\@\S+\.\S+$/';
+ 	
+	public function getLabel() {
+		return E::ts('E-mail');
+	}
+ 	
+	/**
+	 * Returns the name of the validator.
+	 * 
+	 * @return string
+	 */
+	public function getName() {
+		return 'email';
+	}
+	
+	/**
+	 * Validate the input.
+	 * 
+	 * @param mixed $input
+	 * @return bool
+	 */
+	public function validate($input) {
+		$pattern = EmailValidator::PATTERN_LOOSE;
+		return preg_match($pattern, $input);
+	}
+		
+ }
diff --git a/Civi/FormProcessor/Validation/Factory.php b/Civi/FormProcessor/Validation/Factory.php
new file mode 100644
index 0000000..90a3d03
--- /dev/null
+++ b/Civi/FormProcessor/Validation/Factory.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @author Jaap Jansma (CiviCooP) <jaap.jansma@civicoop.org>
+ * @license http://www.gnu.org/licenses/agpl-3.0.html
+ */
+ 
+ namespace Civi\FormProcessor\Validation;
+ 
+ use \Civi\FormProcessor\Validation\AbstractValidator;
+ use \Civi\FormProcessor\Validation\EmailValidator;
+ 
+ use CRM_FormProcessor_ExtensionUtil as E;
+ 
+ class Factory {
+ 	
+	/**
+	 * @var array<AbstractValidator>
+	 */
+	protected $types = array();
+ 	
+	/**
+	 * @var Factory
+	 */
+	private static $singleton;
+	
+	private function __construct() {
+		$this->addValidator(new EmailValidator());
+	}
+	
+	/**
+	 * @return Factory
+	 */
+	public static function singleton(){
+		if (!self::$singleton) {
+			self::$singleton = new Factory();
+		}
+		return self::$singleton;
+	}
+	
+	/**
+	 * Adds a validator
+	 * 
+	 * @param AbstractFactory $validator
+	 * @return Factory
+	 */
+	public function addValidator(AbstractValidator $validator) {
+		$this->validators[$validator->getName()] = $validator;
+		return $this;
+	}
+	
+	/**
+	 * Returns a validator
+	 * 
+	 * @param string $name
+	 *   The name of the validator.
+	 * @return AbstractValidator
+	 */
+	public function getValidatorByName($name) {
+		return $this->validators[$name];
+	}
+	
+	/**
+	 * @return array<AbstractValidator>
+	 */
+	public function getValidators() {
+		return $this->validators;
+	}
+ }
\ No newline at end of file
diff --git a/ang/form_processor.ang.php b/ang/form_processor.ang.php
index 7507192..520547d 100644
--- a/ang/form_processor.ang.php
+++ b/ang/form_processor.ang.php
@@ -12,6 +12,7 @@ return array (
       'crmUi',
       'crmUtil',
       'ngRoute',
+      'ngSanitize',
       'dialogService',
       'ui.utils',
       'action_provider',
@@ -33,6 +34,7 @@ return array (
   'settings' => 
   array (
   	'inputTypes' => \Civi::service('form_processor_type_factory')->getTypeLabels(),
+  	'validators' => \Civi::service('form_processor_validation_factory')->getValidators(),
   	'actionTypes' => $action_provider->getActions(),
   ),
 );
diff --git a/ang/form_processor/ActionDialogCtrl.html b/ang/form_processor/ActionDialogCtrl.html
index f4af01c..59b2caa 100644
--- a/ang/form_processor/ActionDialogCtrl.html
+++ b/ang/form_processor/ActionDialogCtrl.html
@@ -3,7 +3,7 @@
     <div class="crm-group">
     	
     	<div crm-ui-field="{name: 'ActionForm.type', title: ts('Type')}">
-    		<div>{{model.action.type.title}}</div>
+    		<div>{{action.type.title}}</div>
   		</div>
     	
     	<div crm-ui-field="{name: 'ActionForm.title', title: ts('Title')}">
@@ -11,7 +11,7 @@
         crm-ui-id="addActionForm.title"
         type="text"
         name="title"
-        ng-model="model.action.title"
+        ng-model="action.title"
         class="big crm-form-text"
         required
         autofocus
@@ -19,13 +19,13 @@
 	    </div>
 	    
 	    <crm-ap-action-configuration 
-	    	configuration="model.action.configuration" 
-	    	action="model.action.type"
+	    	configuration="action.configuration" 
+	    	action="action.type"
 	    	fields="model.formProcessor.fields"
-	    	mapping="model.action.mapping"
+	    	mapping="action.mapping"
     	></crm-ap-action-configuration>
 
-			<button crm-icon="fa-check" ng-click="saveClick()" ng-disabled="addActionForm.$invalid">{{ts('Save and close')}}</button>
+			<button crm-icon="fa-check" ng-click="saveClick()" ng-disabled="ActionForm.$invalid">{{ts('Save and close')}}</button>
 			<button crm-icon="fa-times" ng-click="cancelClick()">{{ts('Cancel')}}</button>
 			
     </div>
diff --git a/ang/form_processor/ActionDialogCtrl.js b/ang/form_processor/ActionDialogCtrl.js
index af3660e..f56c60a 100644
--- a/ang/form_processor/ActionDialogCtrl.js
+++ b/ang/form_processor/ActionDialogCtrl.js
@@ -3,7 +3,10 @@
   angular.module('form_processor').controller('ActionDialogCtrl', function InputDialogCtrl($scope, dialogService) {
     $scope.ts = CRM.ts(null);
     
+    $scope.action = angular.copy($scope.model.action);
+    
     $scope.saveClick = function() {
+    	$scope.model.action = $scope.action;
     	dialogService.close('ActionDialog', $scope.model);
     };
 		
diff --git a/ang/form_processor/FormProcessorEditCtrl.html b/ang/form_processor/FormProcessorEditCtrl.html
index 9d12984..0d81dbf 100644
--- a/ang/form_processor/FormProcessorEditCtrl.html
+++ b/ang/form_processor/FormProcessorEditCtrl.html
@@ -8,6 +8,10 @@ Required vars: formProcessor
 
 <form name="editFormProcessorForm" unsaved-warning-form>
 <div class="crm-block crm-form-block crmFormProcessor">
+	
+	<div class="status status-warning" ng-show="editFormProcessorForm.$dirty">
+    {{ts('Form processor is not saved')}}
+  </div>
 
   <div ng-include="'~/form_processor/FormProcessorEditCtrl/Details.html'"></div>
   
@@ -16,9 +20,12 @@ Required vars: formProcessor
   <div ng-include="'~/form_processor/FormProcessorEditCtrl/ActionTable.html'"></div>
 
   <div class="crm-submit-buttons">
-    <button crm-icon="fa-check" ng-click="editFormProcessorForm.$setPristine(); save()" ng-disabled="editFormProcessorForm.$invalid || !isNameValid">
+    <button crm-icon="fa-check" ng-click="save(false);" ng-disabled="editFormProcessorForm.$invalid || !isNameValid">
       {{ts('Save')}}
     </button>
+    <button crm-icon="fa-check" ng-click="save(true);" ng-disabled="editFormProcessorForm.$invalid || !isNameValid">
+      {{ts('Save and go back')}}
+    </button>
     <button crm-icon="fa-times" ng-click="editFormProcessorForm.$setPristine(); goto('formProcessors')">
       {{ts('Cancel')}}
     </button>
diff --git a/ang/form_processor/FormProcessorEditCtrl.js b/ang/form_processor/FormProcessorEditCtrl.js
index 7f48fe7..6c47e6d 100644
--- a/ang/form_processor/FormProcessorEditCtrl.js
+++ b/ang/form_processor/FormProcessorEditCtrl.js
@@ -24,7 +24,7 @@
         	apiCalls: function($route, crmApi) {
         		var reqs = {};
             if ($route.current.params.id !== 'new') {
-              reqs.formProcessor = ['FormProcessor', 'getsingle', {
+              reqs.formProcessor = ['FormProcessorInstance', 'getsingle', {
                 id: $route.current.params.id
               }];
             }
@@ -35,7 +35,7 @@
     }
   );
   
-  angular.module('form_processor').controller('FormProcessorEditCtrl', function($scope, dialogService, crmApi, apiCalls, $q) {
+  angular.module('form_processor').controller('FormProcessorEditCtrl', function($scope, dialogService, crmApi, apiCalls, $q, $timeout) {
   	var ts = $scope.ts = CRM.ts(null);
 
     $scope.formProcessor = apiCalls.formProcessor; 
@@ -61,7 +61,7 @@
     
     $scope.$watch('formProcessor.name', function(newFormProcessorName, oldFrmProcessorName) {
     	// Watch for changes in the name field
-      crmApi('FormProcessor', 'validatename', {'name': newFormProcessorName,'id': $scope.formProcessor.id})
+      crmApi('FormProcessorInstance', 'validatename', {'name': newFormProcessorName,'id': $scope.formProcessor.id})
       .then(function(data) {
       	$scope.isNameValid = false;
       	if (data.is_valid) {
@@ -77,12 +77,19 @@
     	}
     }, true);
     
+    $timeout(function(){
+    	// The select boxes for input type and action type do not count for the @pristine state of the
+    	// form.
+  		$scope.editFormProcessorForm.formProcessorInputsForm.type.$pristine = false;
+  		$scope.editFormProcessorForm.formProcessorActionsForm.type.$pristine = false;
+		});
+    
     $scope.isValidName = function(name) {
     	return (!name) || name.match(/^[a-z0-9_]+$/) ? true : false;
     };
     
-    $scope.save = function() {
-      var result = crmApi('FormProcessor', 'create', $scope.formProcessor, true);
+    $scope.save = function(goBack) {
+      var result = crmApi('FormProcessorInstance', 'create', $scope.formProcessor, true);
       result.then(function(data) {
         if (data.is_error === 0 || data.is_error == '0') {
           $scope.formProcessor.id = data.id;  
@@ -96,10 +103,25 @@
           });
           
           angular.forEach($scope.formProcessor.inputs, function(input, key) {
-          	input.form_processor_id = $scope.formProcessor.id;
+          	if (input.id < 1) {
+          		delete input.id;
+          	}
+          	var type = angular.copy(input.type);
+          	input.form_processor_instance_id = $scope.formProcessor.id;
           	input.configuration = input.type.configuration; 
           	input.type = input.type.name;
-          	apiCalls.push(crmApi('FormProcessorInput', 'create', input, true));
+          	apiCalls.push(crmApi('FormProcessorInput', 'create', input, true).then(function (input_result){
+        			input.id = input_result['id'];
+        			input.type = type;
+        			angular.forEach(input.validators, function(validator, validator_index){
+        				validator.form_processor_input_id = input.id;
+        				var validator_type = angular.copy(validator.validator);
+        				validator.validator = validator.validator.name;
+        				crmApi('FormProcessorInputValidation', 'create', validator).then(function (validator_result) {
+        					validator.validator = validator_type;
+        				});
+        			});
+        		}));
           });
           
           angular.forEach($scope.deletedActions, function(action, key) {
@@ -115,12 +137,13 @@
           	if (action.id < 0) {
           		delete action.id;
           	}
-          	action.form_processor_id = $scope.formProcessor.id; 
+          	var action_type = angular.copy(action.type);
+          	action.form_processor_instance_id = $scope.formProcessor.id; 
           	action.type = action.type.name;
           	action.weight = actionWeight;
-          	console.log(action);
           	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;          		
           		}
@@ -143,12 +166,20 @@
 	       				}
 	       			});
 	       			if (actionChanged) {
-	       				crmApi('FormProcessorAction', 'create', action, true);	
+	       				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;
+	       				});
 	       			}
 	       		});
           });
           
-          window.location.href = '#/formprocessors';
+          $scope.editFormProcessorForm.$setPristine();
+          
+          if (goBack) {
+          	window.location.href = '#/formprocessors';
+          }
         }
       });
     };
@@ -159,6 +190,8 @@
     		$scope.formProcessor.inputs.splice(index, 1);
     		$scope.deletedInputs.push(input);
     	}
+    	
+    	$scope.editFormProcessorForm.$setDirty();
     };
     
      // Open a dialog for adding an input
@@ -172,6 +205,7 @@
       	'name': '',
   			'is_required': 0,
   			'default_value': '',
+  			'validators': []
     	};
       $scope.editInput(input);
     };
@@ -184,14 +218,17 @@
         height: 'auto',
         title: ts('Edit input')
       });
+      index = $scope.formProcessor.inputs.indexOf(input);
       var model = {
       	formProcessor: $scope.formProcessor,
       	input: input
       };
       dialogService.open('InputDialog', '~/form_processor/InputDialogCtrl.html', model, options)
-      .then(
-			function(data) {
-				if ($scope.new_id == data.input.id) {
+      .then(function(data) {
+      	$scope.editFormProcessorForm.$setDirty();
+      	if (index >= 0) {
+      		$scope.formProcessor.inputs[index] = data.input;
+      	} else if ($scope.new_id == data.input.id) {
 					$scope.formProcessor.inputs.push(data.input);
 					$scope.new_id --;
 				}
@@ -204,13 +241,15 @@
     		$scope.formProcessor.actions.splice(index, 1);
     		$scope.deletedActions.push(action);
     	}
+    	
+    	$scope.editFormProcessorForm.$setDirty();
     };
   
    // Open a dialog for adding an action
     $scope.addAction = function addAction(actionType) {
     	if (!actionType) {
     		return;
-    	}    	    	
+    	}    
       var action = {
     		id: $scope.new_id,
     		type: angular.copy(actionType),
@@ -231,14 +270,18 @@
       });
       
       $scope.updateFields($scope.formProcessor, action);
-      
+      index = $scope.formProcessor.inputs.indexOf(action);
       var model = {
       	formProcessor: $scope.formProcessor,
       	action: action
       };
       dialogService.open('ActionDialog', '~/form_processor/ActionDialogCtrl.html', model, options)
       .then(function(data) {
-      	if ($scope.new_id == data.action.id) {
+      	$scope.editFormProcessorForm.$setDirty();
+      	
+      	if (index >= 0) {
+      		$scope.formProcessor.actions[index] = data.action;
+      	} else if ($scope.new_id == data.action.id) {
 					$scope.formProcessor.actions.push(data.action);
 					$scope.new_id --;
 				}
diff --git a/ang/form_processor/FormProcessorEditCtrl/Details.html b/ang/form_processor/FormProcessorEditCtrl/Details.html
index ae76bf1..f78991d 100644
--- a/ang/form_processor/FormProcessorEditCtrl/Details.html
+++ b/ang/form_processor/FormProcessorEditCtrl/Details.html
@@ -23,7 +23,7 @@
       <a crm-ui-lock binding="locks.name"></a>
 
       <div ng-show="!isValidName(formProcessor.name) || !isNameValid">
-        <em>{{ts('WARNING: The form processor name includes deprecated characters or is already in use by another form processor.')}}</em>
+        <em>{{ts('WARNING: The form processor name includes invalid characters or is already in use by another form processor.')}}</em>
       </div>
       <div ng-show="formProcessor.id && !locks.name">
         <em>{{ts('WARNING: If any external files or programs reference the old "Name", then they must be updated manually.')}}</em>
diff --git a/ang/form_processor/FormProcessorEditCtrl/InputTable.html b/ang/form_processor/FormProcessorEditCtrl/InputTable.html
index b783247..9a5567c 100644
--- a/ang/form_processor/FormProcessorEditCtrl/InputTable.html
+++ b/ang/form_processor/FormProcessorEditCtrl/InputTable.html
@@ -11,6 +11,7 @@ Required vars: formProcessor
 	    <th>{{ts('Type')}}</th>
 	    <th>{{ts('Is required')}}</th>
 	    <th>{{ts('Default value')}}</th>
+	    <th>{{ts('Validation')}}</th>
 	    <th></th>
 	  </tr>
   </thead>
@@ -20,6 +21,13 @@ Required vars: formProcessor
 	    <td>{{input.type.label}}</td>
 	    <td>{{input.is_required == 1 ? ts('Yes') : ts('No')}}</td>
 	    <td>{{input.default_value}}</td>
+	    <td>
+	    	<ul>
+		    	<li ng-repeat="validator in input.validators | orderBy:'validator.label'">
+		    		{{validator.validator.label}}
+		    	</li>
+	    	</ul>
+	    </td>
 	    <td>
 	      <a crm-icon="fa-edit" class="crm-hover-button" ng-click="editInput(input)" title="{{ts('Edit')}}">{{ts('Edit')}}</a>
 	      <a crm-icon="fa-trash" class="crm-hover-button" ng-click="removeInput(input)" title="{{ts('Remove')}}">{{ts('Remove')}}</a>
diff --git a/ang/form_processor/FormProcessorListCtrl.js b/ang/form_processor/FormProcessorListCtrl.js
index 4f09122..066e7d4 100644
--- a/ang/form_processor/FormProcessorListCtrl.js
+++ b/ang/form_processor/FormProcessorListCtrl.js
@@ -9,7 +9,7 @@
         // under "resolve".
         resolve: {
           formProcessors: function($route, crmApi) {
-            return crmApi('FormProcessor', 'get', {options: {limit: 0}});
+            return crmApi('FormProcessorInstance', 'get', {options: {limit: 0}});
           }
         }
       });
@@ -22,14 +22,14 @@
     $scope.formProcessors = formProcessors.values;
     $scope.toggleFormProcessor = function (formProcessor) {
       formProcessor.is_active = (formProcessor.is_active == '1') ? '0' : '1';
-      crmApi('FormProcessor', 'create', formProcessor, true)
+      crmApi('FormProcessorInstance', 'create', formProcessor, true)
         .catch(function (data) {
           formProcessor.is_active = (formProcessor.is_active == '1') ? '0' : '1'; // revert
           $scope.$digest();
         });
     };
     $scope.deleteFormProcessor = function (formProcessor) {
-      crmApi('FormProcessor', 'delete', {id: formProcessor.id}, {
+      crmApi('FormProcessorInstance', 'delete', {id: formProcessor.id}, {
         error: function (data) {
           CRM.alert(data.error_message, ts('Error'), 'error');
         }
diff --git a/ang/form_processor/InputDialogCtrl.html b/ang/form_processor/InputDialogCtrl.html
index e68e365..6ca5aef 100644
--- a/ang/form_processor/InputDialogCtrl.html
+++ b/ang/form_processor/InputDialogCtrl.html
@@ -3,7 +3,7 @@
     <div class="crm-group">
     	
     	<div crm-ui-field="{name: 'formProcessorDetailForm.type', title: ts('Type')}">
-    		<div>{{model.input.type.label}}</div>
+    		<div>{{input.type.label}}</div>
   		</div>
     	
     	<div crm-ui-field="{name: 'InputForm.name', title: ts('Name')}">
@@ -11,7 +11,7 @@
         crm-ui-id="InputForm.name"
         type="text"
         name="name"
-        ng-model="model.input.name"
+        ng-model="input.name"
         class="big crm-form-text"
         required
         autofocus
@@ -25,7 +25,7 @@
 	    </div>
 	
 			<div crm-ui-field="{title: ts('Is required?')}">
-	      <input name="is_required" type="checkbox" ng-model="model.input.is_required" ng-true-value="'1'" ng-false-value="'0'"/>
+	      <input name="is_required" type="checkbox" ng-model="input.is_required" ng-true-value="'1'" ng-false-value="'0'"/>
 	 		</div>
 	 		
 	 		<div crm-ui-field="{name: 'InputForm.default_value', title: ts('Default value')}">
@@ -33,17 +33,47 @@
 	        crm-ui-id="InputForm.default_value"
 	        type="text"
 	        name="title"
-	        ng-model="model.input.default_value"
+	        ng-model="input.default_value"
 	        class="big crm-form-text"
 	        />
 	    </div>
 	    
 	    <crm-form-processor-type-configuration 
-	    	type=model.input.type
+	    	type=input.type
     	></crm-form-processor-type-configuration>
-
-			<button crm-icon="fa-check" ng-click="saveClick()">{{ts('Save and Close')}}</button>
-			<button crm-icon="fa-times" ng-click="cancelClick()">{{ts('Cancel')}}</button>
+    	
+    	<div class="crm-block" ng-form="InputForm" crm-ui-id-scope>
+    		<div class="crm-group">
+	    		<h2>{{ts('Validation')}}</h2>
+    			<select
+	        		crm-ui-id="InputForm.newValidator"
+	        		name="type"
+	        		ui-jq="select2"
+	        		ui-options="{dropdownAutoWidth : true, allowClear: true}"
+	        		ng-model="newValidator"
+	        		ng-options="newValidator.label for newValidator in validators">
+        		<option value="">{{ts('- Select validation -')}}</option>
+      		</select>
+      		<button crm-icon="fa-check" ng-click="addValidator(newValidator)">{{ts('Add validation rule')}}</button>
+      </div>
+     </div>
+     
+     <div ng-repeat="validator in input.validators | orderBy:'validator.label'" class="crm-block">
+     		<div class="crm-group">
+     		<h3>{{validator.validator.label}}</h3>
+     		<p>
+     			<a crm-icon="fa-trash" class="crm-hover-button" ng-click="removeValidator(validator)" title="{{ts('Remove')}}">{{ts('Remove')}}</a>
+     		</p>
+     		<crm-form-processor-validation-configuration validator=validator></crm-form-processor-validation-configuration>
+     		</div>
+     </div>
+    	
+			<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix">
+				<div class="ui-dialog-buttonset">
+					<button crm-icon="fa-check" ng-click="saveClick()" ng-disabled="InputForm.$invalid">{{ts('Save and Close')}}</button>
+					<button crm-icon="fa-times" ng-click="cancelClick()">{{ts('Cancel')}}</button>
+				</div>
+			</div>
 			
     </div>
   </div>
diff --git a/ang/form_processor/InputDialogCtrl.js b/ang/form_processor/InputDialogCtrl.js
index b24ab2b..99c158c 100644
--- a/ang/form_processor/InputDialogCtrl.js
+++ b/ang/form_processor/InputDialogCtrl.js
@@ -2,9 +2,10 @@
 
   angular.module('form_processor').controller('InputDialogCtrl', function InputDialogCtrl($scope, dialogService) {
     $scope.ts = CRM.ts(null);
-    
+    $scope.input = angular.copy($scope.model.input);
     $scope.invalidName = false;
     $scope.nameExists = false;
+  	$scope.validators = CRM.form_processor.validators;
     
     $scope.$watch('model.input.name', function(newName, oldName) {
     	$scope.invalidName = newName && !newName.match(/^[a-z0-9_]+$/) ? true : false;
@@ -20,13 +21,32 @@
     }, true);
     
     $scope.saveClick = function() {
+    	$scope.model.input = $scope.input;
     	dialogService.close('InputDialog', $scope.model);
     };
 		
-		$scope.cancelClick = function() {
+		$scope.cancelClick = function() {			
     	dialogService.cancel('InputDialog');
     };
     
+    $scope.addValidator = function(newValidator) {
+    	if (!newValidator) {
+    		return;
+    	}
+    	var validator = {
+    		validator: angular.copy(newValidator),
+    		configuration: {}
+    	}; 
+    	$scope.input.validators.push(validator);
+    };
+    
+    $scope.removeValidator = function(validator) {
+    	var index = $scope.input.validators.indexOf(validator);
+    	if (index >= 0) {
+    		$scope.input.validators.splice(index, 1);
+    	}
+    };
+    
   });
 
 })(angular, CRM.$, CRM._);
diff --git a/ang/form_processor/crmFormProcessorValidationConfiguration.html b/ang/form_processor/crmFormProcessorValidationConfiguration.html
new file mode 100644
index 0000000..47e6a24
--- /dev/null
+++ b/ang/form_processor/crmFormProcessorValidationConfiguration.html
@@ -0,0 +1,15 @@
+<ng-repeat ng-repeat="spec in validator.configuration_spec">
+<div crm-ui-field="{name: 'spec.name', title: spec.title, required: spec.required}">
+      <input
+        type="text"
+        name="{{spec.name}}"
+        ng-model="validator.configuration[spec.name]"
+        class="big crm-form-text"
+        ng-required="spec.required"
+        />
+			
+			<div class="description" ng-if="spec.description" ng-bind-html="spec.description"></div>
+
+</ng-repeat>
+
+
diff --git a/ang/form_processor/crmFormProcessorValidationConfiguration.js b/ang/form_processor/crmFormProcessorValidationConfiguration.js
new file mode 100644
index 0000000..d0a6aec
--- /dev/null
+++ b/ang/form_processor/crmFormProcessorValidationConfiguration.js
@@ -0,0 +1,16 @@
+(function(angular, $, _) {
+	
+	angular.module('form_processor').directive('crmFormProcessorValidationConfiguration', function() {
+	  return {
+	    restrict: 'E',
+	    scope: {
+	      validator: '=validator',
+	    },
+	    templateUrl: '~/form_processor/crmFormProcessorValidationConfiguration.html',
+	    link: function($scope, $el, $attr) {
+      	$scope.ts = CRM.ts(null);
+      }
+	  };
+	});
+	
+})(angular, CRM.$, CRM._);
\ No newline at end of file
diff --git a/api/v3/FormProcessorAction/Create.php b/api/v3/FormProcessorAction/Create.php
index 17abf81..7fb16e9 100644
--- a/api/v3/FormProcessorAction/Create.php
+++ b/api/v3/FormProcessorAction/Create.php
@@ -16,11 +16,11 @@ function _civicrm_api3_form_processor_action_create_spec(&$spec) {
 		'type' => CRM_Utils_Type::T_INT,
 		'api_required' => false
 	);
-	$spec['form_processor_id'] = array(
-		'title' => E::ts('Form Processor ID'),
+	$spec['form_processor_instance_id'] = array(
+		'title' => E::ts('Form Processor Instance ID'),
 		'type' => CRM_Utils_Type::T_INT,
 		'api_required' => true,
-		'FKApiName' => 'FormProcessor',
+		'FKApiName' => 'FormProcessorInstance',
 	);
 	$spec['weight'] = array(
 		'title' => E::ts('Weight'),
diff --git a/api/v3/FormProcessorInput/Create.php b/api/v3/FormProcessorInput/Create.php
index b568d4f..920445c 100644
--- a/api/v3/FormProcessorInput/Create.php
+++ b/api/v3/FormProcessorInput/Create.php
@@ -16,11 +16,11 @@ function _civicrm_api3_form_processor_input_create_spec(&$spec) {
 		'type' => CRM_Utils_Type::T_INT,
 		'api_required' => false
 	);
-	$spec['form_processor_id'] = array(
-		'title' => E::ts('Form Processor ID'),
+	$spec['form_processor_instance_id'] = array(
+		'title' => E::ts('Form Processor Instance ID'),
 		'type' => CRM_Utils_Type::T_INT,
 		'api_required' => true,
-		'FKApiName' => 'FormProcessor',
+		'FKApiName' => 'FormProcessorInstance',
 	);
 	$spec['name'] = array(
 		'title' => E::ts('Name'),
diff --git a/api/v3/FormProcessorInput/Get.php b/api/v3/FormProcessorInput/Get.php
index 93ca91b..cf71647 100644
--- a/api/v3/FormProcessorInput/Get.php
+++ b/api/v3/FormProcessorInput/Get.php
@@ -12,6 +12,9 @@ function civicrm_api3_form_processor_input_get($params) {
   $returnValues = CRM_FormProcessor_BAO_FormProcessorInput::getValues($params);
 	foreach($returnValues as $index => $input) {
 		$returnValues[$index]['type'] = $input['type']->toArray();
+		foreach($input['validators'] as $key => $validator) {
+			$returnValues[$index]['validators'][$key]['validator'] = $validator['validator']->toArray();
+		}
 	}
   return civicrm_api3_create_success($returnValues, $params, 'FormProcessorInput', 'Get');
 }
diff --git a/api/v3/FormProcessorInputValidation/Create.php b/api/v3/FormProcessorInputValidation/Create.php
new file mode 100644
index 0000000..646babb
--- /dev/null
+++ b/api/v3/FormProcessorInputValidation/Create.php
@@ -0,0 +1,52 @@
+<?php
+
+use CRM_FormProcessor_ExtensionUtil as E;
+
+/**
+ * FormProcessorInputValidation.Create 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_input_validation_create_spec(&$spec) {
+  $spec['id'] = array(
+		'title' => E::ts('ID'),
+		'type' => CRM_Utils_Type::T_INT,
+		'api_required' => false
+	);
+	$spec['form_processor_input_id'] = array(
+		'title' => E::ts('Form Processor Input ID'),
+		'type' => CRM_Utils_Type::T_INT,
+		'api_required' => true,
+		'FKApiName' => 'FormProcessorInput',
+	);
+	$spec['validator'] = array(
+		'title' => E::ts('Validator'),
+		'type' => CRM_Utils_Type::T_STRING,
+		'api_required' => true
+	);
+	$spec['configuration'] = array(
+		'title' => E::ts('Configuration'),
+		'type' => CRM_Utils_Type::T_TEXT,
+		'api_required' => false
+	);
+}
+
+/**
+ * FormProcessorInputValidation.Create API
+ *
+ * @param array $params
+ * @return array API result descriptor
+ * @see civicrm_api3_create_success
+ * @see civicrm_api3_create_error
+ *
+ *
+ */
+function civicrm_api3_form_processor_input_validation_create($params) {
+  $returnValue = CRM_FormProcessor_BAO_FormProcessorInputValidation::add($params);
+	$returnValues[$returnValue['id']] = $returnValue;
+  return civicrm_api3_create_success($returnValues, $params, 'FormProcessorInputValidation', 'Create');
+}
+
diff --git a/api/v3/FormProcessorInputValidation/Delete.php b/api/v3/FormProcessorInputValidation/Delete.php
new file mode 100644
index 0000000..546a75e
--- /dev/null
+++ b/api/v3/FormProcessorInputValidation/Delete.php
@@ -0,0 +1,37 @@
+<?php
+
+use CRM_FormProcessor_ExtensionUtil as E;
+
+/**
+ * FormProcessorInputValidation.Delete 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/CRMDOC/API+Architecture+Standards
+ */
+function _civicrm_api3_form_processor_input_validation_Delete_spec(&$spec) {
+  $spec['id'] = array(
+		'title' => E::ts('ID'),
+		'type' => CRM_Utils_Type::T_INT,
+		'api_required' => true
+	);
+}
+
+/**
+ * FormProcessorInputValidation.Delete 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_input_validation_Delete($params) {
+  if (!array_key_exists('id', $params) || empty($params['id'])) {
+    throw new API_Exception('Parameter id is mandatory and can not be empty in ' . __METHOD__, 0010);
+  } else {
+    return civicrm_api3_create_success(CRM_FormProcessor_BAO_FormProcessorInputValidation::deleteWithId($params['id']), $params, 'FormProcessorInputValidation', 'Delete');
+  }
+}
+
diff --git a/api/v3/FormProcessorInputValidation/Get.php b/api/v3/FormProcessorInputValidation/Get.php
new file mode 100644
index 0000000..75a8386
--- /dev/null
+++ b/api/v3/FormProcessorInputValidation/Get.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * FormProcessorInputValidation.Get 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_input_validation_get($params) {
+  $returnValues = CRM_FormProcessor_BAO_FormProcessorInputValidation::getValues($params);
+	foreach($returnValues as $index => $input) {
+		$returnValues[$index]['validator'] = $input['validator']->toArray();
+	}
+  return civicrm_api3_create_success($returnValues, $params, 'FormProcessorInputValidation', 'Get');
+}
+
+/**
+ * FormProcessorInputValidation.Get 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_input_validation_get_spec(&$spec) {
+	$fields = CRM_FormProcessor_BAO_FormProcessorInputValidation::fields();
+	foreach($fields as $fieldname => $field) {
+		$spec[$fieldname] = $field;
+	}
+}
+
diff --git a/api/v3/FormProcessor/Create.php b/api/v3/FormProcessorInstance/Create.php
similarity index 86%
rename from api/v3/FormProcessor/Create.php
rename to api/v3/FormProcessorInstance/Create.php
index 181fee3..0be79ee 100644
--- a/api/v3/FormProcessor/Create.php
+++ b/api/v3/FormProcessorInstance/Create.php
@@ -10,7 +10,7 @@ use CRM_FormProcessor_ExtensionUtil as E;
  * @return void
  * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
  */
-function _civicrm_api3_form_processor_create_spec(&$spec) {
+function _civicrm_api3_form_processor_instance_create_spec(&$spec) {
   $spec['id'] = array(
 		'title' => E::ts('ID'),
 		'type' => CRM_Utils_Type::T_INT,
@@ -49,9 +49,9 @@ function _civicrm_api3_form_processor_create_spec(&$spec) {
  *
  *
  */
-function civicrm_api3_form_processor_create($params) {
+function civicrm_api3_form_processor_instance_create($params) {
   if (!isset($params['id']) && empty($params['title'])) {
-    return civicrm_api3_create_error('Title can not be empty when adding a new FormProcessor');
+    return civicrm_api3_create_error('Title can not be empty when adding a new FormProcessorInstance');
   }
 
   /*
@@ -66,8 +66,8 @@ function civicrm_api3_form_processor_create($params) {
     $params['created_date'] = date('Ymd');
     $params['created_user_id'] = $userId;
   }
-  $returnValue = CRM_FormProcessor_BAO_FormProcessor::add($params);
+  $returnValue = CRM_FormProcessor_BAO_FormProcessorInstance::add($params);
 	$returnValues[$returnValue['id']] = $returnValue;
-  return civicrm_api3_create_success($returnValues, $params, 'FormProcessor', 'Create');
+  return civicrm_api3_create_success($returnValues, $params, 'FormProcessorInstance', 'Create');
 }
 
diff --git a/api/v3/FormProcessor/Delete.php b/api/v3/FormProcessorInstance/Delete.php
similarity index 79%
rename from api/v3/FormProcessor/Delete.php
rename to api/v3/FormProcessorInstance/Delete.php
index ac41758..5b45e17 100644
--- a/api/v3/FormProcessor/Delete.php
+++ b/api/v3/FormProcessorInstance/Delete.php
@@ -10,7 +10,7 @@ use CRM_FormProcessor_ExtensionUtil as E;
  * @return void
  * @see http://wiki.civicrm.org/confluence/display/CRMDOC/API+Architecture+Standards
  */
-function _civicrm_api3_form_processor_Delete_spec(&$spec) {
+function _civicrm_api3_form_processor_instance_Delete_spec(&$spec) {
   $spec['id'] = array(
 		'title' => E::ts('ID'),
 		'type' => CRM_Utils_Type::T_INT,
@@ -27,11 +27,11 @@ function _civicrm_api3_form_processor_Delete_spec(&$spec) {
  * @see civicrm_api3_create_error
  * @throws API_Exception
  */
-function civicrm_api3_form_processor_Delete($params) {
+function civicrm_api3_form_processor_instance_Delete($params) {
   if (!array_key_exists('id', $params) || empty($params['id'])) {
     throw new API_Exception('Parameter id is mandatory and can not be empty in ' . __METHOD__, 0010);
   } else {
-    return civicrm_api3_create_success(CRM_FormProcessor_BAO_FormProcessor::deleteWithId($params['id']), $params, 'FormProcessor', 'Delete');
+    return civicrm_api3_create_success(CRM_FormProcessor_BAO_FormProcessorInstance::deleteWithId($params['id']), $params, 'FormProcessorInstance', 'Delete');
   }
 }
 
diff --git a/api/v3/FormProcessor/Get.php b/api/v3/FormProcessorInstance/Get.php
similarity index 66%
rename from api/v3/FormProcessor/Get.php
rename to api/v3/FormProcessorInstance/Get.php
index 589d0b8..2de109f 100644
--- a/api/v3/FormProcessor/Get.php
+++ b/api/v3/FormProcessorInstance/Get.php
@@ -8,17 +8,20 @@
  * @see civicrm_api3_create_error
  * @throws API_Exception
  */
-function civicrm_api3_form_processor_get($params) {
-  $returnValues = CRM_FormProcessor_BAO_FormProcessor::getValues($params);
+function civicrm_api3_form_processor_instance_get($params) {
+  $returnValues = CRM_FormProcessor_BAO_FormProcessorInstance::getValues($params);
 	foreach($returnValues as $index => $formProcessor) {
 		foreach($formProcessor['inputs'] as $key => $input) {
 			$returnValues[$index]['inputs'][$key]['type'] = $input['type']->toArray();
+			foreach($input['validators'] as $validator_key => $validator) {
+				$returnValues[$index]['inputs'][$key]['validators'][$validator_key]['validator'] = $validator['validator']->toArray();
+			}
 		}
 		foreach($formProcessor['actions'] as $key => $action) {
 			$returnValues[$index]['actions'][$key]['type'] = $action['type']->toArray();
 		}
 	}
-  return civicrm_api3_create_success($returnValues, $params, 'FormProcessor', 'Get');
+  return civicrm_api3_create_success($returnValues, $params, 'FormProcessorInstance', 'Get');
 }
 
 /**
@@ -29,8 +32,8 @@ function civicrm_api3_form_processor_get($params) {
  * @return void
  * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
  */
-function _civicrm_api3_form_processor_get_spec(&$spec) {
-	$fields = CRM_FormProcessor_BAO_FormProcessor::fields();
+function _civicrm_api3_form_processor_instance_get_spec(&$spec) {
+	$fields = CRM_FormProcessor_BAO_FormProcessorInstance::fields();
 	foreach($fields as $fieldname => $field) {
 		$spec[$fieldname] = $field;
 	}
diff --git a/api/v3/FormProcessor/Validatename.php b/api/v3/FormProcessorInstance/Validatename.php
similarity index 84%
rename from api/v3/FormProcessor/Validatename.php
rename to api/v3/FormProcessorInstance/Validatename.php
index f21098d..69b6a5c 100644
--- a/api/v3/FormProcessor/Validatename.php
+++ b/api/v3/FormProcessorInstance/Validatename.php
@@ -11,11 +11,11 @@ use CRM_FormProcessor_ExtensionUtil as E;
  * @see civicrm_api3_create_error
  * @throws API_Exception
  */
-function civicrm_api3_form_processor_validatename($params) {
+function civicrm_api3_form_processor_instance_validatename($params) {
 	$name = $params['name'];
 	$id = isset($params['id']) ? $params['id'] : null;
   $returnValues['name'] = $name;
-  $returnValues['is_valid'] = CRM_FormProcessor_BAO_FormProcessor::isNameValid($name, $id);
+  $returnValues['is_valid'] = CRM_FormProcessor_BAO_FormProcessorInstance::isNameValid($name, $id);
   return $returnValues;
 }
 
@@ -27,7 +27,7 @@ function civicrm_api3_form_processor_validatename($params) {
  * @return void
  * @see http://wiki.civicrm.org/confluence/display/CRM/API+Architecture+Standards
  */
-function _civicrm_api3_form_processor_validatename_spec(&$spec) {
+function _civicrm_api3_form_processor_instance_validatename_spec(&$spec) {
 	$spec['id'] = array(
 		'title' => E::ts('ID'),
 		'type' => CRM_Utils_Type::T_INT,
diff --git a/form_processor.php b/form_processor.php
index 8380658..13845c3 100644
--- a/form_processor.php
+++ b/form_processor.php
@@ -14,6 +14,9 @@ use \Symfony\Component\DependencyInjection\Definition;
 function form_processor_civicrm_container(ContainerBuilder $container) {
 	// Register the TypeFactory
 	$container->setDefinition('form_processor_type_factory', new Definition('Civi\FormProcessor\Type\Factory'));
+	$validationFactoryDefinition = new Definition('Civi\FormProcessor\Validation\Factory');
+	$validationFactoryDefinition->setFactory(array('Civi\FormProcessor\Validation\Factory', 'singleton')); 
+	$container->setDefinition('form_processor_validation_factory', $validationFactoryDefinition);
 	
 	
 	// Register our API Provider.
@@ -33,17 +36,21 @@ function form_processor_civicrm_container(ContainerBuilder $container) {
  * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_alterAPIPermissions/
  */
 function form_processor_civicrm_alterAPIPermissions($entity, $action, &$params, &$permissions) {
-		$permissions['form_processor']['get'] = array('administer CiviCRM');
-		$permissions['form_processor']['create'] = array('administer CiviCRM');
-		$permissions['form_processor']['delete'] = array('administer CiviCRM');
+		$permissions['form_processor_instance']['get'] = array('administer CiviCRM');
+		$permissions['form_processor_instance']['create'] = array('administer CiviCRM');
+		$permissions['form_processor_instance']['delete'] = array('administer CiviCRM');
 		
 		$permissions['form_processor_input']['get'] = array('administer CiviCRM');
 		$permissions['form_processor_input']['create'] = array('administer CiviCRM');
 		$permissions['form_processor_input']['delete'] = array('administer CiviCRM');
 		
-		$permissions['form_processor_delete']['get'] = array('administer CiviCRM');
-		$permissions['form_processor_delete']['create'] = array('administer CiviCRM');
-		$permissions['form_processor_delete']['delete'] = array('administer CiviCRM');
+		$permissions['form_processor_input_validation']['get'] = array('administer CiviCRM');
+		$permissions['form_processor_input_validation']['create'] = array('administer CiviCRM');
+		$permissions['form_processor_input_validation']['delete'] = array('administer CiviCRM');
+		
+		$permissions['form_processor_action']['get'] = array('administer CiviCRM');
+		$permissions['form_processor_action']['create'] = array('administer CiviCRM');
+		$permissions['form_processor_action']['delete'] = array('administer CiviCRM');
 }
 
 /**
diff --git a/sql/create_civicrm_form_processor.sql b/sql/create_civicrm_form_processor.sql
index 97ee856..8f0b9be 100644
--- a/sql/create_civicrm_form_processor.sql
+++ b/sql/create_civicrm_form_processor.sql
@@ -1,4 +1,4 @@
-CREATE TABLE IF NOT EXISTS `civicrm_form_processor` (
+CREATE TABLE IF NOT EXISTS `civicrm_form_processor_instance` (
   `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
   `name` VARCHAR(80) NULL,
   `title` VARCHAR(128) NULL,
@@ -14,19 +14,27 @@ CREATE TABLE IF NOT EXISTS `civicrm_form_processor` (
 
 CREATE TABLE IF NOT EXISTS `civicrm_form_processor_input` (
   `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
-  `form_processor_id` INT UNSIGNED NOT NULL,
+  `form_processor_instance_id` INT UNSIGNED NOT NULL,
   `name` VARCHAR(80) NOT NULL,
   `type` VARCHAR(80) NOT NULL,
   `is_required` TINYINT NULL DEFAULT 0,
   `default_value` VARCHAR(256) NULL,
   `configuration` TEXT NULL,
   PRIMARY KEY (`id`),
-  UNIQUE INDEX `name_id_UNIQUE` (`id`, `name` ASC)
+  UNIQUE INDEX `name_id_UNIQUE` (`form_processor_instance_id`, `name` ASC)
+) ENGINE = InnoDB;
+
+CREATE TABLE IF NOT EXISTS `civicrm_form_processor_input_validation` (
+  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+  `form_processor_input_id` INT UNSIGNED NOT NULL,
+  `validator` VARCHAR(80) NOT NULL,
+  `configuration` TEXT NULL,
+  PRIMARY KEY (`id`)
 ) ENGINE = InnoDB;
 
 CREATE TABLE IF NOT EXISTS `civicrm_form_processor_action` (
   `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
-  `form_processor_id` INT UNSIGNED NOT NULL,
+  `form_processor_instance_id` INT UNSIGNED NOT NULL,
   `weight` INT UNSIGNED NOT NULL,
   `title` VARCHAR(80) NOT NULL,
   `type` VARCHAR(80) NOT NULL,
diff --git a/sql/uninstall.sql b/sql/uninstall.sql
index 084e627..0372944 100644
--- a/sql/uninstall.sql
+++ b/sql/uninstall.sql
@@ -1,2 +1,4 @@
-DROP TABLE `civicrm_form_processor_input`;
-DROP TABLE `civicrm_form_processor`;
+DROP TABLE IF EXISTS `civicrm_form_processor_input_validation`;
+DROP TABLE IF EXISTS `civicrm_form_processor_input`;
+DROP TABLE IF EXISTS `civicrm_form_processor_action`;
+DROP TABLE IF EXISTS `civicrm_form_processor_instance`;
diff --git a/tests/phpunit/api/v3/ApiPermissionTest.php b/tests/phpunit/api/v3/ApiPermissionTest.php
index 85a4240..6e98108 100644
--- a/tests/phpunit/api/v3/ApiPermissionTest.php
+++ b/tests/phpunit/api/v3/ApiPermissionTest.php
@@ -30,6 +30,8 @@ class Api_v3_ApiPermissionTest extends \PHPUnit_Framework_TestCase implements He
 	
 	protected $formProcessorInputId;
 	
+	protected $formProcessorInputValidationId;
+	
 	protected $formProcessorActionId;
 	
 	public function setUpHeadless() {
@@ -47,7 +49,7 @@ class Api_v3_ApiPermissionTest extends \PHPUnit_Framework_TestCase implements He
 		$config->userPermissionClass = new \CRM_Core_Permission_UnitTests();
     $config->userPermissionClass->permissions = array();
 		
-		$formProcessor = new \CRM_FormProcessor_BAO_FormProcessor();
+		$formProcessor = new \CRM_FormProcessor_BAO_FormProcessorInstance();
 		$formProcessor->name = 'name';
 		$formProcessor->title = 'Title';
 		$formProcessor->save();
@@ -56,14 +58,20 @@ class Api_v3_ApiPermissionTest extends \PHPUnit_Framework_TestCase implements He
 		$formProcessorInput = new \CRM_FormProcessor_BAO_FormProcessorInput();
 		$formProcessorInput->name = 'contact_id';
 		$formProcessorInput->type = 'Integer';
-		$formProcessorInput->form_processor_id = $this->formProcessorId; 
+		$formProcessorInput->form_processor_instance_id = $this->formProcessorId; 
 		$formProcessorInput->save();
 		$this->formProcessorInputId = $formProcessorInput->id;
 		
+		$formProcessorInputValidation = new \CRM_FormProcessor_BAO_FormProcessorInputValidation();
+		$formProcessorInputValidation->validator = 'email';
+		$formProcessorInputValidation->form_processor_input_id = $this->formProcessorInputId; 
+		$formProcessorInputValidation->save();
+		$this->formProcessorInputValidationId = $formProcessorInputValidation->id;
+		
 		$formProcessorAction = new \CRM_FormProcessor_BAO_FormProcessorAction();
 		$formProcessorAction->title = 'contact_id';
 		$formProcessorAction->type = 'AddToGroup';
-		$formProcessorAction->form_processor_id = $this->formProcessorId; 
+		$formProcessorAction->form_processor_instance_id = $this->formProcessorId; 
 		$formProcessorAction->weight = 0;
 		$formProcessorAction->save();
 		$this->formProcessorActionId = $formProcessorAction->id;
@@ -113,15 +121,15 @@ class Api_v3_ApiPermissionTest extends \PHPUnit_Framework_TestCase implements He
 	
 	public function apiCalls() {
 		return array(
-			'FormProcessor.Get' => array(
-				'entity' => 'FormProcessor',
+			'FormProcessorInstance.Get' => array(
+				'entity' => 'FormProcessorInstance',
 				'action' => 'get',
 				'params' => array(),
 				'id_params' => array('id' => 'formProcessorId'),
 				'expected' => array('count' => 1),
 			),
-			'FormProcessor.Create' => array(
-				'entity' => 'FormProcessor',
+			'FormProcessorInstance.Create' => array(
+				'entity' => 'FormProcessorInstance',
 				'action' => 'create',
 				'params' => array(
 					'title' => 'Test',
@@ -130,8 +138,8 @@ class Api_v3_ApiPermissionTest extends \PHPUnit_Framework_TestCase implements He
 			  'id_params' => array('id' => 'formProcessorId'),
 				'expected' => array('count' => 1),
 			),
-			'FormProcessor.Delete' => array(
-				'entity' => 'FormProcessor',
+			'FormProcessorInstance.Delete' => array(
+				'entity' => 'FormProcessorInstance',
 				'action' => 'delete',
 				'params' => array(
 				),
@@ -166,6 +174,33 @@ class Api_v3_ApiPermissionTest extends \PHPUnit_Framework_TestCase implements He
 				'expected' => array('count' => 0, 'is_error' => 0),
 			),
 			
+			// Test for the input validation api
+			'FormProcessorInputValidation.Get' => array(
+				'entity' => 'FormProcessorInputValidation',
+				'action' => 'get',
+				'params' => array(),
+				'id_params' => array('id' => 'formProcessorInputValidationId'),
+				'expected' => array('count' => 1),
+			),
+			'FormProcessorInputValidation.Create' => array(
+				'entity' => 'FormProcessorInputValidation',
+				'action' => 'create',
+				'params' => array(
+					'validator' => 'email',
+					'configuration' => array(),
+				),
+			  'id_params' => array('id' => 'formProcessorInputValidationId'),
+				'expected' => array('count' => 1),
+			),
+			'FormProcessorInputValidation.Delete' => array(
+				'entity' => 'FormProcessorInputValidation',
+				'action' => 'delete',
+				'params' => array(
+				),
+				'id_params' => array('id' => 'formProcessorInputValidationId'),
+				'expected' => array('count' => 0, 'is_error' => 0),
+			),
+			
 			// Test for the action api.
 			'FormProcessorAction.Get' => array(
 				'entity' => 'FormProcessorAction',
-- 
GitLab