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