Merge remote-tracking branch 'upstream/4.6' into 4.6-master-2015-07-19-17-34-09
authorKurund Jalmi <kurund@yahoo.com>
Sun, 19 Jul 2015 13:09:39 +0000 (18:39 +0530)
committerKurund Jalmi <kurund@yahoo.com>
Sun, 19 Jul 2015 13:09:39 +0000 (18:39 +0530)
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

36 files changed:
1  2 
CRM/Admin/Form/ScheduleReminders.php
CRM/Case/Form/Activity.php
CRM/Contact/BAO/Contact.php
CRM/Contact/BAO/Query.php
CRM/Contact/BAO/Relationship.php
CRM/Contribute/BAO/Contribution.php
CRM/Contribute/Form/Contribution.php
CRM/Contribute/Form/Contribution/Confirm.php
CRM/Contribute/Form/Contribution/Main.php
CRM/Contribute/Form/ContributionPage/Custom.php
CRM/Contribute/Import/Parser/Contribution.php
CRM/Core/BAO/ConfigSetting.php
CRM/Core/BAO/CustomField.php
CRM/Core/BAO/Mapping.php
CRM/Core/DAO.php
CRM/Core/Payment.php
CRM/Core/Payment/AuthorizeNetIPN.php
CRM/Core/xml/Menu/Admin.xml
CRM/Event/Page/ManageEvent.php
CRM/Export/BAO/Export.php
CRM/Logging/Schema.php
CRM/Member/Form/Membership.php
CRM/Price/BAO/PriceSet.php
CRM/Utils/System.php
Civi/API/Subscriber/ChainSubscriber.php
ang/crmUi.js
api/v3/utils.php
bower.json
composer.json
settings/Core.setting.php
templates/CRM/Contribute/Form/Contribution/Main.tpl
templates/CRM/Member/Form/Membership.tpl
tests/phpunit/CiviTest/CiviUnitTestCase.php
tests/phpunit/WebTest/Event/AddEventTest.php
tests/phpunit/api/v3/SyntaxConformanceTest.php
tests/phpunit/api/v3/UtilsTest.php

Simple merge
Simple merge
Simple merge
Simple merge
index f6f5c6bd122fd7f83e300df1f14d1f9dddd4caf6,c026062f0f08e945e32f39daf125a245bba783a5..f931d7a546bb8205f0790b745fbe27b2ee11cb0f
@@@ -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,
Simple merge
index f8fc18b8fb534c05eea7db221c714ecf71f1fc0a,d7b6d75375b401898c4809ba9bbc3c70676060dd..862665413f431f984ac453a4e94c667cd261f440
@@@ -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;
      }
    }
  
