From c2a9eeb9d0d4d3acf59462f32ac8bda5f93a0eba Mon Sep 17 00:00:00 2001
From: Kevin Cristiano <kcristiano@kcristiano.com>
Date: Wed, 20 Nov 2019 19:27:14 -0500
Subject: [PATCH] civicrm release

---
 civicrm.php                                   |   28 +-
 .../CRM/ACL/Form/WordPress/Permissions.php    |    2 +-
 civicrm/CRM/Admin/Page/Persistent.php         |    2 +-
 civicrm/CRM/Campaign/BAO/Query.php            |    2 +-
 civicrm/CRM/Campaign/Form/Survey/Main.php     |    2 +-
 civicrm/CRM/Campaign/Page/AJAX.php            |    2 +-
 civicrm/CRM/Contact/BAO/SavedSearch.php       |    6 +-
 civicrm/CRM/Contact/Form/Search/Advanced.php  |    2 +-
 civicrm/CRM/Contact/Page/AJAX.php             |    2 +-
 civicrm/CRM/Contact/Page/SavedSearch.php      |    2 +-
 civicrm/CRM/Contribute/BAO/Query.php          |    2 +-
 .../CRM/Contribute/Form/AdditionalPayment.php |   12 +-
 .../Form/ContributionPage/Amount.php          |    2 +-
 .../Form/ContributionPage/Widget.php          |    2 -
 civicrm/CRM/Contribute/Form/Search.php        |   38 +-
 civicrm/CRM/Contribute/Selector/Search.php    |    2 +-
 civicrm/CRM/Core/BAO/Dashboard.php            |   34 +-
 civicrm/CRM/Core/BAO/FinancialTrxn.php        |    8 +
 civicrm/CRM/Core/BAO/Persistent.php           |    4 +-
 civicrm/CRM/Core/BAO/PrevNextCache.php        |    6 +-
 civicrm/CRM/Core/BAO/WordReplacement.php      |    4 +-
 civicrm/CRM/Core/Config.php                   |    2 +-
 civicrm/CRM/Core/DAO.php                      |    2 +-
 civicrm/CRM/Core/Form/Search.php              |   42 +-
 civicrm/CRM/Core/Menu.php                     |    4 +-
 civicrm/CRM/Core/Payment/PaymentExpress.php   |    2 +-
 civicrm/CRM/Cxn/BAO/Cxn.php                   |   17 +-
 civicrm/CRM/Mailing/BAO/Mailing.php           |    4 +-
 civicrm/CRM/Mailing/BAO/TrackableURL.php      |    5 +-
 civicrm/CRM/Member/BAO/Membership.php         |    2 +-
 civicrm/CRM/Report/Form.php                   |   13 +-
 civicrm/CRM/Report/Form/Member/Summary.php    |    2 +-
 .../Upgrade/Incremental/sql/5.19.2.mysql.tpl  |    1 +
 civicrm/CRM/Utils/API/HTMLInputCoder.php      |   68 +-
 civicrm/CRM/Utils/String.php                  |   32 +
 civicrm/CRM/Utils/System.php                  |   35 -
 civicrm/CRM/Utils/System/WordPress.php        |   10 +-
 civicrm/Civi/API/SelectQuery.php              |    6 +
 .../Api4/Generic/Traits/DAOActionTrait.php    |    6 +-
 civicrm/Civi/Api4/Query/Api4SelectQuery.php   |    5 +-
 civicrm/Civi/Api4/Service/Spec/FieldSpec.php  |   23 +-
 .../Civi/Api4/Service/Spec/SpecFormatter.php  |    1 +
 civicrm/Civi/Api4/Utils/FormattingUtil.php    |    7 +
 civicrm/Civi/Core/SettingsBag.php             |    4 +-
 civicrm/Civi/Test/Api3TestTrait.php           |   14 +-
 civicrm/Civi/Test/ContactTestTrait.php        |    2 +-
 civicrm/api/v3/Payment.php                    |   27 +-
 civicrm/api/v3/SavedSearch.php                |    4 +-
 civicrm/civicrm-version.php                   |    2 +-
 civicrm/composer.json                         |   15 +-
 civicrm/composer.lock                         |  147 +-
 .../CRM/Core/Payment/Faps.mgd.php             |   60 +
 .../iatspayments/CRM/Core/Payment/Faps.php    |  593 ++++
 .../iatspayments/CRM/Core/Payment/FapsACH.php |  321 +++
 .../CRM/Core/Payment/Iats.mgd.php             |   76 +
 .../CRM/Core/Payment/iATSService.php          |  227 +-
 .../CRM/Core/Payment/iATSServiceACHEFT.php    |  233 +-
 .../CRM/Core/Payment/iATSServiceSWIPE.php     |   65 +-
 .../CRM/Core/Payment/iATSServiceUKDD.php      |  341 ---
 .../ext/iatspayments/CRM/Iats/FapsRequest.php |  133 +
 .../{iATS => Iats}/Form/IATSCustomerLink.php  |   33 +-
 .../{iATS => Iats}/Form/IATSOneTimeCharge.php |   69 +-
 .../Iats/Form/Report/ContributeDetail.mgd.php |   40 +
 .../Form/Report/ContributeDetail.php          |  633 +++--
 .../Iats/Form/Report/ContributeDetailFaps.php | 1058 ++++++++
 .../Form/Report/Journal.mgd.php               |    4 +-
 .../{iATS => Iats}/Form/Report/Journal.php    |    2 +-
 .../Form/Report/JournalFaps.mgd.php}          |   10 +-
 .../CRM/Iats/Form/Report/JournalFaps.php      |  113 +
 .../{iATS => Iats}/Form/Report/Recur.mgd.php  |    4 +-
 .../CRM/{iATS => Iats}/Form/Report/Recur.php  |   44 +-
 .../{iATS => Iats}/Form/Report/Verify.mgd.php |    4 +-
 .../CRM/{iATS => Iats}/Form/Report/Verify.php |    2 +-
 .../Form/Settings.php}                        |   56 +-
 .../{iATS => Iats}/Page/IATSCustomerLink.php  |    7 +-
 .../CRM/{iATS => Iats}/Page/iATSAdmin.php     |    6 +-
 .../CRM/{iATS => Iats}/Page/json.php          |    5 +-
 .../ext/iatspayments/CRM/Iats/Transaction.php |  414 +++
 .../CRM/{iATS => Iats}/Upgrader.php           |   57 +-
 .../CRM/{iATS => Iats}/Upgrader/Base.php      |  190 +-
 .../iATSServiceRequest.php}                   |   17 +-
 .../Form/IATSCustomerUpdateBillingInfo.php    |   52 -
 civicrm/ext/iatspayments/LICENSE.txt          |  667 +++++
 civicrm/ext/iatspayments/README.md            |   17 +-
 .../api/v3/FapsTransaction/Get.php            |   40 +
 .../api/v3/FapsTransaction/GetJournal.php     |  121 +
 .../api/v3/FapsTransaction/Journal.php        |   89 +
 .../api/v3/IatsPayments/GetJournal.php        |   21 +-
 .../api/v3/IatsPayments/Journal.php           |    3 +
 .../iatspayments/api/v3/Job/Fapsquery.mgd.php |   23 +
 .../ext/iatspayments/api/v3/Job/Fapsquery.php |  152 ++
 .../api/v3/Job/Iatsrecurringcontributions.php |  346 +--
 .../api/v3/Job/Iatsreport.mgd.php             |    4 +-
 .../iatspayments/api/v3/Job/Iatsreport.php    |    5 +-
 .../iatspayments/api/v3/Job/Iatsverify.php    |   65 +-
 civicrm/ext/iatspayments/css/crypto.css       |   47 +
 civicrm/ext/iatspayments/iATS_4.4.14.diff     |   49 -
 civicrm/ext/iatspayments/iATS_4.5.8.diff      |   37 -
 civicrm/ext/iatspayments/iats.civix.php       |  324 ++-
 civicrm/ext/iatspayments/iats.php             | 1206 ++------
 .../ext/iatspayments/images/screenshot.png    |  Bin 0 -> 11775 bytes
 civicrm/ext/iatspayments/info.xml             |   15 +-
 civicrm/ext/iatspayments/js/crypto.js         |   92 +
 civicrm/ext/iatspayments/js/dd_acheft.js      |   16 +-
 civicrm/ext/iatspayments/js/dd_cad.js         |  100 +-
 civicrm/ext/iatspayments/js/dd_uk.js          |  150 -
 civicrm/ext/iatspayments/js/recur_start.js    |   43 +-
 civicrm/ext/iatspayments/js/swipe.js          |   74 +-
 .../ext/iatspayments/release-notes/1.7.0.md   |   19 +
 civicrm/ext/iatspayments/sql/install.sql      |   64 +-
 civicrm/ext/iatspayments/sql/uninstall.sql    |    2 +-
 .../ext/iatspayments/sql/upgrade_1_7_001.sql  |   35 +
 .../Form/Settings.tpl}                        |    0
 .../CRM/{iATS => Iats}/BillingBlockDPM.tpl    |    0
 .../CRM/Iats/BillingBlockDirectDebitExtra.tpl |   20 +
 .../BillingBlockDirectDebitExtra_CAD.tpl      |   27 +-
 .../BillingBlockDirectDebitExtra_Other.tpl    |    0
 .../Iats/BillingBlockDirectDebitExtra_USD.tpl |   19 +
 .../CRM/Iats/BillingBlockFapsACH_CAD.tpl      |   11 +
 .../CRM/Iats/BillingBlockFapsACH_USD.tpl      |   11 +
 .../BillingBlockRecurringExtra.tpl            |    1 -
 .../CRM/{iATS => Iats}/BillingBlockSwipe.tpl  |   17 +-
 .../CRM/{iATS => Iats}/CDN_cheque_500x.jpg    |  Bin
 .../CRM/{iATS => Iats}/ContributionRecur.tpl  |    4 +-
 .../{iATS => Iats}/Form/IATSCustomerLink.tpl  |    0
 .../{iATS => Iats}/Form/IATSOneTimeCharge.tpl |    0
 .../Form/Report/ACHEFTVerify.tpl              |    0
 .../{iATS => Iats}/Form/Report/Journal.tpl    |    0
 .../CRM/{iATS => Iats}/Form/Report/Recur.tpl  |    0
 .../templates/CRM/Iats/Form/Settings.tpl      |   18 +
 .../{iATS => Iats}/Page/IATSCustomerLink.tpl  |    0
 .../CRM/{iATS => Iats}/Page/iATSAdmin.tpl     |    0
 .../CRM/{iATS => Iats}/Subscription.tpl       |    0
 .../CRM/{iATS => Iats}/USD_check_500x.jpg     |  Bin
 .../CRM/{iATS => Iats}/credit_card_reader.jpg |  Bin
 .../CRM/{iATS => Iats}/direct-debit.jpg       |  Bin
 .../templates/CRM/{iATS => Iats}/iats.jpg     |  Bin
 .../CRM/{iATS => Iats}/usb_reader.jpg         |  Bin
 .../iATS/BillingBlockDirectDebitExtra_GBP.tpl |   94 -
 .../iATS/BillingBlockDirectDebitExtra_USD.tpl |   24 -
 .../CRM/iATS/ContributeConfirmExtra_UKDD.tpl  |   41 -
 .../CRM/iATS/ContributeThankYouExtra_UKDD.tpl |   38 -
 .../templates/CRM/iATS/GBP_cheque_500x.jpg    |  Bin 57165 -> 0 bytes
 .../iatspayments/templates/CRM/iATS/bacs.png  |  Bin 2616 -> 0 bytes
 .../CRM/{iATS => Iats}/BaseTestClass.php      |    2 +-
 .../{iATS => Iats}/ContributionIATSTest.php   |    6 +-
 .../iatspayments/tests/phpunit/bootstrap.php  |    2 +-
 civicrm/ext/iatspayments/xml/Menu/iats.xml    |   13 +-
 civicrm/js/Common.js                          |   19 +
 civicrm/js/jquery/jquery.dashboard.js         |    4 +-
 civicrm/release-notes.md                      |    7 +
 civicrm/release-notes/5.19.2.md               |   47 +
 civicrm/sql/civicrm_data.mysql                |    2 +-
 civicrm/sql/civicrm_generated.mysql           |    2 +-
 .../CRM/Contribute/Form/AdditionalPayment.tpl |    2 -
 .../CRM/Contribute/Form/Selector.tpl          |    8 +-
 .../CRM/Contribute/Form/Task/Print.tpl        |    4 +-
 .../templates/CRM/Contribute/Page/Widget.tpl  |    2 +-
 civicrm/vendor/autoload.php                   |    2 +-
 civicrm/vendor/composer/autoload_files.php    |    1 +
 civicrm/vendor/composer/autoload_psr4.php     |    1 +
 civicrm/vendor/composer/autoload_real.php     |   14 +-
 civicrm/vendor/composer/autoload_static.php   |   21 +-
 civicrm/vendor/composer/installed.json        |  163 +-
 .../config/ConfigCacheFactoryInterface.php    |    2 +-
 .../symfony/config/Definition/ArrayNode.php   |   30 +-
 .../symfony/config/Definition/BaseNode.php    |    9 +-
 .../symfony/config/Definition/BooleanNode.php |    6 +-
 .../Builder/ArrayNodeDefinition.php           |   32 +-
 .../config/Definition/Builder/ExprBuilder.php |    4 +-
 .../config/Definition/Builder/NodeBuilder.php |    2 +-
 .../Definition/Builder/NodeDefinition.php     |    2 +-
 .../symfony/config/Definition/EnumNode.php    |    6 +-
 .../config/Definition/PrototypedArrayNode.php |   46 +-
 .../symfony/config/Definition/ScalarNode.php  |    6 +-
 .../config/Definition/VariableNode.php        |    6 +-
 .../config/ResourceCheckerConfigCache.php     |    2 +-
 .../vendor/symfony/config/phpunit.xml.dist    |    2 +-
 .../Compiler/CheckDefinitionValidityPass.php  |    8 +-
 .../Compiler/CheckReferenceValidityPass.php   |    7 +-
 .../dependency-injection/Container.php        |    8 +-
 .../ContainerAwareInterface.php               |    3 +
 .../dependency-injection/ContainerBuilder.php |   28 +-
 .../dependency-injection/Definition.php       |    8 +-
 .../dependency-injection/Dumper/PhpDumper.php |   30 +-
 .../Loader/XmlFileLoader.php                  |    8 +-
 .../Loader/YamlFileLoader.php                 |    8 +-
 .../dependency-injection/phpunit.xml.dist     |    2 +-
 .../symfony/event-dispatcher/GenericEvent.php |    2 +-
 .../symfony/event-dispatcher/phpunit.xml.dist |    2 +-
 .../vendor/symfony/filesystem/Filesystem.php  |    2 +-
 .../symfony/filesystem/phpunit.xml.dist       |    2 +-
 civicrm/vendor/symfony/finder/Finder.php      |   46 +-
 civicrm/vendor/symfony/finder/Glob.php        |   14 +-
 .../vendor/symfony/finder/phpunit.xml.dist    |    2 +-
 .../symfony/polyfill-ctype/composer.json      |    2 +-
 .../vendor/symfony/polyfill-iconv/Iconv.php   |    8 +-
 civicrm/vendor/symfony/polyfill-iconv/LICENSE |    2 +-
 .../symfony/polyfill-iconv/composer.json      |    2 +-
 civicrm/vendor/symfony/process/PhpProcess.php |    6 +-
 .../symfony/process/Pipes/WindowsPipes.php    |   57 +-
 civicrm/vendor/symfony/process/Process.php    |   10 +-
 .../vendor/symfony/process/ProcessBuilder.php |    4 +-
 .../vendor/symfony/process/phpunit.xml.dist   |    2 +-
 .../xkerman/restricted-unserialize/.gitignore |    2 +
 .../restricted-unserialize/.scrutinizer.yml   |   26 +
 .../restricted-unserialize/.travis.yml        |   87 +
 .../xkerman/restricted-unserialize/LICENSE    |    9 +
 .../xkerman/restricted-unserialize/README.md  |  101 +
 .../restricted-unserialize/bin/generate.php   |  173 ++
 .../restricted-unserialize/composer.json      |   52 +
 .../restricted-unserialize/composer.lock      | 2415 +++++++++++++++++
 .../docker/Dockerfile.5.2                     |   26 +
 .../docker/Dockerfile.5.3                     |    8 +
 .../docker/Dockerfile.5.4                     |    6 +
 .../docker/Dockerfile.5.5                     |    8 +
 .../src/xKerman/Restricted/ArrayHandler.php   |   56 +
 .../src/xKerman/Restricted/BooleanHandler.php |   20 +
 .../Restricted/EscapedStringHandler.php       |   34 +
 .../xKerman/Restricted/ExpressionParser.php   |   42 +
 .../src/xKerman/Restricted/FloatHandler.php   |   32 +
 .../xKerman/Restricted/HandlerInterface.php   |   17 +
 .../src/xKerman/Restricted/IntegerHandler.php |   20 +
 .../src/xKerman/Restricted/NullHandler.php    |   20 +
 .../xKerman/Restricted/ParserInterface.php    |   16 +
 .../src/xKerman/Restricted/Source.php         |   88 +
 .../src/xKerman/Restricted/StringHandler.php  |   25 +
 .../Restricted/UnserializeFailedException.php |    8 +
 .../src/xKerman/Restricted/bootstrap.php      |   19 +
 .../src/xKerman/Restricted/function.php       |   16 +
 .../xkerman/restricted-unserialize/phpcs.xml  |    9 +
 .../xkerman/restricted-unserialize/phpmd.xml  |   18 +
 .../restricted-unserialize/phpunit.php52.xml  |   27 +
 .../restricted-unserialize/phpunit.xml        |   21 +
 .../src/ArrayHandler.php                      |   66 +
 .../src/BooleanHandler.php                    |   24 +
 .../src/EscapedStringHandler.php              |   39 +
 .../src/ExpressionParser.php                  |   56 +
 .../src/FloatHandler.php                      |   42 +
 .../src/HandlerInterface.php                  |   21 +
 .../src/IntegerHandler.php                    |   24 +
 .../src/NullHandler.php                       |   24 +
 .../src/ParserInterface.php                   |   20 +
 .../restricted-unserialize/src/Source.php     |  103 +
 .../src/StringHandler.php                     |   31 +
 .../src/UnserializeFailedException.php        |   12 +
 .../restricted-unserialize/src/function.php   |   20 +
 civicrm/xml/version.xml                       |    2 +-
 wp-rest/.editorconfig                         |    9 -
 wp-rest/Autoloader.php                        |  115 -
 wp-rest/Civi/Mailing-Hooks.php                |  136 -
 wp-rest/Controller/AuthorizeIPN.php           |  123 -
 wp-rest/Controller/Base.php                   |  107 -
 wp-rest/Controller/Cxn.php                    |  125 -
 wp-rest/Controller/Open.php                   |  129 -
 wp-rest/Controller/PayPalIPN.php              |  134 -
 wp-rest/Controller/PxIPN.php                  |  139 -
 wp-rest/Controller/Rest.php                   |  522 ----
 wp-rest/Controller/Soap.php                   |   98 -
 wp-rest/Controller/Url.php                    |  214 --
 wp-rest/Controller/Widget.php                 |  214 --
 wp-rest/Endpoint/Endpoint-Interface.php       |   35 -
 wp-rest/Plugin.php                            |  193 --
 wp-rest/README.md                             |   59 -
 264 files changed, 10846 insertions(+), 5829 deletions(-)
 create mode 100644 civicrm/CRM/Upgrade/Incremental/sql/5.19.2.mysql.tpl
 create mode 100644 civicrm/ext/iatspayments/CRM/Core/Payment/Faps.mgd.php
 create mode 100644 civicrm/ext/iatspayments/CRM/Core/Payment/Faps.php
 create mode 100644 civicrm/ext/iatspayments/CRM/Core/Payment/FapsACH.php
 create mode 100644 civicrm/ext/iatspayments/CRM/Core/Payment/Iats.mgd.php
 delete mode 100644 civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceUKDD.php
 create mode 100644 civicrm/ext/iatspayments/CRM/Iats/FapsRequest.php
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/IATSCustomerLink.php (87%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/IATSOneTimeCharge.php (71%)
 create mode 100644 civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetail.mgd.php
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/Report/ContributeDetail.php (70%)
 create mode 100644 civicrm/ext/iatspayments/CRM/Iats/Form/Report/ContributeDetailFaps.php
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/Report/Journal.mgd.php (86%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/Report/Journal.php (98%)
 rename civicrm/ext/iatspayments/CRM/{iATS/Form/Report/ContributeDetail.mgd.php => Iats/Form/Report/JournalFaps.mgd.php} (59%)
 create mode 100644 civicrm/ext/iatspayments/CRM/Iats/Form/Report/JournalFaps.php
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/Report/Recur.mgd.php (87%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/Report/Recur.php (91%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/Report/Verify.mgd.php (86%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Form/Report/Verify.php (98%)
 rename civicrm/ext/iatspayments/CRM/{iATS/Form/IatsSettings.php => Iats/Form/Settings.php} (82%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Page/IATSCustomerLink.php (86%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Page/iATSAdmin.php (94%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Page/json.php (94%)
 create mode 100644 civicrm/ext/iatspayments/CRM/Iats/Transaction.php
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Upgrader.php (69%)
 rename civicrm/ext/iatspayments/CRM/{iATS => Iats}/Upgrader/Base.php (54%)
 rename civicrm/ext/iatspayments/CRM/{iATS/iATSService.php => Iats/iATSServiceRequest.php} (98%)
 delete mode 100644 civicrm/ext/iatspayments/CRM/iATS/Form/IATSCustomerUpdateBillingInfo.php
 create mode 100644 civicrm/ext/iatspayments/LICENSE.txt
 create mode 100644 civicrm/ext/iatspayments/api/v3/FapsTransaction/Get.php
 create mode 100644 civicrm/ext/iatspayments/api/v3/FapsTransaction/GetJournal.php
 create mode 100644 civicrm/ext/iatspayments/api/v3/FapsTransaction/Journal.php
 create mode 100644 civicrm/ext/iatspayments/api/v3/Job/Fapsquery.mgd.php
 create mode 100644 civicrm/ext/iatspayments/api/v3/Job/Fapsquery.php
 create mode 100644 civicrm/ext/iatspayments/css/crypto.css
 delete mode 100644 civicrm/ext/iatspayments/iATS_4.4.14.diff
 delete mode 100644 civicrm/ext/iatspayments/iATS_4.5.8.diff
 create mode 100644 civicrm/ext/iatspayments/images/screenshot.png
 create mode 100644 civicrm/ext/iatspayments/js/crypto.js
 delete mode 100644 civicrm/ext/iatspayments/js/dd_uk.js
 create mode 100644 civicrm/ext/iatspayments/release-notes/1.7.0.md
 create mode 100644 civicrm/ext/iatspayments/sql/upgrade_1_7_001.sql
 rename civicrm/ext/iatspayments/templates/CRM/{iATS/Form/IatsSettings.tpl => Faps/Form/Settings.tpl} (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/BillingBlockDPM.tpl (100%)
 create mode 100644 civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra.tpl
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/BillingBlockDirectDebitExtra_CAD.tpl (57%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/BillingBlockDirectDebitExtra_Other.tpl (100%)
 create mode 100644 civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockDirectDebitExtra_USD.tpl
 create mode 100644 civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockFapsACH_CAD.tpl
 create mode 100644 civicrm/ext/iatspayments/templates/CRM/Iats/BillingBlockFapsACH_USD.tpl
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/BillingBlockRecurringExtra.tpl (78%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/BillingBlockSwipe.tpl (52%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/CDN_cheque_500x.jpg (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/ContributionRecur.tpl (63%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/Form/IATSCustomerLink.tpl (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/Form/IATSOneTimeCharge.tpl (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/Form/Report/ACHEFTVerify.tpl (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/Form/Report/Journal.tpl (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/Form/Report/Recur.tpl (100%)
 create mode 100644 civicrm/ext/iatspayments/templates/CRM/Iats/Form/Settings.tpl
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/Page/IATSCustomerLink.tpl (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/Page/iATSAdmin.tpl (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/Subscription.tpl (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/USD_check_500x.jpg (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/credit_card_reader.jpg (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/direct-debit.jpg (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/iats.jpg (100%)
 rename civicrm/ext/iatspayments/templates/CRM/{iATS => Iats}/usb_reader.jpg (100%)
 delete mode 100644 civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_GBP.tpl
 delete mode 100644 civicrm/ext/iatspayments/templates/CRM/iATS/BillingBlockDirectDebitExtra_USD.tpl
 delete mode 100644 civicrm/ext/iatspayments/templates/CRM/iATS/ContributeConfirmExtra_UKDD.tpl
 delete mode 100644 civicrm/ext/iatspayments/templates/CRM/iATS/ContributeThankYouExtra_UKDD.tpl
 delete mode 100644 civicrm/ext/iatspayments/templates/CRM/iATS/GBP_cheque_500x.jpg
 delete mode 100644 civicrm/ext/iatspayments/templates/CRM/iATS/bacs.png
 rename civicrm/ext/iatspayments/tests/phpunit/CRM/{iATS => Iats}/BaseTestClass.php (99%)
 rename civicrm/ext/iatspayments/tests/phpunit/CRM/{iATS => Iats}/ContributionIATSTest.php (98%)
 create mode 100644 civicrm/release-notes/5.19.2.md
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/.gitignore
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/.scrutinizer.yml
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/.travis.yml
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/LICENSE
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/README.md
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/bin/generate.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/composer.json
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/composer.lock
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.2
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.3
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.4
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/docker/Dockerfile.5.5
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ArrayHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/BooleanHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/EscapedStringHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ExpressionParser.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/FloatHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/HandlerInterface.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/IntegerHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/NullHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/ParserInterface.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/Source.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/StringHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/UnserializeFailedException.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/bootstrap.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/generated/src/xKerman/Restricted/function.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/phpcs.xml
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/phpmd.xml
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/phpunit.php52.xml
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/phpunit.xml
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/ArrayHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/BooleanHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/EscapedStringHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/ExpressionParser.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/FloatHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/HandlerInterface.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/IntegerHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/NullHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/ParserInterface.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/Source.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/StringHandler.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/UnserializeFailedException.php
 create mode 100644 civicrm/vendor/xkerman/restricted-unserialize/src/function.php
 delete mode 100644 wp-rest/.editorconfig
 delete mode 100644 wp-rest/Autoloader.php
 delete mode 100644 wp-rest/Civi/Mailing-Hooks.php
 delete mode 100644 wp-rest/Controller/AuthorizeIPN.php
 delete mode 100644 wp-rest/Controller/Base.php
 delete mode 100644 wp-rest/Controller/Cxn.php
 delete mode 100644 wp-rest/Controller/Open.php
 delete mode 100644 wp-rest/Controller/PayPalIPN.php
 delete mode 100644 wp-rest/Controller/PxIPN.php
 delete mode 100644 wp-rest/Controller/Rest.php
 delete mode 100644 wp-rest/Controller/Soap.php
 delete mode 100644 wp-rest/Controller/Url.php
 delete mode 100644 wp-rest/Controller/Widget.php
 delete mode 100644 wp-rest/Endpoint/Endpoint-Interface.php
 delete mode 100644 wp-rest/Plugin.php
 delete mode 100644 wp-rest/README.md

diff --git a/civicrm.php b/civicrm.php
index 34b8947086..f800fd0d07 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 bad293c934..65191fb979 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 4524da5444..4bf7c19aa6 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 d44e128009..e3841d629e 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 68f074ec19..b1602e3c6e 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 ecec019936..b3c0df44df 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 749a716a49..59c11c58ba 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 a3f46e68de..d2d2963425 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 3ac0326cf7..056b936901 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 a101e86283..6d35a6205b 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 8d5e16ee5c..a47a89ef00 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 6891237964..edd43400b9 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 ef3d9071c5..da176fa1ea 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 9d08453b0c..2a88bc06b5 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 6fdb263afb..67b04cc55a 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 1c2d23a3bc..0c26fb6cbe 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 a94154d178..2c89838c40 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 e8e886d41d..6976048beb 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 2694f8bef0..a408c39107 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 f217d87441..7d7cc05a7b 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 f77891ab25..16099a1c27 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 f8952cf419..d63d3d9b42 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 d1b7668fee..d1e845c4ea 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 fed155bbff..cd2d5bfe63 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 c6fefdc91a..6f19e4a573 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 47db6a690c..28552293fe 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 e304c99b32..658bd61fc6 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 ccb7ce99a6..1223735d80 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 759c1053a8..da57a134c3 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 54ea9d0297..e2c3baee60 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 8090032de0..f136435bde 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 78084e4ab7..2f44f15a40 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 0000000000..ecef477739
--- /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 c0f5cdecc8..4c69f64bb8 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 d0f295dd18..6e53d00776 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 cdb6ffbd3f..b529323688 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 9c074c5a88..382892d314 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 752159b3d7..95bd4ce093 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 29e30f86ea..638c8fe83d 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 f7026ba349..35f6fd8080 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 7a44a1d925..c44d58678d 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 b1c1c804e3..f44e267c7a 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 b845ea6608..f848f5ba26 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 4155e7baf8..bd419c0fca 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 e853f5249b..8c4fcb5eb9 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 50e256177f..f3e85083d9 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 956b62f86f..e86c930c71 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 c515e890e2..3146cda4ea 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 ac63a087d3..674139de0c 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 42578e4e90..5517a57852 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 9f5328a8ba..928a8f3e12 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 0000000000..6d0872b479
--- /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 0000000000..2fe106e312
--- /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 0000000000..49960c5bd2
--- /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 0000000000..816b8d7885
--- /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 2dca9ccab5..28263f72bb 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 a93c32b2e4..657ca95a29 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 decbcdd0a7..af808cdb38 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 16d827ce2c..0000000000
--- 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 0000000000..2542fdd9be
--- /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 39e22b9249..f38b1d0850 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 f4a26a4d0d..8e0eab8ad3 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 0000000000..085373446d
--- /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 0c35702825..dd33ba0e0f 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 0000000000..fc8b0013eb
--- /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 2d2af53e6c..69cfedde43 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 e6c52d52a6..e7866b5738 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 16743f18db..eb0525efab 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 0000000000..316585bbdd
--- /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 507c4343bb..55c1fe34c0 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 c5bbd1b75e..bb98151201 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 bdada3dae4..d5a4a0da44 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 f88e248649..28953c0ee9 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 96bf910fd7..f36087fdee 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 c96cd8f6b0..09e615123d 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 6c34df42f9..787573e9db 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 15faf8edd6..d35630443e 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 0000000000..1dc28981ba
--- /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 74d6ff61da..b6843bc577 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 6cf61525ac..956bcaf5b1 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 6c6d02cae0..ace24b8f78 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 03dd92a309..0000000000
--- 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 0000000000..88ae787b13
--- /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 865e2a6c35..7324fe4810 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 0000000000..9c19b3428a
--- /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 0000000000..dad2dd3544
--- /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 0000000000..33dd534191
--- /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 e228c1f521..c22737ec8a 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 a872753cdc..057082a147 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 0000000000..6a7fe7e81d
--- /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 0000000000..a583ae22c3
--- /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 21510bdb67..72b8948821 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 21df020af5..7d7b45509d 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 90bc3c22ff..00be93662e 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 2145781d63..c986a7c1a0 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 0000000000..b0d65319dd
--- /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 02327db7ad..0000000000
--- 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 48e9b1c5e1..0000000000
--- 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 fc2423c50b..3628541f28 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 65467d75fe..91a2feb147 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
GIT binary patch
literal 11775
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
IrTdTn0|#irRsaA1

literal 0
HcmV?d00001

diff --git a/civicrm/ext/iatspayments/info.xml b/civicrm/ext/iatspayments/info.xml
index 55c5172d5b..bfab31caec 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 0000000000..03926b8920
--- /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 43718b16ca..81980e6755 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 c5a8f3a4ef..f7260c0aa2 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 29e54dd414..0000000000
--- 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 96e20daeeb..1260b7ac2c 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 f7fa40d2c9..32594224be 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 0000000000..12995c3b60
--- /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 8f1d57b386..51cbb41247 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 a9df21d401..b3828917b1 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 0000000000..7803580783
--- /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 0000000000..362b249c20
--- /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 5a4a58ab97..7e7a9ad815 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 0000000000..eee2532410
--- /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 0000000000..017f7b24f6
--- /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 0000000000..393fa4f733
--- /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 049cbe7489..32396790da 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 a57ced3207..d12ef13ad4 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 80629a86b9..eb81edc9b2 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 0000000000..8a7f58ee73
--- /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 8510bcf78c..0000000000
--- 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 a6f0cd2c79..0000000000
--- 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 ac0997d214..0000000000
--- 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 1acedb746d..0000000000
--- 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
GIT binary patch
literal 0
HcmV?d00001

literal 57165
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

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
GIT binary patch
literal 0
HcmV?d00001

literal 2616
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

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 e33a4f9f06..75a51e9889 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 81def34a36..6a45578b01 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 5bef121472..ddb9b30ac0 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 6c93d40991..bf2c127d5a 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 25e228b594..47c6e48b18 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 394635d361..b87db357d1 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 811dddb445..c3aa9e951f 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 0000000000..24e9f4de92
--- /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 a6d89a8b80..0f5cff4903 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 3c45984c71..fcc2d4ffc2 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 23443f10fd..ac7feb83fe 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 144d6544db..bcf283e209 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 ca8cf7a2d1..dcf77779c3 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 125d07d340..1074e2e1a7 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 54464533aa..b9e63cb171 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 ee86ff3ce2..b616de8701 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 9c4781cc87..21b9d8f934 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 51423de2c6..9e24f59743 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 d8eef2c19e..29c48c447a 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 b65a780813..f76f71b861 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 bd614c4b6b..8e80142b78 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 1ab4a3ae59..86eacae40b 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 8885775e60..7ca956e211 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 77e90cf7d6..85f467b6be 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 27fadba6f8..31e918af1c 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 fedbe0cc1b..ddbe5b0401 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 1fac66fd37..95863d68f9 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 f94d3f01f8..a14161f082 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 0cb40018b0..a214a854b7 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 6af4a61e47..eddcb32a77 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 e63f3f227f..53c1ed29c2 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 0cd84c72bf..1a3442d961 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 52ae833d44..81c1b97273 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 36ef339fd7..1cfdb3cdc6 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 f79a4b0f5b..ea1e089179 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 416103ec0e..2b380dd352 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 00b34b5968..25532ead23 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 d78491bb96..e7b9d575ec 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 dc09a65820..0e1391f9f8 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 70f68469e3..d9c37ea504 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 fc8092f4a3..2f530e9e3e 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 82b578bec9..ecebe0125e 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 51bc768c0f..d25f654709 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 781f767d54..21dee2a801 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 95c99408de..f0be7e18ff 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 b3ad1bdf5a..f2eb1692cd 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 5df92e733d..df31620561 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 7bba6fc2f0..5515fff1ac 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 007b688675..1ee9017356 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 2e56cf2800..e2988f2576 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 0e1a8669be..078847af96 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 c24e20ca75..090f923ef1 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 92874f506e..77e7ca056b 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 24fa32c2e9..4cd8bdd300 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 816e6bd7ff..0c23267cbe 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 6bf6bb6749..31a855d943 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 f81f65faca..0b2a76387f 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 9589136c6b..a261ea5340 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 f246c871b8..1bac780d63 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 d38846730d..c32f25101e 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 0000000000..f3dec2a43d
--- /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 0000000000..2560e04363
--- /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 0000000000..7f1342089f
--- /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 0000000000..624d118620
--- /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 0000000000..39051c8727
--- /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 0000000000..6dcb130e43
--- /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 0000000000..5f1e5b1ebc
--- /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 0000000000..fe0a489f31
--- /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 0000000000..7033ef7001
--- /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 0000000000..cbd84b2e40
--- /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 0000000000..53766faf3c
--- /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 0000000000..e8c3a7ef9b
--- /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 0000000000..77f75d7a95
--- /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 0000000000..8df47a425f
--- /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 0000000000..8b47a61ed9
--- /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 0000000000..7a34ef21d0
--- /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 0000000000..f00e000de1
--- /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 0000000000..48c3b74241
--- /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 0000000000..7feda401f4
--- /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 0000000000..44eb911d58
--- /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 0000000000..37abac283f
--- /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 0000000000..6a00b76373
--- /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 0000000000..9c74129a53
--- /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 0000000000..73d3ce20ef
--- /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 0000000000..c59dfa5d6b
--- /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 0000000000..7ef3b47161
--- /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 0000000000..0e3e9f8d34
--- /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 0000000000..02511ba095
--- /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 0000000000..8d1fe03512
--- /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 0000000000..031b7cbefa
--- /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 0000000000..3c5e8325bc
--- /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 0000000000..049eec3088
--- /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 0000000000..2e40741336
--- /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 0000000000..186caca9fc
--- /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 0000000000..0facd98884
--- /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 0000000000..d356cb908b
--- /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 0000000000..efeae2f831
--- /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 0000000000..f359837754
--- /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 0000000000..3c11055979
--- /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 0000000000..50c1ae727b
--- /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 0000000000..4d9c48690e
--- /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 0000000000..31a9ebc505
--- /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 0000000000..57bb67cad6
--- /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 87a13c4530..5c2f7e8ff4 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 09dc3747d3..0000000000
--- 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 dfa95f8a02..0000000000
--- 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 7113088b3b..0000000000
--- 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 4cd9da9a97..0000000000
--- 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 7546377e9e..0000000000
--- 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 7f7cca5c56..0000000000
--- 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 450ef991a3..0000000000
--- 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 5b5c380045..0000000000
--- 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 d68fc8d787..0000000000
--- 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 61706f85fd..0000000000
--- 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 17402cc579..0000000000
--- 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 9286856e7c..0000000000
--- 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 13fa1e2add..0000000000
--- 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 9497cde509..0000000000
--- 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 4038a56b1b..0000000000
--- 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 77234de84a..0000000000
--- 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._
-- 
GitLab