Merge pull request #14086 from agh1/caseactqueryfix
[civicrm-core.git] / CRM / UF / Form / Field.php
... / ...
CommitLineData
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
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-2019
32 */
33
34/**
35 * Form to process actions on the field aspect of Custom.
36 */
37class CRM_UF_Form_Field extends CRM_Core_Form {
38
39 /**
40 * The uf group id saved to the session for an update.
41 *
42 * @var int
43 */
44 protected $_gid;
45
46 /**
47 * The field id, used when editing the field.
48 *
49 * @var int
50 */
51 protected $_id;
52
53 /**
54 * The set of fields that we can view/edit in the user field framework
55 *
56 * @var array
57 */
58 protected $_fields;
59
60 /**
61 * The title for field.
62 *
63 * @var int
64 */
65 protected $_title;
66
67 /**
68 * The set of fields sent to the select element.
69 *
70 * @var array
71 */
72 protected $_selectFields;
73
74 /**
75 * store fields with if locationtype exits status.
76 *
77 * @var array
78 */
79 protected $_hasLocationTypes;
80
81 /**
82 * Is this profile has searchable field.
83 * or is any field having in selector true.
84 *
85 * @var boolean.
86 */
87 protected $_hasSearchableORInSelector;
88
89 /**
90 * Set variables up before form is built.
91 *
92 * @return void
93 */
94 public function preProcess() {
95 $this->_gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this);
96 $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this);
97 if ($this->_gid) {
98 $this->_title = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $this->_gid, 'title');
99
100 $this->setPageTitle(ts('Profile Field'));
101
102 $url = CRM_Utils_System::url('civicrm/admin/uf/group/field',
103 "reset=1&action=browse&gid={$this->_gid}"
104 );
105
106 $session = CRM_Core_Session::singleton();
107 $session->pushUserContext($url);
108 $breadCrumb = [
109 [
110 'title' => ts('CiviCRM Profile Fields'),
111 'url' => $url,
112 ],
113 ];
114 CRM_Utils_System::appendBreadCrumb($breadCrumb);
115 }
116
117 $showBestResult = CRM_Utils_Request::retrieve('sbr', 'Positive', CRM_Core_DAO::$_nullArray);
118 if ($showBestResult) {
119 $this->assign('showBestResult', $showBestResult);
120 }
121
122 $this->_fields = CRM_Contact_BAO_Contact::importableFields('All', TRUE, TRUE, TRUE, TRUE, TRUE);
123 $this->_fields = array_merge(CRM_Activity_BAO_Activity::exportableFields('Activity'), $this->_fields);
124
125 //unset campaign related fields.
126 if (isset($this->_fields['activity_campaign_id'])) {
127 $this->_fields['activity_campaign_id']['title'] = ts('Campaign');
128 if (isset($this->_fields['activity_campaign'])) {
129 unset($this->_fields['activity_campaign']);
130 }
131 }
132
133 if (CRM_Core_Permission::access('CiviContribute')) {
134 $this->_fields = array_merge(CRM_Contribute_BAO_Contribution::getContributionFields(FALSE), $this->_fields);
135 $this->_fields = array_merge(CRM_Core_BAO_UFField::getContribBatchEntryFields(), $this->_fields);
136 }
137
138 if (CRM_Core_Permission::access('CiviMember')) {
139 $this->_fields = array_merge(CRM_Member_BAO_Membership::getMembershipFields(), $this->_fields);
140 }
141
142 if (CRM_Core_Permission::access('CiviEvent')) {
143 $this->_fields = array_merge(CRM_Event_BAO_Query::getParticipantFields(), $this->_fields);
144 }
145
146 if (CRM_Core_Permission::access('CiviCase')) {
147 $this->_fields = array_merge(CRM_Case_BAO_Query::getFields(), $this->_fields);
148 }
149
150 $this->_fields = array_merge($this->_fields, CRM_Contact_BAO_Query_Hook::singleton()->getFields());
151
152 $this->_selectFields = [];
153 foreach ($this->_fields as $name => $field) {
154 // lets skip note for now since we dont support it
155 if ($name == 'note') {
156 continue;
157 }
158 $this->_selectFields[$name] = $field['title'];
159 $this->_hasLocationTypes[$name] = CRM_Utils_Array::value('hasLocationType', $field);
160 }
161
162 // lets add group, tag and current_employer to this list
163 $this->_selectFields['group'] = ts('Group(s)');
164 $this->_selectFields['tag'] = ts('Tag(s)');
165 $this->_selectFields['current_employer'] = ts('Current Employer');
166 $this->_selectFields['phone_and_ext'] = ts('Phone and Extension');
167
168 //CRM-4363 check for in selector or searchable fields.
169 $this->_hasSearchableORInSelector = CRM_Core_BAO_UFField::checkSearchableORInSelector($this->_gid);
170
171 $this->assign('fieldId', $this->_id);
172 if ($this->_id) {
173 $fieldTitle = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFField', $this->_id, 'label');
174 $this->assign('fieldTitle', $fieldTitle);
175 }
176 }
177
178 /**
179 * Build the form object.
180 *
181 * @return void
182 */
183 public function buildQuickForm() {
184 if ($this->_action & CRM_Core_Action::DELETE) {
185 $this->addButtons([
186 [
187 'type' => 'next',
188 'name' => ts('Delete Profile Field'),
189 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
190 'isDefault' => TRUE,
191 ],
192 [
193 'type' => 'cancel',
194 'name' => ts('Cancel'),
195 ],
196 ]);
197 return;
198 }
199 $addressCustomFields = array_keys(CRM_Core_BAO_CustomField::getFieldsForImport('Address'));
200
201 if (isset($this->_id)) {
202 $params = ['id' => $this->_id];
203 CRM_Core_BAO_UFField::retrieve($params, $defaults);
204
205 // set it to null if so (avoids crappy E_NOTICE errors below
206 $defaults['location_type_id'] = CRM_Utils_Array::value('location_type_id', $defaults);
207
208 //CRM-20861 - Include custom fields defined for address to set its default location type to 0.
209 $specialFields = array_merge(CRM_Core_BAO_UFGroup::getLocationFields(), $addressCustomFields);
210 if (!$defaults['location_type_id'] &&
211 $defaults["field_type"] != "Formatting" &&
212 in_array($defaults['field_name'], $specialFields)
213 ) {
214 $defaults['location_type_id'] = 0;
215 }
216
217 $defaults['field_name'] = [
218 $defaults['field_type'],
219 ($defaults['field_type'] == "Formatting" ? "" : $defaults['field_name']),
220 ($defaults['field_name'] == "url") ? $defaults['website_type_id'] : $defaults['location_type_id'],
221 CRM_Utils_Array::value('phone_type_id', $defaults),
222 ];
223 $this->_gid = $defaults['uf_group_id'];
224 }
225 else {
226 $defaults['is_active'] = 1;
227 }
228
229 $otherModules = array_values(CRM_Core_BAO_UFGroup::getUFJoinRecord($this->_gid));
230 $this->assign('otherModules', $otherModules);
231
232 if ($this->_action & CRM_Core_Action::ADD) {
233 $fieldValues = ['uf_group_id' => $this->_gid];
234 $defaults['weight'] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_UFField', $fieldValues);
235 }
236
237 // lets trim all the whitespace
238 $this->applyFilter('__ALL__', 'trim');
239
240 //hidden field to catch the group id in profile
241 $this->add('hidden', 'group_id', $this->_gid);
242
243 //hidden field to catch the field id in profile
244 $this->add('hidden', 'field_id', $this->_id);
245
246 $fields = CRM_Core_BAO_UFField::getAvailableFields($this->_gid, $defaults);
247
248 $noSearchable = $hasWebsiteTypes = [];
249
250 foreach ($fields as $key => $value) {
251 foreach ($value as $key1 => $value1) {
252 //CRM-2676, replacing the conflict for same custom field name from different custom group.
253 if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key1)) {
254 $customGroupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $customFieldId, 'custom_group_id');
255 $customGroupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'title');
256 $this->_mapperFields[$key][$key1] = $value1['title'] . ' :: ' . $customGroupName;
257 if (in_array($key1, $addressCustomFields)) {
258 $noSearchable[] = $value1['title'] . ' :: ' . $customGroupName;
259 }
260 }
261 else {
262 $this->_mapperFields[$key][$key1] = $value1['title'];
263 }
264 $hasLocationTypes[$key][$key1] = CRM_Utils_Array::value('hasLocationType', $value1);
265 $hasWebsiteTypes[$key][$key1] = CRM_Utils_Array::value('hasWebsiteType', $value1);
266 // hide the 'is searchable' field for 'File' custom data
267 if (isset($value1['data_type']) &&
268 isset($value1['html_type']) &&
269 (($value1['data_type'] == 'File' && $value1['html_type'] == 'File')
270 || ($value1['data_type'] == 'Link' && $value1['html_type'] == 'Link')
271 )
272 ) {
273 if (!in_array($value1['title'], $noSearchable)) {
274 $noSearchable[] = $value1['title'];
275 }
276 }
277 }
278 }
279 $this->assign('noSearchable', $noSearchable);
280
281 $this->_location_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
282 $defaultLocationType = CRM_Core_BAO_LocationType::getDefault();
283 $this->_website_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Website', 'website_type_id');
284
285 /**
286 * FIXME: dirty hack to make the default option show up first. This
287 * avoids a mozilla browser bug with defaults on dynamically constructed
288 * selector widgets.
289 */
290 if ($defaultLocationType) {
291 $defaultLocation = $this->_location_types[$defaultLocationType->id];
292 unset($this->_location_types[$defaultLocationType->id]);
293 $this->_location_types = [
294 $defaultLocationType->id => $defaultLocation,
295 ] + $this->_location_types;
296 }
297
298 $this->_location_types = ['Primary'] + $this->_location_types;
299
300 // since we need a hierarchical list to display contact types & subtypes,
301 // this is what we going to display in first selector
302 $contactTypes = CRM_Contact_BAO_ContactType::getSelectElements(FALSE, FALSE);
303 unset($contactTypes['']);
304
305 $contactTypes = !empty($contactTypes) ? ['Contact' => 'Contacts'] + $contactTypes : [];
306 $sel1 = ['' => '- select -'] + $contactTypes;
307
308 if (!empty($fields['Activity'])) {
309 $sel1['Activity'] = 'Activity';
310 }
311
312 if (CRM_Core_Permission::access('CiviEvent')) {
313 $sel1['Participant'] = 'Participants';
314 }
315
316 if (!empty($fields['Contribution'])) {
317 $sel1['Contribution'] = 'Contributions';
318 }
319
320 if (!empty($fields['Membership'])) {
321 $sel1['Membership'] = 'Membership';
322 }
323
324 if (!empty($fields['Case'])) {
325 $sel1['Case'] = 'Case';
326 }
327
328 if (!empty($fields['Formatting'])) {
329 $sel1['Formatting'] = 'Formatting';
330 }
331
332 foreach ($sel1 as $key => $sel) {
333 if ($key) {
334 $sel2[$key] = $this->_mapperFields[$key];
335 }
336 }
337 $sel3[''] = NULL;
338 $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
339 ksort($phoneTypes);
340
341 foreach ($sel1 as $k => $sel) {
342 if ($k) {
343 foreach ($this->_location_types as $key => $value) {
344 $sel4[$k]['phone'][$key] = &$phoneTypes;
345 $sel4[$k]['phone_and_ext'][$key] = &$phoneTypes;
346 }
347 }
348 }
349
350 foreach ($sel1 as $k => $sel) {
351 if ($k) {
352 if (is_array($this->_mapperFields[$k])) {
353 foreach ($this->_mapperFields[$k] as $key => $value) {
354 if ($hasLocationTypes[$k][$key]) {
355 $sel3[$k][$key] = $this->_location_types;
356 }
357 elseif ($hasWebsiteTypes[$k][$key]) {
358 $sel3[$k][$key] = $this->_website_types;
359 }
360 else {
361 $sel3[$key] = NULL;
362 }
363 }
364 }
365 }
366 }
367
368 $this->_defaults = [];
369 $js = "<script type='text/javascript'>\n";
370 $formName = "document.{$this->_name}";
371
372 $alreadyMixProfile = FALSE;
373 if (CRM_Core_BAO_UFField::checkProfileType($this->_gid)) {
374 $alreadyMixProfile = TRUE;
375 }
376 $this->assign('alreadyMixProfile', $alreadyMixProfile);
377
378 $sel = &$this->addElement('hierselect', 'field_name', ts('Field Name'));
379
380 $formValues = $this->exportValues();
381
382 if (empty($formValues)) {
383 for ($k = 1; $k < 4; $k++) {
384 if (!isset($defaults['field_name'][$k])) {
385 $js .= "{$formName}['field_name[$k]'].style.display = 'none';\n";
386 }
387 }
388 }
389 else {
390 if (!empty($formValues['field_name'])) {
391 for ($key = 1; $key < 4; $key++) {
392 if (!isset($formValues['field_name'][$key])) {
393 $js .= "{$formName}['field_name[$key]'].style.display = 'none';\n";
394 }
395 else {
396 $js .= "{$formName}['field_name[$key]'].style.display = '';\n";
397 }
398 }
399 }
400 else {
401 for ($k = 1; $k < 4; $k++) {
402 if (!isset($defaults['field_name'][$k])) {
403 $js .= "{$formName}['field_name[$k]'].style.display = 'none';\n";
404 }
405 }
406 }
407 }
408
409 foreach ($sel2 as $k => $v) {
410 if (is_array($sel2[$k])) {
411 asort($sel2[$k]);
412 }
413 }
414
415 $sel->setOptions([$sel1, $sel2, $sel3, $sel4]);
416
417 // proper interpretation of spec in CRM-8732
418 if (!isset($this->_id) && in_array('Search Profile', $otherModules)) {
419 $defaults['visibility'] = 'Public Pages and Listings';
420 }
421
422 $js .= "</script>\n";
423 $this->assign('initHideBoxes', $js);
424
425 $this->add('select',
426 'visibility',
427 ts('Visibility'),
428 CRM_Core_SelectValues::ufVisibility(),
429 TRUE,
430 ['onChange' => "showHideSeletorSearch(this.value);"]
431 );
432
433 //CRM-4363
434 $js = ['onChange' => "mixProfile();"];
435 // should the field appear in selectors (as a column)?
436 $this->add('advcheckbox', 'in_selector', ts('Results Column?'), NULL, NULL, $js);
437 $this->add('advcheckbox', 'is_searchable', ts('Searchable?'), NULL, NULL, $js);
438
439 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_UFField');
440
441 // weight
442 $this->add('number', 'weight', ts('Order'), $attributes['weight'], TRUE);
443 $this->addRule('weight', ts('is a numeric field'), 'numeric');
444
445 $this->add('textarea', 'help_pre', ts('Field Pre Help'), $attributes['help_pre']);
446 $this->add('textarea', 'help_post', ts('Field Post Help'), $attributes['help_post']);
447
448 $this->add('advcheckbox', 'is_required', ts('Required?'));
449
450 $this->add('advcheckbox', 'is_multi_summary', ts('Include in multi-record listing?'));
451 $this->add('advcheckbox', 'is_active', ts('Active?'));
452 $this->add('advcheckbox', 'is_view', ts('View Only?'));
453
454 $this->add('text', 'label', ts('Field Label'), $attributes['label']);
455
456 $js = NULL;
457 if ($this->_hasSearchableORInSelector) {
458 $js = ['onclick' => "return verify( );"];
459 }
460
461 // add buttons
462 $this->addButtons([
463 [
464 'type' => 'next',
465 'name' => ts('Save'),
466 'isDefault' => TRUE,
467 'js' => $js,
468 ],
469 [
470 'type' => 'next',
471 'name' => ts('Save and New'),
472 'subName' => 'new',
473 'js' => $js,
474 ],
475 [
476 'type' => 'cancel',
477 'name' => ts('Cancel'),
478 ],
479 ]);
480
481 $this->addFormRule(['CRM_UF_Form_Field', 'formRule'], $this);
482
483 // if view mode pls freeze it with the done button.
484 if ($this->_action & CRM_Core_Action::VIEW) {
485 $this->freeze();
486 $this->addElement('button', 'done', ts('Done'),
487 ['onclick' => "location.href='civicrm/admin/uf/group/field?reset=1&action=browse&gid=" . $this->_gid . "'"]
488 );
489 }
490
491 $this->setDefaults($defaults);
492 }
493
494 /**
495 * Process the form.
496 *
497 * @return void
498 */
499 public function postProcess() {
500
501 if ($this->_action & CRM_Core_Action::DELETE) {
502 $fieldValues = ['uf_group_id' => $this->_gid];
503 CRM_Utils_Weight::delWeight('CRM_Core_DAO_UFField', $this->_id, $fieldValues);
504 $deleted = CRM_Core_BAO_UFField::del($this->_id);
505
506 //update group_type every time. CRM-3608
507 if ($this->_gid && $deleted) {
508 //get the profile type.
509 $fieldsType = CRM_Core_BAO_UFGroup::calculateGroupType($this->_gid, TRUE);
510 CRM_Core_BAO_UFGroup::updateGroupTypes($this->_gid, $fieldsType);
511 }
512
513 CRM_Core_Session::setStatus(ts('Selected Profile Field has been deleted.'), ts('Profile Field Deleted'), 'success');
514 return;
515 }
516
517 // store the submitted values in an array
518 $params = $this->controller->exportValues('Field');
519 $params['uf_group_id'] = $this->_gid;
520 if ($params['visibility'] == 'User and User Admin Only') {
521 $params['is_searchable'] = $params['in_selector'] = 0;
522 }
523
524 if ($this->_action & CRM_Core_Action::UPDATE) {
525 $params['id'] = $this->_id;
526 }
527
528 $name = NULL;
529 if (isset($params['field_name'][1]) && isset($this->_selectFields[$params['field_name'][1]])) {
530 // we dont get a name for a html formatting element
531 $name = $this->_selectFields[$params['field_name'][1]];
532 }
533
534 // If field_name is missing, it's formatting
535 $fieldName = CRM_Utils_Array::value(1, $params['field_name'], 'formatting');
536
537 //check for duplicate fields
538 $apiFormattedParams = $params;
539 $apiFormattedParams['field_type'] = $params['field_name'][0];
540 $apiFormattedParams['field_name'] = $fieldName;
541 if (!empty($params['field_name'][2])) {
542 if ($fieldName === 'url') {
543 $apiFormattedParams['website_type_id'] = $params['field_name'][2];
544 }
545 else {
546 $apiFormattedParams['location_type_id'] = $params['field_name'][2];
547 }
548 }
549 elseif ($params['field_name'][2] == 0) {
550 // 0 is Primary location type
551 $apiFormattedParams['location_type_id'] = NULL;
552 }
553 if (!empty($params['field_name'][3])) {
554 $apiFormattedParams['phone_type_id'] = $params['field_name'][3];
555 }
556
557 if ($apiFormattedParams['field_type'] != "Formatting" && CRM_Core_BAO_UFField::duplicateField($apiFormattedParams)) {
558 CRM_Core_Error::statusBounce(ts('The selected field already exists in this profile.'), NULL, ts('Field Not Added'));
559 }
560 else {
561 $apiFormattedParams['weight'] = CRM_Core_BAO_UFField::autoWeight($params);
562 civicrm_api3('UFField', 'create', $apiFormattedParams);
563
564 //reset other field is searchable and in selector settings, CRM-4363
565 if ($this->_hasSearchableORInSelector &&
566 in_array($apiFormattedParams['field_type'], ['Participant', 'Contribution', 'Membership', 'Activity', 'Case'])
567 ) {
568 CRM_Core_BAO_UFField::resetInSelectorANDSearchable($this->_gid);
569 }
570
571 $this->setMessageIfCountryNotAboveState($fieldName, CRM_Utils_Array::value('location_type_id', $apiFormattedParams), $apiFormattedParams['weight'], $apiFormattedParams['uf_group_id']);
572
573 CRM_Core_Session::setStatus(ts('Your CiviCRM Profile Field \'%1\' has been saved to \'%2\'.',
574 [1 => $name, 2 => $this->_title]
575 ), ts('Profile Field Saved'), 'success');
576 }
577 $buttonName = $this->controller->getButtonName();
578
579 $session = CRM_Core_Session::singleton();
580 if ($buttonName == $this->getButtonName('next', 'new')) {
581 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field/add',
582 "reset=1&action=add&gid={$this->_gid}"
583 ));
584 }
585 else {
586 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field',
587 "reset=1&action=browse&gid={$this->_gid}"
588 ));
589 }
590 }
591
592 /**
593 * Validation rule for subtype.
594 *
595 * @param string $fieldType
596 * Type of field.
597 * @param array $groupType
598 * Contains all groupTypes.
599 * @param array $errors
600 * List of errors to be posted back to the form.
601 */
602 public static function formRuleSubType($fieldType, $groupType, &$errors) {
603 if (in_array($fieldType, [
604 'Participant',
605 'Contribution',
606 'Membership',
607 'Activity',
608 ])) {
609 $individualSubTypes = CRM_Contact_BAO_ContactType::subTypes('Individual');
610 foreach ($groupType as $value) {
611 if (!in_array($value, $individualSubTypes) &&
612 !in_array($value, [
613 'Participant',
614 'Contribution',
615 'Membership',
616 'Individual',
617 'Contact',
618 'Activity',
619 'Formatting',
620 ])
621 ) {
622 $errors['field_name'] = ts('Cannot add or update profile field "%1" with combination of Household or Organization or any subtypes of Household or Organization.', [1 => $fieldType]);
623 break;
624 }
625 }
626 }
627 else {
628 $basicType = CRM_Contact_BAO_ContactType::getBasicType($groupType);
629 if ($basicType) {
630 if (!is_array($basicType)) {
631 $basicType = [$basicType];
632 }
633 if (!in_array($fieldType, $basicType) && $fieldType != 'Contact') {
634 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of subtype other than "%1".',
635 [1 => $fieldType]
636 );
637 }
638 }
639 }
640 }
641
642 /**
643 * Validation rule for custom data extends entity column values.
644 *
645 * @param Object $customField
646 * Custom field.
647 * @param int $gid
648 * Group Id.
649 * @param string $fieldType
650 * Group type of the field.
651 * @param array $errors
652 * Collect errors.
653 *
654 * @return array
655 * list of errors to be posted back to the form
656 */
657 public static function formRuleCustomDataExtentColumnValue($customField, $gid, $fieldType, &$errors) {
658 // fix me : check object $customField
659 if (in_array($fieldType, [
660 'Participant',
661 'Contribution',
662 'Membership',
663 'Activity',
664 'Case',
665 ])) {
666 $params = ['id' => $customField->custom_group_id];
667 $customGroup = [];
668 CRM_Core_BAO_CustomGroup::retrieve($params, $customGroup);
669 if (($fieldType != CRM_Utils_Array::value('extends', $customGroup)) || empty($customGroup['extends_entity_column_value'])) {
670 return $errors;
671 }
672
673 $extendsColumnValues = [];
674 foreach (explode(CRM_Core_DAO::VALUE_SEPARATOR, $customGroup['extends_entity_column_value']) as $val) {
675 if ($val) {
676 $extendsColumnValues[] = $val;
677 }
678 }
679
680 if (empty($extendsColumnValues)) {
681 return $errors;
682 }
683
684 $fieldTypeValues = CRM_Core_BAO_UFGroup::groupTypeValues($gid, $fieldType);
685 if (empty($fieldTypeValues[$fieldType])) {
686 return $errors;
687 }
688
689 $disallowedTypes = array_diff($extendsColumnValues, $fieldTypeValues[$fieldType]);
690 if (!empty($disallowedTypes)) {
691 $errors['field_name'] = ts('Profile is already having custom fields extending different group types, you can not add or update this custom field.');
692 }
693 }
694 }
695
696 /**
697 * Validation rule to prevent multiple fields of primary location type within the same communication type.
698 *
699 * @param array $fields
700 * Submitted fields.
701 * @param string $profileFieldName
702 * Group Id.
703 * @param array $groupFields
704 * List of fields already in the group.
705 * @param array $errors
706 * Collect errors.
707 *
708 */
709 public static function formRulePrimaryCheck($fields, $profileFieldName, $groupFields, &$errors) {
710 //FIXME: This may need to also apply to website fields if they are refactored to allow more than one per profile
711 $checkPrimary = ['phone' => 'civicrm_phone.phone', 'phone_and_ext' => 'civicrm_phone.phone'];
712 $whereCheck = NULL;
713 $primaryOfSameTypeFound = NULL;
714 $fieldID = empty($fields['field_id']) ? 0 : $fields['field_id'];
715 // Is this a primary location type field of interest
716 if (array_key_exists($profileFieldName, $checkPrimary)) {
717 $whereCheck = $checkPrimary[$profileFieldName];
718 }
719 $potentialLocationType = CRM_Utils_Array::value(2, $fields['field_name']);
720
721 if ($whereCheck && $potentialLocationType == 0) {
722 $primaryOfSameTypeFound = '';
723
724 foreach ($groupFields as $groupField) {
725 // if it is a phone
726 if ($groupField['where'] == $whereCheck && is_null($groupField['location_type_id']) && $groupField['field_id'] != $fieldID) {
727 $primaryOfSameTypeFound = $groupField['title'];
728 break;
729 }
730 }
731 if ($primaryOfSameTypeFound) {
732 $errors['field_name'] = ts('You have already added a primary location field of this type: %1', [1 => $primaryOfSameTypeFound]);
733 }
734 }
735 }
736
737 /**
738 * Global validation rules for the form.
739 *
740 * @param array $fields
741 * Posted values of the form.
742 *
743 * @param $files
744 * @param $self
745 *
746 * @return array
747 * list of errors to be posted back to the form
748 */
749 public static function formRule($fields, $files, $self) {
750 $is_required = CRM_Utils_Array::value('is_required', $fields, FALSE);
751 $is_registration = CRM_Utils_Array::value('is_registration', $fields, FALSE);
752 $is_view = CRM_Utils_Array::value('is_view', $fields, FALSE);
753 $in_selector = CRM_Utils_Array::value('in_selector', $fields, FALSE);
754 $is_active = CRM_Utils_Array::value('is_active', $fields, FALSE);
755
756 $errors = [];
757 if ($is_view && $is_registration) {
758 $errors['is_registration'] = ts('View Only cannot be selected if this field is to be included on the registration form');
759 }
760 if ($is_view && $is_required) {
761 $errors['is_view'] = ts('A View Only field cannot be required');
762 }
763
764 $entityName = $fields['field_name'][0];
765 if (!$entityName) {
766 $errors['field_name'] = ts('Please select a field name');
767 }
768
769 if ($in_selector && in_array($entityName, [
770 'Contribution',
771 'Participant',
772 'Membership',
773 'Activity',
774 ])
775 ) {
776 $errors['in_selector'] = ts("'Results Column' cannot be checked for %1 fields.", [1 => $entityName]);
777 }
778
779 $isCustomField = FALSE;
780 $profileFieldName = CRM_Utils_Array::value(1, $fields['field_name']);
781 if ($profileFieldName) {
782 //get custom field id
783 $customFieldId = explode('_', $profileFieldName);
784 if ($customFieldId[0] == 'custom') {
785 $customField = new CRM_Core_DAO_CustomField();
786 $customField->id = $customFieldId[1];
787 $customField->find(TRUE);
788 $isCustomField = TRUE;
789 if (!empty($fields['field_id']) && !$customField->is_active && $is_active) {
790 $errors['field_name'] = ts('Cannot set this field "Active" since the selected custom field is disabled.');
791 }
792
793 //check if profile already has a different multi-record custom set field configured
794 $customGroupId = CRM_Core_BAO_CustomField::isMultiRecordField($profileFieldName);
795 if ($customGroupId) {
796 if ($profileMultiRecordCustomGid = CRM_Core_BAO_UFField::checkMultiRecordFieldExists($self->_gid)) {
797 if ($customGroupId != $profileMultiRecordCustomGid) {
798 $errors['field_name'] = ts("You cannot configure multi-record custom fields belonging to different custom sets in one profile");
799 }
800 }
801 }
802 }
803 }
804
805 // Get list of fields already in the group
806 $groupFields = CRM_Core_BAO_UFGroup::getFields($fields['group_id'], FALSE, NULL, NULL, NULL, TRUE, NULL, TRUE);
807 // Check if we already added a primary field of the same communication type
808 self::formRulePrimaryCheck($fields, $profileFieldName, $groupFields, $errors);
809
810 //check profile is configured for double option process
811 //adding group field, email field should be present in the group
812 //fixed for issue CRM-2861 & CRM-4153
813 if (CRM_Core_BAO_UFGroup::isProfileDoubleOptin()) {
814 if (CRM_Utils_Array::value(1, $fields['field_name']) == 'group') {
815 $dao = new CRM_Core_BAO_UFField();
816 $dao->uf_group_id = $fields['group_id'];
817 $dao->find();
818 $emailField = FALSE;
819 while ($dao->fetch()) {
820 //check email field is present in the group
821 if ($dao->field_name == 'email') {
822 $emailField = TRUE;
823 break;
824 }
825 }
826
827 if (!$emailField) {
828 $disableSettingURL = CRM_Utils_System::url(
829 'civicrm/admin/setting/preferences/mailing',
830 'reset=1'
831 );
832
833 $errors['field_name'] = ts('Your site is currently configured to require double-opt in when users join (subscribe) to Group(s) via a Profile form. In this mode, you need to include an Email field in a Profile BEFORE you can add the Group(s) field. This ensures that an opt-in confirmation email can be sent. Your site administrator can disable double opt-in on the civimail admin settings: <em>%1</em>', [1 => $disableSettingURL]);
834 }
835 }
836 }
837
838 //fix for CRM-3037
839 $fieldType = $fields['field_name'][0];
840
841 //get the group type.
842 $groupType = CRM_Core_BAO_UFGroup::calculateGroupType($self->_gid, FALSE, CRM_Utils_Array::value('field_id', $fields));
843
844 switch ($fieldType) {
845 case 'Contact':
846 self::formRuleSubType($fieldType, $groupType, $errors);
847 break;
848
849 case 'Individual':
850 if (in_array('Activity', $groupType) ||
851 in_array('Household', $groupType) ||
852 in_array('Organization', $groupType)
853 ) {
854
855 //CRM-7603 - need to support activity + individual.
856 //$errors['field_name'] =
857 //ts( 'Cannot add or update profile field type Individual with combination of Household or Organization or Activity' );
858 if (in_array('Household', $groupType) ||
859 in_array('Organization', $groupType)
860 ) {
861 $errors['field_name'] = ts('Cannot add or update profile field type Individual with combination of Household or Organization');
862 }
863 }
864 else {
865 self::formRuleSubType($fieldType, $groupType, $errors);
866 }
867 break;
868
869 case 'Household':
870 if (in_array('Activity', $groupType) || in_array('Individual', $groupType) || in_array('Organization', $groupType)) {
871 $errors['field_name'] = ts('Cannot add or update profile field type Household with combination of Individual or Organization or Activity');
872 }
873 else {
874 self::formRuleSubType($fieldType, $groupType, $errors);
875 }
876 break;
877
878 case 'Organization':
879 if (in_array('Activity', $groupType) || in_array('Household', $groupType) || in_array('Individual', $groupType)) {
880 $errors['field_name'] = ts('Cannot add or update profile field type Organization with combination of Household or Individual or Activity');
881 }
882 else {
883 self::formRuleSubType($fieldType, $groupType, $errors);
884 }
885 break;
886
887 case 'Activity':
888 if (in_array('Individual', $groupType) ||
889 in_array('Membership', $groupType) ||
890 in_array('Contribution', $groupType) ||
891 in_array('Organization', $groupType) ||
892 in_array('Household', $groupType) ||
893 in_array('Participant', $groupType)
894 ) {
895
896 //CRM-7603 - need to support activity + contact type.
897 //$errors['field_name'] =
898 //ts( 'Cannot add or update profile field type Activity with combination Participant or Membership or Contribution or Household or Organization or Individual' );
899 if (in_array('Membership', $groupType) ||
900 in_array('Contribution', $groupType) ||
901 in_array('Participant', $groupType)
902 ) {
903 $errors['field_name'] = ts('Cannot add or update profile field type Activity with combination Participant or Membership or Contribution');
904 }
905 }
906 else {
907 self::formRuleSubType($fieldType, $groupType, $errors);
908 }
909
910 if ($isCustomField && !isset($errors['field_name'])) {
911 self::formRuleCustomDataExtentColumnValue($customField, $self->_gid, $fieldType, $errors);
912 }
913 break;
914
915 case 'Participant':
916 if (in_array('Membership', $groupType) || in_array('Contribution', $groupType)
917 || in_array('Organization', $groupType) || in_array('Household', $groupType) || in_array('Activity', $groupType)
918 ) {
919 $errors['field_name'] = ts('Cannot add or update profile field type Participant with combination of Activity or Membership or Contribution or Household or Organization.');
920 }
921 else {
922 self::formRuleSubType($fieldType, $groupType, $errors);
923 }
924 break;
925
926 case 'Contribution':
927 //special case where in we allow contribution + oganization fields, for on behalf feature
928 $profileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup',
929 'on_behalf_organization', 'id', 'name'
930 );
931
932 if (in_array('Participant', $groupType) || in_array('Membership', $groupType)
933 || ($profileId != $self->_gid && in_array('Organization', $groupType)) || in_array('Household', $groupType) || in_array('Activity', $groupType)
934 ) {
935 $errors['field_name'] = ts('Cannot add or update profile field type Contribution with combination of Activity or Membership or Participant or Household or Organization');
936 }
937 else {
938 self::formRuleSubType($fieldType, $groupType, $errors);
939 }
940 break;
941
942 case 'Membership':
943 //special case where in we allow contribution + oganization fields, for on behalf feature
944 $profileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup',
945 'on_behalf_organization', 'id', 'name'
946 );
947
948 if (in_array('Participant', $groupType) || in_array('Contribution', $groupType)
949 || ($profileId != $self->_gid && in_array('Organization', $groupType)) || in_array('Household', $groupType) || in_array('Activity', $groupType)
950 ) {
951 $errors['field_name'] = ts('Cannot add or update profile field type Membership with combination of Activity or Participant or Contribution or Household or Organization');
952 }
953 else {
954 self::formRuleSubType($fieldType, $groupType, $errors);
955 }
956 break;
957
958 default:
959 $profileType = CRM_Core_BAO_UFField::getProfileType($fields['group_id'], TRUE, FALSE, TRUE);
960 if (CRM_Contact_BAO_ContactType::isaSubType($fieldType)) {
961 if (CRM_Contact_BAO_ContactType::isaSubType($profileType)) {
962 if ($fieldType != $profileType) {
963 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', [
964 1 => $fieldType,
965 2 => $profileType,
966 ]);
967 }
968 }
969 else {
970 $basicType = CRM_Contact_BAO_ContactType::getBasicType($fieldType);
971 if ($profileType &&
972 $profileType != $basicType &&
973 $profileType != 'Contact'
974 ) {
975 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', [
976 1 => $fieldType,
977 2 => $profileType,
978 ]);
979 }
980 }
981 }
982 elseif (
983 CRM_Utils_Array::value(1, $fields['field_name']) == 'contact_sub_type' &&
984 !in_array($profileType, ['Individual', 'Household', 'Organization']) &&
985 !in_array($profileType, CRM_Contact_BAO_ContactType::subTypes())
986 ) {
987 $errors['field_name'] = ts('Cannot add or update profile field Contact Subtype as profile type is not one of Individual, Household or Organization.');
988 }
989 }
990 return empty($errors) ? TRUE : $errors;
991 }
992
993 /**
994 * Set a message warning the user about putting country first to render states, if required.
995 *
996 * @param string $fieldName
997 * @param int $locationTypeID
998 * @param int $weight
999 * @param int $ufGroupID
1000 */
1001 protected function setMessageIfCountryNotAboveState($fieldName, $locationTypeID, $weight, $ufGroupID) {
1002 $message = ts('For best results, the Country field should precede the State-Province field in your Profile form. You can use the up and down arrows on field listing page for this profile to change the order of these fields or manually edit weight for Country/State-Province Field.');
1003
1004 if (in_array($fieldName, [
1005 'country',
1006 'state_province',
1007 ]) && count(CRM_Core_Config::singleton()->countryLimit) > 1
1008 ) {
1009 // get state or country field weight if exists
1010 $ufFieldDAO = new CRM_Core_DAO_UFField();
1011 $ufFieldDAO->field_name = ($fieldName == 'state_province' ? 'country' : 'state_province');
1012 $ufFieldDAO->location_type_id = $locationTypeID;
1013 $ufFieldDAO->uf_group_id = $ufGroupID;
1014
1015 if ($ufFieldDAO->find(TRUE)) {
1016 if ($ufFieldDAO->field_name == 'country' && $ufFieldDAO->weight > $weight) {
1017 CRM_Core_Session::setStatus($message);
1018 }
1019 elseif ($ufFieldDAO->field_name == 'state_province' && $ufFieldDAO->weight < $weight) {
1020 CRM_Core_Session::setStatus($message);
1021 }
1022 }
1023 }
1024 }
1025
1026}