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