Merge pull request #17580 from samuelsov/dev/core#1670
[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();
470 $this->addElement('button', 'done', ts('Done'),
be2fb01f 471 ['onclick' => "location.href='civicrm/admin/uf/group/field?reset=1&action=browse&gid=" . $this->_gid . "'"]
6a488035
TO
472 );
473 }
474
6a488035
TO
475 $this->setDefaults($defaults);
476 }
477
478 /**
fe482240 479 * Process the form.
6a488035
TO
480 *
481 * @return void
6a488035
TO
482 */
483 public function postProcess() {
78d7cb6d 484
6a488035 485 if ($this->_action & CRM_Core_Action::DELETE) {
be2fb01f 486 $fieldValues = ['uf_group_id' => $this->_gid];
6a488035 487 CRM_Utils_Weight::delWeight('CRM_Core_DAO_UFField', $this->_id, $fieldValues);
353ffa53 488 $deleted = CRM_Core_BAO_UFField::del($this->_id);
6a488035
TO
489
490 //update group_type every time. CRM-3608
491 if ($this->_gid && $deleted) {
492 //get the profile type.
493 $fieldsType = CRM_Core_BAO_UFGroup::calculateGroupType($this->_gid, TRUE);
494 CRM_Core_BAO_UFGroup::updateGroupTypes($this->_gid, $fieldsType);
495 }
496
497 CRM_Core_Session::setStatus(ts('Selected Profile Field has been deleted.'), ts('Profile Field Deleted'), 'success');
498 return;
499 }
500
501 // store the submitted values in an array
502 $params = $this->controller->exportValues('Field');
78d7cb6d 503 $params['uf_group_id'] = $this->_gid;
6a488035
TO
504 if ($params['visibility'] == 'User and User Admin Only') {
505 $params['is_searchable'] = $params['in_selector'] = 0;
506 }
507
508 if ($this->_action & CRM_Core_Action::UPDATE) {
78d7cb6d 509 $params['id'] = $this->_id;
6a488035
TO
510 }
511
6a488035 512 $name = NULL;
f06c4974 513 if (isset($params['field_name'][1]) && isset($this->_selectFields[$params['field_name'][1]])) {
6a488035
TO
514 // we dont get a name for a html formatting element
515 $name = $this->_selectFields[$params['field_name'][1]];
516 }
517
aa9732b9
CW
518 // If field_name is missing, it's formatting
519 $fieldName = CRM_Utils_Array::value(1, $params['field_name'], 'formatting');
6a488035 520
6a488035 521 //check for duplicate fields
b99bd217
ASB
522 $apiFormattedParams = $params;
523 $apiFormattedParams['field_type'] = $params['field_name'][0];
78580003 524 $apiFormattedParams['field_name'] = $fieldName;
b99bd217 525 if (!empty($params['field_name'][2])) {
78580003 526 if ($fieldName === 'url') {
b99bd217
ASB
527 $apiFormattedParams['website_type_id'] = $params['field_name'][2];
528 }
529 else {
530 $apiFormattedParams['location_type_id'] = $params['field_name'][2];
531 }
532 }
2e74ff55
SP
533 elseif ($params['field_name'][2] == 0) {
534 // 0 is Primary location type
b0e240d9 535 $apiFormattedParams['location_type_id'] = '';
2e74ff55 536 }
b99bd217
ASB
537 if (!empty($params['field_name'][3])) {
538 $apiFormattedParams['phone_type_id'] = $params['field_name'][3];
539 }
540
541 if ($apiFormattedParams['field_type'] != "Formatting" && CRM_Core_BAO_UFField::duplicateField($apiFormattedParams)) {
542 CRM_Core_Error::statusBounce(ts('The selected field already exists in this profile.'), NULL, ts('Field Not Added'));
6a488035
TO
543 }
544 else {
78580003
SB
545 $apiFormattedParams['weight'] = CRM_Core_BAO_UFField::autoWeight($params);
546 civicrm_api3('UFField', 'create', $apiFormattedParams);
6a488035
TO
547
548 //reset other field is searchable and in selector settings, CRM-4363
549 if ($this->_hasSearchableORInSelector &&
be2fb01f 550 in_array($apiFormattedParams['field_type'], ['Participant', 'Contribution', 'Membership', 'Activity', 'Case'])
6a488035
TO
551 ) {
552 CRM_Core_BAO_UFField::resetInSelectorANDSearchable($this->_gid);
553 }
554
78580003 555 $this->setMessageIfCountryNotAboveState($fieldName, CRM_Utils_Array::value('location_type_id', $apiFormattedParams), $apiFormattedParams['weight'], $apiFormattedParams['uf_group_id']);
6a488035 556
6a488035 557 CRM_Core_Session::setStatus(ts('Your CiviCRM Profile Field \'%1\' has been saved to \'%2\'.',
be2fb01f 558 [1 => $name, 2 => $this->_title]
353ffa53 559 ), ts('Profile Field Saved'), 'success');
6a488035
TO
560 }
561 $buttonName = $this->controller->getButtonName();
562
563 $session = CRM_Core_Session::singleton();
564 if ($buttonName == $this->getButtonName('next', 'new')) {
6a488035 565 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field/add',
78580003 566 "reset=1&action=add&gid={$this->_gid}"
353ffa53 567 ));
6a488035
TO
568 }
569 else {
570 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field',
353ffa53
TO
571 "reset=1&action=browse&gid={$this->_gid}"
572 ));
6a488035
TO
573 }
574 }
575
576 /**
100fef9d 577 * Validation rule for subtype.
6a488035 578 *
5ce1712d
TO
579 * @param string $fieldType
580 * Type of field.
a1a2a83d
TO
581 * @param array $groupType
582 * Contains all groupTypes.
6a488035 583 * @param array $errors
a1a2a83d 584 * List of errors to be posted back to the form.
6a488035 585 */
a1a2a83d 586 public static function formRuleSubType($fieldType, $groupType, &$errors) {
be2fb01f 587 if (in_array($fieldType, [
353ffa53
TO
588 'Participant',
589 'Contribution',
590 'Membership',
a130e045 591 'Activity',
be2fb01f 592 ])) {
6a488035
TO
593 $individualSubTypes = CRM_Contact_BAO_ContactType::subTypes('Individual');
594 foreach ($groupType as $value) {
595 if (!in_array($value, $individualSubTypes) &&
be2fb01f 596 !in_array($value, [
353ffa53
TO
597 'Participant',
598 'Contribution',
599 'Membership',
600 'Individual',
601 'Contact',
602 'Activity',
8d172516 603 'Formatting',
be2fb01f 604 ])
6a488035 605 ) {
be2fb01f 606 $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
607 break;
608 }
609 }
610 }
611 else {
612 $basicType = CRM_Contact_BAO_ContactType::getBasicType($groupType);
613 if ($basicType) {
614 if (!is_array($basicType)) {
be2fb01f 615 $basicType = [$basicType];
6a488035 616 }
1be099c7 617 if (!in_array($fieldType, $basicType) && $fieldType != 'Contact') {
6a488035 618 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of subtype other than "%1".',
be2fb01f 619 [1 => $fieldType]
6a488035
TO
620 );
621 }
622 }
623 }
624 }
625
626 /**
100fef9d 627 * Validation rule for custom data extends entity column values.
6a488035 628 *
5ce1712d
TO
629 * @param Object $customField
630 * Custom field.
631 * @param int $gid
632 * Group Id.
633 * @param string $fieldType
634 * Group type of the field.
635 * @param array $errors
636 * Collect errors.
6a488035 637 *
a130e045 638 * @return array
a6c01b45 639 * list of errors to be posted back to the form
6a488035 640 */
00be9182 641 public static function formRuleCustomDataExtentColumnValue($customField, $gid, $fieldType, &$errors) {
6a488035 642 // fix me : check object $customField
be2fb01f 643 if (in_array($fieldType, [
353ffa53
TO
644 'Participant',
645 'Contribution',
646 'Membership',
647 'Activity',
a130e045 648 'Case',
be2fb01f
CW
649 ])) {
650 $params = ['id' => $customField->custom_group_id];
651 $customGroup = [];
6a488035 652 CRM_Core_BAO_CustomGroup::retrieve($params, $customGroup);
8cc574cf 653 if (($fieldType != CRM_Utils_Array::value('extends', $customGroup)) || empty($customGroup['extends_entity_column_value'])) {
6a488035
TO
654 return $errors;
655 }
656
be2fb01f 657 $extendsColumnValues = [];
6a488035
TO
658 foreach (explode(CRM_Core_DAO::VALUE_SEPARATOR, $customGroup['extends_entity_column_value']) as $val) {
659 if ($val) {
660 $extendsColumnValues[] = $val;
661 }
662 }
663
664 if (empty($extendsColumnValues)) {
665 return $errors;
666 }
667
668 $fieldTypeValues = CRM_Core_BAO_UFGroup::groupTypeValues($gid, $fieldType);
a7488080 669 if (empty($fieldTypeValues[$fieldType])) {
3655bea4 670 return $errors;
6a488035
TO
671 }
672
673 $disallowedTypes = array_diff($extendsColumnValues, $fieldTypeValues[$fieldType]);
674 if (!empty($disallowedTypes)) {
675 $errors['field_name'] = ts('Profile is already having custom fields extending different group types, you can not add or update this custom field.');
676 }
677 }
678 }
679
b29a9e66 680 /**
fdcc5d07 681 * Validation rule to prevent multiple fields of primary location type within the same communication type.
b29a9e66 682 *
5ce1712d
TO
683 * @param array $fields
684 * Submitted fields.
685 * @param string $profileFieldName
686 * Group Id.
687 * @param array $groupFields
688 * List of fields already in the group.
689 * @param array $errors
690 * Collect errors.
b29a9e66 691 *
b29a9e66 692 */
00be9182 693 public static function formRulePrimaryCheck($fields, $profileFieldName, $groupFields, &$errors) {
b29a9e66 694 //FIXME: This may need to also apply to website fields if they are refactored to allow more than one per profile
be2fb01f 695 $checkPrimary = ['phone' => 'civicrm_phone.phone', 'phone_and_ext' => 'civicrm_phone.phone'];
b29a9e66
LS
696 $whereCheck = NULL;
697 $primaryOfSameTypeFound = NULL;
fdcc5d07 698 $fieldID = empty($fields['field_id']) ? 0 : $fields['field_id'];
b29a9e66
LS
699 // Is this a primary location type field of interest
700 if (array_key_exists($profileFieldName, $checkPrimary)) {
701 $whereCheck = $checkPrimary[$profileFieldName];
702 }
9c1bc317 703 $potentialLocationType = $fields['field_name'][2] ?? NULL;
b29a9e66
LS
704
705 if ($whereCheck && $potentialLocationType == 0) {
706 $primaryOfSameTypeFound = '';
707
708 foreach ($groupFields as $groupField) {
709 // if it is a phone
9147c186 710 if ($groupField['where'] == $whereCheck && is_null($groupField['location_type_id']) && $groupField['field_id'] != $fieldID) {
b29a9e66 711 $primaryOfSameTypeFound = $groupField['title'];
fdcc5d07 712 break;
b29a9e66
LS
713 }
714 }
715 if ($primaryOfSameTypeFound) {
be2fb01f 716 $errors['field_name'] = ts('You have already added a primary location field of this type: %1', [1 => $primaryOfSameTypeFound]);
b29a9e66
LS
717 }
718 }
719 }
720
6a488035 721 /**
fe482240 722 * Global validation rules for the form.
6a488035 723 *
5ce1712d
TO
724 * @param array $fields
725 * Posted values of the form.
6a488035 726 *
77b97be7
EM
727 * @param $files
728 * @param $self
729 *
a6c01b45
CW
730 * @return array
731 * list of errors to be posted back to the form
6a488035 732 */
00be9182 733 public static function formRule($fields, $files, $self) {
353ffa53 734 $is_required = CRM_Utils_Array::value('is_required', $fields, FALSE);
6a488035 735 $is_registration = CRM_Utils_Array::value('is_registration', $fields, FALSE);
353ffa53
TO
736 $is_view = CRM_Utils_Array::value('is_view', $fields, FALSE);
737 $in_selector = CRM_Utils_Array::value('in_selector', $fields, FALSE);
738 $is_active = CRM_Utils_Array::value('is_active', $fields, FALSE);
6a488035 739
be2fb01f 740 $errors = [];
6a488035
TO
741 if ($is_view && $is_registration) {
742 $errors['is_registration'] = ts('View Only cannot be selected if this field is to be included on the registration form');
743 }
744 if ($is_view && $is_required) {
745 $errors['is_view'] = ts('A View Only field cannot be required');
746 }
747
b29a9e66
LS
748 $entityName = $fields['field_name'][0];
749 if (!$entityName) {
6a488035
TO
750 $errors['field_name'] = ts('Please select a field name');
751 }
752
be2fb01f 753 if ($in_selector && in_array($entityName, [
c5c263ca
AH
754 'Contribution',
755 'Participant',
756 'Membership',
757 'Activity',
be2fb01f 758 ])
353ffa53 759 ) {
be2fb01f 760 $errors['in_selector'] = ts("'Results Column' cannot be checked for %1 fields.", [1 => $entityName]);
6a488035
TO
761 }
762
763 $isCustomField = FALSE;
9c1bc317 764 $profileFieldName = $fields['field_name'][1] ?? NULL;
6a488035
TO
765 if ($profileFieldName) {
766 //get custom field id
767 $customFieldId = explode('_', $profileFieldName);
768 if ($customFieldId[0] == 'custom') {
769 $customField = new CRM_Core_DAO_CustomField();
770 $customField->id = $customFieldId[1];
771 $customField->find(TRUE);
772 $isCustomField = TRUE;
773 if (!empty($fields['field_id']) && !$customField->is_active && $is_active) {
774 $errors['field_name'] = ts('Cannot set this field "Active" since the selected custom field is disabled.');
775 }
776
777 //check if profile already has a different multi-record custom set field configured
778 $customGroupId = CRM_Core_BAO_CustomField::isMultiRecordField($profileFieldName);
779 if ($customGroupId) {
780 if ($profileMultiRecordCustomGid = CRM_Core_BAO_UFField::checkMultiRecordFieldExists($self->_gid)) {
781 if ($customGroupId != $profileMultiRecordCustomGid) {
782 $errors['field_name'] = ts("You cannot configure multi-record custom fields belonging to different custom sets in one profile");
783 }
784 }
785 }
786 }
787 }
788
b29a9e66
LS
789 // Get list of fields already in the group
790 $groupFields = CRM_Core_BAO_UFGroup::getFields($fields['group_id'], FALSE, NULL, NULL, NULL, TRUE, NULL, TRUE);
791 // Check if we already added a primary field of the same communication type
fdcc5d07 792 self::formRulePrimaryCheck($fields, $profileFieldName, $groupFields, $errors);
b29a9e66 793
6a488035
TO
794 //check profile is configured for double option process
795 //adding group field, email field should be present in the group
796 //fixed for issue CRM-2861 & CRM-4153
797 if (CRM_Core_BAO_UFGroup::isProfileDoubleOptin()) {
50c3e74b 798 if (CRM_Utils_Array::value(1, $fields['field_name']) == 'group') {
6a488035
TO
799 $dao = new CRM_Core_BAO_UFField();
800 $dao->uf_group_id = $fields['group_id'];
801 $dao->find();
802 $emailField = FALSE;
803 while ($dao->fetch()) {
804 //check email field is present in the group
805 if ($dao->field_name == 'email') {
806 $emailField = TRUE;
807 break;
808 }
809 }
810
811 if (!$emailField) {
812 $disableSettingURL = CRM_Utils_System::url(
813 'civicrm/admin/setting/preferences/mailing',
814 'reset=1'
815 );
816
be2fb01f 817 $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
818 }
819 }
820 }
821
822 //fix for CRM-3037
823 $fieldType = $fields['field_name'][0];
824
825 //get the group type.
826 $groupType = CRM_Core_BAO_UFGroup::calculateGroupType($self->_gid, FALSE, CRM_Utils_Array::value('field_id', $fields));
827
828 switch ($fieldType) {
829 case 'Contact':
830 self::formRuleSubType($fieldType, $groupType, $errors);
831 break;
832
833 case 'Individual':
834 if (in_array('Activity', $groupType) ||
835 in_array('Household', $groupType) ||
836 in_array('Organization', $groupType)
837 ) {
838
839 //CRM-7603 - need to support activity + individual.
840 //$errors['field_name'] =
841 //ts( 'Cannot add or update profile field type Individual with combination of Household or Organization or Activity' );
842 if (in_array('Household', $groupType) ||
843 in_array('Organization', $groupType)
844 ) {
845 $errors['field_name'] = ts('Cannot add or update profile field type Individual with combination of Household or Organization');
846 }
847 }
848 else {
849 self::formRuleSubType($fieldType, $groupType, $errors);
850 }
851 break;
852
853 case 'Household':
854 if (in_array('Activity', $groupType) || in_array('Individual', $groupType) || in_array('Organization', $groupType)) {
855 $errors['field_name'] = ts('Cannot add or update profile field type Household with combination of Individual or Organization or Activity');
856 }
857 else {
858 self::formRuleSubType($fieldType, $groupType, $errors);
859 }
860 break;
861
862 case 'Organization':
863 if (in_array('Activity', $groupType) || in_array('Household', $groupType) || in_array('Individual', $groupType)) {
864 $errors['field_name'] = ts('Cannot add or update profile field type Organization with combination of Household or Individual or Activity');
865 }
866 else {
867 self::formRuleSubType($fieldType, $groupType, $errors);
868 }
869 break;
870
871 case 'Activity':
872 if (in_array('Individual', $groupType) ||
873 in_array('Membership', $groupType) ||
874 in_array('Contribution', $groupType) ||
875 in_array('Organization', $groupType) ||
876 in_array('Household', $groupType) ||
877 in_array('Participant', $groupType)
878 ) {
879
880 //CRM-7603 - need to support activity + contact type.
881 //$errors['field_name'] =
882 //ts( 'Cannot add or update profile field type Activity with combination Participant or Membership or Contribution or Household or Organization or Individual' );
883 if (in_array('Membership', $groupType) ||
884 in_array('Contribution', $groupType) ||
885 in_array('Participant', $groupType)
886 ) {
887 $errors['field_name'] = ts('Cannot add or update profile field type Activity with combination Participant or Membership or Contribution');
888 }
889 }
890 else {
891 self::formRuleSubType($fieldType, $groupType, $errors);
892 }
893
894 if ($isCustomField && !isset($errors['field_name'])) {
895 self::formRuleCustomDataExtentColumnValue($customField, $self->_gid, $fieldType, $errors);
896 }
897 break;
898
899 case 'Participant':
900 if (in_array('Membership', $groupType) || in_array('Contribution', $groupType)
901 || in_array('Organization', $groupType) || in_array('Household', $groupType) || in_array('Activity', $groupType)
902 ) {
7132ac1f 903 $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
904 }
905 else {
906 self::formRuleSubType($fieldType, $groupType, $errors);
907 }
908 break;
909
910 case 'Contribution':
911 //special case where in we allow contribution + oganization fields, for on behalf feature
912 $profileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup',
913 'on_behalf_organization', 'id', 'name'
914 );
915
916 if (in_array('Participant', $groupType) || in_array('Membership', $groupType)
917 || ($profileId != $self->_gid && 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 Contribution with combination of Activity or Membership or Participant or Household or Organization');
920 }
921 else {
922 self::formRuleSubType($fieldType, $groupType, $errors);
923 }
924 break;
925
926 case 'Membership':
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('Contribution', $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 Membership with combination of Activity or Participant or Contribution or Household or Organization');
936 }
937 else {
938 self::formRuleSubType($fieldType, $groupType, $errors);
939 }
940 break;
941
942 default:
943 $profileType = CRM_Core_BAO_UFField::getProfileType($fields['group_id'], TRUE, FALSE, TRUE);
944 if (CRM_Contact_BAO_ContactType::isaSubType($fieldType)) {
945 if (CRM_Contact_BAO_ContactType::isaSubType($profileType)) {
946 if ($fieldType != $profileType) {
be2fb01f 947 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', [
c5c263ca
AH
948 1 => $fieldType,
949 2 => $profileType,
be2fb01f 950 ]);
6a488035
TO
951 }
952 }
953 else {
954 $basicType = CRM_Contact_BAO_ContactType::getBasicType($fieldType);
955 if ($profileType &&
956 $profileType != $basicType &&
957 $profileType != 'Contact'
958 ) {
be2fb01f 959 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', [
c5c263ca
AH
960 1 => $fieldType,
961 2 => $profileType,
be2fb01f 962 ]);
6a488035
TO
963 }
964 }
965 }
50c3e74b
DL
966 elseif (
967 CRM_Utils_Array::value(1, $fields['field_name']) == 'contact_sub_type' &&
be2fb01f 968 !in_array($profileType, ['Individual', 'Household', 'Organization']) &&
6a488035
TO
969 !in_array($profileType, CRM_Contact_BAO_ContactType::subTypes())
970 ) {
971 $errors['field_name'] = ts('Cannot add or update profile field Contact Subtype as profile type is not one of Individual, Household or Organization.');
972 }
973 }
974 return empty($errors) ? TRUE : $errors;
975 }
96025800 976
78580003
SB
977 /**
978 * Set a message warning the user about putting country first to render states, if required.
979 *
980 * @param string $fieldName
981 * @param int $locationTypeID
982 * @param int $weight
983 * @param int $ufGroupID
984 */
985 protected function setMessageIfCountryNotAboveState($fieldName, $locationTypeID, $weight, $ufGroupID) {
986 $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.');
987
be2fb01f 988 if (in_array($fieldName, [
3655bea4
SL
989 'country',
990 'state_province',
991 ]) && count(CRM_Core_Config::singleton()->countryLimit) > 1
78580003
SB
992 ) {
993 // get state or country field weight if exists
994 $ufFieldDAO = new CRM_Core_DAO_UFField();
995 $ufFieldDAO->field_name = ($fieldName == 'state_province' ? 'country' : 'state_province');
996 $ufFieldDAO->location_type_id = $locationTypeID;
997 $ufFieldDAO->uf_group_id = $ufGroupID;
998
999 if ($ufFieldDAO->find(TRUE)) {
1000 if ($ufFieldDAO->field_name == 'country' && $ufFieldDAO->weight > $weight) {
1001 CRM_Core_Session::setStatus($message);
1002 }
1003 elseif ($ufFieldDAO->field_name == 'state_province' && $ufFieldDAO->weight < $weight) {
1004 CRM_Core_Session::setStatus($message);
1005 }
1006 }
1007 }
1008 }
1009
6a488035 1010}