836eb043 |
1 | <?php |
2 | /* |
3 | +--------------------------------------------------------------------+ |
2fe49090 |
4 | | CiviCRM version 5 | |
836eb043 |
5 | +--------------------------------------------------------------------+ |
8c9251b3 |
6 | | Copyright CiviCRM LLC (c) 2004-2018 | |
836eb043 |
7 | +--------------------------------------------------------------------+ |
8 | | This file is a part of CiviCRM. | |
9 | | | |
10 | | CiviCRM is free software; you can copy, modify, and distribute it | |
11 | | under the terms of the GNU Affero General Public License | |
12 | | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. | |
13 | | | |
14 | | CiviCRM is distributed in the hope that it will be useful, but | |
15 | | WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
17 | | See the GNU Affero General Public License for more details. | |
18 | | | |
19 | | You should have received a copy of the GNU Affero General Public | |
20 | | License and the CiviCRM Licensing Exception along | |
21 | | with this program; if not, contact CiviCRM LLC | |
22 | | at info[AT]civicrm[DOT]org. If you have questions about the | |
23 | | GNU Affero General Public License or the licensing of CiviCRM, | |
24 | | see the CiviCRM license FAQ at http://civicrm.org/licensing | |
25 | +--------------------------------------------------------------------+ |
26 | */ |
27 | |
28 | /** |
29 | * Include parent class definition |
30 | */ |
836eb043 |
31 | |
32 | /** |
33 | * Test contact custom search functions |
34 | * |
35 | * @package CiviCRM |
acb109b7 |
36 | * @group headless |
836eb043 |
37 | */ |
38 | class CRM_Contact_Form_SelectorTest extends CiviUnitTestCase { |
39 | |
40 | public function tearDown() { |
41 | |
42 | } |
43 | /** |
44 | * Test the query from the selector class is consistent with the dataset expectation. |
45 | * |
46 | * @param array $dataSet |
47 | * The data set to be tested. Note that when adding new datasets often only form_values and expected where |
48 | * clause will need changing. |
49 | * |
50 | * @dataProvider querySets |
51 | */ |
52 | public function testSelectorQuery($dataSet) { |
53 | $params = CRM_Contact_BAO_Query::convertFormValues($dataSet['form_values'], 0, FALSE, NULL, array()); |
54 | foreach ($dataSet['settings'] as $setting) { |
55 | $this->callAPISuccess('Setting', 'create', array($setting['name'] => $setting['value'])); |
56 | } |
57 | $selector = new CRM_Contact_Selector( |
58 | $dataSet['class'], |
59 | $dataSet['form_values'], |
60 | $params, |
61 | $dataSet['return_properties'], |
62 | $dataSet['action'], |
63 | $dataSet['includeContactIds'], |
64 | $dataSet['searchDescendentGroups'], |
65 | $dataSet['context'] |
66 | ); |
67 | $queryObject = $selector->getQueryObject(); |
68 | $sql = $queryObject->query(); |
69 | $this->wrangleDefaultClauses($dataSet['expected_query']); |
70 | foreach ($dataSet['expected_query'] as $index => $queryString) { |
71 | $this->assertEquals($this->strWrangle($queryString), $this->strWrangle($sql[$index])); |
72 | } |
7ccccc32 |
73 | // Ensure that search builder return individual contact as per criteria |
74 | if (!empty($dataSet['context'] == 'builder')) { |
d567ed10 |
75 | $contactID = $this->individualCreate(['first_name' => 'James', 'last_name' => 'Bond']); |
a220b357 |
76 | $this->callAPISuccess('Address', 'create', [ |
77 | 'contact_id' => $contactID, |
78 | 'location_type_id' => "Home", |
79 | 'is_primary' => 1, |
80 | 'country_id' => "IN", |
81 | ]); |
7ccccc32 |
82 | $rows = $selector->getRows(CRM_Core_Action::VIEW, 0, 50, ''); |
83 | $this->assertEquals(1, count($rows)); |
d567ed10 |
84 | $sortChar = $selector->alphabetQuery()->fetchAll(); |
85 | // sort name is stored in '<last_name>, <first_name>' format, as per which the first character would be B of Bond |
86 | $this->assertEquals('B', $sortChar[0]['sort_name']); |
7ccccc32 |
87 | $this->assertEquals($contactID, key($rows)); |
88 | } |
836eb043 |
89 | } |
90 | |
2eb3ff3e |
91 | /** |
92 | * Test the civicrm_prevnext_cache entry if it correctly stores the search query result |
93 | */ |
94 | public function testPrevNextCache() { |
95 | $contactID = $this->individualCreate(['email' => 'mickey@mouseville.com']); |
96 | $dataSet = array( |
97 | 'description' => 'Normal default behaviour', |
98 | 'class' => 'CRM_Contact_Selector', |
99 | 'settings' => array(), |
100 | 'form_values' => array('email' => 'mickey@mouseville.com'), |
101 | 'params' => array(), |
102 | 'return_properties' => NULL, |
103 | 'context' => 'advanced', |
104 | 'action' => CRM_Core_Action::ADVANCED, |
105 | 'includeContactIds' => NULL, |
106 | 'searchDescendentGroups' => FALSE, |
107 | 'expected_query' => array( |
108 | 0 => 'default', |
109 | 1 => 'default', |
110 | 2 => "WHERE ( civicrm_email.email LIKE '%mickey@mouseville.com%' ) AND (contact_a.is_deleted = 0)", |
111 | ), |
112 | ); |
113 | $params = CRM_Contact_BAO_Query::convertFormValues($dataSet['form_values'], 0, FALSE, NULL, array()); |
114 | |
115 | // create CRM_Contact_Selector instance and set desired query params |
116 | $selector = new CRM_Contact_Selector( |
117 | $dataSet['class'], |
118 | $dataSet['form_values'], |
119 | $params, |
120 | $dataSet['return_properties'], |
121 | $dataSet['action'], |
122 | $dataSet['includeContactIds'], |
123 | $dataSet['searchDescendentGroups'], |
124 | $dataSet['context'] |
125 | ); |
126 | // set cache key |
127 | $key = substr(sha1(rand()), 0, 7); |
128 | $selector->setKey($key); |
129 | |
130 | // fetch row and check the result |
131 | $rows = $selector->getRows(CRM_Core_Action::VIEW, 0, TRUE, NULL); |
132 | $this->assertEquals(1, count($rows)); |
133 | $this->assertEquals($contactID, key($rows)); |
134 | |
135 | // build cache key and use to it to fetch prev-next cache record |
136 | $cacheKey = 'civicrm search ' . $key; |
137 | $contacts = CRM_Utils_SQL_Select::from('civicrm_prevnext_cache') |
138 | ->select(['entity_table', 'entity_id1', 'cacheKey']) |
139 | ->where("cacheKey = '!key'") |
140 | ->param('!key', $cacheKey) |
141 | ->execute() |
142 | ->fetchAll(); |
143 | $this->assertEquals(1, count($contacts)); |
144 | // check the prevNext record matches |
145 | $expectedEntry = [ |
146 | 'entity_table' => 'civicrm_contact', |
147 | 'entity_id1' => $contactID, |
148 | 'cacheKey' => $cacheKey, |
149 | ]; |
150 | $this->checkArrayEquals($contacts[0], $expectedEntry); |
151 | } |
152 | |
836eb043 |
153 | /** |
154 | * Data sets for testing. |
155 | */ |
156 | public function querySets() { |
157 | return array( |
158 | array( |
159 | array( |
160 | 'description' => 'Normal default behaviour', |
161 | 'class' => 'CRM_Contact_Selector', |
162 | 'settings' => array(), |
163 | 'form_values' => array('email' => 'mickey@mouseville.com'), |
164 | 'params' => array(), |
165 | 'return_properties' => NULL, |
166 | 'context' => 'advanced', |
167 | 'action' => CRM_Core_Action::ADVANCED, |
168 | 'includeContactIds' => NULL, |
169 | 'searchDescendentGroups' => FALSE, |
170 | 'expected_query' => array( |
171 | 0 => 'default', |
172 | 1 => 'default', |
173 | 2 => "WHERE ( civicrm_email.email LIKE '%mickey@mouseville.com%' ) AND (contact_a.is_deleted = 0)", |
174 | ), |
175 | ), |
176 | ), |
177 | array( |
178 | array( |
179 | 'description' => 'Normal default + user added wildcard', |
180 | 'class' => 'CRM_Contact_Selector', |
181 | 'settings' => array(), |
182 | 'form_values' => array('email' => '%mickey@mouseville.com', 'sort_name' => 'Mouse'), |
183 | 'params' => array(), |
184 | 'return_properties' => NULL, |
185 | 'context' => 'advanced', |
186 | 'action' => CRM_Core_Action::ADVANCED, |
187 | 'includeContactIds' => NULL, |
188 | 'searchDescendentGroups' => FALSE, |
189 | 'expected_query' => array( |
190 | 0 => 'default', |
191 | 1 => 'default', |
192 | 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)", |
193 | ), |
194 | ), |
195 | ), |
196 | array( |
197 | array( |
198 | 'description' => 'Site set to not pre-pend wildcard', |
199 | 'class' => 'CRM_Contact_Selector', |
200 | 'settings' => array(array('name' => 'includeWildCardInName', 'value' => FALSE)), |
201 | 'form_values' => array('email' => 'mickey@mouseville.com', 'sort_name' => 'Mouse'), |
202 | 'params' => array(), |
203 | 'return_properties' => NULL, |
204 | 'context' => 'advanced', |
205 | 'action' => CRM_Core_Action::ADVANCED, |
206 | 'includeContactIds' => NULL, |
207 | 'searchDescendentGroups' => FALSE, |
208 | 'expected_query' => array( |
209 | 0 => 'default', |
210 | 1 => 'default', |
211 | 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)", |
212 | ), |
213 | ), |
214 | ), |
215 | array( |
216 | array( |
217 | 'description' => 'Use of quotes for exact string', |
218 | 'use_case_comments' => 'This is something that was in the code but seemingly not working. No UI info on it though!', |
219 | 'class' => 'CRM_Contact_Selector', |
220 | 'settings' => array(array('name' => 'includeWildCardInName', 'value' => FALSE)), |
221 | 'form_values' => array('email' => '"mickey@mouseville.com"', 'sort_name' => 'Mouse'), |
222 | 'params' => array(), |
223 | 'return_properties' => NULL, |
224 | 'context' => 'advanced', |
225 | 'action' => CRM_Core_Action::ADVANCED, |
226 | 'includeContactIds' => NULL, |
227 | 'searchDescendentGroups' => FALSE, |
228 | 'expected_query' => array( |
229 | 0 => 'default', |
230 | 1 => 'default', |
231 | 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)", |
232 | ), |
233 | ), |
234 | ), |
7ccccc32 |
235 | array( |
236 | array( |
237 | 'description' => 'Normal search builder behaviour', |
238 | 'class' => 'CRM_Contact_Selector', |
239 | 'settings' => array(), |
a220b357 |
240 | 'form_values' => array('contact_type' => 'Individual', 'country' => array('IS NOT NULL' => 1)), |
7ccccc32 |
241 | 'params' => array(), |
242 | 'return_properties' => array( |
243 | 'contact_type' => 1, |
244 | 'contact_sub_type' => 1, |
245 | 'sort_name' => 1, |
246 | ), |
247 | 'context' => 'builder', |
248 | 'action' => CRM_Core_Action::NONE, |
249 | 'includeContactIds' => NULL, |
250 | 'searchDescendentGroups' => FALSE, |
251 | 'expected_query' => array( |
a220b357 |
252 | 0 => '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`, civicrm_address.id as address_id, civicrm_address.country_id as country_id', |
253 | 1 => ' FROM civicrm_contact contact_a LEFT JOIN civicrm_address ON ( contact_a.id = civicrm_address.contact_id AND civicrm_address.is_primary = 1 )', |
254 | 2 => 'WHERE ( contact_a.contact_type IN ("Individual") AND civicrm_address.country_id IS NOT NULL ) AND (contact_a.is_deleted = 0)', |
7ccccc32 |
255 | ), |
256 | ), |
257 | ), |
836eb043 |
258 | ); |
259 | } |
260 | |
ae066a2b |
261 | /** |
262 | * Test the contact ID query does not fail on country search. |
263 | */ |
264 | public function testContactIDQuery() { |
265 | $params = [[ |
266 | 0 => 'country-1', |
267 | 1 => '=', |
268 | 2 => '1228', |
269 | 3 => 1, |
270 | 4 => 0, |
271 | ]]; |
272 | |
273 | $searchOBJ = new CRM_Contact_Selector(NULL); |
274 | $searchOBJ->contactIDQuery($params, '1_u'); |
275 | } |
276 | |
836eb043 |
277 | /** |
278 | * Get the default select string since this is generally consistent. |
279 | */ |
280 | public function getDefaultSelectString() { |
281 | 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`,' |
282 | . ' 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`,' |
283 | . ' 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`,' |
284 | . ' 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`,' |
285 | . ' contact_a.preferred_communication_method as `preferred_communication_method`, contact_a.preferred_language as `preferred_language`,' |
286 | . ' 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`,' |
287 | . ' 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`,' |
288 | . ' 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`,' |
289 | . ' contact_a.deceased_date as `deceased_date`, contact_a.household_name as `household_name`,' |
290 | . ' 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`,' |
291 | . ' IF ( contact_a.contact_type = \'Individual\', contact_a.organization_name, NULL ) as current_employer, civicrm_address.id as address_id,' |
292 | . ' civicrm_address.street_address as `street_address`, civicrm_address.supplemental_address_1 as `supplemental_address_1`, ' |
207f62c6 |
293 | . 'civicrm_address.supplemental_address_2 as `supplemental_address_2`, civicrm_address.supplemental_address_3 as `supplemental_address_3`, civicrm_address.city as `city`, civicrm_address.postal_code_suffix as `postal_code_suffix`, ' |
836eb043 |
294 | . '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`, ' |
295 | . '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, ' |
296 | . '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, ' |
297 | . 'civicrm_im.provider_id as provider_id, civicrm_im.name as `im`, civicrm_worldregion.id as worldregion_id, civicrm_worldregion.name as `world_region`'; |
298 | } |
299 | |
300 | /** |
301 | * Get the default from string since this is generally consistent. |
302 | */ |
303 | public function getDefaultFromString() { |
304 | return ' FROM civicrm_contact contact_a LEFT JOIN civicrm_address ON ( contact_a.id = civicrm_address.contact_id AND civicrm_address.is_primary = 1 )' |
305 | . ' LEFT JOIN civicrm_email ON (contact_a.id = civicrm_email.contact_id AND civicrm_email.is_primary = 1)' |
306 | . ' LEFT JOIN civicrm_phone ON (contact_a.id = civicrm_phone.contact_id AND civicrm_phone.is_primary = 1)' |
307 | . ' LEFT JOIN civicrm_im ON (contact_a.id = civicrm_im.contact_id AND civicrm_im.is_primary = 1) ' |
308 | . 'LEFT JOIN civicrm_country ON civicrm_address.country_id = civicrm_country.id LEFT JOIN civicrm_worldregion ON civicrm_country.region_id = civicrm_worldregion.id '; |
309 | } |
310 | |
311 | /** |
312 | * Strangle strings into a more matchable format. |
313 | * |
314 | * @param string $string |
315 | * @return string |
316 | */ |
317 | public function strWrangle($string) { |
318 | return str_replace(' ', ' ', $string); |
319 | } |
320 | |
321 | /** |
322 | * Swap out default parts of the query for the actual string. |
323 | * |
324 | * Note that it seems to make more sense to resolve this earlier & pass it in from a clean code point of |
325 | * view, but the output on fail includes long sql statements that are of low relevance then. |
326 | * |
327 | * @param array $expectedQuery |
328 | */ |
329 | public function wrangleDefaultClauses(&$expectedQuery) { |
330 | if ($expectedQuery[0] == 'default') { |
331 | $expectedQuery[0] = $this->getDefaultSelectString(); |
332 | } |
333 | if ($expectedQuery[1] == 'default') { |
334 | $expectedQuery[1] = $this->getDefaultFromString(); |
335 | } |
336 | } |
337 | |
338 | } |