Merge pull request #6456 from eileenmcnaughton/CRM-16993
[civicrm-core.git] / CRM / Member / Form / Membership.php
index ee8f992bbc1bdbef3ffea558ebfdd21ab7682e8c..5767b89d9d02923078b41e85bf4d22bb737e42bd 100644 (file)
  *
  * @package CRM
  * @copyright CiviCRM LLC (c) 2004-2015
- * $Id$
- *
  */
 
 /**
- * This class generates form components for offline membership form
- *
+ * This class generates form components for offline membership form.
  */
 class CRM_Member_Form_Membership extends CRM_Member_Form {
 
@@ -97,7 +94,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
   protected $_receiptContactId = NULL;
 
   /**
-   * Keep a class variable for ALL membeshipID's so
+   * Keep a class variable for ALL membership IDs so
    * postProcess hook function can do something with it
    *
    * @var array
@@ -105,7 +102,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
   protected $_membershipIDs = array();
 
   /**
-   * An array to hold a list of datefields on the form
+   * An array to hold a list of date fields on the form
    * so that they can be converted to ISO in a consistent manner
    *
    * @var array
@@ -114,26 +111,76 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
     'receive_date' => array('default' => 'now'),
   );
 
-  public function preProcess() {
-    //custom data related code
-    $this->_cdType = CRM_Utils_Array::value('type', $_GET);
-    $this->assign('cdType', FALSE);
-    if ($this->_cdType) {
-      $this->assign('cdType', TRUE);
-      return CRM_Custom_Form_CustomData::preProcess($this);
+  /**
+   * Get selected membership type from the form values.
+   *
+   * @param int $priceSetID
+   * @param array $params
+   *
+   * @return array
+   */
+  public static function getSelectedMemberships($priceSetID, $params) {
+    $memTypeSelected = array();
+    $priceFieldIDS = self::getPriceFieldIDs($params);
+    if ($priceSetID && is_array($priceFieldIDS)) {
+      foreach ($priceFieldIDS as $priceFieldId) {
+        if ($id = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) {
+          $memTypeSelected[$id] = $id;
+        }
+      }
+    }
+    else {
+      $memTypeSelected = array($params['membership_type_id'][1] => $params['membership_type_id'][1]);
+    }
+    return $memTypeSelected;
+  }
+
+  /**
+   * Extract price set fields and values from $params.
+   *
+   * @param array $params
+   *
+   * @return array
+   */
+  public static function getPriceFieldIDs($params) {
+    $priceFieldIDS = $priceSet = $fieldIds = array();
+    if (isset(self::$priceSet) && is_array(self::$priceSet)) {
+      $priceSet = self::$_priceSet;
+      if (isset($priceSet['fields']) && is_array($priceSet['fields'])) {
+        $fieldIds = array_keys($priceSet['fields']);
+      }
+    }
+    foreach ($fieldIds as $fieldId) {
+      if (!empty($params['price_' . $fieldId])) {
+        if (is_array($params['price_' . $fieldId])) {
+          foreach ($params['price_' . $fieldId] as $priceFldVal => $isSet) {
+            if ($isSet) {
+              $priceFieldIDS[] = $priceFldVal;
+            }
+          }
+        }
+        else {
+          $priceFieldIDS[] = $params['price_' . $fieldId];
+        }
+      }
     }
+    return $priceFieldIDS;
+  }
 
+  /**
+   * Form preProcess function.
+   *
+   * @throws \Exception
+   */
+  public function preProcess() {
+    // This string makes up part of the class names, differentiating them (not sure why) from the membership fields.
+    $this->assign('formClass', 'membership');
     parent::preProcess();
     // get price set id.
     $this->_priceSetId = CRM_Utils_Array::value('priceSetId', $_GET);
     $this->set('priceSetId', $this->_priceSetId);
     $this->assign('priceSetId', $this->_priceSetId);
 
-    // check for edit permission
-    if (!CRM_Core_Permission::checkActionPermission('CiviMember', $this->_action)) {
-      CRM_Core_Error::fatal(ts('You do not have permission to access this page.'));
-    }
-
     if ($this->_action & CRM_Core_Action::DELETE) {
       $contributionID = CRM_Member_BAO_Membership::getMembershipContributionId($this->_id);
       // check delete permission for contribution
@@ -160,7 +207,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
         if (count($cMemTypes) > 0) {
           $memberorgs = CRM_Member_BAO_MembershipType::getMemberOfContactByMemTypes($cMemTypes);
           $mems_by_org = array();
-          foreach ($contactMemberships as $memid => $mem) {
+          foreach ($contactMemberships as $mem) {
             $mem['member_of_contact_id'] = CRM_Utils_Array::value($mem['membership_type_id'], $memberorgs);
             if (!empty($mem['membership_end_date'])) {
               $mem['membership_end_date'] = CRM_Utils_Date::customformat($mem['membership_end_date']);
@@ -218,16 +265,9 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
   }
 
   /**
-   * Set default values for the form. MobileProvider that in edit/view mode
-   * the default values are retrieved from the database
-   *
-   *
-   * @return void
+   * Set default values for the form.
    */
   public function setDefaultValues() {
-    if ($this->_cdType) {
-      return CRM_Custom_Form_CustomData::setDefaultValues($this);
-    }
 
     if ($this->_priceSetId) {
       return CRM_Price_BAO_PriceSet::setDefaultPriceSet($this, $defaults);
@@ -313,6 +353,7 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
       unset($defaults['record_contribution']);
     }
 
+    $subscriptionCancelled = FALSE;
     if (!empty($defaults['id'])) {
       $subscriptionCancelled = CRM_Member_BAO_Membership::isSubscriptionCancelled($this->_id);
     }
@@ -345,11 +386,11 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
       $billingDefaults = $this->getProfileDefaults('Billing', $this->_contactID);
       $defaults = array_merge($defaults, $billingDefaults);
 
-      //             // hack to simplify credit card entry for testing
-      //             $defaults['credit_card_type']     = 'Visa';
-      //             $defaults['credit_card_number']   = '4807731747657838';
-      //             $defaults['cvv2']                 = '000';
-      //             $defaults['credit_card_exp_date'] = array( 'Y' => '2012', 'M' => '05' );
+      // hack to simplify credit card entry for testing
+      // $defaults['credit_card_type']     = 'Visa';
+      // $defaults['credit_card_number']   = '4807731747657838';
+      // $defaults['cvv2']                 = '000';
+      // $defaults['credit_card_exp_date'] = array( 'Y' => '2012', 'M' => '05' );
     }
 
     $dates = array('join_date', 'start_date', 'end_date');
@@ -373,17 +414,12 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
 
   /**
    * Build the form object.
-   *
-   * @return void
    */
   public function buildQuickForm() {
-    if ($this->_cdType) {
-      return CRM_Custom_Form_CustomData::buildQuickForm($this);
-    }
 
     $this->assign('taxRates', json_encode(CRM_Core_PseudoConstant::getTaxRates()));
-    $config = CRM_Core_Config::singleton();
-    $this->assign('currency', $config->defaultCurrencySymbol);
+
+    $this->assign('currency', CRM_Core_Config::singleton()->defaultCurrencySymbol);
     $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings');
     $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings);
     if (isset($invoicing)) {
@@ -477,48 +513,39 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
 
     $selOrgMemType[0][0] = $selMemTypeOrg[0] = ts('- select -');
 
-    $dao = new CRM_Member_DAO_MembershipType();
-    $dao->domain_id = CRM_Core_Config::domainID();
-    $dao->find();
-
     // retrieve all memberships
-    $allMemberships = CRM_Member_BAO_Membership::buildMembershipTypeValues($this);
+    $allMembershipInfo = array();
+    foreach ($this->allMembershipTypeDetails as $key => $values) {
+      if ($this->_mode && empty($values['minimum_fee'])) {
+        continue;
+      }
+      else {
+        $memberOfContactId = CRM_Utils_Array::value('member_of_contact_id', $values);
+        if (empty($selMemTypeOrg[$memberOfContactId])) {
+          $selMemTypeOrg[$memberOfContactId] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
+            $memberOfContactId,
+            'display_name',
+            'id'
+          );
 
-    $allMembershipInfo = $membershipType = array();
-    foreach ($allMemberships as $key => $values) {
-      if (!empty($values['is_active'])) {
-        $membershipType[$key] = CRM_Utils_Array::value('name', $values);
-        if ($this->_mode && empty($values['minimum_fee'])) {
-          continue;
+          $selOrgMemType[$memberOfContactId][0] = ts('- select -');
         }
-        else {
-          $memberOfContactId = CRM_Utils_Array::value('member_of_contact_id', $values);
-          if (empty($selMemTypeOrg[$memberOfContactId])) {
-            $selMemTypeOrg[$memberOfContactId] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
-              $memberOfContactId,
-              'display_name',
-              'id'
-            );
-
-            $selOrgMemType[$memberOfContactId][0] = ts('- select -');
-          }
-          if (empty($selOrgMemType[$memberOfContactId][$key])) {
-            $selOrgMemType[$memberOfContactId][$key] = CRM_Utils_Array::value('name', $values);
-          }
+        if (empty($selOrgMemType[$memberOfContactId][$key])) {
+          $selOrgMemType[$memberOfContactId][$key] = CRM_Utils_Array::value('name', $values);
         }
-
-        // build membership info array, which is used when membership type is selected to:
-        // - set the payment information block
-        // - set the max related block
-        $allMembershipInfo[$key] = array(
-          'financial_type_id' => CRM_Utils_Array::value('financial_type_id', $values),
-          'total_amount' => CRM_Utils_Money::format($values['minimum_fee'], NULL, '%a'),
-          'total_amount_numeric' => CRM_Utils_Array::value('minimum_fee', $values),
-          'auto_renew' => CRM_Utils_Array::value('auto_renew', $values),
-          'has_related' => isset($values['relationship_type_id']),
-          'max_related' => CRM_Utils_Array::value('max_related', $values),
-        );
       }
+
+      // build membership info array, which is used when membership type is selected to:
+      // - set the payment information block
+      // - set the max related block
+      $allMembershipInfo[$key] = array(
+        'financial_type_id' => CRM_Utils_Array::value('financial_type_id', $values),
+        'total_amount' => CRM_Utils_Money::format($values['minimum_fee'], NULL, '%a'),
+        'total_amount_numeric' => CRM_Utils_Array::value('minimum_fee', $values),
+        'auto_renew' => CRM_Utils_Array::value('auto_renew', $values),
+        'has_related' => isset($values['relationship_type_id']),
+        'max_related' => CRM_Utils_Array::value('max_related', $values),
+      );
     }
 
     $this->assign('allMembershipInfo', json_encode($allMembershipInfo));
@@ -535,63 +562,14 @@ class CRM_Member_Form_Membership extends CRM_Member_Form {
       $selOrgMemType[$index] = $orgMembershipType;
     }
 
-    $memTypeJs = array('onChange' => "CRM.buildCustomData( 'Membership', this.value );");
-
-    //build the form for auto renew.
-    $recurProcessor = $autoRenew = array();
-    if ($this->_mode || ($this->_action & CRM_Core_Action::UPDATE)) {
-      $autoRenewElement = $this->addElement('checkbox',
-        'auto_renew',
-        ts('Membership renewed automatically'),
-        NULL,
-        array('onclick' => "buildReceiptANDNotice( );")
-      );
+    $memTypeJs = array(
+      'onChange' => "buildMaxRelated(this.value,true); CRM.buildCustomData('Membership', this.value);",
+    );
 
-      if ($this->_mode) {
-        //get the valid recurring processors.
-        $test = strtolower($this->_mode) == 'test' ? TRUE : FALSE;
-        $recurring = CRM_Core_PseudoConstant::paymentProcessor(FALSE, $test, 'is_recur = 1');
-        $recurProcessor = array_intersect_key($this->_processors, $recurring);
-        $autoRenew = array();
-        if (!empty($recurProcessor)) {
-          if (!empty($membershipType)) {
-            $sql = '
-SELECT  id,
-        auto_renew,
-        duration_unit,
-        duration_interval
- FROM   civicrm_membership_type
-WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
-            $recurMembershipTypes = CRM_Core_DAO::executeQuery($sql);
-            while ($recurMembershipTypes->fetch()) {
-              $autoRenew[$recurMembershipTypes->id] = $recurMembershipTypes->auto_renew;
-              foreach (array(
-                         'id',
-                         'auto_renew',
-                         'duration_unit',
-                         'duration_interval',
-                       ) as $fld) {
-                $this->_recurMembershipTypes[$recurMembershipTypes->id][$fld] = $recurMembershipTypes->$fld;
-              }
-            }
-          }
-          $memTypeJs = array(
-            'onChange' =>
-            "CRM.buildCustomData( 'Membership', this.value ); buildAutoRenew(this.value, null );",
-          );
-        }
-      }
-    }
-    $allowAutoRenew = FALSE;
-    if ($this->_mode && !empty($recurProcessor)) {
-      $allowAutoRenew = TRUE;
+    if (!empty($this->_recurPaymentProcessors)) {
+      $memTypeJs['onChange'] = "" . $memTypeJs['onChange'] . 'buildAutoRenew(this.value, null);';
     }
-    $this->assign('allowAutoRenew', $allowAutoRenew);
-    $this->assign('autoRenewOptions', json_encode($autoRenew));
-    $this->assign('recurProcessor', json_encode($recurProcessor));
 
-    // for max_related: a little JS to show/hide & set default value
-    $memTypeJs['onChange'] = "buildMaxRelated(this.value,true); " . $memTypeJs['onChange'];
     $this->add('text', 'max_related', ts('Max related'),
       CRM_Core_DAO::getAttribute('CRM_Member_DAO_Membership', 'max_related')
     );
@@ -652,13 +630,17 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
       $this->addDate('receive_date', ts('Received'), FALSE, array('formatType' => 'activityDateTime'));
 
       $this->add('select', 'payment_instrument_id',
-        ts('Paid By'),
+        ts('Payment Method'),
         array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::paymentInstrument(),
         FALSE, array('onChange' => "return showHideByValue('payment_instrument_id','4','checkNumber','table-row','select',false);")
       );
       $this->add('text', 'trxn_id', ts('Transaction ID'));
       $this->addRule('trxn_id', ts('Transaction ID already exists in Database.'),
-        'objectExists', array('CRM_Contribute_DAO_Contribution', $this->_id, 'trxn_id')
+        'objectExists', array(
+          'CRM_Contribute_DAO_Contribution',
+          $this->_id,
+          'trxn_id',
+        )
       );
 
       $allowStatuses = array();
@@ -695,9 +677,8 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
       array('' => ts('- select -')) + CRM_Contribute_PseudoConstant::financialType()
     );
 
-    //CRM-10223 - allow contribution to be recorded against different contact
-    // causes a conflict in standalone mode so skip in standalone for now
     $this->addElement('checkbox', 'is_different_contribution_contact', ts('Record Payment from a Different Contact?'));
+
     $this->addSelect('soft_credit_type_id', array('entity' => 'contribution_soft'));
     $this->addEntityRef('soft_credit_contact_id', ts('Payment From'), array('create' => TRUE));
 
@@ -709,7 +690,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
 
     $this->add('select', 'from_email_address', ts('Receipt From'), $this->_fromEmails);
 
-    $this->add('textarea', 'receipt_text_signup', ts('Receipt Message'));
+    $this->add('textarea', 'receipt_text', ts('Receipt Message'));
 
     // Retrieve the name and email of the contact - this will be the TO for receipt email
     if ($this->_contactID) {
@@ -742,10 +723,8 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
 
     $this->addFormRule(array('CRM_Member_Form_Membership', 'formRule'), $this);
 
-    $mailingInfo = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
-      'mailing_backend'
-    );
-    $this->assign('outBound_option', $mailingInfo['outBound_option']);
+    $this->assign('outBound_option', CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::MAILING_PREFERENCES_NAME,
+      'mailing_backend'));
 
     parent::buildQuickForm();
   }
@@ -756,8 +735,8 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
    * @param array $params
    *   (ref.) an assoc array of name/value pairs.
    *
-   * @param $files
-   * @param $self
+   * @param array $files
+   * @param CRM_Member_Form_Membership $self
    *
    * @throws CiviCRM_API3_Exception
    * @return bool|array
@@ -768,49 +747,27 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
 
     $priceSetId = CRM_Utils_Array::value('price_set_id', $params);
 
+    $selectedMemberships = self::getSelectedMemberships($priceSetId, $params);
+
     if ($priceSetId) {
       CRM_Price_BAO_PriceField::priceSetValidation($priceSetId, $params, $errors);
 
-      $priceFieldIDS = array();
-      foreach ($self->_priceSet['fields'] as $priceIds => $dontCare) {
-
-        if (!empty($params['price_' . $priceIds])) {
-          if (is_array($params['price_' . $priceIds])) {
-            foreach ($params['price_' . $priceIds] as $priceFldVal => $isSet) {
-              if ($isSet) {
-                $priceFieldIDS[] = $priceFldVal;
-              }
-            }
-          }
-          else {
-            $priceFieldIDS[] = $params['price_' . $priceIds];
-          }
-        }
-      }
+      $priceFieldIDS = self::getPriceFieldIDs($params, $self);
 
       if (!empty($priceFieldIDS)) {
         $ids = implode(',', $priceFieldIDS);
 
         $count = CRM_Price_BAO_PriceSet::getMembershipCount($ids);
-        foreach ($count as $id => $occurance) {
-          if ($occurance > 1) {
+        foreach ($count as $occurrence) {
+          if ($occurrence > 1) {
             $errors['_qf_default'] = ts('Select at most one option associated with the same membership type.');
           }
         }
-
-        foreach ($priceFieldIDS as $priceFieldId) {
-          if ($id = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceFieldValue', $priceFieldId, 'membership_type_id')) {
-            $self->_memTypeSelected[$id] = $id;
-          }
-        }
       }
     }
     elseif (empty($params['membership_type_id'][1])) {
       $errors['membership_type_id'] = ts('Please select a membership type.');
     }
-    else {
-      $self->_memTypeSelected[] = $params['membership_type_id'][1];
-    }
 
     if (!$priceSetId) {
       $numterms = CRM_Utils_Array::value('num_terms', $params);
@@ -820,12 +777,12 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
     }
 
     // Return error if empty $self->_memTypeSelected
-    if ($priceSetId && empty($errors) && empty($self->_memTypeSelected)) {
+    if ($priceSetId && empty($errors) && empty($selectedMemberships)) {
       $errors['_qf_default'] = ts('Select at least one membership option.');
     }
 
-    if (!empty($errors) && (count($self->_memTypeSelected) > 1)) {
-      $memberOfContacts = CRM_Member_BAO_MembershipType::getMemberOfContactByMemTypes($self->_memTypeSelected);
+    if (!empty($errors) && (count($selectedMemberships) > 1)) {
+      $memberOfContacts = CRM_Member_BAO_MembershipType::getMemberOfContactByMemTypes($selectedMemberships);
       $duplicateMemberOfContacts = array_count_values($memberOfContacts);
       foreach ($duplicateMemberOfContacts as $countDuplicate) {
         if ($countDuplicate > 1) {
@@ -847,7 +804,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
     }
 
     if (!empty($params['record_contribution']) && empty($params['payment_instrument_id'])) {
-      $errors['payment_instrument_id'] = ts('Paid By is a required field.');
+      $errors['payment_instrument_id'] = ts('Payment Method is a required field.');
     }
 
     if (!empty($params['is_different_contribution_contact'])) {
@@ -869,7 +826,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
 
       $joinDate = CRM_Utils_Date::processDate($params['join_date']);
 
-      foreach ($self->_memTypeSelected as $memType) {
+      foreach ($selectedMemberships as $memType) {
         $startDate = NULL;
         if (!empty($params['start_date'])) {
           $startDate = CRM_Utils_Date::processDate($params['start_date']);
@@ -918,8 +875,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
           }
         }
 
-        //  Default values for start and end dates if not supplied
-        //  on the form
+        // Default values for start and end dates if not supplied on the form.
         $defaultDates = CRM_Member_BAO_MembershipType::getDatesForMembershipType($memType,
           $joinDate,
           $startDate,
@@ -994,195 +950,364 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
 
   /**
    * Process the form submission.
-   *
-   *
-   * @return void
    */
   public function postProcess() {
     if ($this->_action & CRM_Core_Action::DELETE) {
       CRM_Member_BAO_Membership::del($this->_id);
       return;
     }
+    // get the submitted form values.
+    $this->_params = $this->controller->exportValues($this->_name);
 
-    $allMemberStatus = CRM_Member_PseudoConstant::membershipStatus();
-    $allContributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
-
-    $isTest = ($this->_mode == 'test') ? 1 : 0;
-
-    $lineItems = NULL;
-    if (!empty($this->_lineItem)) {
-      $lineItems = $this->_lineItem;
-    }
+    $this->submit($this->_params);
 
-    $config = CRM_Core_Config::singleton();
-    // get the submitted form values.
-    $this->_params = $formValues = $this->controller->exportValues($this->_name);
-    $this->convertDateFieldsToMySQL($formValues);
+    $this->setUserContext();
+  }
 
-    $params = $softParams = $ids = array();
+  /**
+   * Send email receipt.
+   *
+   * @param CRM_Core_Form $form
+   *   Form object.
+   * @param array $formValues
+   * @param object $membership
+   *   Object.
+   *
+   * @return bool
+   *   true if mail was sent successfully
+   */
+  public static function emailReceipt(&$form, &$formValues, &$membership) {
+    // retrieve 'from email id' for acknowledgement
+    $receiptFrom = $formValues['from_email_address'];
 
-    $membershipTypeValues = array();
-    foreach ($this->_memTypeSelected as $memType) {
-      $membershipTypeValues[$memType]['membership_type_id'] = $memType;
+    if (!empty($formValues['payment_instrument_id'])) {
+      $paymentInstrument = CRM_Contribute_PseudoConstant::paymentInstrument();
+      $formValues['paidBy'] = $paymentInstrument[$formValues['payment_instrument_id']];
     }
 
-    //take the required membership recur values.
-    if ($this->_mode && !empty($this->_params['auto_renew'])) {
-      $params['is_recur'] = $this->_params['is_recur'] = $formValues['is_recur'] = TRUE;
-      $mapping = array(
-        'frequency_interval' => 'duration_interval',
-        'frequency_unit' => 'duration_unit',
-      );
-
-      $count = 0;
-      foreach ($this->_memTypeSelected as $memType) {
-        $recurMembershipTypeValues = CRM_Utils_Array::value($memType,
-          $this->_recurMembershipTypes, array()
-        );
-        foreach ($mapping as $mapVal => $mapParam) {
-          $membershipTypeValues[$memType][$mapVal] = CRM_Utils_Array::value($mapParam,
-            $recurMembershipTypeValues
-          );
-          if (!$count) {
-            $this->_params[$mapVal] = $formValues[$mapVal] = CRM_Utils_Array::value($mapParam,
-              $recurMembershipTypeValues
-            );
-          }
+    // 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;
         }
-        $count++;
       }
     }
 
-    // process price set and get total amount and line items.
-    $lineItem = array();
-    $priceSetId = NULL;
-    if (!$priceSetId = CRM_Utils_Array::value('price_set_id', $formValues)) {
-      CRM_Member_BAO_Membership::createLineItems($this, $formValues['membership_type_id'], $priceSetId);
-    }
-    $isQuickConfig = 0;
-    if ($this->_priceSetId && CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) {
-      $isQuickConfig = 1;
+    $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);
     }
 
-    $termsByType = array();
-    if ($priceSetId) {
-      CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'],
-        $this->_params, $lineItem[$priceSetId]);
-      if (CRM_Utils_Array::value('tax_amount', $this->_params)) {
-        $params['tax_amount'] = $this->_params['tax_amount'];
-      }
-      $params['total_amount'] = CRM_Utils_Array::value('amount', $this->_params);
-      $submittedFinancialType = CRM_Utils_Array::value('financial_type_id', $formValues);
-      if (!empty($lineItem[$priceSetId])) {
-        foreach ($lineItem[$priceSetId] as &$li) {
-          if (!empty($li['membership_type_id'])) {
-            if (!empty($li['membership_num_terms'])) {
-              $termsByType[$li['membership_type_id']] = $li['membership_num_terms'];
-            }
-          }
+    CRM_Core_BAO_UFGroup::getValues($formValues['contact_id'], $customFields, $customValues, FALSE, $members);
+    $form->assign('customValues', $customValues);
 
-          ///CRM-11529 for quick config backoffice transactions
-          //when financial_type_id is passed in form, update the
-          //lineitems with the financial type selected in form
-          if ($isQuickConfig && $submittedFinancialType) {
-            $li['financial_type_id'] = $submittedFinancialType;
-          }
-        }
+    if ($form->_mode) {
+      $name = '';
+      if (!empty($form->_params['billing_first_name'])) {
+        $name = $form->_params['billing_first_name'];
       }
-    }
 
-    $this->storeContactFields($formValues);
+      if (!empty($form->_params['billing_middle_name'])) {
+        $name .= " {$form->_params['billing_middle_name']}";
+      }
 
-    $params['contact_id'] = $this->_contactID;
+      if (!empty($form->_params['billing_last_name'])) {
+        $name .= " {$form->_params['billing_last_name']}";
+      }
 
-    $fields = array(
-      'status_id',
-      'source',
-      'is_override',
-      'campaign_id',
-    );
+      $form->assign('billingName', $name);
 
-    foreach ($fields as $f) {
-      $params[$f] = CRM_Utils_Array::value($f, $formValues);
-    }
+      // 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));
 
-    // fix for CRM-3724
-    // when is_override false ignore is_admin statuses during membership
-    // status calculation. similarly we did fix for import in CRM-3570.
-    if (empty($params['is_override'])) {
-      $params['exclude_is_admin'] = TRUE;
+      $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);
     }
 
-    // process date params to mysql date format.
-    $dateTypes = array(
-      'join_date' => 'joinDate',
-      'start_date' => 'startDate',
-      'end_date' => 'endDate',
-    );
-    foreach ($dateTypes as $dateField => $dateVariable) {
-      $$dateVariable = CRM_Utils_Date::processDate($formValues[$dateField]);
-    }
+    $form->assign('module', 'Membership');
+    $form->assign('contactID', $formValues['contact_id']);
 
-    $memTypeNumTerms = empty($termsByType) ? CRM_Utils_Array::value('num_terms', $formValues) : NULL;
+    $form->assign('membershipID', CRM_Utils_Array::value('membership_id', $form->_params, CRM_Utils_Array::value('membership_id', $form->_defaultValues)));
 
-    $calcDates = array();
-    foreach ($this->_memTypeSelected as $memType) {
-      if (empty($memTypeNumTerms)) {
-        $memTypeNumTerms = CRM_Utils_Array::value($memType, $termsByType, 1);
-      }
-      $calcDates[$memType] = CRM_Member_BAO_MembershipType::getDatesForMembershipType($memType,
-        $joinDate, $startDate, $endDate, $memTypeNumTerms
-      );
+    if (!empty($formValues['contribution_id'])) {
+      $form->assign('contributionID', $formValues['contribution_id']);
     }
-
-    foreach ($calcDates as $memType => $calcDate) {
-      foreach (array_keys($dateTypes) as $d) {
-        //first give priority to form values then calDates.
-        $date = CRM_Utils_Array::value($d, $formValues);
-        if (!$date) {
-          $date = CRM_Utils_Array::value($d, $calcDate);
-        }
-
-        $membershipTypeValues[$memType][$d] = CRM_Utils_Date::processDate($date);
-        //$params[$d] = CRM_Utils_Date::processDate( $date );
-      }
+    elseif (isset($form->_onlinePendingContributionId)) {
+      $form->assign('contributionID', $form->_onlinePendingContributionId);
     }
 
-    // max related memberships - take from form or inherit from membership type
-    foreach ($this->_memTypeSelected as $memType) {
-      if (array_key_exists('max_related', $formValues)) {
-        $membershipTypeValues[$memType]['max_related'] = CRM_Utils_Array::value('max_related', $formValues);
+    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'));
+    }
+
+    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);
+
+    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'));
       }
+      $form->assign('membership_name', CRM_Member_PseudoConstant::membershipType($membership->membership_type_id));
     }
 
+    $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'];
+      }
+    }
+    $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;
+    }
+
+    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;
+  }
+
+  /**
+   * Submit function.
+   *
+   * This is also accessed by unit tests.
+   *
+   * @param array $formValues
+   *
+   * @return array
+   */
+  public function submit($formValues) {
+    $isTest = ($this->_mode == 'test') ? 1 : 0;
+
+    $joinDate = $startDate = $endDate = NULL;
+    $membershipTypes = $membership = $calcDate = array();
+    $membershipType = NULL;
+
+    $mailSend = FALSE;
+    $priceSetID = CRM_Utils_Array::value('price_set_id', $formValues);
+
+    $params = $softParams = $ids = array();
+
+    $allMemberStatus = CRM_Member_PseudoConstant::membershipStatus();
+    $allContributionStatus = CRM_Contribute_PseudoConstant::contributionStatus();
+
     if ($this->_id) {
       $ids['membership'] = $params['id'] = $this->_id;
     }
+    $ids['userId'] = CRM_Core_Session::singleton()->get('userID');
 
-    $session = CRM_Core_Session::singleton();
-    $ids['userId'] = $session->get('userID');
+    // Set variables that we normally get from context.
+    // In form mode these are set in preProcess.
+    //TODO: set memberships, fixme
+    $this->setContextVariables($formValues);
+    $this->_memTypeSelected = self::getSelectedMemberships($priceSetID, $formValues);
 
-    // membership type custom data
+    $config = CRM_Core_Config::singleton();
+
+    $this->convertDateFieldsToMySQL($formValues);
+
+    $membershipTypeValues = array();
     foreach ($this->_memTypeSelected as $memType) {
-      $customFields = CRM_Core_BAO_CustomField::getFields('Membership', FALSE, FALSE,
-        $memType
+      $membershipTypeValues[$memType]['membership_type_id'] = $memType;
+    }
+
+    //take the required membership recur values.
+    if ($this->_mode && !empty($formValues['auto_renew'])) {
+      $params['is_recur'] = $this->_params['is_recur'] = $formValues['is_recur'] = TRUE;
+      $mapping = array(
+        'frequency_interval' => 'duration_interval',
+        'frequency_unit' => 'duration_unit',
       );
 
-      $customFields = CRM_Utils_Array::crmArrayMerge($customFields,
-        CRM_Core_BAO_CustomField::getFields('Membership',
-          FALSE, FALSE,
-          NULL, NULL, TRUE
-        )
+      $count = 0;
+      foreach ($this->_memTypeSelected as $memType) {
+        $recurMembershipTypeValues = CRM_Utils_Array::value($memType,
+          $this->_recurMembershipTypes, array()
+        );
+        foreach ($mapping as $mapVal => $mapParam) {
+          $membershipTypeValues[$memType][$mapVal] = CRM_Utils_Array::value($mapParam,
+            $recurMembershipTypeValues
+          );
+          if (!$count) {
+            $this->_params[$mapVal] = $formValues[$mapVal] = CRM_Utils_Array::value($mapParam,
+              $recurMembershipTypeValues
+            );
+          }
+        }
+        $count++;
+      }
+    }
+
+    // process price set and get total amount and line items.
+    if (!$priceSetID) {
+      $priceSetID = CRM_Member_BAO_Membership::createLineItems(
+        $this,
+        $formValues['membership_type_id'],
+        NULL
       );
+    }
+    $isQuickConfig = FALSE;
+    if (CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $priceSetID, 'is_quick_config')) {
+      $isQuickConfig = 1;
+    }
+    $termsByType = array();
 
-      $membershipTypeValues[$memType]['custom'] = CRM_Core_BAO_CustomField::postProcess($formValues,
-        $customFields,
-        $this->_id,
-        'Membership'
+    $lineItem = array();
+    CRM_Price_BAO_PriceSet::processAmount($this->_priceSet['fields'],
+      $this->_params, $lineItem[$priceSetID]);
+    if (CRM_Utils_Array::value('tax_amount', $this->_params)) {
+      $params['tax_amount'] = $this->_params['tax_amount'];
+    }
+    $params['total_amount'] = CRM_Utils_Array::value('amount', $this->_params);
+    $submittedFinancialType = CRM_Utils_Array::value('financial_type_id', $formValues);
+    if (!empty($lineItem[$priceSetID])) {
+      foreach ($lineItem[$priceSetID] as &$li) {
+        if (!empty($li['membership_type_id'])) {
+          if (!empty($li['membership_num_terms'])) {
+            $termsByType[$li['membership_type_id']] = $li['membership_num_terms'];
+          }
+        }
+
+        ///CRM-11529 for quick config backoffice transactions
+        //when financial_type_id is passed in form, update the
+        //lineitems with the financial type selected in form
+        if ($isQuickConfig && $submittedFinancialType) {
+          $li['financial_type_id'] = $submittedFinancialType;
+        }
+      }
+    }
+
+    $this->storeContactFields($formValues);
+
+    $params['contact_id'] = $this->_contactID;
+
+    $fields = array(
+      'status_id',
+      'source',
+      'is_override',
+      'campaign_id',
+    );
+
+    foreach ($fields as $f) {
+      $params[$f] = CRM_Utils_Array::value($f, $formValues);
+    }
+
+    // fix for CRM-3724
+    // when is_override false ignore is_admin statuses during membership
+    // status calculation. similarly we did fix for import in CRM-3570.
+    if (empty($params['is_override'])) {
+      $params['exclude_is_admin'] = TRUE;
+    }
+
+    // process date params to mysql date format.
+    $dateTypes = array(
+      'join_date' => 'joinDate',
+      'start_date' => 'startDate',
+      'end_date' => 'endDate',
+    );
+    foreach ($dateTypes as $dateField => $dateVariable) {
+      $$dateVariable = CRM_Utils_Date::processDate($formValues[$dateField]);
+    }
+
+    $memTypeNumTerms = empty($termsByType) ? CRM_Utils_Array::value('num_terms', $formValues) : NULL;
+
+    $calcDates = array();
+    foreach ($this->_memTypeSelected as $memType) {
+      if (empty($memTypeNumTerms)) {
+        $memTypeNumTerms = CRM_Utils_Array::value($memType, $termsByType, 1);
+      }
+      $calcDates[$memType] = CRM_Member_BAO_MembershipType::getDatesForMembershipType($memType,
+        $joinDate, $startDate, $endDate, $memTypeNumTerms
       );
     }
 
+    foreach ($calcDates as $memType => $calcDate) {
+      foreach (array_keys($dateTypes) as $d) {
+        //first give priority to form values then calDates.
+        $date = CRM_Utils_Array::value($d, $formValues);
+        if (!$date) {
+          $date = CRM_Utils_Array::value($d, $calcDate);
+        }
+
+        $membershipTypeValues[$memType][$d] = CRM_Utils_Date::processDate($date);
+      }
+    }
+
+    // max related memberships - take from form or inherit from membership type
     foreach ($this->_memTypeSelected as $memType) {
+      if (array_key_exists('max_related', $formValues)) {
+        $membershipTypeValues[$memType]['max_related'] = CRM_Utils_Array::value('max_related', $formValues);
+      }
+      $membershipTypeValues[$memType]['custom'] = CRM_Core_BAO_CustomField::postProcess($formValues,
+        $this->_id,
+        'Membership'
+      );
       $membershipTypes[$memType] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType',
         $memType
       );
@@ -1191,7 +1316,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
     $membershipType = implode(', ', $membershipTypes);
 
     // Retrieve the name and email of the current user - this will be the FROM for the receipt email
-    list($userName, $userEmail) = CRM_Contact_BAO_Contact_Location::getEmailDetails($ids['userId']);
+    list($userName) = CRM_Contact_BAO_Contact_Location::getEmailDetails($ids['userId']);
 
     //CRM-13981, allow different person as a soft-contributor of chosen type
     if ($this->_contributorContactID != $this->_contactID) {
@@ -1220,9 +1345,9 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
       if (!$this->_onlinePendingContributionId) {
         if (empty($formValues['source'])) {
           $params['contribution_source'] = ts('%1 Membership: Offline signup (by %2)', array(
-              1 => $membershipType,
-              2 => $userName,
-            ));
+            1 => $membershipType,
+            2 => $userName,
+          ));
         }
         else {
           $params['contribution_source'] = $formValues['source'];
@@ -1255,7 +1380,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
     }
     $createdMemberships = array();
     if ($this->_mode) {
-      if (empty($formValues['total_amount']) && !$priceSetId) {
+      if (empty($formValues['total_amount']) && !$priceSetID) {
         // if total amount not provided minimum for membership type is used
         $params['total_amount'] = $formValues['total_amount'] = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType',
           $formValues['membership_type_id'][1], 'minimum_fee'
@@ -1265,9 +1390,9 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
         $params['total_amount'] = CRM_Utils_Array::value('total_amount', $formValues, 0);
       }
 
-      if ($priceSetId && !$isQuickConfig) {
+      if ($priceSetID && !$isQuickConfig) {
         $params['financial_type_id'] = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet',
-          $priceSetId,
+          $priceSetID,
           'financial_type_id'
         );
       }
@@ -1306,8 +1431,6 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
         $fields["email-{$this->_bltID}"] = 1;
       }
 
-      $ctype = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactID, 'contact_type');
-
       $nameFields = array('first_name', 'middle_name', 'last_name');
 
       foreach ($nameFields as $name) {
@@ -1320,21 +1443,22 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
       if ($this->_contributorContactID == $this->_contactID) {
         //see CRM-12869 for discussion of why we don't do this for separate payee payments
         CRM_Contact_BAO_Contact::createProfileContact($formValues, $fields,
-          $this->_contributorContactID, NULL, NULL, $ctype
+          $this->_contributorContactID, NULL, NULL,
+          CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $this->_contactID, 'contact_type')
         );
       }
 
       // add all the additional payment params we need
-      $this->_params["state_province-{$this->_bltID}"] = $this->_params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($this->_params["billing_state_province_id-{$this->_bltID}"]);
-      $this->_params["country-{$this->_bltID}"] = $this->_params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($this->_params["billing_country_id-{$this->_bltID}"]);
+      $this->_params["state_province-{$this->_bltID}"] = $this->_params["billing_state_province-{$this->_bltID}"]
+        = CRM_Core_PseudoConstant::stateProvinceAbbreviation($formValues["billing_state_province_id-{$this->_bltID}"]);
+      $this->_params["country-{$this->_bltID}"] = $this->_params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($formValues["billing_country_id-{$this->_bltID}"]);
 
-      $this->_params['year'] = CRM_Core_Payment_Form::getCreditCardExpirationYear($this->_params);
-      $this->_params['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($this->_params);
+      $this->_params['year'] = CRM_Core_Payment_Form::getCreditCardExpirationYear($formValues);
+      $this->_params['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($formValues);
       $this->_params['ip_address'] = CRM_Utils_System::ipAddress();
       $this->_params['amount'] = $params['total_amount'];
       $this->_params['currencyID'] = $config->defaultCurrency;
-      $this->_params['description'] = ts('Office Credit Card Membership Signup Contribution');
-      $this->_params['payment_action'] = 'Sale';
+      $this->_params['description'] = ts("Contribution submitted by a staff person using member's credit card for signup");
       $this->_params['invoiceID'] = md5(uniqid(rand(), TRUE));
       $this->_params['financial_type_id'] = $params['financial_type_id'];
 
@@ -1358,24 +1482,30 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
       CRM_Core_Payment_Form::mapParams($this->_bltID, $this->_params, $paymentParams, TRUE);
 
       // CRM-7137 -for recurring membership,
-      // we do need contribution and recuring records.
+      // we do need contribution and recurring records.
       $result = NULL;
       if (!empty($paymentParams['is_recur'])) {
-        $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');
-        }
+        $financialType = new CRM_Financial_DAO_FinancialType();
+        $financialType->id = $params['financial_type_id'];
+        $financialType->find(TRUE);
 
-        $contribution = CRM_Contribute_Form_Contribution_Confirm::processContribution($this,
+        $contribution = CRM_Contribute_Form_Contribution_Confirm::processFormContribution($this,
           $paymentParams,
-          $result,
-          $this->_contributorContactID,
-          $contributionType,
+          NULL,
+          array(
+            'contact_id' => $this->_contributorContactID,
+            'line_item' => $lineItem,
+            'is_test' => $isTest,
+            'campaign_id' => CRM_Utils_Array::value('campaign_id', $paymentParams),
+            'contribution_page_id' => CRM_Utils_Array::value('contribution_page_id', $this->_params),
+            'source' => CRM_Utils_Array::value('source', $paymentParams, CRM_Utils_Array::value('description', $paymentParams)),
+            'thankyou_date' => CRM_Utils_Array::value('thankyou_date', $paymentParams),
+            'payment_instrument_id' => $this->_paymentProcessor['payment_instrument_id'],
+          ),
+          $financialType,
           TRUE,
           FALSE,
-          $isTest,
-          $lineItems
+          $this->_bltID
         );
 
         //create new soft-credit record, CRM-13981
@@ -1396,46 +1526,31 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
       }
 
       if ($params['total_amount'] > 0.0) {
-        $payment = CRM_Core_Payment::singleton($this->_mode, $this->_paymentProcessor, $this);
-        $result = $payment->doDirectPayment($paymentParams);
-      }
-
-      if (is_a($result, 'CRM_Core_Error')) {
-        //make sure to cleanup db for recurring case.
-        if (!empty($paymentParams['contributionID'])) {
-          CRM_Contribute_BAO_Contribution::deleteContribution($paymentParams['contributionID']);
-        }
-        if (!empty($paymentParams['contributionRecurID'])) {
-          CRM_Contribute_BAO_ContributionRecur::deleteRecurContribution($paymentParams['contributionRecurID']);
+        $payment = $this->_paymentProcessor['object'];
+        try {
+          $result = $payment->doPayment($paymentParams);
+          $this->_params = array_merge($this->_params, $result);
+          // Assign amount to template if payment was successful.
+          $this->assign('amount', $params['total_amount']);
         }
+        catch (PaymentProcessorException $e) {
+          if (!empty($paymentParams['contributionID'])) {
+            CRM_Contribute_BAO_Contribution::failPayment($paymentParams['contributionID'], $this->_contactID,
+              $e->getMessage());
+          }
+          if (!empty($paymentParams['contributionRecurID'])) {
+            CRM_Contribute_BAO_ContributionRecur::deleteRecurContribution($paymentParams['contributionRecurID']);
+          }
 
-        CRM_Core_Error::displaySessionError($result);
-        CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/view/membership',
-          "reset=1&action=add&cid={$this->_contactID}&context=&mode={$this->_mode}"
-        ));
-      }
+          CRM_Core_Error::displaySessionError($result);
+          CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/contact/view/membership',
+            "reset=1&action=add&cid={$this->_contactID}&context=&mode={$this->_mode}"
+          ));
 
-      if ($result) {
-        $this->_params = array_merge($this->_params, $result);
-        //assign amount to template if payment was successful
-        $this->assign('amount', $params['total_amount']);
+        }
       }
 
-      // if the payment processor returns a contribution_status_id -> use it!
-      if (isset($result['contribution_status_id'])) {
-        $params['contribution_status_id'] = $result['contribution_status_id'];
-      }
-      elseif (isset($result['payment_status_id'])) {
-        // CRM-16737 $result['contribution_status_id'] is deprecated in favour
-        // of payment_status_id as the payment processor only knows whether the payment is complete
-        // not whether payment completes the contribution
-        $params['contribution_status_id'] = $params['payment_status_id'];
-      }
-      // do what used to happen previously
-      else {
-        $params['contribution_status_id'] = !empty($paymentParams['is_recur']) ? 2 : 1;
-      }
-      if ($params['contribution_status_id'] != array_search('Completed', $allContributionStatus)) {
+      if ($this->_params['payment_status_id'] != array_search('Completed', $allContributionStatus)) {
         $params['status_id'] = array_search('Pending', $allMemberStatus);
         $params['skipStatusCal'] = TRUE;
         // unset send-receipt option, since receipt will be sent when ipn is received.
@@ -1446,7 +1561,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
           'start_date' => 'startDate',
           'end_date' => 'endDate',
         );
-        foreach ($memberDates as $dp => $dv) {
+        foreach ($memberDates as $dv) {
           $$dv = NULL;
           foreach ($this->_memTypeSelected as $memType) {
             $membershipTypeValues[$memType][$dv] = NULL;
@@ -1492,6 +1607,14 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
         if (!empty($softParams) && empty($paymentParams['is_recur'])) {
           $membershipParams['soft_credit'] = $softParams;
         }
+        // This is required to trigger the recording of the membership contribution in the
+        // CRM_Member_BAO_Membership::Create function.
+        // @todo stop setting this & 'teach' the create function to respond to something
+        // appropriate as part of our 2-step always create the pending contribution & then finally add the payment
+        // process -
+        // @see http://wiki.civicrm.org/confluence/pages/viewpage.action?pageId=261062657#Payments&AccountsRoadmap-Movetowardsalwaysusinga2-steppaymentprocess
+        $membershipParams['contribution_status_id'] = CRM_Utils_Array::value('payment_status_id', $result);
+        unset($membershipParams['lineItems']);
         $membership = CRM_Member_BAO_Membership::create($membershipParams, $ids);
         $params['contribution'] = CRM_Utils_Array::value('contribution', $membershipParams);
         unset($params['lineItems']);
@@ -1515,10 +1638,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
           $lineItems = CRM_Price_BAO_LineItem::getLineItems($params['contribution_id'], 'contribution', NULL, TRUE, TRUE);
           $itemId = key($lineItems);
           $priceSetId = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'price_set_id');
-          $fieldType = NULL;
-          if ($itemId && !empty($lineItems[$itemId]['price_field_id'])) {
-            $fieldType = CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceField', $lineItems[$itemId]['price_field_id'], 'html_type');
-          }
+
           $lineItems[$itemId]['unit_price'] = $params['total_amount'];
           $lineItems[$itemId]['line_total'] = $params['total_amount'];
           $lineItems[$itemId]['id'] = $itemId;
@@ -1562,14 +1682,6 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
         // suppress form values in template.
         $this->assign('cancelled', $cancelled);
 
-        // FIX ME: need to recheck this
-        // here we might updated dates, so get from object.
-        foreach ($calcDates[$membership->membership_type_id] as $date => & $val) {
-          if ($membership->$date) {
-            $val = $membership->$date;
-          }
-        }
-
         $createdMemberships[] = $membership;
       }
       else {
@@ -1607,12 +1719,12 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
       }
     }
 
-    if (!empty($lineItem[$priceSetId])) {
+    if (!empty($lineItem[$priceSetID])) {
       $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings');
       $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings);
       $taxAmount = FALSE;
       $totalTaxAmount = 0;
-      foreach ($lineItem[$priceSetId] as & $priceFieldOp) {
+      foreach ($lineItem[$priceSetID] as & $priceFieldOp) {
         if (!empty($priceFieldOp['membership_type_id'])) {
           $priceFieldOp['start_date'] = $membershipTypeValues[$priceFieldOp['membership_type_id']]['start_date'] ? CRM_Utils_Date::customFormat($membershipTypeValues[$priceFieldOp['membership_type_id']]['start_date'], '%B %E%f, %Y') : '-';
           $priceFieldOp['end_date'] = $membershipTypeValues[$priceFieldOp['membership_type_id']]['end_date'] ? CRM_Utils_Date::customFormat($membershipTypeValues[$priceFieldOp['membership_type_id']]['end_date'], '%B %E%f, %Y') : '-';
@@ -1627,7 +1739,7 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
       }
       if ($invoicing) {
         $dataArray = array();
-        foreach ($lineItem[$priceSetId] as $key => $value) {
+        foreach ($lineItem[$priceSetID] as $key => $value) {
           if (isset($value['tax_amount']) && isset($value['tax_rate'])) {
             if (isset($dataArray[$value['tax_rate']])) {
               $dataArray[$value['tax_rate']] = $dataArray[$value['tax_rate']] + CRM_Utils_Array::value('tax_amount', $value);
@@ -1660,61 +1772,51 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
     if (!empty($formValues['send_receipt']) && $receiptSend) {
       $formValues['contact_id'] = $this->_contactID;
       $formValues['contribution_id'] = $contributionId;
-
+      // We really don't need a distinct receipt_text_signup vs receipt_text_renewal as they are
+      // handled in the receipt. But by setting one we avoid breaking templates for now
+      // although at some point we should switch in the templates.
+      $formValues['receipt_text_signup'] = $formValues['receipt_text'];
       // send email receipt
       $mailSend = self::emailReceipt($this, $formValues, $membership);
     }
 
-    if (($this->_action & CRM_Core_Action::UPDATE)) {
-      //end date can be modified by hooks, so if end date is set then use it.
-      $endDate = ($membership->end_date) ? $membership->end_date : $endDate;
-
-      $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));
-      }
-      if ($receiptSend) {
-        $statusMsg .= ' ' . ts('A confirmation and receipt has been sent to %1.', array(1 => $this->_contributorEmail));
-      }
-    }
-    elseif (($this->_action & CRM_Core_Action::ADD)) {
-      // 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,
-        ));
-
-        $membership = $createdMemberships[$memType];
-        $memEndDate = ($membership->end_date) ? $membership->end_date : $endDate;
-
-        //get the end date from calculated dates.
-        if (!$memEndDate && empty($params['is_recur'])) {
-          $memEndDate = CRM_Utils_Array::value('end_date', $calcDates[$memType]);
-        }
-
-        if ($memEndDate && $memEndDate !== 'null') {
-          $memEndDate = CRM_Utils_Date::customFormat($memEndDate);
-          $statusMsg[$memType] .= ' ' . ts('The new membership End Date is %1.', array(1 => $memEndDate));
-        }
-      }
-      $statusMsg = implode('<br/>', $statusMsg);
-      if ($receiptSend && $mailSend) {
-        $statusMsg .= ' ' . ts('A membership confirmation and receipt has been sent to %1.', array(1 => $this->_contributorEmail));
-      }
-    }
-
     // finally set membership id if already not set
     if (!$this->_id) {
       $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');
+    //CRM-15187
+    // dusplay message when membership type is changed
+    if (($this->_action & CRM_Core_Action::UPDATE) && $this->_id && !in_array($this->_memType, $this->_memTypeSelected)) {
+      CRM_Core_Session::setStatus(
+        ts('The financial types associated with the old and new membership types are different. You may want to edit the contribution associated with this membership to adjust its financial type.'),
+        ts('Warning')
+      );
+      CRM_Core_Session::setStatus(
+        ts('The cost of the old and new membership types are different. You may want to edit the contribution associated with this membership to adjust its amount.'),
+        ts('Warning')
+      );
+    }
+    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',
@@ -1735,171 +1837,72 @@ WHERE   id IN ( ' . implode(' , ', array_keys($membershipType)) . ' )';
   }
 
   /**
-   * 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'];
+  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;
 
-    if (!empty($formValues['payment_instrument_id'])) {
-      $paymentInstrument = CRM_Contribute_PseudoConstant::paymentInstrument();
-      $formValues['paidBy'] = $paymentInstrument[$formValues['payment_instrument_id']];
+    $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));
     }
 
-    // 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));
-
-      $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);
-    }
-
-    $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 && !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;
   }
 
 }