From: Kurund Jalmi Date: Sun, 19 Jul 2015 13:09:39 +0000 (+0530) Subject: Merge remote-tracking branch 'upstream/4.6' into 4.6-master-2015-07-19-17-34-09 X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=f85b80630e8324121dcdef2d7aacd432946b541f;p=civicrm-core.git Merge remote-tracking branch 'upstream/4.6' into 4.6-master-2015-07-19-17-34-09 Conflicts: CRM/Contribute/Form/Contribution.php CRM/Contribute/Form/Contribution/Confirm.php CRM/Core/BAO/CustomQuery.php CRM/Core/BAO/Mapping.php CRM/Member/Form/Membership.php bower.json sql/civicrm_generated.mysql templates/CRM/Contribute/Form/Contribution/Main.tpl xml/version.xml --- f85b80630e8324121dcdef2d7aacd432946b541f diff --cc CRM/Contact/BAO/Relationship.php index f6f5c6bd12,c026062f0f..f931d7a546 --- a/CRM/Contact/BAO/Relationship.php +++ b/CRM/Contact/BAO/Relationship.php @@@ -1974,11 -1975,10 +1977,10 @@@ AND cc.sort_name LIKE '%$name%'" if (!empty($relationships)) { // get the total relationships if ($params['context'] != 'user') { - $params['total'] = CRM_Contact_BAO_Relationship::getRelationship($params['contact_id'], - $relationshipStatus, 0, 1, 0, NULL, NULL, $permissionedContacts); + $params['total'] = count($relationships); } else { - // FIX ME: we cannot directly determine total permissioned relationship, hence re-fire query + // FIXME: we cannot directly determine total permissioned relationship, hence re-fire query $permissionedRelationships = CRM_Contact_BAO_Relationship::getRelationship($params['contact_id'], $relationshipStatus, 0, 0, 0, diff --cc CRM/Contribute/Form/Contribution.php index f8fc18b8fb,d7b6d75375..862665413f --- a/CRM/Contribute/Form/Contribution.php +++ b/CRM/Contribute/Form/Contribution.php @@@ -962,106 -1006,549 +962,105 @@@ class CRM_Contribute_Form_Contribution )); return; } - - // Get the submitted form values. $submittedValues = $this->controller->exportValues($this->_name); - if (!empty($submittedValues['price_set_id']) && $this->_action & CRM_Core_Action::UPDATE) { - $line = CRM_Price_BAO_LineItem::getLineItems($this->_id, 'contribution'); - $lineID = key($line); - $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', CRM_Utils_Array::value('price_field_id', $line[$lineID]), 'price_set_id'); - $quickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceSetId, 'is_quick_config'); - if ($quickConfig) { - CRM_Price_BAO_LineItem::deleteLineItems($this->_id, 'civicrm_contribution'); - } - } - - // Process price set and get total amount and line items. - $lineItem = array(); - $priceSetId = CRM_Utils_Array::value('price_set_id', $submittedValues); - if (empty($priceSetId) && !$this->_id) { - $this->_priceSetId = $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_contribution_amount', 'id', 'name'); - $this->_priceSet = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId)); - $fieldID = key($this->_priceSet['fields']); - $fieldValueId = key($this->_priceSet['fields'][$fieldID]['options']); - $this->_priceSet['fields'][$fieldID]['options'][$fieldValueId]['amount'] = $submittedValues['total_amount']; - $submittedValues['price_' . $fieldID] = 1; - } - - if ($priceSetId) { - CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'], - $submittedValues, $lineItem[$priceSetId]); - // Unset tax amount for offline 'is_quick_config' contribution. - if ($this->_priceSet['is_quick_config'] && - !array_key_exists($submittedValues['financial_type_id'], CRM_Core_PseudoConstant::getTaxRates()) - ) { - unset($submittedValues['tax_amount']); - } - $submittedValues['total_amount'] = CRM_Utils_Array::value('amount', $submittedValues); + try { + // Get the submitted form values. + $contribution = $this->submit($submittedValues, $this->_action, $this->_ppID); } - if ($this->_id) { - if ($this->_compId) { - if ($this->_context == 'participant') { - $pId = $this->_compId; - } - elseif ($this->_context == 'membership') { - $isRelatedId = TRUE; - } - else { - $pId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $this->_id, 'participant_id', 'contribution_id'); - } + catch (PaymentProcessorException $e) { + // Set the contribution mode. + $urlParams = "action=add&cid={$this->_contactID}"; + if ($this->_mode) { + $urlParams .= "&mode={$this->_mode}"; } - else { - $contributionDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); - if (array_key_exists('membership', $contributionDetails)) { - $isRelatedId = TRUE; - } - elseif (array_key_exists('participant', $contributionDetails)) { - $pId = $contributionDetails['participant']; - } + if (!empty($this->_ppID)) { + $urlParams .= "&context=pledge&ppid={$this->_ppID}"; } + + CRM_Core_Error::statusBounce($e->getMessage(), $urlParams, ts('Payment Processor Error')); } - if (!$priceSetId && !empty($submittedValues['total_amount']) && $this->_id) { - // CRM-10117 update the line items for participants. - if ($pId) { - $entityTable = 'participant'; - $entityID = $pId; - $isRelatedId = FALSE; - $participantParams = array( - 'fee_amount' => $submittedValues['total_amount'], - 'id' => $entityID, - ); - CRM_Event_BAO_Participant::add($participantParams); - if (empty($this->_lineItems)) { - $this->_lineItems[] = CRM_Price_BAO_LineItem::getLineItems($entityID, 'participant', 1); - } + $session = CRM_Core_Session::singleton(); + $buttonName = $this->controller->getButtonName(); + if ($this->_context == 'standalone') { + if ($buttonName == $this->getButtonName('upload', 'new')) { + $session->replaceUserContext(CRM_Utils_System::url('civicrm/contribute/add', + 'reset=1&action=add&context=standalone' + )); } else { - $entityTable = 'contribution'; - $entityID = $this->_id; - } - - $lineItems = CRM_Price_BAO_LineItem::getLineItems($entityID, $entityTable, NULL, TRUE, $isRelatedId); - foreach (array_keys($lineItems) as $id) { - $lineItems[$id]['id'] = $id; - } - $itemId = key($lineItems); - if ($itemId && !empty($lineItems[$itemId]['price_field_id'])) { - $this->_priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id'); - } - - if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { - //CRM-16833: Ensure tax is applied only once for membership conribution, when status changed.(e.g Pending to Completed). - $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); - if (!CRM_Utils_Array::value('membership', $componentDetails) || !CRM_Utils_Array::value('participant', $componentDetails)) { - if (!($this->_action & CRM_Core_Action::UPDATE && (($this->_defaults['contribution_status_id'] != $submittedValues['contribution_status_id'])))) { - $lineItems[$itemId]['unit_price'] = $lineItems[$itemId]['line_total'] = CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value('total_amount', $submittedValues)); - } - } - // Update line total and total amount with tax on edit. - $financialItemsId = CRM_Core_PseudoConstant::getTaxRates(); - if (array_key_exists($submittedValues['financial_type_id'], $financialItemsId)) { - $lineItems[$itemId]['tax_rate'] = $financialItemsId[$submittedValues['financial_type_id']]; - } - else { - $lineItems[$itemId]['tax_rate'] = $lineItems[$itemId]['tax_amount'] = ""; - $submittedValues['tax_amount'] = 'null'; - } - if ($lineItems[$itemId]['tax_rate']) { - $lineItems[$itemId]['tax_amount'] = ($lineItems[$itemId]['tax_rate'] / 100) * $lineItems[$itemId]['line_total']; - $submittedValues['total_amount'] = $lineItems[$itemId]['line_total'] + $lineItems[$itemId]['tax_amount']; - $submittedValues['tax_amount'] = $lineItems[$itemId]['tax_amount']; - } - } - // CRM-10117 update the line items for participants. - if (!empty($lineItems[$itemId]['price_field_id'])) { - $lineItem[$this->_priceSetId] = $lineItems; + $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view', + "reset=1&cid={$this->_contactID}&selectedChild=contribute" + )); } } - - $isQuickConfig = 0; - if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { - $isQuickConfig = 1; + elseif ($this->_context == 'contribution' && $this->_mode && $buttonName == $this->getButtonName('upload', 'new')) { + $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view/contribution', + "reset=1&action=add&context={$this->_context}&cid={$this->_contactID}&mode={$this->_mode}" + )); } - //CRM-11529 for quick config back office transactions - //when financial_type_id is passed in form, update the - //line items with the financial type selected in form - if ($isQuickConfig && !empty($submittedValues['financial_type_id']) && CRM_Utils_Array::value($this->_priceSetId, $lineItem) - ) { - foreach ($lineItem[$this->_priceSetId] as &$values) { - $values['financial_type_id'] = $submittedValues['financial_type_id']; - } + elseif ($buttonName == $this->getButtonName('upload', 'new')) { + $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view/contribution', + "reset=1&action=add&context={$this->_context}&cid={$this->_contactID}" + )); } - if (!isset($submittedValues['total_amount'])) { - $submittedValues['total_amount'] = CRM_Utils_Array::value('total_amount', $this->_values); + //store contribution ID if not yet set (on create) + if (empty($this->_id) && !empty($contribution->id)) { + $this->_id = $contribution->id; } - $this->assign('lineItem', !empty($lineItem) && !$isQuickConfig ? $lineItem : FALSE); + } - if (!empty($submittedValues['pcp_made_through_id'])) { - $pcp = array(); - $fields = array( - 'pcp_made_through_id', - 'pcp_display_in_roll', - 'pcp_roll_nickname', - 'pcp_personal_note', - ); - foreach ($fields as $f) { - $pcp[$f] = CRM_Utils_Array::value($f, $submittedValues); - } + /** + * Process credit card payment. + * + * @param array $submittedValues + * @param array $lineItem + * + * @param int $contactID + * Contact ID + * + * @return bool|\CRM_Contribute_DAO_Contribution + * @throws \CiviCRM_API3_Exception + * @throws \Civi\Payment\Exception\PaymentProcessorException + */ + protected function processCreditCard($submittedValues, $lineItem, $contactID) { - + $isTest = ($this->_mode == 'test') ? 1 : 0; + // CRM-12680 set $_lineItem if its not set + // @todo - I don't believe this would ever BE set. I can't find anywhere in the code. + // It would be better to pass line item out to functions than $this->_lineItem as + // we don't know what is being changed where. + if (empty($this->_lineItem) && !empty($lineItem)) { + $this->_lineItem = $lineItem; } - $isEmpty = array_keys(array_flip($submittedValues['soft_credit_contact_id'])); - if ($this->_id && count($isEmpty) == 1 && key($isEmpty) == NULL) { - //Delete existing soft credit records if soft credit list is empty on update - CRM_Contribute_BAO_ContributionSoft::del(array('contribution_id' => $this->_id, 'pcp_id' => 0)); - } - else { - //build soft credit params - foreach ($submittedValues['soft_credit_contact_id'] as $key => $val) { - if ($val && $submittedValues['soft_credit_amount'][$key]) { - $softParams[$key]['contact_id'] = $val; - $softParams[$key]['amount'] = CRM_Utils_Rule::cleanMoney($submittedValues['soft_credit_amount'][$key]); - $softParams[$key]['soft_credit_type_id'] = $submittedValues['soft_credit_type'][$key]; - if (!empty($submittedValues['soft_credit_id'][$key])) { - $softIDs[] = $softParams[$key]['id'] = $submittedValues['soft_credit_id'][$key]; - } - } - } - } + $this->_paymentObject = Civi\Payment\System::singleton()->getById($submittedValues['payment_processor_id']); + $this->_paymentProcessor = $this->_paymentObject->getPaymentProcessor(); - // set the contact, when contact is selected - if (!empty($submittedValues['contact_id'])) { - $this->_contactID = $submittedValues['contact_id']; + // Set source if not set + if (empty($submittedValues['source'])) { + $userID = CRM_Core_Session::singleton()->get('userID'); + $userSortName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $userID, + 'sort_name' + ); + $submittedValues['source'] = ts('Submit Credit Card Payment by: %1', array(1 => $userSortName)); } - // Credit Card Contribution. - if ($this->_mode) { - $this->processCreditCard($submittedValues, $lineItem); - } - else { - // Offline Contribution. - $submittedValues = $this->unsetCreditCardFields($submittedValues); + $params = $this->_params = $submittedValues; - // get the required field value only. - $formValues = $submittedValues; - $params = $ids = array(); + // Mapping requiring documentation. + $this->_params['payment_processor'] = $submittedValues['payment_processor_id']; - $params['contact_id'] = $this->_contactID; + $now = date('YmdHis'); + $fields = array(); - $params['currency'] = $this->getCurrency($submittedValues); - - $fields = array( - 'financial_type_id', - 'contribution_status_id', - 'payment_instrument_id', - 'cancel_reason', - 'source', - 'check_number', - ); - foreach ($fields as $f) { - $params[$f] = CRM_Utils_Array::value($f, $formValues); - } - - $params['pcp'] = !empty($pcp) ? $pcp : NULL; - $params['soft_credit'] = !empty($softParams) ? $softParams : array(); - $params['soft_credit_ids'] = !empty($softIDs) ? $softIDs : array(); - - // CRM-5740 if priceset is used, no need to cleanup money. - if ($priceSetId) { - $params['skipCleanMoney'] = 1; - } - - $dates = array( - 'receive_date', - 'receipt_date', - 'cancel_date', - ); - - foreach ($dates as $d) { - $params[$d] = CRM_Utils_Date::processDate($formValues[$d], $formValues[$d . '_time'], TRUE); - } - - if (!empty($formValues['is_email_receipt'])) { - $params['receipt_date'] = date("Y-m-d"); - } - - if ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Cancelled', 'name') - || $params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Refunded', 'name') - ) { - if (CRM_Utils_System::isNull(CRM_Utils_Array::value('cancel_date', $params))) { - $params['cancel_date'] = date('Y-m-d'); - } - } - else { - $params['cancel_date'] = $params['cancel_reason'] = 'null'; - } - - // Set is_pay_later flag for back-office offline Pending status contributions CRM-8996 - // else if contribution_status is changed to Completed is_pay_later flag is changed to 0, CRM-15041 - if ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name')) { - $params['is_pay_later'] = 1; - } - elseif ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name')) { - $params['is_pay_later'] = 0; - } - - $ids['contribution'] = $params['id'] = $this->_id; - - // Add Additional common information to formatted params. - CRM_Contribute_Form_AdditionalInfo::postProcessCommon($formValues, $params, $this); - if ($pId) { - $params['contribution_mode'] = 'participant'; - $params['participant_id'] = $pId; - $params['skipLineItem'] = 1; - } - elseif ($isRelatedId) { - $params['contribution_mode'] = 'membership'; - } - $params['line_item'] = $lineItem; - $params['payment_processor_id'] = $params['payment_processor'] = CRM_Utils_Array::value('id', $this->_paymentProcessor); - if (isset($submittedValues['tax_amount'])) { - $params['tax_amount'] = $submittedValues['tax_amount']; - } - //create contribution. - if ($isQuickConfig) { - $params['is_quick_config'] = 1; - } - - // CRM-11956 - // if non_deductible_amount exists i.e. Additional Details field set was opened [and staff typed something] - - // if non_deductible_amount does NOT exist - then calculate it depending on: - // $ContributionType->is_deductible and whether there is a product (premium). - if (empty($params['non_deductible_amount'])) { - $contributionType = new CRM_Financial_DAO_FinancialType(); - $contributionType->id = $params['financial_type_id']; - if (!$contributionType->find(TRUE)) { - CRM_Core_Error::fatal('Could not find a system table'); - } - if ($contributionType->is_deductible) { - - if (isset($formValues['product_name'][0])) { - $selectProduct = $formValues['product_name'][0]; - } - // if there is a product - compare the value to the contribution amount - if (isset($selectProduct)) { - $productDAO = new CRM_Contribute_DAO_Product(); - $productDAO->id = $selectProduct; - $productDAO->find(TRUE); - // product value exceeds contribution amount - if ($params['total_amount'] < $productDAO->price) { - $params['non_deductible_amount'] = $params['total_amount']; - } - // product value does NOT exceed contribution amount - else { - $params['non_deductible_amount'] = $productDAO->price; - } - } - // contribution is deductible - but there is no product - else { - $params['non_deductible_amount'] = '0.00'; - } - } - // contribution is NOT deductible - else { - $params['non_deductible_amount'] = $params['total_amount']; - } - } - - $contribution = CRM_Contribute_BAO_Contribution::create($params, $ids); - - // process associated membership / participant, CRM-4395 - $relatedComponentStatusMsg = NULL; - if ($contribution->id && $this->_action & CRM_Core_Action::UPDATE) { - $relatedComponentStatusMsg = $this->updateRelatedComponent($contribution->id, - $contribution->contribution_status_id, - CRM_Utils_Array::value('contribution_status_id', - $this->_values - ), - $contribution->receive_date - ); - } - - //process note - if ($contribution->id && isset($formValues['note'])) { - CRM_Contribute_Form_AdditionalInfo::processNote($formValues, $this->_contactID, $contribution->id, $this->_noteID); - } - - //process premium - if ($contribution->id && isset($formValues['product_name'][0])) { - CRM_Contribute_Form_AdditionalInfo::processPremium($formValues, $contribution->id, - $this->_premiumID, $this->_options - ); - } - - // assign tax calculation for contribution receipts - $taxRate = array(); - $getTaxDetails = FALSE; - $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings'); - $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings); - if ($invoicing) { - if ($this->_action & CRM_Core_Action::ADD) { - $line = $lineItem; - } - elseif ($this->_action & CRM_Core_Action::UPDATE) { - $line = $this->_lineItems; - } - foreach ($line as $key => $value) { - foreach ($value as $v) { - if (isset($taxRate[(string) CRM_Utils_Array::value('tax_rate', $v)])) { - $taxRate[(string) $v['tax_rate']] = $taxRate[(string) $v['tax_rate']] + CRM_Utils_Array::value('tax_amount', $v); - } - else { - if (isset($v['tax_rate'])) { - $taxRate[(string) $v['tax_rate']] = CRM_Utils_Array::value('tax_amount', $v); - $getTaxDetails = TRUE; - } - } - } - } - } - - if ($invoicing) { - if ($this->_action & CRM_Core_Action::UPDATE) { - if (isset($submittedValues['tax_amount'])) { - $totalTaxAmount = $submittedValues['tax_amount']; - } - else { - $totalTaxAmount = $this->_values['tax_amount']; - } - $this->assign('totalTaxAmount', $totalTaxAmount); - $this->assign('dataArray', $taxRate); - } - else { - if (!empty($submittedValues['price_set_id'])) { - $this->assign('totalTaxAmount', $submittedValues['tax_amount']); - $this->assign('getTaxDetails', $getTaxDetails); - $this->assign('dataArray', $taxRate); - $this->assign('taxTerm', CRM_Utils_Array::value('tax_term', $invoiceSettings)); - } - else { - $this->assign('totalTaxAmount', CRM_Utils_Array::value('tax_amount', $submittedValues)); - } - } - } - - //send receipt mail. - if ($contribution->id && !empty($formValues['is_email_receipt'])) { - $formValues['contact_id'] = $this->_contactID; - $formValues['contribution_id'] = $contribution->id; - - $formValues += CRM_Contribute_BAO_ContributionSoft::getSoftContribution($contribution->id); - - // to get 'from email id' for send receipt - $this->fromEmailId = $formValues['from_email_address']; - $sendReceipt = CRM_Contribute_Form_AdditionalInfo::emailReceipt($this, $formValues); - } - - $pledgePaymentId = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', - $contribution->id, - 'id', - 'contribution_id' - ); - - //update pledge payment status. - if ((($this->_ppID && $contribution->id) && $this->_action & CRM_Core_Action::ADD) || - (($pledgePaymentId) && $this->_action & CRM_Core_Action::UPDATE) - ) { - - if ($this->_ppID) { - //store contribution id in payment record. - CRM_Core_DAO::setFieldValue('CRM_Pledge_DAO_PledgePayment', $this->_ppID, 'contribution_id', $contribution->id); - } - else { - $this->_ppID = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', - $contribution->id, - 'id', - 'contribution_id' - ); - $this->_pledgeID = CRM_Core_DAO::getFieldValue('CRM_Pledge_DAO_PledgePayment', - $contribution->id, - 'pledge_id', - 'contribution_id' - ); - } - - $adjustTotalAmount = FALSE; - if (CRM_Utils_Array::value('option_type', $formValues) == 2) { - $adjustTotalAmount = TRUE; - } - - $updatePledgePaymentStatus = FALSE; - //do only if either the status or the amount has changed - if ($this->_action & CRM_Core_Action::ADD) { - $updatePledgePaymentStatus = TRUE; - } - elseif ($this->_action & CRM_Core_Action::UPDATE && (($this->_defaults['contribution_status_id'] != $formValues['contribution_status_id']) || - ($this->_defaults['total_amount'] != $formValues['total_amount'])) - ) { - $updatePledgePaymentStatus = TRUE; - } - - if ($updatePledgePaymentStatus) { - CRM_Pledge_BAO_PledgePayment::updatePledgePaymentStatus($this->_pledgeID, - array($this->_ppID), - $contribution->contribution_status_id, - NULL, - $contribution->total_amount, - $adjustTotalAmount - ); - } - } - - $statusMsg = ts('The contribution record has been saved.'); - if (!empty($formValues['is_email_receipt']) && $sendReceipt) { - $statusMsg .= ' ' . ts('A receipt has been emailed to the contributor.'); - } - - if ($relatedComponentStatusMsg) { - $statusMsg .= ' ' . $relatedComponentStatusMsg; - } - - CRM_Core_Session::setStatus($statusMsg, ts('Saved'), 'success'); - //Offline Contribution ends. - } - $session = CRM_Core_Session::singleton(); - $buttonName = $this->controller->getButtonName(); - if ($this->_context == 'standalone') { - if ($buttonName == $this->getButtonName('upload', 'new')) { - $session->replaceUserContext(CRM_Utils_System::url('civicrm/contribute/add', - 'reset=1&action=add&context=standalone' - )); - } - else { - $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view', - "reset=1&cid={$this->_contactID}&selectedChild=contribute" - )); - } - } - elseif ($this->_context == 'contribution' && $this->_mode && $buttonName == $this->getButtonName('upload', 'new')) { - $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view/contribution', - "reset=1&action=add&context={$this->_context}&cid={$this->_contactID}&mode={$this->_mode}" - )); - } - elseif ($buttonName == $this->getButtonName('upload', 'new')) { - $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view/contribution', - "reset=1&action=add&context={$this->_context}&cid={$this->_contactID}" - )); - } - - //store contribution ID if not yet set (on create) - if (empty($this->_id) && !empty($contribution->id)) { - $this->_id = $contribution->id; - } - } - - /** - * Process credit card payment. - * - * @param array $submittedValues - * @param array $lineItem - * - * @throws CRM_Core_Exception - */ - protected function processCreditCard($submittedValues, $lineItem) { - $sendReceipt = $contribution = FALSE; - - $unsetParams = array( - 'trxn_id', - 'payment_instrument_id', - 'contribution_status_id', - 'cancel_date', - 'cancel_reason', - ); - foreach ($unsetParams as $key) { - if (isset($submittedValues[$key])) { - unset($submittedValues[$key]); - } - } - $isTest = ($this->_mode == 'test') ? 1 : 0; - // CRM-12680 set $_lineItem if its not set - if (empty($this->_lineItem) && !empty($lineItem)) { - $this->_lineItem = $lineItem; - } - - //Get the require fields value only. - $params = $this->_params = $submittedValues; - - $this->_paymentProcessor = CRM_Financial_BAO_PaymentProcessor::getPayment($this->_params['payment_processor_id'], - $this->_mode - ); - - // Get the payment processor id as per mode. - $this->_params['payment_processor'] = $params['payment_processor_id'] - = $this->_params['payment_processor_id'] = $submittedValues['payment_processor_id'] = $this->_paymentProcessor['id']; - - $now = date('YmdHis'); - $fields = array(); - - // we need to retrieve email address - if ($this->_context == 'standalone' && !empty($submittedValues['is_email_receipt'])) { - list($this->userDisplayName, - $this->userEmail - ) = CRM_Contact_BAO_Contact_Location::getEmailDetails($this->_contactID); - $this->assign('displayName', $this->userDisplayName); - } + // we need to retrieve email address + if ($this->_context == 'standalone' && !empty($submittedValues['is_email_receipt'])) { + list($this->userDisplayName, + $this->userEmail + ) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactID); + $this->assign('displayName', $this->userDisplayName); + } // Set email for primary location. $fields['email-Primary'] = 1; @@@ -1311,521 -1881,4 +1310,527 @@@ } } + /** + * Wrapper for unit testing the post process submit function. + * + * (If we expose through api we can get default additions 'for free'). + * + * @param array $params + * @param int $action + * @param string|null $creditCardMode + * + * @throws \CiviCRM_API3_Exception + */ + public function testSubmit($params, $action, $creditCardMode = NULL) { + $defaults = array( + 'soft_credit_contact_id' => array(), + 'receipt_date' => '', + 'receipt_date_time' => '', + 'cancel_date' => '', + 'cancel_date_time' => '', + 'hidden_Premium' => 1, + ); + $this->_bltID = 5; + if (!empty($params['id'])) { + $existingContribution = civicrm_api3('contribution', 'getsingle', array( + 'id' => $params['id'], + )); + $this->_id = $params['id']; + } + else { + $existingContribution = array(); + } + + $this->_defaults['contribution_status_id'] = CRM_Utils_Array::value('contribution_status_id', + $existingContribution + ); + + $this->_defaults['total_amount'] = CRM_Utils_Array::value('total_amount', + $existingContribution + ); + + if ($creditCardMode) { + $this->_mode = $creditCardMode; + } + + // Required because processCreditCard calls set method on this. + $_SERVER['REQUEST_METHOD'] = 'GET'; + $this->controller = new CRM_Core_Controller(); + + CRM_Contribute_Form_AdditionalInfo::buildPremium($this); + + $this->_fields = array(); + $this->submit(array_merge($defaults, $params), $action, CRM_Utils_Array::value('pledge_payment_id', $params)); + + } + + /** + * @param array $submittedValues + * + * @param int $action + * Action constant + * - CRM_Core_Action::UPDATE + * + * @param $pledgePaymentID + * + * @return array + * @throws \Exception + */ + protected function submit($submittedValues, $action, $pledgePaymentID) { + $softParams = $softIDs = array(); + $pId = $contribution = $isRelatedId = FALSE; + + if (!empty($submittedValues['price_set_id']) && $action & CRM_Core_Action::UPDATE) { + $line = CRM_Price_BAO_LineItem::getLineItems($this->_id, 'contribution'); + $lineID = key($line); + $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', CRM_Utils_Array::value('price_field_id', $line[$lineID]), 'price_set_id'); + $quickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceSetId, 'is_quick_config'); + if ($quickConfig) { + CRM_Price_BAO_LineItem::deleteLineItems($this->_id, 'civicrm_contribution'); + } + } + + // Process price set and get total amount and line items. + $lineItem = array(); + $priceSetId = CRM_Utils_Array::value('price_set_id', $submittedValues); + if (empty($priceSetId) && !$this->_id) { + $this->_priceSetId = $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', 'default_contribution_amount', 'id', 'name'); + $this->_priceSet = current(CRM_Price_BAO_PriceSet::getSetDetail($priceSetId)); + $fieldID = key($this->_priceSet['fields']); + $fieldValueId = key($this->_priceSet['fields'][$fieldID]['options']); + $this->_priceSet['fields'][$fieldID]['options'][$fieldValueId]['amount'] = $submittedValues['total_amount']; + $submittedValues['price_' . $fieldID] = 1; + } + + if ($priceSetId) { + CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'], + $submittedValues, $lineItem[$priceSetId]); + + // Unset tax amount for offline 'is_quick_config' contribution. + if ($this->_priceSet['is_quick_config'] && + !array_key_exists($submittedValues['financial_type_id'], CRM_Core_PseudoConstant::getTaxRates()) + ) { + unset($submittedValues['tax_amount']); + } + $submittedValues['total_amount'] = CRM_Utils_Array::value('amount', $submittedValues); + } + if ($this->_id) { + if ($this->_compId) { + if ($this->_context == 'participant') { + $pId = $this->_compId; + } + elseif ($this->_context == 'membership') { + $isRelatedId = TRUE; + } + else { + $pId = CRM_Core_DAO::getFieldValue('CRM_Event_DAO_ParticipantPayment', $this->_id, 'participant_id', 'contribution_id'); + } + } + else { + $contributionDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); + if (array_key_exists('membership', $contributionDetails)) { + $isRelatedId = TRUE; + } + elseif (array_key_exists('participant', $contributionDetails)) { + $pId = $contributionDetails['participant']; + } + } + } + if (!$priceSetId && !empty($submittedValues['total_amount']) && $this->_id) { + // CRM-10117 update the line items for participants. + if ($pId) { + $entityTable = 'participant'; + $entityID = $pId; + $isRelatedId = FALSE; + $participantParams = array( + 'fee_amount' => $submittedValues['total_amount'], + 'id' => $entityID, + ); + CRM_Event_BAO_Participant::add($participantParams); + if (empty($this->_lineItems)) { + $this->_lineItems[] = CRM_Price_BAO_LineItem::getLineItems($entityID, 'participant', 1); + } + } + else { + $entityTable = 'contribution'; + $entityID = $this->_id; + } + + $lineItems = CRM_Price_BAO_LineItem::getLineItems($entityID, $entityTable, NULL, TRUE, $isRelatedId); + foreach (array_keys($lineItems) as $id) { + $lineItems[$id]['id'] = $id; + } + $itemId = key($lineItems); + if ($itemId && !empty($lineItems[$itemId]['price_field_id'])) { + $this->_priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id'); + } + + if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { - $lineItems[$itemId]['unit_price'] = $lineItems[$itemId]['line_total'] = CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value('total_amount', $submittedValues)); ++ //CRM-16833: Ensure tax is applied only once for membership conribution, when status changed.(e.g Pending to Completed). ++ $componentDetails = CRM_Contribute_BAO_Contribution::getComponentDetails($this->_id); ++ if (!CRM_Utils_Array::value('membership', $componentDetails) || !CRM_Utils_Array::value('participant', $componentDetails)) { ++ if (!($this->_action & CRM_Core_Action::UPDATE && (($this->_defaults['contribution_status_id'] != $submittedValues['contribution_status_id'])))) { ++ $lineItems[$itemId]['unit_price'] = $lineItems[$itemId]['line_total'] = CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value('total_amount', $submittedValues)); ++ } ++ } + + // Update line total and total amount with tax on edit. + $financialItemsId = CRM_Core_PseudoConstant::getTaxRates(); + if (array_key_exists($submittedValues['financial_type_id'], $financialItemsId)) { + $lineItems[$itemId]['tax_rate'] = $financialItemsId[$submittedValues['financial_type_id']]; + } + else { + $lineItems[$itemId]['tax_rate'] = $lineItems[$itemId]['tax_amount'] = ""; + $submittedValues['tax_amount'] = 'null'; + } + if ($lineItems[$itemId]['tax_rate']) { + $lineItems[$itemId]['tax_amount'] = ($lineItems[$itemId]['tax_rate'] / 100) * $lineItems[$itemId]['line_total']; + $submittedValues['total_amount'] = $lineItems[$itemId]['line_total'] + $lineItems[$itemId]['tax_amount']; + $submittedValues['tax_amount'] = $lineItems[$itemId]['tax_amount']; + } + } + // CRM-10117 update the line items for participants. + if (!empty($lineItems[$itemId]['price_field_id'])) { + $lineItem[$this->_priceSetId] = $lineItems; + } + } + + $isQuickConfig = 0; + if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) { + $isQuickConfig = 1; + } + //CRM-11529 for quick config back office transactions + //when financial_type_id is passed in form, update the + //line items with the financial type selected in form + if ($isQuickConfig && !empty($submittedValues['financial_type_id']) && CRM_Utils_Array::value($this->_priceSetId, $lineItem) + ) { + foreach ($lineItem[$this->_priceSetId] as &$values) { + $values['financial_type_id'] = $submittedValues['financial_type_id']; + } + } + + if (!isset($submittedValues['total_amount'])) { + $submittedValues['total_amount'] = CRM_Utils_Array::value('total_amount', $this->_values); + } + $this->assign('lineItem', !empty($lineItem) && !$isQuickConfig ? $lineItem : FALSE); + + if (!empty($submittedValues['pcp_made_through_id'])) { + $pcp = array(); + $fields = array( + 'pcp_made_through_id', + 'pcp_display_in_roll', + 'pcp_roll_nickname', + 'pcp_personal_note', + ); + foreach ($fields as $f) { + $pcp[$f] = CRM_Utils_Array::value($f, $submittedValues); + } + } + + $isEmpty = array_keys(array_flip($submittedValues['soft_credit_contact_id'])); + if ($this->_id && count($isEmpty) == 1 && key($isEmpty) == NULL) { + //Delete existing soft credit records if soft credit list is empty on update - CRM_Contribute_BAO_ContributionSoft::del(array('contribution_id' => $this->_id)); ++ CRM_Contribute_BAO_ContributionSoft::del(array('contribution_id' => $this->_id, 'pcp_id' => 0)); + } + else { + //build soft credit params + foreach ($submittedValues['soft_credit_contact_id'] as $key => $val) { + if ($val && $submittedValues['soft_credit_amount'][$key]) { + $softParams[$key]['contact_id'] = $val; + $softParams[$key]['amount'] = CRM_Utils_Rule::cleanMoney($submittedValues['soft_credit_amount'][$key]); + $softParams[$key]['soft_credit_type_id'] = $submittedValues['soft_credit_type'][$key]; + if (!empty($submittedValues['soft_credit_id'][$key])) { + $softIDs[] = $softParams[$key]['id'] = $submittedValues['soft_credit_id'][$key]; + } + } + } + } + + // set the contact, when contact is selected + if (!empty($submittedValues['contact_id'])) { + $this->_contactID = $submittedValues['contact_id']; + } + $formValues = $submittedValues; + + // Credit Card Contribution. + if ($this->_mode) { + $paramsSetByPaymentProcessingSubsystem = array( + 'trxn_id', + 'payment_instrument_id', + 'contribution_status_id', + 'cancel_date', + 'cancel_reason', + ); + foreach ($paramsSetByPaymentProcessingSubsystem as $key) { + if (isset($formValues[$key])) { + unset($formValues[$key]); + } + } + $contribution = $this->processCreditCard($formValues, $lineItem, $this->_contactID); + foreach ($paramsSetByPaymentProcessingSubsystem as $key) { + $formValues[$key] = $contribution->$key; + } + } + else { + // Offline Contribution. + $submittedValues = $this->unsetCreditCardFields($submittedValues); + + // get the required field value only. + + $params = $ids = array(); + + $params['contact_id'] = $this->_contactID; + $params['currency'] = $this->getCurrency($submittedValues); + + $fields = array( + 'financial_type_id', + 'contribution_status_id', + 'payment_instrument_id', + 'cancel_reason', + 'source', + 'check_number', + ); + foreach ($fields as $f) { + $params[$f] = CRM_Utils_Array::value($f, $formValues); + } + + if (!empty($pcp)) { + $params['pcp'] = $pcp; + } + $params['soft_credit'] = !empty($softParams) ? $softParams : array(); + $params['soft_credit_ids'] = !empty($softIDs) ? $softIDs : array(); + + // CRM-5740 if priceset is used, no need to cleanup money. + if ($priceSetId) { + $params['skipCleanMoney'] = 1; + } + + $dates = array( + 'receive_date', + 'receipt_date', + 'cancel_date', + ); + + foreach ($dates as $d) { + $params[$d] = CRM_Utils_Date::processDate($formValues[$d], $formValues[$d . '_time'], TRUE); + } + + if (!empty($formValues['is_email_receipt'])) { + $params['receipt_date'] = date("Y-m-d"); + } + + if ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Cancelled', 'name') + || $params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Refunded', 'name') + ) { + if (CRM_Utils_System::isNull(CRM_Utils_Array::value('cancel_date', $params))) { + $params['cancel_date'] = date('Y-m-d'); + } + } + else { + $params['cancel_date'] = $params['cancel_reason'] = 'null'; + } + + // Set is_pay_later flag for back-office offline Pending status contributions CRM-8996 + // else if contribution_status is changed to Completed is_pay_later flag is changed to 0, CRM-15041 + if ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name')) { + $params['is_pay_later'] = 1; + } + elseif ($params['contribution_status_id'] == CRM_Core_OptionGroup::getValue('contribution_status', 'Completed', 'name')) { + $params['is_pay_later'] = 0; + } + + $ids['contribution'] = $params['id'] = $this->_id; + + // Add Additional common information to formatted params. + CRM_Contribute_Form_AdditionalInfo::postProcessCommon($formValues, $params, $this); + if ($pId) { + $params['contribution_mode'] = 'participant'; + $params['participant_id'] = $pId; + $params['skipLineItem'] = 1; + } + elseif ($isRelatedId) { + $params['contribution_mode'] = 'membership'; + } + $params['line_item'] = $lineItem; + $params['payment_processor_id'] = $params['payment_processor'] = CRM_Utils_Array::value('id', $this->_paymentProcessor); + if (isset($submittedValues['tax_amount'])) { + $params['tax_amount'] = $submittedValues['tax_amount']; + } + //create contribution. + if ($isQuickConfig) { + $params['is_quick_config'] = 1; + } + $params['non_deductible_amount'] = $this->calculateNonDeductibleAmount($params, $formValues); + + $contribution = CRM_Contribute_BAO_Contribution::create($params, $ids); + + // process associated membership / participant, CRM-4395 + if ($contribution->id && $action & CRM_Core_Action::UPDATE) { + $this->statusMessage[] = $this->updateRelatedComponent($contribution->id, + $contribution->contribution_status_id, + CRM_Utils_Array::value('contribution_status_id', + $this->_values + ), + $contribution->receive_date + ); + } + + array_unshift($this->statusMessage, ts('The contribution record has been saved.')); + + $this->invoicingPostProcessHook($submittedValues, $action, $lineItem); + + //send receipt mail. + if ($contribution->id && !empty($formValues['is_email_receipt'])) { + $formValues['contact_id'] = $this->_contactID; + $formValues['contribution_id'] = $contribution->id; + + $formValues += CRM_Contribute_BAO_ContributionSoft::getSoftContribution($contribution->id); + + // to get 'from email id' for send receipt + $this->fromEmailId = $formValues['from_email_address']; + if (CRM_Contribute_Form_AdditionalInfo::emailReceipt($this, $formValues)) { + $this->statusMessage[] = ts('A receipt has been emailed to the contributor.'); + } + } + + $this->statusMessageTitle = ts('Saved'); + + } + + if ($contribution->id && !empty($formValues['product_name'][0])) { + CRM_Contribute_Form_AdditionalInfo::processPremium($submittedValues, $contribution->id, + $this->_premiumID, $this->_options + ); + } + + if ($contribution->id && isset($submittedValues['note'])) { + CRM_Contribute_Form_AdditionalInfo::processNote($submittedValues, $this->_contactID, $contribution->id, $this->_noteID); + } + + CRM_Core_Session::setStatus(implode(' ', $this->statusMessage), $this->statusMessageTitle, 'success'); + + CRM_Contribute_BAO_Contribution::updateRelatedPledge( + $action, + $pledgePaymentID, + $contribution->id, + (CRM_Utils_Array::value('option_type', $formValues) == 2) ? TRUE : FALSE, + $formValues['total_amount'], + CRM_Utils_Array::value('total_amount', $this->_defaults), + $formValues['contribution_status_id'], + CRM_Utils_Array::value('contribution_status_id', $this->_defaults) + ); + return $contribution; + } + + /** + * Assign tax calculations to contribution receipts. + * + * @param array $submittedValues + * @param int $action + * @param array $lineItem + */ + protected function invoicingPostProcessHook($submittedValues, $action, $lineItem) { + + $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings'); + if (!CRM_Utils_Array::value('invoicing', $invoiceSettings)) { + return; + } + $taxRate = array(); + $getTaxDetails = FALSE; + if ($action & CRM_Core_Action::ADD) { + $line = $lineItem; + } + elseif ($action & CRM_Core_Action::UPDATE) { + $line = $this->_lineItems; + } + foreach ($line as $key => $value) { + foreach ($value as $v) { + if (isset($taxRate[(string) CRM_Utils_Array::value('tax_rate', $v)])) { + $taxRate[(string) $v['tax_rate']] = $taxRate[(string) $v['tax_rate']] + CRM_Utils_Array::value('tax_amount', $v); + } + else { + if (isset($v['tax_rate'])) { + $taxRate[(string) $v['tax_rate']] = CRM_Utils_Array::value('tax_amount', $v); + $getTaxDetails = TRUE; + } + } + } + } + + if ($action & CRM_Core_Action::UPDATE) { + if (isset($submittedValues['tax_amount'])) { + $totalTaxAmount = $submittedValues['tax_amount']; + } + else { + $totalTaxAmount = $this->_values['tax_amount']; + } + $this->assign('totalTaxAmount', $totalTaxAmount); + $this->assign('dataArray', $taxRate); + } + else { + if (!empty($submittedValues['price_set_id'])) { + $this->assign('totalTaxAmount', $submittedValues['tax_amount']); + $this->assign('getTaxDetails', $getTaxDetails); + $this->assign('dataArray', $taxRate); + $this->assign('taxTerm', CRM_Utils_Array::value('tax_term', $invoiceSettings)); + } + else { + $this->assign('totalTaxAmount', CRM_Utils_Array::value('tax_amount', $submittedValues)); + } + } + } + + /** + * Calculate non deductible amount. + * + * CRM-11956 + * if non_deductible_amount exists i.e. Additional Details field set was opened [and staff typed something] - + * if non_deductible_amount does NOT exist - then calculate it depending on: + * $financialType->is_deductible and whether there is a product (premium). + * + * @param $params + * @param $formValues + * + * @return array + */ + protected function calculateNonDeductibleAmount($params, $formValues) { + if (!empty($params['non_deductible_amount'])) { + return $params['non_deductible_amount']; + } + + $financialType = new CRM_Financial_DAO_FinancialType(); + $financialType->id = $params['financial_type_id']; + + if ($financialType->is_deductible) { + + if (isset($formValues['product_name'][0])) { + $selectProduct = $formValues['product_name'][0]; + } + // if there is a product - compare the value to the contribution amount + if (isset($selectProduct)) { + $productDAO = new CRM_Contribute_DAO_Product(); + $productDAO->id = $selectProduct; + $productDAO->find(TRUE); + // product value exceeds contribution amount + if ($params['total_amount'] < $productDAO->price) { + return $params['total_amount']; + } + // product value does NOT exceed contribution amount + else { + return $productDAO->price; + } + } + // contribution is deductible - but there is no product + else { + return '0.00'; + } + } + // contribution is NOT deductible + else { + return $params['total_amount']; + } + + return 0; + } + } diff --cc CRM/Contribute/Form/Contribution/Confirm.php index 8a5a1b9d6a,69856982ea..9cc6b49d4f --- a/CRM/Contribute/Form/Contribution/Confirm.php +++ b/CRM/Contribute/Form/Contribution/Confirm.php @@@ -1507,879 -1868,139 +1507,881 @@@ class CRM_Contribute_Form_Contribution_ } } - return $params; - } + return $params; + } + + /** + * Process membership. + * + * @param array $membershipParams + * @param int $contactID + * @param array $customFieldsFormatted + * @param array $fieldTypes + * @param array $premiumParams + * @param array $membershipLineItems + * Line items specifically relating to memberships. + * @param bool $isPayLater + */ + public function processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams, $membershipLineItems, $isPayLater) { + try { + $membershipTypeIDs = (array) $membershipParams['selectMembership']; + $membershipTypes = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIDs); + $membershipType = empty($membershipTypes) ? array() : reset($membershipTypes); + $isPending = $this->getIsPending(); + + $this->assign('membership_name', CRM_Utils_Array::value('name', $membershipType)); + + $isPaidMembership = FALSE; + if ($this->_amount >= 0.0 && isset($membershipParams['amount'])) { + //amount must be greater than zero for + //adding contribution record to contribution table. + //this condition arises when separate membership payment is + //enabled and contribution amount is not selected. fix for CRM-3010 + $isPaidMembership = TRUE; + } + $isProcessSeparateMembershipTransaction = $this->isSeparateMembershipTransaction($this->_id, $this->_values['amount_block_is_active']); + + if ($this->_values['amount_block_is_active']) { + $financialTypeID = $this->_values['financial_type_id']; + } + else { + $financialTypeID = CRM_Utils_Array::value('financial_type_id', $membershipType, CRM_Utils_Array::value('financial_type_id', $membershipParams)); + } + + if (CRM_Utils_Array::value('membership_source', $this->_params)) { + $membershipParams['contribution_source'] = $this->_params['membership_source']; + } + + $this->postProcessMembership($membershipParams, $contactID, + $this, $premiumParams, $customFieldsFormatted, $fieldTypes, $membershipType, $membershipTypeIDs, $isPaidMembership, $this->_membershipId, $isProcessSeparateMembershipTransaction, $financialTypeID, + $membershipLineItems, $isPayLater, $isPending); + + $this->assign('membership_assign', TRUE); + $this->set('membershipTypeID', $membershipParams['selectMembership']); + } + catch (CRM_Core_Exception $e) { + CRM_Core_Session::singleton()->setStatus($e->getMessage()); + CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=true&qfKey={$this->_params['qfKey']}")); + } + } + + /** + * Process the Memberships. + * + * @param array $membershipParams + * Array of membership fields. + * @param int $contactID + * Contact id. + * @param CRM_Contribute_Form_Contribution_Confirm $form + * Confirmation form object. + * + * @param array $premiumParams + * @param null $customFieldsFormatted + * @param null $includeFieldTypes + * + * @param array $membershipDetails + * + * @param array $membershipTypeIDs + * + * @param bool $isPaidMembership + * @param array $membershipID + * + * @param bool $isProcessSeparateMembershipTransaction + * + * @param int $financialTypeID + * @param array $membershipLineItems + * Line items specific to membership payment that is separate to contribution. + * @param bool $isPayLater + * @param bool $isPending + * + * @throws \CRM_Core_Exception + */ + protected function postProcessMembership( + $membershipParams, $contactID, &$form, $premiumParams, + $customFieldsFormatted = NULL, $includeFieldTypes = NULL, $membershipDetails, $membershipTypeIDs, $isPaidMembership, $membershipID, + $isProcessSeparateMembershipTransaction, $financialTypeID, $membershipLineItems, $isPayLater, $isPending) { + $membershipContribution = NULL; + $isTest = CRM_Utils_Array::value('is_test', $membershipParams, FALSE); + $errors = $createdMemberships = $paymentResult = array(); + + if ($isPaidMembership) { + if ($isProcessSeparateMembershipTransaction) { + // If we have 2 transactions only one can use the invoice id. + $membershipParams['invoiceID'] .= '-2'; + } + + $paymentResult = CRM_Contribute_BAO_Contribution_Utils::processConfirm($form, $membershipParams, + $contactID, + $financialTypeID, + 'membership', + array(), + $isTest, + $isPayLater + ); + + if (!empty($paymentResult['contribution'])) { + $this->postProcessPremium($premiumParams, $paymentResult['contribution']); + //note that this will be over-written if we are using a separate membership transaction. Otherwise there is only one + $membershipContribution = $paymentResult['contribution']; + // Save the contribution ID so that I can be used in email receipts + // For example, if you need to generate a tax receipt for the donation only. + $form->_values['contribution_other_id'] = $membershipContribution->id; + } + } + + if ($isProcessSeparateMembershipTransaction) { + try { + $form->_lineItem = $membershipLineItems; + if (empty($form->_params['auto_renew']) && !empty($membershipParams['is_recur'])) { + unset($membershipParams['is_recur']); + } + $membershipContribution = $this->processSecondaryFinancialTransaction($contactID, $form, $membershipParams, + $isTest, $membershipLineItems, CRM_Utils_Array::value('minimum_fee', $membershipDetails, 0), CRM_Utils_Array::value('financial_type_id', $membershipDetails)); + } + catch (CRM_Core_Exception $e) { + $errors[2] = $e->getMessage(); + $membershipContribution = NULL; + } + } + + $membership = NULL; + if (!empty($membershipContribution) && !is_a($membershipContribution, 'CRM_Core_Error')) { + $membershipContributionID = $membershipContribution->id; + } + + //@todo - why is this nested so deep? it seems like it could be just set on the calling function on the form layer + if (isset($membershipParams['onbehalf']) && !empty($membershipParams['onbehalf']['member_campaign_id'])) { + $form->_params['campaign_id'] = $membershipParams['onbehalf']['member_campaign_id']; + } + //@todo it should no longer be possible for it to get to this point & membership to not be an array + if (is_array($membershipTypeIDs) && !empty($membershipContributionID)) { + $typesTerms = CRM_Utils_Array::value('types_terms', $membershipParams, array()); + foreach ($membershipTypeIDs as $memType) { + $numTerms = CRM_Utils_Array::value($memType, $typesTerms, 1); + if (!empty($membershipContribution)) { + $pendingStatus = CRM_Core_OptionGroup::getValue('contribution_status', 'Pending', 'name'); + $pending = ($membershipContribution->contribution_status_id == $pendingStatus) ? TRUE : FALSE; + } + else { + $pending = $isPending; + } + $contributionRecurID = isset($form->_params['contributionRecurID']) ? $form->_params['contributionRecurID'] : NULL; + + $membershipSource = NULL; + if (!empty($form->_params['membership_source'])) { + $membershipSource = $form->_params['membership_source']; + } + elseif (isset($form->_values['title']) && !empty($form->_values['title'])) { + $membershipSource = ts('Online Contribution:') . ' ' . $form->_values['title']; + } + $isPayLater = NULL; + if (isset($form->_params)) { + $isPayLater = CRM_Utils_Array::value('is_pay_later', $form->_params); + } + $campaignId = NULL; + if (isset($form->_values) && is_array($form->_values) && !empty($form->_values)) { + $campaignId = CRM_Utils_Array::value('campaign_id', $form->_params); + if (!array_key_exists('campaign_id', $form->_params)) { + $campaignId = CRM_Utils_Array::value('campaign_id', $form->_values); + } + } + + list($membership, $renewalMode, $dates) = CRM_Member_BAO_Membership::renewMembership( + $contactID, $memType, $isTest, + date('YmdHis'), CRM_Utils_Array::value('cms_contactID', $membershipParams), + $customFieldsFormatted, + $numTerms, $membershipID, $pending, + $contributionRecurID, $membershipSource, $isPayLater, $campaignId + ); + $form->set('renewal_mode', $renewalMode); + if (!empty($dates)) { + $form->assign('mem_start_date', + CRM_Utils_Date::customFormat($dates['start_date'], '%Y%m%d') + ); + $form->assign('mem_end_date', + CRM_Utils_Date::customFormat($dates['end_date'], '%Y%m%d') + ); + } + + if (!empty($membershipContribution)) { + // update recurring id for membership record + CRM_Member_BAO_Membership::updateRecurMembership($membership, $membershipContribution); + CRM_Member_BAO_Membership::linkMembershipPayment($membership, $membershipContribution); + } + } + if ($form->_priceSetId && !empty($form->_useForMember) && !empty($form->_lineItem)) { + foreach ($form->_lineItem[$form->_priceSetId] as & $priceFieldOp) { + if (!empty($priceFieldOp['membership_type_id']) && + isset($createdMemberships[$priceFieldOp['membership_type_id']]) + ) { + $membershipOb = $createdMemberships[$priceFieldOp['membership_type_id']]; + $priceFieldOp['start_date'] = $membershipOb->start_date ? CRM_Utils_Date::customFormat($membershipOb->start_date, '%B %E%f, %Y') : '-'; + $priceFieldOp['end_date'] = $membershipOb->end_date ? CRM_Utils_Date::customFormat($membershipOb->end_date, '%B %E%f, %Y') : '-'; + } + else { + $priceFieldOp['start_date'] = $priceFieldOp['end_date'] = 'N/A'; + } + } + $form->_values['lineItem'] = $form->_lineItem; + $form->assign('lineItem', $form->_lineItem); + } + } + + if (!empty($errors)) { + $message = $this->compileErrorMessage($errors); + throw new CRM_Core_Exception($message); + } + $form->_params['createdMembershipIDs'] = array(); + + // CRM-7851 - Moved after processing Payment Errors + //@todo - the reasoning for this being here seems a little outdated + foreach ($createdMemberships as $createdMembership) { + CRM_Core_BAO_CustomValueTable::postProcess( + $form->_params, + 'civicrm_membership', + $createdMembership->id, + 'Membership' + ); + $form->_params['createdMembershipIDs'][] = $createdMembership->id; + } + if (count($createdMemberships) == 1) { + //presumably this is only relevant for exactly 1 membership + $form->_params['membershipID'] = $createdMembership->id; + } + + //CRM-15232: Check if membership is created and on the basis of it use + //membership receipt template to send payment receipt + if (count($createdMemberships)) { + $form->_values['isMembership'] = TRUE; + } + if (isset($membershipContributionID)) { + $form->_values['contribution_id'] = $membershipContributionID; + } + if ($form->_contributeMode) { + if ($form->_values['is_monetary'] && $form->_amount > 0.0 && !$form->_params['is_pay_later']) { + // call postProcess hook before leaving + $form->postProcessHook(); + } + $payment = Civi\Payment\System::singleton()->getByProcessor($form->_paymentProcessor); + $result = $payment->doPayment($form->_params, 'contribute'); + + if (CRM_Utils_Array::value('payment_status_id', $result) == 1) { + // Refer to CRM-16737. Payment processors 'should' return payment_status_id + // to denote the outcome of the transaction. + try { + civicrm_api3('contribution', 'completetransaction', array( + 'id' => $paymentResult['contribution']->id, + 'trxn_id' => $paymentResult['contribution']->trxn_id, + 'is_transactional' => FALSE, + )); + } + catch (CiviCRM_API3_Exception $e) { + // if for any reason it is already completed this will fail - e.g extensions hacking around core not completing transactions prior to CRM-15296 + // so let's be gentle here + CRM_Core_Error::debug_log_message('contribution ' . $membershipContribution->id . ' not completed with trxn_id ' . $membershipContribution->trxn_id . ' and message ' . $e->getMessage()); + } + } + // Do not send an email if Recurring transaction is done via Direct Mode + // Email will we sent when the IPN is received. + return; + } + + //finally send an email receipt + CRM_Contribute_BAO_ContributionPage::sendMail($contactID, + $form->_values, + $isTest, FALSE, + $includeFieldTypes + ); + } + + /** + * Turn array of errors into message string. + * + * @param array $errors + * + * @return string + */ + protected function compileErrorMessage($errors) { + foreach ($errors as $error) { + if (is_string($error)) { + $message[] = $error; + } + } + return ts('Payment Processor Error message') . ': ' . implode('
', $message); + } + + /** + * Where a second separate financial transaction is supported we will process it here. + * + * @param int $contactID + * @param CRM_Contribute_Form_Contribution_Confirm $form + * @param array $tempParams + * @param bool $isTest + * @param array $lineItems + * @param $minimumFee + * @param int $financialTypeID + * + * @throws CRM_Core_Exception + * @throws Exception + * @return CRM_Contribute_BAO_Contribution + */ + protected function processSecondaryFinancialTransaction($contactID, &$form, $tempParams, $isTest, $lineItems, $minimumFee, + $financialTypeID) { + $financialType = new CRM_Financial_DAO_FinancialType(); + $financialType->id = $financialTypeID; + $financialType->find(TRUE); + $tempParams['amount'] = $minimumFee; + $tempParams['invoiceID'] = md5(uniqid(rand(), TRUE)); + + $result = NULL; + if ($form->_values['is_monetary'] && !$form->_params['is_pay_later'] && $minimumFee > 0.0) { + // At the moment our tests are calling this form in a way that leaves 'object' empty. For + // now we compensate here. + if (empty($form->_paymentProcessor['object'])) { + $payment = Civi\Payment\System::singleton()->getByProcessor($this->_paymentProcessor); + } + else { + $payment = $form->_paymentProcessor['object']; + } + $result = $payment->doPayment($tempParams, 'contribute'); + } + + //assign receive date when separate membership payment + //and contribution amount not selected. + if ($form->_amount == 0) { + $now = date('YmdHis'); + $form->_params['receive_date'] = $now; + $receiveDate = CRM_Utils_Date::mysqlToIso($now); + $form->set('params', $form->_params); + $form->assign('receive_date', $receiveDate); + } + + $form->set('membership_trx_id', $result['trxn_id']); + $form->set('membership_amount', $minimumFee); + + $form->assign('membership_trx_id', $result['trxn_id']); + $form->assign('membership_amount', $minimumFee); + + // we don't need to create the user twice, so lets disable cms_create_account + // irrespective of the value, CRM-2888 + $tempParams['cms_create_account'] = 0; + + //CRM-16165, scenarios are + // 1) If contribution is_pay_later and if contribution amount is > 0.0 we set pending = TRUE, vice-versa FALSE + // 2) If not pay later but auto-renewal membership is chosen then pending = TRUE as it later triggers + // pending recurring contribution, vice-versa FALSE + $pending = $form->_params['is_pay_later'] ? (($minimumFee > 0.0) ? TRUE : FALSE) : (!empty($form->_params['auto_renew']) ? TRUE : FALSE); + + //set this variable as we are not creating pledge for + //separate membership payment contribution. + //so for differentiating membership contribution from + //main contribution. + $form->_params['separate_membership_payment'] = 1; + $membershipContribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($form, + $tempParams, + $result, + $contactID, + $financialType, + $pending, + TRUE, + $isTest, + $lineItems, + $form->_bltID + ); + return $membershipContribution; + } + + /** + * Is the payment a pending payment. + * + * We are moving towards always creating as pending and updating at the end (based on payment), so this should be + * an interim refactoring. It was shared with another unrelated form & some parameters may not apply to this form. + * + * + * @return bool + */ + protected function getIsPending() { + if (((isset($this->_contributeMode)) || !empty + ($this->_params['is_pay_later']) + ) && + (($this->_values['is_monetary'] && $this->_amount > 0.0)) + ) { + return TRUE; + } + return FALSE; + } + + /** + * Are we going to do 2 financial transactions. + * + * Ie the membership block supports a separate transactions AND the contribution form has been configured for a + * contribution + * transaction AND a membership transaction AND the payment processor supports double financial transactions (ie. NOT doTransferPayment style) + * + * @param int $formID + * @param bool $amountBlockActiveOnForm + * + * @return bool + */ + public function isSeparateMembershipTransaction($formID, $amountBlockActiveOnForm) { + $memBlockDetails = CRM_Member_BAO_Membership::getMembershipBlock($formID); + if (!empty($memBlockDetails['is_separate_payment']) && $amountBlockActiveOnForm) { + return TRUE; + } + return FALSE; + } + + /** + * This function sets the fields. + * + * - $this->_params['amount_level'] + * - $this->_params['selectMembership'] + * And under certain circumstances sets + * $this->_params['amount'] = null; + * + * @param int $priceSetID + */ + public function setFormAmountFields($priceSetID) { + $isQuickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_params['priceSetId'], 'is_quick_config'); + $priceField = new CRM_Price_DAO_PriceField(); + $priceField->price_set_id = $priceSetID; + $priceField->orderBy('weight'); + $priceField->find(); + $paramWeDoNotUnderstand = NULL; + + while ($priceField->fetch()) { + if ($priceField->name == "contribution_amount") { + $paramWeDoNotUnderstand = $priceField->id; + } + if ($isQuickConfig && !empty($this->_params["price_{$priceField->id}"])) { + if ($this->_values['fee'][$priceField->id]['html_type'] != 'Text') { + $this->_params['amount_level'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', + $this->_params["price_{$priceField->id}"], 'label'); + } + if ($priceField->name == "membership_amount") { + $this->_params['selectMembership'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', + $this->_params["price_{$priceField->id}"], 'membership_type_id'); + } + } + // If separate payment we set contribution amount to be null, so that it will not show contribution amount same + // as membership amount. + // @todo - this needs more documentation - it appears the setting to null is tied up with separate membership payments + // but the circumstances are very confusing. Many of these conditions are repeated in the next conditional + // so we should merge them together + // the quick config seems like a red-herring - if this is about a separate membership payment then there + // are 2 types of line items - membership ones & non-membership ones - regardless of whether quick config is set + elseif ( + CRM_Utils_Array::value('is_separate_payment', $this->_membershipBlock) + && !empty($this->_values['fee'][$priceField->id]) + && ($this->_values['fee'][$priceField->id]['name'] == "other_amount") + && CRM_Utils_Array::value("price_{$paramWeDoNotUnderstand}", $this->_params) < 1 + && empty($this->_params["price_{$priceField->id}"]) + ) { + $this->_params['amount'] = NULL; + } + + // Fix for CRM-14375 - If we are using separate payments and "no + // thank you" is selected for the additional contribution, set + // contribution amount to be null, so that it will not show + // contribution amount same as membership amount. + //@todo - merge with section above + if ($this->_membershipBlock['is_separate_payment'] + && !empty($this->_values['fee'][$priceField->id]) + && CRM_Utils_Array::value('name', $this->_values['fee'][$priceField->id]) == 'contribution_amount' + && CRM_Utils_Array::value("price_{$priceField->id}", $this->_params) == '-1' + ) { + $this->_params['amount'] = NULL; + } + } + } + + /** + * Submit function. + * + * @param array $params + * + * @throws CiviCRM_API3_Exception + */ + public static function submit($params) { + $form = new CRM_Contribute_Form_Contribution_Confirm(); + $form->_id = $params['id']; + + CRM_Contribute_BAO_ContributionPage::setValues($form->_id, $form->_values); + $form->_separateMembershipPayment = CRM_Contribute_BAO_ContributionPage::getIsMembershipPayment($form->_id); + //this way the mocked up controller ignores the session stuff + $_SERVER['REQUEST_METHOD'] = 'GET'; + $form->controller = new CRM_Contribute_Controller_Contribution(); + $params['invoiceID'] = md5(uniqid(rand(), TRUE)); + $paramsProcessedForForm = $form->_params = self::getFormParams($params['id'], $params); + $form->_amount = $params['amount']; + $priceSetID = $form->_params['priceSetId'] = $paramsProcessedForForm['price_set_id']; + $priceFields = CRM_Price_BAO_PriceSet::getSetDetail($priceSetID); + $priceSetFields = reset($priceFields); + $form->_values['fee'] = $priceSetFields['fields']; + $form->_priceSetId = $priceSetID; + $form->setFormAmountFields($priceSetID); + if (!empty($params['payment_processor_id'])) { + $form->_paymentProcessor = civicrm_api3('payment_processor', 'getsingle', array( + 'id' => $params['payment_processor_id'], + )); + if ($form->_paymentProcessor['billing_mode'] == 1) { + $form->_contributeMode = 'direct'; + } + else { + $form->_contributeMode = 'notify'; + } + } + else { + $form->_params['payment_processor_id'] = 0; + } + $priceFields = $priceFields[$priceSetID]['fields']; + CRM_Price_BAO_PriceSet::processAmount($priceFields, $paramsProcessedForForm, $lineItems, 'civicrm_contribution'); + $form->_lineItem = array($priceSetID => $lineItems); + $form->processFormSubmission(CRM_Utils_Array::value('contact_id', $params)); + } + + /** + * Helper function for static submit function. + * + * Set relevant params - help us to build up an array that we can pass in. + * + * @param int $id + * @param array $params + * + * @return array + * @throws CiviCRM_API3_Exception + */ + public static function getFormParams($id, array $params) { + if (!isset($params['is_pay_later'])) { + if (!empty($params['payment_processor_id'])) { + $params['is_pay_later'] = 0; + } + else { + $params['is_pay_later'] = civicrm_api3('contribution_page', 'getvalue', array( + 'id' => $id, + 'return' => 'is_pay_later', + )); + } + } + if (empty($params['price_set_id'])) { + $params['price_set_id'] = CRM_Price_BAO_PriceSet::getFor('civicrm_contribution_page', $params['id']); + } + return $params; + } + + /** + * Post form submission handling. + * + * This is also called from the test suite. + * + * @param int $contactID + * + * @return array + */ + protected function processFormSubmission($contactID) { + $isPayLater = $this->_params['is_pay_later']; + if (isset($this->_params['payment_processor_id']) && $this->_params['payment_processor_id'] == 0) { + $this->_params['is_pay_later'] = $isPayLater = TRUE; + } + // add a description field at the very beginning + $this->_params['description'] = ts('Online Contribution') . ': ' . (($this->_pcpInfo['title']) ? $this->_pcpInfo['title'] : $this->_values['title']); + + $this->_params['accountingCode'] = CRM_Utils_Array::value('accountingCode', $this->_values); + + // fix currency ID + $this->_params['currencyID'] = CRM_Core_Config::singleton()->defaultCurrency; + + //carry payment processor id. + if (CRM_Utils_Array::value('id', $this->_paymentProcessor)) { + $this->_params['payment_processor_id'] = $this->_paymentProcessor['id']; + } ++ ++ $premiumParams = $membershipParams = $params = $this->_params; + if (!empty($params['image_URL'])) { + CRM_Contact_BAO_Contact::processImageParams($params); + } - $premiumParams = $membershipParams = $params = $this->_params; ++ + $fields = array('email-Primary' => 1); + + // get the add to groups + $addToGroups = array(); + + // now set the values for the billing location. + foreach ($this->_fields as $name => $value) { + $fields[$name] = 1; + + // get the add to groups for uf fields + if (!empty($value['add_to_group_id'])) { + $addToGroups[$value['add_to_group_id']] = $value['add_to_group_id']; + } + } + + if (!array_key_exists('first_name', $fields)) { + $nameFields = array('first_name', 'middle_name', 'last_name'); + foreach ($nameFields as $name) { + $fields[$name] = 1; + if (array_key_exists("billing_$name", $params)) { + $params[$name] = $params["billing_{$name}"]; + $params['preserveDBName'] = TRUE; + } + } + } + + // billing email address + $fields["email-{$this->_bltID}"] = 1; + + //unset the billing parameters if it is pay later mode + //to avoid creation of billing location + if ($isPayLater && !$this->_isBillingAddressRequiredForPayLater) { + $billingFields = array( + 'billing_first_name', + 'billing_middle_name', + 'billing_last_name', + "billing_street_address-{$this->_bltID}", + "billing_city-{$this->_bltID}", + "billing_state_province-{$this->_bltID}", + "billing_state_province_id-{$this->_bltID}", + "billing_postal_code-{$this->_bltID}", + "billing_country-{$this->_bltID}", + "billing_country_id-{$this->_bltID}", + ); + + foreach ($billingFields as $value) { + unset($params[$value]); + unset($fields[$value]); + } + } + + // if onbehalf-of-organization contribution, take out + // organization params in a separate variable, to make sure + // normal behavior is continued. And use that variable to + // process on-behalf-of functionality. + if (!empty($this->_params['hidden_onbehalf_profile'])) { + $behalfOrganization = array(); + $orgFields = array('organization_name', 'organization_id', 'org_option'); + foreach ($orgFields as $fld) { + if (array_key_exists($fld, $params)) { + $behalfOrganization[$fld] = $params[$fld]; + unset($params[$fld]); + } + } + + if (is_array($params['onbehalf']) && !empty($params['onbehalf'])) { + foreach ($params['onbehalf'] as $fld => $values) { + if (strstr($fld, 'custom_')) { + $behalfOrganization[$fld] = $values; + } + elseif (!(strstr($fld, '-'))) { + if (in_array($fld, array( + 'contribution_campaign_id', + 'member_campaign_id', + ))) { + $fld = 'campaign_id'; + } + else { + $behalfOrganization[$fld] = $values; + } + $this->_params[$fld] = $values; + } + } + } + + if (array_key_exists('onbehalf_location', $params) && is_array($params['onbehalf_location'])) { + foreach ($params['onbehalf_location'] as $block => $vals) { + //fix for custom data (of type checkbox, multi-select) + if (substr($block, 0, 7) == 'custom_') { + continue; + } + // fix the index of block elements + if (is_array($vals)) { + foreach ($vals as $key => $val) { + //dont adjust the index of address block as + //it's index is WRT to location type + $newKey = ($block == 'address') ? $key : ++$key; + $behalfOrganization[$block][$newKey] = $val; + } + } + } + unset($params['onbehalf_location']); + } + if (!empty($params['onbehalf[image_URL]'])) { + $behalfOrganization['image_URL'] = $params['onbehalf[image_URL]']; + } + } + + // check for profile double opt-in and get groups to be subscribed + $subscribeGroupIds = CRM_Core_BAO_UFGroup::getDoubleOptInGroupIds($params, $contactID); + + // since we are directly adding contact to group lets unset it from mailing + if (!empty($addToGroups)) { + foreach ($addToGroups as $groupId) { + if (isset($subscribeGroupIds[$groupId])) { + unset($subscribeGroupIds[$groupId]); + } + } + } + + foreach ($addToGroups as $k) { + if (array_key_exists($k, $subscribeGroupIds)) { + unset($addToGroups[$k]); + } + } + + if (empty($contactID)) { + $dupeParams = $params; + if (!empty($dupeParams['onbehalf'])) { + unset($dupeParams['onbehalf']); + } + if (!empty($dupeParams['honor'])) { + unset($dupeParams['honor']); + } + + $dedupeParams = CRM_Dedupe_Finder::formatParams($dupeParams, 'Individual'); + $dedupeParams['check_permission'] = FALSE; + $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Individual'); + + // if we find more than one contact, use the first one + $contactID = CRM_Utils_Array::value(0, $ids); + + // Fetch default greeting id's if creating a contact + if (!$contactID) { + foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) { + if (!isset($params[$greeting])) { + $params[$greeting] = CRM_Contact_BAO_Contact_Utils::defaultGreeting('Individual', $greeting); + } + } + } + $contactType = NULL; + } + else { + $contactType = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactID, 'contact_type'); + } + $contactID = CRM_Contact_BAO_Contact::createProfileContact( + $params, + $fields, + $contactID, + $addToGroups, + NULL, + $contactType, + TRUE + ); + + // Make the contact ID associated with the contribution available at the Class level. + // Also make available to the session. + //@todo consider handling this in $this->getContactID(); + $this->set('contactID', $contactID); + $this->_contactID = $contactID; + + //get email primary first if exist + $subscriptionEmail = array('email' => CRM_Utils_Array::value('email-Primary', $params)); + if (!$subscriptionEmail['email']) { + $subscriptionEmail['email'] = CRM_Utils_Array::value("email-{$this->_bltID}", $params); + } + // subscribing contact to groups + if (!empty($subscribeGroupIds) && $subscriptionEmail['email']) { + CRM_Mailing_Event_BAO_Subscribe::commonSubscribe($subscribeGroupIds, $subscriptionEmail, $contactID); + } + + // If onbehalf-of-organization contribution / signup, add organization + // and it's location. + if (isset($params['hidden_onbehalf_profile']) && isset($behalfOrganization['organization_name'])) { + $ufFields = array(); + foreach ($this->_fields['onbehalf'] as $name => $value) { + $ufFields[$name] = 1; + } + self::processOnBehalfOrganization($behalfOrganization, $contactID, $this->_values, + $this->_params, $ufFields + ); + } + elseif (!empty($this->_membershipContactID) && $contactID != $this->_membershipContactID) { + // this is an onbehalf renew case for inherited membership. For e.g a permissioned member of household, + // store current user id as related contact for later use for mailing / activity.. + $this->_values['related_contact'] = $contactID; + $this->_params['related_contact'] = $contactID; + // swap contact like we do for on-behalf-org case, so parent/primary membership is affected + $contactID = $this->_membershipContactID; + } + + // lets store the contactID in the session + // for things like tell a friend + $session = CRM_Core_Session::singleton(); + if (!$session->get('userID')) { + $session->set('transaction.userID', $contactID); + } + else { + $session->set('transaction.userID', NULL); + } - /** - * Process membership. - * - * @param array $membershipParams - * @param int $contactID - * @param array $customFieldsFormatted - * @param array $fieldTypes - * @param array $premiumParams - * @param array $membershipLineItems - * Line items specifically relating to memberships. - * @param $isPayLater - */ - public function processMembership($membershipParams, $contactID, $customFieldsFormatted, $fieldTypes, $premiumParams, $membershipLineItems, $isPayLater) { - try { - $membershipTypeIDs = (array) $membershipParams['selectMembership']; - $membershipTypes = CRM_Member_BAO_Membership::buildMembershipTypeValues($this, $membershipTypeIDs); - $membershipType = empty($membershipTypes) ? array() : reset($membershipTypes); - $this->assign('membership_name', CRM_Utils_Array::value('name', $membershipType)); + $this->_useForMember = $this->get('useForMember'); - $isPaidMembership = FALSE; - if ($this->_amount >= 0.0 && isset($membershipParams['amount'])) { - //amount must be greater than zero for - //adding contribution record to contribution table. - //this condition arises when separate membership payment is - //enabled and contribution amount is not selected. fix for CRM-3010 - $isPaidMembership = TRUE; + // store the fact that this is a membership and membership type is selected + if ((!empty($membershipParams['selectMembership']) && + $membershipParams['selectMembership'] != 'no_thanks' + ) || + $this->_useForMember + ) { + if (!$this->_useForMember) { + $this->assign('membership_assign', TRUE); + $this->set('membershipTypeID', $this->_params['selectMembership']); } - $isProcessSeparateMembershipTransaction = $this->isSeparateMembershipTransaction($this->_id, $this->_values['amount_block_is_active']); - if ($this->_values['amount_block_is_active']) { - $contributionTypeId = $this->_values['financial_type_id']; + if ($this->_action & CRM_Core_Action::PREVIEW) { + $membershipParams['is_test'] = 1; } - else { - $contributionTypeId = CRM_Utils_Array::value('financial_type_id', $membershipType, CRM_Utils_Array::value('financial_type_id', $membershipParams)); + if ($this->_params['is_pay_later']) { + $membershipParams['is_pay_later'] = 1; } - CRM_Member_BAO_Membership::postProcessMembership($membershipParams, $contactID, - $this, $premiumParams, $customFieldsFormatted, $fieldTypes, $membershipType, $membershipTypeIDs, $isPaidMembership, $this->_membershipId, $isProcessSeparateMembershipTransaction, $contributionTypeId, - $membershipLineItems, $isPayLater - ); - $this->assign('membership_assign', TRUE); - $this->set('membershipTypeID', $membershipParams['selectMembership']); - } - catch (CRM_Core_Exception $e) { - CRM_Core_Session::singleton()->setStatus($e->getMessage()); - CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contribute/transact', "_qf_Main_display=true&qfKey={$this->_params['qfKey']}")); - } - } + //inherit campaign from contribution page. + if (!array_key_exists('campaign_id', $membershipParams)) { + $membershipParams['campaign_id'] = CRM_Utils_Array::value('campaign_id', $this->_values); + } - /** - * Are we going to do 2 financial transactions. - * - * Ie the membership block supports a separate transactions AND the contribution form has been configured for a - * contribution - * transaction AND a membership transaction AND the payment processor supports double financial transactions (ie. NOT doTransferPayment style) - * - * @param int $formID - * @param bool $amountBlockActiveOnForm - * - * @return bool - */ - public function isSeparateMembershipTransaction($formID, $amountBlockActiveOnForm) { - $memBlockDetails = CRM_Member_BAO_Membership::getMembershipBlock($formID); - if (!empty($memBlockDetails['is_separate_payment']) && $amountBlockActiveOnForm) { - return TRUE; + CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $membershipParams, TRUE); + $this->doMembershipProcessing($contactID, $membershipParams, $premiumParams, $isPayLater); } - return FALSE; - } - - /** - * This function sets the fields. - * - * - $this->_params['amount_level'] - * - $this->_params['selectMembership'] - * And under certain circumstances sets - * $this->_params['amount'] = null; - * - * @param int $priceSetID - */ - public function setFormAmountFields($priceSetID) { - $isQuickConfig = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_params['priceSetId'], 'is_quick_config'); - $priceField = new CRM_Price_DAO_PriceField(); - $priceField->price_set_id = $priceSetID; - $priceField->orderBy('weight'); - $priceField->find(); - $paramWeDoNotUnderstand = NULL; + else { + // at this point we've created a contact and stored its address etc + // all the payment processors expect the name and address to be in the + // so we copy stuff over to first_name etc. + $paymentParams = $this->_params; + $contributionTypeId = $this->_values['financial_type_id']; - while ($priceField->fetch()) { - if ($priceField->name == "contribution_amount") { - $paramWeDoNotUnderstand = $priceField->id; - } - if ($isQuickConfig && !empty($this->_params["price_{$priceField->id}"])) { - if ($this->_values['fee'][$priceField->id]['html_type'] != 'Text') { - $this->_params['amount_level'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', - $this->_params["price_{$priceField->id}"], 'label'); - } - if ($priceField->name == "membership_amount") { - $this->_params['selectMembership'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', - $this->_params["price_{$priceField->id}"], 'membership_type_id'); - } - } - // If separate payment we set contribution amount to be null, so that it will not show contribution amount same - // as membership amount. - // @todo - this needs more documentation - it appears the setting to null is tied up with separate membership payments - // but the circumstances are very confusing. Many of these conditions are repeated in the next conditional - // so we should merge them together - // the quick config seems like a red-herring - if this is about a separate membership payment then there - // are 2 types of line items - membership ones & non-membership ones - regardless of whether quick config is set - elseif ( - CRM_Utils_Array::value('is_separate_payment', $this->_membershipBlock) - && !empty($this->_values['fee'][$priceField->id]) - && ($this->_values['fee'][$priceField->id]['name'] == "other_amount") - && CRM_Utils_Array::value("price_{$paramWeDoNotUnderstand}", $this->_params) < 1 - && empty($this->_params["price_{$priceField->id}"]) + $fieldTypes = array(); + if (!empty($paymentParams['onbehalf']) && + is_array($paymentParams['onbehalf']) ) { - $this->_params['amount'] = NULL; + foreach ($paymentParams['onbehalf'] as $key => $value) { + if (strstr($key, 'custom_')) { + $this->_params[$key] = $value; + } + } + $fieldTypes = array('Contact', 'Organization', 'Contribution'); } + $financialTypeID = $this->wrangleFinancialTypeID($contributionTypeId); - // Fix for CRM-14375 - If we are using separate payments and "no - // thank you" is selected for the additional contribution, set - // contribution amount to be null, so that it will not show - // contribution amount same as membership amount. - //@todo - merge with section above - if ($this->_membershipBlock['is_separate_payment'] - && !empty($this->_values['fee'][$priceField->id]) - && CRM_Utils_Array::value('name', $this->_values['fee'][$priceField->id]) == 'contribution_amount' - && CRM_Utils_Array::value("price_{$priceField->id}", $this->_params) == '-1' - ) { - $this->_params['amount'] = NULL; + $result = CRM_Contribute_BAO_Contribution_Utils::processConfirm($this, $paymentParams, + $contactID, + $financialTypeID, + 'contribution', + $fieldTypes, + ($this->_mode == 'test') ? 1 : 0, + $isPayLater + ); + + if (!empty($result['is_payment_failure'])) { + return $result; } + // @todo move premium processing to complete transaction if it truly is an 'after' action. + $this->postProcessPremium($premiumParams, $result['contribution']); + if (CRM_Utils_Array::value('payment_status_id', $result) == 1) { + civicrm_api3('contribution', 'completetransaction', array( + 'id' => $result['contribution']->id, + 'trxn_id' => CRM_Utils_Array::value('trxn_id', $result), + ) + ); + } + return $result; } } diff --cc CRM/Member/Form/Membership.php index ffc6e62fbd,17c4d81bf0..1bf1106e4e --- a/CRM/Member/Form/Membership.php +++ b/CRM/Member/Form/Membership.php @@@ -1781,28 -1712,9 +1781,27 @@@ class CRM_Member_Form_Membership extend $this->_id = $membership->id; } + $statusMsg = ''; + if (($this->_action & CRM_Core_Action::UPDATE)) { + $statusMsg = $this->getStatusMessageForUpdate($membership, $endDate, $receiptSend); - + } + elseif (($this->_action & CRM_Core_Action::ADD)) { + $statusMsg = $this->getStatusMessageForCreate($endDate, $receiptSend, $membershipTypes, $createdMemberships, + $params, $calcDates, $mailSend); + } + CRM_Core_Session::setStatus($statusMsg, ts('Complete'), 'success'); + return $createdMemberships; + } + + /** + * Set context in session. + */ + protected function setUserContext() { $buttonName = $this->controller->getButtonName(); + $session = CRM_Core_Session::singleton(); + if ($this->_context == 'standalone') { if ($buttonName == $this->getButtonName('upload', 'new')) { $session->replaceUserContext(CRM_Utils_System::url('civicrm/member/add', @@@ -1823,72 -1735,171 +1822,72 @@@ } /** - * Send email receipt. + * Get status message for updating membership. * - * @param CRM_Core_Form $form - * Form object. - * @param array $formValues - * @param object $membership - * Object. + * @param CRM_Member_BAO_Membership $membership + * @param string $endDate + * @param bool $receiptSend * - * @return bool - * true if mail was sent successfully + * @return string */ - public static function emailReceipt(&$form, &$formValues, &$membership) { - // retrieve 'from email id' for acknowledgement - $receiptFrom = $formValues['from_email_address']; - - if (!empty($formValues['payment_instrument_id'])) { - $paymentInstrument = CRM_Contribute_PseudoConstant::paymentInstrument(); - $formValues['paidBy'] = $paymentInstrument[$formValues['payment_instrument_id']]; - } - - // retrieve custom data - $customFields = $customValues = array(); - if (property_exists($form, '_groupTree') - && !empty($form->_groupTree) - ) { - foreach ($form->_groupTree as $groupID => $group) { - if ($groupID == 'info') { - continue; - } - foreach ($group['fields'] as $k => $field) { - $field['title'] = $field['label']; - $customFields["custom_{$k}"] = $field; - } - } - } - - $members = array(array('member_id', '=', $membership->id, 0, 0)); - // check whether its a test drive - if ($form->_mode == 'test') { - $members[] = array('member_test', '=', 1, 0, 0); - } - - CRM_Core_BAO_UFGroup::getValues($formValues['contact_id'], $customFields, $customValues, FALSE, $members); - - if ($form->_mode) { - if (!empty($form->_params['billing_first_name'])) { - $name = $form->_params['billing_first_name']; - } - - if (!empty($form->_params['billing_middle_name'])) { - $name .= " {$form->_params['billing_middle_name']}"; - } - - if (!empty($form->_params['billing_last_name'])) { - $name .= " {$form->_params['billing_last_name']}"; - } - - $form->assign('billingName', $name); - - // assign the address formatted up for display - $addressParts = array( - "street_address-{$form->_bltID}", - "city-{$form->_bltID}", - "postal_code-{$form->_bltID}", - "state_province-{$form->_bltID}", - "country-{$form->_bltID}", - ); - $addressFields = array(); - foreach ($addressParts as $part) { - list($n, $id) = explode('-', $part); - if (isset($form->_params['billing_' . $part])) { - $addressFields[$n] = $form->_params['billing_' . $part]; - } - } - $form->assign('address', CRM_Utils_Address::format($addressFields)); + protected function getStatusMessageForUpdate($membership, $endDate, $receiptSend) { + // End date can be modified by hooks, so if end date is set then use it. + $endDate = ($membership->end_date) ? $membership->end_date : $endDate; - $date = CRM_Utils_Date::format($form->_params['credit_card_exp_date']); - $date = CRM_Utils_Date::mysqlToIso($date); - $form->assign('credit_card_exp_date', $date); - $form->assign('credit_card_number', - CRM_Utils_System::mungeCreditCard($form->_params['credit_card_number']) - ); - $form->assign('credit_card_type', $form->_params['credit_card_type']); - $form->assign('contributeMode', 'direct'); - $form->assign('isAmountzero', 0); - $form->assign('is_pay_later', 0); - $form->assign('isPrimary', 1); + $statusMsg = ts('Membership for %1 has been updated.', array(1 => $this->_memberDisplayName)); + if ($endDate && $endDate !== 'null') { + $endDate = CRM_Utils_Date::customFormat($endDate); + $statusMsg .= ' ' . ts('The membership End Date is %1.', array(1 => $endDate)); } - $form->assign('module', 'Membership'); - $form->assign('contactID', $formValues['contact_id']); - - $form->assign('membershipID', CRM_Utils_Array::value('membership_id', $form->_params, CRM_Utils_Array::value('membership_id', $form->_defaultValues))); - - if (!empty($formValues['contribution_id'])) { - $form->assign('contributionID', $formValues['contribution_id']); - } - elseif (isset($form->_onlinePendingContributionId)) { - $form->assign('contributionID', $form->_onlinePendingContributionId); + if ($receiptSend) { + $statusMsg .= ' ' . ts('A confirmation and receipt has been sent to %1.', array(1 => $this->_contributorEmail)); } + return $statusMsg; + } - if (!empty($formValues['contribution_status_id'])) { - $form->assign('contributionStatusID', $formValues['contribution_status_id']); - $form->assign('contributionStatus', CRM_Contribute_PseudoConstant::contributionStatus($formValues['contribution_status_id'], 'name')); - } + /** + * Get status message for create action. + * + * @param string $endDate + * @param bool $receiptSend + * @param array $membershipTypes + * @param array $createdMemberships + * @param array $params + * @param array $calcDates + * @param bool $mailSent + * + * @return array|string + */ + protected function getStatusMessageForCreate($endDate, $receiptSend, $membershipTypes, $createdMemberships, + $params, $calcDates, $mailSent) { + // FIX ME: fix status messages + + $statusMsg = array(); + foreach ($membershipTypes as $memType => $membershipType) { + $statusMsg[$memType] = ts('%1 membership for %2 has been added.', array( + 1 => $membershipType, + 2 => $this->_memberDisplayName, + )); - if (!empty($formValues['is_renew'])) { - $form->assign('receiptType', 'membership renewal'); - } - else { - $form->assign('receiptType', 'membership signup'); - } - $form->assign('receive_date', CRM_Utils_Date::processDate(CRM_Utils_Array::value('receive_date', $formValues))); - $form->assign('formValues', $formValues); + $membership = $createdMemberships[$memType]; + $memEndDate = ($membership->end_date) ? $membership->end_date : $endDate; - if (empty($lineItem)) { - $form->assign('mem_start_date', CRM_Utils_Date::customFormat($membership->start_date, '%B %E%f, %Y')); - if (!CRM_Utils_System::isNull($membership->end_date)) { - $form->assign('mem_end_date', CRM_Utils_Date::customFormat($membership->end_date, '%B %E%f, %Y')); + //get the end date from calculated dates. + if (!$memEndDate && empty($params['is_recur'])) { + $memEndDate = CRM_Utils_Array::value('end_date', $calcDates[$memType]); } - $form->assign('membership_name', CRM_Member_PseudoConstant::membershipType($membership->membership_type_id)); - } - $form->assign('customValues', $customValues); - $isBatchProcess = is_a($form, 'CRM_Batch_Form_Entry'); - if ((empty($form->_contributorDisplayName) || empty($form->_contributorEmail)) || $isBatchProcess) { - // in this case the form is being called statically from the batch editing screen - // having one class in the form layer call another statically is not greate - // & we should aim to move this function to the BAO layer in future. - // however, we can assume that the contact_id passed in by the batch - // function will be the recipient - list($form->_contributorDisplayName, $form->_contributorEmail) - = CRM_Contact_BAO_Contact_Location::getEmailDetails($formValues['contact_id']); - if (empty($form->_receiptContactId) || $isBatchProcess) { - $form->_receiptContactId = $formValues['contact_id']; + if ($memEndDate && $memEndDate !== 'null') { + $memEndDate = CRM_Utils_Date::customFormat($memEndDate); + $statusMsg[$memType] .= ' ' . ts('The new membership End Date is %1.', array(1 => $memEndDate)); } } - $template = CRM_Core_Smarty::singleton(); - $taxAmt = $template->get_template_vars('dataArray'); - $eventTaxAmt = $template->get_template_vars('totalTaxAmount'); - $prefixValue = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings'); - $invoicing = CRM_Utils_Array::value('invoicing', $prefixValue); - if ((!empty($taxAmt) || isset($eventTaxAmt)) && (isset($invoicing) && isset($prefixValue['is_email_pdf']))) { - $isEmailPdf = TRUE; - } - else { - $isEmailPdf = FALSE; + $statusMsg = implode('
', $statusMsg); - if ($receiptSend && $mailSent) { ++ if ($receiptSend && !empty($mailSent)) { + $statusMsg .= ' ' . ts('A membership confirmation and receipt has been sent to %1.', array(1 => $this->_contributorEmail)); } - - list($mailSend, $subject, $message, $html) = CRM_Core_BAO_MessageTemplate::sendTemplate( - array( - 'groupName' => 'msg_tpl_workflow_membership', - 'valueName' => 'membership_offline_receipt', - 'contactId' => $form->_receiptContactId, - 'from' => $receiptFrom, - 'toName' => $form->_contributorDisplayName, - 'toEmail' => $form->_contributorEmail, - 'PDFFilename' => ts('receipt') . '.pdf', - 'isEmailPdf' => $isEmailPdf, - 'contributionId' => $formValues['contribution_id'], - 'isTest' => (bool) ($form->_action & CRM_Core_Action::PREVIEW), - ) - ); - - return TRUE; + return $statusMsg; } } diff --cc bower.json index 9afef09731,6ada8891fc..80c253902f --- a/bower.json +++ b/bower.json @@@ -21,9 -21,7 +21,10 @@@ "google-code-prettify": "~1.0", "select2": "colemanw/select2#stable/3.5", "jquery-validation": "~1.13", + "datatables": "~1.10", + "ckeditor": "~4.5", - "font-awesome": "~4.3" ++ "font-awesome": "~4.3", + "angular-sanitize": "~1.3.15" }, "resolutions": { "angular": "~1.3.8" diff --cc composer.json index 0b54273098,66cf769bbe..5dd0da7bd5 --- a/composer.json +++ b/composer.json @@@ -10,9 -10,10 +10,10 @@@ "symfony/dependency-injection": "2.3.*", "symfony/event-dispatcher": "2.3.*", "symfony/process": "2.3.*", - "psr/log": "1.0.0", + "psr/log": "~1.0.0", "symfony/finder": "2.3.*", - "totten/ca-config": "~13.02" + "totten/ca-config": "~13.02", + "civicrm/civicrm-cxn-rpc": "~0.15.07.07" }, "scripts": { "post-install-cmd": [ diff --cc templates/CRM/Contribute/Form/Contribution/Main.tpl index c0d93ad010,df84e8382e..c80e01cd7e --- a/templates/CRM/Contribute/Form/Contribution/Main.tpl +++ b/templates/CRM/Contribute/Form/Contribution/Main.tpl @@@ -430,7 -484,7 +430,8 @@@ flag = true; } }); - cj('.price-set-option-content input').change( function () { ++ + cj('.price-set-option-content input[data-amount]').change( function () { if (cj(this).attr('data-amount').replace(/[^\/\d]/g,'') == 0 ) { flag = true; } else { diff --cc templates/CRM/Member/Form/Membership.tpl index 6a9bfaec76,df299607d5..32e6a7fa87 --- a/templates/CRM/Member/Form/Membership.tpl +++ b/templates/CRM/Member/Form/Membership.tpl @@@ -171,18 -215,74 +171,18 @@@
{ts}Membership Payment and Receipt{/ts} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {if $action neq 2 } - - - - - {/if} - - - - -
{$form.is_different_contribution_contact.label}{$form.is_different_contribution_contact.html}  {help id="id-contribution_contact"}
  - - - - - - - - - -
{$form.soft_credit_type_id.label}{$form.soft_credit_type_id.html}
{$form.soft_credit_contact_id.label}{$form.soft_credit_contact_id.html}
-
{$form.financial_type_id.label}{$form.financial_type_id.html}
- {ts}Select the appropriate financial type for this payment.{/ts}
{$form.total_amount.label}{$form.total_amount.html}
- {ts}Membership payment amount. A contribution record will be created for this amount.{/ts}
{$form.receive_date.label}{include file="CRM/common/jcalendar.tpl" elementName=receive_date}
{$form.payment_instrument_id.label} *{$form.payment_instrument_id.html} {help id="payment_instrument_id" file="CRM/Contribute/Page/Tab.hlp"}
{$form.check_number.label}{$form.check_number.html|crmAddClass:six}
{$form.trxn_id.label}{$form.trxn_id.html}
{$form.contribution_status_id.label}{$form.contribution_status_id.html}
-
- - {else} -
- {/if} + {/if} + {include file="CRM/Member/Form/MembershipCommon.tpl"} - {if $emailExists and $outBound_option != 2 } + {if $emailExists and $outBound_option != 2} {$form.send_receipt.label}{$form.send_receipt.html}
- {ts 1=$emailExists}Automatically email a membership confirmation and receipt to %1?{/ts} + {ts 1=$emailExists}Automatically email a membership confirmation and receipt to %1 ?{/ts} {ts}OR if the payment is from a different contact, this email will only go to them.{/ts} - {elseif $context eq 'standalone' and $outBound_option != 2 } + {elseif $context eq 'standalone' and $outBound_option != 2} {$form.send_receipt.label}{$form.send_receipt.html}
- {ts}Automatically email a membership confirmation and receipt to {/ts}? + {ts}Automatically email a membership confirmation and receipt to {/ts}? {ts}OR if the payment is from a different contact, this email will only go to them.{/ts} {/if}