From 0ef3e7ce37d2cd05df42703db6b068d432654baf Mon Sep 17 00:00:00 2001 From: Dominic Tubach Date: Wed, 26 Aug 2026 10:41:41 +0200 Subject: [PATCH 1/2] Use managed entities for option values --- CRM/Banking/Upgrader.php | 10 +- banking.php | 13 +- banking_options.php | 304 +--------- ...oup_civicrm_banking_bank_tx_status.mgd.php | 103 ++++ ...oup_civicrm_banking_plugin_classes.mgd.php | 103 ++++ ...Group_civicrm_banking_plugin_types.mgd.php | 564 ++++++++++++++++++ ...up_civicrm_banking_reference_types.mgd.php | 318 ++++++++++ 7 files changed, 1095 insertions(+), 320 deletions(-) create mode 100644 managed/OptionGroup_civicrm_banking_bank_tx_status.mgd.php create mode 100644 managed/OptionGroup_civicrm_banking_plugin_classes.mgd.php create mode 100644 managed/OptionGroup_civicrm_banking_plugin_types.mgd.php create mode 100644 managed/OptionGroup_civicrm_banking_reference_types.mgd.php diff --git a/CRM/Banking/Upgrader.php b/CRM/Banking/Upgrader.php index 48d2d9e7..4563a454 100644 --- a/CRM/Banking/Upgrader.php +++ b/CRM/Banking/Upgrader.php @@ -169,7 +169,7 @@ public function upgrade_0701() { */ public function upgrade_0702() { // update option groups - // replaced by 0703: banking_civicrm_install_options(_banking_options()); + // replaced by managed entities return TRUE; } @@ -181,7 +181,7 @@ public function upgrade_0702() { */ public function upgrade_0703() { // update option groups - banking_civicrm_install_options(_banking_options()); + // replaced by managed entities return TRUE; } @@ -244,8 +244,7 @@ public function upgrade_0801() { */ public function upgrade_0802() { // update option groups - $this->ctx->log->info('Updated options.'); - banking_civicrm_install_options(_banking_options()); + // replaced by managed entities return TRUE; } @@ -289,8 +288,7 @@ public function upgrade_0804() { */ public function upgrade_0805() { // update option groups - $this->ctx->log->info('Updated options.'); - banking_civicrm_install_options(_banking_options()); + // replaced by managed entities return TRUE; } diff --git a/banking.php b/banking.php index 678d0d24..a22d2700 100755 --- a/banking.php +++ b/banking.php @@ -18,7 +18,6 @@ // phpcs:disable PSR1.Files.SideEffects.FoundWithSymbols require_once 'banking.civix.php'; -require_once 'banking_options.php'; // phpcs:enable use Civi\Banking\DependencyInjection\Compiler\ActionProviderPass; @@ -66,9 +65,6 @@ function banking_civicrm_install() { $sqlfile = dirname(__FILE__) . '/sql/banking.sql'; CRM_Utils_File::sourceSQLFile($config->dsn, $sqlfile, NULL, FALSE); - //add the required option groups - banking_civicrm_install_options(_banking_options()); - // Set the bank account reference probability to 100%. Civi::settings()->set('reference_matching_probability', 1.0); @@ -78,17 +74,10 @@ function banking_civicrm_install() { /** * Implements hook_civicrm_enable(). */ -function banking_civicrm_enable() { - //add the required option groups - banking_civicrm_install_options(_banking_options()); - +function banking_civicrm_enable(): void { _banking_civix_civicrm_enable(); } -function banking_civicrm_angularModules(&$angularModules) { - return; -} - /** * Inject contribution - transaction link */ diff --git a/banking_options.php b/banking_options.php index cc2b19b3..fda47531 100644 --- a/banking_options.php +++ b/banking_options.php @@ -19,310 +19,10 @@ use Civi\Api4\OptionGroup; use CRM_Banking_ExtensionUtil as E; -/** - * define CiviBanking option values - */ -function _banking_options() { - // start with the lowest weight value - return [ - 'civicrm_banking.plugin_types' => [ - 'title' => 'CiviBanking plugin types', - 'description' => 'The set of possible CiviBanking plugin types', - 'values' => [ - 'importer_dummy' => [ - 'label' => 'Dummy Data Importer Plugin', - 'value' => 'CRM_Banking_PluginImpl_Dummy', - 'description' => 'For testing purposes only', - 'is_default' => 0, - ], - 'importer_csv' => [ - 'label' => 'Configurable CSV Importer', - 'value' => 'CRM_Banking_PluginImpl_Importer_CSV', - 'description' => 'This importer should be configurable to import any CSV based data.', - 'is_default' => 0, - ], - 'importer_xml' => [ - 'label' => 'Configurable XML Importer', - 'value' => 'CRM_Banking_PluginImpl_Importer_XML', - 'description' => 'This importer should be configurable to import a variety of XML based data.', - 'is_default' => 0, - ], - 'importer_fixed' => [ - 'label' => 'Fixed Width TXT Importer', - 'value' => 'CRM_Banking_PluginImpl_Importer_Fixed', - 'description' => 'This importer should be configurable for most fixed-width txt standards.', - 'is_default' => 0, - ], - 'matcher_create' => [ - 'label' => 'Create Contribution Matcher Plugin', - 'value' => 'CRM_Banking_PluginImpl_Matcher_CreateContribution', - 'is_default' => 0, - ], - 'matcher_create_campaign' => [ - 'label' => 'Create Campaign Contribution Matcher Plugin', - 'value' => 'CRM_Banking_PluginImpl_Matcher_CreateCampaignContribution', - 'is_default' => 0, - ], - 'matcher_create_multiple' => [ - 'label' => 'Create Multiple Contributions Matcher Plugin', - 'value' => 'CRM_Banking_PluginImpl_Matcher_CreateMultipleContributions', - 'is_default' => 0, - ], - 'matcher_recurring' => [ - 'label' => 'Recurring Contribution Matcher Plugin', - 'value' => 'CRM_Banking_PluginImpl_Matcher_RecurringContribution', - 'is_default' => 0, - ], - 'matcher_membership' => [ - 'label' => 'Membership Matcher Plugin', - 'value' => 'CRM_Banking_PluginImpl_Matcher_Membership', - 'is_default' => 0, - ], - 'matcher_yes' => [ - 'label' => 'Dummy Matcher Test Plugin', - 'value' => 'CRM_Banking_PluginImpl_Matcher_Yes', - 'description' => 'For testing purposes only', - 'is_default' => 0, - ], - 'matcher_default' => [ - 'label' => 'Default Options Matcher', - 'value' => 'CRM_Banking_PluginImpl_Matcher_DefaultOptions', - 'description' => 'Generates default options, namely "ignore" and "process manually"', - 'is_default' => 0, - ], - 'matcher_ignore' => [ - 'label' => 'Ignore Matcher', - 'value' => 'CRM_Banking_PluginImpl_Matcher_Ignore', - 'description' => 'Can be configured to ignore a transaction when certain patterns are detected.', - 'is_default' => 0, - ], - 'matcher_contribution' => [ - 'label' => 'Contribution Matcher', - 'value' => 'CRM_Banking_PluginImpl_Matcher_ExistingContribution', - 'description' => 'Will match the transaction to existing (pending) contributions.', - 'is_default' => 0, - ], - 'matcher_batch' => [ - 'label' => 'Batch Matcher', - 'value' => 'CRM_Banking_PluginImpl_Matcher_Batches', - 'description' => 'Tries to identify transactions that settle contribution batches.', - 'is_default' => 0, - ], - 'matcher_sepa' => [ - 'label' => 'SEPA Matcher', - 'value' => 'CRM_Banking_PluginImpl_Matcher_SepaMandate', - 'description' => 'Will match SEPA DD transactions to contributions created by the org.project60.sepa module.', - 'is_default' => 0, - ], - 'analyser_regex' => [ - 'label' => 'RegEx Analyser', - 'value' => 'CRM_Banking_PluginImpl_Matcher_RegexAnalyser', - 'description' => 'Analyses and enriches the transactions information using regular expressions.', - 'is_default' => 0, - ], - 'analyser_account' => [ - 'label' => 'Account Lookup Analyser', - 'value' => 'CRM_Banking_PluginImpl_Matcher_AccountLookup', - 'description' => 'Looks up a transaction\'s bank accounts (again)', - 'is_default' => 0, - ], - 'analyser_rules' => [ - 'label' => 'Rule Analyser Plugin', - 'value' => 'CRM_Banking_PluginImpl_Matcher_RulesAnalyser', - 'is_default' => 0, - ], - 'postprocessor_accounts' => [ - 'label' => 'Bank Accounts PostProcessor', - 'value' => 'CRM_Banking_PluginImpl_PostProcessor_Accounts', - 'description' => 'Allows you to store the bank accounts with the contact or contribution', - 'is_default' => 0, - ], - 'postprocessor_addressupdate' => [ - 'label' => 'Update Address PostProcessor', - 'value' => 'CRM_Banking_PluginImpl_PostProcessor_AddressUpdate', - 'description' => 'Updates a contact\'s address with the one from the transaction', - 'is_default' => 0, - ], - 'postprocessor_membership_payment' => [ - 'label' => 'MembershipPayment PostProcessor', - 'value' => 'CRM_Banking_PluginImpl_PostProcessor_MembershipPayment', - 'description' => 'Assigns newly created contributions to memberships', - 'is_default' => 0, - ], - 'postprocessor_api' => [ - 'label' => 'API PostProcessor', - 'value' => 'CRM_Banking_PluginImpl_PostProcessor_API', - 'description' => 'Triggers any API action', - 'is_default' => 0, - ], - 'postprocessor_contact_deceased' => [ - 'label' => 'Contact Deceased PostProcessor', - 'value' => 'CRM_Banking_PluginImpl_PostProcessor_ContactDeceased', - 'description' => 'Marks a contact as "deceased"', - 'is_default' => 0, - ], - 'postprocessor_recurring_fails' => [ - 'label' => 'Recurring Contribution Fails PostProcessor', - 'value' => 'CRM_Banking_PluginImpl_PostProcessor_RecurringFails', - 'description' => 'Processes contribution fails and cancellations for recurring contributions, including CiviSEPA DDs', - 'is_default' => 0, - ], - 'postprocessor_membership_extension' => [ - 'label' => 'Membership Extension PostProcessor', - 'value' => 'CRM_Banking_PluginImpl_PostProcessor_MembershipExtension', - 'description' => 'Will automatically extend memberships if the right contribution is processed', - 'is_default' => 0, - ], - 'exporter_csv' => [ - 'label' => 'Configurable CSV Exporter', - 'value' => 'CRM_Banking_PluginImpl_Exporter_CSV', - 'description' => 'This exporter should be configurable to export paymetns to a CSV format.', - 'is_default' => 0, - ], - ], - ], - 'civicrm_banking.reference_types' => [ - 'title' => 'CiviBanking bank account reference types', - 'description' => 'The set of possible CiviBanking bank account reference types', - 'values' => [ - 'IBAN' => [ - 'label' => E::ts('International Bank Account Number'), - 'value' => 'IBAN', - 'description' => E::ts('Format: see IBAN on WikiPedia.'), - 'is_default' => 1, - ], - 'NBAN_DE' => [ - 'label' => E::ts('German Bank Account Number'), - 'value' => 'NBAN_DE', - 'description' => E::ts('Format is "BBBBBBBB/KKKKKKKKKK", (B="BLZ", K="Kontonummer") eg. "12345678/0000123456"'), - 'is_default' => 0, - ], - 'NBAN_AT' => [ - 'label' => E::ts('Austrian Bank Account Number'), - 'value' => 'NBAN_AT', - 'description' => E::ts('Format is "BBBBB/KKKKKKKKKKK", (B="BLZ", K="Kontonummer") eg. "12345/00001234567"'), - 'is_default' => 0, - ], - 'NBAN_BE' => [ - 'label' => E::ts('Belgian Bank Account Number'), - 'value' => 'NBAN_BE', - 'is_default' => 0, - ], - 'NBAN_CH' => [ - 'label' => E::ts('Swiss Bank Account Number'), - 'value' => 'NBAN_CH', - 'description' => E::ts('Format is XX-XXXXXXXXX-X.'), - 'is_default' => 0, - ], - 'NBAN_CZ' => [ - 'label' => E::ts('Czech Bank Account Number'), - 'value' => 'NBAN_CZ', - 'description' => E::ts('Format is "PPPPPP-UUUUUUUUUU/CCCC" (P="predcisli/prefix", U="vlastni cislo/bank account", C="bank code") The first part (PPPPPP-) is optional.'), - 'is_default' => 0, - ], - 'NBAN_FP' => [ - 'label' => E::ts('Fingerprint'), - 'value' => 'NBAN_FP', - 'description' => E::ts('SHA1 fingerprint of some tell-tale value in the transaction information'), - 'is_reserved' => 1, - 'is_default' => 0, - ], - 'ENTITY' => [ - 'label' => E::ts('Internal Link'), - 'value' => 'ENTITY', - 'description' => 'Links a bank account to a CiviCRM entity, reference format is ":"', - 'is_reserved' => 1, - 'is_default' => 0, - ], - 'NBAN_GC' => [ - 'label' => E::ts('GoCardless'), - 'value' => 'NBAN_GC', - 'description' => E::ts('GoCardless customer ID'), - 'is_default' => 0, - ], - 'NBAN_WP' => [ - 'label' => E::ts('WorldPay'), - 'value' => 'NBAN_WP', - 'description' => E::ts('WorldPay merchant ID'), - 'is_default' => 0, - ], - 'NBAN_SP' => [ - 'label' => E::ts('Stewardship'), - 'value' => 'NBAN_SP', - 'description' => E::ts('Stewardship merchant ID'), - 'is_default' => 0, - ], - 'NBAN_PP' => [ - 'label' => E::ts('PayPal'), - 'value' => 'NBAN_PP', - 'description' => E::ts('PayPal account identification (email)'), - 'is_default' => 0, - ], - 'NBAN_ES' => [ - 'label' => E::ts('Spanish Bank Account Number'), - 'value' => 'NBAN_ES', - 'description' => E::ts('Traditional Spanish bank account number'), - 'is_default' => 0, - ], - ], - ], - 'civicrm_banking.plugin_classes' => [ - 'title' => 'CiviBanking plugin classes', - 'description' => 'The set of existing CiviBanking plugin types', - 'values' => [ - 'import' => [ - 'label' => 'Import plugin', - 'value' => 1, - 'is_default' => 0, - ], - 'match' => [ - 'label' => 'Match plugin', - 'value' => 2, - 'is_default' => 0, - ], - 'postprocess' => [ - 'label' => 'Post Processor', - 'value' => 4, - 'is_default' => 0, - ], - 'export' => [ - 'label' => 'Export plugin', - 'value' => 3, - 'is_default' => 0, - ], - ], - ], - 'civicrm_banking.bank_tx_status' => [ - 'title' => 'CiviBanking bank transaction processing status', - 'description' => 'The set of possible processing statuses for a CiviBanking bank transaction', - 'values' => [ - 'new' => [ - 'label' => 'New', - 'value' => 0, - 'is_default' => 1, - ], - 'suggestions' => [ - 'label' => 'Suggestions', - 'value' => 2, - 'is_default' => 0, - ], - 'processed' => [ - 'label' => 'Processed', - 'value' => 3, - 'is_default' => 0, - ], - 'ignored' => [ - 'label' => 'Ignored', - 'value' => 1, - 'is_default' => 0, - ], - ], - ], - ]; -} - /** * Install/Update the option values + * + * @deprecated Use managed entities instead. */ function banking_civicrm_install_options($data) { foreach ($data as $groupName => $group) { diff --git a/managed/OptionGroup_civicrm_banking_bank_tx_status.mgd.php b/managed/OptionGroup_civicrm_banking_bank_tx_status.mgd.php new file mode 100644 index 00000000..4bd3527b --- /dev/null +++ b/managed/OptionGroup_civicrm_banking_bank_tx_status.mgd.php @@ -0,0 +1,103 @@ + 'OptionGroup_civicrm_banking_bank_tx_status', + 'entity' => 'OptionGroup', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'civicrm_banking.bank_tx_status', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + 'title' => E::ts('CiviBanking bank transaction processing status'), + 'description' => E::ts('The set of possible processing statuses for a CiviBanking bank transaction'), + 'option_value_fields' => ['name', 'label', 'description'], + ], + 'match' => ['name'], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_bank_tx_status_OptionValue_new', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.bank_tx_status', + 'label' => E::ts('New'), + 'value' => '0', + 'name' => 'new', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_bank_tx_status_OptionValue_processed', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.bank_tx_status', + 'label' => E::ts('Processed'), + 'value' => '3', + 'name' => 'processed', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_bank_tx_status_OptionValue_ignored', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.bank_tx_status', + 'label' => E::ts('Ignored'), + 'value' => '1', + 'name' => 'ignored', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_bank_tx_status_OptionValue_suggestions', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.bank_tx_status', + 'label' => E::ts('Suggestions'), + 'value' => '2', + 'name' => 'suggestions', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], +]; diff --git a/managed/OptionGroup_civicrm_banking_plugin_classes.mgd.php b/managed/OptionGroup_civicrm_banking_plugin_classes.mgd.php new file mode 100644 index 00000000..58b72dc4 --- /dev/null +++ b/managed/OptionGroup_civicrm_banking_plugin_classes.mgd.php @@ -0,0 +1,103 @@ + 'OptionGroup_civicrm_banking_plugin_classes', + 'entity' => 'OptionGroup', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'civicrm_banking.plugin_classes', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + 'title' => E::ts('CiviBanking plugin classes'), + 'description' => E::ts('The set of existing CiviBanking plugin types'), + 'option_value_fields' => ['name', 'label', 'description'], + ], + 'match' => ['name'], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_classes_OptionValue_import', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_classes', + 'label' => E::ts('Import plugin'), + 'value' => '1', + 'name' => 'import', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_classes_OptionValue_match', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_classes', + 'label' => E::ts('Match plugin'), + 'value' => '2', + 'name' => 'match', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_classes_OptionValue_postprocess', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_classes', + 'label' => E::ts('Post Processor'), + 'value' => '4', + 'name' => 'postprocess', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_classes_OptionValue_export', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_classes', + 'label' => E::ts('Export plugin'), + 'value' => '3', + 'name' => 'export', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], +]; diff --git a/managed/OptionGroup_civicrm_banking_plugin_types.mgd.php b/managed/OptionGroup_civicrm_banking_plugin_types.mgd.php new file mode 100644 index 00000000..35b99788 --- /dev/null +++ b/managed/OptionGroup_civicrm_banking_plugin_types.mgd.php @@ -0,0 +1,564 @@ + 'OptionGroup_civicrm_banking_plugin_types', + 'entity' => 'OptionGroup', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'civicrm_banking.plugin_types', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + 'title' => E::ts('CiviBanking plugin types'), + 'description' => E::ts('The set of possible CiviBanking plugin types'), + 'option_value_fields' => ['name', 'label', 'description'], + ], + 'match' => ['name'], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_importer_dummy', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Dummy Data Importer Plugin'), + 'value' => 'CRM_Banking_PluginImpl_Dummy', + 'name' => 'importer_dummy', + 'description' => E::ts('For testing purposes only'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_importer_csv', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Configurable CSV Importer'), + 'value' => 'CRM_Banking_PluginImpl_Importer_CSV', + 'name' => 'importer_csv', + 'description' => E::ts('This importer should be configurable to import any CSV based data.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_importer_xml', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Configurable XML Importer'), + 'value' => 'CRM_Banking_PluginImpl_Importer_XML', + 'name' => 'importer_xml', + 'description' => E::ts('This importer should be configurable to import a variety of XML based data.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_importer_fixed', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Fixed Width TXT Importer'), + 'value' => 'CRM_Banking_PluginImpl_Importer_Fixed', + 'name' => 'importer_fixed', + 'description' => E::ts('This importer should be configurable for most fixed-width txt standards.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_create', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Create Contribution Matcher Plugin'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_CreateContribution', + 'name' => 'matcher_create', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_create_campaign', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Create Campaign Contribution Matcher Plugin'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_CreateCampaignContribution', + 'name' => 'matcher_create_campaign', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_create_multiple', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Create Multiple Contributions Matcher Plugin'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_CreateMultipleContributions', + 'name' => 'matcher_create_multiple', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_recurring', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Recurring Contribution Matcher Plugin'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_RecurringContribution', + 'name' => 'matcher_recurring', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_membership', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Membership Matcher Plugin'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_Membership', + 'name' => 'matcher_membership', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_yes', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Dummy Matcher Test Plugin'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_Yes', + 'name' => 'matcher_yes', + 'description' => E::ts('For testing purposes only'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_default', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Default Options Matcher'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_DefaultOptions', + 'name' => 'matcher_default', + 'description' => E::ts('Generates default options, namely "ignore" and "process manually"'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_ignore', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Ignore Matcher'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_Ignore', + 'name' => 'matcher_ignore', + 'description' => E::ts('Can be configured to ignore a transaction when certain patterns are detected.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_contribution', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Contribution Matcher'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_ExistingContribution', + 'name' => 'matcher_contribution', + 'description' => E::ts('Will match the transaction to existing (pending) contributions.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_batch', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Batch Matcher'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_Batches', + 'name' => 'matcher_batch', + 'description' => E::ts('Tries to identify transactions that settle contribution batches.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_matcher_sepa', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('SEPA Matcher'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_SepaMandate', + 'name' => 'matcher_sepa', + 'description' => E::ts('Will match SEPA DD transactions to contributions created by the org.project60.sepa module.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_analyser_regex', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('RegEx Analyser'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_RegexAnalyser', + 'name' => 'analyser_regex', + 'description' => E::ts('Analyses and enriches the transactions information using regular expressions.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_analyser_account', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Account Lookup Analyser'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_AccountLookup', + 'name' => 'analyser_account', + 'description' => 'Looks up a transaction\'s bank accounts (again)', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_analyser_rules', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Rule Analyser Plugin'), + 'value' => 'CRM_Banking_PluginImpl_Matcher_RulesAnalyser', + 'name' => 'analyser_rules', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_postprocessor_accounts', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Bank Accounts PostProcessor'), + 'value' => 'CRM_Banking_PluginImpl_PostProcessor_Accounts', + 'name' => 'postprocessor_accounts', + 'description' => E::ts('Allows you to store the bank accounts with the contact or contribution'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_postprocessor_addressupdate', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Update Address PostProcessor'), + 'value' => 'CRM_Banking_PluginImpl_PostProcessor_AddressUpdate', + 'name' => 'postprocessor_addressupdate', + 'description' => 'Updates a contact\'s address with the one from the transaction', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_postprocessor_membership_payment', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('MembershipPayment PostProcessor'), + 'value' => 'CRM_Banking_PluginImpl_PostProcessor_MembershipPayment', + 'name' => 'postprocessor_membership_payment', + 'description' => E::ts('Assigns newly created contributions to memberships'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_postprocessor_api', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('API PostProcessor'), + 'value' => 'CRM_Banking_PluginImpl_PostProcessor_API', + 'name' => 'postprocessor_api', + 'description' => E::ts('Triggers any API action'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_postprocessor_contact_deceased', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Contact Deceased PostProcessor'), + 'value' => 'CRM_Banking_PluginImpl_PostProcessor_ContactDeceased', + 'name' => 'postprocessor_contact_deceased', + 'description' => E::ts('Marks a contact as "deceased"'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_postprocessor_recurring_fails', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Recurring Contribution Fails PostProcessor'), + 'value' => 'CRM_Banking_PluginImpl_PostProcessor_RecurringFails', + 'name' => 'postprocessor_recurring_fails', + 'description' => E::ts('Processes contribution fails and cancellations for recurring contributions, including CiviSEPA DDs'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_postprocessor_membership_extension', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Membership Extension PostProcessor'), + 'value' => 'CRM_Banking_PluginImpl_PostProcessor_MembershipExtension', + 'name' => 'postprocessor_membership_extension', + 'description' => E::ts('Will automatically extend memberships if the right contribution is processed'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_exporter_csv', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => E::ts('Configurable CSV Exporter'), + 'value' => 'CRM_Banking_PluginImpl_Exporter_CSV', + 'name' => 'exporter_csv', + 'description' => E::ts('This exporter should be configurable to export paymetns to a CSV format.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], +]; diff --git a/managed/OptionGroup_civicrm_banking_reference_types.mgd.php b/managed/OptionGroup_civicrm_banking_reference_types.mgd.php new file mode 100644 index 00000000..d32b3267 --- /dev/null +++ b/managed/OptionGroup_civicrm_banking_reference_types.mgd.php @@ -0,0 +1,318 @@ + 'OptionGroup_civicrm_banking_reference_types', + 'entity' => 'OptionGroup', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'name' => 'civicrm_banking.reference_types', + 'is_active' => TRUE, + 'is_reserved' => TRUE, + 'title' => E::ts('CiviBanking bank account reference types'), + 'description' => E::ts('The set of possible CiviBanking bank account reference types'), + 'option_value_fields' => ['name', 'label', 'description'], + ], + 'match' => ['name'], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_IBAN', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('International Bank Account Number'), + 'value' => 'IBAN', + 'name' => 'IBAN', + 'description' => E::ts('Format: see IBAN on WikiPedia.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_DE', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('German Bank Account Number'), + 'value' => 'NBAN_DE', + 'name' => 'NBAN_DE', + 'description' => E::ts('Format is "BBBBBBBB/KKKKKKKKKK", (B="BLZ", K="Kontonummer") eg. "12345678/0000123456"'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_AT', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Austrian Bank Account Number'), + 'value' => 'NBAN_AT', + 'name' => 'NBAN_AT', + 'description' => E::ts('Format is "BBBBB/KKKKKKKKKKK", (B="BLZ", K="Kontonummer") eg. "12345/00001234567"'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_BE', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Belgian Bank Account Number'), + 'value' => 'NBAN_BE', + 'name' => 'NBAN_BE', + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_CH', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Swiss Bank Account Number'), + 'value' => 'NBAN_CH', + 'name' => 'NBAN_CH', + 'description' => E::ts('Format is XX-XXXXXXXXX-X.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_CZ', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Czech Bank Account Number'), + 'value' => 'NBAN_CZ', + 'name' => 'NBAN_CZ', + 'description' => E::ts('Format is "PPPPPP-UUUUUUUUUU/CCCC" (P="predcisli/prefix", U="vlastni cislo/bank account", C="bank code") The first part (PPPPPP-) is optional.'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_FP', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Fingerprint'), + 'value' => 'NBAN_FP', + 'name' => 'NBAN_FP', + 'description' => E::ts('SHA1 fingerprint of some tell-tale value in the transaction information'), + 'is_reserved' => TRUE, + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_ENTITY', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Internal Link'), + 'value' => 'ENTITY', + 'name' => 'ENTITY', + 'description' => E::ts('Links a bank account to a CiviCRM entity, reference format is ":"'), + 'is_reserved' => TRUE, + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_GC', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('GoCardless'), + 'value' => 'NBAN_GC', + 'name' => 'NBAN_GC', + 'description' => E::ts('GoCardless customer ID'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_WP', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('WorldPay'), + 'value' => 'NBAN_WP', + 'name' => 'NBAN_WP', + 'description' => E::ts('WorldPay merchant ID'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_SP', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Stewardship'), + 'value' => 'NBAN_SP', + 'name' => 'NBAN_SP', + 'description' => E::ts('Stewardship merchant ID'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_PP', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('PayPal'), + 'value' => 'NBAN_PP', + 'name' => 'NBAN_PP', + 'description' => E::ts('PayPal account identification (email)'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_ES', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Spanish Bank Account Number'), + 'value' => 'NBAN_ES', + 'name' => 'NBAN_ES', + 'description' => E::ts('Traditional Spanish bank account number'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], + [ + 'name' => 'OptionGroup_civicrm_banking_reference_types_OptionValue_NBAN_ST', + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.reference_types', + 'label' => E::ts('Stripe'), + 'value' => 'NBAN_ST', + 'name' => 'NBAN_ST', + 'description' => E::ts('Stripe account email'), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], +]; From 5d4f5064dfb295492e7b3c91a0c4e70f0270c12a Mon Sep 17 00:00:00 2001 From: Dominic Tubach Date: Thu, 27 Aug 2026 16:22:50 +0200 Subject: [PATCH 2/2] Add post processor to run custom actions Currently the new post processor can perform custom APIv4 calls. There can be made more types of actions possible through implementations of `\Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerInterface`. --- CRM/Banking/Matcher/Context.php | 55 +++------ CRM/Banking/Matcher/Engine.php | 2 - .../PluginImpl/Matcher/SepaMandate.php | 2 +- CRM/Banking/PluginImpl/PostProcessor/API.php | 21 ++-- .../PluginImpl/PostProcessor/Accounts.php | 25 ++-- .../PostProcessor/AddressUpdate.php | 29 ++--- .../PostProcessor/ContactDeceased.php | 93 +++++++-------- .../PostProcessor/MembershipExtension.php | 22 ++-- .../PostProcessor/MembershipPayment.php | 24 ++-- .../PostProcessor/RecurringFails.php | 22 ++-- CRM/Banking/PluginModel/BtxBase.php | 6 +- CRM/Banking/PluginModel/PostProcessor.php | 111 +++++++++--------- .../PostProcessorCustomActionHandlerPass.php | 71 +++++++++++ .../Api4CustomActionHandler.php | 92 +++++++++++++++ .../CustomAction/CustomActionContext.php | 82 +++++++++++++ .../CustomActionHandlerCollector.php | 53 +++++++++ .../CustomActionHandlerInterface.php | 36 ++++++ .../CustomActionsPostProcessor.php | 74 ++++++++++++ banking.php | 10 +- composer.json | 2 +- ...Group_civicrm_banking_plugin_types.mgd.php | 22 ++++ .../Api4CustomActionHandlerTest.php | 83 +++++++++++++ .../TestCustomActionHandler.php | 68 +++++++++++ .../CustomActionsPostProcessorTest.php | 74 ++++++++++++ tests/phpunit/bootstrap.php | 5 + 25 files changed, 874 insertions(+), 210 deletions(-) create mode 100644 Civi/Banking/DependencyInjection/Compiler/PostProcessorCustomActionHandlerPass.php create mode 100644 Civi/Banking/PostProcessor/CustomAction/ActionHandlers/Api4CustomActionHandler.php create mode 100644 Civi/Banking/PostProcessor/CustomAction/CustomActionContext.php create mode 100644 Civi/Banking/PostProcessor/CustomAction/CustomActionHandlerCollector.php create mode 100644 Civi/Banking/PostProcessor/CustomAction/CustomActionHandlerInterface.php create mode 100644 Civi/Banking/PostProcessor/CustomActionsPostProcessor.php create mode 100644 tests/phpunit/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/Api4CustomActionHandlerTest.php create mode 100644 tests/phpunit/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/TestCustomActionHandler.php create mode 100644 tests/phpunit/Civi/Banking/PostProcessor/CustomActionsPostProcessorTest.php diff --git a/CRM/Banking/Matcher/Context.php b/CRM/Banking/Matcher/Context.php index d245f24a..c2d3d3b8 100755 --- a/CRM/Banking/Matcher/Context.php +++ b/CRM/Banking/Matcher/Context.php @@ -20,22 +20,20 @@ class CRM_Banking_Matcher_Context { - // reference to the BTX being processed - public $btx; - // set to the executed suggestion - protected $executed_suggestion = NULL; - - // will store generic attributes from the various matchers - private $_attributes = []; + private ?CRM_Banking_Matcher_Suggestion $executed_suggestion = NULL; - // will store cached data needed/produced by the helper functions - private $_caches; + /** + * will store cached data needed/produced by the helper functions + * @var array + */ + private array $cache = []; - protected $bank_account_reference_matching_probability = NULL; + private $bank_account_reference_matching_probability = NULL; - public function __construct(CRM_Banking_BAO_BankTransaction $btx) { - $this->btx = $btx; + public function __construct( + public readonly CRM_Banking_BAO_BankTransaction $btx + ) { $btx->context = $this; $this->bank_account_reference_matching_probability = CRM_Core_BAO_Setting::getItem('CiviBanking', 'reference_matching_probability'); @@ -226,9 +224,9 @@ public function getAccountContact() { if ($contact_id === NULL) { if ($this->btx->party_ba_id) { $account = new CRM_Banking_BAO_BankAccount(); - $account->get('id', $this->btx->party_ba_id); - if ($account->contact_id) { - $contact_id = $account->contact_id; + $account->get('id', (string) $this->btx->party_ba_id); + if (NULL !== $account->contact_id) { + $contact_id = (int) $account->contact_id; } else { $contact_id = 0; @@ -283,26 +281,21 @@ public function filterDeletedContacts(array $contact2probability): array { * * @return mixed the previously stored value, or NULL */ - public function getCachedEntry($key): mixed { - if (isset($this->_caches[$key])) { - return $this->_caches[$key]; - } - else { - return NULL; - } + public function getCachedEntry(string $key): mixed { + return $this->cache[$key] ?? NULL; } /** * Set the given cache value */ - public function setCachedEntry($key, mixed $value): void { - $this->_caches[$key] = $value; + public function setCachedEntry(string $key, mixed $value): void { + $this->cache[$key] = $value; } /** * Set the executed suggestion */ - public function setExecutedSuggestion($suggestion) { + public function setExecutedSuggestion(CRM_Banking_Matcher_Suggestion $suggestion): void { $this->executed_suggestion = $suggestion; } @@ -310,18 +303,8 @@ public function setExecutedSuggestion($suggestion) { * Get the executed suggestion. * Will be NULL if non has been executed yet */ - public function getExecutedSuggestion() { + public function getExecutedSuggestion(): ?CRM_Banking_Matcher_Suggestion { return $this->executed_suggestion; } - /** - * remove the internal values, so the GC can pick it up - */ - public function destroy() { - $this->btx = NULL; - $this->executed_suggestion = NULL; - $this->_caches = []; - $this->_attributes = []; - } - } diff --git a/CRM/Banking/Matcher/Engine.php b/CRM/Banking/Matcher/Engine.php index 089ce960..cf8f2cb9 100755 --- a/CRM/Banking/Matcher/Engine.php +++ b/CRM/Banking/Matcher/Engine.php @@ -226,7 +226,6 @@ public function match($btx_id, $override_processed = FALSE) { $btx->setStatus($newStatus); $lock->release(); - $context->destroy(); $logger->logTime("Matching of btx [{$btx_id}]", 'matcher'); return FALSE; } @@ -297,7 +296,6 @@ public function runPostProcessors($suggestion, $btx, $matcher) { } $logger->logTime("Postprocessing of btx [{$btx->id}]", 'postprocessing'); - $context->destroy(); } /** diff --git a/CRM/Banking/PluginImpl/Matcher/SepaMandate.php b/CRM/Banking/PluginImpl/Matcher/SepaMandate.php index 7d1515c4..a9b7d06d 100644 --- a/CRM/Banking/PluginImpl/Matcher/SepaMandate.php +++ b/CRM/Banking/PluginImpl/Matcher/SepaMandate.php @@ -353,7 +353,7 @@ public function match(CRM_Banking_BAO_BankTransaction $btx, CRM_Banking_Matcher_ // calculate penalties (based on CRM_Banking_PluginImpl_Matcher_ExistingContribution::rateContribution) $contribution_amount = $contribution['total_amount']; - $target_amount = -$context->btx->amount; + $target_amount = -(float) $context->btx->amount; $amount_range_rel = $contribution_amount * ($config->cancellation_amount_relative_maximum - $config->cancellation_amount_relative_minimum); $amount_range_abs = $config->cancellation_amount_absolute_maximum - $config->cancellation_amount_absolute_minimum; $amount_range = max($amount_range_rel, $amount_range_abs); diff --git a/CRM/Banking/PluginImpl/PostProcessor/API.php b/CRM/Banking/PluginImpl/PostProcessor/API.php index ce61fa96..80936a25 100644 --- a/CRM/Banking/PluginImpl/PostProcessor/API.php +++ b/CRM/Banking/PluginImpl/PostProcessor/API.php @@ -54,8 +54,8 @@ protected function shouldExecute( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, - $preview = FALSE - ) { + bool $preview = FALSE + ): bool { $config = $this->_plugin_config; // check if an entity is set @@ -73,14 +73,13 @@ protected function shouldExecute( } /** - * Postprocess the (already executed) match - * - * @param $match the executed match - * @param $btx the related transaction - * @param $context the matcher context contains cache data and context information - * + * @inheritDoc */ - public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context) { + public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): ?bool { $config = $this->_plugin_config; if ($this->shouldExecute($match, $matcher, $context)) { @@ -116,7 +115,11 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ catch (Exception $e) { $this->logMessage("CALLING {$config->entity}.{$config->action} failed: " . $e->getMessage(), 'error'); } + + return NULL; } + + return FALSE; } /** diff --git a/CRM/Banking/PluginImpl/PostProcessor/Accounts.php b/CRM/Banking/PluginImpl/PostProcessor/Accounts.php index feca2f30..9f239b9b 100644 --- a/CRM/Banking/PluginImpl/PostProcessor/Accounts.php +++ b/CRM/Banking/PluginImpl/PostProcessor/Accounts.php @@ -71,8 +71,8 @@ protected function shouldExecute( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, - $preview = FALSE - ) { + bool $preview = FALSE + ): bool { $config = $this->_plugin_config; $btx = $context->btx; @@ -107,17 +107,16 @@ protected function shouldExecute( } /** - * Postprocess the (already executed) match - * - * @param $match the executed match - * @param $btx the related transaction - * @param $context the matcher context contains cache data and context information - * + * @inheritDoc */ - public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context) { + public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): ?bool { if (!$this->shouldExecute($match, $matcher, $context)) { $this->logMessage('Accounts PostProcessor not executing', 'info'); - return; + return FALSE; } // compile update @@ -141,7 +140,7 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ if (empty($update)) { // there's nothing to update - return; + return NULL; } // get the entity ID @@ -152,7 +151,7 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ $object = $this->getPropagationObject($config->target, $context->btx); if (empty($object['id'])) { $this->logMessage("Related object '{$config->target}' could not be (uniquely) identified.", 'warn'); - return; + return NULL; } else { $update['id'] = $object['id']; @@ -162,6 +161,8 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ $this->logMessage("Accounts Post Processor calling {$config->target}.create: " . json_encode($update), 'debug'); civicrm_api3($config->target, 'create', $update); } + + return NULL; } /** diff --git a/CRM/Banking/PluginImpl/PostProcessor/AddressUpdate.php b/CRM/Banking/PluginImpl/PostProcessor/AddressUpdate.php index 17bd9081..a2abd34a 100644 --- a/CRM/Banking/PluginImpl/PostProcessor/AddressUpdate.php +++ b/CRM/Banking/PluginImpl/PostProcessor/AddressUpdate.php @@ -17,6 +17,7 @@ declare(strict_types = 1); use CRM_Banking_ExtensionUtil as E; +use Webmozart\Assert\Assert; /** * This PostProcessor can update the contact's address with the one from the bank statement @@ -83,8 +84,8 @@ protected function shouldExecute( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, - $preview = FALSE - ) { + bool $preview = FALSE + ): bool { $config = $this->_plugin_config; // check if there is a single contact @@ -114,7 +115,7 @@ public function previewMatch( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context - ) { + ): ?string { $preview = NULL; $config = $this->_plugin_config; if ( @@ -148,19 +149,19 @@ public function previewMatch( } /** - * Postprocess the (already executed) match - * - * @param $match the executed match - * @param $btx the related transaction - * @param $context the matcher context contains cache data and context information + * @inheritDoc * * phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh */ - public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context) { + public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): ?bool { // phpcs:enable if (!$this->shouldExecute($match, $matcher, $context)) { // TODO: log: not executing... - return; + return FALSE; } $config = $this->_plugin_config; @@ -169,10 +170,7 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ // this matcher only makes sense for individuals $contact_id = $this->getSoleContactID($context); - if (empty($contact_id)) { - // this shouldn't happen, since it's checked in shouldExecute - return; - } + assert(is_int($contact_id)); // compile what we have $address_fields = ['location_type_id', 'postal_code', 'street_address', 'city', 'country_id', 'is_primary', 'is_billing']; @@ -207,6 +205,7 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ // and tag it if (is_array($config->tag_create)) { + Assert::allString($config->tag_create); $this->tagContact($contact_id, $config->tag_create); } @@ -240,6 +239,8 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ // there's multiple addresses $this->logMessage('Multiple addresses found. Not doing anything.', 'error'); } + + return NULL; } /** diff --git a/CRM/Banking/PluginImpl/PostProcessor/ContactDeceased.php b/CRM/Banking/PluginImpl/PostProcessor/ContactDeceased.php index cb233b83..9916990d 100644 --- a/CRM/Banking/PluginImpl/PostProcessor/ContactDeceased.php +++ b/CRM/Banking/PluginImpl/PostProcessor/ContactDeceased.php @@ -16,6 +16,8 @@ declare(strict_types = 1); +use Webmozart\Assert\Assert; + /** * This PostProcessor will mark the matched contact as 'deceased' */ @@ -42,63 +44,62 @@ public function __construct($config_name) { } /** - * Postprocess the (already executed) match - * - * @param $match the executed match - * @param $btx the related transaction - * @param $context the matcher context contains cache data and context information - * + * @inheritDoc */ - public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context) { + public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): ?bool { $config = $this->_plugin_config; if ($this->shouldExecute($match, $matcher, $context)) { - // first: identify contact(s) - $contact_id = NULL; - $contributions = $this->getContributions($context); - foreach ($contributions as $contribution) { - if ($contact_id == NULL) { - $contact_id = $contribution['contact_id']; - } - elseif ($contact_id != $contribution['contact_id']) { - // there are multiple contacts connected to this match - $this->logMessage('Multiple contacts connected to this match, cannot proceed', 'error'); - return; - } - } - - // if we have a contact: - if ($contact_id) { - $contact_lookup = civicrm_api3('Contact', 'get', [ - 'id' => $contact_id, - 'return' => 'is_deceased,is_deleted,deceased_date,id', - ]); - if ($contact_lookup['id']) { + $contact_id = $this->getSoleContactID($context); + assert(NULL !== $contact_id); - // mark contact as deceased - $contact = reset($contact_lookup['values']); - if (!$contact['is_deceased']) { - $contact_update = [ - 'id' => $contact['id'], - 'is_deceased' => 1, - ]; + $contact_lookup = civicrm_api3('Contact', 'get', [ + 'id' => $contact_id, + 'return' => 'is_deceased,is_deleted,deceased_date,id', + ]); + if ($contact_lookup['id']) { + // mark contact as deceased + $contact = reset($contact_lookup['values']); + if (!$contact['is_deceased']) { + $contact_update = [ + 'id' => $contact['id'], + 'is_deceased' => 1, + ]; - // calculate the deceased date - $deceased_date = $this->getPropagationValue($context->btx, $match, $config->set_deceased_date); - if ($deceased_date) { - $contact_update['deceased_date'] = date('YmdHis', strtotime($deceased_date)); - } - civicrm_api3('Contact', 'create', $contact_update); - $this->logMessage("Contact [{$contact['id']}] marked as deceased.", 'info'); + // calculate the deceased date + $deceased_date = $this->getPropagationValue($context->btx, $match, $config->set_deceased_date); + if ($deceased_date) { + $contact_update['deceased_date'] = date('YmdHis', strtotime($deceased_date)); } + civicrm_api3('Contact', 'create', $contact_update); + $this->logMessage("Contact [{$contact['id']}] marked as deceased.", 'info'); + } - // set Tag in any case - if (is_array($config->tag_contact)) { - $this->tagContact($contact_id, $config->tag_contact); - } + // set Tag in any case + if (is_array($config->tag_contact)) { + Assert::allString($config->tag_contact); + $this->tagContact($contact_id, $config->tag_contact); } } + + return NULL; } + + return FALSE; + } + + public function shouldExecute( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context, + bool $preview = FALSE + ): bool { + return parent::shouldExecute($match, $matcher, $context, $preview) + && ($preview || NULL !== $this->getSoleContactID($context)); } } diff --git a/CRM/Banking/PluginImpl/PostProcessor/MembershipExtension.php b/CRM/Banking/PluginImpl/PostProcessor/MembershipExtension.php index 15b19de8..3116b0d0 100644 --- a/CRM/Banking/PluginImpl/PostProcessor/MembershipExtension.php +++ b/CRM/Banking/PluginImpl/PostProcessor/MembershipExtension.php @@ -157,8 +157,8 @@ protected function shouldExecute( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, - $preview = FALSE - ) { + bool $preview = FALSE + ): bool { if (!$preview) { $contributions = $this->getEligibleContributions($context); if (empty($contributions)) { @@ -190,7 +190,7 @@ public function previewMatch( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context - ) { + ): ?string { // phpcs:enable $preview = NULL; $config = $this->_plugin_config; @@ -404,12 +404,16 @@ public function previewMatch( * * @throws Exception if anything goes wrong */ - public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context) { - $result = NULL; + public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): bool|array { $config = $this->_plugin_config; // this is pretty straightforward if ($this->shouldExecute($match, $matcher, $context)) { + $result = ['memberships' => []]; $contributions = $this->getEligibleContributions($context); foreach ($contributions as $contribution) { // get memberships @@ -465,10 +469,12 @@ public function visualizeExecutedMatch( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, - $result - ) { + ?array $result + ): string { $return = $this->getName() . '
    '; - foreach ($result['memberships'] as $membership) { + /** @var list $memberships */ + $memberships = $result['memberships'] ?? []; + foreach ($memberships as $membership) { $url = CRM_Utils_System::url( 'civicrm/contact/view/membership', [ diff --git a/CRM/Banking/PluginImpl/PostProcessor/MembershipPayment.php b/CRM/Banking/PluginImpl/PostProcessor/MembershipPayment.php index c1d2cb1d..f5317f37 100644 --- a/CRM/Banking/PluginImpl/PostProcessor/MembershipPayment.php +++ b/CRM/Banking/PluginImpl/PostProcessor/MembershipPayment.php @@ -89,8 +89,8 @@ protected function shouldExecute( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, - $preview = FALSE - ) { + bool $preview = FALSE + ): bool { if (!$preview) { $contributions = $this->getEligibleContributions($context); if (empty($contributions)) { @@ -104,17 +104,15 @@ protected function shouldExecute( } /** - * Postprocess the (already executed) match - * - * @param $match CRM_Banking_Matcher_Suggestion the executed match - * @param $matcher CRM_Banking_PluginModel_Matcher the related transaction - * @param $context CRM_Banking_Matcher_Context the matcher context contains cache data and context information - * - * @throws Exception if anything goes wrong + * @inheritDoc * * phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh */ - public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context) { + public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): ?bool { // phpcs:enable $config = $this->_plugin_config; @@ -205,7 +203,11 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ } } } + + return NULL; } + + return FALSE; } /** @@ -313,7 +315,7 @@ protected function getContributionRecurID($contribution, $membership_id, $match, * deliver the first of the eligible contributions * overwrites parent::getFirstContribution() */ - protected function getFirstContribution($context) { + protected function getFirstContribution(CRM_Banking_Matcher_Context $context): ?array { $contributions = $this->getEligibleContributions($context); if (empty($contributions)) { return NULL; diff --git a/CRM/Banking/PluginImpl/PostProcessor/RecurringFails.php b/CRM/Banking/PluginImpl/PostProcessor/RecurringFails.php index 3f84d371..e43b5498 100644 --- a/CRM/Banking/PluginImpl/PostProcessor/RecurringFails.php +++ b/CRM/Banking/PluginImpl/PostProcessor/RecurringFails.php @@ -61,8 +61,8 @@ protected function shouldExecute( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, - $preview = FALSE - ) { + bool $preview = FALSE + ): bool { $config = $this->_plugin_config; // check if there are rules @@ -83,15 +83,13 @@ protected function shouldExecute( } /** - * Post-process the (already executed) match - * - * @param $match CRM_Banking_Matcher_Suggestion the executed match - * @param $matcher CRM_Banking_PluginModel_Matcher the related transaction - * @param $context CRM_Banking_Matcher_Context the matcher context contains cache data and context information - * - * @throws Exception + * @inheritDoc */ - public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context) { + public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): ?bool { $config = $this->_plugin_config; if ($this->shouldExecute($match, $matcher, $context)) { @@ -118,7 +116,11 @@ public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_ } } } + + return NULL; } + + return FALSE; } /** diff --git a/CRM/Banking/PluginModel/BtxBase.php b/CRM/Banking/PluginModel/BtxBase.php index a8679b45..77fbb41e 100644 --- a/CRM/Banking/PluginModel/BtxBase.php +++ b/CRM/Banking/PluginModel/BtxBase.php @@ -28,7 +28,7 @@ abstract class CRM_Banking_PluginModel_BtxBase extends CRM_Banking_PluginModel_B * * phpcs:disable Generic.Metrics.CyclomaticComplexity.MaxExceeded, Generic.Metrics.NestingLevel.TooHigh */ - public function requiredValuesPresent(CRM_Banking_BAO_BankTransaction &$btx, $required_values_override = NULL): bool { + public function requiredValuesPresent(CRM_Banking_BAO_BankTransaction $btx, $required_values_override = NULL): bool { // phpcs:enable $config = $this->_plugin_config; @@ -350,6 +350,10 @@ public function getPropagationSet($btx, $suggestion, $subset = '', $propagation_ /** * Fetch a named propagation object. + * + * @param string $name + * @param \CRM_Banking_BAO_BankTransaction $btx + * * @see CRM_Banking_PluginModel_BtxBase::getPropagationValue */ public function getPropagationObject($name, $btx) { diff --git a/CRM/Banking/PluginModel/PostProcessor.php b/CRM/Banking/PluginModel/PostProcessor.php index 9ecdae60..62679e46 100644 --- a/CRM/Banking/PluginModel/PostProcessor.php +++ b/CRM/Banking/PluginModel/PostProcessor.php @@ -29,30 +29,29 @@ public function __construct($plugin_dao) { // read config, set defaults $config = $this->_plugin_config; - if (!isset($config->require_btx_status_list)) { - $config->require_btx_status_list = ['processed']; - } - if (!isset($config->contribution_fields_required)) { - $config->contribution_fields_required = ''; - } - if (!isset($config->membership_fields_required)) { - $config->membership_fields_required = ''; - } + $config->require_btx_status_list ??= ['processed']; + $config->contribution_fields_required ??= ''; + $config->membership_fields_required ??= ''; } /** * Postprocess the (already executed) match * - * @param $match the executed match - * @param $btx the related transaction - * @param $matcher the matcher plugin executed - * @param $context the matcher context contains cache data and context information + * @param \CRM_Banking_Matcher_Suggestion $match the executed match + * @param \CRM_Banking_PluginModel_Matcher $matcher the matcher plugin executed + * @param \CRM_Banking_Matcher_Context $context the matcher context contains cache data and context information * - * @return array | FALSE | NULL + * @return array|FALSE|NULL * The result of the execution, or FALSE when it has not been executed, or * NULL when it might have been executed. + * + * @throws \CRM_Core_Exception */ - abstract public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context); + abstract public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): array|bool|null; /** * Visualizes the post processing result for the (already executed) match. @@ -60,11 +59,14 @@ abstract public function processExecutedMatch(CRM_Banking_Matcher_Suggestion $ma * @param CRM_Banking_Matcher_Suggestion $match * @param CRM_Banking_PluginModel_Matcher $matcher * @param CRM_Banking_Matcher_Context $context - * @param array $result - * - * @return mixed + * @param array|null $result */ - public function visualizeExecutedMatch(CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, $result) { + public function visualizeExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context, + ?array $result + ): string { return E::ts('%1 might have been executed.', [1 => $this->getName()]); } @@ -91,7 +93,7 @@ public function previewMatch( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context - ) { + ): ?string { return $this->shouldExecute( $match, $matcher, @@ -115,14 +117,14 @@ public function previewMatch( * @return bool * Whether this postprocessor is to be executed. * - * @throws Exception + * @throws \CRM_Core_Exception */ protected function shouldExecute( CRM_Banking_Matcher_Suggestion $match, CRM_Banking_PluginModel_Matcher $matcher, CRM_Banking_Matcher_Context $context, - $preview = FALSE - ) { + bool $preview = FALSE + ): bool { if (!$preview) { // check if the btx status is accepted $config = $this->_plugin_config; @@ -135,7 +137,7 @@ protected function shouldExecute( 'id' => $context->btx->status_id, ] ); - if (!in_array($btx_status_name, $config->require_btx_status_list)) { + if (!in_array($btx_status_name, $config->require_btx_status_list, TRUE)) { // TODO: log: NOT IN STATUS $this->logMessage('Not executing, not in status ' . json_encode($config->require_btx_status_list), 'debug'); return FALSE; @@ -152,11 +154,7 @@ protected function shouldExecute( } /** - * Fetch a named propagation object. - * - * @param string $name - * - * @see CRM_Banking_PluginModel_BtxBase::getPropagationValue + * @inheritDoc */ public function getPropagationObject($name, $btx) { // in this default implementation, no extra objects are provided @@ -186,7 +184,7 @@ public function getPropagationObject($name, $btx) { * * @return int|null contact_id of the unique contact linked to the transaction, NULL if not exists/unique */ - protected function getSoleContactID(CRM_Banking_Matcher_Context $context) { + protected function getSoleContactID(CRM_Banking_Matcher_Context $context): ?int { $contact_id = NULL; $contributions = $this->getContributions($context); foreach ($contributions as $contribution) { @@ -215,13 +213,14 @@ protected function getSoleContactID(CRM_Banking_Matcher_Context $context) { * Get the ONE contact this transaction has been associated with. If there are * multiple candidates, NULL is returned * - * @param $context the matcher context contains cache data and context information + * @param \CRM_Banking_Matcher_Context $context the matcher context contains cache data and context information * - * @return contact contact data or NULL + * @return array|null contact data or NULL */ - protected function getSoleContact(CRM_Banking_Matcher_Context $context) { + protected function getSoleContact(CRM_Banking_Matcher_Context $context): ?array { $contact_id = $this->getSoleContactID($context); if ($contact_id) { + /** @var array */ return civicrm_api3('Contact', 'getsingle', ['id' => $contact_id]); } else { @@ -232,25 +231,23 @@ protected function getSoleContact(CRM_Banking_Matcher_Context $context) { /** * deliver the first of the eligible contributions * overwrites parent::getFirstContribution() + * + * @return array|null */ - protected function getFirstContribution($context) { + protected function getFirstContribution(CRM_Banking_Matcher_Context $context): ?array { $contributions = $this->getContributions($context); - if (empty($contributions)) { - return NULL; - } - else { - return reset($contributions); - } + + return [] === $contributions ? NULL : reset($contributions); } /** * Get the list of contributions linked to this trxn ID * - * @param $context the matcher context contains cache data and context information + * @param \CRM_Banking_Matcher_Context $context the matcher context contains cache data and context information * * @return list> contributions */ - protected function getContributions(CRM_Banking_Matcher_Context $context) { + protected function getContributions(CRM_Banking_Matcher_Context $context): array { $cache_key = "{$this->_plugin_id}_contributions_{$context->btx->id}"; /** @var list>|null $cached_result */ $cached_result = $context->getCachedEntry($cache_key); @@ -290,16 +287,12 @@ protected function getContributions(CRM_Banking_Matcher_Context $context) { * * @param CRM_Banking_Matcher_Context $context the matcher context contains cache data and context information * - * @return array membership data + * @return array|null membership data */ - protected function getFirstMembership(CRM_Banking_Matcher_Context $context) { + protected function getFirstMembership(CRM_Banking_Matcher_Context $context): ?array { $memberships = $this->getMemberships($context); - if (empty($memberships)) { - return NULL; - } - else { - return reset($memberships); - } + + return [] === $memberships ? NULL : reset($memberships); } /** @@ -309,7 +302,7 @@ protected function getFirstMembership(CRM_Banking_Matcher_Context $context) { * * @return list> memberships data */ - protected function getMemberships(CRM_Banking_Matcher_Context $context) { + protected function getMemberships(CRM_Banking_Matcher_Context $context): array { $cache_key = "{$this->_plugin_id}_memberships_{$context->btx->id}"; /** @var list>|null $cached_result */ $cached_result = $context->getCachedEntry($cache_key); @@ -367,19 +360,19 @@ protected function getMemberships(CRM_Banking_Matcher_Context $context) { /** * Get the list of contributions linked to this trxn ID * - * @param $context the matcher context contains cache data and context information + * @param \CRM_Banking_Matcher_Context $context the matcher context contains cache data and context information * - * @return array contribution IDs + * @return list contribution IDs */ - protected function getContributionIDs(CRM_Banking_Matcher_Context $context) { + protected function getContributionIDs(CRM_Banking_Matcher_Context $context): array { $match = $context->getExecutedSuggestion(); $contribution_ids = []; - if ($match) { + if (NULL !== $match) { // get the single-style ('contribution_id') $single_id = $match->getParameter('contribution_id'); if (is_numeric($single_id)) { - $contribution_ids[$single_id] = 1; + $contribution_ids[(int) $single_id] = 1; } // get the multi-style ('contribution_ids') @@ -387,7 +380,7 @@ protected function getContributionIDs(CRM_Banking_Matcher_Context $context) { if (is_array($multi_ids)) { foreach ($multi_ids as $contribution_id) { if (is_numeric($contribution_id)) { - $contribution_ids[$contribution_id] = 1; + $contribution_ids[(int) $contribution_id] = 1; } } } @@ -398,8 +391,10 @@ protected function getContributionIDs(CRM_Banking_Matcher_Context $context) { /** * Add all given tags to the given contact + * + * @param array $tag_names */ - protected function tagContact($contact_id, $tag_names) { + protected function tagContact(int|string $contact_id, array $tag_names): void { foreach ($tag_names as $tag_name) { $tag = civicrm_api3('Tag', 'get', [ 'name' => $tag_name, diff --git a/Civi/Banking/DependencyInjection/Compiler/PostProcessorCustomActionHandlerPass.php b/Civi/Banking/DependencyInjection/Compiler/PostProcessorCustomActionHandlerPass.php new file mode 100644 index 00000000..424586ff --- /dev/null +++ b/Civi/Banking/DependencyInjection/Compiler/PostProcessorCustomActionHandlerPass.php @@ -0,0 +1,71 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\DependencyInjection\Compiler; + +use Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerCollector; +use Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerInterface; +use Civi\Core\ClassScanner; +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Reference; + +final class PostProcessorCustomActionHandlerPass implements CompilerPassInterface { + + public function process(ContainerBuilder $container): void { + $services = []; + + foreach (ClassScanner::get(['interface' => CustomActionHandlerInterface::class]) as $class) { + if (CustomActionHandlerCollector::class === $class) { + continue; + } + + $constantName = $class . '::TYPE'; + if (!defined($constantName)) { + throw new \RuntimeException(sprintf('Constant "TYPE" is missing in class "%s"', $class)); + } + + /** @var string $type */ + $type = constant($constantName); + if (isset($services[$type])) { + throw new \RuntimeException( + sprintf( + 'Duplicate action handler with action type "%s" (%s, %s)', + $type, + (string) $services[$type], + $class, + ) + ); + } + + if (!$container->has($class)) { + $container->autowire($class, $class); + } + + $services[$type] = new Reference($class); + } + + $container->register(CustomActionHandlerInterface::class, CustomActionHandlerCollector::class) + ->addArgument(ServiceLocatorTagPass::register($container, $services)) + ->setPublic(TRUE); + } + +} diff --git a/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/Api4CustomActionHandler.php b/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/Api4CustomActionHandler.php new file mode 100644 index 00000000..a0431cde --- /dev/null +++ b/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/Api4CustomActionHandler.php @@ -0,0 +1,92 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\PostProcessor\CustomAction\ActionHandlers; + +use Civi\Banking\Matcher\Helper\Api4ParamsFactory; +use Civi\Banking\Matcher\Helper\Api4ResultMapOptions; +use Civi\Banking\Matcher\Helper\Api4ResultMapper; +use Civi\Banking\Matcher\Helper\ExpressionLanguageValuesGenerator; +use Civi\Banking\PostProcessor\CustomAction\CustomActionContext; +use Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerInterface; +use Webmozart\Assert\Assert; + +/** + * @implements CustomActionHandlerInterface + */ +final class Api4CustomActionHandler implements CustomActionHandlerInterface { + + public const TYPE = 'api4'; + + /** + * @var \Closure(string, string, array): \Civi\Api4\Generic\Result + */ + private \Closure $api4Callback; + + /** + * @param null|callable(string, string, array): \Civi\Api4\Generic\Result $api4Callback + */ + public function __construct( + private readonly Api4ParamsFactory $paramFactory, + private readonly Api4ResultMapper $resultMapper, + ?callable $api4Callback = NULL + ) { + $this->api4Callback = ($api4Callback ?? 'civicrm_api4')(...); + } + + /** + * @throws \CRM_Core_Exception + * @throws \Civi\API\Exception\NotImplementedException + */ + public function execute(\stdClass $action, CustomActionContext $context): void { + $entityName = $action->entity; + Assert::notNull($entityName, 'Entity name is missing'); + Assert::string($entityName, 'Entity name has to be a string, got "%s"'); + $actionName = $action->action; + Assert::notNull($actionName, 'Action name is missing'); + Assert::string($actionName, 'Action name has to be a string, got "%s"'); + + $params = $this->paramFactory->createParams( + $action, + ExpressionLanguageValuesGenerator::generateValuesForPrefixes( + ['btx', 'ba', 'party_ba', 'match'], + fn(string $key) => $context->getValue($key) + ) + ); + + $context->log("Execute APIv4 call $entityName.$actionName with " . json_encode($params)); + /** @throws \CRM_Core_Exception */ + $result = ($this->api4Callback)($entityName, $actionName, $params); + + if (property_exists($action, 'result_map')) { + Assert::isInstanceOf($action->result_map, \stdClass::class, 'Result map has to be an object, got %s'); + $resultMap = (array) $action->result_map; + $resultMapOptions = Api4ResultMapOptions::fromObject($action->result_map_options ?? NULL); + foreach ($this->resultMapper->applyResultMap($result, $resultMap, $resultMapOptions) as $key => $value) { + $context->setValue($key, $value); + }; + } + } + +} diff --git a/Civi/Banking/PostProcessor/CustomAction/CustomActionContext.php b/Civi/Banking/PostProcessor/CustomAction/CustomActionContext.php new file mode 100644 index 00000000..1114108d --- /dev/null +++ b/Civi/Banking/PostProcessor/CustomAction/CustomActionContext.php @@ -0,0 +1,82 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\PostProcessor\CustomAction; + +class CustomActionContext { + + public function __construct( + private readonly \CRM_Banking_PluginModel_PostProcessor $postProcessor, + private readonly \CRM_Banking_Matcher_Suggestion $match, + private readonly \CRM_Banking_Matcher_Context $matcherContext, + ) {} + + public function getValue(string $key): mixed { + if (str_starts_with($key, 'tmp.')) { + return $this->matcherContext->getCachedEntry($key); + } + + return $this->postProcessor->getPropagationValue($this->matcherContext->btx, $this->match, $key); + } + + // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh + public function setValue(string $key, mixed $value): void { + [$keyPrefix, $key] = explode('.', $key, 2) + [NULL, NULL]; + if (NULL === $key) { + $key = $keyPrefix; + $keyPrefix = 'btx'; + } + + $btx = $this->matcherContext->btx; + if ('btx' === $keyPrefix) { + $data = $btx->getDataParsed(); + $data[$key] = $value; + $btx->setDataParsed($data); + } + elseif ('tmp' === $keyPrefix) { + $this->matcherContext->setCachedEntry("tmp.$key", $value); + } + elseif ('ba' === $keyPrefix) { + $data = $btx->getBankAccount()?->getDataParsed(); + if (NULL !== $data) { + $data[$key] = $value; + $btx->getBankAccount()->setDataParsed($data); + } + } + elseif ('party_ba' === $keyPrefix) { + $data = $btx->getPartyBankAccount()?->getDataParsed(); + if (NULL !== $data) { + $data[$key] = $value; + $btx->getPartyBankAccount()->setDataParsed($data); + } + } + elseif ('match' === $keyPrefix) { + $this->match->setParameter($key, $value); + } + else { + throw new \InvalidArgumentException("Unknown key prefix '$keyPrefix'"); + } + } + + public function log(string $message): void { + $this->postProcessor->logMessage($message); + } + +} diff --git a/Civi/Banking/PostProcessor/CustomAction/CustomActionHandlerCollector.php b/Civi/Banking/PostProcessor/CustomAction/CustomActionHandlerCollector.php new file mode 100644 index 00000000..c9eb78f0 --- /dev/null +++ b/Civi/Banking/PostProcessor/CustomAction/CustomActionHandlerCollector.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\PostProcessor\CustomAction; + +use Psr\Container\ContainerInterface; +use Webmozart\Assert\Assert; + +/** + * @implements CustomActionHandlerInterface + */ +final class CustomActionHandlerCollector implements CustomActionHandlerInterface { + + public function __construct( + private readonly ContainerInterface $container, + ) {} + + public function execute(\stdClass $action, CustomActionContext $context): void { + Assert::propertyExists($action, 'type'); + Assert::string($action->type); + $this->getHandler($action->type)->execute($action, $context); + } + + /** + * @phpstan-ignore missingType.generics + */ + private function getHandler(string $type): CustomActionHandlerInterface { + if (!$this->container->has($type)) { + throw new \InvalidArgumentException(sprintf('Unknown action type "%s"', $type)); + } + + // @phpstan-ignore return.type + return $this->container->get($type); + } + +} diff --git a/Civi/Banking/PostProcessor/CustomAction/CustomActionHandlerInterface.php b/Civi/Banking/PostProcessor/CustomAction/CustomActionHandlerInterface.php new file mode 100644 index 00000000..eadd6904 --- /dev/null +++ b/Civi/Banking/PostProcessor/CustomAction/CustomActionHandlerInterface.php @@ -0,0 +1,36 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\PostProcessor\CustomAction; + +/** + * @template T of object + */ +interface CustomActionHandlerInterface { + + /** + * @phpstan-param T&\stdClass $action + * + * @throws \InvalidArgumentException + * @throws \CRM_Core_Exception + */ + public function execute(\stdClass $action, CustomActionContext $context): void; + +} diff --git a/Civi/Banking/PostProcessor/CustomActionsPostProcessor.php b/Civi/Banking/PostProcessor/CustomActionsPostProcessor.php new file mode 100644 index 00000000..44fef30b --- /dev/null +++ b/Civi/Banking/PostProcessor/CustomActionsPostProcessor.php @@ -0,0 +1,74 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\PostProcessor; + +use Civi\Banking\PostProcessor\CustomAction\CustomActionContext; +use Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerInterface; +use CRM_Banking_Matcher_Context; +use CRM_Banking_Matcher_Suggestion; +use CRM_Banking_PluginModel_Matcher; + +use CRM_Banking_ExtensionUtil as E; + +final class CustomActionsPostProcessor extends \CRM_Banking_PluginModel_PostProcessor { + + public const NAME = 'postprocessor_custom_actions'; + + public static function description(): string { + return E::ts('Executes custom actions.'); + } + + public static function title(): string { + return E::ts('Custom Actions PostProcessor'); + } + + public function __construct(\CRM_Banking_DAO_PluginInstance $pluginDao) { + parent::__construct($pluginDao); + + $config = $this->getConfig(); + $config->actions ??= []; + } + + /** + * @inheritDoc + */ + public function processExecutedMatch( + CRM_Banking_Matcher_Suggestion $match, + CRM_Banking_PluginModel_Matcher $matcher, + CRM_Banking_Matcher_Context $context + ): ?bool { + if (!$this->shouldExecute($match, $matcher, $context)) { + return FALSE; + } + + $config = $this->getConfig(); + + /** @var \Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerInterface $actionHandler */ + $actionHandler = \Civi::service(CustomActionHandlerInterface::class); + $customActionContext = new CustomActionContext($this, $match, $context); + foreach ($config->actions as $action) { + $actionHandler->execute($action, $customActionContext); + } + + return NULL; + } + +} diff --git a/banking.php b/banking.php index a22d2700..2720bb9d 100755 --- a/banking.php +++ b/banking.php @@ -22,6 +22,7 @@ use Civi\Banking\DependencyInjection\Compiler\ActionProviderPass; use Civi\Banking\DependencyInjection\Compiler\ExpressionLanguagePass; +use Civi\Banking\DependencyInjection\Compiler\PostProcessorCustomActionHandlerPass; use Civi\Banking\DependencyInjection\Compiler\RegexAnalyserActionHandlerPass; use Civi\Banking\Matcher\Helper\Api4ParamsFactory; use Civi\Banking\Matcher\Helper\Api4ResultMapper; @@ -41,11 +42,16 @@ function _banking_composer_autoload(): void { function banking_civicrm_container(ContainerBuilder $container): void { _banking_composer_autoload(); $container->addCompilerPass(new ActionProviderPass()); + $container->addCompilerPass(new PostProcessorCustomActionHandlerPass()); $container->addCompilerPass(new ExpressionLanguagePass()); $container->addCompilerPass(new RegexAnalyserActionHandlerPass()); $container->autowire(Api4ParamsFactory::class); $container->autowire(Api4ResultMapper::class); + + if (function_exists('_banking_test_civicrm_container')) { + _banking_test_civicrm_container($container); + } } /** @@ -92,10 +98,12 @@ function banking_civicrm_pageRun(&$page) { * @param list $classes */ function banking_civicrm_scanClasses(array &$classes): void { + // @phpstan-ignore parameterByRef.type + ClassScanner::scanFolders($classes, __DIR__, 'Civi/Banking/ExpressionLanguage', '\\'); // @phpstan-ignore parameterByRef.type ClassScanner::scanFolders($classes, __DIR__, 'Civi/Banking/Matcher', '\\'); // @phpstan-ignore parameterByRef.type - ClassScanner::scanFolders($classes, __DIR__, 'Civi/Banking/ExpressionLanguage', '\\'); + ClassScanner::scanFolders($classes, __DIR__, 'Civi/Banking/PostProcessor', '\\'); } /** diff --git a/composer.json b/composer.json index f619cd1d..37236398 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "civicrm/civicrm-core": ">=5.76", "civicrm/civicrm-packages": ">=5.76", "symfony/expression-language": "^5.4 || ^6 || ^7", - "webmozart/assert": "^1 || ^2" + "webmozart/assert": "^1.1 || ^2" }, "autoload": { "classmap": ["ComposerHelper.php"] diff --git a/managed/OptionGroup_civicrm_banking_plugin_types.mgd.php b/managed/OptionGroup_civicrm_banking_plugin_types.mgd.php index 35b99788..8d81e2d2 100644 --- a/managed/OptionGroup_civicrm_banking_plugin_types.mgd.php +++ b/managed/OptionGroup_civicrm_banking_plugin_types.mgd.php @@ -1,5 +1,6 @@ 'OptionGroup_civicrm_banking_plugin_types_OptionValue_' . CustomActionsPostProcessor::NAME, + 'entity' => 'OptionValue', + 'cleanup' => 'unused', + 'update' => 'unmodified', + 'params' => [ + 'version' => 4, + 'values' => [ + 'option_group_id.name' => 'civicrm_banking.plugin_types', + 'label' => CustomActionsPostProcessor::title(), + 'value' => CustomActionsPostProcessor::class, + 'name' => CustomActionsPostProcessor::NAME, + 'description' => CustomActionsPostProcessor::description(), + ], + 'match' => [ + 'option_group_id', + 'name', + 'value', + ], + ], + ], [ 'name' => 'OptionGroup_civicrm_banking_plugin_types_OptionValue_exporter_csv', 'entity' => 'OptionValue', diff --git a/tests/phpunit/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/Api4CustomActionHandlerTest.php b/tests/phpunit/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/Api4CustomActionHandlerTest.php new file mode 100644 index 00000000..e82b0aec --- /dev/null +++ b/tests/phpunit/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/Api4CustomActionHandlerTest.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\PostProcessor\CustomAction\ActionHandlers; + +use Civi\Api4\Generic\Result; +use Civi\Banking\Api4Mock; +use Civi\Banking\ExpressionLanguage\BankingExpressionLanguage; +use Civi\Banking\Matcher\Helper\Api4ParamsFactory; +use Civi\Banking\Matcher\Helper\Api4ResultMapper; +use Civi\Banking\PostProcessor\CustomAction\CustomActionContext; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +/** + * @covers \Civi\Banking\PostProcessor\CustomAction\ActionHandlers\Api4CustomActionHandler + */ +final class Api4CustomActionHandlerTest extends TestCase { + + private Api4Mock&MockObject $api4Mock; + + private Api4CustomActionHandler $handler; + + protected function setUp(): void { + parent::setUp(); + $this->api4Mock = $this->createMock(Api4Mock::class); + $expressionLanguage = new BankingExpressionLanguage(); + $this->handler = new Api4CustomActionHandler( + new Api4ParamsFactory($expressionLanguage), + new Api4ResultMapper($expressionLanguage), + $this->api4Mock + ); + } + + public function testExecute(): void { + $action = (object) [ + 'entity' => 'SomeEntity', + 'action' => 'create', + 'params' => (object) [ + 'values' => (object) [ + 'foo' => '@=btx.foo', + ], + ], + 'result_map' => (object) [ + 'tmp.id' => 'id', + ], + ]; + + $contextMock = $this->createMock(CustomActionContext::class); + $contextMock->method('getValue')->with('btx.foo')->willReturn('bar'); + + $this->api4Mock->expects(static::once()) + ->method('__invoke') + ->with('SomeEntity', 'create', [ + 'values' => [ + 'foo' => 'bar', + ], + ]) + ->willReturn(new Result([['id' => 123]])); + + $contextMock->expects(static::once())->method('setValue')->with('tmp.id', 123); + + $this->handler->execute($action, $contextMock); + } + +} diff --git a/tests/phpunit/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/TestCustomActionHandler.php b/tests/phpunit/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/TestCustomActionHandler.php new file mode 100644 index 00000000..bb7fa093 --- /dev/null +++ b/tests/phpunit/Civi/Banking/PostProcessor/CustomAction/ActionHandlers/TestCustomActionHandler.php @@ -0,0 +1,68 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\PostProcessor\CustomAction\ActionHandlers; + +use Civi\Banking\PostProcessor\CustomAction\CustomActionContext; +use Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerInterface; + +/** + * @implements CustomActionHandlerInterface + */ +final class TestCustomActionHandler implements CustomActionHandlerInterface { + + /** + * @var \Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerInterface + */ + private CustomActionHandlerInterface $decorated; + + /** + * @var list + */ + private static array $testCalls = []; + + public static function clearTestCalls(): void { + self::$testCalls = []; + } + + /** + * @return list + */ + public static function getTestCalls(): array { + return self::$testCalls; + } + + /** + * @param \Civi\Banking\PostProcessor\CustomAction\CustomActionHandlerInterface $decorated + */ + public function __construct(CustomActionHandlerInterface $decorated) { + $this->decorated = $decorated; + } + + public function execute(\stdClass $action, CustomActionContext $context): void { + if ('test' === $action->type) { + self::$testCalls[] = [$action, $context]; + } + else { + $this->decorated->execute($action, $context); + } + } + +} diff --git a/tests/phpunit/Civi/Banking/PostProcessor/CustomActionsPostProcessorTest.php b/tests/phpunit/Civi/Banking/PostProcessor/CustomActionsPostProcessorTest.php new file mode 100644 index 00000000..708b18e8 --- /dev/null +++ b/tests/phpunit/Civi/Banking/PostProcessor/CustomActionsPostProcessorTest.php @@ -0,0 +1,74 @@ +. + */ + +declare(strict_types = 1); + +namespace Civi\Banking\PostProcessor; + +use Civi\Api4\BankTransaction; +use Civi\Banking\PostProcessor\CustomAction\ActionHandlers\TestCustomActionHandler; + +/** + * @covers \Civi\Banking\PostProcessor\CustomActionsPostProcessor + * + * @group headless + */ +final class CustomActionsPostProcessorTest extends \CRM_Banking_TestBase { + + protected function tearDown(): void { + parent::tearDown(); + TestCustomActionHandler::clearTestCalls(); + } + + public function testProcessExecutedMatch(): void { + $pluginDao = new \CRM_Banking_DAO_PluginInstance(); + $pluginDao->config = <<addValue('bank_reference', 'test') + ->addValue('value_date', '2026-03-24 17:30:00') + ->addValue('booking_date', '2026-03-23 16:00:00') + ->addValue('amount', -0.1) + ->addValue('type_id', 1) + ->addValue('status_id:name', 'processed') + ->addValue('data_raw', '{}') + ->addValue('data_parsed', '{}') + ->execute() + ->single(); + $btxBao = \CRM_Banking_BAO_BankTransaction::findById($btxValues['id']); + + $matcherMock = $this->createMock(\CRM_Banking_PluginModel_Matcher::class); + $match = new \CRM_Banking_Matcher_Suggestion($matcherMock, $btxBao); + $matcherContext = new \CRM_Banking_Matcher_Context($btxBao); + + $postProcessor->processExecutedMatch($match, $matcherMock, $matcherContext); + static::assertCount(1, TestCustomActionHandler::getTestCalls()); + static::assertEquals((object) ['type' => 'test', 'foo' => 'bar'], TestCustomActionHandler::getTestCalls()[0][0]); + } + +} diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index a28dcc8c..4fb20192 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -1,6 +1,8 @@ autowire(TestCustomActionHandler::class) + ->setDecoratedService(CustomActionHandlerInterface::class); } function addExtensionToClassLoader(string $extension): void {