Merge pull request #11737 from aydun/CRM-21816-relative-dates-in-search
[civicrm-core.git] / tests / phpunit / WebTest / Import / ImportCiviSeleniumTestCase.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
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 along with this program; if not, contact CiviCRM LLC |
21 | at info[AT]civicrm[DOT]org. If you have questions about the |
22 | GNU Affero General Public License or the licensing of CiviCRM, |
23 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
24 +--------------------------------------------------------------------+
25 */
26
27 require_once 'CiviTest/CiviSeleniumTestCase.php';
28 require_once 'CRM/Utils/Array.php';
29
30 /**
31 * Class ImportCiviSeleniumTestCase
32 */
33 class ImportCiviSeleniumTestCase extends CiviSeleniumTestCase {
34
35 /**
36 * Test csv import for each component.
37 *
38 * @param string $component
39 * Component name ( Event, Contribution, Membership, Activity etc).
40 * @param array $headers
41 * Csv data headers.
42 * @param array $rows
43 * Csv data rows.
44 * @param string $contactType
45 * Contact type.
46 * @param string $mode
47 * Import mode.
48 * @param array $fieldMapper
49 * Select mapper fields while import.
50 * @param array $other
51 * Other parameters.
52 * useMappingName : to reuse mapping
53 * dateFormat : date format of data
54 * checkMapperHeaders : to override default check mapper headers
55 * saveMapping : save current mapping?
56 * saveMappingName : to override mapping name
57 */
58 public function importCSVComponent(
59 $component,
60 $headers,
61 $rows,
62 $contactType = 'Individual',
63 $mode = 'Skip',
64 $fieldMapper = array(),
65 $other = array()
66 ) {
67
68 // Go to contact import page.
69 $this->openCiviPage($this->_getImportComponentUrl($component), 'reset=1', "uploadFile");
70
71 // Create csv file of sample data.
72 $csvFile = $this->webtestCreateCSV($headers, $rows);
73
74 // Attach csv file.
75 $this->webtestAttachFile('uploadFile', $csvFile);
76
77 // First row is header.
78 $this->click('skipColumnHeader');
79
80 // select mode, default is 'Skip'.
81 if ($mode == 'Update') {
82 $this->click("CIVICRM_QFID_4_4");
83 }
84 elseif ($mode == 'No Duplicate Checking') {
85 $this->click("CIVICRM_QFID_16_6");
86 }
87
88 // select contact type, default is 'Individual'.
89 if ($component != 'Activity') {
90 $contactTypeOption = $this->_getImportComponentContactType($component, $contactType);
91 $this->click($contactTypeOption);
92 }
93
94 // Date format, default: yyyy-mm-dd OR yyyymmdd
95 if (isset($other['dateFormat'])) {
96 // default
97 $dateFormatMapper = array(
98 'yyyy-mm-dd OR yyyymmdd' => "CIVICRM_QFID_1_14",
99 'mm/dd/yy OR mm-dd-yy' => "CIVICRM_QFID_2_16",
100 'mm/dd/yyyy OR mm-dd-yyyy' => "CIVICRM_QFID_4_18",
101 'Month dd, yyyy' => "CIVICRM_QFID_8_20",
102 'dd-mon-yy OR dd/mm/yy' => "CIVICRM_QFID_16_22",
103 'dd/mm/yyyy' => "CIVICRM_QFID_32_24",
104 );
105 $this->click($dateFormatMapper[$other['dateFormat']]);
106 }
107
108 // Use already created mapping
109 $existingMapping = NULL;
110 if (isset($other['useMappingName'])) {
111 $this->select('savedMapping', "label=" . $other['useMappingName']);
112 $existingMapping = $other['useMappingName'];
113 }
114
115 // Submit form.
116 $this->click('_qf_DataSource_upload');
117 $this->waitForPageToLoad($this->getTimeoutMsec());
118
119 // Select matching field for cvs data.
120 if (!empty($fieldMapper)) {
121 foreach ($fieldMapper as $field => $value) {
122 $this->select($field, "value={$value}");
123 }
124 }
125
126 // Check mapping data.
127 $this->_checkImportMapperData($headers,
128 $rows,
129 $existingMapping,
130 isset($other['checkMapperHeaders']) ? $other['checkMapperHeaders'] : array()
131 );
132
133 // Save mapping
134 if (isset($other['saveMapping'])) {
135 $mappingName = isset($other['saveMappingName']) ? $other['saveMappingName'] : "{$component}Import_" . substr(sha1(rand()), 0, 7);
136
137 $this->click('saveMapping');
138 $this->type('saveMappingName', $mappingName);
139 $this->type('saveMappingDesc', "Mapping for {$contactType}");
140 }
141
142 // Submit form.
143 $this->click('_qf_MapField_next');
144 $this->waitForElementPresent('_qf_Preview_next-bottom');
145
146 // Check mapping data.
147 $this->_checkImportMapperData($headers, $rows, $existingMapping, isset($other['checkMapperHeaders']) ? $other['checkMapperHeaders'] : array());
148
149 // Submit form.
150 $this->clickLink('_qf_Preview_next-bottom', "_qf_Summary_next");
151
152 // Check success message.
153 $this->assertTrue($this->isTextPresent("Import has completed successfully. The information below summarizes the results."));
154
155 // Check summary Details.
156 $importedRecords = count($rows);
157 $checkSummary = array(
158 'Total Rows' => $importedRecords,
159 'Records Imported' => $importedRecords,
160 );
161
162 foreach ($checkSummary as $label => $value) {
163 $this->verifyText("xpath=//table[@id='summary-counts']/tbody/tr/td[text()='{$label}']/following-sibling::td", preg_quote($value));
164 }
165 }
166
167 /**
168 * Test contact import.
169 *
170 * @param array $headers
171 * Csv data headers.
172 * @param array $rows
173 * Csv data rows.
174 * @param string $contactType
175 * Contact type.
176 * @param string $mode
177 * Import mode.
178 * @param array $fieldMapper
179 * Select mapper fields while import.
180 * @param array $other
181 * Other parameters.
182 * contactSubtype : import for selected Contact Subtype
183 * useMappingName : to reuse mapping
184 * dateFormat : date format of data
185 * checkMapperHeaders : to override default check mapper headers
186 * saveMapping : save current mapping?
187 * saveMappingName : to override mapping name
188 * createGroup : create new group?
189 * createGroupName : to override new Group name
190 * createTag : create new tag?
191 * createTagName : to override new Tag name
192 * selectGroup : select existing group for contacts
193 * selectTag : select existing tag for contacts
194 * callbackImportSummary : function to override default import summary assertions
195 *
196 * @param string $type
197 * Import type (csv/sql).
198 * @todo:currently only supports csv, need to work on sql import
199 */
200 public function importContacts($headers, $rows, $contactType = 'Individual', $mode = 'Skip', $fieldMapper = array(), $other = array(), $type = 'csv') {
201
202 // Go to contact import page.
203 $this->openCiviPage("import/contact", "reset=1", "uploadFile");
204
205 $originalHeaders = $headers;
206 $originalRows = $rows;
207
208 // format headers and row to import contacts with relationship data.
209 $this->_formatContactCSVdata($headers, $rows);
210
211 // Create csv file of sample data.
212 $csvFile = $this->webtestCreateCSV($headers, $rows);
213
214 // Attach csv file.
215 $this->webtestAttachFile('uploadFile', $csvFile);
216
217 // First row is header.
218 $this->click('skipColumnHeader');
219
220 // select mode, default is 'Skip'.
221 if ($mode == 'Update') {
222 $this->click("CIVICRM_QFID_4_4");
223 }
224 elseif ($mode == 'Fill') {
225 $this->click("CIVICRM_QFID_8_6");
226 }
227 elseif ($mode == 'No Duplicate Checking') {
228 $this->click("CIVICRM_QFID_16_8");
229 }
230
231 // select contact type, default is 'Individual'.
232 if ($contactType == 'Organization') {
233 $this->click("CIVICRM_QFID_4_14");
234 }
235 elseif ($contactType == 'Household') {
236 $this->click("CIVICRM_QFID_2_12");
237 }
238
239 // Select contact subtype
240 if (isset($other['contactSubtype'])) {
241 $this->waitForElementPresent("xpath=//div[@id='common-form-controls']/table/tbody/tr[1]/td[2]/span");
242 $this->select('subType', $other['contactSubtype']);
243 }
244
245 if (isset($other['dedupe'])) {
246 $this->waitForElementPresent("dedupe");
247 $this->select('dedupe', 'value=' . $other['dedupe']);
248 }
249
250 // Use already created mapping
251 $existingMapping = NULL;
252 if (isset($other['useMappingName'])) {
253 $this->select('savedMapping', "label=" . $other['useMappingName']);
254 $existingMapping = $other['useMappingName'];
255 }
256
257 // Date format, default: yyyy-mm-dd OR yyyymmdd
258 if (isset($other['dateFormat'])) {
259 // default
260 $dateFormatMapper = array(
261 'yyyy-mm-dd OR yyyymmdd' => "CIVICRM_QFID_1_16",
262 'mm/dd/yy OR mm-dd-yy' => "CIVICRM_QFID_2_18",
263 'mm/dd/yyyy OR mm-dd-yyyy' => "CIVICRM_QFID_4_20",
264 'Month dd, yyyy' => "CIVICRM_QFID_8_22",
265 'dd-mon-yy OR dd/mm/yy' => "CIVICRM_QFID_16_24",
266 'dd/mm/yyyy' => "CIVICRM_QFID_32_26",
267 );
268 $this->click($dateFormatMapper[$other['dateFormat']]);
269 }
270
271 // Submit form.
272 $this->clickLink('_qf_DataSource_upload');
273
274 if (isset($other['checkMapperHeaders'])) {
275 $checkMapperHeaders = $other['checkMapperHeaders'];
276 }
277 else {
278 $checkMapperHeaders = array(
279 1 => 'Column Names',
280 2 => 'Import Data (row 1)',
281 3 => 'Import Data (row 2)',
282 4 => 'Matching CiviCRM Field',
283 );
284 }
285
286 // Check mapping data.
287 $this->_checkImportMapperData($headers, $rows, $existingMapping, $checkMapperHeaders, 'td');
288
289 // Select matching field for cvs data.
290 if (!empty($fieldMapper)) {
291 foreach ($fieldMapper as $field => $value) {
292 $this->select($field, "value={$value}");
293 }
294 }
295
296 // Save mapping
297 if (isset($other['saveMapping'])) {
298 $mappingName = isset($other['saveMappingName']) ? $other['saveMappingName'] : 'ContactImport_' . substr(sha1(rand()), 0, 7);
299 $this->click('saveMapping');
300 $this->type('saveMappingName', $mappingName);
301 $this->type('saveMappingDesc', "Mapping for {$contactType}");
302 }
303
304 // Submit form.
305 $this->click('_qf_MapField_next');
306 $this->waitForPageToLoad($this->getTimeoutMsec());
307
308 // Check mapping data.
309 $this->_checkImportMapperData($headers, $rows, $existingMapping, $checkMapperHeaders, 'td');
310
311 // Add imported contacts in new group.
312 $groupName = NULL;
313 $existingGroups = array();
314 if (isset($other['createGroup'])) {
315 $groupName = isset($other['createGroupName']) ? $other['createGroupName'] : 'ContactImport_' . substr(sha1(rand()), 0, 7);
316
317 $this->click("css=#new-group div.crm-accordion-header");
318 $this->type('newGroupName', $groupName);
319 $this->type('newGroupDesc', "Group For {$contactType}");
320 }
321 if (isset($other['selectGroup'])) {
322 // reuse existing groups.
323 if (is_array($other['selectGroup'])) {
324 foreach ($other['selectGroup'] as $existingGroup) {
325 $this->select('groups[]', 'label=' . $existingGroup);
326 $existingGroups[] = $existingGroup;
327 }
328 }
329 else {
330 $this->select('groups[]', 'label=' . $other['selectGroup']);
331 $existingGroups[] = $other['selectGroup'];
332 }
333 }
334
335 // Assign new tag to the imported contacts.
336 $tagName = NULL;
337 $existingTags = array();
338 if (isset($other['createTag'])) {
339 $tagName = isset($other['createTagName']) ? $other['createTagName'] : "{$contactType}_" . substr(sha1(rand()), 0, 7);
340
341 $this->click("css=#new-tag div.crm-accordion-header");
342 $this->type('newTagName', $tagName);
343 $this->type('newTagDesc', "Tag for {$contactType}");
344 }
345 if (isset($other['selectTag'])) {
346 $this->click("css=#existing-tags div.crm-accordion-header");
347 // reuse existing tags.
348 if (is_array($other['selectTag'])) {
349 foreach ($other['selectTag'] as $existingTag) {
350 $this->click("xpath=//div[@id='existing-tags']//div[@class='crm-accordion-body']//label[text()='{$existingTag}']");
351 $existingTags[] = $existingTag;
352 }
353 }
354 else {
355 $this->click("xpath=//div[@id='existing-tags']//div[@class='crm-accordion-body']//label[text()='" . $other['selectTag'] . "']");
356 $existingTags[] = $other['selectTag'];
357 }
358 }
359
360 // Submit form.
361 $this->click('_qf_Preview_next');
362 $this->waitForPageToLoad($this->getTimeoutMsec());
363
364 // Check confirmation alert.
365 $this->assertTrue((bool) preg_match("/^Backing up your database before importing is recommended, as there is no Undo for this. Are you sure you want to Import now[\s\S]$/", $this->getConfirmation()));
366 $this->chooseOkOnNextConfirmation();
367 $this->waitForPageToLoad($this->getTimeoutMsec());
368
369 // Visit summary page.
370 $this->waitForElementPresent("_qf_Summary_next");
371
372 // Check success message.
373 $this->assertTrue($this->isTextPresent("Import has completed successfully. The information below summarizes the results."));
374
375 // Check summary Details.
376 $importedContacts = $totalRows = count($originalRows);
377
378 // Include relationships contacts ( if exists )
379 if (isset($originalHeaders['contact_relationships']) && is_array($originalHeaders['contact_relationships'])) {
380 foreach ($originalRows as $row) {
381 $importedContacts += count($row['contact_relationships']);
382 }
383 }
384
385 $importedContactsCount = ($importedContacts == 1) ? 'One contact' : "$importedContacts contacts";
386 $taggedContactsCount = ($importedContacts == 1) ? 'One contact is' : "$importedContacts contacts are";
387 $checkSummary = array(
388 'Total Rows' => $totalRows,
389 'Total Contacts' => $importedContacts,
390 );
391
392 if ($groupName) {
393 $checkSummary['Import to Groups'] = "{$groupName}: {$importedContactsCount} added to this new group.";
394 }
395
396 if ($tagName) {
397 $checkSummary['Tagged Imported Contacts'] = "{$tagName}: {$taggedContactsCount} tagged with this tag.";
398 }
399
400 if ($existingGroups) {
401 if (!isset($checkSummary['Import to Groups'])) {
402 $checkSummary['Import to Groups'] = '';
403 }
404 foreach ($existingGroups as $existingGroup) {
405 $checkSummary['Import to Groups'] .= "{$existingGroup}: {$importedContactsCount} added to this existing group.";
406 }
407 }
408
409 if ($existingTags) {
410 if (!isset($checkSummary['Tagged Imported Contacts'])) {
411 $checkSummary['Tagged Imported Contacts'] = '';
412 }
413 foreach ($existingTags as $existingTag) {
414 $checkSummary['Tagged Imported Contacts'] .= "{$existingTag}: {$taggedContactsCount} tagged with this tag.";
415 }
416 }
417
418 if (!empty($other['callbackImportSummary']) && is_callable(array(
419 $this,
420 $other['callbackImportSummary'],
421 ))
422 ) {
423 $callbackImportSummary = $other['callbackImportSummary'];
424 $this->$callbackImportSummary($originalHeaders, $originalRows, $checkSummary);
425 }
426 else {
427 foreach ($checkSummary as $label => $value) {
428 $this->verifyText("xpath=//table[@id='summary-counts']/tbody/tr/td[text()='{$label}']/following-sibling::td", preg_quote($value));
429 }
430 }
431 }
432
433 /**
434 * Helper function to get the import url of the component.
435 * @param string $component
436 * Component name.
437 *
438 * @return string
439 * import url
440 */
441 private function _getImportComponentUrl($component) {
442 $importComponentUrl = array(
443 'Event' => 'event/import',
444 'Contribution' => 'contribute/import',
445 'Membership' => 'member/import',
446 'Activity' => 'import/activity',
447 );
448
449 return $importComponentUrl[$component];
450 }
451
452 /**
453 * @param $component
454 * @param $contactType
455 *
456 * @return string
457 */
458 public function _getImportComponentContactType($component, $contactType) {
459 $importComponentMode = array(
460 'Event' => array(
461 'Individual' => 'CIVICRM_QFID_1_20',
462 'Household' => 'CIVICRM_QFID_2_22',
463 'Organization' => 'CIVICRM_QFID_4_24',
464 ),
465 'Contribution' => array(
466 'Individual' => 'CIVICRM_QFID_1_18',
467 'Household' => 'CIVICRM_QFID_2_20',
468 'Organization' => 'CIVICRM_QFID_4_22',
469 ),
470 'Membership' => array(
471 'Individual' => 'CIVICRM_QFID_1_18',
472 'Household' => 'CIVICRM_QFID_2_20',
473 'Organization' => 'CIVICRM_QFID_4_22',
474 ),
475 );
476
477 return $importComponentMode[$component][$contactType];
478 }
479
480 /**
481 * Helper function to check import mapping fields.
482 * @param array $headers
483 * @param array $rows
484 * @param null $existingMapping
485 * @param array $checkMapperHeaders
486 * @param string $headerSelector
487 */
488 public function _checkImportMapperData($headers, $rows, $existingMapping = NULL, $checkMapperHeaders = array(), $headerSelector = 'th') {
489
490 if (empty($checkMapperHeaders)) {
491 $checkMapperHeaders = array(
492 1 => 'Column Headers',
493 2 => 'Import Data (row 2)',
494 3 => 'Import Data (row 3)',
495 4 => 'Matching CiviCRM Field',
496 );
497 }
498
499 $rowNumber = 1;
500 if ($existingMapping) {
501 $this->verifyText("xpath=//div[@id='map-field']//table[1]/tbody/tr[{$rowNumber}]/th[1]", preg_quote("Saved Field Mapping: {$existingMapping}"));
502 $rowNumber++;
503 }
504
505 foreach ($checkMapperHeaders as $rownum => $value) {
506 $this->verifyText("xpath=//div[@id='map-field']//table[1]/tbody/tr[{$rowNumber}]/{$headerSelector}[{$rownum}]", preg_quote($value));
507 }
508 $rowNumber++;
509
510 foreach ($headers as $field => $header) {
511 $this->verifyText("xpath=//div[@id='map-field']//table[1]/tbody/tr[{$rowNumber}]/td[1]", preg_quote($header));
512 $colnum = 2;
513 foreach ($rows as $row) {
514 $this->verifyText("xpath=//div[@id='map-field']//table[1]/tbody/tr[{$rowNumber}]/td[{$colnum}]", preg_quote($row[$field]));
515 $colnum++;
516 }
517 $rowNumber++;
518 }
519 }
520
521 /**
522 * Helper function to get imported contact ids.
523 *
524 * @param array $rows
525 * @param string $contactType
526 *
527 * @return array
528 * imported contact ids
529 */
530 public function _getImportedContactIds($rows, $contactType = 'Individual') {
531 $contactIds = array();
532
533 foreach ($rows as $row) {
534 $searchName = '';
535
536 // Build search name.
537 if ($contactType == 'Individual') {
538 $searchName = "{$row['last_name']}, {$row['first_name']}";
539 }
540 elseif ($contactType == 'Organization') {
541 $searchName = $row['organization_name'];
542 }
543 elseif ($contactType == 'Household') {
544 $searchName = $row['household_name'];
545 }
546
547 $this->openCiviPage("dashboard", "reset=1");
548
549 // Type search name in autocomplete.
550 $this->click("css=input#sort_name_navigation");
551 $this->type("css=input#sort_name_navigation", $searchName);
552 $this->typeKeys("css=input#sort_name_navigation", $searchName);
553
554 // Wait for result list.
555 $this->waitForElementPresent("css=ul.ui-autocomplete li");
556
557 // Visit contact summary page.
558 $this->click("css=ul.ui-autocomplete li");
559 $this->waitForPageToLoad($this->getTimeoutMsec());
560
561 // Get contact id from url.
562 $contactIds[] = $this->urlArg('cid');
563 }
564
565 return $contactIds;
566 }
567
568 /**
569 * Helper function to format headers and rows for contact relationship data.
570 *
571 * @param array $headers
572 * @param array $rows
573 */
574 public function _formatContactCSVdata(&$headers, &$rows) {
575 if (!isset($headers['contact_relationships'])) {
576 return;
577 }
578
579 $relationshipHeaders = $headers['contact_relationships'];
580 unset($headers['contact_relationships']);
581
582 if (empty($relationshipHeaders) || !is_array($relationshipHeaders)) {
583 return;
584 }
585
586 foreach ($relationshipHeaders as $relationshipHeader) {
587 $headers = array_merge($headers, $relationshipHeader);
588 }
589
590 foreach ($rows as & $row) {
591 $relationshipRows = $row['contact_relationships'];
592 unset($row['contact_relationships']);
593 foreach ($relationshipRows as $relationshipRow) {
594 $row = array_merge($row, $relationshipRow);
595 }
596 }
597 }
598
599 }