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