-         $lineItems[$itemId]['unit_price'] = $lineItems[$itemId]['line_total'] = CRM_Utils_Rule::cleanMoney(CRM_Utils_Array::value('total_amount', $submittedValues));
 +  /**
 +   * 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')) {
-       CRM_Contribute_BAO_ContributionSoft::del(array('contribution_id' => $this->_id));
++        //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, '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;
 +  }
 +
  }
index 8a5a1b9d6a2935c9f6ad655347802e6f2e6151ae,69856982eaaeb3a05c498a879ac1bda3dfaa81d7..9cc6b49d4f3227cef48a9c0f8d8cd41f25a22d59
@@@ -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('<br/>', $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;
      }
    }
  
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index ffc6e62fbda4ea935ae7944ea5ec524b0f3472c8,17c4d81bf073bff988fdb2664a24f6736a33b883..1bf1106e4eb1d443fa1dfb08cfeb0d817e981ffd
@@@ -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',
    }
  
    /**
 -   * 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('<br/>', $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;
    }
  
  }
Simple merge
Simple merge
diff --cc ang/crmUi.js
Simple merge
Simple merge
diff --cc bower.json
index 9afef097312ea61ac9382deff3420bda91ca2529,6ada8891fc3ea3f7c93affcd421af4076b60ed73..80c253902f6fa80e88a4d1adc1c6e40bc3023b33
      "google-code-prettify": "~1.0",
      "select2": "colemanw/select2#stable/3.5",
      "jquery-validation": "~1.13",
-     "font-awesome": "~4.3"
 +    "datatables": "~1.10",
 +    "ckeditor": "~4.5",
++    "font-awesome": "~4.3",
+     "angular-sanitize": "~1.3.15"
    },
    "resolutions": {
      "angular": "~1.3.8"
diff --cc composer.json
index 0b54273098730ff9b7d92df9f970be76d41bbd21,66cf769bbe330190d504a8e2020f2115fbf13295..5dd0da7bd53be464168855fff21355eb061b91fc
      "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": [
Simple merge
index c0d93ad010e6f3782292a91ad69b328a706fa6ef,df84e8382ee2ec5f14e17f275be286589762b7d6..c80e01cd7e9ce08fae3edc4345f0854376044b94
            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 {
index 6a9bfaec76798684a6be42002ef3ef441309cf62,df299607d5036a5827de510d45d0dcccedaf2342..32e6a7fa87c4b5a18e1296d0e1265e73151d73bc
            </tr>
            <tr class="crm-membership-form-block-record_contribution"><td colspan="2">
              <fieldset id="recordContribution"><legend>{ts}Membership Payment and Receipt{/ts}</legend>
 -              <table>
 -                <tr class="crm-membership-form-block-contribution-contact">
 -                  <td class="label">{$form.is_different_contribution_contact.label}</td>
 -                  <td>{$form.is_different_contribution_contact.html}&nbsp;&nbsp;{help id="id-contribution_contact"}</td>
 -                </tr>
 -                <tr id="record-different-contact">
 -                  <td>&nbsp;</td>
 -                  <td>
 -                    <table class="compressed">
 -                      <tr class="crm-membership-form-block-soft-credit-type">
 -                        <td class="label">{$form.soft_credit_type_id.label}</td>
 -                        <td>{$form.soft_credit_type_id.html}</td>
 -                      </tr>
 -                      <tr class="crm-membership-form-block-soft-credit-contact-id">
 -                        <td class="label">{$form.soft_credit_contact_id.label}</td>
 -                        <td>{$form.soft_credit_contact_id.html}</td>
 -                      </tr>
 -                    </table>
 -                  </td>
 -                </tr>
 -                  <tr class="crm-membership-form-block-financial_type_id">
 -                      <td class="label">{$form.financial_type_id.label}</td>
 -                      <td>{$form.financial_type_id.html}<br />
 -                      <span class="description">{ts}Select the appropriate financial type for this payment.{/ts}</span></td>
 -                </tr>
 -                <tr class="crm-membership-form-block-total_amount">
 -                  <td class="label">{$form.total_amount.label}</td>
 -                  <td>{$form.total_amount.html}<br />
 -                    <span class="description">{ts}Membership payment amount. A contribution record will be created for this amount.{/ts}</span><div class="totaltaxAmount"></div></td>
 -                </tr>
 -                <tr class="crm-membership-form-block-receive_date">
 -                  <td class="label">{$form.receive_date.label}</td>
 -                  <td>{include file="CRM/common/jcalendar.tpl" elementName=receive_date}</td>
 -                </tr>
 -                <tr class="crm-membership-form-block-payment_instrument_id">
 -                  <td class="label">{$form.payment_instrument_id.label}<span class="marker"> *</span></td>
 -                  <td>{$form.payment_instrument_id.html} {help id="payment_instrument_id" file="CRM/Contribute/Page/Tab.hlp"}</td>
 -                </tr>
 -                <tr id="checkNumber" class="crm-membership-form-block-check_number">
 -                  <td class="label">{$form.check_number.label}</td>
 -                  <td>{$form.check_number.html|crmAddClass:six}</td>
 -                </tr>
 -                {if $action neq 2 }
 -                  <tr class="crm-membership-form-block-trxn_id">
 -                    <td class="label">{$form.trxn_id.label}</td>
 -                    <td>{$form.trxn_id.html}</td>
 -                  </tr>
 -                {/if}
 -                <tr class="crm-membership-form-block-contribution_status_id">
 -                  <td class="label">{$form.contribution_status_id.label}</td>
 -                  <td>{$form.contribution_status_id.html}</td>
 -                </tr>
 -              </table>
 -            </fieldset>
 -          </td></tr>
 -          {else}
 -          <div class="spacer"></div>
 -        {/if}
 +         {/if}
 +        {include file="CRM/Member/Form/MembershipCommon.tpl"}
  
 -        {if $emailExists and $outBound_option != 2 }
 +        {if $emailExists and $outBound_option != 2}
            <tr id="send-receipt" class="crm-membership-form-block-send_receipt">
              <td class="label">{$form.send_receipt.label}</td><td>{$form.send_receipt.html}<br />
-             <span class="description">{ts 1=$emailExists}Automatically email a membership confirmation and receipt to %1?{/ts}</span></td>
+             <span class="description">{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}</span></td>
            </tr>
 -          {elseif $context eq 'standalone' and $outBound_option != 2 }
 +          {elseif $context eq 'standalone' and $outBound_option != 2}
            <tr id="email-receipt" style="display:none;">
              <td class="label">{$form.send_receipt.label}</td><td>{$form.send_receipt.html}<br />
-             <span class="description">{ts}Automatically email a membership confirmation and receipt to {/ts}<span id="email-address"></span>?</span></td>
+             <span class="description">{ts}Automatically email a membership confirmation and receipt to {/ts}<span id="email-address"></span>? {ts}OR if the payment is from a different contact, this email will only go to them.{/ts}</span></td>
            </tr>
          {/if}
          <tr id="fromEmail" style="display:none;">
Simple merge