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