Merge pull request #17296 from agh1/fa-accordions
[civicrm-core.git] / CRM / Core / BAO / UFGroup.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 * UF group BAO class.
20 */
21 class CRM_Core_BAO_UFGroup extends CRM_Core_DAO_UFGroup {
22
23 const PUBLIC_VISIBILITY = 1,
24 ADMIN_VISIBILITY = 2,
25 LISTINGS_VISIBILITY = 4;
26
27 /**
28 * Cache the match clause used in this transaction.
29 *
30 * @var string
31 */
32 public static $_matchFields = NULL;
33
34 /**
35 * Fetch object based on array of properties.
36 *
37 * @param array $params
38 * (reference) an assoc array of name/value pairs.
39 * @param array $defaults
40 * (reference) an assoc array to hold the flattened values.
41 *
42 * @return object
43 * CRM_Core_DAO_UFGroup object
44 */
45 public static function retrieve(&$params, &$defaults) {
46 return CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_UFGroup', $params, $defaults);
47 }
48
49 /**
50 * Retrieve the first non-generic contact type
51 *
52 * @param int $id
53 * Id of uf_group.
54 *
55 * @return string
56 * contact type
57 */
58 public static function getContactType($id) {
59
60 $validTypes = array_filter(array_keys(CRM_Core_SelectValues::contactType()));
61 $validSubTypes = CRM_Contact_BAO_ContactType::subTypeInfo();
62
63 $typesParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $id, 'group_type'));
64 $types = explode(',', $typesParts[0]);
65
66 $cType = NULL;
67 foreach ($types as $type) {
68 if (in_array($type, $validTypes)) {
69 $cType = $type;
70 }
71 elseif (array_key_exists($type, $validSubTypes)) {
72 $cType = $validSubTypes[$type]['parent'] ?? NULL;
73 }
74 if ($cType) {
75 break;
76 }
77 }
78
79 return $cType;
80 }
81
82 /**
83 * Get the form title.
84 *
85 * @param int $id
86 * Id of uf_form.
87 *
88 * @return string
89 * title
90 *
91 */
92 public static function getTitle($id) {
93 return CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $id, 'title');
94 }
95
96 /**
97 * Update the is_active flag in the db.
98 *
99 * @param int $id
100 * Id of the database record.
101 * @param bool $is_active
102 * Value we want to set the is_active field.
103 *
104 * @return bool
105 * true if we found and updated the object, else false
106 */
107 public static function setIsActive($id, $is_active) {
108 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_UFGroup', $id, 'is_active', $is_active);
109 }
110
111 /**
112 * Get all the registration fields.
113 *
114 * @param int $action
115 * What action are we doing.
116 * @param int $mode
117 * Mode.
118 *
119 * @param string $ctype
120 *
121 * @return array
122 * the fields that are needed for registration
123 *
124 * @throws \Exception
125 */
126 public static function getRegistrationFields($action, $mode, $ctype = NULL) {
127 if ($mode & CRM_Profile_Form::MODE_REGISTER) {
128 $ufGroups = CRM_Core_BAO_UFGroup::getModuleUFGroup('User Registration');
129 }
130 else {
131 $ufGroups = CRM_Core_BAO_UFGroup::getModuleUFGroup('Profile');
132 }
133
134 if (!is_array($ufGroups)) {
135 return FALSE;
136 }
137
138 $fields = [];
139
140 foreach ($ufGroups as $id => $title) {
141 if ($ctype) {
142 $fieldType = CRM_Core_BAO_UFField::getProfileType($id);
143 if (($fieldType != 'Contact') &&
144 ($fieldType != $ctype) &&
145 !CRM_Contact_BAO_ContactType::isExtendsContactType($fieldType, $ctype)
146 ) {
147 continue;
148 }
149 if (CRM_Contact_BAO_ContactType::isaSubType($fieldType)) {
150 $profileSubType = $fieldType;
151 }
152 }
153
154 $subset = self::getFields($id, TRUE, $action,
155 NULL, NULL, FALSE, NULL, TRUE, $ctype
156 );
157
158 // we do not allow duplicates. the first field is the winner
159 foreach ($subset as $name => $field) {
160 if (empty($fields[$name])) {
161 $fields[$name] = $field;
162 }
163 }
164 }
165
166 return $fields;
167 }
168
169 /**
170 * Get all the listing fields.
171 *
172 * @param int $action
173 * What action are we doing.
174 * @param int $visibility
175 * Visibility of fields we are interested in.
176 * @param bool $considerSelector
177 * Whether to consider the in_selector parameter.
178 * @param array $ufGroupIds
179 * @param bool $searchable
180 *
181 * @param null $restrict
182 * @param bool $skipPermission
183 * @param int $permissionType
184 *
185 * @return array
186 * the fields that are listings related
187 *
188 * @throws \Exception
189 */
190 public static function getListingFields(
191 $action,
192 $visibility,
193 $considerSelector = FALSE,
194 $ufGroupIds = NULL,
195 $searchable = NULL,
196 $restrict = NULL,
197 $skipPermission = FALSE,
198 $permissionType = CRM_Core_Permission::SEARCH
199 ) {
200 if ($ufGroupIds) {
201 $subset = self::getFields($ufGroupIds, FALSE, $action,
202 $visibility, $searchable,
203 FALSE, $restrict,
204 $skipPermission,
205 NULL,
206 $permissionType
207 );
208 if ($considerSelector) {
209 // drop the fields not meant for the selector
210 foreach ($subset as $name => $field) {
211 if (!$field['in_selector']) {
212 unset($subset[$name]);
213 }
214 }
215 }
216 $fields = $subset;
217 }
218 else {
219 $ufGroups = CRM_Core_PseudoConstant::get('CRM_Core_DAO_UFField', 'uf_group_id');
220
221 $fields = [];
222 foreach ($ufGroups as $id => $title) {
223 $subset = self::getFields($id, FALSE, $action,
224 $visibility, $searchable,
225 FALSE, $restrict,
226 $skipPermission,
227 NULL,
228 $permissionType
229 );
230 if ($considerSelector) {
231 // drop the fields not meant for the selector
232 foreach ($subset as $name => $field) {
233 if (!$field['in_selector']) {
234 unset($subset[$name]);
235 }
236 }
237 }
238 $fields = array_merge($fields, $subset);
239 }
240 }
241 return $fields;
242 }
243
244 /**
245 * Get all the fields that belong to the group with the name title,
246 * and format for use with buildProfile. This is the SQL analog of
247 * formatUFFields().
248 *
249 * @param mix $id
250 * The id of the UF group or ids of ufgroup.
251 * @param bool|int $register are we interested in registration fields
252 * @param int $action
253 * What action are we doing.
254 * @param int $visibility
255 * Visibility of fields we are interested in.
256 * @param $searchable
257 * @param bool $showAll
258 * @param string $restrict
259 * Should we restrict based on a specified profile type.
260 * @param bool $skipPermission
261 * @param null $ctype
262 * @param int $permissionType
263 * @param string $orderBy
264 * @param null $orderProfiles
265 *
266 * @param bool $eventProfile
267 *
268 * @return array
269 * The fields that belong to this ufgroup(s)
270 *
271 * @throws \CRM_Core_Exception
272 */
273 public static function getFields(
274 $id,
275 $register = FALSE,
276 $action = NULL,
277 $visibility = NULL,
278 $searchable = NULL,
279 $showAll = FALSE,
280 $restrict = NULL,
281 $skipPermission = FALSE,
282 $ctype = NULL,
283 $permissionType = CRM_Core_Permission::CREATE,
284 $orderBy = 'field_name',
285 $orderProfiles = NULL,
286 $eventProfile = FALSE
287 ) {
288 if (!is_array($id)) {
289 $id = CRM_Utils_Type::escape($id, 'Positive');
290 $profileIds = [$id];
291 }
292 else {
293 $profileIds = $id;
294 }
295
296 $gids = implode(',', $profileIds);
297 $params = [];
298 if ($restrict) {
299 $query = "SELECT g.* from civicrm_uf_group g
300 LEFT JOIN civicrm_uf_join j ON (j.uf_group_id = g.id)
301 WHERE g.id IN ( {$gids} )
302 AND ((j.uf_group_id IN ( {$gids} ) AND j.module = %1) OR g.is_reserved = 1 )
303 ";
304 $params = [1 => [$restrict, 'String']];
305 }
306 else {
307 $query = "SELECT g.* from civicrm_uf_group g WHERE g.id IN ( {$gids} ) ";
308 }
309
310 if (!$showAll) {
311 $query .= " AND g.is_active = 1";
312 }
313
314 $checkPermission = [
315 [
316 'administer CiviCRM',
317 'manage event profiles',
318 ],
319 ];
320 if ($eventProfile && CRM_Core_Permission::check($checkPermission)) {
321 $skipPermission = TRUE;
322 }
323
324 // add permissioning for profiles only if not registration
325 if (!$skipPermission) {
326 $permissionClause = CRM_Core_Permission::ufGroupClause($permissionType, 'g.');
327 $query .= " AND $permissionClause ";
328 }
329
330 if ($orderProfiles and count($profileIds) > 1) {
331 $query .= " ORDER BY FIELD( g.id, {$gids} )";
332 }
333 $group = CRM_Core_DAO::executeQuery($query, $params);
334 $fields = [];
335 $validGroup = FALSE;
336
337 while ($group->fetch()) {
338 $validGroup = TRUE;
339 $query = self::createUFFieldQuery($group->id, $searchable, $showAll, $visibility, $orderBy);
340 $field = CRM_Core_DAO::executeQuery($query);
341
342 $importableFields = self::getProfileFieldMetadata($showAll);
343 list($customFields, $addressCustomFields) = self::getCustomFields($ctype);
344
345 while ($field->fetch()) {
346 list($name, $formattedField) = self::formatUFField($group, $field, $customFields, $addressCustomFields, $importableFields, $permissionType);
347 if ($formattedField !== NULL) {
348 $fields[$name] = $formattedField;
349 }
350 }
351 }
352
353 if (empty($fields) && !$validGroup) {
354 throw new CRM_Core_Exception(ts('The requested Profile (gid=%1) is disabled OR it is not configured to be used for \'Profile\' listings in its Settings OR there is no Profile with that ID OR you do not have permission to access this profile. Please contact the site administrator if you need assistance.',
355 [1 => implode(',', $profileIds)]
356 ));
357 }
358 else {
359 self::reformatProfileFields($fields);
360 }
361
362 return $fields;
363 }
364
365 /**
366 * Format a list of UFFields for use with buildProfile. This is the in-memory analog
367 * of getFields().
368 *
369 * @param array $groupArr
370 * (mimic CRM_UF_DAO_UFGroup).
371 * @param array $fieldArrs
372 * List of fields (each mimics CRM_UF_DAO_UFField).
373 * @param bool $visibility
374 * Visibility of fields we are interested in.
375 * @param bool $searchable
376 * @param bool $showAll
377 * @param null $ctype
378 * @param int $permissionType
379 *
380 * @return array
381 * @see self::getFields
382 */
383 public static function formatUFFields(
384 $groupArr,
385 $fieldArrs,
386 $visibility = NULL,
387 $searchable = NULL,
388 $showAll = FALSE,
389 $ctype = NULL,
390 $permissionType = CRM_Core_Permission::CREATE
391 ) {
392 // $group = new CRM_Core_DAO_UFGroup();
393 // $group->copyValues($groupArr); // no... converts string('') to string('null')
394 $group = (object) $groupArr;
395
396 // Refactoring note: The $fieldArrs here may be slightly different than the $ufFields
397 // used by calculateGroupType, but I don't think the missing fields matter, and -- if
398 // they did -- the obvious fix would produce mutual recursion.
399 $ufGroupType = self::_calculateGroupType($fieldArrs);
400 $profileType = CRM_Core_BAO_UFField::calculateProfileType(implode(',', $ufGroupType));
401 $contactActivityProfile = CRM_Core_BAO_UFField::checkContactActivityProfileTypeByGroupType(implode(',', $ufGroupType));
402 $importableFields = self::getImportableFields($showAll, $profileType, $contactActivityProfile);
403 list($customFields, $addressCustomFields) = self::getCustomFields($ctype);
404
405 $formattedFields = [];
406 foreach ($fieldArrs as $fieldArr) {
407 $field = (object) $fieldArr;
408 if (!self::filterUFField($field, $searchable, $showAll, $visibility)) {
409 continue;
410 }
411
412 list($name, $formattedField) = self::formatUFField($group, $field, $customFields, $addressCustomFields, $importableFields, $permissionType);
413 if ($formattedField !== NULL) {
414 $formattedFields[$name] = $formattedField;
415 }
416 }
417 return $formattedFields;
418 }
419
420 /**
421 * Prepare a field for rendering with CRM_Core_BAO_UFGroup::buildProfile.
422 *
423 * @param CRM_Core_DAO_UFGroup|CRM_Core_DAO $group
424 * @param CRM_Core_DAO_UFField|CRM_Core_DAO $field
425 * @param array $customFields
426 * @param array $addressCustomFields
427 * @param array $importableFields
428 * @param int $permissionType
429 * Eg CRM_Core_Permission::CREATE.
430 *
431 * @return array
432 */
433 protected static function formatUFField(
434 $group,
435 $field,
436 $customFields,
437 $addressCustomFields,
438 $importableFields,
439 $permissionType = CRM_Core_Permission::CREATE
440 ) {
441 $name = $field->field_name;
442 $title = $field->label;
443
444 $addressCustom = FALSE;
445 if (in_array($permissionType, [CRM_Core_Permission::CREATE, CRM_Core_Permission::EDIT]) &&
446 in_array($field->field_name, array_keys($addressCustomFields))
447 ) {
448 $addressCustom = TRUE;
449 $name = "address_{$name}";
450 }
451 if ($field->field_name == 'url') {
452 $name .= "-{$field->website_type_id}";
453 }
454 elseif (!empty($field->location_type_id)) {
455 $name .= "-{$field->location_type_id}";
456 }
457 else {
458 $locationFields = self::getLocationFields();
459 if (in_array($field->field_name, $locationFields) || $addressCustom) {
460 $name .= '-Primary';
461 }
462 }
463
464 if (isset($field->phone_type_id)) {
465 $name .= "-{$field->phone_type_id}";
466 }
467 $fieldMetaData = CRM_Utils_Array::value($name, $importableFields, ($importableFields[$field->field_name] ?? []));
468
469 // No lie: this is bizarre; why do we need to mix so many UFGroup properties into UFFields?
470 // I guess to make field self sufficient with all the required data and avoid additional calls
471 $formattedField = [
472 'name' => $name,
473 'groupTitle' => $group->title,
474 'groupName' => $group->name,
475 'groupDisplayTitle' => (!empty($group->frontend_title)) ? $group->frontend_title : $group->title,
476 'groupHelpPre' => empty($group->help_pre) ? '' : $group->help_pre,
477 'groupHelpPost' => empty($group->help_post) ? '' : $group->help_post,
478 'title' => $title,
479 'where' => CRM_Utils_Array::value('where', CRM_Utils_Array::value($field->field_name, $importableFields)),
480 'attributes' => CRM_Core_DAO::makeAttribute(CRM_Utils_Array::value($field->field_name, $importableFields)),
481 'is_required' => $field->is_required,
482 'is_view' => $field->is_view,
483 'help_pre' => $field->help_pre,
484 'help_post' => $field->help_post,
485 'visibility' => $field->visibility,
486 'in_selector' => $field->in_selector,
487 'rule' => CRM_Utils_Array::value('rule', CRM_Utils_Array::value($field->field_name, $importableFields)),
488 'location_type_id' => $field->location_type_id ?? NULL,
489 'website_type_id' => $field->website_type_id ?? NULL,
490 'phone_type_id' => $field->phone_type_id ?? NULL,
491 'group_id' => $group->id,
492 'add_to_group_id' => $group->add_to_group_id ?? NULL,
493 'add_captcha' => $group->add_captcha ?? NULL,
494 'field_type' => $field->field_type,
495 'field_id' => $field->id,
496 'pseudoconstant' => CRM_Utils_Array::value(
497 'pseudoconstant',
498 CRM_Utils_Array::value($field->field_name, $importableFields)
499 ),
500 // obsolete this when we remove the name / dbName discrepancy with gender/suffix/prefix
501 'dbName' => CRM_Utils_Array::value(
502 'dbName',
503 CRM_Utils_Array::value($field->field_name, $importableFields)
504 ),
505 'skipDisplay' => 0,
506 'data_type' => CRM_Utils_Type::getDataTypeFromFieldMetadata($fieldMetaData),
507 'bao' => $fieldMetaData['bao'] ?? NULL,
508 ];
509
510 $formattedField = CRM_Utils_Date::addDateMetadataToField($fieldMetaData, $formattedField);
511
512 //adding custom field property
513 if (substr($field->field_name, 0, 6) == 'custom' ||
514 substr($field->field_name, 0, 14) === 'address_custom'
515 ) {
516 // if field is not present in customFields, that means the user
517 // DOES NOT HAVE permission to access that field
518 if (array_key_exists($field->field_name, $customFields)) {
519 $formattedField['serialize'] = !empty($customFields[$field->field_name]['serialize']);
520 $formattedField['is_search_range'] = $customFields[$field->field_name]['is_search_range'];
521 // fix for CRM-1994
522 $formattedField['options_per_line'] = $customFields[$field->field_name]['options_per_line'];
523 $formattedField['html_type'] = $customFields[$field->field_name]['html_type'];
524
525 if (CRM_Utils_Array::value('html_type', $formattedField) == 'Select Date') {
526 $formattedField['date_format'] = $customFields[$field->field_name]['date_format'];
527 $formattedField['time_format'] = $customFields[$field->field_name]['time_format'];
528 $formattedField['is_datetime_field'] = TRUE;
529 $formattedField['smarty_view_format'] = CRM_Utils_Date::getDateFieldViewFormat($formattedField['date_format']);
530 }
531
532 $formattedField['is_multi_summary'] = $field->is_multi_summary;
533 return [$name, $formattedField];
534 }
535 else {
536 $formattedField = NULL;
537 return [$name, $formattedField];
538 }
539 }
540 return [$name, $formattedField];
541 }
542
543 /**
544 * Create a query to find all visible UFFields in a UFGroup.
545 *
546 * This is the SQL-variant of checkUFFieldDisplayable().
547 *
548 * @param int $groupId
549 * @param bool $searchable
550 * @param bool $showAll
551 * @param int $visibility
552 * @param string $orderBy
553 * Comma-delimited list of SQL columns.
554 *
555 * @return string
556 */
557 protected static function createUFFieldQuery($groupId, $searchable, $showAll, $visibility, $orderBy) {
558 $where = " WHERE uf_group_id = {$groupId}";
559
560 if ($searchable) {
561 $where .= " AND is_searchable = 1";
562 }
563
564 if (!$showAll) {
565 $where .= " AND is_active = 1";
566 }
567
568 if ($visibility) {
569 $clause = [];
570 if ($visibility & self::PUBLIC_VISIBILITY) {
571 $clause[] = 'visibility = "Public Pages"';
572 }
573 if ($visibility & self::ADMIN_VISIBILITY) {
574 $clause[] = 'visibility = "User and User Admin Only"';
575 }
576 if ($visibility & self::LISTINGS_VISIBILITY) {
577 $clause[] = 'visibility = "Public Pages and Listings"';
578 }
579 if (!empty($clause)) {
580 $where .= ' AND ( ' . implode(' OR ', $clause) . ' ) ';
581 }
582 }
583
584 $query = "SELECT * FROM civicrm_uf_field $where ORDER BY weight";
585 if ($orderBy) {
586 $query .= ", " . $orderBy;
587 return $query;
588 }
589 return $query;
590 }
591
592 /**
593 * Create a query to find all visible UFFields in a UFGroup.
594 *
595 * This is the PHP in-memory variant of createUFFieldQuery().
596 *
597 * @param CRM_Core_DAO_UFField|CRM_Core_DAO $field
598 * @param bool $searchable
599 * @param bool $showAll
600 * @param int $visibility
601 *
602 * @return bool
603 * TRUE if field is displayable
604 */
605 protected static function filterUFField($field, $searchable, $showAll, $visibility) {
606 if ($searchable && $field->is_searchable != 1) {
607 return FALSE;
608 }
609
610 if (!$showAll && $field->is_active != 1) {
611 return FALSE;
612 }
613
614 if ($visibility) {
615 $allowedVisibilities = [];
616 if ($visibility & self::PUBLIC_VISIBILITY) {
617 $allowedVisibilities[] = 'Public Pages';
618 }
619 if ($visibility & self::ADMIN_VISIBILITY) {
620 $allowedVisibilities[] = 'User and User Admin Only';
621 }
622 if ($visibility & self::LISTINGS_VISIBILITY) {
623 $allowedVisibilities[] = 'Public Pages and Listings';
624 }
625 // !empty($allowedVisibilities) seems silly to me, but it is equivalent to the pre-existing SQL
626 if (!empty($allowedVisibilities) && !in_array($field->visibility, $allowedVisibilities)) {
627 return FALSE;
628 }
629 }
630
631 return TRUE;
632 }
633
634 /**
635 * Get a list of filtered field metadata.
636 *
637 * @param $showAll
638 * @param $profileType
639 * @param $contactActivityProfile
640 * @param bool $filterMode
641 * Filter mode means you are using importable fields for filtering rather than just getting metadata.
642 * With filter mode = FALSE BOTH activity fields and component fields are returned.
643 * I can't see why you would ever want to use this function in filter mode as the component fields are
644 * still unfiltered. However, I feel scared enough to leave it as it is. I have marked this function as
645 * deprecated and am recommending the wrapper 'getProfileFieldMetadata' in order to try to
646 * send this confusion to history.
647 *
648 * @return array
649 * @deprecated use getProfileFieldMetadata
650 *
651 */
652 protected static function getImportableFields($showAll, $profileType, $contactActivityProfile, $filterMode = TRUE) {
653 if (!$showAll) {
654 $importableFields = CRM_Contact_BAO_Contact::importableFields('All', FALSE, FALSE, FALSE, TRUE, TRUE);
655 }
656 else {
657 $importableFields = CRM_Contact_BAO_Contact::importableFields('All', FALSE, TRUE, FALSE, TRUE, TRUE);
658 }
659
660 $activityFields = CRM_Activity_BAO_Activity::getProfileFields();
661 $componentFields = CRM_Core_Component::getQueryFields();
662 if ($filterMode == TRUE) {
663 if ($profileType == 'Activity' || $contactActivityProfile) {
664 $importableFields = array_merge($importableFields, $activityFields);
665 }
666 else {
667 $importableFields = array_merge($importableFields, $componentFields);
668 }
669 }
670 else {
671 $importableFields = array_merge($importableFields, $activityFields, $componentFields);
672 }
673
674 $importableFields['group']['title'] = ts('Group(s)');
675 $importableFields['group']['where'] = NULL;
676 $importableFields['tag']['title'] = ts('Tag(s)');
677 $importableFields['tag']['where'] = NULL;
678 return $importableFields;
679 }
680
681 /**
682 * Get the metadata for all potential profile fields.
683 *
684 * @param bool $isIncludeInactive
685 * Should disabled fields be included.
686 *
687 * @return array
688 * Field metadata for all fields that might potentially be in a profile.
689 */
690 protected static function getProfileFieldMetadata($isIncludeInactive) {
691 return self::getImportableFields($isIncludeInactive, NULL, NULL, NULL, TRUE);
692 }
693
694 /**
695 * Get the fields relating to locations.
696 *
697 * @return array
698 */
699 public static function getLocationFields() {
700 static $locationFields = [
701 'street_address',
702 'supplemental_address_1',
703 'supplemental_address_2',
704 'supplemental_address_3',
705 'city',
706 'postal_code',
707 'postal_code_suffix',
708 'geo_code_1',
709 'geo_code_2',
710 'state_province',
711 'country',
712 'county',
713 'phone',
714 'phone_and_ext',
715 'email',
716 'im',
717 'address_name',
718 'phone_ext',
719 ];
720 return $locationFields;
721 }
722
723 /**
724 * @param $ctype
725 *
726 * @return mixed
727 */
728 protected static function getCustomFields($ctype) {
729 $cacheKey = 'uf_group_custom_fields_' . $ctype;
730 if (!Civi::cache('metadata')->has($cacheKey)) {
731 $customFields = CRM_Core_BAO_CustomField::getFieldsForImport($ctype, FALSE, FALSE, FALSE, TRUE, TRUE);
732
733 // hack to add custom data for components
734 $components = ['Contribution', 'Participant', 'Membership', 'Activity', 'Case'];
735 foreach ($components as $value) {
736 $customFields = array_merge($customFields, CRM_Core_BAO_CustomField::getFieldsForImport($value));
737 }
738 $addressCustomFields = CRM_Core_BAO_CustomField::getFieldsForImport('Address');
739 $customFields = array_merge($customFields, $addressCustomFields);
740 Civi::cache('metadata')->set($cacheKey, [$customFields, $addressCustomFields]);
741 }
742 return Civi::cache('metadata')->get($cacheKey);
743 }
744
745 /**
746 * Check the data validity.
747 *
748 * @param int $userID
749 * The user id that we are actually editing.
750 * @param string $name
751 * The machine-name of the group we are interested in.
752 * @param bool $register
753 * @param int $action
754 * The action of the form.
755 *
756 * @pram boolean $register is this the registrtion form
757 * @return bool
758 * true if form is valid
759 */
760 public static function isValid($userID, $name, $register = FALSE, $action = NULL) {
761 if ($register) {
762 $controller = new CRM_Core_Controller_Simple('CRM_Profile_Form_Dynamic',
763 ts('Dynamic Form Creator'),
764 $action
765 );
766 $controller->set('id', $userID);
767 $controller->set('register', 1);
768 $controller->process();
769 return $controller->validate();
770 }
771 else {
772 // make sure we have a valid group
773 $group = new CRM_Core_DAO_UFGroup();
774
775 $group->name = $name;
776
777 if ($group->find(TRUE) && $userID) {
778 $controller = new CRM_Core_Controller_Simple('CRM_Profile_Form_Dynamic', ts('Dynamic Form Creator'), $action);
779 $controller->set('gid', $group->id);
780 $controller->set('id', $userID);
781 $controller->set('register', 0);
782 $controller->process();
783 return $controller->validate();
784 }
785 return TRUE;
786 }
787 }
788
789 /**
790 * Get the html for the form that represents this particular group.
791 *
792 * @param int $userID
793 * The user id that we are actually editing.
794 * @param string $title
795 * The title of the group we are interested in.
796 * @param int $action
797 * The action of the form.
798 * @param bool $register
799 * Is this the registration form.
800 * @param bool $reset
801 * Should we reset the form?.
802 * @param int $profileID
803 * Do we have the profile ID?.
804 *
805 * @param bool $doNotProcess
806 * @param null $ctype
807 *
808 * @return string
809 * the html for the form on success, otherwise empty string
810 */
811 public static function getEditHTML(
812 $userID,
813 $title,
814 $action = NULL,
815 $register = FALSE,
816 $reset = FALSE,
817 $profileID = NULL,
818 $doNotProcess = FALSE,
819 $ctype = NULL
820 ) {
821
822 if ($register) {
823 $controller = new CRM_Core_Controller_Simple('CRM_Profile_Form_Dynamic',
824 ts('Dynamic Form Creator'),
825 $action
826 );
827 if ($reset || $doNotProcess) {
828 // hack to make sure we do not process this form
829 $oldQFDefault = CRM_Utils_Array::value('_qf_default',
830 $_POST
831 );
832 unset($_POST['_qf_default']);
833 unset($_REQUEST['_qf_default']);
834 if ($reset) {
835 $controller->reset();
836 }
837 }
838
839 $controller->set('id', $userID);
840 $controller->set('register', 1);
841 $controller->set('skipPermission', 1);
842 $controller->set('ctype', $ctype);
843 $controller->process();
844 if ($doNotProcess || !empty($_POST)) {
845 $controller->validate();
846 }
847 $controller->setEmbedded(TRUE);
848
849 //CRM-5839 - though we want to process form, get the control back.
850 $controller->setSkipRedirection(!$doNotProcess);
851
852 $controller->run();
853
854 // we are done processing so restore the POST/REQUEST vars
855 if (($reset || $doNotProcess) && $oldQFDefault) {
856 $_POST['_qf_default'] = $_REQUEST['_qf_default'] = $oldQFDefault;
857 }
858
859 $template = CRM_Core_Smarty::singleton();
860
861 // Hide CRM error messages if they are displayed using drupal form_set_error.
862 if (!empty($_POST)) {
863 $template->assign('suppressForm', TRUE);
864 }
865
866 return trim($template->fetch('CRM/Profile/Form/Dynamic.tpl'));
867 }
868 else {
869 if (!$profileID) {
870 // make sure we have a valid group
871 $group = new CRM_Core_DAO_UFGroup();
872
873 $group->title = $title;
874
875 if ($group->find(TRUE)) {
876 $profileID = $group->id;
877 }
878 }
879
880 if ($profileID) {
881 // make sure profileID and ctype match if ctype exists
882 if ($ctype) {
883 $profileType = CRM_Core_BAO_UFField::getProfileType($profileID);
884 if (CRM_Contact_BAO_ContactType::isaSubType($profileType)) {
885 $profileType = CRM_Contact_BAO_ContactType::getBasicType($profileType);
886 }
887
888 if (($profileType != 'Contact') && ($profileType != $ctype)) {
889 return NULL;
890 }
891 }
892
893 $controller = new CRM_Core_Controller_Simple('CRM_Profile_Form_Dynamic',
894 ts('Dynamic Form Creator'),
895 $action
896 );
897 if ($reset) {
898 $controller->reset();
899 }
900 $controller->set('gid', $profileID);
901 $controller->set('id', $userID);
902 $controller->set('register', 0);
903 $controller->set('skipPermission', 1);
904 if ($ctype) {
905 $controller->set('ctype', $ctype);
906 }
907 $controller->process();
908 $controller->setEmbedded(TRUE);
909
910 //CRM-5846 - give the control back to drupal.
911 $controller->setSkipRedirection(!$doNotProcess);
912 $controller->run();
913
914 $template = CRM_Core_Smarty::singleton();
915
916 // Hide CRM error messages if they are displayed using drupal form_set_error.
917 if (!empty($_POST) && CRM_Core_Config::singleton()->userFramework == 'Drupal') {
918 if (arg(0) == 'user' || (arg(0) == 'admin' && arg(1) == 'people')) {
919 $template->assign('suppressForm', TRUE);
920 }
921 }
922
923 $templateFile = "CRM/Profile/Form/{$profileID}/Dynamic.tpl";
924 if (!$template->template_exists($templateFile)) {
925 $templateFile = 'CRM/Profile/Form/Dynamic.tpl';
926 }
927 return trim($template->fetch($templateFile));
928 }
929 else {
930 $userEmail = CRM_Contact_BAO_Contact_Location::getEmailDetails($userID);
931
932 // if post not empty then only proceed
933 if (!empty($_POST)) {
934 // get the new email
935 $config = CRM_Core_Config::singleton();
936 $email = $_POST['mail'] ?? NULL;
937
938 if (CRM_Utils_Rule::email($email) && ($email != $userEmail[1])) {
939 CRM_Core_BAO_UFMatch::updateContactEmail($userID, $email);
940 }
941 }
942 }
943 }
944 return '';
945 }
946
947 /**
948 * Given a contact id and a field set, return the values from the db.
949 *
950 * @param int $cid
951 * @param array $fields
952 * The profile fields of interest.
953 * @param array $values
954 * The values for the above fields.
955 * @param bool $searchable
956 * Searchable or not.
957 * @param array $componentWhere
958 * Component condition.
959 * @param bool $absolute
960 * Return urls in absolute form (useful when sending an email).
961 * @param null $additionalWhereClause
962 *
963 * @return null|array
964 */
965 public static function getValues(
966 $cid, &$fields, &$values,
967 $searchable = TRUE, $componentWhere = NULL,
968 $absolute = FALSE, $additionalWhereClause = NULL
969 ) {
970 if (empty($cid) && empty($componentWhere)) {
971 return NULL;
972 }
973
974 // get the contact details (hier)
975 $returnProperties = CRM_Contact_BAO_Contact::makeHierReturnProperties($fields);
976 $params = $cid ? [['contact_id', '=', $cid, 0, 0]] : [];
977
978 // add conditions specified by components. eg partcipant_id etc
979 if (!empty($componentWhere)) {
980 $params = array_merge($params, $componentWhere);
981 }
982
983 $query = new CRM_Contact_BAO_Query($params, $returnProperties, $fields);
984
985 $details = $query->searchQuery(0, 0, NULL, FALSE, FALSE,
986 FALSE, FALSE, FALSE, $additionalWhereClause);
987 while ($details->fetch()) {
988 if (!$details) {
989 return;
990 }
991 }
992 $query->convertToPseudoNames($details);
993
994 $locationTypes = CRM_Core_BAO_Address::buildOptions('location_type_id', 'validate');
995 $imProviders = CRM_Core_DAO_IM::buildOptions('provider_id');
996 $websiteTypes = CRM_Core_DAO_Website::buildOptions('website_type_id');
997
998 $multipleFields = ['url'];
999
1000 //start of code to set the default values
1001 foreach ($fields as $name => $field) {
1002 // fix for CRM-3962
1003 if ($name == 'id') {
1004 $name = 'contact_id';
1005 }
1006
1007 // skip fields that should not be displayed separately
1008 if (!empty($field['skipDisplay'])) {
1009 continue;
1010 }
1011
1012 // Create a unique, non-empty index for each field.
1013 $index = $field['title'];
1014 if ($index === '') {
1015 $index = ' ';
1016 }
1017 while (array_key_exists($index, $values)) {
1018 $index .= ' ';
1019 }
1020
1021 $params[$index] = $values[$index] = '';
1022 $customFieldName = NULL;
1023 // hack for CRM-665
1024 if (isset($details->$name) || $name == 'group' || $name == 'tag') {
1025 // to handle gender / suffix / prefix
1026 if (in_array(substr($name, 0, -3), ['gender', 'prefix', 'suffix'])) {
1027 $params[$index] = $details->$name;
1028 $values[$index] = $details->$name;
1029 }
1030 elseif (in_array($name, CRM_Contact_BAO_Contact::$_greetingTypes)) {
1031 $dname = $name . '_display';
1032 $values[$index] = $details->$dname;
1033 $name = $name . '_id';
1034 $params[$index] = $details->$name;
1035 }
1036 elseif (in_array($name, [
1037 'state_province',
1038 'country',
1039 'county',
1040 ])) {
1041 $values[$index] = $details->$name;
1042 $idx = $name . '_id';
1043 $params[$index] = $details->$idx;
1044 }
1045 elseif ($name === 'preferred_language') {
1046 $params[$index] = $details->$name;
1047 $values[$index] = CRM_Core_PseudoConstant::getLabel('CRM_Contact_DAO_Contact', 'preferred_language', $details->$name);
1048 }
1049 elseif ($name == 'group') {
1050 $groups = CRM_Contact_BAO_GroupContact::getContactGroup($cid, 'Added', NULL, FALSE, TRUE);
1051 $title = $ids = [];
1052
1053 foreach ($groups as $g) {
1054 // CRM-8362: User and User Admin visibility groups should be included in display if user has
1055 // VIEW permission on that group
1056 $groupPerm = CRM_Contact_BAO_Group::checkPermission($g['group_id'], TRUE);
1057
1058 if ($g['visibility'] != 'User and User Admin Only' ||
1059 CRM_Utils_Array::key(CRM_Core_Permission::VIEW, $groupPerm)
1060 ) {
1061 $title[] = $g['title'];
1062 if ($g['visibility'] == 'Public Pages') {
1063 $ids[] = $g['group_id'];
1064 }
1065 }
1066 }
1067 $values[$index] = implode(', ', $title);
1068 $params[$index] = implode(',', $ids);
1069 }
1070 elseif ($name == 'tag') {
1071 $entityTags = CRM_Core_BAO_EntityTag::getTag($cid);
1072 $allTags = CRM_Core_PseudoConstant::get('CRM_Core_DAO_EntityTag', 'tag_id', ['onlyActive' => FALSE]);
1073 $title = [];
1074 foreach ($entityTags as $tagId) {
1075 $title[] = $allTags[$tagId];
1076 }
1077 $values[$index] = implode(', ', $title);
1078 $params[$index] = implode(',', $entityTags);
1079 }
1080 elseif ($name == 'activity_status_id') {
1081 $activityStatus = CRM_Core_PseudoConstant::activityStatus();
1082 $values[$index] = $activityStatus[$details->$name];
1083 $params[$index] = $details->$name;
1084 }
1085 elseif ($name == 'activity_date_time') {
1086 $values[$index] = CRM_Utils_Date::customFormat($details->$name);
1087 $params[$index] = $details->$name;
1088 }
1089 elseif ($name == 'contact_sub_type') {
1090 $contactSubTypeNames = explode(CRM_Core_DAO::VALUE_SEPARATOR, $details->$name);
1091 if (!empty($contactSubTypeNames)) {
1092 $contactSubTypeLabels = [];
1093 // get all contact subtypes
1094 $allContactSubTypes = CRM_Contact_BAO_ContactType::subTypeInfo();
1095 // build contact subtype labels array
1096 foreach ($contactSubTypeNames as $cstName) {
1097 if ($cstName) {
1098 $contactSubTypeLabels[] = $allContactSubTypes[$cstName]['label'];
1099 }
1100 }
1101 $values[$index] = implode(',', $contactSubTypeLabels);
1102 }
1103
1104 $params[$index] = $details->$name;
1105 }
1106 else {
1107 if (substr($name, 0, 7) === 'do_not_' || substr($name, 0, 3) === 'is_') {
1108 if ($details->$name) {
1109 $values[$index] = '[ x ]';
1110 }
1111 }
1112 else {
1113 if ($cfID = CRM_Core_BAO_CustomField::getKeyID($name)) {
1114 $htmlType = $field['html_type'];
1115
1116 // field_type is only set when we are retrieving profile values
1117 // when sending email, we call the same function to get custom field
1118 // values etc, i.e. emulating a profile
1119 $fieldType = $field['field_type'] ?? NULL;
1120
1121 if ($htmlType == 'File') {
1122 $entityId = $cid;
1123 if (!$cid &&
1124 $fieldType == 'Activity' && !empty($componentWhere[0][2])
1125 ) {
1126 $entityId = $componentWhere[0][2];
1127 }
1128
1129 $fileURL = CRM_Core_BAO_CustomField::getFileURL($entityId,
1130 $cfID,
1131 NULL,
1132 $absolute,
1133 $additionalWhereClause
1134 );
1135 $params[$index] = $values[$index] = $fileURL['file_url'];
1136 }
1137 else {
1138 $customVal = NULL;
1139 if (isset($dao) && property_exists($dao, 'data_type') &&
1140 ($dao->data_type == 'Int' ||
1141 $dao->data_type == 'Boolean'
1142 )
1143 ) {
1144 $customVal = (int ) ($details->{$name});
1145 }
1146 elseif (isset($dao) && property_exists($dao, 'data_type')
1147 && $dao->data_type == 'Float'
1148 ) {
1149 $customVal = (float ) ($details->{$name});
1150 }
1151 elseif (!CRM_Utils_System::isNull(explode(CRM_Core_DAO::VALUE_SEPARATOR,
1152 $details->{$name}
1153 ))
1154 ) {
1155 $customVal = $details->{$name};
1156 }
1157
1158 //CRM-4582
1159 if (CRM_Utils_System::isNull($customVal)) {
1160 continue;
1161 }
1162
1163 $params[$index] = $customVal;
1164 $values[$index] = CRM_Core_BAO_CustomField::displayValue($customVal, $cfID);
1165 if ($field['data_type'] == 'ContactReference') {
1166 $params[$index] = $values[$index];
1167 }
1168 if (CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField',
1169 $cfID, 'is_search_range'
1170 )
1171 ) {
1172 $customFieldName = "{$name}_from";
1173 }
1174 }
1175 }
1176 elseif ($name == 'image_URL') {
1177 list($width, $height) = getimagesize(CRM_Utils_String::unstupifyUrl($details->$name));
1178 list($thumbWidth, $thumbHeight) = CRM_Contact_BAO_Contact::getThumbSize($width, $height);
1179
1180 $image_URL = '<img src="' . $details->$name . '" height= ' . $thumbHeight . ' width= ' . $thumbWidth . ' />';
1181 $values[$index] = "<a href='#' onclick='contactImagePopUp(\"{$details->$name}\", {$width}, {$height});'>{$image_URL}</a>";
1182 }
1183 elseif (in_array($name, [
1184 'birth_date',
1185 'deceased_date',
1186 ])) {
1187 // @todo this set should be determined from metadata, not hard-coded.
1188 $values[$index] = CRM_Utils_Date::customFormat($details->$name);
1189 $params[$index] = CRM_Utils_Date::isoToMysql($details->$name);
1190 }
1191 else {
1192 $dao = '';
1193 if ($index == 'Campaign') {
1194 $dao = 'CRM_Campaign_DAO_Campaign';
1195 }
1196 elseif ($index == 'Contribution Page') {
1197 $dao = 'CRM_Contribute_DAO_ContributionPage';
1198 }
1199 if ($dao) {
1200 $value = CRM_Core_DAO::getFieldValue($dao, $details->$name, 'title');
1201 }
1202 else {
1203 $value = $details->$name;
1204 }
1205 $values[$index] = $value;
1206 }
1207 }
1208 }
1209 }
1210 elseif (strpos($name, '-') !== FALSE) {
1211 list($fieldName, $id, $type) = CRM_Utils_System::explode('-', $name, 3);
1212
1213 if (!in_array($fieldName, $multipleFields)) {
1214 if ($id == 'Primary') {
1215 // fix for CRM-1543
1216 // not sure why we'd every use Primary location type id
1217 // we need to fix the source if we are using it
1218 // $locationTypeName = CRM_Contact_BAO_Contact::getPrimaryLocationType( $cid );
1219 $locationTypeName = 1;
1220 }
1221 else {
1222 $locationTypeName = $locationTypes[$id] ?? NULL;
1223 }
1224
1225 if (!$locationTypeName) {
1226 continue;
1227 }
1228
1229 $detailName = "{$locationTypeName}-{$fieldName}";
1230 $detailName = str_replace(' ', '_', $detailName);
1231
1232 if (in_array($fieldName, [
1233 'phone',
1234 'im',
1235 'email',
1236 'openid',
1237 ])) {
1238 if ($type) {
1239 $detailName .= "-{$type}";
1240 }
1241 }
1242
1243 if (in_array($fieldName, [
1244 'state_province',
1245 'country',
1246 'county',
1247 ])) {
1248 $values[$index] = $details->$detailName;
1249 $idx = $detailName . '_id';
1250 $params[$index] = $details->$idx;
1251 }
1252 elseif ($fieldName == 'im') {
1253 $providerId = $detailName . '-provider_id';
1254 if (isset($imProviders[$details->$providerId])) {
1255 $values[$index] = $details->$detailName . " (" . $imProviders[$details->$providerId] . ")";
1256 }
1257 else {
1258 $values[$index] = $details->$detailName;
1259 }
1260 $params[$index] = $details->$detailName;
1261 }
1262 elseif ($fieldName == 'phone') {
1263 $phoneExtField = str_replace('phone', 'phone_ext', $detailName);
1264 if (isset($details->$phoneExtField)) {
1265 $values[$index] = $details->$detailName . " (" . $details->$phoneExtField . ")";
1266 }
1267 else {
1268 $values[$index] = $details->$detailName;
1269 }
1270 $params[$index] = $details->$detailName;
1271 }
1272 else {
1273 $values[$index] = $params[$index] = $details->$detailName;
1274 }
1275 }
1276 else {
1277 $detailName = "website-{$id}-{$fieldName}";
1278 $url = CRM_Utils_System::fixURL($details->$detailName);
1279 if ($details->$detailName) {
1280 $websiteTypeId = "website-{$id}-website_type_id";
1281 $websiteType = $websiteTypes[$details->$websiteTypeId];
1282 $values[$index] = "<a href=\"$url\">{$details->$detailName} ( {$websiteType} )</a>";
1283 }
1284 else {
1285 $values[$index] = '';
1286 }
1287 }
1288 }
1289
1290 if ((CRM_Utils_Array::value('visibility', $field) == 'Public Pages and Listings') &&
1291 CRM_Core_Permission::check('profile listings and forms')
1292 ) {
1293
1294 if (CRM_Utils_System::isNull($params[$index])) {
1295 $params[$index] = $values[$index];
1296 }
1297 if (!isset($params[$index])) {
1298 continue;
1299 }
1300 if (!$customFieldName) {
1301 $fieldName = $field['name'];
1302 }
1303 else {
1304 $fieldName = $customFieldName;
1305 }
1306
1307 $url = NULL;
1308 if (CRM_Core_BAO_CustomField::getKeyID($field['name'])) {
1309 $htmlType = $field['html_type'];
1310 if ($htmlType == 'Link') {
1311 $url = $params[$index];
1312 }
1313 elseif (in_array($htmlType, [
1314 'CheckBox',
1315 'Multi-Select',
1316 'Multi-Select State/Province',
1317 'Multi-Select Country',
1318 ])) {
1319 $valSeperator = CRM_Core_DAO::VALUE_SEPARATOR;
1320 $selectedOptions = explode($valSeperator, $params[$index]);
1321
1322 foreach ($selectedOptions as $key => $multiOption) {
1323 if ($multiOption) {
1324 $url[] = CRM_Utils_System::url('civicrm/profile',
1325 'reset=1&force=1&gid=' . $field['group_id'] . '&' .
1326 urlencode($fieldName) .
1327 '=' .
1328 urlencode($multiOption)
1329 );
1330 }
1331 }
1332 }
1333 else {
1334 $url = CRM_Utils_System::url('civicrm/profile',
1335 'reset=1&force=1&gid=' . $field['group_id'] . '&' .
1336 urlencode($fieldName) .
1337 '=' .
1338 urlencode($params[$index])
1339 );
1340 }
1341 }
1342 else {
1343 $url = CRM_Utils_System::url('civicrm/profile',
1344 'reset=1&force=1&gid=' . $field['group_id'] . '&' .
1345 urlencode($fieldName) .
1346 '=' .
1347 urlencode($params[$index])
1348 );
1349 }
1350
1351 if ($url &&
1352 !empty($values[$index]) &&
1353 $searchable
1354 ) {
1355
1356 if (is_array($url) && !empty($url)) {
1357 $links = [];
1358 $eachMultiValue = explode(', ', $values[$index]);
1359 foreach ($eachMultiValue as $key => $valueLabel) {
1360 $links[] = '<a href="' . $url[$key] . '">' . $valueLabel . '</a>';
1361 }
1362 $values[$index] = implode(', ', $links);
1363 }
1364 else {
1365 $values[$index] = '<a href="' . $url . '">' . $values[$index] . '</a>';
1366 }
1367 }
1368 }
1369 }
1370 }
1371
1372 /**
1373 * Check if profile Group used by any module.
1374 *
1375 * @param int $id
1376 * Profile Id.
1377 *
1378 * @return bool
1379 *
1380 */
1381 public static function usedByModule($id) {
1382 //check whether this group is used by any module(check uf join records)
1383 $sql = "SELECT id
1384 FROM civicrm_uf_join
1385 WHERE civicrm_uf_join.uf_group_id=$id";
1386
1387 $dao = new CRM_Core_DAO();
1388 $dao->query($sql);
1389 if ($dao->fetch()) {
1390 return TRUE;
1391 }
1392 else {
1393 return FALSE;
1394 }
1395 }
1396
1397 /**
1398 * Delete the profile Group.
1399 *
1400 * @param int $id
1401 * Profile Id.
1402 *
1403 * @return bool
1404 *
1405 */
1406 public static function del($id) {
1407 //check whether this group contains any profile fields
1408 $profileField = new CRM_Core_DAO_UFField();
1409 $profileField->uf_group_id = $id;
1410 $profileField->find();
1411 while ($profileField->fetch()) {
1412 CRM_Core_BAO_UFField::del($profileField->id);
1413 }
1414
1415 //delete records from uf join table
1416 $ufJoin = new CRM_Core_DAO_UFJoin();
1417 $ufJoin->uf_group_id = $id;
1418 $ufJoin->delete();
1419
1420 //delete profile group
1421 $group = new CRM_Core_DAO_UFGroup();
1422 $group->id = $id;
1423 $group->delete();
1424 return 1;
1425 }
1426
1427 /**
1428 * Add the UF Group.
1429 *
1430 * @param array $params
1431 * Reference array contains the values submitted by the form.
1432 * @param array $ids
1433 * Reference array contains the id.
1434 *
1435 *
1436 * @return object
1437 */
1438 public static function add(&$params, $ids = []) {
1439 $fields = [
1440 'is_active',
1441 'add_captcha',
1442 'is_map',
1443 'is_update_dupe',
1444 'is_edit_link',
1445 'is_uf_link',
1446 'is_cms_user',
1447 ];
1448 foreach ($fields as $field) {
1449 $params[$field] = CRM_Utils_Array::value($field, $params, FALSE);
1450 }
1451
1452 $params['limit_listings_group_id'] = $params['group'] ?? NULL;
1453 $params['add_to_group_id'] = $params['add_contact_to_group'] ?? NULL;
1454
1455 //CRM-15427
1456 if (!empty($params['group_type']) && is_array($params['group_type'])) {
1457 $params['group_type'] = implode(',', $params['group_type']);
1458 }
1459 $ufGroup = new CRM_Core_DAO_UFGroup();
1460 $ufGroup->copyValues($params);
1461
1462 $ufGroupID = CRM_Utils_Array::value('ufgroup', $ids, CRM_Utils_Array::value('id', $params));
1463 if (!$ufGroupID && empty($params['name'])) {
1464 $ufGroup->name = CRM_Utils_String::munge($ufGroup->title, '_', 56);
1465 }
1466 $ufGroup->id = $ufGroupID;
1467
1468 $ufGroup->save();
1469
1470 if (!$ufGroupID && empty($params['name'])) {
1471 $ufGroup->name = $ufGroup->name . "_{$ufGroup->id}";
1472 $ufGroup->save();
1473 }
1474
1475 return $ufGroup;
1476 }
1477
1478 /**
1479 * Make uf join entries for an uf group.
1480 *
1481 * @param int $weight
1482 * @param array $groupTypes
1483 * An assoc array of name/value pairs.
1484 * @param int $ufGroupId
1485 * Ufgroup id.
1486 */
1487 public static function createUFJoin($weight, $groupTypes, $ufGroupId) {
1488
1489 // get ufjoin records for uf group
1490 $ufGroupRecord = CRM_Core_BAO_UFGroup::getUFJoinRecord($ufGroupId);
1491
1492 // get the list of all ufgroup types
1493 $allUFGroupType = CRM_Core_SelectValues::ufGroupTypes();
1494
1495 // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input
1496 if (!is_array($groupTypes)) {
1497 $groupTypes = [];
1498 }
1499
1500 // this fix is done to prevent warning generated by array_key_exits incase of empty array is given as input
1501 if (!is_array($ufGroupRecord)) {
1502 $ufGroupRecord = [];
1503 }
1504
1505 // check which values has to be inserted/deleted for contact
1506 $menuRebuild = FALSE;
1507 foreach ($allUFGroupType as $key => $value) {
1508 $joinParams = [];
1509 $joinParams['uf_group_id'] = $ufGroupId;
1510 $joinParams['module'] = $key;
1511 if ($key === 'User Account') {
1512 $menuRebuild = TRUE;
1513 }
1514 if (array_key_exists($key, $groupTypes) && !in_array($key, $ufGroupRecord)) {
1515 // insert a new record
1516 CRM_Core_BAO_UFGroup::addUFJoin($joinParams);
1517 }
1518 elseif (!array_key_exists($key, $groupTypes) && in_array($key, $ufGroupRecord)) {
1519 // delete a record for existing ufgroup
1520 CRM_Core_BAO_UFGroup::delUFJoin($joinParams);
1521 }
1522 }
1523
1524 //update the weight
1525 $query = '
1526 UPDATE civicrm_uf_join
1527 SET weight = %1
1528 WHERE uf_group_id = %2
1529 AND ( entity_id IS NULL OR entity_id <= 0 )
1530 ';
1531 $p = [
1532 1 => [$weight, 'Integer'],
1533 2 => [$ufGroupId, 'Integer'],
1534 ];
1535 CRM_Core_DAO::executeQuery($query, $p);
1536
1537 // Do a menu rebuild, so it gets all the new menu entries for user account
1538 if ($menuRebuild) {
1539 $config = CRM_Core_Config::singleton();
1540 $config->userSystem->updateCategories();
1541 }
1542 }
1543
1544 /**
1545 * Get the UF Join records for an ufgroup id.
1546 *
1547 * @param int $ufGroupId
1548 * Uf group id.
1549 * @param int $displayName
1550 * If set return display name in array.
1551 * @param int $status
1552 * If set return module other than default modules (User Account/User registration/Profile).
1553 *
1554 * @return array
1555 *
1556 */
1557 public static function getUFJoinRecord($ufGroupId = NULL, $displayName = NULL, $status = NULL) {
1558 if ($displayName) {
1559 $UFGroupType = [];
1560 $UFGroupType = CRM_Core_SelectValues::ufGroupTypes();
1561 }
1562
1563 $ufJoin = [];
1564 $dao = new CRM_Core_DAO_UFJoin();
1565
1566 if ($ufGroupId) {
1567 $dao->uf_group_id = $ufGroupId;
1568 }
1569
1570 $dao->find();
1571 $ufJoin = [];
1572
1573 while ($dao->fetch()) {
1574 if (!$displayName) {
1575 $ufJoin[$dao->id] = $dao->module;
1576 }
1577 else {
1578 if (isset($UFGroupType[$dao->module])) {
1579 // skip the default modules
1580 if (!$status) {
1581 $ufJoin[$dao->id] = $UFGroupType[$dao->module];
1582 }
1583 // added for CRM-1475
1584 }
1585 elseif (!CRM_Utils_Array::key($dao->module, $ufJoin)) {
1586 $ufJoin[$dao->id] = $dao->module;
1587 }
1588 }
1589 }
1590 return $ufJoin;
1591 }
1592
1593 /**
1594 * Function takes an associative array and creates a ufjoin record for ufgroup.
1595 *
1596 * @param array $params
1597 * (reference) an assoc array of name/value pairs.
1598 *
1599 * @return CRM_Core_BAO_UFJoin
1600 */
1601 public static function addUFJoin(&$params) {
1602 $ufJoin = new CRM_Core_DAO_UFJoin();
1603 $ufJoin->copyValues($params);
1604 $ufJoin->save();
1605 return $ufJoin;
1606 }
1607
1608 /**
1609 * Delete the uf join record for an uf group.
1610 *
1611 * @param array $params
1612 * (reference) an assoc array of name/value pairs.
1613 */
1614 public static function delUFJoin(&$params) {
1615 $ufJoin = new CRM_Core_DAO_UFJoin();
1616 $ufJoin->copyValues($params);
1617 $ufJoin->delete();
1618 }
1619
1620 /**
1621 * Get the weight for ufjoin record.
1622 *
1623 * @param int $ufGroupId
1624 * If $ufGroupId get update weight or add weight.
1625 *
1626 * @return int
1627 * weight of the UFGroup
1628 */
1629 public static function getWeight($ufGroupId = NULL) {
1630 //calculate the weight
1631 $p = [];
1632 if (!$ufGroupId) {
1633 $queryString = "SELECT ( MAX(civicrm_uf_join.weight)+1) as new_weight
1634 FROM civicrm_uf_join
1635 WHERE module = 'User Registration' OR module = 'User Account' OR module = 'Profile'";
1636 }
1637 else {
1638 $queryString = "SELECT MAX(civicrm_uf_join.weight) as new_weight
1639 FROM civicrm_uf_join
1640 WHERE civicrm_uf_join.uf_group_id = %1
1641 AND ( entity_id IS NULL OR entity_id <= 0 )";
1642 $p[1] = [$ufGroupId, 'Integer'];
1643 }
1644
1645 $dao = CRM_Core_DAO::executeQuery($queryString, $p);
1646 $dao->fetch();
1647 return ($dao->new_weight) ? $dao->new_weight : 1;
1648 }
1649
1650 /**
1651 * Get the uf group for a module.
1652 *
1653 * @param string $moduleName
1654 * Module name.
1655 * @param int $count
1656 * No to increment the weight.
1657 * @param bool $skipPermission
1658 * @param int $op
1659 * Which operation (view, edit, create, etc) to check permission for.
1660 * @param array|NULL $returnFields list of UFGroup fields to return; NULL for default
1661 *
1662 * @return array
1663 * array of ufgroups for a module
1664 */
1665 public static function getModuleUFGroup($moduleName = NULL, $count = 0, $skipPermission = TRUE, $op = CRM_Core_Permission::VIEW, $returnFields = NULL) {
1666 $selectFields = ['id', 'title', 'created_id', 'is_active', 'is_reserved', 'group_type'];
1667
1668 if (CRM_Core_BAO_SchemaHandler::checkIfFieldExists('civicrm_uf_group', 'description')) {
1669 // CRM-13555, since description field was added later (4.4), and to avoid any problems with upgrade
1670 $selectFields[] = 'description';
1671 }
1672
1673 if (CRM_Core_BAO_SchemaHandler::checkIfFieldExists('civicrm_uf_group', 'frontend_title')) {
1674 $selectFields[] = 'frontend_title';
1675 }
1676
1677 if (!empty($returnFields)) {
1678 $selectFields = array_merge($returnFields, array_diff($selectFields, $returnFields));
1679 }
1680
1681 $queryString = 'SELECT civicrm_uf_group.' . implode(', civicrm_uf_group.', $selectFields) . '
1682 FROM civicrm_uf_group
1683 LEFT JOIN civicrm_uf_join ON (civicrm_uf_group.id = uf_group_id)';
1684 $p = [];
1685 if ($moduleName) {
1686 $queryString .= ' AND civicrm_uf_group.is_active = 1
1687 WHERE civicrm_uf_join.module = %2';
1688 $p[2] = [$moduleName, 'String'];
1689 }
1690
1691 // add permissioning for profiles only if not registration
1692 if (!$skipPermission) {
1693 $permissionClause = CRM_Core_Permission::ufGroupClause($op, 'civicrm_uf_group.');
1694 if (strpos($queryString, 'WHERE') !== FALSE) {
1695 $queryString .= " AND $permissionClause ";
1696 }
1697 else {
1698 $queryString .= " $permissionClause ";
1699 }
1700 }
1701
1702 $queryString .= ' ORDER BY civicrm_uf_join.weight, civicrm_uf_group.title';
1703 $dao = CRM_Core_DAO::executeQuery($queryString, $p);
1704
1705 $ufGroups = [];
1706 while ($dao->fetch()) {
1707 //skip mix profiles in user Registration / User Account
1708 if (($moduleName === 'User Registration' || $moduleName === 'User Account') &&
1709 CRM_Core_BAO_UFField::checkProfileType($dao->id)
1710 ) {
1711 continue;
1712 }
1713 foreach ($selectFields as $key => $field) {
1714 if ($field === 'id') {
1715 continue;
1716 }
1717 $ufGroups[$dao->id][$field] = $dao->$field;
1718 }
1719 }
1720
1721 // Allow other modules to alter/override the UFGroups.
1722 CRM_Utils_Hook::buildUFGroupsForModule($moduleName, $ufGroups);
1723
1724 return $ufGroups;
1725 }
1726
1727 /**
1728 * Filter ufgroups based on logged in user contact type.
1729 *
1730 * @param int $ufGroupId
1731 * Uf group id (profile id).
1732 * @param int $contactID
1733 *
1734 * @return bool
1735 * true or false
1736 */
1737 public static function filterUFGroups($ufGroupId, $contactID = NULL) {
1738 if (!$contactID) {
1739 $session = CRM_Core_Session::singleton();
1740 $contactID = $session->get('userID');
1741 }
1742
1743 if ($contactID) {
1744 //get the contact type
1745 $contactType = CRM_Contact_BAO_Contact::getContactType($contactID);
1746
1747 //match if exixting contact type is same as profile contact type
1748 $profileType = CRM_Core_BAO_UFField::getProfileType($ufGroupId);
1749
1750 if (CRM_Contact_BAO_ContactType::isaSubType($profileType)) {
1751 $profileType = CRM_Contact_BAO_ContactType::getBasicType($profileType);
1752
1753 //in some cases getBasicType() returns a cached array instead of string. Example: array ('sponsor' => 'organization')
1754 if (is_array($profileType)) {
1755 $profileType = array_shift($profileType);
1756 }
1757 }
1758
1759 //allow special mix profiles for Contribution and Participant
1760 $specialProfiles = ['Contribution', 'Participant', 'Membership'];
1761
1762 if (in_array($profileType, $specialProfiles)) {
1763 return TRUE;
1764 }
1765
1766 if (($contactType == $profileType) || $profileType == 'Contact') {
1767 return TRUE;
1768 }
1769 }
1770
1771 return FALSE;
1772 }
1773
1774 /**
1775 * Add profile field to a form.
1776 *
1777 * @param CRM_Core_Form $form
1778 * @param array $field
1779 * Properties.
1780 * @param int $mode
1781 * Profile mode.
1782 * @param int $contactId
1783 * @param bool $online
1784 * @param string $usedFor
1785 * For building up prefixed fieldname for special cases (e.g. onBehalf, Honor).
1786 * @param int $rowNumber
1787 * @param string $prefix
1788 *
1789 * @return null
1790 */
1791 public static function buildProfile(
1792 &$form,
1793 &$field,
1794 $mode,
1795 $contactId = NULL,
1796 $online = FALSE,
1797 $usedFor = NULL,
1798 $rowNumber = NULL,
1799 $prefix = ''
1800 ) {
1801 $defaultValues = [];
1802 $fieldName = $field['name'];
1803 $title = $field['title'];
1804 $attributes = $field['attributes'];
1805 $rule = $field['rule'];
1806 $view = $field['is_view'];
1807 $required = ($mode == CRM_Profile_Form::MODE_SEARCH) ? FALSE : $field['is_required'];
1808 $search = $mode == CRM_Profile_Form::MODE_SEARCH;
1809 $isShared = CRM_Utils_Array::value('is_shared', $field, 0);
1810
1811 // do not display view fields in drupal registration form
1812 // CRM-4632
1813 if ($view && $mode == CRM_Profile_Form::MODE_REGISTER) {
1814 return NULL;
1815 }
1816
1817 if ($usedFor == 'onbehalf') {
1818 $name = "onbehalf[$fieldName]";
1819 }
1820 elseif ($usedFor == 'honor') {
1821 $name = "honor[$fieldName]";
1822 }
1823 elseif ($contactId && !$online) {
1824 $name = "field[$contactId][$fieldName]";
1825 }
1826 elseif ($rowNumber) {
1827 $name = "field[$rowNumber][$fieldName]";
1828 }
1829 elseif (!empty($prefix)) {
1830 $name = $prefix . "[$fieldName]";
1831 }
1832 else {
1833 $name = $fieldName;
1834 }
1835
1836 $selectAttributes = ['class' => 'crm-select2', 'placeholder' => TRUE];
1837
1838 if ($fieldName == 'image_URL' && $mode == CRM_Profile_Form::MODE_EDIT) {
1839 $deleteExtra = json_encode(ts('Are you sure you want to delete contact image.'));
1840 $deleteURL = [
1841 CRM_Core_Action::DELETE => [
1842 'name' => ts('Delete Contact Image'),
1843 'url' => 'civicrm/contact/image',
1844 'qs' => 'reset=1&id=%%id%%&gid=%%gid%%&action=delete',
1845 'extra' => 'onclick = "' . htmlspecialchars("if (confirm($deleteExtra)) this.href+='&confirmed=1'; else return false;") . '"',
1846 ],
1847 ];
1848 $deleteURL = CRM_Core_Action::formLink($deleteURL,
1849 CRM_Core_Action::DELETE,
1850 [
1851 'id' => $form->get('id'),
1852 'gid' => $form->get('gid'),
1853 ],
1854 ts('more'),
1855 FALSE,
1856 'contact.profileimage.delete',
1857 'Contact',
1858 $form->get('id')
1859 );
1860 $form->assign('deleteURL', $deleteURL);
1861 }
1862 $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
1863 'address_options', TRUE, NULL, TRUE
1864 );
1865
1866 if (substr($fieldName, 0, 14) === 'state_province') {
1867 $form->addChainSelect($name, ['label' => $title, 'required' => $required]);
1868 $config = CRM_Core_Config::singleton();
1869 if (!in_array($mode, [CRM_Profile_Form::MODE_EDIT, CRM_Profile_Form::MODE_SEARCH]) &&
1870 $config->defaultContactStateProvince
1871 ) {
1872 $defaultValues[$name] = $config->defaultContactStateProvince;
1873 $form->setDefaults($defaultValues);
1874 }
1875 }
1876 elseif (substr($fieldName, 0, 7) === 'country') {
1877 $form->add('select', $name, $title, ['' => ts('- select -')] + CRM_Core_PseudoConstant::country(), $required, $selectAttributes);
1878 $config = CRM_Core_Config::singleton();
1879 if (!in_array($mode, [CRM_Profile_Form::MODE_EDIT, CRM_Profile_Form::MODE_SEARCH]) &&
1880 $config->defaultContactCountry
1881 ) {
1882 $defaultValues[$name] = $config->defaultContactCountry;
1883 $form->setDefaults($defaultValues);
1884 }
1885 }
1886 elseif (substr($fieldName, 0, 6) === 'county') {
1887 if ($addressOptions['county']) {
1888 $form->addChainSelect($name, ['label' => $title, 'required' => $required]);
1889 }
1890 }
1891 elseif (substr($fieldName, 0, 9) === 'image_URL') {
1892 $form->add('file', $name, $title, $attributes, $required);
1893 $form->addUploadElement($name);
1894 }
1895 elseif (substr($fieldName, 0, 2) === 'im') {
1896 $form->add('text', $name, $title, $attributes, $required);
1897 if (!$contactId) {
1898 if ($usedFor) {
1899 if (substr($name, -1) === ']') {
1900 $providerName = substr($name, 0, -1) . '-provider_id]';
1901 }
1902 $form->add('select', $providerName, NULL,
1903 [
1904 '' => ts('- select -'),
1905 ] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id'), $required
1906 );
1907 }
1908 else {
1909 $form->add('select', $name . '-provider_id', $title,
1910 [
1911 '' => ts('- select -'),
1912 ] + CRM_Core_PseudoConstant::get('CRM_Core_DAO_IM', 'provider_id'), $required
1913 );
1914 }
1915
1916 if ($view && $mode != CRM_Profile_Form::MODE_SEARCH) {
1917 $form->freeze($name . '-provider_id');
1918 }
1919 }
1920 }
1921 elseif (CRM_Utils_Array::value('name', $field) == 'membership_type') {
1922 list($orgInfo, $types) = CRM_Member_BAO_MembershipType::getMembershipTypeInfo();
1923 $sel = &$form->addElement('hierselect', $name, $title);
1924 $select = ['' => ts('- select -')];
1925 if (count($orgInfo) == 1 && $field['is_required']) {
1926 // we only have one org - so we should default to it. Not sure about defaulting to first type
1927 // as it could be missed - so adding a select
1928 // however, possibly that is more similar to the membership form
1929 if (count($types[1]) > 1) {
1930 $types[1] = $select + $types[1];
1931 }
1932 }
1933 else {
1934 $orgInfo = $select + $orgInfo;
1935 }
1936 $sel->setOptions([$orgInfo, $types]);
1937 }
1938 elseif (CRM_Utils_Array::value('name', $field) == 'membership_status') {
1939 $form->add('select', $name, $title,
1940 [
1941 '' => ts('- select -'),
1942 ] + CRM_Member_PseudoConstant::membershipStatus(NULL, NULL, 'label'), $required
1943 );
1944 }
1945 elseif (in_array($fieldName, ['gender_id', 'communication_style_id'])) {
1946 $options = [];
1947 $pseudoValues = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', $fieldName);
1948 foreach ($pseudoValues as $key => $var) {
1949 $options[$key] = $form->createElement('radio', NULL, ts($title), $var, $key);
1950 }
1951 $group = $form->addGroup($options, $name, $title);
1952 if ($required) {
1953 $form->addRule($name, ts('%1 is a required field.', [1 => $title]), 'required');
1954 }
1955 else {
1956 $group->setAttribute('allowClear', TRUE);
1957 }
1958 }
1959 elseif ($fieldName === 'prefix_id' || $fieldName === 'suffix_id') {
1960 $form->addSelect($name, [
1961 'label' => $title,
1962 'entity' => 'contact',
1963 'field' => $fieldName,
1964 'class' => 'six',
1965 'placeholder' => '',
1966 ], $required);
1967 }
1968 elseif ($fieldName === 'contact_sub_type') {
1969 $gId = $form->get('gid') ? $form->get('gid') : CRM_Utils_Array::value('group_id', $field);
1970 if ($usedFor == 'onbehalf') {
1971 $profileType = 'Organization';
1972 }
1973 elseif ($usedFor == 'honor') {
1974 $profileType = CRM_Core_BAO_UFField::getProfileType($form->_params['honoree_profile_id']);
1975 }
1976 else {
1977 $profileType = $gId ? CRM_Core_BAO_UFField::getProfileType($gId) : NULL;
1978 if ($profileType == 'Contact') {
1979 $profileType = 'Individual';
1980 }
1981 }
1982
1983 $setSubtype = FALSE;
1984 if (CRM_Contact_BAO_ContactType::isaSubType($profileType)) {
1985 $setSubtype = $profileType;
1986 $profileType = CRM_Contact_BAO_ContactType::getBasicType($profileType);
1987 }
1988
1989 $subtypes = $profileType ? CRM_Contact_BAO_ContactType::subTypePairs($profileType) : [];
1990
1991 if ($setSubtype) {
1992 $subtypeList = [];
1993 $subtypeList[$setSubtype] = $subtypes[$setSubtype];
1994 }
1995 else {
1996 $subtypeList = $subtypes;
1997 }
1998
1999 $form->add('select', $name, $title, $subtypeList, $required, ['class' => 'crm-select2', 'multiple' => TRUE]);
2000 }
2001 elseif (in_array($fieldName, CRM_Contact_BAO_Contact::$_greetingTypes)) {
2002 // Get contact type for greeting selector
2003 $gId = $form->get('gid') ?: CRM_Utils_Array::value('group_id', $field);
2004 $profileType = CRM_Core_BAO_UFField::getProfileType($gId, TRUE, FALSE, TRUE);
2005
2006 if (!$profileType || in_array($profileType, ['Contact', 'Contribution', 'Participant', 'Membership'])) {
2007 $profileType = ($profileType == 'Contact' && $form->get('id')) ? CRM_Contact_BAO_Contact::getContactType($form->get('id')) : 'Individual';
2008 }
2009 if (CRM_Contact_BAO_ContactType::isaSubType($profileType)) {
2010 $profileType = CRM_Contact_BAO_ContactType::getBasicType($profileType);
2011 }
2012 $greeting = [
2013 'contact_type' => $profileType,
2014 'greeting_type' => $fieldName,
2015 ];
2016 $form->add('select', $name, $title, ['' => ts('- select -')] + CRM_Core_PseudoConstant::greeting($greeting), $required);
2017 // add custom greeting element
2018 $form->add('text', $fieldName . '_custom', ts('Custom %1', [1 => ucwords(str_replace('_', ' ', $fieldName))]),
2019 NULL, FALSE
2020 );
2021 }
2022 elseif ($fieldName === 'preferred_communication_method') {
2023 $communicationFields = CRM_Core_PseudoConstant::get('CRM_Contact_DAO_Contact', 'preferred_communication_method');
2024 foreach ($communicationFields as $key => $var) {
2025 if ($key == '') {
2026 continue;
2027 }
2028 $communicationOptions[] = $form->createElement('checkbox', $key, NULL, $var);
2029 }
2030 $form->addGroup($communicationOptions, $name, $title, '<br/>');
2031 }
2032 elseif ($fieldName === 'preferred_mail_format') {
2033 $form->add('select', $name, $title, CRM_Core_SelectValues::pmf());
2034 }
2035 elseif ($fieldName === 'preferred_language') {
2036 $form->add('select', $name, $title, ['' => ts('- select -')] + CRM_Contact_BAO_Contact::buildOptions('preferred_language'));
2037 }
2038 elseif ($fieldName == 'external_identifier') {
2039 $form->add('text', $name, $title, $attributes, $required);
2040 $contID = $contactId;
2041 if (!$contID) {
2042 $contID = $form->get('id');
2043 }
2044 $form->addRule($name,
2045 ts('External ID already exists in Database.'),
2046 'objectExists',
2047 ['CRM_Contact_DAO_Contact', $contID, 'external_identifier']
2048 );
2049 }
2050 elseif ($fieldName === 'group') {
2051 CRM_Contact_Form_Edit_TagsAndGroups::buildQuickForm($form, $contactId,
2052 CRM_Contact_Form_Edit_TagsAndGroups::GROUP,
2053 TRUE, $required,
2054 $title, NULL, $name
2055 );
2056 }
2057 elseif ($fieldName === 'tag') {
2058 CRM_Contact_Form_Edit_TagsAndGroups::buildQuickForm($form, $contactId,
2059 CRM_Contact_Form_Edit_TagsAndGroups::TAG,
2060 FALSE, $required,
2061 NULL, $title, $name
2062 );
2063 }
2064 elseif (substr($fieldName, 0, 4) === 'url-') {
2065 $form->add('text', $name, $title, CRM_Core_DAO::getAttribute('CRM_Core_DAO_Website', 'url'), $required);
2066 $form->addRule($name, ts('Enter a valid web address beginning with \'http://\' or \'https://\'.'), 'url');
2067 }
2068 // Note should be rendered as textarea
2069 elseif (substr($fieldName, -4) == 'note') {
2070 $form->add('textarea', $name, $title, $attributes, $required);
2071 }
2072 elseif (substr($fieldName, 0, 6) === 'custom') {
2073 $customFieldID = CRM_Core_BAO_CustomField::getKeyID($fieldName);
2074 if ($customFieldID) {
2075 CRM_Core_BAO_CustomField::addQuickFormElement($form, $name, $customFieldID, $required, $search, $title);
2076 }
2077 }
2078 elseif (substr($fieldName, 0, 14) === 'address_custom') {
2079 list($fName, $locTypeId) = CRM_Utils_System::explode('-', $fieldName, 2);
2080 $customFieldID = CRM_Core_BAO_CustomField::getKeyID(substr($fName, 8));
2081 if ($customFieldID) {
2082 CRM_Core_BAO_CustomField::addQuickFormElement($form, $name, $customFieldID, $required, $search, $title);
2083 }
2084 }
2085 elseif ($fieldName == 'send_receipt') {
2086 $form->addElement('checkbox', $name, $title);
2087 }
2088 elseif ($fieldName == 'soft_credit') {
2089 $form->addEntityRef("soft_credit_contact_id[$rowNumber]", ts('Soft Credit To'), ['create' => TRUE]);
2090 $form->addMoney("soft_credit_amount[{$rowNumber}]", ts('Amount'), FALSE, NULL, FALSE);
2091 }
2092 elseif ($fieldName === 'product_name') {
2093 list($products, $options) = CRM_Contribute_BAO_Premium::getPremiumProductInfo();
2094 $sel = &$form->addElement('hierselect', $name, $title);
2095 $products = ['0' => ts('- select -')] + $products;
2096 $sel->setOptions([$products, $options]);
2097 }
2098 elseif ($fieldName === 'payment_instrument') {
2099 $form->add('select', $name, $title,
2100 ['' => ts('- select -')] + CRM_Contribute_PseudoConstant::paymentInstrument(), $required);
2101 }
2102 elseif ($fieldName === 'financial_type') {
2103 $form->add('select', $name, $title,
2104 [
2105 '' => ts('- select -'),
2106 ] + CRM_Contribute_PseudoConstant::financialType(), $required
2107 );
2108 }
2109 elseif ($fieldName === 'contribution_status_id') {
2110 $contributionStatuses = CRM_Contribute_BAO_Contribution_Utils::getContributionStatuses();
2111
2112 $form->add('select', $name, $title,
2113 [
2114 '' => ts('- select -'),
2115 ] + $contributionStatuses, $required
2116 );
2117 }
2118 elseif ($fieldName === 'soft_credit_type') {
2119 $name = "soft_credit_type[$rowNumber]";
2120 $form->add('select', $name, $title,
2121 [
2122 '' => ts('- select -'),
2123 ] + CRM_Core_OptionGroup::values("soft_credit_type")
2124 );
2125 //CRM-15350: choose SCT field default value as 'Gift' for membership use
2126 //else (for contribution), use configured SCT default value
2127 $SCTDefaultValue = CRM_Core_OptionGroup::getDefaultValue("soft_credit_type");
2128 if ($field['field_type'] == 'Membership') {
2129 $SCTDefaultValue = CRM_Core_PseudoConstant::getKey('CRM_Contribute_BAO_ContributionSoft', 'soft_credit_type_id', 'gift');
2130 }
2131 $form->addElement('hidden', 'sct_default_id', $SCTDefaultValue, ['id' => 'sct_default_id']);
2132 }
2133 elseif ($fieldName == 'contribution_soft_credit_pcp_id') {
2134 CRM_Contribute_Form_SoftCredit::addPCPFields($form, "[$rowNumber]");
2135 }
2136 elseif ($fieldName == 'currency') {
2137 $form->addCurrency($name, $title, $required, NULL, FALSE, FALSE);
2138 }
2139 elseif ($fieldName == 'contribution_page_id') {
2140 $form->add('select', $name, $title,
2141 [
2142 '' => ts('- select -'),
2143 ] + CRM_Contribute_PseudoConstant::contributionPage(), $required, 'class="big"'
2144 );
2145 }
2146 elseif ($fieldName == 'activity_status_id') {
2147 $form->add('select', $name, $title,
2148 [
2149 '' => ts('- select -'),
2150 ] + CRM_Core_PseudoConstant::activityStatus(), $required
2151 );
2152 }
2153 elseif ($fieldName == 'activity_engagement_level') {
2154 $form->add('select', $name, $title,
2155 [
2156 '' => ts('- select -'),
2157 ] + CRM_Campaign_PseudoConstant::engagementLevel(), $required
2158 );
2159 }
2160 elseif ($fieldName == 'participant_status') {
2161 $cond = NULL;
2162 if ($online == TRUE) {
2163 $cond = 'visibility_id = 1';
2164 }
2165 $form->add('select', $name, $title,
2166 [
2167 '' => ts('- select -'),
2168 ] + CRM_Event_PseudoConstant::participantStatus(NULL, $cond, 'label'), $required
2169 );
2170 }
2171 elseif ($fieldName == 'participant_role') {
2172 if (!empty($field['is_multiple'])) {
2173 $form->addCheckBox($name, $title, CRM_Event_PseudoConstant::participantRole(), NULL, NULL, NULL, NULL, '&nbsp', TRUE);
2174 }
2175 else {
2176 $form->add('select', $name, $title,
2177 [
2178 '' => ts('- select -'),
2179 ] + CRM_Event_PseudoConstant::participantRole(), $required
2180 );
2181 }
2182 }
2183 elseif ($fieldName == 'world_region') {
2184 $form->add('select', $name, $title, CRM_Core_PseudoConstant::worldRegion(), $required, $selectAttributes);
2185 }
2186 elseif ($fieldName == 'signature_html') {
2187 $form->add('wysiwyg', $name, $title, CRM_Core_DAO::getAttribute('CRM_Core_DAO_Email', $fieldName));
2188 }
2189 elseif ($fieldName == 'signature_text') {
2190 $form->add('textarea', $name, $title, CRM_Core_DAO::getAttribute('CRM_Core_DAO_Email', $fieldName));
2191 }
2192 elseif (substr($fieldName, -11) == 'campaign_id') {
2193 if (CRM_Campaign_BAO_Campaign::isCampaignEnable()) {
2194 $campaigns = CRM_Campaign_BAO_Campaign::getCampaigns(CRM_Utils_Array::value($contactId,
2195 $form->_componentCampaigns
2196 ));
2197 $form->add('select', $name, $title,
2198 [
2199 '' => ts('- select -'),
2200 ] + $campaigns, $required, 'class="crm-select2 big"'
2201 );
2202 }
2203 }
2204 elseif ($fieldName == 'activity_details') {
2205 $form->add('wysiwyg', $fieldName, $title, ['rows' => 4, 'cols' => 60], $required);
2206 }
2207 elseif ($fieldName == 'activity_duration') {
2208 $form->add('text', $name, $title, $attributes, $required);
2209 $form->addRule($name, ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger');
2210 }
2211 elseif ($fieldName == 'case_status') {
2212 $form->add('select', $name, $title,
2213 [
2214 '' => ts('- select -'),
2215 ] + CRM_Case_BAO_Case::buildOptions('case_status_id', 'create'),
2216 $required
2217 );
2218 }
2219 else {
2220 if (substr($fieldName, 0, 3) === 'is_' or substr($fieldName, 0, 7) === 'do_not_') {
2221 $form->add('advcheckbox', $name, $title, $attributes, $required);
2222 }
2223 elseif (CRM_Utils_Array::value('html_type', $field) === 'Select Date') {
2224 $extra = isset($field['datepicker']) ? $field['datepicker']['extra'] : CRM_Utils_Date::getDatePickerExtra($field);
2225 $attributes = isset($field['datepicker']) ? $field['datepicker']['attributes'] : CRM_Utils_Date::getDatePickerAttributes($field);
2226 $form->add('datepicker', $name, $title, $attributes, $required, $extra);
2227 }
2228 else {
2229 $form->add('text', $name, $title, $attributes, $required);
2230 }
2231 }
2232
2233 static $hiddenSubtype = FALSE;
2234 if (!$hiddenSubtype && CRM_Contact_BAO_ContactType::isaSubType($field['field_type'])) {
2235 // In registration mode params are submitted via POST and we don't have any clue
2236 // about profile-id or the profile-type (which could be a subtype)
2237 // To generalize the behavior and simplify the process,
2238 // lets always add the hidden
2239 //subtype value if there is any, and we won't have to
2240 // compute it while processing.
2241 if ($usedFor) {
2242 $form->addElement('hidden', $usedFor . '[contact_sub_type]', $field['field_type']);
2243 }
2244 else {
2245 $form->addElement('hidden', 'contact_sub_type_hidden', $field['field_type']);
2246 }
2247 $hiddenSubtype = TRUE;
2248 }
2249
2250 if (($view && $mode != CRM_Profile_Form::MODE_SEARCH) || $isShared) {
2251 $form->freeze($name);
2252 }
2253
2254 //add the rules
2255 if (in_array($fieldName, [
2256 'non_deductible_amount',
2257 'total_amount',
2258 'fee_amount',
2259 'net_amount',
2260 ])) {
2261 $form->addRule($name, ts('Please enter a valid amount.'), 'money');
2262 }
2263 if ($rule) {
2264 if (!($rule == 'email' && $mode == CRM_Profile_Form::MODE_SEARCH)) {
2265 $form->addRule($name, ts('Please enter a valid %1', [1 => $title]), $rule);
2266 }
2267 }
2268 }
2269
2270 /**
2271 * Set profile defaults.
2272 *
2273 * @param int $contactId
2274 * Contact id.
2275 * @param array $fields
2276 * Associative array of fields.
2277 * @param array $defaults
2278 * Defaults array.
2279 * @param bool $singleProfile
2280 * True for single profile else false(Update multiple items).
2281 * @param int $componentId
2282 * Id for specific components like contribute, event etc.
2283 * @param null $component
2284 */
2285 public static function setProfileDefaults(
2286 $contactId, &$fields, &$defaults,
2287 $singleProfile = TRUE, $componentId = NULL, $component = NULL
2288 ) {
2289 if (!$componentId) {
2290 //get the contact details
2291 $contactDetails = CRM_Contact_BAO_Contact::getHierContactDetails($contactId, $fields);
2292 $details = $contactDetails[$contactId] ?? NULL;
2293 $multipleFields = ['website' => 'url'];
2294
2295 //start of code to set the default values
2296 foreach ($fields as $name => $field) {
2297 // skip pseudo fields
2298 if (substr($name, 0, 9) == 'phone_ext') {
2299 continue;
2300 }
2301
2302 //set the field name depending upon the profile mode(single/multiple)
2303 if ($singleProfile) {
2304 $fldName = $name;
2305 }
2306 else {
2307 $fldName = "field[$contactId][$name]";
2308 }
2309
2310 if ($name == 'group') {
2311 CRM_Contact_Form_Edit_TagsAndGroups::setDefaults($contactId, $defaults, CRM_Contact_Form_Edit_TagsAndGroups::GROUP, $fldName);
2312 }
2313 if ($name == 'tag') {
2314 CRM_Contact_Form_Edit_TagsAndGroups::setDefaults($contactId, $defaults, CRM_Contact_Form_Edit_TagsAndGroups::TAG, $fldName);
2315 }
2316
2317 if (!empty($details[$name]) || isset($details[$name])) {
2318 //to handle custom data (checkbox) to be written
2319 // to handle birth/deceased date, greeting_type and few other fields
2320 if (in_array($name, CRM_Contact_BAO_Contact::$_greetingTypes)) {
2321 $defaults[$fldName] = $details[$name . '_id'];
2322 $defaults[$name . '_custom'] = $details[$name . '_custom'];
2323 }
2324 elseif ($name == 'preferred_communication_method') {
2325 $v = $details[$name];
2326 if (!is_array($details[$name])) {
2327 $v = explode(CRM_Core_DAO::VALUE_SEPARATOR, $v);
2328 }
2329 foreach ($v as $item) {
2330 if ($item) {
2331 $defaults[$fldName . "[$item]"] = 1;
2332 }
2333 }
2334 }
2335 elseif ($name == 'contact_sub_type') {
2336 $defaults[$fldName] = explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($details[$name], CRM_Core_DAO::VALUE_SEPARATOR));
2337 }
2338 elseif ($name == 'world_region') {
2339 $defaults[$fldName] = $details['worldregion_id'];
2340 }
2341 elseif (CRM_Core_BAO_CustomField::getKeyID($name)) {
2342 $defaults[$fldName] = self::formatCustomValue($field, $details[$name]);
2343 }
2344 else {
2345 $defaults[$fldName] = $details[$name];
2346 }
2347 }
2348 else {
2349 $blocks = ['email', 'phone', 'im', 'openid'];
2350 list($fieldName, $locTypeId, $phoneTypeId) = CRM_Utils_System::explode('-', $name, 3);
2351 if (!in_array($fieldName, $multipleFields)) {
2352 if (is_array($details)) {
2353 foreach ($details as $key => $value) {
2354 // when we fixed CRM-5319 - get primary loc
2355 // type as per loc field and removed below code.
2356 $primaryLocationType = FALSE;
2357 if ($locTypeId == 'Primary') {
2358 if (is_array($value) && array_key_exists($fieldName, $value)) {
2359 $primaryLocationType = TRUE;
2360 if (in_array($fieldName, $blocks)) {
2361 $locTypeId = CRM_Contact_BAO_Contact::getPrimaryLocationType($contactId, FALSE, $fieldName);
2362 }
2363 else {
2364 $locTypeId = CRM_Contact_BAO_Contact::getPrimaryLocationType($contactId, FALSE, 'address');
2365 }
2366 }
2367 }
2368
2369 // fixed for CRM-665
2370 if (is_numeric($locTypeId)) {
2371 if ($primaryLocationType || $locTypeId == CRM_Utils_Array::value('location_type_id', $value)) {
2372 if (!empty($value[$fieldName])) {
2373 //to handle stateprovince and country
2374 if ($fieldName == 'state_province') {
2375 $defaults[$fldName] = $value['state_province_id'];
2376 }
2377 elseif ($fieldName == 'county') {
2378 $defaults[$fldName] = $value['county_id'];
2379 }
2380 elseif ($fieldName == 'country') {
2381 if (!isset($value['country_id']) || !$value['country_id']) {
2382 $config = CRM_Core_Config::singleton();
2383 if ($config->defaultContactCountry) {
2384 $defaults[$fldName] = $config->defaultContactCountry;
2385 }
2386 }
2387 else {
2388 $defaults[$fldName] = $value['country_id'];
2389 }
2390 }
2391 elseif ($fieldName == 'phone') {
2392 if ($phoneTypeId) {
2393 if (isset($value['phone'][$phoneTypeId])) {
2394 $defaults[$fldName] = $value['phone'][$phoneTypeId];
2395 }
2396 if (isset($value['phone_ext'][$phoneTypeId])) {
2397 $defaults[str_replace('phone', 'phone_ext', $fldName)] = $value['phone_ext'][$phoneTypeId];
2398 }
2399 }
2400 else {
2401 $phoneDefault = $value['phone'] ?? NULL;
2402 // CRM-9216
2403 if (!is_array($phoneDefault)) {
2404 $defaults[$fldName] = $phoneDefault;
2405 }
2406 }
2407 }
2408 elseif ($fieldName == 'email') {
2409 //adding the first email (currently we don't support multiple emails of same location type)
2410 $defaults[$fldName] = $value['email'];
2411 }
2412 elseif ($fieldName == 'im') {
2413 //adding the first im (currently we don't support multiple ims of same location type)
2414 $defaults[$fldName] = $value['im'];
2415 $defaults[$fldName . '-provider_id'] = $value['im_provider_id'];
2416 }
2417 else {
2418 $defaults[$fldName] = $value[$fieldName];
2419 }
2420 }
2421 elseif (strpos($fieldName, 'address_custom') === 0 && !empty($value[substr($fieldName, 8)])) {
2422 $defaults[$fldName] = self::formatCustomValue($field, $value[substr($fieldName, 8)]);
2423 }
2424 }
2425 }
2426 elseif (strpos($fieldName, 'address_custom') === 0 && !empty($value[substr($fieldName, 8)])) {
2427 $defaults[$fldName] = self::formatCustomValue($field, $value[substr($fieldName, 8)]);
2428 }
2429 }
2430 }
2431 }
2432 else {
2433 if (is_array($details)) {
2434 if ($fieldName === 'url'
2435 && !empty($details['website'])
2436 && !empty($details['website'][$locTypeId])
2437 ) {
2438 $defaults[$fldName] = $details['website'][$locTypeId]['url'] ?? NULL;
2439 }
2440 }
2441 }
2442 }
2443 }
2444 }
2445
2446 // Handling Contribution Part of the batch profile
2447 if (CRM_Core_Permission::access('CiviContribute') && $component == 'Contribute') {
2448 self::setComponentDefaults($fields, $componentId, $component, $defaults);
2449 }
2450
2451 // Handling Event Participation Part of the batch profile
2452 if (CRM_Core_Permission::access('CiviEvent') && $component == 'Event') {
2453 self::setComponentDefaults($fields, $componentId, $component, $defaults);
2454 }
2455
2456 // Handling membership Part of the batch profile
2457 if (CRM_Core_Permission::access('CiviMember') && $component == 'Membership') {
2458 self::setComponentDefaults($fields, $componentId, $component, $defaults);
2459 }
2460
2461 // Handling Activity Part of the batch profile
2462 if ($component == 'Activity') {
2463 self::setComponentDefaults($fields, $componentId, $component, $defaults);
2464 }
2465
2466 // Handling Case Part of the batch profile
2467 if (CRM_Core_Permission::access('CiviCase') && $component == 'Case') {
2468 self::setComponentDefaults($fields, $componentId, $component, $defaults);
2469 }
2470 }
2471
2472 /**
2473 * Get profiles by type eg: pure Individual etc
2474 *
2475 * @param array $types
2476 * Associative array of types eg: types('Individual').
2477 * @param bool $onlyPure
2478 * True if only pure profiles are required.
2479 *
2480 * @return array
2481 * associative array of profiles
2482 */
2483 public static function getProfiles($types, $onlyPure = FALSE) {
2484 $profiles = [];
2485 $ufGroups = CRM_Core_PseudoConstant::get('CRM_Core_DAO_UFField', 'uf_group_id');
2486
2487 CRM_Utils_Hook::aclGroup(CRM_Core_Permission::ADMIN, NULL, 'civicrm_uf_group', $ufGroups, $ufGroups);
2488
2489 // Exclude Batch Data Entry profiles - CRM-10901
2490 $batchProfiles = CRM_Core_BAO_UFGroup::getBatchProfiles();
2491
2492 foreach ($ufGroups as $id => $title) {
2493 $ptype = CRM_Core_BAO_UFField::getProfileType($id, FALSE, $onlyPure);
2494 if (in_array($ptype, $types) && !array_key_exists($id, $batchProfiles)) {
2495 $profiles[$id] = $title;
2496 }
2497 }
2498 return $profiles;
2499 }
2500
2501 /**
2502 * Check whether a profile is valid combination of
2503 * required and/or optional profile types
2504 *
2505 * @param array $required
2506 * Array of types those are required.
2507 * @param array $optional
2508 * Array of types those are optional.
2509 *
2510 * @return array
2511 * associative array of profiles
2512 */
2513 public static function getValidProfiles($required, $optional = NULL) {
2514 if (!is_array($required) || empty($required)) {
2515 return NULL;
2516 }
2517
2518 $profiles = [];
2519 $ufGroups = CRM_Core_PseudoConstant::get('CRM_Core_DAO_UFField', 'uf_group_id');
2520
2521 CRM_Utils_Hook::aclGroup(CRM_Core_Permission::ADMIN, NULL, 'civicrm_uf_group', $ufGroups, $ufGroups);
2522
2523 foreach ($ufGroups as $id => $title) {
2524 $type = CRM_Core_BAO_UFField::checkValidProfileType($id, $required, $optional);
2525 if ($type) {
2526 $profiles[$id] = $title;
2527 }
2528 }
2529
2530 return $profiles;
2531 }
2532
2533 /**
2534 * Check whether a profile is valid combination of
2535 * required profile fields
2536 *
2537 * @param array $ufId
2538 * Integer id of the profile.
2539 * @param array $required
2540 * Array of fields those are required in the profile.
2541 *
2542 * @return array
2543 * associative array of profiles
2544 */
2545 public static function checkValidProfile($ufId, $required = NULL) {
2546 $validProfile = FALSE;
2547 if (!$ufId) {
2548 return $validProfile;
2549 }
2550
2551 if (!CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $ufId, 'is_active')) {
2552 return $validProfile;
2553 }
2554
2555 $profileFields = self::getFields($ufId, FALSE, CRM_Core_Action::VIEW, NULL,
2556 NULL, FALSE, NULL, FALSE, NULL,
2557 CRM_Core_Permission::CREATE, NULL
2558 );
2559
2560 $validProfile = [];
2561 if (!empty($profileFields)) {
2562 $fields = array_keys($profileFields);
2563 foreach ($fields as $val) {
2564 foreach ($required as $key => $field) {
2565 if (strpos($val, $field) === 0) {
2566 unset($required[$key]);
2567 }
2568 }
2569 }
2570
2571 $validProfile = (empty($required));
2572 }
2573
2574 return $validProfile;
2575 }
2576
2577 /**
2578 * Get default value for Register.
2579 *
2580 * @param array $fields
2581 * @param array $defaults
2582 *
2583 * @return array
2584 */
2585 public static function setRegisterDefaults(&$fields, &$defaults) {
2586 $config = CRM_Core_Config::singleton();
2587 foreach ($fields as $name => $field) {
2588 if (substr($name, 0, 8) == 'country-') {
2589 if (!empty($config->defaultContactCountry)) {
2590 $defaults[$name] = $config->defaultContactCountry;
2591 }
2592 }
2593 elseif (substr($name, 0, 15) == 'state_province-') {
2594 if (!empty($config->defaultContactStateProvince)) {
2595 $defaults[$name] = $config->defaultContactStateProvince;
2596 }
2597 }
2598 }
2599 return $defaults;
2600 }
2601
2602 /**
2603 * make a copy of a profile, including
2604 * all the fields in the profile
2605 *
2606 * @param int $id
2607 * The profile id to copy.
2608 *
2609 * @return \CRM_Core_DAO
2610 */
2611 public static function copy($id) {
2612 $maxId = CRM_Core_DAO::singleValueQuery("SELECT max(id) FROM civicrm_uf_group");
2613
2614 $title = ts('[Copy id %1]', [1 => $maxId + 1]);
2615 $fieldsFix = [
2616 'suffix' => [
2617 'title' => ' ' . $title,
2618 'name' => '__Copy_id_' . ($maxId + 1) . '_',
2619 ],
2620 ];
2621
2622 $copy = CRM_Core_DAO::copyGeneric('CRM_Core_DAO_UFGroup',
2623 ['id' => $id],
2624 NULL,
2625 $fieldsFix
2626 );
2627
2628 if ($pos = strrpos($copy->name, "_{$id}")) {
2629 $copy->name = substr_replace($copy->name, '', $pos);
2630 }
2631 $copy->name = CRM_Utils_String::munge($copy->name, '_', 56) . "_{$copy->id}";
2632 $copy->save();
2633
2634 $copyUFJoin = CRM_Core_DAO::copyGeneric('CRM_Core_DAO_UFJoin',
2635 ['uf_group_id' => $id],
2636 ['uf_group_id' => $copy->id],
2637 NULL,
2638 'entity_table'
2639 );
2640
2641 $copyUFField = CRM_Core_DAO::copyGeneric('CRM_Core_BAO_UFField',
2642 ['uf_group_id' => $id],
2643 ['uf_group_id' => $copy->id]
2644 );
2645
2646 $maxWeight = CRM_Utils_Weight::getMax('CRM_Core_DAO_UFJoin', NULL, 'weight');
2647
2648 //update the weight
2649 $query = "
2650 UPDATE civicrm_uf_join
2651 SET weight = %1
2652 WHERE uf_group_id = %2
2653 AND ( entity_id IS NULL OR entity_id <= 0 )
2654 ";
2655 $p = [
2656 1 => [$maxWeight + 1, 'Integer'],
2657 2 => [$copy->id, 'Integer'],
2658 ];
2659 CRM_Core_DAO::executeQuery($query, $p);
2660 if ($copy->is_reserved) {
2661 $query = "UPDATE civicrm_uf_group SET is_reserved = 0 WHERE id = %1";
2662 $params = [1 => [$copy->id, 'Integer']];
2663 CRM_Core_DAO::executeQuery($query, $params);
2664 }
2665 CRM_Utils_Hook::copy('UFGroup', $copy);
2666
2667 return $copy;
2668 }
2669
2670 /**
2671 * Process that send notification e-mails
2672 *
2673 * @param int $contactID
2674 * Contact id.
2675 * @param array $values
2676 * Associative array of name/value pair.
2677 */
2678 public static function commonSendMail($contactID, &$values) {
2679 if (!$contactID || !$values) {
2680 return;
2681
2682 }
2683 $template = CRM_Core_Smarty::singleton();
2684
2685 $displayName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
2686 $contactID,
2687 'display_name'
2688 );
2689
2690 self::profileDisplay($values['id'], $values['values'], $template);
2691 $emailList = explode(',', $values['email']);
2692
2693 $contactLink = CRM_Utils_System::url('civicrm/contact/view',
2694 "reset=1&cid=$contactID",
2695 TRUE, NULL, FALSE, FALSE, TRUE
2696 );
2697
2698 //get the default domain email address.
2699 list($domainEmailName, $domainEmailAddress) = CRM_Core_BAO_Domain::getNameAndEmail();
2700
2701 if (!$domainEmailAddress || $domainEmailAddress == 'info@EXAMPLE.ORG') {
2702 $fixUrl = CRM_Utils_System::url('civicrm/admin/domain', 'action=update&reset=1');
2703 CRM_Core_Error::fatal(ts('The site administrator needs to enter a valid \'FROM Email Address\' in <a href="%1">Administer CiviCRM &raquo; Communications &raquo; FROM Email Addresses</a>. The email address used may need to be a valid mail account with your email service provider.', [1 => $fixUrl]));
2704 }
2705
2706 foreach ($emailList as $emailTo) {
2707 // FIXME: take the below out of the foreach loop
2708 CRM_Core_BAO_MessageTemplate::sendTemplate(
2709 [
2710 'groupName' => 'msg_tpl_workflow_uf',
2711 'valueName' => 'uf_notify',
2712 'contactId' => $contactID,
2713 'tplParams' => [
2714 'displayName' => $displayName,
2715 'currentDate' => date('r'),
2716 'contactLink' => $contactLink,
2717 ],
2718 'from' => "$domainEmailName <$domainEmailAddress>",
2719 'toEmail' => $emailTo,
2720 ]
2721 );
2722 }
2723 }
2724
2725 /**
2726 * Given a contact id and a group id, returns the field values from the db
2727 * for this group and notify email only if group's notify field is
2728 * set and field values are not empty
2729 *
2730 * @param int $gid
2731 * Group id.
2732 * @param int $cid
2733 * Contact id.
2734 * @param array $params
2735 * @param bool $skipCheck
2736 *
2737 * @return array
2738 */
2739 public function checkFieldsEmptyValues($gid, $cid, $params, $skipCheck = FALSE) {
2740 if ($gid) {
2741 if (CRM_Core_BAO_UFGroup::filterUFGroups($gid, $cid) || $skipCheck) {
2742 $values = [];
2743 $fields = CRM_Core_BAO_UFGroup::getFields($gid, FALSE, CRM_Core_Action::VIEW);
2744 CRM_Core_BAO_UFGroup::getValues($cid, $fields, $values, FALSE, $params, TRUE);
2745
2746 $email = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $gid, 'notify');
2747
2748 if (!empty($values) &&
2749 !empty($email)
2750 ) {
2751 $val = [
2752 'id' => $gid,
2753 'values' => $values,
2754 'email' => $email,
2755 ];
2756 return $val;
2757 }
2758 }
2759 }
2760 return NULL;
2761 }
2762
2763 /**
2764 * Assign uf fields to template.
2765 *
2766 * @param int $gid
2767 * Group id.
2768 * @param array $values
2769 * @param CRM_Core_Smarty $template
2770 */
2771 public static function profileDisplay($gid, $values, $template) {
2772 $groupTitle = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $gid, 'title');
2773 $template->assign('grouptitle', $groupTitle);
2774 if (count($values)) {
2775 $template->assign('values', $values);
2776 }
2777 }
2778
2779 /**
2780 * Format fields for dupe Contact Matching.
2781 *
2782 * @param array $params
2783 *
2784 * @param int $contactId
2785 *
2786 * @return array
2787 * associated formatted array
2788 */
2789 public static function formatFields($params, $contactId = NULL) {
2790 if ($contactId) {
2791 // get the primary location type id and email
2792 list($name, $primaryEmail, $primaryLocationType) = CRM_Contact_BAO_Contact_Location::getEmailDetails($contactId);
2793 }
2794 else {
2795 $defaultLocationType = CRM_Core_BAO_LocationType::getDefault();
2796 $primaryLocationType = $defaultLocationType->id;
2797 }
2798
2799 $data = [];
2800 $locationType = [];
2801 $count = 1;
2802 $primaryLocation = 0;
2803 foreach ($params as $key => $value) {
2804 list($fieldName, $locTypeId, $phoneTypeId) = explode('-', $key);
2805
2806 if ($locTypeId == 'Primary') {
2807 $locTypeId = $primaryLocationType;
2808 }
2809
2810 if (is_numeric($locTypeId)) {
2811 if (!in_array($locTypeId, $locationType)) {
2812 $locationType[$count] = $locTypeId;
2813 $count++;
2814 }
2815 $loc = CRM_Utils_Array::key($locTypeId, $locationType);
2816
2817 $data['location'][$loc]['location_type_id'] = $locTypeId;
2818
2819 // if we are getting in a new primary email, dont overwrite the new one
2820 if ($locTypeId == $primaryLocationType) {
2821 if (!empty($params['email-' . $primaryLocationType])) {
2822 $data['location'][$loc]['email'][$loc]['email'] = $fields['email-' . $primaryLocationType];
2823 }
2824 elseif (isset($primaryEmail)) {
2825 $data['location'][$loc]['email'][$loc]['email'] = $primaryEmail;
2826 }
2827 $primaryLocation++;
2828 }
2829
2830 if ($loc == 1) {
2831 $data['location'][$loc]['is_primary'] = 1;
2832 }
2833 if ($fieldName == 'phone') {
2834 if ($phoneTypeId) {
2835 $data['location'][$loc]['phone'][$loc]['phone_type_id'] = $phoneTypeId;
2836 }
2837 else {
2838 $data['location'][$loc]['phone'][$loc]['phone_type_id'] = '';
2839 }
2840 $data['location'][$loc]['phone'][$loc]['phone'] = $value;
2841 }
2842 elseif ($fieldName == 'email') {
2843 $data['location'][$loc]['email'][$loc]['email'] = $value;
2844 }
2845 elseif ($fieldName == 'im') {
2846 $data['location'][$loc]['im'][$loc]['name'] = $value;
2847 }
2848 else {
2849 if ($fieldName === 'state_province') {
2850 $data['location'][$loc]['address']['state_province_id'] = $value;
2851 }
2852 elseif ($fieldName === 'country') {
2853 $data['location'][$loc]['address']['country_id'] = $value;
2854 }
2855 else {
2856 $data['location'][$loc]['address'][$fieldName] = $value;
2857 }
2858 }
2859 }
2860 else {
2861 // TODO: prefix, suffix and gender translation may no longer be necessary - check inputs
2862 if ($key === 'individual_suffix') {
2863 $data['suffix_id'] = $value;
2864 }
2865 elseif ($key === 'individual_prefix') {
2866 $data['prefix_id'] = $value;
2867 }
2868 elseif ($key === 'gender') {
2869 $data['gender_id'] = $value;
2870 }
2871 elseif (substr($key, 0, 6) === 'custom') {
2872 if ($customFieldID = CRM_Core_BAO_CustomField::getKeyID($key)) {
2873 //fix checkbox
2874 if ($customFields[$customFieldID]['html_type'] == 'CheckBox') {
2875 $value = implode(CRM_Core_DAO::VALUE_SEPARATOR, array_keys($value));
2876 }
2877 // fix the date field
2878 if ($customFields[$customFieldID]['data_type'] == 'Date') {
2879 $date = CRM_Utils_Date::format($value);
2880 if (!$date) {
2881 $date = '';
2882 }
2883 $value = $date;
2884 }
2885
2886 $data['custom'][$customFieldID] = [
2887 'id' => $id,
2888 'value' => $value,
2889 'extends' => $customFields[$customFieldID]['extends'],
2890 'type' => $customFields[$customFieldID]['data_type'],
2891 'custom_field_id' => $customFieldID,
2892 ];
2893 }
2894 }
2895 elseif ($key == 'edit') {
2896 continue;
2897 }
2898 else {
2899 $data[$key] = $value;
2900 }
2901 }
2902 }
2903
2904 if (!$primaryLocation) {
2905 $loc++;
2906 $data['location'][$loc]['email'][$loc]['email'] = $primaryEmail;
2907 }
2908
2909 return $data;
2910 }
2911
2912 /**
2913 * Calculate the profile type 'group_type' as per profile fields.
2914 *
2915 * @param int $gId
2916 * Profile id.
2917 * @param bool $includeTypeValues
2918 * @param int $ignoreFieldId
2919 * Ignore particular profile field.
2920 *
2921 * @return array
2922 * list of calculated group type
2923 */
2924 public static function calculateGroupType($gId, $includeTypeValues = FALSE, $ignoreFieldId = NULL) {
2925 //get the profile fields.
2926 $ufFields = self::getFields($gId, FALSE, NULL, NULL, NULL, TRUE, NULL, TRUE);
2927 return self::_calculateGroupType($ufFields, $includeTypeValues, $ignoreFieldId);
2928 }
2929
2930 /**
2931 * Calculate the profile type 'group_type' as per profile fields.
2932 *
2933 * @param $ufFields
2934 * @param bool $includeTypeValues
2935 * @param int $ignoreFieldId
2936 * Ignore perticular profile field.
2937 *
2938 * @return array
2939 * list of calculated group type
2940 */
2941 public static function _calculateGroupType($ufFields, $includeTypeValues = FALSE, $ignoreFieldId = NULL) {
2942 $groupType = $groupTypeValues = $customFieldIds = [];
2943 if (!empty($ufFields)) {
2944 foreach ($ufFields as $fieldName => $fieldValue) {
2945 //ignore field from group type when provided.
2946 //in case of update profile field.
2947 if ($ignoreFieldId && ($ignoreFieldId == $fieldValue['field_id'])) {
2948 continue;
2949 }
2950 if (!in_array($fieldValue['field_type'], $groupType)) {
2951 $groupType[$fieldValue['field_type']] = $fieldValue['field_type'];
2952 }
2953
2954 if ($includeTypeValues && ($fldId = CRM_Core_BAO_CustomField::getKeyID($fieldName))) {
2955 $customFieldIds[$fldId] = $fldId;
2956 }
2957 }
2958 }
2959
2960 if (!empty($customFieldIds)) {
2961 $query = 'SELECT DISTINCT(cg.id), cg.extends, cg.extends_entity_column_id, cg.extends_entity_column_value FROM civicrm_custom_group cg LEFT JOIN civicrm_custom_field cf ON cf.custom_group_id = cg.id WHERE cg.extends_entity_column_value IS NOT NULL AND cf.id IN (' . implode(',', $customFieldIds) . ')';
2962
2963 $customGroups = CRM_Core_DAO::executeQuery($query);
2964 while ($customGroups->fetch()) {
2965 if (!$customGroups->extends_entity_column_value) {
2966 continue;
2967 }
2968
2969 $groupTypeName = "{$customGroups->extends}Type";
2970 if ($customGroups->extends == 'Participant' && $customGroups->extends_entity_column_id) {
2971 $groupTypeName = CRM_Core_PseudoConstant::getName('CRM_Core_DAO_CustomGroup', 'extends_entity_column_id', $customGroups->extends_entity_column_id);
2972 }
2973
2974 foreach (explode(CRM_Core_DAO::VALUE_SEPARATOR, $customGroups->extends_entity_column_value) as $val) {
2975 if ($val) {
2976 $groupTypeValues[$groupTypeName][$val] = $val;
2977 }
2978 }
2979 }
2980
2981 if (!empty($groupTypeValues)) {
2982 $groupType = array_merge($groupType, $groupTypeValues);
2983 }
2984 }
2985
2986 return $groupType;
2987 }
2988
2989 /**
2990 * Update the profile type 'group_type' as per profile fields including group types and group subtype values.
2991 * Build and store string like: group_type1,group_type2[VALUE_SEPERATOR]group_type1Type:1:2:3,group_type2Type:1:2
2992 *
2993 * FIELDS GROUP_TYPE
2994 * BirthDate + Email Individual,Contact
2995 * BirthDate + Subject Individual,Activity
2996 * BirthDate + Subject + SurveyOnlyField Individual,Activity\0ActivityType:28
2997 * BirthDate + Subject + SurveyOnlyField + PhoneOnlyField (Not allowed)
2998 * BirthDate + SurveyOnlyField Individual,Activity\0ActivityType:28
2999 * BirthDate + Subject + SurveyOrPhoneField Individual,Activity\0ActivityType:2:28
3000 * BirthDate + SurveyOrPhoneField Individual,Activity\0ActivityType:2:28
3001 * BirthDate + SurveyOrPhoneField + SurveyOnlyField Individual,Activity\0ActivityType:2:28
3002 * BirthDate + StudentField + Subject + SurveyOnlyField Individual,Activity,Student\0ActivityType:28
3003 *
3004 * @param int $gId
3005 * @param array $groupTypes
3006 * With key having group type names.
3007 *
3008 * @return bool
3009 */
3010 public static function updateGroupTypes($gId, $groupTypes = []) {
3011 if (!is_array($groupTypes) || !$gId) {
3012 return FALSE;
3013 }
3014
3015 // If empty group types set group_type as 'null'
3016 if (empty($groupTypes)) {
3017 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_UFGroup', $gId, 'group_type', 'null');
3018 }
3019
3020 $componentGroupTypes = ['Contribution', 'Participant', 'Membership', 'Activity', 'Case'];
3021 $validGroupTypes = array_merge([
3022 'Contact',
3023 'Individual',
3024 'Organization',
3025 'Household',
3026 ], $componentGroupTypes, CRM_Contact_BAO_ContactType::subTypes());
3027
3028 $gTypes = $gTypeValues = [];
3029
3030 $participantExtends = ['ParticipantRole', 'ParticipantEventName', 'ParticipantEventType'];
3031 // Get valid group type and group subtypes
3032 foreach ($groupTypes as $groupType => $value) {
3033 if (in_array($groupType, $validGroupTypes) && !in_array($groupType, $gTypes)) {
3034 $gTypes[] = $groupType;
3035 }
3036
3037 $subTypesOf = NULL;
3038
3039 if (in_array($groupType, $participantExtends)) {
3040 $subTypesOf = $groupType;
3041 }
3042 elseif (strpos($groupType, 'Type') > 0) {
3043 $subTypesOf = substr($groupType, 0, strpos($groupType, 'Type'));
3044 }
3045 else {
3046 continue;
3047 }
3048
3049 if (!empty($value) &&
3050 (in_array($subTypesOf, $componentGroupTypes) ||
3051 in_array($subTypesOf, $participantExtends)
3052 )
3053 ) {
3054 $gTypeValues[$subTypesOf] = $groupType . ":" . implode(':', $value);
3055 }
3056 }
3057
3058 if (empty($gTypes)) {
3059 return FALSE;
3060 }
3061
3062 // Build String to store group types and group subtypes
3063 $groupTypeString = implode(',', $gTypes);
3064 if (!empty($gTypeValues)) {
3065 $groupTypeString .= CRM_Core_DAO::VALUE_SEPARATOR . implode(',', $gTypeValues);
3066 }
3067
3068 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_UFGroup', $gId, 'group_type', $groupTypeString);
3069 }
3070
3071 /**
3072 * Create a "group_type" string.
3073 *
3074 * @param array $coreTypes
3075 * E.g. array('Individual','Contact','Student').
3076 * @param array $subTypes
3077 * E.g. array('ActivityType' => array(7, 11)).
3078 * @param string $delim
3079 *
3080 * @return string
3081 * @throws CRM_Core_Exception
3082 */
3083 public static function encodeGroupType($coreTypes, $subTypes, $delim = CRM_Core_DAO::VALUE_SEPARATOR) {
3084 $groupTypeExpr = '';
3085 if ($coreTypes) {
3086 $groupTypeExpr .= implode(',', $coreTypes);
3087 }
3088 if ($subTypes) {
3089 //CRM-15427 Allow Multiple subtype filtering
3090 //if (count($subTypes) > 1) {
3091 //throw new CRM_Core_Exception("Multiple subtype filtering is not currently supported by widget.");
3092 //}
3093 foreach ($subTypes as $subType => $subTypeIds) {
3094 $groupTypeExpr .= $delim . $subType . ':' . implode(':', $subTypeIds);
3095 }
3096 }
3097 return $groupTypeExpr;
3098 }
3099
3100 /**
3101 * setDefault componet specific profile fields.
3102 *
3103 * @param array $fields
3104 * Profile fields.
3105 * @param int $componentId
3106 * ComponetID.
3107 * @param string $component
3108 * Component name.
3109 * @param array $defaults
3110 * An array of default values.
3111 *
3112 * @param bool $isStandalone
3113 */
3114 public static function setComponentDefaults(&$fields, $componentId, $component, &$defaults, $isStandalone = FALSE) {
3115 if (!$componentId ||
3116 !in_array($component, ['Contribute', 'Membership', 'Event', 'Activity', 'Case'])
3117 ) {
3118 return;
3119 }
3120
3121 $componentBAO = $componentSubType = NULL;
3122 switch ($component) {
3123 case 'Membership':
3124 $componentBAO = 'CRM_Member_BAO_Membership';
3125 $componentBAOName = 'Membership';
3126 $componentSubType = ['membership_type_id'];
3127 break;
3128
3129 case 'Contribute':
3130 $componentBAO = 'CRM_Contribute_BAO_Contribution';
3131 $componentBAOName = 'Contribution';
3132 $componentSubType = ['financial_type_id'];
3133 break;
3134
3135 case 'Event':
3136 $componentBAO = 'CRM_Event_BAO_Participant';
3137 $componentBAOName = 'Participant';
3138 $componentSubType = ['role_id', 'event_id', 'event_type_id'];
3139 break;
3140
3141 case 'Activity':
3142 $componentBAO = 'CRM_Activity_BAO_Activity';
3143 $componentBAOName = 'Activity';
3144 $componentSubType = ['activity_type_id'];
3145 break;
3146
3147 case 'Case':
3148 $componentBAO = 'CRM_Case_BAO_Case';
3149 $componentBAOName = 'Case';
3150 $componentSubType = ['case_type_id'];
3151 break;
3152 }
3153
3154 $values = [];
3155 $params = ['id' => $componentId];
3156
3157 //get the component values.
3158 CRM_Core_DAO::commonRetrieve($componentBAO, $params, $values);
3159 if ($componentBAOName == 'Participant') {
3160 $values += ['event_type_id' => CRM_Core_DAO::getFieldValue('CRM_Event_DAO_Event', $values['event_id'], 'event_type_id')];
3161 }
3162
3163 $formattedGroupTree = [];
3164
3165 foreach ($fields as $name => $field) {
3166 $fldName = $isStandalone ? $name : "field[$componentId][$name]";
3167 if (array_key_exists($name, $values)) {
3168 $defaults[$fldName] = $values[$name];
3169 }
3170 elseif ($name == 'participant_note') {
3171 $noteDetails = CRM_Core_BAO_Note::getNote($componentId, 'civicrm_participant');
3172 $defaults[$fldName] = array_pop($noteDetails);
3173 }
3174 elseif (in_array($name, [
3175 'financial_type',
3176 'payment_instrument',
3177 'participant_status',
3178 'participant_role',
3179 ])) {
3180 $defaults[$fldName] = $values["{$name}_id"];
3181 }
3182 elseif ($name == 'membership_type') {
3183 // since membership_type field is a hierselect -
3184 $defaults[$fldName][0]
3185 = CRM_Core_DAO::getFieldValue('CRM_Member_DAO_MembershipType', $values['membership_type_id'], 'member_of_contact_id', 'id');
3186 $defaults[$fldName][1] = $values['membership_type_id'];
3187 }
3188 elseif ($name == 'membership_status') {
3189 $defaults[$fldName] = $values['status_id'];
3190 }
3191 elseif ($name == 'case_status') {
3192 $defaults[$fldName] = $values['case_status_id'];
3193 }
3194 elseif (CRM_Core_BAO_CustomField::getKeyID($name, TRUE) !== [NULL, NULL]) {
3195 if (empty($formattedGroupTree)) {
3196 //get the groupTree as per subTypes.
3197 $groupTree = [];
3198 foreach ($componentSubType as $subType) {
3199 $subTree = CRM_Core_BAO_CustomGroup::getTree($componentBAOName, NULL,
3200 $componentId, 0, $values[$subType]
3201 );
3202 $groupTree = CRM_Utils_Array::crmArrayMerge($groupTree, $subTree);
3203 }
3204 $formattedGroupTree = CRM_Core_BAO_CustomGroup::formatGroupTree($groupTree, 1);
3205 CRM_Core_BAO_CustomGroup::setDefaults($formattedGroupTree, $defaults);
3206 }
3207
3208 //FIX ME: We need to loop defaults, but once we move to custom_1_x convention this code can be simplified.
3209 foreach ($defaults as $customKey => $customValue) {
3210 if ($customFieldDetails = CRM_Core_BAO_CustomField::getKeyID($customKey, TRUE)) {
3211 if ($name == 'custom_' . $customFieldDetails[0]) {
3212
3213 //hack to set default for checkbox
3214 //basically this is for weired field name like field[33][custom_19]
3215 //we are converting this field name to array structure and assign value.
3216 $skipValue = FALSE;
3217
3218 foreach ($formattedGroupTree as $tree) {
3219 if (!empty($tree['fields'][$customFieldDetails[0]])) {
3220 if ('CheckBox' == CRM_Utils_Array::value('html_type', $tree['fields'][$customFieldDetails[0]])) {
3221 $skipValue = TRUE;
3222 $defaults['field'][$componentId][$name] = $customValue;
3223 break;
3224 }
3225 elseif (CRM_Utils_Array::value('data_type', $tree['fields'][$customFieldDetails[0]]) == 'Date') {
3226 $skipValue = TRUE;
3227
3228 // CRM-6681, $default contains formatted date, time values.
3229 $defaults[$fldName] = $customValue;
3230 if (!empty($defaults[$customKey . '_time'])) {
3231 $defaults['field'][$componentId][$name . '_time'] = $defaults[$customKey . '_time'];
3232 }
3233 }
3234 }
3235 }
3236
3237 if (!$skipValue || $isStandalone) {
3238 $defaults[$fldName] = $customValue;
3239 }
3240 unset($defaults[$customKey]);
3241 break;
3242 }
3243 }
3244 }
3245 }
3246 elseif (isset($values[$fldName])) {
3247 $defaults[$fldName] = $values[$fldName];
3248 }
3249 }
3250 }
3251
3252 /**
3253 * Retrieve groups of profiles.
3254 *
3255 * @param int $profileID
3256 * Id of the profile.
3257 *
3258 * @return array
3259 * returns array
3260 */
3261 public static function profileGroups($profileID) {
3262 $groupTypes = [];
3263 $profileTypes = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $profileID, 'group_type');
3264 if ($profileTypes) {
3265 $groupTypeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $profileTypes);
3266 $groupTypes = explode(',', $groupTypeParts[0]);
3267 }
3268 return $groupTypes;
3269 }
3270
3271 /**
3272 * Alter contact params by filtering existing subscribed groups and returns
3273 * unsubscribed groups array for subscription.
3274 *
3275 * @param array $params
3276 * Contact params.
3277 * @param int $contactId
3278 * User contact id.
3279 *
3280 * @return array
3281 * This contains array of groups for subscription
3282 */
3283 public static function getDoubleOptInGroupIds(&$params, $contactId = NULL) {
3284 $config = CRM_Core_Config::singleton();
3285 $subscribeGroupIds = [];
3286
3287 // process further only if profileDoubleOptIn enabled and if groups exist
3288 if (!array_key_exists('group', $params) ||
3289 !self::isProfileDoubleOptin() ||
3290 CRM_Utils_System::isNull($params['group'])
3291 ) {
3292 return $subscribeGroupIds;
3293 }
3294
3295 //check if contact email exist.
3296 $hasEmails = FALSE;
3297 foreach ($params as $name => $value) {
3298 if (strpos($name, 'email-') !== FALSE) {
3299 $hasEmails = TRUE;
3300 break;
3301 }
3302 }
3303
3304 //Proceed furthur only if email present
3305 if (!$hasEmails) {
3306 return $subscribeGroupIds;
3307 }
3308
3309 //do check for already subscriptions.
3310 $contactGroups = [];
3311 if ($contactId) {
3312 $query = "
3313 SELECT group_id
3314 FROM civicrm_group_contact
3315 WHERE status = 'Added'
3316 AND contact_id = %1";
3317
3318 $dao = CRM_Core_DAO::executeQuery($query, [1 => [$contactId, 'Integer']]);
3319 while ($dao->fetch()) {
3320 $contactGroups[$dao->group_id] = $dao->group_id;
3321 }
3322 }
3323
3324 //since we don't have names, compare w/ label.
3325 $mailingListGroupType = array_search('Mailing List', CRM_Core_OptionGroup::values('group_type'));
3326
3327 //actual processing start.
3328 foreach ($params['group'] as $groupId => $isSelected) {
3329 //unset group those are not selected.
3330 if (!$isSelected) {
3331 unset($params['group'][$groupId]);
3332 continue;
3333 }
3334
3335 $groupTypes = explode(CRM_Core_DAO::VALUE_SEPARATOR,
3336 CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Group', $groupId, 'group_type', 'id')
3337 );
3338 //get only mailing type group and unset it from params
3339 if (in_array($mailingListGroupType, $groupTypes) && !in_array($groupId, $contactGroups)) {
3340 $subscribeGroupIds[$groupId] = $groupId;
3341 unset($params['group'][$groupId]);
3342 }
3343 }
3344
3345 return $subscribeGroupIds;
3346 }
3347
3348 /**
3349 * Check if we are rendering mixed profiles.
3350 *
3351 * @param array $profileIds
3352 * Associated array of profile ids.
3353 *
3354 * @return bool
3355 * true if profile is mixed
3356 */
3357 public static function checkForMixProfiles($profileIds) {
3358 $mixProfile = FALSE;
3359
3360 $contactTypes = ['Individual', 'Household', 'Organization'];
3361 $subTypes = CRM_Contact_BAO_ContactType::subTypes();
3362
3363 $components = ['Contribution', 'Participant', 'Membership', 'Activity'];
3364
3365 $typeCount = ['ctype' => [], 'subtype' => []];
3366 foreach ($profileIds as $gid) {
3367 $profileType = CRM_Core_BAO_UFField::getProfileType($gid);
3368 // ignore profile of type Contact
3369 if ($profileType == 'Contact') {
3370 continue;
3371 }
3372 if (in_array($profileType, $contactTypes)) {
3373 if (!isset($typeCount['ctype'][$profileType])) {
3374 $typeCount['ctype'][$profileType] = 1;
3375 }
3376
3377 // check if we are rendering profile of different contact types
3378 if (count($typeCount['ctype']) == 2) {
3379 $mixProfile = TRUE;
3380 break;
3381 }
3382 }
3383 elseif (in_array($profileType, $components)) {
3384 $mixProfile = TRUE;
3385 break;
3386 }
3387 else {
3388 if (!isset($typeCount['subtype'][$profileType])) {
3389 $typeCount['subtype'][$profileType] = 1;
3390 }
3391 // check if we are rendering profile of different contact sub types
3392 if (count($typeCount['subtype']) == 2) {
3393 $mixProfile = TRUE;
3394 break;
3395 }
3396 }
3397 }
3398 return $mixProfile;
3399 }
3400
3401 /**
3402 * Determine of we show overlay profile or not.
3403 *
3404 * @return bool
3405 * true if profile should be shown else false
3406 */
3407 public static function showOverlayProfile() {
3408 $showOverlay = TRUE;
3409
3410 // get the id of overlay profile
3411 $overlayProfileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', 'summary_overlay', 'id', 'name');
3412 $query = "SELECT count(id) FROM civicrm_uf_field WHERE uf_group_id = {$overlayProfileId} AND visibility IN ('Public Pages', 'Public Pages and Listings') ";
3413
3414 $count = CRM_Core_DAO::singleValueQuery($query);
3415
3416 //check if there are no public fields and use is anonymous
3417 $session = CRM_Core_Session::singleton();
3418 if (!$count && !$session->get('userID')) {
3419 $showOverlay = FALSE;
3420 }
3421
3422 return $showOverlay;
3423 }
3424
3425 /**
3426 * Get group type values of the profile.
3427 *
3428 * @param int $profileId
3429 * @param string $groupType
3430 *
3431 * @return array
3432 * group type values
3433 */
3434 public static function groupTypeValues($profileId, $groupType = NULL) {
3435 $groupTypeValue = [];
3436 $groupTypes = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $profileId, 'group_type');
3437
3438 $groupTypeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $groupTypes);
3439 if (empty($groupTypeParts[1])) {
3440 return $groupTypeValue;
3441 }
3442 $participantExtends = ['ParticipantRole', 'ParticipantEventName', 'ParticipantEventType'];
3443
3444 foreach (explode(',', $groupTypeParts[1]) as $groupTypeValues) {
3445 $values = [];
3446 $valueParts = explode(':', $groupTypeValues);
3447 if ($groupType &&
3448 ($valueParts[0] != "{$groupType}Type" ||
3449 ($groupType == 'Participant' &&
3450 !in_array($valueParts[0], $participantExtends)
3451 )
3452 )
3453 ) {
3454 continue;
3455 }
3456 foreach ($valueParts as $val) {
3457 if (CRM_Utils_Rule::integer($val)) {
3458 $values[$val] = $val;
3459 }
3460 }
3461 if (!empty($values)) {
3462 $typeName = substr($valueParts[0], 0, -4);
3463 if (in_array($valueParts[0], $participantExtends)) {
3464 $typeName = $valueParts[0];
3465 }
3466 $groupTypeValue[$typeName] = $values;
3467 }
3468 }
3469
3470 return $groupTypeValue;
3471 }
3472
3473 /**
3474 * @return bool|object
3475 */
3476 public static function isProfileDoubleOptin() {
3477 // check for double optin
3478 $config = CRM_Core_Config::singleton();
3479 if (in_array('CiviMail', $config->enableComponents)) {
3480 return Civi::settings()->get('profile_double_optin');
3481 }
3482 return FALSE;
3483 }
3484
3485 /**
3486 * @return bool|object
3487 */
3488 public static function isProfileAddToGroupDoubleOptin() {
3489 // check for add to group double optin
3490 $config = CRM_Core_Config::singleton();
3491 if (in_array('CiviMail', $config->enableComponents)) {
3492 return Civi::settings()->get('profile_add_to_group_double_optin');
3493 }
3494 return FALSE;
3495 }
3496
3497 /**
3498 * Get profiles used for batch entry.
3499 *
3500 * @return array
3501 * profileIds profile ids
3502 */
3503 public static function getBatchProfiles() {
3504 $query = "SELECT id
3505 FROM civicrm_uf_group
3506 WHERE name IN ('contribution_batch_entry', 'membership_batch_entry')";
3507 $dao = CRM_Core_DAO::executeQuery($query);
3508 $profileIds = [];
3509 while ($dao->fetch()) {
3510 $profileIds[$dao->id] = $dao->id;
3511 }
3512 return $profileIds;
3513 }
3514
3515 /**
3516 * @param $source
3517 * @param $destination
3518 * @param bool $returnMultiSummaryFields
3519 *
3520 * @return array|null
3521 * @todo what do I do?
3522 */
3523 public static function shiftMultiRecordFields(&$source, &$destination, $returnMultiSummaryFields = FALSE) {
3524 $multiSummaryFields = $returnMultiSummaryFields ? [] : NULL;
3525 foreach ($source as $field => $properties) {
3526 if (!CRM_Core_BAO_CustomField::getKeyID($field)) {
3527 continue;
3528 }
3529 if (CRM_Core_BAO_CustomField::isMultiRecordField($field)) {
3530 $destination[$field] = $properties;
3531 if ($returnMultiSummaryFields) {
3532 if ($properties['is_multi_summary']) {
3533 $multiSummaryFields[$field] = $properties;
3534 }
3535 }
3536 unset($source[$field]);
3537 }
3538 }
3539 return $multiSummaryFields;
3540 }
3541
3542 /**
3543 * This is function is used to format pseudo fields.
3544 *
3545 * @param array $fields
3546 * Associated array of profile fields.
3547 *
3548 */
3549 public static function reformatProfileFields(&$fields) {
3550 //reformat fields array
3551 foreach ($fields as $name => $field) {
3552 //reformat phone and extension field
3553 if (substr($field['name'], 0, 13) == 'phone_and_ext') {
3554 $fieldSuffix = str_replace('phone_and_ext-', '', $field['name']);
3555
3556 // retain existing element properties and just update and replace key
3557 CRM_Utils_Array::crmReplaceKey($fields, $name, "phone-{$fieldSuffix}");
3558 $fields["phone-{$fieldSuffix}"]['name'] = "phone-{$fieldSuffix}";
3559 $fields["phone-{$fieldSuffix}"]['where'] = 'civicrm_phone.phone';
3560
3561 // add additional phone extension field
3562 $fields["phone_ext-{$fieldSuffix}"] = $field;
3563 $fields["phone_ext-{$fieldSuffix}"]['title'] = $field['title'] . ' - ' . ts('Ext.');
3564 $fields["phone_ext-{$fieldSuffix}"]['name'] = "phone_ext-{$fieldSuffix}";
3565 $fields["phone_ext-{$fieldSuffix}"]['where'] = 'civicrm_phone.phone_ext';
3566 $fields["phone_ext-{$fieldSuffix}"]['skipDisplay'] = 1;
3567 //ignore required for extension field
3568 $fields["phone_ext-{$fieldSuffix}"]['is_required'] = 0;
3569 }
3570 }
3571 }
3572
3573 /**
3574 * Get the frontend_title for the profile, falling back on 'title' if none.
3575 *
3576 * @param int $profileID
3577 *
3578 * @return string
3579 *
3580 * @throws \CiviCRM_API3_Exception
3581 */
3582 public static function getFrontEndTitle(int $profileID) {
3583 $profile = civicrm_api3('UFGroup', 'getsingle', ['id' => $profileID, 'return' => ['title', 'frontend_title']]);
3584 return $profile['frontend_title'] ?? $profile['title'];
3585 }
3586
3587 /**
3588 * Format custom field value for use in prepopulating a quickform profile field.
3589 *
3590 * @param array $field
3591 * Field metadata.
3592 * @param string $value
3593 * Raw value
3594 *
3595 * @return mixed
3596 * String or array, depending on the html type
3597 */
3598 private static function formatCustomValue($field, $value) {
3599 if (CRM_Core_BAO_CustomField::isSerialized($field)) {
3600 $value = CRM_Utils_Array::explodePadded($value);
3601
3602 if ($field['html_type'] === 'CheckBox') {
3603 $checkboxes = [];
3604 foreach (array_filter($value) as $item) {
3605 $checkboxes[$item] = 1;
3606 // CRM-2969 seems like we need this for QF style checkboxes in profile where its multiindexed
3607 $checkboxes["[{$item}]"] = 1;
3608 }
3609 return $checkboxes;
3610 }
3611 }
3612 return $value;
3613 }
3614
3615 }