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