Start extracting the transform funcition
[civicrm-core.git] / CRM / Activity / Import / Parser / Activity.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 /**
20 * Class to parse activity csv files.
21 */
22 class CRM_Activity_Import_Parser_Activity extends CRM_Activity_Import_Parser {
23
24 protected $_mapperKeys;
25
26 private $_contactIdIndex;
27
28 /**
29 * Array of successfully imported activity id's
30 *
31 * @var array
32 */
33 protected $_newActivity;
34
35 /**
36 * Class constructor.
37 *
38 * @param array $mapperKeys
39 */
40 public function __construct($mapperKeys) {
41 parent::__construct();
42 $this->_mapperKeys = $mapperKeys;
43 }
44
45 /**
46 * Function of undocumented functionality required by the interface.
47 */
48 protected function fini() {}
49
50 /**
51 * The initializer code, called before the processing.
52 */
53 public function init() {
54 $activityContact = CRM_Activity_BAO_ActivityContact::import();
55 $activityTarget['target_contact_id'] = $activityContact['contact_id'];
56 $fields = array_merge(CRM_Activity_BAO_Activity::importableFields(),
57 $activityTarget
58 );
59
60 $fields = array_merge($fields, [
61 'source_contact_id' => [
62 'title' => ts('Source Contact'),
63 'headerPattern' => '/Source.Contact?/i',
64 ],
65 'activity_label' => [
66 'title' => ts('Activity Type Label'),
67 'headerPattern' => '/(activity.)?type label?/i',
68 ],
69 ]);
70
71 foreach ($fields as $name => $field) {
72 $field['type'] = CRM_Utils_Array::value('type', $field, CRM_Utils_Type::T_INT);
73 $field['dataPattern'] = CRM_Utils_Array::value('dataPattern', $field, '//');
74 $field['headerPattern'] = CRM_Utils_Array::value('headerPattern', $field, '//');
75 $this->addField($name, $field['title'], $field['type'], $field['headerPattern'], $field['dataPattern']);
76 }
77
78 $this->_newActivity = [];
79
80 $this->setActiveFields($this->_mapperKeys);
81
82 // FIXME: we should do this in one place together with Form/MapField.php
83 $this->_contactIdIndex = -1;
84
85 $index = 0;
86 foreach ($this->_mapperKeys as $key) {
87 switch ($key) {
88 case 'target_contact_id':
89 case 'external_identifier':
90 $this->_contactIdIndex = $index;
91 break;
92 }
93 $index++;
94 }
95 }
96
97 /**
98 * Handle the values in mapField mode.
99 *
100 * @param array $values
101 * The array of values belonging to this line.
102 *
103 * @return bool
104 */
105 public function mapField(&$values) {
106 return CRM_Import_Parser::VALID;
107 }
108
109 /**
110 * Handle the values in preview mode.
111 *
112 * @param array $values
113 * The array of values belonging to this line.
114 *
115 * @return bool
116 * the result of this processing
117 */
118 public function preview(&$values) {
119 return $this->summary($values);
120 }
121
122 /**
123 * Handle the values in summary mode.
124 *
125 * @param array $values
126 * The array of values belonging to this line.
127 *
128 * @return bool
129 * the result of this processing
130 */
131 public function summary(&$values) {
132 try {
133 $this->validateValues($values);
134 }
135 catch (CRM_Core_Exception $e) {
136 return $this->addError($values, [$e->getMessage()]);
137 }
138
139 return CRM_Import_Parser::VALID;
140 }
141
142 /**
143 * Handle the values in import mode.
144 *
145 * @param int $onDuplicate
146 * The code for what action to take on duplicates.
147 * @param array $values
148 * The array of values belonging to this line.
149 *
150 * @return bool
151 * the result of this processing
152 * @throws \CRM_Core_Exception
153 */
154 public function import($onDuplicate, &$values) {
155 // First make sure this is a valid line
156 try {
157 $this->validateValues($values);
158 }
159 catch (CRM_Core_Exception $e) {
160 return $this->addError($values, [$e->getMessage()]);
161 }
162 $params = $this->getApiReadyParams($values);
163 // For date-Formats.
164 $session = CRM_Core_Session::singleton();
165 $dateType = $session->get('dateTypes');
166 if (!isset($params['source_contact_id'])) {
167 $params['source_contact_id'] = $session->get('userID');
168 }
169
170 $customFields = CRM_Core_BAO_CustomField::getFields('Activity');
171
172 foreach ($params as $key => $val) {
173 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
174 if (!empty($customFields[$customFieldID]) && $customFields[$customFieldID]['data_type'] == 'Date') {
175 CRM_Contact_Import_Parser_Contact::formatCustomDate($params, $params, $dateType, $key);
176 }
177 elseif (!empty($customFields[$customFieldID]) && $customFields[$customFieldID]['data_type'] == 'Boolean') {
178 $params[$key] = CRM_Utils_String::strtoboolstr($val);
179 }
180 }
181 elseif ($key === 'activity_date_time') {
182 $params[$key] = CRM_Utils_Date::formatDate($val, $dateType);
183 }
184 elseif ($key === 'activity_subject') {
185 $params['subject'] = $val;
186 }
187 }
188
189 if ($this->_contactIdIndex < 0) {
190
191 // Retrieve contact id using contact dedupe rule.
192 // Since we are supporting only individual's activity import.
193 $params['contact_type'] = 'Individual';
194 $params['version'] = 3;
195 $error = _civicrm_api3_deprecated_duplicate_formatted_contact($params);
196
197 if (CRM_Core_Error::isAPIError($error, CRM_Core_ERROR::DUPLICATE_CONTACT)) {
198 $matchedIDs = explode(',', $error['error_message']['params'][0]);
199 if (count($matchedIDs) > 1) {
200 array_unshift($values, 'Multiple matching contact records detected for this row. The activity was not imported');
201 return CRM_Import_Parser::ERROR;
202 }
203 $cid = $matchedIDs[0];
204 $params['target_contact_id'] = $cid;
205 $params['version'] = 3;
206 $newActivity = civicrm_api('activity', 'create', $params);
207 if (!empty($newActivity['is_error'])) {
208 array_unshift($values, $newActivity['error_message']);
209 return CRM_Import_Parser::ERROR;
210 }
211
212 $this->_newActivity[] = $newActivity['id'];
213 return CRM_Import_Parser::VALID;
214
215 }
216 // Using new Dedupe rule.
217 $ruleParams = [
218 'contact_type' => 'Individual',
219 'used' => 'Unsupervised',
220 ];
221 $fieldsArray = CRM_Dedupe_BAO_Rule::dedupeRuleFields($ruleParams);
222
223 $disp = NULL;
224 foreach ($fieldsArray as $value) {
225 if (array_key_exists(trim($value), $params)) {
226 $paramValue = $params[trim($value)];
227 if (is_array($paramValue)) {
228 $disp .= $params[trim($value)][0][trim($value)] . " ";
229 }
230 else {
231 $disp .= $params[trim($value)] . " ";
232 }
233 }
234 }
235
236 if (!empty($params['external_identifier'])) {
237 if ($disp) {
238 $disp .= "AND {$params['external_identifier']}";
239 }
240 else {
241 $disp = $params['external_identifier'];
242 }
243 }
244
245 array_unshift($values, 'No matching Contact found for (' . $disp . ')');
246 return CRM_Import_Parser::ERROR;
247 }
248 if (!empty($params['external_identifier'])) {
249 $targetContactId = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
250 $params['external_identifier'], 'id', 'external_identifier'
251 );
252
253 if (!empty($params['target_contact_id']) &&
254 $params['target_contact_id'] != $targetContactId
255 ) {
256 array_unshift($values, 'Mismatch of External ID:' . $params['external_identifier'] . ' and Contact Id:' . $params['target_contact_id']);
257 return CRM_Import_Parser::ERROR;
258 }
259 if ($targetContactId) {
260 $params['target_contact_id'] = $targetContactId;
261 }
262 else {
263 array_unshift($values, 'No Matching Contact for External ID:' . $params['external_identifier']);
264 return CRM_Import_Parser::ERROR;
265 }
266 }
267
268 $params['version'] = 3;
269 $newActivity = civicrm_api('activity', 'create', $params);
270 if (!empty($newActivity['is_error'])) {
271 array_unshift($values, $newActivity['error_message']);
272 return CRM_Import_Parser::ERROR;
273 }
274
275 $this->_newActivity[] = $newActivity['id'];
276 return CRM_Import_Parser::VALID;
277 }
278
279 /**
280 *
281 * Get the value for the given field from the row of values.
282 *
283 * @param array $row
284 * @param string $fieldName
285 *
286 * @return null|string
287 */
288 protected function getFieldValue(array $row, string $fieldName) {
289 if (!is_numeric($this->getFieldIndex($fieldName))) {
290 return NULL;
291 }
292 return $row[$this->getFieldIndex($fieldName)] ?? NULL;
293 }
294
295 /**
296 * Get the index for the given field.
297 *
298 * @param string $fieldName
299 *
300 * @return false|int
301 */
302 protected function getFieldIndex(string $fieldName) {
303 return array_search($fieldName, $this->_mapperKeys, TRUE);
304
305 }
306
307 /**
308 * Add an error to the values.
309 *
310 * @param array $values
311 * @param array $error
312 *
313 * @return int
314 */
315 protected function addError(array &$values, array $error): int {
316 array_unshift($values, implode(';', $error));
317 return CRM_Import_Parser::ERROR;
318 }
319
320 /**
321 * Validate that the activity type id does not conflict with the label.
322 *
323 * @param array $values
324 *
325 * @return void
326 * @throws \CRM_Core_Exception
327 */
328 protected function validateActivityTypeIDAndLabel(array $values): void {
329 $activityLabel = $this->getFieldValue($values, 'activity_label');
330 $activityTypeID = $this->getFieldValue($values, 'activity_type_id');
331 if ($activityLabel && $activityTypeID
332 && $activityLabel !== CRM_Core_PseudoConstant::getLabel('CRM_Activity_BAO_Activity', 'activity_type_id', $activityTypeID)) {
333 throw new CRM_Core_Exception(ts('Activity type label and Activity type ID are in conflict'));
334 }
335 }
336
337 /**
338 * Is the supplied date field valid based on selected date format.
339 *
340 * @param string $value
341 *
342 * @return bool
343 */
344 protected function isValidDate(string $value): bool {
345 return (bool) CRM_Utils_Date::formatDate($value, CRM_Core_Session::singleton()->get('dateTypes'));
346 }
347
348 /**
349 * Is the supplied field a valid contact id.
350 *
351 * @param string|int $value
352 *
353 * @return bool
354 */
355 protected function isValidContactID($value): bool {
356 if (!CRM_Utils_Rule::integer($value)) {
357 return FALSE;
358 }
359 if (!CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_contact WHERE id = " . (int) $value)) {
360 return FALSE;
361 }
362 return TRUE;
363 }
364
365 /**
366 * Validate custom fields.
367 *
368 * @param array $values
369 *
370 * @throws \CRM_Core_Exception
371 */
372 protected function validateCustomFields($values):void {
373 $this->setActiveFieldValues($values);
374 $params = $this->getActiveFieldParams();
375 $errorMessage = NULL;
376 // Checking error in custom data.
377 $params['contact_type'] = 'Activity';
378 CRM_Contact_Import_Parser_Contact::isErrorInCustomData($params, $errorMessage);
379 if ($errorMessage) {
380 throw new CRM_Core_Exception('Invalid value for field(s) : ' . $errorMessage);
381 }
382 }
383
384 /**
385 * @param array $values
386 *
387 * @throws \CRM_Core_Exception
388 */
389 protected function validateValues(array $values): void {
390 // Check required fields if this is not an update.
391 if (!$this->getFieldValue($values, 'activity_id')) {
392 if (!$this->getFieldValue($values, 'activity_label')
393 && !$this->getFieldValue($values, 'activity_type_id')) {
394 throw new CRM_Core_Exception(ts('Missing required fields: Activity type label or Activity type ID'));
395 }
396 if (!$this->getFieldValue($values, 'activity_date_time')) {
397 throw new CRM_Core_Exception(ts('Missing required fields'));
398 }
399 }
400
401 $this->validateActivityTypeIDAndLabel($values);
402 if ($this->getFieldValue($values, 'activity_date_time')
403 && !$this->isValidDate($this->getFieldValue($values, 'activity_date_time'))) {
404 throw new CRM_Core_Exception(ts('Invalid Activity Date'));
405 }
406
407 if ($this->getFieldValue($values, 'activity_engagement_level')
408 && !CRM_Utils_Rule::positiveInteger($this->getFieldValue($values, 'activity_engagement_level'))) {
409 throw new CRM_Core_Exception(ts('Activity Engagement Index'));
410 }
411
412 $targetContactID = $this->getFieldValue($values, 'target_contact_id');
413 if ($targetContactID && !$this->isValidContactID($targetContactID)) {
414 throw new CRM_Core_Exception("Invalid Contact ID: There is no contact record with contact_id = " . CRM_Utils_Type::escape($targetContactID, 'String'));
415 }
416 $this->validateCustomFields($values);
417 }
418
419 /**
420 * Get array of parameters formatted for the api from the submitted values.
421 *
422 * @param array $values
423 *
424 * @return array
425 */
426 protected function getApiReadyParams(array $values): array {
427 $this->setActiveFieldValues($values);
428 $params = $this->getActiveFieldParams();
429 if ($this->getFieldValue($values, 'activity_label')) {
430 $params['activity_type_id'] = array_search(
431 $this->getFieldValue($values, 'activity_label'),
432 CRM_Activity_BAO_Activity::buildOptions('activity_type_id', 'create'),
433 TRUE
434 );
435 }
436 return $params;
437 }
438
439 }