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