Merge pull request #23691 from eileenmcnaughton/contact_type
[civicrm-core.git] / CRM / Event / Import / Form / MapField.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class gets the name of the file to upload
20 */
21 class CRM_Event_Import_Form_MapField extends CRM_Import_Form_MapField {
22
23 /**
24 * Set variables up before form is built.
25 *
26 * @return void
27 */
28 public function preProcess() {
29 $this->_mapperFields = $this->get('fields');
30 asort($this->_mapperFields);
31 unset($this->_mapperFields['participant_is_test']);
32 $this->_columnCount = $this->get('columnCount');
33 $this->assign('columnCount', $this->_columnCount);
34 $this->_dataValues = $this->get('dataValues');
35 $this->assign('dataValues', $this->_dataValues);
36
37 $skipColumnHeader = $this->controller->exportValue('DataSource', 'skipColumnHeader');
38 $this->_onDuplicate = $this->get('onDuplicate');
39 $highlightedFields = [];
40 if ($skipColumnHeader) {
41 $this->assign('skipColumnHeader', $skipColumnHeader);
42 $this->assign('rowDisplayCount', 3);
43 /* if we had a column header to skip, stash it for later */
44
45 $this->_columnHeaders = $this->_dataValues[0];
46 }
47 else {
48 $this->assign('rowDisplayCount', 2);
49 }
50 if ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) {
51 $remove = array('participant_contact_id', 'email', 'first_name', 'last_name', 'external_identifier');
52 foreach ($remove as $value) {
53 unset($this->_mapperFields[$value]);
54 }
55 $highlightedFieldsArray = array('participant_id', 'event_id', 'event_title', 'participant_status_id');
56 foreach ($highlightedFieldsArray as $name) {
57 $highlightedFields[] = $name;
58 }
59 }
60 elseif ($this->_onDuplicate == CRM_Import_Parser::DUPLICATE_SKIP ||
61 $this->_onDuplicate == CRM_Import_Parser::DUPLICATE_NOCHECK
62 ) {
63 unset($this->_mapperFields['participant_id']);
64 $highlightedFieldsArray = array(
65 'participant_contact_id',
66 'event_id',
67 'email',
68 'first_name',
69 'last_name',
70 'external_identifier',
71 'participant_status_id',
72 );
73 foreach ($highlightedFieldsArray as $name) {
74 $highlightedFields[] = $name;
75 }
76 }
77 $this->assign('highlightedFields', $highlightedFields);
78 }
79
80 /**
81 * Build the form object.
82 *
83 * @return void
84 */
85 public function buildQuickForm() {
86
87 //to save the current mappings
88 if (!$this->get('savedMapping')) {
89 $saveDetailsName = ts('Save this field mapping');
90 $this->applyFilter('saveMappingName', 'trim');
91 $this->add('text', 'saveMappingName', ts('Name'));
92 $this->add('text', 'saveMappingDesc', ts('Description'));
93 }
94 else {
95 $savedMapping = $this->get('savedMapping');
96
97 list($mappingName, $mappingContactType, $mappingLocation, $mappingPhoneType, $mappingRelation) = CRM_Core_BAO_Mapping::getMappingFields($savedMapping);
98
99 $mappingName = $mappingName[1];
100 $mappingContactType = $mappingContactType[1];
101 $mappingLocation = $mappingLocation['1'] ?? NULL;
102 $mappingPhoneType = $mappingPhoneType['1'] ?? NULL;
103 $mappingRelation = $mappingRelation['1'] ?? NULL;
104
105 //mapping is to be loaded from database
106
107 $this->set('loadedMapping', $savedMapping);
108
109 $getMappingName = new CRM_Core_DAO_Mapping();
110 $getMappingName->id = $savedMapping;
111 $getMappingName->mapping_type = 'Import Participants';
112 $getMappingName->find();
113 while ($getMappingName->fetch()) {
114 $mapperName = $getMappingName->name;
115 }
116
117 $this->assign('savedMappingName', $mapperName);
118
119 $this->add('hidden', 'mappingId', $savedMapping);
120
121 $this->addElement('checkbox', 'updateMapping', ts('Update this field mapping'), NULL);
122 $saveDetailsName = ts('Save as a new field mapping');
123 $this->add('text', 'saveMappingName', ts('Name'));
124 $this->add('text', 'saveMappingDesc', ts('Description'));
125 }
126
127 $this->addElement('checkbox', 'saveMapping', $saveDetailsName, NULL, array('onclick' => "showSaveDetails(this)"));
128
129 $this->addFormRule(array('CRM_Event_Import_Form_MapField', 'formRule'), $this);
130
131 $defaults = [];
132 $mapperKeys = array_keys($this->_mapperFields);
133 $hasHeaders = !empty($this->_columnHeaders);
134 $headerPatterns = $this->get('headerPatterns');
135 $dataPatterns = $this->get('dataPatterns');
136 /* Initialize all field usages to false */
137
138 foreach ($mapperKeys as $key) {
139 $this->_fieldUsed[$key] = FALSE;
140 }
141 $this->_location_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
142 $sel1 = $this->_mapperFields;
143
144 $sel2[''] = NULL;
145 $js = "<script type='text/javascript'>\n";
146 $formName = 'document.forms.' . $this->_name;
147
148 //used to warn for mismatch column count or mismatch mapping
149 $warning = 0;
150 for ($i = 0; $i < $this->_columnCount; $i++) {
151 $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', array(1 => $i)), NULL);
152 $jsSet = FALSE;
153 if ($this->get('savedMapping')) {
154 if (isset($mappingName[$i])) {
155 if ($mappingName[$i] != ts('- do not import -')) {
156
157 $mappingHeader = array_keys($this->_mapperFields, $mappingName[$i]);
158
159 if (!isset($locationId) || !$locationId) {
160 $js .= "{$formName}['mapper[$i][1]'].style.display = 'none';\n";
161 }
162
163 if (!isset($phoneType) || !$phoneType) {
164 $js .= "{$formName}['mapper[$i][2]'].style.display = 'none';\n";
165 }
166
167 $js .= "{$formName}['mapper[$i][3]'].style.display = 'none';\n";
168 $defaults["mapper[$i]"] = array(
169 $mappingHeader[0],
170 (isset($locationId)) ? $locationId : "",
171 (isset($phoneType)) ? $phoneType : "",
172 );
173 $jsSet = TRUE;
174 }
175 else {
176 $defaults["mapper[$i]"] = [];
177 }
178 if (!$jsSet) {
179 for ($k = 1; $k < 4; $k++) {
180 $js .= "{$formName}['mapper[$i][$k]'].style.display = 'none';\n";
181 }
182 }
183 }
184 else {
185 // this load section to help mapping if we ran out of saved columns when doing Load Mapping
186 $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_" . $i . "_');\n";
187
188 if ($hasHeaders) {
189 $defaults["mapper[$i]"] = array($this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns));
190 }
191 else {
192 $defaults["mapper[$i]"] = array($this->defaultFromData($dataPatterns, $i));
193 }
194 }
195 //end of load mapping
196 }
197 else {
198 $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_" . $i . "_');\n";
199 if ($hasHeaders) {
200 // Infer the default from the skipped headers if we have them
201 $defaults["mapper[$i]"] = array(
202 $this->defaultFromHeader($this->_columnHeaders[$i],
203 $headerPatterns
204 ),
205 // $defaultLocationType->id
206 0,
207 );
208 }
209 else {
210 // Otherwise guess the default from the form of the data
211 $defaults["mapper[$i]"] = array(
212 $this->defaultFromData($dataPatterns, $i),
213 // $defaultLocationType->id
214 0,
215 );
216 }
217 }
218 $sel->setOptions(array($sel1, $sel2, (isset($sel3)) ? $sel3 : "", (isset($sel4)) ? $sel4 : ""));
219 }
220 $js .= "</script>\n";
221 $this->assign('initHideBoxes', $js);
222
223 //set warning if mismatch in more than
224 if (isset($mappingName)) {
225 if (($this->_columnCount != count($mappingName))) {
226 $warning++;
227 }
228 }
229 if ($warning != 0 && $this->get('savedMapping')) {
230 $session = CRM_Core_Session::singleton();
231 $session->setStatus(ts('The data columns in this import file appear to be different from the saved mapping. Please verify that you have selected the correct saved mapping before continuing.'));
232 }
233 else {
234 $session = CRM_Core_Session::singleton();
235 $session->setStatus(NULL);
236 }
237
238 $this->setDefaults($defaults);
239
240 $this->addButtons(array(
241 array(
242 'type' => 'back',
243 'name' => ts('Previous'),
244 ),
245 array(
246 'type' => 'next',
247 'name' => ts('Continue'),
248 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
249 'isDefault' => TRUE,
250 ),
251 array(
252 'type' => 'cancel',
253 'name' => ts('Cancel'),
254 ),
255 ));
256 }
257
258 /**
259 * Global validation rules for the form.
260 *
261 * @param array $fields
262 * Posted values of the form.
263 *
264 * @param $files
265 * @param self $self
266 *
267 * @return array
268 * list of errors to be posted back to the form
269 */
270 public static function formRule($fields, $files, $self) {
271 $errors = [];
272 // define so we avoid notices below
273 $errors['_qf_default'] = '';
274 $contactFieldsBelowWeightMessage = NULL;
275 if (!array_key_exists('savedMapping', $fields)) {
276 $importKeys = [];
277 foreach ($fields['mapper'] as $mapperPart) {
278 $importKeys[] = $mapperPart[0];
279 }
280 // FIXME: should use the schema titles, not redeclare them
281 $requiredFields = array(
282 'participant_contact_id' => ts('Contact ID'),
283 'event_id' => ts('Event ID'),
284 );
285
286 $contactTypeId = $self->get('contactType');
287 $contactTypes = array(
288 CRM_Import_Parser::CONTACT_INDIVIDUAL => 'Individual',
289 CRM_Import_Parser::CONTACT_HOUSEHOLD => 'Household',
290 CRM_Import_Parser::CONTACT_ORGANIZATION => 'Organization',
291 );
292 $contactFieldsBelowWeightMessage = self::validateRequiredContactMatchFields($contactTypes[$contactTypeId], $importKeys);
293
294 foreach ($requiredFields as $field => $title) {
295 if (!in_array($field, $importKeys)) {
296 if ($field == 'participant_contact_id') {
297 if (!$contactFieldsBelowWeightMessage || in_array('external_identifier', $importKeys) ||
298 in_array('participant_id', $importKeys)
299 ) {
300 continue;
301 }
302 if ($self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) {
303 $errors['_qf_default'] .= ts('Missing required field: Provide Participant ID') . '<br />';
304 }
305 else {
306 $errors['_qf_default'] .= ts('Missing required contact matching fields.') . " $contactFieldsBelowWeightMessage " . ' ' . ts('Or Provide Contact ID or External ID.') . '<br />';
307 }
308 }
309 elseif (!in_array('event_title', $importKeys)) {
310 $errors['_qf_default'] .= ts('Missing required field: Provide %1 or %2',
311 array(1 => $title, 2 => 'Event Title')
312 ) . '<br />';
313 }
314 }
315 }
316 }
317
318 if (!empty($fields['saveMapping'])) {
319 $nameField = $fields['saveMappingName'] ?? NULL;
320 if (empty($nameField)) {
321 $errors['saveMappingName'] = ts('Name is required to save Import Mapping');
322 }
323 else {
324 if (CRM_Core_BAO_Mapping::checkMapping($nameField, CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Participant'))) {
325 $errors['saveMappingName'] = ts('Duplicate Import Participant Mapping Name');
326 }
327 }
328 }
329
330 //display Error if loaded mapping is not selected
331 if (array_key_exists('loadMapping', $fields)) {
332 $getMapName = $fields['savedMapping'] ?? NULL;
333 if (empty($getMapName)) {
334 $errors['savedMapping'] = ts('Select saved mapping');
335 }
336 }
337
338 if (empty($errors['_qf_default'])) {
339 unset($errors['_qf_default']);
340 }
341 if (!empty($errors)) {
342 if (!empty($errors['saveMappingName'])) {
343 $_flag = 1;
344 $assignError = new CRM_Core_Page();
345 $assignError->assign('mappingDetailsError', $_flag);
346 }
347 return $errors;
348 }
349
350 return TRUE;
351 }
352
353 /**
354 * Process the mapped fields and map it into the uploaded file
355 * preview the file and extract some summary statistics
356 *
357 * @return void
358 */
359 public function postProcess() {
360 $params = $this->controller->exportValues('MapField');
361 //reload the mapfield if load mapping is pressed
362 if (!empty($params['savedMapping'])) {
363 $this->set('savedMapping', $params['savedMapping']);
364 $this->controller->resetPage($this->_name);
365 return;
366 }
367
368 $mapper = [];
369 $mapperKeys = $this->controller->exportValue($this->_name, 'mapper');
370 $mapperKeysMain = [];
371
372 for ($i = 0; $i < $this->_columnCount; $i++) {
373 $mapper[$i] = $this->_mapperFields[$mapperKeys[$i][0]];
374 $mapperKeysMain[$i] = $mapperKeys[$i][0];
375 }
376
377 $this->set('mapper', $mapper);
378
379 // store mapping Id to display it in the preview page
380 $this->set('loadMappingId', CRM_Utils_Array::value('mappingId', $params));
381
382 //Updating Mapping Records
383 if (!empty($params['updateMapping'])) {
384
385 $mappingFields = new CRM_Core_DAO_MappingField();
386 $mappingFields->mapping_id = $params['mappingId'];
387 $mappingFields->find();
388
389 $mappingFieldsId = [];
390 while ($mappingFields->fetch()) {
391 if ($mappingFields->id) {
392 $mappingFieldsId[$mappingFields->column_number] = $mappingFields->id;
393 }
394 }
395
396 for ($i = 0; $i < $this->_columnCount; $i++) {
397 $updateMappingFields = new CRM_Core_DAO_MappingField();
398 $updateMappingFields->id = $mappingFieldsId[$i];
399 $updateMappingFields->mapping_id = $params['mappingId'];
400 $updateMappingFields->column_number = $i;
401 $updateMappingFields->name = $mapper[$i];
402 $updateMappingFields->save();
403 }
404 }
405
406 //Saving Mapping Details and Records
407 if (!empty($params['saveMapping'])) {
408 $mappingParams = array(
409 'name' => $params['saveMappingName'],
410 'description' => $params['saveMappingDesc'],
411 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Participant'),
412 );
413 $saveMapping = CRM_Core_BAO_Mapping::add($mappingParams);
414
415 for ($i = 0; $i < $this->_columnCount; $i++) {
416 $saveMappingFields = new CRM_Core_DAO_MappingField();
417 $saveMappingFields->mapping_id = $saveMapping->id;
418 $saveMappingFields->column_number = $i;
419 $saveMappingFields->name = $mapper[$i];
420 $saveMappingFields->save();
421 }
422 $this->set('savedMapping', $saveMappingFields->mapping_id);
423 }
424
425 $parser = new CRM_Event_Import_Parser_Participant($mapperKeysMain);
426 $parser->run($this->getSubmittedValue('uploadFile'), $this->getSubmittedValue('fieldSeparator'), $mapper, $this->getSubmittedValue('skipColumnHeader'),
427 CRM_Import_Parser::MODE_PREVIEW, $this->get('contactType')
428 );
429 // add all the necessary variables to the form
430 $parser->set($this);
431 }
432
433 /**
434 * @return CRM_Event_Import_Parser_Participant
435 */
436 protected function getParser(): CRM_Event_Import_Parser_Participant {
437 if (!$this->parser) {
438 $this->parser = new CRM_Event_Import_Parser_Participant();
439 $this->parser->setUserJobID($this->getUserJobID());
440 $this->parser->init();
441 }
442 return $this->parser;
443 }
444
445 }