From 836eb0432b7e0dc2e7ccd22528eb9908f0cdf8ff Mon Sep 17 00:00:00 2001 From: eileen Date: Sun, 15 Nov 2015 09:09:47 -0800 Subject: [PATCH] CRM-17574 fix search to 'listen' to setting for no automatic wildcard. Note the test uses the dataprovider option --- CRM/Contact/BAO/Query.php | 60 +++--- CRM/Contact/Form/Search.php | 2 +- CRM/Contact/Selector.php | 14 ++ tests/phpunit/CRM/Contact/SelectorTest.php | 221 +++++++++++++++++++++ 4 files changed, 271 insertions(+), 26 deletions(-) create mode 100644 tests/phpunit/CRM/Contact/SelectorTest.php diff --git a/CRM/Contact/BAO/Query.php b/CRM/Contact/BAO/Query.php index de7b0f9ca8..e4e6a48ba6 100644 --- a/CRM/Contact/BAO/Query.php +++ b/CRM/Contact/BAO/Query.php @@ -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"; + } + } + } diff --git a/CRM/Contact/Form/Search.php b/CRM/Contact/Form/Search.php index cde0585c59..311e53d707 100644 --- a/CRM/Contact/Form/Search.php +++ b/CRM/Contact/Form/Search.php @@ -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']; diff --git a/CRM/Contact/Selector.php b/CRM/Contact/Selector.php index 36d4a94f15..2143bb8ccd 100644 --- a/CRM/Contact/Selector.php +++ b/CRM/Contact/Selector.php @@ -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 index 0000000000..9be68b379a --- /dev/null +++ b/tests/phpunit/CRM/Contact/SelectorTest.php @@ -0,0 +1,221 @@ +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(); + } + } + +} -- 2.25.1