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