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