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() . '