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