Merge pull request #18397 from swastikpareek/issue-1987-fix-isFrontPage-function
[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('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 ]
475 );
476 }
477
478 $this->setDefaults($defaults);
479 }
480
481 /**
482 * Process the form.
483 *
484 * @return void
485 */
486 public function postProcess() {
487
488 if ($this->_action & CRM_Core_Action::DELETE) {
489 $fieldValues = ['uf_group_id' => $this->_gid];
490 CRM_Utils_Weight::delWeight('CRM_Core_DAO_UFField', $this->_id, $fieldValues);
491 $deleted = CRM_Core_BAO_UFField::del($this->_id);
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');
506 $params['uf_group_id'] = $this->_gid;
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) {
512 $params['id'] = $this->_id;
513 }
514
515 $name = NULL;
516 if (isset($params['field_name'][1]) && isset($this->_selectFields[$params['field_name'][1]])) {
517 // we dont get a name for a html formatting element
518 $name = $this->_selectFields[$params['field_name'][1]];
519 }
520
521 // If field_name is missing, it's formatting
522 $fieldName = CRM_Utils_Array::value(1, $params['field_name'], 'formatting');
523
524 //check for duplicate fields
525 $apiFormattedParams = $params;
526 $apiFormattedParams['field_type'] = $params['field_name'][0];
527 $apiFormattedParams['field_name'] = $fieldName;
528 if (!empty($params['field_name'][2])) {
529 if ($fieldName === 'url') {
530 $apiFormattedParams['website_type_id'] = $params['field_name'][2];
531 }
532 else {
533 $apiFormattedParams['location_type_id'] = $params['field_name'][2];
534 }
535 }
536 elseif (isset($params['field_name'][2]) && $params['field_name'][2] == 0) {
537 // 0 is Primary location type
538 $apiFormattedParams['location_type_id'] = '';
539 }
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'));
546 }
547 else {
548 $apiFormattedParams['weight'] = CRM_Core_BAO_UFField::autoWeight($params);
549 civicrm_api3('UFField', 'create', $apiFormattedParams);
550
551 //reset other field is searchable and in selector settings, CRM-4363
552 if ($this->_hasSearchableORInSelector &&
553 in_array($apiFormattedParams['field_type'], ['Participant', 'Contribution', 'Membership', 'Activity', 'Case'])
554 ) {
555 CRM_Core_BAO_UFField::resetInSelectorANDSearchable($this->_gid);
556 }
557
558 $this->setMessageIfCountryNotAboveState($fieldName, CRM_Utils_Array::value('location_type_id', $apiFormattedParams), $apiFormattedParams['weight'], $apiFormattedParams['uf_group_id']);
559
560 CRM_Core_Session::setStatus(ts('Your CiviCRM Profile Field \'%1\' has been saved to \'%2\'.',
561 [1 => $name, 2 => $this->_title]
562 ), ts('Profile Field Saved'), 'success');
563 }
564 $buttonName = $this->controller->getButtonName();
565
566 $session = CRM_Core_Session::singleton();
567 if ($buttonName == $this->getButtonName('next', 'new')) {
568 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field/add',
569 "reset=1&action=add&gid={$this->_gid}"
570 ));
571 }
572 else {
573 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field',
574 "reset=1&action=browse&gid={$this->_gid}"
575 ));
576 }
577 }
578
579 /**
580 * Validation rule for subtype.
581 *
582 * @param string $fieldType
583 * Type of field.
584 * @param array $groupType
585 * Contains all groupTypes.
586 * @param array $errors
587 * List of errors to be posted back to the form.
588 */
589 public static function formRuleSubType($fieldType, $groupType, &$errors) {
590 if (in_array($fieldType, [
591 'Participant',
592 'Contribution',
593 'Membership',
594 'Activity',
595 ])) {
596 $individualSubTypes = CRM_Contact_BAO_ContactType::subTypes('Individual');
597 foreach ($groupType as $value) {
598 if (!in_array($value, $individualSubTypes) &&
599 !in_array($value, [
600 'Participant',
601 'Contribution',
602 'Membership',
603 'Individual',
604 'Contact',
605 'Activity',
606 'Formatting',
607 ])
608 ) {
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]);
610 break;
611 }
612 }
613 }
614 else {
615 $basicType = CRM_Contact_BAO_ContactType::getBasicType($groupType);
616 if ($basicType) {
617 if (!is_array($basicType)) {
618 $basicType = [$basicType];
619 }
620 if (!in_array($fieldType, $basicType) && $fieldType != 'Contact') {
621 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of subtype other than "%1".',
622 [1 => $fieldType]
623 );
624 }
625 }
626 }
627 }
628
629 /**
630 * Validation rule for custom data extends entity column values.
631 *
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.
640 *
641 * @return array
642 * list of errors to be posted back to the form
643 */
644 public static function formRuleCustomDataExtentColumnValue($customField, $gid, $fieldType, &$errors) {
645 // fix me : check object $customField
646 if (in_array($fieldType, [
647 'Participant',
648 'Contribution',
649 'Membership',
650 'Activity',
651 'Case',
652 ])) {
653 $params = ['id' => $customField->custom_group_id];
654 $customGroup = [];
655 CRM_Core_BAO_CustomGroup::retrieve($params, $customGroup);
656 if (($fieldType != CRM_Utils_Array::value('extends', $customGroup)) || empty($customGroup['extends_entity_column_value'])) {
657 return $errors;
658 }
659
660 $extendsColumnValues = [];
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);
672 if (empty($fieldTypeValues[$fieldType])) {
673 return $errors;
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
683 /**
684 * Validation rule to prevent multiple fields of primary location type within the same communication type.
685 *
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.
694 *
695 */
696 public static function formRulePrimaryCheck($fields, $profileFieldName, $groupFields, &$errors) {
697 //FIXME: This may need to also apply to website fields if they are refactored to allow more than one per profile
698 $checkPrimary = ['phone' => 'civicrm_phone.phone', 'phone_and_ext' => 'civicrm_phone.phone'];
699 $whereCheck = NULL;
700 $primaryOfSameTypeFound = NULL;
701 $fieldID = empty($fields['field_id']) ? 0 : $fields['field_id'];
702 // Is this a primary location type field of interest
703 if (array_key_exists($profileFieldName, $checkPrimary)) {
704 $whereCheck = $checkPrimary[$profileFieldName];
705 }
706 $potentialLocationType = $fields['field_name'][2] ?? NULL;
707
708 if ($whereCheck && $potentialLocationType == 0) {
709 $primaryOfSameTypeFound = '';
710
711 foreach ($groupFields as $groupField) {
712 // if it is a phone
713 if ($groupField['where'] == $whereCheck && is_null($groupField['location_type_id']) && $groupField['field_id'] != $fieldID) {
714 $primaryOfSameTypeFound = $groupField['title'];
715 break;
716 }
717 }
718 if ($primaryOfSameTypeFound) {
719 $errors['field_name'] = ts('You have already added a primary location field of this type: %1', [1 => $primaryOfSameTypeFound]);
720 }
721 }
722 }
723
724 /**
725 * Global validation rules for the form.
726 *
727 * @param array $fields
728 * Posted values of the form.
729 *
730 * @param $files
731 * @param $self
732 *
733 * @return array
734 * list of errors to be posted back to the form
735 */
736 public static function formRule($fields, $files, $self) {
737 $is_required = CRM_Utils_Array::value('is_required', $fields, FALSE);
738 $is_registration = CRM_Utils_Array::value('is_registration', $fields, FALSE);
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);
742
743 $errors = [];
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
751 $entityName = $fields['field_name'][0];
752 if (!$entityName) {
753 $errors['field_name'] = ts('Please select a field name');
754 }
755
756 if ($in_selector && in_array($entityName, [
757 'Contribution',
758 'Participant',
759 'Membership',
760 'Activity',
761 ])
762 ) {
763 $errors['in_selector'] = ts("'Results Column' cannot be checked for %1 fields.", [1 => $entityName]);
764 }
765
766 $isCustomField = FALSE;
767 $profileFieldName = $fields['field_name'][1] ?? NULL;
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
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
795 self::formRulePrimaryCheck($fields, $profileFieldName, $groupFields, $errors);
796
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()) {
801 if (CRM_Utils_Array::value(1, $fields['field_name']) == 'group') {
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
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]);
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 ) {
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.');
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) {
950 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', [
951 1 => $fieldType,
952 2 => $profileType,
953 ]);
954 }
955 }
956 else {
957 $basicType = CRM_Contact_BAO_ContactType::getBasicType($fieldType);
958 if ($profileType &&
959 $profileType != $basicType &&
960 $profileType != 'Contact'
961 ) {
962 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', [
963 1 => $fieldType,
964 2 => $profileType,
965 ]);
966 }
967 }
968 }
969 elseif (
970 CRM_Utils_Array::value(1, $fields['field_name']) == 'contact_sub_type' &&
971 !in_array($profileType, ['Individual', 'Household', 'Organization']) &&
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 }
979
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
991 if (in_array($fieldName, [
992 'country',
993 'state_province',
994 ]) && count(CRM_Core_Config::singleton()->countryLimit) > 1
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
1013 }