Check the billing fields returned by each payment processor (rather than a fixed...
[civicrm-core.git] / CRM / Core / BAO / UFField.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
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 |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 */
17
18/**
8eedd10a 19 * This class contains function for UFField.
6a488035
TO
20 */
21class CRM_Core_BAO_UFField extends CRM_Core_DAO_UFField {
22
23 /**
fe482240 24 * Batch entry fields.
518fa0ee 25 * @var array
6a488035
TO
26 */
27 private static $_contriBatchEntryFields = NULL;
28 private static $_memberBatchEntryFields = NULL;
29
c8ec6bbf
SB
30 /**
31 * Create UFField object.
32 *
33 * @param array $params
34 * Array per getfields metadata.
35 *
36 * @return \CRM_Core_BAO_UFField
37 * @throws \API_Exception
38 */
aa9732b9 39 public static function create($params) {
9c1bc317 40 $id = $params['id'] ?? NULL;
c8ec6bbf 41
41e85062
SL
42 $op = empty($id) ? 'create' : 'edit';
43 CRM_Utils_Hook::pre('UFField', $op, $id, $params);
aa9732b9
CW
44 // Merge in data from existing field
45 if (!empty($id)) {
46 $UFField = new CRM_Core_BAO_UFField();
47 $UFField->id = $params['id'];
48 if ($UFField->find(TRUE)) {
49 $defaults = $UFField->toArray();
50 // This will be calculated based on field name
51 unset($defaults['field_type']);
52 $params += $defaults;
53 }
54 else {
55 throw new API_Exception("UFFIeld id {$params['id']} not found.");
56 }
57 }
c8ec6bbf 58
aa9732b9
CW
59 // Validate field_name
60 if (strpos($params['field_name'], 'formatting') !== 0 && !CRM_Core_BAO_UFField::isValidFieldName($params['field_name'])) {
c8ec6bbf
SB
61 throw new API_Exception('The field_name is not valid');
62 }
63
aa9732b9
CW
64 // Supply default label if not set
65 if (empty($id) && !isset($params['label'])) {
66 $params['label'] = self::getAvailableFieldTitles()[$params['field_name']];
c8ec6bbf
SB
67 }
68
aa9732b9
CW
69 // Supply field_type if not set
70 if (empty($params['field_type']) && strpos($params['field_name'], 'formatting') !== 0) {
71 $params['field_type'] = CRM_Utils_Array::pathGet(self::getAvailableFieldsFlat(), [$params['field_name'], 'field_type']);
72 }
73 elseif (empty($params['field_type'])) {
74 $params['field_type'] = 'Formatting';
75 }
76
77 // Generate unique name for formatting fields
78 if ($params['field_name'] === 'formatting') {
79 $params['field_name'] = 'formatting_' . substr(uniqid(), -4);
c8ec6bbf 80 }
c8ec6bbf 81
aa9732b9 82 if (self::duplicateField($params)) {
c8ec6bbf
SB
83 throw new API_Exception("The field was not added. It already exists in this profile.");
84 }
85
c8ec6bbf
SB
86 //@todo why is this even optional? Surely weight should just be 'managed' ??
87 if (CRM_Utils_Array::value('option.autoweight', $params, TRUE)) {
88 $params['weight'] = CRM_Core_BAO_UFField::autoWeight($params);
89 }
aa9732b9
CW
90
91 // Set values for uf field properties and save
78580003
SB
92 $ufField = new CRM_Core_DAO_UFField();
93 $ufField->copyValues($params);
78580003 94
aa9732b9
CW
95 if ($params['field_name'] == 'url') {
96 $ufField->location_type_id = 'null';
78580003
SB
97 }
98 else {
aa9732b9 99 $ufField->website_type_id = 'null';
78580003 100 }
aa9732b9
CW
101 if (!strstr($params['field_name'], 'phone')) {
102 $ufField->phone_type_id = 'null';
78580003
SB
103 }
104
78580003 105 $ufField->save();
c8ec6bbf 106
aa9732b9
CW
107 $fieldsType = CRM_Core_BAO_UFGroup::calculateGroupType($ufField->uf_group_id, TRUE);
108 CRM_Core_BAO_UFGroup::updateGroupTypes($ufField->uf_group_id, $fieldsType);
c8ec6bbf 109
41e85062
SL
110 CRM_Utils_Hook::post('UFField', $op, $ufField->id, $ufField);
111
be2fb01f 112 civicrm_api3('profile', 'getfields', ['cache_clear' => TRUE]);
c8ec6bbf
SB
113 return $ufField;
114 }
115
6a488035 116 /**
fe482240 117 * Fetch object based on array of properties.
6a488035 118 *
6a0b768e
TO
119 * @param array $params
120 * (reference ) an assoc array of name/value pairs.
121 * @param array $defaults
122 * (reference ) an assoc array to hold the flattened values.
6a488035 123 *
16b10e64 124 * @return CRM_Core_BAO_UFField
6a488035 125 */
00be9182 126 public static function retrieve(&$params, &$defaults) {
6a488035
TO
127 return CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_UFField', $params, $defaults);
128 }
129
6a488035 130 /**
fe482240 131 * Update the is_active flag in the db.
6a488035 132 *
6a0b768e
TO
133 * @param int $id
134 * Id of the database record.
135 * @param bool $is_active
136 * Value we want to set the is_active field.
6a488035 137 *
8a4fede3 138 * @return bool
139 * true if we found and updated the object, else false
6a488035 140 */
00be9182 141 public static function setIsActive($id, $is_active) {
6a488035
TO
142 //check if custom data profile field is disabled
143 if ($is_active) {
144 if (CRM_Core_BAO_UFField::checkUFStatus($id)) {
145 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_UFField', $id, 'is_active', $is_active);
146 }
147 else {
148 CRM_Core_Session::setStatus(ts('Cannot enable this UF field since the used custom field is disabled.'), ts('Check Custom Field'), 'error');
149 }
150 }
151 else {
152 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_UFField', $id, 'is_active', $is_active);
153 }
154 }
155
156 /**
157 * Delete the profile Field.
158 *
6a0b768e
TO
159 * @param int $id
160 * Field Id.
6a488035 161 *
608e6658 162 * @return bool
6a488035 163 *
6a488035
TO
164 */
165 public static function del($id) {
166 //delete field field
167 $field = new CRM_Core_DAO_UFField();
168 $field->id = $id;
169 $field->delete();
170 return TRUE;
171 }
172
173 /**
fe482240 174 * Check duplicate for duplicate field in a group.
6a488035 175 *
6a0b768e
TO
176 * @param array $params
177 * An associative array with field and values.
6a488035 178 *
b99bd217 179 * @return bool
6a488035 180 */
b99bd217 181 public static function duplicateField($params) {
6a488035 182 $ufField = new CRM_Core_DAO_UFField();
9c1bc317
CW
183 $ufField->uf_group_id = $params['uf_group_id'] ?? NULL;
184 $ufField->field_type = $params['field_type'] ?? NULL;
185 $ufField->field_name = $params['field_name'] ?? NULL;
186 $ufField->website_type_id = $params['website_type_id'] ?? NULL;
2e74ff55
SP
187 if (is_null(CRM_Utils_Array::value('location_type_id', $params, ''))) {
188 // primary location type have NULL value in DB
189 $ufField->whereAdd("location_type_id IS NULL");
190 }
191 else {
9c1bc317 192 $ufField->location_type_id = $params['location_type_id'] ?? NULL;
2e74ff55 193 }
9c1bc317 194 $ufField->phone_type_id = $params['phone_type_id'] ?? NULL;
6a488035 195
b99bd217
ASB
196 if (!empty($params['id'])) {
197 $ufField->whereAdd("id <> " . $params['id']);
6a488035
TO
198 }
199
aa9732b9 200 return (bool) $ufField->find(TRUE);
6a488035
TO
201 }
202
f3c39657 203 /**
ad37ac8e 204 * Does profile consists of a multi-record custom field.
205 *
206 * @param int $gId
207 *
208 * @return bool
6a488035
TO
209 */
210 public static function checkMultiRecordFieldExists($gId) {
211 $queryString = "SELECT f.field_name
212 FROM civicrm_uf_field f, civicrm_uf_group g
213 WHERE f.uf_group_id = g.id
214 AND g.id = %1 AND f.field_name LIKE 'custom%'";
be2fb01f 215 $p = [1 => [$gId, 'Integer']];
6a488035 216 $dao = CRM_Core_DAO::executeQuery($queryString, $p);
be2fb01f 217 $customFieldIds = [];
6a488035
TO
218 $isMultiRecordFieldPresent = FALSE;
219 while ($dao->fetch()) {
220 if ($customId = CRM_Core_BAO_CustomField::getKeyID($dao->field_name)) {
221 if (is_numeric($customId)) {
222 $customFieldIds[] = $customId;
223 }
224 }
225 }
226
227 if (!empty($customFieldIds) && count($customFieldIds) == 1) {
228 $customFieldId = array_pop($customFieldIds);
229 $isMultiRecordFieldPresent = CRM_Core_BAO_CustomField::isMultiRecordField($customFieldId);
230 }
231 elseif (count($customFieldIds) > 1) {
232 $customFieldIds = implode(", ", $customFieldIds);
233 $queryString = "
234 SELECT cg.id as cgId
235 FROM civicrm_custom_group cg
cbb7c7e0 236 INNER JOIN civicrm_custom_field cf
6a488035
TO
237 ON cg.id = cf.custom_group_id
238WHERE cf.id IN (" . $customFieldIds . ") AND is_multiple = 1 LIMIT 0,1";
239
240 $dao = CRM_Core_DAO::executeQuery($queryString);
241 if ($dao->fetch()) {
242 $isMultiRecordFieldPresent = ($dao->cgId) ? $dao->cgId : FALSE;
243 }
244 }
245
246 return $isMultiRecordFieldPresent;
247 }
248
6a488035 249 /**
fe482240 250 * Automatically determine one weight and modify others.
6a488035 251 *
6a0b768e
TO
252 * @param array $params
253 * UFField record, e.g. with 'weight', 'uf_group_id', and 'field_id'.
6a488035
TO
254 * @return int
255 */
256 public static function autoWeight($params) {
257 // fix for CRM-316
258 $oldWeight = NULL;
259
aa9732b9
CW
260 if (!empty($params['field_id']) || !empty($params['id'])) {
261 $oldWeight = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFField', !empty($params['id']) ? $params['id'] : $params['field_id'], 'weight', 'id');
6a488035 262 }
aa9732b9 263 $fieldValues = ['uf_group_id' => !empty($params['uf_group_id']) ? $params['uf_group_id'] : $params['group_id']];
44cc86bd 264 return CRM_Utils_Weight::updateOtherWeights('CRM_Core_DAO_UFField', $oldWeight, CRM_Utils_Array::value('weight', $params, 0), $fieldValues);
6a488035
TO
265 }
266
267 /**
100fef9d 268 * Enable/disable profile field given a custom field id
6a488035 269 *
6a0b768e
TO
270 * @param int $customFieldId
271 * Custom field id.
272 * @param bool $is_active
273 * Set the is_active field.
6a488035 274 */
00be9182 275 public static function setUFField($customFieldId, $is_active) {
a43adbad 276 // Find the profile id given custom field.
6a488035
TO
277 $ufField = new CRM_Core_DAO_UFField();
278 $ufField->field_name = "custom_" . $customFieldId;
279
280 $ufField->find();
281 while ($ufField->fetch()) {
a43adbad 282 // Enable/ disable profile.
6a488035
TO
283 CRM_Core_BAO_UFField::setIsActive($ufField->id, $is_active);
284 }
285 }
286
287 /**
100fef9d 288 * Copy existing profile fields to
6a488035
TO
289 * new profile from the already built profile
290 *
6a0b768e
TO
291 * @param int $old_id
292 * From which we need to copy.
293 * @param bool $new_id
294 * In which to copy.
6a488035 295 */
00be9182 296 public static function copy($old_id, $new_id) {
6a488035
TO
297 $ufField = new CRM_Core_DAO_UFField();
298 $ufField->uf_group_id = $old_id;
299 $ufField->find();
300 while ($ufField->fetch()) {
301 //copy the field records as it is on new ufgroup id
302 $ufField->uf_group_id = $new_id;
303 $ufField->id = NULL;
304 $ufField->save();
305 }
306 }
307
308 /**
fe482240 309 * Delete profile field given a custom field.
6a488035 310 *
6a0b768e
TO
311 * @param int $customFieldId
312 * ID of the custom field to be deleted.
6a488035 313 */
00be9182 314 public static function delUFField($customFieldId) {
6a488035
TO
315 //find the profile id given custom field id
316 $ufField = new CRM_Core_DAO_UFField();
317 $ufField->field_name = "custom_" . $customFieldId;
318
319 $ufField->find();
320 while ($ufField->fetch()) {
321 //enable/ disable profile
322 CRM_Core_BAO_UFField::del($ufField->id);
323 }
324 }
325
326 /**
100fef9d 327 * Enable/disable profile field given a custom group id
6a488035 328 *
6a0b768e
TO
329 * @param int $customGroupId
330 * Custom group id.
331 * @param bool $is_active
332 * Value we want to set the is_active field.
6a488035 333 */
00be9182 334 public static function setUFFieldStatus($customGroupId, $is_active) {
6a488035
TO
335 //find the profile id given custom group id
336 $queryString = "SELECT civicrm_custom_field.id as custom_field_id
337 FROM civicrm_custom_field, civicrm_custom_group
338 WHERE civicrm_custom_field.custom_group_id = civicrm_custom_group.id
339 AND civicrm_custom_group.id = %1";
be2fb01f 340 $p = [1 => [$customGroupId, 'Integer']];
6a488035
TO
341 $dao = CRM_Core_DAO::executeQuery($queryString, $p);
342
343 while ($dao->fetch()) {
a43adbad 344 // Enable/ disable profile.
6a488035
TO
345 CRM_Core_BAO_UFField::setUFField($dao->custom_field_id, $is_active);
346 }
347 }
348
349 /**
fe482240 350 * Check the status of custom field used in uf fields.
6a488035 351 *
c490a46a 352 * @param int $UFFieldId
f3c39657 353 *
608e6658 354 * @return bool
a6c01b45 355 * false if custom field are disabled else true
6a488035 356 */
00be9182 357 public static function checkUFStatus($UFFieldId) {
6a488035
TO
358 $fieldName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFField', $UFFieldId, 'field_name');
359 // return if field is not a custom field
360 if (!$customFieldId = CRM_Core_BAO_CustomField::getKeyID($fieldName)) {
361 return TRUE;
362 }
363
364 $customField = new CRM_Core_DAO_CustomField();
365 $customField->id = $customFieldId;
366 // if uf field is custom field
367 if ($customField->find(TRUE)) {
368 if (!$customField->is_active) {
369 return FALSE;
370 }
371 else {
372 return TRUE;
373 }
374 }
375 }
376
44cc86bd 377 /**
100fef9d 378 * Find out whether given profile group using Activity
6a488035 379 * Profile fields with contact fields
ea3ddccf 380 *
381 * @param int $ufGroupId
382 *
383 * @return bool
6a488035 384 */
00be9182 385 public static function checkContactActivityProfileType($ufGroupId) {
6a488035
TO
386 $ufGroup = new CRM_Core_DAO_UFGroup();
387 $ufGroup->id = $ufGroupId;
388 $ufGroup->find(TRUE);
389
390 return self::checkContactActivityProfileTypeByGroupType($ufGroup->group_type);
391 }
392
393 /**
394 * FIXME say 10 ha
395 * @param $ufGroupType
396 * @return bool
397 */
398 public static function checkContactActivityProfileTypeByGroupType($ufGroupType) {
be2fb01f 399 $profileTypes = [];
6a488035
TO
400 if ($ufGroupType) {
401 $typeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $ufGroupType);
402 $profileTypes = explode(',', $typeParts[0]);
403 }
404
405 if (empty($profileTypes)) {
406 return FALSE;
407 }
be2fb01f 408 $components = ['Contribution', 'Participant', 'Membership'];
6a488035
TO
409 if (!in_array('Activity', $profileTypes)) {
410 return FALSE;
411 }
412 elseif (count($profileTypes) == 1) {
413 return FALSE;
414 }
415
416 if ($index = array_search('Contact', $profileTypes)) {
417 unset($profileTypes[$index]);
418 if (count($profileTypes) == 1) {
419 return TRUE;
420 }
421 }
422
be2fb01f 423 $contactTypes = ['Individual', 'Household', 'Organization'];
6a488035
TO
424 $subTypes = CRM_Contact_BAO_ContactType::subTypes();
425
426 $profileTypeComponent = array_intersect($components, $profileTypes);
427 if (!empty($profileTypeComponent) ||
428 count(array_intersect($contactTypes, $profileTypes)) > 1 ||
429 count(array_intersect($subTypes, $profileTypes)) > 1
430 ) {
431 return FALSE;
432 }
433
434 return TRUE;
435 }
436
f3c39657 437 /**
100fef9d 438 * Find out whether given profile group uses $required
44cc86bd 439 * and/or $optional profile types
cbb7c7e0 440 *
6a0b768e
TO
441 * @param int $ufGroupId
442 * Profile id.
443 * @param array $required
444 * Array of types those are required.
445 * @param array $optional
446 * Array of types those are optional.
6a488035 447 *
608e6658 448 * @return bool
6a488035 449 */
00be9182 450 public static function checkValidProfileType($ufGroupId, $required, $optional = NULL) {
6a488035 451 if (!is_array($required) || empty($required)) {
608e6658 452 return FALSE;
6a488035
TO
453 }
454
455 $ufGroup = new CRM_Core_DAO_UFGroup();
456 $ufGroup->id = $ufGroupId;
457 $ufGroup->find(TRUE);
458
be2fb01f 459 $profileTypes = [];
6a488035
TO
460 if ($ufGroup->group_type) {
461 $typeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $ufGroup->group_type);
462 $profileTypes = explode(',', $typeParts[0]);
463 }
464
465 if (empty($profileTypes)) {
466 return FALSE;
467 }
468
469 $valid = TRUE;
470 foreach ($required as $key => $val) {
471 if (!in_array($val, $profileTypes)) {
472 $valid = FALSE;
473 break;
474 }
475 }
476
477 if ($valid && is_array($optional)) {
478 foreach ($optional as $key => $val) {
479 if (in_array($val, $profileTypes)) {
480 $valid = TRUE;
481 break;
482 }
483 }
484 }
485
486 return $valid;
487 }
488
489 /**
c490a46a 490 * Check for mix profile fields (eg: individual + other contact types)
6a488035 491 *
c490a46a 492 * @param int $ufGroupId
f3c39657 493 *
72b3a70c
CW
494 * @return bool
495 * true for mix profile else false
6a488035 496 */
00be9182 497 public static function checkProfileType($ufGroupId) {
6a488035
TO
498 $ufGroup = new CRM_Core_DAO_UFGroup();
499 $ufGroup->id = $ufGroupId;
500 $ufGroup->find(TRUE);
501
be2fb01f 502 $profileTypes = [];
6a488035
TO
503 if ($ufGroup->group_type) {
504 $typeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $ufGroup->group_type);
505 $profileTypes = explode(',', $typeParts[0]);
506 }
507
508 //early return if new profile.
509 if (empty($profileTypes)) {
510 return FALSE;
511 }
512
513 //we need to unset Contact
514 if (count($profileTypes) > 1) {
515 $index = array_search('Contact', $profileTypes);
516 if ($index !== FALSE) {
517 unset($profileTypes[$index]);
518 }
519 }
520
521 // suppress any subtypes if present
522 CRM_Contact_BAO_ContactType::suppressSubTypes($profileTypes);
523
be2fb01f
CW
524 $contactTypes = ['Contact', 'Individual', 'Household', 'Organization'];
525 $components = ['Contribution', 'Participant', 'Membership', 'Activity'];
526 $fields = [];
6a488035
TO
527
528 // check for mix profile condition
529 if (count($profileTypes) > 1) {
530 //check the there are any components include in profile
531 foreach ($components as $value) {
532 if (in_array($value, $profileTypes)) {
533 return TRUE;
534 }
535 }
536 //check if there are more than one contact types included in profile
537 if (count($profileTypes) > 1) {
538 return TRUE;
539 }
540 }
541 elseif (count($profileTypes) == 1) {
542 // note for subtype case count would be zero
543 $profileTypes = array_values($profileTypes);
544 if (!in_array($profileTypes[0], $contactTypes)) {
545 return TRUE;
546 }
547 }
548
549 return FALSE;
550 }
551
552 /**
100fef9d 553 * Get the profile type (eg: individual/organization/household)
6a488035 554 *
6a0b768e
TO
555 * @param int $ufGroupId
556 * Uf group id.
557 * @param bool $returnMixType
558 * This is true, then field type of mix profile field is returned.
559 * @param bool $onlyPure
560 * True if only pure profiles are required.
f3c39657 561 *
562 * @param bool $skipComponentType
6a488035 563 *
a6c01b45
CW
564 * @return string
565 * profile group_type
6a488035 566 *
6a488035 567 */
00be9182 568 public static function getProfileType($ufGroupId, $returnMixType = TRUE, $onlyPure = FALSE, $skipComponentType = FALSE) {
6a488035
TO
569 $ufGroup = new CRM_Core_DAO_UFGroup();
570 $ufGroup->id = $ufGroupId;
571 $ufGroup->is_active = 1;
572
573 $ufGroup->find(TRUE);
574 return self::calculateProfileType($ufGroup->group_type, $returnMixType, $onlyPure, $skipComponentType);
575 }
576
577 /**
100fef9d 578 * Get the profile type (eg: individual/organization/household)
6a488035 579 *
c490a46a 580 * @param string $ufGroupType
6a0b768e
TO
581 * @param bool $returnMixType
582 * This is true, then field type of mix profile field is returned.
583 * @param bool $onlyPure
584 * True if only pure profiles are required.
f3c39657 585 * @param bool $skipComponentType
6a488035 586 *
608e6658 587 * @return string profile group_type
6a488035 588 *
6a488035 589 */
2aa397bc 590 public static function calculateProfileType($ufGroupType, $returnMixType = TRUE, $onlyPure = FALSE, $skipComponentType = FALSE) {
6a488035 591 // profile types
be2fb01f 592 $contactTypes = ['Contact', 'Individual', 'Household', 'Organization'];
6a488035 593 $subTypes = CRM_Contact_BAO_ContactType::subTypes();
be2fb01f 594 $components = ['Contribution', 'Participant', 'Membership', 'Activity'];
6a488035 595
be2fb01f 596 $profileTypes = [];
6a488035
TO
597 if ($ufGroupType) {
598 $typeParts = explode(CRM_Core_DAO::VALUE_SEPARATOR, $ufGroupType);
599 $profileTypes = explode(',', $typeParts[0]);
600 }
601
602 if ($onlyPure) {
603 if (count($profileTypes) == 1) {
604 return $profileTypes[0];
605 }
606 else {
607 return NULL;
608 }
609 }
610
611 //we need to unset Contact
612 if (count($profileTypes) > 1) {
613 $index = array_search('Contact', $profileTypes);
614 if ($index !== FALSE) {
615 unset($profileTypes[$index]);
616 }
617 }
618
619 $profileType = $mixProfileType = NULL;
620
621 // this case handles pure profile
622 if (count($profileTypes) == 1) {
623 $profileType = array_pop($profileTypes);
624 }
625 else {
626 //check the there are any components include in profile
be2fb01f 627 $componentCount = [];
6a488035
TO
628 foreach ($components as $value) {
629 if (in_array($value, $profileTypes)) {
630 $componentCount[] = $value;
631 }
632 }
633
634 //check contact type included in profile
be2fb01f 635 $contactTypeCount = [];
6a488035
TO
636 foreach ($contactTypes as $value) {
637 if (in_array($value, $profileTypes)) {
638 $contactTypeCount[] = $value;
639 }
640 }
641 // subtype counter
be2fb01f 642 $subTypeCount = [];
6a488035
TO
643 foreach ($subTypes as $value) {
644 if (in_array($value, $profileTypes)) {
645 $subTypeCount[] = $value;
646 }
647 }
648 if (!$skipComponentType && count($componentCount) == 1) {
649 $profileType = $componentCount[0];
650 }
651 elseif (count($componentCount) > 1) {
652 $mixProfileType = $componentCount[1];
653 }
654 elseif (count($subTypeCount) == 1) {
655 $profileType = $subTypeCount[0];
656 }
657 elseif (count($contactTypeCount) == 1) {
658 $profileType = $contactTypeCount[0];
659 }
660 elseif (count($subTypeCount) > 1) {
661 // this is mix subtype profiles
662 $mixProfileType = $subTypeCount[1];
663 }
664 elseif (count($contactTypeCount) > 1) {
665 // this is mix contact profiles
666 $mixProfileType = $contactTypeCount[1];
667 }
668 }
669
670 if ($mixProfileType) {
671 if ($returnMixType) {
672 return $mixProfileType;
673 }
674 else {
675 return 'Mixed';
676 }
677 }
678 else {
679 return $profileType;
680 }
681 }
682
683 /**
100fef9d 684 * Check for mix profiles groups (eg: individual + other contact types)
6a488035 685 *
f3c39657 686 * @param $ctype
687 *
72b3a70c
CW
688 * @return bool
689 * true for mix profile group else false
6a488035 690 */
00be9182 691 public static function checkProfileGroupType($ctype) {
6a488035
TO
692 $ufGroup = new CRM_Core_DAO_UFGroup();
693
694 $query = "
695SELECT ufg.id as id
696 FROM civicrm_uf_group as ufg, civicrm_uf_join as ufj
697 WHERE ufg.id = ufj.uf_group_id
698 AND ufj.module = 'User Registration'
699 AND ufg.is_active = 1 ";
700
701 $ufGroup = CRM_Core_DAO::executeQuery($query);
702
be2fb01f
CW
703 $fields = [];
704 $validProfiles = ['Individual', 'Organization', 'Household', 'Contribution'];
6a488035
TO
705 while ($ufGroup->fetch()) {
706 $profileType = self::getProfileType($ufGroup->id);
707 if (in_array($profileType, $validProfiles)) {
708 continue;
709 }
710 elseif ($profileType) {
711 return FALSE;
712 }
713 }
714
715 return TRUE;
716 }
717
718 /**
c490a46a 719 * Check for searchable or in selector field for given profile.
6a488035 720 *
c490a46a 721 * @param int $profileID
f3c39657 722 *
608e6658 723 * @return bool
6a488035 724 */
00be9182 725 public static function checkSearchableORInSelector($profileID) {
6a488035
TO
726 $result = FALSE;
727 if (!$profileID) {
728 return $result;
729 }
730
731 $query = "
cbb7c7e0 732SELECT id
733 From civicrm_uf_field
6a488035
TO
734 WHERE (in_selector = 1 OR is_searchable = 1)
735 AND uf_group_id = {$profileID}";
736
737 $ufFields = CRM_Core_DAO::executeQuery($query);
738 while ($ufFields->fetch()) {
739 $result = TRUE;
740 break;
741 }
742
743 return $result;
744 }
745
746 /**
c490a46a 747 * Reset In selector and is searchable values for given $profileID.
6a488035 748 *
c490a46a 749 * @param int $profileID
6a488035 750 */
00be9182 751 public function resetInSelectorANDSearchable($profileID) {
6a488035
TO
752 if (!$profileID) {
753 return;
754 }
755 $query = "UPDATE civicrm_uf_field SET in_selector = 0, is_searchable = 0 WHERE uf_group_id = {$profileID}";
756 CRM_Core_DAO::executeQuery($query);
757 }
758
759 /**
760 * Add fields to $profileAddressFields as appropriate.
761 * profileAddressFields is assigned to the template to tell it
762 * what fields are in the profile address
763 * that potentially should be copied to the Billing fields
764 * we want to give precedence to
765 * 1) Billing &
766 * 2) then Primary designated as 'Primary
767 * 3) location_type is primary
768 * 4) if none of these apply then it just uses the first one
769 *
770 * as this will be used to
771 * transfer profile address data to billing fields
772 * http://issues.civicrm.org/jira/browse/CRM-5869
f3c39657 773 *
6a0b768e
TO
774 * @param string $key
775 * Field key - e.g. street_address-Primary, first_name.
776 * @param array $profileAddressFields
777 * Array of profile fields that relate to address fields.
778 * @param array $profileFilter
779 * Filter to apply to profile fields - expected usage is to only fill based on.
16b10e64 780 * the bottom profile per CRM-13726
9cff1ee2
CC
781 * @param array $paymentProcessorBillingFields
782 * Array of billing fields required by the payment processor.
1ad7255f 783 *
a6c01b45
CW
784 * @return bool
785 * Can the address block be hidden safe in the knowledge all fields are elsewhere collected (see CRM-15118)
6a488035 786 */
9cff1ee2 787 public static function assignAddressField($key, &$profileAddressFields, $profileFilter, $paymentProcessorBillingFields = NULL) {
6a488035
TO
788 $billing_id = CRM_Core_BAO_LocationType::getBilling();
789 list($prefixName, $index) = CRM_Utils_System::explode('-', $key, 2);
790
fddfc93f 791 $profileFields = civicrm_api3('uf_field', 'get', array_merge($profileFilter,
be2fb01f 792 [
353ffa53
TO
793 'is_active' => 1,
794 'return' => 'field_name, is_required',
be2fb01f 795 'options' => [
353ffa53 796 'limit' => 0,
be2fb01f
CW
797 ],
798 ]
fddfc93f 799 ));
6a488035 800 //check for valid fields ( fields that are present in billing block )
9cff1ee2
CC
801 if (!empty($paymentProcessorBillingFields)) {
802 $validBillingFields = $paymentProcessorBillingFields;
803 }
804 else {
805 $validBillingFields = [
806 'first_name',
807 'middle_name',
808 'last_name',
809 'street_address',
810 'supplemental_address_1',
811 'city',
812 'state_province',
813 'postal_code',
814 'country',
815 ];
816 }
be2fb01f
CW
817 $requiredBillingFields = array_diff($validBillingFields, ['middle_name', 'supplemental_address_1']);
818 $validProfileFields = [];
819 $requiredProfileFields = [];
6a488035 820
7d613bb7 821 foreach ($profileFields['values'] as $field) {
22e263ad 822 if (in_array($field['field_name'], $validBillingFields)) {
7d613bb7
DG
823 $validProfileFields[] = $field['field_name'];
824 }
de6c59ca 825 if (!empty($field['is_required'])) {
1ad7255f
DG
826 $requiredProfileFields[] = $field['field_name'];
827 }
7d613bb7
DG
828 }
829
481a74f4 830 if (!in_array($prefixName, $validProfileFields)) {
608e6658 831 return FALSE;
6a488035
TO
832 }
833
834 if (!empty($index) && (
353ffa53 835 // it's empty so we set it OR
1817b503 836 !CRM_Utils_Array::value($prefixName, $profileAddressFields)
6a488035
TO
837 //we are dealing with billing id (precedence)
838 || $index == $billing_id
839 // we are dealing with primary & billing not set
840 || ($index == 'Primary' && $profileAddressFields[$prefixName] != $billing_id)
841 || ($index == CRM_Core_BAO_LocationType::getDefault()->id
353ffa53
TO
842 && $profileAddressFields[$prefixName] != $billing_id
843 && $profileAddressFields[$prefixName] != 'Primary'
844 )
6a488035 845 )
6a488035
TO
846 ) {
847 $profileAddressFields[$prefixName] = $index;
848 }
d75f2f47
EM
849
850 $potentiallyMissingRequiredFields = array_diff($requiredBillingFields, $requiredProfileFields);
9cff1ee2 851 $billingProfileIsHideable = empty($potentiallyMissingRequiredFields);
353ffa53 852 CRM_Core_Resources::singleton()
9cff1ee2
CC
853 ->addSetting(['billing' => ['billingProfileIsHideable' => $billingProfileIsHideable]]);
854 return $billingProfileIsHideable;
6a488035
TO
855 }
856
857 /**
fe482240 858 * Get a list of fields which can be added to profiles.
6a488035 859 *
353ffa53
TO
860 * @param int $gid : UF group ID
861 * @param array $defaults : Form defaults
6a488035 862 * @return array, multidimensional; e.g. $result['FieldGroup']['field_name']['label']
6a488035 863 */
be2fb01f
CW
864 public static function getAvailableFields($gid = NULL, $defaults = []) {
865 $fields = [
866 'Contact' => [],
6a488035
TO
867 'Individual' => CRM_Contact_BAO_Contact::importableFields('Individual', FALSE, FALSE, TRUE, TRUE, TRUE),
868 'Household' => CRM_Contact_BAO_Contact::importableFields('Household', FALSE, FALSE, TRUE, TRUE, TRUE),
869 'Organization' => CRM_Contact_BAO_Contact::importableFields('Organization', FALSE, FALSE, TRUE, TRUE, TRUE),
be2fb01f 870 ];
6a488035 871
61750d67
DS
872 // include hook injected fields
873 $fields['Contact'] = array_merge($fields['Contact'], CRM_Contact_BAO_Query_Hook::singleton()->getFields());
874
6a488035 875 // add current employer for individuals
be2fb01f 876 $fields['Individual']['current_employer'] = [
6a488035
TO
877 'name' => 'organization_name',
878 'title' => ts('Current Employer'),
be2fb01f 879 ];
6a488035
TO
880
881 $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
882 'address_options', TRUE, NULL, TRUE
883 );
884
dcd27695 885 if (empty($addressOptions['county'])) {
6a488035
TO
886 unset($fields['Individual']['county'], $fields['Household']['county'], $fields['Organization']['county']);
887 }
888
889 // break out common contact fields array CRM-3037.
890 // from a UI perspective this makes very little sense
891 foreach ($fields['Individual'] as $key => $value) {
892 if (!empty($fields['Household'][$key]) && !empty($fields['Organization'][$key])) {
893 $fields['Contact'][$key] = $value;
894 unset($fields['Individual'][$key], $fields['Household'][$key], $fields['Organization'][$key]);
895 }
896 }
897
898 // Internal field not exposed to forms
899 unset($fields['Contact']['contact_type']);
deb17853 900 unset($fields['Contact']['master_id']);
6a488035
TO
901
902 // convert phone extension in to psedo-field phone + phone extension
903 //unset extension
904 unset($fields['Contact']['phone_ext']);
905 //add psedo field
be2fb01f 906 $fields['Contact']['phone_and_ext'] = [
6a488035
TO
907 'name' => 'phone_and_ext',
908 'title' => ts('Phone and Extension'),
909 'hasLocationType' => 1,
be2fb01f 910 ];
6a488035
TO
911
912 // include Subtypes For Profile
913 $subTypes = CRM_Contact_BAO_ContactType::subTypeInfo();
914 foreach ($subTypes as $name => $val) {
915 //custom fields for sub type
3550c9f6 916 $subTypeFields = CRM_Core_BAO_CustomField::getFieldsForImport($name, FALSE, FALSE, FALSE, TRUE, TRUE);
6a488035
TO
917 if (array_key_exists($val['parent'], $fields)) {
918 $fields[$name] = $fields[$val['parent']] + $subTypeFields;
919 }
920 else {
921 $fields[$name] = $subTypeFields;
922 }
923 }
924
925 if (CRM_Core_Permission::access('CiviContribute')) {
926 $contribFields = CRM_Contribute_BAO_Contribution::getContributionFields(FALSE);
927 if (!empty($contribFields)) {
928 unset($contribFields['is_test']);
929 unset($contribFields['is_pay_later']);
930 unset($contribFields['contribution_id']);
be2fb01f 931 $contribFields['contribution_note'] = [
6a488035
TO
932 'name' => 'contribution_note',
933 'title' => ts('Contribution Note'),
be2fb01f 934 ];
752ddf35 935 $fields['Contribution'] = array_merge($contribFields, self::getContribBatchEntryFields());
6a488035
TO
936 }
937 }
938
939 if (CRM_Core_Permission::access('CiviEvent')) {
940 $participantFields = CRM_Event_BAO_Query::getParticipantFields();
941 if ($participantFields) {
942 // Remove fields not supported by profiles
943 CRM_Utils_Array::remove($participantFields,
944 'external_identifier',
945 'event_id',
946 'participant_contact_id',
947 'participant_role_id',
948 'participant_status_id',
949 'participant_is_test',
950 'participant_fee_level',
951 'participant_id',
952 'participant_is_pay_later',
953 'participant_campaign'
954 );
955 if (isset($participantFields['participant_campaign_id'])) {
956 $participantFields['participant_campaign_id']['title'] = ts('Campaign');
957 }
958 $fields['Participant'] = $participantFields;
959 }
960 }
961
962 if (CRM_Core_Permission::access('CiviMember')) {
963 $membershipFields = CRM_Member_BAO_Membership::getMembershipFields();
964 // Remove fields not supported by profiles
965 CRM_Utils_Array::remove($membershipFields,
966 'membership_id',
967 'membership_type_id',
968 'member_is_test',
969 'is_override',
8f67d99a 970 'member_is_override',
e136f704 971 'status_override_end_date',
6a488035
TO
972 'status_id',
973 'member_is_pay_later'
974 );
975 if ($gid && CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $gid, 'name') == 'membership_batch_entry') {
976 $fields['Membership'] = array_merge($membershipFields, self::getMemberBatchEntryFields());
977 }
978 else {
979 $fields['Membership'] = $membershipFields;
980 }
981 }
982
1cb28d5d 983 if (CRM_Core_Permission::access('CiviCase')) {
984 $caseFields = CRM_Case_BAO_Query::getFields(TRUE);
985 $caseFields = array_merge($caseFields, CRM_Core_BAO_CustomField::getFieldsForImport('Case'));
986 if ($caseFields) {
987 // Remove fields not supported by profiles
988 CRM_Utils_Array::remove($caseFields,
989 'case_id',
990 'case_type',
1cb28d5d 991 'case_role',
1cb28d5d 992 'case_deleted'
993 );
994 }
995 $fields['Case'] = $caseFields;
996 }
997
6a488035
TO
998 $activityFields = CRM_Activity_BAO_Activity::getProfileFields();
999 if ($activityFields) {
1000 // campaign related fields.
1001 if (isset($activityFields['activity_campaign_id'])) {
1002 $activityFields['activity_campaign_id']['title'] = ts('Campaign');
1003 }
1004 $fields['Activity'] = $activityFields;
1005 }
1006
be2fb01f 1007 $fields['Formatting']['format_free_html_' . rand(1000, 9999)] = [
6a488035
TO
1008 'name' => 'free_html',
1009 'import' => FALSE,
1010 'export' => FALSE,
1011 'title' => 'Free HTML',
be2fb01f 1012 ];
6a488035
TO
1013
1014 // Sort by title
1015 foreach ($fields as &$values) {
1016 $values = CRM_Utils_Array::crmArraySortByField($values, 'title');
1017 }
1018
1019 //group selected and unwanted fields list
be2fb01f
CW
1020 $ufFields = $gid ? CRM_Core_BAO_UFGroup::getFields($gid, FALSE, NULL, NULL, NULL, TRUE, NULL, TRUE) : [];
1021 $groupFieldList = array_merge($ufFields, [
6a488035
TO
1022 'note',
1023 'email_greeting_custom',
1024 'postal_greeting_custom',
1025 'addressee_custom',
1026 'id',
be2fb01f 1027 ]);
6a488035
TO
1028 //unset selected fields
1029 foreach ($groupFieldList as $key => $value) {
608e6658 1030 if (is_int($key)) {
6a488035
TO
1031 unset($fields['Individual'][$value], $fields['Household'][$value], $fields['Organization'][$value]);
1032 continue;
1033 }
1034 if (!empty($defaults['field_name'])
1035 && $defaults['field_name']['0'] == $value['field_type']
1036 && $defaults['field_name']['1'] == $key
1037 ) {
1038 continue;
1039 }
1040 unset($fields[$value['field_type']][$key]);
1041 }
1042
92f06cdd 1043 // Allow extensions to alter the array of entity => fields permissible in a CiviCRM Profile.
9fdf2f17 1044 CRM_Utils_Hook::alterUFFields($fields);
6a488035
TO
1045 return $fields;
1046 }
1047
1048 /**
fe482240 1049 * Get a list of fields which can be added to profiles.
6a488035 1050 *
f3c39657 1051 * @param bool $force
1052 *
ffb9cdec
CW
1053 * @return array
1054 * e.g. $result['field_name']['label']
6a488035
TO
1055 */
1056 public static function getAvailableFieldsFlat($force = FALSE) {
ffb9cdec
CW
1057 if (!isset(Civi::$statics['UFFieldsFlat']) || $force) {
1058 Civi::$statics['UFFieldsFlat'] = [];
1059 foreach (self::getAvailableFields() as $fieldType => $fields) {
1060 foreach ($fields as $fieldName => $field) {
1061 if (!isset(Civi::$statics['UFFieldsFlat'][$fieldName])) {
1062 $field['field_type'] = $fieldType;
1063 Civi::$statics['UFFieldsFlat'][$fieldName] = $field;
6a488035
TO
1064 }
1065 }
1066 }
1067 }
ffb9cdec
CW
1068 return Civi::$statics['UFFieldsFlat'];
1069 }
1070
1071 /**
1072 * Get a list of fields which can be added to profiles in the format [name => title]
1073 *
1074 * @return array
1075 */
1076 public static function getAvailableFieldTitles() {
1077 $fields = self::getAvailableFieldsFlat();
75c4fcec 1078 $fields['formatting'] = ['title' => ts('Formatting')];
ffb9cdec 1079 return CRM_Utils_Array::collect('title', $fields);
6a488035
TO
1080 }
1081
1082 /**
fe482240 1083 * Determine whether the given field_name is valid.
6a488035
TO
1084 *
1085 * @param string $fieldName
1086 * @return bool
1087 */
00be9182 1088 public static function isValidFieldName($fieldName) {
6a488035
TO
1089 $availableFields = CRM_Core_BAO_UFField::getAvailableFieldsFlat();
1090 return isset($availableFields[$fieldName]);
1091 }
1092
b5c2afd0
EM
1093 /**
1094 * @return array|null
1095 */
00be9182 1096 public static function getContribBatchEntryFields() {
6a488035 1097 if (self::$_contriBatchEntryFields === NULL) {
be2fb01f
CW
1098 self::$_contriBatchEntryFields = [
1099 'send_receipt' => [
6a488035
TO
1100 'name' => 'send_receipt',
1101 'title' => ts('Send Receipt'),
be2fb01f
CW
1102 ],
1103 'soft_credit' => [
6a488035
TO
1104 'name' => 'soft_credit',
1105 'title' => ts('Soft Credit'),
be2fb01f
CW
1106 ],
1107 'soft_credit_type' => [
752ddf35
N
1108 'name' => 'soft_credit_type',
1109 'title' => ts('Soft Credit Type'),
be2fb01f
CW
1110 ],
1111 'product_name' => [
6a488035
TO
1112 'name' => 'product_name',
1113 'title' => ts('Premiums'),
be2fb01f
CW
1114 ],
1115 'contribution_note' => [
6a488035
TO
1116 'name' => 'contribution_note',
1117 'title' => ts('Contribution Note'),
be2fb01f
CW
1118 ],
1119 'contribution_soft_credit_pcp_id' => [
03ad81ae
AH
1120 'name' => 'contribution_soft_credit_pcp_id',
1121 'title' => ts('Personal Campaign Page'),
be2fb01f
CW
1122 ],
1123 ];
6a488035
TO
1124 }
1125 return self::$_contriBatchEntryFields;
1126 }
1127
b5c2afd0
EM
1128 /**
1129 * @return array|null
1130 */
6a488035
TO
1131 public static function getMemberBatchEntryFields() {
1132 if (self::$_memberBatchEntryFields === NULL) {
be2fb01f
CW
1133 self::$_memberBatchEntryFields = [
1134 'send_receipt' => [
6a488035
TO
1135 'name' => 'send_receipt',
1136 'title' => ts('Send Receipt'),
be2fb01f
CW
1137 ],
1138 'soft_credit' => [
6a488035
TO
1139 'name' => 'soft_credit',
1140 'title' => ts('Soft Credit'),
be2fb01f
CW
1141 ],
1142 'product_name' => [
6a488035
TO
1143 'name' => 'product_name',
1144 'title' => ts('Premiums'),
be2fb01f
CW
1145 ],
1146 'financial_type' => [
6a488035
TO
1147 'name' => 'financial_type',
1148 'title' => ts('Financial Type'),
be2fb01f
CW
1149 ],
1150 'total_amount' => [
6a488035
TO
1151 'name' => 'total_amount',
1152 'title' => ts('Total Amount'),
be2fb01f
CW
1153 ],
1154 'receive_date' => [
6a488035 1155 'name' => 'receive_date',
7bc6b5bb 1156 'title' => ts('Date Received'),
be2fb01f
CW
1157 ],
1158 'payment_instrument' => [
6a488035 1159 'name' => 'payment_instrument',
536f0e02 1160 'title' => ts('Payment Method'),
be2fb01f
CW
1161 ],
1162 'contribution_status_id' => [
6a488035
TO
1163 'name' => 'contribution_status_id',
1164 'title' => ts('Contribution Status'),
be2fb01f
CW
1165 ],
1166 'trxn_id' => [
47087bdc 1167 'name' => 'contribution_trxn_id',
1168 'title' => ts('Contribution Transaction ID'),
be2fb01f
CW
1169 ],
1170 ];
6a488035
TO
1171 }
1172 return self::$_memberBatchEntryFields;
1173 }
96025800 1174
6a488035 1175}