CRM-12167: Finish Implementing Visibility per Price Option
authorCamilo Rodriguez <camilo@compucorp.co.uk>
Fri, 25 Aug 2017 15:16:28 +0000 (15:16 +0000)
committerCamilo Rodriguez <camilo@compucorp.co.uk>
Mon, 25 Sep 2017 14:53:00 +0000 (14:53 +0000)
Added validations so that:
- A public price field has at least one public option
- An admin price field has all admin options

Implemented removal for Select and Checkbox field types. Also added condition
to remove Admin values only if logged user doen't have appropriate
permissions.

Implemented different permissions check to see if admin options should be
deleted or not if the price set is being used for a contribution page or an
event page.

Implemented functionality so that if a user chooses Admin visibility for the
price field, all options are forced to have Admin visibility as well.

Added default visibility as public on creation, in case it's not set, and
protected access to unexisting visibility_id array key.

Moved SQL to add new visibility field to PHP upgrader for v4.7.26.

15 files changed:
CRM/Event/Form/Registration/Register.php
CRM/Price/BAO/PriceField.php
CRM/Price/BAO/PriceSet.php
CRM/Price/Form/Field.php
CRM/Price/Form/Option.php
CRM/Upgrade/Incremental/php/FourSeven.php
CRM/Upgrade/Incremental/sql/4.7.23.mysql.tpl [changed mode: 0755->0644]
templates/CRM/Price/Form/Field.tpl
templates/CRM/Price/Form/Option.tpl
templates/CRM/Price/Form/OptionFields.tpl
templates/CRM/Price/Form/PriceSet.tpl
templates/CRM/Price/Page/Field.hlp
tests/phpunit/CRM/Price/BAO/PriceFieldValueTest.php
tests/phpunit/CRM/Price/Form/FieldTest.php
tests/phpunit/CRM/Price/Form/OptionTest.php

index c9114de5dfdbef3823c4874977cae51626a5cd11..f2418bfc0214c126e85c1368758c752f90900d1c 100644 (file)
@@ -566,6 +566,11 @@ class CRM_Event_Form_Registration_Register extends CRM_Event_Form_Registration {
         $adminFieldVisible = TRUE;
       }
 
