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