CRM-17075 switch membership_type_id to use entity reference
authoreileenmcnaugton <eileen@fuzion.co.nz>
Mon, 14 Sep 2015 00:55:24 +0000 (12:55 +1200)
committereileenmcnaugton <eileen@fuzion.co.nz>
Mon, 14 Sep 2015 09:03:50 +0000 (21:03 +1200)
This commit also adds generic handling for changing select fields to
entity reference fields with tests to ensure previous smart group
behaviour is respected by handling within the convertFormValues function.

This reverts advanced search domain filtering to ?4.4? 4.5? behaviour which does not assume only that domain's memberships should be searched on.

It make be that domain filtering + allowing an override makes most sense. I did think the pseudoconstant & getoptions use the same
mechanism, but it seems they don't. Really they should both allow the same behaviour & override

CRM/Contact/BAO/Query.php
CRM/Core/Form/Search.php
CRM/Member/BAO/Query.php
CRM/Member/Form/Search.php
tests/phpunit/CRM/Member/BAO/QueryTest.php [new file with mode: 0644]

index 87301322f8b6daa4389d2eb91ae9fbd0214d4f49..cd69b39b1ecc639db6a83aa2215c712c72f79678 100644 (file)
@@ -1465,15 +1465,23 @@ class CRM_Contact_BAO_Query {
    *
    * @param string $apiEntity
    *
+   * @param array $entityReferenceFields
+   *   Field names of any entity reference fields (which will need reformatting to IN syntax).
+   *
    * @return array
    */
-  public static function convertFormValues(&$formValues, $wildcard = 0, $useEquals = FALSE, $apiEntity = NULL) {
+  public static function convertFormValues(&$formValues, $wildcard = 0, $useEquals = FALSE, $apiEntity = NULL,
+    $entityReferenceFields = array()) {
     $params = array();
     if (empty($formValues)) {
       return $params;
     }
 
     foreach ($formValues as $id => $values) {
+      if (self::isAlreadyProcessedForQueryFormat($values)) {
+        $params[] = $values;
+        continue;
+      }
       if ($id == 'privacy') {
         if (is_array($formValues['privacy'])) {
           $op = !empty($formValues['privacy']['do_not_toggle']) ? '=' : '!=';
@@ -1526,6 +1534,10 @@ class CRM_Contact_BAO_Query {
           continue;
         }
       }
+      elseif (in_array($id, $entityReferenceFields) && !empty($values) && is_string($values) && (strpos($values, ',') !=
+        FALSE)) {
+        $params[] = array($id, 'IN', explode(',', $values), 0, 0);
+      }
       else {
         $values = CRM_Contact_BAO_Query::fixWhereValues($id, $values, $wildcard, $useEquals, $apiEntity);
 
@@ -4392,6 +4404,30 @@ civicrm_relationship.is_permission_a_b = 0
     );
   }
 
+  /**
+   * Has this field already been reformatting to Query object syntax.
+   *
+   * The form layer passed formValues to this function in preProcess & postProcess. Reason unknown. This seems
+   * to come with associated double queries & is possibly damaging performance.
+   *
+   * However, here we add a tested function to ensure convertFormValues identifies pre-processed fields & returns
+   * them as they are.
+   *
+   * @param mixed $values
+   *   Value in formValues for the field.
+   *
+   * @return bool;
+   */
+  protected static function isAlreadyProcessedForQueryFormat($values) {
+    if (!is_array($values)) {
+      return FALSE;
+    }
+    if (($operator = CRM_Utils_Array::value(1, $values)) == FALSE) {
+      return FALSE;
+    }
+    return in_array($operator, CRM_Core_DAO::acceptedSQLOperators());
+  }
+
   /**
    * Create and query the db for an contact search.
    *
index 4ff32c0c12cb664c126c18a8e089377de21c26b1..6d83b1c7316856c9d00e5d148cb2d9ea7710edc1 100644 (file)
@@ -79,6 +79,16 @@ class CRM_Core_Form_Search extends CRM_Core_Form {
    */
   protected $_taskList = array();
 
+  /**
+   * Declare entity reference fields as they will need to be converted.
+   *
+   * The entity reference format looks like '2,3' whereas the Query object expects array(2, 3)
+   * or array('IN' => array(2, 3). The latter is the one we are moving towards standardising on.
+   *
+   * @var array
+   */
+  protected $entityReferenceFields = array();
+
   /**
    * Builds the list of tasks or actions that a searcher can perform on a result set.
    *
index 6100658aa3c4b5d3a58996f1c70c73818106126c..09f8089ab5aa89f23805f496507c1535665884b7 100644 (file)
@@ -409,6 +409,12 @@ class CRM_Member_BAO_Query {
       'class' => 'crm-select2',
     ));
 
+    $form->addEntityRef('membership_type_id', ts('Membership Type(s)'), array(
+      'entity' => 'MembershipType',
+      'multiple' => TRUE,
+      'placeholder' => ts('- any -'),
+    ));
+
     $form->addElement('text', 'member_source', ts('Source'));
 
     CRM_Core_Form_Date::buildDateRange($form, 'member_join_date', 1, '_low', '_high', ts('From'), FALSE);
index 47c6e488f3c7cf35c0a89adbacec2927dfe84a7b..7d42293edbcc656f64ae34815f54105cbd80c11e 100644 (file)
@@ -68,6 +68,13 @@ class CRM_Member_Form_Search extends CRM_Core_Form_Search {
    */
   protected $_prefix = "member_";
 
+  /**
+   * Declare entity reference fields as they will need to be converted to using 'IN'.
+   *
+   * @var array
+   */
+  protected $entityReferenceFields = array('membership_type_id');
+
   /**
    * Processing needed for buildForm and later.
    */
@@ -114,7 +121,7 @@ class CRM_Member_Form_Search extends CRM_Core_Form_Search {
       );
     }
 
-    $this->_queryParams = CRM_Contact_BAO_Query::convertFormValues($this->_formValues);
+    $this->_queryParams = CRM_Contact_BAO_Query::convertFormValues($this->_formValues, 0, FALSE, NULL, $this->entityReferenceFields);
     $selector = new CRM_Member_Selector_Search($this->_queryParams,
       $this->_action,
       NULL,
@@ -197,7 +204,7 @@ class CRM_Member_Form_Search extends CRM_Core_Form_Search {
 
     CRM_Core_BAO_CustomValue::fixCustomFieldValue($this->_formValues);
 
-    $this->_queryParams = CRM_Contact_BAO_Query::convertFormValues($this->_formValues);
+    $this->_queryParams = CRM_Contact_BAO_Query::convertFormValues($this->_formValues, 0, FALSE, NULL, $this->entityReferenceFields);
 
     $this->set('formValues', $this->_formValues);
     $this->set('queryParams', $this->_queryParams);
diff --git a/tests/phpunit/CRM/Member/BAO/QueryTest.php b/tests/phpunit/CRM/Member/BAO/QueryTest.php
new file mode 100644 (file)
index 0000000..84a5270
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+require_once 'CiviTest/CiviUnitTestCase.php';
+/**
+ *  Include dataProvider for tests
+ */
+class CRM_Member_BAO_QueryTest extends CiviUnitTestCase {
+
+  /**
+   * Set up function.
+   *
+   * Ensure CiviCase is enabled.
+   */
+  public function setUp() {
+    parent::setUp();
+    CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase');
+  }
+
+  /**
+   * Check that membership type is handled.
+   *
+   * We want to see the following syntaxes for membership_type_id field handled:
+   *   1) membership_type_id => 1
+   */
+  public function testConvertEntityFieldSingleValue() {
+    $formValues = array('membership_type_id' => 2);
+    $params = CRM_Contact_BAO_Query::convertFormValues($formValues, 0, FALSE, NULL, array('membership_type_id'));
+    $this->assertEquals(array('membership_type_id', '=', 2, 0, 0), $params[0]);
+    $obj = new CRM_Contact_BAO_Query($params);
+    $this->assertEquals(array('civicrm_membership.membership_type_id = 2'), $obj->_where[0]);
+  }
+
+  /**
+   * Check that membership type is handled.
+   *
+   * We want to see the following syntaxes for membership_type_id field handled:
+   *   2) membership_type_id => 5,6
+   *
+   * The last of these is the format used prior to converting membership_type_id to an entity reference field.
+   */
+  public function testConvertEntityFieldMultipleValueEntityRef() {
+    $formValues = array('membership_type_id' => '1,2');
+    $params = CRM_Contact_BAO_Query::convertFormValues($formValues, 0, FALSE, NULL, array('membership_type_id'));
+    $this->assertEquals(array('membership_type_id', 'IN', array(1, 2), 0, 0), $params[0]);
+    $obj = new CRM_Contact_BAO_Query($params);
+    $this->assertEquals(array('civicrm_membership.membership_type_id IN ("1", "2")'), $obj->_where[0]);
+  }
+
+  /**
+   * Check that membership type is handled.
+   *
+   * We want to see the following syntaxes for membership_type_id field handled:
+   *   3) membership_type_id => array(5,6)
+   *
+   * The last of these is the format used prior to converting membership_type_id to an entity reference field. It will
+   * be used by pre-existing smart groups.
+   */
+  public function testConvertEntityFieldMultipleValueLegacy() {
+    $formValues = array('membership_type_id' => array(1, 2));
+    $params = CRM_Contact_BAO_Query::convertFormValues($formValues, 0, FALSE, NULL, array('membership_type_id'));
+    $this->assertEquals(array('membership_type_id', 'IN', array(1, 2), 0, 0), $params[0]);
+    $obj = new CRM_Contact_BAO_Query($params);
+    $this->assertEquals(array('civicrm_membership.membership_type_id IN ("1", "2")'), $obj->_where[0]);
+  }
+
+  /**
+   * Check that running convertFormValues more than one doesn't mangle the array.
+   *
+   * Unfortunately the convertFormValues & indeed much of the query code is run in pre-process AND post-process.
+   *
+   * The convertFormValues function should cope with this until such time as we can rationalise that.
+   */
+  public function testConvertEntityFieldMultipleValueEntityRefDoubleRun() {
+    $formValues = array('membership_type_id' => '1,2');
+    $params = CRM_Contact_BAO_Query::convertFormValues($formValues, 0, FALSE, NULL, array('membership_type_id'));
+    $this->assertEquals(array('membership_type_id', 'IN', array(1, 2), 0, 0), $params[0]);
+    $params = CRM_Contact_BAO_Query::convertFormValues($params, 0, FALSE, NULL, array('membership_type_id'));
+    $this->assertEquals(array('membership_type_id', 'IN', array(1, 2), 0, 0), $params[0]);
+    $obj = new CRM_Contact_BAO_Query($params);
+    $this->assertEquals(array('civicrm_membership.membership_type_id IN ("1", "2")'), $obj->_where[0]);
+  }
+
+}