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