X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=CRM%2FContribute%2FBAO%2FContribution.php;h=c8ee4df56b094de158f9b073548d72d9a30bc735;hb=1844808f5fffae828954677d7417112c3d5dd0a7;hp=964f3e971bfd175d8e00b046e5f60bc9b6cbae68;hpb=ec04cb127aabe14fe794a37c3ac9b992ce073b97;p=civicrm-core.git diff --git a/CRM/Contribute/BAO/Contribution.php b/CRM/Contribute/BAO/Contribution.php index 964f3e971b..c8ee4df56b 100644 --- a/CRM/Contribute/BAO/Contribution.php +++ b/CRM/Contribute/BAO/Contribution.php @@ -1,7 +1,7 @@ $contributionID), CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray); + } if ($contributionID) { CRM_Utils_Hook::pre('edit', 'Contribution', $contributionID, $params); @@ -172,13 +173,12 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { $contribution->id = $contributionID; - if (!CRM_Utils_Rule::currencyCode($contribution->currency)) { - $config = CRM_Core_Config::singleton(); - $contribution->currency = $config->defaultCurrency; - } - - if ($contributionID && $setPrevContribution) { - $params['prevContribution'] = self::getValues(array('id' => $contributionID), CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray); + if (empty($contribution->id)) { + // (only) on 'create', make sure that a valid currency is set (CRM-16845) + if (!CRM_Utils_Rule::currencyCode($contribution->currency)) { + $config = CRM_Core_Config::singleton(); + $contribution->currency = $config->defaultCurrency; + } } $result = $contribution->save(); @@ -228,9 +228,10 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { * @param array $ids * The array that holds all the db ids. * - * @return CRM_Contribute_BAO_Contribution|null the found object or null + * @return CRM_Contribute_BAO_Contribution|null + * The found object or null */ - public static function &getValues($params, &$values, &$ids) { + public static function getValues($params, &$values, &$ids) { if (empty($params)) { return NULL; } @@ -257,9 +258,13 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { * * @param array $params * Params for a new contribution before they are saved. + * @param int|null $contributionID + * Contribution ID if we are dealing with an update. + * + * @throws \CiviCRM_API3_Exception */ - public static function calculateMissingAmountParams(&$params) { - if (!isset($params['fee_amount'])) { + public static function calculateMissingAmountParams(&$params, $contributionID) { + if (!$contributionID && !isset($params['fee_amount'])) { if (isset($params['total_amount']) && isset($params['net_amount'])) { $params['fee_amount'] = $params['total_amount'] - $params['net_amount']; } @@ -268,8 +273,79 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { } } if (!isset($params['net_amount'])) { - $params['net_amount'] = $params['total_amount'] - $params['fee_amount']; + if (!$contributionID) { + $params['net_amount'] = $params['total_amount'] - $params['fee_amount']; + } + else { + if (isset($params['fee_amount']) || isset($params['total_amount'])) { + // We have an existing contribution and fee_amount or total_amount has been passed in but not net_amount. + // net_amount may need adjusting. + $contribution = civicrm_api3('Contribution', 'getsingle', array( + 'id' => $contributionID, + 'return' => array('total_amount', 'net_amount'), + )); + $totalAmount = isset($params['total_amount']) ? $params['total_amount'] : CRM_Utils_Array::value('total_amount', $contribution); + $feeAmount = isset($params['fee_amount']) ? $params['fee_amount'] : CRM_Utils_Array::value('fee_amount', $contribution); + $params['net_amount'] = $totalAmount - $feeAmount; + } + } + } + } + + /** + * @param $params + * @param $billingLocationTypeID + * + * @return array + */ + protected static function getBillingAddressParams($params, $billingLocationTypeID) { + $hasBillingField = FALSE; + $billingFields = array( + 'street_address', + 'city', + 'state_province_id', + 'postal_code', + 'country_id', + ); + + //build address array + $addressParams = array(); + $addressParams['location_type_id'] = $billingLocationTypeID; + $addressParams['is_billing'] = 1; + + $billingFirstName = CRM_Utils_Array::value('billing_first_name', $params); + $billingMiddleName = CRM_Utils_Array::value('billing_middle_name', $params); + $billingLastName = CRM_Utils_Array::value('billing_last_name', $params); + $addressParams['address_name'] = "{$billingFirstName}" . CRM_Core_DAO::VALUE_SEPARATOR . "{$billingMiddleName}" . CRM_Core_DAO::VALUE_SEPARATOR . "{$billingLastName}"; + + foreach ($billingFields as $value) { + $addressParams[$value] = CRM_Utils_Array::value("billing_{$value}-{$billingLocationTypeID}", $params); + if (!empty($addressParams[$value])) { + $hasBillingField = TRUE; + } } + return array($hasBillingField, $addressParams); + } + + /** + * Get address params ready to be passed to the payment processor. + * + * We need address params in a couple of formats. For the payment processor we wan state_province_id-5. + * To create an address we need state_province_id. + * + * @param array $params + * @param int $billingLocationTypeID + * + * @return array + */ + public static function getPaymentProcessorReadyAddressParams($params, $billingLocationTypeID) { + list($hasBillingField, $addressParams) = self::getBillingAddressParams($params, $billingLocationTypeID); + foreach ($addressParams as $name => $field) { + if (substr($name, 0, 8) == 'billing_') { + $addressParams[substr($name, 9)] = $addressParams[$field]; + } + } + return array($hasBillingField, $addressParams); } /** @@ -367,62 +443,7 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { } } - // Handle soft credit and / or link to personal campaign page - $softIDs = CRM_Contribute_BAO_ContributionSoft::getSoftCreditIds($contribution->id); - - $pcpId = CRM_Contribute_BAO_ContributionSoft::getSoftCreditIds($contribution->id, TRUE); - - if ($pcp = CRM_Utils_Array::value('pcp', $params)) { - $softParams = array(); - $softParams['id'] = $pcpId ? $pcpId : NULL; - $softParams['contribution_id'] = $contribution->id; - $softParams['pcp_id'] = $pcp['pcp_made_through_id']; - $softParams['contact_id'] = CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCP', - $pcp['pcp_made_through_id'], 'contact_id' - ); - $softParams['currency'] = $contribution->currency; - $softParams['amount'] = $contribution->total_amount; - $softParams['pcp_display_in_roll'] = CRM_Utils_Array::value('pcp_display_in_roll', $pcp); - $softParams['pcp_roll_nickname'] = CRM_Utils_Array::value('pcp_roll_nickname', $pcp); - $softParams['pcp_personal_note'] = CRM_Utils_Array::value('pcp_personal_note', $pcp); - $softParams['soft_credit_type_id'] = CRM_Core_OptionGroup::getValue('soft_credit_type', 'pcp', 'name'); - $contributionSoft = CRM_Contribute_BAO_ContributionSoft::add($softParams); - //Send notification to owner for PCP - if ($contributionSoft->pcp_id && empty($pcpId)) { - CRM_Contribute_Form_Contribution_Confirm::pcpNotifyOwner($contribution, $contributionSoft); - } - } - //Delete PCP against this contribution and create new on submitted PCP information - elseif (array_key_exists('pcp', $params) && $pcpId) { - $deleteParams = array('id' => $pcpId); - CRM_Contribute_BAO_ContributionSoft::del($deleteParams); - } - if (isset($params['soft_credit'])) { - $softParams = $params['soft_credit']; - foreach ($softParams as $softParam) { - if (!empty($softIDs)) { - $key = key($softIDs); - $softParam['id'] = $softIDs[$key]; - unset($softIDs[$key]); - } - $softParam['contribution_id'] = $contribution->id; - $softParam['currency'] = $contribution->currency; - //case during Contribution Import when we assign soft contribution amount as contribution's total_amount by default - if (empty($softParam['amount'])) { - $softParam['amount'] = $contribution->total_amount; - } - CRM_Contribute_BAO_ContributionSoft::add($softParam); - } - - if (!empty($softIDs)) { - foreach ($softIDs as $softID) { - if (!in_array($softID, $params['soft_credit_ids'])) { - $deleteParams = array('id' => $softID); - CRM_Contribute_BAO_ContributionSoft::del($deleteParams); - } - } - } - } + CRM_Contribute_BAO_ContributionSoft::processSoftContribution($params, $contribution); $transaction->commit(); @@ -435,6 +456,9 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { 'name' ); if (!$activity->find(TRUE)) { + if (empty($contribution->contact_id)) { + $contribution->find(TRUE); + } CRM_Activity_BAO_Activity::addActivity($contribution, 'Offline'); } else { @@ -501,8 +525,6 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { * (reference) the default values, some of which need to be resolved. * @param bool $reverse * True if we want to resolve the values in the reverse direction (value -> name). - * - * @return void */ public static function resolveDefaults(&$defaults, $reverse = FALSE) { self::lookupValue($defaults, 'financial_type', CRM_Contribute_PseudoConstant::financialType(), $reverse); @@ -724,8 +746,18 @@ class CRM_Contribute_BAO_Contribution extends CRM_Contribute_DAO_Contribution { ), ); + // CRM-16713 - contribution search by Premiums on 'Find Contribution' form. + $premiums = array( + 'contribution_product_id' => array( + 'title' => ts('Premium'), + 'name' => 'contribution_product_id', + 'where' => 'civicrm_product.id', + 'data_type' => CRM_Utils_Type::T_INT, + ), + ); + $fields = array_merge($impFields, $typeField, $contributionStatus, $contributionPage, $optionField, $expFieldProduct, - $expFieldsContrib, $contributionNote, $contributionRecurId, $extraFields, $softCreditFields, $financialAccount, + $expFieldsContrib, $contributionNote, $contributionRecurId, $extraFields, $softCreditFields, $financialAccount, $premiums, CRM_Core_BAO_CustomField::getFieldsForImport('Contribution') ); @@ -863,6 +895,26 @@ INNER JOIN civicrm_contact contact ON ( contact.id = civicrm_contribution.conta return $results; } + /** + * React to a financial transaction (payment) failure. + * + * Prior to CRM-16417 these were simply removed from the database but it has been agreed that seeing attempted + * payments is important for forensic and outreach reasons. + * + * @param int $contributionID + * @param string $message + */ + public static function failPayment($contributionID, $contactID, $message) { + civicrm_api3('activity', 'create', array( + 'activity_type_id' => 'Failed Payment', + 'details' => $message, + 'subject' => ts('Payment failed at payment processor'), + 'source_record_id' => $contributionID, + 'source_contact_id' => CRM_Core_Session::getLoggedInContactID() ? CRM_Core_Session::getLoggedInContactID() : + $contactID, + )); + } + /** * Check if there is a contribution with the same trxn_id or invoice_id. * @@ -1038,7 +1090,6 @@ GROUP BY p.id * * @return array * list of contribution fields - * */ public static function getHonorContacts($honorId) { $params = array(); @@ -1120,12 +1171,14 @@ WHERE civicrm_contribution.contact_id = civicrm_contact.id $nextYear = $year + 1; if ($config->fiscalYearStart) { - if ($config->fiscalYearStart['M'] < 10) { - $config->fiscalYearStart['M'] = '0' . $config->fiscalYearStart['M']; + $newFiscalYearStart = $config->fiscalYearStart; + if ($newFiscalYearStart['M'] < 10) { + $newFiscalYearStart['M'] = '0' . $newFiscalYearStart['M']; } - if ($config->fiscalYearStart['d'] < 10) { - $config->fiscalYearStart['d'] = '0' . $config->fiscalYearStart['d']; + if ($newFiscalYearStart['d'] < 10) { + $newFiscalYearStart['d'] = '0' . $newFiscalYearStart['d']; } + $config->fiscalYearStart = $newFiscalYearStart; $monthDay = $config->fiscalYearStart['M'] . $config->fiscalYearStart['d']; } else { @@ -1169,6 +1222,7 @@ WHERE civicrm_contribution.contact_id = civicrm_contact.id /** * Check if there is a contribution with the params passed in. + * * Used for trxn_id,invoice_id and contribution_id * * @param array $params @@ -1208,7 +1262,6 @@ WHERE civicrm_contribution.contact_id = civicrm_contact.id * * @return array * associated array - * */ public static function getContributionDetails($exportMode, $componentIds) { $paymentDetails = array(); @@ -1266,38 +1319,25 @@ LEFT JOIN civicrm_option_value contribution_status ON (civicrm_contribution.cont /** * Create address associated with contribution record. * + * As long as there is one or more billing field in the parameters we will create the address. + * + * (historically the decision to create or not was based on the payment 'type' but these lines are greyer than once + * thought). + * * @param array $params * @param int $billingLocationTypeID * * @return int * address id */ - public static function createAddress(&$params, $billingLocationTypeID) { - $billingFields = array( - 'street_address', - 'city', - 'state_province_id', - 'postal_code', - 'country_id', - ); - - //build address array - $addressParams = array(); - $addressParams['location_type_id'] = $billingLocationTypeID; - $addressParams['is_billing'] = 1; - - $billingFirstName = CRM_Utils_Array::value('billing_first_name', $params); - $billingMiddleName = CRM_Utils_Array::value('billing_middle_name', $params); - $billingLastName = CRM_Utils_Array::value('billing_last_name', $params); - $addressParams['address_name'] = "{$billingFirstName}" . CRM_Core_DAO::VALUE_SEPARATOR . "{$billingMiddleName}" . CRM_Core_DAO::VALUE_SEPARATOR . "{$billingLastName}"; - - foreach ($billingFields as $value) { - $addressParams[$value] = CRM_Utils_Array::value("billing_{$value}-{$billingLocationTypeID}", $params); + public static function createAddress($params, $billingLocationTypeID) { + list($hasBillingField, $addressParams) = self::getBillingAddressParams($params, $billingLocationTypeID); + if ($hasBillingField) { + $address = CRM_Core_BAO_Address::add($addressParams, FALSE); + return $address->id; } + return NULL; - $address = CRM_Core_BAO_Address::add($addressParams, FALSE); - - return $address->id; } /** @@ -1305,7 +1345,6 @@ LEFT JOIN civicrm_option_value contribution_status ON (civicrm_contribution.cont * * @param int $contributionId * @param int $contactId - * */ public static function deleteAddress($contributionId = NULL, $contactId = NULL) { $clauses = array(); @@ -1407,6 +1446,13 @@ LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_ /** * Update contribution as well as related objects. * + * This function by-passes hooks - to address this - don't use this function. + * + * @deprecated + * + * Use api contribute.completetransaction + * For failures use failPayment (preferably exposing by api in the process). + * * @param array $params * @param bool $processContributionObject * @@ -1866,6 +1912,47 @@ LEFT JOIN civicrm_contribution contribution ON ( componentPayment.contribution_ return CRM_Core_DAO::singleValueQuery($query); } + /** + * Repeat a transaction as part of a recurring series. + * + * Only call this via the api as it is being refactored. The intention is that the repeatTransaction function + * (possibly living on the ContributionRecur BAO) would be called first to create a pending contribution with a + * subsequent call to the contribution.completetransaction api. + * + * The completeTransaction functionality has historically been overloaded to both complete and repeat payments. + * + * @param CRM_Contribute_BAO_Contribution $contribution + * @param array $input + * @param array $contributionParams + * + * @return array + */ + protected static function repeatTransaction(&$contribution, &$input, $contributionParams) { + if (!empty($contribution->id)) { + return FALSE; + } + if (empty($contribution->id)) { + // Unclear why this would only be set for repeats. + if (!empty($input['amount'])) { + $contribution->total_amount = $contributionParams['total_amount'] = $input['amount']; + } + $templateContribution = civicrm_api3('Contribution', 'getsingle', array( + 'contribution_recur_id' => $contributionParams['contribution_recur_id'], + 'options' => array('limit' => 1), + )); + $contributionParams['skipLineItem'] = TRUE; + $contributionParams['status_id'] = 'Pending'; + $contributionParams['financial_type_id'] = $templateContribution['financial_type_id']; + $contributionParams['contact_id'] = $templateContribution['contact_id']; + $contributionParams['source'] = empty($templateContribution['source']) ? ts('Recurring contribution') : $templateContribution['source']; + $createContribution = civicrm_api3('Contribution', 'create', $contributionParams); + $contribution->id = $createContribution['id']; + $input['line_item'] = CRM_Contribute_BAO_ContributionRecur::addRecurLineItems($contribution->contribution_recur_id, $contribution); + CRM_Contribute_BAO_ContributionRecur::copyCustomValues($contributionParams['contribution_recur_id'], $contribution->id); + return TRUE; + } + } + /** * Get individual id for onbehalf contribution. * @@ -1986,15 +2073,13 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac * Input as delivered from Payment Processor. * @param array $ids * Ids as Loaded by Payment Processor. - * @param bool $required - * Is Payment processor / contribution page required. * @param bool $loadAll * Load all related objects - even where id not passed in? (allows API to call this). * * @return bool * @throws Exception */ - public function loadRelatedObjects(&$input, &$ids, $required = FALSE, $loadAll = FALSE) { + public function loadRelatedObjects(&$input, &$ids, $loadAll = FALSE) { if ($loadAll) { $ids = array_merge($this->getComponentDetails($this->id), $ids); if (empty($ids['contact']) && isset($this->contact_id)) { @@ -2009,18 +2094,64 @@ INNER JOIN civicrm_activity ON civicrm_activity_contact.activity_id = civicrm_ac $this->_component = strtolower(CRM_Utils_Array::value('component', $input, 'contribute')); } } - $paymentProcessorID = CRM_Utils_Array::value('paymentProcessor', $ids); - $contributionType = new CRM_Financial_BAO_FinancialType(); - $contributionType->id = $this->financial_type_id; - if (!$contributionType->find(TRUE)) { - throw new Exception("Could not find financial type record: " . $this->financial_type_id); + + // If the object is not fully populated then make sure it is - this is a more about legacy paths & cautious + // refactoring than anything else, and has unit test coverage. + if (empty($this->financial_type_id)) { + $this->find(TRUE); } - if (!empty($ids['contact'])) { - $this->_relatedObjects['contact'] = new CRM_Contact_BAO_Contact(); - $this->_relatedObjects['contact']->id = $ids['contact']; - $this->_relatedObjects['contact']->find(TRUE); + + $paymentProcessorID = CRM_Utils_Array::value('payment_processor_id', $input, CRM_Utils_Array::value( + 'paymentProcessor', + $ids + )); + + if (!$paymentProcessorID && $this->contribution_page_id) { + $paymentProcessorID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', + $this->contribution_page_id, + 'payment_processor' + ); + if ($paymentProcessorID) { + $intentionalEnotice = $CRM16923AnUnreliableMethodHasBeenUserToDeterminePaymentProcessorFromContributionPage; + } + } + + $ids['contributionType'] = $this->financial_type_id; + $ids['financialType'] = $this->financial_type_id; + + $entities = array( + 'contact' => 'CRM_Contact_BAO_Contact', + 'contributionRecur' => 'CRM_Contribute_BAO_ContributionRecur', + 'contributionType' => 'CRM_Financial_BAO_FinancialType', + 'financialType' => 'CRM_Financial_BAO_FinancialType', + ); + foreach ($entities as $entity => $bao) { + if (!empty($ids[$entity])) { + $this->_relatedObjects[$entity] = new $bao(); + $this->_relatedObjects[$entity]->id = $ids[$entity]; + if (!$this->_relatedObjects[$entity]->find(TRUE)) { + throw new CRM_Core_Exception($entity . ' could not be loaded'); + } + } + } + + if (!empty($ids['contributionRecur']) && !$paymentProcessorID) { + $paymentProcessorID = $this->_relatedObjects['contributionRecur']->payment_processor_id; + } + + if (!empty($ids['pledge_payment'])) { + foreach ($ids['pledge_payment'] as $key => $paymentID) { + if (empty($paymentID)) { + continue; + } + $payment = new CRM_Pledge_BAO_PledgePayment(); + $payment->id = $paymentID; + if (!$payment->find(TRUE)) { + throw new Exception("Could not find pledge payment record: " . $paymentID); + } + $this->_relatedObjects['pledge_payment'][] = $payment; + } } - $this->_relatedObjects['contributionType'] = $contributionType; if ($this->_component == 'contribute') { // retrieve the other optional objects first so @@ -2061,50 +2192,6 @@ WHERE contribution_id = %1 "; } } } - - if (!empty($ids['pledge_payment'])) { - - foreach ($ids['pledge_payment'] as $key => $paymentID) { - if (empty($paymentID)) { - continue; - } - $payment = new CRM_Pledge_BAO_PledgePayment(); - $payment->id = $paymentID; - if (!$payment->find(TRUE)) { - throw new Exception("Could not find pledge payment record: " . $paymentID); - } - $this->_relatedObjects['pledge_payment'][] = $payment; - } - } - - if (!empty($ids['contributionRecur'])) { - $recur = new CRM_Contribute_BAO_ContributionRecur(); - $recur->id = $ids['contributionRecur']; - if (!$recur->find(TRUE)) { - throw new Exception("Could not find recur record: " . $ids['contributionRecur']); - } - $this->_relatedObjects['contributionRecur'] = &$recur; - //get payment processor id from recur object. - $paymentProcessorID = $recur->payment_processor_id; - } - //for normal contribution get the payment processor id. - if (!$paymentProcessorID) { - if ($this->contribution_page_id) { - // get the payment processor id from contribution page - $paymentProcessorID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_ContributionPage', - $this->contribution_page_id, - 'payment_processor' - ); - } - //fail to load payment processor id. - elseif (empty($ids['pledge_payment'])) { - $loadObjectSuccess = TRUE; - if ($required) { - throw new Exception("Could not find contribution page for contribution record: " . $this->id); - } - return $loadObjectSuccess; - } - } } else { // we are in event mode @@ -2130,8 +2217,13 @@ WHERE contribution_id = %1 "; $this->_relatedObjects['participant'] = &$participant; + // get the payment processor id from event - this is inaccurate see CRM-16923 + // in future we should look at throwing an exception here rather than an dubious guess. if (!$paymentProcessorID) { $paymentProcessorID = $this->_relatedObjects['event']->payment_processor; + if ($paymentProcessorID) { + $intentionalEnotice = $CRM16923AnUnreliableMethodHasBeenUserToDeterminePaymentProcessorFromEvent; + } } } @@ -2140,12 +2232,8 @@ WHERE contribution_id = %1 "; $this->is_test ? 'test' : 'live' ); $ids['paymentProcessor'] = $paymentProcessorID; - $this->_relatedObjects['paymentProcessor'] = &$paymentProcessor; - } - elseif ($required) { - throw new Exception("Could not find payment processor for contribution record: " . $this->id); + $this->_relatedObjects['paymentProcessor'] = $paymentProcessor; } - return TRUE; } @@ -2173,9 +2261,8 @@ WHERE contribution_id = %1 "; * @throws Exception */ public function composeMessageArray(&$input, &$ids, &$values, $recur = FALSE, $returnMessageText = TRUE) { - if (empty($this->_relatedObjects)) { - $this->loadRelatedObjects($input, $ids); - } + $this->loadRelatedObjects($input, $ids); + if (empty($this->_component)) { $this->_component = CRM_Utils_Array::value('component', $input); } @@ -2187,10 +2274,7 @@ WHERE contribution_id = %1 "; //what does recur 'mean here - to do with payment processor return functionality but // what is the importance if ($recur && !empty($this->_relatedObjects['paymentProcessor'])) { - $paymentObject = &CRM_Core_Payment::singleton( - $this->is_test ? 'test' : 'live', - $this->_relatedObjects['paymentProcessor'] - ); + $paymentObject = Civi\Payment\System::singleton()->getByProcessor($this->_relatedObjects['paymentProcessor']); $entityID = $entity = NULL; if (isset($ids['contribution'])) { @@ -2206,14 +2290,9 @@ WHERE contribution_id = %1 "; $entityID = $ids['membership'][0]; } - $url = $paymentObject->subscriptionURL($entityID, $entity); - $template->assign('cancelSubscriptionUrl', $url); - - $url = $paymentObject->subscriptionURL($entityID, $entity, 'billing'); - $template->assign('updateSubscriptionBillingUrl', $url); - - $url = $paymentObject->subscriptionURL($entityID, $entity, 'update'); - $template->assign('updateSubscriptionUrl', $url); + $template->assign('cancelSubscriptionUrl', $paymentObject->subscriptionURL($entityID, $entity)); + $template->assign('updateSubscriptionBillingUrl', $paymentObject->subscriptionURL($entityID, $entity, 'billing')); + $template->assign('updateSubscriptionUrl', $paymentObject->subscriptionURL($entityID, $entity, 'update')); if ($this->_relatedObjects['paymentProcessor']['billing_mode'] & CRM_Core_Payment::BILLING_MODE_FORM) { //direct mode showing billing block, so use directIPN for temporary @@ -2222,6 +2301,39 @@ WHERE contribution_id = %1 "; } // todo remove strtolower - check consistency if (strtolower($this->_component) == 'event') { + $eventParams = array('id' => $this->_relatedObjects['participant']->event_id); + $values['event'] = array(); + + CRM_Event_BAO_Event::retrieve($eventParams, $values['event']); + + //get location details + $locationParams = array('entity_id' => $this->_relatedObjects['participant']->event_id, 'entity_table' => 'civicrm_event'); + $values['location'] = CRM_Core_BAO_Location::getValues($locationParams); + + $ufJoinParams = array( + 'entity_table' => 'civicrm_event', + 'entity_id' => $ids['event'], + 'module' => 'CiviEvent', + ); + + list($custom_pre_id, + $custom_post_ids + ) = CRM_Core_BAO_UFJoin::getUFGroupIds($ufJoinParams); + + $values['custom_pre_id'] = $custom_pre_id; + $values['custom_post_id'] = $custom_post_ids; + //for tasks 'Change Participant Status' and 'Batch Update Participants Via Profile' case + //and cases involving status updation through ipn + // whatever that means! + // total_amount appears to be the preferred input param & it is unclear why we support amount here + // perhaps we should throw an e-notice if amount is set & force total_amount? + if (!empty($input['amount'])) { + $values['totalAmount'] = $input['amount']; + } + + if ($values['event']['is_email_confirm']) { + $values['is_email_receipt'] = 1; + } return CRM_Event_BAO_Event::sendMail($ids['contact'], $values, $this->_relatedObjects['participant']->id, $this->is_test, $returnMessageText ); @@ -2315,6 +2427,11 @@ WHERE contribution_id = %1 "; $values['address'] = $addressDetails[0]['display']; } if ($this->_component == 'contribute') { + //get soft contributions + $softContributions = CRM_Contribute_BAO_ContributionSoft::getSoftContribution($this->id, TRUE); + if (!empty($softContributions)) { + $values['softContributions'] = $softContributions['soft_credit']; + } if (isset($this->contribution_page_id)) { CRM_Contribute_BAO_ContributionPage::setValues( $this->contribution_page_id, @@ -2370,6 +2487,52 @@ WHERE contribution_id = %1 "; $values['event'] = array(); CRM_Event_BAO_Event::retrieve($eventParams, $values['event']); + // add custom fields for event + $eventGroupTree = CRM_Core_BAO_CustomGroup::getTree('Event', $this->_relatedObjects['event'], $this->_relatedObjects['event']->id); + + $eventCustomGroup = array(); + foreach ($eventGroupTree as $key => $group) { + if ($key === 'info') { + continue; + } + + foreach ($group['fields'] as $k => $customField) { + $groupLabel = $group['title']; + if (!empty($customField['customValue'])) { + foreach ($customField['customValue'] as $customFieldValues) { + $eventCustomGroup[$groupLabel][$customField['label']] = CRM_Utils_Array::value('data', $customFieldValues); + } + } + } + } + $values['event']['customGroup'] = $eventCustomGroup; + + //get participant details + $participantParams = array( + 'id' => $this->_relatedObjects['participant']->id, + ); + + $values['participant'] = array(); + + CRM_Event_BAO_Participant::getValues($participantParams, $values['participant'], $participantIds); + // add custom fields for event + $participantGroupTree = CRM_Core_BAO_CustomGroup::getTree('Participant', $this->_relatedObjects['participant'], $this->_relatedObjects['participant']->id); + $participantCustomGroup = array(); + foreach ($participantGroupTree as $key => $group) { + if ($key === 'info') { + continue; + } + + foreach ($group['fields'] as $k => $customField) { + $groupLabel = $group['title']; + if (!empty($customField['customValue'])) { + foreach ($customField['customValue'] as $customFieldValues) { + $participantCustomGroup[$groupLabel][$customField['label']] = CRM_Utils_Array::value('data', $customFieldValues); + } + } + } + } + $values['participant']['customGroup'] = $participantCustomGroup; //get location details $locationParams = array( @@ -2405,6 +2568,25 @@ WHERE contribution_id = %1 "; } } + $groupTree = CRM_Core_BAO_CustomGroup::getTree('Contribution', $this, $this->id); + + $customGroup = array(); + foreach ($groupTree as $key => $group) { + if ($key === 'info') { + continue; + } + + foreach ($group['fields'] as $k => $customField) { + $groupLabel = $group['title']; + if (!empty($customField['customValue'])) { + foreach ($customField['customValue'] as $customFieldValues) { + $customGroup[$groupLabel][$customField['label']] = CRM_Utils_Array::value('data', $customFieldValues); + } + } + } + } + $values['customGroup'] = $customGroup; + return $values; } @@ -2519,6 +2701,12 @@ WHERE contribution_id = %1 "; $template->assign('is_recur', (bool) $recur); $template->assign('currency', $this->currency); $template->assign('address', CRM_Utils_Address::format($input)); + if (!empty($values['customGroup'])) { + $template->assign('customGroup', $values['customGroup']); + } + if (!empty($values['softContributions'])) { + $template->assign('softContributions', $values['softContributions']); + } if ($this->_component == 'event') { $template->assign('title', $values['event']['title']); $participantRoles = CRM_Event_PseudoConstant::participantRole(); @@ -2528,6 +2716,7 @@ WHERE contribution_id = %1 "; } $values['event']['participant_role'] = implode(', ', $viewRoles); $template->assign('event', $values['event']); + $template->assign('participant', $values['participant']); $template->assign('location', $values['location']); $template->assign('customPre', $values['custom_pre_id']); $template->assign('customPost', $values['custom_post_id']); @@ -2928,7 +3117,7 @@ WHERE contribution_id = %1 "; $params['entity_id'] = $financialTxn->id; } } - // record line items and finacial items + // record line items and financial items if (empty($params['skipLineItem'])) { CRM_Price_BAO_LineItem::processPriceSet($entityId, CRM_Utils_Array::value('line_item', $params), $params['contribution'], $entityTable, $update); } @@ -2984,8 +3173,14 @@ WHERE contribution_id = %1 "; ) { return; } + if (($params['prevContribution']->contribution_status_id == array_search('Partially paid', $contributionStatus)) + && $params['contribution']->contribution_status_id == array_search('Completed', $contributionStatus) + && $context == 'changedStatus' + ) { + return; + } if ($context == 'changedAmount' || $context == 'changeFinancialType') { - $itemAmount = $params['trxnParams']['total_amount'] = $params['total_amount'] - $params['prevContribution']->total_amount; + $itemAmount = $params['trxnParams']['total_amount'] = $params['trxnParams']['net_amount'] = $params['total_amount'] - $params['prevContribution']->total_amount; } if ($context == 'changedStatus') { //get all the statuses @@ -2996,6 +3191,9 @@ WHERE contribution_id = %1 "; || $params['contribution']->contribution_status_id == array_search('Cancelled', $contributionStatus)) ) { $params['trxnParams']['total_amount'] = -$params['total_amount']; + if (is_null($params['contribution']->creditnote_id) || $params['contribution']->creditnote_id == "null") { + self::createCreditNoteId($params['contribution']->id); + } } elseif (($params['prevContribution']->contribution_status_id == array_search('Pending', $contributionStatus) && $params['prevContribution']->is_pay_later) || $params['prevContribution']->contribution_status_id == array_search('In Progress', $contributionStatus) @@ -3007,6 +3205,9 @@ WHERE contribution_id = %1 "; if ($params['contribution']->contribution_status_id == array_search('Cancelled', $contributionStatus)) { $params['trxnParams']['to_financial_account_id'] = $arAccountId; $params['trxnParams']['total_amount'] = -$params['total_amount']; + if (is_null($params['contribution']->creditnote_id) || $params['contribution']->creditnote_id == "null") { + self::createCreditNoteId($params['contribution']->id); + } } else { $params['trxnParams']['from_financial_account_id'] = $arAccountId; @@ -3172,6 +3373,7 @@ WHERE contribution_id = %1 "; 'Pending' => array('Cancelled', 'Completed', 'Failed'), 'In Progress' => array('Cancelled', 'Completed', 'Failed'), 'Refunded' => array('Cancelled', 'Completed'), + 'Partially paid' => array('Completed'), ); if (!in_array($contributionStatuses[$fields['contribution_status_id']], $checkStatus[$contributionStatuses[$values['contribution_status_id']]])) { @@ -3730,4 +3932,474 @@ WHERE con.id = {$contributionId} } } + /** + * Update related pledge payment payments. + * + * This function has been refactored out of the back office contribution form and may + * still overlap with other functions. + * + * @param string $action + * @param int $pledgePaymentID + * @param int $contributionID + * @param bool $adjustTotalAmount + * @param float $total_amount + * @param float $original_total_amount + * @param int $contribution_status_id + * @param int $original_contribution_status_id + */ + public static function updateRelatedPledge( + $action, + $pledgePaymentID, + $contributionID, + $adjustTotalAmount, + $total_amount, + $original_total_amount, + $contribution_status_id, + $original_contribution_status_id + ) { + if (!$pledgePaymentID || $action & CRM_Core_Action::ADD && !$contributionID) { + return; + } + + if ($pledgePaymentID) { + //store contribution id in payment record. + CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $pledgePaymentID, 'contribution_id', $contributionID); + } + else { + $pledgePaymentID = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', + $contributionID, + 'id', + 'contribution_id' + ); + } + $pledgeID = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', + $contributionID, + 'pledge_id', + 'contribution_id' + ); + + $updatePledgePaymentStatus = FALSE; + + // If either the status or the amount has changed we update the pledge status. + if ($action & CRM_Core_Action::ADD) { + $updatePledgePaymentStatus = TRUE; + } + elseif ($action & CRM_Core_Action::UPDATE && (($original_contribution_status_id != $contribution_status_id) || + ($original_total_amount != $total_amount)) + ) { + $updatePledgePaymentStatus = TRUE; + } + + if ($updatePledgePaymentStatus) { + CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($pledgeID, + array($pledgePaymentID), + $contribution_status_id, + NULL, + $total_amount, + $adjustTotalAmount + ); + } + } + + /** + * Compute the stats values + * + * @param $stat either 'mode' or 'median' + * @param $sql + * @param $alias of civicrm_contribution + */ + public static function computeStats($stat, $sql, $alias = NULL) { + $mode = $median = array(); + switch ($stat) { + case 'mode': + $modeDAO = CRM_Core_DAO::executeQuery($sql); + while ($modeDAO->fetch()) { + if ($modeDAO->civicrm_contribution_total_amount_count > 1) { + $mode[] = CRM_Utils_Money::format($modeDAO->amount, $modeDAO->currency); + } + else { + $mode[] = 'N/A'; + } + } + return $mode; + + case 'median': + $currencies = CRM_Core_OptionGroup::values('currencies_enabled'); + foreach ($currencies as $currency => $val) { + $midValue = 0; + $where = "AND {$alias}.currency = '{$currency}'"; + $rowCount = CRM_Core_DAO::singleValueQuery("SELECT count(*) as count {$sql} {$where}"); + + $even = FALSE; + $offset = 1; + $medianRow = floor($rowCount / 2); + if ($rowCount % 2 == 0 && !empty($medianRow)) { + $even = TRUE; + $offset++; + $medianRow--; + } + + $medianValue = "SELECT {$alias}.total_amount as median + {$sql} {$where} + ORDER BY median LIMIT {$medianRow},{$offset}"; + $medianValDAO = CRM_Core_DAO::executeQuery($medianValue); + while ($medianValDAO->fetch()) { + if ($even) { + $midValue = $midValue + $medianValDAO->median; + } + else { + $median[] = CRM_Utils_Money::format($medianValDAO->median, $currency); + } + } + if ($even) { + $midValue = $midValue / 2; + $median[] = CRM_Utils_Money::format($midValue, $currency); + } + } + return $median; + + default: + return; + } + } + + /** + * Complete an order. + * + * Do not call this directly - use the contribution.completetransaction api as this function is being refactored. + * + * Currently overloaded to complete a transaction & repeat a transaction - fix! + * + * Moving it out of the BaseIPN class is just the first step. + * + * @param array $input + * @param array $ids + * @param array $objects + * @param CRM_Core_Transaction $transaction + * @param int $recur + * @param CRM_Contribute_BAO_Contribution $contribution + * @param bool $isRecurring + * Duplication of param needs review. Only used by AuthorizeNetIPN + * @param int $isFirstOrLastRecurringPayment + * Deprecated param only used by AuthorizeNetIPN. + */ + public static function completeOrder(&$input, &$ids, $objects, $transaction, $recur, $contribution, $isRecurring, $isFirstOrLastRecurringPayment) { + $primaryContributionID = isset($contribution->id) ? $contribution->id : $objects['first_contribution']->id; + // The previous details are used when calculating line items so keep it before any code that 'does something' + if (!empty($contribution->id)) { + $input['prevContribution'] = CRM_Contribute_BAO_Contribution::getValues(array('id' => $contribution->id), + CRM_Core_DAO::$_nullArray, CRM_Core_DAO::$_nullArray); + } + $inputContributionWhiteList = array( + 'fee_amount', + 'net_amount', + 'trxn_id', + 'check_number', + 'payment_instrument_id', + 'is_test', + 'campaign_id', + 'receive_date', + ); + + $contributionParams = array_merge(array( + 'contribution_status_id' => 'Completed', + ), array_intersect_key($input, array_fill_keys($inputContributionWhiteList, 1) + )); + + $participant = CRM_Utils_Array::value('participant', $objects); + $memberships = CRM_Utils_Array::value('membership', $objects); + $recurContrib = CRM_Utils_Array::value('contributionRecur', $objects); + if (!empty($recurContrib->id)) { + $contributionParams['contribution_recur_id'] = $recurContrib->id; + } + self::repeatTransaction($contribution, $input, $contributionParams); + + if (is_numeric($memberships)) { + $memberships = array($objects['membership']); + } + + $changeDate = CRM_Utils_Array::value('trxn_date', $input, date('YmdHis')); + + $values = array(); + if (isset($input['is_email_receipt'])) { + $values['is_email_receipt'] = $input['is_email_receipt']; + } + + if ($input['component'] == 'contribute') { + if ($contribution->contribution_page_id) { + CRM_Contribute_BAO_ContributionPage::setValues($contribution->contribution_page_id, $values); + $contributionParams['source'] = ts('Online Contribution') . ': ' . $values['title']; + } + elseif ($recurContrib && $recurContrib->id) { + $contributionParams['contribution_page_id'] = NULL; + $values['amount'] = $recurContrib->amount; + $values['financial_type_id'] = $objects['contributionType']->id; + $values['title'] = $source = ts('Offline Recurring Contribution'); + $domainValues = CRM_Core_BAO_Domain::getNameAndEmail(); + $values['receipt_from_name'] = $domainValues[0]; + $values['receipt_from_email'] = $domainValues[1]; + } + + if (empty($contributionParams['receive_date']) && $changeDate) { + $contributionParams['receive_date'] = $changeDate; + } + + if ($recurContrib && $recurContrib->id && !isset($input['is_email_receipt'])) { + //CRM-13273 - is_email_receipt setting on recurring contribution should take precedence over contribution page setting + // but CRM-16124 if $input['is_email_receipt'] is set then that should not be overridden. + $values['is_email_receipt'] = $recurContrib->is_email_receipt; + } + + if (!empty($values['is_email_receipt'])) { + $contributionParams['receipt_date'] = $changeDate; + } + + if (!empty($memberships)) { + foreach ($memberships as $membershipTypeIdKey => $membership) { + if ($membership) { + $membershipParams = array( + 'id' => $membership->id, + 'contact_id' => $membership->contact_id, + 'is_test' => $membership->is_test, + 'membership_type_id' => $membership->membership_type_id, + ); + + $currentMembership = CRM_Member_BAO_Membership::getContactMembership($membershipParams['contact_id'], + $membershipParams['membership_type_id'], + $membershipParams['is_test'], + $membershipParams['id'] + ); + + // CRM-8141 update the membership type with the value recorded in log when membership created/renewed + // this picks up membership type changes during renewals + $sql = " +SELECT membership_type_id +FROM civicrm_membership_log +WHERE membership_id={$membershipParams['id']} +ORDER BY id DESC +LIMIT 1;"; + $dao = CRM_Core_DAO::executeQuery($sql); + if ($dao->fetch()) { + if (!empty($dao->membership_type_id)) { + $membershipParams['membership_type_id'] = $dao->membership_type_id; + } + } + $dao->free(); + + $membershipParams['num_terms'] = $contribution->getNumTermsByContributionAndMembershipType( + $membershipParams['membership_type_id'], + $primaryContributionID + ); + $dates = array_fill_keys(array('join_date', 'start_date', 'end_date'), NULL); + if ($currentMembership) { + /* + * Fixed FOR CRM-4433 + * In BAO/Membership.php(renewMembership function), we skip the extend membership date and status + * when Contribution mode is notify and membership is for renewal ) + */ + CRM_Member_BAO_Membership::fixMembershipStatusBeforeRenew($currentMembership, $changeDate); + + // @todo - we should pass membership_type_id instead of null here but not + // adding as not sure of testing + $dates = CRM_Member_BAO_MembershipType::getRenewalDatesForMembershipType($membershipParams['id'], + $changeDate, NULL, $membershipParams['num_terms'] + ); + + $dates['join_date'] = $currentMembership['join_date']; + } + + //get the status for membership. + $calcStatus = CRM_Member_BAO_MembershipStatus::getMembershipStatusByDate($dates['start_date'], + $dates['end_date'], + $dates['join_date'], + 'today', + TRUE, + $membershipParams['membership_type_id'], + $membershipParams + ); + + $membershipParams['status_id'] = CRM_Utils_Array::value('id', $calcStatus, 'New'); + //we might be renewing membership, + //so make status override false. + $membershipParams['is_override'] = FALSE; + civicrm_api3('Membership', 'create', $membershipParams); + + //update related Memberships. + CRM_Member_BAO_Membership::updateRelatedMemberships($membership->id, $membershipParams); + } + } + } + } + else { + if (empty($input['IAmAHorribleNastyBeyondExcusableHackInTheCRMEventFORMTaskClassThatNeedsToBERemoved'])) { + $eventDetail = civicrm_api3('Event', 'getsingle', array('id' => $objects['event']->id)); + $contributionParams['source'] = ts('Online Event Registration') . ': ' . $eventDetail['title']; + if ($eventDetail['is_email_confirm']) { + // @todo this should be set by the function that sends the mail after sending. + $contributionParams['receipt_date'] = $changeDate; + } + $participantParams['id'] = $participant->id; + $participantParams['status_id'] = 'Registered'; + civicrm_api3('Participant', 'create', $participantParams); + } + } + + $contributionParams['id'] = $contribution->id; + + civicrm_api3('Contribution', 'create', $contributionParams); + + // Add new soft credit against current $contribution. + if (CRM_Utils_Array::value('contributionRecur', $objects) && $objects['contributionRecur']->id) { + CRM_Contribute_BAO_ContributionRecur::addrecurSoftCredit($objects['contributionRecur']->id, $contribution->id); + } + + $paymentProcessorId = ''; + if (isset($objects['paymentProcessor'])) { + if (is_array($objects['paymentProcessor'])) { + $paymentProcessorId = $objects['paymentProcessor']['id']; + } + else { + $paymentProcessorId = $objects['paymentProcessor']->id; + } + } + + $contributionStatuses = CRM_Core_PseudoConstant::get('CRM_Contribute_DAO_Contribution', 'contribution_status_id', array( + 'labelColumn' => 'name', + 'flip' => 1, + )); + if ((empty($input['prevContribution']) && $paymentProcessorId) || (!$input['prevContribution']->is_pay_later && $input['prevContribution']->contribution_status_id == $contributionStatuses['Pending'])) { + $input['payment_processor'] = $paymentProcessorId; + } + $input['contribution_status_id'] = $contributionStatuses['Completed']; + $input['total_amount'] = $input['amount']; + $input['contribution'] = $contribution; + $input['financial_type_id'] = $contribution->financial_type_id; + + if (!empty($contribution->_relatedObjects['participant'])) { + $input['contribution_mode'] = 'participant'; + $input['participant_id'] = $contribution->_relatedObjects['participant']->id; + $input['skipLineItem'] = 1; + } + elseif (!empty($contribution->_relatedObjects['membership'])) { + $input['skipLineItem'] = TRUE; + $input['contribution_mode'] = 'membership'; + } + //@todo writing a unit test I was unable to create a scenario where this line did not fatal on second + // and subsequent payments. In this case the line items are created at + // CRM_Contribute_BAO_ContributionRecur::addRecurLineItems + // and since the contribution is saved prior to this line there is always a contribution-id, + // however there is never a prevContribution (which appears to mean original contribution not previous + // contribution - or preUpdateContributionObject most accurately) + // so, this is always called & only appears to succeed when prevContribution exists - which appears + // to mean "are we updating an exisitng pending contribution" + //I was able to make the unit test complete as fataling here doesn't prevent + // the contribution being created - but activities would not be created or emails sent + + CRM_Contribute_BAO_Contribution::recordFinancialAccounts($input, NULL); + + CRM_Core_Error::debug_log_message("Contribution record updated successfully"); + $transaction->commit(); + + CRM_Contribute_BAO_ContributionRecur::updateRecurLinkedPledge($contribution); + + // create an activity record + if ($input['component'] == 'contribute') { + //CRM-4027 + $targetContactID = NULL; + if (!empty($ids['related_contact'])) { + $targetContactID = $contribution->contact_id; + $contribution->contact_id = $ids['related_contact']; + } + CRM_Activity_BAO_Activity::addActivity($contribution, NULL, $targetContactID); + // event + } + else { + CRM_Activity_BAO_Activity::addActivity($participant); + } + + // CRM-9132 legacy behaviour was that receipts were sent out in all instances. Still sending + // when array_key 'is_email_receipt doesn't exist in case some instances where is needs setting haven't been set + if (!array_key_exists('is_email_receipt', $values) || + $values['is_email_receipt'] == 1 + ) { + self::sendMail($input, $ids, $objects['contribution'], $values, $recur, FALSE); + CRM_Core_Error::debug_log_message("Receipt sent"); + } + + CRM_Core_Error::debug_log_message("Success: Database updated"); + if ($isRecurring) { + CRM_Contribute_BAO_ContributionRecur::sendRecurringStartOrEndNotification($ids, $recur, + $isFirstOrLastRecurringPayment); + } + } + + /** + * Send receipt from contribution. + * + * Do not call this directly - it is being refactored. use contribution.sendmessage api call. + * + * Note that the compose message part has been moved to contribution + * In general LoadObjects is called first to get the objects but the composeMessageArray function now calls it. + * + * @param array $input + * Incoming data from Payment processor. + * @param array $ids + * Related object IDs. + * @param CRM_Contribute_BAO_Contribution $contribution + * @param array $values + * Values related to objects that have already been loaded. + * @param bool $recur + * Is it part of a recurring contribution. + * @param bool $returnMessageText + * Should text be returned instead of sent. This. + * is because the function is also used to generate pdfs + * + * @return array + */ + public static function sendMail(&$input, &$ids, $contribution, &$values, $recur = FALSE, $returnMessageText = FALSE) { + $input['is_recur'] = $recur; + // set receipt from e-mail and name in value + if (!$returnMessageText) { + $session = CRM_Core_Session::singleton(); + $userID = $session->get('userID'); + if (!empty($userID)) { + list($userName, $userEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($userID); + $values['receipt_from_email'] = CRM_Utils_Array::value('receipt_from_email', $input, $userEmail); + $values['receipt_from_name'] = CRM_Utils_Array::value('receipt_from_name', $input, $userName); + } + } + return $contribution->composeMessageArray($input, $ids, $values, $recur, $returnMessageText); + } + + /** + * Generate credit note id with next avaible number + * + * @param Integer $contributionId + * Contribution Id. + * + * @return string + * Credit Note Id. + */ + public static function createCreditNoteId($contributionId) { + $prefixValue = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings'); + + $query = "select count(creditnote_id) as creditnote_number from civicrm_contribution"; + $dao = CRM_Core_DAO::executeQuery($query); + $dao->fetch(); + $creditNoteNum = $dao->creditnote_number; + $creditNoteId = NULL; + + do { + $creditNoteNum++; + $creditNoteId = CRM_Utils_Array::value('credit_notes_prefix', $prefixValue) . "" . $creditNoteNum; + $result = civicrm_api3('Contribution', 'getcount', array( + 'sequential' => 1, + 'creditnote_id' => $creditNoteId, + )); + } while ($result > 0); + + CRM_Core_DAO::setFieldValue('CRM_Contribute_DAO_Contribution', $contributionId, 'creditnote_id', $creditNoteId); + return $creditNoteId; + } + }