Merge pull request #22115 from artfulrobot/artfulrobot-api4-count-methods
[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 $hasLocationTypes = $this->get('fieldTypes');
137 /* Initialize all field usages to false */
138
139 foreach ($mapperKeys as $key) {
140 $this->_fieldUsed[$key] = FALSE;
141 }
142 $this->_location_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
143 $sel1 = $this->_mapperFields;
144
145 $sel2[''] = NULL;
146 $js = "<script type='text/javascript'>\n";
147 $formName = 'document.forms.' . $this->_name;
148
149 //used to warn for mismatch column count or mismatch mapping
150 $warning = 0;
151 for ($i = 0; $i < $this->_columnCount; $i++) {
152 $sel = &$this->addElement('hierselect', "mapper[$i]", ts('Mapper for Field %1', array(1 => $i)), NULL);
153 $jsSet = FALSE;
154 if ($this->get('savedMapping')) {
155 if (isset($mappingName[$i])) {
156 if ($mappingName[$i] != ts('- do not import -')) {
157
158 $mappingHeader = array_keys($this->_mapperFields, $mappingName[$i]);
159
160 if (!isset($locationId) || !$locationId) {
161 $js .= "{$formName}['mapper[$i][1]'].style.display = 'none';\n";
162 }
163
164 if (!isset($phoneType) || !$phoneType) {
165 $js .= "{$formName}['mapper[$i][2]'].style.display = 'none';\n";
166 }
167
168 $js .= "{$formName}['mapper[$i][3]'].style.display = 'none';\n";
169 $defaults["mapper[$i]"] = array(
170 $mappingHeader[0],
171 (isset($locationId)) ? $locationId : "",
172 (isset($phoneType)) ? $phoneType : "",
173 );
174 $jsSet = TRUE;
175 }
176 else {
177 $defaults["mapper[$i]"] = [];
178 }
179 if (!$jsSet) {
180 for ($k = 1; $k < 4; $k++) {
181 $js .= "{$formName}['mapper[$i][$k]'].style.display = 'none';\n";
182 }
183 }
184 }
185 else {
186 // this load section to help mapping if we ran out of saved columns when doing Load Mapping
187 $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_" . $i . "_');\n";
188
189 if ($hasHeaders) {
190 $defaults["mapper[$i]"] = array($this->defaultFromHeader($this->_columnHeaders[$i], $headerPatterns));
191 }
192 else {
193 $defaults["mapper[$i]"] = array($this->defaultFromData($dataPatterns, $i));
194 }
195 }
196 //end of load mapping
197 }
198 else {
199 $js .= "swapOptions($formName, 'mapper[$i]', 0, 3, 'hs_mapper_" . $i . "_');\n";
200 if ($hasHeaders) {
201 // Infer the default from the skipped headers if we have them
202 $defaults["mapper[$i]"] = array(
203 $this->defaultFromHeader($this->_columnHeaders[$i],
204 $headerPatterns
205 ),
206 // $defaultLocationType->id
207 0,
208 );
209 }
210 else {
211 // Otherwise guess the default from the form of the data
212 $defaults["mapper[$i]"] = array(
213 $this->defaultFromData($dataPatterns, $i),
214 // $defaultLocationType->id
215 0,
216 );
217 }
218 }
219 $sel->setOptions(array($sel1, $sel2, (isset($sel3)) ? $sel3 : "", (isset($sel4)) ? $sel4 : ""));
220 }
221 $js .= "</script>\n";
222 $this->assign('initHideBoxes', $js);
223
224 //set warning if mismatch in more than
225 if (isset($mappingName)) {
226 if (($this->_columnCount != count($mappingName))) {
227 $warning++;
228 }
229 }
230 if ($warning != 0 && $this->get('savedMapping')) {
231 $session = CRM_Core_Session::singleton();
232 $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.'));
233 }
234 else {
235 $session = CRM_Core_Session::singleton();
236 $session->setStatus(NULL);
237 }
238
239 $this->setDefaults($defaults);
240
241 $this->addButtons(array(
242 array(
243 'type' => 'back',
244 'name' => ts('Previous'),
245 ),
246 array(
247 'type' => 'next',
248 'name' => ts('Continue'),
249 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
250 'isDefault' => TRUE,
251 ),
252 array(
253 'type' => 'cancel',
254 'name' => ts('Cancel'),
255 ),
256 ));
257 }
258
259 /**
260 * Global validation rules for the form.
261 *
262 * @param array $fields
263 * Posted values of the form.
264 *
265 * @param $files
266 * @param self $self
267 *
268 * @return array
269 * list of errors to be posted back to the form
270 */
271 public static function formRule($fields, $files, $self) {
272 $errors = [];
273 // define so we avoid notices below
274 $errors['_qf_default'] = '';
275 $contactFieldsBelowWeightMessage = NULL;
276 if (!array_key_exists('savedMapping', $fields)) {
277 $importKeys = [];
278 foreach ($fields['mapper'] as $mapperPart) {
279 $importKeys[] = $mapperPart[0];
280 }
281 // FIXME: should use the schema titles, not redeclare them
282 $requiredFields = array(
283 'participant_contact_id' => ts('Contact ID'),
284 'event_id' => ts('Event ID'),
285 );
286
287 $contactTypeId = $self->get('contactType');
288 $contactTypes = array(
289 CRM_Import_Parser::CONTACT_INDIVIDUAL => 'Individual',
290 CRM_Import_Parser::CONTACT_HOUSEHOLD => 'Household',
291 CRM_Import_Parser::CONTACT_ORGANIZATION => 'Organization',
292 );
293 $contactFieldsBelowWeightMessage = self::validateRequiredContactMatchFields($contactTypes[$contactTypeId], $importKeys);
294
295 foreach ($requiredFields as $field => $title) {
296 if (!in_array($field, $importKeys)) {
297 if ($field == 'participant_contact_id') {
298 if (!$contactFieldsBelowWeightMessage || in_array('external_identifier', $importKeys) ||
299 in_array('participant_id', $importKeys)
300 ) {
301 continue;
302 }
303 if ($self->_onDuplicate == CRM_Import_Parser::DUPLICATE_UPDATE) {
304 $errors['_qf_default'] .= ts('Missing required field: Provide Participant ID') . '<br />';
305 }
306 else {
307 $errors['_qf_default'] .= ts('Missing required contact matching fields.') . " $contactFieldsBelowWeightMessage " . ' ' . ts('Or Provide Contact ID or External ID.') . '<br />';
308 }
309 }
310 elseif (!in_array('event_title', $importKeys)) {
311 $errors['_qf_default'] .= ts('Missing required field: Provide %1 or %2',
312 array(1 => $title, 2 => 'Event Title')
313 ) . '<br />';
314 }
315 }
316 }
317 }
318
319 if (!empty($fields['saveMapping'])) {
320 $nameField = $fields['saveMappingName'] ?? NULL;
321 if (empty($nameField)) {
322 $errors['saveMappingName'] = ts('Name is required to save Import Mapping');
323 }
324 else {
325 if (CRM_Core_BAO_Mapping::checkMapping($nameField, CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Participant'))) {
326 $errors['saveMappingName'] = ts('Duplicate Import Participant Mapping Name');
327 }
328 }
329 }
330
331 //display Error if loaded mapping is not selected
332 if (array_key_exists('loadMapping', $fields)) {
333 $getMapName = $fields['savedMapping'] ?? NULL;
334 if (empty($getMapName)) {
335 $errors['savedMapping'] = ts('Select saved mapping');
336 }
337 }
338
339 if (empty($errors['_qf_default'])) {
340 unset($errors['_qf_default']);
341 }
342 if (!empty($errors)) {
343 if (!empty($errors['saveMappingName'])) {
344 $_flag = 1;
345 $assignError = new CRM_Core_Page();
346 $assignError->assign('mappingDetailsError', $_flag);
347 }
348 return $errors;
349 }
350
351 return TRUE;
352 }
353
354 /**
355 * Process the mapped fields and map it into the uploaded file
356 * preview the file and extract some summary statistics
357 *
358 * @return void
359 */
360 public function postProcess() {
361 $params = $this->controller->exportValues('MapField');
362 //reload the mapfield if load mapping is pressed
363 if (!empty($params['savedMapping'])) {
364 $this->set('savedMapping', $params['savedMapping']);
365 $this->controller->resetPage($this->_name);
366 return;
367 }
368
369 $mapper = [];
370 $mapperKeys = $this->controller->exportValue($this->_name, 'mapper');
371 $mapperKeysMain = [];
372
373 for ($i = 0; $i < $this->_columnCount; $i++) {
374 $mapper[$i] = $this->_mapperFields[$mapperKeys[$i][0]];
375 $mapperKeysMain[$i] = $mapperKeys[$i][0];
376 }
377
378 $this->set('mapper', $mapper);
379
380 // store mapping Id to display it in the preview page
381 $this->set('loadMappingId', CRM_Utils_Array::value('mappingId', $params));
382
383 //Updating Mapping Records
384 if (!empty($params['updateMapping'])) {
385
386 $mappingFields = new CRM_Core_DAO_MappingField();
387 $mappingFields->mapping_id = $params['mappingId'];
388 $mappingFields->find();
389
390 $mappingFieldsId = [];
391 while ($mappingFields->fetch()) {
392 if ($mappingFields->id) {
393 $mappingFieldsId[$mappingFields->column_number] = $mappingFields->id;
394 }
395 }
396
397 for ($i = 0; $i < $this->_columnCount; $i++) {
398 $updateMappingFields = new CRM_Core_DAO_MappingField();
399 $updateMappingFields->id = $mappingFieldsId[$i];
400 $updateMappingFields->mapping_id = $params['mappingId'];
401 $updateMappingFields->column_number = $i;
402 $updateMappingFields->name = $mapper[$i];
403 $updateMappingFields->save();
404 }
405 }
406
407 //Saving Mapping Details and Records
408 if (!empty($params['saveMapping'])) {
409 $mappingParams = array(
410 'name' => $params['saveMappingName'],
411 'description' => $params['saveMappingDesc'],
412 'mapping_type_id' => CRM_Core_PseudoConstant::getKey('CRM_Core_BAO_Mapping', 'mapping_type_id', 'Import Participant'),
413 );
414 $saveMapping = CRM_Core_BAO_Mapping::add($mappingParams);
415
416 for ($i = 0; $i < $this->_columnCount; $i++) {
417 $saveMappingFields = new CRM_Core_DAO_MappingField();
418 $saveMappingFields->mapping_id = $saveMapping->id;
419 $saveMappingFields->column_number = $i;
420 $saveMappingFields->name = $mapper[$i];
421 $saveMappingFields->save();
422 }
423 $this->set('savedMapping', $saveMappingFields->mapping_id);
424 }
425
426 $parser = new CRM_Event_Import_Parser_Participant($mapperKeysMain);
427 $parser->run($this->getSubmittedValue('uploadFile'), $this->getSubmittedValue('fieldSeparator'), $mapper, $this->getSubmittedValue('skipColumnHeader'),
428 CRM_Import_Parser::MODE_PREVIEW, $this->get('contactType')
429 );
430 // add all the necessary variables to the form
431 $parser->set($this);
432 }
433
434 }