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