Merge remote-tracking branch 'upstream/4.5' into 4.5-master-2014-12-01-14-40-22
[civicrm-core.git] / CRM / Core / Payment / BaseIPN.php
index d0e1832578898035be18e735f00201c32858df1a..746bc2dc4080648ebb9d076a19b68a145edcbe29 100644 (file)
@@ -43,6 +43,9 @@ class CRM_Core_Payment_BaseIPN {
    */
   protected $_inputParameters = array();
 
+  protected $_isRecurring = FALSE;
+
+  protected $_isFirstOrLastRecurringPayment = FALSE;
   /**
    * Constructor
    */
@@ -104,7 +107,12 @@ class CRM_Core_Payment_BaseIPN {
     if (!$this->loadObjects($input, $ids, $objects, $required, $paymentProcessorID)) {
       return FALSE;
     }
-
+    //the process is that the loadObjects is kind of hacked by loading the objects for the original contribution and then somewhat inconsistently using them for the
+    //current contribution. Here we ensure that the original contribution is available to the complete transaction function
+    //we don't want to fix this in the payment processor classes because we would have to fix all of them - so better to fix somewhere central
+    if (isset($objects['contributionRecur'])) {
+      $objects['first_contribution'] = $objects['contribution'];
+    }
     return TRUE;
   }
 
@@ -197,7 +205,7 @@ class CRM_Core_Payment_BaseIPN {
 
     //add lineitems for recurring payments
     if (!empty($objects['contributionRecur']) && $objects['contributionRecur']->id && $addLineItems) {
-      $this->addrecurLineItems($objects['contributionRecur']->id, $contribution->id, CRM_Core_DAO::$_nullArray);
+      $this->addRecurLineItems($objects['contributionRecur']->id, $contribution);
     }
 
     //add new soft credit against current contribution id and
@@ -277,7 +285,7 @@ class CRM_Core_Payment_BaseIPN {
 
     //add lineitems for recurring payments
     if (!empty($objects['contributionRecur']) && $objects['contributionRecur']->id && $addLineItems) {
-      $this->addrecurLineItems($objects['contributionRecur']->id, $contribution->id, CRM_Core_DAO::$_nullArray);
+      $this->addRecurLineItems($objects['contributionRecur']->id, $contribution);
     }
 
     //add new soft credit against current $contribution and
@@ -334,6 +342,9 @@ class CRM_Core_Payment_BaseIPN {
    */
   function completeTransaction(&$input, &$ids, &$objects, &$transaction, $recur = FALSE) {
     $contribution = &$objects['contribution'];
+
+    $primaryContributionID = isset($contribution->id) ? $contribution->id : $objects['first_contribution']->id;
+
     $memberships = &$objects['membership'];
     if (is_numeric($memberships)) {
       $memberships = array($objects['membership']);
@@ -400,7 +411,7 @@ LIMIT 1;";
             // else fall back to using current membership type
             $dao->free();
 
-            $num_terms = $contribution->getNumTermsByContributionAndMembershipType($membership->membership_type_id);
+            $num_terms = $contribution->getNumTermsByContributionAndMembershipType($membership->membership_type_id, $primaryContributionID);
             if ($currentMembership) {
               /*
                * Fixed FOR CRM-4433
@@ -559,8 +570,14 @@ LIMIT 1;";
     }
 
     //add lineitems for recurring payments
-    if (!empty($objects['contributionRecur']) && $objects['contributionRecur']->id && $addLineItems) {
-      $this->addrecurLineItems($objects['contributionRecur']->id, $contribution->id, $input);
+    if (!empty($objects['contributionRecur']) && $objects['contributionRecur']->id) {
+      if ($addLineItems) {
+        $input ['line_item'] = $this->addRecurLineItems($objects['contributionRecur']->id, $contribution);
+      }
+      else {
+        // this is just to prevent e-notices when we call recordFinancialAccounts - per comments on that line - intention is somewhat unclear
+        $input['line_item'] = array();
+      }
     }
 
     //copy initial contribution custom fields for recurring contributions
@@ -605,7 +622,7 @@ LIMIT 1;";
         $input['contribution_mode'] = 'membership';
       }
       //@todo writing a unit test I was unable to create a scenario where this line did not fatal on second
-      // and subsequent payments. In this case the line items are created at $this->addrecurLineItems
+      // and subsequent payments. In this case the line items are created at $this->addRecurLineItems
       // and since the contribution is saved prior to this line there is always a contribution-id,
       // however there is never a prevContribution (which appears to mean original contribution not previous
       // contribution - or preUpdateContributionObject most accurately)
@@ -613,6 +630,7 @@ LIMIT 1;";
       // to mean "are we updating an exisitng pending contribution"
       //I was able to make the unit test complete as fataling here doesn't prevent
       // the contribution being created - but activities would not be created or emails sent
+
       CRM_Contribute_BAO_Contribution::recordFinancialAccounts($input, NULL);
     }
 
@@ -646,6 +664,9 @@ LIMIT 1;";
     }
 
     CRM_Core_Error::debug_log_message("Success: Database updated");
+    if ($this->_isRecurring) {
+      $this->sendRecurringStartOrEndNotification($ids, $recur);
+    }
   }
 
   /**
@@ -667,26 +688,19 @@ LIMIT 1;";
     return TRUE;
   }
 
-  /*
+  /**
    * Send receipt from contribution. Note that the compose message part has been moved to contribution
    * In general LoadObjects is called first to get the objects but the composeMessageArray function now calls it
    *
-   * @params array $input Incoming data from Payment processor
-   * @params array $ids Related object IDs
-   * @params array $values values related to objects that have already been loaded
-   * @params bool $recur is it part of a recurring contribution
-   * @params bool $returnMessageText Should text be returned instead of sent. This
-   * is because the function is also used to generate pdfs
-   */
-  /**
-   * @param $input
-   * @param $ids
+   * @param array $input Incoming data from Payment processor
+   * @param array $ids Related object IDs
    * @param $objects
-   * @param $values
-   * @param bool $recur
-   * @param bool $returnMessageText
+   * @param array $values values related to objects that have already been loaded
+   * @param bool $recur is it part of a recurring contribution
+   * @param bool $returnMessageText Should text be returned instead of sent. This
+   *  is because the function is also used to generate pdfs
    *
-   * @return mixed
+   * @return array
    */
   function sendMail(&$input, &$ids, &$objects, &$values, $recur = FALSE, $returnMessageText = FALSE) {
     $contribution = &$objects['contribution'];
@@ -704,6 +718,30 @@ LIMIT 1;";
     return $contribution->composeMessageArray($input, $ids, $values, $recur, $returnMessageText);
   }
 
+  /**
+   * Send start or end notification for recurring payments
+   * @param $ids
+   * @param $recur
+   */
+  function sendRecurringStartOrEndNotification($ids, $recur) {
+    if ($this->_isFirstOrLastRecurringPayment) {
+      $autoRenewMembership = FALSE;
+      if ($recur->id &&
+        isset($ids['membership']) && $ids['membership']
+      ) {
+        $autoRenewMembership = TRUE;
+      }
+
+      //send recurring Notification email for user
+      CRM_Contribute_BAO_ContributionPage::recurringNotify($this->_isFirstOrLastRecurringPayment,
+        $ids['contact'],
+        $ids['contributionPage'],
+        $recur,
+        $autoRenewMembership
+      );
+    }
+  }
+
   /**
    * Update contribution status - this is only called from one place in the code &
    * it is unclear whether it is a function on the way in or on the way out
@@ -891,32 +929,37 @@ LIMIT 1;";
   }
 
   /**
-   * @param $recurId
-   * @param $contributionId
-   * @param $input
+   * @param int $recurId
+   * @param $contribution
+   *
+   * @return array
    */
-  function addrecurLineItems($recurId, $contributionId, &$input) {
-    $lineSets = $lineItems = array();
-
-    //Get the first contribution id with recur id
-    if ($recurId) {
-      $contriID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $recurId, 'id', 'contribution_recur_id');
-      $lineItems = CRM_Price_BAO_LineItem::getLineItems($contriID, 'contribution');
-      if (!empty($lineItems)) {
-        foreach ($lineItems as $key => $value) {
-          $pricesetID = new CRM_Price_DAO_PriceField();
-          $pricesetID->id = $value['price_field_id'];
-          $pricesetID->find(TRUE);
-          $lineSets[$pricesetID->price_set_id][] = $value;
+  function addRecurLineItems($recurId, $contribution) {
+    $lineSets = array();
+
+    $originalContributionID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution', $recurId, 'id', 'contribution_recur_id');
+    $lineItems = CRM_Price_BAO_LineItem::getLineItemsByContributionID($originalContributionID);
+    if (!empty($lineItems)) {
+      foreach ($lineItems as $key => $value) {
+        $priceField = new CRM_Price_DAO_PriceField();
+        $priceField->id = $value['price_field_id'];
+        $priceField->find(TRUE);
+        $lineSets[$priceField->price_set_id][] = $value;
+        if ($value['entity_table'] == 'civicrm_membership') {
+          try {
+            civicrm_api3('membership_payment', 'create', array('membership_id' => $value['entity_id'], 'contribution_id' => $contribution->id));
+          }
+          catch (CiviCRM_API3_Exception $e) {
+            // we are catching & ignoring errors as an extra precaution since lost IPNs may be more serious that lost membership_payment data
+            // this fn is unit-tested so risk of changes elsewhere breaking it are otherwise mitigated
+          }
         }
       }
-      if (!empty($input)) {
-        $input['line_item'] = $lineSets;
-      }
-      else {
-        CRM_Price_BAO_LineItem::processPriceSet($contributionId, $lineSets);
-      }
     }
+    else {
+      CRM_Price_BAO_LineItem::processPriceSet($contribution->id, $lineSets, $contribution);
+    }
+    return $lineSets;
   }
 
   // function to copy custom data of the