1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Form
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 use Joomla\String\Normalise;
13 use Joomla\String\StringHelper;
14
15 /**
16 * Abstract Form Field class for the Joomla Platform.
17 *
18 * @since 11.1
19 */
20 abstract class JFormField
21 {
22 /**
23 * The description text for the form field. Usually used in tooltips.
24 *
25 * @var string
26 * @since 11.1
27 */
28 protected $description;
29
30 /**
31 * The hint text for the form field used to display hint inside the field.
32 *
33 * @var string
34 * @since 3.2
35 */
36 protected $hint;
37
38 /**
39 * The autocomplete state for the form field. If 'off' element will not be automatically
40 * completed by browser.
41 *
42 * @var mixed
43 * @since 3.2
44 */
45 protected $autocomplete = 'on';
46
47 /**
48 * The spellcheck state for the form field.
49 *
50 * @var boolean
51 * @since 3.2
52 */
53 protected $spellcheck = true;
54
55 /**
56 * The autofocus request for the form field. If true element will be automatically
57 * focused on document load.
58 *
59 * @var boolean
60 * @since 3.2
61 */
62 protected $autofocus = false;
63
64 /**
65 * The SimpleXMLElement object of the `<field>` XML element that describes the form field.
66 *
67 * @var SimpleXMLElement
68 * @since 11.1
69 */
70 protected $element;
71
72 /**
73 * The JForm object of the form attached to the form field.
74 *
75 * @var JForm
76 * @since 11.1
77 */
78 protected $form;
79
80 /**
81 * The form control prefix for field names from the JForm object attached to the form field.
82 *
83 * @var string
84 * @since 11.1
85 */
86 protected $formControl;
87
88 /**
89 * The hidden state for the form field.
90 *
91 * @var boolean
92 * @since 11.1
93 */
94 protected = false;
95
96 /**
97 * True to translate the field label string.
98 *
99 * @var boolean
100 * @since 11.1
101 */
102 protected $translateLabel = true;
103
104 /**
105 * True to translate the field description string.
106 *
107 * @var boolean
108 * @since 11.1
109 */
110 protected $translateDescription = true;
111
112 /**
113 * True to translate the field hint string.
114 *
115 * @var boolean
116 * @since 3.2
117 */
118 protected $translateHint = true;
119
120 /**
121 * The document id for the form field.
122 *
123 * @var string
124 * @since 11.1
125 */
126 protected $id;
127
128 /**
129 * The input for the form field.
130 *
131 * @var string
132 * @since 11.1
133 */
134 protected $input;
135
136 /**
137 * The label for the form field.
138 *
139 * @var string
140 * @since 11.1
141 */
142 protected $label;
143
144 /**
145 * The multiple state for the form field. If true then multiple values are allowed for the
146 * field. Most often used for list field types.
147 *
148 * @var boolean
149 * @since 11.1
150 */
151 protected $multiple = false;
152
153 /**
154 * Allows extensions to create repeat elements
155 *
156 * @var mixed
157 * @since 3.2
158 */
159 public $repeat = false;
160
161 /**
162 * The pattern (Reg Ex) of value of the form field.
163 *
164 * @var string
165 * @since 11.1
166 */
167 protected $pattern;
168
169 /**
170 * The name of the form field.
171 *
172 * @var string
173 * @since 11.1
174 */
175 protected $name;
176
177 /**
178 * The name of the field.
179 *
180 * @var string
181 * @since 11.1
182 */
183 protected $fieldname;
184
185 /**
186 * The group of the field.
187 *
188 * @var string
189 * @since 11.1
190 */
191 protected $group;
192
193 /**
194 * The required state for the form field. If true then there must be a value for the field to
195 * be considered valid.
196 *
197 * @var boolean
198 * @since 11.1
199 */
200 protected $required = false;
201
202 /**
203 * The disabled state for the form field. If true then the field will be disabled and user can't
204 * interact with the field.
205 *
206 * @var boolean
207 * @since 3.2
208 */
209 protected $disabled = false;
210
211 /**
212 * The readonly state for the form field. If true then the field will be readonly.
213 *
214 * @var boolean
215 * @since 3.2
216 */
217 protected $readonly = false;
218
219 /**
220 * The form field type.
221 *
222 * @var string
223 * @since 11.1
224 */
225 protected $type;
226
227 /**
228 * The validation method for the form field. This value will determine which method is used
229 * to validate the value for a field.
230 *
231 * @var string
232 * @since 11.1
233 */
234 protected $validate;
235
236 /**
237 * The value of the form field.
238 *
239 * @var mixed
240 * @since 11.1
241 */
242 protected $value;
243
244 /**
245 * The default value of the form field.
246 *
247 * @var mixed
248 * @since 11.1
249 */
250 protected $default;
251
252 /**
253 * The size of the form field.
254 *
255 * @var integer
256 * @since 3.2
257 */
258 protected $size;
259
260 /**
261 * The class of the form field
262 *
263 * @var mixed
264 * @since 3.2
265 */
266 protected $class;
267
268 /**
269 * The label's CSS class of the form field
270 *
271 * @var mixed
272 * @since 11.1
273 */
274 protected $labelclass;
275
276 /**
277 * The javascript onchange of the form field.
278 *
279 * @var string
280 * @since 3.2
281 */
282 protected $onchange;
283
284 /**
285 * The javascript onclick of the form field.
286 *
287 * @var string
288 * @since 3.2
289 */
290 protected $onclick;
291
292 /**
293 * The conditions to show/hide the field.
294 *
295 * @var string
296 * @since 3.7.0
297 */
298 protected $showon;
299
300 /**
301 * The count value for generated name field
302 *
303 * @var integer
304 * @since 11.1
305 */
306 protected static $count = 0;
307
308 /**
309 * The string used for generated fields names
310 *
311 * @var string
312 * @since 11.1
313 */
314 protected static $generated_fieldname = '__field';
315
316 /**
317 * Name of the layout being used to render the field
318 *
319 * @var string
320 * @since 3.5
321 */
322 protected $layout;
323
324 /**
325 * Layout to render the form field
326 *
327 * @var string
328 */
329 protected $renderLayout = 'joomla.form.renderfield';
330
331 /**
332 * Layout to render the label
333 *
334 * @var string
335 */
336 protected $renderLabelLayout = 'joomla.form.renderlabel';
337
338 /**
339 * Method to instantiate the form field object.
340 *
341 * @param JForm $form The form to attach to the form field object.
342 *
343 * @since 11.1
344 */
345 public function __construct($form = null)
346 {
347 // If there is a form passed into the constructor set the form and form control properties.
348 if ($form instanceof JForm)
349 {
350 $this->form = $form;
351 $this->formControl = $form->getFormControl();
352 }
353
354 // Detect the field type if not set
355 if (!isset($this->type))
356 {
357 $parts = Normalise::fromCamelCase(get_called_class(), true);
358
359 if ($parts[0] == 'J')
360 {
361 $this->type = StringHelper::ucfirst($parts[count($parts) - 1], '_');
362 }
363 else
364 {
365 $this->type = StringHelper::ucfirst($parts[0], '_') . StringHelper::ucfirst($parts[count($parts) - 1], '_');
366 }
367 }
368 }
369
370 /**
371 * Method to get certain otherwise inaccessible properties from the form field object.
372 *
373 * @param string $name The property name for which to the the value.
374 *
375 * @return mixed The property value or null.
376 *
377 * @since 11.1
378 */
379 public function __get($name)
380 {
381 switch ($name)
382 {
383 case 'description':
384 case 'hint':
385 case 'formControl':
386 case 'hidden':
387 case 'id':
388 case 'multiple':
389 case 'name':
390 case 'required':
391 case 'type':
392 case 'validate':
393 case 'value':
394 case 'class':
395 case 'layout':
396 case 'labelclass':
397 case 'size':
398 case 'onchange':
399 case 'onclick':
400 case 'fieldname':
401 case 'group':
402 case 'disabled':
403 case 'readonly':
404 case 'autofocus':
405 case 'autocomplete':
406 case 'spellcheck':
407 case 'showon':
408 return $this->$name;
409
410 case 'input':
411 // If the input hasn't yet been generated, generate it.
412 if (empty($this->input))
413 {
414 $this->input = $this->getInput();
415 }
416
417 return $this->input;
418
419 case 'label':
420 // If the label hasn't yet been generated, generate it.
421 if (empty($this->label))
422 {
423 $this->label = $this->getLabel();
424 }
425
426 return $this->label;
427
428 case 'title':
429 return $this->getTitle();
430 }
431
432 return;
433 }
434
435 /**
436 * Method to set certain otherwise inaccessible properties of the form field object.
437 *
438 * @param string $name The property name for which to the the value.
439 * @param mixed $value The value of the property.
440 *
441 * @return void
442 *
443 * @since 3.2
444 */
445 public function __set($name, $value)
446 {
447 switch ($name)
448 {
449 case 'class':
450 // Removes spaces from left & right and extra spaces from middle
451 $value = preg_replace('/\s+/', ' ', trim((string) $value));
452
453 case 'description':
454 case 'hint':
455 case 'value':
456 case 'labelclass':
457 case 'layout':
458 case 'onchange':
459 case 'onclick':
460 case 'validate':
461 case 'pattern':
462 case 'group':
463 case 'showon':
464 case 'default':
465 $this->$name = (string) $value;
466 break;
467
468 case 'id':
469 $this->id = $this->getId((string) $value, $this->fieldname);
470 break;
471
472 case 'fieldname':
473 $this->fieldname = $this->getFieldName((string) $value);
474 break;
475
476 case 'name':
477 $this->fieldname = $this->getFieldName((string) $value);
478 $this->name = $this->getName($this->fieldname);
479 break;
480
481 case 'multiple':
482 // Allow for field classes to force the multiple values option.
483 $value = (string) $value;
484 $value = $value === '' && isset($this->forceMultiple) ? (string) $this->forceMultiple : $value;
485
486 case 'required':
487 case 'disabled':
488 case 'readonly':
489 case 'autofocus':
490 case 'hidden':
491 $value = (string) $value;
492 $this->$name = ($value === 'true' || $value === $name || $value === '1');
493 break;
494
495 case 'autocomplete':
496 $value = (string) $value;
497 $value = ($value == 'on' || $value == '') ? 'on' : $value;
498 $this->$name = ($value === 'false' || $value === 'off' || $value === '0') ? false : $value;
499 break;
500
501 case 'spellcheck':
502 case 'translateLabel':
503 case 'translateDescription':
504 case 'translateHint':
505 $value = (string) $value;
506 $this->$name = !($value === 'false' || $value === 'off' || $value === '0');
507 break;
508
509 case 'translate_label':
510 $value = (string) $value;
511 $this->translateLabel = $this->translateLabel && !($value === 'false' || $value === 'off' || $value === '0');
512 break;
513
514 case 'translate_description':
515 $value = (string) $value;
516 $this->translateDescription = $this->translateDescription && !($value === 'false' || $value === 'off' || $value === '0');
517 break;
518
519 case 'size':
520 $this->$name = (int) $value;
521 break;
522
523 default:
524 if (property_exists(__CLASS__, $name))
525 {
526 JLog::add("Cannot access protected / private property $name of " . __CLASS__);
527 }
528 else
529 {
530 $this->$name = $value;
531 }
532 }
533 }
534
535 /**
536 * Method to attach a JForm object to the field.
537 *
538 * @param JForm $form The JForm object to attach to the form field.
539 *
540 * @return JFormField The form field object so that the method can be used in a chain.
541 *
542 * @since 11.1
543 */
544 public function setForm(JForm $form)
545 {
546 $this->form = $form;
547 $this->formControl = $form->getFormControl();
548
549 return $this;
550 }
551
552 /**
553 * Method to attach a JForm object to the field.
554 *
555 * @param SimpleXMLElement $element The SimpleXMLElement object representing the `<field>` tag for the form field object.
556 * @param mixed $value The form field value to validate.
557 * @param string $group The field name group control value. This acts as an array container for the field.
558 * For example if the field has name="foo" and the group value is set to "bar" then the
559 * full field name would end up being "bar[foo]".
560 *
561 * @return boolean True on success.
562 *
563 * @since 11.1
564 */
565 public function setup(SimpleXMLElement $element, $value, $group = null)
566 {
567 // Make sure there is a valid JFormField XML element.
568 if ((string) $element->getName() != 'field')
569 {
570 return false;
571 }
572
573 // Reset the input and label values.
574 $this->input = null;
575 $this->label = null;
576
577 // Set the XML element object.
578 $this->element = $element;
579
580 // Set the group of the field.
581 $this->group = $group;
582
583 $attributes = array(
584 'multiple', 'name', 'id', 'hint', 'class', 'description', 'labelclass', 'onchange', 'onclick', 'validate', 'pattern', 'default',
585 'required', 'disabled', 'readonly', 'autofocus', 'hidden', 'autocomplete', 'spellcheck', 'translateHint', 'translateLabel',
586 'translate_label', 'translateDescription', 'translate_description', 'size', 'showon');
587
588 $this->default = isset($element['value']) ? (string) $element['value'] : $this->default;
589
590 // Set the field default value.
591 $this->value = $value;
592
593 foreach ($attributes as $attributeName)
594 {
595 $this->__set($attributeName, $element[$attributeName]);
596 }
597
598 // Allow for repeatable elements
599 $repeat = (string) $element['repeat'];
600 $this->repeat = ($repeat == 'true' || $repeat == 'multiple' || (!empty($this->form->repeat) && $this->form->repeat == 1));
601
602 // Set the visibility.
603 $this->hidden = ($this->hidden || (string) $element['type'] == 'hidden');
604
605 $this->layout = !empty($this->element['layout']) ? (string) $this->element['layout'] : $this->layout;
606
607 // Add required to class list if field is required.
608 if ($this->required)
609 {
610 $this->class = trim($this->class . ' required');
611 }
612
613 return true;
614 }
615
616 /**
617 * Simple method to set the value
618 *
619 * @param mixed $value Value to set
620 *
621 * @return void
622 *
623 * @since 3.2
624 */
625 public function setValue($value)
626 {
627 $this->value = $value;
628 }
629
630 /**
631 * Method to get the id used for the field input tag.
632 *
633 * @param string $fieldId The field element id.
634 * @param string $fieldName The field element name.
635 *
636 * @return string The id to be used for the field input tag.
637 *
638 * @since 11.1
639 */
640 protected function getId($fieldId, $fieldName)
641 {
642 $id = '';
643
644 // If there is a form control set for the attached form add it first.
645 if ($this->formControl)
646 {
647 $id .= $this->formControl;
648 }
649
650 // If the field is in a group add the group control to the field id.
651 if ($this->group)
652 {
653 // If we already have an id segment add the group control as another level.
654 if ($id)
655 {
656 $id .= '_' . str_replace('.', '_', $this->group);
657 }
658 else
659 {
660 $id .= str_replace('.', '_', $this->group);
661 }
662 }
663
664 // If we already have an id segment add the field id/name as another level.
665 if ($id)
666 {
667 $id .= '_' . ($fieldId ? $fieldId : $fieldName);
668 }
669 else
670 {
671 $id .= ($fieldId ? $fieldId : $fieldName);
672 }
673
674 // Clean up any invalid characters.
675 $id = preg_replace('#\W#', '_', $id);
676
677 // If this is a repeatable element, add the repeat count to the ID
678 if ($this->repeat)
679 {
680 $repeatCounter = empty($this->form->repeatCounter) ? 0 : $this->form->repeatCounter;
681 $id .= '-' . $repeatCounter;
682
683 if (strtolower($this->type) == 'radio')
684 {
685 $id .= '-';
686 }
687 }
688
689 return $id;
690 }
691
692 /**
693 * Method to get the field input markup.
694 *
695 * @return string The field input markup.
696 *
697 * @since 11.1
698 */
699 protected function getInput()
700 {
701 if (empty($this->layout))
702 {
703 throw new UnexpectedValueException(sprintf('%s has no layout assigned.', $this->name));
704 }
705
706 return $this->getRenderer($this->layout)->render($this->getLayoutData());
707 }
708
709 /**
710 * Method to get the field title.
711 *
712 * @return string The field title.
713 *
714 * @since 11.1
715 */
716 protected function getTitle()
717 {
718 $title = '';
719
720 if ($this->hidden)
721 {
722 return $title;
723 }
724
725 // Get the label text from the XML element, defaulting to the element name.
726 $title = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
727 $title = $this->translateLabel ? JText::_($title) : $title;
728
729 return $title;
730 }
731
732 /**
733 * Method to get the field label markup.
734 *
735 * @return string The field label markup.
736 *
737 * @since 11.1
738 */
739 protected function getLabel()
740 {
741 if ($this->hidden)
742 {
743 return '';
744 }
745
746 $data = $this->getLayoutData();
747
748 // Forcing the Alias field to display the tip below
749 $position = $this->element['name'] == 'alias' ? ' data-placement="bottom" ' : '';
750
751 // Here mainly for B/C with old layouts. This can be done in the layouts directly
752 $extraData = array(
753 'text' => $data['label'],
754 'for' => $this->id,
755 'classes' => explode(' ', $data['labelclass']),
756 'position' => $position,
757 );
758
759 return $this->getRenderer($this->renderLabelLayout)->render(array_merge($data, $extraData));
760 }
761
762 /**
763 * Method to get the name used for the field input tag.
764 *
765 * @param string $fieldName The field element name.
766 *
767 * @return string The name to be used for the field input tag.
768 *
769 * @since 11.1
770 */
771 protected function getName($fieldName)
772 {
773 // To support repeated element, extensions can set this in plugin->onRenderSettings
774
775 $name = '';
776
777 // If there is a form control set for the attached form add it first.
778 if ($this->formControl)
779 {
780 $name .= $this->formControl;
781 }
782
783 // If the field is in a group add the group control to the field name.
784 if ($this->group)
785 {
786 // If we already have a name segment add the group control as another level.
787 $groups = explode('.', $this->group);
788
789 if ($name)
790 {
791 foreach ($groups as $group)
792 {
793 $name .= '[' . $group . ']';
794 }
795 }
796 else
797 {
798 $name .= array_shift($groups);
799
800 foreach ($groups as $group)
801 {
802 $name .= '[' . $group . ']';
803 }
804 }
805 }
806
807 // If we already have a name segment add the field name as another level.
808 if ($name)
809 {
810 $name .= '[' . $fieldName . ']';
811 }
812 else
813 {
814 $name .= $fieldName;
815 }
816
817 // If the field should support multiple values add the final array segment.
818 if ($this->multiple)
819 {
820 switch (strtolower((string) $this->element['type']))
821 {
822 case 'text':
823 case 'textarea':
824 case 'email':
825 case 'password':
826 case 'radio':
827 case 'calendar':
828 case 'editor':
829 case 'hidden':
830 break;
831 default:
832 $name .= '[]';
833 }
834 }
835
836 return $name;
837 }
838
839 /**
840 * Method to get the field name used.
841 *
842 * @param string $fieldName The field element name.
843 *
844 * @return string The field name
845 *
846 * @since 11.1
847 */
848 protected function getFieldName($fieldName)
849 {
850 if ($fieldName)
851 {
852 return $fieldName;
853 }
854 else
855 {
856 self::$count = self::$count + 1;
857
858 return self::$generated_fieldname . self::$count;
859 }
860 }
861
862 /**
863 * Method to get an attribute of the field
864 *
865 * @param string $name Name of the attribute to get
866 * @param mixed $default Optional value to return if attribute not found
867 *
868 * @return mixed Value of the attribute / default
869 *
870 * @since 3.2
871 */
872 public function getAttribute($name, $default = null)
873 {
874 if ($this->element instanceof SimpleXMLElement)
875 {
876 $attributes = $this->element->attributes();
877
878 // Ensure that the attribute exists
879 if (property_exists($attributes, $name))
880 {
881 $value = $attributes->$name;
882
883 if ($value !== null)
884 {
885 return (string) $value;
886 }
887 }
888 }
889
890 return $default;
891 }
892
893 /**
894 * Method to get a control group with label and input.
895 *
896 * @return string A string containing the html for the control group
897 *
898 * @since 3.2
899 * @deprecated 3.2.3 Use renderField() instead
900 */
901 public function getControlGroup()
902 {
903 JLog::add('JFormField->getControlGroup() is deprecated use JFormField->renderField().', JLog::WARNING, 'deprecated');
904
905 return $this->renderField();
906 }
907
908 /**
909 * Render a layout of this field
910 *
911 * @param string $layoutId Layout identifier
912 * @param array $data Optional data for the layout
913 *
914 * @return string
915 *
916 * @since 3.5
917 */
918 public function render($layoutId, $data = array())
919 {
920 $data = array_merge($this->getLayoutData(), $data);
921
922 return $this->getRenderer($layoutId)->render($data);
923 }
924
925 /**
926 * Method to get a control group with label and input.
927 *
928 * @param array $options Options to be passed into the rendering of the field
929 *
930 * @return string A string containing the html for the control group
931 *
932 * @since 3.2
933 */
934 public function renderField($options = array())
935 {
936 if ($this->hidden)
937 {
938 return $this->getInput();
939 }
940
941 if (!isset($options['class']))
942 {
943 $options['class'] = '';
944 }
945
946 $options['rel'] = '';
947
948 if (empty($options['hiddenLabel']) && $this->getAttribute('hiddenLabel'))
949 {
950 $options['hiddenLabel'] = true;
951 }
952
953 if ($this->showon)
954 {
955 $options['rel'] = ' data-showon=\'' .
956 json_encode(JFormHelper::parseShowOnConditions($this->showon, $this->formControl, $this->group)) . '\'';
957 $options['showonEnabled'] = true;
958 }
959
960 $data = array(
961 'input' => $this->getInput(),
962 'label' => $this->getLabel(),
963 'options' => $options,
964 );
965
966 return $this->getRenderer($this->renderLayout)->render($data);
967 }
968
969 /**
970 * Method to get the data to be passed to the layout for rendering.
971 *
972 * @return array
973 *
974 * @since 3.5
975 */
976 protected function getLayoutData()
977 {
978 // Label preprocess
979 $label = $this->element['label'] ? (string) $this->element['label'] : (string) $this->element['name'];
980 $label = $this->translateLabel ? JText::_($label) : $label;
981
982 // Description preprocess
983 $description = !empty($this->description) ? $this->description : null;
984 $description = !empty($description) && $this->translateDescription ? JText::_($description) : $description;
985
986 $alt = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $this->fieldname);
987
988 return array(
989 'autocomplete' => $this->autocomplete,
990 'autofocus' => $this->autofocus,
991 'class' => $this->class,
992 'description' => $description,
993 'disabled' => $this->disabled,
994 'field' => $this,
995 'group' => $this->group,
996 'hidden' => $this->hidden,
997 'hint' => $this->translateHint ? JText::alt($this->hint, $alt) : $this->hint,
998 'id' => $this->id,
999 'label' => $label,
1000 'labelclass' => $this->labelclass,
1001 'multiple' => $this->multiple,
1002 'name' => $this->name,
1003 'onchange' => $this->onchange,
1004 'onclick' => $this->onclick,
1005 'pattern' => $this->pattern,
1006 'readonly' => $this->readonly,
1007 'repeat' => $this->repeat,
1008 'required' => (bool) $this->required,
1009 'size' => $this->size,
1010 'spellcheck' => $this->spellcheck,
1011 'validate' => $this->validate,
1012 'value' => $this->value,
1013 );
1014 }
1015
1016 /**
1017 * Allow to override renderer include paths in child fields
1018 *
1019 * @return array
1020 *
1021 * @since 3.5
1022 */
1023 protected function getLayoutPaths()
1024 {
1025 return array();
1026 }
1027
1028 /**
1029 * Get the renderer
1030 *
1031 * @param string $layoutId Id to load
1032 *
1033 * @return JLayout
1034 *
1035 * @since 3.5
1036 */
1037 protected function getRenderer($layoutId = 'default')
1038 {
1039 $renderer = new JLayoutFile($layoutId);
1040
1041 $renderer->setDebug($this->isDebugEnabled());
1042
1043 $layoutPaths = $this->getLayoutPaths();
1044
1045 if ($layoutPaths)
1046 {
1047 $renderer->setIncludePaths($layoutPaths);
1048 }
1049
1050 return $renderer;
1051 }
1052
1053 /**
1054 * Is debug enabled for this field
1055 *
1056 * @return boolean
1057 *
1058 * @since 3.5
1059 */
1060 protected function isDebugEnabled()
1061 {
1062 return $this->getAttribute('debug', 'false') === 'true';
1063 }
1064 }
1065