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