CRM-17574 fix search to 'listen' to setting for no automatic wildcard.
authoreileen <emcnaughton@wikimedia.org>
Sun, 15 Nov 2015 17:09:47 +0000 (09:09 -0800)
committereileen <emcnaughton@wikimedia.org>
Sun, 15 Nov 2015 17:38:12 +0000 (09:38 -0800)
Note the test uses the dataprovider option

CRM/Contact/BAO/Query.php
CRM/Contact/Form/Search.php
CRM/Contact/Selector.php
tests/phpunit/CRM/Contact/SelectorTest.php [new file with mode: 0644]

index de7b0f9ca8f861f82d299aee7c6abdab6c58b5ce..e4e6a48ba6d40960f20510398b6df84209f28a7f 100644 (file)
@@ -3304,18 +3304,7 @@ WHERE  $smartGroupClause
     $value = $strtolower(CRM_Core_DAO::escapeString(trim($value)));
     if (strlen($value)) {
       $fieldsub = array();
-      if ($wildcard && $op == 'LIKE') {
-        if ($config->includeWildCardInName) {
-          $value = "'%$value%'";
-        }
-        else {
-          $value = "'$value%'";
-        }
-        $op = 'LIKE';
-      }
-      else {
-        $value = "'$value'";
-      }
+      $value = "'" . $this->getWildCardedValue($wildcard, $op, $value) . "'";
       if ($fieldName == 'sort_name') {
         $wc = self::caseImportant($op) ? "LOWER(contact_a.sort_name)" : "contact_a.sort_name";
       }
@@ -3364,29 +3353,20 @@ WHERE  $smartGroupClause
    *
    * @param array $values
    */
-  public function email(&$values) {
+  protected function email(&$values) {
     list($name, $op, $value, $grouping, $wildcard) = $values;
 
-    $n = trim($value);
+    $n = strtolower(trim($value));
     if ($n) {
-      $config = CRM_Core_Config::singleton();
-
       if (substr($n, 0, 1) == '"' &&
         substr($n, -1, 1) == '"'
       ) {
         $n = substr($n, 1, -1);
-        $value = strtolower(CRM_Core_DAO::escapeString($n));
-        $value = "'$value'";
+        $value = CRM_Core_DAO::escapeString($n);
         $op = '=';
       }
       else {
-        $value = strtolower($n);
-        if ($wildcard) {
-          if (strpos($value, '%') === FALSE) {
-            $value = "%{$value}%";
-          }
-          $op = 'LIKE';
-        }
+        $value = $this->getWildCardedValue($wildcard, $op, $n);
       }
       $this->_qill[$grouping][] = ts('Email') . " $op '$n'";
       $this->_where[$grouping][] = self::buildClause('civicrm_email.email', $op, $value, 'String');
@@ -5896,4 +5876,34 @@ AND   displayRelType.is_active = 1
     return array(CRM_Utils_Array::value($op, $qillOperators, $op), $fieldValue);
   }
 
+  /**
+   * Alter value to reflect wildcard settings.
+   *
+   * The form will have tried to guess whether this is a good field to wildcard but there is
+   * also a site-wide setting that specifies whether it is OK to append the wild card to the beginning
+   * or only the end of the string
+   *
+   * @param bool $wildcard
+   *   This is a bool made on an assessment 'elsewhere' on whether this is a good field to wildcard.
+   * @param string $op
+   *   Generally '=' or 'LIKE'.
+   * @param string $value
+   *   The search string.
+   *
+   * @return string
+   */
+  public function getWildCardedValue($wildcard, $op, $value) {
+    if ($wildcard && $op == 'LIKE') {
+      if (CRM_Core_Config::singleton()->includeWildCardInName && (substr($value, 0, 1) != '%')) {
+        return "%$value%";
+      }
+      else {
+        return "$value%";
+      }
+    }
+    else {
+      return "$value";
+    }
+  }
+
 }
index cde0585c590a03f0014193cbcc6c76457aff507f..311e53d7076671960a8faf11fb15a6b8678334ae 100644 (file)
@@ -678,7 +678,7 @@ class CRM_Contact_Form_Search extends CRM_Core_Form_Search {
     if (!isset($this->_componentMode)) {
       $this->_componentMode = CRM_Contact_BAO_Query::MODE_CONTACTS;
     }
-    $modeValues = self::getModeValue($this->_componentMode);
+    self::setModeValues();
 
     self::$_selectorName = $this->_modeValue['selectorName'];
 
index 36d4a94f15309b55826091328d06e17d82d7e797..2143bb8ccdbeccbe561701edfe4942d8c3d1676c 100644 (file)
@@ -116,8 +116,22 @@ class CRM_Contact_Selector extends CRM_Core_Selector_Base implements CRM_Core_Se
 
   protected $_searchContext;
 
+  /**
+   * Query object for this selector.
+   *
+   * @var CRM_Contact_BAO_Query
+   */
   protected $_query;
 
+  /**
+   * Get the query object for this selector.
+   *
+   * @return CRM_Contact_BAO_Query
+   */
+  public function getQueryObject() {
+    return $this->_query;
+  }
+
   /**
    * Group id
    *
diff --git a/tests/phpunit/CRM/Contact/SelectorTest.php b/tests/phpunit/CRM/Contact/SelectorTest.php
new file mode 100644 (file)
index 0000000..9be68b3
--- /dev/null
@@ -0,0 +1,221 @@
+<?php
+/*
+ +--------------------------------------------------------------------+
+ | CiviCRM version 4.7                                                |
+ +--------------------------------------------------------------------+
+ | Copyright CiviCRM LLC (c) 2004-2015                                |
+ +--------------------------------------------------------------------+
+ | This file is a part of CiviCRM.                                    |
+ |                                                                    |
+ | CiviCRM is free software; you can copy, modify, and distribute it  |
+ | under the terms of the GNU Affero General Public License           |
+ | Version 3, 19 November 2007 and the CiviCRM Licensing Exception.   |
+ |                                                                    |
+ | CiviCRM is distributed in the hope that it will be useful, but     |
+ | WITHOUT ANY WARRANTY; without even the implied warranty of         |
+ | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               |
+ | See the GNU Affero General Public License for more details.        |
+ |                                                                    |
+ | You should have received a copy of the GNU Affero General Public   |
+ | License and the CiviCRM Licensing Exception along                  |
+ | with this program; if not, contact CiviCRM LLC                     |
+ | at info[AT]civicrm[DOT]org. If you have questions about the        |
+ | GNU Affero General Public License or the licensing of CiviCRM,     |
+ | see the CiviCRM license FAQ at http://civicrm.org/licensing        |
+ +--------------------------------------------------------------------+
+ */
+
+/**
+ *  Include parent class definition
+ */
+require_once 'CiviTest/CiviUnitTestCase.php';
+
+/**
+ *  Test contact custom search functions
+ *
+ * @package CiviCRM
+ */
+class CRM_Contact_Form_SelectorTest extends CiviUnitTestCase {
+
+  public function tearDown() {
+
+  }
+  /**
+   * Test the query from the selector class is consistent with the dataset expectation.
+   *
+   * @param array $dataSet
+   *   The data set to be tested. Note that when adding new datasets often only form_values and expected where
+   *   clause will need changing.
+   *
+   * @dataProvider querySets
+   */
+  public function testSelectorQuery($dataSet) {
+    $params = CRM_Contact_BAO_Query::convertFormValues($dataSet['form_values'], 0, FALSE, NULL, array());
+    foreach ($dataSet['settings'] as $setting) {
+      $this->callAPISuccess('Setting', 'create', array($setting['name'] => $setting['value']));
+    }
+    $selector = new CRM_Contact_Selector(
+      $dataSet['class'],
+      $dataSet['form_values'],
+      $params,
+      $dataSet['return_properties'],
+      $dataSet['action'],
+      $dataSet['includeContactIds'],
+      $dataSet['searchDescendentGroups'],
+      $dataSet['context']
+    );
+    $queryObject = $selector->getQueryObject();
+    $sql = $queryObject->query();
+    $this->wrangleDefaultClauses($dataSet['expected_query']);
+    foreach ($dataSet['expected_query'] as $index => $queryString) {
+      $this->assertEquals($this->strWrangle($queryString), $this->strWrangle($sql[$index]));
+    }
+  }
+
+  /**
+   * Data sets for testing.
+   */
+  public function querySets() {
+    return array(
+      array(
+        array(
+          'description' => 'Normal default behaviour',
+          'class' => 'CRM_Contact_Selector',
+          'settings' => array(),
+          'form_values' => array('email' => 'mickey@mouseville.com'),
+          'params' => array(),
+          'return_properties' => NULL,
+          'context' => 'advanced',
+          'action' => CRM_Core_Action::ADVANCED,
+          'includeContactIds' => NULL,
+          'searchDescendentGroups' => FALSE,
+          'expected_query' => array(
+            0 => 'default',
+            1 => 'default',
+            2 => "WHERE  ( civicrm_email.email LIKE '%mickey@mouseville.com%' )  AND (contact_a.is_deleted = 0)",
+          ),
+        ),
+      ),
+      array(
+        array(
+          'description' => 'Normal default + user added wildcard',
+          'class' => 'CRM_Contact_Selector',
+          'settings' => array(),
+          'form_values' => array('email' => '%mickey@mouseville.com', 'sort_name' => 'Mouse'),
+          'params' => array(),
+          'return_properties' => NULL,
+          'context' => 'advanced',
+          'action' => CRM_Core_Action::ADVANCED,
+          'includeContactIds' => NULL,
+          'searchDescendentGroups' => FALSE,
+          'expected_query' => array(
+            0 => 'default',
+            1 => 'default',
+            2 => "WHERE  ( civicrm_email.email LIKE '%mickey@mouseville.com%'  AND ( ( ( contact_a.sort_name LIKE '%mouse%' ) OR ( civicrm_email.email LIKE '%mouse%' ) ) ) ) AND (contact_a.is_deleted = 0)",
+          ),
+        ),
+      ),
+      array(
+        array(
+          'description' => 'Site set to not pre-pend wildcard',
+          'class' => 'CRM_Contact_Selector',
+          'settings' => array(array('name' => 'includeWildCardInName', 'value' => FALSE)),
+          'form_values' => array('email' => 'mickey@mouseville.com', 'sort_name' => 'Mouse'),
+          'params' => array(),
+          'return_properties' => NULL,
+          'context' => 'advanced',
+          'action' => CRM_Core_Action::ADVANCED,
+          'includeContactIds' => NULL,
+          'searchDescendentGroups' => FALSE,
+          'expected_query' => array(
+            0 => 'default',
+            1 => 'default',
+            2 => "WHERE  ( civicrm_email.email LIKE 'mickey@mouseville.com%'  AND ( ( ( contact_a.sort_name LIKE 'mouse%' ) OR ( civicrm_email.email LIKE 'mouse%' ) ) ) ) AND (contact_a.is_deleted = 0)",
+          ),
+        ),
+      ),
+      array(
+        array(
+          'description' => 'Use of quotes for exact string',
+          'use_case_comments' => 'This is something that was in the code but seemingly not working. No UI info on it though!',
+          'class' => 'CRM_Contact_Selector',
+          'settings' => array(array('name' => 'includeWildCardInName', 'value' => FALSE)),
+          'form_values' => array('email' => '"mickey@mouseville.com"', 'sort_name' => 'Mouse'),
+          'params' => array(),
+          'return_properties' => NULL,
+          'context' => 'advanced',
+          'action' => CRM_Core_Action::ADVANCED,
+          'includeContactIds' => NULL,
+          'searchDescendentGroups' => FALSE,
+          'expected_query' => array(
+            0 => 'default',
+            1 => 'default',
+            2 => "WHERE  ( civicrm_email.email = 'mickey@mouseville.com'  AND ( ( ( contact_a.sort_name LIKE 'mouse%' ) OR ( civicrm_email.email LIKE 'mouse%' ) ) ) ) AND (contact_a.is_deleted = 0)",
+          ),
+        ),
+      ),
+    );
+  }
+
+  /**
+   * Get the default select string since this is generally consistent.
+   */
+  public function getDefaultSelectString() {
+    return 'SELECT contact_a.id as contact_id, contact_a.contact_type  as `contact_type`, contact_a.contact_sub_type  as `contact_sub_type`, contact_a.sort_name  as `sort_name`,'
+    . ' contact_a.display_name  as `display_name`, contact_a.do_not_email  as `do_not_email`, contact_a.do_not_phone as `do_not_phone`, contact_a.do_not_mail  as `do_not_mail`,'
+    . ' contact_a.do_not_sms  as `do_not_sms`, contact_a.do_not_trade as `do_not_trade`, contact_a.is_opt_out  as `is_opt_out`, contact_a.legal_identifier  as `legal_identifier`,'
+    . ' contact_a.external_identifier  as `external_identifier`, contact_a.nick_name  as `nick_name`, contact_a.legal_name  as `legal_name`, contact_a.image_URL  as `image_URL`,'
+    . ' contact_a.preferred_communication_method  as `preferred_communication_method`, contact_a.preferred_language  as `preferred_language`,'
+    . ' contact_a.preferred_mail_format  as `preferred_mail_format`, contact_a.first_name  as `first_name`, contact_a.middle_name  as `middle_name`, contact_a.last_name  as `last_name`,'
+    . ' contact_a.prefix_id  as `prefix_id`, contact_a.suffix_id  as `suffix_id`, contact_a.formal_title  as `formal_title`, contact_a.communication_style_id  as `communication_style_id`,'
+    . ' contact_a.job_title  as `job_title`, contact_a.gender_id  as `gender_id`, contact_a.birth_date  as `birth_date`, contact_a.is_deceased  as `is_deceased`,'
+    . ' contact_a.deceased_date  as `deceased_date`, contact_a.household_name  as `household_name`,'
+    . ' IF ( contact_a.contact_type = \'Individual\', NULL, contact_a.organization_name ) as organization_name, contact_a.sic_code  as `sic_code`, contact_a.is_deleted  as `contact_is_deleted`,'
+    . ' IF ( contact_a.contact_type = \'Individual\', contact_a.organization_name, NULL ) as current_employer, civicrm_address.id as address_id,'
+    . ' civicrm_address.street_address as `street_address`, civicrm_address.supplemental_address_1 as `supplemental_address_1`, '
+    . 'civicrm_address.supplemental_address_2 as `supplemental_address_2`, civicrm_address.city as `city`, civicrm_address.postal_code_suffix as `postal_code_suffix`, '
+    . 'civicrm_address.postal_code as `postal_code`, civicrm_address.geo_code_1 as `geo_code_1`, civicrm_address.geo_code_2 as `geo_code_2`, '
+    . 'civicrm_address.state_province_id as state_province_id, civicrm_address.country_id as country_id, civicrm_phone.id as phone_id, civicrm_phone.phone_type_id as phone_type_id, '
+    . 'civicrm_phone.phone as `phone`, civicrm_email.id as email_id, civicrm_email.email as `email`, civicrm_email.on_hold as `on_hold`, civicrm_im.id as im_id, '
+    . 'civicrm_im.provider_id as provider_id, civicrm_im.name as `im`, civicrm_worldregion.id as worldregion_id, civicrm_worldregion.name as `world_region`';
+  }
+
+  /**
+   * Get the default from string since this is generally consistent.
+   */
+  public function getDefaultFromString() {
+    return ' FROM civicrm_contact contact_a LEFT JOIN civicrm_address ON ( contact_a.id = civicrm_address.contact_id AND civicrm_address.is_primary = 1 )'
+    . ' LEFT JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id AND civicrm_email.is_primary = 1)'
+    . ' LEFT JOIN civicrm_phone ON (contact_a.id = civicrm_phone.contact_id AND civicrm_phone.is_primary = 1)'
+    . ' LEFT JOIN civicrm_im ON (contact_a.id = civicrm_im.contact_id AND civicrm_im.is_primary = 1) '
+    . 'LEFT JOIN civicrm_country ON civicrm_address.country_id = civicrm_country.id LEFT JOIN civicrm_worldregion ON civicrm_country.region_id = civicrm_worldregion.id ';
+  }
+
+  /**
+   * Strangle strings into a more matchable format.
+   *
+   * @param string $string
+   * @return string
+   */
+  public function strWrangle($string) {
+    return str_replace('  ', ' ', $string);
+  }
+
+  /**
+   * Swap out default parts of the query for the actual string.
+   *
+   * Note that it seems to make more sense to resolve this earlier & pass it in from a clean code point of
+   * view, but the output on fail includes long sql statements that are of low relevance then.
+   *
+   * @param array $expectedQuery
+   */
+  public function wrangleDefaultClauses(&$expectedQuery) {
+    if ($expectedQuery[0] == 'default') {
+      $expectedQuery[0] = $this->getDefaultSelectString();
+    }
+    if ($expectedQuery[1] == 'default') {
+      $expectedQuery[1] = $this->getDefaultFromString();
+    }
+  }
+
+}