Revert "Swap out button/submit inputs for button elements"
[civicrm-core.git] / CRM / UF / Form / Field.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * Form to process actions on the field aspect of Custom.
20 */
21 class CRM_UF_Form_Field extends CRM_Core_Form {
22
23 /**
24 * The uf group id saved to the session for an update.
25 *
26 * @var int
27 */
28 protected $_gid;
29
30 /**
31 * The field id, used when editing the field.
32 *
33 * @var int
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
41 */
42 protected $_fields;
43
44 /**
45 * The title for field.
46 *
47 * @var int
48 */
49 protected $_title;
50
51 /**
52 * The set of fields sent to the select element.
53 *
54 * @var array
55 */
56 protected $_selectFields;
57
58 /**
59 * store fields with if locationtype exits status.
60 *
61 * @var array
62 */
63 protected $_hasLocationTypes;
64
65 /**
66 * Is this profile has searchable field.
67 * or is any field having in selector true.
68 *
69 * @var bool
70 */
71 protected $_hasSearchableORInSelector;
72
73 /**
74 * Set variables up before form is built.
75 *
76 * @return void
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');
83
84 $this->setPageTitle(ts('Profile Field'));
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);
92 $breadCrumb = [
93 [
94 'title' => ts('CiviCRM Profile Fields'),
95 'url' => $url,
96 ],
97 ];
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
130 if (CRM_Core_Permission::access('CiviCase')) {
131 $this->_fields = array_merge(CRM_Case_BAO_Query::getFields(), $this->_fields);
132 }
133
134 $this->_fields = array_merge($this->_fields, CRM_Contact_BAO_Query_Hook::singleton()->getFields());
135
136 $this->_selectFields = [];
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'];
143 $this->_hasLocationTypes[$name] = $field['hasLocationType'] ?? NULL;
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 /**
163 * Build the form object.
164 *
165 * @return void
166 */
167 public function buildQuickForm() {
168 if ($this->_action & CRM_Core_Action::DELETE) {
169 $this->addButtons([
170 [
171 'type' => 'next',
172 'name' => ts('Delete Profile Field'),
173 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
174 'isDefault' => TRUE,
175 ],
176 [
177 'type' => 'cancel',
178 'name' => ts('Cancel'),
179 ],
180 ]);
181 return;
182 }
183 $addressCustomFields = array_keys(CRM_Core_BAO_CustomField::getFieldsForImport('Address'));
184
185 if (isset($this->_id)) {
186 $params = ['id' => $this->_id];
187 CRM_Core_BAO_UFField::retrieve($params, $defaults);
188
189 // set it to null if so (avoids crappy E_NOTICE errors below
190 $defaults['location_type_id'] = $defaults['location_type_id'] ?? NULL;
191
192 //CRM-20861 - Include custom fields defined for address to set its default location type to 0.
193 $specialFields = array_merge(CRM_Core_BAO_UFGroup::getLocationFields(), $addressCustomFields);
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
201 $defaults['field_name'] = [
202 $defaults['field_type'],
203 ($defaults['field_type'] == "Formatting" ? "" : $defaults['field_name']),
204 ($defaults['field_name'] == "url") ? $defaults['website_type_id'] : $defaults['location_type_id'],
205 CRM_Utils_Array::value('phone_type_id', $defaults),
206 ];
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) {
217 $fieldValues = ['uf_group_id' => $this->_gid];
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
232 $noSearchable = $hasWebsiteTypes = [];
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 }
248 $hasLocationTypes[$key][$key1] = $value1['hasLocationType'] ?? NULL;
249 $hasWebsiteTypes[$key][$key1] = $value1['hasWebsiteType'] ?? NULL;
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
265 $this->_location_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
266 $defaultLocationType = CRM_Core_BAO_LocationType::getDefault();
267 $this->_website_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Website', 'website_type_id');
268
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]);
277 $this->_location_types = [
278 $defaultLocationType->id => $defaultLocation,
279 ] + $this->_location_types;
280 }
281
282 $this->_location_types = ['Primary'] + $this->_location_types;
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
289 $contactTypes = !empty($contactTypes) ? ['Contact' => 'Contacts'] + $contactTypes : [];
290 $sel1 = ['' => '- select -'] + $contactTypes;
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
308 if (!empty($fields['Case'])) {
309 $sel1['Case'] = 'Case';
310 }
311
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;
322 $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
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 }
341 elseif ($hasWebsiteTypes[$k][$key]) {
342 $sel3[$k][$key] = $this->_website_types;
343 }
344 else {
345 $sel3[$key] = NULL;
346 }
347 }
348 }
349 }
350 }
351
352 $this->_defaults = [];
353 $js = "<script type='text/javascript'>\n";
354 $formName = "document.{$this->_name}";
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++) {
368 if (!isset($defaults['field_name'][$k])) {
369 $js .= "{$formName}['field_name[$k]'].style.display = 'none';\n";
370 }
371 }
372 }
373 else {
374 if (!empty($formValues['field_name'])) {
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";
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
399 $sel->setOptions([$sel1, $sel2, $sel3, $sel4]);
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,
414 ['onChange' => "showHideSeletorSearch(this.value);"]
415 );
416
417 //CRM-4363
418 $js = ['onChange' => "mixProfile();"];
419 // should the field appear in selectors (as a column)?
420 $this->add('advcheckbox', 'in_selector', ts('Results Column?'), NULL, NULL, $js);
421 $this->add('advcheckbox', 'is_searchable', ts('Searchable?'), NULL, NULL, $js);
422
423 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_UFField');
424
425 // weight
426 $this->add('number', 'weight', ts('Order'), $attributes['weight'], TRUE);
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
432 $this->add('advcheckbox', 'is_required', ts('Required?'));
433
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?'));
437
438 $this->add('text', 'label', ts('Field Label'), $attributes['label']);
439
440 $js = NULL;
441 if ($this->_hasSearchableORInSelector) {
442 $js = ['onclick' => "return verify( );"];
443 }
444
445 // add buttons
446 $this->addButtons([
447 [
448 'type' => 'next',
449 'name' => ts('Save'),
450 'isDefault' => TRUE,
451 'js' => $js,
452 ],
453 [
454 'type' => 'next',
455 'name' => ts('Save and New'),
456 'subName' => 'new',
457 'js' => $js,
458 ],
459 [
460 'type' => 'cancel',
461 'name' => ts('Cancel'),
462 ],
463 ]);
464
465 $this->addFormRule(['CRM_UF_Form_Field', 'formRule'], $this);
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'),
471 ['onclick' => "location.href='civicrm/admin/uf/group/field?reset=1&action=browse&gid=" . $this->_gid . "'"]
472 );
473 }
474
475 $this->setDefaults($defaults);
476 }
477
478 /**
479 * Process the form.
480 *
481 * @return void
482 */
483 public function postProcess() {
484
485 if ($this->_action & CRM_Core_Action::DELETE) {
486 $fieldValues = ['uf_group_id' => $this->_gid];
487 CRM_Utils_Weight::delWeight('CRM_Core_DAO_UFField', $this->_id, $fieldValues);
488 $deleted = CRM_Core_BAO_UFField::del($this->_id);
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');
503 $params['uf_group_id'] = $this->_gid;
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) {
509 $params['id'] = $this->_id;
510 }
511
512 $name = NULL;
513 if (isset($params['field_name'][1]) && isset($this->_selectFields[$params['field_name'][1]])) {
514 // we dont get a name for a html formatting element
515 $name = $this->_selectFields[$params['field_name'][1]];
516 }
517
518 // If field_name is missing, it's formatting
519 $fieldName = CRM_Utils_Array::value(1, $params['field_name'], 'formatting');
520
521 //check for duplicate fields
522 $apiFormattedParams = $params;
523 $apiFormattedParams['field_type'] = $params['field_name'][0];
524 $apiFormattedParams['field_name'] = $fieldName;
525 if (!empty($params['field_name'][2])) {
526 if ($fieldName === 'url') {
527 $apiFormattedParams['website_type_id'] = $params['field_name'][2];
528 }
529 else {
530 $apiFormattedParams['location_type_id'] = $params['field_name'][2];
531 }
532 }
533 elseif (isset($params['field_name'][2]) && $params['field_name'][2] == 0) {
534 // 0 is Primary location type
535 $apiFormattedParams['location_type_id'] = '';
536 }
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'));
543 }
544 else {
545 $apiFormattedParams['weight'] = CRM_Core_BAO_UFField::autoWeight($params);
546 civicrm_api3('UFField', 'create', $apiFormattedParams);
547
548 //reset other field is searchable and in selector settings, CRM-4363
549 if ($this->_hasSearchableORInSelector &&
550 in_array($apiFormattedParams['field_type'], ['Participant', 'Contribution', 'Membership', 'Activity', 'Case'])
551 ) {
552 CRM_Core_BAO_UFField::resetInSelectorANDSearchable($this->_gid);
553 }
554
555 $this->setMessageIfCountryNotAboveState($fieldName, CRM_Utils_Array::value('location_type_id', $apiFormattedParams), $apiFormattedParams['weight'], $apiFormattedParams['uf_group_id']);
556
557 CRM_Core_Session::setStatus(ts('Your CiviCRM Profile Field \'%1\' has been saved to \'%2\'.',
558 [1 => $name, 2 => $this->_title]
559 ), ts('Profile Field Saved'), 'success');
560 }
561 $buttonName = $this->controller->getButtonName();
562
563 $session = CRM_Core_Session::singleton();
564 if ($buttonName == $this->getButtonName('next', 'new')) {
565 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field/add',
566 "reset=1&action=add&gid={$this->_gid}"
567 ));
568 }
569 else {
570 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field',
571 "reset=1&action=browse&gid={$this->_gid}"
572 ));
573 }
574 }
575
576 /**
577 * Validation rule for subtype.
578 *
579 * @param string $fieldType
580 * Type of field.
581 * @param array $groupType
582 * Contains all groupTypes.
583 * @param array $errors
584 * List of errors to be posted back to the form.
585 */
586 public static function formRuleSubType($fieldType, $groupType, &$errors) {
587 if (in_array($fieldType, [
588 'Participant',
589 'Contribution',
590 'Membership',
591 'Activity',
592 ])) {
593 $individualSubTypes = CRM_Contact_BAO_ContactType::subTypes('Individual');
594 foreach ($groupType as $value) {
595 if (!in_array($value, $individualSubTypes) &&
596 !in_array($value, [
597 'Participant',
598 'Contribution',
599 'Membership',
600 'Individual',
601 'Contact',
602 'Activity',
603 'Formatting',
604 ])
605 ) {
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]);
607 break;
608 }
609 }
610 }
611 else {
612 $basicType = CRM_Contact_BAO_ContactType::getBasicType($groupType);
613 if ($basicType) {
614 if (!is_array($basicType)) {
615 $basicType = [$basicType];
616 }
617 if (!in_array($fieldType, $basicType) && $fieldType != 'Contact') {
618 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of subtype other than "%1".',
619 [1 => $fieldType]
620 );
621 }
622 }
623 }
624 }
625
626 /**
627 * Validation rule for custom data extends entity column values.
628 *
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.
637 *
638 * @return array
639 * list of errors to be posted back to the form
640 */
641 public static function formRuleCustomDataExtentColumnValue($customField, $gid, $fieldType, &$errors) {
642 // fix me : check object $customField
643 if (in_array($fieldType, [
644 'Participant',
645 'Contribution',
646 'Membership',
647 'Activity',
648 'Case',
649 ])) {
650 $params = ['id' => $customField->custom_group_id];
651 $customGroup = [];
652 CRM_Core_BAO_CustomGroup::retrieve($params, $customGroup);
653 if (($fieldType != CRM_Utils_Array::value('extends', $customGroup)) || empty($customGroup['extends_entity_column_value'])) {
654 return $errors;
655 }
656
657 $extendsColumnValues = [];
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);
669 if (empty($fieldTypeValues[$fieldType])) {
670 return $errors;
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
680 /**
681 * Validation rule to prevent multiple fields of primary location type within the same communication type.
682 *
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.
691 *
692 */
693 public static function formRulePrimaryCheck($fields, $profileFieldName, $groupFields, &$errors) {
694 //FIXME: This may need to also apply to website fields if they are refactored to allow more than one per profile
695 $checkPrimary = ['phone' => 'civicrm_phone.phone', 'phone_and_ext' => 'civicrm_phone.phone'];
696 $whereCheck = NULL;
697 $primaryOfSameTypeFound = NULL;
698 $fieldID = empty($fields['field_id']) ? 0 : $fields['field_id'];
699 // Is this a primary location type field of interest
700 if (array_key_exists($profileFieldName, $checkPrimary)) {
701 $whereCheck = $checkPrimary[$profileFieldName];
702 }
703 $potentialLocationType = $fields['field_name'][2] ?? NULL;
704
705 if ($whereCheck && $potentialLocationType == 0) {
706 $primaryOfSameTypeFound = '';
707
708 foreach ($groupFields as $groupField) {
709 // if it is a phone
710 if ($groupField['where'] == $whereCheck && is_null($groupField['location_type_id']) && $groupField['field_id'] != $fieldID) {
711 $primaryOfSameTypeFound = $groupField['title'];
712 break;
713 }
714 }
715 if ($primaryOfSameTypeFound) {
716 $errors['field_name'] = ts('You have already added a primary location field of this type: %1', [1 => $primaryOfSameTypeFound]);
717 }
718 }
719 }
720
721 /**
722 * Global validation rules for the form.
723 *
724 * @param array $fields
725 * Posted values of the form.
726 *
727 * @param $files
728 * @param $self
729 *
730 * @return array
731 * list of errors to be posted back to the form
732 */
733 public static function formRule($fields, $files, $self) {
734 $is_required = CRM_Utils_Array::value('is_required', $fields, FALSE);
735 $is_registration = CRM_Utils_Array::value('is_registration', $fields, FALSE);
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);
739
740 $errors = [];
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
748 $entityName = $fields['field_name'][0];
749 if (!$entityName) {
750 $errors['field_name'] = ts('Please select a field name');
751 }
752
753 if ($in_selector && in_array($entityName, [
754 'Contribution',
755 'Participant',
756 'Membership',
757 'Activity',
758 ])
759 ) {
760 $errors['in_selector'] = ts("'Results Column' cannot be checked for %1 fields.", [1 => $entityName]);
761 }
762
763 $isCustomField = FALSE;
764 $profileFieldName = $fields['field_name'][1] ?? NULL;
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
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
792 self::formRulePrimaryCheck($fields, $profileFieldName, $groupFields, $errors);
793
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()) {
798 if (CRM_Utils_Array::value(1, $fields['field_name']) == 'group') {
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
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]);
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 ) {
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.');
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) {
947 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', [
948 1 => $fieldType,
949 2 => $profileType,
950 ]);
951 }
952 }
953 else {
954 $basicType = CRM_Contact_BAO_ContactType::getBasicType($fieldType);
955 if ($profileType &&
956 $profileType != $basicType &&
957 $profileType != 'Contact'
958 ) {
959 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', [
960 1 => $fieldType,
961 2 => $profileType,
962 ]);
963 }
964 }
965 }
966 elseif (
967 CRM_Utils_Array::value(1, $fields['field_name']) == 'contact_sub_type' &&
968 !in_array($profileType, ['Individual', 'Household', 'Organization']) &&
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 }
976
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
988 if (in_array($fieldName, [
989 'country',
990 'state_province',
991 ]) && count(CRM_Core_Config::singleton()->countryLimit) > 1
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
1010 }