From d7b67902556925c1db3a7c6b7161a68c677fdc72 Mon Sep 17 00:00:00 2001 From: Kevin Cristiano <kcristiano@kcristiano.com> Date: Tue, 28 Mar 2023 06:55:57 -0400 Subject: [PATCH] civicrm release-5.59.4 --- civicrm.php | 4 +- civicrm/CRM/Case/Form/Case.php | 2 +- .../CRM/Contribute/BAO/ContributionSoft.php | 4 +- .../CRM/Contribute/Form/ContributionBase.php | 6 +- civicrm/CRM/Core/Payment.php | 16 ++ civicrm/CRM/Dedupe/Merger.php | 4 +- civicrm/CRM/Event/BAO/Event.php | 5 + civicrm/civicrm-version.php | 2 +- civicrm/ext/afform/admin/info.xml | 2 +- civicrm/ext/afform/core/info.xml | 2 +- civicrm/ext/afform/html/info.xml | 2 +- civicrm/ext/afform/mock/info.xml | 2 +- civicrm/ext/authx/info.xml | 2 +- civicrm/ext/civicrm_admin_ui/info.xml | 2 +- civicrm/ext/civigrant/info.xml | 2 +- civicrm/ext/civiimport/info.xml | 2 +- civicrm/ext/ckeditor4/info.xml | 2 +- .../ext/contributioncancelactions/info.xml | 2 +- civicrm/ext/elavon/info.xml | 2 +- civicrm/ext/eventcart/info.xml | 2 +- civicrm/ext/ewaysingle/info.xml | 2 +- civicrm/ext/financialacls/info.xml | 2 +- civicrm/ext/flexmailer/info.xml | 2 +- civicrm/ext/greenwich/info.xml | 2 +- .../iatspayments/CRM/Core/Payment/Faps.php | 19 +- .../iatspayments/CRM/Core/Payment/FapsACH.php | 14 +- .../CRM/Core/Payment/iATSService.php | 27 ++- .../CRM/Core/Payment/iATSServiceACHEFT.php | 14 +- .../iatspayments/CRM/Iats/Form/Settings.php | 12 ++ .../ext/iatspayments/CRM/Iats/Transaction.php | 72 ++++++- .../CRM/Iats/iATSServiceRequest.php | 78 +++++-- civicrm/ext/iatspayments/README.md | 2 + .../api/v3/Job/Iatsrecurringcontributions.php | 46 +++-- .../iatspayments/api/v3/Job/Iatsreport.php | 2 - .../iatspayments/api/v3/Job/Iatsverify.php | 4 +- civicrm/ext/iatspayments/iats.php | 19 +- civicrm/ext/iatspayments/info.xml | 8 +- civicrm/ext/iatspayments/js/dd_cad.js | 4 +- civicrm/ext/iatspayments/phpunit.xml.dist | 2 +- .../ext/iatspayments/release-notes/1.7.5.md | 27 +++ .../tests/phpunit/CRM/Iats/BaseTestClass.php | 192 ++++-------------- .../phpunit/CRM/Iats/ContributionIATSTest.php | 36 ++-- .../iatspayments/tests/phpunit/bootstrap.php | 1 - civicrm/ext/legacycustomsearches/info.xml | 2 +- civicrm/ext/message_admin/info.xml | 2 +- civicrm/ext/oauth-client/info.xml | 2 +- civicrm/ext/payflowpro/info.xml | 2 +- civicrm/ext/recaptcha/info.xml | 2 +- civicrm/ext/search_kit/info.xml | 2 +- civicrm/ext/sequentialcreditnotes/info.xml | 2 +- civicrm/release-notes.md | 9 + civicrm/release-notes/5.59.4.md | 43 ++++ civicrm/sql/civicrm_data.mysql | 2 +- civicrm/sql/civicrm_generated.mysql | 2 +- .../Contribute/Form/Contribution/Confirm.tpl | 10 +- .../Contribute/Form/Contribution/ThankYou.tpl | 4 +- civicrm/vendor/autoload.php | 2 +- civicrm/vendor/composer/autoload_real.php | 14 +- civicrm/vendor/composer/autoload_static.php | 12 +- civicrm/vendor/composer/installed.php | 4 +- civicrm/xml/version.xml | 2 +- 61 files changed, 449 insertions(+), 323 deletions(-) create mode 100644 civicrm/ext/iatspayments/release-notes/1.7.5.md create mode 100644 civicrm/release-notes/5.59.4.md diff --git a/civicrm.php b/civicrm.php index b9787f8b90..23e7cdd85d 100644 --- a/civicrm.php +++ b/civicrm.php @@ -2,7 +2,7 @@ /** * Plugin Name: CiviCRM * Description: CiviCRM - Growing and Sustaining Relationships - * Version: 5.59.3 + * Version: 5.59.4 * Requires at least: 4.9 * Requires PHP: 7.3 * Author: CiviCRM LLC @@ -36,7 +36,7 @@ if (!defined('ABSPATH')) { } // Set version here: changing it forces Javascript and CSS to reload. -define('CIVICRM_PLUGIN_VERSION', '5.59.3'); +define('CIVICRM_PLUGIN_VERSION', '5.59.4'); // Store reference to this file. if (!defined('CIVICRM_PLUGIN_FILE')) { diff --git a/civicrm/CRM/Case/Form/Case.php b/civicrm/CRM/Case/Form/Case.php index ec036cebc4..76800a4358 100644 --- a/civicrm/CRM/Case/Form/Case.php +++ b/civicrm/CRM/Case/Form/Case.php @@ -200,7 +200,7 @@ class CRM_Case_Form_Case extends CRM_Core_Form { */ public function setDefaultValues() { if ($this->_action & CRM_Core_Action::DELETE || $this->_action & CRM_Core_Action::RENEW) { - return TRUE; + return []; } $className = "CRM_Case_Form_Activity_{$this->_activityTypeFile}"; $defaults = $className::setDefaultValues($this); diff --git a/civicrm/CRM/Contribute/BAO/ContributionSoft.php b/civicrm/CRM/Contribute/BAO/ContributionSoft.php index 2e0115bec9..8c7160f944 100644 --- a/civicrm/CRM/Contribute/BAO/ContributionSoft.php +++ b/civicrm/CRM/Contribute/BAO/ContributionSoft.php @@ -391,10 +391,10 @@ class CRM_Contribute_BAO_ContributionSoft extends CRM_Contribute_DAO_Contributio // This is necessary for dataTables sorting. $dataTableMapping = [ 'sct_label' => 'soft_credit_type_id:label', - 'contributor_name' => 'contact.sort_name', + 'contributor_name' => 'contact_id.sort_name', 'financial_type' => 'contribution_id.financial_type_id:label', 'contribution_status' => 'contribution_id.contribution_status_id:label', - 'receive_date' => 'contribution.receive_date', + 'receive_date' => 'contribution_id.receive_date', 'pcp_title' => 'pcp_id.title', 'amount' => 'amount', ]; diff --git a/civicrm/CRM/Contribute/Form/ContributionBase.php b/civicrm/CRM/Contribute/Form/ContributionBase.php index c31e6ee9af..024e262c0b 100644 --- a/civicrm/CRM/Contribute/Form/ContributionBase.php +++ b/civicrm/CRM/Contribute/Form/ContributionBase.php @@ -779,10 +779,10 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form { // The concept of contributeMode is deprecated. // The payment processor object can provide info about the fields it shows. - if ($isMonetary && is_a($this->_paymentProcessor['object'], 'CRM_Core_Payment')) { - /** @var \CRM_Core_Payment $paymentProcessorObject */ + if ($isMonetary && $this->_paymentProcessor['object'] instanceof \CRM_Core_Payment) { $paymentProcessorObject = $this->_paymentProcessor['object']; - + $this->assign('paymentAgreementTitle', $paymentProcessorObject->getText('agreementTitle', [])); + $this->assign('paymentAgreementText', $paymentProcessorObject->getText('agreementText', [])); $paymentFields = $paymentProcessorObject->getPaymentFormFields(); foreach ($paymentFields as $index => $paymentField) { if (!isset($this->_params[$paymentField])) { diff --git a/civicrm/CRM/Core/Payment.php b/civicrm/CRM/Core/Payment.php index 84e47800bc..10afed4e59 100644 --- a/civicrm/CRM/Core/Payment.php +++ b/civicrm/CRM/Core/Payment.php @@ -619,6 +619,22 @@ abstract class CRM_Core_Payment { } return ''; + case 'agreementTitle': + if ($this->getPaymentTypeName() !== 'direct_debit' || $this->_paymentProcessor['billing_mode'] != 1) { + return ''; + } + // @todo - 'encourage' processors to override... + // CRM_Core_Error::deprecatedWarning('Payment processors should override getText for agreement text'); + return ts('Agreement'); + + case 'agreementText': + if ($this->getPaymentTypeName() !== 'direct_debit' || $this->_paymentProcessor['billing_mode'] != 1) { + return ''; + } + // @todo - 'encourage' processors to override... + // CRM_Core_Error::deprecatedWarning('Payment processors should override getText for agreement text'); + return ts('Your account data will be used to charge your bank account via direct debit. While submitting this form you agree to the charging of your bank account via direct debit.'); + } CRM_Core_Error::deprecatedFunctionWarning('Calls to getText must use a supported method'); return ''; diff --git a/civicrm/CRM/Dedupe/Merger.php b/civicrm/CRM/Dedupe/Merger.php index 2c02b615e1..5e352409b0 100644 --- a/civicrm/CRM/Dedupe/Merger.php +++ b/civicrm/CRM/Dedupe/Merger.php @@ -2122,9 +2122,9 @@ ORDER BY civicrm_custom_group.weight, if (strpos($key, 'custom_') === 0) { $fieldID = (int) substr($key, 7); if (empty($cFields[$fieldID])) { - $htmlType = $cFields[$fieldID]['attributes']['html_type']; + $htmlType = (string) $cFields[$fieldID]['attributes']['html_type']; $isSerialized = CRM_Core_BAO_CustomField::isSerialized($cFields[$fieldID]['attributes']); - $isView = $cFields[$fieldID]['attributes']['is_view']; + $isView = (bool) $cFields[$fieldID]['attributes']['is_view']; $submitted = self::processCustomFields($mainId, $key, $submitted, $value, $fieldID, $isView, $htmlType, $isSerialized); } diff --git a/civicrm/CRM/Event/BAO/Event.php b/civicrm/CRM/Event/BAO/Event.php index 23d26017fe..ce018aadf5 100644 --- a/civicrm/CRM/Event/BAO/Event.php +++ b/civicrm/CRM/Event/BAO/Event.php @@ -108,7 +108,12 @@ class CRM_Event_BAO_Event extends CRM_Event_DAO_Event implements \Civi\Core\Hook if (!empty($params['template_id'])) { $copy = self::copy($params['template_id']); $params['id'] = $copy->id; + unset($params['template_id']); + // unless we are explicitly trying to create a new template + // we want to set the `is_template` flag on the clone to false + // so that the copy is a new event rather than a new template + $params['is_template'] = $params['is_template'] ?? 0; //fix for api from template creation bug civicrm_api4('ActionSchedule', 'update', [ diff --git a/civicrm/civicrm-version.php b/civicrm/civicrm-version.php index b08926b6d4..4b9c6850ef 100644 --- a/civicrm/civicrm-version.php +++ b/civicrm/civicrm-version.php @@ -1,7 +1,7 @@ <?php /** @deprecated */ function civicrmVersion( ) { - return array( 'version' => '5.59.3', + return array( 'version' => '5.59.4', 'cms' => 'Wordpress', 'revision' => '' ); } diff --git a/civicrm/ext/afform/admin/info.xml b/civicrm/ext/afform/admin/info.xml index 4979591a57..72a3c53f90 100644 --- a/civicrm/ext/afform/admin/info.xml +++ b/civicrm/ext/afform/admin/info.xml @@ -13,7 +13,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-01-09</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>beta</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/afform/core/info.xml b/civicrm/ext/afform/core/info.xml index 7baa13bdcb..dd0a196c11 100644 --- a/civicrm/ext/afform/core/info.xml +++ b/civicrm/ext/afform/core/info.xml @@ -13,7 +13,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-01-09</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>beta</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/afform/html/info.xml b/civicrm/ext/afform/html/info.xml index fb592924c1..c360e03868 100644 --- a/civicrm/ext/afform/html/info.xml +++ b/civicrm/ext/afform/html/info.xml @@ -13,7 +13,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-01-09</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>alpha</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/afform/mock/info.xml b/civicrm/ext/afform/mock/info.xml index e3248cf419..4337a37ed2 100644 --- a/civicrm/ext/afform/mock/info.xml +++ b/civicrm/ext/afform/mock/info.xml @@ -12,7 +12,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-01-09</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <tags> <tag>mgmt:hidden</tag> </tags> diff --git a/civicrm/ext/authx/info.xml b/civicrm/ext/authx/info.xml index c900047955..358aca262d 100644 --- a/civicrm/ext/authx/info.xml +++ b/civicrm/ext/authx/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2021-02-11</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/civicrm_admin_ui/info.xml b/civicrm/ext/civicrm_admin_ui/info.xml index 634f5d155e..1cf065a4c3 100644 --- a/civicrm/ext/civicrm_admin_ui/info.xml +++ b/civicrm/ext/civicrm_admin_ui/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2022-01-02</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>alpha</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/civigrant/info.xml b/civicrm/ext/civigrant/info.xml index b88e1be01a..17a488eb54 100644 --- a/civicrm/ext/civigrant/info.xml +++ b/civicrm/ext/civigrant/info.xml @@ -13,7 +13,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2021-11-11</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/civiimport/info.xml b/civicrm/ext/civiimport/info.xml index f0de9fa387..1097ce32be 100644 --- a/civicrm/ext/civiimport/info.xml +++ b/civicrm/ext/civiimport/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2022-08-11</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>alpha</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/ckeditor4/info.xml b/civicrm/ext/ckeditor4/info.xml index d6b7352471..af8a9d240b 100644 --- a/civicrm/ext/ckeditor4/info.xml +++ b/civicrm/ext/ckeditor4/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">https://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2021-05-23</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/contributioncancelactions/info.xml b/civicrm/ext/contributioncancelactions/info.xml index 1cec3c0ca3..f40b8c2d32 100644 --- a/civicrm/ext/contributioncancelactions/info.xml +++ b/civicrm/ext/contributioncancelactions/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-10-12</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/elavon/info.xml b/civicrm/ext/elavon/info.xml index 55b7e2fb5d..cf49eca08d 100644 --- a/civicrm/ext/elavon/info.xml +++ b/civicrm/ext/elavon/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2022-08-05</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/eventcart/info.xml b/civicrm/ext/eventcart/info.xml index cbcfbfffcf..11f7c54850 100644 --- a/civicrm/ext/eventcart/info.xml +++ b/civicrm/ext/eventcart/info.xml @@ -13,7 +13,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-08-03</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <tags> <tag>mgmt:hidden</tag> </tags> diff --git a/civicrm/ext/ewaysingle/info.xml b/civicrm/ext/ewaysingle/info.xml index b2e3597b78..644b6c005d 100644 --- a/civicrm/ext/ewaysingle/info.xml +++ b/civicrm/ext/ewaysingle/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-10-07</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <tags> <tag>mgmt:hidden</tag> </tags> diff --git a/civicrm/ext/financialacls/info.xml b/civicrm/ext/financialacls/info.xml index f7aa50b2a8..f79347bbbf 100644 --- a/civicrm/ext/financialacls/info.xml +++ b/civicrm/ext/financialacls/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-08-27</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/flexmailer/info.xml b/civicrm/ext/flexmailer/info.xml index f3c8a564f2..8abc677e8f 100644 --- a/civicrm/ext/flexmailer/info.xml +++ b/civicrm/ext/flexmailer/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-08-05</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <comments> FlexMailer is an email delivery engine which replaces the internal guts diff --git a/civicrm/ext/greenwich/info.xml b/civicrm/ext/greenwich/info.xml index a1c8efd52b..4d92444446 100644 --- a/civicrm/ext/greenwich/info.xml +++ b/civicrm/ext/greenwich/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-07-21</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <tags> <tag>mgmt:hidden</tag> </tags> diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.php b/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.php index 5719b25bcf..d72b13c58e 100644 --- a/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.php +++ b/civicrm/ext/iatspayments/CRM/Core/Payment/Faps.php @@ -191,7 +191,7 @@ class CRM_Core_Payment_Faps extends CRM_Core_Payment { $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 @@ -268,18 +268,17 @@ class CRM_Core_Payment_Faps extends CRM_Core_Payment { */ public function doPayment(&$params, $component = 'contribute') { // CRM_Core_Error::debug_var('doPayment params', $params); + if (empty($params['amount'])) { + return _iats_payment_status_complete(); + } - // 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); + if ($isRecur && empty($params['contributionRecurID'])) { + return self::error('Invalid call to doPayment with is_recur and no contributionRecurID'); } - $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']; $usingCrypto = !empty($params['cryptogram']); - $ipAddress = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']); + // FAPS only allows ipv4 addresses + $ipAddress = CRM_Iats_Transaction::remote_ip_address(FILTER_FLAG_IPV4); $credentials = array( 'merchantKey' => $this->_paymentProcessor['signature'], 'processorId' => $this->_paymentProcessor['user_name'] diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/FapsACH.php b/civicrm/ext/iatspayments/CRM/Core/Payment/FapsACH.php index ebb1bec2fe..7b9a7f1a52 100644 --- a/civicrm/ext/iatspayments/CRM/Core/Payment/FapsACH.php +++ b/civicrm/ext/iatspayments/CRM/Core/Payment/FapsACH.php @@ -125,14 +125,16 @@ class CRM_Core_Payment_FapsACH extends CRM_Core_Payment_Faps { */ public function doPayment(&$params, $component = 'contribute') { // CRM_Core_Error::debug_var('doPayment params', $params); + if (empty($params['amount'])) { + return _iats_payment_status_complete(); + } - // 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); + if ($isRecur && empty($params['contributionRecurID'])) { + return self::error('Invalid call to doPayment with is_recur and no contributionRecurID'); } - $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']; - $ipAddress = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']); + // FAPS only allows ipv4 addresses + $ipAddress = CRM_Iats_Transaction::remote_ip_address($FILTER_FLAG_IPV4); $credentials = array( 'merchantKey' => $this->_paymentProcessor['signature'], 'processorId' => $this->_paymentProcessor['user_name'] diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php index d1948eba9b..95169d269d 100644 --- a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php +++ b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSService.php @@ -170,16 +170,22 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment { */ public function doPayment(&$params, $component = 'contribute') { + if (empty($params['amount'])) { + return _iats_payment_status_complete(); + } if (!$this->_profile) { return self::error('Unexpected error, missing profile'); } // Use the iATSService object for interacting with iATS. Recurring contributions go through a more complex process. - $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']; + $isRecur = CRM_Utils_Array::value('is_recur', $params); + if ($isRecur && empty($params['contributionRecurID'])) { + return self::error('Invalid call to doPayment with is_recur and no contributionRecurID'); + } $methodType = $isRecur ? 'customer' : 'process'; $method = $isRecur ? 'create_credit_card_customer' : 'cc'; - $iats = new CRM_Iats_iATSServiceRequest(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'], 'currency' => $params['currency'])); $request = $this->convertParams($params, $method); - $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']); + $request['customerIPAddress'] = CRM_Iats_Transaction::remote_ip_address(); $credentials = array( 'agentCode' => $this->_paymentProcessor['user_name'], 'password' => $this->_paymentProcessor['password'], @@ -272,7 +278,7 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment { } 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'])); + $iats = new CRM_Iats_iATSServiceRequest(array('type' => 'process', 'method' => 'cc_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currency' => $params['currency'])); $request = array('invoiceNum' => $params['invoiceID']); $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount'])); $request['customerCode'] = $customer_code; @@ -462,17 +468,8 @@ class CRM_Core_Payment_iATSService extends CRM_Core_Payment { */ public function updateSubscriptionBillingInfo(&$message = '', $params = array()) { - // 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']; + // updatedBillingInfo array has changed a few times, we'll try a few different keys to pull the contribution recurring id + $crid = !empty($params['contributionRecurID']) ? $params['contributionRecurID'] : (!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); diff --git a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php index 5332d44cca..4c1765deab 100644 --- a/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php +++ b/civicrm/ext/iatspayments/CRM/Core/Payment/iATSServiceACHEFT.php @@ -203,17 +203,23 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService { */ public function doPayment(&$params, $component = 'contribute') { + if (empty($params['amount'])) { + return _iats_payment_status_complete(); + } if (!$this->_profile) { return self::error('Unexpected error, missing profile'); } // Use the iATSService object for interacting with iATS, mostly the same for recurring contributions. // We handle both one-time and recurring ACH/EFT - $isRecur = CRM_Utils_Array::value('is_recur', $params) && $params['contributionRecurID']; + $isRecur = CRM_Utils_Array::value('is_recur', $params); + if ($isRecur && empty($params['contributionRecurID'])) { + return self::error('Invalid call to doPayment with is_recur and no contributionRecurID'); + } $methodType = $isRecur ? 'customer' : 'process'; $method = $isRecur ? 'create_acheft_customer_code' : 'acheft'; - $iats = new CRM_Iats_iATSServiceRequest(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'], 'currency' => $params['currency'])); $request = $this->convertParams($params, $method); - $request['customerIPAddress'] = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']); + $request['customerIPAddress'] = CRM_Iats_Transaction::remote_ip_address(); $credentials = array( 'agentCode' => $this->_paymentProcessor['user_name'], 'password' => $this->_paymentProcessor['password'], @@ -313,7 +319,7 @@ class CRM_Core_Payment_iATSServiceACHEFT extends CRM_Core_Payment_iATSService { return $params; } else { - $iats = new CRM_Iats_iATSServiceRequest(array('type' => 'process', 'method' => 'acheft_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currencyID' => $params['currencyID'])); + $iats = new CRM_Iats_iATSServiceRequest(array('type' => 'process', 'method' => 'acheft_with_customer_code', 'iats_domain' => $this->_profile['iats_domain'], 'currency' => $params['currency'])); $request = array('invoiceNum' => $params['invoiceID']); $request['total'] = sprintf('%01.2f', CRM_Utils_Rule::cleanMoney($params['amount'])); $request['customerCode'] = $customer_code; diff --git a/civicrm/ext/iatspayments/CRM/Iats/Form/Settings.php b/civicrm/ext/iatspayments/CRM/Iats/Form/Settings.php index ff46b3bfb6..2805404e67 100644 --- a/civicrm/ext/iatspayments/CRM/Iats/Form/Settings.php +++ b/civicrm/ext/iatspayments/CRM/Iats/Form/Settings.php @@ -16,7 +16,13 @@ class CRM_Iats_Form_Settings extends CRM_Core_Form { 'email_recurring_failure_report', ts('Email Recurring Contribution failure reports to this Email address') ); + $this->add( + 'email', + 'bcc_email_recurring_failure_report', + ts('BCC 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->addRule('bcc_email_recurring_failure_report', ts('Email address is not a valid format.'), 'email'); $this->add( 'text', 'recurring_failure_threshhold', @@ -30,6 +36,12 @@ class CRM_Iats_Form_Settings extends CRM_Core_Form { ts('Email receipt for a Contribution in a Recurring Series'), $receipt_recurring_options ); + + $this->add( + 'checkbox', + 'email_failure_contribution_receipt', + ts('Email receipt for a Contribution if Recurring payment fails - with error message') + ); $this->add( 'checkbox', diff --git a/civicrm/ext/iatspayments/CRM/Iats/Transaction.php b/civicrm/ext/iatspayments/CRM/Iats/Transaction.php index 32857cce6e..f0a84aebc3 100644 --- a/civicrm/ext/iatspayments/CRM/Iats/Transaction.php +++ b/civicrm/ext/iatspayments/CRM/Iats/Transaction.php @@ -80,7 +80,7 @@ class CRM_Iats_Transaction { * Used in the Iatsrecurringcontributions.php job and the one-time ('card on file') form. * */ - static function process_contribution_payment(&$contribution, $paymentProcessor, $payment_token) { + static function process_contribution_payment(&$contribution, $paymentProcessor, $payment_token, $contributionRecurUpdate = []) { // By default, don't use repeattransaction $use_repeattransaction = FALSE; $is_recurrence = !empty($contribution['original_contribution_id']); @@ -93,7 +93,7 @@ class CRM_Iats_Transaction { // 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 */ + /* set the failed transaction status (=4), or pending (= 2) 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; @@ -108,6 +108,34 @@ class CRM_Iats_Transaction { // 2. if we don't already have a contribution id $use_repeattransaction = $is_recurrence && empty($contribution['id']); } + // Update the recurring contribution record with the next scheduled contribution date + // Note: applies if we have come from the iats recurring contribution job. + if (!empty($contributionRecurUpdate)) { + /* by default, just set the failure count back to 0 */ + /* special handling for confirmed, hopefully transient, card failures: try again at next opportunity if we haven't failed too often */ + if (4 == $contribution['contribution_status_id']) { + $contributionRecurUpdate['failure_count'] = $contributionRecurUpdate['failure_count'] + 1; + /* 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 ($contributionRecurUpdate['failure_count'] < $contributionRecurUpdate['failure_threshold']) { + // Override/set the next_sched_contribution_date to it's current value so we can try again. + $contributionRecurUpdate['next_sched_contribution_date'] = $contributionRecurUpdate['current_sched_contribution_date']; + } + // otherwise, we'll keep the next collection date as passed in from the caller + } + elseif (!empty($auth_code)) { + // set the failure count back to zero unless I had a server issue + $contributionRecurUpdate['failure_count'] = 0; + } + // unset some of the passed in values that were useful but not part of the recurring record + unset($contributionRecurUpdate['failure_threshold']); + unset($contributionRecurUpdate['current_sched_contribution_date']); + try { + civicrm_api3('ContributionRecur', 'create', $contributionRecurUpdate); + } + catch (Exception $e) { + // Ignore this, though perhaps I should log it. + } + } if ($use_repeattransaction) { // We processed it successflly and I can try to use repeattransaction. // Requires the original contribution id. @@ -141,13 +169,18 @@ class CRM_Iats_Transaction { // 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'], - )); + try { + 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'], + )); + } + catch (Exception $e) { + // Not sure why this might fail, but let's be careful + } // 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) { @@ -390,7 +423,7 @@ class CRM_Iats_Transaction { * @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. + * A low-level utility function used to get an array of the next allowable start dates */ static function get_future_monthly_start_dates($start_date, $allow_days) { // Future date options. @@ -399,7 +432,7 @@ class CRM_Iats_Transaction { $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); + $allow_days = range(1,31); } for ($j = 0; $j < count($allow_days); $j++) { // So I don't get into an infinite loop somehow .. @@ -422,4 +455,21 @@ class CRM_Iats_Transaction { } return $start_dates; } + + + /* + * Get the ip of the source of the transaction. + * + * Test to make sure we're not sending an invalid value! + * $param $filter = an optional additional FILTER to validate the ip + */ + static function remote_ip_address($filter = 0) { + $ipAddress = (function_exists('ip_address') ? ip_address() : $_SERVER['REMOTE_ADDR']); + $filter = $filter | FILTER_FLAG_NO_PRIV_RANGE; + if (!filter_var($ipAddress, FILTER_VALIDATE_IP, $filter)) { + $ipAddress = ''; + } + return $ipAddress; + } + } diff --git a/civicrm/ext/iatspayments/CRM/Iats/iATSServiceRequest.php b/civicrm/ext/iatspayments/CRM/Iats/iATSServiceRequest.php index 1370012442..7e149e17df 100644 --- a/civicrm/ext/iatspayments/CRM/Iats/iATSServiceRequest.php +++ b/civicrm/ext/iatspayments/CRM/Iats/iATSServiceRequest.php @@ -75,19 +75,19 @@ class CRM_Iats_iATSServiceRequest { $this->options = $options; $this->options['debug'] = _iats_civicrm_domain_info('debug_enabled'); // Check for valid currencies with domain/method combinations. - if (isset($options['currencyID'])) { + if (isset($options['currency'])) { $valid = FALSE; switch ($iats_domain) { case 'www2.iatspayments.com': case 'www.iatspayments.com': - if (in_array($options['currencyID'], array('USD', 'CAD'))) { + if (in_array($options['currency'], array('USD', 'CAD'))) { $valid = TRUE; } break; case 'www.uk.iatspayments.com': if ('cc' == substr($method, 0, 2) || 'create_credit_card_customer' == $method) { - if (in_array($options['currencyID'], array('AUD', 'USD', 'EUR', 'GBP', 'IEE', 'CHF', 'HKD', 'JPY', 'SGD', 'MXN'))) { + if (in_array($options['currency'], array('AUD', 'USD', 'EUR', 'GBP', 'IEE', 'CHF', 'HKD', 'JPY', 'SGD', 'MXN'))) { $valid = TRUE; } } @@ -98,7 +98,7 @@ class CRM_Iats_iATSServiceRequest { break; } if (!$valid) { - throw new PaymentProcessorException(ts('Invalid currency selection: %1 for domain %2', [1 => $options['currencyID'], 2=> $iats_domain])); + throw new PaymentProcessorException(ts('Invalid currency selection: %1 for domain %2', [1 => $options['currency'], 2=> $iats_domain])); } } } @@ -272,8 +272,8 @@ class CRM_Iats_iATSServiceRequest { case 'process': if (!empty($response->PROCESSRESULT)) { $processresult = $response->PROCESSRESULT; - $result['auth_result'] = trim(current($processresult->AUTHORIZATIONRESULT)); - $result['remote_id'] = current($processresult->TRANSACTIONID); + $result['auth_result'] = trim(((array) $processresult->AUTHORIZATIONRESULT)[0] ?? ''); + $result['remote_id'] = ((array) $processresult->TRANSACTIONID)[0] ?? ''; // If we didn't get an approval response code... // Note: do not use SUCCESS property, which just means iATS said "hello". $result['status'] = (substr($result['auth_result'], 0, 2) == self::iATS_TXN_OK) ? 1 : 0; @@ -621,7 +621,7 @@ class CRM_Iats_iATSServiceRequest { return 'Invalid transaction. Verify and re-enter credit card information.'; case 'REJECT: 6': - return 'Please have cardholder call the number on the back of the card.'; + return 'US: Please have cardholder call the number on the back of the card. UK/EU: Transaction not supported by institution.'; case 'REJECT: 7': return 'Lost or stolen card.'; @@ -680,10 +680,10 @@ class CRM_Iats_iATSServiceRequest { return 'Please have cardholder call the number on the back of the card.'; case 'REJECT: 32': - return 'Invalid charge card number.'; + return 'Invalid Credit Card Number.'; case 'REJECT: 39': - return 'Contact iATS at 1-888-955-5455.'; + return 'US: Contact iATS at 1-888-955-5455. UK/EU: Contact IATS at 0808-234-0466'; case 'REJECT: 40': return 'Invalid card number. Card not supported by iATS.'; @@ -695,7 +695,7 @@ class CRM_Iats_iATSServiceRequest { return 'CVV2 required.'; case 'REJECT: 43': - return 'Incorrect AVS.'; + return 'US: Contact iATS at 1-888-955-5455. UK/EU: Incorrect AVS.'; case 'REJECT: 45': return 'Credit card name blocked. Call iATS at 1-888-955-5455.'; @@ -721,11 +721,65 @@ class CRM_Iats_iATSServiceRequest { case 'REJECT: 52': return 'Credit card BIN country blocked. Call iATS at 1-888-955-5455.'; + case 'REJECT: 53': + return 'Wrong File Total'; + + case 'REJECT: 54': + return 'Wrong Currency'; + + case 'REJECT: 55': + return 'System Error'; + + case 'REJECT: 56': + return 'REJ Other'; + + case 'REJECT: 60': + return 'Set-up error. Please contact IATS Customer Service at 0808-234-0466.'; + + case 'REJECT: 61': + return 'Authentication failed.'; + + case 'REJECT: 62': + return 'Invalid timestamp. Please contact IATS Customer Service at 0808-234-0466.'; + + case 'REJECT: 63': + return 'Payment voided.'; + + case 'REJECT: 64': + return 'Pre-authorization expired / CC mismatch / Insufficient amount.'; + + case 'REJECT: 65': + return 'Unsupported card scheme. Please contact IATS Customer Service at 0808-234-0466.'; + + case 'REJECT: 66': + return 'Set-up error - Channel issue. Please contact IATS Customer Service at 0808-234-0466.'; + + case 'REJECT: 67': + return 'Set-up error - No submitter. Please contact IATS Customer Service at 0808-234-0466.'; + + case 'REJECT: 68': + return 'Void payment failure.'; + + case 'REJECT: 69': + return 'Unexpected response. Please contact IATS Customer Service at 0808-234-0466.'; + + case 'REJECT: 70': + return 'Result indeterminate. Please contact IATS Customer Service at 0808-234-0466.'; + + case 'REJECT: 71': + return 'Mandatory field missing.'; + + case 'REJECT: 91': + return 'Issuer unavailable.'; + + case 'REJECT: 98': + return 'Mod Check on RDFI-ID Failed.'; + case 'REJECT: 100': return 'DO NOT REPROCESS. Call iATS at 1-888-955-5455.'; - case 'Timeout': - return 'The system has not responded in the time allotted. Call iATS at 1-888-955-5455.'; + case 'TIMEOUT': + return 'The system has not responded in the time allotted. Call iATS at US: 1-888-955-5455, UK/EU: 0808-234-0466'; } return $code; diff --git a/civicrm/ext/iatspayments/README.md b/civicrm/ext/iatspayments/README.md index db73818c88..5fb5fc9b53 100644 --- a/civicrm/ext/iatspayments/README.md +++ b/civicrm/ext/iatspayments/README.md @@ -109,6 +109,8 @@ Some issues may be related to core CiviCRM issues, and may not have an immediate Below is a list of some of the most common issues: +Unexpected failures. If you get an unexpectedly large number of failures for your recurring contributions, please review this page to understand how the extension does it's best to handle them and what administrators can do: https://github.com/iATSPayments/com.iatspayments.civicrm/wiki/Recurring-Contribution-Failure-Handling + 9002 Error - if you get this when trying to make a contribution, then you're getting that error back from the iATS server due to an account misconfiguration. When this happens and your using a 'legacy' iATS account -> check if you have special characters in your password (and remove them). If you're using '1st Pay' contact iATS Customer Service to ensure your account is configured properly. CiviCRM core assigns Membership status (=new) and extends Membership End date as well as Event status (=registered) as soon as ACH/EFT is submitted (so while payment is still pending - this could be several days for ACH/EFT). If the contribution receives a Ok:BankAccept -> the extension will mark the contribution in CiviCRM as completed. If the contribution does NOT receive a Ok:BankAccept -> the extension will mark the contribution in CiviCRM as rejected - however - associated existing Membership and Event records may need to be updated manually. diff --git a/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php b/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php index 632bbe4de9..aeb3a49ec6 100644 --- a/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php +++ b/civicrm/ext/iatspayments/api/v3/Job/Iatsrecurringcontributions.php @@ -106,7 +106,7 @@ function civicrm_api3_job_Iatsrecurringcontributions($params) { $error_count = 0; $output = []; $settings = Civi::settings()->get('iats_settings'); - $receipt_recurring = $settings['receipt_recurring']; + $receipt_recurring = $settings['receipt_recurring'] ?? null; $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']; @@ -124,7 +124,6 @@ function civicrm_api3_job_Iatsrecurringcontributions($params) { // CRM_Core_Error::debug_var('Contribution Template', $contribution_template); // generate my invoice id like CiviCRM does $hash = md5(uniqid(rand(), TRUE)); - $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 )', [ @@ -234,31 +233,20 @@ function civicrm_api3_job_Iatsrecurringcontributions($params) { // 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) */ + // Assemble an array of recurring information so that process_contribution_payment can update the recurring record. + // But first: calculate 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. + // The next collection date is based on receive_ts, "recieve timestamp" (note effect of catchup mode, above) $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); + // Note: keep track of the currently defined "next_sched_contribution_date" as "current_sched_contribution_date" in case of confirmed transient card failures. + $contribution_recur_update = array('id' => $contribution['contribution_recur_id'], 'next_sched_contribution_date' => $next_collection_date, 'failure_count' => $recurringContribution['failure_count'], 'failure_threshold' => $failure_threshhold, 'current_sched_contribution_date' => $recurringContribution['next_sched_contribution_date']); + // process the payment and update the contribution and recurring contribution records: + $result = CRM_Iats_Transaction::process_contribution_payment($contribution, $paymentProcessor, $payment_token, $contribution_recur_update); // 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: 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 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. - $contribution_recur_set['next_sched_contribution_date'] = $saved_next_sched_contribution_date; - } - } - civicrm_api('ContributionRecur', 'create', $contribution_recur_set); $result = civicrm_api('activity', 'create', array( 'version' => 3, @@ -317,14 +305,28 @@ function civicrm_api3_job_Iatsrecurringcontributions($params) { list($fromName, $fromEmail) = CRM_Core_BAO_Domain::getNameAndEmail(); $mailparams = array( 'from' => $fromName . ' <' . $fromEmail . '> ', - 'to' => 'System Administrator <' . $email_failure_report . '>', + 'toName' => empty($fromName) ? ts('System Administrator') : $fromName, + 'toEmail' => $email_failure_report, + 'bcc' => !empty($bcc_email_failure_report) ? $bcc_email_failure_report : '', 'subject' => ts('iATS Recurring Payment job failure report: ' . date('c')), 'text' => $failure_report_text, - 'returnPath' => $fromEmail, ); // print_r($mailparams); CRM_Utils_Mail::send($mailparams); } + + // Send receipt with error message if [recurring only?] contribution fails $email_failure_contribution_receipt + // CRM_Core_Error::debug_var('Contribution', $contribution); + // CRM_Core_Error::debug_var('iATS response message', $failure_report_text); + if ((strlen($failure_report_text) > 0) && $email_failure_contribution_receipt) { + return civicrm_api3('Contribution', 'sendconfirmation', [ + 'id' => $contribution['id'], + 'receipt_from_name' => empty($fromName) ? ts('Admin') : $fromName, + 'receipt_from_email' => $fromEmail, + 'receipt_text' => ts('It seems something is not quite right with your recurring contribution payment. Please see details below.') . '<hr><br>' . $failure_report_text, + 'bcc_receipt' => !empty($email_failure_report)? $email_failure_report: $fromEmail, + ]); + } // If errors .. if ($error_count > 0) { return civicrm_api3_create_error( diff --git a/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php b/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php index a86648b1fe..549d7530ad 100644 --- a/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php +++ b/civicrm/ext/iatspayments/api/v3/Job/Iatsreport.php @@ -96,7 +96,6 @@ function civicrm_api3_job_iatsreport($params) { foreach ($process_methods_per_type as $method => $payment_status_id) { // initialize my counts $processed[$user_name][$type][$method] = 0; - // watchdog('civicrm_iatspayments_com', 'pp: <pre>!pp</pre>', array('!pp' => print_r($payment_processor,TRUE)), WATCHDOG_NOTICE); /* 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; @@ -187,7 +186,6 @@ function civicrm_api3_job_iatsreport($params) { } } Civi::settings()->set('iats_journal', $iats_journal); - // watchdog('civicrm_iatspayments_com', 'found: <pre>!found</pre>', array('!found' => print_r($processed,TRUE)), WATCHDOG_NOTICE); $message = ''; foreach ($processed as $user_name => $p) { foreach ($p as $type => $ps) { diff --git a/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php b/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php index 61346f1f1b..e648291c70 100644 --- a/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php +++ b/civicrm/ext/iatspayments/api/v3/Job/Iatsverify.php @@ -69,7 +69,7 @@ function _civicrm_api3_job_iatsverify_spec(&$spec) { function civicrm_api3_job_iatsverify($params) { $settings = Civi::settings()->get('iats_settings'); - $receipt_recurring = $settings['receipt_recurring']; + $receipt_recurring = $settings['receipt_recurring'] ?? null; define('IATS_VERIFY_DAYS', 30); // I've added an extra 2 days when getting candidates from CiviCRM to be sure i've got them all. $verify_days = IATS_VERIFY_DAYS + 2; @@ -183,7 +183,7 @@ function civicrm_api3_job_iatsverify($params) { // Restore source field and trxn_id that completetransaction overwrites civicrm_api3('contribution', 'create', array( 'id' => $contribution['id'], - 'source' => $contribution['source'], + 'source' => ($contribution['contribution_source'] ?? $contribution['source']), 'trxn_id' => $trxn_id, )); break; diff --git a/civicrm/ext/iatspayments/iats.php b/civicrm/ext/iatspayments/iats.php index 8f253aa1b3..23b32e0d73 100644 --- a/civicrm/ext/iatspayments/iats.php +++ b/civicrm/ext/iatspayments/iats.php @@ -338,10 +338,13 @@ function iats_civicrm_buildForm($formName, &$form) { /** * Modifications to a (public/frontend) contribution financial forms for iATS * procesors. - * 1. enable public selection of future recurring contribution start date. + * 1. enable public selection of future recurring contribution start date, but only if the form allows recurring! * * 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. + * Special note - if a page offers recurring contributions and if + * future recurring start dates are enabled with restrictions on which days, + * then any one-time contribution will be forced to use those rules as well. */ function iats_civicrm_buildForm_CRM_Financial_Form_Payment(&$form) { @@ -354,8 +357,11 @@ function iats_civicrm_buildForm_CRM_Financial_Form_Payment(&$form) { if (empty($type)) { return; } - - // If enabled provide a way to set future contribution dates. + // Skip if this form doesn't allow recurring + if (empty($form->_values['is_recur'])) { + return; + } + // If future public start dates are enabled on a recurring-enabled page ... // Uses javascript to hide/reset unless they have recurring contributions checked. $settings = Civi::settings()->get('iats_settings'); if (!empty($settings['enable_public_future_recurring_start']) @@ -819,3 +825,10 @@ function iats_civicrm_buildForm_CRM_Contribute_Form_UpdateBilling(&$form) { $form->addElement('hidden', 'crid', $crid); } } + +function _iats_payment_status_complete() { + return [ + 'payment_status_id' => CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_Contribution', 'contribution_status_id', 'Completed'), + 'payment_status' => 'Completed', + ]; +} diff --git a/civicrm/ext/iatspayments/info.xml b/civicrm/ext/iatspayments/info.xml index 76edc14539..3b43883353 100644 --- a/civicrm/ext/iatspayments/info.xml +++ b/civicrm/ext/iatspayments/info.xml @@ -9,7 +9,7 @@ <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.7.4.md</url> + <url desc="Release Notes">https://github.com/iATSPayments/com.iatspayments.civicrm/blob/master/release-notes/1.7.5.md</url> <url desc="Getting Started">https://www.semper-it.com/civicamp-iats-payments-slides</url> </urls> <license>AGPL-3.0</license> @@ -17,11 +17,11 @@ <author>Alan Dixon</author> <email>iats@blackflysolutions.ca</email> </maintainer> - <releaseDate>2021-03-22</releaseDate> - <version>1.7.4</version> + <releaseDate>2023-03-08</releaseDate> + <version>1.7.5</version> <develStage>stable</develStage> <compatibility> - <ver>5.13</ver> + <ver>5.49</ver> </compatibility> <comments>A recommended 1.7.x maintenance release, see release notes above for details.</comments> <civix> diff --git a/civicrm/ext/iatspayments/js/dd_cad.js b/civicrm/ext/iatspayments/js/dd_cad.js index f7260c0aa2..f412abd38b 100644 --- a/civicrm/ext/iatspayments/js/dd_cad.js +++ b/civicrm/ext/iatspayments/js/dd_cad.js @@ -21,7 +21,7 @@ CRM.$(function ($) { }); $('#cad_bank_number').blur(function(eventObj) { var myCount = onlyNumbers($(this)); - if (myCount != 3) { + if ((myCount > 0) && (myCount != 3)) { $(this).crmError(ts('Your Bank Number requires three digits, use a leading "0" if necessary')); } switch($(this).val()) { @@ -48,7 +48,7 @@ CRM.$(function ($) { }); $('#cad_transit_number').blur(function(eventObj) { var myCount = onlyNumbers($(this)); - if (myCount != 5) { + if ((myCount > 0) && (myCount != 5)) { $(this).crmError(ts('Your Bank Transit Number requires exactly five digits')); } iatsSetBankIdenficationNumber(); diff --git a/civicrm/ext/iatspayments/phpunit.xml.dist b/civicrm/ext/iatspayments/phpunit.xml.dist index 754565f404..2e4e326f14 100644 --- a/civicrm/ext/iatspayments/phpunit.xml.dist +++ b/civicrm/ext/iatspayments/phpunit.xml.dist @@ -1,5 +1,5 @@ <?xml version="1.0"?> -<phpunit backupGlobals="false" backupStaticAttributes="false" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" bootstrap="tests/phpunit/bootstrap.php"> +<phpunit backupGlobals="false" backupStaticAttributes="false" colors="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" bootstrap="tests/phpunit/bootstrap.php" cacheResult="false"> <testsuites> <testsuite name="iATS Test Suite"> <directory>./tests/phpunit</directory> diff --git a/civicrm/ext/iatspayments/release-notes/1.7.5.md b/civicrm/ext/iatspayments/release-notes/1.7.5.md new file mode 100644 index 0000000000..1394b3ac4d --- /dev/null +++ b/civicrm/ext/iatspayments/release-notes/1.7.5.md @@ -0,0 +1,27 @@ +# iATS CiviCRM Extension 1.7.5 + +Mar 8, 2023 + +This release is a maintenance release for the 1.7 series. + +It is recommended for all CiviCRM installs on 5.57+ and above. + +This release covers a collection of accumulated issues and improvements since Mar 2021. + +The most important changes are in handling failures and in complex combinations with public selection of future recurring contributions. + +Here's a fuller list with thanks and credit to their authors: + +1. @mattwire: update use of parameter currencyID to currency +2. Some unit test updates from @seamuslee001 and demeritcowboy +3. Handle $0 amount transactions +4. @yurg: updated iats response code labels +5. @yurg: create an option to email an contribution invoice on recurring contribution failure +6. @seamuslee001: filter out ipv6 addresses as client ip for FirstPay +7. also prevent internal ips from getting sent as client ips (e.g. when using a front-end proxy). +8. various fixes to how the "public selection of future recurring contribution dates" is implemented. +9. @seamuslee001: update to recurring contribution flow to avoid multiple charges when other issues cause failures +10. @shaneonabike: handle invalid doPayment requests that have is_recur but no recurring contribution id +11. @demeritcowboy and @paulrooney php8 fixes +12. @shaneonabike: better handling of ACH user input errors +13. @demeritcowboy fix of contribution source field issue diff --git a/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/BaseTestClass.php b/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/BaseTestClass.php index 75a51e9889..40c5523dab 100644 --- a/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/BaseTestClass.php +++ b/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/BaseTestClass.php @@ -17,7 +17,8 @@ use Civi\Test\TransactionalInterface; * b. Disable TransactionalInterface, and handle all setup/teardown yourself. */ abstract class BaseTestClass extends \PHPUnit\Framework\TestCase implements HeadlessInterface, HookInterface, TransactionalInterface { - //class BaseTestClass extends \PHPUnit_Framework_TestCase implements HeadlessInterface, HookInterface { + + use \Civi\Test\Api3TestTrait; /** * Configure the headless environment. @@ -30,167 +31,58 @@ abstract class BaseTestClass extends \PHPUnit\Framework\TestCase implements Head ->apply(); } - private $_apiversion = 3; - /** - * wrap api functions. - * so we can ensure they succeed & throw exceptions without litterering the test with checks + * Copied from civicrm core CiviUnitTestCase. * - * @param string $entity - * @param string $action - * @param array $params - * @param mixed $checkAgainst - * Optional value to check result against, implemented for getvalue,. - * getcount, getsingle. Note that for getvalue the type is checked rather than the value - * for getsingle the array is compared against an array passed in - the id is not compared (for - * better or worse ) + * Instantiate form object. * - * @return array|int - */ - public function callAPISuccess($entity, $action, $params, $checkAgainst = NULL) { - $params = array_merge(array( - 'version' => $this->_apiversion, - 'debug' => 1, - ), - $params - ); - switch (strtolower($action)) { - case 'getvalue': - return $this->callAPISuccessGetValue($entity, $params, $checkAgainst); - - case 'getsingle': - return $this->callAPISuccessGetSingle($entity, $params, $checkAgainst); - - case 'getcount': - return $this->callAPISuccessGetCount($entity, $params, $checkAgainst); - } - $result = $this->civicrm_api($entity, $action, $params); - $this->assertAPISuccess($result, "Failure in api call for $entity $action"); - return $result; - } - /** - * This function exists to wrap api getValue function & check the result - * so we can ensure they succeed & throw exceptions without litterering the test with checks - * There is a type check in this + * We need to instantiate the form to run preprocess, which means we have to trick it about the request method. * - * @param string $entity - * @param array $params - * @param string $type - * Per http://php.net/manual/en/function.gettype.php possible types. - * - boolean - * - integer - * - double - * - string - * - array - * - object + * @param string $class + * Name of form class. * - * @return array|int - */ - public function callAPISuccessGetValue($entity, $params, $type = NULL) { - $params += array( - 'version' => $this->_apiversion, - 'debug' => 1, - ); - $result = $this->civicrm_api($entity, 'getvalue', $params); - if ($type) { - if ($type == 'integer') { - // api seems to return integers as strings - $this->assertTrue(is_numeric($result), "expected a numeric value but got " . print_r($result, 1)); - } - else { - $this->assertType($type, $result, "returned result should have been of type $type but was "); - } - } - return $result; - } - /** - * This function exists to wrap api getValue function & check the result - * so we can ensure they succeed & throw exceptions without litterering the test with checks - * There is a type check in this - * @param string $entity - * @param array $params - * @param null $count - * @throws Exception - * @return array|int - */ - public function callAPISuccessGetCount($entity, $params, $count = NULL) { - $params += array( - 'version' => $this->_apiversion, - 'debug' => 1, - ); - $result = $this->civicrm_api($entity, 'getcount', $params); - if (!is_int($result) || !empty($result['is_error']) || isset($result['values'])) { - throw new Exception('Invalid getcount result : ' . print_r($result, TRUE) . " type :" . gettype($result)); - } - if (is_int($count)) { - $this->assertEquals($count, $result, "incorrect count returned from $entity getcount"); - } - return $result; - } - /** - * This function exists to wrap api getsingle function & check the result - * so we can ensure they succeed & throw exceptions without litterering the test with checks + * @param array $formValues * - * @param string $entity - * @param array $params - * @param array $checkAgainst - * Array to compare result against. - * - boolean - * - integer - * - double - * - string - * - array - * - object + * @param string $pageName * - * @throws Exception - * @return array|int - */ - public function callAPISuccessGetSingle($entity, $params, $checkAgainst = NULL) { - $params += array( - 'version' => $this->_apiversion, - 'debug' => 1, - ); - $result = $this->civicrm_api($entity, 'getsingle', $params); - if (!is_array($result) || !empty($result['is_error']) || isset($result['values'])) { - throw new Exception('Invalid getsingle result' . print_r($result, TRUE)); - } - if ($checkAgainst) { - // @todo - have gone with the fn that unsets id? should we check id? - $this->checkArrayEquals($result, $checkAgainst); - } - return $result; - } - /** - * Check that api returned 'is_error' => 0. + * @param array $searchFormValues + * Values for the search form if the form is a task eg. + * for selected ids 6 & 8: + * [ + * 'radio_ts' => 'ts_sel', + * 'task' => CRM_Member_Task::PDF_LETTER, + * 'mark_x_6' => 1, + * 'mark_x_8' => 1, + * ] * - * @param array $apiResult - * Api result. - * @param string $prefix - * Extra test to add to message. + * @return \CRM_Core_Form */ - public function assertAPISuccess($apiResult, $prefix = '') { - if (!empty($prefix)) { - $prefix .= ': '; + public function getFormObject($class, $formValues = [], $pageName = '', $searchFormValues = []) { + $_POST = $formValues; + /* @var CRM_Core_Form $form */ + $form = new $class(); + $_SERVER['REQUEST_METHOD'] = 'GET'; + switch ($class) { + case 'CRM_Event_Cart_Form_Checkout_Payment': + case 'CRM_Event_Cart_Form_Checkout_ParticipantsAndPrices': + $form->controller = new CRM_Event_Cart_Controller_Checkout(); + break; + + default: + $form->controller = new CRM_Core_Controller(); } - $errorMessage = empty($apiResult['error_message']) ? '' : " " . $apiResult['error_message']; - if (!empty($apiResult['debug_information'])) { - $errorMessage .= "\n " . print_r($apiResult['debug_information'], TRUE); + if (!$pageName) { + $pageName = $form->getName(); } - if (!empty($apiResult['trace'])) { - $errorMessage .= "\n" . print_r($apiResult['trace'], TRUE); + $form->controller->setStateMachine(new CRM_Core_StateMachine($form->controller)); + $_SESSION['_' . $form->controller->_name . '_container']['values'][$pageName] = $formValues; + if ($searchFormValues) { + $_SESSION['_' . $form->controller->_name . '_container']['values']['Search'] = $searchFormValues; } - $this->assertEquals(0, $apiResult['is_error'], $prefix . $errorMessage); - } - /** - * A stub for the API interface. This can be overriden by subclasses to change how the API is called. - * - * @param $entity - * @param $action - * @param array $params - * @return array|int - */ - public function civicrm_api($entity, $action, $params) { - return civicrm_api($entity, $action, $params); + if (isset($formValues['_qf_button_name'])) { + $_SESSION['_' . $form->controller->_name . '_container']['_qf_button_name'] = $formValues['_qf_button_name']; + } + return $form; } } diff --git a/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/ContributionIATSTest.php b/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/ContributionIATSTest.php index f81d2e57b2..3811c32946 100644 --- a/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/ContributionIATSTest.php +++ b/civicrm/ext/iatspayments/tests/phpunit/CRM/Iats/ContributionIATSTest.php @@ -35,19 +35,19 @@ class CRM_Iats_ContributioniATSTest extends BaseTestClass { ->apply(); } - public function setUp() { + public function setUp(): void { $this->_apiversion = 3; parent::setUp(); } - public function tearDown() { + public function tearDown(): void { parent::tearDown(); } /** * Test a Credit Card Contribution - one time iATS Credit Card - TEST41 - Backend */ - public function testIATSCreditCardBackend() { + public function testIATSCreditCardBackend(): void { $params = array( 'sequential' => 1, @@ -64,14 +64,11 @@ class CRM_Iats_ContributioniATSTest extends BaseTestClass { $processor = $this->paymentProcessor->getPaymentProcessor(); $this->paymentProcessorID = $processor['id']; - $form = new CRM_Contribute_Form_Contribution(); - $form->_mode = 'Live'; - $contribution_params = array( + 'soft_credit_contact_id' => array(), 'total_amount' => 1.00, 'financial_type_id' => 1, - 'receive_date' => '08/03/2017', - 'receive_date_time' => '11:59PM', + 'receive_date' => date('Y-m-d H:i:s'), 'contact_id' => $individual['id'], 'payment_instrument_id' => 1, 'contribution_status_id' => 1, @@ -96,13 +93,16 @@ class CRM_Iats_ContributioniATSTest extends BaseTestClass { 'hidden_AdditionalDetail' => 1, 'hidden_Premium' => 1, 'receipt_date' => '', - 'receipt_date_time' => '', + 'cancel_date' => '', 'payment_processor_id' => $this->paymentProcessorID, 'currency' => 'CAD', 'source' => 'iATS CC TEST88', ); - $form->testSubmit($contribution_params, CRM_Core_Action::ADD); + $form = $this->getFormObject('CRM_Contribute_Form_Contribution', $contribution_params); + $form->buildForm(); + $form->_mode = 'Live'; + $form->postProcess(); $contribution = $this->callAPISuccessGetSingle('Contribution', array( 'contact_id' => $individual['id'], @@ -120,7 +120,7 @@ class CRM_Iats_ContributioniATSTest extends BaseTestClass { /** * Test a SWIPE Contribution - one time iATS SWIPE - TEST41 - Backend */ - public function testIATSSWIPEBackend() { + public function testIATSSWIPEBackend(): void { $params = array( 'sequential' => 1, @@ -137,14 +137,11 @@ class CRM_Iats_ContributioniATSTest extends BaseTestClass { $processor = $this->paymentProcessor->getPaymentProcessor(); $this->paymentProcessorID = $processor['id']; - $form = new CRM_Contribute_Form_Contribution(); - $form->_mode = 'Live'; - $contribution_params = array( + 'soft_credit_contact_id' => array(), 'total_amount' => 2.00, 'financial_type_id' => 1, - 'receive_date' => '08/03/2017', - 'receive_date_time' => '11:59PM', + 'receive_date' => date('Y-m-d H:i:s'), 'contact_id' => $individual['id'], 'payment_instrument_id' => 1, 'contribution_status_id' => 1, @@ -170,13 +167,16 @@ class CRM_Iats_ContributioniATSTest extends BaseTestClass { 'hidden_AdditionalDetail' => 1, 'hidden_Premium' => 1, 'receipt_date' => '', - 'receipt_date_time' => '', + 'cancel_date' => '', 'payment_processor_id' => $this->paymentProcessorID, 'currency' => 'CAD', 'source' => 'iATS SWIPE TEST88', ); - $form->testSubmit($contribution_params, CRM_Core_Action::ADD); + $form = $this->getFormObject('CRM_Contribute_Form_Contribution', $contribution_params); + $form->buildForm(); + $form->_mode = 'Live'; + $form->postProcess(); $contribution = $this->callAPISuccessGetSingle('Contribution', array( 'contact_id' => $individual['id'], diff --git a/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php b/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php index ddb9b30ac0..df1524daf1 100644 --- a/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php +++ b/civicrm/ext/iatspayments/tests/phpunit/bootstrap.php @@ -1,7 +1,6 @@ <?php ini_set('memory_limit', '2G'); -ini_set('safe_mode', 0); eval(cv('php:boot --level=classloader', 'phpcode')); require_once __DIR__ . '/CRM/Iats/BaseTestClass.php'; diff --git a/civicrm/ext/legacycustomsearches/info.xml b/civicrm/ext/legacycustomsearches/info.xml index 0b63aa514f..fd05619ca0 100644 --- a/civicrm/ext/legacycustomsearches/info.xml +++ b/civicrm/ext/legacycustomsearches/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2021-07-25</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <tags> <tag>mgmt:hidden</tag> diff --git a/civicrm/ext/message_admin/info.xml b/civicrm/ext/message_admin/info.xml index 6cbd34f579..27dd551427 100644 --- a/civicrm/ext/message_admin/info.xml +++ b/civicrm/ext/message_admin/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2021-06-12</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>alpha</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/oauth-client/info.xml b/civicrm/ext/oauth-client/info.xml index 62afa975ce..b5f07c6f09 100644 --- a/civicrm/ext/oauth-client/info.xml +++ b/civicrm/ext/oauth-client/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-10-23</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/payflowpro/info.xml b/civicrm/ext/payflowpro/info.xml index b505fe1948..5aa54aaf0a 100644 --- a/civicrm/ext/payflowpro/info.xml +++ b/civicrm/ext/payflowpro/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2021-04-13</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <compatibility> <ver>5.59</ver> diff --git a/civicrm/ext/recaptcha/info.xml b/civicrm/ext/recaptcha/info.xml index 87b8b910cf..7eb2eca5e1 100644 --- a/civicrm/ext/recaptcha/info.xml +++ b/civicrm/ext/recaptcha/info.xml @@ -13,7 +13,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2021-04-03</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <tags> <tag>mgmt:hidden</tag> </tags> diff --git a/civicrm/ext/search_kit/info.xml b/civicrm/ext/search_kit/info.xml index 100768b71b..ed8ca3afd2 100644 --- a/civicrm/ext/search_kit/info.xml +++ b/civicrm/ext/search_kit/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2021-01-06</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <develStage>stable</develStage> <tags> <tag>mgmt:required</tag> diff --git a/civicrm/ext/sequentialcreditnotes/info.xml b/civicrm/ext/sequentialcreditnotes/info.xml index 87067952f1..1fb3612d03 100644 --- a/civicrm/ext/sequentialcreditnotes/info.xml +++ b/civicrm/ext/sequentialcreditnotes/info.xml @@ -15,7 +15,7 @@ <url desc="Licensing">http://www.gnu.org/licenses/agpl-3.0.html</url> </urls> <releaseDate>2020-01-28</releaseDate> - <version>5.59.3</version> + <version>5.59.4</version> <tags> <tag>mgmt:hidden</tag> </tags> diff --git a/civicrm/release-notes.md b/civicrm/release-notes.md index f0703c8e4c..7741436a04 100644 --- a/civicrm/release-notes.md +++ b/civicrm/release-notes.md @@ -15,6 +15,15 @@ Other resources for identifying changes are: * https://github.com/civicrm/civicrm-joomla * https://github.com/civicrm/civicrm-wordpress +## CiviCRM 5.59.4 + +Released March 27, 2023 + +- **[Synopsis](release-notes/5.59.4.md#synopsis)** +- **[Bugs resolved](release-notes/5.59.4.md#bugs)** +- **[Credits](release-notes/5.59.4.md#credits)** +- **[Feedback](release-notes/5.59.4.md#feedback)** + ## CiviCRM 5.59.3 Released March 15, 2023 diff --git a/civicrm/release-notes/5.59.4.md b/civicrm/release-notes/5.59.4.md new file mode 100644 index 0000000000..59272f37d8 --- /dev/null +++ b/civicrm/release-notes/5.59.4.md @@ -0,0 +1,43 @@ +# CiviCRM 5.59.4 + +Released March 27, 2023 + +- **[Synopsis](#synopsis)** +- **[Bugs resolved](#bugs)** +- **[Credits](#credits)** +- **[Feedback](#feedback)** + +## <a name="synopsis"></a>Synopsis + +| *Does this version...?* | | +| --------------------------------------------------------------- | -------- | +| Change the database schema? | no | +| Alter the API? | no | +| Require attention to configuration options? | no | +| Fix problems installing or upgrading to a previous version? | no | +| Introduce features? | no | +| **Fix bugs?** | **yes** | +| Fix security vulnerabilities? | no | + +## <a name="bugs"></a>Bugs resolved + +* **_CiviCase_: "Delete Case" leads to "Network Error" ([dev/core#4190](https://lab.civicrm.org/dev/core/-/issues/4190): [#25849](https://github.com/civicrm/civicrm-core/pull/25849))** +* **_CiviContribute_: Sorting by soft-credit name/date leads to AJAX error ([dev/core#4187](https://lab.civicrm.org/dev/core/-/issues/4187): [#25846](https://github.com/civicrm/civicrm-core/pull/25846))** +* **_CiviContribute_: Confirmation screen displays extraneous debit fields ([dev/core#4189](https://lab.civicrm.org/dev/core/-/issues/4189): [#25910](https://github.com/civicrm/civicrm-core/pull/25910))** +* **_CiviEvent_: Event created via API incorrectly marked as template ([dev/core#4205](https://lab.civicrm.org/dev/core/-/issues/4205): [#25932](https://github.com/civicrm/civicrm-core/pull/25932))** +* **_Dedupe/Merge_: Harden against possibility of inconsistent value for `is_view` ([dev/core#4197](https://lab.civicrm.org/dev/core/-/issues/4197): [#25912](https://github.com/civicrm/civicrm-core/pull/25912))** + +## <a name="credits"></a>Credits + +This release was developed by the following authors and reviewers: + +Wikimedia Foundation - Eileen McNaughton; Romy Ebert; MJW Consulting - Matthew Wire; +Lemniscus - Noah Miller; Korlon - Stuart Gaston; JMA Consulting - Seamus Lee; Coop +SymbioTIC - Mathieu Lutfy; composerjk; CiviCRM - Tim Otten; Black Brick Software - David +Hayes; Benjamin W; Andreas Howiller + +## <a name="feedback"></a>Feedback + +These release notes are edited by Tim Otten and Andie Hunt. If you'd like to +provide feedback on them, please login to https://chat.civicrm.org/civicrm and +contact `@agh1`. diff --git a/civicrm/sql/civicrm_data.mysql b/civicrm/sql/civicrm_data.mysql index 88867fe817..ee8acfbe9f 100644 --- a/civicrm/sql/civicrm_data.mysql +++ b/civicrm/sql/civicrm_data.mysql @@ -23678,4 +23678,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.59.3'; +UPDATE civicrm_domain SET version = '5.59.4'; diff --git a/civicrm/sql/civicrm_generated.mysql b/civicrm/sql/civicrm_generated.mysql index 0858513092..d747b24a9b 100644 --- a/civicrm/sql/civicrm_generated.mysql +++ b/civicrm/sql/civicrm_generated.mysql @@ -3055,7 +3055,7 @@ UNLOCK TABLES; LOCK TABLES `civicrm_domain` WRITE; /*!40000 ALTER TABLE `civicrm_domain` DISABLE KEYS */; INSERT INTO `civicrm_domain` (`id`, `name`, `description`, `version`, `contact_id`, `locales`, `locale_custom_strings`) VALUES - (1,'Default Domain Name',NULL,'5.59.3',1,NULL,'a:1:{s:5:\"en_US\";a:0:{}}'); + (1,'Default Domain Name',NULL,'5.59.4',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/Contribution/Confirm.tpl b/civicrm/templates/CRM/Contribute/Form/Contribution/Confirm.tpl index 9ef4ad93d0..bff0c22270 100644 --- a/civicrm/templates/CRM/Contribute/Form/Contribution/Confirm.tpl +++ b/civicrm/templates/CRM/Contribute/Form/Contribution/Confirm.tpl @@ -243,25 +243,25 @@ {$paymentFieldsetLabel} </div> {/if} - {if in_array('bank_account_number', $form)} + {if in_array('bank_account_number', $form) && $bank_account_number} <div class="display-block"> {ts}Account Holder{/ts}: {$account_holder}<br/> {ts}Bank Account Number{/ts}: {$bank_account_number}<br/> {ts}Bank Identification Number{/ts}: {$bank_identification_number}<br/> {ts}Bank Name{/ts}: {$bank_name}<br/> </div> - {if $contributeMode eq 'direct'} + {if $paymentAgreementText} <div class="crm-group debit_agreement-group"> <div class="header-dark"> - {ts}Agreement{/ts} + {$paymentAgreementTitle} </div> <div class="display-block"> - {ts}Your account data will be used to charge your bank account via direct debit. While submitting this form you agree to the charging of your bank account via direct debit.{/ts} + {$paymentAgreementText} </div> </div> {/if} {/if} - {if in_array('credit_card_number', $form)} + {if in_array('credit_card_number', $form) && $credit_card_number} <div class="crm-section no-label credit_card_details-section"> <div class="content">{$credit_card_type}</div> <div class="content">{$credit_card_number}</div> diff --git a/civicrm/templates/CRM/Contribute/Form/Contribution/ThankYou.tpl b/civicrm/templates/CRM/Contribute/Form/Contribution/ThankYou.tpl index f119e135ca..697baf22d8 100644 --- a/civicrm/templates/CRM/Contribute/Form/Contribution/ThankYou.tpl +++ b/civicrm/templates/CRM/Contribute/Form/Contribution/ThankYou.tpl @@ -281,7 +281,7 @@ {/if} {/if} - {if $contributeMode eq 'direct' and ! $is_pay_later and $is_monetary and ( $amount GT 0 OR $minimum_fee GT 0 )} + {if in_array('credit_card_number', $form) || in_array('bank_account_number', $form) && ($amount GT 0 OR $minimum_fee GT 0)} {crmRegion name="contribution-thankyou-billing-block"} <div class="crm-group credit_card-group"> {if $paymentFieldsetLabel} @@ -289,7 +289,7 @@ {$paymentFieldsetLabel} </div> {/if} - {if $paymentProcessor.payment_type == 2} + {if in_array('bank_account_number', $form) && $bank_account_number} <div class="display-block"> {ts}Account Holder{/ts}: {$account_holder}<br /> {ts}Bank Identification Number{/ts}: {$bank_identification_number}<br /> diff --git a/civicrm/vendor/autoload.php b/civicrm/vendor/autoload.php index c5905e4e4b..e35fae40b4 100644 --- a/civicrm/vendor/autoload.php +++ b/civicrm/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit5b9fa8178f0547c9453906e5e95835fb::getLoader(); +return ComposerAutoloaderInit85641ecafad0fd91a9bd9d9f47db5e32::getLoader(); diff --git a/civicrm/vendor/composer/autoload_real.php b/civicrm/vendor/composer/autoload_real.php index 5fa4f9c480..1bd06617e6 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 ComposerAutoloaderInit5b9fa8178f0547c9453906e5e95835fb +class ComposerAutoloaderInit85641ecafad0fd91a9bd9d9f47db5e32 { private static $loader; @@ -24,9 +24,9 @@ class ComposerAutoloaderInit5b9fa8178f0547c9453906e5e95835fb require __DIR__ . '/platform_check.php'; - spl_autoload_register(array('ComposerAutoloaderInit5b9fa8178f0547c9453906e5e95835fb', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit85641ecafad0fd91a9bd9d9f47db5e32', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); - spl_autoload_unregister(array('ComposerAutoloaderInit5b9fa8178f0547c9453906e5e95835fb', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit85641ecafad0fd91a9bd9d9f47db5e32', 'loadClassLoader')); $includePaths = require __DIR__ . '/include_paths.php'; $includePaths[] = get_include_path(); @@ -36,7 +36,7 @@ class ComposerAutoloaderInit5b9fa8178f0547c9453906e5e95835fb if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInit85641ecafad0fd91a9bd9d9f47db5e32::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { @@ -57,12 +57,12 @@ class ComposerAutoloaderInit5b9fa8178f0547c9453906e5e95835fb $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb::$files; + $includeFiles = Composer\Autoload\ComposerStaticInit85641ecafad0fd91a9bd9d9f47db5e32::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire5b9fa8178f0547c9453906e5e95835fb($fileIdentifier, $file); + composerRequire85641ecafad0fd91a9bd9d9f47db5e32($fileIdentifier, $file); } return $loader; @@ -74,7 +74,7 @@ class ComposerAutoloaderInit5b9fa8178f0547c9453906e5e95835fb * @param string $file * @return void */ -function composerRequire5b9fa8178f0547c9453906e5e95835fb($fileIdentifier, $file) +function composerRequire85641ecafad0fd91a9bd9d9f47db5e32($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; diff --git a/civicrm/vendor/composer/autoload_static.php b/civicrm/vendor/composer/autoload_static.php index 1663548a41..a58958a732 100644 --- a/civicrm/vendor/composer/autoload_static.php +++ b/civicrm/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb +class ComposerStaticInit85641ecafad0fd91a9bd9d9f47db5e32 { public static $files = array ( 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', @@ -744,11 +744,11 @@ class ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb::$prefixDirsPsr4; - $loader->prefixesPsr0 = ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb::$prefixesPsr0; - $loader->fallbackDirsPsr0 = ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb::$fallbackDirsPsr0; - $loader->classMap = ComposerStaticInit5b9fa8178f0547c9453906e5e95835fb::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit85641ecafad0fd91a9bd9d9f47db5e32::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit85641ecafad0fd91a9bd9d9f47db5e32::$prefixDirsPsr4; + $loader->prefixesPsr0 = ComposerStaticInit85641ecafad0fd91a9bd9d9f47db5e32::$prefixesPsr0; + $loader->fallbackDirsPsr0 = ComposerStaticInit85641ecafad0fd91a9bd9d9f47db5e32::$fallbackDirsPsr0; + $loader->classMap = ComposerStaticInit85641ecafad0fd91a9bd9d9f47db5e32::$classMap; }, null, ClassLoader::class); } diff --git a/civicrm/vendor/composer/installed.php b/civicrm/vendor/composer/installed.php index c981e51d1c..550f889dcf 100644 --- a/civicrm/vendor/composer/installed.php +++ b/civicrm/vendor/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'a470f36404babb775566c3ace40a84f8b05be44e', + 'reference' => '5a40b8aac7a5b0cd7b8f432c31deae79c454b9be', 'name' => 'civicrm/civicrm-core', 'dev' => true, ), @@ -61,7 +61,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => 'a470f36404babb775566c3ace40a84f8b05be44e', + 'reference' => '5a40b8aac7a5b0cd7b8f432c31deae79c454b9be', 'dev_requirement' => false, ), 'civicrm/civicrm-cxn-rpc' => array( diff --git a/civicrm/xml/version.xml b/civicrm/xml/version.xml index 353b883116..22e99f7680 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.59.3</version_no> + <version_no>5.59.4</version_no> </version> -- GitLab