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