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