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