diff --git a/CRM/Contribute/Form/Contribution/Main.php b/CRM/Contribute/Form/Contribution/Main.php index 9a94d17b895f..8b78716d9333 100644 --- a/CRM/Contribute/Form/Contribution/Main.php +++ b/CRM/Contribute/Form/Contribution/Main.php @@ -491,8 +491,8 @@ private function buildPriceSet(&$form, $component = NULL) { $adminFieldVisible = CRM_Core_Permission::check('administer CiviCRM'); $checklifetime = FALSE; foreach ($this->getPriceFieldMetaData() as $id => $field) { - if ($field['visibility'] === 'public' || - ($field['visibility'] === 'admin' && $adminFieldVisible) + if ($field['visibility_id:name'] === 'public' || + ($field['visibility_id:name'] === 'admin' && $adminFieldVisible) ) { $options = $field['options'] ?? NULL; if ($this->_membershipContactID && $options) { @@ -508,7 +508,7 @@ private function buildPriceSet(&$form, $component = NULL) { } if (!CRM_Core_Permission::check('edit contributions')) { foreach ($options as $key => $currentOption) { - if ($currentOption['visibility_id'] == CRM_Price_BAO_PriceField::getVisibilityOptionID('admin')) { + if ($currentOption['visibility_id:name'] === 'admin') { unset($options[$key]); } } diff --git a/CRM/Contribute/Form/ContributionBase.php b/CRM/Contribute/Form/ContributionBase.php index e1d433ff8aad..1b0b80b78694 100644 --- a/CRM/Contribute/Form/ContributionBase.php +++ b/CRM/Contribute/Form/ContributionBase.php @@ -556,7 +556,7 @@ private function initSet($form) { if ($form->_action & CRM_Core_Action::UPDATE) { $form->_values['line_items'] = CRM_Price_BAO_LineItem::getLineItems($form->_id, 'contribution'); } - $form->_priceSet = [$this->getPriceSetID() => $this->order->getPriceSetMetadata()]; + $form->_priceSet = $this->order->getPriceSetMetadata(); $this->setPriceFieldMetaData($this->order->getPriceFieldsMetadata()); $form->set('priceSet', $form->_priceSet); } diff --git a/CRM/Financial/BAO/Order.php b/CRM/Financial/BAO/Order.php index d422c7865f34..7f05c741c3fc 100644 --- a/CRM/Financial/BAO/Order.php +++ b/CRM/Financial/BAO/Order.php @@ -711,8 +711,6 @@ public function getPriceSetMetadata(): array { if (empty($this->priceSetMetadata)) { $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($this->priceSetMetadata['fields']); unset($this->priceSetMetadata['fields']); } @@ -725,8 +723,7 @@ public function isMembershipPriceSet() { } // 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); + return in_array(CRM_Core_Component::getComponentID('CiviMember'), $priceSetMetadata['extends'], FALSE); } /** diff --git a/CRM/Price/BAO/PriceField.php b/CRM/Price/BAO/PriceField.php index cb5903a138a3..1b19622a54e2 100644 --- a/CRM/Price/BAO/PriceField.php +++ b/CRM/Price/BAO/PriceField.php @@ -313,7 +313,7 @@ public static function addQuickFormElement( if (!empty($fieldOptions[$optionKey]['label'])) { //check for label. - $label = $fieldOptions[$optionKey]['label']; + $label = CRM_Utils_String::purifyHTML($fieldOptions[$optionKey]['label']); } // @todo - move this back to the only calling function on Contribution_Form_Main.php if ($isQuickConfig && $field->name === 'other_amount') { @@ -383,7 +383,7 @@ public static function addQuickFormElement( $qf->assign('membershipFieldID', $field->id); } - $choice[$opt['id']] = $priceOptionText['label']; + $choice[$opt['id']] = CRM_Utils_String::purifyHTML($priceOptionText['label']); $choiceAttrs[$opt['id']] = $extra; if ($is_pay_later) { $qf->add('text', 'txt-' . $elementName, $label, ['size' => '4']); @@ -771,12 +771,12 @@ public static function priceSetValidation($priceSetId, $fields, &$error, $allowN */ public static function buildPriceOptionText($opt, $isDisplayAmounts, $valueFieldName) { $preHelpText = $postHelpText = ''; - $optionLabel = !empty($opt['label']) ? '' . $opt['label'] . '' : ''; - if (!empty($opt['help_pre'])) { - $preHelpText = '' . $opt['help_pre'] . ''; + $optionLabel = !empty($opt['label']) ? '' . CRM_Utils_String::purifyHTML($opt['label']) . '' : ''; + if (CRM_Utils_String::purifyHTML($opt['help_pre'] ?? '')) { + $preHelpText = '' . CRM_Utils_String::purifyHTML($opt['help_pre']) . ''; } - if (!empty($opt['help_post'])) { - $postHelpText = '' . $opt['help_post'] . ''; + if (CRM_Utils_String::purifyHTML($opt['help_post'] ?? '')) { + $postHelpText = '' . CRM_Utils_String::purifyHTML($opt['help_post']) . ''; } $invoicing = Civi::settings()->get('invoicing'); diff --git a/CRM/Price/BAO/PriceSet.php b/CRM/Price/BAO/PriceSet.php index 8e6048016b4c..8d5661c9216d 100644 --- a/CRM/Price/BAO/PriceSet.php +++ b/CRM/Price/BAO/PriceSet.php @@ -15,6 +15,10 @@ * @copyright CiviCRM LLC https://civicrm.org/licensing */ +use Civi\Api4\PriceField; +use Civi\Api4\PriceFieldValue; +use Civi\Api4\PriceSet; + /** * Business object for managing price sets. * @@ -757,30 +761,50 @@ public static function filterPriceFieldsFromParams($priceSetID, $params) { } /** - * Wrapper for getSetDetail with caching. + * Get PriceSet + Fields + FieldValues nested, with caching. + * + * This gets the same values as getSet but uses apiv4 for more + * predictability & better variable typing. * * We seem to be passing this array around in a painful way - presumably to avoid the hit * of loading it - so lets make it callable with caching. * - * Why not just add caching to the other function? We could do - it just seemed a bit unclear the best caching pattern - * & the function was already pretty fugly. Also, I feel like we need to migrate the interaction with price-sets into - * a more granular interaction - ie. retrieve specific data using specific functions on this class & have the form - * think less about the price sets. - * * @param int $priceSetID * * @return array + * + * @noinspection PhpUnhandledExceptionInspection */ - public static function getCachedPriceSetDetail($priceSetID) { + public static function getCachedPriceSetDetail(int $priceSetID): array { $cacheKey = __CLASS__ . __FUNCTION__ . '_' . $priceSetID; $cache = CRM_Utils_Cache::singleton(); - $values = $cache->get($cacheKey); - if (empty($values)) { - $data = self::getSetDetail($priceSetID); - $values = $data[$priceSetID]; - $cache->set($cacheKey, $values); + $data = $cache->get($cacheKey); + if (empty($data)) { + $data = PriceSet::get(FALSE) + ->addWhere('id', '=', $priceSetID) + ->addSelect('*', 'visibility_id:name', 'extends:name') + ->execute()->first(); + $data['fields'] = (array) PriceField::get(FALSE) + ->addWhere('price_set_id', '=', $priceSetID) + ->addSelect('*', 'visibility_id:name') + ->execute()->indexBy('id'); + foreach ($data['fields'] as &$field) { + $field['options'] = []; + // Add in visibility because Smarty templates expect it and it is hard to adjust them to colon format. + $field['visibility'] = $field['visibility_id:name']; + } + $options = PriceFieldValue::get(FALSE) + ->addWhere('price_field_id', 'IN', array_keys($data['fields'])) + ->addSelect('*', 'membership_type_id.name', 'visibility_id:name') + ->execute(); + foreach ($options as $option) { + // Add in visibility because Smarty templates expect it and it is hard to adjust them to colon format. + $option['visibility'] = $option['visibility_id:name']; + $data['fields'][$option['price_field_id']]['options'][$option['id']] = $option; + } + $cache->set($cacheKey, $data); } - return $values; + return $data; } /** diff --git a/templates/CRM/Price/Form/Field.tpl b/templates/CRM/Price/Form/Field.tpl index aee89a9db979..6753cb935469 100644 --- a/templates/CRM/Price/Form/Field.tpl +++ b/templates/CRM/Price/Form/Field.tpl @@ -81,12 +81,12 @@
- + - + @@ -103,7 +103,7 @@
{$form.label.label}{$form.label.label|escape} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_price_field' field='label' id=$fid}{/if}{$form.label.html}
{$form.html_type.label}{$form.html_type.label|escape} {$form.html_type.html}
- + - + {if $useForEvent} - + - + {/if} - + - + - + {/if} - + - + @@ -44,11 +44,11 @@ - + - + diff --git a/templates/CRM/Price/Form/PriceSet.tpl b/templates/CRM/Price/Form/PriceSet.tpl index 67c1e9be2207..c717226b5b93 100644 --- a/templates/CRM/Price/Form/PriceSet.tpl +++ b/templates/CRM/Price/Form/PriceSet.tpl @@ -10,7 +10,7 @@ {crmRegion name="price-set-1"}
{if $priceSet.help_pre} -
{$priceSet.help_pre}
+
{$priceSet.help_pre|escape}
{/if} {assign var='adminFld' value=false} @@ -28,12 +28,12 @@ {foreach from=$priceSet.fields item=element key=field_id} {* Skip 'Admin' visibility price fields WHEN this tpl is used in online registration unless user has administer CiviCRM permission. *} {if $element.visibility EQ 'public' || ($element.visibility EQ 'admin' && $adminFld EQ true) || $context eq 'standalone' || $context eq 'advanced' || $context eq 'search' || $context eq 'participant' || $context eq 'dashboard'} - {if $element.help_pre}{$element.help_pre}
{/if} -
+ {if $element.help_pre}{$element.help_pre|escape}
{/if} +
{if ($element.html_type eq 'CheckBox' || $element.html_type == 'Radio') && $element.options_per_line} {assign var="element_name" value="price_`$field_id`"} -
{$form.$element_name.label}
-
+
{$form.$element_name.label|purify}
+
{assign var="elementCount" value="0"} {assign var="optionCount" value="0"} {assign var="rowCount" value="0"} @@ -43,7 +43,7 @@ {assign var="optionCount" value=$optionCount+1} {if $optionCount == 1} {assign var="rowCount" value=$rowCount+1} -
+
{/if} {$form.$element_name.$key.html} {if $optionCount == $element.options_per_line || $elementCount == $form.$element_name|@count} @@ -53,15 +53,15 @@ {/if} {/foreach} {if $element.help_post} -
{$element.help_post}
+
{$element.help_post|escape}
{/if}
{else} {assign var="element_name" value="price_"|cat:$field_id} -
{$form.$element_name.label}
-
+
{$form.$element_name.label|escape}
+
{$form.$element_name.html} {if $element.html_type eq 'Text'} {if $element.is_display_amounts} @@ -91,7 +91,7 @@ {/if} {/if} {/if} - {if $element.help_post}
{$element.help_post}{/if} + {if $element.help_post}
{$element.help_post|escape}{/if}
{/if} @@ -102,7 +102,7 @@
{if $form.auto_renew} - {$form.auto_renew.html} {$form.auto_renew.label} + {$form.auto_renew.html} {$form.auto_renew.label|escape} {/if}
@@ -116,7 +116,7 @@ {/foreach} {if $priceSet.help_post} -
{$priceSet.help_post}
+
{$priceSet.help_post|escape}
{/if} {include file="CRM/Price/Form/Calculate.tpl"} diff --git a/templates/CRM/Price/Page/Option.tpl b/templates/CRM/Price/Page/Option.tpl index 90faab6cfebf..1fab0ed7dcef 100644 --- a/templates/CRM/Price/Page/Option.tpl +++ b/templates/CRM/Price/Page/Option.tpl @@ -59,11 +59,11 @@
{foreach from=$customOption item=row} - + - - + + {if $isEvent} diff --git a/tests/phpunit/CRM/Contribute/Form/Contribution/MainTest.php b/tests/phpunit/CRM/Contribute/Form/Contribution/MainTest.php index abd3fedfe6e3..4c8fc4b34d8b 100644 --- a/tests/phpunit/CRM/Contribute/Form/Contribution/MainTest.php +++ b/tests/phpunit/CRM/Contribute/Form/Contribution/MainTest.php @@ -214,7 +214,44 @@ public function testExpiredPriceSet(): void { $allFields = $form->getPriceFieldMetadata(); $this->assertCount(1, $allFields); $field = current($allFields); - $this->assertEquals('test_valid_price_field', $field['name']); + $expectedValues = [ + 'name' => 'test_valid_price_field', + 'label' => 'test valid price field', + 'html_type' => 'Radio', + 'is_enter_qty' => 1, + 'weight' => TRUE, + 'is_display_amounts' => TRUE, + 'options_per_line' => 1, + 'is_active' => TRUE, + 'active_on' => NULL, + 'expire_on' => NULL, + 'javascript' => NULL, + 'visibility_id:name' => 'public', + 'visibility_id' => 1, + 'is_required' => TRUE, + ]; + foreach ($expectedValues as $key => $value) { + $this->assertEquals($value, $field[$key]); + } + + $option = reset($field['options']); + $expectedValues = [ + 'name' => 'rye grass', + 'label' => 'juicy and healthy', + 'amount' => 1, + 'weight' => 1, + 'membership_type_id' => $this->ids['MembershipType']['test'], + 'membership_num_terms' => 2, + 'is_default' => FALSE, + 'is_active' => TRUE, + 'financial_type_id' => '1', + 'non_deductible_amount' => 0, + 'visibility_id' => '1', + ]; + foreach ($expectedValues as $key => $value) { + $this->assertEquals($value, $option[$key]); + } + } /**
{$form.price.label} *{$form.price.label|escape} * {$form.price.html} {if $action neq 4}
{ts}Unit price.{/ts} {help id="id-negative"} @@ -111,25 +111,25 @@
{$form.non_deductible_amount.label}{$form.non_deductible_amount.label|escape} {$form.non_deductible_amount.html}
{$form.count.label}{$form.count.label|escape} {$form.count.html}
{ts}Enter a value here if you want to increment the number of registered participants per unit against the maximum number of participants allowed for this event.{/ts} {help id="id-participant-count"}
{$form.max_value.label}{$form.max_value.label|escape} {$form.max_value.html}
{$form.financial_type_id.label}*{$form.financial_type_id.label|escape}* {if !$financialType} {capture assign=ftUrl}{crmURL p='civicrm/admin/financial/financialType' q="reset=1"}{/capture} @@ -169,7 +169,7 @@
{$form.help_pre.label}{$form.help_pre.label|escape} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_price_field' field='help_pre' id=$fid}{/if}{$form.help_pre.html|crmAddClass:huge}  {if $action neq 4}
{ts}Explanatory text displayed to users at the beginning of this field.{/ts}
@@ -178,7 +178,7 @@
{$form.help_post.label}{$form.help_post.label|escape} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_price_field' field='help_post' id=$fid}{/if}{$form.help_post.html|crmAddClass:huge}  {if $action neq 4}
{ts}Explanatory text displayed to users below this field.{/ts}
diff --git a/templates/CRM/Price/Form/Option.tpl b/templates/CRM/Price/Form/Option.tpl index 52b8ad7a60b5..f64d2b7df087 100644 --- a/templates/CRM/Price/Form/Option.tpl +++ b/templates/CRM/Price/Form/Option.tpl @@ -28,11 +28,11 @@
{$form.label.label}{$form.label.label|escape} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_price_field_value' field='label' id=$optionId}{/if}{$form.label.html}
{$form.amount.label}{$form.amount.label|escape} {$form.amount.html}
{if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_price_field_value' field='description' id=$optionId}{/if}{$form.description.html}
{$form.help_pre.label}{$form.help_pre.label|escape} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_price_field_value' field='help_pre' id=$optionId}{/if}{$form.help_pre.html}
{$form.help_post.label}{$form.help_post.label|escape} {if $action == 2}{include file='CRM/Core/I18n/Dialog.tpl' table='civicrm_price_field_value' field='help_post' id=$optionId}{/if}{$form.help_post.html}
{$row.label}{$row.label|escape} {$row.amount|crmMoney} {$row.non_deductible_amount|crmMoney}{$row.help_pre}{$row.help_post}{$row.help_pre|escape}{$row.help_post|escape}{$row.count} {$row.max_value}