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