+      $hideAdminValues = TRUE;
+      if (CRM_Core_Permission::check('edit event participants')) {
+        $hideAdminValues = FALSE;
+      }
+
       foreach ($form->_feeBlock as $field) {
         // public AND admin visibility fields are included for back-office registration and back-office change selections
         if (CRM_Utils_Array::value('visibility', $field) == 'public' ||
@@ -583,9 +588,18 @@ class CRM_Event_Form_Registration_Register extends CRM_Event_Form_Registration {
 
           //user might modified w/ hook.
           $options = CRM_Utils_Array::value('options', $field);
+          $formClasses = array('CRM_Event_Form_Participant', 'CRM_Event_Form_ParticipantFeeSelection');
+
           if (!is_array($options)) {
             continue;
           }
+          elseif ($hideAdminValues && !in_array($className, $formClasses)) {
+            foreach ($options as $key => $currentOption) {
+              if ($currentOption['visibility_id'] == CRM_Price_BAO_PriceField::getVisibilityOptionID('admin')) {
+                unset($options[$key]);
+              }
+            }
+          }
 
           $optionFullIds = CRM_Utils_Array::value('option_full_ids', $field, array());
 
index 4a5c4649b54c74dfae0cd75daab0f5aa2e81e410..025f62e3ee646d5754ac273e56f33435e139507d 100644 (file)
@@ -41,6 +41,13 @@ class CRM_Price_BAO_PriceField extends CRM_Price_DAO_PriceField {
 
   protected $_options;
 
+  /**
+   * List of visibility option ID's, of the form name => ID
+   *
+   * @var array
+   */
+  private static $visibilityOptionsKeys;
+
   /**
    * Takes an associative array and creates a price field object.
    *
@@ -75,6 +82,7 @@ class CRM_Price_BAO_PriceField extends CRM_Price_DAO_PriceField {
    *   (reference) an assoc array of name/value pairs.
    *
    * @return CRM_Price_DAO_PriceField
+   * @throws \CRM_Core_Exception
    */
   public static function create(&$params) {
     if (empty($params['id']) && empty($params['name'])) {
@@ -147,7 +155,7 @@ class CRM_Price_BAO_PriceField extends CRM_Price_DAO_PriceField {
           'is_default' => CRM_Utils_Array::value($params['option_weight'][$index], $defaultArray) ? $defaultArray[$params['option_weight'][$index]] : 0,
           'membership_num_terms' => NULL,
           'non_deductible_amount' => CRM_Utils_Array::value('non_deductible_amount', $params),
-          'visibility_id' => $params['option_visibility_id'][$index],
+          'visibility_id' => CRM_Utils_Array::value($index, CRM_Utils_Array::value('option_visibility_id', $params), self::getVisibilityOptionID('public')),
         );
 
         if ($options['membership_type_id']) {
@@ -436,7 +444,7 @@ class CRM_Price_BAO_PriceField extends CRM_Price_DAO_PriceField {
             $visibility_id = $opt['visibility_id'];
           }
           else {
-            $visibility_id = 1;
+            $visibility_id = self::getVisibilityOptionID('public');
           }
           $extra = array(
             'price' => json_encode(array($elementName, $priceVal)),
@@ -548,7 +556,7 @@ class CRM_Price_BAO_PriceField extends CRM_Price_DAO_PriceField {
           $visibility_id = $opt['visibility_id'];
         }
         else {
-          $visibility_id = 1;
+          $visibility_id = self::getVisibilityOptionID('public');
         }
         $element = &$qf->add('select', $elementName, $label,
           array(
@@ -596,6 +604,7 @@ class CRM_Price_BAO_PriceField extends CRM_Price_DAO_PriceField {
               'price' => json_encode(array($opt['id'], $priceVal)),
               'data-amount' => $opt[$valueFieldName],
               'data-currency' => $currencyName,
+              'visibility' => $opt['visibility_id'],
             )
           );
           if ($is_pay_later) {
@@ -873,4 +882,31 @@ WHERE  id IN (" . implode(',', array_keys($priceFields)) . ')';
     return $label;
   }
 
+  /**
+   * Given the name of a visibility option, returns its ID.
+   *
+   * @param string $visibilityName
+   *
+   * @return int
+   */
+  public static function getVisibilityOptionID($visibilityName) {
+
+    if (!isset(self::$visibilityOptionsKeys)) {
+      self::$visibilityOptionsKeys = CRM_Price_BAO_PriceField::buildOptions(
+        'visibility_id',
+        NULL,
+        array(
+          'labelColumn' => 'name',
+          'flip' => TRUE,
+        )
+      );
+    }
+
+    if (isset(self::$visibilityOptionsKeys[$visibilityName])) {
+      return self::$visibilityOptionsKeys[$visibilityName];
+    }
+
+    return 0;
+  }
+
 }
index 7d92106f670e91f4b693a70fc95da2c97589e30c..e1dabf52027eb00d1c227746d6ca6ee32a928db9 100644 (file)
@@ -1033,6 +1033,11 @@ WHERE  id = %1";
       $adminFieldVisible = TRUE;
     }
 
+    $hideAdminValues = TRUE;
+    if (CRM_Core_Permission::check('edit contributions')) {
+      $hideAdminValues = FALSE;
+    }
+
     foreach ($feeBlock as $id => $field) {
       if (CRM_Utils_Array::value('visibility', $field) == 'public' ||
         (CRM_Utils_Array::value('visibility', $field) == 'admin' && $adminFieldVisible == TRUE) ||
@@ -1046,9 +1051,19 @@ WHERE  id = %1";
             $form->assign('ispricelifetime', TRUE);
           }
         }
+
+        $formClasses = array('CRM_Contribute_Form_Contribution', 'CRM_Member_Form_Membership');
+
         if (!is_array($options) || !in_array($id, $validPriceFieldIds)) {
           continue;
         }
+        elseif ($hideAdminValues && !in_array($className, $formClasses)) {
+          foreach ($options as $key => $currentOption) {
+            if ($currentOption['visibility_id'] == CRM_Price_BAO_PriceField::getVisibilityOptionID('admin')) {
+              unset($options[$key]);
+            }
+          }
+        }
         if (!empty($options)) {
           CRM_Price_BAO_PriceField::addQuickFormElement($form,
             'price_' . $field['id'],
index ac32ccff260a98360bc67bd17e3f0407d90e7545..75ce1a44b1f67df10ea7ea78a2e57ca652362cd2 100644 (file)
@@ -442,9 +442,10 @@ class CRM_Price_Form_Field extends CRM_Core_Form {
     if ($form->_action & CRM_Core_Action::ADD) {
       if ($fields['html_type'] != 'Text') {
         $countemptyrows = 0;
-        $_flagOption = $_rowError = 0;
+        $publicOptionCount = $_flagOption = $_rowError = 0;
 
         $_showHide = new CRM_Core_ShowHideBlocks('', '');
+        $visibilityOptions = CRM_Price_BAO_PriceFieldValue::buildOptions('visibility_id', NULL, array('labelColumn' => 'name'));
 
         for ($index = 1; $index <= self::NUM_OPTION; $index++) {
 
@@ -530,6 +531,12 @@ class CRM_Price_Form_Field extends CRM_Core_Form {
             $_showHide->addHide($hideBlock);
           }
 
+          if (!empty($fields['option_visibility_id'][$index]) && (!$noLabel || !$noAmount)) {
+            if ($visibilityOptions[$fields['option_visibility_id'][$index]] == 'public') {
+              $publicOptionCount++;
+            }
+          }
+
           $_flagOption = $_emptyRow = 0;
         }
 
@@ -577,6 +584,29 @@ class CRM_Price_Form_Field extends CRM_Core_Form {
           $errors['option_label[1]'] = $errors['option_amount[1]'] = ts('Label and value cannot be empty.');
           $_flagOption = 1;
         }
+
+        if ($visibilityOptions[$fields['visibility_id']] == 'public' && $publicOptionCount == 0) {
+          $errors['visibility_id'] = ts('You have selected to make this field public but have not enabled any public price options. Please update your selections to include a public price option, or make this field admin visibility only.');
+          for ($index = 1; $index <= self::NUM_OPTION; $index++) {
+            if (!empty($fields['option_label'][$index]) || !empty($fields['option_amount'][$index])) {
+              $errors["option_visibility_id[{$index}]"] = ts('Public field should at least have one public option.');
+            }
+          }
+        }
+
+        if ($visibilityOptions[$fields['visibility_id']] == 'admin' && $publicOptionCount > 0) {
+          $errors['visibility_id'] = ts('Field with \'Admin\' visibility should only contain \'Admin\' options.');
+
+          for ($index = 1; $index <= self::NUM_OPTION; $index++) {
+
+            $isOptionSet = !empty($fields['option_label'][$index]) || !empty($fields['option_amount'][$index]);
+            $currentOptionVisibility = CRM_Utils_Array::value($fields['option_visibility_id'][$index], $visibilityOptions);
+
+            if ($isOptionSet && $currentOptionVisibility == 'public') {
+              $errors["option_visibility_id[{$index}]"] = ts('\'Admin\' field should only have \'Admin\' visibility options.');
+            }
+          }
+        }
       }
       elseif (!empty($fields['max_value']) &&
         !empty($fields['count']) &&
index fbb20487149ad2d9119d36aa37f696bb4813344f..022697a744b4c1cf5e04311cbd345b2e6474e29c 100644 (file)
@@ -291,6 +291,27 @@ class CRM_Price_Form_Option extends CRM_Core_Form {
     ) {
       $errors['count'] = ts('Participant count can not be greater than max participants.');
     }
+
+    $priceField = CRM_Price_BAO_PriceField::findById($fields['fieldId']);
+    $visibilityOptions = CRM_Price_BAO_PriceFieldValue::buildOptions('visibility_id', NULL, array('labelColumn' => 'name'));
+
+    $publicCount = 0;
+    $options = CRM_Price_BAO_PriceField::getOptions($priceField->id);
+    foreach ($options as $currentOption) {
+      if ($fields['optionId'] == $currentOption['id'] && $visibilityOptions[$fields['visibility_id']] == 'public') {
+        $publicCount++;
+      }
+      elseif ($fields['optionId'] != $currentOption['id'] && $visibilityOptions[$currentOption['visibility_id']] == 'public') {
+        $publicCount++;
+      }
+    }
+    if ($visibilityOptions[$priceField->visibility_id] == 'public' && $publicCount == 0) {
+      $errors['visibility_id'] = ts('All other options for this \'Public\' field have \'Admin\' visibility. There should at least be one \'Public\' option, or make the field \'Admin\' only.');
+    }
+    elseif ($visibilityOptions[$priceField->visibility_id] == 'admin' && $publicCount > 0) {
+      $errors['visibility_id'] = ts('You must choose \'Admin\' visibility for this price option, as it belongs to a field with \'Admin\' visibility.');
+    }
+
     return empty($errors) ? TRUE : $errors;
   }
 
index a8a23363c4615f8a800b67959b0a42339dc28fc2..50c28a99495c90ceac0099551e69b0e504deacee 100644 (file)
@@ -451,6 +451,8 @@ class CRM_Upgrade_Incremental_php_FourSeven extends CRM_Upgrade_Incremental_Base
 
     $this->addTask('CRM-21195 - Add icon field to civicrm_navigation', 'addColumn',
       'civicrm_navigation', 'icon', "varchar(255) NULL DEFAULT NULL COMMENT 'CSS class name for an icon'");
+    $this->addTask('CRM-12167 - Add visibility column to civicrm_price_field_value', 'addColumn',
+      'civicrm_price_field_value', 'visibility_id', 'int(10) unsigned DEFAULT 1 COMMENT "Implicit FK to civicrm_option_group with name = \'visibility\'"');
   }
 
   /*
old mode 100755 (executable)
new mode 100644 (file)
index 55dede9..dbedca1
@@ -41,7 +41,3 @@ ON price_field.price_field_id = cpf.id
 LEFT JOIN civicrm_price_set ps ON ps.id = cpf.price_set_id
 SET cpf.is_active = 1
 WHERE ps.is_quick_config = 1 AND cpf.is_active = 0;
-
--- CRM-12167
-ALTER TABLE `civicrm_price_field_value`
-ADD COLUMN `visibility_id` int(10);
index cddc9e0a355def29265cefbe420013eb62c09cb4..8e18ab3e557024f4f7aa201c88913a62a9b0b08c 100644 (file)
     }
 
   }
+
+  var adminVisibilityID = 0;
+  cj('#visibility_id').on('change', function () {
+    if (adminVisibilityID == 0) {
+      CRM.api3('OptionValue', 'getvalue', {
+        'sequential': 1,
+        'return': 'value',
+        'option_group_id': 'visibility',
+        'name': 'admin'
+      }).done(function(result) {
+        adminVisibilityID = result.result;
+        if (cj('#visibility_id').val() == adminVisibilityID) {
+          updateVisibilitySelects(adminVisibilityID);
+        }
+      });
+    } else {
+      if (cj('#visibility_id').val() == adminVisibilityID) {
+        updateVisibilitySelects(adminVisibilityID);
+      }
+    }
+  });
+
+  function updateVisibilitySelects(value) {
+    for (var i=1; i<=15; i++) {
+      cj('#option_visibility_id_' + i).val(value);
+    }
+  }
 </script>
 {/literal}
 <div class="crm-block crm-form-block crm-price-field-form-block">
index addfdc2df76d3018d3beabb75fddf9ff000ddf27..767c02c6dda330c779a201be2bb8d940833405ab 100644 (file)
       {/if}
       <tr class="crm-price-field-form-block-visibility_id">
         <td class="label">{$form.visibility_id.label}</td>
-        <td>&nbsp;{$form.visibility_id.html} {help id="id-visibility"}</td>
+        <td>&nbsp;{$form.visibility_id.html} {help id="id-visibility-options" file="CRM/Price/Page/Field.hlp"}</td>
       </tr>
     </table>
 
index 0b11d28bbc9328e54c285c34e8353619bb9bd207..2cc2b7e0068fb1a9979be54523676b2d1d0799c6 100644 (file)
@@ -48,7 +48,7 @@
       <th>{ts}Max Participant{/ts} {help id="id-participant-max"}</th>
   {/if}
         <th>{ts}Order{/ts}</th>
-        <th>{ts}Visibility{/ts}</th>
+        <th>{ts}Visibility{/ts} {help id="id-visibility-options"}</th>
         <th>{ts}Active?{/ts}</th>
     </tr>
 
index 87210e0a22cb1617387b14bd6faee67f527cd09e..4979744ab1d0695971b0a0b6b5a21e56a96a8eea 100644 (file)
                     {/if}
                   {/if}
                 {/foreach}
-                {literal}
-                 <script>
-                   cj('input').each(function(){
-                     if (cj(this).attr('visibility') == 2 && typeof adminpage=='undefined'){
-                       cj(this).parent().hide();
-                     }
-                   });
-                 </script>
-               {/literal}
                 {if $element.help_post}
                   <div class="description">{$element.help_post}</div>
                 {/if}
index d07006d441f8fee031ab650e7b52119c483f40ee..ec2097e4c27f0f702fe13add18ba785dec427ce7 100644 (file)
     {ts}You may enter a negative amount value if the field or option is being used to discount the total price.{/ts}
 {/htxt}
 
+{htxt id="id-visibility-options-title"}
+  {ts}Visibility per Option{/ts}
+{/htxt}
+{htxt id="id-visibility-options"}
+  {ts}Select between Admin/Public for each option. 'Public' will show the option to all users accessing the price set. Use 'Admin' to limit the option to users with the 'CiviEvent: edit event participants’ permission (when viewing the field via the event registration pages) or the ‘CiviContribute: edit contributions’ permission (when accessing the field through a contribution page).{/ts}
+{/htxt}
+
 {htxt id="id-participant-count-title"}
   {ts}Participant Count{/ts}
 {/htxt}
@@ -56,7 +63,7 @@
   {ts}Visibility{/ts}
 {/htxt}
 {htxt id="id-visibility"}
-    {ts}Fields with 'Public' visibility will be displayed on your Event Information page AND will be available in the online registration (self-service) form. For some events you may want to allow staff to select special options with special pricing and / or discounts (using negative price set field values). Select 'Admin' visibility for these Price Fields. They will only be included when staff or volunteers are registering participants from the back-office 'Register Event Participants' screen.{/ts}
+  {ts}Fields with 'Public' visibility will be displayed on your Event Information page AND will be available in the online registration (self-service) form. For some events you may want to allow staff to select special options with special pricing and / or discounts (using negative price set field values). Select 'Admin' visibility for these Price Fields. They will only be included when staff or volunteers are registering participants from the back-office 'Register Event Participants' screen. If the parent price field visibility is ‘Public’ then it should be possible to set individual price options as 'Admin', but it should have at least one 'Public' option. If the parent price field visibility is 'Admin', then all the price field options should be set to 'Admin', including any new options added to the form.{/ts}
 {/htxt}
 
 {htxt id="id-member-price-options-title"}
index c8d3252b8aee88df71e74f7ee51ff15520310e6f..ad083040b242b985755b34b01a8f82f2f0af485d 100755 (executable)
@@ -54,4 +54,5 @@ class CRM_Price_BAO_PriceFieldValueTest extends CiviUnitTestCase {
     $this->assertArrayKeyExists('visibility_id', $fields);
     $this->assertEquals('visibility', $fields['visibility_id']['pseudoconstant']['optionGroupName']);
   }
+
 }
index f87528f1a20f6cf5b7dcf4e6fd708b26267d67b8..d1c13513bc0e13d8831b6c63ec4cb9301e41e18f 100755 (executable)
@@ -71,14 +71,15 @@ class CRM_Price_Form_FieldTest extends CiviUnitTestCase {
     );\r
 \r
     for ($index = 1; $index <= CRM_Price_Form_Field::NUM_OPTION; $index++) {\r
-      $defaultParams['option_label'][$index] = null;\r
-      $defaultParams['option_value'][$index] = null;\r
-      $defaultParams['option_name'][$index] = null;\r
-      $defaultParams['option_weight'][$index] = null;\r
-      $defaultParams['option_amount'][$index] = null;\r
-      $defaultParams['option_visibility_id'][$index] = null;\r
+      $defaultParams['option_label'][$index] = NULL;\r
+      $defaultParams['option_value'][$index] = NULL;\r
+      $defaultParams['option_name'][$index] = NULL;\r
+      $defaultParams['option_weight'][$index] = NULL;\r
+      $defaultParams['option_amount'][$index] = NULL;\r
+      $defaultParams['option_visibility_id'][$index] = NULL;\r
     }\r
 \r
     return array_merge($defaultParams, $params);\r
   }\r
+\r
 }\r
index 8b31935759152311e770fa8ed93c466c79d1ccf8..4d297b86c42e95faf47168b3611d32dcdb1a5972 100755 (executable)
@@ -82,10 +82,11 @@ class CRM_Price_Form_OptionTest extends CiviUnitTestCase {
     foreach ($this->priceFieldValues as $currentField) {\r
       if ($this->visibilityOptions[$currentField['visibility_id']] == 'public') {\r
         $this->publicValue = $currentField;\r
-      } else {\r
+      }\r
+      else {\r
         $this->adminValue = $currentField;\r
       }\r
     }\r
-\r
   }\r
+\r
 }\r