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