diff --git a/CRM/Contribute/BAO/ContributionRecur.php b/CRM/Contribute/BAO/ContributionRecur.php index 0fa36236573b..b468a8fa37c8 100644 --- a/CRM/Contribute/BAO/ContributionRecur.php +++ b/CRM/Contribute/BAO/ContributionRecur.php @@ -403,6 +403,89 @@ public static function supportsFinancialTypeChange($id) { return CRM_Contribute_BAO_Contribution::isSingleLineItem($contribution['id']); } + /** + * Create a template contribution based on the first contribution of an + * recurring contribution. + * When a template contribution already exists this function will not try to create + * a new one. + * This way we make sure only one template contribution exists. + * + * @param int $id + * + * @throws \API_Exception + * @throws \CiviCRM_API3_Exception + * @throws \Civi\API\Exception\UnauthorizedException + * @return int|NULL the ID of the newly created template contribution. + */ + public static function ensureTemplateContributionExists(int $id) { + // Check if a template contribution already exists. + $templateContributions = Contribution::get(FALSE) + ->addWhere('contribution_recur_id', '=', $id) + ->addWhere('is_template', '=', 1) + // we need this line otherwise the is test contribution don't work. + ->addWhere('is_test', 'IN', [0, 1]) + ->addOrderBy('receive_date', 'DESC') + ->setLimit(1) + ->execute(); + if ($templateContributions->count()) { + // A template contribution already exists. + // Skip the creation of a new one. + return $templateContributions->first()['id']; + } + + // Retrieve the most recently added contribution + $mostRecentContribution = Contribution::get(FALSE) + ->addWhere('contribution_recur_id', '=', $id) + ->addWhere('is_template', '=', 0) + // we need this line otherwise the is test contribution don't work. + ->addWhere('is_test', 'IN', [0, 1]) + ->addOrderBy('receive_date', 'DESC') + ->setLimit(1) + ->execute() + ->first(); + if (!$mostRecentContribution) { + // No first contribution is found. + return NULL; + } + + $order = new CRM_Financial_BAO_Order(); + $order->setTemplateContributionID($mostRecentContribution['id']); + $order->setOverrideFinancialTypeID($overrides['financial_type_id'] ?? NULL); + $order->setOverridableFinancialTypeID($mostRecentContribution['financial_type_id']); + $order->setOverrideTotalAmount($mostRecentContribution['total_amount'] ?? NULL); + $order->setIsPermitOverrideFinancialTypeForMultipleLines(FALSE); + $line_items = $order->getLineItems(); + $mostRecentContribution['line_item'][$order->getPriceSetID()] = $line_items; + + // If the template contribution was made on-behalf then add the + // relevant values to ensure the activity reflects that. + $relatedContact = CRM_Contribute_BAO_Contribution::getOnbehalfIds($mostRecentContribution['id']); + + $templateContributionParams = []; + $templateContributionParams['is_test'] = $mostRecentContribution['is_test']; + $templateContributionParams['is_template'] = '1'; + $templateContributionParams['skipRecentView'] = TRUE; + $templateContributionParams['contribution_recur_id'] = $id; + $templateContributionParams['line_item'] = $mostRecentContribution['line_item']; + $templateContributionParams['status_id'] = 'Template'; + foreach (['contact_id', 'campaign_id', 'financial_type_id', 'currency', 'source', 'amount_level', 'address_id', 'on_behalf', 'source_contact_id', 'tax_amount', 'contribution_page_id', 'total_amount'] as $fieldName) { + if (isset($mostRecentContribution[$fieldName])) { + $templateContributionParams[$fieldName] = $mostRecentContribution[$fieldName]; + } + } + if (!empty($relatedContact['individual_id'])) { + $templateContributionParams['on_behalf'] = TRUE; + $templateContributionParams['source_contact_id'] = $relatedContact['individual_id']; + } + $templateContributionParams['source'] = $templateContributionParams['source'] ?? ts('Recurring contribution'); + $templateContribution = civicrm_api3('Contribution', 'create', $templateContributionParams); + $temporaryObject = new CRM_Contribute_BAO_Contribution(); + $temporaryObject->copyCustomFields($mostRecentContribution['id'], $templateContribution['id']); + // Add new soft credit against current $contribution. + CRM_Contribute_BAO_ContributionRecur::addrecurSoftCredit($templateContributionParams['contribution_recur_id'], $templateContribution['id']); + return $templateContribution['id']; + } + /** * Get the contribution to be used as the template for later contributions. * diff --git a/CRM/Contribute/Form/Contribution.php b/CRM/Contribute/Form/Contribution.php index 43133c429a9c..ae1f4da7e212 100644 --- a/CRM/Contribute/Form/Contribution.php +++ b/CRM/Contribute/Form/Contribution.php @@ -278,6 +278,10 @@ public function preProcess() { $this->applyCustomData('Contribution', $this->getFinancialTypeID(), $this->_id); } + if (!empty($this->_values['is_template'])) { + $this->assign('is_template', TRUE); + } + $this->_lineItems = []; if ($this->_id) { if (!empty($this->_compId) && $this->_compContext === 'participant') { @@ -299,6 +303,9 @@ public function preProcess() { $this->assign('payNow', $this->_payNow); CRM_Utils_System::setTitle(ts('Pay with Credit Card')); } + elseif (!empty($this->_values['is_template'])) { + $this->setPageTitle(ts('Template Contribution')); + } elseif ($this->_mode) { $this->setPageTitle($this->_ppID ? ts('Credit Card Pledge Payment') : ts('Credit Card Contribution')); } diff --git a/CRM/Contribute/Form/ContributionView.php b/CRM/Contribute/Form/ContributionView.php index f0cbfbe94351..fa37ed55908e 100644 --- a/CRM/Contribute/Form/ContributionView.php +++ b/CRM/Contribute/Form/ContributionView.php @@ -31,6 +31,18 @@ public function preProcess() { $values = CRM_Contribute_BAO_Contribution::getValuesWithMappings($params); + $force_create_template = CRM_Utils_Request::retrieve('force_create_template', 'Boolean', $this, FALSE, FALSE); + if ($force_create_template && !empty($values['contribution_recur_id']) && empty($values['is_template'])) { + // Create a template contribution. + $templateContributionId = CRM_Contribute_BAO_ContributionRecur::ensureTemplateContributionExists($values['contribution_recur_id']); + if (!empty($templateContributionId)) { + $id = $templateContributionId; + $params = ['id' => $id]; + $values = CRM_Contribute_BAO_Contribution::getValuesWithMappings($params); + } + } + $this->assign('is_template', $values['is_template']); + if (CRM_Financial_BAO_FinancialType::isACLFinancialTypeStatus() && $this->_action & CRM_Core_Action::VIEW) { $financialTypeID = CRM_Contribute_PseudoConstant::financialType($values['financial_type_id']); CRM_Financial_BAO_FinancialType::checkPermissionedLineItems($id, 'view'); @@ -172,16 +184,20 @@ public function preProcess() { $this->assign('totalTaxAmount', $values['tax_amount']); } + // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container $displayName = CRM_Contact_BAO_Contact::displayName($values['contact_id']); $this->assign('displayName', $displayName); - // Check if this is default domain contact CRM-10482 if (CRM_Contact_BAO_Contact::checkDomainContact($values['contact_id'])) { $displayName .= ' (' . ts('default organization') . ')'; } - // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container - CRM_Utils_System::setTitle(ts('View Contribution from') . ' ' . $displayName); + if (empty($values['is_template'])) { + CRM_Utils_System::setTitle(ts('View Contribution from') . ' ' . $displayName); + } + else { + CRM_Utils_System::setTitle(ts('View Template Contribution from') . ' ' . $displayName); + } // add viewed contribution to recent items list $url = CRM_Utils_System::url('civicrm/contact/view/contribution', diff --git a/CRM/Contribute/Page/Tab.php b/CRM/Contribute/Page/Tab.php index 2d75e2096c0c..695ebfc6e7c7 100644 --- a/CRM/Contribute/Page/Tab.php +++ b/CRM/Contribute/Page/Tab.php @@ -57,6 +57,18 @@ public static function recurLinks(int $recurID, $context = 'contribution') { 'qs' => "reset=1&id=%%crid%%&cid=%%cid%%&context={$context}", ], ]; + + $templateContribution = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($recurID); + if (!empty($templateContribution['id']) && $paymentProcessorObj->supportsEditRecurringContribution()) { + // Use constant CRM_Core_Action::PREVIEW as there is no such thing as view template. + // And reusing view will mangle the actions. + $links[CRM_Core_Action::PREVIEW] = [ + 'name' => ts('View Template'), + 'title' => ts('View Template Contribution'), + 'url' => 'civicrm/contact/view/contribution', + 'qs' => "reset=1&id={$templateContribution['id']}&cid=%%cid%%&action=view&context={$context}&force_create_template=1", + ]; + } if ( (CRM_Core_Permission::check('edit contributions') || $context !== 'contribution') && ($paymentProcessorObj->supports('ChangeSubscriptionAmount') diff --git a/templates/CRM/Contribute/Form/Contribution.tpl b/templates/CRM/Contribute/Form/Contribution.tpl index 185d5970d11b..627e28b30972 100644 --- a/templates/CRM/Contribute/Form/Contribution.tpl +++ b/templates/CRM/Contribute/Form/Contribution.tpl @@ -141,7 +141,7 @@ {* CRM-7362 --add campaign to contributions *} {include file="CRM/Campaign/Form/addCampaignToComponent.tpl" campaignTrClass="crm-contribution-form-block-campaign_id"} - {if !$contributionMode || $payNow} + {if (empty($is_template) && (!$contributionMode || $payNow))}