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