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