Merge pull request #21965 from civicrm/5.43
[civicrm-core.git] / CRM / Core / BAO / UFField.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
8eedd10a 19 * This class contains function for UFField.
6a488035
TO
20 */
21class CRM_Core_BAO_UFField extends CRM_Core_DAO_UFField {
22
23 /**
fe482240 24 * Batch entry fields.
518fa0ee 25 * @var array
6a488035
TO
26 */
27 private static $_contriBatchEntryFields = NULL;
28 private static $_memberBatchEntryFields = NULL;
29
c8ec6bbf
SB
30 /**
31 * Create UFField object.
32 *
33 * @param array $params
34 * Array per getfields metadata.
35 *
36 * @return \CRM_Core_BAO_UFField
37 * @throws \API_Exception
38 */
aa9732b9 39 public static function create($params) {
9c1bc317 40 $id = $params['id'] ?? NULL;
c8ec6bbf 41
41e85062
SL
42 $op = empty($id) ? 'create' : 'edit';
43 CRM_Utils_Hook::pre('UFField', $op, $id, $params);
aa9732b9
CW
44 // Merge in data from existing field
45 if (!empty($id)) {
46 $UFField = new CRM_Core_BAO_UFField();
47 $UFField->id = $params['id'];
48 if ($UFField->find(TRUE)) {
49 $defaults = $UFField->toArray();
50 // This will be calculated based on field name
51 unset($defaults['field_type']);
52 $params += $defaults;
53 }
54 else {
55 throw new API_Exception("UFFIeld id {$params['id']} not found.");
56 }
57 }
c8ec6bbf 58
aa9732b9
CW
59 // Validate field_name
60 if (strpos($params['field_name'], 'formatting') !== 0 && !CRM_Core_BAO_UFField::isValidFieldName($params['field_name'])) {
c8ec6bbf
SB
61 throw new API_Exception('The field_name is not valid');
62 }
63
aa9732b9
CW
64 // Supply default label if not set
65 if (empty($id) && !isset($params['label'])) {
66 $params['label'] = self::getAvailableFieldTitles()[$params['field_name']];
c8ec6bbf
SB
67 }
68
aa9732b9
CW
69 // Supply field_type if not set
70 if (empty($params['field_type']) && strpos($params['field_name'], 'formatting') !== 0) {
71 $params['field_type'] = CRM_Utils_Array::pathGet(self::getAvailableFieldsFlat(), [$params['field_name'], 'field_type']);
72 }
73 elseif (empty($params['field_type'])) {
74 $params['field_type'] = 'Formatting';
75 }
76
77 // Generate unique name for formatting fields
78 if ($params['field_name'] === 'formatting') {
79 $params['field_name'] = 'formatting_' . substr(uniqid(), -4);
c8ec6bbf 80 }
c8ec6bbf 81
aa9732b9 82 if (self::duplicateField($params)) {
c8ec6bbf
SB
83 throw new API_Exception("The field was not added. It already exists in this profile.");
84 }
85
c8ec6bbf
SB
86 //@todo why is this even optional? Surely weight should just be 'managed' ??
87 if (CRM_Utils_Array::value('option.autoweight', $params, TRUE)) {
88 $params['weight'] = CRM_Core_BAO_UFField::autoWeight($params);
89 }
aa9732b9
CW
90
91 // Set values for uf field properties and save
78580003
SB
92 $ufField = new CRM_Core_DAO_UFField();
93 $ufField->copyValues($params);
78580003 94
aa9732b9
CW
95 if ($params['field_name'] == 'url') {
96 $ufField->location_type_id = 'null';
78580003
SB
97 }
98 else {
aa9732b9 99 $ufField->website_type_id = 'null';
78580003 100 }
aa9732b9
CW
101 if (!strstr($params['field_name'], 'phone')) {
102 $ufField->phone_type_id = 'null';
78580003
SB
103 }
104
78580003 105 $ufField->save();
c8ec6bbf 106
aa9732b9
CW
107 $fieldsType = CRM_Core_BAO_UFGroup::calculateGroupType($ufField->uf_group_id, TRUE);
108 CRM_Core_BAO_UFGroup::updateGroupTypes($ufField->uf_group_id, $fieldsType);
c8ec6bbf 109
41e85062
SL
110 CRM_Utils_Hook::post('UFField', $op, $ufField->id, $ufField);
111
be2fb01f 112 civicrm_api3('profile', 'getfields', ['cache_clear' => TRUE]);
c8ec6bbf
SB
113 return $ufField;
114 }
115
6a488035 116 /**
fe482240 117 * Fetch object based on array of properties.
6a488035 118 *
6a0b768e
TO
119 * @param array $params
120 * (reference ) an assoc array of name/value pairs.
121 * @param array $defaults
122 * (reference ) an assoc array to hold the flattened values.
6a488035 123 *
16b10e64 124 * @return CRM_Core_BAO_UFField
6a488035 125 */
00be9182 126 public static function retrieve(&$params, &$defaults) {
6a488035
TO
127 return CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_UFField', $params, $defaults);
128 }
129
6a488035 130 /**
fe482240 131 * Update the is_active flag in the db.
6a488035 132 *
6a0b768e
TO
133 * @param int $id
134 * Id of the database record.
135 * @param bool $is_active
136 * Value we want to set the is_active field.
6a488035 137 *
8a4fede3 138 * @return bool
139 * true if we found and updated the object, else false
6a488035 140 */
00be9182 141 public static function setIsActive($id, $is_active) {
6a488035
TO
142 //check if custom data profile field is disabled
143 if ($is_active) {
144 if (CRM_Core_BAO_UFField::checkUFStatus($id)) {
145 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_UFField', $id, 'is_active', $is_active);
146 }
147 else {
148 CRM_Core_Session::setStatus(ts('Cannot enable this UF field since the used custom field is disabled.'), ts('Check Custom Field'), 'error');
149 }
150 }
151 else {
152 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_UFField', $id, 'is_active', $is_active);
153 }
154 }
155
156 /**
157 * Delete the profile Field.
158 *
6a0b768e 159 * @param int $id
67d870c5 160 * @deprecated
608e6658 161 * @return bool
6a488035
TO
162 */
163 public static function del($id) {
67d870c5 164 return (bool) self::deleteRecord(['id' => $id]);
6a488035
TO
165 }
166
167 /**
fe482240 168 * Check duplicate for duplicate field in a group.
6a488035 169 *
6a0b768e
TO
170 * @param array $params
171 * An associative array with field and values.
6a488035 172 *
b99bd217 173 * @return bool
6a488035 174 */
b99bd217 175 public static function duplicateField($params) {
6a488035 176 $ufField = new CRM_Core_DAO_UFField();
9c1bc317
CW
177 $ufField->uf_group_id = $params['uf_group_id'] ?? NULL;
178 $ufField->field_type = $params['field_type'] ?? NULL;
179 $ufField->field_name = $params['field_name'] ?? NULL;
180 $ufField->website_type_id = $params['website_type_id'] ?? NULL;
2e74ff55
SP
181 if (is_null(CRM_Utils_Array::value('location_type_id', $params, ''))) {
182 // primary location type have NULL value in DB
183 $ufField->whereAdd("location_type_id IS NULL");
184 }
185 else {
9c1bc317 186 $ufField->location_type_id = $params['location_type_id'] ?? NULL;
2e74ff55 187 }
9c1bc317 188 $ufField->phone_type_id = $params['phone_type_id'] ?? NULL;
6a488035 189
b99bd217
ASB
190 if (!empty($params['id'])) {
191 $ufField->whereAdd("id <> " . $params['id']);
6a488035
TO
192 }
193
aa9732b9 194 return (bool) $ufField->find(TRUE);
6a488035
TO
195 }
196
f3c39657 197 /**
ad37ac8e 198 * Does profile consists of a multi-record custom field.
199 *
200 * @param int $gId
201 *
202 * @return bool
6a488035
TO
203 */
204 public static function checkMultiRecordFieldExists($gId) {
205 $queryString = "SELECT f.field_name
206 FROM civicrm_uf_field f, civicrm_uf_group g
207 WHERE f.uf_group_id = g.id
208 AND g.id = %1 AND f.field_name LIKE 'custom%'";
be2fb01f 209 $p = [1 => [$gId, 'Integer']];
6a488035 210 $dao = CRM_Core_DAO::executeQuery($queryString, $p);
be2fb01f 211 $customFieldIds = [];
6a488035
TO
212 $isMultiRecordFieldPresent = FALSE;
213 while ($dao->fetch()) {
214 if ($customId = CRM_Core_BAO_CustomField::getKeyID($dao->field_name)) {
215 if (is_numeric($customId)) {
216 $customFieldIds[] = $customId;
217 }
218 }
219 }
220
221 if (!empty($customFieldIds) && count($customFieldIds) == 1) {
222 $customFieldId = array_pop($customFieldIds);
223 $isMultiRecordFieldPresent = CRM_Core_BAO_CustomField::isMultiRecordField($customFieldId);
224 }
225 elseif (count($customFieldIds) > 1) {
226 $customFieldIds = implode(", ", $customFieldIds);
227 $queryString = "
228 SELECT cg.id as cgId
229 FROM civicrm_custom_group cg
cbb7c7e0 230 INNER JOIN civicrm_custom_field cf
6a488035
TO
231 ON cg.id = cf.custom_group_id
232WHERE cf.id IN (" . $customFieldIds . ") AND is_multiple = 1 LIMIT 0,1";
233
234 $dao = CRM_Core_DAO::executeQuery($queryString);
235 if ($dao->fetch()) {
236 $isMultiRecordFieldPresent = ($dao->cgId) ? $dao->cgId : FALSE;
237 }
238 }
239
240 return $isMultiRecordFieldPresent;
241 }
242
6a488035 243 /**
fe482240 244 * Automatically determine one weight and modify others.
6a488035 245 *
6a0b768e
TO
246 * @param array $params
247 * UFField record, e.g. with 'weight', 'uf_group_id', and 'field_id'.
6a488035
TO
248 * @return int
249 */
250 public static function autoWeight($params) {
251 // fix for CRM-316
252 $oldWeight = NULL;
253
aa9732b9
CW
254 if (!empty($params['field_id']) || !empty($params['id'])) {
255 $oldWeight = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFField', !empty($params['id']) ? $params['id'] : $params['field_id'], 'weight', 'id');
6a488035 256 }
aa9732b9 257 $fieldValues = ['uf_group_id' => !empty($params['uf_group_id']) ? $params['uf_group_id'] : $params['group_id']];
44cc86bd 258 return CRM_Utils_Weight::updateOtherWeights('CRM_Core_DAO_UFField', $oldWeight, CRM_Utils_Array::value('weight', $params, 0), $fieldValues);
6a488035
TO
259 }
260
261 /**
100fef9d 262 * Enable/disable profile field given a custom field id
6a488035 263 *
6a0b768e
TO
264 * @param int $customFieldId
265 * Custom field id.
266 * @param bool $is_active
267 * Set the is_active field.
6a488035 268 */
00be9182 269 public static function setUFField($customFieldId, $is_active) {
a43adbad 270 // Find the profile id given custom field.
6a488035
TO
271 $ufField = new CRM_Core_DAO_UFField();
272 $ufField->field_name = "custom_" . $customFieldId;
273
274 $ufField->find();
275 while ($ufField->fetch()) {
a43adbad 276 // Enable/ disable profile.
6a488035
TO
277 CRM_Core_BAO_UFField::setIsActive($ufField->id, $is_active);
278 }
279 }
280
281 /**
100fef9d 282 * Copy existing profile fields to
6a488035
TO
283 * new profile from the already built profile
284 *
6a0b768e
TO
285 * @param int $old_id
286 * From which we need to copy.
287 * @param bool $new_id
288 * In which to copy.
6a488035 289 */
00be9182 290 public static function copy($old_id, $new_id) {
6a488035
TO
291 $ufField = new CRM_Core_DAO_UFField();
292 $ufField->uf_group_id = $old_id;
293 $ufField->find();
294 while ($ufField->fetch()) {
295 //copy the field records as it is on new ufgroup id
296 $ufField->uf_group_id = $new_id;
297 $ufField->id = NULL;
298 $ufField->save();
299 }
300 }
301
302 /**
fe482240 303 * Delete profile field given a custom field.
6a488035 304 *
6a0b768e
TO
305 * @param int $customFieldId
306 * ID of the custom field to be deleted.
6a488035 307 */
00be9182 308 public static function delUFField($customFieldId) {
6a488035
TO
309 //find the profile id given custom field id
310 $ufField = new CRM_Core_DAO_UFField();
311 $ufField->field_name = "custom_" . $customFieldId;
312
313 $ufField->find();
314 while ($ufField->fetch()) {
315 //enable/ disable profile
316 CRM_Core_BAO_UFField::del($ufField->id);
317 }
318 }
319
320 /**
100fef9d 321 * Enable/disable profile field given a custom group id
6a488035 322 *
6a0b768e
TO
323 * @param int $customGroupId
324 * Custom group id.
325 * @param bool $is_active
326 * Value we want to set the is_active field.
6a488035 327 */
00be9182 328 public static function setUFFieldStatus($customGroupId, $is_active) {
6a488035
TO
329 //find the profile id given custom group id
330 $queryString = "SELECT civicrm_custom_field.id as custom_field_id
331 FROM civicrm_custom_field, civicrm_custom_group
332 WHERE civicrm_custom_field.custom_group_id = civicrm_custom_group.id
333 AND civicrm_custom_group.id = %1";
be2fb01f 334 $p = [1 => [$customGroupId, 'Integer']];
6a488035
TO
335 $dao = CRM_Core_DAO::executeQuery($queryString, $p);
336
337 while ($dao->fetch()) {
a43adbad 338 // Enable/ disable profile.
6a488035
TO
339 CRM_Core_BAO_UFField::setUFField($dao->custom_field_id, $is_active);
340 }
341 }
342
343 /**
fe482240 344 * Check the status of custom field used in uf fields.
6a488035 345 *
c490a46a 346 * @param int $UFFieldId
f3c39657 347 *
608e6658 348 * @return bool
a6c01b45 349 * false if custom field are disabled else true
6a488035 350 */
00be9182 351 public static function checkUFStatus($UFFieldId) {
6a488035
TO
352 $fieldName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFField', $UFFieldId, 'field_name');
353 // return if field is not a custom field
354 if (!$customFieldId = CRM_Core_BAO_CustomField::getKeyID($fieldName)) {
355 return TRUE;
356 }
357
358 $customField = new CRM_Core_DAO_CustomField();
359 $customField->id = $customFieldId;
360 // if uf field is custom field
361 if ($customField->find(TRUE)) {
362 if (!$customField->is_active) {
363 return FALSE;
364 }
365 else {
366 return TRUE;
367 }
368 }
369 }
370
44cc86bd 371 /**
100fef9d 372 * Find out whether given profile group using Activity
6a488035 373 * Profile fields with contact fields
ea3ddccf 374 *
375 * @param int $ufGroupId
376 *
377 * @return bool
6a488035 378 */
00be9182 379 public static function checkContactActivityProfileType($ufGroupId) {
6a488035
TO
380 $ufGroup = new CRM_Core_DAO_UFGroup();
381 $ufGroup->id = $ufGroupId;
382 $ufGroup->find(TRUE);
383
384 return self::checkContactActivityProfileTypeByGroupType($ufGroup->group_type);
385 }
386
387 /**
388 * FIXME say 10 ha
389 * @param $ufGroupType
390 * @return bool
391 */
392 public static function checkContactActivityProfileTypeByGroupType($ufGroupType) {
be2fb01f 393 $profileTypes = [];
6a488035
TO
394 if ($ufGroupType) {
395 $typeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $ufGroupType);
396 $profileTypes = explode(',', $typeParts[0]);
397 }
398
399 if (empty($profileTypes)) {
400 return FALSE;
401 }
be2fb01f 402 $components = ['Contribution', 'Participant', 'Membership'];
6a488035
TO
403 if (!in_array('Activity', $profileTypes)) {
404 return FALSE;
405 }
406 elseif (count($profileTypes) == 1) {
407 return FALSE;
408 }
409
410 if ($index = array_search('Contact', $profileTypes)) {
411 unset($profileTypes[$index]);
412 if (count($profileTypes) == 1) {
413 return TRUE;
414 }
415 }
416
be2fb01f 417 $contactTypes = ['Individual', 'Household', 'Organization'];
6a488035
TO
418 $subTypes = CRM_Contact_BAO_ContactType::subTypes();
419
420 $profileTypeComponent = array_intersect($components, $profileTypes);
421 if (!empty($profileTypeComponent) ||
422 count(array_intersect($contactTypes, $profileTypes)) > 1 ||
423 count(array_intersect($subTypes, $profileTypes)) > 1
424 ) {
425 return FALSE;
426 }
427
428 return TRUE;
429 }
430
f3c39657 431 /**
100fef9d 432 * Find out whether given profile group uses $required
44cc86bd 433 * and/or $optional profile types
cbb7c7e0 434 *
6a0b768e
TO
435 * @param int $ufGroupId
436 * Profile id.
437 * @param array $required
438 * Array of types those are required.
439 * @param array $optional
440 * Array of types those are optional.
6a488035 441 *
608e6658 442 * @return bool
6a488035 443 */
00be9182 444 public static function checkValidProfileType($ufGroupId, $required, $optional = NULL) {
6a488035 445 if (!is_array($required) || empty($required)) {
608e6658 446 return FALSE;
6a488035
TO
447 }
448
449 $ufGroup = new CRM_Core_DAO_UFGroup();
450 $ufGroup->id = $ufGroupId;
451 $ufGroup->find(TRUE);
452
be2fb01f 453 $profileTypes = [];
6a488035
TO
454 if ($ufGroup->group_type) {
455 $typeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $ufGroup->group_type);
456 $profileTypes = explode(',', $typeParts[0]);
457 }
458
459 if (empty($profileTypes)) {
460 return FALSE;
461 }
462
463 $valid = TRUE;
464 foreach ($required as $key => $val) {
465 if (!in_array($val, $profileTypes)) {
466 $valid = FALSE;
467 break;
468 }
469 }
470
471 if ($valid && is_array($optional)) {
472 foreach ($optional as $key => $val) {
473 if (in_array($val, $profileTypes)) {
474 $valid = TRUE;
475 break;
476 }
477 }
478 }
479
480 return $valid;
481 }
482
483 /**
c490a46a 484 * Check for mix profile fields (eg: individual + other contact types)
6a488035 485 *
c490a46a 486 * @param int $ufGroupId
f3c39657 487 *
72b3a70c
CW
488 * @return bool
489 * true for mix profile else false
6a488035 490 */
00be9182 491 public static function checkProfileType($ufGroupId) {
6a488035
TO
492 $ufGroup = new CRM_Core_DAO_UFGroup();
493 $ufGroup->id = $ufGroupId;
494 $ufGroup->find(TRUE);
495
be2fb01f 496 $profileTypes = [];
6a488035
TO
497 if ($ufGroup->group_type) {
498 $typeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $ufGroup->group_type);
499 $profileTypes = explode(',', $typeParts[0]);
500 }
501
502 //early return if new profile.
503 if (empty($profileTypes)) {
504 return FALSE;
505 }
506
507 //we need to unset Contact
508 if (count($profileTypes) > 1) {
509 $index = array_search('Contact', $profileTypes);
510 if ($index !== FALSE) {
511 unset($profileTypes[$index]);
512 }
513 }
514
515 // suppress any subtypes if present
516 CRM_Contact_BAO_ContactType::suppressSubTypes($profileTypes);
517
be2fb01f
CW
518 $contactTypes = ['Contact', 'Individual', 'Household', 'Organization'];
519 $components = ['Contribution', 'Participant', 'Membership', 'Activity'];
520 $fields = [];
6a488035
TO
521
522 // check for mix profile condition
523 if (count($profileTypes) > 1) {
524 //check the there are any components include in profile
525 foreach ($components as $value) {
526 if (in_array($value, $profileTypes)) {
527 return TRUE;
528 }
529 }
530 //check if there are more than one contact types included in profile
531 if (count($profileTypes) > 1) {
532 return TRUE;
533 }
534 }
535 elseif (count($profileTypes) == 1) {
536 // note for subtype case count would be zero
537 $profileTypes = array_values($profileTypes);
538 if (!in_array($profileTypes[0], $contactTypes)) {
539 return TRUE;
540 }
541 }
542
543 return FALSE;
544 }
545
546 /**
100fef9d 547 * Get the profile type (eg: individual/organization/household)
6a488035 548 *
6a0b768e
TO
549 * @param int $ufGroupId
550 * Uf group id.
551 * @param bool $returnMixType
552 * This is true, then field type of mix profile field is returned.
553 * @param bool $onlyPure
554 * True if only pure profiles are required.
f3c39657 555 *
556 * @param bool $skipComponentType
6a488035 557 *
a6c01b45
CW
558 * @return string
559 * profile group_type
6a488035 560 *
6a488035 561 */
00be9182 562 public static function getProfileType($ufGroupId, $returnMixType = TRUE, $onlyPure = FALSE, $skipComponentType = FALSE) {
6a488035
TO
563 $ufGroup = new CRM_Core_DAO_UFGroup();
564 $ufGroup->id = $ufGroupId;
565 $ufGroup->is_active = 1;
566
567 $ufGroup->find(TRUE);
568 return self::calculateProfileType($ufGroup->group_type, $returnMixType, $onlyPure, $skipComponentType);
569 }
570
571 /**
100fef9d 572 * Get the profile type (eg: individual/organization/household)
6a488035 573 *
c490a46a 574 * @param string $ufGroupType
6a0b768e
TO
575 * @param bool $returnMixType
576 * This is true, then field type of mix profile field is returned.
577 * @param bool $onlyPure
578 * True if only pure profiles are required.
f3c39657 579 * @param bool $skipComponentType
6a488035 580 *
608e6658 581 * @return string profile group_type
6a488035 582 *
6a488035 583 */
2aa397bc 584 public static function calculateProfileType($ufGroupType, $returnMixType = TRUE, $onlyPure = FALSE, $skipComponentType = FALSE) {
6a488035 585 // profile types
be2fb01f 586 $contactTypes = ['Contact', 'Individual', 'Household', 'Organization'];
6a488035 587 $subTypes = CRM_Contact_BAO_ContactType::subTypes();
be2fb01f 588 $components = ['Contribution', 'Participant', 'Membership', 'Activity'];
6a488035 589
be2fb01f 590 $profileTypes = [];
6a488035
TO
591 if ($ufGroupType) {
592 $typeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $ufGroupType);
593 $profileTypes = explode(',', $typeParts[0]);
594 }
595
596 if ($onlyPure) {
597 if (count($profileTypes) == 1) {
598 return $profileTypes[0];
599 }
600 else {
601 return NULL;
602 }
603 }
604
605 //we need to unset Contact
606 if (count($profileTypes) > 1) {
607 $index = array_search('Contact', $profileTypes);
608 if ($index !== FALSE) {
609 unset($profileTypes[$index]);
610 }
611 }
612
613 $profileType = $mixProfileType = NULL;
614
615 // this case handles pure profile
616 if (count($profileTypes) == 1) {
617 $profileType = array_pop($profileTypes);
618 }
619 else {
620 //check the there are any components include in profile
be2fb01f 621 $componentCount = [];
6a488035
TO
622 foreach ($components as $value) {
623 if (in_array($value, $profileTypes)) {
624 $componentCount[] = $value;
625 }
626 }
627
628 //check contact type included in profile
be2fb01f 629 $contactTypeCount = [];
6a488035
TO
630 foreach ($contactTypes as $value) {
631 if (in_array($value, $profileTypes)) {
632 $contactTypeCount[] = $value;
633 }
634 }
635 // subtype counter
be2fb01f 636 $subTypeCount = [];
6a488035
TO
637 foreach ($subTypes as $value) {
638 if (in_array($value, $profileTypes)) {
639 $subTypeCount[] = $value;
640 }
641 }
642 if (!$skipComponentType && count($componentCount) == 1) {
643 $profileType = $componentCount[0];
644 }
645 elseif (count($componentCount) > 1) {
646 $mixProfileType = $componentCount[1];
647 }
648 elseif (count($subTypeCount) == 1) {
649 $profileType = $subTypeCount[0];
650 }
651 elseif (count($contactTypeCount) == 1) {
652 $profileType = $contactTypeCount[0];
653 }
654 elseif (count($subTypeCount) > 1) {
655 // this is mix subtype profiles
656 $mixProfileType = $subTypeCount[1];
657 }
658 elseif (count($contactTypeCount) > 1) {
659 // this is mix contact profiles
660 $mixProfileType = $contactTypeCount[1];
661 }
662 }
663
664 if ($mixProfileType) {
665 if ($returnMixType) {
666 return $mixProfileType;
667 }
668 else {
669 return 'Mixed';
670 }
671 }
672 else {
673 return $profileType;
674 }
675 }
676
677 /**
100fef9d 678 * Check for mix profiles groups (eg: individual + other contact types)
6a488035 679 *
f3c39657 680 * @param $ctype
681 *
72b3a70c
CW
682 * @return bool
683 * true for mix profile group else false
6a488035 684 */
00be9182 685 public static function checkProfileGroupType($ctype) {
6a488035
TO
686 $ufGroup = new CRM_Core_DAO_UFGroup();
687
688 $query = "
689SELECT ufg.id as id
690 FROM civicrm_uf_group as ufg, civicrm_uf_join as ufj
691 WHERE ufg.id = ufj.uf_group_id
692 AND ufj.module = 'User Registration'
693 AND ufg.is_active = 1 ";
694
695 $ufGroup = CRM_Core_DAO::executeQuery($query);
696
be2fb01f
CW
697 $fields = [];
698 $validProfiles = ['Individual', 'Organization', 'Household', 'Contribution'];
6a488035
TO
699 while ($ufGroup->fetch()) {
700 $profileType = self::getProfileType($ufGroup->id);
701 if (in_array($profileType, $validProfiles)) {
702 continue;
703 }
704 elseif ($profileType) {
705 return FALSE;
706 }
707 }
708
709 return TRUE;
710 }
711
712 /**
c490a46a 713 * Check for searchable or in selector field for given profile.
6a488035 714 *
c490a46a 715 * @param int $profileID
f3c39657 716 *
608e6658 717 * @return bool
6a488035 718 */
00be9182 719 public static function checkSearchableORInSelector($profileID) {
6a488035
TO
720 $result = FALSE;
721 if (!$profileID) {
722 return $result;
723 }
724
725 $query = "
cbb7c7e0 726SELECT id
727 From civicrm_uf_field
6a488035
TO
728 WHERE (in_selector = 1 OR is_searchable = 1)
729 AND uf_group_id = {$profileID}";
730
731 $ufFields = CRM_Core_DAO::executeQuery($query);
732 while ($ufFields->fetch()) {
733 $result = TRUE;
734 break;
735 }
736
737 return $result;
738 }
739
740 /**
c490a46a 741 * Reset In selector and is searchable values for given $profileID.
6a488035 742 *
c490a46a 743 * @param int $profileID
6a488035 744 */
00be9182 745 public function resetInSelectorANDSearchable($profileID) {
6a488035
TO
746 if (!$profileID) {
747 return;
748 }
749 $query = "UPDATE civicrm_uf_field SET in_selector = 0, is_searchable = 0 WHERE uf_group_id = {$profileID}";
750 CRM_Core_DAO::executeQuery($query);
751 }
752
753 /**
754 * Add fields to $profileAddressFields as appropriate.
755 * profileAddressFields is assigned to the template to tell it
756 * what fields are in the profile address
757 * that potentially should be copied to the Billing fields
758 * we want to give precedence to
759 * 1) Billing &
760 * 2) then Primary designated as 'Primary
761 * 3) location_type is primary
762 * 4) if none of these apply then it just uses the first one
763 *
764 * as this will be used to
765 * transfer profile address data to billing fields
766 * http://issues.civicrm.org/jira/browse/CRM-5869
f3c39657 767 *
6a0b768e
TO
768 * @param string $key
769 * Field key - e.g. street_address-Primary, first_name.
770 * @param array $profileAddressFields
771 * Array of profile fields that relate to address fields.
772 * @param array $profileFilter
773 * Filter to apply to profile fields - expected usage is to only fill based on.
16b10e64 774 * the bottom profile per CRM-13726
1ad7255f 775 *
a6c01b45
CW
776 * @return bool
777 * Can the address block be hidden safe in the knowledge all fields are elsewhere collected (see CRM-15118)
6a488035 778 */
00be9182 779 public static function assignAddressField($key, &$profileAddressFields, $profileFilter) {
6a488035
TO
780 $billing_id = CRM_Core_BAO_LocationType::getBilling();
781 list($prefixName, $index) = CRM_Utils_System::explode('-', $key, 2);
782
fddfc93f 783 $profileFields = civicrm_api3('uf_field', 'get', array_merge($profileFilter,
be2fb01f 784 [
353ffa53
TO
785 'is_active' => 1,
786 'return' => 'field_name, is_required',
be2fb01f 787 'options' => [
353ffa53 788 'limit' => 0,
be2fb01f
CW
789 ],
790 ]
fddfc93f 791 ));
6a488035 792 //check for valid fields ( fields that are present in billing block )
be2fb01f 793 $validBillingFields = [
6a488035
TO
794 'first_name',
795 'middle_name',
796 'last_name',
797 'street_address',
798 'supplemental_address_1',
799 'city',
800 'state_province',
801 'postal_code',
21dfd5f5 802 'country',
be2fb01f
CW
803 ];
804 $requiredBillingFields = array_diff($validBillingFields, ['middle_name', 'supplemental_address_1']);
805 $validProfileFields = [];
806 $requiredProfileFields = [];
6a488035 807
7d613bb7 808 foreach ($profileFields['values'] as $field) {
22e263ad 809 if (in_array($field['field_name'], $validBillingFields)) {
7d613bb7
DG
810 $validProfileFields[] = $field['field_name'];
811 }
de6c59ca 812 if (!empty($field['is_required'])) {
1ad7255f
DG
813 $requiredProfileFields[] = $field['field_name'];
814 }
7d613bb7
DG
815 }
816
481a74f4 817 if (!in_array($prefixName, $validProfileFields)) {
608e6658 818 return FALSE;
6a488035
TO
819 }
820
821 if (!empty($index) && (
353ffa53 822 // it's empty so we set it OR
1817b503 823 !CRM_Utils_Array::value($prefixName, $profileAddressFields)
6a488035
TO
824 //we are dealing with billing id (precedence)
825 || $index == $billing_id
826 // we are dealing with primary & billing not set
827 || ($index == 'Primary' && $profileAddressFields[$prefixName] != $billing_id)
828 || ($index == CRM_Core_BAO_LocationType::getDefault()->id
353ffa53
TO
829 && $profileAddressFields[$prefixName] != $billing_id
830 && $profileAddressFields[$prefixName] != 'Primary'
831 )
6a488035 832 )
6a488035
TO
833 ) {
834 $profileAddressFields[$prefixName] = $index;
835 }
d75f2f47
EM
836
837 $potentiallyMissingRequiredFields = array_diff($requiredBillingFields, $requiredProfileFields);
353ffa53 838 CRM_Core_Resources::singleton()
be2fb01f 839 ->addSetting(['billing' => ['billingProfileIsHideable' => empty($potentiallyMissingRequiredFields)]]);
6a488035
TO
840 }
841
842 /**
fe482240 843 * Get a list of fields which can be added to profiles.
6a488035 844 *
353ffa53
TO
845 * @param int $gid : UF group ID
846 * @param array $defaults : Form defaults
6a488035 847 * @return array, multidimensional; e.g. $result['FieldGroup']['field_name']['label']
6a488035 848 */
be2fb01f
CW
849 public static function getAvailableFields($gid = NULL, $defaults = []) {
850 $fields = [
851 'Contact' => [],
6a488035
TO
852 'Individual' => CRM_Contact_BAO_Contact::importableFields('Individual', FALSE, FALSE, TRUE, TRUE, TRUE),
853 'Household' => CRM_Contact_BAO_Contact::importableFields('Household', FALSE, FALSE, TRUE, TRUE, TRUE),
854 'Organization' => CRM_Contact_BAO_Contact::importableFields('Organization', FALSE, FALSE, TRUE, TRUE, TRUE),
be2fb01f 855 ];
6a488035 856
61750d67
DS
857 // include hook injected fields
858 $fields['Contact'] = array_merge($fields['Contact'], CRM_Contact_BAO_Query_Hook::singleton()->getFields());
859
6a488035 860 // add current employer for individuals
be2fb01f 861 $fields['Individual']['current_employer'] = [
6a488035
TO
862 'name' => 'organization_name',
863 'title' => ts('Current Employer'),
be2fb01f 864 ];
6a488035
TO
865
866 $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
867 'address_options', TRUE, NULL, TRUE
868 );
869
dcd27695 870 if (empty($addressOptions['county'])) {
6a488035
TO
871 unset($fields['Individual']['county'], $fields['Household']['county'], $fields['Organization']['county']);
872 }
873
874 // break out common contact fields array CRM-3037.
875 // from a UI perspective this makes very little sense
876 foreach ($fields['Individual'] as $key => $value) {
877 if (!empty($fields['Household'][$key]) && !empty($fields['Organization'][$key])) {
878 $fields['Contact'][$key] = $value;
879 unset($fields['Individual'][$key], $fields['Household'][$key], $fields['Organization'][$key]);
880 }
881 }
882
883 // Internal field not exposed to forms
884 unset($fields['Contact']['contact_type']);
deb17853 885 unset($fields['Contact']['master_id']);
6a488035
TO
886
887 // convert phone extension in to psedo-field phone + phone extension
888 //unset extension
889 unset($fields['Contact']['phone_ext']);
890 //add psedo field
be2fb01f 891 $fields['Contact']['phone_and_ext'] = [
6a488035
TO
892 'name' => 'phone_and_ext',
893 'title' => ts('Phone and Extension'),
894 'hasLocationType' => 1,
be2fb01f 895 ];
6a488035
TO
896
897 // include Subtypes For Profile
898 $subTypes = CRM_Contact_BAO_ContactType::subTypeInfo();
899 foreach ($subTypes as $name => $val) {
900 //custom fields for sub type
3550c9f6 901 $subTypeFields = CRM_Core_BAO_CustomField::getFieldsForImport($name, FALSE, FALSE, FALSE, TRUE, TRUE);
6a488035
TO
902 if (array_key_exists($val['parent'], $fields)) {
903 $fields[$name] = $fields[$val['parent']] + $subTypeFields;
904 }
905 else {
906 $fields[$name] = $subTypeFields;
907 }
908 }
909
910 if (CRM_Core_Permission::access('CiviContribute')) {
911 $contribFields = CRM_Contribute_BAO_Contribution::getContributionFields(FALSE);
912 if (!empty($contribFields)) {
913 unset($contribFields['is_test']);
914 unset($contribFields['is_pay_later']);
915 unset($contribFields['contribution_id']);
be2fb01f 916 $contribFields['contribution_note'] = [
6a488035
TO
917 'name' => 'contribution_note',
918 'title' => ts('Contribution Note'),
be2fb01f 919 ];
752ddf35 920 $fields['Contribution'] = array_merge($contribFields, self::getContribBatchEntryFields());
6a488035
TO
921 }
922 }
923
924 if (CRM_Core_Permission::access('CiviEvent')) {
925 $participantFields = CRM_Event_BAO_Query::getParticipantFields();
926 if ($participantFields) {
927 // Remove fields not supported by profiles
928 CRM_Utils_Array::remove($participantFields,
929 'external_identifier',
930 'event_id',
931 'participant_contact_id',
932 'participant_role_id',
933 'participant_status_id',
934 'participant_is_test',
935 'participant_fee_level',
936 'participant_id',
937 'participant_is_pay_later',
938 'participant_campaign'
939 );
940 if (isset($participantFields['participant_campaign_id'])) {
941 $participantFields['participant_campaign_id']['title'] = ts('Campaign');
942 }
943 $fields['Participant'] = $participantFields;
944 }
945 }
946
947 if (CRM_Core_Permission::access('CiviMember')) {
948 $membershipFields = CRM_Member_BAO_Membership::getMembershipFields();
949 // Remove fields not supported by profiles
950 CRM_Utils_Array::remove($membershipFields,
951 'membership_id',
952 'membership_type_id',
953 'member_is_test',
954 'is_override',
8f67d99a 955 'member_is_override',
e136f704 956 'status_override_end_date',
6a488035
TO
957 'status_id',
958 'member_is_pay_later'
959 );
960 if ($gid && CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $gid, 'name') == 'membership_batch_entry') {
961 $fields['Membership'] = array_merge($membershipFields, self::getMemberBatchEntryFields());
962 }
963 else {
964 $fields['Membership'] = $membershipFields;
965 }
966 }
967
1cb28d5d 968 if (CRM_Core_Permission::access('CiviCase')) {
969 $caseFields = CRM_Case_BAO_Query::getFields(TRUE);
970 $caseFields = array_merge($caseFields, CRM_Core_BAO_CustomField::getFieldsForImport('Case'));
971 if ($caseFields) {
972 // Remove fields not supported by profiles
973 CRM_Utils_Array::remove($caseFields,
974 'case_id',
975 'case_type',
1cb28d5d 976 'case_role',
1cb28d5d 977 'case_deleted'
978 );
979 }
980 $fields['Case'] = $caseFields;
981 }
982
6a488035
TO
983 $activityFields = CRM_Activity_BAO_Activity::getProfileFields();
984 if ($activityFields) {
985 // campaign related fields.
986 if (isset($activityFields['activity_campaign_id'])) {
987 $activityFields['activity_campaign_id']['title'] = ts('Campaign');
988 }
989 $fields['Activity'] = $activityFields;
990 }
991
be2fb01f 992 $fields['Formatting']['format_free_html_' . rand(1000, 9999)] = [
6a488035
TO
993 'name' => 'free_html',
994 'import' => FALSE,
995 'export' => FALSE,
996 'title' => 'Free HTML',
be2fb01f 997 ];
6a488035
TO
998
999 // Sort by title
1000 foreach ($fields as &$values) {
1001 $values = CRM_Utils_Array::crmArraySortByField($values, 'title');
1002 }
1003
1004 //group selected and unwanted fields list
be2fb01f
CW
1005 $ufFields = $gid ? CRM_Core_BAO_UFGroup::getFields($gid, FALSE, NULL, NULL, NULL, TRUE, NULL, TRUE) : [];
1006 $groupFieldList = array_merge($ufFields, [
6a488035
TO
1007 'note',
1008 'email_greeting_custom',
1009 'postal_greeting_custom',
1010 'addressee_custom',
1011 'id',
be2fb01f 1012 ]);
6a488035
TO
1013 //unset selected fields
1014 foreach ($groupFieldList as $key => $value) {
608e6658 1015 if (is_int($key)) {
6a488035
TO
1016 unset($fields['Individual'][$value], $fields['Household'][$value], $fields['Organization'][$value]);
1017 continue;
1018 }
1019 if (!empty($defaults['field_name'])
1020 && $defaults['field_name']['0'] == $value['field_type']
1021 && $defaults['field_name']['1'] == $key
1022 ) {
1023 continue;
1024 }
1025 unset($fields[$value['field_type']][$key]);
1026 }
1027
92f06cdd 1028 // Allow extensions to alter the array of entity => fields permissible in a CiviCRM Profile.
9fdf2f17 1029 CRM_Utils_Hook::alterUFFields($fields);
6a488035
TO
1030 return $fields;
1031 }
1032
1033 /**
fe482240 1034 * Get a list of fields which can be added to profiles.
6a488035 1035 *
f3c39657 1036 * @param bool $force
1037 *
ffb9cdec
CW
1038 * @return array
1039 * e.g. $result['field_name']['label']
6a488035
TO
1040 */
1041 public static function getAvailableFieldsFlat($force = FALSE) {
ffb9cdec
CW
1042 if (!isset(Civi::$statics['UFFieldsFlat']) || $force) {
1043 Civi::$statics['UFFieldsFlat'] = [];
1044 foreach (self::getAvailableFields() as $fieldType => $fields) {
1045 foreach ($fields as $fieldName => $field) {
1046 if (!isset(Civi::$statics['UFFieldsFlat'][$fieldName])) {
1047 $field['field_type'] = $fieldType;
1048 Civi::$statics['UFFieldsFlat'][$fieldName] = $field;
6a488035
TO
1049 }
1050 }
1051 }
1052 }
ffb9cdec
CW
1053 return Civi::$statics['UFFieldsFlat'];
1054 }
1055
1056 /**
1057 * Get a list of fields which can be added to profiles in the format [name => title]
1058 *
1059 * @return array
1060 */
1061 public static function getAvailableFieldTitles() {
1062 $fields = self::getAvailableFieldsFlat();
75c4fcec 1063 $fields['formatting'] = ['title' => ts('Formatting')];
ffb9cdec 1064 return CRM_Utils_Array::collect('title', $fields);
6a488035
TO
1065 }
1066
1067 /**
fe482240 1068 * Determine whether the given field_name is valid.
6a488035
TO
1069 *
1070 * @param string $fieldName
1071 * @return bool
1072 */
00be9182 1073 public static function isValidFieldName($fieldName) {
6a488035
TO
1074 $availableFields = CRM_Core_BAO_UFField::getAvailableFieldsFlat();
1075 return isset($availableFields[$fieldName]);
1076 }
1077
b5c2afd0
EM
1078 /**
1079 * @return array|null
1080 */
00be9182 1081 public static function getContribBatchEntryFields() {
6a488035 1082 if (self::$_contriBatchEntryFields === NULL) {
be2fb01f
CW
1083 self::$_contriBatchEntryFields = [
1084 'send_receipt' => [
6a488035
TO
1085 'name' => 'send_receipt',
1086 'title' => ts('Send Receipt'),
be2fb01f
CW
1087 ],
1088 'soft_credit' => [
6a488035
TO
1089 'name' => 'soft_credit',
1090 'title' => ts('Soft Credit'),
be2fb01f
CW
1091 ],
1092 'soft_credit_type' => [
752ddf35
N
1093 'name' => 'soft_credit_type',
1094 'title' => ts('Soft Credit Type'),
be2fb01f
CW
1095 ],
1096 'product_name' => [
6a488035
TO
1097 'name' => 'product_name',
1098 'title' => ts('Premiums'),
be2fb01f
CW
1099 ],
1100 'contribution_note' => [
6a488035
TO
1101 'name' => 'contribution_note',
1102 'title' => ts('Contribution Note'),
be2fb01f
CW
1103 ],
1104 'contribution_soft_credit_pcp_id' => [
03ad81ae
AH
1105 'name' => 'contribution_soft_credit_pcp_id',
1106 'title' => ts('Personal Campaign Page'),
be2fb01f
CW
1107 ],
1108 ];
6a488035
TO
1109 }
1110 return self::$_contriBatchEntryFields;
1111 }
1112
b5c2afd0
EM
1113 /**
1114 * @return array|null
1115 */
6a488035
TO
1116 public static function getMemberBatchEntryFields() {
1117 if (self::$_memberBatchEntryFields === NULL) {
be2fb01f
CW
1118 self::$_memberBatchEntryFields = [
1119 'send_receipt' => [
6a488035
TO
1120 'name' => 'send_receipt',
1121 'title' => ts('Send Receipt'),
be2fb01f
CW
1122 ],
1123 'soft_credit' => [
6a488035
TO
1124 'name' => 'soft_credit',
1125 'title' => ts('Soft Credit'),
be2fb01f
CW
1126 ],
1127 'product_name' => [
6a488035
TO
1128 'name' => 'product_name',
1129 'title' => ts('Premiums'),
be2fb01f
CW
1130 ],
1131 'financial_type' => [
6a488035
TO
1132 'name' => 'financial_type',
1133 'title' => ts('Financial Type'),
be2fb01f
CW
1134 ],
1135 'total_amount' => [
6a488035
TO
1136 'name' => 'total_amount',
1137 'title' => ts('Total Amount'),
be2fb01f
CW
1138 ],
1139 'receive_date' => [
6a488035 1140 'name' => 'receive_date',
7bc6b5bb 1141 'title' => ts('Date Received'),
be2fb01f
CW
1142 ],
1143 'payment_instrument' => [
6a488035 1144 'name' => 'payment_instrument',
536f0e02 1145 'title' => ts('Payment Method'),
be2fb01f
CW
1146 ],
1147 'contribution_status_id' => [
6a488035
TO
1148 'name' => 'contribution_status_id',
1149 'title' => ts('Contribution Status'),
be2fb01f
CW
1150 ],
1151 'trxn_id' => [
47087bdc 1152 'name' => 'contribution_trxn_id',
1153 'title' => ts('Contribution Transaction ID'),
be2fb01f
CW
1154 ],
1155 ];
6a488035
TO
1156 }
1157 return self::$_memberBatchEntryFields;
1158 }
96025800 1159
6a488035 1160}