diff --git a/civicrm.php b/civicrm.php
index 34b8947086efc9d599733cc549ff1cfb105cfdb4..f800fd0d07879dd7a8f5a747ec04280d5ee32107 100644
--- a/civicrm.php
+++ b/civicrm.php
@@ -2,7 +2,7 @@
 /*
 Plugin Name: CiviCRM
 Description: CiviCRM - Growing and Sustaining Relationships
-Version: 5.19.1
+Version: 5.19.2
 Author: CiviCRM LLC
 Author URI: https://civicrm.org/
 Plugin URI: https://wiki.civicrm.org/confluence/display/CRMDOC/Installing+CiviCRM+for+WordPress
@@ -137,17 +137,6 @@ if ( file_exists( CIVICRM_SETTINGS_PATH )  ) {
 // Prevent CiviCRM from rendering its own header
 define( 'CIVICRM_UF_HEAD', TRUE );
 
-/**
- * Setting this to 'true' will replace all mailing URLs calls to 'extern/url.php'
- * and 'extern/open.php' with their REST counterpart 'civicrm/v3/url' and 'civicrm/v3/open'.
- *
- * Use for test purposes, may affect mailing
- * performance, see Plugin->replace_tracking_urls() method.
- */
-if ( ! defined( 'CIVICRM_WP_REST_REPLACE_MAILING_TRACKING' ) ) {
-  define( 'CIVICRM_WP_REST_REPLACE_MAILING_TRACKING', false );
-}
-
 
 /**
  * Define CiviCRM_For_WordPress Class.
@@ -291,7 +280,6 @@ class CiviCRM_For_WordPress {
 
     // Set a one-time-only option
     add_option( 'civicrm_activation_in_progress', 'true' );
-    add_option('civicrm_setup_do_activation_redirect', true);
 
   }
 
@@ -322,11 +310,6 @@ class CiviCRM_For_WordPress {
 
     // Change option so this action never fires again
     update_option( 'civicrm_activation_in_progress', 'false' );
-    if (!isset($_GET['activate-multi'])) {
-      wp_redirect(admin_url("options-general.php?page=civicrm-install"));
-      exit;
-    }
-    update_option('civicrm_setup_do_activation_redirect', 'false');
 
   }
 
@@ -533,9 +516,6 @@ class CiviCRM_For_WordPress {
     include_once CIVICRM_PLUGIN_DIR . 'includes/civicrm.basepage.php';
     $this->basepage = new CiviCRM_For_WordPress_Basepage;
 
-    // Include REST API autoloader class
-    require_once( CIVICRM_PLUGIN_DIR . 'wp-rest/Autoloader.php' );
-
   }
 
 
@@ -648,12 +628,6 @@ class CiviCRM_For_WordPress {
     // Register hooks for clean URLs.
     $this->register_hooks_clean_urls();
 
-    // Set up REST API.
-    CiviCRM_WP_REST\Autoloader::add_source( $source_path = trailingslashit( CIVICRM_PLUGIN_DIR . 'wp-rest' ) );
-
-    // Init REST API.
-    new CiviCRM_WP_REST\Plugin;
-
   }
 
 
diff --git a/civicrm/CRM/ACL/Form/WordPress/Permissions.php b/civicrm/CRM/ACL/Form/WordPress/Permissions.php
index bad293c934c17f1d0567697ce7abc3b935b3501e..65191fb9793aed95be6ef1190364b5fd59b5a109 100644
--- a/civicrm/CRM/ACL/Form/WordPress/Permissions.php
+++ b/civicrm/CRM/ACL/Form/WordPress/Permissions.php
@@ -54,7 +54,7 @@ class CRM_ACL_Form_WordPress_Permissions extends CRM_Core_Form {
     }
     foreach ($wp_roles->role_names as $role => $name) {
       // Don't show the permissions options for administrator, as they have all permissions
-      if ( is_multisite() OR $role !== 'administrator') {
+      if ($role !== 'administrator') {
         $roleObj = $wp_roles->get_role($role);
         if (!empty($roleObj->capabilities)) {
           foreach ($roleObj->capabilities as $ckey => $cname) {
diff --git a/civicrm/CRM/Admin/Page/Persistent.php b/civicrm/CRM/Admin/Page/Persistent.php
index 4524da54449291f19c90d7832c21bbab97678fb2..4bf7c19aa6355982643ef3756f9b5062751be31d 100644
--- a/civicrm/CRM/Admin/Page/Persistent.php
+++ b/civicrm/CRM/Admin/Page/Persistent.php
@@ -121,7 +121,7 @@ class CRM_Admin_Page_Persistent extends CRM_Core_Page {
           'Persistent',
           $daoResult->id
         );
-        $values[$daoResult->id]['data'] = implode(',', unserialize($daoResult->data));
+        $values[$daoResult->id]['data'] = implode(',', CRM_Utils_String::unserialize($daoResult->data));
         $configCustomization[$daoResult->id] = $values[$daoResult->id];
       }
       if ($daoResult->is_config == 0) {
diff --git a/civicrm/CRM/Campaign/BAO/Query.php b/civicrm/CRM/Campaign/BAO/Query.php
index d44e12800994b1d5ac6a2132cf7780151b299935..e3841d629e5c2dd3afd00f18f7d4f0fb2c8ad8db 100644
--- a/civicrm/CRM/Campaign/BAO/Query.php
+++ b/civicrm/CRM/Campaign/BAO/Query.php
@@ -487,7 +487,7 @@ INNER JOIN  civicrm_custom_group grp on fld.custom_group_id = grp.id
         $recontactInterval = CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey',
           $surveyId, 'recontact_interval'
         );
-        $recontactInterval = unserialize($recontactInterval);
+        $recontactInterval = CRM_Utils_String::unserialize($recontactInterval);
         if ($surveyId &&
           is_array($recontactInterval) &&
           !empty($recontactInterval)
diff --git a/civicrm/CRM/Campaign/Form/Survey/Main.php b/civicrm/CRM/Campaign/Form/Survey/Main.php
index 68f074ec19d2dec9dddad2653fb4782ccd4a93e8..b1602e3c6ed76a44af46c79c5a6876774b6d7f2b 100644
--- a/civicrm/CRM/Campaign/Form/Survey/Main.php
+++ b/civicrm/CRM/Campaign/Form/Survey/Main.php
@@ -104,7 +104,7 @@ class CRM_Campaign_Form_Survey_Main extends CRM_Campaign_Form_Survey {
       if (!empty($defaults['result_id']) && !empty($defaults['recontact_interval'])) {
 
         $resultId = $defaults['result_id'];
-        $recontactInterval = unserialize($defaults['recontact_interval']);
+        $recontactInterval = CRM_Utils_String::unserialize($defaults['recontact_interval']);
 
         unset($defaults['recontact_interval']);
         $defaults['option_group_id'] = $resultId;
diff --git a/civicrm/CRM/Campaign/Page/AJAX.php b/civicrm/CRM/Campaign/Page/AJAX.php
index ecec01993619f5a8578543f4945c00d71b55140b..b3c0df44dfbc4873de51b479daffd52b3e580c37 100644
--- a/civicrm/CRM/Campaign/Page/AJAX.php
+++ b/civicrm/CRM/Campaign/Page/AJAX.php
@@ -122,7 +122,7 @@ class CRM_Campaign_Page_AJAX {
       $survey->result_id = $id;
       if ($survey->find(TRUE)) {
         if ($survey->recontact_interval) {
-          $recontactInterval = unserialize($survey->recontact_interval);
+          $recontactInterval = CRM_Utils_String::unserialize($survey->recontact_interval);
           foreach ($opValues as $opValId => $opVal) {
             if (is_numeric($recontactInterval[$opVal['label']])) {
               $opValues[$opValId]['interval'] = $recontactInterval[$opVal['label']];
diff --git a/civicrm/CRM/Contact/BAO/SavedSearch.php b/civicrm/CRM/Contact/BAO/SavedSearch.php
index 749a716a4938d7b3cfc6ba298d49ffd311afe2b8..59c11c58bae155baac890e1d3fc7d7658356f991 100644
--- a/civicrm/CRM/Contact/BAO/SavedSearch.php
+++ b/civicrm/CRM/Contact/BAO/SavedSearch.php
@@ -104,8 +104,8 @@ class CRM_Contact_BAO_SavedSearch extends CRM_Contact_DAO_SavedSearch {
     $fv = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_SavedSearch', $id, 'form_values');
     $result = NULL;
     if ($fv) {
-      // make sure u unserialize - since it's stored in serialized form
-      $result = unserialize($fv);
+      // make sure u CRM_Utils_String::unserialize - since it's stored in serialized form
+      $result = CRM_Utils_String::unserialize($fv);
     }
 
     $specialFields = ['contact_type', 'group', 'contact_tags', 'member_membership_type_id', 'member_status_id'];
@@ -328,7 +328,7 @@ LEFT JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id AND civicrm_
    * Given a saved search compute the clause and the tables and store it for future use.
    */
   public function buildClause() {
-    $fv = unserialize($this->form_values);
+    $fv = CRM_Utils_String::unserialize($this->form_values);
 
     if ($this->mapping_id) {
       $params = CRM_Core_BAO_Mapping::formattedFields($fv);
diff --git a/civicrm/CRM/Contact/Form/Search/Advanced.php b/civicrm/CRM/Contact/Form/Search/Advanced.php
index a3f46e68de23c863c65d0926baf6c35ba2b50314..d2d29634255658f18541c6655b31454902f753fb 100644
--- a/civicrm/CRM/Contact/Form/Search/Advanced.php
+++ b/civicrm/CRM/Contact/Form/Search/Advanced.php
@@ -201,7 +201,7 @@ class CRM_Contact_Form_Search_Advanced extends CRM_Contact_Form_Search {
       $this->_ssID = $this->get('ssID');
     }
 
-    $defaults = array_merge($this->_formValues, [
+    $defaults = array_merge((array) $this->_formValues, [
       'privacy_toggle' => 1,
       'operator' => 'AND',
     ], $defaults);
diff --git a/civicrm/CRM/Contact/Page/AJAX.php b/civicrm/CRM/Contact/Page/AJAX.php
index 3ac0326cf743eecfef9239b012f5737c38486c86..056b9369013172b005287237f85262aec327aa18 100644
--- a/civicrm/CRM/Contact/Page/AJAX.php
+++ b/civicrm/CRM/Contact/Page/AJAX.php
@@ -724,7 +724,7 @@ LIMIT {$offset}, {$rowCount}
       foreach ($_REQUEST['order'] as $orderInfo) {
         if (!empty($orderInfo['column'])) {
           $orderColumnNumber = $orderInfo['column'];
-          $dir = $orderInfo['dir'];
+          $dir = CRM_Utils_Type::escape($orderInfo['dir'], 'MysqlOrderByDirection', FALSE);
         }
       }
       $columnDetails = CRM_Utils_Array::value($orderColumnNumber, $_REQUEST['columns']);
diff --git a/civicrm/CRM/Contact/Page/SavedSearch.php b/civicrm/CRM/Contact/Page/SavedSearch.php
index a101e86283ba456be0876a872b229b670be1b95a..6d35a6205b19bb327bec4b6ba7c094f5258f2681 100644
--- a/civicrm/CRM/Contact/Page/SavedSearch.php
+++ b/civicrm/CRM/Contact/Page/SavedSearch.php
@@ -91,7 +91,7 @@ class CRM_Contact_Page_SavedSearch extends CRM_Core_Page {
           $row['description'] = $group->description;
 
           $row['id'] = $savedSearch->id;
-          $formValues = unserialize($savedSearch->form_values);
+          $formValues = CRM_Utils_String::unserialize($savedSearch->form_values);
           $query = new CRM_Contact_BAO_Query($formValues);
           $row['query_detail'] = $query->qill();
 
diff --git a/civicrm/CRM/Contribute/BAO/Query.php b/civicrm/CRM/Contribute/BAO/Query.php
index 8d5e16ee5cef803b621da0218b9f2dcb38d1b7fb..a47a89ef002442d8b4dd72719e53eaa4546e0f08 100644
--- a/civicrm/CRM/Contribute/BAO/Query.php
+++ b/civicrm/CRM/Contribute/BAO/Query.php
@@ -762,7 +762,7 @@ class CRM_Contribute_BAO_Query extends CRM_Core_BAO_Query {
       // @todo return this & fix query to do pseudoconstant thing.
       'contribution_status' => 1,
       'currency' => 1,
-      'cancel_date' => 1,
+      'contribution_cancel_date' => 1,
       'contribution_recur_id' => 1,
     ];
     if (self::isSiteHasProducts()) {
diff --git a/civicrm/CRM/Contribute/Form/AdditionalPayment.php b/civicrm/CRM/Contribute/Form/AdditionalPayment.php
index 68912379641bf641404bd51626f3316a07ddae5e..edd43400b91eab2e0b11692976580a06767eef1e 100644
--- a/civicrm/CRM/Contribute/Form/AdditionalPayment.php
+++ b/civicrm/CRM/Contribute/Form/AdditionalPayment.php
@@ -242,7 +242,7 @@ class CRM_Contribute_Form_AdditionalPayment extends CRM_Contribute_Form_Abstract
     $js = NULL;
     // render backoffice payment fields only on offline mode
     if (!$this->_mode) {
-      $js = ['onclick' => "return verify( );"];
+      $js = ['onclick' => 'return verify( );'];
 
       $this->add('select', 'payment_instrument_id',
         ts('Payment Method'),
@@ -258,11 +258,6 @@ class CRM_Contribute_Form_AdditionalPayment extends CRM_Contribute_Form_Abstract
         $attributes['fee_amount']
       );
       $this->addRule('fee_amount', ts('Please enter a valid monetary value for Fee Amount.'), 'money');
-
-      $this->add('text', 'net_amount', ts('Net Amount'),
-        $attributes['net_amount']
-      );
-      $this->addRule('net_amount', ts('Please enter a valid monetary value for Net Amount.'), 'money');
     }
 
     $buttonName = $this->_refund ? 'Record Refund' : 'Record Payment';
@@ -299,10 +294,7 @@ class CRM_Contribute_Form_AdditionalPayment extends CRM_Contribute_Form_Abstract
     if ($self->_paymentType == 'refund' && $fields['total_amount'] != abs($self->_refund)) {
       $errors['total_amount'] = ts('Refund amount must equal refund due amount.');
     }
-    $netAmt = (float) $fields['total_amount'] - (float) CRM_Utils_Array::value('fee_amount', $fields, 0);
-    if (!empty($fields['net_amount']) && $netAmt != $fields['net_amount']) {
-      $errors['net_amount'] = ts('Net amount should be equal to the difference between payment amount and fee amount.');
-    }
+
     if ($self->_paymentProcessor['id'] === 0 && empty($fields['payment_instrument_id'])) {
       $errors['payment_instrument_id'] = ts('Payment method is a required field');
     }
diff --git a/civicrm/CRM/Contribute/Form/ContributionPage/Amount.php b/civicrm/CRM/Contribute/Form/ContributionPage/Amount.php
index ef3d9071c586abdb8b42be1773e1081f681e6ae3..da176fa1eab2def9ac7b43cd3753a9d07701dca8 100644
--- a/civicrm/CRM/Contribute/Form/ContributionPage/Amount.php
+++ b/civicrm/CRM/Contribute/Form/ContributionPage/Amount.php
@@ -333,7 +333,7 @@ class CRM_Contribute_Form_ContributionPage_Amount extends CRM_Contribute_Form_Co
       }
 
       //CRM-16165, Don't allow reccuring contribution if membership block contain any renewable membership option
-      $membershipTypes = unserialize($membershipBlock->membership_types);
+      $membershipTypes = CRM_Utils_String::unserialize($membershipBlock->membership_types);
       if (!empty($fields['is_recur']) && !empty($membershipTypes)) {
         if (!$membershipBlock->is_separate_payment) {
           $errors['is_recur'] = ts('You need to enable Separate Membership Payment when online contribution page is configured for both Membership and Recurring Contribution.');
diff --git a/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php b/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php
index 9d08453b0c008efe76ea934f9e325caf1f5a970e..2a88bc06b5dfee0c1d228ba7512d6175c354a141 100644
--- a/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php
+++ b/civicrm/CRM/Contribute/Form/ContributionPage/Widget.php
@@ -55,8 +55,6 @@ class CRM_Contribute_Form_ContributionPage_Widget extends CRM_Contribute_Form_Co
 
     $this->assign('cpageId', $this->_id);
 
-    $this->assign('widgetExternUrl', CRM_Utils_System::externUrl('extern/widget', "cpageId={$this->_id}&widgetId={$this->_widget->id}&format=3"));
-
     $config = CRM_Core_Config::singleton();
     $title = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage',
       $this->_id,
diff --git a/civicrm/CRM/Contribute/Form/Search.php b/civicrm/CRM/Contribute/Form/Search.php
index 6fdb263afb1a390e9cd1203e4439d16a2a3c0a81..67b04cc55ae3970865a75febcfacea5143374e2c 100644
--- a/civicrm/CRM/Contribute/Form/Search.php
+++ b/civicrm/CRM/Contribute/Form/Search.php
@@ -84,9 +84,7 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
 
     $this->_done = FALSE;
 
-    $this->loadStandardSearchOptionsFromUrl();
-
-    $this->_formValues = $this->getFormValues();
+    parent::preProcess();
 
     //membership ID
     $memberShipId = CRM_Utils_Request::retrieve('memberId', 'Positive', $this);
@@ -98,15 +96,6 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
       $this->_formValues['contribution_participant_id'] = $participantId;
     }
 
-    if ($this->_force) {
-      // Search field metadata is normally added in buildForm but we are bypassing that in this flow
-      // (I've always found the flow kinda confusing & perhaps that is the problem but this mitigates)
-      $this->addSearchFieldMetadata(['Contribution' => CRM_Contribute_BAO_Query::getSearchFieldMetadata()]);
-      $this->addSearchFieldMetadata(['ContributionRecur' => CRM_Contribute_BAO_ContributionRecur::getContributionRecurSearchFieldMetadata()]);
-      $this->postProcess();
-      $this->set('force', 0);
-    }
-
     $sortID = NULL;
     if ($this->get(CRM_Utils_Sort::SORT_ID)) {
       $sortID = CRM_Utils_Sort::sortIDValue($this->get(CRM_Utils_Sort::SORT_ID),
@@ -282,10 +271,12 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
     $this->_done = TRUE;
 
     $this->setFormValues();
+    // @todo - stop changing formValues - respect submitted form values, change a working array.
     $this->fixFormValues();
 
     // We don't show test records in summaries or dashboards
     if (empty($this->_formValues['contribution_test']) && $this->_force && !empty($this->_context) && $this->_context == 'dashboard') {
+      // @todo - stop changing formValues - respect submitted form values, change a working array.
       $this->_formValues["contribution_test"] = 0;
     }
 
@@ -294,11 +285,11 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
       'contribution_amount_high',
     ] as $f) {
       if (isset($this->_formValues[$f])) {
+        // @todo - stop changing formValues - respect submitted form values, change a working array.
         $this->_formValues[$f] = CRM_Utils_Rule::cleanMoney($this->_formValues[$f]);
       }
     }
 
-    $config = CRM_Core_Config::singleton();
     if (!empty($_POST)) {
       $specialParams = [
         'financial_type_id',
@@ -311,10 +302,12 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
         'payment_instrument_id',
         'contribution_batch_id',
       ];
+      // @todo - stop changing formValues - respect submitted form values, change a working array.
       CRM_Contact_BAO_Query::processSpecialFormValue($this->_formValues, $specialParams);
 
       $tags = CRM_Utils_Array::value('contact_tags', $this->_formValues);
       if ($tags && !is_array($tags)) {
+        // @todo - stop changing formValues - respect submitted form values, change a working array.
         unset($this->_formValues['contact_tags']);
         $this->_formValues['contact_tags'][$tags] = 1;
       }
@@ -322,17 +315,20 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
       if ($tags && is_array($tags)) {
         unset($this->_formValues['contact_tags']);
         foreach ($tags as $notImportant => $tagID) {
+          // @todo - stop changing formValues - respect submitted form values, change a working array.
           $this->_formValues['contact_tags'][$tagID] = 1;
         }
       }
 
       $group = CRM_Utils_Array::value('group', $this->_formValues);
       if ($group && !is_array($group)) {
+        // @todo - stop changing formValues - respect submitted form values, change a working array.
         unset($this->_formValues['group']);
         $this->_formValues['group'][$group] = 1;
       }
 
       if ($group && is_array($group)) {
+        // @todo - stop changing formValues - respect submitted form values, change a working array.
         unset($this->_formValues['group']);
         foreach ($group as $groupID) {
           $this->_formValues['group'][$groupID] = 1;
@@ -340,11 +336,12 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
       }
     }
 
+    // @todo - stop changing formValues - respect submitted form values, change a working array.
     CRM_Core_BAO_CustomValue::fixCustomFieldValue($this->_formValues);
 
+    // @todo - stop changing formValues - respect submitted form values, change a working array.
     $this->_queryParams = CRM_Contact_BAO_Query::convertFormValues($this->_formValues);
 
-    $this->set('formValues', $this->_formValues);
     $this->set('queryParams', $this->_queryParams);
 
     $buttonName = $this->controller->getButtonName();
@@ -365,6 +362,7 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
       );
     }
 
+    // @todo - stop changing formValues - respect submitted form values, change a working array.
     $this->_queryParams = CRM_Contact_BAO_Query::convertFormValues($this->_formValues);
     $selector = new CRM_Contribute_Selector_Search($this->_queryParams,
       $this->_action,
@@ -462,8 +460,6 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
       $this->_formValues['contribution_page_id'] = $contribPageId;
     }
 
-    //give values to default.
-    $this->_defaults = $this->_formValues;
   }
 
   /**
@@ -475,4 +471,14 @@ class CRM_Contribute_Form_Search extends CRM_Core_Form_Search {
     return ts('Find Contributions');
   }
 
+  /**
+   * Set the metadata for the form.
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  protected function setSearchMetadata() {
+    $this->addSearchFieldMetadata(['Contribution' => CRM_Contribute_BAO_Query::getSearchFieldMetadata()]);
+    $this->addSearchFieldMetadata(['ContributionRecur' => CRM_Contribute_BAO_ContributionRecur::getContributionRecurSearchFieldMetadata()]);
+  }
+
 }
diff --git a/civicrm/CRM/Contribute/Selector/Search.php b/civicrm/CRM/Contribute/Selector/Search.php
index 1c2d23a3bc422c5019bf404320f8c0b8f9f3fa78..0c26fb6cbe0f0adc4fa01c10b2cb1e5008fd48e0 100644
--- a/civicrm/CRM/Contribute/Selector/Search.php
+++ b/civicrm/CRM/Contribute/Selector/Search.php
@@ -66,7 +66,7 @@ class CRM_Contribute_Selector_Search extends CRM_Core_Selector_Base implements C
     'thankyou_date',
     'contribution_status_id',
     'contribution_status',
-    'cancel_date',
+    'contribution_cancel_date',
     'product_name',
     'is_test',
     'contribution_recur_id',
diff --git a/civicrm/CRM/Core/BAO/Dashboard.php b/civicrm/CRM/Core/BAO/Dashboard.php
index a94154d178f63da4bea9d3aca6b3d5b4df798900..2c89838c40ba87704ecba71db6d9e47488704c43 100644
--- a/civicrm/CRM/Core/BAO/Dashboard.php
+++ b/civicrm/CRM/Core/BAO/Dashboard.php
@@ -357,39 +357,13 @@ class CRM_Core_BAO_Dashboard extends CRM_Core_DAO_Dashboard {
       }
     }
 
-    // Find dashlets in this domain.
-    $domainDashlets = civicrm_api3('Dashboard', 'get', [
-      'return' => array('id'),
-      'domain_id' => CRM_Core_Config::domainID(),
-      'options' => ['limit' => 0],
-    ]);
-
-    // Get the array of IDs.
-    $domainDashletIDs = [];
-    if ($domainDashlets['is_error'] == 0) {
-      $domainDashletIDs = CRM_Utils_Array::collect('id', $domainDashlets['values']);
-    }
-
-    // Restrict query to Dashlets in this domain.
-    $domainDashletClause = !empty($domainDashletIDs) ? "dashboard_id IN (" . implode(',', $domainDashletIDs) . ")" : '(1)';
-
-    // Target only those Dashlets which are inactive.
-    $dashletClause = $dashletIDs ? "dashboard_id NOT IN (" . implode(',', $dashletIDs) . ")" : '(1)';
-
-    // Build params.
-    $params = [
-      1 => [$contactID, 'Integer'],
-    ];
-
-    // Build query.
+    // Disable inactive widgets
+    $dashletClause = $dashletIDs ? "dashboard_id NOT IN  (" . implode(',', $dashletIDs) . ")" : '(1)';
     $updateQuery = "UPDATE civicrm_dashboard_contact
                     SET is_active = 0
-                    WHERE $domainDashletClause
-                    AND $dashletClause
-                    AND contact_id = %1";
+                    WHERE $dashletClause AND contact_id = {$contactID}";
 
-    // Disable inactive widgets.
-    CRM_Core_DAO::executeQuery($updateQuery, $params);
+    CRM_Core_DAO::executeQuery($updateQuery);
   }
 
   /**
diff --git a/civicrm/CRM/Core/BAO/FinancialTrxn.php b/civicrm/CRM/Core/BAO/FinancialTrxn.php
index e8e886d41d0db20c3a25547bea5a8ebc23094b8a..6976048bebce8848149eca2d2472fabb175460bc 100644
--- a/civicrm/CRM/Core/BAO/FinancialTrxn.php
+++ b/civicrm/CRM/Core/BAO/FinancialTrxn.php
@@ -55,6 +55,14 @@ class CRM_Core_BAO_FinancialTrxn extends CRM_Financial_DAO_FinancialTrxn {
     $trxn = new CRM_Financial_DAO_FinancialTrxn();
     $trxn->copyValues($params);
 
+    if (isset($params['fee_amount']) && is_numeric($params['fee_amount'])) {
+      if (!isset($params['total_amount'])) {
+        $trxn->fetch();
+        $params['total_amount'] = $trxn->total_amount;
+      }
+      $trxn->net_amount = $params['total_amount'] - $params['fee_amount'];
+    }
+
     if (empty($params['id']) && !CRM_Utils_Rule::currencyCode($trxn->currency)) {
       $trxn->currency = CRM_Core_Config::singleton()->defaultCurrency;
     }
diff --git a/civicrm/CRM/Core/BAO/Persistent.php b/civicrm/CRM/Core/BAO/Persistent.php
index 2694f8bef0f5b88fd70a6688126c7f8830f9fb85..a408c39107ed16ca4061e56b0de861b18d894dc2 100644
--- a/civicrm/CRM/Core/BAO/Persistent.php
+++ b/civicrm/CRM/Core/BAO/Persistent.php
@@ -51,7 +51,7 @@ class CRM_Core_BAO_Persistent extends CRM_Core_DAO_Persistent {
     if ($dao->find(TRUE)) {
       CRM_Core_DAO::storeValues($dao, $defaults);
       if (CRM_Utils_Array::value('is_config', $defaults) == 1) {
-        $defaults['data'] = unserialize($defaults['data']);
+        $defaults['data'] = CRM_Utils_String::unserialize($defaults['data']);
       }
       return $dao;
     }
@@ -97,7 +97,7 @@ class CRM_Core_BAO_Persistent extends CRM_Core_DAO_Persistent {
       $persisntentDAO->find();
 
       while ($persisntentDAO->fetch()) {
-        $contextNameData[$context][$persisntentDAO->name] = $persisntentDAO->is_config == 1 ? unserialize($persisntentDAO->data) : $persisntentDAO->data;
+        $contextNameData[$context][$persisntentDAO->name] = $persisntentDAO->is_config == 1 ? CRM_Utils_String::unserialize($persisntentDAO->data) : $persisntentDAO->data;
       }
     }
     if (empty($name)) {
diff --git a/civicrm/CRM/Core/BAO/PrevNextCache.php b/civicrm/CRM/Core/BAO/PrevNextCache.php
index f217d874417f1d22551cff250d0e747f5fc039d2..7d7cc05a7b0055265ef85c853f5dc35189c631ee 100644
--- a/civicrm/CRM/Core/BAO/PrevNextCache.php
+++ b/civicrm/CRM/Core/BAO/PrevNextCache.php
@@ -302,7 +302,7 @@ FROM   civicrm_prevnext_cache pn
     $count = 0;
     while ($dao->fetch()) {
       if (self::is_serialized($dao->data)) {
-        $main[$count] = unserialize($dao->data);
+        $main[$count] = CRM_Utils_String::unserialize($dao->data);
       }
       else {
         $main[$count] = $dao->data;
@@ -334,7 +334,7 @@ FROM   civicrm_prevnext_cache pn
    * @return bool
    */
   public static function is_serialized($string) {
-    return (@unserialize($string) !== FALSE);
+    return (@CRM_Utils_String::unserialize($string) !== FALSE);
   }
 
   /**
@@ -507,7 +507,7 @@ WHERE (pn.cachekey $op %1 OR pn.cachekey $op %2)
     foreach ($prevNextId as $id) {
       $dao->id = $id;
       if ($dao->find(TRUE)) {
-        $originalData = unserialize($dao->data);
+        $originalData = CRM_Utils_String::unserialize($dao->data);
         $srcFields = ['ID', 'Name'];
         $swapFields = ['srcID', 'srcName', 'dstID', 'dstName'];
         $data = array_diff_assoc($originalData, array_fill_keys($swapFields, 1));
diff --git a/civicrm/CRM/Core/BAO/WordReplacement.php b/civicrm/CRM/Core/BAO/WordReplacement.php
index f77891ab2564e59026b9480e0633ab39a86a7747..16099a1c27a0321367abb54b047a25f772ff8ab7 100644
--- a/civicrm/CRM/Core/BAO/WordReplacement.php
+++ b/civicrm/CRM/Core/BAO/WordReplacement.php
@@ -239,7 +239,7 @@ WHERE  domain_id = %1
         $params["domain_id"] = $value["id"];
         $params["options"] = ['wp-rebuild' => $rebuildEach];
         // Unserialize word match string.
-        $localeCustomArray = unserialize($value["locale_custom_strings"]);
+        $localeCustomArray = CRM_Utils_String::unserialize($value["locale_custom_strings"]);
         if (!empty($localeCustomArray)) {
           $wordMatchArray = [];
           // Only return the replacement strings of the current language,
@@ -315,7 +315,7 @@ WHERE  domain_id = %1
       1 => [$domainId, 'Integer'],
     ]);
     while ($domain->fetch()) {
-      return empty($domain->locale_custom_strings) ? [] : unserialize($domain->locale_custom_strings);
+      return empty($domain->locale_custom_strings) ? [] : CRM_Utils_String::unserialize($domain->locale_custom_strings);
     }
   }
 
diff --git a/civicrm/CRM/Core/Config.php b/civicrm/CRM/Core/Config.php
index f8952cf41928c30621be8a62829c05314df4f383..d63d3d9b42511d57509adf95695e5c7d00ca480a 100644
--- a/civicrm/CRM/Core/Config.php
+++ b/civicrm/CRM/Core/Config.php
@@ -565,7 +565,7 @@ class CRM_Core_Config extends CRM_Core_Config_MagicMerge {
       WHERE is_domain = 1 AND name = "installed"
     ');
     while ($dao->fetch()) {
-      $value = unserialize($dao->value);
+      $value = CRM_Utils_String::unserialize($dao->value);
       if (!empty($value)) {
         Civi::settings()->set('installed', 1);
         return;
diff --git a/civicrm/CRM/Core/DAO.php b/civicrm/CRM/Core/DAO.php
index d1b7668fee2e305b21841274695a09f8caaf3d4d..d1e845c4eaf853fe4efaf33fd2b9edec1ca3810e 100644
--- a/civicrm/CRM/Core/DAO.php
+++ b/civicrm/CRM/Core/DAO.php
@@ -2927,7 +2927,7 @@ SELECT contact_id
         return strlen($value) ? json_decode($value, TRUE) : [];
 
       case self::SERIALIZE_PHP:
-        return strlen($value) ? unserialize($value, ['allowed_classes' => FALSE]) : [];
+        return strlen($value) ? CRM_Utils_String::unserialize($value) : [];
 
       case self::SERIALIZE_COMMA:
         return explode(',', trim(str_replace(', ', '', $value)));
diff --git a/civicrm/CRM/Core/Form/Search.php b/civicrm/CRM/Core/Form/Search.php
index fed155bbff75a8273d115a755ebe90ccdf455f18..cd2d5bfe63f8f224247b573271b73769297e35bf 100644
--- a/civicrm/CRM/Core/Form/Search.php
+++ b/civicrm/CRM/Core/Form/Search.php
@@ -124,6 +124,20 @@ class CRM_Core_Form_Search extends CRM_Core_Form {
     $this->searchFieldMetadata = array_merge($this->searchFieldMetadata, $searchFieldMetadata);
   }
 
+  /**
+   * Prepare for search by loading options from the url, handling force searches, retrieving form values.
+   *
+   * @throws \CRM_Core_Exception
+   * @throws \CiviCRM_API3_Exception
+   */
+  public function preProcess() {
+    $this->loadStandardSearchOptionsFromUrl();
+    if ($this->_force) {
+      $this->handleForcedSearch();
+    }
+    $this->_formValues = $this->getFormValues();
+  }
+
   /**
    * This virtual function is used to set the default values of various form elements.
    *
@@ -132,7 +146,10 @@ class CRM_Core_Form_Search extends CRM_Core_Form {
    * @throws \CRM_Core_Exception
    */
   public function setDefaultValues() {
-    $defaults = (array) $this->_formValues;
+    // Use the form values stored to the form. Ideally 'formValues'
+    // would remain 'pure' & another array would be wrangled.
+    // We don't do that - so we want the version of formValues stored early on.
+    $defaults = (array) $this->get('formValues');
     foreach (array_keys($this->getSearchFieldMetadata()) as $entity) {
       $defaults = array_merge($this->getEntityDefaults($entity), $defaults);
     }
@@ -146,6 +163,7 @@ class CRM_Core_Form_Search extends CRM_Core_Form {
    */
   protected function setFormValues() {
     $this->_formValues = $this->getFormValues();
+    $this->set('formValues', $this->_formValues);
     $this->convertTextStringsToUseLikeOperator();
   }
 
@@ -491,4 +509,26 @@ class CRM_Core_Form_Search extends CRM_Core_Form {
     return (array) $this->get('formValues');
   }
 
+  /**
+   * Set the metadata for the form.
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  protected function setSearchMetadata() {}
+
+  /**
+   * Handle force=1 in the url.
+   *
+   * Search field metadata is normally added in buildForm but we are bypassing that in this flow
+   * (I've always found the flow kinda confusing & perhaps that is the problem but this mitigates)
+   *
+   * @throws \CiviCRM_API3_Exception
+   */
+  protected function handleForcedSearch() {
+    $this->setSearchMetadata();
+    $this->addContactSearchFields();
+    $this->postProcess();
+    $this->set('force', 0);
+  }
+
 }
diff --git a/civicrm/CRM/Core/Menu.php b/civicrm/CRM/Core/Menu.php
index c6fefdc91a5951343e3edf09bacfff9781cbea00..6f19e4a573e3282376ecf8b6b91ba258b794d58f 100644
--- a/civicrm/CRM/Core/Menu.php
+++ b/civicrm/CRM/Core/Menu.php
@@ -609,13 +609,13 @@ UNION (
       // Move module_data into main item.
       if (isset(self::$_menuCache[$menu->path]['module_data'])) {
         CRM_Utils_Array::extend(self::$_menuCache[$menu->path],
-          unserialize(self::$_menuCache[$menu->path]['module_data']));
+          CRM_Utils_String::unserialize(self::$_menuCache[$menu->path]['module_data']));
         unset(self::$_menuCache[$menu->path]['module_data']);
       }
 
       // Unserialize other elements.
       foreach (self::$_serializedElements as $element) {
-        self::$_menuCache[$menu->path][$element] = unserialize($menu->$element);
+        self::$_menuCache[$menu->path][$element] = CRM_Utils_String::unserialize($menu->$element);
 
         if (strpos($path, $menu->path) !== FALSE) {
           $menuPath = &self::$_menuCache[$menu->path];
diff --git a/civicrm/CRM/Core/Payment/PaymentExpress.php b/civicrm/CRM/Core/Payment/PaymentExpress.php
index 47db6a690ca1fa4072ea3889d0619ee9b5139602..28552293feeda9e863a2fb44186cb8904f034b8e 100644
--- a/civicrm/CRM/Core/Payment/PaymentExpress.php
+++ b/civicrm/CRM/Core/Payment/PaymentExpress.php
@@ -121,7 +121,7 @@ class CRM_Core_Payment_PaymentExpress extends CRM_Core_Payment {
       CRM_Core_Error::fatal(ts('Component is invalid'));
     }
 
-    $url = CRM_Utils_System::externUrl('extern/pxIPN');
+    $url = $config->userFrameworkResourceURL . "extern/pxIPN.php";
 
     if ($component == 'event') {
       $cancelURL = CRM_Utils_System::url('civicrm/event/register',
diff --git a/civicrm/CRM/Cxn/BAO/Cxn.php b/civicrm/CRM/Cxn/BAO/Cxn.php
index e304c99b3278aa2b7109a3d4c1ac97e409c1ca4b..658bd61fc6e83afe1ce516e6d9ed46bd0f7ae0e1 100644
--- a/civicrm/CRM/Cxn/BAO/Cxn.php
+++ b/civicrm/CRM/Cxn/BAO/Cxn.php
@@ -45,7 +45,22 @@ class CRM_Cxn_BAO_Cxn extends CRM_Cxn_DAO_Cxn {
    * @return string
    */
   public static function getSiteCallbackUrl() {
-    return CRM_Utils_System::externUrl('extern/cxn', NULL, NULL, TRUE, TRUE);
+    $config = CRM_Core_Config::singleton();
+
+    if (preg_match('/^(http|https):/', $config->resourceBase)) {
+      $civiUrl = $config->resourceBase;
+    }
+    else {
+      $civiUrl = rtrim(CRM_Utils_System::baseURL(), '/') . '/' . ltrim($config->resourceBase, '/');
+    }
+
+    // In practice, this may not be necessary, but we want to prevent
+    // edge-cases that downgrade security-level below system policy.
+    if (Civi::settings()->get('enableSSL')) {
+      $civiUrl = preg_replace('/^http:/', 'https:', $civiUrl);
+    }
+
+    return rtrim($civiUrl, '/') . '/extern/cxn.php';
   }
 
   /**
diff --git a/civicrm/CRM/Mailing/BAO/Mailing.php b/civicrm/CRM/Mailing/BAO/Mailing.php
index ccb7ce99a644839aeeb2bd8e704c728705a38417..1223735d80477230ba3ab6be062ac4b96d362657 100644
--- a/civicrm/CRM/Mailing/BAO/Mailing.php
+++ b/civicrm/CRM/Mailing/BAO/Mailing.php
@@ -1163,8 +1163,8 @@ ORDER BY   civicrm_email.is_bulkmail DESC
 
     // push the tracking url on to the html email if necessary
     if ($this->open_tracking && $html) {
-      array_push($html, "\n" . '<img src="' . CRM_Utils_System::externUrl('extern/open', "q=$event_queue_id")
-        . '" width="1" height="1" alt="" border="0">'
+      array_push($html, "\n" . '<img src="' . $config->userFrameworkResourceURL .
+        "extern/open.php?q=$event_queue_id\" width='1' height='1' alt='' border='0'>"
       );
     }
 
diff --git a/civicrm/CRM/Mailing/BAO/TrackableURL.php b/civicrm/CRM/Mailing/BAO/TrackableURL.php
index 759c1053a8caee2441769e0ea8693b9b154f3255..da57a134c3eb8674234aa1318cc8dc01a9c8cd4d 100644
--- a/civicrm/CRM/Mailing/BAO/TrackableURL.php
+++ b/civicrm/CRM/Mailing/BAO/TrackableURL.php
@@ -73,6 +73,7 @@ class CRM_Mailing_BAO_TrackableURL extends CRM_Mailing_DAO_TrackableURL {
     else {
 
       $hrefExists = FALSE;
+      $config = CRM_Core_Config::singleton();
 
       $tracker = new CRM_Mailing_BAO_TrackableURL();
       if (preg_match('/^href/i', $url)) {
@@ -88,11 +89,11 @@ class CRM_Mailing_BAO_TrackableURL extends CRM_Mailing_DAO_TrackableURL {
       }
       $id = $tracker->id;
 
-      $redirect = CRM_Utils_System::externUrl('extern/url', "u=$id");
+      $redirect = $config->userFrameworkResourceURL . "extern/url.php?u=$id";
       $urlCache[$mailing_id . $url] = $redirect;
     }
 
-    $returnUrl = CRM_Utils_System::externUrl('extern/url', "u=$id&qid=$queue_id");
+    $returnUrl = "{$urlCache[$mailing_id . $url]}&qid={$queue_id}";
 
     if ($hrefExists) {
       $returnUrl = "href='{$returnUrl}' rel='nofollow'";
diff --git a/civicrm/CRM/Member/BAO/Membership.php b/civicrm/CRM/Member/BAO/Membership.php
index 54ea9d029781cf01ec629f2806aba31aee423c2f..e2c3baee60c0ad5ed5a4aa2ea36f5b1ae79976ba 100644
--- a/civicrm/CRM/Member/BAO/Membership.php
+++ b/civicrm/CRM/Member/BAO/Membership.php
@@ -758,7 +758,7 @@ INNER JOIN  civicrm_membership_type type ON ( type.id = membership.membership_ty
     if ($dao->find(TRUE)) {
       CRM_Core_DAO::storeValues($dao, $membershipBlock);
       if (!empty($membershipBlock['membership_types'])) {
-        $membershipTypes = unserialize($membershipBlock['membership_types']);
+        $membershipTypes = CRM_Utils_String::unserialize($membershipBlock['membership_types']);
         if (!is_array($membershipTypes)) {
           return $membershipBlock;
         }
diff --git a/civicrm/CRM/Report/Form.php b/civicrm/CRM/Report/Form.php
index 8090032de0de291ec5aa7f9295f47592a3493d4f..f136435bde50fc510c4f4134f5e3c2bdf0d32a17 100644
--- a/civicrm/CRM/Report/Form.php
+++ b/civicrm/CRM/Report/Form.php
@@ -155,9 +155,6 @@ class CRM_Report_Form extends CRM_Core_Form {
    */
   protected $_groupFilter = FALSE;
 
-  // [ML] Required for civiexportexcel
-  public $supportsExportExcel = TRUE;
-
   /**
    * Has the report been optimised for group filtering.
    *
@@ -635,7 +632,7 @@ class CRM_Report_Form extends CRM_Core_Form {
 
       $formValues = CRM_Utils_Array::value('form_values', $this->_instanceValues);
       if ($formValues) {
-        $this->_formValues = unserialize($formValues);
+        $this->_formValues = CRM_Utils_String::unserialize($formValues);
       }
       else {
         $this->_formValues = NULL;
@@ -2845,11 +2842,6 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
       $this->_absoluteUrl = TRUE;
       $this->addPaging = FALSE;
     }
-    elseif ($this->_outputMode == 'excel2007') {
-      $printOnly = TRUE;
-      $this->_absoluteUrl = TRUE;
-      $this->addPaging = FALSE;
-    }
     elseif ($this->_outputMode == 'group') {
       $this->assign('outputMode', 'group');
     }
@@ -3502,9 +3494,6 @@ WHERE cg.extends IN ('" . implode("','", $this->_customGroupExtends) . "') AND
     elseif ($this->_outputMode == 'csv') {
       CRM_Report_Utils_Report::export2csv($this, $rows);
     }
-    elseif ($this->_outputMode == 'excel2007') {
-      CRM_CiviExportExcel_Utils_Report::export2excel2007($this, $rows);
-    }
     elseif ($this->_outputMode == 'group') {
       $group = $this->_params['groups'];
       $this->add2group($group);
diff --git a/civicrm/CRM/Report/Form/Member/Summary.php b/civicrm/CRM/Report/Form/Member/Summary.php
index 78084e4ab7163974f67af2b37fd51c7482bccda3..2f44f15a407691f4fc251406ca26b34cdb357b7c 100644
--- a/civicrm/CRM/Report/Form/Member/Summary.php
+++ b/civicrm/CRM/Report/Form/Member/Summary.php
@@ -64,7 +64,7 @@ class CRM_Report_Form_Member_Summary extends CRM_Report_Form {
   public function __construct() {
     $this->_columns = [
       'civicrm_membership' => [
-        'dao' => 'CRM_Member_DAO_MembershipType',
+        'dao' => 'CRM_Member_DAO_Membership',
         'grouping' => 'member-fields',
         'fields' => [
           'membership_type_id' => [
diff --git a/civicrm/CRM/Upgrade/Incremental/sql/5.19.2.mysql.tpl b/civicrm/CRM/Upgrade/Incremental/sql/5.19.2.mysql.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..ecef477739aa19a63e782daabdbab5eb38281983
--- /dev/null
+++ b/civicrm/CRM/Upgrade/Incremental/sql/5.19.2.mysql.tpl
@@ -0,0 +1 @@
+{* file to handle db changes in 5.19.2 during upgrade *}
diff --git a/civicrm/CRM/Utils/API/HTMLInputCoder.php b/civicrm/CRM/Utils/API/HTMLInputCoder.php
index c0f5cdecc8cb3ab9c18adfeebe9ef12d40fb0437..4c69f64bb890b664b6e4e9babe9e9f45766e175b 100644
--- a/civicrm/CRM/Utils/API/HTMLInputCoder.php
+++ b/civicrm/CRM/Utils/API/HTMLInputCoder.php
@@ -146,7 +146,39 @@ class CRM_Utils_API_HTMLInputCoder extends CRM_Utils_API_AbstractFieldCoder {
       }
     }
     elseif ($castToString || is_string($values)) {
-      $values = str_replace(['<', '>'], ['&lt;', '&gt;'], $values);
+      $values = $this->encodeValue($values);
+    }
+  }
+
+  public function encodeValue($value) {
+    return str_replace(['<', '>'], ['&lt;', '&gt;'], $value);
+  }
+
+  /**
+   * Perform in-place decode on strings (in a list of records).
+   *
+   * @param array $rows
+   *   Ex in: $rows[0] = ['first_name' => 'A&W'].
+   *   Ex out: $rows[0] = ['first_name' => 'A&amp;W'].
+   */
+  public function encodeRows(&$rows) {
+    foreach ($rows as $rid => $row) {
+      $this->encodeRow($rows[$rid]);
+    }
+  }
+
+  /**
+   * Perform in-place encode on strings (in a single record).
+   *
+   * @param array $row
+   *   Ex in: ['first_name' => 'A&W'].
+   *   Ex out: ['first_name' => 'A&amp;W'].
+   */
+  public function encodeRow(&$row) {
+    foreach ($row as $k => $v) {
+      if (is_string($v) && !$this->isSkippedField($k)) {
+        $row[$k] = $this->encodeValue($v);
+      }
     }
   }
 
@@ -161,7 +193,39 @@ class CRM_Utils_API_HTMLInputCoder extends CRM_Utils_API_AbstractFieldCoder {
       }
     }
     elseif ($castToString || is_string($values)) {
-      $values = str_replace(['&lt;', '&gt;'], ['<', '>'], $values);
+      $values = $this->decodeValue($values);
+    }
+  }
+
+  public function decodeValue($value) {
+    return str_replace(['&lt;', '&gt;'], ['<', '>'], $value);
+  }
+
+  /**
+   * Perform in-place decode on strings (in a list of records).
+   *
+   * @param array $rows
+   *   Ex in: $rows[0] = ['first_name' => 'A&amp;W'].
+   *   Ex out: $rows[0] = ['first_name' => 'A&W'].
+   */
+  public function decodeRows(&$rows) {
+    foreach ($rows as $rid => $row) {
+      $this->decodeRow($rows[$rid]);
+    }
+  }
+
+  /**
+   * Perform in-place decode on strings (in a single record).
+   *
+   * @param array $row
+   *   Ex in: ['first_name' => 'A&amp;W'].
+   *   Ex out: ['first_name' => 'A&W'].
+   */
+  public function decodeRow(&$row) {
+    foreach ($row as $k => $v) {
+      if (is_string($v) && !$this->isSkippedField($k)) {
+        $row[$k] = $this->decodeValue($v);
+      }
     }
   }
 
diff --git a/civicrm/CRM/Utils/String.php b/civicrm/CRM/Utils/String.php
index d0f295dd18048cd8121fd3bc9b959124572d2ae0..6e53d007768aa88eb6494b239722fc28629f6dd5 100644
--- a/civicrm/CRM/Utils/String.php
+++ b/civicrm/CRM/Utils/String.php
@@ -31,6 +31,9 @@
  * @copyright CiviCRM LLC (c) 2004-2019
  */
 
+use function xKerman\Restricted\unserialize;
+use xKerman\Restricted\UnserializeFailedException;
+
 require_once 'HTML/QuickForm/Rule/Email.php';
 
 /**
@@ -936,4 +939,33 @@ class CRM_Utils_String {
     return array_values(array_unique($result));
   }
 
+  /**
+   * Safely unserialize a string of scalar or array values (but not objects!)
+   *
+   * Use `xkerman/restricted-unserialize` to unserialize strings using PHP's
+   * serialization format. `restricted-unserialize` works like PHP's built-in
+   * `unserialize` function except that it does not deserialize object instances,
+   * making it immune to PHP Object Injection {@see https://www.owasp.org/index.php/PHP_Object_Injection}
+   * vulnerabilities.
+   *
+   * Note: When dealing with user inputs, it is generally recommended to use
+   * safe, standard data interchange formats such as JSON rather than PHP's
+   * serialization format when dealing with user input.
+   *
+   * @param string|NULL $string
+   *
+   * @return mixed
+   */
+  public static function unserialize($string) {
+    if (!is_string($string)) {
+      return FALSE;
+    }
+    try {
+      return unserialize($string);
+    }
+    catch (UnserializeFailedException $e) {
+      return FALSE;
+    }
+  }
+
 }
diff --git a/civicrm/CRM/Utils/System.php b/civicrm/CRM/Utils/System.php
index cdb6ffbd3f8a8899fdd8242c4fd7a3dd1fe387c0..b52932368805f2c7f3579531484d4921059661bf 100644
--- a/civicrm/CRM/Utils/System.php
+++ b/civicrm/CRM/Utils/System.php
@@ -300,41 +300,6 @@ class CRM_Utils_System {
     return $url;
   }
 
-  /**
-   * Generates an extern url.
-   *
-   * @param string $path
-   *   The extern path, such as "extern/url".
-   * @param string $query
-   *   A query string to append to the link.
-   * @param string $fragment
-   *   A fragment identifier (named anchor) to append to the link.
-   * @param bool $absolute
-   *   Whether to force the output to be an absolute link (beginning with a
-   *   URI-scheme such as 'http:').
-   * @param bool $isSSL
-   *   NULL to autodetect. TRUE to force to SSL.
-   */
-  public static function externUrl($path = NULL, $query = NULL, $fragment = NULL, $absolute = TRUE, $isSSL = NULL) {
-    $query = self::makeQueryString($query);
-
-    $url = Civi::paths()->getUrl("[civicrm.root]/{$path}.php", $absolute ? 'absolute' : 'relative', $isSSL)
-      . ($query ? "?$query" : "")
-      . ($fragment ? "#$fragment" : "");
-
-    $parsedUrl = CRM_Utils_Url::parseUrl($url);
-    $event = \Civi\Core\Event\GenericHookEvent::create([
-      'url' => &$parsedUrl,
-      'path' => $path,
-      'query' => $query,
-      'fragment' => $fragment,
-      'absolute' => $absolute,
-      'isSSL' => $isSSL,
-    ]);
-    Civi::service('dispatcher')->dispatch('hook_civicrm_alterExternUrl', $event);
-    return CRM_Utils_Url::unparseUrl($event->url);
-  }
-
   /**
    * Path of the current page e.g. 'civicrm/contact/view'
    *
diff --git a/civicrm/CRM/Utils/System/WordPress.php b/civicrm/CRM/Utils/System/WordPress.php
index 9c074c5a88f968ec53575493c050393beb256563..382892d314b62688942bcfdacbc56655b2324cc1 100644
--- a/civicrm/CRM/Utils/System/WordPress.php
+++ b/civicrm/CRM/Utils/System/WordPress.php
@@ -827,13 +827,11 @@ class CRM_Utils_System_WordPress extends CRM_Utils_System_Base {
     $contactCreated = 0;
     $contactMatching = 0;
 
-    // previously used $wpdb - which means WordPress *must* be bootstrapped
-    $wpUsers = get_users(array(
-      'blog_id' => get_current_blog_id(),
-      'number' => -1,
-    ));
+    global $wpdb;
+    $wpUserIds = $wpdb->get_col("SELECT $wpdb->users.ID FROM $wpdb->users");
 
-    foreach ($wpUsers as $wpUserData) {
+    foreach ($wpUserIds as $wpUserId) {
+      $wpUserData = get_userdata($wpUserId);
       $contactCount++;
       if ($match = CRM_Core_BAO_UFMatch::synchronizeUFMatch($wpUserData,
         $wpUserData->$id,
diff --git a/civicrm/Civi/API/SelectQuery.php b/civicrm/Civi/API/SelectQuery.php
index 752159b3d7aa1213e723fd4fc64e1c85da8ca48b..95bd4ce093d55f7f92646a57f81d1b4c7ef739fb 100644
--- a/civicrm/Civi/API/SelectQuery.php
+++ b/civicrm/Civi/API/SelectQuery.php
@@ -233,6 +233,12 @@ abstract class SelectQuery {
         // Join doesn't exist - might be another param with a dot in it for some reason, we'll just ignore it.
         return NULL;
       }
+
+      // Skip if we don't have permission to access this field
+      if ($this->checkPermissions && !empty($fieldInfo['permission']) && !\CRM_Core_Permission::check($fieldInfo['permission'])) {
+        return NULL;
+      }
+
       $fkTable = \CRM_Core_DAO_AllCoreTables::getTableForClass($fkField['FKClassName']);
       $tableAlias = implode('_to_', $subStack) . "_to_$fkTable";
 
diff --git a/civicrm/Civi/Api4/Generic/Traits/DAOActionTrait.php b/civicrm/Civi/Api4/Generic/Traits/DAOActionTrait.php
index 29e30f86ea93151725c0eb985f9975d426de7bbc..638c8fe83db98af7ec629cecc4fa2c83edfdfd4b 100644
--- a/civicrm/Civi/Api4/Generic/Traits/DAOActionTrait.php
+++ b/civicrm/Civi/Api4/Generic/Traits/DAOActionTrait.php
@@ -92,7 +92,11 @@ trait DAOActionTrait {
     $query->orderBy = $this->getOrderBy();
     $query->limit = $this->getLimit();
     $query->offset = $this->getOffset();
-    return $query->run();
+    $result = $query->run();
+    if (is_array($result)) {
+      \CRM_Utils_API_HTMLInputCoder::singleton()->decodeRows($result);
+    }
+    return $result;
   }
 
   /**
diff --git a/civicrm/Civi/Api4/Query/Api4SelectQuery.php b/civicrm/Civi/Api4/Query/Api4SelectQuery.php
index f7026ba3494315cac0dd5ab8f56fa87650cc0840..35f6fd808060dcf39e5efa64615dbac966c91e96 100644
--- a/civicrm/Civi/Api4/Query/Api4SelectQuery.php
+++ b/civicrm/Civi/Api4/Query/Api4SelectQuery.php
@@ -375,7 +375,10 @@ class Api4SelectQuery extends SelectQuery {
     if ($lastLink instanceof CustomGroupJoinable) {
       $field = $lastLink->getSqlColumn($field);
     }
-
+    // Check Permission on field.
+    if ($this->checkPermissions && !empty($this->apiFieldSpec[$prefix . $field]['permission']) && !\CRM_Core_Permission::check($this->apiFieldSpec[$prefix . $field]['permission'])) {
+      return;
+    }
     $this->fkSelectAliases[$key] = sprintf('%s.%s', $lastLink->getAlias(), $field);
   }
 
diff --git a/civicrm/Civi/Api4/Service/Spec/FieldSpec.php b/civicrm/Civi/Api4/Service/Spec/FieldSpec.php
index 7a44a1d925c6731b897f322a0644eac0212d0b2f..c44d58678d78216c55368535f52cc5e82751d0f5 100644
--- a/civicrm/Civi/Api4/Service/Spec/FieldSpec.php
+++ b/civicrm/Civi/Api4/Service/Spec/FieldSpec.php
@@ -76,7 +76,7 @@ class FieldSpec {
   protected $requiredIf;
 
   /**
-   * @var array|boolean
+   * @var array|bool
    */
   protected $options;
 
@@ -105,6 +105,11 @@ class FieldSpec {
    */
   protected $serialize;
 
+  /**
+   * @var array
+   */
+  protected $permission;
+
   /**
    * Aliases for the valid data types
    *
@@ -286,6 +291,22 @@ class FieldSpec {
     return $this;
   }
 
+  /**
+   * @param array $permission
+   * @return $this
+   */
+  public function setPermission($permission) {
+    $this->permission = $permission;
+    return $this;
+  }
+
+  /**
+   * @return array
+   */
+  public function getPermission() {
+    return $this->permission;
+  }
+
   /**
    * @return string
    */
diff --git a/civicrm/Civi/Api4/Service/Spec/SpecFormatter.php b/civicrm/Civi/Api4/Service/Spec/SpecFormatter.php
index b1c1c804e3d22ab97ee2d3e0930b5d37d3f18d6b..f44e267c7ac95463cf4619c605248e6ea6b77f03 100644
--- a/civicrm/Civi/Api4/Service/Spec/SpecFormatter.php
+++ b/civicrm/Civi/Api4/Service/Spec/SpecFormatter.php
@@ -100,6 +100,7 @@ class SpecFormatter {
     $field->setDescription(ArrayHelper::value('description', $data));
     self::setInputTypeAndAttrs($field, $data, $dataTypeName);
 
+    $field->setPermission(ArrayHelper::value('permission', $data));
     $fkAPIName = ArrayHelper::value('FKApiName', $data);
     $fkClassName = ArrayHelper::value('FKClassName', $data);
     if ($fkAPIName || $fkClassName) {
diff --git a/civicrm/Civi/Api4/Utils/FormattingUtil.php b/civicrm/Civi/Api4/Utils/FormattingUtil.php
index b845ea66084389cb26e253535a068077ef29f7f8..f848f5ba2650969b28d0fa09cbcc564fd198db33 100644
--- a/civicrm/Civi/Api4/Utils/FormattingUtil.php
+++ b/civicrm/Civi/Api4/Utils/FormattingUtil.php
@@ -81,6 +81,8 @@ class FormattingUtil {
         $params[$name] = 'null';
       }
     }
+
+    \CRM_Utils_API_HTMLInputCoder::singleton()->encodeRow($params);
   }
 
   /**
@@ -127,6 +129,11 @@ class FormattingUtil {
         $value = date('Ymd', strtotime($value));
         break;
     }
+
+    $hic = \CRM_Utils_API_HTMLInputCoder::singleton();
+    if (!$hic->isSkippedField($fieldSpec['name'])) {
+      $value = $hic->encodeValue($value);
+    }
   }
 
 }
diff --git a/civicrm/Civi/Core/SettingsBag.php b/civicrm/Civi/Core/SettingsBag.php
index 4155e7baf86d4e1fe2069a63c938d55011538eec..bd419c0fca7a9d4296a3b47d6f102184eb00b446 100644
--- a/civicrm/Civi/Core/SettingsBag.php
+++ b/civicrm/Civi/Core/SettingsBag.php
@@ -150,7 +150,7 @@ class SettingsBag {
     if (!$isUpgradeMode || \CRM_Core_DAO::checkTableExists('civicrm_setting')) {
       $dao = \CRM_Core_DAO::executeQuery($this->createQuery()->toSQL());
       while ($dao->fetch()) {
-        $this->values[$dao->name] = ($dao->value !== NULL) ? unserialize($dao->value) : NULL;
+        $this->values[$dao->name] = ($dao->value !== NULL) ? \CRM_Utils_String::unserialize($dao->value) : NULL;
       }
     }
 
@@ -355,7 +355,7 @@ class SettingsBag {
       foreach ($metadata['on_change'] as $callback) {
         call_user_func(
           \Civi\Core\Resolver::singleton()->get($callback),
-          unserialize($dao->value),
+          \CRM_Utils_String::unserialize($dao->value),
           $value,
           $metadata,
           $this->domainId
diff --git a/civicrm/Civi/Test/Api3TestTrait.php b/civicrm/Civi/Test/Api3TestTrait.php
index e853f5249b14fdb62e776ecc4aaf8b375d0f3fd6..8c4fcb5eb926855f01d556307a64d18c14b65baf 100644
--- a/civicrm/Civi/Test/Api3TestTrait.php
+++ b/civicrm/Civi/Test/Api3TestTrait.php
@@ -320,8 +320,7 @@ trait Api3TestTrait {
       $v4Params['language'] = $language;
     }
     $toRemove = ['option.', 'return', 'api.', 'format.'];
-    $chains = [];
-    $custom = [];
+    $chains = $joins = $custom = [];
     foreach ($v3Params as $key => $val) {
       foreach ($toRemove as $remove) {
         if (strpos($key, $remove) === 0) {
@@ -410,6 +409,13 @@ trait Api3TestTrait {
       case 'get':
         if ($options['return'] && $v3Action !== 'getcount') {
           $v4Params['select'] = array_keys($options['return']);
+          // Convert join syntax
+          foreach ($v4Params['select'] as &$select) {
+            if (strstr($select, '_id.')) {
+              $joins[$select] = explode('.', str_replace('_id.', '.', $select));
+              $select = str_replace('_id.', '.', $select);
+            }
+          }
         }
         if ($options['limit'] && $v4Entity != 'Setting') {
           $v4Params['limit'] = $options['limit'];
@@ -575,6 +581,10 @@ trait Api3TestTrait {
       foreach ($chains as $key => $params) {
         $result[$index][$key] = $this->runApi4LegacyChain($key, $params, $v4Entity, $row, $sequential);
       }
+      // Convert join format
+      foreach ($joins as $api3Key => $api4Path) {
+        $result[$index][$api3Key] = \CRM_Utils_Array::pathGet($result[$index], $api4Path);
+      }
       // Resolve custom field names
       foreach ($custom as $group => $fields) {
         foreach ($fields as $field => $v3FieldName) {
diff --git a/civicrm/Civi/Test/ContactTestTrait.php b/civicrm/Civi/Test/ContactTestTrait.php
index 50e256177fb55d71ffb8e1032d559a7650132018..f3e85083d92e43be8f93dbdfa2c2d3243fd7a2cb 100644
--- a/civicrm/Civi/Test/ContactTestTrait.php
+++ b/civicrm/Civi/Test/ContactTestTrait.php
@@ -160,7 +160,7 @@ trait ContactTestTrait {
    *
    */
   private function _contactCreate($params) {
-    $result = $this->callAPISuccess('contact', 'create', $params);
+    $result = civicrm_api3('contact', 'create', $params);
     if (!empty($result['is_error']) || empty($result['id'])) {
       throw new \CRM_Core_Exception('Could not create test contact, with message: ' . \CRM_Utils_Array::value('error_message', $result) . "\nBacktrace:" . \CRM_Utils_Array::value('trace', $result));
     }
diff --git a/civicrm/api/v3/Payment.php b/civicrm/api/v3/Payment.php
index 956b62f86f964aa321f7aa1c5fa9fa562f6392cd..e86c930c7127675006aa5cbe31ecd8e126a1ed1c 100644
--- a/civicrm/api/v3/Payment.php
+++ b/civicrm/api/v3/Payment.php
@@ -39,6 +39,8 @@
  *
  * @return array
  *   Array of financial transactions which are payments, if error an array with an error id and error message
+ *
+ * @throws \CiviCRM_API3_Exception
  */
 function civicrm_api3_payment_get($params) {
   $financialTrxn = [];
@@ -50,7 +52,10 @@ function civicrm_api3_payment_get($params) {
   if (isset($params['trxn_id'])) {
     $params['financial_trxn_id.trxn_id'] = $params['trxn_id'];
   }
-  $eft = civicrm_api3('EntityFinancialTrxn', 'get', $params);
+  $eftParams = $params;
+  unset($eftParams['return']);
+  // @todo - why do we fetch EFT params at all?
+  $eft = civicrm_api3('EntityFinancialTrxn', 'get', $eftParams);
   if (!empty($eft['values'])) {
     $eftIds = [];
     foreach ($eft['values'] as $efts) {
@@ -83,9 +88,10 @@ function civicrm_api3_payment_get($params) {
  * @param array $params
  *   Input parameters.
  *
- * @throws API_Exception
  * @return array
  *   Api result array
+ *
+ * @throws \CiviCRM_API3_Exception
  */
 function civicrm_api3_payment_delete($params) {
   return civicrm_api3('FinancialTrxn', 'delete', $params);
@@ -132,7 +138,6 @@ function civicrm_api3_payment_cancel($params) {
  * @return array
  *   Api result array
  *
- * @throws \API_Exception
  * @throws \CRM_Core_Exception
  * @throws \CiviCRM_API3_Exception
  */
@@ -164,12 +169,19 @@ function _civicrm_api3_payment_create_spec(&$params) {
       'api.required' => 1,
       'title' => ts('Contribution ID'),
       'type' => CRM_Utils_Type::T_INT,
+      // We accept order_id as an alias so that we can chain like
+      // civicrm_api3('Order', 'create', ['blah' => 'blah', 'contribution_status_id' => 'Pending', 'api.Payment.create => ['total_amount' => 5]]
+      'api.aliases' => ['order_id'],
     ],
     'total_amount' => [
       'api.required' => 1,
       'title' => ts('Total Payment Amount'),
       'type' => CRM_Utils_Type::T_FLOAT,
     ],
+    'fee_amount' => [
+      'title' => ts('Fee Amount'),
+      'type' => CRM_Utils_Type::T_FLOAT,
+    ],
     'payment_processor_id' => [
       'name' => 'payment_processor_id',
       'type' => CRM_Utils_Type::T_INT,
@@ -324,6 +336,15 @@ function _civicrm_api3_payment_get_spec(&$params) {
       'title' => 'Transaction ID',
       'type' => CRM_Utils_Type::T_STRING,
     ],
+    'trxn_date' => [
+      'title' => ts('Payment Date'),
+      'type' => CRM_Utils_Type::T_TIMESTAMP,
+    ],
+    'financial_trxn_id' => [
+      'title' => ts('Payment ID'),
+      'type' => CRM_Utils_Type::T_INT,
+      'api.aliases' => ['payment_id', 'id'],
+    ],
   ];
 }
 
diff --git a/civicrm/api/v3/SavedSearch.php b/civicrm/api/v3/SavedSearch.php
index c515e890e247d45cd302bf9172b2f0b6dbd9cff2..3146cda4eac75bb957f4bbfcbeab69423919bff9 100644
--- a/civicrm/api/v3/SavedSearch.php
+++ b/civicrm/api/v3/SavedSearch.php
@@ -57,7 +57,7 @@ function civicrm_api3_saved_search_create($params) {
     }
     else {
       // Assume that form_values is serialized.
-      $params["formValues"] = unserialize($params["form_values"]);
+      $params["formValues"] = \CRM_Utils_String::unserialize($params["form_values"]);
     }
   }
 
@@ -109,7 +109,7 @@ function _civicrm_api3_saved_search_result_cleanup(&$result) {
     // Only clean up the values if there are values. (A getCount operation
     // for example does not return values.)
     foreach ($result['values'] as $key => $value) {
-      $result['values'][$key]['form_values'] = unserialize($value['form_values']);
+      $result['values'][$key]['form_values'] = \CRM_Utils_String::unserialize($value['form_values']);
     }
   }
 }
diff --git a/civicrm/civicrm-version.php b/civicrm/civicrm-version.php
index ac63a087d342df86910c103e298ca7f2191cd4b6..674139de0c14f4313c7b3a2051527ad67d62837b 100644
--- a/civicrm/civicrm-version.php
+++ b/civicrm/civicrm-version.php
@@ -1,7 +1,7 @@
 <?php
 /** @deprecated */
 function civicrmVersion( ) {
-  return array( 'version'  => '5.19.1',
+  return array( 'version'  => '5.19.2',
                 'cms'      => 'Wordpress',
                 'revision' => '' );
 }
diff --git a/civicrm/composer.json b/civicrm/composer.json
index 42578e4e90e2a86db080108c85aff30edfe90763..5517a57852b8e260fc23092f01bc3cee831b88cb 100644
--- a/civicrm/composer.json
+++ b/civicrm/composer.json
@@ -37,14 +37,14 @@
     "php": "~7.0",
     "dompdf/dompdf" : "0.8.*",
     "electrolinux/phpquery": "^0.9.6",
-    "symfony/config": "^2.8.44 || ~3.0",
+    "symfony/config": "^2.8.50 || ~3.0",
     "symfony/polyfill-iconv": "~1.0",
-    "symfony/dependency-injection": "^2.8.44 || ~3.0",
-    "symfony/event-dispatcher": "^2.8.44 || ~3.0",
-    "symfony/filesystem": "^2.8.44 || ~3.0",
-    "symfony/process": "^2.8.44 || ~3.0",
+    "symfony/dependency-injection": "^2.8.50 || ~3.0",
+    "symfony/event-dispatcher": "^2.8.50 || ~3.0",
+    "symfony/filesystem": "^2.8.50 || ~3.0",
+    "symfony/process": "^2.8.50 || ~3.0",
     "psr/log": "~1.1",
-    "symfony/finder": "^2.8.44 || ~3.0",
+    "symfony/finder": "^2.8.50 || ~3.0",
     "tecnickcom/tcpdf" : "6.2.*",
     "totten/ca-config": "~17.05",
     "zetacomponents/base": "1.9.*",
@@ -64,7 +64,8 @@
     "pear/log": "1.13.1",
     "katzien/php-mime-type": "2.1.0",
     "civicrm/composer-downloads-plugin": "^2.0",
-    "league/csv": "^9.2"
+    "league/csv": "^9.2",
+    "xkerman/restricted-unserialize": "~1.1"
   },
   "require-dev": {
     "cache/integration-tests": "dev-master"
diff --git a/civicrm/composer.lock b/civicrm/composer.lock
index 9f5328a8ba4e478db6ef096b0dec09f507aeee55..928a8f3e1285debe48d849b71bb599ecf6bdf6cc 100644
--- a/civicrm/composer.lock
+++ b/civicrm/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "f0b735ec69179cc1b69b87e335db9c7b",
+    "content-hash": "bfbb5e8d36cb4c2d5fc6d71301ec4aa8",
     "packages": [
         {
             "name": "civicrm/civicrm-cxn-rpc",
@@ -1653,16 +1653,16 @@
         },
         {
             "name": "symfony/config",
-            "version": "v2.8.44",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/config.git",
-                "reference": "06c0be4cdd8363f3ec8d592c9a4d1b981d5052af"
+                "reference": "7dd5f5040dc04c118d057fb5886563963eb70011"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/config/zipball/06c0be4cdd8363f3ec8d592c9a4d1b981d5052af",
-                "reference": "06c0be4cdd8363f3ec8d592c9a4d1b981d5052af",
+                "url": "https://api.github.com/repos/symfony/config/zipball/7dd5f5040dc04c118d057fb5886563963eb70011",
+                "reference": "7dd5f5040dc04c118d057fb5886563963eb70011",
                 "shasum": ""
             },
             "require": {
@@ -1706,20 +1706,20 @@
             ],
             "description": "Symfony Config Component",
             "homepage": "https://symfony.com",
-            "time": "2018-07-26T11:13:39+00:00"
+            "time": "2018-11-26T09:38:12+00:00"
         },
         {
             "name": "symfony/dependency-injection",
-            "version": "v2.8.44",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/dependency-injection.git",
-                "reference": "ad2446d39d11c3daaa7f147d957941a187e47357"
+                "reference": "c306198fee8f872a8f5f031e6e4f6f83086992d8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ad2446d39d11c3daaa7f147d957941a187e47357",
-                "reference": "ad2446d39d11c3daaa7f147d957941a187e47357",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c306198fee8f872a8f5f031e6e4f6f83086992d8",
+                "reference": "c306198fee8f872a8f5f031e6e4f6f83086992d8",
                 "shasum": ""
             },
             "require": {
@@ -1769,20 +1769,20 @@
             ],
             "description": "Symfony DependencyInjection Component",
             "homepage": "https://symfony.com",
-            "time": "2018-07-26T11:13:39+00:00"
+            "time": "2019-04-16T11:33:46+00:00"
         },
         {
             "name": "symfony/event-dispatcher",
-            "version": "v2.8.44",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/event-dispatcher.git",
-                "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12"
+                "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/84ae343f39947aa084426ed1138bb96bf94d1f12",
-                "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12",
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0",
+                "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0",
                 "shasum": ""
             },
             "require": {
@@ -1829,20 +1829,20 @@
             ],
             "description": "Symfony EventDispatcher Component",
             "homepage": "https://symfony.com",
-            "time": "2018-07-26T09:03:18+00:00"
+            "time": "2018-11-21T14:20:20+00:00"
         },
         {
             "name": "symfony/filesystem",
-            "version": "v2.8.44",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/filesystem.git",
-                "reference": "2d6a4deccdfa2e4e9f113138b93457b2d0886c15"
+                "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/filesystem/zipball/2d6a4deccdfa2e4e9f113138b93457b2d0886c15",
-                "reference": "2d6a4deccdfa2e4e9f113138b93457b2d0886c15",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/7ae46872dad09dffb7fe1e93a0937097339d0080",
+                "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080",
                 "shasum": ""
             },
             "require": {
@@ -1879,20 +1879,20 @@
             ],
             "description": "Symfony Filesystem Component",
             "homepage": "https://symfony.com",
-            "time": "2018-07-26T11:13:39+00:00"
+            "time": "2018-11-11T11:18:13+00:00"
         },
         {
             "name": "symfony/finder",
-            "version": "v2.8.44",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/finder.git",
-                "reference": "f0de0b51913eb2caab7dfed6413b87e14fca780e"
+                "reference": "1444eac52273e345d9b95129bf914639305a9ba4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/finder/zipball/f0de0b51913eb2caab7dfed6413b87e14fca780e",
-                "reference": "f0de0b51913eb2caab7dfed6413b87e14fca780e",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4",
+                "reference": "1444eac52273e345d9b95129bf914639305a9ba4",
                 "shasum": ""
             },
             "require": {
@@ -1928,20 +1928,20 @@
             ],
             "description": "Symfony Finder Component",
             "homepage": "https://symfony.com",
-            "time": "2018-07-26T11:13:39+00:00"
+            "time": "2018-11-11T11:18:13+00:00"
         },
         {
             "name": "symfony/polyfill-ctype",
-            "version": "v1.11.0",
+            "version": "v1.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-ctype.git",
-                "reference": "82ebae02209c21113908c229e9883c419720738a"
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
-                "reference": "82ebae02209c21113908c229e9883c419720738a",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
                 "shasum": ""
             },
             "require": {
@@ -1953,7 +1953,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.11-dev"
+                    "dev-master": "1.12-dev"
                 }
             },
             "autoload": {
@@ -1969,13 +1969,13 @@
                 "MIT"
             ],
             "authors": [
-                {
-                    "name": "Symfony Community",
-                    "homepage": "https://symfony.com/contributors"
-                },
                 {
                     "name": "Gert de Pagter",
                     "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
                 }
             ],
             "description": "Symfony polyfill for ctype functions",
@@ -1986,20 +1986,20 @@
                 "polyfill",
                 "portable"
             ],
-            "time": "2019-02-06T07:57:58+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
         },
         {
             "name": "symfony/polyfill-iconv",
-            "version": "v1.9.0",
+            "version": "v1.12.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/polyfill-iconv.git",
-                "reference": "bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2"
+                "reference": "685968b11e61a347c18bf25db32effa478be610f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2",
-                "reference": "bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2",
+                "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f",
+                "reference": "685968b11e61a347c18bf25db32effa478be610f",
                 "shasum": ""
             },
             "require": {
@@ -2011,7 +2011,7 @@
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.9-dev"
+                    "dev-master": "1.12-dev"
                 }
             },
             "autoload": {
@@ -2045,20 +2045,20 @@
                 "portable",
                 "shim"
             ],
-            "time": "2018-08-06T14:22:27+00:00"
+            "time": "2019-08-06T08:03:45+00:00"
         },
         {
             "name": "symfony/process",
-            "version": "v2.8.44",
+            "version": "v2.8.50",
             "source": {
                 "type": "git",
                 "url": "https://github.com/symfony/process.git",
-                "reference": "cc83afdb5ac99147806b3bb65a3ff1227664f596"
+                "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/symfony/process/zipball/cc83afdb5ac99147806b3bb65a3ff1227664f596",
-                "reference": "cc83afdb5ac99147806b3bb65a3ff1227664f596",
+                "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8",
+                "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8",
                 "shasum": ""
             },
             "require": {
@@ -2094,7 +2094,7 @@
             ],
             "description": "Symfony Process Component",
             "homepage": "https://symfony.com",
-            "time": "2018-07-26T11:13:39+00:00"
+            "time": "2018-11-11T11:18:13+00:00"
         },
         {
             "name": "tecnickcom/tcpdf",
@@ -2228,6 +2228,57 @@
             "homepage": "https://github.com/totten/ca_config",
             "time": "2017-05-10T20:08:17+00:00"
         },
+        {
+            "name": "xkerman/restricted-unserialize",
+            "version": "1.1.12",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/xKerman/restricted-unserialize.git",
+                "reference": "4c6cadbb176c04d3e19b9bb8b40df12998460489"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/xKerman/restricted-unserialize/zipball/4c6cadbb176c04d3e19b9bb8b40df12998460489",
+                "reference": "4c6cadbb176c04d3e19b9bb8b40df12998460489",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.2"
+            },
+            "require-dev": {
+                "nikic/php-parser": "^1.4|^3.0|^4.2",
+                "phpmd/phpmd": "^2.6",
+                "phpunit/phpunit": "^4.8|^5.7|^6.5|^7.4|^8.2",
+                "sebastian/phpcpd": "^2.0|^3.0|^4.1",
+                "squizlabs/php_codesniffer": "^2.9|^3.4"
+            },
+            "type": "library",
+            "autoload": {
+                "files": [
+                    "src/function.php"
+                ],
+                "psr-4": {
+                    "xKerman\\Restricted\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "xKerman",
+                    "email": "xKhorasan@gmail.com"
+                }
+            ],
+            "description": "provide PHP Object Injection safe unserialize function",
+            "keywords": [
+                "PHP Object Injection",
+                "deserialize",
+                "unserialize"
+            ],
+            "time": "2019-08-11T00:04:39+00:00"
+        },
         {
             "name": "zendframework/zend-escaper",
             "version": "2.4.13",
@@ -2518,7 +2569,7 @@
                 {
                     "name": "Tobias Nyholm",
                     "email": "tobias.nyholm@gmail.com",
-                    "homepage": "https://github.com/nyholm"
+                    "homepage": "https://github.com/Nyholm"
                 },
                 {
                     "name": "Nicolas Grekas",
diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.mgd.php b/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.mgd.php
new file mode 100644
index 0000000000000000000000000000000000000000..6d0872b479de5d64c356824ebbf51039d24eec31
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.mgd.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * This record will be automatically inserted, updated, or deleted from the
+ * database as appropriate. For more details, see "hook_civicrm_managed" at:
+ * http://wiki.civicrm.org/confluence/display/CRMDOC/Hook+Reference
+ */
+
+return array(
+   0 =>
+    array(
+      'name' => 'iATS Payments 1stPay Processor',
+      'entity' => 'payment_processor_type',
+      'params' =>
+        array(
+          'version' => 3,
+          'title' => 'iATS Payments 1stPay Credit Card',
+          'name' => 'iATS Payments 1stPay Credit Card',
+          'description' => 'iATS Payments Credit Card Processor using 1stPay',
+          'user_name_label' => 'Processor ID',
+          'password_label' => 'Transaction Center ID',
+          'signature_label' => 'Merchant Key',
+          'class_name' => 'Payment_Faps',
+          'url_site_default' => 'https://secure.1stpaygateway.net/secure/RestGW/Gateway/Transaction/',
+          'url_site_test_default' => 'https://secure-v.goemerchant.com/secure/RestGW/Gateway/Transaction/',
+//          'url_recur_default' => 'https://secure.1stpaygateway.net/secure/RestGW/Gateway/Transaction/Sale'
+//          'url_recur_test_default' => 'https://secure-v.goemerchant.com/secure/RestGW/Gateway/Transaction/',
+          'billing_mode' => 1,
+          'payment_type' => 1,
+          'is_recur' => 1,
+          'payment_instrument_id' => 1,
+          'is_active' => 1,
+        ),
+    ),
+   1 =>
+    array(
+      'name' => 'iATS Payments 1stPay ACH Processor',
+      'entity' => 'payment_processor_type',
+      'params' =>
+        array(
+          'version' => 3,
+          'title' => 'iATS Payments 1stPay ACH',
+          'name' => 'iATS Payments 1stPay ACH',
+          'description' => 'iATS Payments ACH Processor using 1stPay',
+          'user_name_label' => 'Processor ID',
+          'password_label' => 'Transaction Center ID',
+          'signature_label' => 'Merchant Key',
+          'class_name' => 'Payment_FapsACH',
+          'url_site_default' => 'https://secure.1stpaygateway.net/secure/RestGW/Gateway/Transaction/',
+          'url_site_test_default' => 'https://secure-v.goemerchant.com/secure/RestGW/Gateway/Transaction/',
+//          'url_recur_default' => 'https://secure.1stpaygateway.net/secure/RestGW/Gateway/Transaction/Sale'
+//          'url_recur_test_default' => 'https://secure-v.goemerchant.com/secure/RestGW/Gateway/Transaction/',
+          'billing_mode' => 1,
+          'payment_type' => 2,
+          'is_recur' => 1,
+          'payment_instrument_id' => 2,
+          'is_active' => 1,
+        ),
+    )
+);
+ 
diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.php b/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.php
new file mode 100644
index 0000000000000000000000000000000000000000..2fe106e3128058b7db133b47b7e39ce88f33d3b0
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.php
@@ -0,0 +1,593 @@
+<?php
+
+class CRM_Core_Payment_Faps extends CRM_Core_Payment {
+
+  /**
+   * mode of operation: live or test
+   *
+   * @var object
+   * @static
+   */
+  protected $_mode = null;
+  protected $disable_cryptogram = FALSE;
+
+  /**
+   * Constructor
+   *
+   * @param string $mode the mode of operation: live or test
+   *
+   * @return void
+   */
+  function __construct( $mode, &$paymentProcessor ) {
+    $this->_mode             = $mode;
+    $this->_paymentProcessor = $paymentProcessor;
+    $this->_processorName    = ts('iATS Payments 1st American Payment System Interface');
+    $this->disable_cryptogram   = iats_get_setting('disable_cryptogram');
+    $this->is_test = ($this->_mode == 'test' ? 1 : 0);
+  }
+
+  /**
+   * This function checks to see if we have the right config values
+   *
+   * @return string the error message if any
+   * @public
+   */
+  function checkConfig( ) {
+
+    $error = array();
+
+    if (empty($this->_paymentProcessor['user_name'])) {
+      $error[] = ts('Processor Id is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+    }
+    if (empty($this->_paymentProcessor['password'])) {
+      $error[] = ts('Transaction Center Id is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+    }
+    if (empty($this->_paymentProcessor['signature'])) {
+      $error[] = ts('Merchant Key is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
+    }
+
+    if (!empty($error)) {
+      return implode('<p>', $error);
+    }
+    else {
+      return NULL;
+    }
+    // TODO: check urls vs. what I'm expecting?
+  }
+
+  /**
+   * Get the iATS configuration values or value.
+   *
+   * Mangle the days settings to make it easier to test if it is set.
+   */
+  protected function getSettings($key = '') {
+    static $settings = array();
+    if (empty($settings)) {
+      try {
+        $settings = civicrm_api3('Setting', 'getvalue', array('name' => 'iats_settings'));
+        if (empty($settings['days'])) {
+          $settings['days'] = array('-1');
+        }
+      }
+      catch (CiviCRM_API3_Exception $e) {
+        // Assume no settings exist, use safest fallback.
+        $settings = array('days' => array('-1'));
+      }
+    }
+    return (empty($key) ? $settings : (empty($settings[$key]) ? '' : $settings[$key]));
+  }
+
+  /**
+   * Get array of fields that should be displayed on the payment form for credit cards.
+   * Use FAPS cryptojs to gather the senstive card information, if enabled.
+   *
+   * @return array
+   */
+
+  protected function getCreditCardFormFields() {
+    $fields =  $this->disable_cryptogram ? parent::getCreditCardFormFields() : array('cryptogram');
+    return $fields;
+  }
+
+  /**
+   * Return an array of all the details about the fields potentially required for payment fields.
+   *
+   * Only those determined by getPaymentFormFields will actually be assigned to the form
+   *
+   * @return array
+   *   field metadata
+   */
+  public function getPaymentFormFieldsMetadata() {
+    $metadata = parent::getPaymentFormFieldsMetadata();
+    if (!$this->disable_cryptogram) {
+      $metadata['cryptogram'] = array(
+        'htmlType' => 'text',
+        'cc_field' => TRUE,
+        'name' => 'cryptogram',
+        'title' => ts('Cryptogram'),
+        'attributes' => array(
+          'class' => 'cryptogram',
+          'size' => 30,
+          'maxlength' => 60,
+          'autocomplete' => 'off',
+        ),
+        'is_required' => TRUE,
+      );
+    }
+    return $metadata;
+  }
+
+  /**
+   * Generate a safe, valid and unique vault key based on an email address.
+   * Used for Faps transactions.
+   */
+  static function generateVaultKey($email) {
+    $safe_email_key = preg_replace("/[^a-z0-9]/", '', strtolower($email));
+    return $safe_email_key . '!'.md5(uniqid(rand(), TRUE));
+  }
+
+/**
+   * Opportunity for the payment processor to override the entire form build.
+   *
+   * @param CRM_Core_Form $form
+   *
+   * @return bool
+   *   Should form building stop at this point?
+   *
+   * For this class, I include some js that will allow the form to dynamically
+   * build the right iframe via jquery.
+   *
+   * return (!empty($form->_paymentFields));
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public function buildForm(&$form) {
+    /* by default, use the cryptogram, but allow it to be disabled */
+    if (iats_get_setting('disable_cryptogram')) {
+      return;
+    }
+    // otherwise, generate some js settings that will allow the included
+    // crypto.js to generate the required iframe.
+    $iats_domain = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
+    // cryptojs is url of the firstpay script that needs to get loaded after the iframe
+    // is generated.
+    $cryptojs = 'https://' . $iats_domain . '/secure/PaymentHostedForm/Scripts/firstpay/firstpay.cryptogram.js';
+    $iframe_src = 'https://' . $iats_domain . '/secure/PaymentHostedForm/v3/CreditCard';
+    $jsVariables = [
+      'paymentProcessorId' => $this->_paymentProcessor['id'], 
+      'transcenterId' => $this->_paymentProcessor['password'],
+      'processorId' => $this->_paymentProcessor['user_name'],
+      'currency' => $form->getCurrency(),
+      'is_test' => $this->is_test,
+      'title' => $form->getTitle(),
+      'iframe_src' => $iframe_src,
+      'cryptojs' => $cryptojs,
+      'paymentInstrumentId' => 1,
+    ];
+    $resources = CRM_Core_Resources::singleton();
+    $cryptoCss = $resources->getUrl('com.iatspayments.civicrm', 'css/crypto.css');
+    $markup = '<link type="text/css" rel="stylesheet" href="'.$cryptoCss.'" media="all" /><script type="text/javascript" src="'.$cryptojs.'"></script>';
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'markup' => $markup,
+    ));
+    // the cryptojs above is the one on the 1pay server, now I load and invoke the extension's crypto.js
+    $myCryptoJs = $resources->getUrl('com.iatspayments.civicrm', 'js/crypto.js');
+    // after manually doing what addVars('iats', $jsVariables) would normally do
+    $script = 'var iatsSettings = ' . json_encode($jsVariables) . ';';
+    $script .= 'var cryptoJs = "'.$myCryptoJs.'";';
+    $script .= 'CRM.$(function ($) { $.getScript(cryptoJs); });';
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'script' => $script,
+    ));
+    return FALSE;
+
+  }
+
+  /**
+   * The first payment date is configurable when setting up back office recurring payments.
+   * For iATSPayments, this is also true for front-end recurring payments.
+   *
+   * @return bool
+   */
+  public function supportsFutureRecurStartDate() {
+    return TRUE;
+  } 
+
+
+  /**
+   * function doDirectPayment
+   *
+   * This is the function for taking a payment using a core payment form of any kind.
+   *
+   * Here's the thing: if we are using the cryptogram with recurring, then the cryptogram
+   * needs to be configured for use with the vault. The cryptogram iframe is created before
+   * I know whether the contribution will be recurring or not, so that forces me to always
+   * use the vault, if recurring is an option.
+   * 
+   * So: the best we can do is to avoid the use of the vault if I'm not using the cryptogram, or if I'm on a page that
+   * doesn't offer recurring contributions.
+   */
+  public function doDirectPayment(&$params) {
+    // CRM_Core_Error::debug_var('doDirectPayment params', $params);
+
+    // Check for valid currency [todo: we have C$ support, but how do we check,
+    // or should we?]
+    if (
+        'USD' != $params['currencyID']
+     && 'CAD' != $params['currencyID']
+    ) {
+      return self::error('Invalid currency selection: ' . $params['currencyID']);
+    }
+    $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
+    $usingCrypto = !empty($params['cryptogram']);
+    $ipAddress = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+    $credentials = array(
+      'merchantKey' => $this->_paymentProcessor['signature'],
+      'processorId' => $this->_paymentProcessor['user_name']
+    );
+    $vault_key = $vault_id = '';
+    if ($isRecur) {
+      // Store the params in a vault before attempting payment
+      // I first have to convert the Auth crypto into a token.
+      $options = array(
+        'action' => 'GenerateTokenFromCreditCard',
+        'test' => $this->is_test,
+      );
+      $token_request = new CRM_Iats_FapsRequest($options);
+      $request = $this->convertParams($params, $options['action']);
+      $request['ipAddress'] = $ipAddress;
+      // Make the request.
+      // CRM_Core_Error::debug_var('token request', $request);
+      $result = $token_request->request($credentials, $request);
+      // CRM_Core_Error::debug_var('token result', $result);
+      // unset the cryptogram param and request values, we can't use the cryptogram again and don't want to return it anyway.
+      unset($params['cryptogram']);
+      unset($request['creditCardCryptogram']);
+      unset($token_request);
+      if (!empty($result['isSuccess'])) {
+        // some of the result[data] is not useful, we're assuming it's not harmful to include in future requests here.
+        $request = array_merge($request, $result['data']);
+      }
+      else {
+        return self::error($result);
+      }
+      $options = array(
+        'action' => 'VaultCreateCCRecord',
+        'test' => $this->is_test,
+      );
+      $vault_request = new CRM_Iats_FapsRequest($options);
+      // auto-generate a compliant vault key  
+      $vault_key = self::generateVaultKey($request['ownerEmail']);
+      $request['vaultKey'] = $vault_key;
+      $request['ipAddress'] = $ipAddress;
+      // Make the request.
+      // CRM_Core_Error::debug_var('vault request', $request);
+      $result = $vault_request->request($credentials, $request);
+      // CRM_Core_Error::debug_var('vault result', $result);
+      if (!empty($result['isSuccess'])) {
+        $vault_id = $result['data']['id'];
+        if ($isRecur) {
+          // save my vault key + vault id as a token
+          $token = $vault_key.':'.$vault_id;
+          $payment_token_params = [
+           'token' => $token,
+           'ip_address' => $request['ipAddress'],
+           'contact_id' => $params['contactID'],
+           'email' => $request['ownerEmail'],
+           'payment_processor_id' => $this->_paymentProcessor['id'],
+          ];
+          $token_result = civicrm_api3('PaymentToken', 'create', $payment_token_params);
+          // Upon success, save the token table's id back in the recurring record.
+          if (!empty($token_result['id'])) {
+            civicrm_api3('ContributionRecur', 'create', [
+              'id' => $params['contributionRecurID'],
+              'payment_token_id' => $token_result['id'],
+            ]);
+          }
+          // Test for admin setting that limits allowable transaction days
+          $allow_days = $this->getSettings('days');
+          // Test for a specific receive date request and convert to a timestamp, default now
+          $receive_date = CRM_Utils_Array::value('receive_date', $params);
+          // my front-end addition to will get stripped out of the params, do a
+          // work-around
+          if (empty($receive_date)) {
+            $receive_date = CRM_Utils_Array::value('receive_date', $_POST);
+          }
+          $receive_ts = empty($receive_date) ? time() : strtotime($receive_date);
+          // If the admin setting is in force, ensure it's compatible.
+          if (max($allow_days) > 0) {
+            $receive_ts = CRM_Iats_Transaction::contributionrecur_next($receive_ts, $allow_days);
+          }
+          // convert to a reliable format
+          $receive_date = date('Ymd', $receive_ts);
+          $today = date('Ymd');
+          // If the receive_date is NOT today, then
+          // create a pending contribution and adjust the next scheduled date.
+          if ($receive_date !== $today) {
+            // set the receieve time to 3:00 am for a better admin experience
+            $update = array(
+              'payment_status_id' => 2,
+              'receive_date' => date('Ymd', $receive_ts) . '030000',
+            );
+            // update the recurring and contribution records with the receive date,
+            // i.e. make up for what core doesn't do
+            $this->updateRecurring($params, $update);
+            $this->updateContribution($params, $update);
+            // and now return the updates to core via the params
+            $params = array_merge($params, $update);
+            return $params;
+          }
+          // otherwise, just call updateRecurring for some housekeeping
+          // before taking the payment.
+          $this->updateRecurring($params);
+        }
+      }
+      else {
+        return self::error($result);
+      }
+      // now set the options for taking the money
+      $options = array(
+        'action' => 'SaleUsingVault',
+        'test' => $this->is_test,
+      );
+    }
+    else { // not recurring, use the simple sale option for taking the money
+      $options = array(
+        'action' => 'Sale',
+        'test' => $this->is_test,
+      );
+    }
+    // now take the money
+    $payment_request = new CRM_Iats_FapsRequest($options);
+    $request = $this->convertParams($params, $options['action']);
+    $request['ipAddress'] = $ipAddress;
+    if ($vault_id) {
+      $request['vaultKey'] = $vault_key;
+      $request['vaultId'] = $vault_id;
+    }
+    // Make the request.
+    // CRM_Core_Error::debug_var('payment request', $request);
+    $result = $payment_request->request($credentials, $request);
+    // CRM_Core_Error::debug_var('result', $result);
+    $success = (!empty($result['isSuccess']));
+    if ($success) {
+      // put the old version of the return param in just to be sure
+      $params['contribution_status_id'] = 1;
+      // For versions >= 4.6.6, the proper key.
+      $params['payment_status_id'] = 1;
+      $params['trxn_id'] = trim($result['data']['referenceNumber']).':'.time();
+      $params['gross_amount'] = $params['amount'];
+      return $params;
+    }
+    else {
+      return self::error($result);
+    }
+  }
+
+  /**
+   * Todo?
+   *
+   * @param array $params name value pair of contribution data
+   *
+   * @return void
+   * @access public
+   *
+   */
+  function doTransferCheckout( &$params, $component ) {
+    CRM_Core_Error::fatal(ts('This function is not implemented'));
+  }
+
+  /**
+   * Support corresponding CiviCRM method
+   */
+  public function changeSubscriptionAmount(&$message = '', $params = array()) {
+    return TRUE;
+  }
+
+  /**
+   * Support corresponding CiviCRM method
+   */
+  public function cancelSubscription(&$message = '', $params = array()) {
+    $userAlert = ts('You have cancelled this recurring contribution.');
+    CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
+    return TRUE;
+  }
+
+  /**
+   * Set additional fields when editing the schedule.
+   *
+   * Note: this doesn't completely replace the form hook, which is still
+   * in use for additional changes, and to support 4.6.
+   * e.g. the commented out fields below don't work properly here.
+   */
+  public function getEditableRecurringScheduleFields() {
+    return array('amount',
+         'installments',
+         'next_sched_contribution_date',
+//         'contribution_status_id',
+//         'start_date',
+         'is_email_receipt',
+       );
+  }
+
+  /*
+   * Set a useful message at the top of the schedule editing form
+   */
+  public function getRecurringScheduleUpdateHelpText() {
+    return 'Use this form to change the amount or number of installments for this recurring contribution.<ul><li>You can not change the contribution frequency.</li><li>You can also modify the next scheduled contribution date.</li><li>You can change whether the contributor is sent an email receipt for each contribution.<li>You have an option to notify the contributor of these changes.</li></ul>';
+  }
+
+  /**
+   * Convert the values in the civicrm params to the request array with keys as expected by FAPS
+   *
+   * @param array $params
+   * @param string $action
+   *
+   * @return array
+   */
+  protected function convertParams($params, $method) {
+    $request = array();
+    $convert = array(
+      'ownerEmail' => 'email',
+      'ownerStreet' => 'street_address',
+      'ownerCity' => 'city',
+      'ownerState' => 'state_province',
+      'ownerZip' => 'postal_code',
+      'ownerCountry' => 'country',
+      'orderId' => 'invoiceID',
+      'cardNumber' => 'credit_card_number',
+//      'cardtype' => 'credit_card_type',
+      'cVV' => 'cvv2',
+      'creditCardCryptogram' => 'cryptogram',
+    );
+    foreach ($convert as $r => $p) {
+      if (isset($params[$p])) {
+        $request[$r] = htmlspecialchars($params[$p]);
+      }
+    }
+    if (empty($params['email'])) {
+      if (isset($params['email-5'])) {
+        $request['ownerEmail'] = $params['email-5'];
+      }
+      elseif (isset($params['email-Primary'])) {
+        $request['ownerEmail'] = $params['email-Primary'];
+      }
+    }
+    $request['ownerName'] = $params['billing_first_name'].' '.$params['billing_last_name'];
+    if (!empty($params['month'])) {
+      $request['cardExpMonth'] = sprintf('%02d', $params['month']);
+    }
+    if (!empty($params['year'])) {
+      $request['cardExpYear'] = sprintf('%02d', $params['year'] % 100);
+    }
+    $request['transactionAmount'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount']));
+    // additional method-specific values (none!)
+    //CRM_Core_Error::debug_var('params for conversion', $params);
+    //CRM_Core_Error::debug_var('method', $method);
+    //CRM_Core_Error::debug_var('request', $request);
+    return $request;
+  }
+
+
+  /**
+   *
+   */
+  public function &error($error = NULL) {
+    $e = CRM_Core_Error::singleton();
+    if (is_object($error)) {
+      $e->push($error->getResponseCode(),
+        0, NULL,
+        $error->getMessage()
+      );
+    }
+    elseif ($error && is_numeric($error)) {
+      $e->push($error,
+        0, NULL,
+        $this->errorString($error)
+      );
+    }
+    elseif (is_array($error)) {
+      $errors = array();
+      if ($error['isError']) {
+        foreach($error['errorMessages'] as $message) {
+          $errors[] = $message;
+        }
+      }
+      if ($error['validationHasFailed']) {
+        foreach($error['validationFailures'] as $message) {
+          $errors[] = 'Validation failure for '.$message['key'].': '.$message['message'];
+        }
+      }
+      $error_string = implode('<br />',$errors);
+      $e->push(9002,
+        0, NULL,
+        $error_string
+      );
+    }
+    else {
+      $e->push(9001, 0, NULL, "Unknown System Error.");
+    }
+    return $e;
+  }
+
+  /*
+   * Update the recurring contribution record.
+   *
+   * Implemented as a function so I can do some cleanup and implement
+   * the ability to set a future start date for recurring contributions.
+   * This functionality will apply to back-end and front-end,
+   * As enabled when configured via the iATS admin settings.
+   *
+   * This function will alter the recurring schedule as an intended side effect.
+   * and return the modified the params.
+   */
+  protected function updateRecurring($params, $update = array()) {
+    // If the recurring record already exists, let's fix the next contribution and start dates,
+    // in case core isn't paying attention.
+    // We also set the schedule to 'in-progress' (even for ACH/EFT when the first one hasn't been verified),
+    // because we want the recurring job to run for this schedule.
+    if (!empty($params['contributionRecurID'])) {
+      $recur_id = $params['contributionRecurID'];
+      $recur_update = array(
+        'id' => $recur_id,
+        'contribution_status_id' => 'In Progress',
+      );
+      // use the receive date to set the next sched contribution date.
+      // By default, it's empty, unless we've got a future start date.
+      if (empty($update['receive_date'])) {
+        $next = strtotime('+' . $params['frequency_interval'] . ' ' . $params['frequency_unit']);
+        $recur_update['next_sched_contribution_date'] = date('Ymd', $next) . '030000';
+      }
+      else {
+        $recur_update['start_date'] = $recur_update['next_sched_contribution_date'] = $update['receive_date'];
+        // If I've got a monthly schedule, let's set the cycle_day for niceness
+        if ('month' == $params['frequency_interval']) {
+          $recur_update['cycle_day'] = date('j', strtotime($recur_update['start_date']));
+        }
+      }
+      try {
+        $result = civicrm_api3('ContributionRecur', 'create', $recur_update);
+        return $result;
+      }
+      catch (CiviCRM_API3_Exception $e) {
+        // Not a critical error, just log and continue.
+        $error = $e->getMessage();
+        Civi::log()->info('Unexpected error updating the next scheduled contribution date for id {id}: {error}', array('id' => $recur_id, 'error' => $error));
+      }
+    }
+    else {
+      Civi::log()->info('Unexpectedly unable to update the next scheduled contribution date, missing id.');
+    }
+    return false;
+  }
+
+  /*
+   * Update the contribution record.
+   *
+   * This function will alter the civi contribution record.
+   * Implemented only to update the receive date.
+   */
+  protected function updateContribution($params, $update = array()) {
+    if (!empty($params['contributionID'])  && !empty($update['receive_date'])) {
+      $contribution_id = $params['contributionID'];
+      $update = array(
+        'id' => $contribution_id,
+        'receive_date' => $update['receive_date']
+      );
+      try {
+        $result = civicrm_api3('Contribution', 'create', $update);
+        return $result;
+      }
+      catch (CiviCRM_API3_Exception $e) {
+        // Not a critical error, just log and continue.
+        $error = $e->getMessage();
+        Civi::log()->info('Unexpected error updating the contribution date for id {id}: {error}', array('id' => $contribution_id, 'error' => $error));
+      }
+    }
+    return false;
+  }
+
+
+}
+
+
+
diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/FapsACH.php b/civicrm/ext/iatspayments/CRM/Core/Payment/FapsACH.php
new file mode 100644
index 0000000000000000000000000000000000000000..49960c5bd292ba644c39f2a572c95a093e54a2ac
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Core/Payment/FapsACH.php
@@ -0,0 +1,321 @@
+<?php
+
+require_once 'CRM/Core/Payment.php';
+
+class CRM_Core_Payment_FapsACH extends CRM_Core_Payment_Faps {
+
+  /**
+   * Constructor
+   *
+   * @param string $mode the mode of operation: live or test
+   *
+   * @return void
+   */
+  function __construct( $mode, &$paymentProcessor ) {
+    $this->_mode             = $mode;
+    $this->_paymentProcessor = $paymentProcessor;
+    $this->_processorName    = ts('iATS Payments 1st American Payment System Interface, ACH');
+    $this->disable_cryptogram    = iats_get_setting('disable_cryptogram');
+    $this->is_test = ($this->_mode == 'test' ? 1 : 0); 
+  }
+
+  /**
+   * Get array of fields that should be displayed on the payment form for credit cards.
+   * Use FAPS cryptojs to gather the senstive card information, if enabled.
+   *
+   * @return array
+   */
+
+  protected function getDirectDebitFormFields() {
+    $fields =  $this->disable_cryptogram ? parent::getDirectDebitFormFields() : array('cryptogram');
+    return $fields;
+  }
+
+/**
+   * Opportunity for the payment processor to override the entire form build.
+   *
+   * @param CRM_Core_Form $form
+   *
+   * @return bool
+   *   Should form building stop at this point?
+   *
+   * return (!empty($form->_paymentFields));
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public function buildForm(&$form) {
+    /* by default, use the cryptogram, but allow it to be disabled */
+    if (iats_get_setting('disable_cryptogram')) {
+      return;
+    }
+    // otherwise, generate some js settings that will allow the included
+    // crypto.js to generate the required iframe.
+    $iats_domain = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
+    // cryptojs is url of the firstpay script that needs to get loaded after the iframe
+    // is generated.
+    $cryptojs = 'https://' . $iats_domain . '/secure/PaymentHostedForm/Scripts/firstpay/firstpay.cryptogram.js';
+    $currency = $form->getCurrency();
+    $iframe_src = 'https://' . $iats_domain . '/secure/PaymentHostedForm/v3/' . (('CAD' == $currency) ? 'CanadianAch' : 'Ach');
+    $jsVariables = [
+      'paymentProcessorId' => $this->_paymentProcessor['id'],
+      'transcenterId' => $this->_paymentProcessor['password'],
+      'processorId' => $this->_paymentProcessor['user_name'],
+      'currency' => $currency,
+      'is_test' => $this->is_test,
+      'title' => $form->getTitle(),
+      'iframe_src' => $iframe_src,
+      'cryptojs' => $cryptojs,
+      'paymentInstrumentId' => 2,
+    ];
+    $resources = CRM_Core_Resources::singleton();
+    $cryptoCss = $resources->getUrl('com.iatspayments.civicrm', 'css/crypto.css');
+    $markup = '<link type="text/css" rel="stylesheet" href="'.$cryptoCss.'" media="all" /><script type="text/javascript" src="'.$cryptojs.'"></script>';
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'markup' => $markup,
+    ));
+    // the cryptojs above is the one on the 1pay server, now I load and invoke the extension's crypto.js
+    $myCryptoJs = $resources->getUrl('com.iatspayments.civicrm', 'js/crypto.js');
+    // after manually doing what addVars('iats', $jsVariables) would normally do
+    $script = 'var iatsSettings = ' . json_encode($jsVariables) . ';';
+    $script .= 'var cryptoJs = "'.$myCryptoJs.'";';
+    $script .= 'CRM.$(function ($) { $.getScript(cryptoJs); });';
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'script' => $script,
+    ));
+    // and now add in a helpful cheque image and description
+    switch($currency) {
+      case 'USD': 
+        CRM_Core_Region::instance('billing-block')->add(array(
+          'template' => 'CRM/Iats/BillingBlockFapsACH_USD.tpl',
+        ));
+      case 'CAD': 
+        CRM_Core_Region::instance('billing-block')->add(array(
+          'template' => 'CRM/Iats/BillingBlockFapsACH_CAD.tpl',
+        ));
+    }
+    return FALSE;
+  }
+
+
+  /**
+   * function doDirectPayment
+   *
+   * This is the function for taking a payment using a core payment form of any kind.
+   *
+   */
+  public function doDirectPayment(&$params) {
+    // CRM_Core_Error::debug_var('doDirectPayment params', $params);
+
+    // Check for valid currency
+    $currency = $params['currencyID'];
+    if (('USD' != $currency) && ('CAD' != $currency)) {
+      return self::error('Invalid currency selection: ' . $currency);
+    }
+    $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
+    $ipAddress = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+    $credentials = array(
+      'merchantKey' => $this->_paymentProcessor['signature'],
+      'processorId' => $this->_paymentProcessor['user_name']
+    );
+    // FAPS has a funny thing called a 'category' that needs to be included with any ACH request.
+    // The category is auto-generated in the getCategoryText function, using some default settings that can be overridden on the FAPS settings page.
+    // Store it in params, will be used by my convert request call(s) later
+    $params['ach_category_text'] = self::getCategoryText($credentials, $this->is_test, $ipAddress);
+
+    $vault_key = $vault_id = '';
+    if ($isRecur) {
+      // Store the params in a vault before attempting payment
+      $options = array(
+        'action' => 'VaultCreateAchRecord',
+        'test' => $this->is_test,
+      );
+      $vault_request = new CRM_Iats_FapsRequest($options);
+      $request = $this->convertParams($params, $options['action']);
+      // auto-generate a compliant vault key  
+      $vault_key = self::generateVaultKey($request['ownerEmail']);
+      $request['vaultKey'] = $vault_key;
+      $request['ipAddress'] = $ipAddress;
+      // Make the request.
+      //CRM_Core_Error::debug_var('vault request', $request);
+      $result = $vault_request->request($credentials, $request);
+      // unset the cryptogram param, we can't use it again and don't want to return it anyway.
+      unset($params['cryptogram']);
+      //CRM_Core_Error::debug_var('vault result', $result);
+      if (!empty($result['isSuccess'])) {
+        $vault_id = $result['data']['id'];
+        if ($isRecur) {
+          // save my vaule key + vault id as a token
+          $token = $vault_key.':'.$vault_id;
+          $payment_token_params = [
+           'token' => $token,
+           'ip_address' => $request['ipAddress'],
+           'contact_id' => $params['contactID'],
+           'email' => $request['ownerEmail'],
+           'payment_processor_id' => $this->_paymentProcessor['id'],
+          ];
+          $token_result = civicrm_api3('PaymentToken', 'create', $payment_token_params);
+          // Upon success, save the token table's id back in the recurring record.
+          if (!empty($token_result['id'])) {
+            civicrm_api3('ContributionRecur', 'create', [
+              'id' => $params['contributionRecurID'],
+              'payment_token_id' => $token_result['id'],
+            ]);
+          }
+          // updateRecurring, incluing updating the next scheduled contribution date, before taking payment.
+          $this->updateRecurring($params);
+        }
+      }
+      else {
+        return self::error($result);
+      }
+      // now set the options for taking the money
+      $options = array(
+        'action' => 'AchDebitUsingVault',
+        'test' => $this->is_test,
+      );
+    }
+    else { // set the simple sale option for taking the money
+      $options = array(
+        'action' => 'AchDebit',
+        'test' => $this->is_test,
+      );
+    }
+    // now take the money
+    $payment_request = new CRM_Iats_FapsRequest($options);
+    $request = $this->convertParams($params, $options['action']);
+    $request['ipAddress'] = $ipAddress;
+    if ($vault_id) {
+      $request['vaultKey'] = $vault_key;
+      $request['vaultId'] = $vault_id;
+    }
+    // Make the request.
+    // CRM_Core_Error::debug_var('payment request', $request);
+    $result = $payment_request->request($credentials, $request);
+    // CRM_Core_Error::debug_var('result', $result);
+    $success = (!empty($result['isSuccess']));
+    if ($success) {
+      $params['payment_status_id'] = 2;
+      $params['trxn_id'] = trim($result['data']['referenceNumber']).':'.time();
+      $params['gross_amount'] = $params['amount'];
+      // Core assumes that a pending result will have no transaction id, but we have a useful one.
+      if (!empty($params['contributionID'])) {
+        $contribution_update = array('id' => $params['contributionID'], 'trxn_id' => $params['trxn_id']);
+        try {
+          $result = civicrm_api3('Contribution', 'create', $contribution_update);
+        }
+        catch (CiviCRM_API3_Exception $e) {
+          // Not a critical error, just log and continue.
+          $error = $e->getMessage();
+          Civi::log()->info('Unexpected error adding the trxn_id for contribution id {id}: {error}', array('id' => $recur_id, 'error' => $error));
+        }
+      }
+      return $params;
+    }
+    else {
+      return self::error($result);
+    }
+  }
+
+  /**
+   * Get the category text. 
+   * Before I return it, check that the category text exists, and create it if
+   * it doesn't.
+   *
+   * FAPS has a funny thing called a 'category' that needs to be included with
+   * any ACH request. This function will test if a category text string exists
+   * and create it if it doesn't.
+   *
+   * TODO: including the setup of the category on the FAPS system within this
+   * function is very funky, it should get done when the account is setup instead.
+   *
+   * @param string $ach_category_text
+   * @param array $credentials
+   *
+   * @return none
+   */
+  public static function getCategoryText($credentials, $is_test, $ipAddress = NULL) {
+    static $ach_category_text_saved;
+    if (!empty($ach_category_text_saved)) {
+      return $ach_category_text_saved;
+    } 
+    $ach_category_text = iats_get_setting('ach_category_text');
+    $ach_category_text = empty($ach_category_text) ? FAPS_DEFAULT_ACH_CATEGORY_TEXT : $ach_category_text;
+    $ach_category_exists = FALSE;
+    // check if it's setup
+    $options = array(
+      'action' => 'AchGetCategories',
+      'test' => $is_test,
+    );
+    $categories_request = new CRM_Iats_FapsRequest($options);
+    $request = empty($ipAddress) ? array() : array('ipAddress' => $ipAddress);
+    $result = $categories_request->request($credentials, $request);
+    // CRM_Core_Error::debug_var('categories request result', $result);
+    if (!empty($result['isSuccess']) && !empty($result['data'])) {
+      foreach($result['data'] as $category) {
+        if ($category['achCategoryText'] == $ach_category_text) {
+          $ach_category_exists = TRUE;
+          break;
+        }
+      }
+    }
+    if (!$ach_category_exists) { // set it up!
+      $options = array(
+        'action' => 'AchCreateCategory',
+        'test' => $is_test,
+      );
+      $categories_request = new CRM_Iats_FapsRequest($options);
+      // I've got some non-offensive defaults in here.
+      $request = array(
+        'achCategoryText' => $ach_category_text,
+        'achClassCode' => 'WEB',
+        'achEntry' => 'CiviCRM',
+      );
+      if (!empty($ipAddress)) {
+        $request['ipAddress'] = $ipAddress;
+      }
+      $result = $categories_request->request($credentials, $request);
+      // I'm being a bit naive and assuming it succeeds.
+    }
+    return $ach_category_text_saved = $ach_category_text;
+  }
+
+  /**
+   * Convert the values in the civicrm params to the request array with keys as expected by FAPS
+   * ACH has different fields from credit card.
+   *
+   * @param array $params
+   * @param string $action
+   *
+   * @return array
+   */
+  protected function convertParams($params, $method) {
+    $request = array();
+    $convert = array(
+      'ownerEmail' => 'email',
+      'ownerStreet' => 'street_address',
+      'ownerCity' => 'city',
+      'ownerState' => 'state_province',
+      'ownerZip' => 'postal_code',
+      'ownerCountry' => 'country',
+      'orderId' => 'invoiceID',
+      'achCryptogram' => 'cryptogram',
+    );
+    foreach ($convert as $r => $p) {
+      if (isset($params[$p])) {
+        $request[$r] = htmlspecialchars($params[$p]);
+      }
+    }
+    if (empty($params['email'])) {
+      if (isset($params['email-5'])) {
+        $request['ownerEmail'] = $params['email-5'];
+      }
+      elseif (isset($params['email-Primary'])) {
+        $request['ownerEmail'] = $params['email-Primary'];
+      }
+    }
+    $request['ownerName'] = $params['billing_first_name'].' '.$params['billing_last_name'];
+    $request['transactionAmount'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount']));
+    $request['categoryText'] = $params['ach_category_text'];
+    return $request;
+  }
+
+}
diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/Iats.mgd.php b/civicrm/ext/iatspayments/CRM/Core/Payment/Iats.mgd.php
new file mode 100644
index 0000000000000000000000000000000000000000..816b8d78850c770f5a9f574af1a8c94102754c34
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Core/Payment/Iats.mgd.php
@@ -0,0 +1,76 @@
+<?php
+/**
+ * This record will be automatically inserted, updated, or deleted from the
+ * database as appropriate. For more details, see "hook_civicrm_managed" at:
+ * http://wiki.civicrm.org/confluence/display/CRMDOC/Hook+Reference
+ */
+
+return array(
+  0 =>
+    array(
+    'module' => 'com.iatspayments.civicrm',
+    'name' => 'iATS Payments',
+    'entity' => 'PaymentProcessorType',
+    'params' => array(
+      'version' => 3,
+      'name' => 'iATS Payments Credit Card',
+      'title' => 'iATS Payments Credit Card',
+      'description' => 'iATS credit card payment processor using the web services interface.',
+      'class_name' => 'Payment_iATSService',
+      'billing_mode' => 'form',
+      'user_name_label' => 'Agent Code',
+      'password_label' => 'Password',
+      'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'is_recur' => 1,
+      'payment_type' => 1,
+    ),
+  ),
+  1 =>
+    array(
+    'module' => 'com.iatspayments.civicrm',
+    'name' => 'iATS Payments ACH/EFT',
+    'entity' => 'PaymentProcessorType',
+    'params' => array(
+      'version' => 3,
+      'name' => 'iATS Payments ACH/EFT',
+      'title' => 'iATS Payments ACH/EFT',
+      'description' => 'iATS ACH/EFT payment processor using the web services interface.',
+      'class_name' => 'Payment_iATSServiceACHEFT',
+      'billing_mode' => 'form',
+      'user_name_label' => 'Agent Code',
+      'password_label' => 'Password',
+      'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'is_recur' => 1,
+      'payment_type' => 2,
+      'payment_instrument_id' => '2', /* "Debit Card"  */
+    ),
+  ),
+  2 =>
+    array(
+    'module' => 'com.iatspayments.civicrm',
+    'name' => 'iATS Payments SWIPE',
+    'entity' => 'PaymentProcessorType',
+    'params' => array(
+      'version' => 3,
+      'name' => 'iATS Payments SWIPE',
+      'title' => 'iATS Payments SWIPE',
+      'description' => 'iATS credit card payment processor using the encrypted USB IDTECH card reader.',
+      'class_name' => 'Payment_iATSServiceSWIPE',
+      'billing_mode' => 'form',
+      'user_name_label' => 'Agent Code',
+      'password_label' => 'Password',
+      'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
+      'is_recur' => 1,
+      'payment_type' => 1,
+    ),
+  ),
+);
diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php
index 2dca9ccab5115c15ffabae78f140d7379e9a574f..28263f72bb79c26668cd2b5261ab1d285b1e4fa3 100644
--- a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php
+++ b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php
@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public
  * License with this program; if not, see http://www.gnu.org/licenses/
  *
- * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
+ * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the CRM_Iats_iATSServiceRequest object
  */
 
 /**
@@ -132,11 +132,10 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
       return self::error('Unexpected error, missing profile');
     }
     // Use the iATSService object for interacting with iATS. Recurring contributions go through a more complex process.
-    require_once "CRM/iATS/iATSService.php";
     $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
     $methodType = $isRecur ? 'customer' : 'process';
     $method = $isRecur ? 'create_credit_card_customer' : 'cc';
-    $iats = new iATS_Service_Request(array('type' => $methodType, 'method' => $method, 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+    $iats = new CRM_Iats_iATSServiceRequest(array('type' => $methodType, 'method' => $method, 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
     $request = $this->convertParams($params, $method);
     $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
     $credentials = array(
@@ -153,8 +152,6 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
       $result = $iats->result($response);
       if ($result['status']) {
         // Success.
-        $params['contribution_status_id'] = 1;
-        // For versions >= 4.6.6, the proper key.
         $params['payment_status_id'] = 1;
         $params['trxn_id'] = trim($result['remote_id']) . ':' . time();
         $params['gross_amount'] = $params['amount'];
@@ -165,13 +162,13 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
       }
     }
     else {
-      // Save the client info in my custom table, then (maybe) run the transaction.
+      // Save the customer info in the payment_token table, then (maybe) run the transaction.
       $customer = $iats->result($response, FALSE);
       // print_r($customer);
       if ($customer['status']) {
         $processresult = $response->PROCESSRESULT;
         $customer_code = (string) $processresult->CUSTOMERCODE;
-        $exp = sprintf('%02d%02d', ($params['year'] % 100), $params['month']);
+        $expiry_date = sprintf('%04d-%02d-01', $params['year'], $params['month']);
         $email = '';
         if (isset($params['email'])) {
           $email = $params['email'];
@@ -182,28 +179,60 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
         elseif (isset($params['email-Primary'])) {
           $email = $params['email-Primary'];
         }
-        $query_params = array(
-          1 => array($customer_code, 'String'),
-          2 => array($request['customerIPAddress'], 'String'),
-          3 => array($exp, 'String'),
-          4 => array($params['contactID'], 'Integer'),
-          5 => array($email, 'String'),
-          6 => array($params['contributionRecurID'], 'Integer'),
-        );
-        CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_customer_codes
-          (customer_code, ip, expiry, cid, email, recur_id) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
+        $payment_token_params = [
+          'token' => $customer_code,
+          'ip_address' => $request['customerIPAddress'],
+          'expiry_date' => $expiry_date,
+          'contact_id' => $params['contactID'],
+          'email' => $email,
+          'payment_processor_id' => $this->_paymentProcessor['id'],
+        ];
+        $token_result = civicrm_api3('PaymentToken', 'create', $payment_token_params);
+        // Upon success, save the token table's id back in the recurring record.
+        if (!empty($token_result['id'])) {
+          civicrm_api3('ContributionRecur', 'create', [
+            'id' => $params['contributionRecurID'],
+            'payment_token_id' => $token_result['id'],
+          ]);
+        }
         // Test for admin setting that limits allowable transaction days
         $allow_days = $this->getSettings('days');
-        // Also test for a specific recieve date request that is not today.
-        $receive_date_request = CRM_Utils_Array::value('receive_date', $params);
+        // Test for a specific receive date request and convert to a timestamp, default now
+        $receive_date = CRM_Utils_Array::value('receive_date', $params);
+        // my front-end addition to will get stripped out of the params, do a
+        // work-around
+        if (empty($receive_date)) {
+          $receive_date = CRM_Utils_Array::value('receive_date', $_POST);
+        }
+        $receive_ts = empty($receive_date) ? time() : strtotime($receive_date);
+        // If the admin setting is in force, ensure it's compatible.
+        if (max($allow_days) > 0) {
+          $receive_ts = CRM_Iats_Transaction::contributionrecur_next($receive_ts, $allow_days);
+        }
+        // convert to a reliable format
+        $receive_date = date('Ymd', $receive_ts);
         $today = date('Ymd');
-        // If the receive_date is set to sometime today, unset it.
-        if (!empty($receive_date_request) && 0 === strpos($receive_date_request, $today)) {
-          unset($receive_date_request);
+        // If the receive_date is NOT today, then
+        // create a pending contribution and adjust the next scheduled date.
+        CRM_Core_Error::debug_var('receive_date', $receieve_date);
+        if ($receive_date !== $today) {
+          // I've got a schedule to adhere to!
+          // set the receieve time to 3:00 am for a better admin experience
+          $update = array(
+            'payment_status_id' => 2,
+            'receive_date' => date('Ymd', $receive_ts) . '030000',
+          );
+          // update the recurring and contribution records with the receive date,
+          // i.e. make up for what core doesn't do
+          $this->updateRecurring($params, $update);
+          $this->updateContribution($params, $update);
+          // and now return the updates to core via the params
+          $params = array_merge($params, $update);
+          return $params;
         }
-        // Normally, run the (first) transaction immediately, unless the admin setting is in force or a specific request is being made.
-        if (max($allow_days) <= 0 && empty($receive_date_request)) {
-          $iats = new iATS_Service_Request(array('type' => 'process', 'method' => 'cc_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+        else {
+          // run the (first) transaction immediately
+          $iats = new CRM_Iats_iATSServiceRequest(array('type' => 'process', 'method' => 'cc_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
           $request = array('invoiceNum' => $params['invoiceID']);
           $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount']));
           $request['customerCode'] = $customer_code;
@@ -211,33 +240,22 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
           $response = $iats->request($credentials, $request);
           $result = $iats->result($response);
           if ($result['status']) {
-            // Add a time string to iATS short authentication string to ensure uniqueness and provide helpful referencing.
+            // Add a time string to iATS short authentication string to ensure 
+            // uniqueness and provide helpful referencing.
             $update = array(
               'trxn_id' => trim($result['remote_id']) . ':' . time(),
               'gross_amount' => $params['amount'],
-              'payment_status_id' => '1',
+              'payment_status_id' => 1,
             );
-            // Setting the next_sched_contribution_date param doesn't do anything, commented out, work around in setRecurReturnParams
-            $params = $this->setRecurReturnParams($params, $update);
+            // do some cleanups to the recurring record in updateRecurring
+            $this->updateRecurring($params, $update);
+            $params = array_merge($params, $update);
             return $params;
           }
           else {
             return self::error($result['reasonMessage']);
           }
         }
-        // I've got a schedule to adhere to!
-        else {
-          // Note that the admin general setting restricting allowable days will overwrite any specific request.
-          $next_sched_contribution_timestamp = (max($allow_days) > 0) ? _iats_contributionrecur_next(time(), $allow_days) 
-            : strtotime($params['receive_date']);
-          // set the receieve time to 3:00 am for a better admin experience
-          $update = array(
-            'payment_status_id' => 'Pending',
-            'receive_date' => date('Ymd', $next_sched_contribution_timestamp) . '030000',
-          );
-          $params = $this->setRecurReturnParams($params, $update);
-          return $params;
-        }
         return self::error('Unexpected error');
       }
       else {
@@ -407,44 +425,96 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
    * This functionality will apply to back-end and front-end,
    * so it's only enabled when configured as on via the iATS admin settings.
    * The default isSupported method is overridden above to achieve this.
+   *
+   * Return TRUE on success or an error.
    */
   public function updateSubscriptionBillingInfo(&$message = '', $params = array()) {
-    require_once('CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php');
 
-    $fakeForm = new IATSCustomerUpdateBillingInfo();
-    $fakeForm->updatedBillingInfo = $params;
+    // Fix billing form update bug https://github.com/iATSPayments/com.iatspayments.civicrm/issues/252 by getting crid from _POST
+    if (empty($params['crid'])) {
+      $params['crid'] = !empty($_POST['crid']) ? (int) $_POST['crid'] : (!empty($_GET['crid']) ? (int) $_GET['crid'] : 0);
+      if (empty($params['crid']) && !empty($params['entryURL'])) {
+        $components = parse_url($params['entryURL']); 
+        parse_str(html_entity_decode($components['query']), $entryURLquery); 
+        $params['crid'] = $entryURLquery['crid'];
+      }
+    }
+    // updatedBillingInfo array changed sometime after 4.7.27
+    $crid = !empty($params['crid']) ? $params['crid'] : $params['recur_id'];
+    if (empty($crid)) {
+      $alert = ts('This system is unable to perform self-service updates to credit cards. Please contact the administrator of this site.');
+      throw new Exception($alert);
+    } 
+    $mop = array(
+      'Visa' => 'VISA',
+      'MasterCard' => 'MC',
+      'Amex' => 'AMX',
+      'Discover' => 'DSC',
+    );
+    $contribution_recur = civicrm_api3('ContributionRecur', 'getsingle', ['id' => $crid]);
+    $payment_token = $result = civicrm_api3('PaymentToken', 'getsingle', ['id' => $contribution_recur['payment_token_id']]);
+    // construct the array of data that I'll submit to the iATS Payments server.
+    $state_province = civicrm_api3('StateProvince', 'getsingle', ['return' => ["abbreviation"], 'id' => $params['state_province_id']]);
+    $submit_values = array(
+      'cid' => $contribution_recur['contact_id'],
+      'customerCode' => $payment_token['token'],
+      'creditCardCustomerName' => "{$params['first_name']} " . (!empty($params['middle_name']) ? "{$params['middle_name']} " : '') . $params['last_name'],
+      'address' => $params['street_address'],
+      'city' => $params['city'],
+      'state' => $state_province['abbreviation'],
+      'zipCode' => $params['postal_code'],
+      'creditCardNum' => $params['credit_card_number'],
+      'creditCardExpiry' => sprintf('%02d/%02d', $params['month'], $params['year'] % 100),
+      'mop' => $mop[$params['credit_card_type']],
+    );
+
+    $credentials = CRM_Iats_iATSServiceRequest::credentials($contribution_recur['payment_processor_id'], 0);
+    $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'update_credit_card_customer');
+    $iats = new CRM_Iats_iATSServiceRequest($iats_service_params);
+    $submit_values['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
+    // Make the soap request.
     try {
-      $fakeForm->postProcess();
+      $response = $iats->request($credentials, $submit_values);
+      // note: don't log this to the iats_response table.
+      $iats_result = $iats->result($response, TRUE);
+      // CRM_Core_Error::debug_var('iats result', $iats_result);
+      if ('OK' == $iats_result['AUTHORIZATIONRESULT']) {
+        // Update my copy of the expiry date.
+        $result = civicrm_api3('PaymentToken', 'get', [
+          'return' => ['id'],
+          'token' => $values['customerCode'],
+        ]);
+        if (count($result['values'])) {
+          list($month, $year) = explode('/', $values['creditCardExpiry']);
+          $expiry_date = sprintf('20%02d-%02d-01', $year, $month);
+          foreach(array_keys($result['values']) as $id) {
+            civicrm_api3('PaymentToken', 'create', [
+              'id' => $id,
+              'expiry_date' => $expiry_date,
+            ]);
+          }
+        }
+        return TRUE;
+      }
+      return $this->error('9002','Authorization failed');
     }
     catch (Exception $error) { // what could go wrong? 
       $message = $error->getMessage();
-      CRM_Core_Session::setStatus($message, ts('Warning'), 'alert');
-      $e = CRM_Core_Error::singleton();
-      return $e; 
+      return $this->error('9002', $message);
     }
-    if ('OK' == $fakeForm->getAuthorizationResult()) {
-      return TRUE;
-    }
-    $message = $fakeForm->getResultMessage();
-    CRM_Core_Session::setStatus($message, ts('Warning'), 'alert');
-    $e = CRM_Core_Error::singleton();
-    return $e;
   }
   
   /*
-   * Set the return params for recurring contributions.
+   * Update the recurring contribution record.
    *
-   * Implemented as a function so I can do some cleanup and implement
+   * Do some cleanup and implement
    * the ability to set a future start date for recurring contributions.
    * This functionality will apply to back-end and front-end,
    * As enabled when configured via the iATS admin settings.
    *
-   * This function will alter the recurring schedule as an intended side effect.
-   * and return the modified the params.
+   * Returns result of api request if a change is made, usually ignored.
    */
-  protected function setRecurReturnParams($params, $update) {
-    // Merge in the updates
-    $params = array_merge($params, $update);
+  protected function updateRecurring($params, $update) {
     // If the recurring record already exists, let's fix the next contribution and start dates,
     // in case core isn't paying attention.
     // We also set the schedule to 'in-progress' (even for ACH/EFT when the first one hasn't been verified), 
@@ -470,6 +540,7 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
       }
       try {
         $result = civicrm_api3('ContributionRecur', 'create', $recur_update);
+        return $result;
       }
       catch (CiviCRM_API3_Exception $e) {
         // Not a critical error, just log and continue.
@@ -480,7 +551,33 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment {
     else {
       Civi::log()->info('Unexpectedly unable to update the next scheduled contribution date, missing id.');
     }
-    return $params;
+    return FALSE;
   }
-  
+
+  /*
+   * Update the contribution record.
+   *
+   * This function will alter the civi contribution record.
+   * Implemented only to update the receive date.
+   */
+  protected function updateContribution($params, $update = array()) {
+    if (!empty($params['contributionID'])  && !empty($update['receive_date'])) {
+      $contribution_id = $params['contributionID'];
+      $update = array(
+        'id' => $contribution_id,
+        'receive_date' => $update['receive_date']
+      );
+      try {
+        $result = civicrm_api3('Contribution', 'create', $update);
+        return $result;
+      }
+      catch (CiviCRM_API3_Exception $e) {
+        // Not a critical error, just log and continue.
+        $error = $e->getMessage();
+        Civi::log()->info('Unexpected error updating the contribution date for id {id}: {error}', array('id' => $contribution_id, 'error' => $error));
+      }
+    }
+    return false;
+  }
+
 }
diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php
index a93c32b2e4f1c4e51ecbe37f9893e9bdfa33d919..657ca95a296ddbb600c1a483627cd87d02964cdb 100644
--- a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php
+++ b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php
@@ -19,7 +19,7 @@
  * You should have received a copy of the GNU Affero General Public
  * License with this program; if not, see http://www.gnu.org/licenses/
  *
- * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
+ * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the CRM_Iats_iATSServiceRequest object
  */
 
 /**
@@ -64,6 +64,139 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
     return self::$_singleton[$processorName];
   }
 
+  /**
+   * Get array of fields that should be displayed on the payment form for ACH/EFT (badly named as debit cards).
+   *
+   * @return array
+   */
+
+  protected function getDirectDebitFormFields() {
+    $fields = parent::getDirectDebitFormFields();
+    $fields[] = 'bank_account_type';
+    // print_r($fields); die();
+    return $fields;
+  }
+
+  /**
+   * Return an array of all the details about the fields potentially required for payment fields.
+   *
+   * Only those determined by getPaymentFormFields will actually be assigned to the form
+   *
+   * @return array
+   *   field metadata
+   */
+  public function getPaymentFormFieldsMetadata() {
+    $metadata = parent::getPaymentFormFieldsMetadata();
+    $metadata['bank_account_type'] = [
+      'htmlType' => 'Select',
+      'name' => 'bank_account_type',
+      'title' => ts('Account type'),
+      'is_required' => TRUE,
+      'attributes' => ['CHECKING' => 'Chequing', 'SAVING' => 'Savings'],
+    ];
+    return $metadata;
+  }
+
+
+  /**
+   * Opportunity for the payment processor to override the entire form build.
+   *
+   * @param CRM_Core_Form $form
+   *
+   * @return bool
+   *   Should form building stop at this point?
+   *
+   * Add ACH/EFT per currency instructions, also do parent (cc) form building to allow future
+   * recurring on public pages.
+   *
+   * return (!empty($form->_paymentFields));
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public function buildForm(&$form) {
+    // If a form allows ACH/EFT and enables recurring, set recurring to the default. 
+    if (isset($form->_elementIndex['is_recur'])) {
+      // Make recurring contrib default to true.
+      $form->setDefaults(array('is_recur' => 1));
+    }
+    $currency = iats_getCurrency($form);
+    // my javascript will (should, not yet) use the currency to rewrite some labels
+    $jsVariables = [
+      'currency' => $currency,
+    ];
+    CRM_Core_Resources::singleton()->addVars('iats', $jsVariables);
+    CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/dd_acheft.js', 10);
+    // add in a billing block template in a currency dependent way.
+    $fname = 'buildForm_' . $currency;
+    if ($currency && method_exists($this,$fname)) {
+      // add in the common fields and rules first to allow modifications
+      //$this->addCommonFields($form, $form->_paymentFields);
+      //$this->addRules($form, $form->_paymentFields);
+      $this->$fname($form);
+    }
+    // Else, I'm handling an unexpected currency.
+    elseif ($currency) {
+      CRM_Core_Region::instance('billing-block')->add(array(
+        'template' => 'CRM/Iats/BillingBlockDirectDebitExtra_Other.tpl',
+      ));
+    }
+    return parent::buildForm($form);
+  }
+
+
+  /**
+   * Customization for USD ACH-EFT billing block.
+   */
+  protected function buildForm_USD(&$form) {
+    /*
+    $element = $form->getElement('account_holder');
+    $element->setLabel(ts('Name of Account Holder'));
+    $element = $form->getElement('bank_account_number');
+    $element->setLabel(ts('Bank Account Number'));
+    $element = $form->getElement('bank_identification_number');
+    $element->setLabel(ts('Bank Routing Number')); */
+    /* if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
+      $form->addRule('bank_identification_number', ts('%1 is a required field.', array(1 => ts('Bank Routing Number'))), 'required');
+  } */
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'template' => 'CRM/Iats/BillingBlockDirectDebitExtra_USD.tpl',
+    ));
+  }
+  
+  /**
+   * Customization for CAD ACH-EFT billing block.
+   *
+   * Add some elements (bank number and transit number) that are used to
+   * generate the bank identification number, which is hidden.
+   * I can't do this in the usual way because it's currency-specific.
+   * Note that this is really just an interface convenience, the ACH/EFT
+   * North American interbank system is consistent across US and Canada.
+   */
+  protected function buildForm_CAD(&$form) {
+    $form->addElement('text', 'cad_bank_number', ts('Bank Number (3 digits)'));
+    $form->addRule('cad_bank_number', ts('%1 is a required field.', array(1 => ts('Bank Number'))), 'required');
+    $form->addRule('cad_bank_number', ts('%1 must contain only digits.', array(1 => ts('Bank Number'))), 'numeric');
+    $form->addRule('cad_bank_number', ts('%1 must be of length 3.', array(1 => ts('Bank Number'))), 'rangelength', array(3, 3));
+    $form->addElement('text', 'cad_transit_number', ts('Transit Number (5 digits)'));
+    $form->addRule('cad_transit_number', ts('%1 is a required field.', array(1 => ts('Transit Number'))), 'required');
+    $form->addRule('cad_transit_number', ts('%1 must contain only digits.', array(1 => ts('Transit Number'))), 'numeric');
+    $form->addRule('cad_transit_number', ts('%1 must be of length 5.', array(1 => ts('Transit Number'))), 'rangelength', array(5, 5));
+    /* minor customization of labels + make them required  */
+    /* $element = $form->getElement('account_holder');
+    $element->setLabel(ts('Name of Account Holder'));
+    $element = $form->getElement('bank_account_number');
+    $element->setLabel(ts('Account Number'));
+    $form->addRule('bank_account_number', ts('%1 must contain only digits.', array(1 => ts('Bank Account Number'))), 'numeric'); */
+    /* the bank_identification_number is hidden and then populated using jquery, in the custom template  */
+    /* $element = $form->getElement('bank_identification_number');
+    $element->setLabel(ts('Bank Number + Transit Number')); */
+    // print_r($form); die();
+    CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/dd_cad.js', 10);
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'template' => 'CRM/Iats/BillingBlockDirectDebitExtra_CAD.tpl',
+    ));
+  }
+
+
   /**
    *
    */
@@ -73,12 +206,11 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
       return self::error('Unexpected error, missing profile');
     }
     // Use the iATSService object for interacting with iATS, mostly the same for recurring contributions.
-    require_once "CRM/iATS/iATSService.php";
     // We handle both one-time and recurring ACH/EFT
     $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
     $methodType = $isRecur ? 'customer' : 'process';
     $method = $isRecur ? 'create_acheft_customer_code' : 'acheft';
-    $iats = new iATS_Service_Request(array('type' => $methodType, 'method' => $method, 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+    $iats = new CRM_Iats_iATSServiceRequest(array('type' => $methodType, 'method' => $method, 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
     $request = $this->convertParams($params, $method);
     $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
     $credentials = array(
@@ -91,9 +223,6 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
       // Process the soap response into a readable result, logging any transaction.
       $result = $iats->result($response);
       if ($result['status']) {
-        // Always set pending status.
-        $params['contribution_status_id'] = 2;
-        // For future versions, the proper key.
         $params['payment_status_id'] = 2;
         $params['trxn_id'] = trim($result['remote_id']) . ':' . time();
         $params['gross_amount'] = $params['amount'];
@@ -116,7 +245,7 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
       }
     }
     else {
-      // Save the client info in my custom table
+      // Save the customer info in to the CiviCRM core payment_token table
       $customer = $iats->result($response);
       if (!$customer['status']) {
         return self::error($customer['reasonMessage']);
@@ -124,8 +253,6 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
       else {
         $processresult = $response->PROCESSRESULT;
         $customer_code = (string) $processresult->CUSTOMERCODE;
-        // $exp = sprintf('%02d%02d', ($params['year'] % 100), $params['month']);.
-        $exp = '0000';
         $email = '';
         if (isset($params['email'])) {
           $email = $params['email'];
@@ -136,27 +263,57 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
         elseif (isset($params['email-Primary'])) {
           $email = $params['email-Primary'];
         }
-        $query_params = array(
-          1 => array($customer_code, 'String'),
-          2 => array($request['customerIPAddress'], 'String'),
-          3 => array($exp, 'String'),
-          4 => array($params['contactID'], 'Integer'),
-          5 => array($email, 'String'),
-          6 => array($params['contributionRecurID'], 'Integer'),
-        );
-        CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_customer_codes
-          (customer_code, ip, expiry, cid, email, recur_id) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
+        $payment_token_params = [
+          'token' => $customer_code,
+          'ip_address' => $request['customerIPAddress'],
+          'contact_id' => $params['contactID'],
+          'email' => $email,
+          'payment_processor_id' => $this->_paymentProcessor['id'],
+        ];
+        $token_result = civicrm_api3('PaymentToken', 'create', $payment_token_params);
+        // Upon success, save the token table's id back in the recurring record.
+        if (!empty($token_result['id'])) {
+          civicrm_api3('ContributionRecur', 'create', [
+            'id' => $params['contributionRecurID'],
+            'payment_token_id' => $token_result['id'],
+          ]);
+        }
+        // Test for admin setting that limits allowable transaction days
         $allow_days = $this->getSettings('days');
-        // Also test for a specific recieve date request that is not today.
-        $receive_date_request = CRM_Utils_Array::value('receive_date', $params);
+        // Test for a specific receive date request and convert to a timestamp, default now
+        $receive_date = CRM_Utils_Array::value('receive_date', $params);
+        // my front-end addition to will get stripped out of the params, do a
+        // work-around
+        if (empty($receive_date)) {
+          $receive_date = CRM_Utils_Array::value('receive_date', $_POST);
+        }
+        $receive_ts = empty($receive_date) ? time() : strtotime($receive_date);
+        // If the admin setting is in force, ensure it's compatible.
+        if (max($allow_days) > 0) {
+          $receive_ts = CRM_Iats_Transaction::contributionrecur_next($receive_ts, $allow_days);
+        }
+        // convert to a reliable format
+        $receive_date = date('Ymd', $receive_ts);
         $today = date('Ymd');
-        // If the receive_date is set to sometime today, unset it.
-        if (!empty($receive_date_request) && 0 === strpos($receive_date_request, $today)) {
-          unset($receive_date_request);
+        // If the receive_date is NOT today, then
+        // create a pending contribution and adjust the next scheduled date.
+        if ($receive_date !== $today) {
+          // I've got a schedule to adhere to!
+          // set the receieve time to 3:00 am for a better admin experience
+          $update = array(
+            'payment_status_id' => 2,
+            'receive_date' => date('Ymd', $receive_ts) . '030000',
+          );
+          // update the recurring and contribution records with the receive date,
+          // i.e. make up for what core doesn't do
+          $this->updateRecurring($params, $update);
+          $this->updateContribution($params, $update);
+          // and now return the updates to core via the params
+          $params = array_merge($params, $update);
+          return $params;
         }
-        // Normally, run the (first) transaction immediately, unless the admin setting is in force or a specific request is being made.
-        if (max($allow_days) <= 0 && empty($receive_date_request)) {
-          $iats = new iATS_Service_Request(array('type' => 'process', 'method' => 'acheft_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
+        else {
+          $iats = new CRM_Iats_iATSServiceRequest(array('type' => 'process', 'method' => 'acheft_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
           $request = array('invoiceNum' => $params['invoiceID']);
           $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount']));
           $request['customerCode'] = $customer_code;
@@ -168,10 +325,12 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
             $update = array(
               'trxn_id' => trim($result['remote_id']) . ':' . time(),
               'gross_amount' => $params['amount'],
-              'payment_status_id' => 2
+              'payment_status_id' => 2,
             );
-            // Setting the next_sched_contribution_date param setting is not doing anything, commented out.
-            $this->setRecurReturnParams($params, $update);
+            // Setting the next_sched_contribution_date param doesn't do anything, 
+            // work around in updateRecurring
+            $this->updateRecurring($params, $update);
+            $params = array_merge($params, $update);
             // Core assumes that a pending result will have no transaction id, but we have a useful one.
             if (!empty($params['contributionID'])) {
               $contribution_update = array('id' => $params['contributionID'], 'trxn_id' => $update['trxn_id']);
@@ -190,20 +349,6 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService {
             return self::error($result['reasonMessage']);
           }
         }
-        // Otherwise, I have a schedule to adhere to.
-        else {    
-          // Note that the admin general setting restricting allowable days may update a specific request.
-          $receive_timestamp = empty($receive_date_request) ? time() : strtotime($receive_date_request);
-          $next_sched_contribution_timestamp = (max($allow_days) > 0) ? _iats_contributionrecur_next($receive_timestamp, $allow_days) 
-            : $receive_timestamp;
-          // set the receieve time to 3:00 am for a better admin experience
-          $update = array(
-            'payment_status_id' => 2,
-            'receive_date' => date('Ymd', $next_sched_contribution_timestamp) . '030000',
-          );
-          $this->setRecurReturnParams($params, $update);
-          return $params;
-        }
       }
       return $params;
     }
diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceSWIPE.php b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceSWIPE.php
index decbcdd0a7b353a4c2c631adb83ef2bc22745561..af808cdb385d2f3a10efc0bdfcb344a9b79b97d8 100644
--- a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceSWIPE.php
+++ b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceSWIPE.php
@@ -18,7 +18,7 @@
  * You should have received a copy of the GNU Affero General Public
  * License with this program; if not, see http://www.gnu.org/licenses/
  *
- * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
+ * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the CRM_Iats_iATSServiceRequest object
  */
 
 /**
@@ -73,4 +73,67 @@ class CRM_Core_Payment_iATSServiceSWIPE extends CRM_Core_Payment_iATSService {
     // Override the default and don't do any validation because my values are encrypted.
   }
 
+  /**
+   * Get array of fields that should be displayed on the payment form for credit cards.
+   * Replace cvv and card type fields with (hidden) swipe field.
+   *
+   * @return array
+   */
+
+  protected function getCreditCardFormFields() {
+    return [
+      'credit_card_number',
+      'credit_card_exp_date',
+      'encrypted_credit_card_number'
+    ];
+  }
+
+  /**
+   * Return an array of all the details about the fields potentially required for payment fields.
+   *
+   * Only those determined by getPaymentFormFields will actually be assigned to the form
+   *
+   * @return array
+   *   field metadata
+   */
+  public function getPaymentFormFieldsMetadata() {
+    $metadata = parent::getPaymentFormFieldsMetadata();
+    $metadata['encrypted_credit_card_number'] = [
+        'htmlType' => 'textarea',
+        'name' => 'encrypted_credit_card_number',
+        'title' => ts('Encrypted Credit Card Details'),
+        'is_required' => TRUE,
+        'attributes' => [
+          'cols' => 80,
+          'rows' => 8,
+          'autocomplete' => 'off',
+          'id' => 'encrypted_credit_card_number',
+        ],
+      ];
+    return $metadata;
+  }
+
+
+  /**
+   * Opportunity for the payment processor to override the entire form build.
+   *
+   * @param CRM_Core_Form $form
+   *
+   * @return bool
+   *   Should form building stop at this point?
+   *
+   * Add SWIPE instructions, also do parent (non-swipe) form building.
+   *
+   * return (!empty($form->_paymentFields));
+   * @throws \Civi\Payment\Exception\PaymentProcessorException
+   */
+  public function buildForm(&$form) {
+    CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/swipe.js', 10);
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'template' => 'CRM/Iats/BillingBlockSwipe.tpl',
+    ));
+    return parent::buildForm($form);
+  }
+
+
 }
diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php
deleted file mode 100644
index 16d827ce2c5d8f37835e83b9e459b12117acd88c..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php
+++ /dev/null
@@ -1,341 +0,0 @@
-<?php
-
-/**
- * @file Copyright iATS Payments (c) 2014.
- * @author Alan Dixon
- *
- * This file is a part of CiviCRM published extension.
- *
- * This extension is free software; you can copy, modify, and distribute it
- * under the terms of the GNU Affero General Public License
- * Version 3, 19 November 2007.
- *
- * It is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public
- * License with this program; if not, see http://www.gnu.org/licenses/
- *
- * This code provides glue between CiviCRM payment model and the iATS Payment model encapsulated in the iATS_Service_Request object
- * for UK Direct Debit Recurring contributions ONLY
- */
-
-/**
- *
- */
-class CRM_Core_Payment_iATSServiceUKDD extends CRM_Core_Payment {
-
-  /**
-   * We only need one instance of this object. So we use the singleton
-   * pattern and cache the instance in this variable.
-   *
-   * @var object
-   * @static
-   */
-  static private $_singleton = NULL;
-
-  /**
-   * Constructor.
-   *
-   * @param string $mode
-   *   the mode of operation: live or test.
-   *
-   * @return void
-   */
-  public function __construct($mode, &$paymentProcessor) {
-    $this->_paymentProcessor = $paymentProcessor;
-    $this->_processorName = ts('iATS Payments UK Direct Debit');
-
-    // Get merchant data from config.
-    $config = CRM_Core_Config::singleton();
-    // Live or test.
-    $this->_profile['mode'] = $mode;
-    // We only use the domain of the configured url, which is different for NA vs. UK.
-    $this->_profile['iats_domain'] = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
-  }
-
-  /**
-   *
-   */
-  static public function &singleton($mode, &$paymentProcessor, &$paymentForm = NULL, $force = FALSE) {
-    $processorName = $paymentProcessor['name'];
-    if (self::$_singleton[$processorName] === NULL) {
-      self::$_singleton[$processorName] = new CRM_Core_Payment_iATSServiceUKDD($mode, $paymentProcessor);
-    }
-    return self::$_singleton[$processorName];
-  }
-
-  /**
-   * Function checkParams.
-   */
-  public function checkParams($params) {
-    if (!$this->_profile) {
-      return self::error('Unexpected error, missing profile');
-    }
-    $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID'];
-    if (!$isRecur) {
-      return self::error('Not a recurring contribution: you can only use UK Direct Debit with a recurring contribution.');
-    }
-    if ('GBP' != $params['currencyID']) {
-      return self::error(ts('Invalid currency %1, must by GBP', array(1 => $params['currencyID'])));
-    }
-    if (empty($params['installments'])) {
-      return self::error(ts('You must specify the number of installments, open-ended contributions are not allowed.'));
-    }
-    elseif (1 >= $params['installments']) {
-      return self::error(ts('You must specify a number of installments greater than 1.'));
-    }
-  }
-
-  /**
-   *
-   */
-  public function getSchedule($params) {
-    // Convert params recurring information into iATS equivalents.
-    $scheduleType = NULL;
-    $paymentsRecur = $params['installments'] - 1;
-    // IATS requires begin and end date, calculated here
-    // to be converted to date format later
-    // begin date has to be more than 12 days from now, not checked here.
-    $beginTime = strtotime($beginDate = $params['payer_validate_start_date']);
-    $date = getdate($beginTime);
-    $interval = $params['frequency_interval'] ? $params['frequency_interval'] : 1;
-    switch ($params['frequency_unit']) {
-      case 'week':
-        if (1 != $interval) {
-          return self::error(ts('You can only choose each week on a weekly schedule.'));
-        }
-        $scheduleType = 'Weekly';
-        $scheduleDate = $date['wday'] + 1;
-        $endTime      = $beginTime + ($paymentsRecur * 7 * 24 * 60 * 60);
-        break;
-
-      case 'month':
-        $scheduleType = 'Monthly';
-        $scheduleDate = $date['mday'];
-        if (3 == $interval) {
-          $scheduleType = 'Quarterly';
-          $scheduleDate = '';
-        }
-        elseif (1 != $interval) {
-          return self::error(ts('You can only choose monthly or every three months (quarterly) for a monthly schedule.'));
-        }
-        $date['mon'] += ($interval * $paymentsRecur);
-        while ($date['mon'] > 12) {
-          $date['mon'] -= 12;
-          $date['year'] += 1;
-        }
-        $endTime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
-        break;
-
-      case 'year':
-        if (1 != $interval) {
-          return self::error(ts('You can only choose each year for a yearly schedule.'));
-        }
-        $scheduleType = 'Yearly';
-        $scheduleDate = '';
-        $date['year'] += $paymentsRecur;
-        $endTime = mktime($date['hours'], $date['minutes'], $date['seconds'], $date['mon'], $date['mday'], $date['year']);
-        break;
-
-      default:
-        return self::error(ts('Invalid frequency unit: %1', array(1 => $params['frequency_unit'])));
-      break;
-
-    }
-    $endDate = date('c', $endTime);
-    $beginDate = date('c', $beginTime);
-    return array('scheduleType' => $scheduleType, 'scheduleDate' => $scheduleDate, 'endDate' => $endDate, 'beginDate' => $beginDate);
-  }
-
-  /**
-   *
-   */
-  public function doDirectPayment(&$params) {
-    $error = $this->checkParams($params);
-    if (!empty($error)) {
-      return $error;
-    }
-    // $params['start_date'] = $params['receive_date'];
-    // use the iATSService object for interacting with iATS.
-    require_once "CRM/iATS/iATSService.php";
-    $iats = new iATS_Service_Request(array('type' => 'customer', 'method' => 'direct_debit_create_acheft_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID']));
-    $schedule = $this->getSchedule($params);
-    // Assume an error object to return.
-    if (!is_array($schedule)) {
-      return $schedule;
-    }
-    $request = array_merge($this->convertParamsCreateCustomerCode($params), $schedule);
-    $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
-    $request['customerCode'] = '';
-    $request['accountType'] = 'CHECKING';
-    $credentials = array(
-      'agentCode' => $this->_paymentProcessor['user_name'],
-      'password'  => $this->_paymentProcessor['password'],
-    );
-    // Get the API endpoint URL for the method's transaction mode.
-    // TODO: enable override of the default url in the request object
-    // $url = $this->_paymentProcessor['url_site'];.
-    // Make the soap request.
-    $response = $iats->request($credentials, $request);
-    // Process the soap response into a readable result.
-    $result = $iats->result($response);
-    // drupal_set_message('<pre>'.print_r($result,TRUE).'</pre>');.
-    if ($result['status']) {
-      // Always pending.
-      $params['contribution_status_id'] = 2;
-      // For future versions, the proper key.
-      $params['payment_status_id'] = 2;
-      $params['trxn_id'] = trim($result['remote_id']) . ':' . time();
-      $params['gross_amount'] = $params['amount'];
-      // Save the client info in my custom table
-      // Allow further manipulation of the arguments via custom hooks,.
-      $customer_code = $result['CUSTOMERCODE'];
-      if (isset($params['email'])) {
-        $email = $params['email'];
-      }
-      elseif (isset($params['email-5'])) {
-        $email = $params['email-5'];
-      }
-      elseif (isset($params['email-Primary'])) {
-        $email = $params['email-Primary'];
-      }
-      $query_params = array(
-        1 => array($customer_code, 'String'),
-        2 => array($request['customerIPAddress'], 'String'),
-        3 => array('', 'String'),
-        4 => array($params['contactID'], 'Integer'),
-        5 => array($email, 'String'),
-        6 => array($params['contributionRecurID'], 'Integer'),
-      );
-      // drupal_set_message('<pre>'.print_r($query_params,TRUE).'</pre>');.
-      CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_customer_codes
-        (customer_code, ip, expiry, cid, email, recur_id) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
-      // Save their payer validation data in civicrm_iats_ukdd_validate.
-      $query_params = array(
-        1 => array($customer_code, 'String'),
-        2 => array($params['payer_validate_reference'], 'String'),
-        3 => array($params['contactID'], 'Integer'),
-        4 => array($params['contributionRecurID'], 'Integer'),
-        5 => array($params['payer_validate_declaration'], 'Integer'),
-        6 => array(date('c'), 'String'),
-      );
-      // drupal_set_message('<pre>'.print_r($query_params,TRUE).'</pre>');.
-      CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_ukdd_validate
-        (customer_code, acheft_reference_num, cid, recur_id, validated, validated_datetime) VALUES (%1, %2, %3, %4, %5, %6)", $query_params);
-      // Set the status of the initial contribution to pending (currently is redundant), and the date to what I'm asking iATS for.
-      $params['contribution_status_id'] = 2;
-      $params['start_date'] = $params['payer_validate_start_date'];
-      // Optimistically set this date, even though CiviCRM will likely not do anything with it yet - I'll change it with my pre hook in the meanwhile
-      // $params['receive_date'] = strtotime($params['payer_validate_start_date']);
-      // also set next_sched_contribution, though it won't be used.
-      $params['next_sched_contribution'] = strtotime($params['payer_validate_start_date'] . ' + ' . $params['frequency_interval'] . ' ' . $params['frequency_unit']);
-      return $params;
-    }
-    else {
-      return self::error($result['reasonMessage']);
-    }
-  }
-
-  /**
-   * TODO: requires custom link
-   * function changeSubscriptionAmount(&$message = '', $params = array()) {
-   * $userAlert = ts('You have updated the amount of this recurring contribution.');
-   * CRM_Core_Session::setStatus($userAlert, ts('Warning'), 'alert');
-   * return TRUE;
-   * } .
-   */
-  public function &error($error = NULL) {
-    $e = CRM_Core_Error::singleton();
-    if (is_object($error)) {
-      $e->push($error->getResponseCode(),
-        0, NULL,
-        $error->getMessage()
-      );
-    }
-    elseif ($error && is_numeric($error)) {
-      $e->push($error,
-        0, NULL,
-        $this->errorString($error)
-      );
-    }
-    elseif (is_string($error)) {
-      $e->push(9002,
-        0, NULL,
-        $error
-      );
-    }
-    else {
-      $e->push(9001, 0, NULL, "Unknown System Error.");
-    }
-    return $e;
-  }
-
-  /**
-   * This function checks to see if we have the right config values.
-   *
-   * @param string $mode
-   *   the mode we are operating in (live or test)
-   *
-   * @return string the error message if any
-   *
-   * @public
-   */
-  public function checkConfig() {
-    $error = array();
-
-    if (empty($this->_paymentProcessor['user_name'])) {
-      $error[] = ts('Agent Code is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
-    }
-
-    if (empty($this->_paymentProcessor['password'])) {
-      $error[] = ts('Password is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
-    }
-    if (empty($this->_paymentProcessor['signature'])) {
-      $error[] = ts('Service User Number (SUN) is not set in the Administer CiviCRM &raquo; System Settings &raquo; Payment Processors.');
-    }
-    $iats_domain = parse_url($this->_paymentProcessor['url_site'], PHP_URL_HOST);
-    if ('www.uk.iatspayments.com' != $iats_domain) {
-      $error[] = ts('You can only use this payment processor with a UK iATS account');
-    }
-    if (!empty($error)) {
-      return implode('<p>', $error);
-    }
-    else {
-      return NULL;
-    }
-  }
-
-  /**
-   * Convert the values in the civicrm params to the request array with keys as expected by iATS.
-   */
-  public function convertParamsCreateCustomerCode($params) {
-    $request = array();
-    $convert = array(
-      'firstName' => 'billing_first_name',
-      'lastName' => 'billing_last_name',
-      'address' => 'street_address',
-      'city' => 'city',
-      'state' => 'state_province',
-      'zipCode' => 'postal_code',
-      'country' => 'country',
-      'ACHEFTReferenceNum' => 'payer_validate_reference',
-      'accountCustomerName' => 'account_holder',
-      'email' => 'email',
-      'recurring' => 'is_recur',
-      'amount' => 'amount',
-    );
-
-    foreach ($convert as $r => $p) {
-      if (isset($params[$p])) {
-        $request[$r] = $params[$p];
-      }
-    }
-    // Account custom name is first name + last name, truncated to a maximum of 30 chars.
-    $request['accountNum'] = trim($params['bank_identification_number']) . trim($params['bank_account_number']);
-    return $request;
-  }
-
-}
diff --git a/civicrm/ext/iatspayments/CRM/Iats/FapsRequest.php b/civicrm/ext/iatspayments/CRM/Iats/FapsRequest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2542fdd9be502a8389c06a3d7cf607ef9a0fe904
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Iats/FapsRequest.php
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file iATSPayments FAPS Request object.
+ *
+ * A lightweight object that encapsulates the details of the iATS Payments FAPS interface.
+ *
+ * Provides the REST interface details, replacing the official "gateway.php" version on github
+ *
+ * Require the method string on construction and any options like trace, logging.
+ * Require the specific payment details, and the client credentials, on request
+ *
+ * TODO: provide logging options for the request, exception and response
+ *
+ * Expected usage:
+ * $faps = new CRM_Iats_FapsRequest($options)
+ * where options usually include
+ *   action: one of the API actions
+ *   category: 'Transactions', 'Ach', or 'Vault'
+ *   test: set to anything non-empty for testing
+ * $result = $faps->request($credentials, $request_params)
+ * 
+ **/
+
+/**
+ * Define a utility class required by FapsRequest 
+ * Should likely be in a namespace.
+ */
+
+class Faps_Transaction implements JsonSerializable {
+  /**
+  * Transaction class: Ties into the PHP JSON Functions & makes them easily available to the CRM_Iats_FapsRequest class.
+  * Using the class like so: $a = json_encode(new Faps_Transaction($txnarray), JSON_PRETTY_PRINT)
+  * Will produce json data that the gateway should understand.
+  */
+  public function __construct(array $array) {
+    $this->array = $array;
+  }
+  public function jsonSerialize() {
+    return $this->array;
+  }
+}
+
+/**
+ *
+ */
+class CRM_Iats_FapsRequest {
+
+  const DEBUG = false;
+  public $result = array();
+  public $status = "";
+  private $liveUrl = "https://secure.1stpaygateway.net/secure/RestGW/Gateway/Transaction/";
+  private $testUrl = "https://secure-v.goemerchant.com/secure/RestGW/Gateway/Transaction/";
+
+  /**
+   *
+   */
+  public function __construct($options) {
+    // category not yet checked/validated/used
+    // $this->category = $options['category'];
+    // TODO: verify action is valid and in category
+    $this->action = $options['action'];
+    $this->apiRequest = (empty($options['test']) ? $this->liveUrl : $this->testUrl ) . $this->action;
+  }
+
+  public function request($credentials, $request_params, $log_failure = TRUE) {
+    if (self::DEBUG) {
+      CRM_Core_Error::debug_var('Credentials', $credentials);
+      CRM_Core_Error::debug_var('Request Params', $request_params);
+      CRM_Core_Error::debug_var('Transaction Type', $this->action);
+      CRM_Core_Error::debug_var('Request URL', $this->apiRequest);
+    }
+    $data = array_merge($credentials, $request_params);
+    try {
+      if ($data == NULL) {
+        $data = array(); 
+      }
+      $url = $this->apiRequest;
+      $this->result = array();
+      $jsondata = json_encode(new Faps_Transaction($data), JSON_PRETTY_PRINT);
+      $jsondata = utf8_encode($jsondata);
+      // CRM_Core_Error::debug_var('jsondata', $jsondata);
+      $curl_handle = curl_init();
+      curl_setopt($curl_handle, CURLOPT_URL, $url);
+      curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, "POST");
+      curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $jsondata);
+      curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, TRUE);
+      curl_setopt($curl_handle, CURLOPT_HTTPHEADER, array(
+        "Content-type: application/json; charset-utf-8",
+        "Content-Length: " . strlen($jsondata)
+      ));
+      curl_setopt($curl_handle, CURLOPT_SSL_VERIFYPEER, FALSE);
+      $this->response = curl_exec($curl_handle);
+      if (self::DEBUG) {
+        CRM_Core_Error::debug_var('JSON Response', $this->response);
+      }
+      $this->status = curl_getinfo($curl_handle,CURLINFO_HTTP_CODE);
+      if (connection_aborted()) {
+        // handle aborted requests that PHP can detect, returning a result that indicates POST was aborted.
+        $this->result = array(
+          "isError" => TRUE,
+          "errorMessages" => "Request Aborted",
+          "isValid" => FALSE,
+          "validations" => array(),
+          "action" => "gatewayError"
+        );
+      }
+      elseif (curl_errno($curl_handle) == 28 ){
+        //This will handle timeouts as per cURL error definitions.
+        $this->result = array(
+          "isError" => TRUE,
+          "errorMessages" => "Request Timed Out",
+          "isValid" => FALSE,
+          "validations" => array(),
+          "action" => "gatewayError"
+        );
+      }
+      else {
+        // CRM_Core_Error::debug_var('Response', $this->response);
+        $this->result = json_decode($this->response, TRUE);
+        if (empty($this->result['isSuccess'])  && $log_failure) {
+          CRM_Core_Error::debug_var('FAPS transaction failure result', $this->result);
+          // $this->result['errorMessages'] = $this->result['data']['authResponse'];
+        } 
+      }
+      return $this->result;
+    }
+    catch (Exception $e){
+      CRM_Core_Error::debug_var('Exception on request', $e);
+      return $e->getMessage();
+    }
+  }
+}
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerLink.php b/civicrm/ext/iatspayments/CRM/Iats/Form/IATSCustomerLink.php
similarity index 87%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerLink.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/IATSCustomerLink.php
index 39e22b9249a60fbe818119c18ce440900e3a95a2..f38b1d085016957431407c264b139d08be44499a 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerLink.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/IATSCustomerLink.php
@@ -11,7 +11,7 @@ require_once 'CRM/Core/Form.php';
  *
  * @see http://wiki.civicrm.org/confluence/display/CRMDOC43/QuickForm+Reference
  */
-class CRM_iATS_Form_IATSCustomerLink extends CRM_Core_Form {
+class CRM_Iats_Form_IATSCustomerLink extends CRM_Core_Form {
 
   private $iats_result = array();
 
@@ -64,10 +64,9 @@ class CRM_iATS_Form_IATSCustomerLink extends CRM_Core_Form {
    *
    */
   protected function getCustomerCodeDetail($params) {
-    require_once "CRM/iATS/iATSService.php";
-    $credentials = iATS_Service_Request::credentials($params['paymentProcessorId'], $params['is_test']);
+    $credentials = CRM_Iats_iATSServiceRequest::credentials($params['paymentProcessorId'], $params['is_test']);
     $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'get_customer_code_detail');
-    $iats = new iATS_Service_Request($iats_service_params);
+    $iats = new CRM_Iats_iATSServiceRequest($iats_service_params);
     // print_r($iats); die();
     $request = array('customerCode' => $params['customerCode']);
     // Make the soap request.
@@ -88,13 +87,12 @@ class CRM_iATS_Form_IATSCustomerLink extends CRM_Core_Form {
    *
    */
   protected function updateCreditCardCustomer($params) {
-    require_once "CRM/iATS/iATSService.php";
-    $credentials = iATS_Service_Request::credentials($params['paymentProcessorId'], $params['is_test']);
+    $credentials = CRM_Iats_iATSServiceRequest::credentials($params['paymentProcessorId'], $params['is_test']);
     unset($params['paymentProcessorId']);
     unset($params['is_test']);
     unset($params['domain']);
     $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'update_credit_card_customer');
-    $iats = new iATS_Service_Request($iats_service_params);
+    $iats = new CRM_Iats_iATSServiceRequest($iats_service_params);
     // print_r($iats); die();
     $params['updateCreditCardNum'] = (0 < strlen($params['creditCardNum']) && (FALSE === strpos($params['creditCardNum'], '*'))) ? 1 : 0;
     if (empty($params['updateCreditCardNum'])) {
@@ -199,13 +197,20 @@ class CRM_iATS_Form_IATSCustomerLink extends CRM_Core_Form {
     CRM_Core_Session::setStatus($this->getResultMessage(), 'Card Update Result');
     if ('OK' == $this->getAuthorizationResult()) {
       // Update my copy of the expiry date.
-      list($month, $year) = explode('/', $values['creditCardExpiry']);
-      $exp = sprintf('%02d%02d', $year, $month);
-      $query_params = array(
-        1 => array($values['customerCode'], 'String'),
-        2 => array($exp, 'String'),
-      );
-      CRM_Core_DAO::executeQuery("UPDATE civicrm_iats_customer_codes SET expiry = %2 WHERE customer_code = %1", $query_params);
+      $result = civicrm_api3('PaymentToken', 'get', [
+        'return' => ['id'],
+        'token' => $values['customerCode'],
+      ]);
+      if (count($result['values'])) {
+        list($month, $year) = explode('/', $values['creditCardExpiry']);
+        $expiry_date = sprintf('20%02d-%02d-01', $year, $month);
+        foreach(array_keys($result['values']) as $id) {
+          civicrm_api3('PaymentToken', 'create', [
+            'id' => $id,
+            'expiry_date' => $expiry_date,
+          ]);
+        }
+      }
     }
     parent::postProcess();
   }
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/IATSOneTimeCharge.php b/civicrm/ext/iatspayments/CRM/Iats/Form/IATSOneTimeCharge.php
similarity index 71%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/IATSOneTimeCharge.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/IATSOneTimeCharge.php
index f4a26a4d0d831212bdf3affc30705b56177bec49..8e0eab8ad3c18b90327723a2a317db792aca1105 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/IATSOneTimeCharge.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/IATSOneTimeCharge.php
@@ -5,14 +5,14 @@
  */
 
 require_once 'CRM/Core/Form.php';
-
+use CRM_Iats_ExtensionUtil as E;
 /**
  * Form controller class.
  *
  * @see http://wiki.civicrm.org/confluence/display/CRMDOC43/QuickForm+Reference
  * A form to generate new one-time charges on an existing recurring schedule.
  */
-class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
+class CRM_Iats_Form_IATSOneTimeCharge extends CRM_Core_Form {
 
   /**
    *
@@ -60,10 +60,9 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
    *
    */
   protected function getCustomerCodeDetail($params) {
-    require_once "CRM/iATS/iATSService.php";
-    $credentials = iATS_Service_Request::credentials($params['paymentProcessorId'], $params['is_test']);
+    $credentials = CRM_Iats_iATSServiceRequest::credentials($params['paymentProcessorId'], $params['is_test']);
     $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'get_customer_code_detail');
-    $iats = new iATS_Service_Request($iats_service_params);
+    $iats = new CRM_Iats_iATSServiceRequest($iats_service_params);
     // print_r($iats); die();
     $request = array('customerCode' => $params['customerCode']);
     // Make the soap request.
@@ -72,7 +71,7 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
     $customer = $iats->result($response, FALSE);
     // print_r($customer); die();
     if (empty($customer['ac1'])) {
-      $alert = ts('Unable to retrieve card details from iATS.<br />%1', array(1 => $customer['AUTHORIZATIONRESULT']));
+      $alert = E::ts('Unable to retrieve card details from iATS.<br />%1', array(1 => $customer['AUTHORIZATIONRESULT']));
       throw new Exception($alert);
     }
     // This is a SimpleXMLElement Object.
@@ -88,7 +87,7 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
     // Generate another (possibly) recurring contribution, matching our recurring template with submitted value.
     $is_recurrence = !empty($values['is_recurrence']);
     $total_amount = $values['amount'];
-    $contribution_template = _iats_civicrm_getContributionTemplate(array('contribution_recur_id' => $values['crid']));
+    $contribution_template = CRM_Iats_Transaction::getContributionTemplate(array('contribution_recur_id' => $values['crid']));
     $contact_id = $values['cid'];
     $hash = md5(uniqid(rand(), TRUE));
     $contribution_recur_id    = $values['crid'];
@@ -111,11 +110,6 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
     foreach (array('payment_instrument_id', 'currency', 'financial_type_id') as $key) {
       $contribution[$key] = $contribution_template[$key];
     }
-    $options = array(
-      'is_email_receipt' => (empty($values['is_email_receipt']) ? '0' : '1'),
-      'customer_code' => $values['customerCode'],
-      'subtype' => $subtype,
-    );
     if ($is_recurrence) {
       $contribution['source'] = "iATS Payments $subtype Recurring Contribution (id=$contribution_recur_id)";
       // We'll use the repeattransaction if the total amount is the same
@@ -126,8 +120,41 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
       unset($contribution['contribution_recur_id']);
       $contribution['source'] = "iATS Payments $subtype One-Time Contribution (using id=$contribution_recur_id)";
     }
+    $contribution['original_contribution_id'] = $original_contribution_id;
+    $contribution['is_email_receipt'] = empty($values['is_email_receipt']) ? '0' : '1';
+    // get the payment token and processor information for the recurring schedule.
+    try {
+      $contribution_recur = civicrm_api3('ContributionRecur', 'getsingle',
+        array(
+          'id' => $contribution_recur_id,
+          'return' => array('payment_token_id'),
+        )
+      );
+      if (!empty($contribution_recur['payment_token_id'])) {
+        $payment_token = civicrm_api3('PaymentToken', 'getsingle', array('id' => $contribution_recur['payment_token_id']));
+      }
+    }
+    catch (Exception $e) {
+      $error = E::ts('Unexpected error getting a payment token for recurring schedule id %1', array(1 => $contribution_recur_id));
+      throw new Exception($error);
+    }
+    if (empty($payment_token['token'])) {
+      $error = E::ts('Recur id %1 is missing a payment token.', array(1 => $contribution_recur_id));
+      throw new Exception($error);
+    }
+    try {
+      $paymentProcessor = civicrm_api3('PaymentProcessor', 'getsingle', array('id' => 3)); 
+    }
+    catch (Exception $e) {
+      $error = E::ts('Unexpected error getting payment processor information for recurring schedule id %1', array(1 => $contribution_recur_id));
+      throw new Exception($error);
+    }
     // Now all the hard work in this function, recycled from the original recurring payment job.
-    $result = _iats_process_contribution_payment($contribution, $options, $original_contribution_id);
+    if (empty($paymentProcessor['id']) || empty($payment_token['token'])) {
+      $error = E::ts('Unexpected error transacting one-time payment for schedule id %1', array(1 => $contribution_recur_id));
+      throw new Exception($error);
+    }
+    $result = CRM_Iats_Transaction::process_contribution_payment($contribution, $paymentProcessor, $payment_token);
     return $result;
   }
 
@@ -162,7 +189,7 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
       $customer = $this->getCustomerCodeDetail($defaults);
     }
     catch (Exception $e) {
-      CRM_Core_Session::setStatus($e->getMessage(), ts('Warning'), 'alert');
+      CRM_Core_Session::setStatus($e->getMessage(), E::ts('Warning'), 'alert');
       return;
     }
     foreach ($labels as $name => $label) {
@@ -185,24 +212,24 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
       'checkbox',
     // Field name.
       'is_email_receipt',
-      ts('Automated email receipt for this contribution.')
+      E::ts('Automated email receipt for this contribution.')
     );
     $this->add(
     // Field type.
       'checkbox',
     // Field name.
       'is_recurrence',
-      ts('Create this as a contribution in the recurring series.')
+      E::ts('Create this as a contribution in the recurring series.')
     );
     $this->addButtons(array(
       array(
         'type' => 'submit',
-        'name' => ts('Charge this card'),
+        'name' => E::ts('Charge this card'),
         'isDefault' => TRUE,
       ),
       array(
         'type' => 'cancel',
-        'name' => ts('Back'),
+        'name' => E::ts('Back'),
       ),
     ));
 
@@ -210,7 +237,7 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
     $this->assign('elementNames', $this->getRenderableElementNames());
     // If necessary, warn the user about the nature of what they are about to do.
     if (0 !== $is_recurrence) { // this if is not working!
-      $message = ts('The contribution created by this form will be saved as contribution in the existing recurring series unless you uncheck the corresponding setting.'); // , $type, $options);.
+      $message = E::ts('The contribution created by this form will be saved as contribution in the existing recurring series unless you uncheck the corresponding setting.'); // , $type, $options);.
       CRM_Core_Session::setStatus($message, 'One-Time Charge');
     }
     parent::buildQuickForm();
@@ -224,9 +251,11 @@ class CRM_iATS_Form_IATSOneTimeCharge extends CRM_Core_Form {
     // print_r($values); die();
     // send charge request to iATS.
     $result = $this->processCreditCardCustomer($values);
-    $message = print_r($result, TRUE);
+    $message = '<pre>' . print_r($result, TRUE). '</pre>';
     // , $type, $options);.
     CRM_Core_Session::setStatus($message, 'Customer Card Charged');
+    $return_qs = http_build_query(array('reset' => 1, 'id' => $values['crid'], 'cid' => $values['cid'], 'context' => 'contribution'));
+    $this->controller->_destination = CRM_Utils_System::url('civicrm/contact/view/contributionrecur', $return_qs);
     parent::postProcess();
   }
 
diff --git a/civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetail.mgd.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetail.mgd.php
new file mode 100644
index 0000000000000000000000000000000000000000..085373446dd7750a3eb94596b6b4ad15b161694a
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetail.mgd.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * This file declares a managed database record of type "ReportTemplate".
+ */
+
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+  0 =>
+  array(
+    'name' => 'CRM_Iats_Form_Report_ContributeDetail',
+    'entity' => 'ReportTemplate',
+    'params' =>
+    array(
+      'version' => 3,
+      'label' => 'iATS Payments - Contribution Reconciliation',
+      'description' => 'Donor Report (Detail) Report with extra iATS Reconciliation fields.',
+      'class_name' => 'CRM_Iats_Form_Report_ContributeDetail',
+      'report_url' => 'com.iatspayments.com/contributedetail',
+      'component' => 'CiviContribute',
+    ),
+  ),
+  1 =>
+  array(
+    'name' => 'CRM_Iats_Form_Report_ContributeDetailFaps',
+    'entity' => 'ReportTemplate',
+    'params' =>
+    array(
+      'version' => 3,
+      'label' => 'iATS Payments 1st American - Contribution Reconciliation',
+      'description' => 'Donor Report (Detail) Report with extra iATS 1st American Reconciliation fields.',
+      'class_name' => 'CRM_Iats_Form_Report_ContributeDetailFaps',
+      'report_url' => 'com.iatspayments.com/contributedetailfaps',
+      'component' => 'CiviContribute',
+    ),
+  ),
+);
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetail.php
similarity index 70%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetail.php
index 0c35702825f4e4dbb535b15f8bd45d83e1b6cc97..dd33ba0e0f45c83ba55c1209f8c0d4dbf593d9fb 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetail.php
@@ -1,14 +1,15 @@
 <?php
 /*
- * A simple modified copy of the core Contribution Detail report with iATS verification detail added
+ * A simple modified copy of the core Contribution Detail report with iATS
+ * verification detail added
  */
 
 /**
  *
  * @package CRM
- * @copyright CiviCRM LLC (c) 2004-2018
+ * @copyright CiviCRM LLC (c) 2004-2019
  */
-class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
+class CRM_Iats_Form_Report_ContributeDetail extends CRM_Report_Form {
 
   protected $_summary = NULL;
 
@@ -16,14 +17,16 @@ class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
 
   protected $noDisplayContributionOrSoftColumn = FALSE;
 
-  protected $_customGroupExtends = array(
+  protected $_customGroupExtends = [
     'Contact',
     'Individual',
     'Contribution',
-  );
+  ];
 
   protected $groupConcatTested = TRUE;
 
+  protected $isTempTableBuilt = FALSE;
+
   static private $_iats_transaction_types = array(
     'VISA' => 'Visa',
     'ACHEFT' => 'ACH/EFT',
@@ -33,6 +36,30 @@ class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
     'DSC' => 'Discover',
   );
 
+  /**
+   * Query mode.
+   *
+   * This can be 'Main' or 'SoftCredit' to denote which query we are building.
+   *
+   * @var string
+   */
+  protected $queryMode = 'Main';
+
+  /**
+   * Is this report being run on contributions as the base entity.
+   *
+   * The report structure is generally designed around a base entity but
+   * depending on input it can be run in a sort of hybrid way that causes a lot
+   * of complexity.
+   *
+   * If it is in isContributionsOnlyMode we can simplify.
+   *
+   * (arguably there should be 2 separate report templates, not one doing double duty.)
+   *
+   * @var bool
+   */
+  protected $isContributionBaseMode = FALSE;
+
   /**
    * This report has been optimised for group filtering.
    *
@@ -47,281 +74,284 @@ class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
    */
   public function __construct() {
     $this->_autoIncludeIndexedFieldsAsOrderBys = 1;
-    // Check if CiviCampaign is a) enabled and b) has active campaigns
-    $config = CRM_Core_Config::singleton();
-    $campaignEnabled = in_array("CiviCampaign", $config->enableComponents);
-    if ($campaignEnabled) {
-      $getCampaigns = CRM_Campaign_BAO_Campaign::getPermissionedCampaigns(NULL, NULL, TRUE, FALSE, TRUE);
-      $this->activeCampaigns = $getCampaigns['campaigns'];
-      asort($this->activeCampaigns);
-    }
-
-    $this->_columns = array_merge($this->getColumns('Contact', array(
-      'order_bys_defaults' => array('sort_name' => 'ASC '),
-      'fields_defaults' => array('sort_name'),
-      'fields_excluded' => array('id'),
-      'fields_required' => array('id'),
-      'filters_defaults' => array('is_deleted' => 0),
+    $this->_columns = array_merge($this->getColumns('Contact', [
+      'order_bys_defaults' => ['sort_name' => 'ASC '],
+      'fields_defaults' => ['sort_name'],
+      'fields_excluded' => ['id'],
+      'fields_required' => ['id'],
+      'filters_defaults' => ['is_deleted' => 0],
       'no_field_disambiguation' => TRUE,
-    )), array(
-      'civicrm_email' => array(
+    ]), [
+      'civicrm_email' => [
         'dao' => 'CRM_Core_DAO_Email',
-        'fields' => array(
-          'email' => array(
+        'fields' => [
+          'email' => [
             'title' => ts('Donor Email'),
             'default' => TRUE,
-          ),
-        ),
+          ],
+        ],
         'grouping' => 'contact-fields',
-      ),
-      'civicrm_line_item' => array(
+      ],
+      'civicrm_line_item' => [
         'dao' => 'CRM_Price_DAO_LineItem',
-      ),
-      'civicrm_phone' => array(
+      ],
+      'civicrm_phone' => [
         'dao' => 'CRM_Core_DAO_Phone',
-        'fields' => array(
-          'phone' => array(
+        'fields' => [
+          'phone' => [
             'title' => ts('Donor Phone'),
             'default' => TRUE,
             'no_repeat' => TRUE,
-          ),
-        ),
+          ],
+        ],
         'grouping' => 'contact-fields',
-      ),
-      'civicrm_contribution' => array(
+      ],
+      'civicrm_contribution' => [
         'dao' => 'CRM_Contribute_DAO_Contribution',
-        'fields' => array(
-          'contribution_id' => array(
+        'fields' => [
+          'contribution_id' => [
             'name' => 'id',
             'no_display' => TRUE,
             'required' => TRUE,
-          ),
-          'list_contri_id' => array(
+          ],
+          'list_contri_id' => [
             'name' => 'id',
             'title' => ts('Contribution ID'),
-          ),
-          'financial_type_id' => array(
+          ],
+          'financial_type_id' => [
             'title' => ts('Financial Type'),
             'default' => TRUE,
-          ),
-          'contribution_status_id' => array(
+          ],
+          'contribution_status_id' => [
             'title' => ts('Contribution Status'),
-          ),
-          'contribution_page_id' => array(
+          ],
+          'contribution_page_id' => [
             'title' => ts('Contribution Page'),
-          ),
-          'source' => array(
+          ],
+          'source' => [
             'title' => ts('Source'),
-          ),
-          'payment_instrument_id' => array(
+          ],
+          'payment_instrument_id' => [
             'title' => ts('Payment Type'),
-          ),
-          'check_number' => array(
+          ],
+          'check_number' => [
             'title' => ts('Check Number'),
-          ),
-          'currency' => array(
+          ],
+          'currency' => [
             'required' => TRUE,
             'no_display' => TRUE,
-          ),
+          ],
           'trxn_id' => NULL,
-          'receive_date' => array('default' => TRUE),
+          'receive_date' => ['default' => TRUE],
           'receipt_date' => NULL,
-          'total_amount' => array(
+          'thankyou_date' => NULL,
+          'total_amount' => [
             'title' => ts('Amount'),
             'required' => TRUE,
-            'statistics' => array('sum' => ts('Amount')),
-          ),
-          'non_deductible_amount' => array(
+          ],
+          'non_deductible_amount' => [
             'title' => ts('Non-deductible Amount'),
-          ),
+          ],
           'fee_amount' => NULL,
           'net_amount' => NULL,
-          'contribution_or_soft' => array(
+          'contribution_or_soft' => [
             'title' => ts('Contribution OR Soft Credit?'),
             'dbAlias' => "'Contribution'",
-          ),
-          'soft_credits' => array(
+          ],
+          'soft_credits' => [
             'title' => ts('Soft Credits'),
             'dbAlias' => "NULL",
-          ),
-          'soft_credit_for' => array(
+          ],
+          'soft_credit_for' => [
             'title' => ts('Soft Credit For'),
             'dbAlias' => "NULL",
-          ),
-        ),
-        'filters' => array(
-          'contribution_or_soft' => array(
+          ],
+          'cancel_date' => [
+            'title' => ts('Cancelled / Refunded Date'),
+            'name' => 'contribution_cancel_date',
+          ],
+          'cancel_reason' => [
+            'title' => ts('Cancellation / Refund Reason'),
+          ],
+        ],
+        'filters' => [
+          'contribution_or_soft' => [
             'title' => ts('Contribution OR Soft Credit?'),
             'clause' => "(1)",
             'operatorType' => CRM_Report_Form::OP_SELECT,
             'type' => CRM_Utils_Type::T_STRING,
-            'options' => array(
+            'options' => [
               'contributions_only' => ts('Contributions Only'),
               'soft_credits_only' => ts('Soft Credits Only'),
               'both' => ts('Both'),
-            ),
-          ),
-          'receive_date' => array('operatorType' => CRM_Report_Form::OP_DATE),
-          'contribution_source' => array(
+            ],
+          ],
+          'receive_date' => ['operatorType' => CRM_Report_Form::OP_DATE],
+          'thankyou_date' => ['operatorType' => CRM_Report_Form::OP_DATE],
+          'contribution_source' => [
             'title' => ts('Source'),
             'name' => 'source',
             'type' => CRM_Utils_Type::T_STRING,
-          ),
-          'currency' => array(
+          ],
+          'currency' => [
             'title' => ts('Currency'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Core_OptionGroup::values('currencies_enabled'),
             'default' => NULL,
             'type' => CRM_Utils_Type::T_STRING,
-          ),
-          'non_deductible_amount' => array(
+          ],
+          'non_deductible_amount' => [
             'title' => ts('Non-deductible Amount'),
-          ),
-          'financial_type_id' => array(
+          ],
+          'financial_type_id' => [
             'title' => ts('Financial Type'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes(),
             'type' => CRM_Utils_Type::T_INT,
-          ),
-          'contribution_page_id' => array(
+          ],
+          'contribution_page_id' => [
             'title' => ts('Contribution Page'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Contribute_PseudoConstant::contributionPage(),
             'type' => CRM_Utils_Type::T_INT,
-          ),
-          'payment_instrument_id' => array(
+          ],
+          'payment_instrument_id' => [
             'title' => ts('Payment Type'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Contribute_PseudoConstant::paymentInstrument(),
             'type' => CRM_Utils_Type::T_INT,
-          ),
-          'contribution_status_id' => array(
+          ],
+          'contribution_status_id' => [
             'title' => ts('Contribution Status'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Contribute_PseudoConstant::contributionStatus(),
-            'default' => array(1),
+            'default' => [1],
             'type' => CRM_Utils_Type::T_INT,
-          ),
-          'total_amount' => array('title' => ts('Contribution Amount')),
-        ),
-        'order_bys' => array(
-          'financial_type_id' => array('title' => ts('Financial Type')),
-          'contribution_status_id' => array('title' => ts('Contribution Status')),
-          'payment_instrument_id' => array('title' => ts('Payment Method')),
-          'receive_date' => array('title' => ts('Date Received')),
-        ),
-        'group_bys' => array(
-          'contribution_id' => array(
+          ],
+          'total_amount' => ['title' => ts('Contribution Amount')],
+          'cancel_date' => [
+            'title' => ts('Cancelled / Refunded Date'),
+            'operatorType' => CRM_Report_Form::OP_DATE,
+            'name' => 'contribution_cancel_date',
+          ],
+          'cancel_reason' => [
+            'title' => ts('Cancellation / Refund Reason'),
+          ],
+        ],
+        'order_bys' => [
+          'financial_type_id' => ['title' => ts('Financial Type')],
+          'contribution_status_id' => ['title' => ts('Contribution Status')],
+          'payment_instrument_id' => ['title' => ts('Payment Method')],
+          'receive_date' => ['title' => ts('Date Received')],
+          'thankyou_date' => ['title' => ts('Thank-you Date')],
+        ],
+        'group_bys' => [
+          'contribution_id' => [
             'name' => 'id',
             'required' => TRUE,
+            'default' => TRUE,
             'title' => ts('Contribution'),
-          ),
-        ),
+          ],
+        ],
         'grouping' => 'contri-fields',
-      ),
-      'civicrm_contribution_soft' => array(
+      ],
+      'civicrm_contribution_soft' => [
         'dao' => 'CRM_Contribute_DAO_ContributionSoft',
-        'fields' => array(
-          'soft_credit_type_id' => array('title' => ts('Soft Credit Type')),
-        ),
-        'filters' => array(
-          'soft_credit_type_id' => array(
+        'fields' => [
+          'soft_credit_type_id' => ['title' => ts('Soft Credit Type')],
+          'soft_credit_amount' => ['title' => ts('Soft Credit amount'), 'name' => 'amount', 'type' => CRM_Utils_Type::T_MONEY],
+        ],
+        'filters' => [
+          'soft_credit_type_id' => [
             'title' => ts('Soft Credit Type'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Core_OptionGroup::values('soft_credit_type'),
             'default' => NULL,
             'type' => CRM_Utils_Type::T_STRING,
-          ),
-        ),
-      ),
-      'civicrm_financial_trxn' => array(
+          ],
+        ],
+        'group_bys' => [
+          'soft_credit_id' => [
+            'name' => 'id',
+            'title' => ts('Soft Credit'),
+          ],
+        ],
+      ],
+      'civicrm_financial_trxn' => [
         'dao' => 'CRM_Financial_DAO_FinancialTrxn',
-        'fields' => array(
-          'card_type_id' => array(
+        'fields' => [
+          'card_type_id' => [
             'title' => ts('Credit Card Type'),
-          ),
-        ),
-        'filters' => array(
-          'card_type_id' => array(
+          ],
+        ],
+        'filters' => [
+          'card_type_id' => [
             'title' => ts('Credit Card Type'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Financial_DAO_FinancialTrxn::buildOptions('card_type_id'),
             'default' => NULL,
             'type' => CRM_Utils_Type::T_STRING,
-          ),
-        ),
-      ),
-      'civicrm_batch' => array(
+          ],
+        ],
+      ],
+      'civicrm_batch' => [
         'dao' => 'CRM_Batch_DAO_EntityBatch',
         'grouping' => 'contri-fields',
-        'fields' => array(
-          'batch_id' => array(
+        'fields' => [
+          'batch_id' => [
             'name' => 'batch_id',
             'title' => ts('Batch Name'),
-          ),
-        ),
-        'filters' => array(
-          'bid' => array(
+          ],
+        ],
+        'filters' => [
+          'bid' => [
             'title' => ts('Batch Name'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Batch_BAO_Batch::getBatches(),
             'type' => CRM_Utils_Type::T_INT,
             'dbAlias' => 'batch_civireport.batch_id',
-          ),
-        ),
-      ),
-      'civicrm_contribution_ordinality' => array(
+          ],
+        ],
+      ],
+      'civicrm_contribution_ordinality' => [
         'dao' => 'CRM_Contribute_DAO_Contribution',
         'alias' => 'cordinality',
-        'filters' => array(
-          'ordinality' => array(
+        'filters' => [
+          'ordinality' => [
             'title' => ts('Contribution Ordinality'),
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
-            'options' => array(
+            'options' => [
               0 => 'First by Contributor',
               1 => 'Second or Later by Contributor',
-            ),
+            ],
             'type' => CRM_Utils_Type::T_INT,
-          ),
-        ),
-      ),
-      'civicrm_note' => array(
+          ],
+        ],
+      ],
+      'civicrm_note' => [
         'dao' => 'CRM_Core_DAO_Note',
-        'fields' => array(
-          'contribution_note' => array(
+        'fields' => [
+          'contribution_note' => [
             'name' => 'note',
             'title' => ts('Contribution Note'),
-          ),
-        ),
-        'filters' => array(
-          'note' => array(
+          ],
+        ],
+        'filters' => [
+          'note' => [
             'name' => 'note',
             'title' => ts('Contribution Note'),
             'operator' => 'like',
             'type' => CRM_Utils_Type::T_STRING,
-          ),
-        ),
-      ),
-    )) + $this->addAddressFields(FALSE);
+          ],
+        ],
+      ],
+    ]) + $this->addAddressFields(FALSE);
     // The tests test for this variation of the sort_name field. Don't argue with the tests :-).
     $this->_columns['civicrm_contact']['fields']['sort_name']['title'] = ts('Donor Name');
     $this->_groupFilter = TRUE;
     $this->_tagFilter = TRUE;
-
-    // If we have active campaigns add those elements to both the fields and filters
-    if ($campaignEnabled && !empty($this->activeCampaigns)) {
-      $this->_columns['civicrm_contribution']['fields']['campaign_id'] = array(
-        'title' => ts('Campaign'),
-        'default' => 'false',
-      );
-      $this->_columns['civicrm_contribution']['filters']['campaign_id'] = array(
-        'title' => ts('Campaign'),
-        'operatorType' => CRM_Report_Form::OP_MULTISELECT,
-        'options' => $this->activeCampaigns,
-        'type' => CRM_Utils_Type::T_INT,
-      );
-      $this->_columns['civicrm_contribution']['order_bys']['campaign_id'] = array('title' => ts('Campaign'));
-    }
+    // If we have campaigns enabled, add those elements to both the fields, filters and sorting
+    $this->addCampaignFields('civicrm_contribution', FALSE, TRUE);
 
     $this->_currencyColumn = 'civicrm_contribution_currency';
 
@@ -387,6 +417,25 @@ class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
     parent::__construct();
   }
 
+  /**
+   * Validate incompatible report settings.
+   *
+   * @return bool
+   *   true if no error found
+   */
+  public function validate() {
+    // If you're displaying Contributions Only, you can't group by soft credit.
+    $contributionOrSoftVal = $this->getElementValue('contribution_or_soft_value');
+    if ($contributionOrSoftVal[0] == 'contributions_only') {
+      $groupBySoft = $this->getElementValue('group_bys');
+      if (CRM_Utils_Array::value('soft_credit_id', $groupBySoft)) {
+        $this->setElementError('group_bys', ts('You cannot group by soft credit when displaying contributions only.  Please uncheck "Soft Credit" in the Grouping tab.'));
+      }
+    }
+
+    return parent::validate();
+  }
+
   /**
    * Set the FROM clause for the report.
    */
@@ -397,18 +446,7 @@ class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
         ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_contribution']}.contact_id
         AND {$this->_aliases['civicrm_contribution']}.is_test = 0";
 
-    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
-      'both'
-    ) {
-      $this->_from .= "\n LEFT JOIN civicrm_contribution_soft contribution_soft_civireport
-                         ON contribution_soft_civireport.contribution_id = {$this->_aliases['civicrm_contribution']}.id";
-    }
-    elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
-      'soft_credits_only'
-    ) {
-      $this->_from .= "\n INNER JOIN civicrm_contribution_soft contribution_soft_civireport
-                         ON contribution_soft_civireport.contribution_id = {$this->_aliases['civicrm_contribution']}.id";
-    }
+    $this->joinContributionToSoftCredit();
     $this->appendAdditionalFromJoins();
   }
 
@@ -420,7 +458,7 @@ class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
   public function statistics(&$rows) {
     $statistics = parent::statistics($rows);
 
-    $totalAmount = $average = $fees = $net = array();
+    $totalAmount = $average = $fees = $net = [];
     $count = 0;
     $select = "
         SELECT COUNT({$this->_aliases['civicrm_contribution']}.total_amount ) as count,
@@ -443,37 +481,37 @@ class CRM_iATS_Form_Report_ContributeDetail extends CRM_Report_Form {
       $average[] = CRM_Utils_Money::format($dao->avg, $dao->currency);
       $count += $dao->count;
     }
-    $statistics['counts']['amount'] = array(
+    $statistics['counts']['amount'] = [
       'title' => ts('Total Amount (Contributions)'),
       'value' => implode(',  ', $totalAmount),
       'type' => CRM_Utils_Type::T_STRING,
-    );
-    $statistics['counts']['count'] = array(
+    ];
+    $statistics['counts']['count'] = [
       'title' => ts('Total Contributions'),
       'value' => $count,
-    );
-    $statistics['counts']['fees'] = array(
+    ];
+    $statistics['counts']['fees'] = [
       'title' => ts('Fees'),
       'value' => implode(',  ', $fees),
       'type' => CRM_Utils_Type::T_STRING,
-    );
-    $statistics['counts']['net'] = array(
+    ];
+    $statistics['counts']['net'] = [
       'title' => ts('Net'),
       'value' => implode(',  ', $net),
       'type' => CRM_Utils_Type::T_STRING,
-    );
-    $statistics['counts']['avg'] = array(
+    ];
+    $statistics['counts']['avg'] = [
       'title' => ts('Average'),
       'value' => implode(',  ', $average),
       'type' => CRM_Utils_Type::T_STRING,
-    );
+    ];
 
     // Stats for soft credits
     if ($this->_softFrom &&
       CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) !=
       'contributions_only'
     ) {
-      $totalAmount = $average = array();
+      $totalAmount = $average = [];
       $count = 0;
       $select = "
 SELECT COUNT(contribution_soft_civireport.amount ) as count,
@@ -492,43 +530,47 @@ GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
         $average[] = CRM_Utils_Money::format($dao->avg, $dao->currency);
         $count += $dao->count;
       }
-      $statistics['counts']['softamount'] = array(
+      $statistics['counts']['softamount'] = [
         'title' => ts('Total Amount (Soft Credits)'),
         'value' => implode(',  ', $totalAmount),
         'type' => CRM_Utils_Type::T_STRING,
-      );
-      $statistics['counts']['softcount'] = array(
+      ];
+      $statistics['counts']['softcount'] = [
         'title' => ts('Total Soft Credits'),
         'value' => $count,
-      );
-      $statistics['counts']['softavg'] = array(
+      ];
+      $statistics['counts']['softavg'] = [
         'title' => ts('Average (Soft Credits)'),
         'value' => implode(',  ', $average),
         'type' => CRM_Utils_Type::T_STRING,
-      );
+      ];
     }
 
     return $statistics;
   }
 
   /**
-   * This function appears to have been overrriden for the purposes of facilitating soft credits in the report.
+   * Build the report query.
    *
-   * The report appears to have 2 different functions:
-   * 1) contribution report
-   * 2) soft credit report - showing a row per 'payment engagement' (payment or soft credit). There is a separate
-   * soft credit report as well.
+   * @param bool $applyLimit
    *
-   * Somewhat confusingly this report returns multiple rows per contribution when soft credits are included. It feels
-   * like there is a case to split it into 2 separate reports.
-   *
-   * Soft credit functionality is not currently unit tested for this report.
+   * @return string
    */
-  public function postProcess() {
-    // get the acl clauses built before we assemble the query
-    $this->buildACLClause($this->_aliases['civicrm_contact']);
+  public function buildQuery($applyLimit = FALSE) {
+    if ($this->isTempTableBuilt) {
+      $this->limit();
+      return "SELECT SQL_CALC_FOUND_ROWS * FROM {$this->temporaryTables['civireport_contribution_detail_temp3']['name']} $this->_orderBy $this->_limit";
+    }
+    return parent::buildQuery($applyLimit);
+  }
 
-    $this->beginPostProcess();
+  /**
+   * Shared function for preliminary processing.
+   *
+   * This is called by the api / unit tests and the form layer and is
+   * the right place to do 'initial analysis of input'.
+   */
+  public function beginPostProcessCommon() {
     // CRM-18312 - display soft_credits and soft_credits_for column
     // when 'Contribution or Soft Credit?' column is not selected
     if (empty($this->_params['fields']['contribution_or_soft'])) {
@@ -536,24 +578,29 @@ GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
       $this->noDisplayContributionOrSoftColumn = TRUE;
     }
 
-    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
-      'contributions_only' &&
-      !empty($this->_params['fields']['soft_credit_type_id'])
+    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'contributions_only') {
+      $this->isContributionBaseMode = TRUE;
+    }
+    if ($this->isContributionBaseMode &&
+      (!empty($this->_params['fields']['soft_credit_type_id'])
+      || !empty($this->_params['soft_credit_type_id_value']))
     ) {
       unset($this->_params['fields']['soft_credit_type_id']);
       if (!empty($this->_params['soft_credit_type_id_value'])) {
-        $this->_params['soft_credit_type_id_value'] = array();
+        $this->_params['soft_credit_type_id_value'] = [];
+        CRM_Core_Session::setStatus(ts('Is it not possible to filter on soft contribution type when not including soft credits.'));
       }
     }
-
     // 1. use main contribution query to build temp table 1
     $sql = $this->buildQuery();
-    $tempQuery = "CREATE TEMPORARY TABLE civireport_contribution_detail_temp1 {$this->_databaseAttributes} AS {$sql}";
-    $this->addToDeveloperTab($tempQuery);
-    CRM_Core_DAO::executeQuery($tempQuery);
-    $this->setPager();
+    $this->createTemporaryTable('civireport_contribution_detail_temp1', $sql);
 
     // 2. customize main contribution query for soft credit, and build temp table 2 with soft credit contributions only
+    $this->queryMode = 'SoftCredit';
+    // Rebuild select with no groupby. Do not let column headers change.
+    $headers = $this->_columnHeaders;
+    $this->select();
+    $this->_columnHeaders = $headers;
     $this->softCreditFrom();
     // also include custom group from if included
     // since this might be included in select
@@ -561,15 +608,12 @@ GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
 
     $select = str_ireplace('contribution_civireport.total_amount', 'contribution_soft_civireport.amount', $this->_select);
     $select = str_ireplace("'Contribution' as", "'Soft Credit' as", $select);
-    // We really don't want to join soft credit in if not required.
-    if (!empty($this->_groupBy) && !$this->noDisplayContributionOrSoftColumn) {
-      $this->_groupBy .= ', contribution_soft_civireport.amount';
-    }
-    // we inner join with temp1 to restrict soft contributions to those in temp1 table
-    $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy}";
-    $tempQuery = "CREATE TEMPORARY TABLE civireport_contribution_detail_temp2 {$this->_databaseAttributes} AS {$sql}";
-    $this->addToDeveloperTab($tempQuery);
-    CRM_Core_DAO::executeQuery($tempQuery);
+
+    // we inner join with temp1 to restrict soft contributions to those in temp1 table.
+    // no group by here as we want to display as many soft credit rows as actually exist.
+    $sql = "{$select} {$this->_from} {$this->_where} $this->_groupBy";
+    $this->createTemporaryTable('civireport_contribution_detail_temp2', $sql);
+
     if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
       'soft_credits_only'
     ) {
@@ -588,40 +632,40 @@ GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
     $this->customDataFrom();
 
     // 3. Decide where to populate temp3 table from
-    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
-      'contributions_only'
+    if ($this->isContributionBaseMode
     ) {
-      $tempQuery = "(SELECT * FROM civireport_contribution_detail_temp1)";
+      $this->createTemporaryTable('civireport_contribution_detail_temp3',
+        "(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp1']['name']})"
+      );
     }
     elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
       'soft_credits_only'
     ) {
-      $tempQuery = "(SELECT * FROM civireport_contribution_detail_temp2)";
+      $this->createTemporaryTable('civireport_contribution_detail_temp3',
+        "(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp2']['name']})"
+      );
     }
     else {
-      $tempQuery = "
-(SELECT * FROM civireport_contribution_detail_temp1)
+      $this->createTemporaryTable('civireport_contribution_detail_temp3', "
+(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp1']['name']})
 UNION ALL
-(SELECT * FROM civireport_contribution_detail_temp2)";
+(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp2']['name']})");
     }
+    $this->isTempTableBuilt = TRUE;
+  }
 
-    // 4. build temp table 3
-    $sql = "CREATE TEMPORARY TABLE civireport_contribution_detail_temp3 {$this->_databaseAttributes} AS {$tempQuery}";
-    $this->addToDeveloperTab($sql);
-    CRM_Core_DAO::executeQuery($sql);
-
-    // 6. show result set from temp table 3
-    $rows = array();
-    $sql = "SELECT * FROM civireport_contribution_detail_temp3 $this->_orderBy";
-    $this->buildRows($sql, $rows);
-
-    // format result set.
-    $this->formatDisplay($rows, FALSE);
-
-    // assign variables to templates
-    $this->doTemplateAssignment($rows);
-    // do print / pdf / instance stuff if needed
-    $this->endPostProcess($rows);
+  /**
+   * Store group bys into array - so we can check elsewhere what is grouped.
+   *
+   * If we are generating a table of soft credits we need to group by them.
+   */
+  protected function storeGroupByArray() {
+    if ($this->queryMode === 'SoftCredit') {
+      $this->_groupByArray = [$this->_aliases['civicrm_contribution_soft'] . '.id'];
+    }
+    else {
+      parent::storeGroupByArray();
+    }
   }
 
   /**
@@ -634,7 +678,6 @@ UNION ALL
    *   Rows generated by SQL, with an array for each row.
    */
   public function alterDisplay(&$rows) {
-    $checkList = array();
     $entryFound = FALSE;
     $display_flag = $prev_cid = $cid = 0;
     $contributionTypes = CRM_Contribute_PseudoConstant::financialType();
@@ -722,24 +765,31 @@ UNION ALL
       }
 
       // Contribution amount links to viewing contribution
-      if (($value = CRM_Utils_Array::value('civicrm_contribution_total_amount', $row)) &&
-        CRM_Core_Permission::check('access CiviContribute')
-      ) {
-        $url = CRM_Utils_System::url("civicrm/contact/view/contribution",
-          "reset=1&id=" . $row['civicrm_contribution_contribution_id'] .
-          "&cid=" . $row['civicrm_contact_id'] .
-          "&action=view&context=contribution&selectedChild=contribute",
-          $this->_absoluteUrl
-        );
-        $rows[$rowNum]['civicrm_contribution_total_amount_link'] = $url;
-        $rows[$rowNum]['civicrm_contribution_total_amount_hover'] = ts("View Details of this Contribution.");
+      if ($value = CRM_Utils_Array::value('civicrm_contribution_total_amount', $row)) {
+        $rows[$rowNum]['civicrm_contribution_total_amount'] = CRM_Utils_Money::format($value, $row['civicrm_contribution_currency']);
+        if (CRM_Core_Permission::check('access CiviContribute')) {
+          $url = CRM_Utils_System::url(
+            "civicrm/contact/view/contribution",
+            [
+              'reset' => 1,
+              'id' => $row['civicrm_contribution_contribution_id'],
+              'cid' => $row['civicrm_contact_id'],
+              'action' => 'view',
+              'context' => 'contribution',
+              'selectedChild' => 'contribute',
+            ],
+            $this->_absoluteUrl
+          );
+          $rows[$rowNum]['civicrm_contribution_total_amount_link'] = $url;
+          $rows[$rowNum]['civicrm_contribution_total_amount_hover'] = ts("View Details of this Contribution.");
+        }
         $entryFound = TRUE;
       }
 
       // convert campaign_id to campaign title
       if (array_key_exists('civicrm_contribution_campaign_id', $row)) {
         if ($value = $row['civicrm_contribution_campaign_id']) {
-          $rows[$rowNum]['civicrm_contribution_campaign_id'] = $this->activeCampaigns[$value];
+          $rows[$rowNum]['civicrm_contribution_campaign_id'] = $this->campaigns[$value];
           $entryFound = TRUE;
         }
       }
@@ -751,10 +801,9 @@ UNION ALL
         array_key_exists('civicrm_contribution_contribution_id', $row)
       ) {
         $query = "
-SELECT civicrm_contact_id, civicrm_contact_sort_name, civicrm_contribution_total_amount_sum, civicrm_contribution_currency
-FROM   civireport_contribution_detail_temp2
+SELECT civicrm_contact_id, civicrm_contact_sort_name, civicrm_contribution_total_amount, civicrm_contribution_currency
+FROM   {$this->temporaryTables['civireport_contribution_detail_temp2']['name']}
 WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
-        $this->addToDeveloperTab($query);
         $dao = CRM_Core_DAO::executeQuery($query);
         $string = '';
         $separator = ($this->_outputMode !== 'csv') ? "<br/>" : ' ';
@@ -763,7 +812,7 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
             $dao->civicrm_contact_id);
           $string = $string . ($string ? $separator : '') .
             "<a href='{$url}'>{$dao->civicrm_contact_sort_name}</a> " .
-            CRM_Utils_Money::format($dao->civicrm_contribution_total_amount_sum, $dao->civicrm_contribution_currency);
+            CRM_Utils_Money::format($dao->civicrm_contribution_total_amount, $dao->civicrm_contribution_currency);
         }
         $rows[$rowNum]['civicrm_contribution_soft_credits'] = $string;
       }
@@ -775,9 +824,8 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
       ) {
         $query = "
 SELECT civicrm_contact_id, civicrm_contact_sort_name
-FROM   civireport_contribution_detail_temp1
+FROM   {$this->temporaryTables['civireport_contribution_detail_temp1']['name']}
 WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
-        $this->addToDeveloperTab($query);
         $dao = CRM_Core_DAO::executeQuery($query);
         $string = '';
         while ($dao->fetch()) {
@@ -830,7 +878,7 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
       // pull section aliases out of $this->_sections
       $sectionAliases = array_keys($this->_sections);
 
-      $ifnulls = array();
+      $ifnulls = [];
       foreach (array_merge($sectionAliases, $this->_selectAliases) as $alias) {
         $ifnulls[] = "ifnull($alias, '') as $alias";
       }
@@ -852,10 +900,10 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
       }
 
       $query = $this->_select .
-        "$addtotals, count(*) as ct from civireport_contribution_detail_temp3 group by " .
+        "$addtotals, count(*) as ct from {$this->temporaryTables['civireport_contribution_detail_temp3']['name']} group by " .
         implode(", ", $sectionAliases);
       // initialize array of total counts
-      $sumcontribs = $totals = array();
+      $sumcontribs = $totals = [];
       $dao = CRM_Core_DAO::executeQuery($query);
       $this->addToDeveloperTab($query);
       while ($dao->fetch()) {
@@ -866,7 +914,7 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
         $row = $rows[0];
 
         // add totals for all permutations of section values
-        $values = array();
+        $values = [];
         $i = 1;
         $aliasCount = count($sectionAliases);
         foreach ($sectionAliases as $alias) {
@@ -889,7 +937,7 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
         }
       }
       if ($showsumcontribs) {
-        $totalandsum = array();
+        $totalandsum = [];
         // ts exception to avoid having ts("%1 %2: %3")
         $title = '%1 contributions / soft-credits: %2';
 
@@ -904,10 +952,10 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
           $title = '%1 soft-credits: %2';
         }
         foreach ($totals as $key => $total) {
-          $totalandsum[$key] = ts($title, array(
+          $totalandsum[$key] = ts($title, [
             1 => $total,
             2 => CRM_Utils_Money::format($sumcontribs[$key]),
-          ));
+          ]);
         }
         $this->assign('sectionTotals', $totalandsum);
       }
@@ -923,7 +971,7 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
   public function softCreditFrom() {
 
     $this->_from = "
-      FROM  civireport_contribution_detail_temp1 temp1_civireport
+      FROM  {$this->temporaryTables['civireport_contribution_detail_temp1']['name']} temp1_civireport
       INNER JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
         ON temp1_civireport.civicrm_contribution_contribution_id = {$this->_aliases['civicrm_contribution']}.id
       INNER JOIN civicrm_contribution_soft contribution_soft_civireport
@@ -933,6 +981,10 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
       {$this->_aclFrom}
     ";
 
+    //Join temp table if report is filtered by group. This is specific to 'notin' operator and covered in unit test(ref dev/core#212)
+    if (!empty($this->_params['gid_op']) && $this->_params['gid_op'] == 'notin') {
+      $this->joinGroupTempTable('civicrm_contact', 'id', $this->_aliases['civicrm_contact']);
+    }
     $this->appendAdditionalFromJoins();
   }
 
@@ -972,15 +1024,32 @@ WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribu
     }
     // for credit card type
     $this->addFinancialTrxnFromClause();
-    // for iats journal data
-    $this->addIatsJournalFromClause();
+    $this->addIatsJournalFromClauses();
   }
 
-  public function addIatsJournalFromClause() {
+  public function addIatsJournalFromClauses() {
     if ($this->isTableSelected('civicrm_iats_journal')) {
-      $this->_from .= "
-         LEFT JOIN civicrm_iats_journal {$this->_aliases['civicrm_iats_journal']}
+      $this->_from .= "LEFT JOIN civicrm_iats_journal {$this->_aliases['civicrm_iats_journal']}
            ON {$this->_aliases['civicrm_contribution']}.invoice_id = {$this->_aliases['civicrm_iats_journal']}.inv \n";
     }
   }
+
+  /**
+   * Add join to the soft credit table.
+   */
+  protected function joinContributionToSoftCredit() {
+    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'contributions_only'
+      && !$this->isTableSelected('civicrm_contribution_soft')) {
+      return;
+    }
+    $joinType = ' LEFT ';
+    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'soft_credits_only') {
+      $joinType = ' INNER ';
+    }
+    $this->_from .= "
+      $joinType JOIN civicrm_contribution_soft {$this->_aliases['civicrm_contribution_soft']}
+      ON {$this->_aliases['civicrm_contribution_soft']}.contribution_id = {$this->_aliases['civicrm_contribution']}.id
+   ";
+  }
+
 }
diff --git a/civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetailFaps.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetailFaps.php
new file mode 100644
index 0000000000000000000000000000000000000000..fc8b0013ebe6f4d0ec4ad31f92721ad9b20fc3a3
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetailFaps.php
@@ -0,0 +1,1058 @@
+<?php
+/*
+ * A simple modified copy of the core Contribution Detail report with iATS
+ * verification detail added
+ */
+opcache_reset();
+/**
+ *
+ * @package CRM
+ * @copyright CiviCRM LLC (c) 2004-2019
+ */
+class CRM_Iats_Form_Report_ContributeDetailFaps extends CRM_Report_Form {
+
+  protected $_summary = NULL;
+
+  protected $_softFrom = NULL;
+
+  protected $noDisplayContributionOrSoftColumn = FALSE;
+
+  protected $_customGroupExtends = [
+    'Contact',
+    'Individual',
+    'Contribution',
+  ];
+
+  protected $groupConcatTested = TRUE;
+
+  protected $isTempTableBuilt = FALSE;
+
+  static private $_iats_faps_card_types = array(
+    'Visa' => 'Visa',
+    'Mastercard' => 'MasterCard',
+    'Amex' => 'AMEX',
+    'Discover' => 'Discover',
+  );
+
+  /**
+   * Query mode.
+   *
+   * This can be 'Main' or 'SoftCredit' to denote which query we are building.
+   *
+   * @var string
+   */
+  protected $queryMode = 'Main';
+
+  /**
+   * Is this report being run on contributions as the base entity.
+   *
+   * The report structure is generally designed around a base entity but
+   * depending on input it can be run in a sort of hybrid way that causes a lot
+   * of complexity.
+   *
+   * If it is in isContributionsOnlyMode we can simplify.
+   *
+   * (arguably there should be 2 separate report templates, not one doing double duty.)
+   *
+   * @var bool
+   */
+  protected $isContributionBaseMode = FALSE;
+
+  /**
+   * This report has been optimised for group filtering.
+   *
+   * CRM-19170
+   *
+   * @var bool
+   */
+  protected $groupFilterNotOptimised = FALSE;
+
+  /**
+   * Class constructor.
+   */
+  public function __construct() {
+    $this->_autoIncludeIndexedFieldsAsOrderBys = 1;
+    $this->_columns = array_merge($this->getColumns('Contact', [
+      'order_bys_defaults' => ['sort_name' => 'ASC '],
+      'fields_defaults' => ['sort_name'],
+      'fields_excluded' => ['id'],
+      'fields_required' => ['id'],
+      'filters_defaults' => ['is_deleted' => 0],
+      'no_field_disambiguation' => TRUE,
+    ]), [
+      'civicrm_email' => [
+        'dao' => 'CRM_Core_DAO_Email',
+        'fields' => [
+          'email' => [
+            'title' => ts('Donor Email'),
+            'default' => TRUE,
+          ],
+        ],
+        'grouping' => 'contact-fields',
+      ],
+      'civicrm_line_item' => [
+        'dao' => 'CRM_Price_DAO_LineItem',
+      ],
+      'civicrm_phone' => [
+        'dao' => 'CRM_Core_DAO_Phone',
+        'fields' => [
+          'phone' => [
+            'title' => ts('Donor Phone'),
+            'default' => TRUE,
+            'no_repeat' => TRUE,
+          ],
+        ],
+        'grouping' => 'contact-fields',
+      ],
+      'civicrm_contribution' => [
+        'dao' => 'CRM_Contribute_DAO_Contribution',
+        'fields' => [
+          'contribution_id' => [
+            'name' => 'id',
+            'no_display' => TRUE,
+            'required' => TRUE,
+          ],
+          'list_contri_id' => [
+            'name' => 'id',
+            'title' => ts('Contribution ID'),
+          ],
+          'financial_type_id' => [
+            'title' => ts('Financial Type'),
+            'default' => TRUE,
+          ],
+          'contribution_status_id' => [
+            'title' => ts('Contribution Status'),
+          ],
+          'contribution_page_id' => [
+            'title' => ts('Contribution Page'),
+          ],
+          'source' => [
+            'title' => ts('Source'),
+          ],
+          'payment_instrument_id' => [
+            'title' => ts('Payment Type'),
+          ],
+          'check_number' => [
+            'title' => ts('Check Number'),
+          ],
+          'currency' => [
+            'required' => TRUE,
+            'no_display' => TRUE,
+          ],
+          'trxn_id' => NULL,
+          'receive_date' => ['default' => TRUE],
+          'receipt_date' => NULL,
+          'thankyou_date' => NULL,
+          'total_amount' => [
+            'title' => ts('Amount'),
+            'required' => TRUE,
+          ],
+          'non_deductible_amount' => [
+            'title' => ts('Non-deductible Amount'),
+          ],
+          'fee_amount' => NULL,
+          'net_amount' => NULL,
+          'contribution_or_soft' => [
+            'title' => ts('Contribution OR Soft Credit?'),
+            'dbAlias' => "'Contribution'",
+          ],
+          'soft_credits' => [
+            'title' => ts('Soft Credits'),
+            'dbAlias' => "NULL",
+          ],
+          'soft_credit_for' => [
+            'title' => ts('Soft Credit For'),
+            'dbAlias' => "NULL",
+          ],
+          'cancel_date' => [
+            'title' => ts('Cancelled / Refunded Date'),
+            'name' => 'contribution_cancel_date',
+          ],
+          'cancel_reason' => [
+            'title' => ts('Cancellation / Refund Reason'),
+          ],
+        ],
+        'filters' => [
+          'contribution_or_soft' => [
+            'title' => ts('Contribution OR Soft Credit?'),
+            'clause' => "(1)",
+            'operatorType' => CRM_Report_Form::OP_SELECT,
+            'type' => CRM_Utils_Type::T_STRING,
+            'options' => [
+              'contributions_only' => ts('Contributions Only'),
+              'soft_credits_only' => ts('Soft Credits Only'),
+              'both' => ts('Both'),
+            ],
+          ],
+          'receive_date' => ['operatorType' => CRM_Report_Form::OP_DATE],
+          'thankyou_date' => ['operatorType' => CRM_Report_Form::OP_DATE],
+          'contribution_source' => [
+            'title' => ts('Source'),
+            'name' => 'source',
+            'type' => CRM_Utils_Type::T_STRING,
+          ],
+          'currency' => [
+            'title' => ts('Currency'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => CRM_Core_OptionGroup::values('currencies_enabled'),
+            'default' => NULL,
+            'type' => CRM_Utils_Type::T_STRING,
+          ],
+          'non_deductible_amount' => [
+            'title' => ts('Non-deductible Amount'),
+          ],
+          'financial_type_id' => [
+            'title' => ts('Financial Type'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes(),
+            'type' => CRM_Utils_Type::T_INT,
+          ],
+          'contribution_page_id' => [
+            'title' => ts('Contribution Page'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => CRM_Contribute_PseudoConstant::contributionPage(),
+            'type' => CRM_Utils_Type::T_INT,
+          ],
+          'payment_instrument_id' => [
+            'title' => ts('Payment Type'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => CRM_Contribute_PseudoConstant::paymentInstrument(),
+            'type' => CRM_Utils_Type::T_INT,
+          ],
+          'contribution_status_id' => [
+            'title' => ts('Contribution Status'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => CRM_Contribute_PseudoConstant::contributionStatus(),
+            'default' => [1],
+            'type' => CRM_Utils_Type::T_INT,
+          ],
+          'total_amount' => ['title' => ts('Contribution Amount')],
+          'cancel_date' => [
+            'title' => ts('Cancelled / Refunded Date'),
+            'operatorType' => CRM_Report_Form::OP_DATE,
+            'name' => 'contribution_cancel_date',
+          ],
+          'cancel_reason' => [
+            'title' => ts('Cancellation / Refund Reason'),
+          ],
+        ],
+        'order_bys' => [
+          'financial_type_id' => ['title' => ts('Financial Type')],
+          'contribution_status_id' => ['title' => ts('Contribution Status')],
+          'payment_instrument_id' => ['title' => ts('Payment Method')],
+          'receive_date' => ['title' => ts('Date Received')],
+          'thankyou_date' => ['title' => ts('Thank-you Date')],
+        ],
+        'group_bys' => [
+          'contribution_id' => [
+            'name' => 'id',
+            'required' => TRUE,
+            'default' => TRUE,
+            'title' => ts('Contribution'),
+          ],
+        ],
+        'grouping' => 'contri-fields',
+      ],
+      'civicrm_contribution_soft' => [
+        'dao' => 'CRM_Contribute_DAO_ContributionSoft',
+        'fields' => [
+          'soft_credit_type_id' => ['title' => ts('Soft Credit Type')],
+          'soft_credit_amount' => ['title' => ts('Soft Credit amount'), 'name' => 'amount', 'type' => CRM_Utils_Type::T_MONEY],
+        ],
+        'filters' => [
+          'soft_credit_type_id' => [
+            'title' => ts('Soft Credit Type'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => CRM_Core_OptionGroup::values('soft_credit_type'),
+            'default' => NULL,
+            'type' => CRM_Utils_Type::T_STRING,
+          ],
+        ],
+        'group_bys' => [
+          'soft_credit_id' => [
+            'name' => 'id',
+            'title' => ts('Soft Credit'),
+          ],
+        ],
+      ],
+      'civicrm_financial_trxn' => [
+        'dao' => 'CRM_Financial_DAO_FinancialTrxn',
+        'fields' => [
+          'card_type_id' => [
+            'title' => ts('Credit Card Type'),
+          ],
+        ],
+        'filters' => [
+          'card_type_id' => [
+            'title' => ts('Credit Card Type'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => CRM_Financial_DAO_FinancialTrxn::buildOptions('card_type_id'),
+            'default' => NULL,
+            'type' => CRM_Utils_Type::T_STRING,
+          ],
+        ],
+      ],
+      'civicrm_batch' => [
+        'dao' => 'CRM_Batch_DAO_EntityBatch',
+        'grouping' => 'contri-fields',
+        'fields' => [
+          'batch_id' => [
+            'name' => 'batch_id',
+            'title' => ts('Batch Name'),
+          ],
+        ],
+        'filters' => [
+          'bid' => [
+            'title' => ts('Batch Name'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => CRM_Batch_BAO_Batch::getBatches(),
+            'type' => CRM_Utils_Type::T_INT,
+            'dbAlias' => 'batch_civireport.batch_id',
+          ],
+        ],
+      ],
+      'civicrm_contribution_ordinality' => [
+        'dao' => 'CRM_Contribute_DAO_Contribution',
+        'alias' => 'cordinality',
+        'filters' => [
+          'ordinality' => [
+            'title' => ts('Contribution Ordinality'),
+            'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+            'options' => [
+              0 => 'First by Contributor',
+              1 => 'Second or Later by Contributor',
+            ],
+            'type' => CRM_Utils_Type::T_INT,
+          ],
+        ],
+      ],
+      'civicrm_note' => [
+        'dao' => 'CRM_Core_DAO_Note',
+        'fields' => [
+          'contribution_note' => [
+            'name' => 'note',
+            'title' => ts('Contribution Note'),
+          ],
+        ],
+        'filters' => [
+          'note' => [
+            'name' => 'note',
+            'title' => ts('Contribution Note'),
+            'operator' => 'like',
+            'type' => CRM_Utils_Type::T_STRING,
+          ],
+        ],
+      ],
+    ]) + $this->addAddressFields(FALSE);
+    // The tests test for this variation of the sort_name field. Don't argue with the tests :-).
+    $this->_columns['civicrm_contact']['fields']['sort_name']['title'] = ts('Donor Name');
+    $this->_groupFilter = TRUE;
+    $this->_tagFilter = TRUE;
+    // If we have campaigns enabled, add those elements to both the fields, filters and sorting
+    $this->addCampaignFields('civicrm_contribution', FALSE, TRUE);
+
+    $this->_currencyColumn = 'civicrm_contribution_currency';
+
+
+    // self::$contributionStatus = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id');
+    // $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
+    $this->_columns['civicrm_iats_faps_journal'] = array(
+          'fields' =>
+            array(
+              'id' => array('title' => 'CiviCRM 1stPay Journal Id', 'default' => TRUE),
+              'transactionId' => array('title' => 'Faps transactionId', 'default' => TRUE),
+              'isAch' => array('title' => 'is ACH', 'default' => TRUE),
+              'cardType' => array('title' => 'Card Type', 'default' => TRUE),
+              'processorId' => array('title' => 'Merchant Processor Id', 'default' => TRUE),
+              'cimRefNumber' => array('title' => 'Customer reference', 'default' => TRUE),
+              'orderId' => array('title' => 'Invoice Reference', 'default' => TRUE),
+              'transDateAndTime' => array('title' => 'Transaction date and time', 'default' => TRUE),
+              'amount' => array('title' => 'Amount', 'default' => TRUE),
+              'authResponse' => array('title' => 'Result string', 'default' => TRUE),
+              'status_id' => array('title' => 'Payment Status', 'default' => TRUE),
+            ),
+          'order_bys' => 
+            array(
+              'id' => array('title' => ts('CiviCRM 1stPay Journal Id'), 'default' => TRUE, 'default_order' => 'DESC'),
+              'transactionId' => array('title' => ts('1stPay Transaction Id')),
+              'transDateAndTime' => array('title' => ts('Transaction Date Time')),
+            ),
+          'filters' =>
+             array(
+               'transDateAndTime' => array(
+                 'title' => 'Transaction date', 
+                 'operatorType' => CRM_Report_Form::OP_DATE,
+                 'type' => CRM_Utils_Type::T_DATE,
+               ),
+               'orderId' => array(
+                 'title' => 'Invoice Reference', 
+                 'type' => CRM_Utils_Type::T_STRING,
+               ),
+               'amount' => array(
+                 'title' => 'Amount', 
+                 'operatorType' => CRM_Report_Form::OP_FLOAT,
+                 'type' => CRM_Utils_Type::T_FLOAT
+               ),
+               'isAch' => array(
+                 'title' => 'Is ACH', 
+                 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+                 'options' => array(0 => 'Credit Card', 1 => 'ACH'),
+                 'type' => CRM_Utils_Type::T_INT,
+               ),
+               'cardType' => array(
+                 'title' => 'Type', 
+                 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+                 'options' => self::$_iats_faps_card_types,
+                 'type' => CRM_Utils_Type::T_STRING,
+               ),
+               'authResponse' => array(
+                 'title' => 'Result string',
+                 'type' => CRM_Utils_Type::T_STRING,
+               ),
+               'status_id' => array(
+                 'title' => ts('iATS Journal Payment Status'),
+                 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+                 'options' => CRM_Contribute_PseudoConstant::contributionStatus(),
+                 'type' => CRM_Utils_Type::T_INT,
+               ),
+             ),
+    );
+    parent::__construct();
+  }
+
+  /**
+   * Validate incompatible report settings.
+   *
+   * @return bool
+   *   true if no error found
+   */
+  public function validate() {
+    // If you're displaying Contributions Only, you can't group by soft credit.
+    $contributionOrSoftVal = $this->getElementValue('contribution_or_soft_value');
+    if ($contributionOrSoftVal[0] == 'contributions_only') {
+      $groupBySoft = $this->getElementValue('group_bys');
+      if (CRM_Utils_Array::value('soft_credit_id', $groupBySoft)) {
+        $this->setElementError('group_bys', ts('You cannot group by soft credit when displaying contributions only.  Please uncheck "Soft Credit" in the Grouping tab.'));
+      }
+    }
+
+    return parent::validate();
+  }
+
+  /**
+   * Set the FROM clause for the report.
+   */
+  public function from() {
+    $this->setFromBase('civicrm_contact');
+    $this->_from .= "
+      INNER JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
+        ON {$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_contribution']}.contact_id
+        AND {$this->_aliases['civicrm_contribution']}.is_test = 0";
+
+    $this->joinContributionToSoftCredit();
+    $this->appendAdditionalFromJoins();
+  }
+
+  /**
+   * @param $rows
+   *
+   * @return array
+   */
+  public function statistics(&$rows) {
+    $statistics = parent::statistics($rows);
+
+    $totalAmount = $average = $fees = $net = [];
+    $count = 0;
+    $select = "
+        SELECT COUNT({$this->_aliases['civicrm_contribution']}.total_amount ) as count,
+               SUM( {$this->_aliases['civicrm_contribution']}.total_amount ) as amount,
+               ROUND(AVG({$this->_aliases['civicrm_contribution']}.total_amount), 2) as avg,
+               {$this->_aliases['civicrm_contribution']}.currency as currency,
+               SUM( {$this->_aliases['civicrm_contribution']}.fee_amount ) as fees,
+               SUM( {$this->_aliases['civicrm_contribution']}.net_amount ) as net
+        ";
+
+    $group = "\nGROUP BY {$this->_aliases['civicrm_contribution']}.currency";
+    $sql = "{$select} {$this->_from} {$this->_where} {$group}";
+    $dao = CRM_Core_DAO::executeQuery($sql);
+    $this->addToDeveloperTab($sql);
+
+    while ($dao->fetch()) {
+      $totalAmount[] = CRM_Utils_Money::format($dao->amount, $dao->currency) . " (" . $dao->count . ")";
+      $fees[] = CRM_Utils_Money::format($dao->fees, $dao->currency);
+      $net[] = CRM_Utils_Money::format($dao->net, $dao->currency);
+      $average[] = CRM_Utils_Money::format($dao->avg, $dao->currency);
+      $count += $dao->count;
+    }
+    $statistics['counts']['amount'] = [
+      'title' => ts('Total Amount (Contributions)'),
+      'value' => implode(',  ', $totalAmount),
+      'type' => CRM_Utils_Type::T_STRING,
+    ];
+    $statistics['counts']['count'] = [
+      'title' => ts('Total Contributions'),
+      'value' => $count,
+    ];
+    $statistics['counts']['fees'] = [
+      'title' => ts('Fees'),
+      'value' => implode(',  ', $fees),
+      'type' => CRM_Utils_Type::T_STRING,
+    ];
+    $statistics['counts']['net'] = [
+      'title' => ts('Net'),
+      'value' => implode(',  ', $net),
+      'type' => CRM_Utils_Type::T_STRING,
+    ];
+    $statistics['counts']['avg'] = [
+      'title' => ts('Average'),
+      'value' => implode(',  ', $average),
+      'type' => CRM_Utils_Type::T_STRING,
+    ];
+
+    // Stats for soft credits
+    if ($this->_softFrom &&
+      CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) !=
+      'contributions_only'
+    ) {
+      $totalAmount = $average = [];
+      $count = 0;
+      $select = "
+SELECT COUNT(contribution_soft_civireport.amount ) as count,
+       SUM(contribution_soft_civireport.amount ) as amount,
+       ROUND(AVG(contribution_soft_civireport.amount), 2) as avg,
+       {$this->_aliases['civicrm_contribution']}.currency as currency";
+      $sql = "
+{$select}
+{$this->_softFrom}
+GROUP BY {$this->_aliases['civicrm_contribution']}.currency";
+      $dao = CRM_Core_DAO::executeQuery($sql);
+      $this->addToDeveloperTab($sql);
+      while ($dao->fetch()) {
+        $totalAmount[] = CRM_Utils_Money::format($dao->amount, $dao->currency) . " (" .
+          $dao->count . ")";
+        $average[] = CRM_Utils_Money::format($dao->avg, $dao->currency);
+        $count += $dao->count;
+      }
+      $statistics['counts']['softamount'] = [
+        'title' => ts('Total Amount (Soft Credits)'),
+        'value' => implode(',  ', $totalAmount),
+        'type' => CRM_Utils_Type::T_STRING,
+      ];
+      $statistics['counts']['softcount'] = [
+        'title' => ts('Total Soft Credits'),
+        'value' => $count,
+      ];
+      $statistics['counts']['softavg'] = [
+        'title' => ts('Average (Soft Credits)'),
+        'value' => implode(',  ', $average),
+        'type' => CRM_Utils_Type::T_STRING,
+      ];
+    }
+
+    return $statistics;
+  }
+
+  /**
+   * Build the report query.
+   *
+   * @param bool $applyLimit
+   *
+   * @return string
+   */
+  public function buildQuery($applyLimit = FALSE) {
+    if ($this->isTempTableBuilt) {
+      $this->limit();
+      return "SELECT SQL_CALC_FOUND_ROWS * FROM {$this->temporaryTables['civireport_contribution_detail_temp3']['name']} $this->_orderBy $this->_limit";
+    }
+    return parent::buildQuery($applyLimit);
+  }
+
+  /**
+   * Shared function for preliminary processing.
+   *
+   * This is called by the api / unit tests and the form layer and is
+   * the right place to do 'initial analysis of input'.
+   */
+  public function beginPostProcessCommon() {
+    // CRM-18312 - display soft_credits and soft_credits_for column
+    // when 'Contribution or Soft Credit?' column is not selected
+    if (empty($this->_params['fields']['contribution_or_soft'])) {
+      $this->_params['fields']['contribution_or_soft'] = 1;
+      $this->noDisplayContributionOrSoftColumn = TRUE;
+    }
+
+    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'contributions_only') {
+      $this->isContributionBaseMode = TRUE;
+    }
+    if ($this->isContributionBaseMode &&
+      (!empty($this->_params['fields']['soft_credit_type_id'])
+      || !empty($this->_params['soft_credit_type_id_value']))
+    ) {
+      unset($this->_params['fields']['soft_credit_type_id']);
+      if (!empty($this->_params['soft_credit_type_id_value'])) {
+        $this->_params['soft_credit_type_id_value'] = [];
+        CRM_Core_Session::setStatus(ts('Is it not possible to filter on soft contribution type when not including soft credits.'));
+      }
+    }
+    // 1. use main contribution query to build temp table 1
+    $sql = $this->buildQuery();
+    $this->createTemporaryTable('civireport_contribution_detail_temp1', $sql);
+
+    // 2. customize main contribution query for soft credit, and build temp table 2 with soft credit contributions only
+    $this->queryMode = 'SoftCredit';
+    // Rebuild select with no groupby. Do not let column headers change.
+    $headers = $this->_columnHeaders;
+    $this->select();
+    $this->_columnHeaders = $headers;
+    $this->softCreditFrom();
+    // also include custom group from if included
+    // since this might be included in select
+    $this->customDataFrom();
+
+    $select = str_ireplace('contribution_civireport.total_amount', 'contribution_soft_civireport.amount', $this->_select);
+    $select = str_ireplace("'Contribution' as", "'Soft Credit' as", $select);
+
+    // we inner join with temp1 to restrict soft contributions to those in temp1 table.
+    // no group by here as we want to display as many soft credit rows as actually exist.
+    $sql = "{$select} {$this->_from} {$this->_where} $this->_groupBy";
+    $this->createTemporaryTable('civireport_contribution_detail_temp2', $sql);
+
+    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+      'soft_credits_only'
+    ) {
+      // revise pager : prev, next based on soft-credits only
+      $this->setPager();
+    }
+
+    // copy _from for later use of stats calculation for soft credits, and reset $this->_from to main query
+    $this->_softFrom = $this->_from;
+
+    // simple reset of ->_from
+    $this->from();
+
+    // also include custom group from if included
+    // since this might be included in select
+    $this->customDataFrom();
+
+    // 3. Decide where to populate temp3 table from
+    if ($this->isContributionBaseMode
+    ) {
+      $this->createTemporaryTable('civireport_contribution_detail_temp3',
+        "(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp1']['name']})"
+      );
+    }
+    elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+      'soft_credits_only'
+    ) {
+      $this->createTemporaryTable('civireport_contribution_detail_temp3',
+        "(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp2']['name']})"
+      );
+    }
+    else {
+      $this->createTemporaryTable('civireport_contribution_detail_temp3', "
+(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp1']['name']})
+UNION ALL
+(SELECT * FROM {$this->temporaryTables['civireport_contribution_detail_temp2']['name']})");
+    }
+    $this->isTempTableBuilt = TRUE;
+  }
+
+  /**
+   * Store group bys into array - so we can check elsewhere what is grouped.
+   *
+   * If we are generating a table of soft credits we need to group by them.
+   */
+  protected function storeGroupByArray() {
+    if ($this->queryMode === 'SoftCredit') {
+      $this->_groupByArray = [$this->_aliases['civicrm_contribution_soft'] . '.id'];
+    }
+    else {
+      parent::storeGroupByArray();
+    }
+  }
+
+  /**
+   * Alter display of rows.
+   *
+   * Iterate through the rows retrieved via SQL and make changes for display purposes,
+   * such as rendering contacts as links.
+   *
+   * @param array $rows
+   *   Rows generated by SQL, with an array for each row.
+   */
+  public function alterDisplay(&$rows) {
+    $entryFound = FALSE;
+    $display_flag = $prev_cid = $cid = 0;
+    $contributionTypes = CRM_Contribute_PseudoConstant::financialType();
+    $contributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
+    $paymentInstruments = CRM_Contribute_PseudoConstant::paymentInstrument();
+    $contributionPages = CRM_Contribute_PseudoConstant::contributionPage();
+    $batches = CRM_Batch_BAO_Batch::getBatches();
+    foreach ($rows as $rowNum => $row) {
+      if (!empty($this->_noRepeats) && $this->_outputMode != 'csv') {
+        // don't repeat contact details if its same as the previous row
+        if (array_key_exists('civicrm_contact_id', $row)) {
+          if ($cid = $row['civicrm_contact_id']) {
+            if ($rowNum == 0) {
+              $prev_cid = $cid;
+            }
+            else {
+              if ($prev_cid == $cid) {
+                $display_flag = 1;
+                $prev_cid = $cid;
+              }
+              else {
+                $display_flag = 0;
+                $prev_cid = $cid;
+              }
+            }
+
+            if ($display_flag) {
+              foreach ($row as $colName => $colVal) {
+                // Hide repeats in no-repeat columns, but not if the field's a section header
+                if (in_array($colName, $this->_noRepeats) &&
+                  !array_key_exists($colName, $this->_sections)
+                ) {
+                  unset($rows[$rowNum][$colName]);
+                }
+              }
+            }
+            $entryFound = TRUE;
+          }
+        }
+      }
+
+      if (CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) ==
+        'Contribution'
+      ) {
+        unset($rows[$rowNum]['civicrm_contribution_soft_soft_credit_type_id']);
+      }
+
+      $entryFound = $this->alterDisplayContactFields($row, $rows, $rowNum, 'contribution/detail', ts('View Contribution Details')) ? TRUE : $entryFound;
+      // convert donor sort name to link
+      if (array_key_exists('civicrm_contact_sort_name', $row) &&
+        !empty($rows[$rowNum]['civicrm_contact_sort_name']) &&
+        array_key_exists('civicrm_contact_id', $row)
+      ) {
+        $url = CRM_Utils_System::url("civicrm/contact/view",
+          'reset=1&cid=' . $row['civicrm_contact_id'],
+          $this->_absoluteUrl
+        );
+        $rows[$rowNum]['civicrm_contact_sort_name_link'] = $url;
+        $rows[$rowNum]['civicrm_contact_sort_name_hover'] = ts("View Contact Summary for this Contact.");
+      }
+
+      if ($value = CRM_Utils_Array::value('civicrm_contribution_financial_type_id', $row)) {
+        $rows[$rowNum]['civicrm_contribution_financial_type_id'] = $contributionTypes[$value];
+        $entryFound = TRUE;
+      }
+      if ($value = CRM_Utils_Array::value('civicrm_contribution_contribution_status_id', $row)) {
+        $rows[$rowNum]['civicrm_contribution_contribution_status_id'] = $contributionStatus[$value];
+        $entryFound = TRUE;
+      }
+      if ($value = CRM_Utils_Array::value('civicrm_contribution_contribution_page_id', $row)) {
+        $rows[$rowNum]['civicrm_contribution_contribution_page_id'] = $contributionPages[$value];
+        $entryFound = TRUE;
+      }
+      if ($value = CRM_Utils_Array::value('civicrm_contribution_payment_instrument_id', $row)) {
+        $rows[$rowNum]['civicrm_contribution_payment_instrument_id'] = $paymentInstruments[$value];
+        $entryFound = TRUE;
+      }
+      if (!empty($row['civicrm_batch_batch_id'])) {
+        $rows[$rowNum]['civicrm_batch_batch_id'] = CRM_Utils_Array::value($row['civicrm_batch_batch_id'], $batches);
+        $entryFound = TRUE;
+      }
+      if (!empty($row['civicrm_financial_trxn_card_type_id'])) {
+        $rows[$rowNum]['civicrm_financial_trxn_card_type_id'] = $this->getLabels($row['civicrm_financial_trxn_card_type_id'], 'CRM_Financial_DAO_FinancialTrxn', 'card_type_id');
+        $entryFound = TRUE;
+      }
+
+      // Contribution amount links to viewing contribution
+      if ($value = CRM_Utils_Array::value('civicrm_contribution_total_amount', $row)) {
+        $rows[$rowNum]['civicrm_contribution_total_amount'] = CRM_Utils_Money::format($value, $row['civicrm_contribution_currency']);
+        if (CRM_Core_Permission::check('access CiviContribute')) {
+          $url = CRM_Utils_System::url(
+            "civicrm/contact/view/contribution",
+            [
+              'reset' => 1,
+              'id' => $row['civicrm_contribution_contribution_id'],
+              'cid' => $row['civicrm_contact_id'],
+              'action' => 'view',
+              'context' => 'contribution',
+              'selectedChild' => 'contribute',
+            ],
+            $this->_absoluteUrl
+          );
+          $rows[$rowNum]['civicrm_contribution_total_amount_link'] = $url;
+          $rows[$rowNum]['civicrm_contribution_total_amount_hover'] = ts("View Details of this Contribution.");
+        }
+        $entryFound = TRUE;
+      }
+
+      // convert campaign_id to campaign title
+      if (array_key_exists('civicrm_contribution_campaign_id', $row)) {
+        if ($value = $row['civicrm_contribution_campaign_id']) {
+          $rows[$rowNum]['civicrm_contribution_campaign_id'] = $this->campaigns[$value];
+          $entryFound = TRUE;
+        }
+      }
+
+      // soft credits
+      if (array_key_exists('civicrm_contribution_soft_credits', $row) &&
+        'Contribution' ==
+        CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) &&
+        array_key_exists('civicrm_contribution_contribution_id', $row)
+      ) {
+        $query = "
+SELECT civicrm_contact_id, civicrm_contact_sort_name, civicrm_contribution_total_amount, civicrm_contribution_currency
+FROM   {$this->temporaryTables['civireport_contribution_detail_temp2']['name']}
+WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
+        $dao = CRM_Core_DAO::executeQuery($query);
+        $string = '';
+        $separator = ($this->_outputMode !== 'csv') ? "<br/>" : ' ';
+        while ($dao->fetch()) {
+          $url = CRM_Utils_System::url("civicrm/contact/view", 'reset=1&cid=' .
+            $dao->civicrm_contact_id);
+          $string = $string . ($string ? $separator : '') .
+            "<a href='{$url}'>{$dao->civicrm_contact_sort_name}</a> " .
+            CRM_Utils_Money::format($dao->civicrm_contribution_total_amount, $dao->civicrm_contribution_currency);
+        }
+        $rows[$rowNum]['civicrm_contribution_soft_credits'] = $string;
+      }
+
+      if (array_key_exists('civicrm_contribution_soft_credit_for', $row) &&
+        'Soft Credit' ==
+        CRM_Utils_Array::value('civicrm_contribution_contribution_or_soft', $rows[$rowNum]) &&
+        array_key_exists('civicrm_contribution_contribution_id', $row)
+      ) {
+        $query = "
+SELECT civicrm_contact_id, civicrm_contact_sort_name
+FROM   {$this->temporaryTables['civireport_contribution_detail_temp1']['name']}
+WHERE  civicrm_contribution_contribution_id={$row['civicrm_contribution_contribution_id']}";
+        $dao = CRM_Core_DAO::executeQuery($query);
+        $string = '';
+        while ($dao->fetch()) {
+          $url = CRM_Utils_System::url("civicrm/contact/view", 'reset=1&cid=' .
+            $dao->civicrm_contact_id);
+          $string = $string .
+            "\n<a href='{$url}'>{$dao->civicrm_contact_sort_name}</a>";
+        }
+        $rows[$rowNum]['civicrm_contribution_soft_credit_for'] = $string;
+      }
+
+      // CRM-18312 - hide 'contribution_or_soft' column if unchecked.
+      if (!empty($this->noDisplayContributionOrSoftColumn)) {
+        unset($rows[$rowNum]['civicrm_contribution_contribution_or_soft']);
+        unset($this->_columnHeaders['civicrm_contribution_contribution_or_soft']);
+      }
+
+      //convert soft_credit_type_id into label
+      if (array_key_exists('civicrm_contribution_soft_soft_credit_type_id', $rows[$rowNum])) {
+        $rows[$rowNum]['civicrm_contribution_soft_soft_credit_type_id'] = CRM_Core_PseudoConstant::getLabel(
+          'CRM_Contribute_BAO_ContributionSoft',
+          'soft_credit_type_id',
+          $row['civicrm_contribution_soft_soft_credit_type_id']
+        );
+      }
+
+      $entryFound = $this->alterDisplayAddressFields($row, $rows, $rowNum, 'contribute/detail', 'List all contribution(s) for this ') ? TRUE : $entryFound;
+
+      // skip looking further in rows, if first row itself doesn't
+      // have the column we need
+      if (!$entryFound) {
+        break;
+      }
+      $lastKey = $rowNum;
+    }
+  }
+
+  public function sectionTotals() {
+
+    // Reports using order_bys with sections must populate $this->_selectAliases in select() method.
+    if (empty($this->_selectAliases)) {
+      return;
+    }
+
+    if (!empty($this->_sections)) {
+      // build the query with no LIMIT clause
+      $select = str_ireplace('SELECT SQL_CALC_FOUND_ROWS ', 'SELECT ', $this->_select);
+      $sql = "{$select} {$this->_from} {$this->_where} {$this->_groupBy} {$this->_having} {$this->_orderBy}";
+
+      // pull section aliases out of $this->_sections
+      $sectionAliases = array_keys($this->_sections);
+
+      $ifnulls = [];
+      foreach (array_merge($sectionAliases, $this->_selectAliases) as $alias) {
+        $ifnulls[] = "ifnull($alias, '') as $alias";
+      }
+      $this->_select = "SELECT " . implode(", ", $ifnulls);
+      $this->_select = CRM_Contact_BAO_Query::appendAnyValueToSelect($ifnulls, $sectionAliases);
+
+      /* Group (un-limited) report by all aliases and get counts. This might
+       * be done more efficiently when the contents of $sql are known, ie. by
+       * overriding this method in the report class.
+       */
+
+      $addtotals = '';
+
+      if (array_search("civicrm_contribution_total_amount", $this->_selectAliases) !==
+        FALSE
+      ) {
+        $addtotals = ", sum(civicrm_contribution_total_amount) as sumcontribs";
+        $showsumcontribs = TRUE;
+      }
+
+      $query = $this->_select .
+        "$addtotals, count(*) as ct from {$this->temporaryTables['civireport_contribution_detail_temp3']['name']} group by " .
+        implode(", ", $sectionAliases);
+      // initialize array of total counts
+      $sumcontribs = $totals = [];
+      $dao = CRM_Core_DAO::executeQuery($query);
+      $this->addToDeveloperTab($query);
+      while ($dao->fetch()) {
+
+        // let $this->_alterDisplay translate any integer ids to human-readable values.
+        $rows[0] = $dao->toArray();
+        $this->alterDisplay($rows);
+        $row = $rows[0];
+
+        // add totals for all permutations of section values
+        $values = [];
+        $i = 1;
+        $aliasCount = count($sectionAliases);
+        foreach ($sectionAliases as $alias) {
+          $values[] = $row[$alias];
+          $key = implode(CRM_Core_DAO::VALUE_SEPARATOR, $values);
+          if ($i == $aliasCount) {
+            // the last alias is the lowest-level section header; use count as-is
+            $totals[$key] = $dao->ct;
+            if ($showsumcontribs) {
+              $sumcontribs[$key] = $dao->sumcontribs;
+            }
+          }
+          else {
+            // other aliases are higher level; roll count into their total
+            $totals[$key] = (array_key_exists($key, $totals)) ? $totals[$key] + $dao->ct : $dao->ct;
+            if ($showsumcontribs) {
+              $sumcontribs[$key] = array_key_exists($key, $sumcontribs) ? $sumcontribs[$key] + $dao->sumcontribs : $dao->sumcontribs;
+            }
+          }
+        }
+      }
+      if ($showsumcontribs) {
+        $totalandsum = [];
+        // ts exception to avoid having ts("%1 %2: %3")
+        $title = '%1 contributions / soft-credits: %2';
+
+        if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+          'contributions_only'
+        ) {
+          $title = '%1 contributions: %2';
+        }
+        elseif (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) ==
+          'soft_credits_only'
+        ) {
+          $title = '%1 soft-credits: %2';
+        }
+        foreach ($totals as $key => $total) {
+          $totalandsum[$key] = ts($title, [
+            1 => $total,
+            2 => CRM_Utils_Money::format($sumcontribs[$key]),
+          ]);
+        }
+        $this->assign('sectionTotals', $totalandsum);
+      }
+      else {
+        $this->assign('sectionTotals', $totals);
+      }
+    }
+  }
+
+  /**
+   * Generate the from clause as it relates to the soft credits.
+   */
+  public function softCreditFrom() {
+
+    $this->_from = "
+      FROM  {$this->temporaryTables['civireport_contribution_detail_temp1']['name']} temp1_civireport
+      INNER JOIN civicrm_contribution {$this->_aliases['civicrm_contribution']}
+        ON temp1_civireport.civicrm_contribution_contribution_id = {$this->_aliases['civicrm_contribution']}.id
+      INNER JOIN civicrm_contribution_soft contribution_soft_civireport
+        ON contribution_soft_civireport.contribution_id = {$this->_aliases['civicrm_contribution']}.id
+      INNER JOIN civicrm_contact      {$this->_aliases['civicrm_contact']}
+        ON {$this->_aliases['civicrm_contact']}.id = contribution_soft_civireport.contact_id
+      {$this->_aclFrom}
+    ";
+
+    //Join temp table if report is filtered by group. This is specific to 'notin' operator and covered in unit test(ref dev/core#212)
+    if (!empty($this->_params['gid_op']) && $this->_params['gid_op'] == 'notin') {
+      $this->joinGroupTempTable('civicrm_contact', 'id', $this->_aliases['civicrm_contact']);
+    }
+    $this->appendAdditionalFromJoins();
+  }
+
+  /**
+   * Append the joins that are required regardless of context.
+   */
+  public function appendAdditionalFromJoins() {
+    if (!empty($this->_params['ordinality_value'])) {
+      $this->_from .= "
+              INNER JOIN (SELECT c.id, IF(COUNT(oc.id) = 0, 0, 1) AS ordinality FROM civicrm_contribution c LEFT JOIN civicrm_contribution oc ON c.contact_id = oc.contact_id AND oc.receive_date < c.receive_date GROUP BY c.id) {$this->_aliases['civicrm_contribution_ordinality']}
+                      ON {$this->_aliases['civicrm_contribution_ordinality']}.id = {$this->_aliases['civicrm_contribution']}.id";
+    }
+    $this->joinPhoneFromContact();
+    $this->joinAddressFromContact();
+    $this->joinEmailFromContact();
+
+    // include contribution note
+    if (!empty($this->_params['fields']['contribution_note']) ||
+      !empty($this->_params['note_value'])
+    ) {
+      $this->_from .= "
+            LEFT JOIN civicrm_note {$this->_aliases['civicrm_note']}
+                      ON ( {$this->_aliases['civicrm_note']}.entity_table = 'civicrm_contribution' AND
+                           {$this->_aliases['civicrm_contribution']}.id = {$this->_aliases['civicrm_note']}.entity_id )";
+    }
+    //for contribution batches
+    if (!empty($this->_params['fields']['batch_id']) ||
+      !empty($this->_params['bid_value'])
+    ) {
+      $this->_from .= "
+        LEFT JOIN civicrm_entity_financial_trxn eft
+          ON eft.entity_id = {$this->_aliases['civicrm_contribution']}.id AND
+            eft.entity_table = 'civicrm_contribution'
+        LEFT JOIN civicrm_entity_batch {$this->_aliases['civicrm_batch']}
+          ON ({$this->_aliases['civicrm_batch']}.entity_id = eft.financial_trxn_id
+          AND {$this->_aliases['civicrm_batch']}.entity_table = 'civicrm_financial_trxn')";
+    }
+    // for credit card type
+    $this->addFinancialTrxnFromClause();
+    $this->addIatsJournalFromClauses();
+  }
+
+  public function addIatsJournalFromClauses() {
+    if ($this->isTableSelected('civicrm_iats_faps_journal')) {
+      $this->_from .= "LEFT JOIN civicrm_iats_faps_journal {$this->_aliases['civicrm_iats_faps_journal']}
+           ON {$this->_aliases['civicrm_contribution']}.invoice_id = {$this->_aliases['civicrm_iats_faps_journal']}.orderId \n";
+    }
+  }
+
+  /**
+   * Add join to the soft credit table.
+   */
+  protected function joinContributionToSoftCredit() {
+    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'contributions_only'
+      && !$this->isTableSelected('civicrm_contribution_soft')) {
+      return;
+    }
+    $joinType = ' LEFT ';
+    if (CRM_Utils_Array::value('contribution_or_soft_value', $this->_params) == 'soft_credits_only') {
+      $joinType = ' INNER ';
+    }
+    $this->_from .= "
+      $joinType JOIN civicrm_contribution_soft {$this->_aliases['civicrm_contribution_soft']}
+      ON {$this->_aliases['civicrm_contribution_soft']}.contribution_id = {$this->_aliases['civicrm_contribution']}.id
+   ";
+  }
+
+}
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.mgd.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Journal.mgd.php
similarity index 86%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.mgd.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Report/Journal.mgd.php
index 2d2af53e6c4ce5cef81da8e02d8f102f854f8e3f..69cfedde43f32e5ac6cb16f048794ce0595d7083 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.mgd.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Journal.mgd.php
@@ -11,14 +11,14 @@
 return array(
   0 =>
   array(
-    'name' => 'CRM_iATS_Form_Report_Journal',
+    'name' => 'CRM_Iats_Form_Report_Journal',
     'entity' => 'ReportTemplate',
     'params' =>
     array(
       'version' => 3,
       'label' => 'iATS Payments - Journal',
       'description' => 'iATS Payments - Journal Report',
-      'class_name' => 'CRM_iATS_Form_Report_Journal',
+      'class_name' => 'CRM_Iats_Form_Report_Journal',
       'report_url' => 'com.iatspayments.com/journal',
       'component' => 'CiviContribute',
     ),
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Journal.php
similarity index 98%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Report/Journal.php
index e6c52d52a6a8a065f1f69f3cfc1d3154fe16909c..e7866b5738c34d51d3a239d0767ccb87e1394973 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Journal.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Journal.php
@@ -10,7 +10,7 @@ require_once('CRM/Report/Form.php');
  *
  * $Id$
  */
-class CRM_iATS_Form_Report_Journal extends CRM_Report_Form {
+class CRM_Iats_Form_Report_Journal extends CRM_Report_Form {
 
   // protected $_customGroupExtends = array('Contact');
 
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.mgd.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/JournalFaps.mgd.php
similarity index 59%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.mgd.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Report/JournalFaps.mgd.php
index 16743f18db25c7154b09fd23895cff3692f9693f..eb0525efabce544a0dc59fedfa2340cac4bf57fc 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/ContributeDetail.mgd.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/JournalFaps.mgd.php
@@ -11,15 +11,15 @@
 return array(
   0 =>
   array(
-    'name' => 'CRM_iATS_Form_Report_ContributeDetail',
+    'name' => 'CRM_Iats_Form_Report_JournalFaps',
     'entity' => 'ReportTemplate',
     'params' =>
     array(
       'version' => 3,
-      'label' => 'iATS Payments - Contribution Reconciliation',
-      'description' => 'Donor Report (Detail) Report with extra iATS Reconciliation fields.',
-      'class_name' => 'CRM_iATS_Form_Report_ContributeDetail',
-      'report_url' => 'com.iatspayments.com/contributedetail',
+      'label' => 'iATS Payments 1stPay - Journal',
+      'description' => 'iATS Payments 1stPay - Journal Report',
+      'class_name' => 'CRM_Iats_Form_Report_JournalFaps',
+      'report_url' => 'com.iatspayments.com/journalfaps',
       'component' => 'CiviContribute',
     ),
   ),
diff --git a/civicrm/ext/iatspayments/CRM/Iats/Form/Report/JournalFaps.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/JournalFaps.php
new file mode 100644
index 0000000000000000000000000000000000000000..316585bbdd73f8467e5975d4f0000217ddaac841
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/JournalFaps.php
@@ -0,0 +1,113 @@
+<?php
+
+require_once('CRM/Report/Form.php');
+
+/**
+ * @file
+ */
+
+/**
+ *
+ * $Id$
+ */
+class CRM_Iats_Form_Report_JournalFaps extends CRM_Report_Form {
+
+  // protected $_customGroupExtends = array('Contact');
+
+  /* static private $processors = array();
+  static private $version = array();
+  static private $financial_types = array();
+  static private $prefixes = array(); */
+  static private $contributionStatus = array(); 
+  static private $card_types = array( 
+    'Visa' => 'Visa',
+    'Mastercard' => 'MasterCard',
+    'AMEX' => 'AMEX',
+    'Discover' => 'Discover',
+  );
+
+  /**
+   *
+   */
+  public function __construct() {
+    self::$contributionStatus = CRM_Contribute_BAO_Contribution::buildOptions('contribution_status_id');
+    $this->_columns = array(
+      'civicrm_iats_faps_journal' =>
+        array(
+          'fields' =>
+            array(
+              'id' => array('title' => 'CiviCRM Journal Id', 'default' => TRUE),
+              'transactionId' => array('title' => '1stPay Transaction Id', 'default' => TRUE),
+              'isAch' => array('title' => 'isACH', 'default' => TRUE),
+              'processorId' => array('title' => 'Processor Id', 'default' => TRUE),
+              'cimRefNumber' => array('title' => 'Customer code', 'default' => TRUE),
+              'orderId' => array('title' => 'Invoice Reference', 'default' => TRUE),
+              'transDateAndTime' => array('title' => 'Transaction date', 'default' => TRUE),
+              'amount' => array('title' => 'Amount', 'default' => TRUE),
+              'authResponse' => array('title' => 'Response string', 'default' => TRUE),
+              'currency' => array('title' => 'Currency', 'default' => TRUE),
+              'status_id' => array('title' => 'Payment Status', 'default' => TRUE),
+            ),
+          'order_bys' => 
+            array(
+              'id' => array('title' => ts('CiviCRM Journal Id'), 'default' => TRUE, 'default_order' => 'DESC'),
+              'transactionId' => array('title' => ts('1stPay Transaction Id')),
+              'transDateAndTime' => array('title' => ts('Transaction Date Time')),
+            ),
+          'filters' =>
+             array(
+               'transDateAndTime' => array(
+                 'title' => 'Transaction date', 
+                 'operatorType' => CRM_Report_Form::OP_DATE,
+                 'type' => CRM_Utils_Type::T_DATE,
+               ),
+               'orderId' => array(
+                 'title' => 'Invoice Reference', 
+                 'type' => CRM_Utils_Type::T_STRING,
+               ),
+               'amount' => array(
+                 'title' => 'Amount', 
+                 'operatorType' => CRM_Report_Form::OP_FLOAT,
+                 'type' => CRM_Utils_Type::T_FLOAT
+               ),
+               /*'isAch' => array(
+                 'title' => 'Type', 
+                 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+                 'options' => self::$transaction_types,
+                 'type' => CRM_Utils_Type::T_STRING,
+               ), */
+               'processorId' => array(
+                 'title' => 'Processor Id',
+                 'type' => CRM_Utils_Type::T_STRING,
+               ),
+               'authResponse' => array(
+                 'title' => 'Response string',
+                 'type' => CRM_Utils_Type::T_STRING,
+               ),
+               'status_id' => array(
+                 'title' => ts('Payment Status'),
+                 'operatorType' => CRM_Report_Form::OP_MULTISELECT,
+                 'options' => self::$contributionStatus,
+                 'type' => CRM_Utils_Type::T_INT,
+               ),
+             ),
+        ),
+    );
+    parent::__construct();
+  }
+
+  /**
+   *
+   */
+  public function getTemplateName() {
+    return 'CRM/Report/Form.tpl';
+  }
+
+  /**
+   *
+   */
+  public function from() {
+    $this->_from = "FROM civicrm_iats_faps_journal  {$this->_aliases['civicrm_iats_faps_journal']}";
+  }
+
+}
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.mgd.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Recur.mgd.php
similarity index 87%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.mgd.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Report/Recur.mgd.php
index 507c4343bbbc9e80bc177ffdaab7fb467534307a..55c1fe34c04164c67230fec4db4c01d63367348a 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.mgd.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Recur.mgd.php
@@ -11,14 +11,14 @@
 return array(
   0 =>
   array(
-    'name' => 'CRM_iATS_Form_Report_Recur',
+    'name' => 'CRM_Iats_Form_Report_Recur',
     'entity' => 'ReportTemplate',
     'params' =>
     array(
       'version' => 3,
       'label' => 'iATS Payments - Recurring Contributions',
       'description' => 'iATS Payments - Recurring Contributions Report',
-      'class_name' => 'CRM_iATS_Form_Report_Recur',
+      'class_name' => 'CRM_Iats_Form_Report_Recur',
       'report_url' => 'com.iatspayments.com/recur',
       'component' => 'CiviContribute',
     ),
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Recur.php
similarity index 91%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Report/Recur.php
index c5bbd1b75ee6c6d798f7f118e118baab3bf69c0e..bb98151201387304fff7d7f261dc6114674c3a65 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Recur.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Recur.php
@@ -33,11 +33,10 @@
  * @copyright CiviCRM LLC (c) 2004-2013
  * $Id$
  */
-class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
+class CRM_Iats_Form_Report_Recur extends CRM_Report_Form {
 
   protected $_customGroupExtends = array('Contact');
 
-  static private $nscd_fid = '';
   static private $processors = array();
   static private $version = array();
   static private $financial_types = array();
@@ -49,7 +48,6 @@ class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
    */
   public function __construct() {
 
-    self::$nscd_fid = _iats_civicrm_nscd_fid();
     self::$version = _iats_civicrm_domain_info('version');
     self::$financial_types = (self::$version[0] <= 4 && self::$version[1] <= 2) ? array() : CRM_Contribute_PseudoConstant::financialType();
     if (self::$version[0] <= 4 && self::$version[1] < 4) {
@@ -129,13 +127,11 @@ class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
             'required' => TRUE,
             'dbAlias' => "GROUP_CONCAT(contribution_civireport.id SEPARATOR ', ')",
           ),
-          'total_amount' => array(
-            'title' => ts('Amount Contributed to date'),
-            'required' => TRUE,
-            'statistics' => array(
-              'sum' => ts("Total Amount contributed"),
-            ),
-          ),
+          'total_amount_sum' => array(
+	    'title' => ts('Amount - to date'),
+	    'required' => TRUE,
+	    'dbAlias' => "SUM(contribution_civireport.total_amount)",
+	  ),
         ),
         'filters' => array(
           'total_amount' => array(
@@ -145,18 +141,18 @@ class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
           ),
         ),
       ),
-      'civicrm_iats_customer_codes' =>
+      'civicrm_payment_token' =>
         array(
           'dao' => 'CRM_Contribute_DAO_Contribution',
           'order_bys' => array(
-            'expiry' => array(
+            'expiry_date' => array(
               'title' => ts("Expiry Date"),
             ),
           ),
           'fields' =>
             array(
-              'customer_code' => array('title' => 'customer code', 'default' => TRUE),
-              'expiry' => array('title' => 'Expiry Date', 'default' => TRUE),
+              'token' => array('title' => 'customer code', 'default' => TRUE),
+              'expiry_date' => array('title' => 'Expiry Date', 'default' => TRUE),
             ),
         ),
       'civicrm_contribution_recur' => array(
@@ -174,7 +170,7 @@ class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
           'modified_date' => array(
             'title' => ts('Modified Date'),
           ),
-          self::$nscd_fid  => array(
+          'next_sched_contribution_date' => array(
             'title' => ts('Next Scheduled Contribution Date'),
           ),
           'cycle_day'  => array(
@@ -240,7 +236,7 @@ class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
           'cancel_date' => array(
             'title' => ts('Cancel Date'),
           ),
-          self::$nscd_fid => array(
+          'next_sched_contribution_date' => array(
             'title' => ts('Next Scheduled Contribution Date'),
             'default' => TRUE,
           ),
@@ -290,7 +286,7 @@ class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
             'operatorType' => CRM_Report_Form::OP_MULTISELECT,
             'options' => CRM_Core_OptionGroup::values('recur_frequency_units'),
           ),
-          self::$nscd_fid  => array(
+          'next_sched_contribution_date' => array(
             'title' => ts('Next Scheduled Contribution Date'),
             'operatorType' => CRM_Report_Form::OP_DATE,
             'type' => CRM_Utils_Type::T_DATE,
@@ -401,8 +397,8 @@ class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
         ON ({$this->_aliases['civicrm_contact']}.id = {$this->_aliases['civicrm_phone']}.contact_id AND
           {$this->_aliases['civicrm_phone']}.is_primary = 1)";
     $this->_from .= "
-      LEFT JOIN civicrm_iats_customer_codes {$this->_aliases['civicrm_iats_customer_codes']}
-        ON ({$this->_aliases['civicrm_iats_customer_codes']}.recur_id = {$this->_aliases['civicrm_contribution_recur']}.id)";
+      LEFT JOIN civicrm_payment_token {$this->_aliases['civicrm_payment_token']}
+        ON ({$this->_aliases['civicrm_payment_token']}.id = {$this->_aliases['civicrm_contribution_recur']}.payment_token_id)";
   }
 
   /**
@@ -445,16 +441,6 @@ class CRM_iATS_Form_Report_Recur extends CRM_Report_Form {
         $entryFound = TRUE;
       }
 
-      // Handle expiry date.
-      if ($value = CRM_Utils_Array::value('civicrm_iats_customer_codes_expiry', $row)) {
-        if ($rows[$rowNum]['civicrm_iats_customer_codes_expiry'] == '0000') {
-          $rows[$rowNum]['civicrm_iats_customer_codes_expiry'] = ' ';
-        }
-        elseif ($rows[$rowNum]['civicrm_iats_customer_codes_expiry'] != '0000') {
-          $rows[$rowNum]['civicrm_iats_customer_codes_expiry'] = '20' . substr($rows[$rowNum]['civicrm_iats_customer_codes_expiry'], 0, 2) . '/' . substr($rows[$rowNum]['civicrm_iats_customer_codes_expiry'], 2, 2);
-        }
-      }
-
       // Handle contribution status id.
       if ($value = CRM_Utils_Array::value('civicrm_contribution_recur_contribution_status_id', $row)) {
         $rows[$rowNum]['civicrm_contribution_recur_contribution_status_id'] = self::$contributionStatus[$value];
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.mgd.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Verify.mgd.php
similarity index 86%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.mgd.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Report/Verify.mgd.php
index bdada3dae43a9c89bf9e3cbc87655149d531e0a3..d5a4a0da4402f34d06b19835dae7e16e98b9daaf 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.mgd.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Verify.mgd.php
@@ -11,14 +11,14 @@
 return array(
   0 =>
   array(
-    'name' => 'CRM_iATS_Form_Report_Verify',
+    'name' => 'CRM_Iats_Form_Report_Verify',
     'entity' => 'ReportTemplate',
     'params' =>
     array(
       'version' => 3,
       'label' => 'iATS Payments - Verify',
       'description' => 'iATS Payments - Verify Report',
-      'class_name' => 'CRM_iATS_Form_Report_Verify',
+      'class_name' => 'CRM_Iats_Form_Report_Verify',
       'report_url' => 'com.iatspayments.com/verify',
       'component' => 'CiviContribute',
     ),
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Verify.php
similarity index 98%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Report/Verify.php
index f88e24864999eb92e68ff5ffa24e83dfcfd362ad..28953c0ee9600aeb400ce5acff10264aacd4b840 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/Report/Verify.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Report/Verify.php
@@ -10,7 +10,7 @@ require_once('CRM/Report/Form.php');
  *
  * $Id$
  */
-class CRM_iATS_Form_Report_Verify extends CRM_Report_Form {
+class CRM_Iats_Form_Report_Verify extends CRM_Report_Form {
 
   static private $contributionStatus = array(); 
   static private $transaction_types = array(
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/IatsSettings.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Settings.php
similarity index 82%
rename from civicrm/ext/iatspayments/CRM/iATS/Form/IatsSettings.php
rename to civicrm/ext/iatspayments/CRM/Iats/Form/Settings.php
index 96bf910fd760c5b869264afa4c321361df78dbb3..f36087fdee5c9024dc1d434103ad1040556fa1be 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/IatsSettings.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Settings.php
@@ -1,67 +1,61 @@
 <?php
 
-/**
- * @file
- */
-
-require_once 'CRM/Core/Form.php';
+use CRM_Iats_ExtensionUtil as E;
 
 /**
- * Form controller class.
+ * Form controller class
  *
- * @see http://wiki.civicrm.org/confluence/display/CRMDOC43/QuickForm+Reference
+ * @see https://wiki.civicrm.org/confluence/display/CRMDOC/QuickForm+Reference
  */
-class CRM_iATS_Form_IatsSettings extends CRM_Core_Form {
-
-  /**
-   *
-   */
+class CRM_Iats_Form_Settings extends CRM_Core_Form {
   public function buildQuickForm() {
 
     // Add form elements.
     $this->add(
-    // Field type.
       'text',
-    // Field name.
       'email_recurring_failure_report',
       ts('Email Recurring Contribution failure reports to this Email address')
     );
     $this->addRule('email_recurring_failure_report', ts('Email address is not a valid format.'), 'email');
     $this->add(
-    // Field type.
       'text',
-    // Field name.
       'recurring_failure_threshhold',
       ts('When failure count is equal to or greater than this number, push the next scheduled contribution date forward')
     );
     $this->addRule('recurring_failure_threshhold', ts('Threshhold must be a positive integer.'), 'integer');
     $receipt_recurring_options =  array('0' => 'Never', '1' => 'Always', '2' => 'As set for a specific Contribution Series');
     $this->add(
-    // Field type.
       'select',
-    // Field name.
       'receipt_recurring',
       ts('Email receipt for a Contribution in a Recurring Series'),
       $receipt_recurring_options
     );
 
     $this->add(
-    // Field type.
       'checkbox',
-    // Field name.
+      'disable_cryptogram',
+      ts('Disable use of cryptogram (only applies to FirstAmerican).')
+    );
+    
+    $this->add(
+      'text',
+      'ach_category_text',
+      ts('ACH Category Text (only applies to FirstAmerican).')
+    );
+
+    $this->add(
+      'checkbox',
       'no_edit_extra',
       ts('Disable extra edit fields for Recurring Contributions')
     );
 
     $this->add(
-    // Field type.
       'checkbox',
-    // Field name.
       'enable_update_subscription_billing_info',
       ts('Enable self-service updates to recurring contribution Contact Billing Info.')
     );
 
-    /* These checkboxes are not yet implemented, ignore for now 
+    /* These checkboxes are not yet implemented, ignore for now
     $this->add(
       'checkbox', // field type
       'import_quick', // field name
@@ -97,9 +91,7 @@ class CRM_iATS_Form_IatsSettings extends CRM_Core_Form {
       'required' => FALSE,
     );
     $day_select = $this->add(
-    // Field type.
       'select',
-    // Field name.
       'days',
       ts('Restrict allowable days of the month for Recurring Contributions'),
       $days,
@@ -122,26 +114,27 @@ class CRM_iATS_Form_IatsSettings extends CRM_Core_Form {
     if (empty($defaults['recurring_failure_threshhold'])) {
       $defaults['recurring_failure_threshhold'] = 3;
     }
+    if (empty($defaults['ach_category_text'])) {
+      $defaults['ach_category_text'] = FAPS_DEFAULT_ACH_CATEGORY_TEXT;
+    }
     $this->setDefaults($defaults);
+
     $this->addButtons(array(
       array(
         'type' => 'submit',
-        'name' => ts('Submit'),
+        'name' => E::ts('Submit'),
         'isDefault' => TRUE,
       ),
     ));
 
-    // Export form elements.
+    // export form elements
     $this->assign('elementNames', $this->getRenderableElementNames());
     parent::buildQuickForm();
   }
 
-  /**
-   *
-   */
   public function postProcess() {
     $values = $this->exportValues();
-    foreach (array('qfKey', '_qf_default', '_qf_IatsSettings_submit', 'entryURL') as $key) {
+    foreach (array('qfKey', '_qf_default', '_qf_Settings_submit', 'entryURL') as $key) {
       if (isset($values[$key])) {
         unset($values[$key]);
       }
@@ -162,6 +155,7 @@ class CRM_iATS_Form_IatsSettings extends CRM_Core_Form {
     // the 'label'.
     $elementNames = array();
     foreach ($this->_elements as $element) {
+      /** @var HTML_QuickForm_Element $element */
       $label = $element->getLabel();
       if (!empty($label)) {
         $elementNames[] = $element->getName();
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Page/IATSCustomerLink.php b/civicrm/ext/iatspayments/CRM/Iats/Page/IATSCustomerLink.php
similarity index 86%
rename from civicrm/ext/iatspayments/CRM/iATS/Page/IATSCustomerLink.php
rename to civicrm/ext/iatspayments/CRM/Iats/Page/IATSCustomerLink.php
index c96cd8f6b092c5ab5b0046f221030a35586c21f8..09e615123d2f21422a066206647bf330eff0b2a8 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Page/IATSCustomerLink.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Page/IATSCustomerLink.php
@@ -8,7 +8,7 @@ require_once 'CRM/Core/Page.php';
 /**
  *
  */
-class CRM_iATS_Page_IATSCustomerLink extends CRM_Core_Page {
+class CRM_Iats_Page_IATSCustomerLink extends CRM_Core_Page {
 
   /**
    *
@@ -20,10 +20,9 @@ class CRM_iATS_Page_IATSCustomerLink extends CRM_Core_Page {
     $paymentProcessorId = CRM_Utils_Request::retrieve('paymentProcessorId', 'Positive');
     $is_test = CRM_Utils_Request::retrieve('is_test', 'Integer');
     $this->assign('customerCode', $customerCode);
-    require_once "CRM/iATS/iATSService.php";
-    $credentials = iATS_Service_Request::credentials($paymentProcessorId, $is_test);
+    $credentials = CRM_Iats_iATSServiceRequest::credentials($paymentProcessorId, $is_test);
     $iats_service_params = array('type' => 'customer', 'iats_domain' => $credentials['domain'], 'method' => 'get_customer_code_detail');
-    $iats = new iATS_Service_Request($iats_service_params);
+    $iats = new CRM_Iats_iATSServiceRequest($iats_service_params);
     // print_r($iats); die();
     $request = array('customerCode' => $customerCode);
     // Make the soap request.
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Page/iATSAdmin.php b/civicrm/ext/iatspayments/CRM/Iats/Page/iATSAdmin.php
similarity index 94%
rename from civicrm/ext/iatspayments/CRM/iATS/Page/iATSAdmin.php
rename to civicrm/ext/iatspayments/CRM/Iats/Page/iATSAdmin.php
index 6c34df42f957bee1bc9a8c982f188ceb147c27b2..787573e9db8e503a2db8b63f8641f7edc3b9db45 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Page/iATSAdmin.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Page/iATSAdmin.php
@@ -9,15 +9,14 @@ require_once 'CRM/Core/Page.php';
 /**
  *
  */
-class CRM_iATS_Page_iATSAdmin extends CRM_Core_Page {
+class CRM_Iats_Page_iATSAdmin extends CRM_Core_Page {
 
   /**
    *
    */
   public function run() {
     // Reset the saved version of the extension.
-    require_once 'CRM/iATS/iATSService.php';
-    $iats_extension_version = iATS_Service_Request::iats_extension_version(1);
+    $iats_extension_version = CRM_Iats_iATSServiceRequest::iats_extension_version(1);
     // The current time.
     $this->assign('currentVersion', $iats_extension_version);
     $this->assign('currentTime', date('Y-m-d H:i:s'));
@@ -76,7 +75,6 @@ class CRM_iATS_Page_iATSAdmin extends CRM_Core_Page {
     $className = get_class($dao);
     $internal = array_keys(get_class_vars($className));
     // Get some customer data while i'm at it
-    // require_once("CRM/iATS/iATSService.php");
     // todo: fix iats_domain below
     // $iats_service_params = array('type' => 'customer', 'method' => 'get_customer_code_detail', 'iats_domain' => 'www.iatspayments.com');.
     while ($dao->fetch()) {
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Page/json.php b/civicrm/ext/iatspayments/CRM/Iats/Page/json.php
similarity index 94%
rename from civicrm/ext/iatspayments/CRM/iATS/Page/json.php
rename to civicrm/ext/iatspayments/CRM/Iats/Page/json.php
index 15faf8edd66f935838d1c47ef83c452898e6514f..d35630443eeea2b06c9852f120734e0ca31d8113 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Page/json.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Page/json.php
@@ -7,7 +7,7 @@
 /**
  *
  */
-class CRM_iATS_Page_json {
+class CRM_Iats_Page_json {
 
   /**
    *
@@ -46,8 +46,7 @@ class CRM_iATS_Page_json {
     }
     // TODO: bail here if I don't have enough for my service request
     // use the iATSService object for interacting with iATS.
-    require_once "CRM/iATS/iATSService.php";
-    $iats = new iATS_Service_Request($options);
+    $iats = new CRM_Iats_iATSServiceRequest($options);
     $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
     // Make the soap request.
     $response = $iats->request($credentials, $request);
diff --git a/civicrm/ext/iatspayments/CRM/Iats/Transaction.php b/civicrm/ext/iatspayments/CRM/Iats/Transaction.php
new file mode 100644
index 0000000000000000000000000000000000000000..1dc28981bad7f76f5eefa2ea456a830efd3ec1ce
--- /dev/null
+++ b/civicrm/ext/iatspayments/CRM/Iats/Transaction.php
@@ -0,0 +1,414 @@
+<?php
+/**
+ * @file IATS Service transaction utility class
+ *
+ * Various functions that used to live in the iats.php file,
+ * now converted into static functions of this class and generalised
+ * to work with both legacy and FAP processors.
+ *
+ **/
+
+/**
+ * Class CRM_Iats_Transaction
+ */
+class CRM_Iats_Transaction {
+
+  /**
+   * For a recurring contribution, find a reasonable candidate for a template, where possible.
+   */
+  static function getContributionTemplate($contribution) {
+    // Get the most recent contribution in this series that matches the same total_amount, if present.
+    $template = array();
+    $get = ['contribution_recur_id' => $contribution['contribution_recur_id'], 'options' => ['sort' => ' id DESC', 'limit' => 1]];
+    if (!empty($contribution['total_amount'])) {
+      $get['total_amount'] = $contribution['total_amount'];
+    }
+    $result = civicrm_api3('contribution', 'get', $get);
+    if (!empty($result['values'])) {
+      $template = reset($result['values']);
+      $contribution_id = $template['id'];
+      $template['original_contribution_id'] = $contribution_id;
+      $template['line_items'] = array();
+      $get = array('entity_table' => 'civicrm_contribution', 'entity_id' => $contribution_id);
+      $result = civicrm_api3('LineItem', 'get', $get);
+      if (!empty($result['values'])) {
+        foreach ($result['values'] as $initial_line_item) {
+          $line_item = array();
+          foreach (array('price_field_id', 'qty', 'line_total', 'unit_price', 'label', 'price_field_value_id', 'financial_type_id') as $key) {
+            $line_item[$key] = $initial_line_item[$key];
+          }
+          $template['line_items'][] = $line_item;
+        }
+      }
+    }
+    return $template;
+  }
+  
+  /**
+   * Function contributionrecur_next.
+   *
+   * @param $from_time: a unix time stamp, the function returns values greater than this
+   * @param $days: an array of allowable days of the month
+   *
+   *   A utility function to calculate the next available allowable day, starting from $from_time.
+   *   Strategy: increment the from_time by one day until the day of the month matches one of my available days of the month.
+   */
+  static function contributionrecur_next($from_time, $allow_mdays) {
+    $dp = getdate($from_time);
+    // So I don't get into an infinite loop somehow.
+    $i = 0;
+    while (($i++ < 60) && !in_array($dp['mday'], $allow_mdays)) {
+      $from_time += (24 * 60 * 60);
+      $dp = getdate($from_time);
+    }
+    return $from_time;
+  }
+  
+  /**
+   * Function contribution_payment
+   *
+   * @param $contribution an array of a contribution to be created (or in case of future start date,
+            possibly an existing pending contribution to recycle, if it already has a contribution id).
+   * @param $paymentProcessor an array of a payment processor record to use
+   * @param $payment_token an array of a payment processor specific token data
+   *        code
+   *
+   *   A high-level utility function for making a contribution payment from an existing recurring schedule
+   *   Used in the Iatsrecurringcontributions.php job and the one-time ('card on file') form.
+   *   
+   */
+  static function process_contribution_payment(&$contribution, $paymentProcessor, $payment_token) {
+    // By default, don't use repeattransaction
+    $use_repeattransaction = FALSE;
+    $is_recurrence = !empty($contribution['original_contribution_id']);
+    // First try and get the money, using my process_payment cover function.
+    $payment_result = self::process_payment($contribution, $paymentProcessor, $payment_token);
+    $success = $payment_result['success'];
+    $auth_code = $payment_result['auth_code'];
+    $auth_response = $payment_result['auth_response'];
+    $trxn_id = $payment_result['trxn_id'];
+    // Handle any case of a failure of some kind, either the card failed, or the system failed.
+    if (!$success) {
+      $error_message = $payment_result['message'];
+      /* set the failed transaction status, or pending if I had a server issue */
+      $contribution['contribution_status_id'] = empty($auth_code) ? 2 : 4;
+      /* and include the reason in the source field */
+      $contribution['source'] .= ' ' . $error_message;
+    }
+    else {
+      // I have a transaction id.
+      $contribution['trxn_id'] = $trxn_id;
+      // Initialize the status to pending
+      $contribution['contribution_status_id'] = 2;
+      // We'll use the repeattransaction api for successful transactions under two conditions:
+      // 1. if we want it (i.e. if it's for a recurring schedule)
+      // 2. if we don't already have a contribution id
+      $use_repeattransaction = $is_recurrence && empty($contribution['id']);
+    }
+    if ($use_repeattransaction) {
+      // We processed it successflly and I can try to use repeattransaction. 
+      // Requires the original contribution id.
+      // Issues with this api call:
+      // 1. Always triggers an email [update: not anymore?] and doesn't include trxn.
+      // 2. Date is wrong.
+      try {
+        // $status = $result['contribution_status_id'] == 1 ? 'Completed' : 'Pending';
+        $contributionResult = civicrm_api3('Contribution', 'repeattransaction', array(
+          'original_contribution_id' => $contribution['original_contribution_id'],
+          'contribution_status_id' => 'Pending',
+          'is_email_receipt' => 0,
+          // 'invoice_id' => $contribution['invoice_id'],
+          ///'receive_date' => $contribution['receive_date'],
+          // 'campaign_id' => $contribution['campaign_id'],
+          // 'financial_type_id' => $contribution['financial_type_id'],.
+          // 'payment_processor_id' => $contribution['payment_processor'],
+          'contribution_recur_id' => $contribution['contribution_recur_id'],
+        ));
+        // watchdog('iats_civicrm','repeat transaction result <pre>@params</pre>',array('@params' => print_r($pending,TRUE)));.
+        $contribution['id'] = CRM_Utils_Array::value('id', $contributionResult);
+      }
+      catch (Exception $e) {
+        // Ignore this, though perhaps I should log it.
+      }
+      if (empty($contribution['id'])) {
+        // Assume I failed completely and I'll fall back to doing it the manual way.
+        $use_repeattransaction = FALSE;
+      }
+      else {
+        // If repeattransaction succeded.
+        // First restore/add various fields that the repeattransaction api may overwrite or ignore.
+        // TODO - fix this in core to allow these to be set above.
+        civicrm_api3('contribution', 'create', array('id' => $contribution['id'], 
+          'invoice_id' => $contribution['invoice_id'],
+          'source' => $contribution['source'],
+          'receive_date' => $contribution['receive_date'],
+          'payment_instrument_id' => $contribution['payment_instrument_id'],
+          // '' => $contribution['receive_date'],
+        ));
+        // Save my status in the contribution array that was passed in.
+        $contribution['contribution_status_id'] = $payment_result['payment_status_id'];
+        if ($contribution['contribution_status_id'] == 1) {
+          // My transaction completed, so record that fact in CiviCRM, potentially sending an invoice.
+          try {
+            civicrm_api3('Contribution', 'completetransaction', array(
+              'id' => $contribution['id'],
+              'payment_processor_id' => $contribution['payment_processor'],
+              'is_email_receipt' => (empty($contribution['is_email_receipt']) ? 0 : 1),
+              'trxn_id' => $contribution['trxn_id'],
+              'receive_date' => $contribution['receive_date'],
+            ));
+          }
+          catch (Exception $e) {
+            // log the error and continue
+            CRM_Core_Error::debug_var('Unexpected Exception', $e);
+          }
+        }
+        else {
+          // just save my trxn_id for ACH verification later
+          try {
+            civicrm_api3('Contribution', 'create', array(
+              'id' => $contribution['id'],
+              'trxn_id' => $contribution['trxn_id'],
+            ));
+          }
+          catch (Exception $e) {
+            // log the error and continue
+            CRM_Core_Error::debug_var('Unexpected Exception', $e);
+          }
+        }
+      }
+    }
+    if (!$use_repeattransaction) {
+      /* If I'm not using repeattransaction for any reason, I'll create the contribution manually */
+      // This code assumes that the contribution_status_id has been set properly above, either pending or failed.
+      $contributionResult = civicrm_api3('contribution', 'create', $contribution);
+      // Pass back the created id indirectly since I'm calling by reference.
+      $contribution['id'] = CRM_Utils_Array::value('id', $contributionResult);
+      // Connect to a membership if requested.
+      if (!empty($contribution['membership_id'])) {
+        try {
+          civicrm_api3('MembershipPayment', 'create', array('contribution_id' => $contribution['id'], 'membership_id' => $contribution['membership_id']));
+        }
+        catch (Exception $e) {
+          // Ignore.
+        }
+      }
+      /* And then I'm done unless it completed */
+      if ($payment_result['payment_status_id'] == 1 && $success) {
+        /* success, and the transaction has completed */
+        $complete = array('id' => $contribution['id'], 
+          'payment_processor_id' => $contribution['payment_processor'],
+          'trxn_id' => $trxn_id, 
+          'receive_date' => $contribution['receive_date']
+        );
+        $complete['is_email_receipt'] = empty($contribution['is_email_receipt']) ? 0 : 1;
+        try {
+          $contributionResult = civicrm_api3('contribution', 'completetransaction', $complete);
+        }
+        catch (Exception $e) {
+          // Don't throw an exception here, or else I won't have updated my next contribution date for example.
+          $contribution['source'] .= ' [with unexpected api.completetransaction error: ' . $e->getMessage() . ']';
+        }
+        // Restore my source field that ipn code irritatingly overwrites, and make sure that the trxn_id is set also.
+        civicrm_api3('contribution', 'setvalue', array('id' => $contribution['id'], 'value' => $contribution['source'], 'field' => 'source'));
+        civicrm_api3('contribution', 'setvalue', array('id' => $contribution['id'], 'value' => $trxn_id, 'field' => 'trxn_id'));
+        // $message = $is_recurrence ? ts('Successfully processed contribution in recurring series id %1: ', array(1 => $contribution['contribution_recur_id'])) : ts('Successfully processed one-time contribution: ');
+      }
+    }
+    // Now return the appropriate message and code.
+    if (!$success) { // calling function will restore next schedule contribution date
+      $message = ts('Failed to process recurring contribution id %1: %2', array(1 => $contribution['contribution_recur_id'], 2 => $payment_result['message']));
+    }
+    elseif ($payment_result['payment_status_id'] == 1) {
+      $message = ts('Successfully processed recurring contribution in series id %1: %2', array(1 => $contribution['contribution_recur_id'], 2 => $auth_response));
+    }
+    else {
+      // I'm using ACH or a processor that doesn't complete.
+      $message = ts('Successfully processed pending recurring contribution in series id %1: %2', array(1 => $contribution['contribution_recur_id'], 2 => $auth_response));
+    }
+    return array('message' => $message, 'result' => $payment_result);
+  }
+
+  /**
+   * Function process_payment
+   *
+   * @param $contribution an array of properties of a contribution to be processed
+   * @param $paymentProcessor an array of a payment processor record
+   * @param $payment_token an array of a payment processor specific values for this
+   *        transaction, e.g. client or vault code
+   *
+   * return an array of return values
+   *   success boolean
+   *   trxn_id transaction id to store in civicrm
+   *   payment_status_id payment status id to store in case of success
+   *   auth_code authorization code returned - if empty, then it's a server
+   *   failure
+   *   result  raw payment processor-dependent array/object
+   *
+   *   A low-level utility function for triggering a payment transaction on iATS using a card on file.
+   */
+  static function process_payment($contribution, $paymentProcessor, $payment_token) {
+    // set default result status
+    $result = [
+      'payment_status_id' => 1,
+      'auth_code' => '',
+    ];
+    $request = [
+    ];
+    switch ($paymentProcessor['class_name']) {
+      case 'Payment_FapsACH':
+        $paymentProcessorGroup = 'Faps';
+        $action = 'AchDebitUsingVault';
+        // Will complete later
+        $result['payment_status_id'] = 2;
+        // store it in request 
+        $credentials = array(
+          'merchantKey' => $paymentProcessor['signature'],
+          'processorId' => $paymentProcessor['user_name']
+        );
+        $request['categoryText'] = CRM_Core_Payment_FapsACH::getCategoryText($credentials, $contribution['is_test']);
+        break;
+      case 'Payment_Faps':
+        $paymentProcessorGroup = 'Faps';
+        $action = 'SaleUsingVault';
+        $credentials = array(
+          'merchantKey' => $paymentProcessor['signature'],
+          'processorId' => $paymentProcessor['user_name']
+        );
+        break;
+      case 'Payment_iATSServiceACHEFT':
+        $paymentProcessorGroup = 'iATS';
+        $method = 'acheft_with_customer_code';
+        // Will complete later.
+        $result['payment_status_id'] = 2;
+        break;
+      case 'Payment_iATSService':
+      case 'Payment_iATSServiceSWIPE':
+        $paymentProcessorGroup = 'iATS';
+        $method = 'cc_with_customer_code';
+        break;
+      default:
+        CRM_Core_Error::debug_var('Unsupported processor class:', $paymentProcessor['class_name']);
+        throw new Exception(ts('Unsupported processor class %1', array(1 => $paymentProcessor['class_name'])));
+    }
+
+    // Two different "group" flows, either Faps or iATS Legacy
+    switch ($paymentProcessorGroup) {
+      case 'Faps':
+        $service_params = array('action' => $action);
+        $faps = new CRM_Iats_FapsRequest($service_params);
+        // Build the request array.
+        // CRM_Core_Error::debug_var('options', $options);
+        // TODO: Get the vault key!
+        list($vaultKey,$vaultId) = explode(':', $payment_token['token'], 2);
+        $request = $request + array(
+          'vaultKey' => $vaultKey,
+          'vaultId' => $vaultId,
+          'orderId' => $contribution['invoice_id'],
+          'transactionAmount' => sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($contribution['total_amount'])),
+        );
+        // Make the request.
+        // CRM_Core_Error::debug_var('process transaction request', $request);
+        $result['result'] = $faps->request($credentials, $request);
+        $data = empty($result['result']['data']) ? [] : $result['result']['data'];
+        // CRM_Core_Error::debug_var('process transaction result', $result);
+        $result['success'] = !empty($result['result']['isSuccess']);
+        if ($result['success']) {
+          $result['trxn_id'] = empty($data['referenceNumber']) ? '' : trim($data['referenceNumber']).':'.time();
+          $result['auth_code'] = empty($data['authCode']) ? '' : trim($data['authCode']);
+          $result['message'] = $result['auth_response'] = empty($data['authResponse']) ? '' : trim($data['authResponse']);
+        }
+        else {
+          $result['message'] = $result['result']['errorMessages'];
+        }
+        /* in case of critical failure set the series to pending */
+        switch ($result['auth_code']) {
+          // Reported lost or stolen.
+          case 'REJECT: 25':
+            // Do not reprocess!
+          case 'REJECT: 100':
+            /* convert the contribution series to pending to avoid reprocessing until dealt with */
+            civicrm_api('ContributionRecur', 'create',
+              array(
+                'version' => 3,
+                'id'      => $contribution['contribution_recur_id'],
+                'contribution_status_id'   => 'Pending',
+              )
+            );
+            break;
+        }
+        break;
+      case 'iATS':
+        $credentials = array(
+          'agentCode' => $paymentProcessor['user_name'],
+          'password' => $paymentProcessor['password'],
+          'domain' => parse_url($paymentProcessor['url_site'], PHP_URL_HOST),
+        );
+        $iats_service_params = array('method' => $method, 'type' => 'process', 'iats_domain' => $credentials['domain']);
+        $iats = new CRM_Iats_iATSServiceRequest($iats_service_params);
+        // Build the request array.
+        $request = array(
+          'customerCode' => $payment_token['token'],
+          'invoiceNum' => $contribution['invoice_id'],
+          'total' => $contribution['total_amount'],
+          'customerIPAddress' => '',
+        );
+        // Make the soap request.
+        $response = $iats->request($credentials, $request);
+        // Process the soap response into a readable result.
+        $result['result'] = $iats->result($response);
+        $result['success'] = !empty($result['result']['status']);
+        if ($result['success']) {
+          $result['trxn_id'] = trim($result['result']['remote_id']) . ':' . time();
+          $result['message'] = $result['auth_code'] = $result['result']['auth_result'];
+        }
+        else {
+          $result['message'] = $result['result']['reasonMessage'];
+        }
+        break;
+      default:
+        CRM_Core_Error::debug_var('Unsupported processor group:', $paymentProcessorGroup);
+        throw new Exception(ts('Unsupported processor group %1', array(1 => $paymentProcessorGroup)));
+    }
+    return $result;
+  }
+  
+  /**
+   * Function get_future_start_dates
+   *
+   * @param $start_date a timestamp, only return dates after this.
+   * @param $allow_days an array of allowable days of the month.
+   *
+   *   A low-level utility function for triggering a transaction on iATS.
+   */
+  static function get_future_monthly_start_dates($start_date, $allow_days) {
+    // Future date options.
+    $start_dates = array();
+    // special handling for today - it means immediately or now.
+    $today = date('Ymd').'030000';
+    // If not set, only allow for the first 28 days of the month.
+    if (max($allow_days) <= 0) {
+      $allow_days = range(1,28);
+    }
+    for ($j = 0; $j < count($allow_days); $j++) {
+      // So I don't get into an infinite loop somehow ..
+      $i = 0;
+      $dp = getdate($start_date);
+      while (($i++ < 60) && !in_array($dp['mday'], $allow_days)) {
+        $start_date += (24 * 60 * 60);
+        $dp = getdate($start_date);
+      }
+      $key = date('Ymd', $start_date).'030000';
+      if ($key == $today) { // special handling
+        $display = ts('Now');
+        $key = ''; // date('YmdHis');
+      }
+      else {
+        $display = strftime('%B %e, %Y', $start_date);
+      }
+      $start_dates[$key] = $display;
+      $start_date += (24 * 60 * 60);
+    }
+    return $start_dates;
+  }
+}
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Upgrader.php b/civicrm/ext/iatspayments/CRM/Iats/Upgrader.php
similarity index 69%
rename from civicrm/ext/iatspayments/CRM/iATS/Upgrader.php
rename to civicrm/ext/iatspayments/CRM/Iats/Upgrader.php
index 74d6ff61daae26723b47507c959ed803e49054aa..b6843bc577bce57ee6ed620cf43258850c804c35 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Upgrader.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Upgrader.php
@@ -3,7 +3,7 @@
 /**
  * Collection of upgrade steps
  */
-class CRM_iATS_Upgrader extends CRM_iATS_Upgrader_Base {
+class CRM_Iats_Upgrader extends CRM_Iats_Upgrader_Base {
 
   // By convention, functions that look like "function upgrade_NNNN()" are
   // upgrade tasks. They are executed in order (like Drupal's hook_update_N).
@@ -147,6 +147,61 @@ class CRM_iATS_Upgrader extends CRM_iATS_Upgrader_Base {
     return TRUE;
   }
 
+  public function upgrade_1_7_001() {
+    $this->ctx->log->info('Applying update 1_7_001');
+    try {
+      $this->executeSqlFile('sql/upgrade_1_7_001.sql');
+    }
+    catch (Exception $e) {
+      $this->ctx->log->info($e->getMessage());
+    }
+    return TRUE;
+  }
+
+  /* convert any iATS legacy iats_customer_codes to using the payment_token table */
+  public function upgrade_1_7_002() {
+    $this->ctx->log->info('Applying update 1_7_002');
+    try {
+      $insert = 'INSERT INTO civicrm_payment_token (contact_id, payment_processor_id, token, ip_address, email) 
+        SELECT cr.contact_id, cr.payment_processor_id, icc.customer_code, icc.ip, icc.email FROM civicrm_contribution_recur cr INNER JOIN civicrm_iats_customer_codes icc ON cr.id = icc.recur_id';
+      $dao = CRM_Core_DAO::executeQuery($insert);
+      $update = 'UPDATE civicrm_contribution_recur cr INNER JOIN civicrm_iats_customer_codes icc ON cr.id = icc.recur_id 
+        INNER JOIN civicrm_payment_token pt on pt.token = icc.customer_code SET cr.payment_token_id = pt.id';
+      $dao = CRM_Core_DAO::executeQuery($update);
+      $rename = 'RENAME TABLE `civicrm_iats_customer_codes` TO `backup_iats_customer_codes`';
+      $dao = CRM_Core_DAO::executeQuery($rename);
+    }
+    catch (Exception $e) {
+      $this->ctx->log->info($e->getMessage());
+    }
+    return TRUE;
+  }
+
+  /* convert any earlier versions of FAPS recurring payment records */
+  public function upgrade_1_7_003() {
+    $this->ctx->log->info('Applying update 1_7_003');
+    try {
+      $insert = 'INSERT INTO civicrm_payment_token (contact_id, payment_processor_id, token)
+        SELECT cr.contact_id, cr.payment_processor_id, cr.processor_id FROM civicrm_contribution_recur cr
+        INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+        WHERE NOT(ISNULL(processor_id)) AND pp.class_name LIKE "Payment_Faps%"';
+      $dao = CRM_Core_DAO::executeQuery($insert);
+      $update = 'UPDATE civicrm_contribution_recur cr
+        INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+        INNER JOIN civicrm_payment_token pt on pt.token = cr.processor_id
+        SET cr.payment_token_id = pt.id WHERE pp.class_name LIKE "Payment_Faps%"';
+      $dao = CRM_Core_DAO::executeQuery($update);
+      $rename = 'UPDATE civicrm_contribution_recur cr
+        INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
+        SET cr.processor_id = NULL WHERE pp.class_name LIKE "Payment_Faps%"';
+      $dao = CRM_Core_DAO::executeQuery($rename);
+    }
+    catch (Exception $e) {
+      $this->ctx->log->info($e->getMessage());
+    }
+    return TRUE;
+  }
+
 
   /**
    * Example: Run an external SQL script
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Upgrader/Base.php b/civicrm/ext/iatspayments/CRM/Iats/Upgrader/Base.php
similarity index 54%
rename from civicrm/ext/iatspayments/CRM/iATS/Upgrader/Base.php
rename to civicrm/ext/iatspayments/CRM/Iats/Upgrader/Base.php
index 6cf61525acc7983e5bcf74bdfc3f5b95514dd973..956bcaf5b14273ec520307abb6b325fd026ceae9 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/Upgrader/Base.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/Upgrader/Base.php
@@ -1,17 +1,15 @@
 <?php
 
-/**
- * @file
- * AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file.
- */
+// AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file
+use CRM_Iats_ExtensionUtil as E;
 
 /**
- * Base class which provides helpers to execute upgrade logic.
+ * Base class which provides helpers to execute upgrade logic
  */
-class CRM_iATS_Upgrader_Base {
+class CRM_Iats_Upgrader_Base {
 
   /**
-   * @var variessubclassofhtis
+   * @var varies, subclass of this
    */
   static $instance;
 
@@ -21,27 +19,33 @@ class CRM_iATS_Upgrader_Base {
   protected $ctx;
 
   /**
-   * @var stringegcomexamplemyextension
+   * @var string, eg 'com.example.myextension'
    */
   protected $extensionName;
 
   /**
-   * @var stringfullpathtotheextensionssourcetree
+   * @var string, full path to the extension's source tree
    */
   protected $extensionDir;
 
   /**
-   * @var arrayrevisionNumbersortednumerically
+   * @var array(revisionNumber) sorted numerically
    */
   private $revisions;
 
   /**
-   * Obtain a refernece to the active upgrade handler.
+   * @var boolean
+   *   Flag to clean up extension revision data in civicrm_setting
+   */
+  private $revisionStorageIsDeprecated = FALSE;
+
+  /**
+   * Obtain a reference to the active upgrade handler.
    */
   static public function instance() {
     if (!self::$instance) {
-      // FIXME auto-generate.
-      self::$instance = new CRM_iATS_Upgrader(
+      // FIXME auto-generate
+      self::$instance = new CRM_Iats_Upgrader(
         'com.iatspayments.civicrm',
         realpath(__DIR__ . '/../../../')
       );
@@ -56,7 +60,7 @@ class CRM_iATS_Upgrader_Base {
    * task-context; otherwise, this will be non-reentrant.
    *
    * @code
-   * CRM_iATS_Upgrader_Base::_queueAdapter($ctx, 'methodName', 'arg1', 'arg2');
+   * CRM_Iats_Upgrader_Base::_queueAdapter($ctx, 'methodName', 'arg1', 'arg2');
    * @endcode
    */
   static public function _queueAdapter() {
@@ -68,21 +72,17 @@ class CRM_iATS_Upgrader_Base {
     return call_user_func_array(array($instance, $method), $args);
   }
 
-  /**
-   *
-   */
   public function __construct($extensionName, $extensionDir) {
     $this->extensionName = $extensionName;
     $this->extensionDir = $extensionDir;
   }
 
-  // ******** Task helpers ********.
+  // ******** Task helpers ********
+
   /**
    * Run a CustomData file.
    *
-   * @param string $relativePath
-   *   the CustomData XML file path (relative to this extension's dir)
-   *
+   * @param string $relativePath the CustomData XML file path (relative to this extension's dir)
    * @return bool
    */
   public function executeCustomDataFile($relativePath) {
@@ -91,15 +91,13 @@ class CRM_iATS_Upgrader_Base {
   }
 
   /**
-   * Run a CustomData file.
+   * Run a CustomData file
    *
-   * @param string $xml_file
-   *   the CustomData XML file path (absolute path)
+   * @param string $xml_file  the CustomData XML file path (absolute path)
    *
    * @return bool
    */
   protected static function executeCustomDataFileByAbsPath($xml_file) {
-    require_once 'CRM/Utils/Migrate/Import.php';
     $import = new CRM_Utils_Migrate_Import();
     $import->run($xml_file);
     return TRUE;
@@ -108,15 +106,33 @@ class CRM_iATS_Upgrader_Base {
   /**
    * Run a SQL file.
    *
-   * @param string $relativePath
-   *   the SQL file path (relative to this extension's dir)
+   * @param string $relativePath the SQL file path (relative to this extension's dir)
    *
    * @return bool
    */
   public function executeSqlFile($relativePath) {
     CRM_Utils_File::sourceSQLFile(
       CIVICRM_DSN,
-      $this->extensionDir . '/' . $relativePath
+      $this->extensionDir . DIRECTORY_SEPARATOR . $relativePath
+    );
+    return TRUE;
+  }
+
+  /**
+   * @param string $tplFile
+   *   The SQL file path (relative to this extension's dir).
+   *   Ex: "sql/mydata.mysql.tpl".
+   * @return bool
+   */
+  public function executeSqlTemplate($tplFile) {
+    // Assign multilingual variable to Smarty.
+    $upgrade = new CRM_Upgrade_Form();
+
+    $tplFile = CRM_Utils_File::isAbsolute($tplFile) ? $tplFile : $this->extensionDir . DIRECTORY_SEPARATOR . $tplFile;
+    $smarty = CRM_Core_Smarty::singleton();
+    $smarty->assign('domainID', CRM_Core_Config::domainID());
+    CRM_Utils_File::sourceSQLFile(
+      CIVICRM_DSN, $smarty->fetch($tplFile), NULL, TRUE
     );
     return TRUE;
   }
@@ -126,17 +142,18 @@ class CRM_iATS_Upgrader_Base {
    *
    * This is just a wrapper for CRM_Core_DAO::executeSql, but it
    * provides syntatic sugar for queueing several tasks that
-   * run different queries.
+   * run different queries
    */
   public function executeSql($query, $params = array()) {
-    // FIXME verify that we raise an exception on error.
-    CRM_Core_DAO::executeSql($query, $params);
+    // FIXME verify that we raise an exception on error
+    CRM_Core_DAO::executeQuery($query, $params);
     return TRUE;
   }
 
   /**
-   * Syntatic sugar for enqueuing a task which calls a function
-   * in this class. The task is weighted so that it is processed
+   * Syntatic sugar for enqueuing a task which calls a function in this class.
+   *
+   * The task is weighted so that it is processed
    * as part of the currently-pending revision.
    *
    * After passing the $funcName, you can also pass parameters that will go to
@@ -153,9 +170,9 @@ class CRM_iATS_Upgrader_Base {
     return $this->queue->createItem($task, array('weight' => -1));
   }
 
-  // ******** Revision-tracking helpers ********.
-  
-/**
+  // ******** Revision-tracking helpers ********
+
+  /**
    * Determine if there are any pending revisions.
    *
    * @return bool
@@ -188,7 +205,8 @@ class CRM_iATS_Upgrader_Base {
           2 => $revision,
         ));
 
-        // note: don't use addTask() because it sets weight=-1.
+        // note: don't use addTask() because it sets weight=-1
+
         $task = new CRM_Queue_Task(
           array(get_class($this), '_queueAdapter'),
           array('upgrade_' . $revision),
@@ -228,83 +246,121 @@ class CRM_iATS_Upgrader_Base {
     return $this->revisions;
   }
 
-  /**
-   *
-   */
   public function getCurrentRevision() {
-    // Return CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName);.
+    $revision = CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName);
+    if (!$revision) {
+      $revision = $this->getCurrentRevisionDeprecated();
+    }
+    return $revision;
+  }
+
+  private function getCurrentRevisionDeprecated() {
     $key = $this->extensionName . ':version';
-    return CRM_Core_BAO_Setting::getItem('Extension', $key);
+    if ($revision = CRM_Core_BAO_Setting::getItem('Extension', $key)) {
+      $this->revisionStorageIsDeprecated = TRUE;
+    }
+    return $revision;
   }
 
-  /**
-   *
-   */
   public function setCurrentRevision($revision) {
-    // We call this during hook_civicrm_install, but the underlying SQL
-    // UPDATE fails because the extension record hasn't been INSERTed yet.
-    // Instead, track revisions in our own namespace.
-    // CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision);.
-    $key = $this->extensionName . ':version';
-    CRM_Core_BAO_Setting::setItem($revision, 'Extension', $key);
+    CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision);
+    // clean up legacy schema version store (CRM-19252)
+    $this->deleteDeprecatedRevision();
     return TRUE;
   }
 
+  private function deleteDeprecatedRevision() {
+    if ($this->revisionStorageIsDeprecated) {
+      $setting = new CRM_Core_BAO_Setting();
+      $setting->name = $this->extensionName . ':version';
+      $setting->delete();
+      CRM_Core_Error::debug_log_message("Migrated extension schema revision ID for {$this->extensionName} from civicrm_setting (deprecated) to civicrm_extension.\n");
+    }
+  }
+
+  // ******** Hook delegates ********
+
   /**
-   * Hook delegates ********.
+   * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install
    */
   public function onInstall() {
-    foreach (glob($this->extensionDir . '/sql/*_install.sql') as $file) {
-      CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+    $files = glob($this->extensionDir . '/sql/*_install.sql');
+    if (is_array($files)) {
+      foreach ($files as $file) {
+        CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+      }
     }
-    foreach (glob($this->extensionDir . '/xml/*_install.xml') as $file) {
-      $this->executeCustomDataFileByAbsPath($file);
+    $files = glob($this->extensionDir . '/sql/*_install.mysql.tpl');
+    if (is_array($files)) {
+      foreach ($files as $file) {
+        $this->executeSqlTemplate($file);
+      }
+    }
+    $files = glob($this->extensionDir . '/xml/*_install.xml');
+    if (is_array($files)) {
+      foreach ($files as $file) {
+        $this->executeCustomDataFileByAbsPath($file);
+      }
     }
     if (is_callable(array($this, 'install'))) {
       $this->install();
     }
+  }
+
+  /**
+   * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall
+   */
+  public function onPostInstall() {
     $revisions = $this->getRevisions();
     if (!empty($revisions)) {
       $this->setCurrentRevision(max($revisions));
     }
+    if (is_callable(array($this, 'postInstall'))) {
+      $this->postInstall();
+    }
   }
 
   /**
-   *
+   * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall
    */
   public function onUninstall() {
+    $files = glob($this->extensionDir . '/sql/*_uninstall.mysql.tpl');
+    if (is_array($files)) {
+      foreach ($files as $file) {
+        $this->executeSqlTemplate($file);
+      }
+    }
     if (is_callable(array($this, 'uninstall'))) {
       $this->uninstall();
     }
-    foreach (glob($this->extensionDir . '/sql/*_uninstall.sql') as $file) {
-      CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+    $files = glob($this->extensionDir . '/sql/*_uninstall.sql');
+    if (is_array($files)) {
+      foreach ($files as $file) {
+        CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file);
+      }
     }
-    $this->setCurrentRevision(NULL);
   }
 
   /**
-   *
+   * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable
    */
   public function onEnable() {
-    // Stub for possible future use.
+    // stub for possible future use
     if (is_callable(array($this, 'enable'))) {
       $this->enable();
     }
   }
 
   /**
-   *
+   * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable
    */
   public function onDisable() {
-    // Stub for possible future use.
+    // stub for possible future use
     if (is_callable(array($this, 'disable'))) {
       $this->disable();
     }
   }
 
-  /**
-   *
-   */
   public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) {
     switch ($op) {
       case 'check':
diff --git a/civicrm/ext/iatspayments/CRM/iATS/iATSService.php b/civicrm/ext/iatspayments/CRM/Iats/iATSServiceRequest.php
similarity index 98%
rename from civicrm/ext/iatspayments/CRM/iATS/iATSService.php
rename to civicrm/ext/iatspayments/CRM/Iats/iATSServiceRequest.php
index 6c6d02cae05aeb63ff8fa1ebfbec659f2ffb8875..ace24b8f788334c8731eee1c2225e6278eddfc0a 100644
--- a/civicrm/ext/iatspayments/CRM/iATS/iATSService.php
+++ b/civicrm/ext/iatspayments/CRM/Iats/iATSServiceRequest.php
@@ -14,7 +14,7 @@
  * TODO: provide logging options for the request, exception and response
  *
  * Expected usage:
- * $iats = new iATS_Service_Request($options)
+ * $iats = new iATSServiceRequest($options)
  * where options usually include
  *   type: 'report', 'customer', 'process'
  *   method: 'cc', etc. as appropriate for that type
@@ -28,7 +28,7 @@
 /**
  *
  */
-class iATS_Service_Request {
+class CRM_Iats_iATSServiceRequest {
 
   // iATS transaction mode definitions:
   const iATS_TXN_NS = 'xmlns';
@@ -92,9 +92,8 @@ class iATS_Service_Request {
             }
           }
           elseif ('direct_debit' == substr($method, 0, 12)) {
-            if (in_array($options['currencyID'], array('GBP'))) {
-              $valid = TRUE;
-            }
+            // discontinued, generate an error
+            $valid = FALSE;
           }
           break;
       }
@@ -169,6 +168,7 @@ class iATS_Service_Request {
         4 => array('', 'String'),
         5 => array($logged_request['total'], 'String'),
       );
+      // CRM_Core_Error::debug_var('query params to request log', $query_params);
       CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_request_log
         (invoice_num, ip, cc, customer_code, total, request_datetime) VALUES (%1, %2, %3, %4, %5, NOW())", $query_params);
       if (!$this->is_ipv4($ip)) {
@@ -220,8 +220,11 @@ class iATS_Service_Request {
       }
     }
     catch (SoapFault $exception) {
-      if (!empty($this->options['debug'])) {
-        CRM_Core_Error::debug_var('SoapFault Exception', $exception);
+      // always log soap faults to the CiviCRM error log
+      CRM_Core_Error::debug_var('SoapFault Exception', $exception);
+      CRM_Core_Error::debug_var('SoapFault Code',$exception->faultcode);
+      CRM_Core_Error::debug_var('SoapFault String',$exception->faultstring);
+      if (!empty($this->options['debug']) && !empty($soapClient)) {
         $response_log = "\n HEADER:\n";
         $response_log .= $soapClient->__getLastResponseHeaders();
         $response_log .= "\n BODY:\n";
diff --git a/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php b/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php
deleted file mode 100644
index 03dd92a3096446bfdc00ce72ed3e3bdd5b1a862d..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-class IATSCustomerUpdateBillingInfo extends CRM_iATS_Form_IATSCustomerLink {
-
-  public $updatedBillingInfo;
-
-  public function __construct() {
-    // no need to call all the form init stuff, we're a fake form
-  }
-
-  public function exportValues($elementList = NULL, $filterInternal = FALSE) {
-
-    $ubi = $this->updatedBillingInfo;
-    // updatedBillingInfo array changed sometime after 4.7.27
-    $crid = !empty($ubi['crid']) ? $ubi['crid'] : $ubi['recur_id'];
-    if (empty($crid)) {
-      $alert = ts('This system is unable to perform self-service updates to credit cards. Please contact the administrator of this site.');
-      throw new Exception($alert);
-    } 
-    $mop = array(
-      'Visa' => 'VISA',
-      'MasterCard' => 'MC',
-      'Amex' => 'AMX',
-      'Discover' => 'DSC',
-    );
-
-    $dao = CRM_Core_DAO::executeQuery("SELECT cr.payment_processor_id, cc.customer_code, cc.cid
-      FROM civicrm_contribution_recur cr
-      LEFT JOIN civicrm_iats_customer_codes cc ON cr.id = cc.recur_id
-      WHERE cr.id=%1", array(1 => array($crid, 'Int')));
-    $dao->fetch();
-
-    $values = array(
-      'cid' => $dao->cid,
-      'customerCode' => $dao->customer_code,
-      'paymentProcessorId' => $dao->payment_processor_id,
-      'is_test' => 0,
-      'creditCardCustomerName' => "{$ubi['first_name']} " . (!empty($ubi['middle_name']) ? "{$ubi['middle_name']} " : '') . $ubi['last_name'],
-      'address' => $ubi['street_address'],
-      'city' => $ubi['city'],
-      'state' => CRM_Core_DAO::singleValueQuery("SELECT abbreviation FROM civicrm_state_province WHERE id=%1", array(1 => array($ubi['state_province_id'], 'Int'))),
-      'zipCode' => $ubi['postal_code'],
-      'creditCardNum' => $ubi['credit_card_number'],
-      'creditCardExpiry' => sprintf('%02d/%02d', $ubi['month'], $ubi['year'] % 100),
-      'mop' => $mop[$ubi['credit_card_type']],
-    );
-
-    return $values;
-
-  }
-
-}
diff --git a/civicrm/ext/iatspayments/LICENSE.txt b/civicrm/ext/iatspayments/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..88ae787b13535274f2c5a192683c95478d44cfaf
--- /dev/null
+++ b/civicrm/ext/iatspayments/LICENSE.txt
@@ -0,0 +1,667 @@
+Package: com.iatspayments.civicrm
+Copyright (C) 2018, Alan Dixon <iats@blackflysolutions.ca>
+Licensed under the GNU Affero Public License 3.0 (below).
+
+-------------------------------------------------------------------------------
+
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/civicrm/ext/iatspayments/README.md b/civicrm/ext/iatspayments/README.md
index 865e2a6c35ec4f10c19b4a14a91f7aaa589633f9..7324fe481027804eaf1943c4e5865301657f6f19 100644
--- a/civicrm/ext/iatspayments/README.md
+++ b/civicrm/ext/iatspayments/README.md
@@ -57,8 +57,6 @@ Extension Testing Notes
 
   * iATS Payments SWIPE: not easy to test - even if you have an Encrypted USB Card Reader (sourced by iATS Payments) you will need a physical fake credit card with: 4222222222222220 security code = 123 and any future Expiration date in the magnetic strip - to process any $amount.
 
-  * iATS Payments UK Direct Debit: use 12345678 for Account Number; 000000 for Sort Code
-
 7. iATS has another test VISA: 41111111111111111 security code = 123 and any future Expiration date
 
 8. Reponses for a transaction with VISA: 41111111111111111 depend on the $amount processed - as follows
@@ -76,23 +74,18 @@ Extension Testing Notes
   * 16.00 REJ: 2;
   * Other Amount REJ: 15
 
-9. After completing a TEST payment -> check the Contributions -> Dashboard. Credit Card Transactions are authorized (=Completed) right away. ACH/EFT + UK direct Debit will be (=Pending).
+9. After completing a TEST payment -> check the Contributions -> Dashboard. Credit Card Transactions are authorized (=Completed) right away. ACH/EFT will be (=Pending).
 
 10. Visit https://home.iatspayments.com -> and click the Client Login button (top right)
   * Login with TEST88 and TEST88
   * hit Reports -> Journal - Credit Card Transactions -> Get Journal -> if it has been a busy day there will be lots of transactions here - so hit display all and scroll down to see the transaction you just processed via CiviCRM.
   * hit Reports -> Journal - ACHEFT Transactions -> List Batches (the test transaction will be here until it is sent to the bank for processing - after that - and depending on the Result - it will appear in either the ACHEFT Approval or the ACHEFT Reject journal.
 
-11. For iATS Payments UK Direct Debit -> visit: https://www.uk.iatspayments.com
-  * Login with UDDD88 and DDTESTUK
-  * hit Virtual Terminal -> Customer Database -> Search by Name -> hit Edit icon (on the left) -> to see all details, including the Reference Number (which should match up with what you saw on your Thank you screen in CiviCRM). 
-  * NOTE: Each charity needs to have a BACS accredited supplier vet their CiviCRM Direct Debit - Contribution Pages
-
-12. If things don't look right, you can turn on Drupal and CiviCRM logging - try another TEST transaction - and then see some detailed logging of the SOAP exchanges for more hints about where it might have gone wrong.
+11. If things don't look right, you can turn on Drupal and CiviCRM logging - try another TEST transaction - and then see some detailed logging of the SOAP exchanges for more hints about where it might have gone wrong.
 
-13. To test recurring contributions - try creating a recurring contribution for every day and then go back the next day and manually trigger Scheduled Job: iATS Payments Recurring Contributions
+12. To test recurring contributions - try creating a recurring contribution for every day and then go back the next day and manually trigger Scheduled Job: iATS Payments Recurring Contributions
 
-14. To test ACH/EFT contributions - manually run Scheduled Job: iATS Payments Verification - it will check with iATS to see if there is any word from the bank yet. How long it takes before a yeah or neah is available depends on the day of the week and the time the transaction is submitted. It can take overnight (over weekend) to get a verification. 
+13. To test ACH/EFT contributions - manually run Scheduled Job: iATS Payments Verification - it will check with iATS to see if there is any word from the bank yet. How long it takes before a yeah or neah is available depends on the day of the week and the time the transaction is submitted. It can take overnight (over weekend) to get a verification. 
 
 Once you're happy all is well - then all you need to do is update the Payment Processor data - with your own iATS' Agent Code and Password.
 
@@ -123,3 +116,5 @@ Please note that ACH Returns require manually processing. iATS Payments will not
 Caution on the use of Pricesets in recurring contributions. This extension will try to use the original transactions' line items. But there are two separate issues here. First, CiviCRM API does an incomplete job with the bookkeeping of line items, so if you need detailed bookkeeping of line items in recurring contributions, you may be disappointed. Separately, if the total amount of the recurring contribution has changed, then there's no machine way of reliably re-allocating it into the original line items, so in that case, they are not used at all. Though not always ideal, a workaround might be to do different transactions for different types of CiviCRM payments instead.
 
 Please post an issue to the github repository if you have any questions.
+=======
+# com.iatspayments.civicrm
diff --git a/civicrm/ext/iatspayments/api/v3/FapsTransaction/Get.php b/civicrm/ext/iatspayments/api/v3/FapsTransaction/Get.php
new file mode 100644
index 0000000000000000000000000000000000000000..9c19b3428a1648862cab5c4758ce3e2ea2b50ef3
--- /dev/null
+++ b/civicrm/ext/iatspayments/api/v3/FapsTransaction/Get.php
@@ -0,0 +1,40 @@
+<?php
+use CRM_Iats_ExtensionUtil as E;
+
+/**
+ * FapsTransaction.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/CRMDOC/API+Architecture+Standards
+ */
+function _civicrm_api3_faps_transaction_Get_spec(&$spec) {
+  $spec['payment_processor_id']['api.required'] = 1;
+  $spec['transactionId']['api.required'] = 1;
+}
+
+/**
+ * FapsTransaction.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_faps_transaction_Get($params) {
+  $paymentProcessor = civicrm_api3('PaymentProcessor', 'getsingle', ['return' => ['password','user_name','signature'], 'id' => $params['payment_processor_id'], 'is_test' => 0]);
+  $credentials = array(
+    'merchantKey' => $paymentProcessor['signature'],
+    'processorId' => $paymentProcessor['user_name']
+  );
+  $service_params = array('action' => 'Query');
+  $faps = new CRM_Iats_FapsRequest($service_params);
+  $request = array(
+    'referenceNumber' => '182668',
+    // 'transactionId' => $params['transactionId'],
+  );
+  $result = $faps->request($credentials, $request);
+  return civicrm_api3_create_success($result, $params, 'FapsTransaction', 'Get');
+}
diff --git a/civicrm/ext/iatspayments/api/v3/FapsTransaction/GetJournal.php b/civicrm/ext/iatspayments/api/v3/FapsTransaction/GetJournal.php
new file mode 100644
index 0000000000000000000000000000000000000000..dad2dd3544aacff8226661313ecd0fcc1d69dd9a
--- /dev/null
+++ b/civicrm/ext/iatspayments/api/v3/FapsTransaction/GetJournal.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Action get journal.
+ *
+ * @param array $params
+ *
+ * Get entries from iATS FAPS in the faps_journal table
+ */
+function _civicrm_api3_faps_transaction_get_journal_spec(&$params) {
+  $params['transactionId'] = array(
+    'name' => 'transactionId',
+    'title' => '1stPay Transaction Id',
+    'api.required' => 0,
+  );
+  $params['isAch'] = array(
+    'name' => 'isAch',
+    'title' => 'is ACH',
+    'api.required' => 0,
+  );
+  $params['cardType'] = array(
+    'name' => 'cardType',
+    'title' => 'Card Type',
+    'api.required' => 0,
+  );
+  $params['orderId'] = array(
+    'name' => 'orderId',
+    'title' => 'Order Id',
+    'api.required' => 0,
+  );
+}
+
+/**
+ * Action FapsTransaction GetJournal
+ *
+ * @param array $params
+ *
+ * @return array
+ *   API result array.
+ *
+ * @throws CiviCRM_API3_Exception
+ */
+
+/**
+ *
+ */
+function civicrm_api3_faps_transaction_get_journal($params) {
+
+  // print_r($params); die();
+  $select = "SELECT * FROM civicrm_iats_faps_journal WHERE TRUE ";
+  $args = array();
+
+  $select_params = array(
+    'transactionId' => 'Integer',
+    'isAch' => 'Integer',
+    'CardType' => 'String',
+    'orderId' => 'String',
+  );
+  $i = 0;
+  foreach ($params as $key => $value) {
+    if (isset($select_params[$key])) {
+      $i++;
+      if (is_string($value)) {
+        $select .= " AND $key = %$i";
+        $args[$i] = array($value, $select_params[$key]);
+      }
+      elseif (is_array($value)) {
+        foreach (array_keys($value) as $sql) {
+          $select .= " AND ($key %$i)";
+          $args[$i] = array($sql, 'String');
+        }
+      }
+    }
+  }
+  if (isset($params['options']['sort'])) {
+    $sort = $params['options']['sort'];
+    $i++;
+    $select .= " ORDER BY %$i";
+    $args[$i] = array($sort, 'String');
+  }
+  else { // by default, get the "latest" entry
+    $select .= " ORDER BY id DESC";
+  }
+  $limit = 1;
+  if (isset($params['options']['limit'])) {
+    $limit = (integer) $params['options']['limit'];
+  }
+  if ($limit > 0) {
+    $i++;
+    $select .= " LIMIT %$i";
+    $args[$i] = array($limit, 'Integer');
+  }
+  $values = array();
+  try {
+    $dao = CRM_Core_DAO::executeQuery($select, $args);
+    while ($dao->fetch()) {
+      /* We index in the id */
+      $record = array();
+      foreach (get_object_vars($dao) as $key => $value) {
+        if ('N' != $key && (0 !== strpos($key, '_'))) {
+          $record[$key] = $value;
+        }
+      }
+      // also return some of this data in "normalized" field names
+      $record['transaction_id'] = $record['transactionId'];
+      $record['client_code'] = $record['cimRefNumber'];
+      $record['auth_result'] = $record['authResponse'];
+      $key = $dao->id;
+      $values[$key] = $record;
+    }
+  }
+  catch (Exception $e) {
+    CRM_Core_Error::debug_var('params', $params);
+    // throw API_Exception('iATS Payments journalling failed: '. $e->getMessage());
+  }
+  return civicrm_api3_create_success($values);
+}
diff --git a/civicrm/ext/iatspayments/api/v3/FapsTransaction/Journal.php b/civicrm/ext/iatspayments/api/v3/FapsTransaction/Journal.php
new file mode 100644
index 0000000000000000000000000000000000000000..33dd534191bc3414a3e2c5b5b6bdb73ce129f678
--- /dev/null
+++ b/civicrm/ext/iatspayments/api/v3/FapsTransaction/Journal.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ */
+
+/**
+ * Action journal.
+ *
+ * @param array $params
+ *
+ * Record an entry from FAPS into its journal table
+ */
+function _civicrm_api3_faps_transaction_journal_spec(&$params) {
+  // $params['transaction_id']['api.required'] = 1;
+}
+
+/**
+ * Action IatsPayments VerifyLog.
+ *
+ * @param array $params
+ *
+ * @return array
+ *   API result array.
+ *
+ * @throws CiviCRM_API3_Exception
+ */
+
+/**
+ *
+ */
+function civicrm_api3_faps_transaction_journal($params) {
+  // CRM_Core_Error::debug_var('params', $params);
+  // return civicrm_api3_create_success(TRUE, array('test' => TRUE));
+  try {
+    $data = $params['orderInfo'];
+    $transactionId = (int) $params['transactionId'];
+    $sql_action = 'REPLACE INTO ';
+    $cardType = isset($params['ccInfo']['cardType']) ? $params['ccInfo']['cardType'] : '';
+    $isAch = empty($params['isAch']) ? 0 : 1;
+    $status_id = 4; // default fail?
+    // calculate the status id of the payment based on the authResponse, which
+    // is different for ACH vs CC
+    if ($data['isSuccessful']) {
+      if ($isAch) {
+        switch ($data['authResponse']) {
+          case 'Settled': $status_id = 1; break;
+          case 'Pending': $status_id = 2; break;
+          default: $status_id = 4; break; // fail
+        }
+      }
+      else { // cc responses are different
+        if ("Approved ".$data['authCode'] == $data['authResponse']) {
+          $status_id = 1; 
+        }
+        else {
+          switch($data['authResponse']) {
+            case 'COMPLETED': $status_id = 1; break;
+            case 'Unknown': $status_id = 2; break;
+            default: $status_id = 4; break; // fail
+          }
+        }
+      }
+    }
+    $query_params = array(
+      2 => array($data['authCode'], 'String'),
+      3 => array($isAch, 'Integer'),
+      4 => array($cardType, 'String'),
+      5 => array($params['processorId'], 'String'),
+      6 => array($data['cimRefNumber'], 'String'),
+      7 => array($data['orderId'], 'String'),
+      8 => array($data['transDateAndTime'], 'String'),
+      9 => array($data['amount'], 'String'),
+      10 => array($data['authResponse'], 'String'),
+      11 => array($params['currency'], 'String'),
+      12 => array($status_id, 'Integer'),
+    );
+    $result = CRM_Core_DAO::executeQuery($sql_action . " civicrm_iats_faps_journal
+        (transactionId, authCode, isAch, cardType, processorId, cimRefNumber, orderId, transDateAndTime, amount, authResponse, currency, status_id) 
+        VALUES ($transactionId, %2, %3, %4, %5, %6, %7, %8, %9, %10, %11, %12)", $query_params);
+  }
+  catch (Exception $e) {
+    CRM_Core_Error::debug_var('query params', $query_params);
+    CRM_Core_Error::debug_var('params', $params);
+    CRM_Core_Error::debug_var('exceptions', $e);
+    // throw CiviCRM_API3_Exception('iATS 1stPay journalling failed: ' . $e->getMessage());
+  }
+  return civicrm_api3_create_success();
+}
diff --git a/civicrm/ext/iatspayments/api/v3/IatsPayments/GetJournal.php b/civicrm/ext/iatspayments/api/v3/IatsPayments/GetJournal.php
index e228c1f5212b02622376c8e3dc306548ac6531c9..c22737ec8a135f5c01827f1c47295056d4da37cd 100644
--- a/civicrm/ext/iatspayments/api/v3/IatsPayments/GetJournal.php
+++ b/civicrm/ext/iatspayments/api/v3/IatsPayments/GetJournal.php
@@ -76,7 +76,16 @@ function civicrm_api3_iats_payments_get_journal($params) {
       }
     }
   }
-  $limit = 25;
+  if (isset($params['options']['sort'])) {
+    $sort = $params['options']['sort'];
+    $i++;
+    $select .= " ORDER BY %$i";
+    $args[$i] = array($sort, 'String');
+  }
+  else { // by default, get the most recent entry
+    $select .= " ORDER BY id DESC";
+  }
+  $limit = 1;
   if (isset($params['options']['limit'])) {
     $limit = (integer) $params['options']['limit'];
   }
@@ -85,12 +94,6 @@ function civicrm_api3_iats_payments_get_journal($params) {
     $select .= " LIMIT %$i";
     $args[$i] = array($limit, 'Integer');
   }
-  if (isset($params['options']['sort'])) {
-    $sort = $params['options']['sort'];
-    $i++;
-    $select .= " ORDER BY %$i";
-    $args[$i] = array($sort, 'String');
-  }
 
   $values = array();
   try {
@@ -104,6 +107,10 @@ function civicrm_api3_iats_payments_get_journal($params) {
         }
       }
       $key = $dao->tnid;
+      // also return some of this data in "normalized" field names
+      $record['transaction_id'] = $record['tnid'];
+      $record['client_code'] = $record['cstc'];
+      $record['auth_result'] = $record['rst'];
       $values[$key] = $record;
     }
   }
diff --git a/civicrm/ext/iatspayments/api/v3/IatsPayments/Journal.php b/civicrm/ext/iatspayments/api/v3/IatsPayments/Journal.php
index a872753cdcd5689b007b5dcd0a569419c74dc94b..057082a1479b551f63acd3cd17a40dee4c26d526 100644
--- a/civicrm/ext/iatspayments/api/v3/IatsPayments/Journal.php
+++ b/civicrm/ext/iatspayments/api/v3/IatsPayments/Journal.php
@@ -53,6 +53,9 @@ function civicrm_api3_iats_payments_journal($params) {
       $iats_journal_id = (int) $data['Journal Id'];
       $sql_action = 'REPLACE INTO ';
     }
+    if ($data['Result'] == 'REJ:TIMEOUT' && $data['Method of Payment'] == 'ACHEFT') {
+      throw new CiviCRM_API3_Exception('iATS Payments journal ignore ACHEFT REJ:TIMEOUT');
+    } 
     $query_params = array(
       1 => array($data['Transaction ID'], 'String'),
       3 => array($dtm, 'String'),
diff --git a/civicrm/ext/iatspayments/api/v3/Job/Fapsquery.mgd.php b/civicrm/ext/iatspayments/api/v3/Job/Fapsquery.mgd.php
new file mode 100644
index 0000000000000000000000000000000000000000..6a7fe7e81d83d62b26badb1ccc390ce1b7e9b1d2
--- /dev/null
+++ b/civicrm/ext/iatspayments/api/v3/Job/Fapsquery.mgd.php
@@ -0,0 +1,23 @@
+<?php
+// This file declares a managed database record of type "Job".
+// The record will be automatically inserted, updated, or deleted from the
+// database as appropriate. For more details, see "hook_civicrm_managed" at:
+// http://wiki.civicrm.org/confluence/display/CRMDOC42/Hook+Reference
+return array(
+  0 =>
+  array(
+    'name' => 'Cron:Job.Fapsquery',
+    'entity' => 'Job',
+    'params' =>
+    array(
+      'version' => 3,
+      'name' => 'iATS Payments 1stPay Query Transactions',
+      'description' => 'Call into iATS Payments 1stPay to get transactions (for auditing and verifying).',
+      'run_frequency' => 'Hourly',
+      'api_entity' => 'Job',
+      'api_action' => 'fapsquery',
+      'parameters' => '',
+    ),
+    'update' => 'never',
+  ),
+);
diff --git a/civicrm/ext/iatspayments/api/v3/Job/Fapsquery.php b/civicrm/ext/iatspayments/api/v3/Job/Fapsquery.php
new file mode 100644
index 0000000000000000000000000000000000000000..a583ae22c3a839f9ca61e05d061a7a3cca50186a
--- /dev/null
+++ b/civicrm/ext/iatspayments/api/v3/Job/Fapsquery.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * Job.FapsQuery API specification (optional)
+ *
+ * Pull in the iATS/FAPS transaction journal and save it in the corresponding table
+ * for local access for easier verification, auditing and reporting.
+ *
+ * @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_job_fapsquery_spec(&$spec) {
+  // no arguments
+  // TODO: configure for a date range, report, etc.
+}
+
+/**
+ * Job.FapsQuery API
+ *
+ * Fetch all recent transactions from iATS/FAPS for the purposes of auditing (in separate jobs).
+ * This addresses multiple needs:
+ * 1. Verify incomplete ACH/EFT contributions.
+ * 2. Verify recent contributions that went through but weren't reported to CiviCRM due to unexpected connection/code breakage.
+ * 3. Input recurring contributions managed by iATS/FAPS
+ * 4. Input one-time contributions that did not go through CiviCRM
+ * 5. Audit for remote changes in iATS/FAPS.
+ *
+ * @param array $params
+ * @return array API result descriptor
+ * @see civicrm_api3_create_success
+ * @see civicrm_api3_create_error
+ * @throws API_Exception
+ */
+function civicrm_api3_job_fapsquery($params) {
+
+  /* get a list of all active/non-test iATS/FAPS payment processors of any type, quit if there are none */
+  /* We'll make sure they are unique from iATS/FAPS point of view (i.e. distinct processorId = username) */
+  try {
+    $result = civicrm_api3('PaymentProcessor', 'get', array(
+      'sequential' => 1,
+      'class_name' => array('LIKE' => 'Payment_FAPS%'),
+      'is_active' => 1,
+      'is_test' => 0,
+    ));
+  }
+  catch (CiviCRM_API3_Exception $e) {
+    throw new API_Exception('Unexpected error getting payment processors: ' . $e->getMessage()); //  . "\n" . $e->getTraceAsString());
+  }
+  if (empty($result['values'])) {
+    return;
+  }
+  $payment_processors = array();
+  foreach ($result['values'] as $payment_processor) {
+    $user_name = $payment_processor['user_name'];
+    $type = $payment_processor['payment_type']; // 1 for cc, 2 for ach/eft
+    $id = $payment_processor['id'];
+    if (empty($payment_processors[$user_name])) {
+      $payment_processors[$user_name] = array();
+    }
+    if (empty($payment_processors[$user_name][$type])) {
+      $payment_processors[$user_name][$type] = array();
+    }
+    $payment_processors[$user_name][$type][$id] = $payment_processor;
+  }
+  // CRM_Core_Error::debug_var('Payment Processors', $payment_processors);
+  // get the settings: TODO allow more detailed configuration of which transactions to import?
+  $iats_settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+  // I also use the settings to keep track of the last time I imported journal data from iATS/FAPS.
+  $iats_faps_journal = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_faps_journal');
+  /* initialize some values so I can report at the end */
+  // count the number of records from each iats account analysed, and the number of each kind found ('action')
+  $processed = array();
+  // save all my api result error messages as well
+  $error_log = array();
+  foreach ($payment_processors as $user_name => $payment_processors_per_user) {
+    $processed[$user_name] = array();
+    foreach ($payment_processors_per_user as $type => $payment_processors_per_user_type) {
+      // we might have multiple civi payment processors by type e.g. separate codes for
+      // one-time and recurring contributions, I only want to process once per user_name + type
+      $payment_processor = reset($payment_processors_per_user_type);
+      $options = array(
+       'action' => 'Query'
+      );
+      $credentials = array(
+        'merchantKey' => $payment_processor['signature'],
+        'processorId' => $payment_processor['user_name']
+      );
+      // unlike iATS legacy, we only have one method and set each contribution's status based on result instead.
+      // initialize my counts
+      $processed[$user_name][$type] = 0;
+      // watchdog('civicrm_iatspayments_com', 'pp: <pre>!pp</pre>', array('!pp' => print_r($payment_processor,TRUE)), WATCHDOG_NOTICE);
+      $query_request = new CRM_Iats_FapsRequest($options);
+      $request = array(
+        // 'toDate' => date('Y-m-d', strtotime('-1 day')) . 'T23:59:59+00:00',
+        // 'customerIPAddress' => (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']),
+      );
+      // Calculate how far back I want to go, default 2 days ago.
+      $fromDate = strtotime('-2 days');
+      // Check when I last downloaded this box journal
+      if (!empty($iats_faps_journal)) {
+        // Make sure I fill in any gaps if this cron hasn't run for a while, but no more than a month
+        $fromDate = min(strtotime($iats_faps_journal), strtotime('-2 days'));
+        $fromDate = max($fromDate, strtotime('-30 days'));
+      }
+      else {
+        // If I've cleared the settings, then go back a month of data.
+        $fromDate = strtotime('-30 days');
+      }
+      // reset the request fromDate, from the beginning of fromDate's day.
+      $request['queryStartDay'] = date('d', $fromDate);
+      $request['queryStartMonth'] = date('m', $fromDate);
+      $request['queryStartYear'] = date('Y', $fromDate);
+      // TODO: should I set the timezone?
+      $result = $query_request->request($credentials, $request);
+      // CRM_Core_Error::debug_var('result', $result);
+      // convert the result into transactions and then write to the journal table
+      // via the api
+      $transactions = $result['isSuccess'] ? $result['data']['orders'] : array();
+      foreach ($transactions as $transaction) {
+        try {
+          $transaction['currency'] = ''; // unknown, but should be retrievable from processor information
+          $transaction['processorId'] = $user_name;
+          civicrm_api3('FapsTransaction', 'journal', $transaction);
+          $processed[$user_name][$type]++;
+        }
+        catch (CiviCRM_API3_Exception $e) {
+          $error_log[] = $e->getMessage();
+        }
+      }
+    }
+  }
+  // record the current date into the settings for next time.
+  $iats_faps_journal = date('c'); // ISO 8601
+  CRM_Core_BAO_Setting::setItem($iats_faps_journal, 'iATS Payments Extension', 'iats_faps_journal');
+  $message = '';
+  foreach ($processed as $user_name => $p) {
+    foreach ($p as $type_id => $count) {
+      $type = ($type_id == 1) ? 'cc' : 'acheft';
+      $results = array(
+        1 => $user_name,
+        2 => $type,
+        3 => $count
+      );
+      $message .= '<br />' . ts('For account %1, type %2, retreived %3 transactions.', $results);
+    }
+  }
+  if (count($error_log) > 0) {
+    return civicrm_api3_create_error($message . '</br />' . implode('<br />', $error_log));
+  }
+  return civicrm_api3_create_success($message);
+}
diff --git a/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php b/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php
index 21510bdb67123599d2a56cf512f967819a494e65..72b8948821e5e0ee4c7c1a5b6a1f13f9da5cdec0 100644
--- a/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php
+++ b/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php
@@ -1,23 +1,15 @@
 <?php
+use CRM_Iats_ExtensionUtil as E;
 
 /**
- * @file
- */
-
-/**
- * Job.iATSRecurringContributions API specification.
+ * Job.Iatsrecurringcontributions 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
- */
-
-/**
- *
+ * @see http://wiki.civicrm.org/confluence/display/CRMDOC/API+Architecture+Standards
  */
-function _civicrm_api3_job_iatsrecurringcontributions_spec(&$spec) {
+function _civicrm_api3_job_Iatsrecurringcontributions_spec(&$spec) {
   $spec['recur_id'] = array(
     'name' => 'recur_id',
     'title' => 'Recurring payment id',
@@ -47,32 +39,35 @@ function _civicrm_api3_job_iatsrecurringcontributions_spec(&$spec) {
 }
 
 /**
- * Job.iATSRecurringContributions API.
+ * Job.Iatsrecurringcontributions 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_job_iatsrecurringcontributions($params) {
+function civicrm_api3_job_Iatsrecurringcontributions($params) {
   // Running this job in parallell could generate bad duplicate contributions.
-  $lock = new CRM_Core_Lock('civicrm.job.IatsRecurringContributions');
+  $lock = new CRM_Core_Lock('civicrm.job.Iatsrecurringcontributions');
 
   if (!$lock->acquire()) {
     return civicrm_api3_create_success(ts('Failed to acquire lock. No contribution records were processed.'));
   }
+  // Restrict this method of recurring contribution processing to only iATS (Faps + Legacy) active payment processors.
+  // TODO: exclude test processors?
+  $fapsProcessors = _iats_filter_payment_processors('Faps%', array(), array('active' => 1));
+  $iatsProcessors = _iats_filter_payment_processors('iATS%', array(), array('active' => 1));
+  $paymentProcessors = $fapsProcessors + $iatsProcessors;
+  if (empty($paymentProcessors)) {
+    return;
+  }
+  // use catchup mode to calculate next scheduled contribution based on current value rather than current date
   $catchup = !empty($params['catchup']);
   unset($params['catchup']);
+  // do memberships by default, i.e. copy any membership information/relationship from contribution template
   $domemberships = empty($params['ignoremembership']);
   unset($params['ignoremembership']);
-
-  // TODO: what kind of extra security do we want or need here to prevent it from being triggered inappropriately? Or does it matter?
-  // The next scheduled contribution date field name is civicrm version dependent.
-  define('IATS_CIVICRM_NSCD_FID', _iats_civicrm_nscd_fid());
   // $config = &CRM_Core_Config::singleton();
   // $debug  = false;
   // do my calculations based on yyyymmddhhmmss representation of the time
@@ -81,168 +76,81 @@ function civicrm_api3_job_iatsrecurringcontributions($params) {
   $dtCurrentDayStart = $dtCurrentDay . "000000";
   $dtCurrentDayEnd   = $dtCurrentDay . "235959";
   $expiry_limit = date('ym');
-  // Restrict this method of recurring contribution processing to only these three payment processors.
-  $args = array(
-    1 => array('Payment_iATSService', 'String'),
-    2 => array('Payment_iATSServiceACHEFT', 'String'),
-    3 => array('Payment_iATSServiceSWIPE', 'String'),
-  );
-  // Before triggering payments, we need to do some housekeeping of the civicrm_contribution_recur records.
-  // First update the end_date and then the complete/in-progress values.
-  // We do this both to fix any failed settings previously, and also
-  // to deal with the possibility that the settings for the number of payments (installments) for an existing record has changed.
-  // First check for recur end date values on non-open-ended recurring contribution records that are either complete or in-progress.
-  $select = 'SELECT cr.id, count(c.id) AS installments_done, cr.installments, cr.end_date, NOW() as test_now
-      FROM civicrm_contribution_recur cr
-      INNER JOIN civicrm_contribution c ON cr.id = c.contribution_recur_id
-      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
-      WHERE
-        (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
-        AND (cr.installments > 0)
-        AND (cr.contribution_status_id IN (1,5))
-        AND (c.contribution_status_id IN (1,2))
-      GROUP BY c.contribution_recur_id';
-  $dao = CRM_Core_DAO::executeQuery($select, $args);
-  while ($dao->fetch()) {
-    // Check for end dates that should be unset because I haven't finished
-    // at least one more installment todo.
-    if ($dao->installments_done < $dao->installments) {
-      // Unset the end_date.
-      if (($dao->end_date > 0) && ($dao->end_date <= $dao->test_now)) {
-        $update = 'UPDATE civicrm_contribution_recur SET end_date = NULL, contribution_status_id = 5 WHERE id = %1';
-        CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
-      }
-    }
-    // otherwise, check if my end date should be set to the past because I have finished
-    // I'm done with installments.
-    elseif ($dao->installments_done >= $dao->installments) {
-      if (empty($dao->end_date) || ($dao->end_date >= $dao->test_now)) {
-        // This interval complete, set the end_date to an hour ago.
-        $update = 'UPDATE civicrm_contribution_recur SET end_date = DATE_SUB(NOW(),INTERVAL 1 HOUR) WHERE id = %1';
-        CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
-      }
-    }
-  }
-  // Second, make sure any open-ended recurring contributions have no end date set.
-  $update = 'UPDATE civicrm_contribution_recur cr
-      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
-      SET
-        cr.end_date = NULL
-      WHERE
-        cr.contribution_status_id IN (1,5)
-        AND NOT(cr.installments > 0)
-        AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
-        AND NOT(ISNULL(cr.end_date))';
-  $dao = CRM_Core_DAO::executeQuery($update, $args);
-
-  // Third, we update the status_id of the all in-progress or completed recurring contribution records
-  // Unexpire uncompleted cycles.
-  $update = 'UPDATE civicrm_contribution_recur cr
-      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
-      SET
-        cr.contribution_status_id = 5
-      WHERE
-        cr.contribution_status_id = 1
-        AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
-        AND (cr.end_date IS NULL OR cr.end_date > NOW())';
-  $dao = CRM_Core_DAO::executeQuery($update, $args);
-  // Expire or badly-defined completed cycles.
-  $update = 'UPDATE civicrm_contribution_recur cr
-      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
-      SET
-        cr.contribution_status_id = 1
-      WHERE
-        cr.contribution_status_id = 5
-        AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)
-        AND (
-          (NOT(cr.end_date IS NULL) AND cr.end_date <= NOW())
-          OR
-          ISNULL(cr.frequency_unit)
-          OR
-          (frequency_interval = 0)
-        )';
-  $dao = CRM_Core_DAO::executeQuery($update, $args);
-
+  // TODO: before triggering payments, do some housekeeping of the civicrm_contribution_recur records?
   // Now we're ready to trigger payments
-  // Select the ongoing recurring payments for iATSServices where the next scheduled contribution date (NSCD) is before the end of of the current day.
-  $select = 'SELECT cr.*, icc.customer_code, icc.expiry as icc_expiry, icc.cid as icc_contact_id, pp.class_name as pp_class_name, pp.url_site as url_site, pp.is_test
-      FROM civicrm_contribution_recur cr
-      INNER JOIN civicrm_payment_processor pp ON cr.payment_processor_id = pp.id
-      INNER JOIN civicrm_iats_customer_codes icc ON cr.id = icc.recur_id
-      WHERE
-        cr.contribution_status_id = 5
-        AND (pp.class_name = %1 OR pp.class_name = %2 OR pp.class_name = %3)';
-  // AND pp.is_test = 0
-  // in case the job was called to execute a specific recurring contribution id -- not yet implemented!
+  // Select the ongoing recurring payments for FAPS where the next scheduled contribution date is before the end of of the current day.
+  $get = array(
+      'next_sched_contribution_date' => ['<=' => $dtCurrentDayEnd],
+      'payment_processor_id' => ['IN' => array_keys($paymentProcessors)],
+      'contribution_status_id' => ['IN' => ['In Progress']],
+      'payment_token_id' => ['>' => 0],
+      'options' => ['limit' => 0],
+      'return' => ['id', 'contact_id', 'amount', 'failure_count', 'payment_processor_id', 'next_sched_contribution_date',
+        'payment_instrument_id', 'is_test', 'currency', 'financial_type_id','is_email_receipt',
+        'frequency_interval', 'frequency_unit', 'payment_token_id'],
+  );
+  // additional filters that may be passed in as params
   if (!empty($params['recur_id'])) {
-    $select .= ' AND icc.recur_id = %4';
-    $args[4] = array($params['recur_id'], 'Int');
+    $get['id'] = $params['recur_id'];
   }
-  // If (!empty($params['scheduled'])) {.
-  else {
-    // normally, process all recurring contributions due today or earlier.
-    $select .= ' AND cr.' . IATS_CIVICRM_NSCD_FID . ' <= %4';
-    $args[4] = array($dtCurrentDayEnd, 'String');
-    // ' AND cr.next_sched_contribution >= %2
-    // $args[2] = array($dtCurrentDayStart, 'String');
-    // also filter by cycle day.
-    if (!empty($params['cycle_day'])) {
-      $select .= ' AND cr.cycle_day = %5';
-      $args[5] = array($params['cycle_day'], 'Int');
-    }
-    // Also filter by cycle day.
-    if (isset($params['failure_count'])) {
-      $select .= ' AND cr.failure_count = %6';
-      $args[6] = array($params['failure_count'], 'Int');
-    }
+  if (!empty($params['cycle_day'])) {
+    $get['cycle_day'] = $params['cycle_day'];
   }
-  $dao = CRM_Core_DAO::executeQuery($select, $args);
+  if (isset($params['failure_count'])) {
+    $get['failure_count'] = $params['failure_count'];
+  }
+  $recurringContributions = civicrm_api3('ContributionRecur', 'get',  $get);
+  //CRM_Core_Error::debug_var('Recurring contributions get params', $get);
+  //CRM_Core_Error::debug_var('Recurring contributions to be generated for', $recurringContributions['values']);
   $counter = 0;
   $error_count  = 0;
-  $output  = array();
+  $output  = [];
   $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
   $receipt_recurring = $settings['receipt_recurring'];
   $email_failure_report = empty($settings['email_recurring_failure_report']) ? '' : $settings['email_recurring_failure_report'];
   // By default, after 3 failures move the next scheduled contribution date forward.
   $failure_threshhold = empty($settings['recurring_failure_threshhold']) ? 3 : (int) $settings['recurring_failure_threshhold'];
-
-  /* while ($dao->fetch()) {
-  foreach($dao as $key => $value) {
-  echo "$value,";
-  }
-  echo "\n";
-  }
-  die();  */
   $failure_report_text = '';
-  while ($dao->fetch()) {
-
+  foreach($recurringContributions['values'] as $recurringContribution) {
     // Strategy: create the contribution record with status = 2 (= pending), try the payment, and update the status to 1 if successful
+    //           also, advance the next scheduled payment before the payment attempt and pull it back if we know it fails.
+    $contribution_recur_id    = $recurringContribution['id'];
+    $contact_id = $recurringContribution['contact_id'];
+    $total_amount = $recurringContribution['amount'];
+    $payment_processor_id = $recurringContribution['payment_processor_id'];
     // Try to get a contribution template for this contribution series - if none matches (e.g. if a donation amount has been changed), we'll just be naive about it.
-    $contribution_template = _iats_civicrm_getContributionTemplate(array('contribution_recur_id' => $dao->id, 'total_amount' => $dao->amount));
-    $contact_id = $dao->contact_id;
-    $total_amount = $dao->amount;
+    $contribution_template = CRM_Iats_Transaction::getContributionTemplate(['contribution_recur_id' => $contribution_recur_id, 'total_amount' => $total_amount]);
+    // CRM_Core_Error::debug_var('Contribution Template', $contribution_template);
+    // generate my invoice id like CiviCRM does
     $hash = md5(uniqid(rand(), TRUE));
-    $contribution_recur_id    = $dao->id;
-    $original_contribution_id = $contribution_template['original_contribution_id'];
-    $failure_count    = $dao->failure_count;
-    $subtype = substr($dao->pp_class_name, 19);
-    $source = "iATS Payments $subtype Recurring Contribution (id=$contribution_recur_id)";
-    $receive_ts = $catchup ? strtotime($dao->next_sched_contribution_date) : time();
+    $failure_count    = $recurringContribution['failure_count'];
+    $paymentProcessor = $paymentProcessors[$payment_processor_id];
+    $paymentClass = substr($paymentProcessor['class_name'],8);
+    $source = E::ts('iATS Payments (%1) Recurring Contribution ( id = %2 )', [
+      1 => $paymentClass,
+      2 => $contribution_recur_id,
+    ]);
+    $receive_ts = $catchup ? strtotime($recurringContribution['next_sched_contribution_date']) : time();
     // i.e. now or whenever it was supposed to run if in catchup mode.
     $receive_date = date("YmdHis", $receive_ts);
     // Check if we already have an error.
     $errors = array();
-    if (empty($dao->customer_code)) {
-      $errors[] = ts('Recur id %1 is missing a customer code.', array(1 => $contribution_recur_id));
-    }
-    else {
-      if ($dao->contact_id != $dao->icc_contact_id) {
-        $errors[] = ts('Recur id %1 is has a mismatched contact id for the customer code.', array(1 => $contribution_recur_id));
+    if (!empty($recurringContribution['payment_token_id'])) {
+      try {
+        $payment_token = civicrm_api3('PaymentToken', 'getsingle', array('id' => $recurringContribution['payment_token_id']));
+        if (empty($payment_token['token'])) {
+          $errors[] = E::ts('Recur id %1 is missing a payment token.', array(1 => $contribution_recur_id));
+        }
       }
-      if (($dao->icc_expiry != '0000') && ($dao->icc_expiry < $expiry_limit)) {
-        // $errors[] = ts('Recur id %1 is has an expired cc for the customer code.', array(1 => $contribution_recur_id));.
+      catch (Exception $e) {
+        $errors[] = E::ts('Unexpected error getting a payment token for recurring schedule id %1', array(1 => $contribution_recur_id));
+        CRM_Core_Error::debug_var('Unexpected error getting payment token', $e);
+        $payment_token = array();
       }
     }
+    else {
+      $errors[] = E::ts('Unexpected error, no payment token for recurring schedule id %1', array(1 => $contribution_recur_id));
+    }
     if (count($errors)) {
       $source .= ' Errors: ' . implode(' ', $errors);
     }
@@ -251,35 +159,29 @@ function civicrm_api3_job_iatsrecurringcontributions($params) {
       'contact_id'       => $contact_id,
       'receive_date'       => $receive_date,
       'total_amount'       => $total_amount,
-      'payment_instrument_id'  => $dao->payment_instrument_id,
+      'payment_instrument_id'  => $recurringContribution['payment_instrument_id'],
       'contribution_recur_id'  => $contribution_recur_id,
       'invoice_id'       => $hash,
       'source'         => $source,
-      'contribution_status_id' => 2, /* initialize as pending, so we can run completetransaction after taking the money */
-      'currency'  => $dao->currency,
-      'payment_processor'   => $dao->payment_processor_id,
-      'is_test'        => $dao->is_test, /* propagate the is_test value from the parent contribution */
+      'contribution_status_id' => 'Pending', /* initialize as pending, so we can run completetransaction after taking the money */
+      'currency'  => $recurringContribution['currency'],
+      'payment_processor'   => $payment_processor_id,
+      'is_test'        => $recurringContribution['is_test'], /* propagate the is_test value from the recurring record */
+      'financial_type_id' => $recurringContribution['financial_type_id'],
+      'is_email_receipt' => (($receipt_recurring < 2) ? $receipt_recurring : $recurringContribution['is_email_receipt']),
     );
-    $get_from_template = array('contribution_campaign_id', 'amount_level');
+    $get_from_template = ['contribution_campaign_id', 'amount_level', 'original_contribution_id'];
     foreach ($get_from_template as $field) {
       if (isset($contribution_template[$field])) {
         $contribution[$field] = is_array($contribution_template[$field]) ? implode(', ', $contribution_template[$field]) : $contribution_template[$field];
       }
     }
-    // 4.2.
-    if (isset($dao->contribution_type_id)) {
-      $contribution['contribution_type_id'] = $dao->contribution_type_id;
-    }
-    // 4.3+.
-    else {
-      $contribution['financial_type_id'] = $dao->financial_type_id;
-    }
     // if we have a created a pending contribution record due to a future start time, then recycle that CiviCRM contribution record now.
     // Note that the date and amount both could have changed.
     // The key is to only match if we find a single pending contribution, with a NULL transaction id, for this recurring schedule.
     // We'll need to pay attention later that we may or may not already have a contribution id.
     try {
-      $pending_contribution = civicrm_api3('Contribution', 'get', array(
+      $pending_contribution = civicrm_api3('Contribution', 'getsingle', array(
         'return' => array('id'),
         'trxn_id' => array('IS NULL' => 1),
         'contribution_recur_id' => $contribution_recur_id,
@@ -293,7 +195,7 @@ function civicrm_api3_job_iatsrecurringcontributions($params) {
       // ignore, we'll proceed normally without a contribution id
     }
     // If I'm not recycling a contribution record and my original has line_items, then I'll add them to the contribution creation array.
-    // TODO: if the amount of a matched pending contribution has changed, then we should be removing line items from the contribution and replacing them.
+    // Note: if the amount of a matched pending contribution has changed, then we need to remove the line items from the contribution.
     if (empty($contribution['id']) && !empty($contribution_template['line_items'])) {
       $contribution['skipLineItem'] = 1;
       $contribution['api.line_item.create'] = $contribution_template['line_items'];
@@ -312,66 +214,47 @@ function civicrm_api3_job_iatsrecurringcontributions($params) {
       }
       continue;
     }
-    else {
-      // Assign basic options.
-      $options = array(
-        'is_email_receipt' => (($receipt_recurring < 2) ? $receipt_recurring : $dao->is_email_receipt),
-        'customer_code' => $dao->customer_code,
-        'subtype' => $subtype,
-      );
-      // If our template contribution is a membership payment, make this one also.
-      if ($domemberships && !empty($contribution_template['contribution_id'])) {
-        try {
-          $membership_payment = civicrm_api('MembershipPayment', 'getsingle', array('version' => 3, 'contribution_id' => $contribution_template['contribution_id']));
-          if (!empty($membership_payment['membership_id'])) {
-            $options['membership_id'] = $membership_payment['membership_id'];
-          }
-        }
-        catch (Exception $e) {
-          // ignore, if will fail correctly if there is no membership payment.
+    // Else: no errors in the setup, continue.
+    // If our template contribution is a membership payment, make this one also.
+    if ($domemberships && !empty($contribution_template['contribution_id'])) {
+      try {
+        $membership_payment = civicrm_api('MembershipPayment', 'getsingle', array('version' => 3, 'contribution_id' => $contribution_template['contribution_id']));
+        if (!empty($membership_payment['membership_id'])) {
+          // a slightly hacky was of passing this information in, membership_id
+          // isn't normally a property of a contribution.
+          $contribution['membership_id'] = $membership_payment['membership_id'];
         }
       }
-      // So far so, good ... now create the pending contribution, and save its id
-      // and then try to get the money, and do one of:
-      // update the contribution to failed, leave as pending for server failure, complete the transaction,
-      // or update a pending ach/eft with it's transaction id.
-      $result = _iats_process_contribution_payment($contribution, $options, $original_contribution_id);
-      if ($email_failure_report && !empty($contribution['iats_reject_code'])) {
-        $failure_report_text .= "\n $result ";
-      }
-      $output[] = $result;
-    }
-
-    /* in case of critical failure set the series to pending */
-    if (!empty($contribution['iats_reject_code'])) {
-      switch ($contribution['iats_reject_code']) {
-        // Reported lost or stolen.
-        case 'REJECT: 25':
-          // Do not reprocess!
-        case 'REJECT: 100':
-          /* convert the contribution series to pending to avoid reprocessing until dealt with */
-          civicrm_api('ContributionRecur', 'create',
-            array(
-              'version' => 3,
-              'id'      => $contribution['contribution_recur_id'],
-              'contribution_status_id'   => 2,
-            )
-          );
-          break;
+      catch (Exception $e) {
+        // ignore, if will fail correctly if there is no membership payment.
       }
     }
-
+    // So far so, good ... now use my utility function process_contribution_payment to
+    // create the pending contribution and try to get the money, and then do one of:
+    // update the contribution to failed, leave as pending for server failure, complete the transaction,
+    // or update a pending ach/eft with it's transaction id.
+    // But first: advance the next collection date now so that in case of server failure on return from a payment request I don't try to take money again.
+    // Save the current value to restore in case of payment failure (perhaps ...).
+    $saved_next_sched_contribution_date = $recurringContribution['next_sched_contribution_date'];
     /* calculate the next collection date, based on the recieve date (note effect of catchup mode, above)  */
-    $next_collection_date = date('Y-m-d H:i:s', strtotime("+$dao->frequency_interval $dao->frequency_unit", $receive_ts));
-    /* by default, advance to the next schduled date and set the failure count back to 0 */
+    $next_collection_date = date('Y-m-d H:i:s', strtotime('+'.$recurringContribution['frequency_interval'].' '.$recurringContribution['frequency_unit'], $receive_ts));
+    $contribution_recur_set = array('version' => 3, 'id' => $contribution['contribution_recur_id'], 'next_sched_contribution_date' => $next_collection_date);
+    $result = CRM_Iats_Transaction::process_contribution_payment($contribution, $paymentProcessor, $payment_token);
+    // append result message to report if I'm going to mail out a failures
+    // report
+    if ($email_failure_report && !$result['result']['success']) {
+      $failure_report_text .= "\n".$result['message'];
+    }
+    $output[] = $result['message'];
+    /* by default, just set the failure count back to 0 */
     $contribution_recur_set = array('version' => 3, 'id' => $contribution['contribution_recur_id'], 'failure_count' => '0', 'next_sched_contribution_date' => $next_collection_date);
-    /* special handling for failures */
+    /* special handling for failures: try again at next opportunity if we haven't failed too often */
     if (4 == $contribution['contribution_status_id']) {
       $contribution_recur_set['failure_count'] = $failure_count + 1;
-      /* if it has failed but the failure threshold will not be reached with this failure, leave the next sched contribution date as it was */
+      /* if it has failed and the failure threshold will not be reached with this failure, set the next sched contribution date to what it was */
       if ($contribution_recur_set['failure_count'] < $failure_threshhold) {
         // Should the failure count be reset otherwise? It is not.
-        unset($contribution_recur_set['next_sched_contribution_date']);
+        $contribution_recur_set['next_sched_contribution_date'] = $saved_next_sched_contribution_date;
       }
     }
     civicrm_api('ContributionRecur', 'create', $contribution_recur_set);
@@ -382,7 +265,7 @@ function civicrm_api3_job_iatsrecurringcontributions($params) {
         'source_contact_id'   => $contact_id,
         'source_record_id' => $contribution['id'],
         'assignee_contact_id' => $contact_id,
-        'subject'       => "Attempted iATS Payments $subtype Recurring Contribution for " . $total_amount,
+        'subject'       => ts('Attempted iATS Payments (%1) Recurring Contribution for %2', array(1 => $paymentClass, 2 => $total_amount)),
         'status_id'       => 2,
         'activity_date_time'  => date("YmdHis"),
       )
@@ -405,6 +288,7 @@ function civicrm_api3_job_iatsrecurringcontributions($params) {
 
   // Now update the end_dates and status for non-open-ended contribution series if they are complete (so that the recurring contribution status will show correctly)
   // This is a simplified version of what we did before the processing.
+  /*
   $select = 'SELECT cr.id, count(c.id) AS installments_done, cr.installments
       FROM civicrm_contribution_recur cr
       INNER JOIN civicrm_contribution c ON cr.id = c.contribution_recur_id
@@ -425,7 +309,7 @@ function civicrm_api3_job_iatsrecurringcontributions($params) {
       CRM_Core_DAO::executeQuery($update, array(1 => array($dao->id, 'Int')));
     }
   }
-
+  */
   $lock->release();
   // If errors ..
   if ((strlen($failure_report_text) > 0) && $email_failure_report) {
diff --git a/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.mgd.php b/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.mgd.php
index 21df020af58ad2b70dc40c2c53a3709471b95218..7d7b45509d61cae6867de5c558521098d7f1c571 100644
--- a/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.mgd.php
+++ b/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.mgd.php
@@ -11,8 +11,8 @@ return array(
     'params' =>
     array(
       'version' => 3,
-      'name' => 'iATS Payments Get Transaction Journal',
-      'description' => 'Call into iATS to get transaction journals (for auditing and verifying).',
+      'name' => 'iATS Payments Get Legacy Transaction Journal',
+      'description' => 'Call into iATS to get transaction journals, legacy processor (for auditing and verifying).',
       'run_frequency' => 'Hourly',
       'api_entity' => 'Job',
       'api_action' => 'iatsreport',
diff --git a/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php b/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php
index 90bc3c22ff7b1508cfe9670e99e9b9ac4424d4f8..00be93662e04abbe686f40a6c424376c6c83ecf2 100644
--- a/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php
+++ b/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php
@@ -71,7 +71,6 @@ function civicrm_api3_job_iatsreport($params) {
   foreach (array('quick', 'recur', 'series') as $setting) {
     $import[$setting] = empty($iats_settings['import_' . $setting]) ? 0 : 1;
   }
-  require_once "CRM/iATS/iATSService.php";
   // an array of types => methods => payment status of the records retrieved
   $process_methods = array(
     1 => array('cc_journal_csv' => 1, 'cc_payment_box_journal_csv' => 1, 'cc_payment_box_reject_csv' => 4),
@@ -92,7 +91,7 @@ function civicrm_api3_job_iatsreport($params) {
       $process_methods_per_type = $process_methods[$type];
       $iats_service_params = array('type' => 'report', 'iats_domain' => parse_url($payment_processor['url_site'], PHP_URL_HOST)); // + $iats_service_params;
       /* the is_test below should always be 0, but I'm leaving it in, in case eventually we want to be verifying tests */
-      $credentials = iATS_Service_Request::credentials($payment_processor['id'], $payment_processor['is_test']);
+      $credentials = CRM_Iats_iATSServiceRequest::credentials($payment_processor['id'], $payment_processor['is_test']);
 
       foreach ($process_methods_per_type as $method => $payment_status_id) {
         // initialize my counts
@@ -101,7 +100,7 @@ function civicrm_api3_job_iatsreport($params) {
         /* get approvals from yesterday, approvals from previous days, and then rejections for this payment processor */
         /* we're going to assume that all the payment_processors_per_type are using the same server */
         $iats_service_params['method'] = $method;
-        $iats = new iATS_Service_Request($iats_service_params);
+        $iats = new CRM_Iats_iATSServiceRequest($iats_service_params);
         // For some methods, I only want to check once per day.
         $skip_method = FALSE;
         $journal_setting_key = 'last_update_' . $method;
diff --git a/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php b/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php
index 2145781d63ef2e1d9a16f85e372b28821adba3cc..c986a7c1a095e00065681b1ccd936a656f65e728 100644
--- a/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php
+++ b/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php
@@ -77,7 +77,7 @@ function civicrm_api3_job_iatsverify($params) {
   // And see if they are approved in my iATS Journal.
   // This could include ACH/EFT approvals, as well as CC contributions that were completed but didn't get back from iATS.
   // Count the number of each kind found.
-  $processed = array(1 => 0, 4 => 0);
+  $processed = array(1 => 0, 2 => 0, 4 => 0);
   // Save all my api error result messages.
   $error_log = array();
   $select_params = array(
@@ -105,24 +105,46 @@ function civicrm_api3_job_iatsverify($params) {
   if ($invoice_id) {
     $select_params['invoice_id'] = $invoice_id;
   }
-
+  // use these two settings to see if it's worth checking their respective
+  // journal tables.
+  $iats_journal_date = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_journal');
+  $iats_faps_journal_date = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_faps_journal');
   $message = '';
   try {
-    $contributions_verify = civicrm_api3('Contribution', 'get', $select_params);
-    $message .= '<br />' . ts('Found %1 contributions to verify.', array(1 => count($contributions_verify['values'])));
+    $contributions = civicrm_api3('Contribution', 'get', $select_params);
+    $message .= '<br />' . ts('Found %1 contributions to verify.', array(1 => count($contributions['values'])));
+    $contributions_verify = $contributions['values'];
     // CRM_Core_Error::debug_var('Verifying contributions', $contributions_verify);
-    foreach ($contributions_verify['values'] as $contribution) {
-      $journal_matches = civicrm_api3('IatsPayments', 'get_journal', array(
-        'sequential' => 1,
-        'inv' => $contribution['invoice_id'],
-      ));
-      if ($journal_matches['count'] > 0) {
-        /* found a matching journal entry, we can approve or fail it */
-        $is_recur = empty($pending_contribution['contribution_recur_id']) ? FALSE : TRUE;
+    foreach ($contributions_verify as $contribution) {
+      unset($journal_entry);
+      // first check the legacy journal if I've used it recently
+      if (!empty($iats_journal_date)) {
+        $journal_matches = civicrm_api3('IatsPayments', 'get_journal', array(
+          'sequential' => 1,
+          'inv' => $contribution['invoice_id'],
+        ));
+        if ($journal_matches['count'] > 0) {
+          // CRM_Core_Error::debug_var('Found legacy match(es)', $journal_matches['values']);
+          $journal_entry = reset($journal_matches['values']);
+        }
+      }
+      if (empty($journal_entry) && !empty($iats_faps_journal_date)) {
+        // try the FAPS journal
+        $journal_matches = civicrm_api3('FapsTransaction', 'get_journal', array(
+          'sequential' => 1,
+          'orderId' => $contribution['invoice_id'],
+        ));
+        if ($journal_matches['count'] > 0) {
+          // CRM_Core_Error::debug_var('Found faps match(es)', $journal_matches['values']);
+          $journal_entry = reset($journal_matches['values']);
+        }
+      }
+      if (!empty($journal_entry)) {
+        // CRM_Core_Error::debug_var('Matching journal entry', $journal_entry);
+        /* found a matching journal entry with a transaction id, we can approve or fail it */
+        $is_recur = empty($contribution['contribution_recur_id']) ? FALSE : TRUE;
         // I only use the first one to determine the new status of the contribution.
         // TODO, deal with multiple partial payments
-        $journal_entry = reset($journal_matches['values']);
-        $transaction_id = $journal_entry['tnid'];
         $contribution_status_id = (int) $journal_entry['status_id'];
         // Keep track of how many of each time I've processed.
         $processed[$contribution_status_id]++;
@@ -131,7 +153,7 @@ function civicrm_api3_job_iatsverify($params) {
             // Updating a contribution status to complete needs some extra bookkeeping.
             // Note that I'm updating the timestamp portion of the transaction id here, since this might be useful at some point
             // Should I update the receive date to when it was actually received? Would that confuse membership dates?
-            $trxn_id = $transaction_id . ':' . time();
+            $trxn_id = $journal_entry['transaction_id'] . ':' . time();
             $complete = array('version' => 3, 'id' => $contribution['id'], 'trxn_id' => $trxn_id, 'receive_date' => $contribution['receive_date']);
             if ($is_recur) {
               // For email receipting, use either my iats extension global, or the specific setting for this schedule.
@@ -154,6 +176,7 @@ function civicrm_api3_job_iatsverify($params) {
               $contributionResult = civicrm_api3('contribution', 'completetransaction', $complete);
             }
             catch (CiviCRM_API3_Exception $e) {
+              CRM_Core_Error::debug_var('Failed to complete transaction with', $complete);
               $error_log[] = 'Failed to complete transaction: ' . $e->getMessage() . "\n";
             }
 
@@ -163,21 +186,24 @@ function civicrm_api3_job_iatsverify($params) {
               'source' => $contribution['source'],
               'trxn_id' => $trxn_id,
             ));
+            break;
           case 4: // failed, just update the contribution status.
             civicrm_api3('Contribution', 'create', array(
               'id' => $contribution['id'],
               'contribution_status_id' => $contribution_status_id,
             ));
+            break;
         }
         // Always log these requests in my cutom civicrm table for auditing type purposes
         $query_params = array(
-          1 => array($journal_entry['cstc'], 'String'),
+          1 => array($journal_entry['client_code'], 'String'),
           2 => array($contribution['contact_id'], 'Integer'),
           3 => array($contribution['id'], 'Integer'),
           4 => array($contribution_status_id, 'Integer'),
-          5 => array($journal_entry['rst'], 'String'),
+          5 => array($journal_entry['auth_result'], 'String'),
           6 => array($contribution['contribution_recur_id'], 'Integer'),
         );
+        // CRM_Core_Error::debug_var('Logging verify', $query_params);
         if (empty($contribution['contribution_recur_id'])) {
           unset($query_params[6]);
           CRM_Core_DAO::executeQuery("INSERT INTO civicrm_iats_verify
@@ -198,10 +224,11 @@ function civicrm_api3_job_iatsverify($params) {
       1 => count($error_log),
     )
   );
-  $message .= '<br />' . ts('Processed %1 approvals and %2 rejection records from the previous ' . IATS_VERIFY_DAYS . ' days.',
+  $message .= '<br />' . ts('Processed %1 approvals, %2 pending and %3 rejection records from the previous ' . IATS_VERIFY_DAYS . ' days.',
     array(
       1 => $processed[1],
-      2 => $processed[4],
+      2 => $processed[2],
+      3 => $processed[4],
     )
   );
   // If errors ..
diff --git a/civicrm/ext/iatspayments/css/crypto.css b/civicrm/ext/iatspayments/css/crypto.css
new file mode 100644
index 0000000000000000000000000000000000000000..b0d65319dd0772c35298364b4e91c46bbe8207b0
--- /dev/null
+++ b/civicrm/ext/iatspayments/css/crypto.css
@@ -0,0 +1,47 @@
+/* crypto css */
+
+.cryptogram-section {
+  display: none;
+}
+
+#firstpay-iframe {
+  width: 100%;
+  xmin-height: 220px;
+  zoom: 1;
+  display: block;
+  border: none;
+  overflow: hidden;
+}
+
+/* override default firstpay styling with shoreditch-y css */
+
+.firstpay-container {
+  font-family: "Open Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
+  font-size: 12px;
+  padding: 0;
+  width: 300px;
+}
+
+.firstpay-label {
+  display: none;
+  font-weight: 600;
+  color: #464354;
+  margin: 4px 0;
+}
+
+.firstpay-input {
+  font-size: 13px;
+  padding: 5px 0;
+  border-radius: 3px;
+  vertical-align: middle;
+  max-width: 100%;
+  margin: 4px 0;
+
+.firstpay-input {
+  background: #fff;
+  border-color: #c2cfd8;
+  border-radius: 2px;
+  box-shadow: 0 0 3px 0 rgba(0,0,0,0.2) inset;
+  color: #4d4d69;
+}
+
diff --git a/civicrm/ext/iatspayments/iATS_4.4.14.diff b/civicrm/ext/iatspayments/iATS_4.4.14.diff
deleted file mode 100644
index 02327db7ad48da9ff32463c19f89c9fe68d2f23e..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/iATS_4.4.14.diff
+++ /dev/null
@@ -1,49 +0,0 @@
---- ./CRM/Core/Payment/Form.php	2014-07-01 20:52:02.000000000 -0400
-+++ ./CRM/Core/Payment/Form.php	2014-09-12 08:27:20.564179607 -0400
-@@ -363,9 +363,9 @@
-         $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
-       }
-     }
--    elseif (!empty($values['credit_card_number'])) {
--      $errors['credit_card_number'] = ts('Please enter a valid Card Number');
--    }
-+    /* elseif (!empty($values['credit_card_number'])) {
-+      $errors['credit_card_number'] = ts('Please enter a Card Number');
-+    } */
-   }
- 
-   /**
---- ./CRM/Member/Form/Membership.php	2014-07-01 20:52:02.000000000 -0400
-+++ ./CRM/Member/Form/Membership.php	2014-09-11 13:42:33.470862876 -0400
-@@ -150,7 +150,7 @@
-     if ($this->_mode) {
-       $this->_paymentProcessor = array('billing_mode' => 1);
-       $validProcessors = array();
--      $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'billing_mode IN ( 1, 3 )');
-+      $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'billing_mode IN ( 1, 3 ) AND payment_type = 1');
- 
-       foreach ($processors as $ppID => $label) {
-         $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($ppID, $this->_mode);
---- ./CRM/Event/Form/Participant.php	2014-07-01 20:52:02.000000000 -0400
-+++ ./CRM/Event/Form/Participant.php	2014-09-11 12:36:41.549807505 -0400
-@@ -264,7 +264,7 @@
-       $this->_paymentProcessor = array('billing_mode' => 1);
- 
-       $validProcessors = array();
--      $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, "billing_mode IN ( 1, 3 )");
-+      $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, "billing_mode IN ( 1, 3 ) AND payment_type = 1");
- 
-       foreach ($processors as $ppID => $label) {
-         $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($ppID, $this->_mode);
---- ./CRM/Event/Form/Participant.php    2015-03-26 19:51:15.208118122 -0400
-+++ ./CRM/Event/Form/Participant.php    2015-03-26 19:52:20.455620537 -0400
-@@ -1340,7 +1340,8 @@
-       $payment = CRM_Core_Payment::singleton($this->_mode, $this->_paymentProcessor, $this);
- 
-       // CRM-15622: fix for incorrect contribution.fee_amount
--      $paymentParams['fee_amount'] = NULL;
-+      // KG 4.4 issue only
-+      // $paymentParams['fee_amount'] = NULL;
-       $result = $payment->doDirectPayment($paymentParams);
- 
-       if (is_a($result, 'CRM_Core_Error')) {
diff --git a/civicrm/ext/iatspayments/iATS_4.5.8.diff b/civicrm/ext/iatspayments/iATS_4.5.8.diff
deleted file mode 100644
index 48e9b1c5e1f4f68ec88bdd18acce1090f590c841..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/iATS_4.5.8.diff
+++ /dev/null
@@ -1,37 +0,0 @@
---- CRM/Core/Payment/Form.php
-+++ CRM/Core/Payment/Form.php
-@@ -364,9 +364,9 @@ class CRM_Core_Payment_Form {
-         $errors['cvv2'] = ts('Please enter a valid Card Verification Number');
-       }
-     }
--    elseif (!empty($values['credit_card_number'])) {
--      $errors['credit_card_number'] = ts('Please enter a valid Card Number');
--    }
-+    /* elseif (!empty($values['credit_card_number'])) {
-+      $errors['credit_card_number'] = ts('Please enter a Card Number');
-+    } */
-   }
- 
-   /**
---- CRM/Event/Form/Participant.php
-+++ CRM/Event/Form/Participant.php
-@@ -274,7 +274,7 @@ class CRM_Event_Form_Participant extends CRM_Contact_Form_Task {
-       $this->_paymentProcessor = array('billing_mode' => 1);
- 
-       $validProcessors = array();
--      $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, "billing_mode IN ( 1, 3 )");
-+      $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, "billing_mode IN ( 1, 3 ) AND payment_type = 1");
- 
-       foreach ($processors as $ppID => $label) {
-         $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($ppID, $this->_mode);
---- CRM/Member/Form/Membership.php
-+++ CRM/Member/Form/Membership.php
-@@ -150,7 +150,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
-     if ($this->_mode) {
-       $this->_paymentProcessor = array('billing_mode' => 1);
-       $validProcessors = array();
--      $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'billing_mode IN ( 1, 3 )');
-+      $processors = CRM_Core_PseudoConstant::paymentProcessor(FALSE, FALSE, 'billing_mode IN ( 1, 3 ) AND payment_type = 1');
- 
-       foreach ($processors as $ppID => $label) {
-         $paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($ppID, $this->_mode);
diff --git a/civicrm/ext/iatspayments/iats.civix.php b/civicrm/ext/iatspayments/iats.civix.php
index fc2423c50b10030182b66636802ef6aaf88e25cc..3628541f28b8e2c8b1aeaf3bb0f140b5119b5752 100644
--- a/civicrm/ext/iatspayments/iats.civix.php
+++ b/civicrm/ext/iatspayments/iats.civix.php
@@ -3,35 +3,116 @@
 // AUTO-GENERATED FILE -- Civix may overwrite any changes made to this file
 
 /**
- * (Delegated) Implementation of hook_civicrm_config
+ * The ExtensionUtil class provides small stubs for accessing resources of this
+ * extension.
+ */
+class CRM_Iats_ExtensionUtil {
+  const SHORT_NAME = "iats";
+  const LONG_NAME = "com.iatspayments.civicrm";
+  const CLASS_PREFIX = "CRM_Iats";
+
+  /**
+   * Translate a string using the extension's domain.
+   *
+   * If the extension doesn't have a specific translation
+   * for the string, fallback to the default translations.
+   *
+   * @param string $text
+   *   Canonical message text (generally en_US).
+   * @param array $params
+   * @return string
+   *   Translated text.
+   * @see ts
+   */
+  public static function ts($text, $params = array()) {
+    if (!array_key_exists('domain', $params)) {
+      $params['domain'] = array(self::LONG_NAME, NULL);
+    }
+    return ts($text, $params);
+  }
+
+  /**
+   * Get the URL of a resource file (in this extension).
+   *
+   * @param string|NULL $file
+   *   Ex: NULL.
+   *   Ex: 'css/foo.css'.
+   * @return string
+   *   Ex: 'http://example.org/sites/default/ext/org.example.foo'.
+   *   Ex: 'http://example.org/sites/default/ext/org.example.foo/css/foo.css'.
+   */
+  public static function url($file = NULL) {
+    if ($file === NULL) {
+      return rtrim(CRM_Core_Resources::singleton()->getUrl(self::LONG_NAME), '/');
+    }
+    return CRM_Core_Resources::singleton()->getUrl(self::LONG_NAME, $file);
+  }
+
+  /**
+   * Get the path of a resource file (in this extension).
+   *
+   * @param string|NULL $file
+   *   Ex: NULL.
+   *   Ex: 'css/foo.css'.
+   * @return string
+   *   Ex: '/var/www/example.org/sites/default/ext/org.example.foo'.
+   *   Ex: '/var/www/example.org/sites/default/ext/org.example.foo/css/foo.css'.
+   */
+  public static function path($file = NULL) {
+    // return CRM_Core_Resources::singleton()->getPath(self::LONG_NAME, $file);
+    return __DIR__ . ($file === NULL ? '' : (DIRECTORY_SEPARATOR . $file));
+  }
+
+  /**
+   * Get the name of a class within this extension.
+   *
+   * @param string $suffix
+   *   Ex: 'Page_HelloWorld' or 'Page\\HelloWorld'.
+   * @return string
+   *   Ex: 'CRM_Foo_Page_HelloWorld'.
+   */
+  public static function findClass($suffix) {
+    return self::CLASS_PREFIX . '_' . str_replace('\\', '_', $suffix);
+  }
+
+}
+
+use CRM_Iats_ExtensionUtil as E;
+
+/**
+ * (Delegated) Implements hook_civicrm_config().
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_config
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_config
  */
 function _iats_civix_civicrm_config(&$config = NULL) {
   static $configured = FALSE;
-  if ($configured) return;
+  if ($configured) {
+    return;
+  }
   $configured = TRUE;
 
   $template =& CRM_Core_Smarty::singleton();
 
-  $extRoot = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
+  $extRoot = dirname(__FILE__) . DIRECTORY_SEPARATOR;
   $extDir = $extRoot . 'templates';
 
-  if ( is_array( $template->template_dir ) ) {
-      array_unshift( $template->template_dir, $extDir );
-  } else {
-      $template->template_dir = array( $extDir, $template->template_dir );
+  if (is_array($template->template_dir)) {
+    array_unshift($template->template_dir, $extDir);
+  }
+  else {
+    $template->template_dir = array($extDir, $template->template_dir);
   }
 
-  $include_path = $extRoot . PATH_SEPARATOR . get_include_path( );
-  set_include_path( $include_path );
+  $include_path = $extRoot . PATH_SEPARATOR . get_include_path();
+  set_include_path($include_path);
 }
 
 /**
- * (Delegated) Implementation of hook_civicrm_xmlMenu
+ * (Delegated) Implements hook_civicrm_xmlMenu().
  *
  * @param $files array(string)
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_xmlMenu
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_xmlMenu
  */
 function _iats_civix_civicrm_xmlMenu(&$files) {
   foreach (_iats_civix_glob(__DIR__ . '/xml/Menu/*.xml') as $file) {
@@ -40,59 +121,74 @@ function _iats_civix_civicrm_xmlMenu(&$files) {
 }
 
 /**
- * Implementation of hook_civicrm_install
+ * Implements hook_civicrm_install().
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_install
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install
  */
 function _iats_civix_civicrm_install() {
   _iats_civix_civicrm_config();
   if ($upgrader = _iats_civix_upgrader()) {
-    return $upgrader->onInstall();
+    $upgrader->onInstall();
   }
 }
 
 /**
- * Implementation of hook_civicrm_uninstall
+ * Implements hook_civicrm_postInstall().
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall
+ */
+function _iats_civix_civicrm_postInstall() {
+  _iats_civix_civicrm_config();
+  if ($upgrader = _iats_civix_upgrader()) {
+    if (is_callable(array($upgrader, 'onPostInstall'))) {
+      $upgrader->onPostInstall();
+    }
+  }
+}
+
+/**
+ * Implements hook_civicrm_uninstall().
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall
  */
 function _iats_civix_civicrm_uninstall() {
   _iats_civix_civicrm_config();
   if ($upgrader = _iats_civix_upgrader()) {
-    return $upgrader->onUninstall();
+    $upgrader->onUninstall();
   }
 }
 
 /**
- * (Delegated) Implementation of hook_civicrm_enable
+ * (Delegated) Implements hook_civicrm_enable().
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_enable
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable
  */
 function _iats_civix_civicrm_enable() {
   _iats_civix_civicrm_config();
   if ($upgrader = _iats_civix_upgrader()) {
     if (is_callable(array($upgrader, 'onEnable'))) {
-      return $upgrader->onEnable();
+      $upgrader->onEnable();
     }
   }
 }
 
 /**
- * (Delegated) Implementation of hook_civicrm_disable
+ * (Delegated) Implements hook_civicrm_disable().
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable
+ * @return mixed
  */
 function _iats_civix_civicrm_disable() {
   _iats_civix_civicrm_config();
   if ($upgrader = _iats_civix_upgrader()) {
     if (is_callable(array($upgrader, 'onDisable'))) {
-      return $upgrader->onDisable();
+      $upgrader->onDisable();
     }
   }
 }
 
 /**
- * (Delegated) Implementation of hook_civicrm_upgrade
+ * (Delegated) Implements hook_civicrm_upgrade().
  *
  * @param $op string, the type of operation being performed; 'check' or 'enqueue'
  * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks
@@ -100,7 +196,7 @@ function _iats_civix_civicrm_disable() {
  * @return mixed  based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending)
  *                for 'enqueue', returns void
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_upgrade
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_upgrade
  */
 function _iats_civix_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
   if ($upgrader = _iats_civix_upgrader()) {
@@ -109,13 +205,14 @@ function _iats_civix_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
 }
 
 /**
- * @return CRM_iATS_Upgrader
+ * @return CRM_Iats_Upgrader
  */
 function _iats_civix_upgrader() {
-  if (!file_exists(__DIR__.'/CRM/iATS/Upgrader.php')) {
+  if (!file_exists(__DIR__ . '/CRM/Iats/Upgrader.php')) {
     return NULL;
-  } else {
-    return CRM_iATS_Upgrader_Base::instance();
+  }
+  else {
+    return CRM_Iats_Upgrader_Base::instance();
   }
 }
 
@@ -147,7 +244,8 @@ function _iats_civix_find_files($dir, $pattern) {
       while (FALSE !== ($entry = readdir($dh))) {
         $path = $subdir . DIRECTORY_SEPARATOR . $entry;
         if ($entry{0} == '.') {
-        } elseif (is_dir($path)) {
+        }
+        elseif (is_dir($path)) {
           $todos[] = $path;
         }
       }
@@ -157,19 +255,23 @@ function _iats_civix_find_files($dir, $pattern) {
   return $result;
 }
 /**
- * (Delegated) Implementation of hook_civicrm_managed
+ * (Delegated) Implements hook_civicrm_managed().
  *
  * Find any *.mgd.php files, merge their content, and return.
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_managed
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_managed
  */
 function _iats_civix_civicrm_managed(&$entities) {
   $mgdFiles = _iats_civix_find_files(__DIR__, '*.mgd.php');
+  sort($mgdFiles);
   foreach ($mgdFiles as $file) {
     $es = include $file;
     foreach ($es as $e) {
       if (empty($e['module'])) {
-        $e['module'] = 'com.iatspayments.civicrm';
+        $e['module'] = E::LONG_NAME;
+      }
+      if (empty($e['params']['version'])) {
+        $e['params']['version'] = '3';
       }
       $entities[] = $e;
     }
@@ -177,13 +279,13 @@ function _iats_civix_civicrm_managed(&$entities) {
 }
 
 /**
- * (Delegated) Implementation of hook_civicrm_caseTypes
+ * (Delegated) Implements hook_civicrm_caseTypes().
  *
  * Find any and return any files matching "xml/case/*.xml"
  *
  * Note: This hook only runs in CiviCRM 4.4+.
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_caseTypes
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_caseTypes
  */
 function _iats_civix_civicrm_caseTypes(&$caseTypes) {
   if (!is_dir(__DIR__ . '/xml/case')) {
@@ -198,13 +300,57 @@ function _iats_civix_civicrm_caseTypes(&$caseTypes) {
       // throw new CRM_Core_Exception($errorMessage);
     }
     $caseTypes[$name] = array(
-      'module' => 'com.iatspayments.civicrm',
+      'module' => E::LONG_NAME,
       'name' => $name,
       'file' => $file,
     );
   }
 }
 
+/**
+ * (Delegated) Implements hook_civicrm_angularModules().
+ *
+ * Find any and return any files matching "ang/*.ang.php"
+ *
+ * Note: This hook only runs in CiviCRM 4.5+.
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_angularModules
+ */
+function _iats_civix_civicrm_angularModules(&$angularModules) {
+  if (!is_dir(__DIR__ . '/ang')) {
+    return;
+  }
+
+  $files = _iats_civix_glob(__DIR__ . '/ang/*.ang.php');
+  foreach ($files as $file) {
+    $name = preg_replace(':\.ang\.php$:', '', basename($file));
+    $module = include $file;
+    if (empty($module['ext'])) {
+      $module['ext'] = E::LONG_NAME;
+    }
+    $angularModules[$name] = $module;
+  }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_themes().
+ *
+ * Find any and return any files matching "*.theme.php"
+ */
+function _iats_civix_civicrm_themes(&$themes) {
+  $files = _iats_civix_glob(__DIR__ . '/*.theme.php');
+  foreach ($files as $file) {
+    $themeMeta = include $file;
+    if (empty($themeMeta['name'])) {
+      $themeMeta['name'] = preg_replace(':\.theme\.php$:', '', basename($file));
+    }
+    if (empty($themeMeta['ext'])) {
+      $themeMeta['ext'] = E::LONG_NAME;
+    }
+    $themes[$themeMeta['name']] = $themeMeta;
+  }
+}
+
 /**
  * Glob wrapper which is guaranteed to return an array.
  *
@@ -223,37 +369,35 @@ function _iats_civix_glob($pattern) {
 }
 
 /**
- * Inserts a navigation menu item at a given place in the hierarchy
+ * Inserts a navigation menu item at a given place in the hierarchy.
  *
- * $menu - menu hierarchy
- * $path - path where insertion should happen (ie. Administer/System Settings)
- * $item - menu you need to insert (parent/child attributes will be filled for you)
- * $parentId - used internally to recurse in the menu structure
+ * @param array $menu - menu hierarchy
+ * @param string $path - path to parent of this item, e.g. 'my_extension/submenu'
+ *    'Mailing', or 'Administer/System Settings'
+ * @param array $item - the item to insert (parent/child attributes will be
+ *    filled for you)
  */
-function _iats_civix_insert_navigation_menu(&$menu, $path, $item, $parentId = NULL) {
-  static $navId;
-
+function _iats_civix_insert_navigation_menu(&$menu, $path, $item) {
   // If we are done going down the path, insert menu
   if (empty($path)) {
-    if (!$navId) $navId = CRM_Core_DAO::singleValueQuery("SELECT max(id) FROM civicrm_navigation");
-    $navId ++;
-    $menu[$navId] = array (
-      'attributes' => array_merge($item, array(
+    $menu[] = array(
+      'attributes' => array_merge(array(
         'label'      => CRM_Utils_Array::value('name', $item),
         'active'     => 1,
-        'parentID'   => $parentId,
-        'navID'      => $navId,
-      ))
+      ), $item),
     );
-    return true;
-  } else {
+    return TRUE;
+  }
+  else {
     // Find an recurse into the next level down
-    $found = false;
+    $found = FALSE;
     $path = explode('/', $path);
     $first = array_shift($path);
     foreach ($menu as $key => &$entry) {
       if ($entry['attributes']['name'] == $first) {
-        if (!$entry['child']) $entry['child'] = array();
+        if (!isset($entry['child'])) {
+          $entry['child'] = array();
+        }
         $found = _iats_civix_insert_navigation_menu($entry['child'], implode('/', $path), $item, $key);
       }
     }
@@ -262,17 +406,69 @@ function _iats_civix_insert_navigation_menu(&$menu, $path, $item, $parentId = NU
 }
 
 /**
- * (Delegated) Implementation of hook_civicrm_alterSettingsFolders
+ * (Delegated) Implements hook_civicrm_navigationMenu().
+ */
+function _iats_civix_navigationMenu(&$nodes) {
+  if (!is_callable(array('CRM_Core_BAO_Navigation', 'fixNavigationMenu'))) {
+    _iats_civix_fixNavigationMenu($nodes);
+  }
+}
+
+/**
+ * Given a navigation menu, generate navIDs for any items which are
+ * missing them.
+ */
+function _iats_civix_fixNavigationMenu(&$nodes) {
+  $maxNavID = 1;
+  array_walk_recursive($nodes, function($item, $key) use (&$maxNavID) {
+    if ($key === 'navID') {
+      $maxNavID = max($maxNavID, $item);
+    }
+  });
+  _iats_civix_fixNavigationMenuItems($nodes, $maxNavID, NULL);
+}
+
+function _iats_civix_fixNavigationMenuItems(&$nodes, &$maxNavID, $parentID) {
+  $origKeys = array_keys($nodes);
+  foreach ($origKeys as $origKey) {
+    if (!isset($nodes[$origKey]['attributes']['parentID']) && $parentID !== NULL) {
+      $nodes[$origKey]['attributes']['parentID'] = $parentID;
+    }
+    // If no navID, then assign navID and fix key.
+    if (!isset($nodes[$origKey]['attributes']['navID'])) {
+      $newKey = ++$maxNavID;
+      $nodes[$origKey]['attributes']['navID'] = $newKey;
+      $nodes[$newKey] = $nodes[$origKey];
+      unset($nodes[$origKey]);
+      $origKey = $newKey;
+    }
+    if (isset($nodes[$origKey]['child']) && is_array($nodes[$origKey]['child'])) {
+      _iats_civix_fixNavigationMenuItems($nodes[$origKey]['child'], $maxNavID, $nodes[$origKey]['attributes']['navID']);
+    }
+  }
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_alterSettingsFolders().
  *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_alterSettingsFolders
  */
 function _iats_civix_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
-  static $configured = FALSE;
-  if ($configured) return;
-  $configured = TRUE;
-
   $settingsDir = __DIR__ . DIRECTORY_SEPARATOR . 'settings';
-  if(is_dir($settingsDir) && !in_array($settingsDir, $metaDataFolders)) {
+  if (!in_array($settingsDir, $metaDataFolders) && is_dir($settingsDir)) {
     $metaDataFolders[] = $settingsDir;
   }
-}
\ No newline at end of file
+}
+
+/**
+ * (Delegated) Implements hook_civicrm_entityTypes().
+ *
+ * Find any *.entityType.php files, merge their content, and return.
+ *
+ * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes
+ */
+
+function _iats_civix_civicrm_entityTypes(&$entityTypes) {
+  $entityTypes = array_merge($entityTypes, array (
+  ));
+}
diff --git a/civicrm/ext/iatspayments/iats.php b/civicrm/ext/iatspayments/iats.php
index 65467d75fe6e588f82ce162a8b1849ae6a7fbee7..91a2feb147cf892e45a6ad34faa228cc17dfd0c0 100644
--- a/civicrm/ext/iatspayments/iats.php
+++ b/civicrm/ext/iatspayments/iats.php
@@ -19,147 +19,183 @@
  * License with this program; if not, see http://www.gnu.org/licenses/
  */
 
+//opcache_reset();
+
 require_once 'iats.civix.php';
+use CRM_Iats_ExtensionUtil as E;
+
+/* First American requires a "category" for ACH transaction requests */
+
+define('FAPS_DEFAULT_ACH_CATEGORY_TEXT', 'CiviCRM ACH');
 
 /**
- * Implementation of hook_civicrm_config().
+ * Implements hook_civicrm_config().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_config
  */
 function iats_civicrm_config(&$config) {
   _iats_civix_civicrm_config($config);
 }
 
 /**
- * Implementation of hook_civicrm_xmlMenu.
+ * Implements hook_civicrm_xmlMenu().
  *
- * @param $files array(string)
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_xmlMenu
  */
 function iats_civicrm_xmlMenu(&$files) {
   _iats_civix_civicrm_xmlMenu($files);
 }
 
 /**
- * Implementation of hook_civicrm_install.
+ * Implements hook_civicrm_install().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_install
+ * TODO: don't require iATS if we're not installing the old processors.
  */
 function iats_civicrm_install() {
   if (!class_exists('SoapClient')) {
     $session = CRM_Core_Session::singleton();
     $session->setStatus(ts('The PHP SOAP extension is not installed on this server, but is required for this extension'), ts('iATS Payments Installation'), 'error');
   }
-  return _iats_civix_civicrm_install();
+  _iats_civix_civicrm_install();
+}
+
+/**
+ * Implements hook_civicrm_postInstall().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_postInstall
+ */
+function iats_civicrm_postInstall() {
+  _iats_civix_civicrm_postInstall();
 }
 
 /**
- * Implementation of hook_civicrm_uninstall.
+ * Implements hook_civicrm_uninstall().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_uninstall
  */
 function iats_civicrm_uninstall() {
-  return _iats_civix_civicrm_uninstall();
+  _iats_civix_civicrm_uninstall();
 }
 
 /**
- * Implementation of hook_civicrm_enable.
+ * Implements hook_civicrm_enable().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_enable
  */
 function iats_civicrm_enable() {
   if (!class_exists('SoapClient')) {
     $session = CRM_Core_Session::singleton();
     $session->setStatus(ts('The PHP SOAP extension is not installed on this server, but is required for this extension'), ts('iATS Payments Installation'), 'error');
   }
-  return _iats_civix_civicrm_enable();
+  _iats_civix_civicrm_enable();
 }
 
 /**
- * Implementation of hook_civicrm_disable.
+ * Implements hook_civicrm_disable().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_disable
  */
 function iats_civicrm_disable() {
-  return _iats_civix_civicrm_disable();
+  _iats_civix_civicrm_disable();
 }
 
 /**
- * Implementation of hook_civicrm_upgrade.
- *
- * @param $op string, the type of operation being performed; 'check' or 'enqueue'
- * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks
+ * Implements hook_civicrm_upgrade().
  *
- * @return mixed  based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending)
- *                for 'enqueue', returns void
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_upgrade
  */
 function iats_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) {
   return _iats_civix_civicrm_upgrade($op, $queue);
 }
 
 /**
- * Implementation of hook_civicrm_managed.
+ * Implements hook_civicrm_managed().
  *
  * Generate a list of entities to create/deactivate/delete when this module
  * is installed, disabled, uninstalled.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_managed
  */
 function iats_civicrm_managed(&$entities) {
-  $entities[] = array(
-    'module' => 'com.iatspayments.civicrm',
-    'name' => 'iATS Payments',
-    'entity' => 'PaymentProcessorType',
-    'params' => array(
-      'version' => 3,
-      'name' => 'iATS Payments Credit Card',
-      'title' => 'iATS Payments Credit Card',
-      'description' => 'iATS credit card payment processor using the web services interface.',
-      'class_name' => 'Payment_iATSService',
-      'billing_mode' => 'form',
-      'user_name_label' => 'Agent Code',
-      'password_label' => 'Password',
-      'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'is_recur' => 1,
-      'payment_type' => 1,
-    ),
-  );
-  $entities[] = array(
-    'module' => 'com.iatspayments.civicrm',
-    'name' => 'iATS Payments ACH/EFT',
-    'entity' => 'PaymentProcessorType',
-    'params' => array(
-      'version' => 3,
-      'name' => 'iATS Payments ACH/EFT',
-      'title' => 'iATS Payments ACH/EFT',
-      'description' => 'iATS ACH/EFT payment processor using the web services interface.',
-      'class_name' => 'Payment_iATSServiceACHEFT',
-      'billing_mode' => 'form',
-      'user_name_label' => 'Agent Code',
-      'password_label' => 'Password',
-      'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'is_recur' => 1,
-      'payment_type' => 2,
-      'payment_instrument_id' => '2', /* "Debit Card"  */
-    ),
-  );
-  $entities[] = array(
-    'module' => 'com.iatspayments.civicrm',
-    'name' => 'iATS Payments SWIPE',
-    'entity' => 'PaymentProcessorType',
-    'params' => array(
-      'version' => 3,
-      'name' => 'iATS Payments SWIPE',
-      'title' => 'iATS Payments SWIPE',
-      'description' => 'iATS credit card payment processor using the encrypted USB IDTECH card reader.',
-      'class_name' => 'Payment_iATSServiceSWIPE',
-      'billing_mode' => 'form',
-      'user_name_label' => 'Agent Code',
-      'password_label' => 'Password',
-      'url_site_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_recur_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_site_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'url_recur_test_default' => 'https://www.iatspayments.com/NetGate/ProcessLinkv2.asmx?WSDL',
-      'is_recur' => 1,
-      'payment_type' => 1,
-    ),
-  );
-  return _iats_civix_civicrm_managed($entities);
+  _iats_civix_civicrm_managed($entities);
+}
+
+/**
+ * Implements hook_civicrm_caseTypes().
+ *
+ * Generate a list of case-types.
+ *
+ * Note: This hook only runs in CiviCRM 4.4+.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_caseTypes
+ */
+function iats_civicrm_caseTypes(&$caseTypes) {
+  _iats_civix_civicrm_caseTypes($caseTypes);
+}
+
+/**
+ * Implements hook_civicrm_angularModules().
+ *
+ * Generate a list of Angular modules.
+ *
+ * Note: This hook only runs in CiviCRM 4.5+. It may
+ * use features only available in v4.6+.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_angularModules
+ */
+function iats_civicrm_angularModules(&$angularModules) {
+  _iats_civix_civicrm_angularModules($angularModules);
+}
+
+/**
+ * Implements hook_civicrm_alterSettingsFolders().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders
+ */
+function iats_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
+  _iats_civix_civicrm_alterSettingsFolders($metaDataFolders);
+}
+
+/**
+ * Implements hook_civicrm_entityTypes().
+ *
+ * Declare entity types provided by this module.
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_entityTypes
+ */
+function iats_civicrm_entityTypes(&$entityTypes) {
+  _iats_civix_civicrm_entityTypes($entityTypes);
 }
 
+// --- Functions below this ship commented out. Uncomment as required. ---
+
+/**
+ * Implements hook_civicrm_preProcess().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_preProcess
+ *
+function iats_civicrm_preProcess($formName, &$form) {
+
+} // */
+
+/**
+ * Implements hook_civicrm_navigationMenu().
+ *
+ * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_navigationMenu
+ *
+function iats_civicrm_navigationMenu(&$menu) {
+  _iats_civix_insert_navigation_menu($menu, 'Mailings', array(
+    'label' => E::ts('New subliminal message'),
+    'name' => 'mailing_subliminal_message',
+    'url' => 'civicrm/mailing/subliminal',
+    'permission' => 'access CiviMail',
+    'operator' => 'OR',
+    'separator' => 0,
+  ));
+  _iats_civix_navigationMenu($menu);
+} // */
+
 /**
  * Implements hook_civicrm_check().
  */
@@ -229,35 +265,7 @@ function _iats_civicrm_domain_info($key) {
 
 /* START utility functions to allow this extension to work with different civicrm version */
 
-/**
- * Does this version of Civi implement repeattransaction well?
- */
-function _iats_civicrm_use_repeattransaction() {
-  $version = CRM_Utils_System::version();
-  return (version_compare($version, '4.7.12') < 0) ? FALSE : TRUE;
-}
-
-/**
- * Get the name of the next scheduled contribution date field, (not necessary since 4.4)
- */
-function _iats_civicrm_nscd_fid() {
-  $version = CRM_Utils_System::version();
-  return (version_compare($version, '4.4') < 0) ? 'next_sched_contribution' : 'next_sched_contribution_date';
-}
-
-/**
- * Set js config values, version dependent. Access is also version dependent.
- */
-function _iats_civicrm_varset($vars) {
-  $version = CRM_Utils_System::version();
-  // Support 4.4!
-  if (version_compare($version, '4.5') < 0) {
-    CRM_Core_Resources::singleton()->addSetting('iatspayments', $vars);
-  }
-  else {
-    CRM_Core_Resources::singleton()->addVars('iatspayments', $vars);
-  }
-}
+// removed, 1.7 release
 
 /* END functions to allow this extension to work with different civicrm version */
 
@@ -318,70 +326,78 @@ function iats_civicrm_navigationMenu(&$navMenu) {
  * Do a Drupal 7 style thing so we can write smaller functions.
  */
 function iats_civicrm_buildForm($formName, &$form) {
-  // But start by grouping a few forms together for nicer code.
-  switch ($formName) {
-    case 'CRM_Event_Form_Participant':
-    case 'CRM_Member_Form_Membership':
-    case 'CRM_Contribute_Form_Contribution':
-      // Override normal convention, deal with all these backend credit card contribution forms the same way.
-      $fname = 'iats_civicrm_buildForm_CreditCard_Backend';
-      break;
-
-    case 'CRM_Contribute_Form_Contribution_Main':
-    case 'CRM_Event_Form_Registration_Register':
-    case 'CRM_Financial_Form_Payment':
-      // Override normal convention, deal with all these front-end contribution forms the same way.
-      $fname = 'iats_civicrm_buildForm_Contribution_Frontend';
-      break;
-
-    case 'CRM_Contribute_Form_Contribution_Confirm':
-      // On the confirmation form, we know the processor, so only do processor specific customizations.
-      $fname = 'iats_civicrm_buildForm_Contribution_Confirm_' . $form->_paymentProcessor['class_name'];
-      break;
-
-    case 'CRM_Contribute_Form_Contribution_ThankYou':
-      // On the confirmation form, we know the processor, so only do processor specific customizations.
-      $fname = 'iats_civicrm_buildForm_Contribution_ThankYou_' . $form->_paymentProcessor['class_name'];
-      break;
-
-    default:
-      $fname = 'iats_civicrm_buildForm_' . $formName;
-      break;
-  }
+  $fname = 'iats_civicrm_buildForm_' . $formName;
   if (function_exists($fname)) {
+    // CRM_Core_Error::debug_var('overridden formName',$formName);
     $fname($form);
   }
   // Else echo $fname;.
 }
 
+
 /**
- *
+ * Modifications to a (public/frontend) contribution financial forms for iATS
+ * procesors.
+ * 1. enable public selection of future recurring contribution start date.
+ * 
+ * We're only handling financial payment class forms here. Note that we can no
+ * longer test for whether the page has/is recurring or not. 
  */
-function iats_civicrm_pageRun(&$page) {
-  $fname = 'iats_civicrm_pageRun_' . $page->getVar('_name');
-  if (function_exists($fname)) {
-    $fname($page);
+
+function iats_civicrm_buildForm_CRM_Financial_Form_Payment(&$form) {
+  // We're on CRM_Financial_Form_Payment, we've got just one payment processor
+  // Skip this if it's not an iATS-type of processor
+  $type = _iats_civicrm_is_iats($form->_paymentProcessor['id']);
+  if (empty($type)) {
+    return;
+  }
+
+  // If enabled provide a way to set future contribution dates. 
+  // Uses javascript to hide/reset unless they have recurring contributions checked.
+  $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+  if (!empty($settings['enable_public_future_recurring_start'])
+    && $form->_paymentObject->supportsFutureRecurStartDate()
+  ) {
+    $allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
+    $start_dates = CRM_Iats_Transaction::get_future_monthly_start_dates(time(), $allow_days);
+    $form->addElement('select', 'receive_date', ts('Date of first contribution'), $start_dates);
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'template' => 'CRM/Iats/BillingBlockRecurringExtra.tpl',
+    ));
+    $recurStartJs = CRM_Core_Resources::singleton()->getUrl('com.iatspayments.civicrm', 'js/recur_start.js');
+    $script = 'var recurStartJs = "' . $recurStartJs . '";';
+    $script .= 'CRM.$(function ($) { $.getScript(recurStartJs); });';
+    CRM_Core_Region::instance('billing-block')->add(array(
+      'script' => $script,
+    ));
   }
 }
 
+/**
+ * The main civicrm contribution form is the public one, there are some
+ * edge cases where we need to do the same as the Financial form above.
+ */
+function iats_civicrm_buildForm_CRM_Contribute_Form_Contribution_Main(&$form) {
+  return iats_civicrm_buildForm_CRM_Financial_Form_Payment($form);
+}
+
 /**
  *
  */
-function iats_civicrm_pageRun_CRM_Contact_Page_View_Summary(&$page) {
-  // Because of AJAX loading, I need to load my backend swipe js here.
-  $swipe = iats_civicrm_processors(NULL, 'SWIPE', array('is_default' => 1));
-  if (count($swipe) > 0) {
-    CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/swipe.js', 10);
+function iats_civicrm_pageRun(&$page) {
+  $fname = 'iats_civicrm_pageRun_' . $page->getVar('_name');
+  if (function_exists($fname)) {
+    $fname($page);
   }
 }
 
 /**
  * Modify the recurring contribution (subscription) page.
- * Display extra information about recurring contributions using iATS, and
+ * Display extra information about recurring contributions using Legacy iATS, and
  * link to iATS CustomerLink display and editing pages.
  */
 function iats_civicrm_pageRun_CRM_Contribute_Page_ContributionRecur($page) {
-  // Get the corresponding (most recently created) iATS customer code record referenced from the customer_codes table via the recur_id ('crid')
+  // Get the corresponding (most recently created) iATS customer code record 
   // we'll also get the expiry date and last four digits (at least, our best information about that).
   $extra = array();
   $crid = CRM_Utils_Request::retrieve('id', 'Integer', $page, FALSE);
@@ -392,36 +408,27 @@ function iats_civicrm_pageRun_CRM_Contribute_Page_ContributionRecur($page) {
     return;
   }
   $type = _iats_civicrm_is_iats($recur['payment_processor_id']);
-  if (!$type) {
+  if ((0 !== strpos($type,'iATSService')) || empty($recur['payment_token_id'])) {
     return;
   }
   try {
-    $params = array(1 => array($crid, 'Integer'));
-    $dao = CRM_Core_DAO::executeQuery("SELECT customer_code,expiry FROM civicrm_iats_customer_codes WHERE recur_id = %1 ORDER BY id DESC LIMIT 1", $params);
-    if ($dao->fetch()) {
-      $customer_code = $dao->customer_code;
-      $extra['iATS Customer Code'] = $customer_code;
-      $customerLinkView = CRM_Utils_System::url('civicrm/contact/view/iatscustomerlink',
+    $payment_token = civicrm_api3('PaymentToken', 'getsingle', [
+          'id' => $recur['payment_token_id'],
+    ]);
+    $customer_code = $payment_token['token'];
+    $extra['iATS Customer Code'] = $customer_code;
+    $customerLinkView = CRM_Utils_System::url('civicrm/contact/view/iatscustomerlink',
+      'reset=1&cid=' . $recur['contact_id'] . '&customerCode=' . $customer_code . '&paymentProcessorId=' . $recur['payment_processor_id'] . '&is_test=' . $recur['is_test']);
+    $extra['customerLink'] = "<a href='$customerLinkView'>View</a>";
+    if ($type == 'iATSService' || $type == 'iATSServiceSWIPE') {
+      $customerLinkEdit = CRM_Utils_System::url('civicrm/contact/edit/iatscustomerlink',
         'reset=1&cid=' . $recur['contact_id'] . '&customerCode=' . $customer_code . '&paymentProcessorId=' . $recur['payment_processor_id'] . '&is_test=' . $recur['is_test']);
-      $extra['customerLink'] = "<a href='$customerLinkView'>View</a>";
-      if ($type == 'iATSService' || $type == 'iATSServiceSWIPE') {
-        $customerLinkEdit = CRM_Utils_System::url('civicrm/contact/edit/iatscustomerlink',
-          'reset=1&cid=' . $recur['contact_id'] . '&customerCode=' . $customer_code . '&paymentProcessorId=' . $recur['payment_processor_id'] . '&is_test=' . $recur['is_test']);
-        $extra['customerLink'] .= " | <a href='$customerLinkEdit'>Edit</a>";
-        $processLink = CRM_Utils_System::url('civicrm/contact/iatsprocesslink',
-          'reset=1&cid=' . $recur['contact_id'] . '&customerCode=' . $customer_code . '&paymentProcessorId=' . $recur['payment_processor_id'] . '&crid=' . $crid . '&is_test=' . $recur['is_test']);
-        $extra['customerLink'] .= " | <a href='$processLink'>Process</a>";
-        $expiry = str_split($dao->expiry, 2);
-        $extra['expiry'] = '20' . implode('-', $expiry);
-      }
-    }
-    if (!empty($recur['invoice_id'])) {
-      // We may have the last 4 digits via the original request log, though they may no longer be accurate, but let's get it anyway if we can.
-      $params = array(1 => array($recur['invoice_id'], 'String'));
-      $dao = CRM_Core_DAO::executeQuery("SELECT cc FROM civicrm_iats_request_log WHERE invoice_num = %1", $params);
-      if ($dao->fetch()) {
-        $extra['cc'] = $dao->cc;
-      }
+      $extra['customerLink'] .= " | <a href='$customerLinkEdit'>Edit</a>";
+      $processLink = CRM_Utils_System::url('civicrm/contact/iatsprocesslink',
+        'reset=1&cid=' . $recur['contact_id'] . '&customerCode=' . $customer_code . '&paymentProcessorId=' . $recur['payment_processor_id'] . '&crid=' . $crid . '&is_test=' . $recur['is_test']);
+      $extra['customerLink'] .= " | <a href='$processLink'>Process</a>";
+      $expiry = $payment_token['expiry_date'];
+      $extra['expiry'] = date('Y-m', strtotime($expiry));
     }
   }
   catch (CiviCRM_API3_Exception $e) {
@@ -435,7 +442,7 @@ function iats_civicrm_pageRun_CRM_Contribute_Page_ContributionRecur($page) {
     $template->assign($key, $value);
   }
   CRM_Core_Region::instance('page-body')->add(array(
-    'template' => 'CRM/iATS/ContributionRecur.tpl',
+    'template' => 'CRM/Iats/ContributionRecur.tpl',
   ));
   CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/subscription_view.js');
 }
@@ -446,7 +453,6 @@ function iats_civicrm_pageRun_CRM_Contribute_Page_ContributionRecur($page) {
  */
 function iats_civicrm_merge($type, &$data, $mainId = NULL, $otherId = NULL, $tables = NULL) {
   if ('cidRefs' == $type) {
-    $data['civicrm_iats_customer_codes'] = array('cid');
     $data['civicrm_iats_verify'] = array('cid');
   }
 }
@@ -468,75 +474,16 @@ function iats_civicrm_merge($type, &$data, $mainId = NULL, $otherId = NULL, $tab
  * TODO: CiviCRM should have nicer ways to handle this.
  */
 function iats_civicrm_pre($op, $objectName, $objectId, &$params) {
-  // Since this function gets called a lot, quickly determine if I care about the record being created.
-  if (('create' == $op) && ('Contribution' == $objectName) && !empty($params['contribution_status_id'])) {
-    // watchdog('iats_civicrm','hook_civicrm_pre for Contribution <pre>@params</pre>',array('@params' => print_r($params));
-    // figure out the payment processor id, not nice.
-    $version = CRM_Utils_System::version();
-    $payment_processor_id = !empty($params['payment_processor']) ? $params['payment_processor'] :
-                                (!empty($params['contribution_recur_id']) ? _iats_civicrm_get_payment_processor_id($params['contribution_recur_id']) :
-                                 0);
-    if ($type = _iats_civicrm_is_iats($payment_processor_id)) {
-      $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
-      $allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
-      switch ($type . $objectName) {
-        // Cc contribution, test if it's been set to status 2 on a recurring contribution.
-        case 'iATSServiceContribution':
-        case 'iATSServiceSWIPEContribution':
-          // For civi version before 4.6.6, we had to force the status to 1.
-          if ((2 == $params['contribution_status_id'])
-            && !empty($params['contribution_recur_id'])
-            && (max($allow_days) <= 0)
-            && (version_compare($version, '4.6.6') < 0)
-          ) {
-            // But only for the first one.
-            $count = civicrm_api3('Contribution', 'getcount', array('contribution_recur_id' => $params['contribution_recur_id']));
-            if (
-              (is_array($count) && empty($count['result']))
-              || empty($count)
-            ) {
-              // watchdog('iats_civicrm','hook_civicrm_pre updating status_id for objectName @id, count <pre>!count</pre>, params <pre>!params</pre>, ',array('@id' => $objectName, '!count' => print_r($count,TRUE),'!params' => print_r($params,TRUE)));.
-              $params['contribution_status_id'] = 1;
-            }
-          }
-          break;
-
-        case 'iATSServiceACHEFTContribution':
-          // ach/eft contribution: update the payment instrument if it's still showing cc or empty
-          if ($params['payment_instrument_id'] <= 1) {
-            $params['payment_instrument_id'] = 2;
-          }
-          // And push the status to 2 if civicrm thinks it's 1, i.e. for one-time contributions
-          // in other words, never create ach/eft contributions as complete, always push back to pending and verify.
-          if ($params['contribution_status_id'] == 1) {
-            $params['contribution_status_id'] = 2;
-          }
-          break;
-
-        // UK DD contribution: update the payment instrument, fix the receive date.
-        case 'iATSServiceUKDDContribution':
-          if ($params['payment_instrument_id'] <= 1) {
-            $params['payment_instrument_id'] = 2;
-          }
-          if ($start_date = strtotime($_POST['payer_validate_start_date'])) {
-            $params['receive_date'] = date('Ymd', $start_date) . '120000';
-          }
-          break;
-
-      }
-    }
-    // watchdog('iats_civicrm','ignoring hook_civicrm_pre for objectName @id',array('@id' => $objectName));.
-  }
-  // If I've set fixed monthly recurring dates, force any iats (non uk dd) recurring contribution schedule records to comply.
+  // If I've set fixed monthly recurring dates, force any iats recurring contribution schedule records to comply.
   if (('ContributionRecur' == $objectName) && ('create' == $op || 'edit' == $op) && !empty($params['payment_processor_id'])) {
     if ($type = _iats_civicrm_is_iats($params['payment_processor_id'])) {
-      if ($type != 'iATSServiceUKDD' && !empty($params['next_sched_contribution_date'])) {
+      if (!empty($params['next_sched_contribution_date'])) {
         $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
         $allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
         // Force one of the fixed days, and set the cycle_day at the same time.
         if (0 < max($allow_days)) {
           $init_time = ('create' == $op) ? time() : strtotime($params['next_sched_contribution_date']);
-          $from_time = _iats_contributionrecur_next($init_time, $allow_days);
+          $from_time = CRM_Iats_Transaction::contributionrecur_next($init_time, $allow_days);
           $params['next_sched_contribution_date'] = date('YmdHis', $from_time);
           // Day of month without leading 0.
           $params['cycle_day'] = date('j', $from_time);
@@ -550,6 +497,14 @@ function iats_civicrm_pre($op, $objectName, $objectId, &$params) {
   }
 }
 
+function iats_get_setting($key = NULL) {
+  static $settings;
+  if (empty($settings)) { 
+    $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
+  }
+  return empty($key) ?  $settings : (isset($settings[$key]) ? $settings[$key] : '');
+}
+
 /**
  * The contribution itself doesn't tell you which payment processor it came from
  * So we have to dig back via the contribution_recur_id that it is associated with.
@@ -588,83 +543,71 @@ function _iats_civicrm_is_iats($payment_processor_id) {
   }
   catch (CiviCRM_API3_Exception $e) {
     return FALSE;
+    // TODO: log error?.
   }
   if (empty($result['class_name'])) {
     return FALSE;
-    // TODO: log error.
+    // TODO: log error?.
   }
-  $type = substr($result['class_name'], 0, 19);
-  $subtype = substr($result['class_name'], 19);
-  return ('Payment_iATSService' == $type) ? 'iATSService' . $subtype : FALSE;
+  // type is class name with Payment_ stripped from the front
+  $type = substr($result['class_name'], 8);
+  $is_iats = (0 == strpos($type, 'iATSService')) || (0 == strpos($type, 'Faps'));
+  return ($is_iats ? $type : FALSE);
 }
 
 /**
  * Internal utility function: return the id's of any iATS processors matching various conditions.
  *
- * Processors: an array of payment processors indexed by id to filter by,
- *             or if NULL, it searches through all
- * subtype: the iats service class name subtype
+ * class: the payment object class name to match (prefixed w/ 'Payment_')
+ * processors: an array of payment processors indexed by id to filter by
  * params: an array of additional params to pass to the api call.
  */
-function iats_civicrm_processors($processors, $subtype = '', $params = array()) {
+function _iats_filter_payment_processors($class, $processors = array(), $params = array()) {
   $list = array();
-  $match_all = ('*' == $subtype) ? TRUE : FALSE;
-  if (!$match_all) {
-    $params['class_name'] = 'Payment_iATSService' . $subtype;
+  $params['class_name'] = ['LIKE' => 'Payment_' . $class];
+  // On the chance that there are a lot of payment processors and the caller
+  // hasn't specified a limit, assume they want them all.
+  if (empty($params['options']['limit'])) {
+    $params['options']['limit'] = 0;
   }
-
   // Set the domain id if not passed in.
   if (!array_key_exists('domain_id', $params)) {
     $params['domain_id']    = CRM_Core_Config::domainID();
   }
-
+  $params['sequential'] = FALSE; // return list indexed by processor id
   $result = civicrm_api3('PaymentProcessor', 'get', $params);
   if (0 == $result['is_error'] && count($result['values']) > 0) {
-    foreach ($result['values'] as $paymentProcessor) {
-      $id = $paymentProcessor['id'];
-      if ((is_null($processors)) || !empty($processors[$id])) {
-        if (!$match_all || (0 === strpos($paymentProcessor['class_name'], 'Payment_iATSService'))) {
-          $list[$id] = $paymentProcessor;
-        }
-      }
-    }
+    $list = (0 < count($processors)) ? array_intersect_key($result['values'], $processors) : $result['values'];
   }
   return $list;
 }
 
+
 /**
- * Customize direct debit billing blocks, per currency.
- *
- * Each country has different rules about direct debit, so only currencies that we explicitly handle will be
- * customized, others will get a warning.
- *
- * The currency-specific functions will do things like modify labels, add exta fields,
- * add legal requirement notice and perhaps checkbox acceptance for electronic acceptance of ACH/EFT, and
- * make this form nicer by include a sample check with instructions for getting the various numbers
- *
- * Each one also includes some javascript to move the new fields around on the DOM
- */
-function iats_acheft_form_customize($form) {
-  $currency = iats_getCurrency($form);
-  $fname = 'iats_acheft_form_customize_' . $currency;
-  /* we always want these three fields to be required, in all currencies. As of 4.6.?, this is in core */
-  if (empty($form->billingFieldSets['direct_debit']['fields']['account_holder']['is_required'])) {
-    $form->addRule('account_holder', ts('%1 is a required field.', array(1 => ts('Name of Account Holder'))), 'required');
-  }
-  if (empty($form->billingFieldSets['direct_debit']['fields']['bank_account_number']['is_required'])) {
-    $form->addRule('bank_account_number', ts('%1 is a required field.', array(1 => ts('Account Number'))), 'required');
-  }
-  if (empty($form->billingFieldSets['direct_debit']['fields']['bank_name']['is_required'])) {
-    $form->addRule('bank_name', ts('%1 is a required field.', array(1 => ts('Bank Name'))), 'required');
-  }
-  if ($currency && function_exists($fname)) {
-    $fname($form);
+ * Internal utility function: return a list of the payment processors attached
+ * to a contribution form of variable class
+ * */
+function _iats_get_form_payment_processors($form) {
+  $form_class = get_class($form);
+
+  if ($form_class == 'CRM_Financial_Form_Payment') {
+    // We're on CRM_Financial_Form_Payment, we've got just one payment processor
+    $id = $form->_paymentProcessor['id'];
+    return array($id => $form->_paymentProcessor);
   }
-  // I'm handling an unexpected currency.
-  elseif ($currency) {
-    CRM_Core_Region::instance('billing-block')->add(array(
-      'template' => 'CRM/iATS/BillingBlockDirectDebitExtra_Other.tpl',
-    ));
+  else { 
+    // Handle the legacy: event and contribution page forms
+    if (empty($form->_paymentProcessors)) {
+      if (empty($form->_paymentProcessorIDs)) {
+        return;
+      }
+      else {
+        return array_fill_keys($form->_paymentProcessorIDs, 1);
+      }
+    }
+    else {
+      return $form->_paymentProcessors;
+    }
   }
 }
 
@@ -698,309 +641,6 @@ function iats_getCurrency($form) {
   return $currency;
 }
 
-/**
- * Customization for USD ACH-EFT billing block.
- */
-function iats_acheft_form_customize_USD($form) {
-  $form->addElement('select', 'bank_account_type', ts('Account type'), array('CHECKING' => 'Checking', 'SAVING' => 'Saving'));
-  $form->addRule('bank_account_type', ts('%1 is a required field.', array(1 => ts('Account type'))), 'required');
-  $element = $form->getElement('account_holder');
-  $element->setLabel(ts('Name of Account Holder'));
-  $element = $form->getElement('bank_account_number');
-  $element->setLabel(ts('Bank Account Number'));
-  $element = $form->getElement('bank_identification_number');
-  $element->setLabel(ts('Bank Routing Number'));
-  if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
-    $form->addRule('bank_identification_number', ts('%1 is a required field.', array(1 => ts('Bank Routing Number'))), 'required');
-  }
-  CRM_Core_Region::instance('billing-block')->add(array(
-    'template' => 'CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl',
-  ));
-}
-
-/**
- * Customization for CAD ACH-EFT billing block.
- */
-function iats_acheft_form_customize_CAD($form) {
-  $form->addElement('text', 'cad_bank_number', ts('Bank Number (3 digits)'));
-  $form->addRule('cad_bank_number', ts('%1 is a required field.', array(1 => ts('Bank Number'))), 'required');
-  $form->addRule('cad_bank_number', ts('%1 must contain only digits.', array(1 => ts('Bank Number'))), 'numeric');
-  $form->addRule('cad_bank_number', ts('%1 must be of length 3.', array(1 => ts('Bank Number'))), 'rangelength', array(3, 3));
-  $form->addElement('text', 'cad_transit_number', ts('Transit Number (5 digits)'));
-  $form->addRule('cad_transit_number', ts('%1 is a required field.', array(1 => ts('Transit Number'))), 'required');
-  $form->addRule('cad_transit_number', ts('%1 must contain only digits.', array(1 => ts('Transit Number'))), 'numeric');
-  $form->addRule('cad_transit_number', ts('%1 must be of length 5.', array(1 => ts('Transit Number'))), 'rangelength', array(5, 5));
-  $form->addElement('select', 'bank_account_type', ts('Account type'), array('CHECKING' => 'Chequing', 'SAVING' => 'Savings'));
-  $form->addRule('bank_account_type', ts('%1 is a required field.', array(1 => ts('Account type'))), 'required');
-  /* minor customization of labels + make them required */
-  $element = $form->getElement('account_holder');
-  $element->setLabel(ts('Name of Account Holder'));
-  $element = $form->getElement('bank_account_number');
-  $element->setLabel(ts('Account Number'));
-  $form->addRule('bank_account_number', ts('%1 must contain only digits.', array(1 => ts('Bank Account Number'))), 'numeric');
-  /* the bank_identification_number is hidden and then populated using jquery, in the custom template */
-  $element = $form->getElement('bank_identification_number');
-  $element->setLabel(ts('Bank Number + Transit Number'));
-  CRM_Core_Region::instance('billing-block')->add(array(
-    'template' => 'CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl',
-  ));
-}
-
-/**
- * Contribution form customization for iATS secure swipe.
- */
-function iats_swipe_form_customize($form) {
-  // Remove two fields that are replaced by the swipe code data
-  // we need to remove them from the _paymentFields as well or they'll sneak back in!
-  $form->removeElement('credit_card_type', TRUE);
-  $form->removeElement('cvv2', TRUE);
-  unset($form->_paymentFields['credit_card_type']);
-  unset($form->_paymentFields['cvv2']);
-  // Add a single text area to store/display the encrypted cc number that the swipe device will fill.
-  $form->addElement('textarea', 'encrypted_credit_card_number', ts('Encrypted'), array('cols' => '80', 'rows' => '8'));
-  $form->addRule('encrypted_credit_card_number', ts('%1 is a required field.', array(1 => ts('Encrypted'))), 'required');
-  CRM_Core_Region::instance('billing-block')->add(array(
-    'template' => 'CRM/iATS/BillingBlockSwipe.tpl',
-  ));
-}
-
-/**
- * Customize direct debit billing block for UK Direct Debit.
- *
- * This could be handled by iats_acheft_form_customize, except there's some tricky multi-page stuff for the payer validate step.
- */
-function iats_ukdd_form_customize($form) {
-  /* uk direct debits have to start 16 days after the initial request is made */
-  if (!$form->elementExists('is_recur')) {
-    // Todo generate an error on the page.
-    return;
-  }
-  define('IATS_UKDD_START_DELAY', 16 * 24 * 60 * 60);
-  /* For batch efficiencies, restrict to a specific set of days of the month, less than 28 */
-  // you can change these if you're sensible and careful.
-  $start_days = array('1', '15');
-  $start_dates = _iats_get_future_monthly_start_dates(time() + IATS_UKDD_START_DELAY, $start_days);
-  $service_user_number = $form->_paymentProcessor['signature'];
-  $payee = _iats_civicrm_domain_info('name');
-  $phone = _iats_civicrm_domain_info('domain_phone');
-  $email = _iats_civicrm_domain_info('domain_email');
-  $form->addRule('is_recur', ts('You can only use this form to make recurring contributions.'), 'required');
-  /* declaration checkbox at the top */
-  $form->addElement('checkbox', 'payer_validate_declaration', ts('I wish to start a Direct Debit'));
-  $form->addElement('static', 'payer_validate_contact', ts(''), ts('Organization: %1, Phone: %2, Email: %3', array('%1' => $payee, '%2' => $phone['phone'], '%3' => $email)));
-  $form->addElement('select', 'payer_validate_start_date', ts('Date of first collection'), $start_dates);
-  $form->addRule('payer_validate_declaration', ts('%1 is a required field.', array(1 => ts('The Declaration'))), 'required');
-  $form->addRule('installments', ts('%1 is a required field.', array(1 => ts('Number of installments'))), 'required');
-  /* customization of existing elements */
-  $element = $form->getElement('account_holder');
-  $element->setLabel(ts('Account Holder Name'));
-  if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
-    $form->addRule('account_holder', ts('%1 is a required field.', array(1 => ts('Name of Account Holder'))), 'required');
-  }
-  $element = $form->getElement('bank_account_number');
-  $element->setLabel(ts('Account Number'));
-  if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
-    $form->addRule('bank_account_number', ts('%1 is a required field.', array(1 => ts('Account Number'))), 'required');
-  }
-  $element = $form->getElement('bank_identification_number');
-  $element->setLabel(ts('Sort Code'));
-  if (empty($form->billingFieldSets['direct_debit']['fields']['bank_identification_number']['is_required'])) {
-    $form->addRule('bank_identification_number', ts('%1 is a required field.', array(1 => ts('Sort Code'))), 'required');
-  }
-  /* new payer validation elements */
-  $form->addElement('textarea', 'payer_validate_address', ts('Name and full postal address of your Bank or Building Society'), array('rows' => '6', 'columns' => '30'));
-  $form->addElement('text', 'payer_validate_service_user_number', ts('Service User Number'));
-  $form->addElement('text', 'payer_validate_reference', ts('Reference'), array());
-  // Date on which the validation happens, reference.
-  $form->addElement('text', 'payer_validate_date', ts('Today\'s Date'), array());
-  $form->addElement('static', 'payer_validate_instruction', ts('Instruction to your Bank or Building Society'), ts('Please pay %1 Direct Debits from the account detailed in this instruction subject to the safeguards assured by the Direct Debit Guarantee. I understand that this instruction may remain with TestingTest and, if so, details will be passed electronically to my Bank / Building Society.', array('%1' => "<strong>$payee</strong>")));
-  // $form->addRule('bank_name', ts('%1 is a required field.', array(1 => ts('Bank Name'))), 'required');
-  // $form->addRule('bank_account_type', ts('%1 is a required field.', array(1 => ts('Account type'))), 'required');
-  /* only allow recurring contributions, set date */
-  $form->setDefaults(array(
-    'is_recur' => 1,
-    'payer_validate_date' => date('F j, Y'),
-    'payer_validate_start_date' => current(array_keys($start_dates)),
-    'payer_validate_service_user_number' => $service_user_number,
-  // Make recurring contrib default to true.
-  ));
-  CRM_Core_Region::instance('billing-block')->add(array(
-    'template' => 'CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl',
-  ));
-}
-
-/**
- * Modifications to a (public/frontend) contribution forms.
- * 1. set recurring to be the default, if enabled (ACH/EFT) [previously forced recurring, removed in 1.2.4]
- * 2. add extra fields/modify labels.
- * 3. enable public selection of future recurring contribution start date.
- * 
- * We're handling event, contribution, and financial payment class forms here. 
- * The new 4.7 financial payment class form is used when switching payment processors (sometimes).
- */
-function iats_civicrm_buildForm_Contribution_Frontend(&$form) {
-
-  $form_class = get_class($form);
-
-  if ($form_class == 'CRM_Financial_Form_Payment') {
-    // We're on CRM_Financial_Form_Payment, we've got just one payment processor
-    $id = $form->_paymentProcessor['id'];
-    $iats_processors = iats_civicrm_processors(array($id => $form->_paymentProcessor), '*');
-  }
-  else { 
-    // Handle the event and contribution page forms
-    if (empty($form->_paymentProcessors)) {
-      if (empty($form->_paymentProcessorIDs)) {
-        return;
-      }
-      else {
-        $form_payment_processors = array_fill_keys($form->_paymentProcessorIDs,1);
-      }
-    }
-    else {
-      $form_payment_processors = $form->_paymentProcessors;
-    }
-    $iats_processors = iats_civicrm_processors($form_payment_processors, '*');
-  }
-  if (empty($iats_processors)) {
-    return;
-  }
-  $ukdd = $swipe = $acheft = array();
-  foreach ($iats_processors as $id => $processor) {
-    switch ($processor['class_name']) {
-      case 'Payment_iATSServiceACHEFT':
-        $acheft[$id] = $processor;
-        break;
-
-      case 'Payment_iATSServiceSWIPE':
-        $swipe[$id] = $processor;
-        break;
-
-      case 'Payment_iATSServiceUKDD':
-        $ukdd[$id] = $processor;
-        break;
-    }
-  }
-  // Include the required javascripts for available customized selections
-  // If a form allows ACH/EFT and enables recurring, set recurring to the default.
-  if (0 < count($acheft)) {
-    if (isset($form->_elementIndex['is_recur'])) {
-      // Make recurring contrib default to true.
-      $form->setDefaults(array('is_recur' => 1));
-    }
-  }
-  if (0 < count($swipe)) {
-    CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/swipe.js', 10);
-  }
-  if (0 < count($ukdd)) {
-    CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/dd_uk.js', 10);
-    if (isset($form->_elementIndex['is_recur'])) {
-      // Make recurring contrib default to true.
-      $form->setDefaults(array('is_recur' => 1));
-    }
-  }
-
-  // If enabled on a page with monthly recurring contributions enabled, provide a way to set future contribution dates. 
-  // Uses javascript to hide/reset unless they have recurring contributions checked.
-  if (isset($form->_elementIndex['is_recur'])) {
-    $settings = CRM_Core_BAO_Setting::getItem('iATS Payments Extension', 'iats_settings');
-    if (!empty($settings['enable_public_future_recurring_start'])) {
-      $allow_days = empty($settings['days']) ? array('-1') : $settings['days'];
-      $start_dates = _iats_get_future_monthly_start_dates(time(), $allow_days);
-      $form->addElement('select', 'receive_date', ts('Date of first contribution'), $start_dates);
-      CRM_Core_Region::instance('billing-block')->add(array(
-        'template' => 'CRM/iATS/BillingBlockRecurringExtra.tpl',
-      ));
-      CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/recur_start.js', 10);
-    }
-  }
-  
-  /* Mangle the ajax bit of the form (if any) by processor type */
-  if (!empty($form->_paymentProcessor['id'])) {
-    $id = $form->_paymentProcessor['id'];
-    /* Note that Ach/Eft is currency dependent */
-    if (!empty($acheft[$id])) {
-      iats_acheft_form_customize($form);
-      // watchdog('iats_acheft',kprint_r($form,TRUE));.
-    }
-    elseif (!empty($swipe[$id])) {
-      iats_swipe_form_customize($form);
-    }
-    /* UK Direct debit option */
-    elseif (!empty($ukdd[$id])) {
-      iats_ukdd_form_customize($form);
-      // watchdog('iats_acheft',kprint_r($form,TRUE));.
-    }
-  }
-
-}
-
-/**
- * Fix the backend credit card contribution forms
- * Includes CRM_Contribute_Form_Contribution, CRM_Event_Form_Participant, CRM_Member_Form_Membership
- * 1. Remove my ACH/EFT processors
- * Now fixed in core for contribution forms: https://issues.civicrm.org/jira/browse/CRM-14442
- * 2. Force SWIPE (i.e. remove all others) if it's the default, and mangle the form accordingly.
- * For now, this form doesn't refresh when you change payment processors, so I can't use swipe if it's not the default, so i have to remove it.
- */
-function iats_civicrm_buildForm_CreditCard_Backend(&$form) {
-  // Skip if i don't have any processors.
-  if (empty($form->_processors)) {
-    return;
-  }
-  // Get all my swipe processors.
-  $swipe = iats_civicrm_processors($form->_processors, 'SWIPE');
-  // Get all my ACH/EFT processors (should be 0, but I'm fixing old core bugs)
-  $acheft = iats_civicrm_processors($form->_processors, 'ACHEFT');
-  // If an iATS SWIPE payment processor is enabled and default remove all other payment processors.
-  $swipe_id_default = 0;
-  if (0 < count($swipe)) {
-    foreach ($swipe as $id => $pp) {
-      if ($pp['is_default']) {
-        $swipe_id_default = $id;
-        break;
-      }
-    }
-  }
-  // Find the available pp options form element (update this if we ever switch from quickform, uses a quickform internals)
-  // not all invocations of the form include this, so check for non-empty value first.
-  if (!empty($form->_elementIndex['payment_processor_id'])) {
-    $pp_form_id = $form->_elementIndex['payment_processor_id'];
-    // Now cycle through them, either removing everything except the default swipe or just removing the ach/eft.
-    $element = $form->_elements[$pp_form_id]->_options;
-    foreach ($element as $option_id => $option) {
-      // Key is set to payment processor id.
-      $pp_id = $option['attr']['value'];
-      if ($swipe_id_default) {
-        // Remove any that are not my swipe default pp.
-        if ($pp_id != $swipe_id_default) {
-          unset($form->_elements[$pp_form_id]->_options[$option_id]);
-          unset($form->_processors[$pp_id]);
-          if (!empty($form->_recurPaymentProcessors[$pp_id])) {
-            unset($form->_recurPaymentProcessors[$pp_id]);
-          }
-        }
-      }
-      elseif (!empty($acheft[$pp_id]) || !empty($swipe[$pp_id])) {
-        // Remove my ach/eft and swipe, which both require form changes.
-        unset($form->_elements[$pp_form_id]->_options[$option_id]);
-        unset($form->_processors[$pp_id]);
-        if (!empty($form->_recurPaymentProcessors[$pp_id])) {
-          unset($form->_recurPaymentProcessors[$pp_id]);
-        }
-      }
-    }
-  }
-
-  // If i'm using swipe as default and I've got a billing section, then customize it.
-  if ($swipe_id_default) {
-    CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/swipe.js', 10);
-    if (!empty($form->_elementIndex['credit_card_exp_date'])) {
-      iats_swipe_form_customize($form);
-    }
-  }
-}
-
 /**
  * Provide helpful links to backend-ish payment pages for ACH/EFT, since the backend credit card pages don't work/apply
  * Could do the same for swipe?
@@ -1011,7 +651,7 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_Search(&$form) {
     return;
   }
   $contactID = $form->_defaultValues['contact_id'];
-  $acheft = iats_civicrm_processors(NULL, 'ACHEFT', array('is_active' => 1, 'is_test' => 0));
+  $acheft = _iats_filter_payment_processors('iATSServiceACHEFT', array(), array('is_active' => 1, 'is_test' => 0));
   $acheft_backoffice_links = array();
   // For each ACH/EFT payment processor, try to provide a different mechanism for 'backoffice' type contributions
   // note: only offer payment pages that provide iATS ACH/EFT exclusively.
@@ -1026,7 +666,7 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_Search(&$form) {
     }
   }
   if (count($acheft_backoffice_links)) {
-    _iats_civicrm_varset(array('backofficeLinks' => $acheft_backoffice_links));
+    CRM_Core_Resources::singleton()->addVars('iatspayments', array('backofficeLinks' => $acheft_backoffice_links));
     CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/contribute_form_search.js');
   }
 }
@@ -1050,44 +690,6 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_CancelSubscription(&$form) {
   }
 }
 
-/**
- * Modify the contribution Confirm screen for iATS UK DD
- * 1. display extra field data injected earlier for payer validation.
- */
-function iats_civicrm_buildForm_Contribution_Confirm_Payment_iATSServiceUKDD(&$form) {
-  $form->addElement('textarea', 'payer_validate_address', ts('Name and full postal address of your Bank or Building Society'), array('rows' => '6', 'columns' => '30'));
-  $form->addElement('text', 'payer_validate_service_user_number', ts('Service User Number'));
-  $form->addElement('text', 'payer_validate_reference', ts('Reference'));
-  // Date on which the validation happens, reference.
-  $form->addElement('text', 'payer_validate_date', ts('Today\'s Date'), array());
-  $form->addElement('text', 'payer_validate_start_date', ts('Date of first collection'));
-  $form->addElement('static', 'payer_validate_instruction', ts('Instruction to your Bank or Building Society'), ts('Please pay %1 Direct Debits from the account detailed in this instruction subject to the safeguards assured by the Direct Debit Guarantee. I understand that this instruction may remain with TestingTest and, if so, details will be passed electronically to my Bank / Building Society.', array('%1' => "<strong>$payee</strong>")));
-  $defaults = array(
-    'payer_validate_date' => date('F j, Y'),
-  );
-  foreach (array('address', 'service_user_number', 'reference', 'date', 'start_date') as $k) {
-    $key = 'payer_validate_' . $k;
-    $defaults[$key] = $form->_params[$key];
-  };
-  $form->setDefaults($defaults);
-  CRM_Core_Region::instance('contribution-confirm-billing-block')->add(array(
-    'template' => 'CRM/iATS/ContributeConfirmExtra_UKDD.tpl',
-  ));
-}
-
-/**
- * Modify the contribution Thank You screen for iATS UK DD.
- */
-function iats_civicrm_buildForm_Contribution_ThankYou_Payment_iATSServiceUKDD(&$form) {
-  foreach (array('address', 'service_user_number', 'reference', 'date', 'start_date') as $k) {
-    $key = 'payer_validate_' . $k;
-    $form->addElement('static', $key, $key, $form->_params[$key]);
-  };
-  CRM_Core_Region::instance('contribution-thankyou-billing-block')->add(array(
-    'template' => 'CRM/iATS/ContributeThankYouExtra_UKDD.tpl',
-  ));
-}
-
 /**
  * Add some functionality to the update subscription form for recurring contributions.
  */
@@ -1099,12 +701,10 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateSubscription(&$form) {
   if (!CRM_Core_Permission::check('edit contributions')) {
     return;
   }
-  // Only mangle this form for recurring contributions using iATS, (and not the UKDD version)
+  // Only mangle this form for recurring contributions using iATS
   $payment_processor_type = empty($form->_paymentProcessor) ? substr(get_class($form->_paymentProcessorObj),9) : $form->_paymentProcessor['class_name'];
-  if (0 !== strpos($payment_processor_type, 'Payment_iATSService')) {
-    return;
-  }
-  if ('Payment_iATSServiceUKDD' == $payment_processor_type) {
+  if (  (0 !== strpos($payment_processor_type, 'Payment_iATSService'))
+     && (0 !== strpos($payment_processor_type, 'Payment_Faps')) ){
     return;
   }
   $settings = civicrm_api3('Setting', 'getvalue', array('name' => 'iats_settings'));
@@ -1148,7 +748,7 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateSubscription(&$form) {
   );
   $dupe_fields = array();
   // To be a good citizen, I check if core or another extension hasn't already added these fields 
-  // and remove them if they have.
+  // and don't add them again if they have.
   foreach (array_keys($edit_fields) as $fid) {
     if ($form->elementExists($fid)) {
       unset($edit_fields[$fid]);
@@ -1159,7 +759,7 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateSubscription(&$form) {
     }
   }
   // Use this in my js to identify which fields need to be removed from the tpl I inject below
-  _iats_civicrm_varset(array('dupeSubscriptionFields' => $dupe_fields));
+  CRM_Core_Resources::singleton()->addVars('iatspayments', array('dupeSubscriptionFields' => $dupe_fields));
   foreach ($edit_fields as $fid => $label) {
     switch($fid) {
       case 'contribution_status_id':
@@ -1189,7 +789,7 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateSubscription(&$form) {
   $form->addElement('static', 'payment_instrument', $label);
   $form->addElement('static', 'failure_count', $recur['failure_count']);
   CRM_Core_Region::instance('page-body')->add(array(
-    'template' => 'CRM/iATS/Subscription.tpl',
+    'template' => 'CRM/Iats/Subscription.tpl',
   ));
   CRM_Core_Resources::singleton()->addScriptFile('com.iatspayments.civicrm', 'js/subscription.js');
 }
@@ -1216,315 +816,3 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateBilling(&$form) {
     $form->addElement('hidden', 'crid', $crid);
   }
 }
-
-/**
- * Implementation of hook_civicrm_alterSettingsFolders.
- *
- * @link http://wiki.civicrm.org/confluence/display/CRMDOC/hook_civicrm_alterSettingsFolders
- */
-function iats_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) {
-  _iats_civix_civicrm_alterSettingsFolders($metaDataFolders);
-}
-
-/**
- * For a recurring contribution, find a reasonable candidate for a template, where possible.
- */
-function _iats_civicrm_getContributionTemplate($contribution) {
-  // Get the first contribution in this series that matches the same total_amount, if present.
-  $template = array();
-  $get = array('contribution_recur_id' => $contribution['contribution_recur_id'], 'options' => array('sort' => ' id', 'limit' => 1));
-  if (!empty($contribution['total_amount'])) {
-    $get['total_amount'] = $contribution['total_amount'];
-  }
-  $result = civicrm_api3('contribution', 'get', $get);
-  if (!empty($result['values'])) {
-    $contribution_ids = array_keys($result['values']);
-    $template = $result['values'][$contribution_ids[0]];
-    $template['original_contribution_id'] = $contribution_ids[0];
-    $template['line_items'] = array();
-    $get = array('entity_table' => 'civicrm_contribution', 'entity_id' => $contribution_ids[0]);
-    $result = civicrm_api3('LineItem', 'get', $get);
-    if (!empty($result['values'])) {
-      foreach ($result['values'] as $initial_line_item) {
-        $line_item = array();
-        foreach (array('price_field_id', 'qty', 'line_total', 'unit_price', 'label', 'price_field_value_id', 'financial_type_id') as $key) {
-          $line_item[$key] = $initial_line_item[$key];
-        }
-        $template['line_items'][] = $line_item;
-      }
-    }
-  }
-  return $template;
-}
-
-/**
- * Function _iats_contributionrecur_next.
- *
- * @param $from_time: a unix time stamp, the function returns values greater than this
- * @param $days: an array of allowable days of the month
- *
- *   A utility function to calculate the next available allowable day, starting from $from_time.
- *   Strategy: increment the from_time by one day until the day of the month matches one of my available days of the month.
- */
-function _iats_contributionrecur_next($from_time, $allow_mdays) {
-  $dp = getdate($from_time);
-  // So I don't get into an infinite loop somehow.
-  $i = 0;
-  while (($i++ < 60) && !in_array($dp['mday'], $allow_mdays)) {
-    $from_time += (24 * 60 * 60);
-    $dp = getdate($from_time);
-  }
-  return $from_time;
-}
-
-/**
- * Function _iats_contribution_payment
- *
- * @param $contribution an array of a contribution to be created (or in case of future start date,
-          possibly an existing pending contribution to recycle, if it already has a contribution id).
- * @param $options must include customer code, subtype and iats_domain, may include a membership id
- * @param $original_contribution_id if included, use as a template for a recurring contribution.
- *
- *   A high-level utility function for making a contribution payment from an existing recurring schedule
- *   Used in the Iatsrecurringcontributions.php job and the one-time ('card on file') form.
- *   
- *   Since 4.7.12, we can are using the new repeattransaction api.
- */
-function _iats_process_contribution_payment(&$contribution, $options, $original_contribution_id) {
-  // By default, don't use repeattransaction
-  $use_repeattransaction = FALSE;
-  $is_recurrence = !empty($original_contribution_id);
-  // First try and get the money with iATS Payments, using my cover function.
-  // TODO: convert this into an api job?
-  $result =  _iats_process_transaction($contribution, $options);
-
-  // Handle any case of a failure of some kind, either the card failed, or the system failed.
-  if (empty($result['status'])) {
-    /* set the failed transaction status, or pending if I had a server issue */
-    $contribution['contribution_status_id'] = empty($result['auth_result']) ? 2 : 4;
-    /* and include the reason in the source field */
-    $contribution['source'] .= ' ' . $result['reasonMessage'];
-    // Save my reject code here for processing by the calling function (a bit lame)
-    $contribution['iats_reject_code'] = $result['auth_result'];
-  }
-  else {
-    // I have a transaction id.
-    $trxn_id = $contribution['trxn_id'] = trim($result['remote_id']) . ':' . time();
-    // Initialize the status to pending
-    $contribution['contribution_status_id'] = 2;
-    // We'll use the repeattransaction api for successful transactions under three conditions:
-    // 1. if we want it
-    // 2. if we don't already have a contribution id
-    // 3. if we trust it
-    $use_repeattransaction = $is_recurrence && empty($contribution['id']) && _iats_civicrm_use_repeattransaction();
-  }
-  if ($use_repeattransaction) {
-    // We processed it successflly and I can try to use repeattransaction. 
-    // Requires the original contribution id.
-    // Issues with this api call:
-    // 1. Always triggers an email and doesn't include trxn.
-    // 2. Date is wrong.
-    try {
-      // $status = $result['contribution_status_id'] == 1 ? 'Completed' : 'Pending';
-      $contributionResult = civicrm_api3('Contribution', 'repeattransaction', array(
-        'original_contribution_id' => $original_contribution_id,
-        'contribution_status_id' => 'Pending',
-        'is_email_receipt' => 0,
-        // 'invoice_id' => $contribution['invoice_id'],
-        ///'receive_date' => $contribution['receive_date'],
-        // 'campaign_id' => $contribution['campaign_id'],
-        // 'financial_type_id' => $contribution['financial_type_id'],.
-        // 'payment_processor_id' => $contribution['payment_processor'],
-        'contribution_recur_id' => $contribution['contribution_recur_id'],
-      ));
-
-      // watchdog('iats_civicrm','repeat transaction result <pre>@params</pre>',array('@params' => print_r($pending,TRUE)));.
-      $contribution['id'] = CRM_Utils_Array::value('id', $contributionResult);
-    }
-    catch (Exception $e) {
-      // Ignore this, though perhaps I should log it.
-    }
-    if (empty($contribution['id'])) {
-      // Assume I failed completely and I'll fall back to doing it the manual way.
-      $use_repeattransaction = FALSE;
-    }
-    else {
-      // If repeattransaction succeded.
-      // First restore/add various fields that the repeattransaction api may overwrite or ignore.
-      // TODO - fix this in core to allow these to be set above.
-      civicrm_api3('contribution', 'create', array('id' => $contribution['id'], 
-        'invoice_id' => $contribution['invoice_id'],
-        'source' => $contribution['source'],
-        'receive_date' => $contribution['receive_date'],
-        'payment_instrument_id' => $contribution['payment_instrument_id'],
-        // '' => $contribution['receive_date'],
-      ));
-      // Save my status in the contribution array that was passed in.
-      $contribution['contribution_status_id'] = $result['contribution_status_id'];
-      if ($result['contribution_status_id'] == 1) {
-        // My transaction completed, so record that fact in CiviCRM, potentially sending an invoice.
-        try {
-          civicrm_api3('Contribution', 'completetransaction', array(
-            'id' => $contribution['id'],
-            'payment_processor_id' => $contribution['payment_processor'],
-            'is_email_receipt' => (empty($options['is_email_receipt']) ? 0 : 1),
-            'trxn_id' => $contribution['trxn_id'],
-            'receive_date' => $contribution['receive_date'],
-          ));
-        }
-        catch (Exception $e) {
-          // log the error and continue
-          CRM_Core_Error::debug_var('Unexpected Exception', $e);
-        }
-      }
-      else {
-        // just save my trxn_id for ACH/EFT verification later
-        try {
-          civicrm_api3('Contribution', 'create', array(
-            'id' => $contribution['id'],
-            'trxn_id' => $contribution['trxn_id'],
-          ));
-        }
-        catch (Exception $e) {
-          // log the error and continue
-          CRM_Core_Error::debug_var('Unexpected Exception', $e);
-        }
-      }
-    }
-  }
-  if (!$use_repeattransaction) {
-    /* If I'm not using repeattransaction for any reason, I'll create the contribution manually */
-    // This code assumes that the contribution_status_id has been set properly above, either pending or failed.
-    $contributionResult = civicrm_api3('contribution', 'create', $contribution);
-    // Pass back the created id indirectly since I'm calling by reference.
-    $contribution['id'] = CRM_Utils_Array::value('id', $contributionResult);
-    // Connect to a membership if requested.
-    if (!empty($options['membership_id'])) {
-      try {
-        civicrm_api3('MembershipPayment', 'create', array('contribution_id' => $contribution['id'], 'membership_id' => $options['membership_id']));
-      }
-      catch (Exception $e) {
-        // Ignore.
-      }
-    }
-    /* And then I'm done unless it completed */
-    if ($result['contribution_status_id'] == 1 && !empty($result['status'])) {
-      /* success, and the transaction has completed */
-      $complete = array('id' => $contribution['id'], 
-        'payment_processor_id' => $contribution['payment_processor'],
-        'trxn_id' => $trxn_id, 
-        'receive_date' => $contribution['receive_date']
-      );
-      $complete['is_email_receipt'] = empty($options['is_email_receipt']) ? 0 : 1;
-      try {
-        $contributionResult = civicrm_api3('contribution', 'completetransaction', $complete);
-      }
-      catch (Exception $e) {
-        // Don't throw an exception here, or else I won't have updated my next contribution date for example.
-        $contribution['source'] .= ' [with unexpected api.completetransaction error: ' . $e->getMessage() . ']';
-      }
-      // Restore my source field that ipn code irritatingly overwrites, and make sure that the trxn_id is set also.
-      civicrm_api3('contribution', 'setvalue', array('id' => $contribution['id'], 'value' => $contribution['source'], 'field' => 'source'));
-      civicrm_api3('contribution', 'setvalue', array('id' => $contribution['id'], 'value' => $trxn_id, 'field' => 'trxn_id'));
-      $message = $is_recurrence ? ts('Successfully processed contribution in recurring series id %1: ', array(1 => $contribution['contribution_recur_id'])) : ts('Successfully processed one-time contribution: ');
-      return $message . $result['auth_result'];
-    }
-  }
-  // Now return the appropriate message. 
-  if (empty($result['status'])) {
-    return ts('Failed to process recurring contribution id %1: ', array(1 => $contribution['contribution_recur_id'])) . $result['reasonMessage'];
-  }
-  elseif ($result['contribution_status_id'] == 1) {
-    return ts('Successfully processed recurring contribution in series id %1: ', array(1 => $contribution['contribution_recur_id'])) . $result['auth_result'];
-  }
-  else {
-    // I'm using ACH/EFT or a processor that doesn't complete.
-    return ts('Successfully processed pending recurring contribution in series id %1: ', array(1 => $contribution['contribution_recur_id'])) . $result['auth_result'];
-  }
-}
-
-/**
- * Function _iats_process_transaction.
- *
- * @param $contribution an array of properties of a contribution to be processed
- * @param $options must include customer code, subtype and iats_domain
- *
- *   A low-level utility function for triggering a transaction on iATS.
- */
-function _iats_process_transaction($contribution, $options) {
-  require_once "CRM/iATS/iATSService.php";
-  switch ($options['subtype']) {
-    case 'ACHEFT':
-      $method = 'acheft_with_customer_code';
-      // Will not complete.
-      $contribution_status_id = 2;
-      break;
-
-    default:
-      $method = 'cc_with_customer_code';
-      $contribution_status_id = 1;
-      break;
-  }
-  $credentials = iATS_Service_Request::credentials($contribution['payment_processor'], $contribution['is_test']);
-  $iats_service_params = array('method' => $method, 'type' => 'process', 'iats_domain' => $credentials['domain']);
-  $iats = new iATS_Service_Request($iats_service_params);
-  // Build the request array.
-  $request = array(
-    'customerCode' => $options['customer_code'],
-    'invoiceNum' => $contribution['invoice_id'],
-    'total' => $contribution['total_amount'],
-  );
-  $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']);
-  // remove the customerIPAddress if it's the internal loopback to prevent
-  // being locked out due to velocity checks
-  if ('127.0.0.1' == $request['customerIPAddress']) {
-     $request['customerIPAddress'] = '';
-  }
-
-  // Make the soap request.
-  $response = $iats->request($credentials, $request);
-  // Process the soap response into a readable result.
-  $result = $iats->result($response);
-  // pass back the anticipated status_id based on the method (i.e. 1 for CC, 2 for ACH/EFT)
-  $result['contribution_status_id'] = $contribution_status_id;
-  return $result;
-}
-
-/**
- * Function _iats_get_future_start_dates
- *
- * @param $start_date a timestamp, only return dates after this.
- * @param $allow_days an array of allowable days of the month.
- *
- *   A low-level utility function for triggering a transaction on iATS.
- */
-function _iats_get_future_monthly_start_dates($start_date, $allow_days) {
-  // Future date options.
-  $start_dates = array();
-  // special handling for today - it means immediately or now.
-  $today = date('Ymd').'030000';
-  // If not set, only allow for the first 28 days of the month.
-  if (max($allow_days) <= 0) {
-    $allow_days = range(1,28);
-  }
-  for ($j = 0; $j < count($allow_days); $j++) {
-    // So I don't get into an infinite loop somehow ..
-    $i = 0;
-    $dp = getdate($start_date);
-    while (($i++ < 60) && !in_array($dp['mday'], $allow_days)) {
-      $start_date += (24 * 60 * 60);
-      $dp = getdate($start_date);
-    }
-    $key = date('Ymd', $start_date).'030000';
-    if ($key == $today) { // special handling
-      $display = ts('Now');
-      $key = ''; // date('YmdHis');
-    }
-    else {
-      $display = strftime('%B %e, %Y', $start_date);
-    }
-    $start_dates[$key] = $display;
-    $start_date += (24 * 60 * 60);
-  }
-  return $start_dates;
-}
diff --git a/civicrm/ext/iatspayments/images/screenshot.png b/civicrm/ext/iatspayments/images/screenshot.png
new file mode 100644
index 0000000000000000000000000000000000000000..6765b696fa03249ac2cd605d5f0e4aa000ad6dad
Binary files /dev/null and b/civicrm/ext/iatspayments/images/screenshot.png differ
diff --git a/civicrm/ext/iatspayments/info.xml b/civicrm/ext/iatspayments/info.xml
index 55c5172d5b717a3e96b117d9981b5928ade232bc..bfab31caec33250ad0319b187d3cee25853cb885 100644
--- a/civicrm/ext/iatspayments/info.xml
+++ b/civicrm/ext/iatspayments/info.xml
@@ -2,12 +2,14 @@
 <extension key="com.iatspayments.civicrm" type="module">
   <file>iats</file>
   <name>iATS Payments</name>
-  <description>iATS Payments Web Services Payment Processor</description>
+  <description>Payment processor extension for iATS Payments.</description>
   <urls>
+    <url desc="Main Extension Page">https://github.com/iATSPayments/com.iatspayments.civicrm</url>
+    <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url>
     <url desc="Support">https://github.com/iATSPayments/com.iatspayments.civicrm/issues?state=open</url>
     <url desc="Installation Instructions">https://github.com/iATSPayments/com.iatspayments.civicrm</url>
     <url desc="Documentation">https://github.com/iATSPayments/com.iatspayments.civicrm</url>
-    <url desc="Release Notes">https://github.com/iATSPayments/com.iatspayments.civicrm/blob/master/release-notes/1.6.2.md</url>
+    <url desc="Release Notes">https://github.com/iATSPayments/com.iatspayments.civicrm/blob/master/release-notes/1.7.0.md</url>
     <url desc="Getting Started">https://www.semper-it.com/civicamp-iats-payments-slides</url>
   </urls>
   <license>AGPL-3.0</license>
@@ -15,15 +17,14 @@
     <author>Alan Dixon</author>
     <email>iats@blackflysolutions.ca</email>
   </maintainer>
-  <releaseDate>2018-08-15</releaseDate>
-  <version>1.6.2</version>
+  <releaseDate>2019-11-18</releaseDate>
+  <version>1.7.0</version>
   <develStage>stable</develStage>
   <compatibility>
     <ver>5.0</ver>
   </compatibility>
-  <comments>This release resolves some 5.x compatibility issues, but should continue to work on 4.7.x. Please see the current release notes linked above for details.
-</comments>
+  <comments>This release adds new functionality for the iATS Payments 1stPay processors, refactors a lot of code, and improves error handling.</comments>
   <civix>
-    <namespace>CRM/iATS</namespace>
+    <namespace>CRM/Iats</namespace>
   </civix>
 </extension>
diff --git a/civicrm/ext/iatspayments/js/crypto.js b/civicrm/ext/iatspayments/js/crypto.js
new file mode 100644
index 0000000000000000000000000000000000000000..03926b89200e59ef120241390e8fe4c912b8cfad
--- /dev/null
+++ b/civicrm/ext/iatspayments/js/crypto.js
@@ -0,0 +1,92 @@
+/* 
+ * custom js so we can use the FAPS cryptojs script
+ *
+ */
+
+/*jslint indent: 2 */
+/*global CRM, ts */
+
+cj(function ($) {
+  'use strict';
+  if (0 < $('#iats-faps-ach-extra').length) {
+    /* move my helpful cheque image + instructions to the top of the section */
+    $('.direct_debit_info-section').prepend($('#iats-faps-ach-extra'));
+  }
+
+  var isRecur = $('#is_recur, #auto_renew').prop('checked');
+  generateFirstpayIframe(isRecur);
+  $('#is_recur, #auto_renew').click(function() {
+    // remove any existing iframe and message handlers first
+    $('#firstpay-iframe').remove();
+    if (window.addEventListener) {
+      window.removeEventListener("message",fapsIframeMessage);
+    }
+    else {
+      window.detachEvent("onmessage", fapsIframeMessage);
+    }
+    isRecur = this.checked;
+    generateFirstpayIframe(isRecur);
+  });
+
+  function generateFirstpayIframe(isRecur) {
+    // we get iatsSettings from global scope
+    // we have four potential "transaction types" that generate different
+    // iframes
+    if (iatsSettings.paymentInstrumentId == '1') {
+      var transactionType = isRecur ? 'Auth' : 'Sale';
+    }
+    else {
+      var transactionType = isRecur ? 'Vault' : 'AchDebit';
+    }
+    // console.log(transactionType);
+    // generate an iframe below the cryptgram field
+    $('<iframe>', {
+      'src': iatsSettings.iframe_src,
+      'id':  'firstpay-iframe',
+      'data-transcenter-id': iatsSettings.transcenterId,
+      'data-processor-id': iatsSettings.processorId,
+      'data-transaction-type': transactionType,
+      'data-manual-submit': 'false',
+      'frameborder': 0,
+      'style': 'width: 100%',
+      'scrolling': 'no'
+    }).insertAfter('#payment_information .billing_mode-group .cryptogram-section');
+    // load the cryptojs script and install event listeners - this needs to happen
+    // after the iframe is generated.
+    $.getScript(iatsSettings.cryptojs,  function(data, textStatus, jqxhr) {
+      // handle "firstpay" messages (from iframes), supporting multiple javascript versions
+      if (window.addEventListener) {
+        window.addEventListener("message",fapsIframeMessage, false);
+      }
+      else {
+        window.attachEvent("onmessage", fapsIframeMessage);
+      }
+    });
+  }
+
+});
+
+
+var fapsIframeMessage = function (event) {
+  if (event.data.firstpay) {
+    // console.log(event.data);
+    switch(event.data.type) {
+      case 'newCryptogram':
+        // assign the cryptogram value into my special field
+        var newCryptogram = event.data.cryptogram;
+        // console.log(newCryptogram);
+        cj('#cryptogram').val(newCryptogram);
+        break;
+      case 'generatingCryptogram':
+        // prevent submission before it's done ?
+        break;
+      case 'generatingCryptogramFinished':
+        // can be ignored?
+        break;
+      case 'cryptogramFailed':
+        // alert user
+        cj('#cryptogram').crmError(ts(event.data.message));
+        break;
+    }
+  }
+}
diff --git a/civicrm/ext/iatspayments/js/dd_acheft.js b/civicrm/ext/iatspayments/js/dd_acheft.js
index 43718b16caae027ac6d9c33233c54b7825fc2678..81980e6755266de4061262eec40da55d43d8f1ec 100644
--- a/civicrm/ext/iatspayments/js/dd_acheft.js
+++ b/civicrm/ext/iatspayments/js/dd_acheft.js
@@ -1,12 +1,10 @@
 /*jslint indent: 2 */
 /*global CRM, ts */
 
-function iatsACHEFTRefresh() {
-  cj(function ($) {
-    'use strict';
-    if (0 < $('#iats-direct-debit-extra').length) {
-      /* move my custom fields up where they belong */
-      $('.direct_debit_info-section').prepend($('#iats-direct-debit-extra'));
-    }
-  });
-}
+CRM.$(function ($) {
+  'use strict';
+  if (0 < $('#iats-direct-debit-extra').length) {
+    /* move my custom fields up where they belong */
+    $('.direct_debit_info-section').prepend($('#iats-direct-debit-extra'));
+  }
+});
diff --git a/civicrm/ext/iatspayments/js/dd_cad.js b/civicrm/ext/iatspayments/js/dd_cad.js
index c5a8f3a4ef08fd5c2e6184f723d0e4bdb891cae0..f7260c0aa2b2f30d01b847e4d932359a96c3be35 100644
--- a/civicrm/ext/iatspayments/js/dd_cad.js
+++ b/civicrm/ext/iatspayments/js/dd_cad.js
@@ -1,58 +1,56 @@
 /* Custom cleanup/validation of Canadian ACH/EFT */
 /*jslint indent: 2 */
 /*global CRM, ts */
-function iatsACHEFTca() {
-  cj(function ($) {
-    'use strict';
-    function onlyNumbers(jq) {
-      var myVal = jq.val();
-      jq.val(myVal.replace(/\D/g,''));
-      return jq.val().length;
+CRM.$(function ($) {
+  'use strict';
+  function onlyNumbers(jq) {
+    var myVal = jq.val();
+    jq.val(myVal.replace(/\D/g,''));
+    return jq.val().length;
+  }
+  function iatsSetBankIdenficationNumber() {
+    var bin = $('#cad_bank_number').val() + $('#cad_transit_number').val();
+    // console.log('bin: '+bin);
+    $('#bank_identification_number').val(bin);
+  }
+  /* hide the bank identiication number field */
+  $('.bank_identification_number-section').hide();
+  iatsSetBankIdenficationNumber();
+  $('#bank_account_number').blur(function(eventObj) {
+    onlyNumbers($(this));
+  });
+  $('#cad_bank_number').blur(function(eventObj) {
+    var myCount = onlyNumbers($(this));
+    if (myCount != 3) {
+      $(this).crmError(ts('Your Bank Number requires three digits, use a leading "0" if necessary')); 
     }
-    function iatsSetBankIdenficationNumber() {
-      var bin = $('#cad_bank_number').val() + $('#cad_transit_number').val();
-      // console.log('bin: '+bin);
-      $('#bank_identification_number').val(bin);
+    switch($(this).val()) {
+      case '001': $('#bank_name').val('Bank of Montreal'); break;
+      case '002': $('#bank_name').val('Bank of Nova Scotia'); break;
+      case '003': $('#bank_name').val('Royal Bank of Canada'); break;
+      case '004': $('#bank_name').val('Toronto Dominion Bank'); break;
+      case '006': $('#bank_name').val('National Bank of Canada'); break;
+      case '010': $('#bank_name').val('Canadian Imperial Bank of Commerce'); break;
+      case '016': $('#bank_name').val('HSBC Canada'); break;
+      case '030': $('#bank_name').val('Canadian Western Bank'); break;
+      case '326': $('#bank_name').val('President\'s Choice Financial'); break;
+
+      case '839': // Credit Union Heritage (Nova Scotia)
+      case '879': // Credit Union Central of Manitoba
+      case '889': // Credit Union Central of Saskatchewan
+      case '899': // Credit Union Central Alberta
+      case '809': // Credit Union British Columbia
+        $('#bank_name').val('Credit Union'); break;
+
+      default: $('#bank_name').val(''); break;
+    }
+    iatsSetBankIdenficationNumber();
+  });
+  $('#cad_transit_number').blur(function(eventObj) {
+    var myCount = onlyNumbers($(this));
+    if (myCount != 5) {
+      $(this).crmError(ts('Your Bank Transit Number requires exactly five digits')); 
     }
-    /* hide the bank identiication number field */
-    $('.bank_identification_number-section').hide();
     iatsSetBankIdenficationNumber();
-    $('#bank_account_number').blur(function(eventObj) {
-      onlyNumbers($(this));
-    });
-    $('#cad_bank_number').blur(function(eventObj) {
-      var myCount = onlyNumbers($(this));
-      if (myCount != 3) {
-        $(this).crmError(ts('Your Bank Number requires three digits, use a leading "0" if necessary')); 
-      }
-      switch($(this).val()) {
-        case '001': $('#bank_name').val('Bank of Montreal'); break;
-        case '002': $('#bank_name').val('Bank of Nova Scotia'); break;
-        case '003': $('#bank_name').val('Royal Bank of Canada'); break;
-        case '004': $('#bank_name').val('Toronto Dominion Bank'); break;
-        case '006': $('#bank_name').val('National Bank of Canada'); break;
-        case '010': $('#bank_name').val('Canadian Imperial Bank of Commerce'); break;
-        case '016': $('#bank_name').val('HSBC Canada'); break;
-        case '030': $('#bank_name').val('Canadian Western Bank'); break;
-        case '326': $('#bank_name').val('President\'s Choice Financial'); break;
-
-        case '839': // Credit Union Heritage (Nova Scotia)
-        case '879': // Credit Union Central of Manitoba
-        case '889': // Credit Union Central of Saskatchewan
-        case '899': // Credit Union Central Alberta
-        case '809': // Credit Union British Columbia
-          $('#bank_name').val('Credit Union'); break;
-        
-        default: $('#bank_name').val(''); break;
-      }
-      iatsSetBankIdenficationNumber();
-    });
-    $('#cad_transit_number').blur(function(eventObj) {
-      var myCount = onlyNumbers($(this));
-      if (myCount != 5) {
-        $(this).crmError(ts('Your Bank Transit Number requires exactly five digits')); 
-      }
-      iatsSetBankIdenficationNumber();
-    });
   });
-}
+});
diff --git a/civicrm/ext/iatspayments/js/dd_uk.js b/civicrm/ext/iatspayments/js/dd_uk.js
deleted file mode 100644
index 29e54dd4147081c8aabd6b7eb8f7ff6059e8433e..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/js/dd_uk.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/* custom js for uk dd */
-/*jslint indent: 2 */
-/*global CRM, ts */
-
-function iatsUKDDRefresh() {
-  cj(function ($) {
-    'use strict';
-    if (0 < $('#iats-direct-debit-gbp-payer-validate').length) {
-      /* move my custom fields around and make it a multistep form experience via javascript */
-      /* move my custom fields up where they belong */
-      var pgDeclaration = $('#iats-direct-debit-gbp-declaration');
-      var pgNonDeclaration = $('.crm-contribution-main-form-block');
-      var pgPayerValidate = $('#billing-payment-block');
-      // var pgPayerValidateHide = $('#billing-payment-block');
-      /* i don't want to show my default civicrm payment notice */
-      $('#payment_notice').hide();
-      /* move some fields around to better flow like the iATS DD samples */
-      $('input[name|="email"]').parents('.crm-section').prependTo('.billing_name_address-section');
-      $('.direct_debit_info-section').before($('#iats-direct-debit-extra'));
-      $('.is_recur-section').after($('#iats-direct-debit-start-date'));
-      $('.crm-contribution-main-form-block').before($('#iats-direct-debit-gbp-declaration'));
-      $('#payer_validate_amend').hide();
-      /* page 1: Declaration */
-      if ($('#payer_validate_declaration').is(':checked')) {
-        pgDeclaration.hide();
-      }
-      else {
-        pgNonDeclaration.hide();
-      }
-      $('#payer_validate_declaration').change(function() {
-        if (this.checked) {
-          pgDeclaration.hide('slow');
-          pgNonDeclaration.show('slow');
-        }
-        else {
-          pgDeclaration.hide('slow');
-          pgNonDeclaration.show('slow');
-        }
-      });
-      /* page 2: Normal CiviCRM form with extra fields */
-      // hide fields that are used for error display before validation
-      $('#iats-direct-debit-gbp-continue .crm-error').hide();
-      // hide the fields that will get validation field lookup values
-      $('#iats-direct-debit-gbp-payer-validate').hide();
-      $('.bank_name-section').hide(); // I don't ask this!
-      /* initiate a payer validation: check for required fields, then do a background POST to retrieve bank info */
-      $('#crm-submit-buttons .crm-button input').click(function( defaultSubmitEvent ) {
-        var inputButton = $(this);
-        inputButton.val('Processing ...').prop('disabled',true);
-        // reset the list of errors
-        $('#payer-validate-required').html('');
-        // the billing address group is all required (except middle name) but doesn't have the class marked
-        $('#Main .billing_name_address-group input:visible, #Main input.required:visible').each(function() {
-          // console.log(this.value.length);
-          if (0 == this.value.length && this.id != 'billing_middle_name') {
-            if ('installments' == this.id) { // todo: check out other exceptions
-              var myLabel = 'Installments';
-            }
-            else {
-              var myLabel = cj.trim($(this).parent('.content').prev('.label').find('label').text().replace('*',''));
-            }
-            if (myLabel.length > 0) { // skip any weird hidden fields (e.g. select2-offscreen class)
-              $('#payer-validate-required').append('<li>' + myLabel + ' is a required field.</li>');
-            }
-          }
-        })
-        // if all pre-validation requirements are met, I can do the sycronous POST to try to get my banking information
-        if (0 == $('#payer-validate-required').html().length) {
-          var validatePayer = {};
-          var startDateStr = $('#payer_validate_start_date').val();
-          var startDate = new Date(startDateStr);
-          validatePayer.beginDate = cj.datepicker.formatDate('yy-mm-dd',startDate);
-          var endDate = startDate;
-          var frequencyInterval = $('input[name=frequency_interval]').val();
-          var frequencyUnit = $('[name="frequency_unit"]').val();
-          var installments = $('input[name="installments"]').val();
-          switch(frequencyUnit) {
-            case 'year':
-              var myYear = endDate.getFullYear() + (frequencyInterval * installments);
-              endDate.setFullYear(myYear);
-              break;
-            case 'month':
-              var myMonth = endDate.getMonth() + (frequencyInterval * installments);
-              endDate.setMonth(myMonth);
-              break;
-            case 'week':
-              var myDay = endDate.getDate() + (frequencyInterval * installments * 7);
-              endDate.setDate(myDay);
-              break;
-            case 'day':
-              var myDay = endDate.getDate() + (frequencyInterval * installments * 1);
-              endDate.setDate(myDay);
-              break;
-          }
-          validatePayer.endDate = endDate.toISOString();
-          validatePayer.firstName = $('#billing_first_name').val();
-          validatePayer.lastName = $('#billing_last_name').val();
-          validatePayer.address = $('input[name|="billing_street_address"]').val();
-          validatePayer.city = $('input[name|="billing_city"]').val();
-          validatePayer.zipCode = $('input[name|="billing_postal_code"]').val();
-          validatePayer.country = $('input[name|="billing_country_id"]').find('selected').text();
-          validatePayer.accountCustomerName = $('#account_holder').val();
-          validatePayer.accountNum = $('#bank_identification_number').val() + $('#bank_account_number').val();
-          validatePayer.email = $('input[name|="email"]').val();
-          validatePayer.ACHEFTReferenceNum = '';
-          validatePayer.companyName = '';
-          validatePayer.type = 'customer';
-          validatePayer.method = 'direct_debit_acheft_payer_validate';
-          validatePayer.payment_processor_id = $('input[name="payment_processor"]').val();
-          var payerValidateUrl = $('input[name="payer_validate_url"]').val();
-          // console.log(payerValidateUrl);
-          // console.log(validatePayer);
-          /* this ajax call has to be sychronous! */
-          cj.ajax({
-            type: 'POST',
-            url: payerValidateUrl,
-            data: validatePayer,
-            dataType: 'json',
-            async: false,
-            success: function(result) {
-              // console.log(result);
-              if (result == null) {
-                $('#payer-validate-required').append('<li>Unexpected Error</li>');
-              }
-              else if ('string' == typeof(result.ACHREFNUM)) {
-                $('#bank_name').val(result.BANK_NAME);
-                $('#payer_validate_address').val(result.BANK_BRANCH + "\n" + result.BANKADDRESS1 + "\n" + result.BANK_CITY + ", " + result.BANK_STATE + "\n" + result.BANK_POSTCODE);
-                $('#payer_validate_reference').val(result.ACHREFNUM);
-              }
-              else {
-                $('#payer-validate-required').append('<li>' + result.reasonMessage + '</li>');
-              }
-            },
-          })
-          .fail(function() {
-            $('#payer-validate-required').append('<li>Unexpected Server Error.</li>');
-          });
-          // console.log('done');
-        }
-        if (0 < $('#payer-validate-required').html().length) {
-          $('#iats-direct-debit-gbp-continue .crm-error').show('slow');
-          inputButton.val('Retry').prop('disabled',false);
-          return false;
-        };
-        inputButton.val('Contribute').prop('disabled',false);
-        return true;
-      }); // end of click handler
-    }
-  });
-}
diff --git a/civicrm/ext/iatspayments/js/recur_start.js b/civicrm/ext/iatspayments/js/recur_start.js
index 96e20daeebaeee3c4945d24f51cc4ed929873f98..1260b7ac2c807ba842118294678cd33c16d05deb 100644
--- a/civicrm/ext/iatspayments/js/recur_start.js
+++ b/civicrm/ext/iatspayments/js/recur_start.js
@@ -1,27 +1,32 @@
-/* custom js for public selection of future recurring start dates */
-/* only show option when recurring is selected */
+/* custom js for public selection of future recurring start dates 
+ * only show option when recurring is selected 
+ * start by removing any previously injected similar field
+ */
 /*jslint indent: 2 */
 /*global CRM, ts */
-
-function iatsRecurStartRefresh() {
-  cj(function ($) {
-    'use strict';
-     $('.is_recur-section').after($('#iats-recurring-start-date'));
+cj(function ($) {
+  'use strict';
+   if ($('.is_recur-section').length) {
+     $('.is_recur-section #iats-recurring-start-date').remove();
+     $('.is_recur-section').append($('#iats-recurring-start-date'));
      cj('input[id="is_recur"]').on('change', function() {
        toggleRecur();
      });
      toggleRecur();
+   }
+   else { // I'm not on the right kind of page, just remove the extra field
+     $('#iats-recurring-start-date').remove();
+   }
 
-     function toggleRecur( ) {
-       var isRecur = cj('input[id="is_recur"]:checked');
-       if (isRecur.val() > 0) {
-         cj('#iats-recurring-start-date').show().val('');
-       }
-       else {
-         cj('#iats-recurring-start-date').hide();
-         $("#iats-recurring-start-date option:selected").prop("selected", false);
-         $("#iats-recurring-start-date option:first").prop("selected", "selected");
-       }
+   function toggleRecur( ) {
+     var isRecur = $('input[id="is_recur"]:checked');
+     if (isRecur.val() > 0) {
+       $('#iats-recurring-start-date').show().val('');
+     }
+     else {
+       $('#iats-recurring-start-date').hide();
+       $("#iats-recurring-start-date option:selected").prop("selected", false);
+       $("#iats-recurring-start-date option:first").prop("selected", "selected");
      }
-  });
-}
+   }
+});
diff --git a/civicrm/ext/iatspayments/js/swipe.js b/civicrm/ext/iatspayments/js/swipe.js
index f7fa40d2c9c66d173504cd517ad5e9ad618921af..32594224bebfd86f77feeab8ed01e44ea4b91081 100644
--- a/civicrm/ext/iatspayments/js/swipe.js
+++ b/civicrm/ext/iatspayments/js/swipe.js
@@ -1,45 +1,43 @@
 /*jslint indent: 2 */
 /*global CRM, ts */
 
-function iatsSWIPERefresh() {
-  cj(function ($) {
-    'use strict';
+CRM.$(function ($) {
+  'use strict';
 
-    function iatsSetCreditCardNumber() {
-      var bin = $('#encrypted_credit_card_number').val();
-      // console.log('bin: '+bin);
-      if (bin.charAt(0) == '0') {
-        /* if 0 -> IDTech -> prefix = 00|@| */
-        var withprefix = "00|@|"+bin;
-        $('#credit_card_number').val(withprefix);
-        // console.log('withprefix: '+withprefix);
-      }
-      if (bin.charAt(0) == '%') {
-        /* if % -> MagTek -> prefix = 02|@| */
-        var withprefix = "02|@|"+bin;
-        $('#credit_card_number').val(withprefix);
-        // console.log('withprefix: '+withprefix);
-      }
+  function iatsSetCreditCardNumber() {
+    var bin = $('#encrypted_credit_card_number').val();
+    // console.log('bin: '+bin);
+    if (bin.charAt(0) == '0') {
+      /* if 0 -> IDTech -> prefix = 00|@| */
+      var withprefix = "00|@|"+bin;
+      $('#credit_card_number').val(withprefix);
+      // console.log('withprefix: '+withprefix);
     }
-    
-    function clearField() {
-      var field = $('#encrypted_credit_card_number').val();
-      /* console.log('field: '+field); */
-      if (field == ts('Click here - then swipe.')) {
-        $('#encrypted_credit_card_number').val('');
-      }
+    if (bin.charAt(0) == '%') {
+      /* if % -> MagTek -> prefix = 02|@| */
+      var withprefix = "02|@|"+bin;
+      $('#credit_card_number').val(withprefix);
+      // console.log('withprefix: '+withprefix);
     }
- 
-    if (0 < $('#iats-swipe').length) {
-      /* move my custom fields up where they belong */
-      $('#payment_information .credit_card_info-group').prepend(cj('#iats-swipe'));
-      /* hide the number credit card number field  */
-      $('.credit_card_number-section').hide();
-      /* hide some ghost fields from a bad template on the front end form */
-      $('.-section').hide();  
-      iatsSetCreditCardNumber();
-      var defaultValue = ts('Click here - then swipe.');
-      $('#encrypted_credit_card_number').val(defaultValue).focus(clearField).blur(iatsSetCreditCardNumber);
+  }
+  
+  function clearField() {
+    var field = $('#encrypted_credit_card_number').val();
+    /* console.log('field: '+field); */
+    if (field == ts('Click here - then swipe.')) {
+      $('#encrypted_credit_card_number').val('');
     }
-  });
-}
+  }
+ 
+  if (0 < $('#iats-swipe').length) {
+    /* move my custom fields up where they belong */
+    $('#payment_information .credit_card_info-group').prepend($('#iats-swipe'));
+    /* hide the number credit card number field  */
+    $('.credit_card_number-section').hide();
+    /* hide some ghost fields from a bad template on the front end form */
+    $('.-section').hide();  
+    iatsSetCreditCardNumber();
+    var defaultValue = ts('Click here - then swipe.');
+    $('#encrypted_credit_card_number').val(defaultValue).focus(clearField).blur(iatsSetCreditCardNumber);
+  }
+});
diff --git a/civicrm/ext/iatspayments/release-notes/1.7.0.md b/civicrm/ext/iatspayments/release-notes/1.7.0.md
new file mode 100644
index 0000000000000000000000000000000000000000..12995c3b6012a9c74622cf30b74b0b946706c67a
--- /dev/null
+++ b/civicrm/ext/iatspayments/release-notes/1.7.0.md
@@ -0,0 +1,19 @@
+# iATS CiviCRM Extension 1.7.0
+
+Nov 18, 2019
+
+This release is a major update from the 1.6.x versions of the iATS payment extension for CiviCRM.
+It is recommended, but not urgently, for all CiviCRM installs on 5.x and above.
+It is required if you want to make use of the new 1st Pay processor.
+
+The major change is the addition of compatibility with the 1st Pay processor.
+
+It also:
+1. removes old CiviCRM compatibility code
+2. removes code for UK direct debit, no longer supported
+3. modernizes some of the old boilerplate/civix code
+4. modifies some class names to be civix-standard
+5. makes use of the core payment object's buildForm instead of relying on the global buildForm hook
+6. makes use of the new payment token table, replacing the custom iats_customer_code table
+
+Note that this release also includes the code in the unreleased 1.6.3 branch that improved the handling of server communication failures.
diff --git a/civicrm/ext/iatspayments/sql/install.sql b/civicrm/ext/iatspayments/sql/install.sql
index 8f1d57b38696cf16e1f598e621475165fbad6e9a..51cbb41247dfbd49c9747368341846fa052ece1d 100644
--- a/civicrm/ext/iatspayments/sql/install.sql
+++ b/civicrm/ext/iatspayments/sql/install.sql
@@ -1,20 +1,5 @@
 -- install sql for iATS Services extension, create a table to hold custom codes
 
-CREATE TABLE `civicrm_iats_customer_codes` (
-  `id` int unsigned NOT NULL AUTO_INCREMENT  COMMENT 'Custom code Id',
-  `customer_code` varchar(255) NOT NULL COMMENT 'Customer code returned from iATS',
-  `ip` varchar(255) DEFAULT NULL COMMENT 'Last IP from which this customer code was accessed or created',
-  `expiry` varchar(4) DEFAULT NULL COMMENT 'CC expiry yymm',
-  `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
-  `email` varchar(255) DEFAULT NULL COMMENT 'Customer-constituent Email address',
-  `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
-  PRIMARY KEY ( `id` ),
-  UNIQUE INDEX (`customer_code`),
-  KEY (`cid`),
-  KEY (`email`),
-  KEY (`recur_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to store customer codes';
-
 CREATE TABLE `civicrm_iats_request_log` (
   `id` int unsigned NOT NULL AUTO_INCREMENT  COMMENT 'Request Log Id',
   `invoice_num` varchar(255) NOT NULL COMMENT 'Invoice number being sent to iATS',
@@ -61,22 +46,6 @@ CREATE TABLE `civicrm_iats_verify` (
   KEY (`auth_result`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to store verification information';
 
-CREATE TABLE `civicrm_iats_ukdd_validate` (
-  `id` int unsigned NOT NULL AUTO_INCREMENT  COMMENT 'UK DirectDebit validation Id',
-  `customer_code` varchar(255) NOT NULL COMMENT 'Customer code returned from iATS',
-  `acheft_reference_num` varchar(255) NOT NULL COMMENT 'Reference number returned from iATS',
-  `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
-  `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
-  `validated` int(10) unsigned DEFAULT '0' COMMENT 'Status id of 0 or 1 (after validation)',
-  `validated_datetime` datetime COMMENT 'Date time of validation',
-  `xml` longtext COMMENT 'XML response to initial validation request',
-  PRIMARY KEY ( `id` ),
-  KEY (`customer_code`),
-  KEY (`acheft_reference_num`),
-  KEY (`cid`),
-  KEY (`recur_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to store UK Direct Debit validation information';
-
 CREATE TABLE `civicrm_iats_journal` (
   `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'CiviCRM Journal Id',
   `iats_id` int unsigned DEFAULT NULL COMMENT 'iATS Journal Id',
@@ -107,5 +76,36 @@ CREATE TABLE `civicrm_iats_journal` (
   KEY (`financial_trxn_id`),
   KEY (`contribution_id`),
   KEY (`verify_datetime`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to iATS journal transactions imported via the iATSPayments ReportLink.'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table to iATS journal transactions imported via the iATSPayments ReportLink.';
 
+CREATE TABLE `civicrm_iats_faps_journal` (
+  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'CiviCRM Journal Id',
+  `transactionId` int unsigned DEFAULT NULL COMMENT 'FAPS Transaction Id',
+  `authCode` varchar(255) NOT NULL COMMENT 'Authentication code',
+  `isAch` boolean DEFAULT '0' COMMENT 'Transaction type: is ACH',
+  `cardType` varchar(255) NOT NULL COMMENT 'Card Type',
+  `processorId` varchar(255) NOT NULL COMMENT 'Unique merchant account identifier',
+  `cimRefNumber` varchar(255) NOT NULL COMMENT 'CIM Reference Number',
+  `orderId` varchar(255) COMMENT 'Order Id = Invoice Number',
+  `transDateAndTime` datetime NOT NULL COMMENT 'DateTime',
+  `amount` decimal(20,2) COMMENT 'Amount',
+  `authResponse` varchar(255) COMMENT 'Response',
+  `currency` varchar(3) COMMENT 'Currency',
+  `status_id` int(10) unsigned DEFAULT '0' COMMENT 'Status of the payment',
+  `financial_trxn_id` int(10) unsigned DEFAULT '0' COMMENT 'Foreign key into CiviCRM financial trxn table id',
+  `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
+  `contribution_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contribution table id',
+  `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
+  `verify_datetime` datetime COMMENT 'Date time of verification',
+  PRIMARY KEY ( `id` ),
+  UNIQUE KEY (`transactionId`,`processorId`,`authResponse`),
+  KEY (`authCode`),
+  KEY (`isAch`),
+  KEY (`cardType`),
+  KEY (`authResponse`),
+  KEY (`orderId`),
+  KEY (`transDateAndTime`),
+  KEY (`financial_trxn_id`),
+  KEY (`contribution_id`),
+  KEY (`verify_datetime`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table of iATS/FAPS transactions imported via the query api.'
diff --git a/civicrm/ext/iatspayments/sql/uninstall.sql b/civicrm/ext/iatspayments/sql/uninstall.sql
index a9df21d40150885d9411103aad6a29039617c29a..b3828917b12c8ab7afd213d5fda01784c0dba908 100644
--- a/civicrm/ext/iatspayments/sql/uninstall.sql
+++ b/civicrm/ext/iatspayments/sql/uninstall.sql
@@ -1,7 +1,7 @@
-RENAME TABLE `civicrm_iats_customer_codes` TO `backup_iats_customer_codes`;
 DROP TABLE IF EXISTS `civicrm_iats_customer_codes`;
 DROP TABLE IF EXISTS `civicrm_iats_request_log`;
 DROP TABLE IF EXISTS `civicrm_iats_response_log`;
 DROP TABLE IF EXISTS `civicrm_iats_verify`;
 DROP TABLE IF EXISTS `civicrm_iats_ukdd_validate`;
 DROP TABLE IF EXISTS `civicrm_iats_journal`;
+DROP TABLE IF EXISTS `civicrm_iats_faps_journal`;
diff --git a/civicrm/ext/iatspayments/sql/upgrade_1_7_001.sql b/civicrm/ext/iatspayments/sql/upgrade_1_7_001.sql
new file mode 100644
index 0000000000000000000000000000000000000000..78035807838eb4dfd64a60d7de6990194bd2a6b3
--- /dev/null
+++ b/civicrm/ext/iatspayments/sql/upgrade_1_7_001.sql
@@ -0,0 +1,35 @@
+/* placeholder for 1.7 upgrade */
+/* TODO: 
+ * 1. remove any remaining UKDD entries 
+ * */
+CREATE TABLE IF NOT EXISTS `civicrm_iats_faps_journal` (
+  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'CiviCRM Journal Id',
+  `transactionId` int unsigned DEFAULT NULL COMMENT 'FAPS Transaction Id',
+  `authCode` varchar(255) NOT NULL COMMENT 'Authentication code',
+  `isAch` boolean DEFAULT '0' COMMENT 'Transaction type: is ACH',
+  `cardType` varchar(255) NOT NULL COMMENT 'Card Type',
+  `processorId` varchar(255) NOT NULL COMMENT 'Unique merchant account identifier',
+  `cimRefNumber` varchar(255) NOT NULL COMMENT 'CIM Reference Number',
+  `orderId` varchar(255) COMMENT 'Order Id = Invoice Number',
+  `transDateAndTime` datetime NOT NULL COMMENT 'DateTime',
+  `amount` decimal(20,2) COMMENT 'Amount',
+  `authResponse` varchar(255) COMMENT 'Response',
+  `currency` varchar(3) COMMENT 'Currency',
+  `status_id` int(10) unsigned DEFAULT '0' COMMENT 'Status of the payment',
+  `financial_trxn_id` int(10) unsigned DEFAULT '0' COMMENT 'Foreign key into CiviCRM financial trxn table id',
+  `cid` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contact id',
+  `contribution_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM contribution table id',
+  `recur_id` int(10) unsigned DEFAULT '0' COMMENT 'CiviCRM recurring_contribution table id',
+  `verify_datetime` datetime COMMENT 'Date time of verification',
+  PRIMARY KEY ( `id` ),
+  UNIQUE KEY (`transactionId`,`processorId`,`authResponse`),
+  KEY (`authCode`),
+  KEY (`isAch`),
+  KEY (`cardType`),
+  KEY (`authResponse`),
+  KEY (`orderId`),
+  KEY (`transDateAndTime`),
+  KEY (`financial_trxn_id`),
+  KEY (`contribution_id`),
+  KEY (`verify_datetime`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='Table of iATS/FAPS transactions imported via the query api.'
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IatsSettings.tpl b/civicrm/ext/iatspayments/templates/CRM/Faps/Form/Settings.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Form/IatsSettings.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Faps/Form/Settings.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDPM.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDPM.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDPM.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDPM.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..362b249c20255d4f506256618acae566e6a3facb
--- /dev/null
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra.tpl
@@ -0,0 +1,20 @@
+{*
+ Extra fields for iats direct debit, template
+*}
+    <div id="iats-direct-debit-extra">
+    <div class="description">You can find your Bank number and branch transit numbers by inspecting a cheque.</div>
+                        <div class="crm-section {$form.bank_account_type.name}-section">
+                            <div class="label">{$form.bank_account_type.label}</div>
+                            <div class="content">{$form.bank_account_type.html}</div>
+                            <div class="clear"></div>
+                        </div>
+    </div>
+
+     <script type="text/javascript">
+     {literal}
+
+cj( function( ) { /* move my account type box up where it belongs */
+  cj('.direct_debit_info-section').prepend(cj('#iats-direct-debit-extra'));
+});
+{/literal}
+</script>
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_CAD.tpl
similarity index 57%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_CAD.tpl
index 5a4a58ab979ffd0a6a6b4a90b1e3aedc04bd15e4..7e7a9ad8150b1319b2cddf34f3b6a103f806b172 100644
--- a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_CAD.tpl
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_CAD.tpl
@@ -1,36 +1,31 @@
 {*
  Extra fields for iats direct debit, template for CAD
 *}
-
-<script type="text/javascript" src="{crmResURL ext=com.iatspayments.civicrm file=js/dd_acheft.js}"></script>
-<script type="text/javascript" src="{crmResURL ext=com.iatspayments.civicrm file=js/dd_cad.js}"></script>
-
     <div id="iats-direct-debit-extra">
       <div class="crm-section cad-instructions-section">
         <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Transit number, Bank number and Account number by inspecting a cheque.{/ts}</em></div>
-        <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/CDN_cheque_500x.jpg}"></div>
+        <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/Iats/CDN_cheque_500x.jpg}"></div>
         <div class="description"><em>{ts domain='com.iatspayments.civicrm'}Please enter them below without any punctuation or spaces.{/ts}</em></div>
         <div class="clear"></div>
       </div>
       <div class="crm-section cad-transit-number-section">
-        <div class="label">{$form.cad_transit_number.label}</div>
+        <div class="label">{ts domain='com.iatspayments.civicrm'}{$form.cad_transit_number.label}{/ts}</div>
         <div class="content">{$form.cad_transit_number.html}</div>
         <div class="clear"></div>
       </div>
       <div class="crm-section cad-bank-number-section">
-        <div class="label">{$form.cad_bank_number.label}</div>
+        <div class="label">{ts domain='com.iatspayments.civicrm'}{$form.cad_bank_number.label}{/ts}</div>
         <div class="content">{$form.cad_bank_number.html}</div>
         <div class="clear"></div>
       </div>
-      <div class="crm-section bank-account-type-section">
-        <div class="label">{$form.bank_account_type.label}</div>
-        <div class="content">{$form.bank_account_type.html}</div>
-        <div class="clear"></div>
-      </div>
     </div>
-{literal}<script type="text/javascript">
-  cj(function ($) {
-    iatsACHEFTRefresh();iatsACHEFTca();
+<script type="text/javascript">
+  var ddAcheftJs = "{crmResURL ext=com.iatspayments.civicrm file=js/dd_acheft.js}";
+  var ddCadJs = "{crmResURL ext=com.iatspayments.civicrm file=js/dd_cad.js}";
+{literal}
+  CRM.$(function ($) {
+    $.getScript(ddAcheftJs);
+    $.getScript(ddCadJs);
   });
-</script>
 {/literal}
+</script>
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_Other.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_Other.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_Other.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_Other.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_USD.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_USD.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..eee253241066aadd81fac2213448ea977906ebf1
--- /dev/null
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_USD.tpl
@@ -0,0 +1,19 @@
+{*
+ Extra fields for iats direct debit, template for USD
+*}
+
+<div id="iats-direct-debit-extra">
+  <div class="crm-section usd-instructions-section">
+    <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Bank Routing Number and Bank Account number by inspecting a check.{/ts}</em></div>
+    <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/Iats/USD_check_500x.jpg}"></div>
+    <div class="clear"></div>
+  </div>
+</div>
+<script type="text/javascript">
+  var ddAcheftJs = "{crmResURL ext=com.iatspayments.civicrm file=js/dd_acheft.js}";
+{literal}
+  CRM.$(function ($) {
+    $.getScript(ddAcheftJs);
+  });
+{/literal}
+</script>
diff --git a/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockFapsACH_CAD.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockFapsACH_CAD.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..017f7b24f6417769125915c2b4d473bc5f33b535
--- /dev/null
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockFapsACH_CAD.tpl
@@ -0,0 +1,11 @@
+{*
+ Extra fields for iats direct debit, template for CAD
+*}
+<div id="iats-faps-ach-extra">
+  <div class="crm-section cad-instructions-section">
+    <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Transit number, Bank number and Account number by inspecting a cheque.{/ts}</em></div>
+    <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/Iats/CDN_cheque_500x.jpg}"></div>
+    <div class="description"><em>{ts domain='com.iatspayments.civicrm'}Please enter them below without any punctuation or spaces.{/ts}</em></div>
+    <div class="clear"></div>
+  </div>
+</div>
diff --git a/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockFapsACH_USD.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockFapsACH_USD.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..393fa4f733f8ee7a119d1659888da4c090bd07ac
--- /dev/null
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockFapsACH_USD.tpl
@@ -0,0 +1,11 @@
+{*
+ Extra fields for iats 1st pay ACH, template for USD
+*}
+
+<div id="iats-faps-ach-extra">
+  <div class="crm-section usd-instructions-section">
+    <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Bank Routing Number and Bank Account number by inspecting a check.{/ts}</em></div>
+    <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/Iats/USD_check_500x.jpg}"></div>
+    <div class="clear"></div>
+  </div>
+</div>
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockRecurringExtra.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockRecurringExtra.tpl
similarity index 78%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockRecurringExtra.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockRecurringExtra.tpl
index 049cbe748957929d8f58da30862aebac0bf554ff..32396790dafbc1f64d24c8dc3715b50efc12a4c7 100644
--- a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockRecurringExtra.tpl
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockRecurringExtra.tpl
@@ -7,4 +7,3 @@
     <div class="clear"></div>
   </div>
 </div>
-{literal}<script type="text/javascript">cj(function ($) { iatsRecurStartRefresh();});</script>{/literal}
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockSwipe.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockSwipe.tpl
similarity index 52%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockSwipe.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockSwipe.tpl
index a57ced3207fba1db23b07149f3b64d2d5f6f9e9e..d12ef13ad47010ba6f3a83ecae64834d9fb4cdb9 100644
--- a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockSwipe.tpl
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockSwipe.tpl
@@ -5,18 +5,15 @@
 <div id="iats-swipe">
       <div class="crm-section cad-instructions-section">
         <div class="label"><em>{ts domain='com.iatspayments.civicrm'}Get ready to SWIPE! Place your cursor in the Encrypted field below and swipe card.{/ts}</em></div>
-        <div class="content"><img width=220 height=220 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/usb_reader.jpg}"></div>
-        <div class="clear"></div>
-      </div>
-      <div class="crm-section encrypted-credit-card-section">
-        <div class="label">{$form.encrypted_credit_card_number.label}</div>
-        <div class="content">{$form.encrypted_credit_card_number.html}</div>
+        <div class="content"><img width=220 height=220 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/Iats/usb_reader.jpg}"></div>
         <div class="clear"></div>
       </div>
 </div>
-{literal}<script type="text/javascript">
-  cj(function ($) {
-    iatsSWIPERefresh();
+<script type="text/javascript">
+  var swipeJs = "{crmResURL ext=com.iatspayments.civicrm file=js/swipe.js}";
+{literal}
+  CRM.$(function ($) {
+    $.getScript(swipeJs);
   });
-</script>
 {/literal}
+</script>
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/CDN_cheque_500x.jpg b/civicrm/ext/iatspayments/templates/CRM/Iats/CDN_cheque_500x.jpg
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/CDN_cheque_500x.jpg
rename to civicrm/ext/iatspayments/templates/CRM/Iats/CDN_cheque_500x.jpg
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/ContributionRecur.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/ContributionRecur.tpl
similarity index 63%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/ContributionRecur.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/ContributionRecur.tpl
index 80629a86b94e1058729fc75f8defc9e5176b3282..eb81edc9b211cdfbeb6629938bb3a26832ba548a 100644
--- a/civicrm/ext/iatspayments/templates/CRM/iATS/ContributionRecur.tpl
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/ContributionRecur.tpl
@@ -1,9 +1,7 @@
 {* more fields on recurring contribution display *}
 <table id="iats-extra">
-<tr><td class="label">Expiry</td>
+<tr><td class="label">Expiry Date</td>
         <td class="content">{$expiry}</td></tr>
-<tr><td class="label">Last 4 digits (of original card)</td>
-        <td class="content">{$cc}</td></tr>
 <tr><td class="label">{ts}Card on File{/ts}</td>
         <td class="content">{$customerLink}</td></tr>
 </table>
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSCustomerLink.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Form/IATSCustomerLink.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSCustomerLink.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/Form/IATSCustomerLink.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSOneTimeCharge.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Form/IATSOneTimeCharge.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Form/IATSOneTimeCharge.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/Form/IATSOneTimeCharge.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/ACHEFTVerify.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Form/Report/ACHEFTVerify.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/ACHEFTVerify.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/Form/Report/ACHEFTVerify.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Journal.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Form/Report/Journal.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Journal.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/Form/Report/Journal.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Recur.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Form/Report/Recur.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Form/Report/Recur.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/Form/Report/Recur.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/Iats/Form/Settings.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Form/Settings.tpl
new file mode 100644
index 0000000000000000000000000000000000000000..8a7f58ee7366ed36c77ac0e60334a478c28a8c48
--- /dev/null
+++ b/civicrm/ext/iatspayments/templates/CRM/Iats/Form/Settings.tpl
@@ -0,0 +1,18 @@
+{* HEADER *}
+
+<div class="crm-submit-buttons">
+{include file="CRM/common/formButtons.tpl" location="top"}
+</div>
+
+{foreach from=$elementNames item=elementName}
+  <div class="crm-section">
+    <div class="label">{$form.$elementName.label}</div>
+    <div class="content">{$form.$elementName.html}</div>
+    <div class="clear"></div>
+  </div>
+{/foreach}
+
+{* FOOTER *}
+<div class="crm-submit-buttons">
+{include file="CRM/common/formButtons.tpl" location="bottom"}
+</div>
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Page/IATSCustomerLink.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Page/IATSCustomerLink.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Page/IATSCustomerLink.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/Page/IATSCustomerLink.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Page/iATSAdmin.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Page/iATSAdmin.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Page/iATSAdmin.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/Page/iATSAdmin.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/Subscription.tpl b/civicrm/ext/iatspayments/templates/CRM/Iats/Subscription.tpl
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/Subscription.tpl
rename to civicrm/ext/iatspayments/templates/CRM/Iats/Subscription.tpl
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/USD_check_500x.jpg b/civicrm/ext/iatspayments/templates/CRM/Iats/USD_check_500x.jpg
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/USD_check_500x.jpg
rename to civicrm/ext/iatspayments/templates/CRM/Iats/USD_check_500x.jpg
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/credit_card_reader.jpg b/civicrm/ext/iatspayments/templates/CRM/Iats/credit_card_reader.jpg
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/credit_card_reader.jpg
rename to civicrm/ext/iatspayments/templates/CRM/Iats/credit_card_reader.jpg
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/direct-debit.jpg b/civicrm/ext/iatspayments/templates/CRM/Iats/direct-debit.jpg
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/direct-debit.jpg
rename to civicrm/ext/iatspayments/templates/CRM/Iats/direct-debit.jpg
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/iats.jpg b/civicrm/ext/iatspayments/templates/CRM/Iats/iats.jpg
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/iats.jpg
rename to civicrm/ext/iatspayments/templates/CRM/Iats/iats.jpg
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/usb_reader.jpg b/civicrm/ext/iatspayments/templates/CRM/Iats/usb_reader.jpg
similarity index 100%
rename from civicrm/ext/iatspayments/templates/CRM/iATS/usb_reader.jpg
rename to civicrm/ext/iatspayments/templates/CRM/Iats/usb_reader.jpg
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl b/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl
deleted file mode 100644
index 8510bcf78c4b119d9f5cccd84acfb05e2a955d05..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl
+++ /dev/null
@@ -1,94 +0,0 @@
-{*
- iATS direct debit UK customization
- Extra fields
- Requires js/dd_uk.js to to all it's proper work
-*}
-<div id="iats-direct-debit-gbp-declaration">
-  <fieldset class="iats-direct-debit-gbp-declaration">
-  <legend>Declaration</legend>
-  <div class="crm-section">
-    <div class="label">{$form.payer_validate_declaration.label}</div>
-    <div class="content">{$form.payer_validate_declaration.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section">
-    <div class="content"><strong>{ts domain='com.iatspayments.civicrm'}Note: {/ts}</strong>{ts domain='com.iatspayments.civicrm'}All Direct Debits are protected by a guarantee. In future, if there is a change to the date, amount of frequency of your Direct Debit, we will always give you 5 working days notice in advance of your account being debited. In the event of any error, you are entitled to an immediate refund from your Bank of Building Society. You have the right to cancel at any time and this guarantee is offered by all the Banks and Building Societies that accept instructions to pay Direct Debits. A copy of the safeguards under the Direct Debit Guarantee will be sent to you with our confirmation letter.{/ts}
-    </div>
-    <div><br/></div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section">
-    <div class="label">{$form.payer_validate_contact.label}</div>
-    <div class="content"><strong>{ts domain='com.iatspayments.civicrm'}Contact Information: {/ts}</strong>{$form.payer_validate_contact.html}</div>
-    <div class="clear"></div>
-    <div class="content">
-  <img width=166 height=61 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/bacs.png}">
-  <img width=148 height=57 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/direct-debit.jpg}">
-  <img width=134 height=55 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/iats.jpg}">
-</div>
-  </div>
-  </fieldset>
-</div>
-
-<div id="iats-direct-debit-extra">
-  <div class="crm-section gbp-instructions-section">
-    <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Account Number and Sort Code by inspecting a cheque.{/ts}</em></div>
-    <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/GBP_cheque_500x.jpg}"></div>
-  </div>
-</div>
-<div>
-  <div id="iats-direct-debit-logos"></div>
-  <img width=166 height=61 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/bacs.png}">
-  <img width=148 height=57 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/direct-debit.jpg}">
-  <img width=134 height=55 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/iats.jpg}">
-</div>
-<div id="iats-direct-debit-start-date">
-  <div class="crm-section payer-validate-start-date">
-    <div class="label">{$form.payer_validate_start_date.label}</div>
-    <div class="content">{$form.payer_validate_start_date.html}</div>
-    <div class="content">{ts domain='com.iatspayments.civicrm'}You may select a later start date if you wish.{/ts}</div>
-    <div class="clear"></div>
-  </div>
-</div>
-<div id="iats-direct-debit-gbp-payer-validate">
-  <div class="crm-section payer-validate-address">
-    <div class="label">{$form.payer_validate_address.label}</div>
-    <div class="content">{$form.payer_validate_address.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section payer-validate-service-user-number">
-    <div class="label">{$form.payer_validate_service_user_number.label}</div>
-    <div class="content">{$form.payer_validate_service_user_number.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section payer-validate-reference">
-    <div class="label">{$form.payer_validate_reference.label}</div>
-    <div class="content">{$form.payer_validate_reference.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section payer-validate-instruction">
-    <div class="label">{$form.payer_validate_instruction.label}</div>
-    <div class="content">{$form.payer_validate_instruction.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section payer-validate-date">
-    <div class="label">{$form.payer_validate_date.label}</div>
-    <div class="content">{$form.payer_validate_date.html}</div>
-    <div class="clear"></div>
-  </div>
-  <input name="payer_validate_url" type="hidden" value="{crmURL p='civicrm/iatsjson' q='reset=1'}">
-</div>
-<div id="iats-direct-debit-gbp-continue">
-  <div class="messages crm-error">
-    <div class="icon red-icon alert-icon"></div>
-    {ts}Please fix the following errors in the form fields above:{/ts}
-    <ul id="payer-validate-required">
-    </ul>
-  </div>
-</div>
-{literal}<script type="text/javascript">
-  cj(function ($) {
-    iatsUKDDRefresh();
-  });
-</script>
-{/literal}
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl b/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl
deleted file mode 100644
index a6f0cd2c7913a78bb9e172617f707531610a25df..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl
+++ /dev/null
@@ -1,24 +0,0 @@
-{*
- Extra fields for iats direct debit, template for USD
-*}
-
-<script type="text/javascript" src="{crmResURL ext=com.iatspayments.civicrm file=js/dd_acheft.js}"></script>
-
-<div id="iats-direct-debit-extra">
-  <div class="crm-section usd-instructions-section">
-    <div class="label"><em>{ts domain='com.iatspayments.civicrm'}You can find your Bank Routing Number and Bank Account number by inspecting a check.{/ts}</em></div>
-    <div class="content"><img width=500 height=303 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/USD_check_500x.jpg}"></div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section bank-account-type-section">
-    <div class="label">{$form.bank_account_type.label}</div>
-    <div class="content">{$form.bank_account_type.html}</div>
-    <div class="clear"></div>
-  </div>
-</div>
-{literal}<script type="text/javascript">
-  cj(function ($) {
-    iatsACHEFTRefresh();
-  });
-</script>
-{/literal}
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeConfirmExtra_UKDD.tpl b/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeConfirmExtra_UKDD.tpl
deleted file mode 100644
index ac0997d214f397fb9808732c10a11dcd6fcde3f3..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeConfirmExtra_UKDD.tpl
+++ /dev/null
@@ -1,41 +0,0 @@
-{*
- iATS direct debit UK customization
- Extra fields in Confirmation Screen
-*}
-<div id="iats-direct-debit-gbp-payer-validate">
-  <div class="crm-section payer-validate-address">
-    <div class="label">{$form.payer_validate_address.label}</div>
-    <div class="content">{$form.payer_validate_address.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section payer-validate-service-user-number">
-    <div class="label">{$form.payer_validate_service_user_number.label}</div>
-    <div class="content">{$form.payer_validate_service_user_number.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section payer-validate-reference">
-    <div class="label">{$form.payer_validate_reference.label}</div>
-    <div class="content">{$form.payer_validate_reference.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section payer-validate-instruction">
-    <div class="label">{$form.payer_validate_instruction.label}</div>
-    <div class="content">{$form.payer_validate_instruction.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section start-date">
-    <div class="label">{$form.payer_validate_start_date.label}</div>
-    <div class="content">{$form.payer_validate_start_date.html}</div>
-    <div class="clear"></div>
-  </div>
-  <div class="crm-section payer-validate-date">
-    <div class="label">{$form.payer_validate_date.label}</div>
-    <div class="content">{$form.payer_validate_date.html}</div>
-    <div class="clear"></div>
-    <div>
-      <img width=166 height=61 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/bacs.png}">
-      <img width=148 height=57 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/direct-debit.jpg}">
-      <img width=134 height=55 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/iats.jpg}">
-    </div>
-  </div>
-</div>
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeThankYouExtra_UKDD.tpl b/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeThankYouExtra_UKDD.tpl
deleted file mode 100644
index 1acedb746da87e5f9eae085c7792a77261b3e76d..0000000000000000000000000000000000000000
--- a/civicrm/ext/iatspayments/templates/CRM/iATS/ContributeThankYouExtra_UKDD.tpl
+++ /dev/null
@@ -1,38 +0,0 @@
-{*
- iATS direct debit UK customization
- Extra fields in Thank You Screen
-*}
-<div>
-  <div class="display-block">
-Bank Address: {$form.payer_validate_address.html}<br />
-Service User Number: {$form.payer_validate_service_user_number.html}<br />
-Reference: {$form.payer_validate_reference.html}<br />
-Start Date: {$form.payer_validate_start_date.html}
-Today's Date: {$form.payer_validate_date.html}
-  </div>
-  <h3>Direct Debit Guarantee</h3>
-  <ul>
-    <li>The Guarantee is offered by all banks and building societies that accept instructions to pay Direct Debits</li>
-    <li>If there are any changes to the amount, date or frequency of your Direct Debit the organisation will notify you (normally 10 working days) in advance of your account being debited or as otherwise agreed. If you request the organisation to collect a payment, confirmation of the amount and date will be given to you at the time of the request</li>
-    <li>If an error is made in the payment of your Direct Debit, by the organisation or your bank or building society, you are entitled to a full and immediate refund of the amount paid from your bank or building society</li>
-    <li>If you receive a refund you are not entitled to, you must pay it back when the organisation asks you to</li>
-    <li>You can cancel a Direct Debit at any time by simply contacting your bank or building society. Written confirmation may be required. Please also notify the organisation</li>
-  </ul>
-  <br/>
-  <div class="messages status continue_instructions-section">
-    <p>Please print this page for your records.</p>
-    <div id="printer-friendly">
-      <a title="Print this page." onclick="window.print(); return false;" href="#">
-        <div class="ui-icon ui-icon-print"></div>
-      </a>
-    </div>
-    <div class="clear"></div>
-  </div>
-  <br/>
-  <div class="clear"></div>
-    <div>
-      <img width=166 height=61 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/bacs.png}">
-      <img width=148 height=57 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/direct-debit.jpg}">
-      <img width=134 height=55 src="{crmResURL ext=com.iatspayments.civicrm file=templates/CRM/iATS/iats.jpg}">
-    </div>
-</div>
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/GBP_cheque_500x.jpg b/civicrm/ext/iatspayments/templates/CRM/iATS/GBP_cheque_500x.jpg
deleted file mode 100644
index 78ee735c3da53795e87e676315caff988b571444..0000000000000000000000000000000000000000
Binary files a/civicrm/ext/iatspayments/templates/CRM/iATS/GBP_cheque_500x.jpg and /dev/null differ
diff --git a/civicrm/ext/iatspayments/templates/CRM/iATS/bacs.png b/civicrm/ext/iatspayments/templates/CRM/iATS/bacs.png
deleted file mode 100644
index d0e1fe25666ca49264767c586a2df4258b3dad0d..0000000000000000000000000000000000000000
Binary files a/civicrm/ext/iatspayments/templates/CRM/iATS/bacs.png and /dev/null differ
diff --git a/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/BaseTestClass.php b/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/BaseTestClass.php
similarity index 99%
rename from civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/BaseTestClass.php
rename to civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/BaseTestClass.php
index e33a4f9f0680b58d3b5580b9f4be837bf359ac26..75a51e98898e02e273b1e22b7af97da0a90477ed 100644
--- a/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/BaseTestClass.php
+++ b/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/BaseTestClass.php
@@ -16,7 +16,7 @@ use Civi\Test\TransactionalInterface;
  *       a. Do all that using setupHeadless() and Civi\Test.
  *       b. Disable TransactionalInterface, and handle all setup/teardown yourself.
  */
-abstract class BaseTestClass extends \PHPUnit_Framework_TestCase implements HeadlessInterface, HookInterface, TransactionalInterface {
+abstract class BaseTestClass extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface, TransactionalInterface {
   //class BaseTestClass extends \PHPUnit_Framework_TestCase implements HeadlessInterface, HookInterface {
 
   /**
diff --git a/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/ContributionIATSTest.php b/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/ContributionIATSTest.php
similarity index 98%
rename from civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/ContributionIATSTest.php
rename to civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/ContributionIATSTest.php
index 81def34a36ed2c338d5425f63bff19a64a8bbdc8..6a45578b01d8415ac92adba2e430416003636478 100644
--- a/civicrm/ext/iatspayments/tests/phpunit/CRM/iATS/ContributionIATSTest.php
+++ b/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/ContributionIATSTest.php
@@ -25,7 +25,7 @@ use Civi\Payment\System;
  *
  * @group headless
  */
-class CRM_iATS_ContributioniATSTest extends BaseTestClass {
+class CRM_Iats_ContributioniATSTest extends BaseTestClass {
 
   public function setUpHeadless() {
     // Civi\Test has many helpers, like install(), uninstall(), sql(), and sqlFile().
@@ -227,7 +227,7 @@ class CRM_iATS_ContributioniATSTest extends BaseTestClass {
     $processorParams = array(
       'domain_id' => 1,
       'name' => 'iATS Credit Card - TE4188',
-      'payment_processor_type_id' => 13,
+      'payment_processor_type_id' => 15,
       'financial_account_id' => 12,
       'is_test' => FALSE,
       'is_active' => 1,
@@ -257,7 +257,7 @@ class CRM_iATS_ContributioniATSTest extends BaseTestClass {
     $processorParams = array(
       'domain_id' => 1,
       'name' => 'iATS Credit Card - TE4188',
-      'payment_processor_type_id' => 15,
+      'payment_processor_type_id' => 17,
       'financial_account_id' => 12,
       'is_test' => FALSE,
       'is_active' => 1,
diff --git a/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php b/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php
index 5bef121472820cf6a8f6a6384e42e6c2a7fd163d..ddb9b30ac0cd861643864218c97c2c7e01ae8901 100644
--- a/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php
+++ b/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php
@@ -4,7 +4,7 @@ ini_set('memory_limit', '2G');
 ini_set('safe_mode', 0);
 eval(cv('php:boot --level=classloader', 'phpcode'));
 
-require_once __DIR__ . '/CRM/iATS/BaseTestClass.php';
+require_once __DIR__ . '/CRM/Iats/BaseTestClass.php';
 
 /**
  * Call the "cv" command.
diff --git a/civicrm/ext/iatspayments/xml/Menu/iats.xml b/civicrm/ext/iatspayments/xml/Menu/iats.xml
index 6c93d4099108ca1ca95d95a993a6885802db1f35..bf2c127d5a3f10fab9623df0c6ae4a98156f4e4d 100644
--- a/civicrm/ext/iatspayments/xml/Menu/iats.xml
+++ b/civicrm/ext/iatspayments/xml/Menu/iats.xml
@@ -2,37 +2,36 @@
 <menu>
   <item>
     <path>civicrm/iATSAdmin</path>
-    <page_callback>CRM_iATS_Page_iATSAdmin</page_callback>
+    <page_callback>CRM_Iats_Page_iATSAdmin</page_callback>
     <title>iATS Payments Administration</title>
     <access_arguments>access CiviContribute</access_arguments>
   </item>
   <item>
     <path>civicrm/iatsjson</path>
-    <page_callback>CRM_iATS_Page_json</page_callback>
+    <page_callback>CRM_Iats_Page_json</page_callback>
     <access_arguments>make online contributions</access_arguments>
   </item>
   <item>
     <path>civicrm/admin/contribute/iatssettings</path>
-    <page_callback>CRM_iATS_Form_IatsSettings</page_callback>
+    <page_callback>CRM_Iats_Form_Settings</page_callback>
     <title>iATS Payments Settings</title>
     <access_arguments>access CiviContribute,administer CiviCRM</access_arguments>
   </item>
   <item>
     <path>civicrm/contact/view/iatscustomerlink</path>
-    <page_callback>CRM_iATS_Page_IATSCustomerLink</page_callback>
+    <page_callback>CRM_Iats_Page_IATSCustomerLink</page_callback>
     <title>CustomerLink Information at iATS</title>
     <access_arguments>access CiviContribute</access_arguments>
   </item>
   <item>
     <path>civicrm/contact/edit/iatscustomerlink</path>
-    <page_callback>CRM_iATS_Form_IATSCustomerLink</page_callback>
+    <page_callback>CRM_Iats_Form_IATSCustomerLink</page_callback>
     <title>Edit CustomerLink Information at iATS</title>
     <access_arguments>access CiviContribute</access_arguments>
   </item>
   <item>
     <path>civicrm/contact/iatsprocesslink</path>
-    <page_callback>CRM_iATS_Form_IATSOneTimeCharge</page_callback>
+    <page_callback>CRM_Iats_Form_IATSOneTimeCharge</page_callback>
     <title>IATSOneTimeCharge</title>
-    <access_arguments>access CiviCRM</access_arguments>
   </item>
 </menu>
diff --git a/civicrm/js/Common.js b/civicrm/js/Common.js
index 25e228b594c3d41cda2825aa5d98fef43099e178..47c6e48b18e095dd3950eaa92d0d0e7e034f8ca0 100644
--- a/civicrm/js/Common.js
+++ b/civicrm/js/Common.js
@@ -1598,6 +1598,25 @@ if (!CRM.vars) CRM.vars = {};
     return (yiq >= 128) ? 'black' : 'white';
   };
 
+  // based on https://github.com/janl/mustache.js/blob/master/mustache.js
+  // If you feel the need to use this function, consider whether assembling HTML
+  // via DOM might be a cleaner approach rather than using string concatenation.
+  CRM.utils.escapeHtml = function(string) {
+    var entityMap = {
+      '&': '&amp;',
+      '<': '&lt;',
+      '>': '&gt;',
+      '"': '&quot;',
+      "'": '&#39;',
+      '/': '&#x2F;',
+      '`': '&#x60;',
+      '=': '&#x3D;'
+    };
+    return String(string).replace(/[&<>"'`=\/]/g, function fromEntityMap (s) {
+      return entityMap[s];
+    });
+  }
+
   // CVE-2015-9251 - Prevent auto-execution of scripts when no explicit dataType was provided
   $.ajaxPrefilter(function(s) {
     if (s.crossDomain) {
diff --git a/civicrm/js/jquery/jquery.dashboard.js b/civicrm/js/jquery/jquery.dashboard.js
index 394635d3618260770817cc7a4f01479f4d24a63f..b87db357d16ade482d2938ce58436919a016d29f 100644
--- a/civicrm/js/jquery/jquery.dashboard.js
+++ b/civicrm/js/jquery/jquery.dashboard.js
@@ -389,7 +389,7 @@
         });
         CRM.alert(
           ts('You can re-add it by clicking the "Configure Your Dashboard" button.'),
-          ts('"%1" Removed', {1: widget.title}),
+          ts('"%1" Removed', {1: CRM.utils.escapeHtml(widget.title)}),
           'success'
         );
       };
@@ -483,7 +483,7 @@
       function widgetHTML() {
         var html = '';
         html += '<div class="widget-wrapper">';
-        html += '  <div class="widget-controls"><h3 class="widget-header">' + widget.title + '</h3></div>';
+        html += '  <div class="widget-controls"><h3 class="widget-header">' + CRM.utils.escapeHtml(widget.title) + '</h3></div>';
         html += '  <div class="widget-content"></div>';
         html += '</div>';
         return html;
diff --git a/civicrm/release-notes.md b/civicrm/release-notes.md
index 811dddb44593b3d1691b55b43fa86d72ade19d02..c3aa9e951ffb56308e56a482e2513b75c178761e 100644
--- a/civicrm/release-notes.md
+++ b/civicrm/release-notes.md
@@ -15,6 +15,13 @@ Other resources for identifying changes are:
     * https://github.com/civicrm/civicrm-joomla
     * https://github.com/civicrm/civicrm-wordpress
 
+## CiviCRM 5.19.2
+
+Released November 20, 2019
+
+- **[Bugs resolved](release-notes/5.19.2.md#bugs)**
+- **[Security advisories](release-notes/5.19.2.md#security)**
+
 ## CiviCRM 5.19.1
 
 Released November 8, 2019
diff --git a/civicrm/release-notes/5.19.2.md b/civicrm/release-notes/5.19.2.md
new file mode 100644
index 0000000000000000000000000000000000000000..24e9f4de926e2a1f9b89f6b85d79c2ba0af0b182
--- /dev/null
+++ b/civicrm/release-notes/5.19.2.md
@@ -0,0 +1,47 @@
+# CiviCRM 5.19.2
+
+Released November 20, 2019
+
+- **[Security advisories](#security)**
+- **[Bugs resolved](#bugs)**
+- **[Credits](#credits)**
+
+## <a name="synopsis"></a>Synopsis
+
+| *Does this version...?*                                         |         |
+|:--------------------------------------------------------------- |:-------:|
+| **Fix security vulnerabilities?**                               | **yes** |
+| Change the database schema?                                     |   no    |
+| Alter the API?                                                  | **yes** |
+| Require attention to configuration options?                     |   no    |
+| Fix problems installing or upgrading to a previous version?     |   no    |
+| Introduce features?                                             |   no    |
+| **Fix bugs?**                                                   | **yes** |
+
+## <a name="security"></a>Security advisories
+
+- **[CIVI-SA-2019-19](https://civicrm.org/advisory/civi-sa-2019-19-sqli-in-dedupefind): SQL injection in "dedupefind"**
+- **[CIVI-SA-2019-20](https://civicrm.org/advisory/civi-sa-2019-20-privilege-escalation-via-leaked-key): Privilege escalation via leaked key**
+- **[CIVI-SA-2019-21](https://civicrm.org/advisory/civi-sa-2019-21-poi-saved-search-and-report-instance-apis): PHP object injection via "Saved Search" and "Report Instance" APIs**
+- **[CIVI-SA-2019-22](https://civicrm.org/advisory/civi-sa-2019-22-xss-in-dashboard-titles): Cross-site scripting in dashboard titles**
+- **[CIVI-SA-2019-23](https://civicrm.org/advisory/civi-sa-2019-23-incorrect-storage-encoding-for-apiv4): Incorrect storage encoding for APIv4**
+- **[CIVIEXT-SA-2019-02](https://civicrm.org/advisory/civiext-sa-2019-02-xss-in-civicase-v5-extension): Cross-site scripting in CiviCase v5 extension**
+
+## <a name="bugs"></a>Bugs resolved
+
+- **_Member Summary Report_ - Fix filtering by "Member Since" ([dev/core#1406](https://lab.civicrm.org/dev/core/issues/1406): [15894](https://github.com/civicrm/civicrm-core/pull/15894))**
+- **_Contribution Search_ - Fix issue with displaying cancellation date ([dev/core#1391](https://lab.civicrm.org/dev/core/issues/1391): [15893](https://github.com/civicrm/civicrm-core/pull/15893))**
+- **_Contribution Search_ - Fix issue where search criteria were applied inconsistently ([dev/core#1374](https://lab.civicrm.org/dev/core/issues/1374): [15896](https://github.com/civicrm/civicrm-core/pull/15896))**
+- **_Additional Payment Form, Payment API_ - Calculate "Net Amount" automatically. Remove error-prone field from UI. ([dev/core#1409](https://lab.civicrm.org/dev/core/issues/1409): [15889](https://github.com/civicrm/civicrm-core/pull/15889))**
+
+## <a name="credits"></a>Credits
+
+This release was developed by the following people, who participated in
+various stages of reporting, analysis, development, review, and testing:
+
+Alan Dixon of Blackfly Solutions; Coleman Watts of CiviCRM; Daniel Compton
+of Armadillo Sec Ltd; Dave D; Eileen McNaughton of Wikimedia Foundation;
+Karin Gerritsen of Semper IT; Kevin Cristiano of Tadpole Collective; Mark
+Burdett of Electronic Frontier Foundation; Morgan Robinson of Palante
+Technology Cooperative; Patrick Figel of Greenpeace CEE; Seamus Lee of
+Australian Greens; Tim Otten of CiviCRM
\ No newline at end of file
diff --git a/civicrm/sql/civicrm_data.mysql b/civicrm/sql/civicrm_data.mysql
index a6d89a8b8017ad11f76a83de6f3d44858f93b9d7..0f5cff49037c6eeb50b5aef8fbeaca91cdbba98e 100644
--- a/civicrm/sql/civicrm_data.mysql
+++ b/civicrm/sql/civicrm_data.mysql
@@ -24051,4 +24051,4 @@ INSERT INTO `civicrm_report_instance`
     ( `domain_id`, `title`, `report_id`, `description`, `permission`, `form_values`)
 VALUES
     (  @domainID, 'Survey Details', 'survey/detail', 'Detailed report for canvassing, phone-banking, walk lists or other surveys.', 'access CiviReport', 'a:39:{s:6:"fields";a:2:{s:9:"sort_name";s:1:"1";s:6:"result";s:1:"1";}s:22:"assignee_contact_id_op";s:2:"eq";s:25:"assignee_contact_id_value";s:0:"";s:12:"sort_name_op";s:3:"has";s:15:"sort_name_value";s:0:"";s:17:"street_number_min";s:0:"";s:17:"street_number_max";s:0:"";s:16:"street_number_op";s:3:"lte";s:19:"street_number_value";s:0:"";s:14:"street_name_op";s:3:"has";s:17:"street_name_value";s:0:"";s:15:"postal_code_min";s:0:"";s:15:"postal_code_max";s:0:"";s:14:"postal_code_op";s:3:"lte";s:17:"postal_code_value";s:0:"";s:7:"city_op";s:3:"has";s:10:"city_value";s:0:"";s:20:"state_province_id_op";s:2:"in";s:23:"state_province_id_value";a:0:{}s:13:"country_id_op";s:2:"in";s:16:"country_id_value";a:0:{}s:12:"survey_id_op";s:2:"in";s:15:"survey_id_value";a:0:{}s:12:"status_id_op";s:2:"eq";s:15:"status_id_value";s:1:"1";s:11:"custom_1_op";s:2:"in";s:14:"custom_1_value";a:0:{}s:11:"custom_2_op";s:2:"in";s:14:"custom_2_value";a:0:{}s:17:"custom_3_relative";s:1:"0";s:13:"custom_3_from";s:0:"";s:11:"custom_3_to";s:0:"";s:11:"description";s:75:"Detailed report for canvassing, phone-banking, walk lists or other surveys.";s:13:"email_subject";s:0:"";s:8:"email_to";s:0:"";s:8:"email_cc";s:0:"";s:10:"permission";s:17:"access CiviReport";s:6:"groups";s:0:"";s:9:"domain_id";i:1;}');
-UPDATE civicrm_domain SET version = '5.19.1';
+UPDATE civicrm_domain SET version = '5.19.2';
diff --git a/civicrm/sql/civicrm_generated.mysql b/civicrm/sql/civicrm_generated.mysql
index 3c45984c71a61aef7e75492917d90650241fe299..fcc2d4ffc2695a19d7d5feea894076f488d4b8df 100644
--- a/civicrm/sql/civicrm_generated.mysql
+++ b/civicrm/sql/civicrm_generated.mysql
@@ -399,7 +399,7 @@ UNLOCK TABLES;
 
 LOCK TABLES `civicrm_domain` WRITE;
 /*!40000 ALTER TABLE `civicrm_domain` DISABLE KEYS */;
-INSERT INTO `civicrm_domain` (`id`, `name`, `description`, `config_backend`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES (1,'Default Domain Name',NULL,NULL,'5.19.1',1,NULL,'a:1:{s:5:\"en_US\";a:0:{}}');
+INSERT INTO `civicrm_domain` (`id`, `name`, `description`, `config_backend`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES (1,'Default Domain Name',NULL,NULL,'5.19.2',1,NULL,'a:1:{s:5:\"en_US\";a:0:{}}');
 /*!40000 ALTER TABLE `civicrm_domain` ENABLE KEYS */;
 UNLOCK TABLES;
 
diff --git a/civicrm/templates/CRM/Contribute/Form/AdditionalPayment.tpl b/civicrm/templates/CRM/Contribute/Form/AdditionalPayment.tpl
index 23443f10fd5f261c039193f2143266610b6e29c9..ac7feb83fe8f3e8b05683a2b098ce8dd7aadbcae 100644
--- a/civicrm/templates/CRM/Contribute/Form/AdditionalPayment.tpl
+++ b/civicrm/templates/CRM/Contribute/Form/AdditionalPayment.tpl
@@ -110,8 +110,6 @@
           </tr>
           <tr class="crm-payment-form-block-fee_amount"><td class="label">{$form.fee_amount.label}</td><td{$valueStyle}>{$form.fee_amount.html|crmMoney:$currency:'XXX':'YYY'}<br />
             <span class="description">{ts}Processing fee for this transaction (if applicable).{/ts}</span></td></tr>
-           <tr class="crm-payment-form-block-net_amount"><td class="label">{$form.net_amount.label}</td><td>{$form.net_amount.html|crmMoney:$currency:'':1}<br />
-            <span class="description">{ts}Net value of the payment (Total Amount minus Fee).{/ts}</span></td></tr>
         </table>
       </div>
       {/if}
diff --git a/civicrm/templates/CRM/Contribute/Form/Selector.tpl b/civicrm/templates/CRM/Contribute/Form/Selector.tpl
index 144d6544db43661bf2e6e125d00ac0afb43da6ac..bcf283e209dd322d19d3f848070ea5a178aeeb8d 100644
--- a/civicrm/templates/CRM/Contribute/Form/Selector.tpl
+++ b/civicrm/templates/CRM/Contribute/Form/Selector.tpl
@@ -51,7 +51,7 @@
 
     {counter start=0 skip=1 print=false}
     {foreach from=$rows item=row}
-      <tr id="rowid{$row.contribution_id}" class="{cycle values="odd-row,even-row"} {if $row.cancel_date} cancelled{/if} crm-contribution_{$row.contribution_id}">
+      <tr id="rowid{$row.contribution_id}" class="{cycle values="odd-row,even-row"} {if $row.contribution_cancel_date} cancelled{/if} crm-contribution_{$row.contribution_id}">
         {if !$single }
           {if $context eq 'Search' }
             {assign var=cbName value=$row.checkbox}
@@ -72,11 +72,11 @@
         {if !$columnName}{* if field_name has not been set skip, this helps with not changing anything not specifically edited *}
         {elseif $columnName === 'total_amount'}{* rendered above as soft credit columns = confusing *}
         {elseif $column.type === 'actions'}{* rendered below as soft credit column handling = not fixed *}
-        {elseif $columnName == 'contribution-status'}
+        {elseif $columnName == 'contribution_status'}
           <td class="crm-contribution-status">
             {$row.contribution_status}<br/>
-            {if $row.cancel_date}
-              {$row.cancel_date|crmDate}
+            {if $row.contribution_cancel_date}
+              {$row.contribution_cancel_date|crmDate}
             {/if}
           </td>
         {else}
diff --git a/civicrm/templates/CRM/Contribute/Form/Task/Print.tpl b/civicrm/templates/CRM/Contribute/Form/Task/Print.tpl
index ca8cf7a2d17a73ffcf8a6986406ca25ebfdf1d34..dcf77779c35ea6fc3cf373c725b8dae5ab342bc1 100644
--- a/civicrm/templates/CRM/Contribute/Form/Task/Print.tpl
+++ b/civicrm/templates/CRM/Contribute/Form/Task/Print.tpl
@@ -53,8 +53,8 @@
         <td class="crm-contribution-thankyou_date">{$row.thankyou_date|truncate:10:''|crmDate}</td>
         <td class="crm-contribution-status crm-contribution-status_{$row.contribution_status_id}">
             {$row.contribution_status_id}<br />
-            {if $row.cancel_date}
-                {$row.cancel_date|truncate:10:''|crmDate}
+            {if $row.contribution_cancel_date}
+                {$row.contribution_cancel_date|truncate:10:''|crmDate}
             {/if}
         </td>
         <td class="crm-contribution-product_name">{$row.product_name}</td>
diff --git a/civicrm/templates/CRM/Contribute/Page/Widget.tpl b/civicrm/templates/CRM/Contribute/Page/Widget.tpl
index 125d07d34029f9be19ce44b4a1e82f2646598969..1074e2e1a71f9f0f59f67742f33f8ee07c09e718 100644
--- a/civicrm/templates/CRM/Contribute/Page/Widget.tpl
+++ b/civicrm/templates/CRM/Contribute/Page/Widget.tpl
@@ -211,4 +211,4 @@ function onReady( ) {
 }
 </script>
 {/literal}
-<script type="text/javascript" src="{$widgetExternUrl}"></script>
+<script type="text/javascript" src="{$config->userFrameworkResourceURL}/extern/widget.php?cpageId={$cpageId}&widgetId={$widget_id}&format=3"></script>
diff --git a/civicrm/vendor/autoload.php b/civicrm/vendor/autoload.php
index 54464533aac1099d5443d15b6dc90e5508a81502..b9e63cb171e96aac397d27f1e043b84a4b4a24cb 100644
--- a/civicrm/vendor/autoload.php
+++ b/civicrm/vendor/autoload.php
@@ -4,4 +4,4 @@
 
 require_once __DIR__ . '/composer/autoload_real.php';
 
-return ComposerAutoloaderInit9e8f3a34b149e5d9ad0b5dd7250ff3d0::getLoader();
+return ComposerAutoloaderInit53562b2e016aa20a7e0e3e29e4c391cd::getLoader();
diff --git a/civicrm/vendor/composer/autoload_files.php b/civicrm/vendor/composer/autoload_files.php
index ee86ff3ce2234af69d9beeb07635f57a98c4521c..b616de87014e0a3d7f94525867fe4f11e17bd69f 100644
--- a/civicrm/vendor/composer/autoload_files.php
+++ b/civicrm/vendor/composer/autoload_files.php
@@ -15,4 +15,5 @@ return array(
     '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
     '9e4824c5afbdc1482b6025ce3d4dfde8' => $vendorDir . '/league/csv/src/functions_include.php',
     'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php',
+    'bad842bce63596a608e2623519fb382c' => $vendorDir . '/xkerman/restricted-unserialize/src/function.php',
 );
diff --git a/civicrm/vendor/composer/autoload_psr4.php b/civicrm/vendor/composer/autoload_psr4.php
index 9c4781cc873399b86ffe96b6a6a807ba2097872e..21b9d8f934d6cf7612e9ecaab603e9dccd764d4d 100644
--- a/civicrm/vendor/composer/autoload_psr4.php
+++ b/civicrm/vendor/composer/autoload_psr4.php
@@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
 $baseDir = dirname($vendorDir);
 
 return array(
+    'xKerman\\Restricted\\' => array($vendorDir . '/xkerman/restricted-unserialize/src'),
     'cweagans\\Composer\\' => array($vendorDir . '/cweagans/composer-patches/src'),
     'Zend\\Validator\\' => array($vendorDir . '/zendframework/zend-validator/src'),
     'Zend\\Stdlib\\' => array($vendorDir . '/zendframework/zend-stdlib/src'),
diff --git a/civicrm/vendor/composer/autoload_real.php b/civicrm/vendor/composer/autoload_real.php
index 51423de2c6ead4792e1b3d74f0856737cc70fe82..9e24f597435ea4f0d719f1fda817d721543dd2d7 100644
--- a/civicrm/vendor/composer/autoload_real.php
+++ b/civicrm/vendor/composer/autoload_real.php
@@ -2,7 +2,7 @@
 
 // autoload_real.php @generated by Composer
 
-class ComposerAutoloaderInit9e8f3a34b149e5d9ad0b5dd7250ff3d0
+class ComposerAutoloaderInit53562b2e016aa20a7e0e3e29e4c391cd
 {
     private static $loader;
 
@@ -19,9 +19,9 @@ class ComposerAutoloaderInit9e8f3a34b149e5d9ad0b5dd7250ff3d0
             return self::$loader;
         }
 
-        spl_autoload_register(array('ComposerAutoloaderInit9e8f3a34b149e5d9ad0b5dd7250ff3d0', 'loadClassLoader'), true, true);
+        spl_autoload_register(array('ComposerAutoloaderInit53562b2e016aa20a7e0e3e29e4c391cd', 'loadClassLoader'), true, true);
         self::$loader = $loader = new \Composer\Autoload\ClassLoader();
-        spl_autoload_unregister(array('ComposerAutoloaderInit9e8f3a34b149e5d9ad0b5dd7250ff3d0', 'loadClassLoader'));
+        spl_autoload_unregister(array('ComposerAutoloaderInit53562b2e016aa20a7e0e3e29e4c391cd', 'loadClassLoader'));
 
         $includePaths = require __DIR__ . '/include_paths.php';
         $includePaths[] = get_include_path();
@@ -31,7 +31,7 @@ class ComposerAutoloaderInit9e8f3a34b149e5d9ad0b5dd7250ff3d0
         if ($useStaticLoader) {
             require_once __DIR__ . '/autoload_static.php';
 
-            call_user_func(\Composer\Autoload\ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0::getInitializer($loader));
+            call_user_func(\Composer\Autoload\ComposerStaticInit53562b2e016aa20a7e0e3e29e4c391cd::getInitializer($loader));
         } else {
             $map = require __DIR__ . '/autoload_namespaces.php';
             foreach ($map as $namespace => $path) {
@@ -52,19 +52,19 @@ class ComposerAutoloaderInit9e8f3a34b149e5d9ad0b5dd7250ff3d0
         $loader->register(true);
 
         if ($useStaticLoader) {
-            $includeFiles = Composer\Autoload\ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0::$files;
+            $includeFiles = Composer\Autoload\ComposerStaticInit53562b2e016aa20a7e0e3e29e4c391cd::$files;
         } else {
             $includeFiles = require __DIR__ . '/autoload_files.php';
         }
         foreach ($includeFiles as $fileIdentifier => $file) {
-            composerRequire9e8f3a34b149e5d9ad0b5dd7250ff3d0($fileIdentifier, $file);
+            composerRequire53562b2e016aa20a7e0e3e29e4c391cd($fileIdentifier, $file);
         }
 
         return $loader;
     }
 }
 
-function composerRequire9e8f3a34b149e5d9ad0b5dd7250ff3d0($fileIdentifier, $file)
+function composerRequire53562b2e016aa20a7e0e3e29e4c391cd($fileIdentifier, $file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
         require $file;
diff --git a/civicrm/vendor/composer/autoload_static.php b/civicrm/vendor/composer/autoload_static.php
index d8eef2c19edcc78a2091827560d683dd11acd26c..29c48c447aa87c13db7450678fb07f70642d44a2 100644
--- a/civicrm/vendor/composer/autoload_static.php
+++ b/civicrm/vendor/composer/autoload_static.php
@@ -4,7 +4,7 @@
 
 namespace Composer\Autoload;
 
-class ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0
+class ComposerStaticInit53562b2e016aa20a7e0e3e29e4c391cd
 {
     public static $files = array (
         '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
@@ -16,9 +16,14 @@ class ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0
         '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
         '9e4824c5afbdc1482b6025ce3d4dfde8' => __DIR__ . '/..' . '/league/csv/src/functions_include.php',
         'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php',
+        'bad842bce63596a608e2623519fb382c' => __DIR__ . '/..' . '/xkerman/restricted-unserialize/src/function.php',
     );
 
     public static $prefixLengthsPsr4 = array (
+        'x' => 
+        array (
+            'xKerman\\Restricted\\' => 19,
+        ),
         'c' => 
         array (
             'cweagans\\Composer\\' => 18,
@@ -83,6 +88,10 @@ class ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0
     );
 
     public static $prefixDirsPsr4 = array (
+        'xKerman\\Restricted\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/xkerman/restricted-unserialize/src',
+        ),
         'cweagans\\Composer\\' => 
         array (
             0 => __DIR__ . '/..' . '/cweagans/composer-patches/src',
@@ -468,11 +477,11 @@ class ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0
     public static function getInitializer(ClassLoader $loader)
     {
         return \Closure::bind(function () use ($loader) {
-            $loader->prefixLengthsPsr4 = ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0::$prefixLengthsPsr4;
-            $loader->prefixDirsPsr4 = ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0::$prefixDirsPsr4;
-            $loader->prefixesPsr0 = ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0::$prefixesPsr0;
-            $loader->fallbackDirsPsr0 = ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0::$fallbackDirsPsr0;
-            $loader->classMap = ComposerStaticInit9e8f3a34b149e5d9ad0b5dd7250ff3d0::$classMap;
+            $loader->prefixLengthsPsr4 = ComposerStaticInit53562b2e016aa20a7e0e3e29e4c391cd::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit53562b2e016aa20a7e0e3e29e4c391cd::$prefixDirsPsr4;
+            $loader->prefixesPsr0 = ComposerStaticInit53562b2e016aa20a7e0e3e29e4c391cd::$prefixesPsr0;
+            $loader->fallbackDirsPsr0 = ComposerStaticInit53562b2e016aa20a7e0e3e29e4c391cd::$fallbackDirsPsr0;
+            $loader->classMap = ComposerStaticInit53562b2e016aa20a7e0e3e29e4c391cd::$classMap;
 
         }, null, ClassLoader::class);
     }
diff --git a/civicrm/vendor/composer/installed.json b/civicrm/vendor/composer/installed.json
index b65a780813dab92dc6a50fae10b5fdb1918185e1..f76f71b86180ed15a2b4b1d1b080ebdedf53c40c 100644
--- a/civicrm/vendor/composer/installed.json
+++ b/civicrm/vendor/composer/installed.json
@@ -103,7 +103,7 @@
             {
                 "name": "Tobias Nyholm",
                 "email": "tobias.nyholm@gmail.com",
-                "homepage": "https://github.com/nyholm"
+                "homepage": "https://github.com/Nyholm"
             },
             {
                 "name": "Nicolas Grekas",
@@ -1877,17 +1877,17 @@
     },
     {
         "name": "symfony/config",
-        "version": "v2.8.44",
-        "version_normalized": "2.8.44.0",
+        "version": "v2.8.50",
+        "version_normalized": "2.8.50.0",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/config.git",
-            "reference": "06c0be4cdd8363f3ec8d592c9a4d1b981d5052af"
+            "reference": "7dd5f5040dc04c118d057fb5886563963eb70011"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/config/zipball/06c0be4cdd8363f3ec8d592c9a4d1b981d5052af",
-            "reference": "06c0be4cdd8363f3ec8d592c9a4d1b981d5052af",
+            "url": "https://api.github.com/repos/symfony/config/zipball/7dd5f5040dc04c118d057fb5886563963eb70011",
+            "reference": "7dd5f5040dc04c118d057fb5886563963eb70011",
             "shasum": ""
         },
         "require": {
@@ -1901,7 +1901,7 @@
         "suggest": {
             "symfony/yaml": "To use the yaml reference dumper"
         },
-        "time": "2018-07-26T11:13:39+00:00",
+        "time": "2018-11-26T09:38:12+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
@@ -1936,17 +1936,17 @@
     },
     {
         "name": "symfony/dependency-injection",
-        "version": "v2.8.44",
-        "version_normalized": "2.8.44.0",
+        "version": "v2.8.50",
+        "version_normalized": "2.8.50.0",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/dependency-injection.git",
-            "reference": "ad2446d39d11c3daaa7f147d957941a187e47357"
+            "reference": "c306198fee8f872a8f5f031e6e4f6f83086992d8"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ad2446d39d11c3daaa7f147d957941a187e47357",
-            "reference": "ad2446d39d11c3daaa7f147d957941a187e47357",
+            "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/c306198fee8f872a8f5f031e6e4f6f83086992d8",
+            "reference": "c306198fee8f872a8f5f031e6e4f6f83086992d8",
             "shasum": ""
         },
         "require": {
@@ -1966,7 +1966,7 @@
             "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
             "symfony/yaml": ""
         },
-        "time": "2018-07-26T11:13:39+00:00",
+        "time": "2019-04-16T11:33:46+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
@@ -2001,17 +2001,17 @@
     },
     {
         "name": "symfony/event-dispatcher",
-        "version": "v2.8.44",
-        "version_normalized": "2.8.44.0",
+        "version": "v2.8.50",
+        "version_normalized": "2.8.50.0",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/event-dispatcher.git",
-            "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12"
+            "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/84ae343f39947aa084426ed1138bb96bf94d1f12",
-            "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12",
+            "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0",
+            "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0",
             "shasum": ""
         },
         "require": {
@@ -2028,7 +2028,7 @@
             "symfony/dependency-injection": "",
             "symfony/http-kernel": ""
         },
-        "time": "2018-07-26T09:03:18+00:00",
+        "time": "2018-11-21T14:20:20+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
@@ -2063,24 +2063,24 @@
     },
     {
         "name": "symfony/filesystem",
-        "version": "v2.8.44",
-        "version_normalized": "2.8.44.0",
+        "version": "v2.8.50",
+        "version_normalized": "2.8.50.0",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/filesystem.git",
-            "reference": "2d6a4deccdfa2e4e9f113138b93457b2d0886c15"
+            "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/filesystem/zipball/2d6a4deccdfa2e4e9f113138b93457b2d0886c15",
-            "reference": "2d6a4deccdfa2e4e9f113138b93457b2d0886c15",
+            "url": "https://api.github.com/repos/symfony/filesystem/zipball/7ae46872dad09dffb7fe1e93a0937097339d0080",
+            "reference": "7ae46872dad09dffb7fe1e93a0937097339d0080",
             "shasum": ""
         },
         "require": {
             "php": ">=5.3.9",
             "symfony/polyfill-ctype": "~1.8"
         },
-        "time": "2018-07-26T11:13:39+00:00",
+        "time": "2018-11-11T11:18:13+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
@@ -2115,23 +2115,23 @@
     },
     {
         "name": "symfony/finder",
-        "version": "v2.8.44",
-        "version_normalized": "2.8.44.0",
+        "version": "v2.8.50",
+        "version_normalized": "2.8.50.0",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/finder.git",
-            "reference": "f0de0b51913eb2caab7dfed6413b87e14fca780e"
+            "reference": "1444eac52273e345d9b95129bf914639305a9ba4"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/finder/zipball/f0de0b51913eb2caab7dfed6413b87e14fca780e",
-            "reference": "f0de0b51913eb2caab7dfed6413b87e14fca780e",
+            "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4",
+            "reference": "1444eac52273e345d9b95129bf914639305a9ba4",
             "shasum": ""
         },
         "require": {
             "php": ">=5.3.9"
         },
-        "time": "2018-07-26T11:13:39+00:00",
+        "time": "2018-11-11T11:18:13+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
@@ -2166,17 +2166,17 @@
     },
     {
         "name": "symfony/polyfill-ctype",
-        "version": "v1.11.0",
-        "version_normalized": "1.11.0.0",
+        "version": "v1.12.0",
+        "version_normalized": "1.12.0.0",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/polyfill-ctype.git",
-            "reference": "82ebae02209c21113908c229e9883c419720738a"
+            "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a",
-            "reference": "82ebae02209c21113908c229e9883c419720738a",
+            "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
+            "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
             "shasum": ""
         },
         "require": {
@@ -2185,11 +2185,11 @@
         "suggest": {
             "ext-ctype": "For best performance"
         },
-        "time": "2019-02-06T07:57:58+00:00",
+        "time": "2019-08-06T08:03:45+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
-                "dev-master": "1.11-dev"
+                "dev-master": "1.12-dev"
             }
         },
         "installation-source": "dist",
@@ -2206,13 +2206,13 @@
             "MIT"
         ],
         "authors": [
-            {
-                "name": "Symfony Community",
-                "homepage": "https://symfony.com/contributors"
-            },
             {
                 "name": "Gert de Pagter",
                 "email": "BackEndTea@gmail.com"
+            },
+            {
+                "name": "Symfony Community",
+                "homepage": "https://symfony.com/contributors"
             }
         ],
         "description": "Symfony polyfill for ctype functions",
@@ -2226,17 +2226,17 @@
     },
     {
         "name": "symfony/polyfill-iconv",
-        "version": "v1.9.0",
-        "version_normalized": "1.9.0.0",
+        "version": "v1.12.0",
+        "version_normalized": "1.12.0.0",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/polyfill-iconv.git",
-            "reference": "bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2"
+            "reference": "685968b11e61a347c18bf25db32effa478be610f"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2",
-            "reference": "bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2",
+            "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f",
+            "reference": "685968b11e61a347c18bf25db32effa478be610f",
             "shasum": ""
         },
         "require": {
@@ -2245,11 +2245,11 @@
         "suggest": {
             "ext-iconv": "For best performance"
         },
-        "time": "2018-08-06T14:22:27+00:00",
+        "time": "2019-08-06T08:03:45+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
-                "dev-master": "1.9-dev"
+                "dev-master": "1.12-dev"
             }
         },
         "installation-source": "dist",
@@ -2287,23 +2287,23 @@
     },
     {
         "name": "symfony/process",
-        "version": "v2.8.44",
-        "version_normalized": "2.8.44.0",
+        "version": "v2.8.50",
+        "version_normalized": "2.8.50.0",
         "source": {
             "type": "git",
             "url": "https://github.com/symfony/process.git",
-            "reference": "cc83afdb5ac99147806b3bb65a3ff1227664f596"
+            "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8"
         },
         "dist": {
             "type": "zip",
-            "url": "https://api.github.com/repos/symfony/process/zipball/cc83afdb5ac99147806b3bb65a3ff1227664f596",
-            "reference": "cc83afdb5ac99147806b3bb65a3ff1227664f596",
+            "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8",
+            "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8",
             "shasum": ""
         },
         "require": {
             "php": ">=5.3.9"
         },
-        "time": "2018-07-26T11:13:39+00:00",
+        "time": "2018-11-11T11:18:13+00:00",
         "type": "library",
         "extra": {
             "branch-alias": {
@@ -2474,6 +2474,59 @@
         "description": "Default configuration for certificate authorities",
         "homepage": "https://github.com/totten/ca_config"
     },
+    {
+        "name": "xkerman/restricted-unserialize",
+        "version": "1.1.12",
+        "version_normalized": "1.1.12.0",
+        "source": {
+            "type": "git",
+            "url": "https://github.com/xKerman/restricted-unserialize.git",
+            "reference": "4c6cadbb176c04d3e19b9bb8b40df12998460489"
+        },
+        "dist": {
+            "type": "zip",
+            "url": "https://api.github.com/repos/xKerman/restricted-unserialize/zipball/4c6cadbb176c04d3e19b9bb8b40df12998460489",
+            "reference": "4c6cadbb176c04d3e19b9bb8b40df12998460489",
+            "shasum": ""
+        },
+        "require": {
+            "php": ">=5.2"
+        },
+        "require-dev": {
+            "nikic/php-parser": "^1.4|^3.0|^4.2",
+            "phpmd/phpmd": "^2.6",
+            "phpunit/phpunit": "^4.8|^5.7|^6.5|^7.4|^8.2",
+            "sebastian/phpcpd": "^2.0|^3.0|^4.1",
+            "squizlabs/php_codesniffer": "^2.9|^3.4"
+        },
+        "time": "2019-08-11T00:04:39+00:00",
+        "type": "library",
+        "installation-source": "dist",
+        "autoload": {
+            "files": [
+                "src/function.php"
+            ],
+            "psr-4": {
+                "xKerman\\Restricted\\": "src"
+            }
+        },
+        "notification-url": "https://packagist.org/downloads/",
+        "license": [
+            "MIT"
+        ],
+        "authors": [
+            {
+                "name": "xKerman",
+                "email": "xKhorasan@gmail.com"
+            }
+        ],
+        "description": "provide PHP Object Injection safe unserialize function",
+        "keywords": [
+            "PHP Object Injection",
+            "deserialize",
+            "unserialize"
+        ]
+    },
     {
         "name": "zendframework/zend-escaper",
         "version": "2.4.13",
diff --git a/civicrm/vendor/symfony/config/ConfigCacheFactoryInterface.php b/civicrm/vendor/symfony/config/ConfigCacheFactoryInterface.php
index bd614c4b6b39357e515a29107d5bc5cb5bc7ad32..8e80142b7816d100cbf5cb46d5b7ad465e653787 100644
--- a/civicrm/vendor/symfony/config/ConfigCacheFactoryInterface.php
+++ b/civicrm/vendor/symfony/config/ConfigCacheFactoryInterface.php
@@ -26,7 +26,7 @@ interface ConfigCacheFactoryInterface
      * @param string   $file     The absolute cache file path
      * @param callable $callable The callable to be executed when the cache needs to be filled (i. e. is not fresh). The cache will be passed as the only parameter to this callback
      *
-     * @return ConfigCacheInterface $configCache The cache instance
+     * @return ConfigCacheInterface The cache instance
      */
     public function cache($file, $callable);
 }
diff --git a/civicrm/vendor/symfony/config/Definition/ArrayNode.php b/civicrm/vendor/symfony/config/Definition/ArrayNode.php
index 1ab4a3ae59e43c645dc1f6fa9c49053bfaa61b85..86eacae40b3d3a17ad147af97d8065c10d1d7ee8 100644
--- a/civicrm/vendor/symfony/config/Definition/ArrayNode.php
+++ b/civicrm/vendor/symfony/config/Definition/ArrayNode.php
@@ -92,7 +92,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
     /**
      * Gets the xml remappings that should be performed.
      *
-     * @return array $remappings an array of the form array(array(string, string))
+     * @return array an array of the form array(array(string, string))
      */
     public function getXmlRemappings()
     {
@@ -219,15 +219,13 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
     protected function finalizeValue($value)
     {
         if (false === $value) {
-            $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
-            throw new UnsetKeyException($msg);
+            throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value)));
         }
 
         foreach ($this->children as $name => $child) {
             if (!array_key_exists($name, $value)) {
                 if ($child->isRequired()) {
-                    $msg = sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath());
-                    $ex = new InvalidConfigurationException($msg);
+                    $ex = new InvalidConfigurationException(sprintf('The child node "%s" at path "%s" must be configured.', $name, $this->getPath()));
                     $ex->setPath($this->getPath());
 
                     throw $ex;
@@ -260,11 +258,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
     protected function validateType($value)
     {
         if (!\is_array($value) && (!$this->allowFalse || false !== $value)) {
-            $ex = new InvalidTypeException(sprintf(
-                'Invalid type for path "%s". Expected array, but got %s',
-                $this->getPath(),
-                \gettype($value)
-            ));
+            $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected array, but got %s', $this->getPath(), \gettype($value)));
             if ($hint = $this->getInfo()) {
                 $ex->addHint($hint);
             }
@@ -294,7 +288,10 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
         $normalized = array();
         foreach ($value as $name => $val) {
             if (isset($this->children[$name])) {
-                $normalized[$name] = $this->children[$name]->normalize($val);
+                try {
+                    $normalized[$name] = $this->children[$name]->normalize($val);
+                } catch (UnsetKeyException $e) {
+                }
                 unset($value[$name]);
             } elseif (!$this->removeExtraKeys) {
                 $normalized[$name] = $val;
@@ -303,8 +300,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
 
         // if extra fields are present, throw exception
         if (\count($value) && !$this->ignoreExtraKeys) {
-            $msg = sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath());
-            $ex = new InvalidConfigurationException($msg);
+            $ex = new InvalidConfigurationException(sprintf('Unrecognized option%s "%s" under "%s"', 1 === \count($value) ? '' : 's', implode(', ', array_keys($value)), $this->getPath()));
             $ex->setPath($this->getPath());
 
             throw $ex;
@@ -363,13 +359,7 @@ class ArrayNode extends BaseNode implements PrototypeNodeInterface
             // no conflict
             if (!array_key_exists($k, $leftSide)) {
                 if (!$this->allowNewKeys) {
-                    $ex = new InvalidConfigurationException(sprintf(
-                        'You are not allowed to define new elements for path "%s". '
-                       .'Please define all elements for this path in one config file. '
-                       .'If you are trying to overwrite an element, make sure you redefine it '
-                       .'with the same name.',
-                        $this->getPath()
-                    ));
+                    $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file. If you are trying to overwrite an element, make sure you redefine it with the same name.', $this->getPath()));
                     $ex->setPath($this->getPath());
 
                     throw $ex;
diff --git a/civicrm/vendor/symfony/config/Definition/BaseNode.php b/civicrm/vendor/symfony/config/Definition/BaseNode.php
index 8885775e607dc1a6b78cc809db121c2370157b3a..7ca956e2113b0ae3f6ab5901e3615a1dc98e50e8 100644
--- a/civicrm/vendor/symfony/config/Definition/BaseNode.php
+++ b/civicrm/vendor/symfony/config/Definition/BaseNode.php
@@ -205,12 +205,7 @@ abstract class BaseNode implements NodeInterface
     final public function merge($leftSide, $rightSide)
     {
         if (!$this->allowOverwrite) {
-            throw new ForbiddenOverwriteException(sprintf(
-                'Configuration path "%s" cannot be overwritten. You have to '
-               .'define all options for this path, and any of its sub-paths in '
-               .'one configuration section.',
-                $this->getPath()
-            ));
+            throw new ForbiddenOverwriteException(sprintf('Configuration path "%s" cannot be overwritten. You have to define all options for this path, and any of its sub-paths in one configuration section.', $this->getPath()));
         }
 
         $this->validateType($leftSide);
@@ -250,7 +245,7 @@ abstract class BaseNode implements NodeInterface
      *
      * @param $value
      *
-     * @return $value The normalized array value
+     * @return The normalized array value
      */
     protected function preNormalize($value)
     {
diff --git a/civicrm/vendor/symfony/config/Definition/BooleanNode.php b/civicrm/vendor/symfony/config/Definition/BooleanNode.php
index 77e90cf7d624a98d04c260dbd1c64d31cff743b2..85f467b6bea92b5b5e234ff683aeb7eeca8f71b2 100644
--- a/civicrm/vendor/symfony/config/Definition/BooleanNode.php
+++ b/civicrm/vendor/symfony/config/Definition/BooleanNode.php
@@ -26,11 +26,7 @@ class BooleanNode extends ScalarNode
     protected function validateType($value)
     {
         if (!\is_bool($value)) {
-            $ex = new InvalidTypeException(sprintf(
-                'Invalid type for path "%s". Expected boolean, but got %s.',
-                $this->getPath(),
-                \gettype($value)
-            ));
+            $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected boolean, but got %s.', $this->getPath(), \gettype($value)));
             if ($hint = $this->getInfo()) {
                 $ex->addHint($hint);
             }
diff --git a/civicrm/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php b/civicrm/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php
index 27fadba6f8ab6e528904ed6d26c3d51304e792f2..31e918af1ce47e54e92782a9a3c0b395736950bc 100644
--- a/civicrm/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php
+++ b/civicrm/vendor/symfony/config/Definition/Builder/ArrayNodeDefinition.php
@@ -411,27 +411,19 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition
         $path = $node->getPath();
 
         if (null !== $this->key) {
-            throw new InvalidDefinitionException(
-                sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path)
-            );
+            throw new InvalidDefinitionException(sprintf('->useAttributeAsKey() is not applicable to concrete nodes at path "%s"', $path));
         }
 
         if (true === $this->atLeastOne) {
-            throw new InvalidDefinitionException(
-                sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path)
-            );
+            throw new InvalidDefinitionException(sprintf('->requiresAtLeastOneElement() is not applicable to concrete nodes at path "%s"', $path));
         }
 
         if ($this->default) {
-            throw new InvalidDefinitionException(
-                sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path)
-            );
+            throw new InvalidDefinitionException(sprintf('->defaultValue() is not applicable to concrete nodes at path "%s"', $path));
         }
 
         if (false !== $this->addDefaultChildren) {
-            throw new InvalidDefinitionException(
-                sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path)
-            );
+            throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() is not applicable to concrete nodes at path "%s"', $path));
         }
     }
 
@@ -445,28 +437,20 @@ class ArrayNodeDefinition extends NodeDefinition implements ParentNodeDefinition
         $path = $node->getPath();
 
         if ($this->addDefaults) {
-            throw new InvalidDefinitionException(
-                sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path)
-            );
+            throw new InvalidDefinitionException(sprintf('->addDefaultsIfNotSet() is not applicable to prototype nodes at path "%s"', $path));
         }
 
         if (false !== $this->addDefaultChildren) {
             if ($this->default) {
-                throw new InvalidDefinitionException(
-                    sprintf('A default value and default children might not be used together at path "%s"', $path)
-                );
+                throw new InvalidDefinitionException(sprintf('A default value and default children might not be used together at path "%s"', $path));
             }
 
             if (null !== $this->key && (null === $this->addDefaultChildren || \is_int($this->addDefaultChildren) && $this->addDefaultChildren > 0)) {
-                throw new InvalidDefinitionException(
-                    sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path)
-                );
+                throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() should set default children names as ->useAttributeAsKey() is used at path "%s"', $path));
             }
 
             if (null === $this->key && (\is_string($this->addDefaultChildren) || \is_array($this->addDefaultChildren))) {
-                throw new InvalidDefinitionException(
-                    sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path)
-                );
+                throw new InvalidDefinitionException(sprintf('->addDefaultChildrenIfNoneSet() might not set default children names as ->useAttributeAsKey() is not used at path "%s"', $path));
             }
         }
     }
diff --git a/civicrm/vendor/symfony/config/Definition/Builder/ExprBuilder.php b/civicrm/vendor/symfony/config/Definition/Builder/ExprBuilder.php
index fedbe0cc1bba226d1d0abf6f26b8506decc1054d..ddbe5b0401ab6bc8756e1366586904683bcf1405 100644
--- a/civicrm/vendor/symfony/config/Definition/Builder/ExprBuilder.php
+++ b/civicrm/vendor/symfony/config/Definition/Builder/ExprBuilder.php
@@ -149,7 +149,7 @@ class ExprBuilder
     }
 
     /**
-     * Sets a closure marking the value as invalid at validation time.
+     * Sets a closure marking the value as invalid at processing time.
      *
      * if you want to add the value of the node in your message just use a %s placeholder.
      *
@@ -167,7 +167,7 @@ class ExprBuilder
     }
 
     /**
-     * Sets a closure unsetting this key of the array at validation time.
+     * Sets a closure unsetting this key of the array at processing time.
      *
      * @return $this
      *
diff --git a/civicrm/vendor/symfony/config/Definition/Builder/NodeBuilder.php b/civicrm/vendor/symfony/config/Definition/Builder/NodeBuilder.php
index 1fac66fd3702f2d14730cf01b2c7553e7a725817..95863d68f9bba317436981060bac62d6cba0c39d 100644
--- a/civicrm/vendor/symfony/config/Definition/Builder/NodeBuilder.php
+++ b/civicrm/vendor/symfony/config/Definition/Builder/NodeBuilder.php
@@ -133,7 +133,7 @@ class NodeBuilder implements NodeParentInterface
     /**
      * Returns the parent node.
      *
-     * @return ParentNodeDefinitionInterface|NodeDefinition The parent node
+     * @return NodeDefinition&ParentNodeDefinitionInterface The parent node
      */
     public function end()
     {
diff --git a/civicrm/vendor/symfony/config/Definition/Builder/NodeDefinition.php b/civicrm/vendor/symfony/config/Definition/Builder/NodeDefinition.php
index f94d3f01f803abaec38aac0f536733e0eba455a1..a14161f082b9d99e7b28493861e13588470f2b37 100644
--- a/civicrm/vendor/symfony/config/Definition/Builder/NodeDefinition.php
+++ b/civicrm/vendor/symfony/config/Definition/Builder/NodeDefinition.php
@@ -327,7 +327,7 @@ abstract class NodeDefinition implements NodeParentInterface
     /**
      * Instantiate and configure the node according to this definition.
      *
-     * @return NodeInterface $node The node instance
+     * @return NodeInterface The node instance
      *
      * @throws InvalidDefinitionException When the definition is invalid
      */
diff --git a/civicrm/vendor/symfony/config/Definition/EnumNode.php b/civicrm/vendor/symfony/config/Definition/EnumNode.php
index 0cb40018b04fea200956eded49602fc3e2ecc75f..a214a854b758700ffa4026685baef60208dbfe2a 100644
--- a/civicrm/vendor/symfony/config/Definition/EnumNode.php
+++ b/civicrm/vendor/symfony/config/Definition/EnumNode.php
@@ -43,11 +43,7 @@ class EnumNode extends ScalarNode
         $value = parent::finalizeValue($value);
 
         if (!\in_array($value, $this->values, true)) {
-            $ex = new InvalidConfigurationException(sprintf(
-                'The value %s is not allowed for path "%s". Permissible values: %s',
-                json_encode($value),
-                $this->getPath(),
-                implode(', ', array_map('json_encode', $this->values))));
+            $ex = new InvalidConfigurationException(sprintf('The value %s is not allowed for path "%s". Permissible values: %s', json_encode($value), $this->getPath(), implode(', ', array_map('json_encode', $this->values))));
             $ex->setPath($this->getPath());
 
             throw $ex;
diff --git a/civicrm/vendor/symfony/config/Definition/PrototypedArrayNode.php b/civicrm/vendor/symfony/config/Definition/PrototypedArrayNode.php
index 6af4a61e475d4896d8eb13855c36f8e7f27ba26b..eddcb32a77871b2c942b7fd2680b781e00b58ad3 100644
--- a/civicrm/vendor/symfony/config/Definition/PrototypedArrayNode.php
+++ b/civicrm/vendor/symfony/config/Definition/PrototypedArrayNode.php
@@ -185,8 +185,7 @@ class PrototypedArrayNode extends ArrayNode
     protected function finalizeValue($value)
     {
         if (false === $value) {
-            $msg = sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value));
-            throw new UnsetKeyException($msg);
+            throw new UnsetKeyException(sprintf('Unsetting key for path "%s", value: %s', $this->getPath(), json_encode($value)));
         }
 
         foreach ($value as $k => $v) {
@@ -199,8 +198,7 @@ class PrototypedArrayNode extends ArrayNode
         }
 
         if (\count($value) < $this->minNumberOfElements) {
-            $msg = sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements);
-            $ex = new InvalidConfigurationException($msg);
+            $ex = new InvalidConfigurationException(sprintf('The path "%s" should have at least %d element(s) defined.', $this->getPath(), $this->minNumberOfElements));
             $ex->setPath($this->getPath());
 
             throw $ex;
@@ -232,8 +230,7 @@ class PrototypedArrayNode extends ArrayNode
         foreach ($value as $k => $v) {
             if (null !== $this->keyAttribute && \is_array($v)) {
                 if (!isset($v[$this->keyAttribute]) && \is_int($k) && !$isAssoc) {
-                    $msg = sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath());
-                    $ex = new InvalidConfigurationException($msg);
+                    $ex = new InvalidConfigurationException(sprintf('The attribute "%s" must be set for path "%s".', $this->keyAttribute, $this->getPath()));
                     $ex->setPath($this->getPath());
 
                     throw $ex;
@@ -262,8 +259,7 @@ class PrototypedArrayNode extends ArrayNode
                 }
 
                 if (array_key_exists($k, $normalized)) {
-                    $msg = sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath());
-                    $ex = new DuplicateKeyException($msg);
+                    $ex = new DuplicateKeyException(sprintf('Duplicate key "%s" for path "%s".', $k, $this->getPath()));
                     $ex->setPath($this->getPath());
 
                     throw $ex;
@@ -314,11 +310,7 @@ class PrototypedArrayNode extends ArrayNode
             // no conflict
             if (!array_key_exists($k, $leftSide)) {
                 if (!$this->allowNewKeys) {
-                    $ex = new InvalidConfigurationException(sprintf(
-                        'You are not allowed to define new elements for path "%s". '.
-                        'Please define all elements for this path in one config file.',
-                        $this->getPath()
-                    ));
+                    $ex = new InvalidConfigurationException(sprintf('You are not allowed to define new elements for path "%s". Please define all elements for this path in one config file.', $this->getPath()));
                     $ex->setPath($this->getPath());
 
                     throw $ex;
@@ -342,27 +334,31 @@ class PrototypedArrayNode extends ArrayNode
      * one is same as this->keyAttribute and the other is 'value', then the prototype will be different.
      *
      * For example, assume $this->keyAttribute is 'name' and the value array is as follows:
-     * array(
+     *
      *     array(
-     *         'name' => 'name001',
-     *         'value' => 'value001'
+     *         array(
+     *             'name' => 'name001',
+     *             'value' => 'value001'
+     *         )
      *     )
-     * )
      *
      * Now, the key is 0 and the child node is:
-     * array(
-     *    'name' => 'name001',
-     *    'value' => 'value001'
-     * )
+     *
+     *     array(
+     *        'name' => 'name001',
+     *        'value' => 'value001'
+     *     )
      *
      * When normalizing the value array, the 'name' element will removed from the child node
      * and its value becomes the new key of the child node:
-     * array(
-     *     'name001' => array('value' => 'value001')
-     * )
+     *
+     *     array(
+     *         'name001' => array('value' => 'value001')
+     *     )
      *
      * Now only 'value' element is left in the child node which can be further simplified into a string:
-     * array('name001' => 'value001')
+     *
+     *     array('name001' => 'value001')
      *
      * Now, the key becomes 'name001' and the child node becomes 'value001' and
      * the prototype of child node 'name001' should be a ScalarNode instead of an ArrayNode instance.
diff --git a/civicrm/vendor/symfony/config/Definition/ScalarNode.php b/civicrm/vendor/symfony/config/Definition/ScalarNode.php
index e63f3f227f7028a55746d919da716a6724120b60..53c1ed29c299d4608333b946866199ac402e0eb1 100644
--- a/civicrm/vendor/symfony/config/Definition/ScalarNode.php
+++ b/civicrm/vendor/symfony/config/Definition/ScalarNode.php
@@ -33,11 +33,7 @@ class ScalarNode extends VariableNode
     protected function validateType($value)
     {
         if (!is_scalar($value) && null !== $value) {
-            $ex = new InvalidTypeException(sprintf(
-                'Invalid type for path "%s". Expected scalar, but got %s.',
-                $this->getPath(),
-                \gettype($value)
-            ));
+            $ex = new InvalidTypeException(sprintf('Invalid type for path "%s". Expected scalar, but got %s.', $this->getPath(), \gettype($value)));
             if ($hint = $this->getInfo()) {
                 $ex->addHint($hint);
             }
diff --git a/civicrm/vendor/symfony/config/Definition/VariableNode.php b/civicrm/vendor/symfony/config/Definition/VariableNode.php
index 0cd84c72bf303431690dab26a7d6055fae5b7343..1a3442d9613db1ca1223c5f659449cb22b608728 100644
--- a/civicrm/vendor/symfony/config/Definition/VariableNode.php
+++ b/civicrm/vendor/symfony/config/Definition/VariableNode.php
@@ -82,11 +82,7 @@ class VariableNode extends BaseNode implements PrototypeNodeInterface
     protected function finalizeValue($value)
     {
         if (!$this->allowEmptyValue && $this->isValueEmpty($value)) {
-            $ex = new InvalidConfigurationException(sprintf(
-                'The path "%s" cannot contain an empty value, but got %s.',
-                $this->getPath(),
-                json_encode($value)
-            ));
+            $ex = new InvalidConfigurationException(sprintf('The path "%s" cannot contain an empty value, but got %s.', $this->getPath(), json_encode($value)));
             if ($hint = $this->getInfo()) {
                 $ex->addHint($hint);
             }
diff --git a/civicrm/vendor/symfony/config/ResourceCheckerConfigCache.php b/civicrm/vendor/symfony/config/ResourceCheckerConfigCache.php
index 52ae833d441821b5117a4d57b04fea8d2be0eb0f..81c1b972734c7ffdc572b844f0a33e21f190cc0d 100644
--- a/civicrm/vendor/symfony/config/ResourceCheckerConfigCache.php
+++ b/civicrm/vendor/symfony/config/ResourceCheckerConfigCache.php
@@ -151,7 +151,7 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface
             }
         }
 
-        if (\function_exists('opcache_invalidate') && ini_get('opcache.enable')) {
+        if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
             @opcache_invalidate($this->file, true);
         }
     }
diff --git a/civicrm/vendor/symfony/config/phpunit.xml.dist b/civicrm/vendor/symfony/config/phpunit.xml.dist
index 36ef339fd78e422a39dacfcb2a6b8b6219577686..1cfdb3cdc67271a994db715a7c459921596af5da 100644
--- a/civicrm/vendor/symfony/config/phpunit.xml.dist
+++ b/civicrm/vendor/symfony/config/phpunit.xml.dist
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
diff --git a/civicrm/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php b/civicrm/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php
index f79a4b0f5b75fd2587524ee421a9427e44bcebe8..ea1e089179a83762606e0a6133c199b23d2b7315 100644
--- a/civicrm/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php
+++ b/civicrm/vendor/symfony/dependency-injection/Compiler/CheckDefinitionValidityPass.php
@@ -64,13 +64,7 @@ class CheckDefinitionValidityPass implements CompilerPassInterface
                     throw new RuntimeException(sprintf('Please add the class to service "%s" even if it is constructed by a factory since we might need to add method calls based on compile-time checks.', $id));
                 }
 
-                throw new RuntimeException(sprintf(
-                    'The definition for "%s" has no class. If you intend to inject '
-                   .'this service dynamically at runtime, please mark it as synthetic=true. '
-                   .'If this is an abstract definition solely used by child definitions, '
-                   .'please add abstract=true, otherwise specify a class to get rid of this error.',
-                   $id
-                ));
+                throw new RuntimeException(sprintf('The definition for "%s" has no class. If you intend to inject this service dynamically at runtime, please mark it as synthetic=true. If this is an abstract definition solely used by child definitions, please add abstract=true, otherwise specify a class to get rid of this error.', $id));
             }
 
             // tag attribute values must be scalars
diff --git a/civicrm/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php b/civicrm/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php
index 416103ec0e69dc5fe20a0476f2a8bf5965767c1a..2b380dd352a851a62dc010c0c36df9df209ddf99 100644
--- a/civicrm/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php
+++ b/civicrm/vendor/symfony/dependency-injection/Compiler/CheckReferenceValidityPass.php
@@ -94,12 +94,7 @@ class CheckReferenceValidityPass implements CompilerPassInterface
                 $targetDefinition = $this->getDefinition((string) $argument);
 
                 if (null !== $targetDefinition && $targetDefinition->isAbstract()) {
-                    throw new RuntimeException(sprintf(
-                        'The definition "%s" has a reference to an abstract definition "%s". '
-                       .'Abstract definitions cannot be the target of references.',
-                       $this->currentId,
-                       $argument
-                    ));
+                    throw new RuntimeException(sprintf('The definition "%s" has a reference to an abstract definition "%s". Abstract definitions cannot be the target of references.', $this->currentId, $argument));
                 }
 
                 $this->validateScope($argument, $targetDefinition);
diff --git a/civicrm/vendor/symfony/dependency-injection/Container.php b/civicrm/vendor/symfony/dependency-injection/Container.php
index 00b34b59682cd99bffd9d6092bccb3fda2a84332..25532ead233d5b954cf9c1e9a31a8b157a672fc7 100644
--- a/civicrm/vendor/symfony/dependency-injection/Container.php
+++ b/civicrm/vendor/symfony/dependency-injection/Container.php
@@ -33,11 +33,9 @@ use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  * A service can also be defined by creating a method named
  * getXXXService(), where XXX is the camelized version of the id:
  *
- * <ul>
- *   <li>request -> getRequestService()</li>
- *   <li>mysql_session_storage -> getMysqlSessionStorageService()</li>
- *   <li>symfony.mysql_session_storage -> getSymfony_MysqlSessionStorageService()</li>
- * </ul>
+ *  * request -> getRequestService()
+ *  * mysql_session_storage -> getMysqlSessionStorageService()
+ *  * symfony.mysql_session_storage -> getSymfony_MysqlSessionStorageService()
  *
  * The container can have three possible behaviors when a service does not exist:
  *
diff --git a/civicrm/vendor/symfony/dependency-injection/ContainerAwareInterface.php b/civicrm/vendor/symfony/dependency-injection/ContainerAwareInterface.php
index d78491bb9633572af40871684da4843bb569651e..e7b9d575ece50637213e1222073e8cab5a8be7ff 100644
--- a/civicrm/vendor/symfony/dependency-injection/ContainerAwareInterface.php
+++ b/civicrm/vendor/symfony/dependency-injection/ContainerAwareInterface.php
@@ -18,5 +18,8 @@ namespace Symfony\Component\DependencyInjection;
  */
 interface ContainerAwareInterface
 {
+    /**
+     * Sets the container.
+     */
     public function setContainer(ContainerInterface $container = null);
 }
diff --git a/civicrm/vendor/symfony/dependency-injection/ContainerBuilder.php b/civicrm/vendor/symfony/dependency-injection/ContainerBuilder.php
index dc09a6582058714ae4917861fc286b36194f1945..0e1391f9f81e577d007d7a8dc5378c1c868f264c 100644
--- a/civicrm/vendor/symfony/dependency-injection/ContainerBuilder.php
+++ b/civicrm/vendor/symfony/dependency-injection/ContainerBuilder.php
@@ -493,10 +493,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
      * the parameters passed to the container constructor to have precedence
      * over the loaded ones.
      *
-     * $container = new ContainerBuilder(new ParameterBag(array('foo' => 'bar')));
-     * $loader = new LoaderXXX($container);
-     * $loader->load('resource_name');
-     * $container->register('foo', 'stdClass');
+     *     $container = new ContainerBuilder(new ParameterBag(array('foo' => 'bar')));
+     *     $loader = new LoaderXXX($container);
+     *     $loader->load('resource_name');
+     *     $container->register('foo', 'stdClass');
      *
      * In the above example, even if the loaded resource defines a foo
      * parameter, the value will still be 'bar' as defined in the ContainerBuilder
@@ -641,6 +641,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
     {
         $alias = strtolower($alias);
 
+        if ('' === $alias || '\\' === substr($alias, -1) || \strlen($alias) !== strcspn($alias, "\0\r\n'")) {
+            throw new InvalidArgumentException(sprintf('Invalid alias id: "%s"', $alias));
+        }
+
         if (\is_string($id)) {
             $id = new Alias($id);
         } elseif (!$id instanceof Alias) {
@@ -775,6 +779,10 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
 
         $id = strtolower($id);
 
+        if ('' === $id || '\\' === substr($id, -1) || \strlen($id) !== strcspn($id, "\0\r\n'")) {
+            throw new InvalidArgumentException(sprintf('Invalid service id: "%s"', $id));
+        }
+
         unset($this->aliasDefinitions[$id]);
 
         return $this->definitions[$id] = $definition;
@@ -999,14 +1007,14 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
      *
      * Example:
      *
-     * $container->register('foo')->addTag('my.tag', array('hello' => 'world'));
+     *     $container->register('foo')->addTag('my.tag', array('hello' => 'world'));
      *
-     * $serviceIds = $container->findTaggedServiceIds('my.tag');
-     * foreach ($serviceIds as $serviceId => $tags) {
-     *     foreach ($tags as $tag) {
-     *         echo $tag['hello'];
+     *     $serviceIds = $container->findTaggedServiceIds('my.tag');
+     *     foreach ($serviceIds as $serviceId => $tags) {
+     *         foreach ($tags as $tag) {
+     *             echo $tag['hello'];
+     *         }
      *     }
-     * }
      *
      * @param string $name The tag name
      *
diff --git a/civicrm/vendor/symfony/dependency-injection/Definition.php b/civicrm/vendor/symfony/dependency-injection/Definition.php
index 70f68469e30dc7aabe75e7a1803feee57fe9c9bb..d9c37ea50420dbcf3d03ab2e43593cd3d5e4b302 100644
--- a/civicrm/vendor/symfony/dependency-injection/Definition.php
+++ b/civicrm/vendor/symfony/dependency-injection/Definition.php
@@ -79,7 +79,7 @@ class Definition
     /**
      * Gets the factory.
      *
-     * @return string|array The PHP function or an array containing a class/Reference and a method to call
+     * @return string|array|null The PHP function or an array containing a class/Reference and a method to call
      */
     public function getFactory()
     {
@@ -142,8 +142,8 @@ class Definition
     /**
      * Sets the service that this service is decorating.
      *
-     * @param null|string $id        The decorated service id, use null to remove decoration
-     * @param null|string $renamedId The new decorated service id
+     * @param string|null $id        The decorated service id, use null to remove decoration
+     * @param string|null $renamedId The new decorated service id
      * @param int         $priority  The priority of decoration
      *
      * @return $this
@@ -168,7 +168,7 @@ class Definition
     /**
      * Gets the service that this service is decorating.
      *
-     * @return null|array An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated
+     * @return array|null An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated
      */
     public function getDecoratedService()
     {
diff --git a/civicrm/vendor/symfony/dependency-injection/Dumper/PhpDumper.php b/civicrm/vendor/symfony/dependency-injection/Dumper/PhpDumper.php
index fc8092f4a34ec6c5c18da4787b7759027d5bab6d..2f530e9e3e4ed7146ea39c0d562ab860d988aaae 100644
--- a/civicrm/vendor/symfony/dependency-injection/Dumper/PhpDumper.php
+++ b/civicrm/vendor/symfony/dependency-injection/Dumper/PhpDumper.php
@@ -233,7 +233,9 @@ class PhpDumper extends Dumper
         $strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments');
 
         foreach ($definitions as $definition) {
-            $proxyCode = "\n".$this->getProxyDumper()->getProxyCode($definition);
+            if ("\n" === $proxyCode = "\n".$this->getProxyDumper()->getProxyCode($definition)) {
+                continue;
+            }
             if ($strip) {
                 $proxyCode = "<?php\n".$proxyCode;
                 $proxyCode = substr(Kernel::stripComments($proxyCode), 5);
@@ -377,9 +379,9 @@ class PhpDumper extends Dumper
         $instantiation = '';
 
         if (!$isProxyCandidate && $definition->isShared() && ContainerInterface::SCOPE_CONTAINER === $definition->getScope(false)) {
-            $instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance');
+            $instantiation = sprintf('$this->services[%s] = %s', var_export($id, true), $simple ? '' : '$instance');
         } elseif (!$isProxyCandidate && $definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope(false)) {
-            $instantiation = "\$this->services['$id'] = \$this->scopedServices['$scope']['$id'] = ".($simple ? '' : '$instance');
+            $instantiation = sprintf('$this->services[%s] = $this->scopedServices[%s][%1$s] = %s', var_export($id, true), var_export($scope, true), $simple ? '' : '$instance');
         } elseif (!$simple) {
             $instantiation = '$instance';
         }
@@ -607,6 +609,9 @@ class PhpDumper extends Dumper
      * Gets the $public '$id'$shared$autowired service.
      *
      * $return
+EOF;
+            $code = str_replace('*/', ' ', $code).<<<EOF
+
      */
     {$visibility} function get{$this->camelize($id)}Service($lazyInitialization)
     {
@@ -618,7 +623,7 @@ EOF;
         if (!\in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) {
             $code .= <<<EOF
         if (!isset(\$this->scopedServices['$scope'])) {
-            throw new InactiveScopeException('$id', '$scope');
+            throw new InactiveScopeException({$this->export($id)}, '$scope');
         }
 
 
@@ -626,7 +631,7 @@ EOF;
         }
 
         if ($definition->isSynthetic()) {
-            $code .= sprintf("        throw new RuntimeException('You have requested a synthetic service (\"%s\"). The DIC does not know how to construct this service.');\n    }\n", $id);
+            $code .= sprintf("        throw new RuntimeException(%s);\n    }\n", var_export("You have requested a synthetic service (\"$id\"). The DIC does not know how to construct this service.", true));
         } else {
             if ($definition->isDeprecated()) {
                 $code .= sprintf("        @trigger_error(%s, E_USER_DEPRECATED);\n\n", var_export($definition->getDeprecationMessage($id), true));
@@ -704,10 +709,11 @@ EOF;
                             $arguments[] = $this->dumpValue($value);
                         }
 
-                        $call = $this->wrapServiceConditionals($call[1], sprintf("\$this->get('%s')->%s(%s);", $definitionId, $call[0], implode(', ', $arguments)));
+                        $definitionId = var_export($definitionId, true);
+                        $call = $this->wrapServiceConditionals($call[1], sprintf('$this->get(%s)->%s(%s);', $definitionId, $call[0], implode(', ', $arguments)));
 
                         $code .= <<<EOF
-        if (\$this->initialized('$definitionId')) {
+        if (\$this->initialized($definitionId)) {
             $call
         }
 
@@ -1140,7 +1146,7 @@ EOF;
 
         $conditions = array();
         foreach ($services as $service) {
-            $conditions[] = sprintf("\$this->has('%s')", $service);
+            $conditions[] = sprintf('$this->has(%s)', var_export($service, true));
         }
 
         // re-indent the wrapped code
@@ -1417,11 +1423,13 @@ EOF;
      */
     public function dumpParameter($name)
     {
+        $name = (string) $name;
+
         if ($this->container->isFrozen() && $this->container->hasParameter($name)) {
             return $this->dumpValue($this->container->getParameter($name), false);
         }
 
-        return sprintf("\$this->getParameter('%s')", strtolower($name));
+        return sprintf('$this->getParameter(%s)', var_export($name, true));
     }
 
     /**
@@ -1456,10 +1464,10 @@ EOF;
         }
 
         if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
-            return sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id);
+            return sprintf('$this->get(%s, ContainerInterface::NULL_ON_INVALID_REFERENCE)', var_export($id, true));
         }
 
-        return sprintf('$this->get(\'%s\')', $id);
+        return sprintf('$this->get(%s)', var_export($id, true));
     }
 
     /**
diff --git a/civicrm/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php b/civicrm/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php
index 82b578bec9dbaa885d640547cd3f098e58869005..ecebe0125ec28321baee2e020d60e4c4c2365f66 100644
--- a/civicrm/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php
+++ b/civicrm/vendor/symfony/dependency-injection/Loader/XmlFileLoader.php
@@ -543,13 +543,7 @@ EOF
             // can it be handled by an extension?
             if (!$this->container->hasExtension($node->namespaceURI)) {
                 $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getNamespace(); }, $this->container->getExtensions()));
-                throw new InvalidArgumentException(sprintf(
-                    'There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s',
-                    $node->tagName,
-                    $file,
-                    $node->namespaceURI,
-                    $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'
-                ));
+                throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', $node->tagName, $file, $node->namespaceURI, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'));
             }
         }
     }
diff --git a/civicrm/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php b/civicrm/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php
index 51bc768c0fed87d51258aedaf90b4f615accd881..d25f654709b877146df6a79de9550d4b8a3ae212 100644
--- a/civicrm/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php
+++ b/civicrm/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php
@@ -396,13 +396,7 @@ class YamlFileLoader extends FileLoader
 
             if (!$this->container->hasExtension($namespace)) {
                 $extensionNamespaces = array_filter(array_map(function ($ext) { return $ext->getAlias(); }, $this->container->getExtensions()));
-                throw new InvalidArgumentException(sprintf(
-                    'There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s',
-                    $namespace,
-                    $file,
-                    $namespace,
-                    $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'
-                ));
+                throw new InvalidArgumentException(sprintf('There is no extension able to load the configuration for "%s" (in %s). Looked for namespace "%s", found %s', $namespace, $file, $namespace, $extensionNamespaces ? sprintf('"%s"', implode('", "', $extensionNamespaces)) : 'none'));
             }
         }
 
diff --git a/civicrm/vendor/symfony/dependency-injection/phpunit.xml.dist b/civicrm/vendor/symfony/dependency-injection/phpunit.xml.dist
index 781f767d544821f256ee397137545cdd18a93c5d..21dee2a801afd464a4c8df82afc540e2781eccb1 100644
--- a/civicrm/vendor/symfony/dependency-injection/phpunit.xml.dist
+++ b/civicrm/vendor/symfony/dependency-injection/phpunit.xml.dist
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
diff --git a/civicrm/vendor/symfony/event-dispatcher/GenericEvent.php b/civicrm/vendor/symfony/event-dispatcher/GenericEvent.php
index 95c99408de20f3ee779c1f44a009f56903adbace..f0be7e18ff3c343b552cae3a70268275acd54955 100644
--- a/civicrm/vendor/symfony/event-dispatcher/GenericEvent.php
+++ b/civicrm/vendor/symfony/event-dispatcher/GenericEvent.php
@@ -38,7 +38,7 @@ class GenericEvent extends Event implements \ArrayAccess, \IteratorAggregate
     /**
      * Getter for subject property.
      *
-     * @return mixed $subject The observer subject
+     * @return mixed The observer subject
      */
     public function getSubject()
     {
diff --git a/civicrm/vendor/symfony/event-dispatcher/phpunit.xml.dist b/civicrm/vendor/symfony/event-dispatcher/phpunit.xml.dist
index b3ad1bdf5a8e3344357ea623024e7fdfe18b3d8f..f2eb1692cdbbab8633d66aa8b109e017821ed3d8 100644
--- a/civicrm/vendor/symfony/event-dispatcher/phpunit.xml.dist
+++ b/civicrm/vendor/symfony/event-dispatcher/phpunit.xml.dist
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
diff --git a/civicrm/vendor/symfony/filesystem/Filesystem.php b/civicrm/vendor/symfony/filesystem/Filesystem.php
index 5df92e733d366959fe35266413500893c62e4384..df316205619fc69e0715f6b158b729c0aba8c2a9 100644
--- a/civicrm/vendor/symfony/filesystem/Filesystem.php
+++ b/civicrm/vendor/symfony/filesystem/Filesystem.php
@@ -578,7 +578,7 @@ class Filesystem
      *
      * @param string   $filename The file to be written to
      * @param string   $content  The data to write into the file
-     * @param null|int $mode     The file mode (octal). If null, file permissions are not modified
+     * @param int|null $mode     The file mode (octal). If null, file permissions are not modified
      *                           Deprecated since version 2.3.12, to be removed in 3.0.
      *
      * @throws IOException if the file cannot be written to
diff --git a/civicrm/vendor/symfony/filesystem/phpunit.xml.dist b/civicrm/vendor/symfony/filesystem/phpunit.xml.dist
index 7bba6fc2f0dad9f453fd23f233880eca77dd8c15..5515fff1ac944fbe1f73b96c4c1f4ad191738102 100644
--- a/civicrm/vendor/symfony/filesystem/phpunit.xml.dist
+++ b/civicrm/vendor/symfony/filesystem/phpunit.xml.dist
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
diff --git a/civicrm/vendor/symfony/finder/Finder.php b/civicrm/vendor/symfony/finder/Finder.php
index 007b68867532bef2a088d6189e8a4985507769e8..1ee9017356903d7c618ec2fdbf3b3db41572dc5c 100644
--- a/civicrm/vendor/symfony/finder/Finder.php
+++ b/civicrm/vendor/symfony/finder/Finder.php
@@ -36,7 +36,7 @@ use Symfony\Component\Finder\Iterator\SortableIterator;
  *
  * All methods return the current Finder object to allow easy chaining:
  *
- * $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
+ *     $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
  *
  * @author Fabien Potencier <fabien@symfony.com>
  */
@@ -215,8 +215,8 @@ class Finder implements \IteratorAggregate, \Countable
      *
      * Usage:
      *
-     *   $finder->depth('> 1') // the Finder will start matching at level 1.
-     *   $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
+     *     $finder->depth('> 1') // the Finder will start matching at level 1.
+     *     $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
      *
      * @param string|int $level The depth level expression
      *
@@ -237,10 +237,10 @@ class Finder implements \IteratorAggregate, \Countable
      *
      * The date must be something that strtotime() is able to parse:
      *
-     *   $finder->date('since yesterday');
-     *   $finder->date('until 2 days ago');
-     *   $finder->date('> now - 2 hours');
-     *   $finder->date('>= 2005-10-15');
+     *     $finder->date('since yesterday');
+     *     $finder->date('until 2 days ago');
+     *     $finder->date('> now - 2 hours');
+     *     $finder->date('>= 2005-10-15');
      *
      * @param string $date A date range string
      *
@@ -262,9 +262,9 @@ class Finder implements \IteratorAggregate, \Countable
      *
      * You can use patterns (delimited with / sign), globs or simple strings.
      *
-     * $finder->name('*.php')
-     * $finder->name('/\.php$/') // same as above
-     * $finder->name('test.php')
+     *     $finder->name('*.php')
+     *     $finder->name('/\.php$/') // same as above
+     *     $finder->name('test.php')
      *
      * @param string $pattern A pattern (a regexp, a glob, or a string)
      *
@@ -300,8 +300,8 @@ class Finder implements \IteratorAggregate, \Countable
      *
      * Strings or PCRE patterns can be used:
      *
-     * $finder->contains('Lorem ipsum')
-     * $finder->contains('/Lorem ipsum/i')
+     *     $finder->contains('Lorem ipsum')
+     *     $finder->contains('/Lorem ipsum/i')
      *
      * @param string $pattern A pattern (string or regexp)
      *
@@ -321,8 +321,8 @@ class Finder implements \IteratorAggregate, \Countable
      *
      * Strings or PCRE patterns can be used:
      *
-     * $finder->notContains('Lorem ipsum')
-     * $finder->notContains('/Lorem ipsum/i')
+     *     $finder->notContains('Lorem ipsum')
+     *     $finder->notContains('/Lorem ipsum/i')
      *
      * @param string $pattern A pattern (string or regexp)
      *
@@ -342,8 +342,8 @@ class Finder implements \IteratorAggregate, \Countable
      *
      * You can use patterns (delimited with / sign) or simple strings.
      *
-     * $finder->path('some/special/dir')
-     * $finder->path('/some\/special\/dir/') // same as above
+     *     $finder->path('some/special/dir')
+     *     $finder->path('/some\/special\/dir/') // same as above
      *
      * Use only / as dirname separator.
      *
@@ -365,8 +365,8 @@ class Finder implements \IteratorAggregate, \Countable
      *
      * You can use patterns (delimited with / sign) or simple strings.
      *
-     * $finder->notPath('some/special/dir')
-     * $finder->notPath('/some\/special\/dir/') // same as above
+     *     $finder->notPath('some/special/dir')
+     *     $finder->notPath('/some\/special\/dir/') // same as above
      *
      * Use only / as dirname separator.
      *
@@ -386,9 +386,9 @@ class Finder implements \IteratorAggregate, \Countable
     /**
      * Adds tests for file sizes.
      *
-     * $finder->size('> 10K');
-     * $finder->size('<= 1Ki');
-     * $finder->size(4);
+     *     $finder->size('> 10K');
+     *     $finder->size('<= 1Ki');
+     *     $finder->size(4);
      *
      * @param string|int $size A size range string or an integer
      *
@@ -699,7 +699,7 @@ class Finder implements \IteratorAggregate, \Countable
      *
      * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
      *
-     * @param mixed $iterator
+     * @param iterable $iterator
      *
      * @return $this
      *
@@ -751,7 +751,7 @@ class Finder implements \IteratorAggregate, \Countable
     }
 
     /**
-     * @param $dir
+     * @param string $dir
      *
      * @return \Iterator
      */
diff --git a/civicrm/vendor/symfony/finder/Glob.php b/civicrm/vendor/symfony/finder/Glob.php
index 2e56cf2800511492fa16b88ae75bef60fdb291ce..e2988f25768ececf60db1a57c573624c5c250d13 100644
--- a/civicrm/vendor/symfony/finder/Glob.php
+++ b/civicrm/vendor/symfony/finder/Glob.php
@@ -14,14 +14,14 @@ namespace Symfony\Component\Finder;
 /**
  * Glob matches globbing patterns against text.
  *
- *   if match_glob("foo.*", "foo.bar") echo "matched\n";
+ *     if match_glob("foo.*", "foo.bar") echo "matched\n";
  *
- * // prints foo.bar and foo.baz
- * $regex = glob_to_regex("foo.*");
- * for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t)
- * {
- *   if (/$regex/) echo "matched: $car\n";
- * }
+ *     // prints foo.bar and foo.baz
+ *     $regex = glob_to_regex("foo.*");
+ *     for (array('foo.bar', 'foo.baz', 'foo', 'bar') as $t)
+ *     {
+ *         if (/$regex/) echo "matched: $car\n";
+ *     }
  *
  * Glob implements glob(3) style matching that can be used to match
  * against text, rather than fetching names from a filesystem.
diff --git a/civicrm/vendor/symfony/finder/phpunit.xml.dist b/civicrm/vendor/symfony/finder/phpunit.xml.dist
index 0e1a8669beabeec3143a0db2aee668bc33dfcf4f..078847af96add0b0afa41c11b0ac06144d0bfc82 100644
--- a/civicrm/vendor/symfony/finder/phpunit.xml.dist
+++ b/civicrm/vendor/symfony/finder/phpunit.xml.dist
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
diff --git a/civicrm/vendor/symfony/polyfill-ctype/composer.json b/civicrm/vendor/symfony/polyfill-ctype/composer.json
index c24e20ca75f1ebb0cab11790ee6991fd4835c2a6..090f923ef1dcb151a058806d94a8c658e4889136 100644
--- a/civicrm/vendor/symfony/polyfill-ctype/composer.json
+++ b/civicrm/vendor/symfony/polyfill-ctype/composer.json
@@ -28,7 +28,7 @@
     "minimum-stability": "dev",
     "extra": {
         "branch-alias": {
-            "dev-master": "1.11-dev"
+            "dev-master": "1.12-dev"
         }
     }
 }
diff --git a/civicrm/vendor/symfony/polyfill-iconv/Iconv.php b/civicrm/vendor/symfony/polyfill-iconv/Iconv.php
index 92874f506eef85c9f7224c4ed55f51ac88cd0190..77e7ca056bfafef8f42ef6ba1d56785d9ca3e955 100644
--- a/civicrm/vendor/symfony/polyfill-iconv/Iconv.php
+++ b/civicrm/vendor/symfony/polyfill-iconv/Iconv.php
@@ -174,8 +174,8 @@ final class Iconv
             }
         } while ($loop);
 
-        if (isset(self::$alias[ $inCharset])) {
-            $inCharset = self::$alias[ $inCharset];
+        if (isset(self::$alias[$inCharset])) {
+            $inCharset = self::$alias[$inCharset];
         }
         if (isset(self::$alias[$outCharset])) {
             $outCharset = self::$alias[$outCharset];
@@ -292,7 +292,7 @@ final class Iconv
             if ((ICONV_MIME_DECODE_CONTINUE_ON_ERROR & $mode)
               && 'utf-8' !== $c
               && !isset(self::$alias[$c])
-              && !self::loadMap('from.', $c,  $d)) {
+              && !self::loadMap('from.', $c, $d)) {
                 $d = false;
             } elseif ('B' === strtoupper($str[$i + 1])) {
                 $d = base64_decode($str[$i + 2]);
@@ -433,7 +433,7 @@ final class Iconv
     {
         static $hasXml = null;
         if (null === $hasXml) {
-            $hasXml = extension_loaded('xml');
+            $hasXml = \extension_loaded('xml');
         }
 
         if ($hasXml) {
diff --git a/civicrm/vendor/symfony/polyfill-iconv/LICENSE b/civicrm/vendor/symfony/polyfill-iconv/LICENSE
index 24fa32c2e9b27aef3eac523f0042b8ab9ba81944..4cd8bdd3007da4d62985ec9e5ca81a1e18ae34d1 100644
--- a/civicrm/vendor/symfony/polyfill-iconv/LICENSE
+++ b/civicrm/vendor/symfony/polyfill-iconv/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2015-2018 Fabien Potencier
+Copyright (c) 2015-2019 Fabien Potencier
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/civicrm/vendor/symfony/polyfill-iconv/composer.json b/civicrm/vendor/symfony/polyfill-iconv/composer.json
index 816e6bd7ffe11f0c4273be2e3d472605782bb9af..0c23267cbe3400460de51b8c711e9bc3d1ab9435 100644
--- a/civicrm/vendor/symfony/polyfill-iconv/composer.json
+++ b/civicrm/vendor/symfony/polyfill-iconv/composer.json
@@ -28,7 +28,7 @@
     "minimum-stability": "dev",
     "extra": {
         "branch-alias": {
-            "dev-master": "1.9-dev"
+            "dev-master": "1.12-dev"
         }
     }
 }
diff --git a/civicrm/vendor/symfony/process/PhpProcess.php b/civicrm/vendor/symfony/process/PhpProcess.php
index 6bf6bb67495b26ea9adf1b92a1415e6e553a9c8b..31a855d943af2fbf61ce4f3ca7843b03c8b76b61 100644
--- a/civicrm/vendor/symfony/process/PhpProcess.php
+++ b/civicrm/vendor/symfony/process/PhpProcess.php
@@ -16,9 +16,9 @@ use Symfony\Component\Process\Exception\RuntimeException;
 /**
  * PhpProcess runs a PHP script in an independent process.
  *
- * $p = new PhpProcess('<?php echo "foo"; ?>');
- * $p->run();
- * print $p->getOutput()."\n";
+ *     $p = new PhpProcess('<?php echo "foo"; ?>');
+ *     $p->run();
+ *     print $p->getOutput()."\n";
  *
  * @author Fabien Potencier <fabien@symfony.com>
  */
diff --git a/civicrm/vendor/symfony/process/Pipes/WindowsPipes.php b/civicrm/vendor/symfony/process/Pipes/WindowsPipes.php
index f81f65facae1681e64651fda5edaa4ec78462908..0b2a76387fdf90873ad08a85006e271b549c91e0 100644
--- a/civicrm/vendor/symfony/process/Pipes/WindowsPipes.php
+++ b/civicrm/vendor/symfony/process/Pipes/WindowsPipes.php
@@ -28,6 +28,7 @@ class WindowsPipes extends AbstractPipes
 {
     private $files = array();
     private $fileHandles = array();
+    private $lockHandles = array();
     private $readBytes = array(
         Process::STDOUT => 0,
         Process::STDERR => 0,
@@ -47,31 +48,33 @@ class WindowsPipes extends AbstractPipes
                 Process::STDOUT => Process::OUT,
                 Process::STDERR => Process::ERR,
             );
-            $tmpCheck = false;
             $tmpDir = sys_get_temp_dir();
             $lastError = 'unknown reason';
             set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
             for ($i = 0;; ++$i) {
                 foreach ($pipes as $pipe => $name) {
                     $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
-                    if (file_exists($file) && !unlink($file)) {
-                        continue 2;
-                    }
-                    $h = fopen($file, 'xb');
-                    if (!$h) {
-                        $error = $lastError;
-                        if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) {
-                            continue;
-                        }
+
+                    if (!$h = fopen($file.'.lock', 'w')) {
                         restore_error_handler();
-                        throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error));
+                        throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $lastError));
                     }
-                    if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) {
+                    if (!flock($h, LOCK_EX | LOCK_NB)) {
                         continue 2;
                     }
-                    if (isset($this->files[$pipe])) {
-                        unlink($this->files[$pipe]);
+                    if (isset($this->lockHandles[$pipe])) {
+                        flock($this->lockHandles[$pipe], LOCK_UN);
+                        fclose($this->lockHandles[$pipe]);
                     }
+                    $this->lockHandles[$pipe] = $h;
+
+                    if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
+                        flock($this->lockHandles[$pipe], LOCK_UN);
+                        fclose($this->lockHandles[$pipe]);
+                        unset($this->lockHandles[$pipe]);
+                        continue 2;
+                    }
+                    $this->fileHandles[$pipe] = $h;
                     $this->files[$pipe] = $file;
                 }
                 break;
@@ -85,7 +88,6 @@ class WindowsPipes extends AbstractPipes
     public function __destruct()
     {
         $this->close();
-        $this->removeFiles();
     }
 
     /**
@@ -145,8 +147,11 @@ class WindowsPipes extends AbstractPipes
                 $read[$type] = $data;
             }
             if ($close) {
+                ftruncate($fileHandle, 0);
                 fclose($fileHandle);
-                unset($this->fileHandles[$type]);
+                flock($this->lockHandles[$type], LOCK_UN);
+                fclose($this->lockHandles[$type]);
+                unset($this->fileHandles[$type], $this->lockHandles[$type]);
             }
         }
 
@@ -167,10 +172,13 @@ class WindowsPipes extends AbstractPipes
     public function close()
     {
         parent::close();
-        foreach ($this->fileHandles as $handle) {
+        foreach ($this->fileHandles as $type => $handle) {
+            ftruncate($handle, 0);
             fclose($handle);
+            flock($this->lockHandles[$type], LOCK_UN);
+            fclose($this->lockHandles[$type]);
         }
-        $this->fileHandles = array();
+        $this->fileHandles = $this->lockHandles = array();
     }
 
     /**
@@ -185,17 +193,4 @@ class WindowsPipes extends AbstractPipes
     {
         return new static($process->isOutputDisabled(), $input);
     }
-
-    /**
-     * Removes temporary files.
-     */
-    private function removeFiles()
-    {
-        foreach ($this->files as $filename) {
-            if (file_exists($filename)) {
-                @unlink($filename);
-            }
-        }
-        $this->files = array();
-    }
 }
diff --git a/civicrm/vendor/symfony/process/Process.php b/civicrm/vendor/symfony/process/Process.php
index 9589136c6b096ea190b5098df2381eef70c1d9d0..a261ea5340b1fcfbc5f94371e8e9f75c36eb70bb 100644
--- a/civicrm/vendor/symfony/process/Process.php
+++ b/civicrm/vendor/symfony/process/Process.php
@@ -567,7 +567,7 @@ class Process
     /**
      * Returns the exit code returned by the process.
      *
-     * @return null|int The exit status code, null if the Process is not terminated
+     * @return int|null The exit status code, null if the Process is not terminated
      *
      * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
      */
@@ -588,7 +588,7 @@ class Process
      * This method relies on the Unix exit code status standardization
      * and might not be relevant for other operating systems.
      *
-     * @return null|string A string representation for the exit status code, null if the Process is not terminated
+     * @return string|null A string representation for the exit status code, null if the Process is not terminated
      *
      * @see http://tldp.org/LDP/abs/html/exitcodes.html
      * @see http://en.wikipedia.org/wiki/Unix_signal
@@ -1044,7 +1044,7 @@ class Process
     /**
      * Gets the Process input.
      *
-     * @return null|string The Process input
+     * @return string|null The Process input
      */
     public function getInput()
     {
@@ -1413,8 +1413,8 @@ class Process
         $this->exitcode = null;
         $this->fallbackStatus = array();
         $this->processInformation = null;
-        $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
-        $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+');
+        $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
+        $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+b');
         $this->process = null;
         $this->latestSignal = null;
         $this->status = self::STATUS_READY;
diff --git a/civicrm/vendor/symfony/process/ProcessBuilder.php b/civicrm/vendor/symfony/process/ProcessBuilder.php
index f246c871b87b71b25fe88062ab4f3a7376af7d61..1bac780d6329ba440d68d21b28262dd330683138 100644
--- a/civicrm/vendor/symfony/process/ProcessBuilder.php
+++ b/civicrm/vendor/symfony/process/ProcessBuilder.php
@@ -99,7 +99,7 @@ class ProcessBuilder
     /**
      * Sets the working directory.
      *
-     * @param null|string $cwd The working directory
+     * @param string|null $cwd The working directory
      *
      * @return $this
      */
@@ -131,7 +131,7 @@ class ProcessBuilder
      * defined environment variable.
      *
      * @param string      $name  The variable name
-     * @param null|string $value The variable value
+     * @param string|null $value The variable value
      *
      * @return $this
      */
diff --git a/civicrm/vendor/symfony/process/phpunit.xml.dist b/civicrm/vendor/symfony/process/phpunit.xml.dist
index d38846730d72b075adf261c0dff03bcceaa2deab..c32f25101efefc33617af9610ab57c544e1fc2d4 100644
--- a/civicrm/vendor/symfony/process/phpunit.xml.dist
+++ b/civicrm/vendor/symfony/process/phpunit.xml.dist
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
+         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/5.2/phpunit.xsd"
          backupGlobals="false"
          colors="true"
          bootstrap="vendor/autoload.php"
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/.gitignore b/civicrm/vendor/xkerman/restricted-unserialize/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..f3dec2a43d75545fd60820112b1f65a8fa8ed22c
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/.gitignore
@@ -0,0 +1,2 @@
+vendor/
+report/
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/.scrutinizer.yml b/civicrm/vendor/xkerman/restricted-unserialize/.scrutinizer.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2560e0436335fafd801b0b86551e08e7ed66ab2c
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/.scrutinizer.yml
@@ -0,0 +1,26 @@
+checks:
+  php: true
+filter:
+  excluded_paths:
+    - bin/*
+    - generated/*
+    - test/*
+build:
+  environment:
+    php:
+      version: 7.3
+  nodes:
+    analysis: # https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#security-analysis
+      project_setup:
+        override: true
+      tests:
+        override:
+          - php-scrutinizer-run --enable-security-analysis
+    coverage:    # https://scrutinizer-ci.com/docs/build/code_coverage
+      tests:
+        override:
+          -
+            command: composer test
+            coverage:
+              file: report/coverage/clover.xml
+              format: php-clover
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/.travis.yml b/civicrm/vendor/xkerman/restricted-unserialize/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..7f1342089f34c7a2b248bbb3d27b296467d5fbd4
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/.travis.yml
@@ -0,0 +1,87 @@
+language: php
+sudo: required
+service:
+  - docker
+
+php: dummy                  # this is needed for allow_failures setting
+
+stages:
+  - name: test
+  - name: check dependencies
+    if: type = cron
+
+jobs:
+  allow_failures:
+    - php: nightly
+
+  exclude:
+    - php: dummy
+
+  include:
+    - &test
+      stage: test
+      php: nightly
+      sudo: false
+      env: USE_DOCKER=0 PHP_VERSION=nightly TEST=test
+      before_install:
+        - if [ $USE_DOCKER -eq 1 ]; then docker build -t xkerman/php-$PHP_VERSION -f docker/Dockerfile.$PHP_VERSION docker; fi
+      install:
+        - rm composer.lock
+        - if [ $USE_DOCKER -eq 0 ]; then composer install --no-interaction; fi
+        - if [ $USE_DOCKER -eq 1 ]; then curl -s -O https://getcomposer.org/composer.phar; fi
+      script:
+        - if [ $USE_DOCKER -eq 0 ]; then composer $TEST; fi
+        - if [ $USE_DOCKER -eq 1 -a $PHP_VERSION != '5.2' ]; then docker run -v $(pwd):/tmp  -w /tmp xkerman/php-$PHP_VERSION sh -c 'php -v && php composer.phar install --no-interaction && php composer.phar test-legacy'; fi
+        - if [ $USE_DOCKER -eq 1 -a $PHP_VERSION = '5.2' ]; then git diff --exit-code -- generated; fi
+        - if [ $USE_DOCKER -eq 1 -a $PHP_VERSION = '5.2' ]; then docker run -v $(pwd):/tmp  -w /tmp xkerman/php-$PHP_VERSION sh -c 'php -v && php /usr/local/php/phpunit/phpunit.php --configuration phpunit.php52.xml'; fi
+      after_success:
+        - if [ $USE_DOCKER -eq 1 ]; then sudo chown -R $(whoami) ./report; fi
+        - bash <(curl -s https://codecov.io/bash) -c -F $(echo $PHP_VERSION | sed -e 's/\./_/g')
+    - <<: *test
+      php: '7.4snapshot'
+      sudo: false
+      env: USE_DOCKER=0 PHP_VERSION=7.4 TEST=test
+    - <<: *test
+      php: '7.3'
+      sudo: false
+      env: USE_DOCKER=0 PHP_VERSION=7.3 TEST=test
+    - <<: *test
+      php: '7.2'
+      sudo: false
+      env: USE_DOCKER=0 PHP_VERSION=7.2 TEST=test
+    - <<: *test
+      php: '7.1'
+      sudo: false
+      env: USE_DOCKER=0 PHP_VERSION=7.1 TEST=test
+    - <<: *test
+      php: '7.0'
+      sudo: false
+      env: USE_DOCKER=0 PHP_VERSION=7.0 TEST=test
+    - <<: *test
+      php: '5.6'
+      sudo: false
+      env: USE_DOCKER=0 PHP_VERSION=5.6 TEST=test-legacy
+    - <<: *test
+      php: '5.6'
+      sudo: required
+      env: USE_DOCKER=1 PHP_VERSION=5.5
+    - <<: *test
+      php: '5.6'
+      sudo: required
+      env: USE_DOCKER=1 PHP_VERSION=5.4
+    - <<: *test
+      php: '5.6'
+      sudo: required
+      env: USE_DOCKER=1 PHP_VERSION=5.3
+    - <<: *test
+      php: '5.6'
+      sudo: required
+      env: USE_DOCKER=1 PHP_VERSION=5.2
+
+    - stage: check dependencies
+      php: 7.3
+      sudo: false
+      install:
+        - composer install
+      script:
+        - composer outdated --direct --strict
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/LICENSE b/civicrm/vendor/xkerman/restricted-unserialize/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..624d118620eadab820981046b43712352a3945f2
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/LICENSE
@@ -0,0 +1,9 @@
+The MIT License (MIT)
+
+Copyright © 2016-2019 xKerman
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/README.md b/civicrm/vendor/xkerman/restricted-unserialize/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..39051c872706fecca155f77385cfcfd0e9d18f14
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/README.md
@@ -0,0 +1,101 @@
+# restricted-unserialize
+
+[![Build Status](https://travis-ci.org/xKerman/restricted-unserialize.svg?branch=master)](https://travis-ci.org/xKerman/restricted-unserialize)
+[![codecov](https://codecov.io/gh/xKerman/restricted-unserialize/branch/master/graph/badge.svg)](https://codecov.io/gh/xKerman/restricted-unserialize)
+[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/xKerman/restricted-unserialize/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/xKerman/restricted-unserialize/?branch=master)
+[![Latest Stable Version](https://poser.pugx.org/xkerman/restricted-unserialize/v/stable)](https://packagist.org/packages/xkerman/restricted-unserialize)
+
+This composer package provides `unserialize` function that is safe for [PHP Obejct Injection (POI)](https://www.owasp.org/index.php/PHP_Object_Injection).
+
+If normal `unserialize` function is used for deserializing user input in your PHP application:
+
+1. Don't use this package, use `json_decode` in order to avoid PHP Object Injection
+2. If compatibility matters, first use this function and then try to use `json_decode` in the near future
+
+
+## Why POI-safe?
+
+`unserialize` function in this package only deserializes boolean, integer, floating point number, string, and array, and not deserializes object instance.
+Since any instances that has magic method for POP chain (such as `__destruct` or `__toString`) cannot instantiate, any plan to exploit POP chain just fails.
+( You can read detailed explanation of POP chain https://www.insomniasec.com/downloads/publications/Practical%20PHP%20Object%20Injection.pdf )
+
+
+
+## Installation
+
+```
+$ composer require xkerman/restricted-unserialize
+```
+
+
+## How to use
+
+if your PHP version > 5.5:
+
+```
+require 'path/to/vendor/autoload.php';
+
+use function xKerman\Restricted\unserialize;
+use xKerman\Restricted\UnserializeFailedException;
+
+try {
+    var_dump(unserialize($data));
+} catch (UnserializeFailedException $e) {
+    echo 'failed to unserialize';
+}
+```
+
+if your PHP version >= 5.3 and <= 5.5:
+
+```
+require 'path/to/vendor/autoload.php';
+
+use xKerman\Restricted;
+use xKerman\Restricted\UnserializeFailedException;
+
+try {
+    var_dump(Restricted\unserialize($data));
+} catch (UnserializeFailedException $e) {
+    echo 'failed to unserialize';
+}
+```
+
+if your PHP version is 5.2:
+
+```
+require_once 'path/to/generated/src/xKerman/Restricted/bootstrap.php';
+
+try {
+    var_dump(xKerman_Restricted_unserialize($data));
+} catch (xKerman_Restricted_UnserializeFailedException $e) {
+    echo 'failed to unserialize';
+}
+```
+
+## Related other packages
+
+### mikegarde/unserialize-fix
+
+[mikegarde/unserialize-fix](https://github.com/MikeGarde/unserialize-fix) package provides `\unserialize\fix` function that tries to use `unserialize` function first.  So the function is not POI-safe.
+
+
+### academe/serializeparser
+
+[academe/serializeparser](https://github.com/academe/SerializeParser) package privides `\Academe\SerializeParser\Parser::parse` method that is PHP-implemented `unserialize`, but doesn't deserialize object instances.  So the method seems that POI-safe, but there is no test.
+
+
+### jeroenvdheuve/serialization
+
+[jeroenvdheuve/serialization](https://github.com/jeroenvdheuvel/serialization) package provides `\jvdh\Serialization\Unserializer\unserialize` method that is also PHP-implemented `unserialize`, and doesn't deserialize object instance.  So the method seems that POI-safe.
+The method can deserialize serialized PHP references, which cannot deserialized by this (xkerman/restricted-unserilize) package.  By using PHP reference, we can create cyclic structure, but that makes migration to `json_decode` harder, since JSON doesn't support cyclic structure decode/encode.
+
+
+## Development
+
+To generate code for PHP 5.2, run `composer run generate`.
+Generated code will be saved under `genereated/` directory.
+
+
+## LICENSE
+
+MIT License
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/bin/generate.php b/civicrm/vendor/xkerman/restricted-unserialize/bin/generate.php
new file mode 100644
index 0000000000000000000000000000000000000000..6dcb130e439904343080d0029e7736398f45c0c2
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/bin/generate.php
@@ -0,0 +1,173 @@
+<?php
+// see: https://github.com/nikic/PHP-Parser/blob/master/doc/2_Usage_of_basic_components.markdown
+
+require __DIR__ . '/../vendor/autoload.php';
+
+use PhpParser\BuilderFactory;
+use PhpParser\Comment;
+use PhpParser\Node;
+use PhpParser\Node\Expr;
+use PhpParser\Node\Stmt;
+use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitor\NameResolver;
+use PhpParser\ParserFactory;
+use PhpParser\PrettyPrinter;
+
+class NameSpaceConverter extends \PhpParser\NodeVisitorAbstract
+{
+    public function leaveNode(Node $node) {
+        if ($node instanceof Node\Name) {
+            return new Node\Name(str_replace('\\', '_', $node->toString()));
+        }
+        if ($node instanceof Stmt\Class_ ||
+            $node instanceof Stmt\Interface_ ||
+            $node instanceof Stmt\Function_) {
+            $node->name = str_replace('\\', '_', $node->namespacedName->toString());
+        }
+        if ($node instanceof Stmt\Const_) {
+            foreach ($node->consts as $const) {
+                $const->name = str_replace('\\', '_', $const->namespacedName->toString());
+            }
+        }
+        if ($node instanceof Stmt\Namespace_) {
+            return $node->stmts;
+        }
+        if ($node instanceof Stmt\Use_) {
+            return NodeTraverser::REMOVE_NODE;
+        }
+        if ($node instanceof Stmt\ClassMethod) {
+            $doc = $node->getDocComment();
+            if (is_null($doc)) {
+                return $node;
+            }
+
+            $text = preg_replace('/\\\\xKerman\\\\Restricted\\\\([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff])/', 'xKerman_Restricted_$1', $doc->getText());
+            $text = preg_replace('/ @covers ::(?:[a-zA-Z_][a-zA-Z0-9_]*)/', '', $text);
+
+            $search = [
+                '\InvalidArgumentException',
+            ];
+            $replace = [
+                'InvalidArgumentException',
+            ];
+            $newDoc = new Comment\Doc(
+                str_replace($search, $replace, $text),
+                $doc->getLine(),
+                $doc->getFilePos()
+            );
+            $node->setAttribute('comments', [$newDoc]);
+        }
+        if ($node instanceof Expr\MethodCall) {
+            if ($node->name->toString() === 'expectException' && $node->args[0]->value instanceof Node\Scalar\String_) {
+                $newName = substr(str_replace('\\', '_', $node->args[0]->value->value), 1);
+                $node->args[0] = new Node\Arg(
+                    new Node\Scalar\String_($newName)
+                );
+            }
+        }
+    }
+}
+
+function convert($inDir, $outDir)
+{
+    $factory = new ParserFactory();
+    $parser = $factory->create(ParserFactory::ONLY_PHP5);
+    $traverser = new NodeTraverser();
+    $printer = new PrettyPrinter\Standard();
+
+    $traverser->addVisitor(new NameResolver());
+    $traverser->addVisitor(new NameSpaceConverter());
+
+    $files = new \RecursiveIteratorIterator(new RecursiveDirectoryIterator($inDir));
+    $files = new \RegexIterator($files, '/\.php\z/');
+
+    if (!file_exists($outDir)) {
+        mkdir($outDir, 0755, true);
+    }
+
+    foreach ($files as $file) {
+        try {
+            $code = file_get_contents($file);
+            $statements = $parser->parse($code);
+            $statements = $traverser->traverse($statements);
+            $sep = DIRECTORY_SEPARATOR;
+            file_put_contents(
+                "{$outDir}{$sep}{$file->getFileName()}",
+                $printer->prettyPrintFile($statements)
+            );
+        } catch (PhpParser\Error $e) {
+            echo 'Parsee Error: ', $e->getMessage();
+        }
+    }
+}
+
+function generateBootstrap($dir)
+{
+    $code = <<<'PHPCODE'
+<?php
+
+function xKerman_Restricted_bootstrap($classname)
+{
+    if (strpos($classname, 'xKerman_Restricted_') !== 0) {
+        return false;
+    }
+    $sep = DIRECTORY_SEPARATOR;
+    $namespace = explode('_', $classname);
+    $filename = array_pop($namespace);
+    $path = dirname(__FILE__) . "{$sep}{$filename}.php";
+    if (file_exists($path)) {
+        require_once $path;
+    }
+}
+
+spl_autoload_register('xKerman_Restricted_bootstrap');
+$sep = DIRECTORY_SEPARATOR;
+require_once dirname(__FILE__) . "{$sep}function.php";
+
+PHPCODE;
+
+    $sep = DIRECTORY_SEPARATOR;
+    file_put_contents(
+        "{$dir}{$sep}bootstrap.php",
+        $code
+    );
+}
+
+function generateBootstrapForTest($dir, $bootstrap)
+{
+    $code = <<<'PHPCODE'
+<?php
+
+function xKerman_Restricted_Test_bootstrap($classname)
+{
+    if (strpos($classname, 'xKerman_Restricted_Test') !== 0) {
+        return false;
+    }
+    $sep = DIRECTORY_SEPARATOR;
+    $namespace = explode('_', $classname);
+    $filename = array_pop($namespace);
+    $path = dirname(__FILE__) . "{$sep}{$filename}.php";
+    if (file_exists($path)) {
+        require_once $path;
+    }
+}
+
+$sep = DIRECTORY_SEPARATOR;
+require_once %s;
+spl_autoload_register('xKerman_Restricted_Test_bootstrap');
+
+PHPCODE;
+
+    $sep = DIRECTORY_SEPARATOR;
+    file_put_contents(
+        "{$dir}{$sep}bootstrap.test.php",
+        sprintf($code, $bootstrap),
+    );
+}
+
+// main
+convert(__DIR__ . '/../src', __DIR__ . '/../generated/src/xKerman/Restricted');
+convert(__DIR__ . '/../test', __DIR__ . '/../generated/test/xKerman/Restricted');
+generateBootstrap(__DIR__ . '/../generated/src/xKerman/Restricted');
+$bootstrap = 'dirname(dirname(dirname(dirname(__FILE__)))) . "{$sep}src{$sep}xKerman{$sep}Restricted{$sep}bootstrap.php"';
+generateBootstrapForTest(__DIR__ . '/../generated/test/xKerman/Restricted', $bootstrap);
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/composer.json b/civicrm/vendor/xkerman/restricted-unserialize/composer.json
new file mode 100644
index 0000000000000000000000000000000000000000..5f1e5b1ebce6d28a63c3ace72400171a31bc6143
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/composer.json
@@ -0,0 +1,52 @@
+{
+    "name": "xkerman/restricted-unserialize",
+    "description": "provide PHP Object Injection safe unserialize function",
+    "type": "library",
+    "keywords": ["unserialize", "deserialize", "PHP Object Injection"],
+    "require": {
+        "php": ">=5.2"
+    },
+    "require-dev": {
+        "phpmd/phpmd": "^2.6",
+        "phpunit/phpunit": "^4.8|^5.7|^6.5|^7.4|^8.2",
+        "sebastian/phpcpd": "^2.0|^3.0|^4.1",
+        "squizlabs/php_codesniffer": "^2.9|^3.4",
+        "nikic/php-parser": "^1.4|^3.0|^4.2"
+    },
+    "license": "MIT",
+    "authors": [
+        {
+            "name": "xKerman",
+            "email": "xKhorasan@gmail.com"
+        }
+    ],
+    "autoload": {
+        "files": ["src/function.php"],
+        "psr-4": {
+            "xKerman\\Restricted\\": "src"
+        }
+    },
+    "autoload-dev": {
+        "psr-4": {
+            "xKerman\\Restricted\\Test\\": "test"
+        }
+    },
+    "scripts": {
+        "test": [
+            "phpcs",
+            "phpmd src/ text ./phpmd.xml",
+            "phpcpd src/",
+            "phpdbg -qrr ./vendor/bin/phpunit"
+        ],
+        "test-legacy": [
+            "phpcs",
+            "phpmd src/ text ./phpmd.xml",
+            "phpcpd src/",
+            "phpunit"
+        ],
+        "generate": [
+            "rm -rf generated/",
+            "php bin/generate.php"
+        ]
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/composer.lock b/civicrm/vendor/xkerman/restricted-unserialize/composer.lock
new file mode 100644
index 0000000000000000000000000000000000000000..fe0a489f310d9dbdb38e0728f5aeaf1337341df0
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/composer.lock
@@ -0,0 +1,2415 @@
+{
+    "_readme": [
+        "This file locks the dependencies of your project to a known state",
+        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+        "This file is @generated automatically"
+    ],
+    "content-hash": "c0c4fec1b87e499dc5e6cec5eef8b410",
+    "packages": [],
+    "packages-dev": [
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "a2c590166b2133a4633738648b6b064edae0814a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a",
+                "reference": "a2c590166b2133a4633738648b6b064edae0814a",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "doctrine/coding-standard": "^6.0",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpbench/phpbench": "^0.13",
+                "phpstan/phpstan-phpunit": "^0.11",
+                "phpstan/phpstan-shim": "^0.11",
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2019-03-17T17:37:11+00:00"
+        },
+        {
+            "name": "myclabs/deep-copy",
+            "version": "1.9.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/myclabs/DeepCopy.git",
+                "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea",
+                "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "replace": {
+                "myclabs/deep-copy": "self.version"
+            },
+            "require-dev": {
+                "doctrine/collections": "^1.0",
+                "doctrine/common": "^2.6",
+                "phpunit/phpunit": "^7.1"
+            },
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "DeepCopy\\": "src/DeepCopy/"
+                },
+                "files": [
+                    "src/DeepCopy/deep_copy.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "description": "Create deep copies (clones) of your objects",
+            "keywords": [
+                "clone",
+                "copy",
+                "duplicate",
+                "object",
+                "object graph"
+            ],
+            "time": "2019-08-09T12:45:53+00:00"
+        },
+        {
+            "name": "nikic/php-parser",
+            "version": "v4.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/nikic/PHP-Parser.git",
+                "reference": "1bd73cc04c3843ad8d6b0bfc0956026a151fc420"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bd73cc04c3843ad8d6b0bfc0956026a151fc420",
+                "reference": "1bd73cc04c3843ad8d6b0bfc0956026a151fc420",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": ">=7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.5 || ^7.0"
+            },
+            "bin": [
+                "bin/php-parse"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.2-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "PhpParser\\": "lib/PhpParser"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Nikita Popov"
+                }
+            ],
+            "description": "A PHP parser written in PHP",
+            "keywords": [
+                "parser",
+                "php"
+            ],
+            "time": "2019-05-25T20:07:01+00:00"
+        },
+        {
+            "name": "pdepend/pdepend",
+            "version": "2.5.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/pdepend/pdepend.git",
+                "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/pdepend/pdepend/zipball/9daf26d0368d4a12bed1cacae1a9f3a6f0adf239",
+                "reference": "9daf26d0368d4a12bed1cacae1a9f3a6f0adf239",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.7",
+                "symfony/config": "^2.3.0|^3|^4",
+                "symfony/dependency-injection": "^2.3.0|^3|^4",
+                "symfony/filesystem": "^2.3.0|^3|^4"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.8|^5.7",
+                "squizlabs/php_codesniffer": "^2.0.0"
+            },
+            "bin": [
+                "src/bin/pdepend"
+            ],
+            "type": "library",
+            "autoload": {
+                "psr-4": {
+                    "PDepend\\": "src/main/php/PDepend"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "description": "Official version of pdepend to be handled with Composer",
+            "time": "2017-12-13T13:21:38+00:00"
+        },
+        {
+            "name": "phar-io/manifest",
+            "version": "1.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/manifest.git",
+                "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+                "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-phar": "*",
+                "phar-io/version": "^2.0",
+                "php": "^5.6 || ^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+            "time": "2018-07-08T19:23:20+00:00"
+        },
+        {
+            "name": "phar-io/version",
+            "version": "2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phar-io/version.git",
+                "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+                "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6 || ^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Heuer",
+                    "email": "sebastian@phpeople.de",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Library for handling version information and constraints",
+            "time": "2018-07-08T19:19:57+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-common",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+                "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.5"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.6"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Jaap van Otterdijk",
+                    "email": "opensource@ijaap.nl"
+                }
+            ],
+            "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+            "homepage": "http://www.phpdoc.org",
+            "keywords": [
+                "FQSEN",
+                "phpDocumentor",
+                "phpdoc",
+                "reflection",
+                "static analysis"
+            ],
+            "time": "2017-09-11T18:02:19+00:00"
+        },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "4.3.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c",
+                "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0",
+                "phpdocumentor/reflection-common": "^1.0.0",
+                "phpdocumentor/type-resolver": "^0.4.0",
+                "webmozart/assert": "^1.0"
+            },
+            "require-dev": {
+                "doctrine/instantiator": "~1.0.5",
+                "mockery/mockery": "^1.0",
+                "phpunit/phpunit": "^6.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                }
+            ],
+            "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+            "time": "2019-04-30T17:48:53+00:00"
+        },
+        {
+            "name": "phpdocumentor/type-resolver",
+            "version": "0.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/TypeResolver.git",
+                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
+                "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.5 || ^7.0",
+                "phpdocumentor/reflection-common": "^1.0"
+            },
+            "require-dev": {
+                "mockery/mockery": "^0.9.4",
+                "phpunit/phpunit": "^5.2||^4.8.24"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "phpDocumentor\\Reflection\\": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "me@mikevanriel.com"
+                }
+            ],
+            "time": "2017-07-14T14:27:02+00:00"
+        },
+        {
+            "name": "phpmd/phpmd",
+            "version": "2.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpmd/phpmd.git",
+                "reference": "a05a999c644f4bc9a204846017db7bb7809fbe4c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpmd/phpmd/zipball/a05a999c644f4bc9a204846017db7bb7809fbe4c",
+                "reference": "a05a999c644f4bc9a204846017db7bb7809fbe4c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-xml": "*",
+                "pdepend/pdepend": "^2.5",
+                "php": ">=5.3.9"
+            },
+            "require-dev": {
+                "gregwar/rst": "^1.0",
+                "mikey179/vfsstream": "^1.6.4",
+                "phpunit/phpunit": "^4.8.36 || ^5.7.27",
+                "squizlabs/php_codesniffer": "^2.0"
+            },
+            "bin": [
+                "src/bin/phpmd"
+            ],
+            "type": "library",
+            "autoload": {
+                "psr-0": {
+                    "PHPMD\\": "src/main/php"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Manuel Pichler",
+                    "role": "Project Founder",
+                    "email": "github@manuel-pichler.de",
+                    "homepage": "https://github.com/manuelpichler"
+                },
+                {
+                    "name": "Marc Würth",
+                    "role": "Project Maintainer",
+                    "email": "ravage@bluewin.ch",
+                    "homepage": "https://github.com/ravage84"
+                },
+                {
+                    "name": "Other contributors",
+                    "role": "Contributors",
+                    "homepage": "https://github.com/phpmd/phpmd/graphs/contributors"
+                }
+            ],
+            "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.",
+            "homepage": "https://phpmd.org/",
+            "keywords": [
+                "mess detection",
+                "mess detector",
+                "pdepend",
+                "phpmd",
+                "pmd"
+            ],
+            "time": "2019-07-30T21:13:32+00:00"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "1.8.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
+                "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "php": "^5.3|^7.0",
+                "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
+                "sebastian/comparator": "^1.1|^2.0|^3.0",
+                "sebastian/recursion-context": "^1.0|^2.0|^3.0"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "^2.5|^3.2",
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.8.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Prophecy\\": "src/Prophecy"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2019-06-13T12:50:23+00:00"
+        },
+        {
+            "name": "phpunit/php-code-coverage",
+            "version": "7.0.7",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+                "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7743bbcfff2a907e9ee4a25be13d0f8ec5e73800",
+                "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-xmlwriter": "*",
+                "php": "^7.2",
+                "phpunit/php-file-iterator": "^2.0.2",
+                "phpunit/php-text-template": "^1.2.1",
+                "phpunit/php-token-stream": "^3.1.0",
+                "sebastian/code-unit-reverse-lookup": "^1.0.1",
+                "sebastian/environment": "^4.2.2",
+                "sebastian/version": "^2.0.1",
+                "theseer/tokenizer": "^1.1.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.2.2"
+            },
+            "suggest": {
+                "ext-xdebug": "^2.7.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "7.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+            "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+            "keywords": [
+                "coverage",
+                "testing",
+                "xunit"
+            ],
+            "time": "2019-07-25T05:31:54+00:00"
+        },
+        {
+            "name": "phpunit/php-file-iterator",
+            "version": "2.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+                "reference": "050bedf145a257b1ff02746c31894800e5122946"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946",
+                "reference": "050bedf145a257b1ff02746c31894800e5122946",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+            "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+            "keywords": [
+                "filesystem",
+                "iterator"
+            ],
+            "time": "2018-09-13T20:33:42+00:00"
+        },
+        {
+            "name": "phpunit/php-text-template",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-text-template.git",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Simple template engine.",
+            "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+            "keywords": [
+                "template"
+            ],
+            "time": "2015-06-21T13:50:34+00:00"
+        },
+        {
+            "name": "phpunit/php-timer",
+            "version": "2.1.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-timer.git",
+                "reference": "1038454804406b0b5f5f520358e78c1c2f71501e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e",
+                "reference": "1038454804406b0b5f5f520358e78c1c2f71501e",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Utility class for timing",
+            "homepage": "https://github.com/sebastianbergmann/php-timer/",
+            "keywords": [
+                "timer"
+            ],
+            "time": "2019-06-07T04:22:29+00:00"
+        },
+        {
+            "name": "phpunit/php-token-stream",
+            "version": "3.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/php-token-stream.git",
+                "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a",
+                "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a",
+                "shasum": ""
+            },
+            "require": {
+                "ext-tokenizer": "*",
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Wrapper around PHP's tokenizer extension.",
+            "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
+            "keywords": [
+                "tokenizer"
+            ],
+            "time": "2019-07-25T05:29:42+00:00"
+        },
+        {
+            "name": "phpunit/phpunit",
+            "version": "8.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpunit.git",
+                "reference": "c319d08ebd31e137034c84ad7339054709491485"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c319d08ebd31e137034c84ad7339054709491485",
+                "reference": "c319d08ebd31e137034c84ad7339054709491485",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.2.0",
+                "ext-dom": "*",
+                "ext-json": "*",
+                "ext-libxml": "*",
+                "ext-mbstring": "*",
+                "ext-xml": "*",
+                "ext-xmlwriter": "*",
+                "myclabs/deep-copy": "^1.9.1",
+                "phar-io/manifest": "^1.0.3",
+                "phar-io/version": "^2.0.1",
+                "php": "^7.2",
+                "phpspec/prophecy": "^1.8.1",
+                "phpunit/php-code-coverage": "^7.0.7",
+                "phpunit/php-file-iterator": "^2.0.2",
+                "phpunit/php-text-template": "^1.2.1",
+                "phpunit/php-timer": "^2.1.2",
+                "sebastian/comparator": "^3.0.2",
+                "sebastian/diff": "^3.0.2",
+                "sebastian/environment": "^4.2.2",
+                "sebastian/exporter": "^3.1.0",
+                "sebastian/global-state": "^3.0.0",
+                "sebastian/object-enumerator": "^3.0.3",
+                "sebastian/resource-operations": "^2.0.1",
+                "sebastian/type": "^1.1.3",
+                "sebastian/version": "^2.0.1"
+            },
+            "require-dev": {
+                "ext-pdo": "*"
+            },
+            "suggest": {
+                "ext-soap": "*",
+                "ext-xdebug": "*",
+                "phpunit/php-invoker": "^2.0.0"
+            },
+            "bin": [
+                "phpunit"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "8.3-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "role": "lead",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "The PHP Unit Testing framework.",
+            "homepage": "https://phpunit.de/",
+            "keywords": [
+                "phpunit",
+                "testing",
+                "xunit"
+            ],
+            "time": "2019-08-03T15:41:47+00:00"
+        },
+        {
+            "name": "psr/container",
+            "version": "1.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/container.git",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Container\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "http://www.php-fig.org/"
+                }
+            ],
+            "description": "Common Container Interface (PHP FIG PSR-11)",
+            "homepage": "https://github.com/php-fig/container",
+            "keywords": [
+                "PSR-11",
+                "container",
+                "container-interface",
+                "container-interop",
+                "psr"
+            ],
+            "time": "2017-02-14T16:28:37+00:00"
+        },
+        {
+            "name": "sebastian/code-unit-reverse-lookup",
+            "version": "1.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+                "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+                "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.6 || ^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^5.7 || ^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Looks up which function or method a line of code belongs to",
+            "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+            "time": "2017-03-04T06:30:41+00:00"
+        },
+        {
+            "name": "sebastian/comparator",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
+                "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1",
+                "sebastian/diff": "^3.0",
+                "sebastian/exporter": "^3.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "https://github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2018-07-12T15:12:46+00:00"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "3.0.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+                "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.5 || ^8.0",
+                "symfony/process": "^2 || ^3.3 || ^4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "https://github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff",
+                "udiff",
+                "unidiff",
+                "unified diff"
+            ],
+            "time": "2019-02-04T06:01:07+00:00"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "4.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
+                "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^7.5"
+            },
+            "suggest": {
+                "ext-posix": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.2-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2019-05-05T09:05:15+00:00"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "3.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
+                "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0",
+                "sebastian/recursion-context": "^3.0"
+            },
+            "require-dev": {
+                "ext-mbstring": "*",
+                "phpunit/phpunit": "^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.1.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides the functionality to export PHP variables for visualization",
+            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "keywords": [
+                "export",
+                "exporter"
+            ],
+            "time": "2017-04-03T13:19:02+00:00"
+        },
+        {
+            "name": "sebastian/finder-facade",
+            "version": "1.2.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/finder-facade.git",
+                "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/finder-facade/zipball/4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f",
+                "reference": "4a3174709c2dc565fe5fb26fcf827f6a1fc7b09f",
+                "shasum": ""
+            },
+            "require": {
+                "symfony/finder": "~2.3|~3.0|~4.0",
+                "theseer/fdomdocument": "~1.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.",
+            "homepage": "https://github.com/sebastianbergmann/finder-facade",
+            "time": "2017-11-18T17:31:49+00:00"
+        },
+        {
+            "name": "sebastian/global-state",
+            "version": "3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/global-state.git",
+                "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4",
+                "reference": "edf8a461cf1d4005f19fb0b6b8b95a9f7fa0adc4",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2",
+                "sebastian/object-reflector": "^1.1.1",
+                "sebastian/recursion-context": "^3.0"
+            },
+            "require-dev": {
+                "ext-dom": "*",
+                "phpunit/phpunit": "^8.0"
+            },
+            "suggest": {
+                "ext-uopz": "*"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Snapshotting of global state",
+            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "keywords": [
+                "global state"
+            ],
+            "time": "2019-02-01T05:30:01+00:00"
+        },
+        {
+            "name": "sebastian/object-enumerator",
+            "version": "3.0.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+                "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+                "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0",
+                "sebastian/object-reflector": "^1.1.1",
+                "sebastian/recursion-context": "^3.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+            "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+            "time": "2017-08-03T12:35:26+00:00"
+        },
+        {
+            "name": "sebastian/object-reflector",
+            "version": "1.1.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/object-reflector.git",
+                "reference": "773f97c67f28de00d397be301821b06708fca0be"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
+                "reference": "773f97c67f28de00d397be301821b06708fca0be",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Allows reflection of object attributes, including inherited and non-public ones",
+            "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+            "time": "2017-03-29T09:07:27+00:00"
+        },
+        {
+            "name": "sebastian/phpcpd",
+            "version": "4.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/phpcpd.git",
+                "reference": "0d9afa762f2400de077b2192f4a9d127de0bb78e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpcpd/zipball/0d9afa762f2400de077b2192f4a9d127de0bb78e",
+                "reference": "0d9afa762f2400de077b2192f4a9d127de0bb78e",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "php": "^7.1",
+                "phpunit/php-timer": "^2.0",
+                "sebastian/finder-facade": "^1.1",
+                "sebastian/version": "^1.0|^2.0",
+                "symfony/console": "^2.7|^3.0|^4.0"
+            },
+            "bin": [
+                "phpcpd"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Copy/Paste Detector (CPD) for PHP code.",
+            "homepage": "https://github.com/sebastianbergmann/phpcpd",
+            "time": "2018-09-17T17:17:27+00:00"
+        },
+        {
+            "name": "sebastian/recursion-context",
+            "version": "3.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/recursion-context.git",
+                "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+                "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^6.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                },
+                {
+                    "name": "Adam Harvey",
+                    "email": "aharvey@php.net"
+                }
+            ],
+            "description": "Provides functionality to recursively process PHP variables",
+            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "time": "2017-03-03T06:23:57+00:00"
+        },
+        {
+            "name": "sebastian/resource-operations",
+            "version": "2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/resource-operations.git",
+                "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
+                "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides a list of PHP built-in functions that operate on resources",
+            "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+            "time": "2018-10-04T04:07:39+00:00"
+        },
+        {
+            "name": "sebastian/type",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/type.git",
+                "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3",
+                "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^8.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Collection of value objects that represent the types of the PHP type system",
+            "homepage": "https://github.com/sebastianbergmann/type",
+            "time": "2019-07-02T08:10:15+00:00"
+        },
+        {
+            "name": "sebastian/version",
+            "version": "2.0.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/version.git",
+                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
+                "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.6"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+            "homepage": "https://github.com/sebastianbergmann/version",
+            "time": "2016-10-03T07:35:21+00:00"
+        },
+        {
+            "name": "squizlabs/php_codesniffer",
+            "version": "3.4.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
+                "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8",
+                "reference": "b8a7362af1cc1aadb5bd36c3defc4dda2cf5f0a8",
+                "shasum": ""
+            },
+            "require": {
+                "ext-simplexml": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": ">=5.4.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
+            },
+            "bin": [
+                "bin/phpcs",
+                "bin/phpcbf"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Greg Sherwood",
+                    "role": "lead"
+                }
+            ],
+            "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.",
+            "homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
+            "keywords": [
+                "phpcs",
+                "standards"
+            ],
+            "time": "2019-04-10T23:49:02+00:00"
+        },
+        {
+            "name": "symfony/config",
+            "version": "v4.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/config.git",
+                "reference": "a17a2aea43950ce83a0603ed301bac362eb86870"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/config/zipball/a17a2aea43950ce83a0603ed301bac362eb86870",
+                "reference": "a17a2aea43950ce83a0603ed301bac362eb86870",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/filesystem": "~3.4|~4.0",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "conflict": {
+                "symfony/finder": "<3.4"
+            },
+            "require-dev": {
+                "symfony/dependency-injection": "~3.4|~4.0",
+                "symfony/event-dispatcher": "~3.4|~4.0",
+                "symfony/finder": "~3.4|~4.0",
+                "symfony/messenger": "~4.1",
+                "symfony/yaml": "~3.4|~4.0"
+            },
+            "suggest": {
+                "symfony/yaml": "To use the yaml reference dumper"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Config\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Config Component",
+            "homepage": "https://symfony.com",
+            "time": "2019-07-18T10:34:59+00:00"
+        },
+        {
+            "name": "symfony/console",
+            "version": "v4.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/console.git",
+                "reference": "8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/console/zipball/8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9",
+                "reference": "8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/polyfill-mbstring": "~1.0",
+                "symfony/polyfill-php73": "^1.8",
+                "symfony/service-contracts": "^1.1"
+            },
+            "conflict": {
+                "symfony/dependency-injection": "<3.4",
+                "symfony/event-dispatcher": "<4.3",
+                "symfony/process": "<3.3"
+            },
+            "provide": {
+                "psr/log-implementation": "1.0"
+            },
+            "require-dev": {
+                "psr/log": "~1.0",
+                "symfony/config": "~3.4|~4.0",
+                "symfony/dependency-injection": "~3.4|~4.0",
+                "symfony/event-dispatcher": "^4.3",
+                "symfony/lock": "~3.4|~4.0",
+                "symfony/process": "~3.4|~4.0",
+                "symfony/var-dumper": "^4.3"
+            },
+            "suggest": {
+                "psr/log": "For using the console logger",
+                "symfony/event-dispatcher": "",
+                "symfony/lock": "",
+                "symfony/process": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Console\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Console Component",
+            "homepage": "https://symfony.com",
+            "time": "2019-07-24T17:13:59+00:00"
+        },
+        {
+            "name": "symfony/dependency-injection",
+            "version": "v4.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/dependency-injection.git",
+                "reference": "9ad1b83d474ae17156f6914cb81ffe77aeac3a9b"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/9ad1b83d474ae17156f6914cb81ffe77aeac3a9b",
+                "reference": "9ad1b83d474ae17156f6914cb81ffe77aeac3a9b",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "psr/container": "^1.0",
+                "symfony/service-contracts": "^1.1.2"
+            },
+            "conflict": {
+                "symfony/config": "<4.3",
+                "symfony/finder": "<3.4",
+                "symfony/proxy-manager-bridge": "<3.4",
+                "symfony/yaml": "<3.4"
+            },
+            "provide": {
+                "psr/container-implementation": "1.0",
+                "symfony/service-implementation": "1.0"
+            },
+            "require-dev": {
+                "symfony/config": "^4.3",
+                "symfony/expression-language": "~3.4|~4.0",
+                "symfony/yaml": "~3.4|~4.0"
+            },
+            "suggest": {
+                "symfony/config": "",
+                "symfony/expression-language": "For using expressions in service container configuration",
+                "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
+                "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
+                "symfony/yaml": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\DependencyInjection\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony DependencyInjection Component",
+            "homepage": "https://symfony.com",
+            "time": "2019-07-26T07:03:43+00:00"
+        },
+        {
+            "name": "symfony/filesystem",
+            "version": "v4.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/filesystem.git",
+                "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/filesystem/zipball/b9896d034463ad6fd2bf17e2bf9418caecd6313d",
+                "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "symfony/polyfill-ctype": "~1.8"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Filesystem\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Filesystem Component",
+            "homepage": "https://symfony.com",
+            "time": "2019-06-23T08:51:25+00:00"
+        },
+        {
+            "name": "symfony/finder",
+            "version": "v4.3.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/finder.git",
+                "reference": "9638d41e3729459860bb96f6247ccb61faaa45f2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/finder/zipball/9638d41e3729459860bb96f6247ccb61faaa45f2",
+                "reference": "9638d41e3729459860bb96f6247ccb61faaa45f2",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Component\\Finder\\": ""
+                },
+                "exclude-from-classmap": [
+                    "/Tests/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony Finder Component",
+            "homepage": "https://symfony.com",
+            "time": "2019-06-28T13:16:30+00:00"
+        },
+        {
+            "name": "symfony/polyfill-ctype",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-ctype.git",
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
+                "reference": "550ebaac289296ce228a706d0867afc34687e3f4",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-ctype": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Ctype\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Gert de Pagter",
+                    "email": "BackEndTea@gmail.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for ctype functions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "ctype",
+                "polyfill",
+                "portable"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-mbstring",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-mbstring.git",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "suggest": {
+                "ext-mbstring": "For best performance"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Mbstring\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill for the Mbstring extension",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "mbstring",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/polyfill-php73",
+            "version": "v1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/polyfill-php73.git",
+                "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188",
+                "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.12-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Polyfill\\Php73\\": ""
+                },
+                "files": [
+                    "bootstrap.php"
+                ],
+                "classmap": [
+                    "Resources/stubs"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "compatibility",
+                "polyfill",
+                "portable",
+                "shim"
+            ],
+            "time": "2019-08-06T08:03:45+00:00"
+        },
+        {
+            "name": "symfony/service-contracts",
+            "version": "v1.1.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/service-contracts.git",
+                "reference": "f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d",
+                "reference": "f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1.3",
+                "psr/container": "^1.0"
+            },
+            "suggest": {
+                "symfony/service-implementation": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Symfony\\Contracts\\Service\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Nicolas Grekas",
+                    "email": "p@tchwork.com"
+                },
+                {
+                    "name": "Symfony Community",
+                    "homepage": "https://symfony.com/contributors"
+                }
+            ],
+            "description": "Generic abstractions related to writing services",
+            "homepage": "https://symfony.com",
+            "keywords": [
+                "abstractions",
+                "contracts",
+                "decoupling",
+                "interfaces",
+                "interoperability",
+                "standards"
+            ],
+            "time": "2019-06-13T11:15:36+00:00"
+        },
+        {
+            "name": "theseer/fdomdocument",
+            "version": "1.6.6",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theseer/fDOMDocument.git",
+                "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theseer/fDOMDocument/zipball/6e8203e40a32a9c770bcb62fe37e68b948da6dca",
+                "reference": "6e8203e40a32a9c770bcb62fe37e68b948da6dca",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "lib-libxml": "*",
+                "php": ">=5.3.3"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "lead"
+                }
+            ],
+            "description": "The classes contained within this repository extend the standard DOM to use exceptions at all occasions of errors instead of PHP warnings or notices. They also add various custom methods and shortcuts for convenience and to simplify the usage of DOM.",
+            "homepage": "https://github.com/theseer/fDOMDocument",
+            "time": "2017-06-30T11:53:12+00:00"
+        },
+        {
+            "name": "theseer/tokenizer",
+            "version": "1.1.3",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/theseer/tokenizer.git",
+                "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
+                "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-tokenizer": "*",
+                "ext-xmlwriter": "*",
+                "php": "^7.0"
+            },
+            "type": "library",
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Arne Blankerts",
+                    "email": "arne@blankerts.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+            "time": "2019-06-13T22:48:21+00:00"
+        },
+        {
+            "name": "webmozart/assert",
+            "version": "1.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/webmozart/assert.git",
+                "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9",
+                "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^5.3.3 || ^7.0",
+                "symfony/polyfill-ctype": "^1.8"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "^4.6",
+                "sebastian/version": "^1.0.1"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Webmozart\\Assert\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@gmail.com"
+                }
+            ],
+            "description": "Assertions to validate method input/output with nice error messages.",
+            "keywords": [
+                "assert",
+                "check",
+                "validate"
+            ],
+            "time": "2018-12-25T11:19:39+00:00"
+        }
+    ],
+    "aliases": [],
+    "minimum-stability": "stable",
+    "stability-flags": [],
+    "prefer-stable": false,
+    "prefer-lowest": false,
+    "platform": {
+        "php": ">=5.2"
+    },
+    "platform-dev": []
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.2 b/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.2
new file mode 100644
index 0000000000000000000000000000000000000000..7033ef7001b7500290a5924ef7946f1953a1bfc0
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.2
@@ -0,0 +1,26 @@
+FROM nyanpass/apache2.2-php5.2.17
+
+RUN echo "deb http://deb.debian.org/debian jessie main" > /etc/apt/sources.list && \
+    echo "deb http://security.debian.org jessie/updates main" >> /etc/apt/sources.list && \
+    apt-get update && \
+    apt-get install -y git unzip
+
+# see: http://qiita.com/dozo/items/d76c36e911059951f1b6
+RUN cd /usr/local && \
+    mkdir php && cd php && \
+    git clone git://github.com/sebastianbergmann/phpunit.git && \
+    git clone git://github.com/sebastianbergmann/php-file-iterator.git && \
+    git clone git://github.com/sebastianbergmann/php-code-coverage.git && \
+    git clone git://github.com/sebastianbergmann/php-text-template.git && \
+    git clone git://github.com/sebastianbergmann/php-timer.git && \
+    git clone git://github.com/sebastianbergmann/php-token-stream.git && \
+    git clone git://github.com/sebastianbergmann/phpunit-mock-objects.git && \
+    cd phpunit && git checkout 3.6.12 && cd .. && \
+    cd php-file-iterator && git checkout tags/1.3.2 && cd .. && \
+    cd php-code-coverage && git checkout 1.1 && cd .. && \
+    cd php-text-template && git checkout tags/1.1.1 && cd .. && \
+    cd php-timer && git checkout tags/1.0.3 && cd .. && \
+    cd php-token-stream && git checkout tags/1.1.4 && cd .. && \
+    cd phpunit-mock-objects && git checkout 1.1 && cd .. && \
+    echo 'date.timezone="UTC"' >> $PHP_INI && \
+    echo 'include_path=".:/usr/local/php/phpunit/:/usr/local/php/php-code-coverage/:/usr/local/php/php-file-iterator/:/usr/local/php/php-text-template/:/usr/local/php/php-timer:/usr/local/php/php-token-stream:/usr/local/php/phpunit-mock-objects/"' >> $PHP_INI
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.3 b/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.3
new file mode 100644
index 0000000000000000000000000000000000000000..cbd84b2e40e70d658547d1f29049072262e2aba4
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.3
@@ -0,0 +1,8 @@
+FROM mindk/php5.3.29-apache
+
+RUN echo "deb http://deb.debian.org/debian jessie main" > /etc/apt/sources.list && \
+    echo "deb http://security.debian.org jessie/updates main" >> /etc/apt/sources.list && \
+    apt-get update && \
+    apt-get install unzip && \
+    pecl install xdebug-2.2.7 && \
+    docker-php-ext-enable xdebug
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.4 b/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.4
new file mode 100644
index 0000000000000000000000000000000000000000..53766faf3c0a3023162a284a9d31d34d482a1092
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.4
@@ -0,0 +1,6 @@
+FROM inblank/php5.4-xdebug
+
+RUN echo "deb http://deb.debian.org/debian jessie main" > /etc/apt/sources.list && \
+    echo "deb http://security.debian.org jessie/updates main" >> /etc/apt/sources.list && \
+    apt-get update && \
+    apt-get install unzip
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.5 b/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.5
new file mode 100644
index 0000000000000000000000000000000000000000..e8c3a7ef9bf45ae93f6b565de7ea2a89be14ab42
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.5
@@ -0,0 +1,8 @@
+FROM nyanpass/php5.5:5.5-cli
+
+RUN echo "deb http://deb.debian.org/debian jessie main" > /etc/apt/sources.list && \
+    echo "deb http://security.debian.org jessie/updates main" >> /etc/apt/sources.list && \
+    apt-get update && \
+    apt-get install unzip && \
+    pecl install xdebug-2.5.5 && \
+    docker-php-ext-enable xdebug
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ArrayHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ArrayHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..77f75d7a95bedfb04693ccc89aafebbbc83fcee0
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ArrayHandler.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * Handler for PHP serialiezed array
+ */
+class xKerman_Restricted_ArrayHandler implements xKerman_Restricted_HandlerInterface
+{
+    /** @var ParserInterface $expressionParser parser for unserialize expression */
+    private $expressionParser;
+    /** @var integer */
+    const CLOSE_BRACE_LENGTH = 1;
+    /**
+     * constructor
+     *
+     * @param ParserInterface $expressionParser parser for unserialize expression
+     */
+    public function __construct(xKerman_Restricted_ParserInterface $expressionParser)
+    {
+        $this->expressionParser = $expressionParser;
+    }
+    /**
+     * parse given `$source` as PHP serialized array
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   array length
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(xKerman_Restricted_Source $source, $args)
+    {
+        $length = intval($args, 10);
+        $result = array();
+        for ($i = 0; $i < $length; ++$i) {
+            list($key, $source) = $this->parseKey($source);
+            list($value, $source) = $this->expressionParser->parse($source);
+            $result[$key] = $value;
+        }
+        $source->consume('}', self::CLOSE_BRACE_LENGTH);
+        return array($result, $source);
+    }
+    /**
+     * parse given `$source` as array key (s.t. integer|string)
+     *
+     * @param Source $source input
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    private function parseKey($source)
+    {
+        list($key, $source) = $this->expressionParser->parse($source);
+        if (!is_integer($key) && !is_string($key)) {
+            return $source->triggerError();
+        }
+        return array($key, $source);
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/BooleanHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/BooleanHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..8df47a425f8bae39702304c87c6c163c23c01f3e
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/BooleanHandler.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Handler for PHP serialized boolean
+ */
+class xKerman_Restricted_BooleanHandler implements xKerman_Restricted_HandlerInterface
+{
+    /**
+     * parse given `$source` as PHP serialized boolean
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   boolean information
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(xKerman_Restricted_Source $source, $args)
+    {
+        return array((bool) $args, $source);
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/EscapedStringHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/EscapedStringHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..8b47a61ed987db76475600a8cd9bd94169689fdb
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/EscapedStringHandler.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Handler for escaped string
+ */
+class xKerman_Restricted_EscapedStringHandler implements xKerman_Restricted_HandlerInterface
+{
+    /** @var integer */
+    const CLOSE_STRING_LENGTH = 2;
+    /**
+     * parse given `$source` as escaped string
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   string length
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(xKerman_Restricted_Source $source, $args)
+    {
+        $length = intval($args, 10);
+        $result = array();
+        for ($i = 0; $i < $length; ++$i) {
+            $char = $source->read(1);
+            if ($char !== '\\') {
+                $result[] = $char;
+                continue;
+            }
+            $hex = $source->match('/\\G([0-9a-fA-F]{2})/');
+            $result[] = chr(intval($hex[0], 16));
+        }
+        $source->consume('";', self::CLOSE_STRING_LENGTH);
+        return array(implode('', $result), $source);
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ExpressionParser.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ExpressionParser.php
new file mode 100644
index 0000000000000000000000000000000000000000..7a34ef21d08b8cf86df611ec3de3c3609c0994ff
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ExpressionParser.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Parser for serialized PHP values
+ */
+class xKerman_Restricted_ExpressionParser implements xKerman_Restricted_ParserInterface
+{
+    /** @var array $handlers handlers list to use */
+    private $handlers;
+    /**
+     * constructor
+     */
+    public function __construct()
+    {
+        $this->handlers = array('N' => new xKerman_Restricted_NullHandler(), 'b' => new xKerman_Restricted_BooleanHandler(), 'i' => new xKerman_Restricted_IntegerHandler(), 'd' => new xKerman_Restricted_FloatHandler(), 's' => new xKerman_Restricted_StringHandler(), 'S' => new xKerman_Restricted_EscapedStringHandler(), 'a' => new xKerman_Restricted_ArrayHandler($this));
+    }
+    /**
+     * parse given `$source` as PHP serialized value
+     *
+     * @param Source $source parser input
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function parse(xKerman_Restricted_Source $source)
+    {
+        $matches = $source->match('/\\G(?|
+            (s):([0-9]+):"
+            |(i):([+-]?[0-9]+);
+            |(a):([0-9]+):{
+            |(d):((?:
+                [+-]?(?:[0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+|[0-9]+)(?:[eE][+-]?[0-9]+)?)
+                |-?INF
+                |NAN);
+            |(b):([01]);
+            |(N);
+            |(S):([0-9]+):"
+        )/x');
+        $tag = $matches[0];
+        $args = isset($matches[1]) ? $matches[1] : null;
+        return $this->handlers[$tag]->handle($source, $args);
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/FloatHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/FloatHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..f00e000de136ebcd64bc94706ba8ffa548b81b33
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/FloatHandler.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Handler for PHP serialized float number
+ */
+class xKerman_Restricted_FloatHandler implements xKerman_Restricted_HandlerInterface
+{
+    /** @var array $mapping parser result mapping */
+    private $mapping;
+    /**
+     * constructor
+     */
+    public function __construct()
+    {
+        $this->mapping = array('INF' => INF, '-INF' => -INF, 'NAN' => NAN);
+    }
+    /**
+     * parse given `$source` as PHP serialized float number
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   float value
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(xKerman_Restricted_Source $source, $args)
+    {
+        if (array_key_exists($args, $this->mapping)) {
+            return array($this->mapping[$args], $source);
+        }
+        return array(floatval($args), $source);
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/HandlerInterface.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/HandlerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..48c3b74241f4e9dbf266f0ff2a8f361a4ccba4ad
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/HandlerInterface.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * Interface for Handler
+ */
+interface xKerman_Restricted_HandlerInterface
+{
+    /**
+     * parse given `$source`
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   information for parsing
+     * @return array parse result
+     * @throws UnserializeFailedException
+     */
+    public function handle(xKerman_Restricted_Source $source, $args);
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/IntegerHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/IntegerHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..7feda401f415319dc7d5ece7787688f0726787ad
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/IntegerHandler.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Handler for PHP serialized integer
+ */
+class xKerman_Restricted_IntegerHandler implements xKerman_Restricted_HandlerInterface
+{
+    /**
+     * parse given `$source` as PHP serialized integer
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   integer value
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(xKerman_Restricted_Source $source, $args)
+    {
+        return array(intval($args, 10), $source);
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/NullHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/NullHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..44eb911d58d27f32c6428fd6fa11591a01a5ee88
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/NullHandler.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Handler to parse PHP serialized null value
+ */
+class xKerman_Restricted_NullHandler implements xKerman_Restricted_HandlerInterface
+{
+    /**
+     * parse given `$source` as PHP serialized null value
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   null
+     * @return array parse result
+     * @throws UnserializeFailedException
+     */
+    public function handle(xKerman_Restricted_Source $source, $args)
+    {
+        return array($args, $source);
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ParserInterface.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ParserInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..37abac283fb86ad7fea134b11dc780ca2603fc96
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ParserInterface.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * Interface for Parser
+ */
+interface xKerman_Restricted_ParserInterface
+{
+    /**
+     * parse given `$source`
+     *
+     * @param Source $source parser input
+     * @return array parse result
+     * @throws UnserializeFailedException
+     */
+    public function parse(xKerman_Restricted_Source $source);
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/Source.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/Source.php
new file mode 100644
index 0000000000000000000000000000000000000000..6a00b76373941a0d6142d0f09e29e7858e8b88d7
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/Source.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Parser Input
+ */
+class xKerman_Restricted_Source
+{
+    /** @var string $str given string to deserialize */
+    private $str;
+    /** @var int $length given string length */
+    private $length;
+    /** @var int $current current position of parser */
+    private $current;
+    /**
+     * constructor
+     *
+     * @param string $str parser input
+     * @throws InvalidArgumentException
+     */
+    public function __construct($str)
+    {
+        if (!is_string($str)) {
+            throw new InvalidArgumentException('expected string, but got: ' . gettype($str));
+        }
+        $this->str = $str;
+        $this->length = strlen($str);
+        $this->current = 0;
+    }
+    /**
+     * throw error with currnt position
+     *
+     * @return void
+     * @throws UnserializeFailedException
+     */
+    public function triggerError()
+    {
+        $bytes = strlen($this->str);
+        throw new xKerman_Restricted_UnserializeFailedException("unserialize(): Error at offset {$this->current} of {$bytes} bytes");
+    }
+    /**
+     * consume given string if it is as expected
+     *
+     * @param string  $expected expected string
+     * @param integer $length   length of $expected
+     * @return void
+     * @throws UnserializeFailedException
+     */
+    public function consume($expected, $length)
+    {
+        if (strpos($this->str, $expected, $this->current) !== $this->current) {
+            return $this->triggerError();
+        }
+        $this->current += $length;
+    }
+    /**
+     * read givin length substring
+     *
+     * @param integer $length length to read
+     * @return string
+     * @throws UnserializeFailedException
+     */
+    public function read($length)
+    {
+        if ($length < 0) {
+            return $this->triggerError();
+        }
+        if ($this->current + $length > $this->length) {
+            return $this->triggerError();
+        }
+        $this->current += $length;
+        return substr($this->str, $this->current - $length, $length);
+    }
+    /**
+     * return matching string for given regexp
+     *
+     * @param string $regexp Regular Expression for expected substring
+     * @return array
+     */
+    public function match($regexp)
+    {
+        if (!preg_match($regexp, $this->str, $matches, 0, $this->current)) {
+            return $this->triggerError();
+        }
+        $this->current += strlen($matches[0]);
+        array_shift($matches);
+        return $matches;
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/StringHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/StringHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..9c74129a53407e76699cb28db275dd0506462bfb
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/StringHandler.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Handler class for parse serialized PHP stirng
+ */
+class xKerman_Restricted_StringHandler implements xKerman_Restricted_HandlerInterface
+{
+    /** @var integer */
+    const CLOSE_STRING_LENGTH = 2;
+    /**
+     * parse give `$source` as PHP serialized string
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   string length
+     * @return array parser result
+     * @throws UnserializeFailedException
+     */
+    public function handle(xKerman_Restricted_Source $source, $args)
+    {
+        $length = intval($args, 10);
+        $result = $source->read($length);
+        $source->consume('";', self::CLOSE_STRING_LENGTH);
+        return array($result, $source);
+    }
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/UnserializeFailedException.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/UnserializeFailedException.php
new file mode 100644
index 0000000000000000000000000000000000000000..73d3ce20efdf5974c5a0e0113891fb5d80863ff1
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/UnserializeFailedException.php
@@ -0,0 +1,8 @@
+<?php
+
+/**
+ * Exception that representes `unserialize` call failure
+ */
+class xKerman_Restricted_UnserializeFailedException extends Exception
+{
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/bootstrap.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/bootstrap.php
new file mode 100644
index 0000000000000000000000000000000000000000..c59dfa5d6b8d0767c4bb7e0bf1c0ff5d347a5029
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/bootstrap.php
@@ -0,0 +1,19 @@
+<?php
+
+function xKerman_Restricted_bootstrap($classname)
+{
+    if (strpos($classname, 'xKerman_Restricted_') !== 0) {
+        return false;
+    }
+    $sep = DIRECTORY_SEPARATOR;
+    $namespace = explode('_', $classname);
+    $filename = array_pop($namespace);
+    $path = dirname(__FILE__) . "{$sep}{$filename}.php";
+    if (file_exists($path)) {
+        require_once $path;
+    }
+}
+
+spl_autoload_register('xKerman_Restricted_bootstrap');
+$sep = DIRECTORY_SEPARATOR;
+require_once dirname(__FILE__) . "{$sep}function.php";
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/function.php b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/function.php
new file mode 100644
index 0000000000000000000000000000000000000000..7ef3b47161a777ba4acd59561baee469b0eb8192
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/function.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * parse serialized string and return result
+ *
+ * @param string $str serialized string
+ * @return mixed
+ * @throws UnserializeFailedException
+ */
+function xKerman_Restricted_unserialize($str)
+{
+    $source = new xKerman_Restricted_Source($str);
+    $parser = new xKerman_Restricted_ExpressionParser();
+    list($result, ) = $parser->parse($source);
+    return $result;
+}
\ No newline at end of file
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/phpcs.xml b/civicrm/vendor/xkerman/restricted-unserialize/phpcs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0e3e9f8d342e9ab5d068d245d7d603abb7c1d7e1
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/phpcs.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ruleset>
+  <rule ref="PSR2" />
+  <rule ref="PEAR.Commenting.FunctionComment">
+    <exclude-pattern>test/*</exclude-pattern>
+  </rule>
+  <file>src</file>
+  <file>test</file>
+</ruleset>
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/phpmd.xml b/civicrm/vendor/xkerman/restricted-unserialize/phpmd.xml
new file mode 100644
index 0000000000000000000000000000000000000000..02511ba095f75e56d8cf1241e02bf70295862621
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/phpmd.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<ruleset name="phpmd setting for this package"
+         xmlns="http://pmd.sf.net/ruleset/1.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
+                     http://pmd.sf.net/ruleset_xml_schema.xsd"
+         xsi:noNamespaceSchemaLocation="
+                     http://pmd.sf.net/ruleset_xml_schema.xsd">
+    <description>
+        My custom rule set that checks my code...
+    </description>
+    <rule ref="rulesets/cleancode.xml" />
+    <rule ref="rulesets/codesize.xml" />
+    <rule ref="rulesets/controversial.xml" />
+    <rule ref="rulesets/design.xml" />
+    <rule ref="rulesets/naming.xml" />
+    <rule ref="rulesets/unusedcode.xml" />
+</ruleset>
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/phpunit.php52.xml b/civicrm/vendor/xkerman/restricted-unserialize/phpunit.php52.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8d1fe035122b2b0b48297a4e6569b7df5aa11115
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/phpunit.php52.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit bootstrap="generated/test/xKerman/Restricted/bootstrap.test.php">
+  <testsuites>
+    <testsuite name="restricted-unserialize">
+      <directory>generated/test</directory>
+    </testsuite>
+  </testsuites>
+  <filter>
+    <whitelist processUncoveredFilesFromWhitelist="true">
+      <directory suffix=".php">generated/src</directory>
+      <exclude>
+        <directory suffix="bootstrap.php">generated/src/xKerman/Restricted</directory>
+        <!-- work around for https://github.com/sebastianbergmann/php-code-coverage/issues/102-->
+        <directory suffix="Interface.php">generated/src/xKerman/Restricted</directory>
+        <directory suffix="Exception.php">generated/src/xKerman/Restricted</directory>
+      </exclude>
+    </whitelist>
+  </filter>
+  <logging>
+    <log type="coverage-clover" target="report/coverage/clover.xml" />
+    <log type="coverage-html" target="report/coverage" />
+    <log type="coverage-text" target="php://stdout" showOnlySummary="true" />
+  </logging>
+  <php>
+    <ini name="error_reporting" value="-1" />
+  </php>
+</phpunit>
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/phpunit.xml b/civicrm/vendor/xkerman/restricted-unserialize/phpunit.xml
new file mode 100644
index 0000000000000000000000000000000000000000..031b7cbefa5947d47f42f2b95b85d93fdd6d11ac
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/phpunit.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit>
+  <testsuites>
+    <testsuite name="restricted-unserialize">
+      <directory>test</directory>
+    </testsuite>
+  </testsuites>
+  <filter>
+    <whitelist processUncoveredFilesFromWhitelist="true">
+      <directory suffix=".php">src</directory>
+    </whitelist>
+  </filter>
+  <logging>
+    <log type="coverage-clover" target="report/coverage/clover.xml" />
+    <log type="coverage-html" target="report/coverage" />
+    <log type="coverage-text" target="php://stdout" showOnlySummary="true" />
+  </logging>
+  <php>
+    <ini name="error_reporting" value="-1" />
+  </php>
+</phpunit>
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/ArrayHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/src/ArrayHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c5e8325bc81c2d341a37bd760772c85cc9ea265
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/ArrayHandler.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * handler for PHP serialized array
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Handler for PHP serialiezed array
+ */
+class ArrayHandler implements HandlerInterface
+{
+    /** @var ParserInterface $expressionParser parser for unserialize expression */
+    private $expressionParser;
+
+    /** @var integer */
+    const CLOSE_BRACE_LENGTH = 1;
+
+    /**
+     * constructor
+     *
+     * @param ParserInterface $expressionParser parser for unserialize expression
+     */
+    public function __construct(ParserInterface $expressionParser)
+    {
+        $this->expressionParser = $expressionParser;
+    }
+
+    /**
+     * parse given `$source` as PHP serialized array
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   array length
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(Source $source, $args)
+    {
+        $length = intval($args, 10);
+
+        $result = array();
+        for ($i = 0; $i < $length; ++$i) {
+            list($key, $source) = $this->parseKey($source);
+            list($value, $source) = $this->expressionParser->parse($source);
+            $result[$key] = $value;
+        }
+
+        $source->consume('}', self::CLOSE_BRACE_LENGTH);
+        return array($result, $source);
+    }
+
+    /**
+     * parse given `$source` as array key (s.t. integer|string)
+     *
+     * @param Source $source input
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    private function parseKey($source)
+    {
+        list($key, $source) = $this->expressionParser->parse($source);
+        if (!is_integer($key) && !is_string($key)) {
+            return $source->triggerError();
+        }
+        return array($key, $source);
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/BooleanHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/src/BooleanHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..049eec3088def633b682e52fd8d0fcf02632c443
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/BooleanHandler.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * handler for PHP serialized boolean
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Handler for PHP serialized boolean
+ */
+class BooleanHandler implements HandlerInterface
+{
+    /**
+     * parse given `$source` as PHP serialized boolean
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   boolean information
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(Source $source, $args)
+    {
+        return array((boolean)$args, $source);
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/EscapedStringHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/src/EscapedStringHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..2e4074133637346d85f18a21ad72d5dd6a54f05e
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/EscapedStringHandler.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * handler for escaped string
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Handler for escaped string
+ */
+class EscapedStringHandler implements HandlerInterface
+{
+    /** @var integer */
+    const CLOSE_STRING_LENGTH = 2;
+
+    /**
+     * parse given `$source` as escaped string
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   string length
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(Source $source, $args)
+    {
+        $length = intval($args, 10);
+        $result = array();
+        for ($i = 0; $i < $length; ++$i) {
+            $char = $source->read(1);
+            if ($char !== '\\') {
+                $result[] = $char;
+                continue;
+            }
+            $hex = $source->match('/\G([0-9a-fA-F]{2})/');
+            $result[] = chr(intval($hex[0], 16));
+        }
+        $source->consume('";', self::CLOSE_STRING_LENGTH);
+        return array(implode('', $result), $source);
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/ExpressionParser.php b/civicrm/vendor/xkerman/restricted-unserialize/src/ExpressionParser.php
new file mode 100644
index 0000000000000000000000000000000000000000..186caca9fc6dee2922cfe5e9a41a116f9dba5269
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/ExpressionParser.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * parser for serialized expression
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Parser for serialized PHP values
+ */
+class ExpressionParser implements ParserInterface
+{
+    /** @var array $handlers handlers list to use */
+    private $handlers;
+
+    /**
+     * constructor
+     */
+    public function __construct()
+    {
+        $this->handlers = array(
+            'N' => new NullHandler(),
+            'b' => new BooleanHandler(),
+            'i' => new IntegerHandler(),
+            'd' => new FloatHandler(),
+            's' => new StringHandler(),
+            'S' => new EscapedStringHandler(),
+            'a' => new ArrayHandler($this),
+        );
+    }
+
+    /**
+     * parse given `$source` as PHP serialized value
+     *
+     * @param Source $source parser input
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function parse(Source $source)
+    {
+        $matches = $source->match('/\G(?|
+            (s):([0-9]+):"
+            |(i):([+-]?[0-9]+);
+            |(a):([0-9]+):{
+            |(d):((?:
+                [+-]?(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+|[0-9]+)(?:[eE][+-]?[0-9]+)?)
+                |-?INF
+                |NAN);
+            |(b):([01]);
+            |(N);
+            |(S):([0-9]+):"
+        )/x');
+        $tag = $matches[0];
+        $args = isset($matches[1]) ? $matches[1] : null;
+        return $this->handlers[$tag]->handle($source, $args);
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/FloatHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/src/FloatHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..0facd988844a26e1ce2fdabbc725e15cc0476162
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/FloatHandler.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * handler for PHP serialized float number
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Handler for PHP serialized float number
+ */
+class FloatHandler implements HandlerInterface
+{
+    /** @var array $mapping parser result mapping */
+    private $mapping;
+
+    /**
+     * constructor
+     */
+    public function __construct()
+    {
+        $this->mapping = array(
+            'INF'  => INF,
+            '-INF' => -INF,
+            'NAN'  => NAN,
+        );
+    }
+
+    /**
+     * parse given `$source` as PHP serialized float number
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   float value
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(Source $source, $args)
+    {
+        if (array_key_exists($args, $this->mapping)) {
+            return array($this->mapping[$args], $source);
+        }
+        return array(floatval($args), $source);
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/HandlerInterface.php b/civicrm/vendor/xkerman/restricted-unserialize/src/HandlerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..d356cb908b3a397f3088a120e2da37f10baced33
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/HandlerInterface.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * provide interface for Handler
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Interface for Handler
+ */
+interface HandlerInterface
+{
+    /**
+     * parse given `$source`
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   information for parsing
+     * @return array parse result
+     * @throws UnserializeFailedException
+     */
+    public function handle(Source $source, $args);
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/IntegerHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/src/IntegerHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..efeae2f83198b76dba958bcb0e1d4bb99c9d6065
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/IntegerHandler.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * handler for PHP serialized integer
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Handler for PHP serialized integer
+ */
+class IntegerHandler implements HandlerInterface
+{
+    /**
+     * parse given `$source` as PHP serialized integer
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   integer value
+     * @return array
+     * @throws UnserializeFailedException
+     */
+    public function handle(Source $source, $args)
+    {
+        return array(intval($args, 10), $source);
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/NullHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/src/NullHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..f3598377540ca28c84a7cede4c0359d70282ef12
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/NullHandler.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * handler for PHP null value
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Handler to parse PHP serialized null value
+ */
+class NullHandler implements HandlerInterface
+{
+    /**
+     * parse given `$source` as PHP serialized null value
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   null
+     * @return array parse result
+     * @throws UnserializeFailedException
+     */
+    public function handle(Source $source, $args)
+    {
+        return array($args, $source);
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/ParserInterface.php b/civicrm/vendor/xkerman/restricted-unserialize/src/ParserInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c110559793a4f95ed32aefc93100d3fca1b3c7f
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/ParserInterface.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * provide interface for Parser
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Interface for Parser
+ */
+interface ParserInterface
+{
+    /**
+     * parse given `$source`
+     *
+     * @param Source $source parser input
+     * @return array parse result
+     * @throws UnserializeFailedException
+     */
+    public function parse(Source $source);
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/Source.php b/civicrm/vendor/xkerman/restricted-unserialize/src/Source.php
new file mode 100644
index 0000000000000000000000000000000000000000..50c1ae727b8629b8ddb866401aefbe1313b341c5
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/Source.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ * Input for parser
+ */
+namespace xKerman\Restricted;
+
+use InvalidArgumentException;
+
+/**
+ * Parser Input
+ */
+class Source
+{
+    /** @var string $str given string to deserialize */
+    private $str;
+
+    /** @var int $length given string length */
+    private $length;
+
+    /** @var int $current current position of parser */
+    private $current;
+
+    /**
+     * constructor
+     *
+     * @param string $str parser input
+     * @throws \InvalidArgumentException
+     */
+    public function __construct($str)
+    {
+        if (!is_string($str)) {
+            throw new InvalidArgumentException('expected string, but got: ' . gettype($str));
+        }
+        $this->str = $str;
+        $this->length = strlen($str);
+        $this->current = 0;
+    }
+
+    /**
+     * throw error with currnt position
+     *
+     * @return void
+     * @throws UnserializeFailedException
+     */
+    public function triggerError()
+    {
+        $bytes = strlen($this->str);
+        throw new UnserializeFailedException("unserialize(): Error at offset {$this->current} of {$bytes} bytes");
+    }
+
+    /**
+     * consume given string if it is as expected
+     *
+     * @param string  $expected expected string
+     * @param integer $length   length of $expected
+     * @return void
+     * @throws UnserializeFailedException
+     */
+    public function consume($expected, $length)
+    {
+        if (strpos($this->str, $expected, $this->current) !== $this->current) {
+            return $this->triggerError();
+        }
+        $this->current += $length;
+    }
+
+    /**
+     * read givin length substring
+     *
+     * @param integer $length length to read
+     * @return string
+     * @throws UnserializeFailedException
+     */
+    public function read($length)
+    {
+        if ($length < 0) {
+            return $this->triggerError();
+        }
+        if ($this->current + $length > $this->length) {
+            return $this->triggerError();
+        }
+
+        $this->current += $length;
+        return substr($this->str, $this->current - $length, $length);
+    }
+
+    /**
+     * return matching string for given regexp
+     *
+     * @param string $regexp Regular Expression for expected substring
+     * @return array
+     */
+    public function match($regexp)
+    {
+        if (!preg_match($regexp, $this->str, $matches, 0, $this->current)) {
+            return $this->triggerError();
+        }
+
+        $this->current += strlen($matches[0]);
+        array_shift($matches);
+        return $matches;
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/StringHandler.php b/civicrm/vendor/xkerman/restricted-unserialize/src/StringHandler.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d9c48690e10072847f8b3a68ef62605f7900d5a
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/StringHandler.php
@@ -0,0 +1,31 @@
+<?php
+/**
+ * handler for serialized string
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Handler class for parse serialized PHP stirng
+ */
+class StringHandler implements HandlerInterface
+{
+    /** @var integer */
+    const CLOSE_STRING_LENGTH = 2;
+
+    /**
+     * parse give `$source` as PHP serialized string
+     *
+     * @param Source      $source parser input
+     * @param string|null $args   string length
+     * @return array parser result
+     * @throws UnserializeFailedException
+     */
+    public function handle(Source $source, $args)
+    {
+        $length = intval($args, 10);
+        $result = $source->read($length);
+        $source->consume('";', self::CLOSE_STRING_LENGTH);
+
+        return array($result, $source);
+    }
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/UnserializeFailedException.php b/civicrm/vendor/xkerman/restricted-unserialize/src/UnserializeFailedException.php
new file mode 100644
index 0000000000000000000000000000000000000000..31a9ebc5059cb1adadf98c450f660b7e531992d1
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/UnserializeFailedException.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * Exception for unserialize failure
+ */
+namespace xKerman\Restricted;
+
+/**
+ * Exception that representes `unserialize` call failure
+ */
+class UnserializeFailedException extends \Exception
+{
+}
diff --git a/civicrm/vendor/xkerman/restricted-unserialize/src/function.php b/civicrm/vendor/xkerman/restricted-unserialize/src/function.php
new file mode 100644
index 0000000000000000000000000000000000000000..57bb67cad64354f9796aca27a3fdbd269b3cab5c
--- /dev/null
+++ b/civicrm/vendor/xkerman/restricted-unserialize/src/function.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * provide `unserialize` function that is safe for PHP Object Injection
+ */
+namespace xKerman\Restricted;
+
+/**
+ * parse serialized string and return result
+ *
+ * @param string $str serialized string
+ * @return mixed
+ * @throws UnserializeFailedException
+ */
+function unserialize($str)
+{
+    $source = new Source($str);
+    $parser = new ExpressionParser();
+    list($result,) = $parser->parse($source);
+    return $result;
+}
diff --git a/civicrm/xml/version.xml b/civicrm/xml/version.xml
index 87a13c4530142d7976544defa59a9c52a0185968..5c2f7e8ff42931d1cb318e092f1bbd30736903fc 100644
--- a/civicrm/xml/version.xml
+++ b/civicrm/xml/version.xml
@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="iso-8859-1" ?>
 <version>
-  <version_no>5.19.1</version_no>
+  <version_no>5.19.2</version_no>
 </version>
diff --git a/wp-rest/.editorconfig b/wp-rest/.editorconfig
deleted file mode 100644
index 09dc3747d33a42560841336306fc106d96b39a47..0000000000000000000000000000000000000000
--- a/wp-rest/.editorconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-# EditorConfig is awesome: https://editorconfig.org
-
-# Not top-most EditorConfig file
-root = false
-
-# Tab indentation
-[*.php]
-indent_style = tab
-indent_size = 4
diff --git a/wp-rest/Autoloader.php b/wp-rest/Autoloader.php
deleted file mode 100644
index dfa95f8a0219f6d8126f0b77110135049c693690..0000000000000000000000000000000000000000
--- a/wp-rest/Autoloader.php
+++ /dev/null
@@ -1,115 +0,0 @@
-<?php
-/**
- * Autoloader class.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST;
-
-class Autoloader {
-
-	/**
-	 * Instance.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	private static $instance = null;
-
-	/**
-	 * Namespace.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	private $namespace = 'CiviCRM_WP_REST';
-
-	/**
-	 * Autoloader directory sources.
-	 *
-	 * @since 0.1
-	 * @var array
-	 */
-	private static $source_directories = [];
-
-	/**
-	 * Constructor.
-	 *
-	 * @since 0.1
-	 */
-	private function __construct() {
-
-		$this->register_autoloader();
-
-	}
-
-	/**
-	 * Creates an instance of this class.
-	 *
-	 * @since 0.1
-	 */
-	private static function instance() {
-
-		if ( ! self::$instance ) self::$instance = new self;
-
-	}
-
-	/**
-	 * Adds a directory source.
-	 *
-	 * @since 0.1
-	 * @param string $source The source path
-	 */
-	public static function add_source( string $source_path ) {
-
-		// make sure we have an instance
-		self::instance();
-
-		if ( ! is_readable( trailingslashit( $source_path ) ) )
-			return \WP_Error( 'civicrm_wp_rest_error', sprintf( __( 'The source %s is not readable.', 'civicrm' ), $source ) );
-
-		self::$source_directories[] = $source_path;
-
-	}
-
-	/**
-	 * Registers the autoloader.
-	 *
-	 * @since 0.1
-	 * @return bool Wehather the autoloader has been registered or not
-	 */
-	private function register_autoloader() {
-
-		return spl_autoload_register( [ $this, 'autoload' ] );
-
-	}
-
-	/**
-	 * Loads the classes.
-	 *
-	 * @since 0.1
-	 * @param string $class_name The class name to load
-	 */
-	private function autoload( $class_name ) {
-
-		if ( false === strpos( $class_name, $this->namespace ) ) return;
-
-		$parts = explode( '\\', $class_name );
-
-		// remove namespace and join class path
-		$class_path = str_replace( '_', '-', implode( DIRECTORY_SEPARATOR, array_slice( $parts, 1 ) ) );
-
-		array_map( function( $source_path ) use ( $class_path ) {
-
-			$path = $source_path . $class_path . '.php';
-
-			if ( ! file_exists( $path ) ) return;
-
-			require $path;
-
-		}, static::$source_directories );
-
-	}
-
-}
diff --git a/wp-rest/Civi/Mailing-Hooks.php b/wp-rest/Civi/Mailing-Hooks.php
deleted file mode 100644
index 7113088b3ba4f868b6ad0ed286af3c205979b3f5..0000000000000000000000000000000000000000
--- a/wp-rest/Civi/Mailing-Hooks.php
+++ /dev/null
@@ -1,136 +0,0 @@
-<?php
-/**
- * CiviCRM Mailing_Hooks class.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Civi;
-
-class Mailing_Hooks {
-
-	/**
-	 * Mailing Url endpoint.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	public $url_endpoint;
-
-	/**
-	 * Mailing Open endpoint.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	public $open_endpoint;
-
-	/**
-	 * Constructor.
-	 *
-	 * @since 0.1
-	 */
-	public function __construct() {
-
-		$this->url_endpoint = rest_url( 'civicrm/v3/url' );
-
-		$this->open_endpoint = rest_url( 'civicrm/v3/open' );
-
-	}
-
-	/**
-	 * Register hooks.
-	 *
-	 * @since 0.1
-	 */
-	public function register_hooks() {
-
-		add_filter( 'civicrm_alterMailParams', [ $this, 'do_mailing_urls' ], 10, 2 );
-
-	}
-
-	/**
-	 * Filters the mailing html and replaces calls to 'extern/url.php' and
-	 * 'extern/open.php' with their REST counterparts 'civicrm/v3/url' and 'civicrm/v3/open'.
-	 *
-	 * @uses 'civicrm_alterMailParams'
-	 *
-	 * @since 0.1
-	 * @param array &$params Mail params
-	 * @param string $context The Context
-	 * @return array $params The filtered Mail params
-	 */
-	public function do_mailing_urls( &$params, $context ) {
-
-		if ( $context == 'civimail' ) {
-
-			$params['html'] = $this->replace_html_mailing_tracking_urls( $params['html'] );
-
-			$params['text'] = $this->replace_text_mailing_tracking_urls( $params['text'] );
-
-		}
-
-		return $params;
-
-	}
-
-	/**
-	 * Replace html mailing tracking urls.
-	 *
-	 * @since 0.1
-	 * @param string $contnet The mailing content
-	 * @return string $content The mailing content
-	 */
-	public function replace_html_mailing_tracking_urls( string $content ) {
-
-		$doc = \phpQuery::newDocument( $content );
-
-		foreach ( $doc[ '[href*="civicrm/extern/url.php"], [src*="civicrm/extern/open.php"]' ] as $element ) {
-
-			$href = pq( $element )->attr( 'href' );
-			$src = pq( $element )->attr( 'src' );
-
-			// replace extern/url
-			if ( strpos( $href, 'civicrm/extern/url.php' ) )	{
-
-				$query_string = strstr( $href, '?' );
-				pq( $element )->attr( 'href', $this->url_endpoint . $query_string );
-
-			}
-
-			// replace extern/open
-			if ( strpos( $src, 'civicrm/extern/open.php' ) ) {
-
-				$query_string = strstr( $src, '?' );
-				pq( $element )->attr( 'src', $this->open_endpoint . $query_string );
-
-			}
-
-			unset( $href, $src, $query_string );
-
-		}
-
-		return $doc->html();
-
-	}
-
-	/**
-	 * Replace text mailing tracking urls.
-	 *
-	 * @since 0.1
-	 * @param string $contnet The mailing content
-	 * @return string $content The mailing content
-	 */
-	public function replace_text_mailing_tracking_urls( string $content ) {
-
-		// replace extern url
-		$content = preg_replace( '/http.*civicrm\/extern\/url\.php/i', $this->url_endpoint, $content );
-
-		// replace open url
-		$content = preg_replace( '/http.*civicrm\/extern\/open\.php/i', $this->open_endpoint, $content );
-
-		return $content;
-
-	}
-
-}
diff --git a/wp-rest/Controller/AuthorizeIPN.php b/wp-rest/Controller/AuthorizeIPN.php
deleted file mode 100644
index 4cd9da9a97786f3176f55ec59c2528a77b774554..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/AuthorizeIPN.php
+++ /dev/null
@@ -1,123 +0,0 @@
-<?php
-/**
- * AuthorizeIPN controller class.
- *
- * Replacement for CiviCRM's 'extern/authorizeIPN.php'.
- *
- * @see https://docs.civicrm.org/sysadmin/en/latest/setup/payment-processors/authorize-net/#shell-script-testing-method
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class AuthorizeIPN extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'authorizeIPN';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::ALLMETHODS,
-				'callback' => [ $this, 'get_item' ]
-			]
-		] );
-
-	}
-
-	/**
-	 * Get items.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_item( $request ) {
-
-		/**
-		 * Filter request params.
-		 *
-		 * @since 0.1
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$params = apply_filters( 'civi_wp_rest/controller/authorizeIPN/params', $request->get_params(), $request );
-
-		$authorize_IPN = new \CRM_Core_Payment_AuthorizeNetIPN( $params );
-
-		// log notification
-		\Civi::log()->alert( 'payment_notification processor_name=AuthNet', $params );
-
-		/**
-		 * Filter AuthorizeIPN object.
-		 *
-		 * @param CRM_Core_Payment_AuthorizeNetIPN $authorize_IPN
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$authorize_IPN = apply_filters( 'civi_wp_rest/controller/authorizeIPN/instance', $authorize_IPN, $params, $request );
-
-		try {
-
-			if ( ! method_exists( $authorize_IPN, 'main' ) || ! $this->instance_of_crm_base_ipn( $authorize_IPN ) )
-				return $this->civi_rest_error( sprintf( __( '%s must implement a "main" method.', 'civicrm' ), get_class( $authorize_IPN ) ) );
-
-			$result = $authorize_IPN->main();
-
-		} catch ( \CRM_Core_Exception $e ) {
-
-			\Civi::log()->error( $e->getMessage() );
-			\Civi::log()->error( 'error data ', [ 'data' => $e->getErrorData() ] );
-			\Civi::log()->error( 'REQUEST ', [ 'params' => $params ] );
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		}
-
-		return rest_ensure_response( $result );
-
-	}
-
-	/**
-	 * Checks whether object is an instance of CRM_Core_Payment_AuthorizeNetIPN or CRM_Core_Payment_BaseIPN.
-	 *
-	 * Needed because the instance is being filtered through 'civi_wp_rest/controller/authorizeIPN/instance'.
-	 *
-	 * @since 0.1
-	 * @param CRM_Core_Payment_AuthorizeNetIPN|CRM_Core_Payment_BaseIPN $object
-	 * @return bool
-	 */
-	public function instance_of_crm_base_ipn( $object ) {
-
-		return $object instanceof \CRM_Core_Payment_BaseIPN || $object instanceof \CRM_Core_Payment_AuthorizeNetIPN;
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {}
-
-}
diff --git a/wp-rest/Controller/Base.php b/wp-rest/Controller/Base.php
deleted file mode 100644
index 7546377e9e0973103beeaf4fff5e9789df04070c..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/Base.php
+++ /dev/null
@@ -1,107 +0,0 @@
-<?php
-/**
- * Base controller class.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-use CiviCRM_WP_REST\Endpoint\Endpoint_Interface;
-
-abstract class Base extends \WP_REST_Controller implements Endpoint_Interface {
-
-	/**
-	 * Route namespace.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $namespace = 'civicrm/v3';
-
-	/**
-	 * Gets the endpoint namespace.
-	 *
-	 * @since 0.1
-	 * @return string $namespace
-	 */
-	public function get_namespace() {
-
-		return $this->namespace;
-
-	}
-
-	/**
-	 * Gets the rest base route.
-	 *
-	 * @since 0.1
-	 * @return string $rest_base
-	 */
-	public function get_rest_base() {
-
-		return '/' . $this->rest_base;
-
-	}
-
-	/**
-	 * Retrieves the endpoint ie. '/civicrm/v3/rest'.
-	 *
-	 * @since 0.1
-	 * @return string $rest_base
-	 */
-	public function get_endpoint() {
-
-		return '/' . $this->get_namespace() . $this->get_rest_base();
-
-	}
-
-	/**
-	 * Checks whether the requested route is equal to this endpoint.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 * @return bool $is_current_endpoint True if it's equal, false otherwise
-	 */
-	public function is_current_endpoint( $request ) {
-
-		return $this->get_endpoint() == $request->get_route();
-
-	}
-
-	/**
-	 * Authorization status code.
-	 *
-	 * @since 0.1
-	 * @return int $status
-	 */
-	protected function authorization_status_code() {
-
-		$status = 401;
-
-		if ( is_user_logged_in() ) $status = 403;
-
-		return $status;
-
-	}
-
-	/**
-	 * Wrapper for WP_Error.
-	 *
-	 * @since 0.1
-	 * @param string|\CiviCRM_API3_Exception $error
-	 * @param mixed $data Error data
-	 * @return WP_Error $error
-	 */
-	protected function civi_rest_error( $error, $data = [] ) {
-
-		if ( $error instanceof \CiviCRM_API3_Exception ) {
-
-			return $error->getExtraParams();
-
-		}
-
-		return new \WP_Error( 'civicrm_rest_api_error', $error, empty( $data ) ? [ 'status' => $this->authorization_status_code() ] : $data );
-
-	}
-
-}
diff --git a/wp-rest/Controller/Cxn.php b/wp-rest/Controller/Cxn.php
deleted file mode 100644
index 7f7cca5c5621c3eb3441ca7060d5e1048eb85ade..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/Cxn.php
+++ /dev/null
@@ -1,125 +0,0 @@
-<?php
-/**
- * Cxn controller class.
- *
- * CiviConnect endpoint, replacement for CiviCRM's 'extern/cxn.php'.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class Cxn extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'cxn';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::ALLMETHODS,
-				'callback' => [ $this, 'get_item' ]
-			]
-		] );
-
-	}
-
-	/**
-	 * Get items.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_item( $request ) {
-
-		/**
-		 * Filter request params.
-		 *
-		 * @since 0.1
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$params = apply_filters( 'civi_wp_rest/controller/cxn/params', $request->get_params(), $request );
-
-		// init connection server
-		$cxn = \CRM_Cxn_BAO_Cxn::createApiServer();
-
-		/**
-		 * Filter connection server object.
-		 *
-		 * @param Civi\Cxn\Rpc\ApiServer $cxn
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$cxn = apply_filters( 'civi_wp_rest/controller/cxn/instance', $cxn, $params, $request );
-
-		try {
-
-			$result = $cxn->handle( $request->get_body() );
-
-		} catch ( Civi\Cxn\Rpc\Exception\CxnException $e ) {
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		} catch ( Civi\Cxn\Rpc\Exception\ExpiredCertException $e ) {
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		} catch ( Civi\Cxn\Rpc\Exception\InvalidCertException $e ) {
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		} catch ( Civi\Cxn\Rpc\Exception\InvalidMessageException $e ) {
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		} catch ( Civi\Cxn\Rpc\Exception\GarbledMessageException $e ) {
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		}
-
-		/**
-		 * Bypass WP and send request from Cxn.
-		 */
-		add_filter( 'rest_pre_serve_request', function( $served, $response, $request, $server ) use ( $result ) {
-
-			// Civi\Cxn\Rpc\Message->send()
-			$result->send();
-
-			return true;
-
-		}, 10, 4 );
-
-		return rest_ensure_response( $result );
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {}
-
-}
diff --git a/wp-rest/Controller/Open.php b/wp-rest/Controller/Open.php
deleted file mode 100644
index 450ef991a34897a169761dc9c1fdfcc57b4a0bf5..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/Open.php
+++ /dev/null
@@ -1,129 +0,0 @@
-<?php
-/**
- * Open controller class.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class Open extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'open';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::READABLE,
-				'callback' => [ $this, 'get_item' ],
-				'args' => $this->get_item_args()
-			],
-			'schema' => [ $this, 'get_item_schema' ]
-		] );
-
-	}
-
-	/**
-	 * Get item.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_item( $request ) {
-
-		$queue_id = $request->get_param( 'q' );
-
-		// track open
-		\CRM_Mailing_Event_BAO_Opened::open( $queue_id );
-
-		// serve tracker file
-		add_filter( 'rest_pre_serve_request', [ $this, 'serve_tracker_file' ], 10, 4 );
-
-	}
-
-	/**
-	 * Serves the tracker gif file.
-	 *
-	 * @since 0.1
-	 * @param bool $served Whether the request has been served
-	 * @param WP_REST_Response $result
-	 * @param WP_REST_Request $request
-	 * @param WP_REST_Server $server
-	 * @return bool $served Whether the request has been served
-	 */
-	public function serve_tracker_file( $served, $result, $request, $server ) {
-
-		// tracker file path
-		$file = CIVICRM_PLUGIN_DIR . 'civicrm/i/tracker.gif';
-
-		// set headers
-		$server->send_header( 'Content-type', 'image/gif' );
-		$server->send_header( 'Cache-Control', 'must-revalidate, post-check=0, pre-check=0' );
-		$server->send_header( 'Content-Description', 'File Transfer' );
-		$server->send_header( 'Content-Disposition', 'inline; filename=tracker.gif' );
-		$server->send_header( 'Content-Length', filesize( $file ) );
-
-		$buffer = readfile( $file );
-
-		return true;
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {
-
-		return [
-			'$schema' => 'http://json-schema.org/draft-04/schema#',
-			'title' => 'civicrm/v3/open',
-			'description' => __( 'CiviCRM Open endpoint', 'civicrm' ),
-			'type' => 'object',
-			'required' => [ 'q' ],
-			'properties' => [
-				'q' => [
-					'type' => 'integer'
-				]
-			]
-		];
-
-	}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {
-
-		return [
-			'q' => [
-				'type' => 'integer',
-				'required' => true,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_numeric( $value );
-
-				}
-			]
-		];
-
-	}
-
-}
diff --git a/wp-rest/Controller/PayPalIPN.php b/wp-rest/Controller/PayPalIPN.php
deleted file mode 100644
index 5b5c38004525287b69024d51ea378cb5615f60bc..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/PayPalIPN.php
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-/**
- * PayPalIPN controller class.
- *
- * PayPal IPN endpoint, replacement for CiviCRM's 'extern/ipn.php'.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class PayPalIPN extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'ipn';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::ALLMETHODS,
-				'callback' => [ $this, 'get_item' ]
-			]
-		] );
-
-	}
-
-	/**
-	 * Get items.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_item( $request ) {
-
-		/**
-		 * Filter request params.
-		 *
-		 * @since 0.1
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$params = apply_filters( 'civi_wp_rest/controller/ipn/params', $request->get_params(), $request );
-
-		if ( $request->get_method() == 'GET' ) {
-
-			// paypal standard
-			$paypal_IPN = new \CRM_Core_Payment_PayPalIPN( $params );
-
-			// log notification
-			\Civi::log()->alert( 'payment_notification processor_name=PayPal_Standard', $params );
-
-		} else {
-
-			// paypal pro
-			$paypal_IPN = new \CRM_Core_Payment_PayPalProIPN( $params );
-
-			// log notification
-			\Civi::log()->alert( 'payment_notification processor_name=PayPal', $params );
-
-		}
-
-		/**
-		 * Filter PayPalIPN object.
-		 *
-		 * @param CRM_Core_Payment_PayPalIPN|CRM_Core_Payment_PayPalProIPN $paypal_IPN
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$paypal_IPN = apply_filters( 'civi_wp_rest/controller/ipn/instance', $paypal_IPN, $params, $request );
-
-		try {
-
-			if ( ! method_exists( $paypal_IPN, 'main' ) || ! $this->instance_of_crm_base_ipn( $paypal_IPN ) )
-				return $this->civi_rest_error( sprintf( __( '%s must implement a "main" method.', 'civicrm' ), get_class( $paypal_IPN ) ) );
-
-			$result = $paypal_IPN->main();
-
-		} catch ( \CRM_Core_Exception $e ) {
-
-			\Civi::log()->error( $e->getMessage() );
-			\Civi::log()->error( 'error data ', [ 'data' => $e->getErrorData() ] );
-			\Civi::log()->error( 'REQUEST ', [ 'params' => $params ] );
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		}
-
-		return rest_ensure_response( $result );
-
-	}
-
-	/**
-	 * Checks whether object is an instance of CRM_Core_Payment_BaseIPN|CRM_Core_Payment_PayPalProIPN|CRM_Core_Payment_PayPalIPN.
-	 *
-	 * Needed because the instance is being filtered through 'civi_wp_rest/controller/ipn/instance'.
-	 *
-	 * @since 0.1
-	 * @param CRM_Core_Payment_BaseIPN|CRM_Core_Payment_PayPalProIPN|CRM_Core_Payment_PayPalIPN $object
-	 * @return bool
-	 */
-	public function instance_of_crm_base_ipn( $object ) {
-
-		return $object instanceof \CRM_Core_Payment_BaseIPN || $object instanceof \CRM_Core_Payment_PayPalProIPN || $object instanceof \CRM_Core_Payment_PayPalIPN;
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {}
-
-}
diff --git a/wp-rest/Controller/PxIPN.php b/wp-rest/Controller/PxIPN.php
deleted file mode 100644
index d68fc8d787ae3e87eb449f36b459e0b8d24d845a..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/PxIPN.php
+++ /dev/null
@@ -1,139 +0,0 @@
-<?php
-/**
- * PxIPN controller class.
- *
- * PxPay IPN endpoint, replacement for CiviCRM's 'extern/pxIPN.php'.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class PxIPN extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'pxIPN';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::ALLMETHODS,
-				'callback' => [ $this, 'get_item' ]
-			]
-		] );
-
-	}
-
-	/**
-	 * Get items.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_item( $request ) {
-
-		/**
-		 * Filter payment processor params.
-		 *
-		 * @since 0.1
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$params = apply_filters(
-			'civi_wp_rest/controller/pxIPN/params',
-			$this->get_payment_processor_args( $request ),
-			$request
-		);
-
-		// log notification
-		\Civi::log()->alert( 'payment_notification processor_name=Payment_Express', $params );
-
-		try {
-
-			$result = \CRM_Core_Payment_PaymentExpressIPN::main( ...$params );
-
-		} catch ( \CRM_Core_Exception $e ) {
-
-			\Civi::log()->error( $e->getMessage() );
-			\Civi::log()->error( 'error data ', [ 'data' => $e->getErrorData() ] );
-			\Civi::log()->error( 'REQUEST ', [ 'params' => $params ] );
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		}
-
-		return rest_ensure_response( $result );
-
-	}
-
-	/**
-	 * Get payment processor necessary params.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Resquest $request
-	 * @return array $args
-	 */
-	public function get_payment_processor_args( $request ) {
-
-		// get payment processor types
-		$payment_processor_types = civicrm_api3( 'PaymentProcessor', 'getoptions', [
-			'field' => 'payment_processor_type_id'
-		] );
-
-		// payment processor params
-		$params = apply_filters( 'civi_wp_rest/controller/pxIPN/payment_processor_params', [
-			'user_name' => $request->get_param( 'userid' ),
-			'payment_processor_type_id' => array_search(
-				'DPS Payment Express',
-				$payment_processor_types['values']
-			),
-			'is_active' => 1,
-			'is_test' => 0
-		] );
-
-		// get payment processor
-		$payment_processor = civicrm_api3( 'PaymentProcessor', 'get', $params );
-
-		$args = $payment_processor['values'][$payment_processor['id']];
-
-		$method = empty( $args['signature'] ) ? 'pxpay' : 'pxaccess';
-
-		return [
-			$method,
-			$request->get_param( 'result' ),
-			$args['url_site'],
-			$args['user_name'],
-			$args['password'],
-			$args['signature']
-		];
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {}
-
-}
diff --git a/wp-rest/Controller/Rest.php b/wp-rest/Controller/Rest.php
deleted file mode 100644
index 61706f85fdc56b540829ca685dc607b173e45795..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/Rest.php
+++ /dev/null
@@ -1,522 +0,0 @@
-<?php
-/**
- * Rest controller class.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class Rest extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'rest';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::ALLMETHODS,
-				'callback' => [ $this, 'get_items' ],
-				'permission_callback' => [ $this, 'permissions_check' ],
-				'args' => $this->get_item_args()
-			],
-			'schema' => [ $this, 'get_item_schema' ]
-		] );
-
-	}
-
-	/**
-	 * Check get permission.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 * @return bool
-	 */
-	public function permissions_check( $request ) {
-
-		if ( ! $this->is_valid_api_key( $request ) )
-			return $this->civi_rest_error( __( 'Param api_key is not valid.', 'civicrm' ) );
-
-		if ( ! $this->is_valid_site_key() )
-			return $this->civi_rest_error( __( 'Param key is not valid.', 'civicrm' ) );
-
-		return true;
-
-	}
-
-	/**
-	 * Get items.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_items( $request ) {
-
-		/**
-		 * Filter formatted api params.
-		 *
-		 * @since 0.1
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$params = apply_filters( 'civi_wp_rest/controller/rest/api_params', $this->get_formatted_api_params( $request ), $request );
-
-		try {
-
-			$items = civicrm_api3( ...$params );
-
-		} catch ( \CiviCRM_API3_Exception $e ) {
-
-			$items = $this->civi_rest_error( $e );
-
-		}
-
-		if ( ! isset( $items ) || empty( $items ) )
-			return rest_ensure_response( [] );
-
-		/**
-		 * Filter civi api result.
-		 *
-		 * @since 0.1
-		 * @param array $items
-		 * @param WP_REST_Request $request
-		 */
-		$data = apply_filters( 'civi_wp_rest/controller/rest/api_result', $items, $params, $request );
-
-		// only collections of items, ie any action but 'getsingle'
-		if ( isset( $data['values'] ) ) {
-
-			$data['values'] = array_reduce( $items['values'] ?? $items, function( $items, $item ) use ( $request ) {
-
-				$response = $this->prepare_item_for_response( $item, $request );
-
-				$items[] = $this->prepare_response_for_collection( $response );
-
-				return $items;
-
-			}, [] );
-
-		}
-
-		$response = rest_ensure_response( $data );
-
-		// check wheather we need to serve xml or json
-		if ( ! in_array( 'json', array_keys( $request->get_params() ) ) ) {
-
-			/**
-			 * Adds our response holding Civi data before dispatching.
-			 *
-			 * @since 0.1
-			 * @param WP_HTTP_Response $result Result to send to client
-			 * @param WP_REST_Server $server The REST server
-			 * @param WP_REST_Request $request The request
-			 * @return WP_HTTP_Response $result Result to send to client
-			 */
-			add_filter( 'rest_post_dispatch', function( $result, $server, $request ) use ( $response ) {
-
-				return $response;
-
-			}, 10, 3 );
-
-			// serve xml
-			add_filter( 'rest_pre_serve_request', [ $this, 'serve_xml_response' ], 10, 4 );
-
-		} else {
-
-			// return json
-			return $response;
-
-		}
-
-	}
-
-	/**
-	 * Get formatted api params.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Resquest $request
-	 * @return array $params
-	 */
-	public function get_formatted_api_params( $request ) {
-
-		$args = $request->get_params();
-
-		$entity = $args['entity'];
-		$action = $args['action'];
-
-		// unset unnecessary args
-		unset( $args['entity'], $args['action'], $args['key'], $args['api_key'] );
-
-		if ( ! isset( $args['json'] ) || is_numeric( $args['json'] ) ) {
-
-			$params = $args;
-
-		} else {
-
-			$params = is_string( $args['json'] ) ? json_decode( $args['json'], true ) : [];
-
-		}
-
-		// ensure check permissions is enabled
-		$params['check_permissions'] = true;
-
-		return [ $entity, $action, $params ];
-
-	}
-
-	/**
-	 * Matches the item data to the schema.
-	 *
-	 * @since 0.1
-	 * @param object $item
-	 * @param WP_REST_Request $request
-	 */
-	public function prepare_item_for_response( $item, $request ) {
-
-		return rest_ensure_response( $item );
-
-	}
-
-	/**
-	 * Serves XML response.
-	 *
-	 * @since 0.1
-	 * @param bool $served Whether the request has already been served
-	 * @param WP_REST_Response $result
-	 * @param WP_REST_Request $request
-	 * @param WP_REST_Server $server
-	 */
-	public function serve_xml_response( $served, $result, $request, $server ) {
-
-		// get xml from response
-		$xml = $this->get_xml_formatted_data( $result->get_data() );
-
-		// set content type header
-		$server->send_header( 'Content-Type', 'text/xml' );
-
-		echo $xml;
-
-		return true;
-
-	}
-
-	/**
-	 * Formats CiviCRM API result to XML.
-	 *
-	 * @since 0.1
-	 * @param array $data The CiviCRM api result
-	 * @return string $xml The formatted xml
-	 */
-	protected function get_xml_formatted_data( array $data ) {
-
-		// xml document
-		$xml = new \DOMDocument();
-
-		// result set element <ResultSet>
-		$result_set = $xml->createElement( 'ResultSet' );
-
-		// xmlns:xsi attribute
-		$result_set->setAttribute( 'xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance' );
-
-		// count attribute
-		if ( isset( $data['count'] ) ) $result_set->setAttribute( 'count', $data['count'] );
-
-		// build result from result => values
-		if ( isset( $data['values'] ) ) {
-
-			array_map( function( $item ) use ( $result_set, $xml ) {
-
-				// result element <Result>
-				$result = $xml->createElement( 'Result' );
-
-				// format item
-				$result = $this->get_xml_formatted_item( $item, $result, $xml );
-
-				// append result to result set
-				$result_set->appendChild( $result );
-
-			}, $data['values'] );
-
-		} else {
-
-			// result element <Result>
-			$result = $xml->createElement( 'Result' );
-
-			// format item
-			$result = $this->get_xml_formatted_item( $data, $result, $xml );
-
-			// append result to result set
-			$result_set->appendChild( $result );
-
-		}
-
-		// append result set
-		$xml->appendChild( $result_set );
-
-		return $xml->saveXML();
-
-	}
-
-	/**
-	 * Formats a single api result to xml.
-	 *
-	 * @since 0.1
-	 * @param array $item The single api result
-	 * @param DOMElement $parent The parent element to append to
-	 * @param DOMDocument $doc The document
-	 * @return DOMElement $parent The parent element
-	 */
-	public function get_xml_formatted_item( array $item, \DOMElement $parent, \DOMDocument $doc ) {
-
-		// build field => values
-		array_map( function( $field, $value ) use ( $parent, $doc ) {
-
-			// entity field element
-			$element = $doc->createElement( $field );
-
-			// handle array values
-			if ( is_array( $value ) ) {
-
-				array_map( function( $key, $val ) use ( $element, $doc ) {
-
-					// child element, append underscore '_' otherwise createElement
-					// will throw an Invalid character exception as elements cannot start with a number
-					$child = $doc->createElement( '_' . $key, $val );
-
-					// append child
-					$element->appendChild( $child );
-
-				}, array_keys( $value ), $value );
-
-			} else {
-
-				// assign value
-				$element->nodeValue = $value;
-
-			}
-
-			// append element
-			$parent->appendChild( $element );
-
-		}, array_keys( $item ), $item );
-
-		return $parent;
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {
-
-		return [
-			'$schema' => 'http://json-schema.org/draft-04/schema#',
-			'title' => 'civicrm/v3/rest',
-			'description' => __( 'CiviCRM API3 WP rest endpoint wrapper', 'civicrm' ),
-			'type' => 'object',
-			'required' => [ 'entity', 'action', 'params' ],
-			'properties' => [
-				'is_error' => [
-					'type' => 'integer'
-				],
-				'version' => [
-					'type' => 'integer'
-				],
-				'count' => [
-					'type' => 'integer'
-				],
-				'values' => [
-					'type' => 'array'
-				]
-			]
-		];
-
-	}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {
-
-		return [
-			'key' => [
-				'type' => 'string',
-				'required' => true,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return $this->is_valid_site_key();
-
-				}
-			],
-			'api_key' => [
-				'type' => 'string',
-				'required' => true,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return $this->is_valid_api_key( $request );
-
-				}
-			],
-			'entity' => [
-				'type' => 'string',
-				'required' => true,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_string( $value );
-
-				}
-			],
-			'action' => [
-				'type' => 'string',
-				'required' => true,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_string( $value );
-
-				}
-			],
-			'json' => [
-				'type' => ['integer', 'string', 'array'],
-				'required' => false,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_numeric( $value ) || is_array( $value ) || $this->is_valid_json( $value );
-
-				}
-			]
-		];
-
-	}
-
-	/**
-	 * Checks if string is a valid json.
-	 *
-	 * @since 0.1
-	 * @param string $param
-	 * @return bool
-	 */
-	protected function is_valid_json( $param ) {
-
-		$param = json_decode( $param, true );
-
-		if ( ! is_array( $param ) ) return false;
-
- 		return ( json_last_error() == JSON_ERROR_NONE );
-
-	}
-
-	/**
-	 * Validates the site key.
-	 *
-	 * @since 0.1
-	 * @return bool $is_valid_site_key
-	 */
-	private function is_valid_site_key() {
-
-		return \CRM_Utils_System::authenticateKey( false );
-
-	}
-
-	/**
-	 * Validates the api key.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Resquest $request
-	 * @return bool $is_valid_api_key
-	 */
-	private function is_valid_api_key( $request ) {
-
-		$api_key = $request->get_param( 'api_key' );
-
-		if ( ! $api_key ) return false;
-
-		$contact_id = \CRM_Core_DAO::getFieldValue( 'CRM_Contact_DAO_Contact', $api_key, 'id', 'api_key' );
-
-		// validate contact and login
-		if ( $contact_id ) {
-
-			$wp_user = $this->get_wp_user( $contact_id );
-
-			$this->do_user_login( $wp_user );
-
-			return true;
-
-		}
-
-		return false;
-
-	}
-
-	/**
-	 * Get WordPress user data.
-	 *
-	 * @since 0.1
-	 * @param int $contact_id The contact id
-	 * @return bool|WP_User $user The WordPress user data
-	 */
-	protected function get_wp_user( int $contact_id ) {
-
-		try {
-
-			// Get CiviCRM domain group ID from constant, if set.
-			$domain_id = defined( 'CIVICRM_DOMAIN_ID' ) ? CIVICRM_DOMAIN_ID : 0;
-
-			// If this fails, get it from config.
-			if ( $domain_id === 0 ) {
-				$domain_id = CRM_Core_Config::domainID();
-			}
-
-			// Call API.
-			$uf_match = civicrm_api3( 'UFMatch', 'getsingle', [
-				'contact_id' => $contact_id,
-				'domain_id' => $domain_id,
-			] );
-
-		} catch ( \CiviCRM_API3_Exception $e ) {
-
-			return $this->civi_rest_error( $e->getMessage() );
-
-		}
-
-		$wp_user = get_userdata( $uf_match['uf_id'] );
-
-		return $wp_user;
-
-	}
-
-	/**
-	 * Logs in the WordPress user, needed to respect CiviCRM ACL and permissions.
-	 *
-	 * @since 0.1
-	 * @param  WP_User $user
-	 */
-	protected function do_user_login( \WP_User $user ) {
-
-		if ( is_user_logged_in() ) return;
-
-		wp_set_current_user( $user->ID, $user->user_login );
-
-		wp_set_auth_cookie( $user->ID );
-
-		do_action( 'wp_login', $user->user_login, $user );
-
-	}
-
-}
diff --git a/wp-rest/Controller/Soap.php b/wp-rest/Controller/Soap.php
deleted file mode 100644
index 17402cc579a834ca8854014e683a53aad5011399..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/Soap.php
+++ /dev/null
@@ -1,98 +0,0 @@
-<?php
-/**
- * Soap controller class.
- *
- * Soap endpoint, replacement for CiviCRM's 'extern/soap.php'.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class Soap extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'soap';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::ALLMETHODS,
-				'callback' => [ $this, 'get_item' ]
-			]
-		] );
-
-	}
-
-	/**
-	 * Get items.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_item( $request ) {
-
-		/**
-		 * Filter request params.
-		 *
-		 * @since 0.1
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$params = apply_filters( 'civi_wp_rest/controller/soap/params', $request->get_params(), $request );
-
-		// init soap server
-		$soap_server = new \SoapServer(
-			NULL,
-			[
-				'uri' => 'urn:civicrm',
-				'soap_version' => SOAP_1_2,
-			]
-		);
-
-		$crm_soap_server = new \CRM_Utils_SoapServer();
-
-		$soap_server->setClass( 'CRM_Utils_SoapServer', \CRM_Core_Config::singleton()->userFrameworkClass );
-		$soap_server->setPersistence( SOAP_PERSISTENCE_SESSION );
-
-		/**
-		 * Bypass WP and send request from Soap server.
-		 */
-		add_filter( 'rest_pre_serve_request', function( $served, $response, $request, $server ) use ( $soap_server ) {
-
-			$soap_server->handle();
-
-			return true;
-
-		}, 10, 4 );
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {}
-
-}
diff --git a/wp-rest/Controller/Url.php b/wp-rest/Controller/Url.php
deleted file mode 100644
index 9286856e7c88e6d1cf9057cf78da943cb34f73d1..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/Url.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-/**
- * Url controller class.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class Url extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'url';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::READABLE,
-				'callback' => [ $this, 'get_item' ],
-				'args' => $this->get_item_args()
-			],
-			'schema' => [ $this, 'get_item_schema' ]
-		] );
-
-	}
-
-	/**
-	 * Get items.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_item( $request ) {
-
-		/**
-		 * Filter formatted api params.
-		 *
-		 * @since 0.1
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$params = apply_filters( 'civi_wp_rest/controller/url/params', $this->get_formatted_params( $request ), $request );
-
-		// track url
-		$url = \CRM_Mailing_Event_BAO_TrackableURLOpen::track( $params['queue_id'], $params['url_id'] );
-
-		/**
-		 * Filter url.
-		 *
-		 * @param string $url
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$url = apply_filters( 'civi_wp_rest/controller/url/before_parse_url', $url, $params, $request );
-
-		// parse url
-		$url = $this->parse_url( $url, $params );
-
-		$this->do_redirect( $url );
-
-	}
-
-	/**
-	 * Get formatted api params.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Resquest $request
-	 * @return array $params
-	 */
-	protected function get_formatted_params( $request ) {
-
-		$args = $request->get_params();
-
-		$params = [
-			'queue_id' => isset( $args['qid'] ) ? $args['qid'] ?? '' : $args['q'] ?? '',
-			'url_id' => $args['u']
-		];
-
-		// unset unnecessary args
-		unset( $args['qid'], $args['u'], $args['q'] );
-
-		if ( ! empty( $args ) ) {
-
-			$params['query'] = http_build_query( $args );
-
-		}
-
-		return $params;
-
-	}
-
-	/**
-	 * Parses the url.
-	 *
-	 * @since 0.1
-	 * @param string $url
-	 * @param array $params
-	 * @return string $url
-	 */
-	protected function parse_url( $url, $params ) {
-
-		// CRM-18320 - Fix encoded ampersands
-		$url = str_replace( '&amp;', '&', $url );
-
-		// CRM-7103 - Look for additional query variables and append them
-		if ( isset( $params['query'] ) && strpos( $url, '?' ) ) {
-
-			$url .= '&' . $params['query'];
-
-		} elseif ( isset( $params['query'] ) ) {
-
-			$url .= '?' . $params['query'];
-
-		}
-
-		return apply_filters( 'civi_wp_rest/controller/url/parsed_url', $url, $params );
-
-	}
-
-	/**
-	 * Do redirect.
-	 *
-	 * @since 0.1
-	 * @param string $url
-	 */
-	protected function do_redirect( $url ) {
-
-		wp_redirect( $url );
-
-		exit;
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {
-
-		return [
-			'$schema' => 'http://json-schema.org/draft-04/schema#',
-			'title' => 'civicrm_api3/v3/url',
-			'description' => __( 'CiviCRM API3 wrapper', 'civicrm' ),
-			'type' => 'object',
-			'required' => [ 'qid', 'u' ],
-			'properties' => [
-				'qid' => [
-					'type' => 'integer'
-				],
-				'q' => [
-					'type' => 'integer'
-				],
-				'u' => [
-					'type' => 'integer'
-				]
-			]
-		];
-
-	}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {
-
-		return [
-			'qid' => [
-				'type' => 'integer',
-				'required' => false,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_numeric( $value );
-
-				}
-			],
-			'q' => [
-				'type' => 'integer',
-				'required' => false,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_numeric( $value );
-
-				}
-			],
-			'u' => [
-				'type' => 'integer',
-				'required' => true,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_numeric( $value );
-
-				}
-			]
-		];
-
-	}
-
-}
diff --git a/wp-rest/Controller/Widget.php b/wp-rest/Controller/Widget.php
deleted file mode 100644
index 13fa1e2adde648de8c24b1278039385569bd5c21..0000000000000000000000000000000000000000
--- a/wp-rest/Controller/Widget.php
+++ /dev/null
@@ -1,214 +0,0 @@
-<?php
-/**
- * Widget controller class.
- *
- * Widget endpoint, replacement for CiviCRM's 'extern/widget.php'
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Controller;
-
-class Widget extends Base {
-
-	/**
-	 * The base route.
-	 *
-	 * @since 0.1
-	 * @var string
-	 */
-	protected $rest_base = 'widget';
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes() {
-
-		register_rest_route( $this->get_namespace(), $this->get_rest_base(), [
-			[
-				'methods' => \WP_REST_Server::READABLE,
-				'callback' => [ $this, 'get_item' ],
-				'args' => $this->get_item_args()
-			],
-			'schema' => [ $this, 'get_item_schema' ]
-		] );
-
-	}
-
-	/**
-	 * Get item.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request
-	 */
-	public function get_item( $request ) {
-
-		/**
-		 * Filter mandatory params.
-		 *
-		 * @since 0.1
-		 * @param array $params
-		 * @param WP_REST_Request $request
-		 */
-		$params = apply_filters(
-			'civi_wp_rest/controller/widget/params',
-			$this->get_mandatory_params( $request ),
-			$request
-		);
-
-		$jsonvar = 'jsondata';
-
-		if ( ! empty( $request->get_param( 'format' ) ) ) $jsonvar .= $request->get_param( 'cpageId' );
-
-		$data = \CRM_Contribute_BAO_Widget::getContributionPageData( ...$params );
-
-		$response = 'var ' . $jsonvar . ' = ' . json_encode( $data ) . ';';
-
-		/**
-		 * Adds our response data before dispatching.
-		 *
-		 * @since 0.1
-		 * @param WP_HTTP_Response $result Result to send to client
-		 * @param WP_REST_Server $server The REST server
-		 * @param WP_REST_Request $request The request
-		 * @return WP_HTTP_Response $result Result to send to client
-		 */
-		add_filter( 'rest_post_dispatch', function( $result, $server, $request ) use ( $response ) {
-
-			return rest_ensure_response( $response );
-
-		}, 10, 3 );
-
-		// serve javascript
-		add_filter( 'rest_pre_serve_request', [ $this, 'serve_javascript' ], 10, 4 );
-
-	}
-
-	/**
-	 * Get mandatory params from request.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Resquest $request
-	 * @return array $params The widget params
-	 */
-	protected function get_mandatory_params( $request ) {
-
-		$args = $request->get_params();
-
-		return [
-			$args['cpageId'],
-			$args['widgetId'],
-			$args['includePending'] ?? false
-		];
-
-	}
-
-	/**
-	 * Serve jsondata response.
-	 *
-	 * @since 0.1
-	 * @param bool $served Whether the request has already been served
-	 * @param WP_REST_Response $result
-	 * @param WP_REST_Request $request
-	 * @param WP_REST_Server $server
-	 * @return bool $served
-	 */
-	public function serve_javascript( $served, $result, $request, $server ) {
-
-		// set content type header
-		$server->send_header( 'Expires', gmdate( 'D, d M Y H:i:s \G\M\T', time() + 60 ) );
-		$server->send_header( 'Content-Type', 'application/javascript' );
-		$server->send_header( 'Cache-Control', 'max-age=60, public' );
-
-		echo $result->get_data();
-
-		return true;
-
-	}
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema() {
-
-		return [
-			'$schema' => 'http://json-schema.org/draft-04/schema#',
-			'title' => 'civicrm_api3/v3/widget',
-			'description' => __( 'CiviCRM API3 wrapper', 'civicrm' ),
-			'type' => 'object',
-			'required' => [ 'cpageId', 'widgetId' ],
-			'properties' => [
-				'cpageId' => [
-					'type' => 'integer',
-					'minimum' => 1
-				],
-				'widgetId' => [
-					'type' => 'integer',
-					'minimum' => 1
-				],
-				'format' => [
-					'type' => 'integer'
-				],
-				'includePending' => [
-					'type' => 'boolean'
-				]
-			]
-		];
-
-	}
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args() {
-
-		return [
-			'cpageId' => [
-				'type' => 'integer',
-				'required' => true,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_numeric( $value );
-
-				}
-			],
-			'widgetId' => [
-				'type' => 'integer',
-				'required' => true,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_numeric( $value );
-
-				}
-			],
-			'format' => [
-				'type' => 'integer',
-				'required' => false,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_numeric( $value );
-
-				}
-			],
-			'includePending' => [
-				'type' => 'boolean',
-				'required' => false,
-				'validate_callback' => function( $value, $request, $key ) {
-
-					return is_string( $value );
-
-				}
-			]
-		];
-
-	}
-
-}
diff --git a/wp-rest/Endpoint/Endpoint-Interface.php b/wp-rest/Endpoint/Endpoint-Interface.php
deleted file mode 100644
index 9497cde5099ecbd7936571b0b73e318652abea7f..0000000000000000000000000000000000000000
--- a/wp-rest/Endpoint/Endpoint-Interface.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-/**
- * Endpoint Interface class.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST\Endpoint;
-
-interface Endpoint_Interface {
-
-	/**
-	 * Registers routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_routes();
-
-	/**
-	 * Item schema.
-	 *
-	 * @since 0.1
-	 * @return array $schema
-	 */
-	public function get_item_schema();
-
-	/**
-	 * Item arguments.
-	 *
-	 * @since 0.1
-	 * @return array $arguments
-	 */
-	public function get_item_args();
-
-}
diff --git a/wp-rest/Plugin.php b/wp-rest/Plugin.php
deleted file mode 100644
index 4038a56b1b6cab395e1a2d143cfe8882a7edb456..0000000000000000000000000000000000000000
--- a/wp-rest/Plugin.php
+++ /dev/null
@@ -1,193 +0,0 @@
-<?php
-/**
- * Main plugin class.
- *
- * @since 0.1
- */
-
-namespace CiviCRM_WP_REST;
-
-use CiviCRM_WP_REST\Civi\Mailing_Hooks;
-
-class Plugin {
-
-	/**
-	 * Constructor.
-	 *
-	 * @since 0.1
-	 */
-	public function __construct() {
-
-		$this->register_hooks();
-
-		$this->setup_objects();
-
-	}
-
-	/**
-	 * Register hooks.
-	 *
-	 * @since 1.0
-	 */
-	protected function register_hooks() {
-
-		add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
-
-		add_filter( 'rest_pre_dispatch', [ $this, 'bootstrap_civi' ], 10, 3 );
-
-		add_filter( 'rest_post_dispatch',  [ $this, 'maybe_reset_wp_timezone' ], 10, 3);
-
-	}
-
-	/**
-	 * Bootstrap CiviCRM when hitting a the 'civicrm' namespace.
-	 *
-	 * @since 0.1
-	 * @param mixed $result
-	 * @param WP_REST_Server $server REST server instance
-	 * @param WP_REST_Request $request The request
-	 * @return mixed $result
-	 */
-	public function bootstrap_civi( $result, $server, $request ) {
-
-		if ( false !== strpos( $request->get_route(), 'civicrm' ) ) {
-
-			$this->maybe_set_user_timezone( $request );
-
-			civi_wp()->initialize();
-
-		}
-
-		return $result;
-
-	}
-
-	/**
-	 * Setup objects.
-	 *
-	 * @since 0.1
-	 */
-	private function setup_objects() {
-
-		if ( CIVICRM_WP_REST_REPLACE_MAILING_TRACKING ) {
-
-			// register mailing hooks
-			$mailing_hooks = ( new Mailing_Hooks )->register_hooks();
-
-		}
-
-	}
-
-	/**
-	 * Registers Rest API routes.
-	 *
-	 * @since 0.1
-	 */
-	public function register_rest_routes() {
-
-		// rest endpoint
-		$rest_controller = new Controller\Rest;
-		$rest_controller->register_routes();
-
-		// url controller
-		$url_controller = new Controller\Url;
-		$url_controller->register_routes();
-
-		// open controller
-		$open_controller = new Controller\Open;
-		$open_controller->register_routes();
-
-		// authorizenet controller
-		$authorizeIPN_controller = new Controller\AuthorizeIPN;
-		$authorizeIPN_controller->register_routes();
-
-		// paypal controller
-		$paypalIPN_controller = new Controller\PayPalIPN;
-		$paypalIPN_controller->register_routes();
-
-		// pxpay controller
-		$paypalIPN_controller = new Controller\PxIPN;
-		$paypalIPN_controller->register_routes();
-
-		// civiconnect controller
-		$cxn_controller = new Controller\Cxn;
-		$cxn_controller->register_routes();
-
-		// widget controller
-		$widget_controller = new Controller\Widget;
-		$widget_controller->register_routes();
-
-		// soap controller
-		$soap_controller = new Controller\Soap;
-		$soap_controller->register_routes();
-
-		/**
-		 * Opportunity to add more rest routes.
-		 *
-		 * @since 0.1
-		 */
-		do_action( 'civi_wp_rest/plugin/rest_routes_registered' );
-
-	}
-
-	/**
-	 * Sets the timezone to the users timezone when
-	 * calling the civicrm/v3/rest endpoint.
-	 *
-	 * @since 0.1
-	 * @param WP_REST_Request $request The request
-	 */
-	private function maybe_set_user_timezone( $request ) {
-
-		if ( $request->get_route() != '/civicrm/v3/rest' ) return;
-
-		$timezones = [
-			'wp_timezone' => date_default_timezone_get(),
-			'user_timezone' => get_option( 'timezone_string', false )
-		];
-
-		// filter timezones
-		add_filter( 'civi_wp_rest/plugin/timezones', function() use ( $timezones ) {
-
-			return $timezones;
-
-		} );
-
-		if ( empty( $timezones['user_timezone'] ) ) return;
-
-		/**
-		 * CRM-12523
-		 * CRM-18062
-		 * CRM-19115
-		 */
-		date_default_timezone_set( $timezones['user_timezone'] );
-		\CRM_Core_Config::singleton()->userSystem->setMySQLTimeZone();
-
-	}
-
-	/**
-	 * Resets the timezone to the original WP
-	 * timezone after calling the civicrm/v3/rest endpoint.
-	 *
-	 * @since 0.1
-	 * @param mixed $result
-	 * @param WP_REST_Server $server REST server instance
-	 * @param WP_REST_Request $request The request
-	 * @return mixed $result
-	 */
-	public function maybe_reset_wp_timezone( $result, $server, $request ) {
-
-		if ( $request->get_route() != '/civicrm/v3/rest' ) return $result;
-
-		$timezones = apply_filters( 'civi_wp_rest/plugin/timezones', null );
-
-		if ( empty( $timezones['wp_timezone'] ) ) return $result;
-
-		// reset wp timezone
-		date_default_timezone_set( $timezones['wp_timezone'] );
-
-		return $result;
-
-	}
-
-}
diff --git a/wp-rest/README.md b/wp-rest/README.md
deleted file mode 100644
index 77234de84a195dc6fbb22e1e7d460ff7d44587f2..0000000000000000000000000000000000000000
--- a/wp-rest/README.md
+++ /dev/null
@@ -1,59 +0,0 @@
-# CiviCRM WP REST API Wrapper
-
-This is a WordPress plugin that aims to expose CiviCRM's [extern](https://github.com/civicrm/civicrm-core/tree/master/extern) scripts as WordPress REST endpoints.
-
-This plugin requires:
-
--   PHP 7.1+
--   WordPress 4.7+
--   CiviCRM to be installed and activated.
-
-### Endpoints
-
-1. `civicrm/v3/rest` - a wrapper around `civicrm_api3()`
-
-    **Parameters**:
-
-    - `key` - **required**, the site key
-    - `api_key` - **required**, the contact api key
-    - `entity` - **required**, the API entity
-    - `action` - **required**, the API action
-    - `json` - **optional**, json formatted string with the API parameters/argumets, or `1` as in `json=1`
-
-    By default all calls to `civicrm/v3/rest` return XML formatted results, to get `json` formatted result pass `json=1` or a json formatted string with the API parameters, like in the example 2 below.
-
-    **Examples**:
-
-    1. `https://example.com/wp-json/civicrm/v3/rest?entity=Contact&action=get&key=<site_key>&api_key=<api_key>&group=Administrators`
-
-    2. `https://example.com/wp-json/civicrm/v3/rest?entity=Contact&action=get&key=<site_key>&api_key=<api_key>&json={"group": "Administrators"}`
-
-2. `civicrm/v3/url` - a substition for `civicrm/extern/url.php` mailing tracking
-
-3. `civicrm/v3/open` - a substition for `civicrm/extern/open.php` mailing tracking
-
-4. `civicrm/v3/authorizeIPN` - a substition for `civicrm/extern/authorizeIPN.php` (for testing Authorize.net as per [docs](https://docs.civicrm.org/sysadmin/en/latest/setup/payment-processors/authorize-net/#shell-script-testing-method))
-
-    **_Note_**: this endpoint has **not been tested**
-
-5. `civicrm/v3/ipn` - a substition for `civicrm/extern/ipn.php` (for PayPal Standard and Pro live transactions)
-
-    **_Note_**: this endpoint has **not been tested**
-
-6. `civicrm/v3/cxn` - a substition for `civicrm/extern/cxn.php`
-
-7. `civicrm/v3/pxIPN` - a substition for `civicrm/extern/pxIPN.php`
-
-    **_Note_**: this endpoint has **not been tested**
-
-8. `civicrm/v3/widget` - a substition for `civicrm/extern/widget.php`
-
-9. `civicrm/v3/soap` - a substition for `civicrm/extern/soap.php`
-
-    **_Note_**: this endpoint has **not been tested**
-
-### Settings
-
-Set the `CIVICRM_WP_REST_REPLACE_MAILING_TRACKING` constant to `true` to replace mailing url and open tracking calls with their counterpart REST endpoints, `civicrm/v3/url` and `civicrm/v3/open`.
-
-_Note: use this setting with caution, it may affect performance on large mailings, see `CiviCRM_WP_REST\Civi\Mailing_Hooks` class._