Fix notices on premiums across all 3 pages
authorEileen McNaughton <emcnaughton@wikimedia.org>
Sun, 26 Nov 2023 19:56:28 +0000 (08:56 +1300)
committerEileen McNaughton <emcnaughton@wikimedia.org>
Wed, 29 Nov 2023 20:17:47 +0000 (09:17 +1300)
This includes switching to apiv4 & adding escape to the templates
to reflect that change

13 files changed:
CRM/Contribute/BAO/Premium.php
CRM/Contribute/Form/Contribution/Confirm.php
CRM/Contribute/Form/Contribution/ThankYou.php
CRM/Contribute/Form/ContributionBase.php
CRM/Core/Permission.php
Civi/Test/ContributionPageTestTrait.php
templates/CRM/Contribute/Form/Contribution/Confirm.tpl
templates/CRM/Contribute/Form/Contribution/Main.tpl
templates/CRM/Contribute/Form/Contribution/PremiumBlock.tpl
templates/CRM/Contribute/Form/Contribution/ThankYou.tpl
tests/phpunit/CRM/Contribute/Form/Contribution/ConfirmTest.php
tests/phpunit/CiviTest/CiviUnitTestCase.php
tests/phpunit/api/v3/ContributionPageTest.php

index 3a84d7b9452949f4e9976e068087428637a1525f..349545ca5a53030033dc61ead72f419cdae2517a 100644 (file)
@@ -140,7 +140,7 @@ class CRM_Contribute_BAO_Premium extends CRM_Contribute_DAO_Premium {
         }
       }
       if (count($products)) {
-        $form->assign('showPremium', $formItems);
+        $form->assign('showPremiumSelectionFields', $formItems);
         $form->assign('showSelectOptions', $formItems);
         $form->assign('premiumBlock', $premiumBlock);
       }
