0a06125b036b04033d57fcbeaf4962b948dffd3b
[civicrm-core.git] / CRM / Custom / 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_Custom_Form_Field extends CRM_Core_Form {
22
23 /**
24 * Constants for number of options for data types of multiple option.
25 */
26 const NUM_OPTION = 11;
27
28 /**
29 * The custom group id saved to the session for an update.
30 *
31 * @var int
32 */
33 protected $_gid;
34
35 /**
36 * The field id, used when editing the field
37 *
38 * @var int
39 */
40 protected $_id;
41
42 /**
43 * The default custom data/input types, when editing the field
44 *
45 * @var array
46 */
47 protected $_defaultDataType;
48
49 /**
50 * Array of custom field values if update mode.
51 * @var array
52 */
53 protected $_values;
54
55 /**
56 * Array for valid combinations of data_type & html_type
57 *
58 * @var array
59 */
60 private static $_dataTypeValues = NULL;
61 private static $_dataTypeKeys = NULL;
62
63 private static $_dataToLabels = NULL;
64
65 /**
66 * Used for mapping data types to html type options.
67 *
68 * Each item in this array corresponds to the same index in the dataType array
69 * @var array
70 */
71 public static $_dataToHTML = [
72 [
73 'Text' => 'Text',
74 'Select' => 'Select',
75 'Radio' => 'Radio',
76 'CheckBox' => 'CheckBox',
77 'Autocomplete-Select' => 'Autocomplete-Select',
78 ],
79 ['Text' => 'Text', 'Select' => 'Select', 'Radio' => 'Radio'],
80 ['Text' => 'Text', 'Select' => 'Select', 'Radio' => 'Radio'],
81 ['Text' => 'Text', 'Select' => 'Select', 'Radio' => 'Radio'],
82 ['TextArea' => 'TextArea', 'RichTextEditor' => 'RichTextEditor'],
83 ['Date' => 'Select Date'],
84 ['Radio' => 'Radio'],
85 ['StateProvince' => 'Select State/Province'],
86 ['Country' => 'Select Country'],
87 ['File' => 'File'],
88 ['Link' => 'Link'],
89 ['ContactReference' => 'Autocomplete-Select'],
90 ];
91
92 /**
93 * Set variables up before form is built.
94 *
95 * @return void
96 */
97 public function preProcess() {
98 if (!(self::$_dataTypeKeys)) {
99 self::$_dataTypeKeys = array_keys(CRM_Core_BAO_CustomField::dataType());
100 self::$_dataTypeValues = array_values(CRM_Core_BAO_CustomField::dataType());
101 }
102
103 //custom field id
104 $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this);
105
106 $this->_values = [];
107 //get the values form db if update.
108 if ($this->_id) {
109 $params = ['id' => $this->_id];
110 CRM_Core_BAO_CustomField::retrieve($params, $this->_values);
111 // note_length is an alias for the text_length field
112 $this->_values['note_length'] = $this->_values['text_length'] ?? NULL;
113 // custom group id
114 $this->_gid = $this->_values['custom_group_id'];
115 }
116 else {
117 // custom group id
118 $this->_gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this);
119 }
120
121 if ($isReserved = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_gid, 'is_reserved', 'id')) {
122 CRM_Core_Error::statusBounce("You cannot add or edit fields in a reserved custom field-set.");
123 }
124
125 if ($this->_gid) {
126 $url = CRM_Utils_System::url('civicrm/admin/custom/group/field',
127 "reset=1&action=browse&gid={$this->_gid}"
128 );
129
130 $session = CRM_Core_Session::singleton();
131 $session->pushUserContext($url);
132 }
133
134 if (self::$_dataToLabels == NULL) {
135 self::$_dataToLabels = [
136 [
137 'Text' => ts('Text'),
138 'Select' => ts('Select'),
139 'Radio' => ts('Radio'),
140 'CheckBox' => ts('CheckBox'),
141 'Autocomplete-Select' => ts('Autocomplete-Select'),
142 ],
143 [
144 'Text' => ts('Text'),
145 'Select' => ts('Select'),
146 'Radio' => ts('Radio'),
147 ],
148 [
149 'Text' => ts('Text'),
150 'Select' => ts('Select'),
151 'Radio' => ts('Radio'),
152 ],
153 [
154 'Text' => ts('Text'),
155 'Select' => ts('Select'),
156 'Radio' => ts('Radio'),
157 ],
158 ['TextArea' => ts('TextArea'), 'RichTextEditor' => ts('Rich Text Editor')],
159 ['Date' => ts('Select Date')],
160 ['Radio' => ts('Radio')],
161 ['StateProvince' => ts('Select State/Province')],
162 ['Country' => ts('Select Country')],
163 ['File' => ts('Select File')],
164 ['Link' => ts('Link')],
165 ['ContactReference' => ts('Autocomplete-Select')],
166 ];
167 }
168 }
169
170 /**
171 * Set default values for the form. Note that in edit/view mode
172 * the default values are retrieved from the database
173 *
174 * @return array
175 * array of default values
176 */
177 public function setDefaultValues() {
178 $defaults = $this->_values;
179
180 if ($this->_id) {
181 $this->assign('id', $this->_id);
182 $this->_gid = $defaults['custom_group_id'];
183
184 //get the value for state or country
185 if ($defaults['data_type'] == 'StateProvince' &&
186 $stateId = CRM_Utils_Array::value('default_value', $defaults)
187 ) {
188 $defaults['default_value'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_StateProvince', $stateId);
189 }
190 elseif ($defaults['data_type'] == 'Country' &&
191 $countryId = CRM_Utils_Array::value('default_value', $defaults)
192 ) {
193 $defaults['default_value'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Country', $countryId);
194 }
195
196 if ($defaults['data_type'] == 'ContactReference' && !empty($defaults['filter'])) {
197 $contactRefFilter = 'Advance';
198 if (strpos($defaults['filter'], 'action=lookup') !== FALSE &&
199 strpos($defaults['filter'], 'group=') !== FALSE
200 ) {
201 $filterParts = explode('&', $defaults['filter']);
202
203 if (count($filterParts) == 2) {
204 $contactRefFilter = 'Group';
205 foreach ($filterParts as $part) {
206 if (strpos($part, 'group=') === FALSE) {
207 continue;
208 }
209 $groups = substr($part, strpos($part, '=') + 1);
210 foreach (explode(',', $groups) as $grp) {
211 if (CRM_Utils_Rule::positiveInteger($grp)) {
212 $defaults['group_id'][] = $grp;
213 }
214 }
215 }
216 }
217 }
218 $defaults['filter_selected'] = $contactRefFilter;
219 }
220
221 if (!empty($defaults['data_type'])) {
222 $defaultDataType = array_search($defaults['data_type'],
223 self::$_dataTypeKeys
224 );
225 $defaultHTMLType = array_search($defaults['html_type'],
226 self::$_dataToHTML[$defaultDataType]
227 );
228 $defaults['data_type'] = [
229 '0' => $defaultDataType,
230 '1' => $defaultHTMLType,
231 ];
232 $this->_defaultDataType = $defaults['data_type'];
233 }
234
235 $defaults['option_type'] = 2;
236
237 $this->assign('changeFieldType', CRM_Custom_Form_ChangeFieldType::fieldTypeTransitions($this->_values['data_type'], $this->_values['html_type']));
238 }
239 else {
240 $defaults['is_active'] = 1;
241 $defaults['option_type'] = 1;
242 $defaults['is_search_range'] = 1;
243 }
244
245 // set defaults for weight.
246 for ($i = 1; $i <= self::NUM_OPTION; $i++) {
247 $defaults['option_status[' . $i . ']'] = 1;
248 $defaults['option_weight[' . $i . ']'] = $i;
249 $defaults['option_value[' . $i . ']'] = $i;
250 }
251
252 if ($this->_action & CRM_Core_Action::ADD) {
253 $fieldValues = ['custom_group_id' => $this->_gid];
254 $defaults['weight'] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_CustomField', $fieldValues);
255
256 $defaults['text_length'] = 255;
257 $defaults['note_columns'] = 60;
258 $defaults['note_rows'] = 4;
259 $defaults['is_view'] = 0;
260 }
261
262 if (!empty($defaults['html_type'])) {
263 $dontShowLink = substr($defaults['html_type'], -14) == 'State/Province' || substr($defaults['html_type'], -7) == 'Country' ? 1 : 0;
264 }
265
266 if (isset($dontShowLink)) {
267 $this->assign('dontShowLink', $dontShowLink);
268 }
269 if ($this->_action & CRM_Core_Action::ADD &&
270 CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_gid, 'is_multiple', 'id')
271 ) {
272 $defaults['in_selector'] = 1;
273 }
274
275 return $defaults;
276 }
277
278 /**
279 * Build the form object.
280 *
281 * @return void
282 */
283 public function buildQuickForm() {
284 if ($this->_gid) {
285 $this->_title = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_gid, 'title');
286 CRM_Utils_System::setTitle($this->_title . ' - ' . ($this->_id ? ts('Edit Field') : ts('New Field')));
287 $this->assign('gid', $this->_gid);
288 }
289
290 $this->assign('dataTypeKeys', self::$_dataTypeKeys);
291
292 // lets trim all the whitespace
293 $this->applyFilter('__ALL__', 'trim');
294
295 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_CustomField');
296
297 // label
298 $this->add('text',
299 'label',
300 ts('Field Label'),
301 $attributes['label'],
302 TRUE
303 );
304
305 $dt = &self::$_dataTypeValues;
306 $it = [];
307 foreach ($dt as $key => $value) {
308 $it[$key] = self::$_dataToLabels[$key];
309 }
310 $sel = &$this->addElement('hierselect',
311 'data_type',
312 ts('Data and Input Field Type'),
313 '&nbsp;&nbsp;&nbsp;'
314 );
315 $sel->setOptions([$dt, $it]);
316
317 if (CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_gid, 'is_multiple')) {
318 $this->add('checkbox', 'in_selector', ts('Display in Table?'));
319 }
320
321 $optionGroupParams = [
322 'is_reserved' => 0,
323 'is_active' => 1,
324 'options' => ['limit' => 0, 'sort' => "title ASC"],
325 'return' => ['title'],
326 ];
327
328 $this->add('checkbox', 'serialize', ts('Multi-Select'));
329
330 if ($this->_action == CRM_Core_Action::UPDATE) {
331 $this->freeze('data_type');
332 if (!empty($this->_values['option_group_id'])) {
333 // Before dev/core#155 we didn't set the is_reserved flag properly, which should be handled by the upgrade script...
334 // but it is still possible that existing installs may have optiongroups linked to custom fields that are marked reserved.
335 $optionGroupParams['id'] = $this->_values['option_group_id'];
336 $optionGroupParams['options']['or'] = [["is_reserved", "id"]];
337 }
338 }
339
340 // Retrieve optiongroups for selection list
341 $optionGroupMetadata = civicrm_api3('OptionGroup', 'get', $optionGroupParams);
342
343 // OptionGroup selection
344 $optionTypes = ['1' => ts('Create a new set of options')];
345
346 if (!empty($optionGroupMetadata['values'])) {
347 $emptyOptGroup = FALSE;
348 $optionGroups = CRM_Utils_Array::collect('title', $optionGroupMetadata['values']);
349 $optionTypes['2'] = ts('Reuse an existing set');
350
351 $this->add('select',
352 'option_group_id',
353 ts('Multiple Choice Option Sets'),
354 [
355 '' => ts('- select -'),
356 ] + $optionGroups
357 );
358 }
359 else {
360 // No custom (non-reserved) option groups
361 $emptyOptGroup = TRUE;
362 }
363
364 $element = &$this->addRadio('option_type',
365 ts('Option Type'),
366 $optionTypes,
367 [
368 'onclick' => "showOptionSelect();",
369 ], '<br/>'
370 );
371 // if empty option group freeze the option type.
372 if ($emptyOptGroup) {
373 $element->freeze();
374 }
375
376 $contactGroups = CRM_Core_PseudoConstant::group();
377 asort($contactGroups);
378
379 $this->add('select',
380 'group_id',
381 ts('Limit List to Group'),
382 $contactGroups,
383 FALSE,
384 ['multiple' => 'multiple', 'class' => 'crm-select2']
385 );
386
387 $this->add('text',
388 'filter',
389 ts('Advanced Filter'),
390 $attributes['filter']
391 );
392
393 $this->add('hidden', 'filter_selected', 'Group', ['id' => 'filter_selected']);
394
395 // form fields of Custom Option rows
396 $defaultOption = [];
397 $_showHide = new CRM_Core_ShowHideBlocks('', '');
398 for ($i = 1; $i <= self::NUM_OPTION; $i++) {
399
400 //the show hide blocks
401 $showBlocks = 'optionField_' . $i;
402 if ($i > 2) {
403 $_showHide->addHide($showBlocks);
404 if ($i == self::NUM_OPTION) {
405 $_showHide->addHide('additionalOption');
406 }
407 }
408 else {
409 $_showHide->addShow($showBlocks);
410 }
411
412 $optionAttributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue');
413 // label
414 $this->add('text', 'option_label[' . $i . ']', ts('Label'),
415 $optionAttributes['label']
416 );
417
418 // value
419 $this->add('text', 'option_value[' . $i . ']', ts('Value'),
420 $optionAttributes['value']
421 );
422
423 // weight
424 $this->add('number', "option_weight[$i]", ts('Order'),
425 $optionAttributes['weight']
426 );
427
428 // is active ?
429 $this->add('checkbox', "option_status[$i]", ts('Active?'));
430
431 $defaultOption[$i] = $this->createElement('radio', NULL, NULL, NULL, $i);
432
433 //for checkbox handling of default option
434 $this->add('checkbox', "default_checkbox_option[$i]", NULL);
435 }
436
437 //default option selection
438 $this->addGroup($defaultOption, 'default_option');
439
440 $_showHide->addToTemplate();
441
442 // text length for alpha numeric data types
443 $this->add('number',
444 'text_length',
445 ts('Database field length'),
446 $attributes['text_length'] + ['min' => 1],
447 FALSE
448 );
449 $this->addRule('text_length', ts('Value should be a positive number'), 'integer');
450
451 $this->add('text',
452 'start_date_years',
453 ts('Dates may be up to'),
454 $attributes['start_date_years'],
455 FALSE
456 );
457 $this->add('text',
458 'end_date_years',
459 ts('Dates may be up to'),
460 $attributes['end_date_years'],
461 FALSE
462 );
463
464 $this->addRule('start_date_years', ts('Value should be a positive number'), 'integer');
465 $this->addRule('end_date_years', ts('Value should be a positive number'), 'integer');
466
467 $this->add('select', 'date_format', ts('Date Format'),
468 ['' => ts('- select -')] + CRM_Core_SelectValues::getDatePluginInputFormats()
469 );
470
471 $this->add('select', 'time_format', ts('Time'),
472 ['' => ts('- none -')] + CRM_Core_SelectValues::getTimeFormats()
473 );
474
475 // for Note field
476 $this->add('number',
477 'note_columns',
478 ts('Width (columns)') . ' ',
479 $attributes['note_columns'],
480 FALSE
481 );
482 $this->add('number',
483 'note_rows',
484 ts('Height (rows)') . ' ',
485 $attributes['note_rows'],
486 FALSE
487 );
488 $this->add('number',
489 'note_length',
490 ts('Maximum length') . ' ',
491 // note_length is an alias for the text-length field
492 $attributes['text_length'],
493 FALSE
494 );
495
496 $this->addRule('note_columns', ts('Value should be a positive number'), 'positiveInteger');
497 $this->addRule('note_rows', ts('Value should be a positive number'), 'positiveInteger');
498 $this->addRule('note_length', ts('Value should be a positive number'), 'positiveInteger');
499
500 // weight
501 $this->add('number', 'weight', ts('Order'),
502 $attributes['weight'],
503 TRUE
504 );
505 $this->addRule('weight', ts('is a numeric field'), 'numeric');
506
507 // is required ?
508 $this->add('advcheckbox', 'is_required', ts('Required?'));
509
510 // checkbox / radio options per line
511 $this->add('number', 'options_per_line', ts('Options Per Line'), ['min' => 0]);
512 $this->addRule('options_per_line', ts('must be a numeric value'), 'numeric');
513
514 // default value, help pre, help post, mask, attributes, javascript ?
515 $this->add('text', 'default_value', ts('Default Value'),
516 $attributes['default_value']
517 );
518 $this->add('textarea', 'help_pre', ts('Field Pre Help'),
519 $attributes['help_pre']
520 );
521 $this->add('textarea', 'help_post', ts('Field Post Help'),
522 $attributes['help_post']
523 );
524 $this->add('text', 'mask', ts('Mask'),
525 $attributes['mask']
526 );
527
528 // is active ?
529 $this->add('advcheckbox', 'is_active', ts('Active?'));
530
531 // is active ?
532 $this->add('advcheckbox', 'is_view', ts('View Only?'));
533
534 // is searchable ?
535 $this->addElement('advcheckbox',
536 'is_searchable',
537 ts('Is this Field Searchable?')
538 );
539
540 // is searchable by range?
541 $searchRange = [];
542 $searchRange[] = $this->createElement('radio', NULL, NULL, ts('Yes'), '1');
543 $searchRange[] = $this->createElement('radio', NULL, NULL, ts('No'), '0');
544
545 $this->addGroup($searchRange, 'is_search_range', ts('Search by Range?'));
546
547 // add buttons
548 $this->addButtons([
549 [
550 'type' => 'done',
551 'name' => ts('Save'),
552 'isDefault' => TRUE,
553 ],
554 [
555 'type' => 'next',
556 'name' => ts('Save and New'),
557 'subName' => 'new',
558 ],
559 [
560 'type' => 'cancel',
561 'name' => ts('Cancel'),
562 ],
563 ]);
564
565 // add a form rule to check default value
566 $this->addFormRule(['CRM_Custom_Form_Field', 'formRule'], $this);
567
568 // if view mode pls freeze it with the done button.
569 if ($this->_action & CRM_Core_Action::VIEW) {
570 $this->freeze();
571 $url = CRM_Utils_System::url('civicrm/admin/custom/group/field', 'reset=1&action=browse&gid=' . $this->_gid);
572 $this->addElement('button',
573 'done',
574 ts('Done'),
575 ['onclick' => "location.href='$url'"]
576 );
577 }
578 }
579
580 /**
581 * Global validation rules for the form.
582 *
583 * @param array $fields
584 * Posted values of the form.
585 *
586 * @param $files
587 * @param $self
588 *
589 * @return array
590 * if errors then list of errors to be posted back to the form,
591 * true otherwise
592 */
593 public static function formRule($fields, $files, $self) {
594 $default = $fields['default_value'] ?? NULL;
595
596 $errors = [];
597
598 self::clearEmptyOptions($fields);
599
600 //validate field label as well as name.
601 $title = $fields['label'];
602 $name = CRM_Utils_String::munge($title, '_', 64);
603 // CRM-7564
604 $gId = $self->_gid;
605 $query = 'select count(*) from civicrm_custom_field where ( name like %1 OR label like %2 ) and id != %3 and custom_group_id = %4';
606 $fldCnt = CRM_Core_DAO::singleValueQuery($query, [
607 1 => [$name, 'String'],
608 2 => [$title, 'String'],
609 3 => [(int) $self->_id, 'Integer'],
610 4 => [$gId, 'Integer'],
611 ]);
612 if ($fldCnt) {
613 $errors['label'] = ts('Custom field \'%1\' already exists in Database.', [1 => $title]);
614 }
615
616 //checks the given custom field name doesnot start with digit
617 if (!empty($title)) {
618 // gives the ascii value
619 $asciiValue = ord($title[0]);
620 if ($asciiValue >= 48 && $asciiValue <= 57) {
621 $errors['label'] = ts("Name cannot not start with a digit");
622 }
623 }
624
625 // ensure that the label is not 'id'
626 if (strtolower($title) == 'id') {
627 $errors['label'] = ts("You cannot use 'id' as a field label.");
628 }
629
630 if (!isset($fields['data_type'][0]) || !isset($fields['data_type'][1])) {
631 $errors['_qf_default'] = ts('Please enter valid - Data and Input Field Type.');
632 }
633
634 $dataType = self::$_dataTypeKeys[$fields['data_type'][0]];
635
636 if ($default || $dataType == 'ContactReference') {
637 switch ($dataType) {
638 case 'Int':
639 if (!CRM_Utils_Rule::integer($default)) {
640 $errors['default_value'] = ts('Please enter a valid integer.');
641 }
642 break;
643
644 case 'Float':
645 if (!CRM_Utils_Rule::numeric($default)) {
646 $errors['default_value'] = ts('Please enter a valid number.');
647 }
648 break;
649
650 case 'Money':
651 if (!CRM_Utils_Rule::money($default)) {
652 $errors['default_value'] = ts('Please enter a valid number.');
653 }
654 break;
655
656 case 'Link':
657 if (!CRM_Utils_Rule::url($default)) {
658 $errors['default_value'] = ts('Please enter a valid link.');
659 }
660 break;
661
662 case 'Date':
663 if (!CRM_Utils_Rule::date($default)) {
664 $errors['default_value'] = ts('Please enter a valid date as default value using YYYY-MM-DD format. Example: 2004-12-31.');
665 }
666 break;
667
668 case 'Boolean':
669 if ($default != '1' && $default != '0') {
670 $errors['default_value'] = ts('Please enter 1 (for Yes) or 0 (for No) if you want to set a default value.');
671 }
672 break;
673
674 case 'Country':
675 if (!empty($default)) {
676 $query = "SELECT count(*) FROM civicrm_country WHERE name = %1 OR iso_code = %1";
677 $params = [1 => [$fields['default_value'], 'String']];
678 if (CRM_Core_DAO::singleValueQuery($query, $params) <= 0) {
679 $errors['default_value'] = ts('Invalid default value for country.');
680 }
681 }
682 break;
683
684 case 'StateProvince':
685 if (!empty($default)) {
686 $query = "
687 SELECT count(*)
688 FROM civicrm_state_province
689 WHERE name = %1
690 OR abbreviation = %1";
691 $params = [1 => [$fields['default_value'], 'String']];
692 if (CRM_Core_DAO::singleValueQuery($query, $params) <= 0) {
693 $errors['default_value'] = ts('The invalid default value for State/Province data type');
694 }
695 }
696 break;
697
698 case 'ContactReference':
699 if ($fields['filter_selected'] == 'Advance' && !empty($fields['filter'])) {
700 if (strpos($fields['filter'], 'entity=') !== FALSE) {
701 $errors['filter'] = ts("Please do not include entity parameter (entity is always 'contact')");
702 }
703 elseif (strpos($fields['filter'], 'action=get') === FALSE) {
704 $errors['filter'] = ts("Only 'get' action is supported.");
705 }
706 }
707 $self->setDefaults(['filter_selected', $fields['filter_selected']]);
708 break;
709 }
710 }
711
712 if (self::$_dataTypeKeys[$fields['data_type'][0]] == 'Date') {
713 if (!$fields['date_format']) {
714 $errors['date_format'] = ts('Please select a date format.');
715 }
716 }
717
718 /** Check the option values entered
719 * Appropriate values are required for the selected datatype
720 * Incomplete row checking is also required.
721 */
722 $_flagOption = $_rowError = 0;
723 $_showHide = new CRM_Core_ShowHideBlocks('', '');
724 $dataType = self::$_dataTypeKeys[$fields['data_type'][0]];
725 if (isset($fields['data_type'][1])) {
726 $dataField = $fields['data_type'][1];
727 }
728 $optionFields = ['Select', 'CheckBox', 'Radio'];
729
730 if (isset($fields['option_type']) && $fields['option_type'] == 1) {
731 //capture duplicate Custom option values
732 if (!empty($fields['option_value'])) {
733 $countValue = count($fields['option_value']);
734 $uniqueCount = count(array_unique($fields['option_value']));
735
736 if ($countValue > $uniqueCount) {
737
738 $start = 1;
739 while ($start < self::NUM_OPTION) {
740 $nextIndex = $start + 1;
741 while ($nextIndex <= self::NUM_OPTION) {
742 if ($fields['option_value'][$start] == $fields['option_value'][$nextIndex] &&
743 strlen($fields['option_value'][$nextIndex])
744 ) {
745 $errors['option_value[' . $start . ']'] = ts('Duplicate Option values');
746 $errors['option_value[' . $nextIndex . ']'] = ts('Duplicate Option values');
747 $_flagOption = 1;
748 }
749 $nextIndex++;
750 }
751 $start++;
752 }
753 }
754 }
755
756 //capture duplicate Custom Option label
757 if (!empty($fields['option_label'])) {
758 $countValue = count($fields['option_label']);
759 $uniqueCount = count(array_unique($fields['option_label']));
760
761 if ($countValue > $uniqueCount) {
762 $start = 1;
763 while ($start < self::NUM_OPTION) {
764 $nextIndex = $start + 1;
765 while ($nextIndex <= self::NUM_OPTION) {
766 if ($fields['option_label'][$start] == $fields['option_label'][$nextIndex] &&
767 !empty($fields['option_label'][$nextIndex])
768 ) {
769 $errors['option_label[' . $start . ']'] = ts('Duplicate Option label');
770 $errors['option_label[' . $nextIndex . ']'] = ts('Duplicate Option label');
771 $_flagOption = 1;
772 }
773 $nextIndex++;
774 }
775 $start++;
776 }
777 }
778 }
779
780 for ($i = 1; $i <= self::NUM_OPTION; $i++) {
781 if (!$fields['option_label'][$i]) {
782 if ($fields['option_value'][$i]) {
783 $errors['option_label[' . $i . ']'] = ts('Option label cannot be empty');
784 $_flagOption = 1;
785 }
786 else {
787 $_emptyRow = 1;
788 }
789 }
790 else {
791 if (!strlen(trim($fields['option_value'][$i]))) {
792 if (!$fields['option_value'][$i]) {
793 $errors['option_value[' . $i . ']'] = ts('Option value cannot be empty');
794 $_flagOption = 1;
795 }
796 }
797 }
798
799 if ($fields['option_value'][$i] && $dataType != 'String') {
800 if ($dataType == 'Int') {
801 if (!CRM_Utils_Rule::integer($fields['option_value'][$i])) {
802 $_flagOption = 1;
803 $errors['option_value[' . $i . ']'] = ts('Please enter a valid integer.');
804 }
805 }
806 elseif ($dataType == 'Money') {
807 if (!CRM_Utils_Rule::money($fields['option_value'][$i])) {
808 $_flagOption = 1;
809 $errors['option_value[' . $i . ']'] = ts('Please enter a valid money value.');
810 }
811 }
812 else {
813 if (!CRM_Utils_Rule::numeric($fields['option_value'][$i])) {
814 $_flagOption = 1;
815 $errors['option_value[' . $i . ']'] = ts('Please enter a valid number.');
816 }
817 }
818 }
819
820 $showBlocks = 'optionField_' . $i;
821 if ($_flagOption) {
822 $_showHide->addShow($showBlocks);
823 $_rowError = 1;
824 }
825
826 if (!empty($_emptyRow)) {
827 $_showHide->addHide($showBlocks);
828 }
829 else {
830 $_showHide->addShow($showBlocks);
831 }
832 if ($i == self::NUM_OPTION) {
833 $hideBlock = 'additionalOption';
834 $_showHide->addHide($hideBlock);
835 }
836
837 $_flagOption = $_emptyRow = 0;
838 }
839 }
840 elseif (isset($dataField) &&
841 in_array($dataField, $optionFields) &&
842 !in_array($dataType, ['Boolean', 'Country', 'StateProvince'])
843 ) {
844 if (!$fields['option_group_id']) {
845 $errors['option_group_id'] = ts('You must select a Multiple Choice Option set if you chose Reuse an existing set.');
846 }
847 else {
848 $query = "
849 SELECT count(*)
850 FROM civicrm_custom_field
851 WHERE data_type != %1
852 AND option_group_id = %2";
853 $params = [
854 1 => [
855 self::$_dataTypeKeys[$fields['data_type'][0]],
856 'String',
857 ],
858 2 => [$fields['option_group_id'], 'Integer'],
859 ];
860 $count = CRM_Core_DAO::singleValueQuery($query, $params);
861 if ($count > 0) {
862 $errors['option_group_id'] = ts('The data type of the multiple choice option set you\'ve selected does not match the data type assigned to this field.');
863 }
864 }
865 }
866
867 $assignError = new CRM_Core_Page();
868 if ($_rowError) {
869 $_showHide->addToTemplate();
870 $assignError->assign('optionRowError', $_rowError);
871 }
872 else {
873 if (isset($fields['data_type'][1])) {
874 switch (self::$_dataToHTML[$fields['data_type'][0]][$fields['data_type'][1]]) {
875 case 'Radio':
876 $_fieldError = 1;
877 $assignError->assign('fieldError', $_fieldError);
878 break;
879
880 case 'Checkbox':
881 $_fieldError = 1;
882 $assignError->assign('fieldError', $_fieldError);
883 break;
884
885 case 'Select':
886 $_fieldError = 1;
887 $assignError->assign('fieldError', $_fieldError);
888 break;
889
890 default:
891 $_fieldError = 0;
892 $assignError->assign('fieldError', $_fieldError);
893 }
894 }
895
896 for ($idx = 1; $idx <= self::NUM_OPTION; $idx++) {
897 $showBlocks = 'optionField_' . $idx;
898 if (!empty($fields['option_label'][$idx])) {
899 $_showHide->addShow($showBlocks);
900 }
901 else {
902 $_showHide->addHide($showBlocks);
903 }
904 }
905 $_showHide->addToTemplate();
906 }
907
908 // we can not set require and view at the same time.
909 if (!empty($fields['is_required']) && !empty($fields['is_view'])) {
910 $errors['is_view'] = ts('Can not set this field Required and View Only at the same time.');
911 }
912
913 return empty($errors) ? TRUE : $errors;
914 }
915
916 /**
917 * Process the form.
918 *
919 * @return void
920 */
921 public function postProcess() {
922 // store the submitted values in an array
923 $params = $this->controller->exportValues($this->_name);
924 self::clearEmptyOptions($params);
925 if ($this->_action == CRM_Core_Action::UPDATE) {
926 $dataTypeKey = $this->_defaultDataType[0];
927 $params['data_type'] = self::$_dataTypeKeys[$this->_defaultDataType[0]];
928 $params['html_type'] = self::$_dataToHTML[$this->_defaultDataType[0]][$this->_defaultDataType[1]];
929 }
930 else {
931 $dataTypeKey = $params['data_type'][0];
932 $params['html_type'] = self::$_dataToHTML[$params['data_type'][0]][$params['data_type'][1]];
933 $params['data_type'] = self::$_dataTypeKeys[$params['data_type'][0]];
934 }
935
936 //fix for 'is_search_range' field.
937 if (in_array($dataTypeKey, [
938 1,
939 2,
940 3,
941 5,
942 ])) {
943 if (empty($params['is_searchable'])) {
944 $params['is_search_range'] = 0;
945 }
946 }
947 else {
948 $params['is_search_range'] = 0;
949 }
950
951 // Serialization cannot be changed on update
952 if ($this->_id) {
953 unset($params['serialize']);
954 }
955 elseif (strpos($params['html_type'], 'Select') === 0) {
956 $params['serialize'] = $params['serialize'] ? CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND : 'null';
957 }
958 else {
959 $params['serialize'] = $params['html_type'] == 'CheckBox' ? CRM_Core_DAO::SERIALIZE_SEPARATOR_BOOKEND : 'null';
960 }
961
962 $filter = 'null';
963 if ($dataTypeKey == 11 && !empty($params['filter_selected'])) {
964 if ($params['filter_selected'] == 'Advance' && trim(CRM_Utils_Array::value('filter', $params))) {
965 $filter = trim($params['filter']);
966 }
967 elseif ($params['filter_selected'] == 'Group' && !empty($params['group_id'])) {
968
969 $filter = 'action=lookup&group=' . implode(',', $params['group_id']);
970 }
971 }
972 $params['filter'] = $filter;
973
974 // fix for CRM-316
975 $oldWeight = NULL;
976 if ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD)) {
977 $fieldValues = ['custom_group_id' => $this->_gid];
978 if ($this->_id) {
979 $oldWeight = $this->_values['weight'];
980 }
981 $params['weight'] = CRM_Utils_Weight::updateOtherWeights('CRM_Core_DAO_CustomField', $oldWeight, $params['weight'], $fieldValues);
982 }
983
984 $strtolower = function_exists('mb_strtolower') ? 'mb_strtolower' : 'strtolower';
985
986 //store the primary key for State/Province or Country as default value.
987 if (strlen(trim($params['default_value']))) {
988 switch ($params['data_type']) {
989 case 'StateProvince':
990 $fieldStateProvince = $strtolower($params['default_value']);
991
992 // LOWER in query below roughly translates to 'hurt my database without deriving any benefit' See CRM-19811.
993 $query = "
994 SELECT id
995 FROM civicrm_state_province
996 WHERE LOWER(name) = '$fieldStateProvince'
997 OR abbreviation = '$fieldStateProvince'";
998 $dao = CRM_Core_DAO::executeQuery($query);
999 if ($dao->fetch()) {
1000 $params['default_value'] = $dao->id;
1001 }
1002 break;
1003
1004 case 'Country':
1005 $fieldCountry = $strtolower($params['default_value']);
1006
1007 // LOWER in query below roughly translates to 'hurt my database without deriving any benefit' See CRM-19811.
1008 $query = "
1009 SELECT id
1010 FROM civicrm_country
1011 WHERE LOWER(name) = '$fieldCountry'
1012 OR iso_code = '$fieldCountry'";
1013 $dao = CRM_Core_DAO::executeQuery($query);
1014 if ($dao->fetch()) {
1015 $params['default_value'] = $dao->id;
1016 }
1017 break;
1018 }
1019 }
1020
1021 // The text_length attribute for Memo fields is in a different input as there
1022 // are different label, help text and default value than for other type fields
1023 if ($params['data_type'] == "Memo") {
1024 $params['text_length'] = $params['note_length'];
1025 }
1026
1027 // need the FKEY - custom group id
1028 $params['custom_group_id'] = $this->_gid;
1029
1030 if ($this->_action & CRM_Core_Action::UPDATE) {
1031 $params['id'] = $this->_id;
1032 }
1033 $customField = CRM_Core_BAO_CustomField::create($params);
1034 $this->_id = $customField->id;
1035
1036 // reset the cache
1037 Civi::cache('fields')->flush();
1038
1039 $msg = '<p>' . ts("Custom field '%1' has been saved.", [1 => $customField->label]) . '</p>';
1040
1041 $buttonName = $this->controller->getButtonName();
1042 $session = CRM_Core_Session::singleton();
1043 if ($buttonName == $this->getButtonName('next', 'new')) {
1044 $msg .= '<p>' . ts("Ready to add another.") . '</p>';
1045 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/custom/group/field/add',
1046 'reset=1&action=add&gid=' . $this->_gid
1047 ));
1048 }
1049 else {
1050 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/custom/group/field',
1051 'reset=1&action=browse&gid=' . $this->_gid
1052 ));
1053 }
1054 $session->setStatus($msg, ts('Saved'), 'success');
1055
1056 // Add data when in ajax contect
1057 $this->ajaxResponse['customField'] = $customField->toArray();
1058 }
1059
1060 /**
1061 * Removes value from fields with no label.
1062 *
1063 * This allows default values to be set in the form, but ignored in post-processing.
1064 *
1065 * @param array $fields
1066 */
1067 public static function clearEmptyOptions(&$fields) {
1068 foreach ($fields['option_label'] as $i => $label) {
1069 if (!strlen(trim($label))) {
1070 $fields['option_value'][$i] = '';
1071 }
1072 }
1073 }
1074
1075 }