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