index 45dd561553cf03d81517e46214b4cc8ff79dc801..214efd93e7743c93945fde2b28d43270c0ad8761 100644 (file)
@@ -500,11 +500,7 @@ class CRM_Contribute_Form_Contribution_Confirm extends CRM_Contribute_Form_Contr
 
     if (!empty($params['selectProduct']) && $params['selectProduct'] !== 'no_thanks') {
       $option = $params['options_' . $params['selectProduct']] ?? NULL;
-      $productID = $params['selectProduct'];
-      $this->buildPremiumsBlock(FALSE,
-        $productID, $option
-      );
-      $this->set('productID', $productID);
+      $this->buildPremiumsBlock(FALSE, $option);
       $this->set('option', $option);
     }
     else {
index 38854cb47f27c7fa52f2d02b30817d9711563e6b..4b2568be19fe8070ded4f9457dbf1f5ce8a867a0 100644 (file)
@@ -95,13 +95,12 @@ class CRM_Contribute_Form_Contribution_ThankYou extends CRM_Contribute_Form_Cont
     // FIXME: Some of this code is identical to Confirm.php and should be broken out into a shared function
     $this->assignToTemplate();
     $this->_ccid = $this->get('ccid');
-    $productID = $this->get('productID');
     $option = $this->get('option');
     $membershipTypeID = $this->get('membershipTypeID');
     $this->assign('receiptFromEmail', CRM_Utils_Array::value('receipt_from_email', $this->_values));
 
-    if ($productID) {
-      $this->buildPremiumsBlock(FALSE, $productID, $option);
+    if ($this->getProductID()) {
+      $this->buildPremiumsBlock(FALSE, $option);
     }
 
     $params = $this->_params;
index 4a37189126b57b83b490d983fa11cff066b46973..0260e8454383c04cf7119c0d68ed6e4f420f3199 100644 (file)
@@ -15,6 +15,7 @@
  * @copyright CiviCRM LLC https://civicrm.org/licensing
  */
 
+use Civi\Api4\PremiumsProduct;
 use Civi\Api4\PriceSet;
 
 /**
@@ -945,72 +946,55 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
    * Build Premium Block im Contribution Pages.
    *
    * @param bool $formItems
-   * @param int $selectedProductID
    * @param string $selectedOption
+   *
+   * @noinspection PhpUnhandledExceptionInspection
    */
-  protected function buildPremiumsBlock($formItems = FALSE, $selectedProductID = NULL, $selectedOption = NULL) {
-    $this->add('hidden', "selectProduct", $selectedProductID, ['id' => 'selectProduct']);
-
-    $premiumDao = new CRM_Contribute_DAO_Premium();
-    $premiumDao->entity_table = 'civicrm_contribution_page';
-    $premiumDao->entity_id = $this->getContributionPageID();
-    $premiumDao->premiums_active = 1;
-
-    if ($premiumDao->find(TRUE)) {
-      $premiumID = $premiumDao->id;
-      $premiumBlock = [];
-      CRM_Core_DAO::storeValues($premiumDao, $premiumBlock);
-
-      CRM_Financial_BAO_FinancialType::getAvailableFinancialTypes($financialTypes, CRM_Core_Action::ADD);
-      $addWhere = "financial_type_id IN (0)";
-      if (!empty($financialTypes)) {
-        $addWhere = "financial_type_id IN (" . implode(',', array_keys($financialTypes)) . ")";
-      }
-      $addWhere = "{$addWhere} OR financial_type_id IS NULL";
-
-      $premiumsProductDao = new CRM_Contribute_DAO_PremiumsProduct();
-      $premiumsProductDao->premiums_id = $premiumID;
-      $premiumsProductDao->whereAdd($addWhere);
-      $premiumsProductDao->orderBy('weight');
-      $premiumsProductDao->find();
-
-      $products = [];
-      while ($premiumsProductDao->fetch()) {
-        $productDAO = new CRM_Contribute_DAO_Product();
-        $productDAO->id = $premiumsProductDao->product_id;
-        $productDAO->is_active = 1;
-        if ($productDAO->find(TRUE)) {
-          if ($selectedProductID != NULL) {
-            if ($selectedProductID == $productDAO->id) {
-              if ($selectedOption) {
-                $productDAO->options = ts('Selected Option') . ': ' . $selectedOption;
-              }
-              else {
-                $productDAO->options = NULL;
-              }
-              CRM_Core_DAO::storeValues($productDAO, $products[$productDAO->id]);
-            }
+  protected function buildPremiumsBlock(bool $formItems = FALSE, $selectedOption = NULL): void {
+    $selectedProductID = $this->getProductID();
+    $this->add('hidden', 'selectProduct', $selectedProductID, ['id' => 'selectProduct']);
+    $premiumProducts = PremiumsProduct::get()
+      ->addSelect('product_id.*')
+      ->addSelect('product_id')
+      ->addSelect('premiums_id.*')
+      ->addWhere('product_id.is_active', '=', TRUE)
+      ->addWhere('premiums_id.entity_id', '=', $this->getContributionPageID())
+      ->addWhere('premiums_id.entity_table', '=', 'civicrm_contribution_page')
+      ->addOrderBy('weight')
+      ->execute();
+    $products = [];
+    $premium = [];
+    foreach ($premiumProducts as $premiumProduct) {
+      $product = ['options' => NULL];
+      foreach ($premiumProduct as $key => $value) {
+        if (str_starts_with($key, 'product_id.')) {
+          if ($key === 'product_id.options' && $selectedProductID === $product['id'] && $selectedOption) {
+            // In this case we are on the thank you or confirm page so assign
+            // the selected option to the page for display.
+            $product['options'] = ts('Selected Option') . ': ' . $selectedOption;
           }
           else {
-            // Why? should we not skip if not found?
-            CRM_Core_DAO::storeValues($productDAO, $products[$productDAO->id]);
+            $product[str_replace('product_id.', '', $key)] = $value;
           }
         }
-        $options = $temp = [];
-        $temp = explode(',', $productDAO->options);
-        foreach ($temp as $value) {
-          $options[trim($value)] = trim($value);
+        if (str_starts_with($key, 'premiums_id.')) {
+          $premium[str_replace('premiums_id.', '', $key)] = $value;
         }
-        if ($temp[0] != '') {
-          $this->addElement('select', 'options_' . $productDAO->id, NULL, $options);
+      }
+      $options = array_filter(explode(',', $product['options']));
+      $productOptions = [];
+      foreach ($options as $option) {
+        $optionValue = trim($option);
+        if ($optionValue) {
+          $productOptions[$optionValue] = $optionValue;
         }
       }
-      if (count($products)) {
-        $this->assign('showPremium', $formItems);
-        $this->assign('showSelectOptions', $formItems);
-        $this->assign('premiumBlock', $premiumBlock);
+      if (!empty($options)) {
+        $this->addElement('select', 'options_' . $product['id'], NULL, $productOptions);
       }
+      $products[$premiumProduct['product_id']] = $product;
     }
+    $this->assign('premiumBlock', $premium);
     $this->assign('products', $products ?? NULL);
   }
 
@@ -1383,6 +1367,15 @@ class CRM_Contribute_Form_ContributionBase extends CRM_Core_Form {
     return $this->_ccid ?: CRM_Utils_Request::retrieve('ccid', 'Positive', $this);
   }
 
+  /**
+   * @return int|bool
+   */
+  protected function getProductID() {
+    $productID = $this->getSubmittedValue('selectProduct') ? (int) $this->getSubmittedValue('selectProduct') : FALSE;
+    $this->set('productID', $productID);
+    return $productID;
+  }
+
   /**
    * Get the submitted value, accessing it from whatever form in the flow it is
    * submitted on.
index f8f2fb7ace178231d4f2f4e491825b41b2f7f1d2..72a7fd80b5df77d90bf95a5ed164721ae052ac8b 100644 (file)
@@ -1152,7 +1152,8 @@ class CRM_Core_Permission {
       ],
     ];
     $permissions['line_item'] = $permissions['contribution'];
-    $permissions['product'] = $permissions['contribution'];
+    $permissions['product'] = $permissions['premiums'] = $permissions['premiums_product'] = $permissions['contribution'];
+    $permissions['product']['get'] = $permissions['premiums']['get'] = $permissions['premiums_product']['get'] = [['access CiviCRM', 'access CiviContribute', 'make online contributions']];
 
     $permissions['financial_item'] = $permissions['contribution'];
     $permissions['financial_type']['get'] = $permissions['contribution']['get'];
index 5f6e8338278bae3537321edcc8b8671660bac175..1c386681afd9846dcefeb91855a4bb4225075023 100644 (file)
@@ -122,6 +122,7 @@ trait ContributionPageTestTrait {
       'description' => '5 dollars worth of monopoly money',
       'options' => 'White, Black, Green',
       'price' => 1,
+      'is_active' => TRUE,
       'min_contribution' => 5,
       'cost' => .05,
     ], '5_dollars');
@@ -130,6 +131,7 @@ trait ContributionPageTestTrait {
       'description' => '10 dollars worth of monopoly money',
       'options' => 'White, Black, Green',
       'price' => 2,
+      'is_active' => TRUE,
       'min_contribution' => 10,
       'cost' => .05,
     ], '10_dollars');
@@ -137,6 +139,7 @@ trait ContributionPageTestTrait {
       'entity_id' => $this->getContributionPageID($identifier),
       'entity_table' => 'civicrm_contribution_page',
       'premiums_intro_title' => 'Get free monopoly money with your donation',
+      'premiums_active' => TRUE,
     ], $identifier);
     $this->createTestEntity('PremiumsProduct', [
       'premiums_id' => $this->ids['Premium'][$identifier],
index e39176129d35ae69cfa89f746f3b458e9ddd22bf..73fbf903bec6eba5d242037dc3a0044b50078757 100644 (file)
     {/if}
     {/crmRegion}
 
-  {include file="CRM/Contribute/Form/Contribution/PremiumBlock.tpl" context="confirmContribution"}
+  {include file="CRM/Contribute/Form/Contribution/PremiumBlock.tpl" context="confirmContribution" showPremiumSelectionFields=false preview=false}
 
   {if $customPost}
     <fieldset class="label-left crm-profile-view">
index 7d25368f5abdea4cea40a22f718ef39027f06122..96ab9b0d5f99565953c1b3e1c9dd7ff3d28ecf4b 100644 (file)
         {include file="CRM/common/CMSUser.tpl"}
       </div>
       <div class="crm-public-form-item crm-section premium_block-section">
-        {include file="CRM/Contribute/Form/Contribution/PremiumBlock.tpl" context="makeContribution" preview=false}
+        {include file="CRM/Contribute/Form/Contribution/PremiumBlock.tpl" context="makeContribution" preview=false showPremiumSelectionFields=true}
       </div>
 
       {if $honoreeProfileFields && $honoreeProfileFields|@count}
index 9ad512c6e2bbece72b798eadeea2cf996b7fbbca..6f69bf58b20ccc7722713749e0d3ce35963d74ee 100644 (file)
     {if $context EQ "makeContribution"}
       <fieldset class="crm-group premiums_select-group">
       {if $premiumBlock.premiums_intro_title}
-        <legend>{$premiumBlock.premiums_intro_title}</legend>
+        <legend>{$premiumBlock.premiums_intro_title|escape}</legend>
       {/if}
       {if $premiumBlock.premiums_intro_text}
         <div id="premiums-intro" class="crm-section premiums_intro-section">
-          {$premiumBlock.premiums_intro_text}
+          {$premiumBlock.premiums_intro_text|escape}
         </div>
       {/if}
     {/if}
@@ -25,7 +25,7 @@
     <div class="crm-group premium_display-group">
       <div class="header-dark">
         {if $premiumBlock.premiums_intro_title}
-          {$premiumBlock.premiums_intro_title}
+          {$premiumBlock.premiums_intro_title|escape}
         {else}
           {ts}Your Premium Selection{/ts}
         {/if}
     {/if}
 
     {if $preview}
-      {assign var="showSelectOptions" value="1"}
+      {assign var="showPremiumSelectionFields" value="1"}
     {/if}
 
     {strip}
       <div id="premiums-listings">
-      {if $showPremium AND !$preview AND $premiumBlock.premiums_nothankyou_position EQ 1}
+      {if $showPremiumSelectionFields AND !$preview AND $premiumBlock.premiums_nothankyou_position EQ 1}
         <div class="premium premium-no_thanks" id="premium_id-no_thanks" min_contribution="0">
           <div class="premium-short">
-            <input type="checkbox" disabled="disabled" /> {$premiumBlock.premiums_nothankyou_label}
+            <input type="checkbox" disabled="disabled" /> {$premiumBlock.premiums_nothankyou_label|escape}
           </div>
           <div class="premium-full">
-            <input type="checkbox" checked="checked" disabled="disabled" /> {$premiumBlock.premiums_nothankyou_label}
+            <input type="checkbox" checked="checked" disabled="disabled" /> {$premiumBlock.premiums_nothankyou_label|escape}
           </div>
         </div>
       {/if}
       {foreach from=$products item=row}
-        <div class="premium {if $showPremium}premium-selectable{/if}" id="premium_id-{$row.id}" min_contribution="{$row.min_contribution}">
+        <div class="premium {if $showPremiumSelectionFields}premium-selectable{/if}" id="premium_id-{$row.id}" min_contribution="{$row.min_contribution}">
           <div class="premium-short">
             {if $row.thumbnail}<div class="premium-short-thumbnail"><img src="{$row.thumbnail|purify}" alt="{$row.name|escape}" /></div>{/if}
             <div class="premium-short-content">{$row.name|escape}</div>
               <div class="premium-full-description">
                 {$row.description|escape}
               </div>
-              {if $showSelectOptions}
-                {assign var="pid" value="options_"|cat:$row.id}
-                {if $pid}
+              {if $showPremiumSelectionFields}
+                {assign var="premium_option" value="options_"|cat:$row.id}
                   <div class="premium-full-options">
-                    <p>{$form.$pid.html}</p>
+                    <p>{$form.$premium_option.html}</p>
                   </div>
-                {/if}
               {else}
                 <div class="premium-full-options">
                   <p><strong>{$row.options|purify}</strong></p>
           <div style="clear:both"></div>
         </div>
       {/foreach}
-      {if $showPremium AND !$preview AND $premiumBlock.premiums_nothankyou_position EQ 2}
+      {if $showPremiumSelectionFields AND !$preview AND $premiumBlock.premiums_nothankyou_position EQ 2}
         <div class="premium premium-no_thanks" id="premium_id-no_thanks" min_contribution="0">
           <div class="premium-short">
-            <input type="checkbox" disabled="disabled" /> {$premiumBlock.premiums_nothankyou_label}
+            <input type="checkbox" disabled="disabled" /> {$premiumBlock.premiums_nothankyou_label|escape}
           </div>
           <div class="premium-full">
-            <input type="checkbox" checked="checked" disabled="disabled" /> {$premiumBlock.premiums_nothankyou_label}
+            <input type="checkbox" checked="checked" disabled="disabled" /> {$premiumBlock.premiums_nothankyou_label|escape}
           </div>
         </div>
       {/if}
index 5d6e7943b54f86ba32eabc3678200a123ae541ee..ccae978698c19757d26aea28dab97b2fd0fc95d0 100644 (file)
     {/crmRegion}
   {/if}
 
-  {include file="CRM/Contribute/Form/Contribution/PremiumBlock.tpl" context="thankContribution"}
+  {include file="CRM/Contribute/Form/Contribution/PremiumBlock.tpl" context="thankContribution" showPremiumSelectionFields=false preview=false}
 
   {if $customPost}
     <fieldset class="label-left crm-profile-view">
index 521252c5f2d032810dbc842ada4a68b50d0de3e2..26c98f0f9f544df95083d580806f6d0517590015 100644 (file)
@@ -315,12 +315,13 @@ class CRM_Contribute_Form_Contribution_ConfirmTest extends CiviUnitTestCase {
 
   /**
    * @param array $submittedValues
-   * @param int $contributionPageID
+   * @param int|null $contributionPageID
+   *   Will default to calling $this->>getContributionPageID()
    *
    * @return \Civi\Test\FormWrapper|\Civi\Test\FormWrappers\EventFormOnline|\Civi\Test\FormWrappers\EventFormParticipant|null
    */
-  protected function submitOnlineContributionForm(array $submittedValues, int $contributionPageID) {
-    $form = $this->getTestForm('CRM_Contribute_Form_Contribution_Main', $submittedValues, ['id' => $contributionPageID])
+  protected function submitOnlineContributionForm(array $submittedValues, ?int $contributionPageID = NULL) {
+    $form = $this->getTestForm('CRM_Contribute_Form_Contribution_Main', $submittedValues, ['id' => $contributionPageID ?: $this->getContributionPageID()])
       ->addSubsequentForm('CRM_Contribute_Form_Contribution_Confirm');
     $form->processForm();
     return $form;
@@ -444,6 +445,25 @@ class CRM_Contribute_Form_Contribution_ConfirmTest extends CiviUnitTestCase {
     $mailUtil->checkMailLog([\Civi::format()->money(337.55), 'Tax Rate', 'Subtotal']);
   }
 
+  /**
+   * Test form submission with basic price set.
+   */
+  public function testSubmit(): void {
+    $this->contributionPageWithPriceSetCreate();
+    $this->submitOnlineContributionForm([
+      'id' => $this->getContributionPageID(),
+      'first_name' => 'J',
+      'last_name' => 'T',
+      'email-5' => 'JT@ohcanada.ca',
+      'receive_date' => date('Y-m-d H:i:s'),
+      'payment_processor_id' => 0,
+      'priceSetId' => $this->getPriceSetID('ContributionPage'),
+      'price_' . $this->ids['PriceField']['radio_field'] => $this->ids['PriceFieldValue']['10_dollars'],
+    ]);
+    $contribution = $this->getCreatedContribution();
+    $this->assertEquals(5.00, $contribution['non_deductible_amount']);
+  }
+
   /**
    * Test form submission with multiple option price set.
    *
@@ -615,4 +635,16 @@ class CRM_Contribute_Form_Contribution_ConfirmTest extends CiviUnitTestCase {
     $this->assertMailSentNotContainingString('Amount');
   }
 
+  /**
+   * Get the just-created contribution.
+   *
+   * @return array
+   */
+  protected function getCreatedContribution(): array {
+    return $this->callAPISuccessGetSingle('Contribution', [
+      'contribution_page_id' => $this->getContributionPageID(),
+      'version' => 4,
+    ]);
+  }
+
 }
index 0774edd7c4954b5ed73bf4ad6f0e5f6ddaf2ad0f..7c14de40f0455f9f752938b3e7c64639a4a2bcbf 100644 (file)
@@ -1662,6 +1662,9 @@ class CiviUnitTestCase extends PHPUnit\Framework\TestCase {
       'civicrm_price_set_entity',
       'civicrm_price_field_value',
       'civicrm_price_field',
+      'civicrm_product',
+      'civicrm_premiums',
+      'civicrm_premiums_product',
     ];
     $this->quickCleanup($tablesToTruncate);
     CRM_Core_DAO::executeQuery("DELETE FROM civicrm_membership_status WHERE name NOT IN('New', 'Current', 'Grace', 'Expired', 'Pending', 'Cancelled', 'Deceased')");
index c992a3a3b0fa1641fa8268844af9792cc45eab7a..28055fc315271912f15a6f0f56c527ffcb12787f 100644 (file)
@@ -133,19 +133,6 @@ class api_v3_ContributionPageTest extends CiviUnitTestCase {
     $this->assertEquals(12, $result['values']['start_date']['type']);
   }
 
-  /**
-   * Test form submission with basic price set.
-   */
-  public function testSubmit(): void {
-    $this->setUpContributionPage();
-    $submitParams = $this->getBasicSubmitParams();
-
-    $this->callAPISuccess('ContributionPage', 'submit', $submitParams);
-    $contribution = $this->callAPISuccess('Contribution', 'getsingle', ['contribution_page_id' => $this->getContributionPageID(), 'return' => ['non_deductible_amount']]);
-    //assert non-deductible amount
-    $this->assertEquals(5.00, $contribution['non_deductible_amount']);
-  }
-
   /**
    * Test form submission with basic price set.
    */