Fix notices relating to membership on Main contribution page
authorEileen McNaughton <emcnaughton@wikimedia.org>
Mon, 23 Oct 2023 03:18:26 +0000 (16:18 +1300)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Mon, 23 Oct 2023 03:28:00 +0000 (16:28 +1300)
CRM/Contribute/Form/Contribution/Main.php
CRM/Contribute/Form/ContributionBase.php
CRM/Financial/BAO/Order.php

index e29b12bf9c2c3f639fecc9f013012e1608ba6eb0..ad032d4f4069af37ec1f4e48724ddc6591f3e78a 100644 (file)
@@ -479,52 +479,24 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
    * @throws \CRM_Core_Exception
    */
   private function buildPriceSet(&$form, $component = NULL) {
-    $priceSetId = $this->getPriceSetID();
-    $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($priceSetId, TRUE, TRUE);
-    $form->_priceSet = $priceSet[$priceSetId] ?? NULL;
-    $validPriceFieldIds = array_keys($form->_priceSet['fields']);
-
-    // Mark which field should have the auto-renew checkbox, if any. CRM-18305
-    if (!empty($form->_membershipTypeValues) && is_array($form->_membershipTypeValues)) {
-      $autoRenewMembershipTypes = [];
-      foreach ($form->_membershipTypeValues as $membershipTypeValue) {
-        if ($membershipTypeValue['auto_renew']) {
-          $autoRenewMembershipTypes[] = $membershipTypeValue['id'];
-        }
-      }
-      foreach ($form->_priceSet['fields'] as $field) {
-        if (array_key_exists('options', $field) && is_array($field['options'])) {
-          foreach ($field['options'] as $option) {
-            if (!empty($option['membership_type_id'])) {
-              if (in_array($option['membership_type_id'], $autoRenewMembershipTypes)) {
-                $form->_priceSet['auto_renew_membership_field'] = $field['id'];
-                // Only one field can offer auto_renew memberships, so break here.
-                break;
-              }
-            }
-          }
-        }
-      }
-    }
-    $form->_priceSet['id'] = $form->_priceSet['id'] ?? $priceSetId;
+    $validPriceFieldIds = array_keys($this->getPriceFieldMetaData());
     $form->assign('priceSet', $form->_priceSet);
 
+    // @todo - this hook wrangling can be done earlier if we set the form on $this->>order.
     $feeBlock = &$form->_values['fee'];
-
     // Call the buildAmount hook.
     CRM_Utils_Hook::buildAmount($component ?? 'contribution', $form, $feeBlock);
 
     // CRM-14492 Admin price fields should show up on event registration if user has 'administer CiviCRM' permissions
     $adminFieldVisible = CRM_Core_Permission::check('administer CiviCRM');
     $checklifetime = FALSE;
-    foreach ($feeBlock as $id => $field) {
+    foreach ($this->getPriceFieldMetaData() as $id => $field) {
       if ($field['visibility'] === 'public' ||
         ($field['visibility'] === 'admin' && $adminFieldVisible)
       ) {
         $options = $field['options'] ?? NULL;
-        $contactId = $form->getVar('_membershipContactID');
-        if ($contactId && $options) {
-          $contactsLifetimeMemberships = CRM_Member_BAO_Membership::getAllContactMembership($contactId, FALSE, TRUE);
+        if ($this->_membershipContactID && $options) {
+          $contactsLifetimeMemberships = CRM_Member_BAO_Membership::getAllContactMembership($this->_membershipContactID, FALSE, TRUE);
           $contactsLifetimeMembershipTypes = array_column($contactsLifetimeMemberships, 'membership_type_id');
           $memTypeIdsInPriceField = array_column($options, 'membership_type_id');
           $isCurrentMember = (bool) array_intersect($memTypeIdsInPriceField, $contactsLifetimeMembershipTypes);
@@ -625,7 +597,7 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
    */
   private function buildMembershipBlock() {
     $cid = $this->_membershipContactID;
-    $isTest = (bool) ($this->_action & CRM_Core_Action::PREVIEW);
+    $isTest = (bool) ($this->getAction() & CRM_Core_Action::PREVIEW);
     $separateMembershipPayment = FALSE;
     $this->addOptionalQuickFormElement('auto_renew');
     if ($this->_membershipBlock) {
@@ -640,7 +612,7 @@ class CRM_Contribute_Form_Contribution_Main extends CRM_Contribute_Form_Contribu
 
       $separateMembershipPayment = $this->_membershipBlock['is_separate_payment'] ?? NULL;
 
-      foreach ($this->_priceSet['fields'] as $pField) {
+      foreach ($this->getPriceFieldMetaData() as $pField) {
         if (empty($pField['options'])) {
           continue;
         }
index 5bcd39961163c29c941adb2c1f048433607f6d17..7cd9380250cdf3f8d9472ed96b9c02323f365ae7 100644 (file)
@@ -555,14 +555,29 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
       if ($form->_action & CRM_Core_Action::UPDATE) {
         $form->_values['line_items'] = CRM_Price_BAO_LineItem::getLineItems($form->_id, 'contribution');
       }
-
-      $priceSet = CRM_Price_BAO_PriceSet::getSetDetail($priceSetId);
-      $form->_priceSet = $priceSet[$priceSetId] ?? NULL;
-      $form->_values['fee'] = $form->_priceSet['fields'] ?? NULL;
+      $form->_priceSet = [$this->getPriceSetID() => $this->order->getPriceSetMetadata()];
+      $this->setPriceFieldMetaData($this->order->getPriceFieldsMetadata());
       $form->set('priceSet', $form->_priceSet);
     }
   }
 
+  /**
+   * @param array $metadata
+   */
+  public function setPriceFieldMetaData(array $metadata): void {
+    $this->_values['fee'] = $this->_priceSet['fields'] = $metadata;
+  }
+
+  public function getPriceFieldMetaData() {
+    if (!empty($this->_values['fee'])) {
+      return $this->_values['fee'];
+    }
+    if (!empty($this->_priceSet['fields'])) {
+      return $this->_priceSet['fields'];
+    }
+    return $this->order->getPriceFieldsMetadata();
+  }
+
   /**
    * Set the default values.
    */
index ecc528c18fedb75268b7594bbe3f3548fd91d536..bd74582544cbe8bbd5136cf3b372ed50005a38ea 100644 (file)
@@ -78,6 +78,18 @@ class CRM_Financial_BAO_Order {
    */
   protected $overridableFinancialTypeID;
 
+  private $isExcludeExpiredFields = FALSE;
+
+  /**
+   * @param bool $isExcludeExpiredFields
+   *
+   * @return CRM_Financial_BAO_Order
+   */
+  public function setIsExcludeExpiredFields(bool $isExcludeExpiredFields): CRM_Financial_BAO_Order {
+    $this->isExcludeExpiredFields = $isExcludeExpiredFields;
+    return $this;
+  }
+
   /**
    * Get overridable financial type id.
    *
@@ -658,8 +670,30 @@ class CRM_Financial_BAO_Order {
    *
    * @param array $metadata
    */
-  protected function setPriceFieldMetadata($metadata) {
+  protected function setPriceFieldMetadata(array $metadata): void {
+    foreach ($metadata as $index => $priceField) {
+      if ($this->isExcludeExpiredFields && !empty($priceField['active_on']) && time() < strtotime($priceField['active_on'])) {
+        unset($metadata[$index]);
+      }
+      elseif ($this->isExcludeExpiredFields && !empty($priceField['expires_on']) && strtotime($priceField['expires_on']) < time()) {
+        unset($metadata[$index]);
+      }
+      elseif (!empty($priceField['options'])) {
+        foreach ($priceField['options'] as $optionID => $option) {
+          if (!empty($option['membership_type_id'])) {
+            $membershipType = CRM_Member_BAO_MembershipType::getMembershipType((int) $option['membership_type_id']);
+            $metadata[$index]['options'][$optionID]['auto_renew'] = (int) $membershipType['auto_renew'];
+            if ($membershipType['auto_renew'] && empty($this->priceSetMetadata['auto_renew_membership_type'])) {
+              // Quick form layer supports one auto-renew membership type per price set. If we
+              // want more for any reason we can add another array property.
+              $this->priceSetMetadata['auto_renew_membership_type'] = (int) $option['membership_type_id'];
+            }
+          }
+        }
+      }
+    }
     $this->priceFieldMetadata = $metadata;
+
     if ($this->getForm()) {
       CRM_Utils_Hook::buildAmount($this->form->getFormContext(), $this->form, $this->priceFieldMetadata);
     }
@@ -668,18 +702,19 @@ class CRM_Financial_BAO_Order {
   /**
    * Get the metadata for the fields in the price set.
    *
+   * @return array
+   * @throws \CRM_Core_Exception
    * @internal use in tested core code only.
    *
-   * @return array
    */
   public function getPriceSetMetadata(): array {
     if (empty($this->priceSetMetadata)) {
-      $priceSetMetadata = CRM_Price_BAO_PriceSet::getCachedPriceSetDetail($this->getPriceSetID());
+      $this->priceSetMetadata = CRM_Price_BAO_PriceSet::getCachedPriceSetDetail($this->getPriceSetID());
+      $this->priceSetMetadata['id'] = $this->getPriceSetID();
       // @todo - make sure this is an array - commented out for now as this PR is against the rc.
       // $priceSetMetadata['extends'] = explode(CRM_Core_DAO::VALUE_SEPARATOR, $priceSetMetadata['extends']);
-      $this->setPriceFieldMetadata($priceSetMetadata['fields']);
-      unset($priceSetMetadata['fields']);
-      $this->priceSetMetadata = $priceSetMetadata;
+      $this->setPriceFieldMetadata($this->priceSetMetadata['fields']);
+      unset($this->priceSetMetadata['fields']);
     }
     return $this->priceSetMetadata;
   }
@@ -688,7 +723,9 @@ class CRM_Financial_BAO_Order {
     if (!CRM_Core_Component::isEnabled('CiviMember')) {
       return FALSE;
     }
-    $extends = explode(CRM_Core_DAO::VALUE_SEPARATOR, $this->getPriceSetMetadata()['extends']);
+    // Access the property if set, to avoid a potential loop when the hook is called.
+    $priceSetMetadata = $this->priceSetMetadata ?: $this->getPriceSetMetadata();
+    $extends = explode(CRM_Core_DAO::VALUE_SEPARATOR, $priceSetMetadata['extends']);
     return in_array(CRM_Core_Component::getComponentID('CiviMember'), $extends, FALSE);
   }