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