1<?php
2
3/* Copyright (c) 2016 Richard Klees <richard.klees@concepts-and-training.de> Extended GPL, see docs/LICENSE */
4
5namespace ILIAS\UI\Implementation\Component\Input\Field;
6
7use ILIAS\Data\DateFormat as DateFormat;
8use ILIAS\UI\Component;
9use ILIAS\UI\Component\Input\Field\MultiSelect;
10use ILIAS\UI\Component\Input\Field\Password;
11use ILIAS\UI\Component\Input\Field\Select;
12use ILIAS\UI\Implementation\Render\AbstractComponentRenderer;
13use ILIAS\UI\Implementation\Render\ResourceRegistry;
14use ILIAS\UI\Implementation\Render\Template;
15use ILIAS\UI\Renderer as RendererInterface;
16
17/**
18 * Class Renderer
19 *
20 * @package ILIAS\UI\Implementation\Component\Input
21 */
22class Renderer extends AbstractComponentRenderer
23{
24    const DATEPICKER_MINMAX_FORMAT = 'Y/m/d';
25
26    const DATEPICKER_FORMAT_MAPPING = [
27        'd' => 'DD',
28        'jS' => 'Do',
29        'l' => 'dddd',
30        'D' => 'dd',
31        'S' => 'o',
32        'W' => '',
33        'm' => 'MM',
34        'F' => 'MMMM',
35        'M' => 'MMM',
36        'Y' => 'YYYY',
37        'y' => 'YY'
38    ];
39
40    /**
41     * @inheritdoc
42     */
43    public function render(Component\Component $component, RendererInterface $default_renderer)
44    {
45        /**
46         * @var $component Input
47         */
48        $this->checkComponent($component);
49
50        $input_tpl = null;
51        $id = null;
52        $dependant_group_html = null;
53
54        if ($component instanceof Component\Input\Field\Text) {
55            $input_tpl = $this->getTemplate("tpl.text.html", true, true);
56        } elseif ($component instanceof Component\Input\Field\Numeric) {
57            $input_tpl = $this->getTemplate("tpl.numeric.html", true, true);
58        } elseif ($component instanceof Component\Input\Field\Checkbox) {
59            $input_tpl = $this->getTemplate("tpl.checkbox.html", true, true);
60        } elseif ($component instanceof Component\Input\Field\OptionalGroup) {
61            $input_tpl = $this->getTemplate("tpl.checkbox.html", true, true);
62            $component = $component->withAdditionalOnLoadCode(function ($id) {
63                return $this->getOptionalGroupOnLoadCode($id);
64            });
65            $dependant_group_html = $this->renderFieldGroups($component, $default_renderer);
66            $id = $this->bindJavaScript($component);
67            return $this->renderInputFieldWithContext($default_renderer, $input_tpl, $component, $id, $dependant_group_html);
68        } elseif ($component instanceof Component\Input\Field\SwitchableGroup) {
69            return $this->renderSwitchableGroupField($component, $default_renderer);
70        } elseif ($component instanceof Component\Input\Field\Tag) {
71            $input_tpl = $this->getTemplate("tpl.tag_input.html", true, true);
72        } elseif ($component instanceof Password) {
73            $input_tpl = $this->getTemplate("tpl.password.html", true, true);
74        } elseif ($component instanceof Component\Input\Field\File) {
75            $input_tpl = $this->getTemplate("tpl.file.html", true, true);
76            $input_tpl->setVariable('BUTTON', $default_renderer->render($this->getUIFactory()->button()->shy($this->txt('select_files_from_computer'), "#")));
77        } elseif ($component instanceof Select) {
78            $input_tpl = $this->getTemplate("tpl.select.html", true, true);
79        } elseif ($component instanceof Component\Input\Field\Textarea) {
80            $input_tpl = $this->getTemplate("tpl.textarea.html", true, true);
81        } elseif ($component instanceof Component\Input\Field\Radio) {
82            return $this->renderRadioField($component, $default_renderer);
83        } elseif ($component instanceof MultiSelect) {
84            $input_tpl = $this->getTemplate("tpl.multiselect.html", true, true);
85        } elseif ($component instanceof Component\Input\Field\DateTime) {
86            $input_tpl = $this->getTemplate("tpl.datetime.html", true, true);
87        } elseif ($component instanceof Component\Input\Field\Group) {
88            return $this->renderFieldGroups($component, $default_renderer);
89        } else {
90            throw new \LogicException("Cannot render '" . get_class($component) . "'");
91        }
92
93        return $this->renderInputFieldWithContext($default_renderer, $input_tpl, $component, $id);
94    }
95
96
97    /**
98     * @inheritdoc
99     */
100    public function registerResources(ResourceRegistry $registry)
101    {
102        parent::registerResources($registry);
103        $registry->register('./libs/bower/bower_components/typeahead.js/dist/typeahead.bundle.js');
104        $registry->register('./libs/bower/bower_components/bootstrap-tagsinput/dist/bootstrap-tagsinput.min.js');
105        $registry->register('./libs/bower/bower_components/bootstrap-tagsinput/dist/bootstrap-tagsinput-typeahead.css');
106        $registry->register('./src/UI/templates/js/Input/Field/tagInput.js');
107        $registry->register('./src/UI/templates/js/Input/Field/textarea.js');
108        $registry->register('./src/UI/templates/js/Input/Field/input.js');
109        $registry->register('./src/UI/templates/js/Input/Field/duration.js');
110        $registry->register('./libs/bower/bower_components/dropzone/dist/min/dropzone.min.js');
111        $registry->register('./src/UI/templates/js/Input/Field/file.js');
112    }
113
114
115    /**
116     * @param Input $input
117     * @return Input|\ILIAS\UI\Implementation\Component\JavaScriptBindable
118     */
119    protected function setSignals(Input $input)
120    {
121        $signals = null;
122        foreach ($input->getTriggeredSignals() as $s) {
123            $signals[] = [
124                "signal_id" => $s->getSignal()->getId(),
125                "event" => $s->getEvent(),
126                "options" => $s->getSignal()->getOptions()
127            ];
128        }
129        if ($signals !== null) {
130            $signals = json_encode($signals);
131
132
133            $input = $input->withAdditionalOnLoadCode(function ($id) use ($signals) {
134                $code = "il.UI.input.setSignalsForId('$id', $signals);";
135                return $code;
136            });
137
138            $input = $input->withAdditionalOnLoadCode($input->getUpdateOnLoadCode());
139        }
140        return $input;
141    }
142
143
144    /**
145     * @param Group             $group
146     * @param RendererInterface $default_renderer
147     *
148     * @return string
149     */
150    protected function renderFieldGroups(Group $group, RendererInterface $default_renderer)
151    {
152        if ($group instanceof Component\Input\Field\Section) {
153            /**
154             * @var $group Section
155             */
156            return $this->renderSection($group, $default_renderer);
157        } elseif ($group instanceof Component\Input\Field\Duration) {
158            /**
159             * @var $group Duration
160             */
161            return $this->renderDurationInput($group, $default_renderer);
162        }
163
164        $inputs = "";
165        foreach ($group->getInputs() as $input) {
166            $inputs .= $default_renderer->render($input);
167        }
168
169        return $inputs;
170    }
171
172    /**
173     * @param Component\JavascriptBindable $component
174     * @param                              $tpl
175     */
176    protected function maybeRenderId(Component\JavascriptBindable $component, Template $tpl)
177    {
178        $id = $this->bindJavaScript($component);
179        if ($id !== null) {
180            $tpl->setCurrentBlock("id");
181            $tpl->setVariable("ID", $id);
182            $tpl->parseCurrentBlock();
183        }
184    }
185
186
187    /**
188     * @param Section           $section
189     * @param RendererInterface $default_renderer
190     *
191     * @return string
192     */
193    protected function renderSection(Section $section, RendererInterface $default_renderer)
194    {
195        $section_tpl = $this->getTemplate("tpl.section.html", true, true);
196        $section_tpl->setVariable("LABEL", $section->getLabel());
197
198        if ($section->getByline() !== null) {
199            $section_tpl->setCurrentBlock("byline");
200            $section_tpl->setVariable("BYLINE", $section->getByline());
201            $section_tpl->parseCurrentBlock();
202        }
203
204        if ($section->getError() !== null) {
205            $section_tpl->setCurrentBlock("error");
206            $section_tpl->setVariable("ERROR", $section->getError());
207            $section_tpl->parseCurrentBlock();
208        }
209        $inputs_html = "";
210
211        foreach ($section->getInputs() as $input) {
212            $inputs_html .= $default_renderer->render($input);
213        }
214        $section_tpl->setVariable("INPUTS", $inputs_html);
215
216        return $section_tpl->get();
217    }
218
219    /**
220     * @param RendererInterface $default_renderer
221     * @param Template $input_tpl
222     * @param Input    $input
223     * @param null     $id
224     * @param null     $dependant_group_html
225     *
226     * @return string
227     */
228    protected function renderInputFieldWithContext(RendererInterface $default_renderer, Template $input_tpl, Input $input, $id = null, $dependant_group_html = null)
229    {
230        $tpl = $this->getTemplate("tpl.context_form.html", true, true);
231        /**
232         * TODO: should we throw an error in case for no name or render without name?
233         *
234         * if(!$input->getName()){
235         * throw new \LogicException("Cannot render '".get_class($input)."' no input name given.
236         * Is there a name source attached (is this input packed into a container attaching
237         * a name source)?");
238         * } */
239        if ($input->getName()) {
240            $tpl->setVariable("NAME", $input->getName());
241        } else {
242            $tpl->setVariable("NAME", "");
243        }
244
245        $tpl->setVariable("LABEL", $input->getLabel());
246        $tpl->setVariable("INPUT", $this->renderInputField($input_tpl, $input, $id, $default_renderer));
247
248        if ($input->getByline() !== null) {
249            $tpl->setCurrentBlock("byline");
250            $tpl->setVariable("BYLINE", $input->getByline());
251            $tpl->parseCurrentBlock();
252        }
253
254        if ($input->isRequired()) {
255            $tpl->touchBlock("required");
256        }
257
258        if ($input->getError() !== null) {
259            $tpl->setCurrentBlock("error");
260            $tpl->setVariable("ERROR", $input->getError());
261            $tpl->parseCurrentBlock();
262        }
263
264        if ($dependant_group_html !== null) {
265            $tpl->setVariable("DEPENDANT_GROUP", $dependant_group_html);
266        }
267
268
269        return $tpl->get();
270    }
271
272    /**
273     * Escape values for rendering.
274     * In order to prevent XSS-attacks, values need to be stripped of
275     * special chars (such as quotes or tags).
276     * Needs vary according to the type of component (e.g. the html
277     * generated for this component and the placement of {VALUE} in the
278     * template.)
279     * Please note: this may not work for customized templates!
280     */
281    protected function prepareValue(Input $component, $value)
282    {
283        switch (true) {
284            case ($component instanceof Textarea):
285                return htmlentities($value);
286            case ($component instanceof Text):
287            case ($component instanceof Password):
288            case ($component instanceof Numeric):
289                return htmlspecialchars($value, ENT_QUOTES);
290            default:
291                return $value;
292        }
293    }
294
295    /**
296     * @param Template $tpl
297     * @param Input    $input
298     * @param          $id
299     * @param RendererInterface $default_renderer
300     *
301     * @return string
302     */
303    protected function renderInputField(Template $tpl, Input $input, $id, RendererInterface $default_renderer)
304    {
305        $input = $this->setSignals($input);
306
307        if ($input instanceof Component\Input\Field\Password) {
308            $id = $this->additionalRenderPassword($tpl, $input, $default_renderer);
309        }
310
311        if ($input instanceof Textarea) {
312            $this->renderTextareaField($tpl, $input);
313        }
314
315        $tpl->setVariable("NAME", $input->getName());
316
317        switch (true) {
318            case ($input instanceof Checkbox || $input instanceof OptionalGroup):
319                if ($input->getValue()) {
320                    $tpl->touchBlock("value");
321                }
322                // no break
323            case ($input instanceof Text):
324            case ($input instanceof Numeric):
325            case ($input instanceof Password):
326            case ($input instanceof Textarea):
327                $tpl->setVariable("NAME", $input->getName());
328
329                if ($input->getValue() !== null && !($input instanceof Checkbox)) {
330                    $tpl->setCurrentBlock("value");
331                    /*
332                    ATTENTION! Please see docs of "prepareValue".
333                    */
334                    $tpl->setVariable("VALUE", $this->prepareValue($input, $input->getValue()));
335                    $tpl->parseCurrentBlock();
336                }
337                if ($input->isDisabled()) {
338                    $tpl->setCurrentBlock("disabled");
339                    $tpl->setVariable("DISABLED", 'disabled="disabled"');
340                    $tpl->parseCurrentBlock();
341                }
342                if ($id) {
343                    $tpl->setCurrentBlock("id");
344                    $tpl->setVariable("ID", $id);
345                    $tpl->parseCurrentBlock();
346                }
347                break;
348            case ($input instanceof Select):
349                $tpl = $this->renderSelectInput($tpl, $input);
350                break;
351            case ($input instanceof MultiSelect):
352                $tpl = $this->renderMultiSelectInput($tpl, $input);
353                break;
354
355            case ($input instanceof Tag):
356                $configuration = $input->getConfiguration();
357                $input = $input->withAdditionalOnLoadCode(
358                    function ($id) use ($configuration) {
359                        $encoded = json_encode($configuration);
360
361                        return "il.UI.Input.tagInput.init('{$id}', {$encoded});";
362                    }
363                );
364                $id = $this->bindJavaScript($input);
365                /**
366                 * @var $input \ILIAS\UI\Implementation\Component\Input\Field\Tag
367                 */
368                $tpl->setVariable("ID", $id);
369                $tpl->setVariable("NAME", $input->getName());
370                if ($input->isDisabled()) {
371                    $tpl->setCurrentBlock("disabled");
372                    $tpl->setVariable("DISABLED", "disabled");
373                    $tpl->parseCurrentBlock();
374                }
375                if ($input->getValue()) {
376                    $value = $input->getValue();
377                    $tpl->setVariable("VALUE_COMMA_SEPARATED", implode(",", $value));
378                    foreach ($value as $tag) {
379                        $tpl->setCurrentBlock('existing_tags');
380                        $tpl->setVariable("FIELD_ID", $id);
381                        $tpl->setVariable("FIELD_NAME", $input->getName());
382                        $tpl->setVariable("TAG_NAME", $tag);
383                        $tpl->parseCurrentBlock();
384                    }
385                }
386                break;
387            case ($input instanceof DateTime):
388                return $this->renderDateTimeInput($default_renderer, $tpl, $input);
389                break;
390            case ($input instanceof Component\Input\Field\File):
391                $input = $this->renderFileInput($input);
392                break;
393        }
394
395        if ($id === null) {
396            $this->maybeRenderId($input, $tpl);
397        }
398
399        return $tpl->get();
400    }
401
402    public function renderSelectInput(Template $tpl, Select $input)
403    {
404        if ($input->isDisabled()) {
405            $tpl->setCurrentBlock("disabled");
406            $tpl->setVariable("DISABLED", 'disabled="disabled"');
407            $tpl->parseCurrentBlock();
408        }
409        $value = $input->getValue();
410        //disable first option if required.
411        $tpl->setCurrentBlock("options");
412        if (!$value) {
413            $tpl->setVariable("SELECTED", "selected");
414        }
415        if ($input->isRequired()) {
416            $tpl->setVariable("DISABLED_OPTION", "disabled");
417            $tpl->setVariable("HIDDEN", "hidden");
418        }
419        $tpl->setVariable("VALUE", null);
420        $tpl->setVariable("VALUE_STR", "-");
421        $tpl->parseCurrentBlock();
422        //rest of options.
423        foreach ($input->getOptions() as $option_key => $option_value) {
424            $tpl->setCurrentBlock("options");
425            if ($value == $option_key) {
426                $tpl->setVariable("SELECTED", "selected");
427            }
428            $tpl->setVariable("VALUE", $option_key);
429            $tpl->setVariable("VALUE_STR", $option_value);
430            $tpl->parseCurrentBlock();
431        }
432
433        return $tpl;
434    }
435
436    public function renderMultiSelectInput(Template $tpl, MultiSelect $input) : Template
437    {
438        $value = $input->getValue();
439        $name = $input->getName();
440
441        foreach ($input->getOptions() as $opt_value => $opt_label) {
442            $tpl->setCurrentBlock("option");
443            $tpl->setVariable("NAME", $name);
444            $tpl->setVariable("VALUE", $opt_value);
445            $tpl->setVariable("LABEL", $opt_label);
446
447            if ($value && in_array($opt_value, $value)) {
448                $tpl->setVariable("CHECKED", 'checked="checked"');
449            }
450            if ($input->isDisabled()) {
451                $tpl->setVariable("DISABLED", 'disabled="disabled"');
452            }
453
454            $tpl->parseCurrentBlock();
455        }
456        return $tpl;
457    }
458
459
460    /**
461     * Render revelation-glyphs for password and register signals/functions
462     * @param Template $tpl
463     * @param Password $input
464     *
465     * @return string | false
466     */
467    protected function additionalRenderPassword(Template $tpl, Component\Input\Field\Password $input, RendererInterface $default_renderer)
468    {
469        $id = null;
470        if ($input->getRevelation()) {
471            $f = $this->getUIFactory();
472
473            $input = $input->withResetSignals();
474            $sig_reveal = $input->getRevealSignal();
475            $sig_mask = $input->getMaskSignal();
476
477            $input = $input->withAdditionalOnLoadCode(function ($id) use ($sig_reveal, $sig_mask) {
478                return
479                    "$(document).on('{$sig_reveal}', function() {
480						$('#{$id}').addClass('revealed');
481						$('#{$id}')[0].getElementsByTagName('input')[0].type='text';
482					});" .
483                    "$(document).on('{$sig_mask}', function() {
484						$('#{$id}').removeClass('revealed');
485						$('#{$id}')[0].getElementsByTagName('input')[0].type='password';
486					});"
487                    ;
488            });
489            $id = $this->bindJavaScript($input);
490            $tpl->setVariable("ID", $id);
491
492            $glyph_reveal = $f->symbol()->glyph()->eyeopen("#")
493                ->withOnClick($sig_reveal);
494            $glyph_mask = $f->symbol()->glyph()->eyeclosed("#")
495                ->withOnClick($sig_mask);
496            $tpl->setCurrentBlock('revelation');
497            $tpl->setVariable('PASSWORD_REVEAL', $default_renderer->render($glyph_reveal));
498            $tpl->setVariable('PASSWORD_MASK', $default_renderer->render($glyph_mask));
499            $tpl->parseCurrentBlock();
500        }
501        return $id;
502    }
503
504
505    protected function renderTextareaField(Template $tpl, Textarea $input)
506    {
507        if ($input->isLimited()) {
508            $this->toJS("ui_chars_remaining");
509            $this->toJS("ui_chars_min");
510            $this->toJS("ui_chars_max");
511
512            $counter_id_prefix = "textarea_feedback_";
513            $min = $input->getMinLimit();
514            $max = $input->getMaxLimit();
515
516            $input = $input->withAdditionalOnLoadCode(function ($id) use ($counter_id_prefix, $min, $max) {
517                return "il.UI.textarea.changeCounter('$id','$counter_id_prefix','$min','$max');";
518            });
519
520            $textarea_id = $this->bindJavaScript($input);
521            $tpl->setCurrentBlock("id");
522            $tpl->setVariable("ID", $textarea_id);
523            $tpl->parseCurrentBlock();
524            $tpl->setCurrentBlock("limit");
525            $tpl->setVariable("COUNT_ID", $textarea_id);
526            $tpl->setVariable("FEEDBACK_MAX_LIMIT", $max);
527            $tpl->parseCurrentBlock();
528        }
529    }
530
531
532    /**
533     * @param Radio $input
534     * @param RendererInterface    $default_renderer
535     *
536     * @return string
537     */
538    protected function renderRadioField(Component\Input\Field\Radio $input, RendererInterface $default_renderer)
539    {
540        $input_tpl = $this->getTemplate("tpl.radio.html", true, true);
541
542        //monitor change-events
543        $input = $this->setSignals($input);
544        $id = $this->bindJavaScript($input) ?? $this->createId();
545        $input_tpl->setVariable("ID", $id);
546
547        foreach ($input->getOptions() as $value => $label) {
548            $opt_id = $id . '_' . $value . '_opt';
549
550            $input_tpl->setCurrentBlock('optionblock');
551            $input_tpl->setVariable("NAME", $input->getName());
552            $input_tpl->setVariable("OPTIONID", $opt_id);
553            $input_tpl->setVariable("VALUE", $value);
554            $input_tpl->setVariable("LABEL", $label);
555
556            if ($input->getValue() !== null && $input->getValue() == $value) {
557                $input_tpl->setVariable("CHECKED", 'checked="checked"');
558            }
559            if ($input->isDisabled()) {
560                $input_tpl->setVariable("DISABLED", 'disabled="disabled"');
561            }
562
563            $byline = $input->getBylineFor($value);
564            if (!empty($byline)) {
565                $input_tpl->setVariable("BYLINE", $byline);
566            }
567
568            $input_tpl->parseCurrentBlock();
569        }
570        $options_html = $input_tpl->get();
571
572        //render with context:
573        $tpl = $this->getTemplate("tpl.context_form.html", true, true);
574        $tpl->setVariable("LABEL", $input->getLabel());
575        $tpl->setVariable("INPUT", $options_html);
576
577        if ($input->getByline() !== null) {
578            $tpl->setCurrentBlock("byline");
579            $tpl->setVariable("BYLINE", $input->getByline());
580            $tpl->parseCurrentBlock();
581        }
582        if ($input->isRequired()) {
583            $tpl->touchBlock("required");
584        }
585        if ($input->getError() !== null) {
586            $tpl->setCurrentBlock("error");
587            $tpl->setVariable("ERROR", $input->getError());
588            $tpl->parseCurrentBlock();
589        }
590        return $tpl->get();
591    }
592
593    /**
594     * @param Radio $input
595     * @param RendererInterface    $default_renderer
596     *
597     * @return string
598     */
599    protected function renderSwitchableGroupField(Component\Input\Field\SwitchableGroup $input, RendererInterface $default_renderer)
600    {
601        $input_tpl = $this->getTemplate("tpl.radio.html", true, true);
602
603        $input = $input->withAdditionalOnLoadCode(function ($id) {
604            return $this->getSwitchableGroupOnLoadCode($id);
605        });
606        $id = $this->bindJavaScript($input);
607        $input_tpl->setVariable("ID", $id);
608
609        foreach ($input->getInputs() as $key => $group) {
610            $opt_id = $id . '_' . $key . '_opt';
611
612            $input_tpl->setCurrentBlock('optionblock');
613            $input_tpl->setVariable("NAME", $input->getName());
614            $input_tpl->setVariable("OPTIONID", $opt_id);
615            $input_tpl->setVariable("VALUE", $key);
616            $input_tpl->setVariable("LABEL", $group->getLabel());
617
618            if ($input->getValue() !== null) {
619                list($index, $subvalues) = $input->getValue();
620                if ($index == $key) {
621                    $input_tpl->setVariable("CHECKED", 'checked="checked"');
622                }
623            }
624            if ($input->isDisabled()) {
625                $input_tpl->setVariable("DISABLED", 'disabled="disabled"');
626            }
627
628            $dependant_group_html = $this->renderFieldGroups($group, $default_renderer);
629            $input_tpl->setVariable("DEPENDANT_FIELDS", $dependant_group_html);
630            $input_tpl->parseCurrentBlock();
631        }
632        $options_html = $input_tpl->get();
633
634        //render with context:
635        $tpl = $this->getTemplate("tpl.context_form.html", true, true);
636        $tpl->setVariable("LABEL", $input->getLabel());
637        $tpl->setVariable("INPUT", $options_html);
638
639        if ($input->getByline() !== null) {
640            $tpl->setCurrentBlock("byline");
641            $tpl->setVariable("BYLINE", $input->getByline());
642            $tpl->parseCurrentBlock();
643        }
644        if ($input->isRequired()) {
645            $tpl->touchBlock("required");
646        }
647        if ($input->getError() !== null) {
648            $tpl->setCurrentBlock("error");
649            $tpl->setVariable("ERROR", $input->getError());
650            $tpl->parseCurrentBlock();
651        }
652        return $tpl->get();
653    }
654
655
656    protected function getOptionalGroupOnLoadCode($id)
657    {
658        return <<<JS
659var $id = $("#$id");
660var {$id}_group = $id.siblings(".form-group").show();
661var {$id}_adjust = function() {
662	if ({$id}[0].checked) {
663		{$id}_group.show();
664	}
665	else {
666		{$id}_group.hide()
667	}
668}
669$id.change({$id}_adjust);
670{$id}_adjust();
671JS;
672    }
673
674    protected function getSwitchableGroupOnLoadCode($id)
675    {
676        return <<<JS
677var radio = $("#$id");
678radio.change(function(event){
679	var r = $(this),
680		options = r.children('.il-input-radiooption').children('input');
681
682	options.each(function(index, opt) {
683		var group = $(opt).siblings('.form-group');
684		if(opt.checked) {
685			group.show();
686		} else {
687			group.hide();
688		}
689	});
690});
691radio.trigger('change');
692
693JS;
694    }
695
696    /**
697     * Return the datetime format in a form fit for the JS-component of this input.
698     * Currently, this means transforming the elements of DateFormat to momentjs.
699     *
700     * http://eonasdan.github.io/bootstrap-datetimepicker/Options/#format
701     * http://momentjs.com/docs/#/displaying/format/
702    */
703    protected function getTransformedDateFormat(
704        DateFormat\DateFormat $origin,
705        array $mapping
706    ) : string {
707        $ret = '';
708        foreach ($origin->toArray() as $element) {
709            if (array_key_exists($element, $mapping)) {
710                $ret .= $mapping[$element];
711            } else {
712                $ret .= $element;
713            }
714        }
715        return $ret;
716    }
717
718    /**
719     * @param Template $tpl
720     * @param DateTime $input
721     *
722     * @return string
723     */
724    protected function renderDateTimeInput(RendererInterface $default_renderer, Template $tpl, DateTime $input) : string
725    {
726        $f = $this->getUIFactory();
727
728        if ($input->getTimeOnly() === true) {
729            $cal_glyph = $f->symbol()->glyph()->time("#");
730            $format = $input::TIME_FORMAT;
731        } else {
732            $cal_glyph = $f->symbol()->glyph()->calendar("#");
733
734            $format = $this->getTransformedDateFormat(
735                $input->getFormat(),
736                self::DATEPICKER_FORMAT_MAPPING
737            );
738
739            if ($input->getUseTime() === true) {
740                $format .= ' ' . $input::TIME_FORMAT;
741            }
742        }
743
744        $tpl->setVariable("CALENDAR_GLYPH", $default_renderer->render($cal_glyph));
745
746        $config = [
747            'showClear' => true,
748            'sideBySide' => true,
749            'format' => $format,
750        ];
751        $config = array_merge($config, $input->getAdditionalPickerConfig());
752
753        $min_date = $input->getMinValue();
754        if (!is_null($min_date)) {
755            $config['minDate'] = date_format($min_date, self::DATEPICKER_MINMAX_FORMAT);
756        }
757        $max_date = $input->getMaxValue();
758        if (!is_null($max_date)) {
759            $config['maxDate'] = date_format($max_date, self::DATEPICKER_MINMAX_FORMAT);
760        }
761
762        $tpl->setVariable("NAME", $input->getName());
763        $tpl->setVariable("PLACEHOLDER", $format);
764
765        if ($input->getValue() !== null) {
766            $tpl->setCurrentBlock("value");
767            $tpl->setVariable("VALUE", $input->getValue());
768            $tpl->parseCurrentBlock();
769        }
770
771        require_once("./Services/Calendar/classes/class.ilCalendarUtil.php");
772        \ilCalendarUtil::initDateTimePicker();
773        $input = $this->setSignals($input);
774
775        $disabled = $input->isDisabled();
776        $input = $input->withAdditionalOnLoadCode(function ($id) use ($config, $disabled) {
777            $js = '$("#' . $id . '").datetimepicker(' . json_encode($config) . ');';
778            if ($disabled) {
779                $js .= '$("#' . $id . ' input").prop(\'disabled\', true);';
780            }
781            return $js;
782        });
783
784        $id = $this->bindJavaScript($input);
785        $tpl->setVariable("ID", $id);
786
787        return $tpl->get();
788    }
789
790
791    protected function renderDurationInput(Duration $input, RendererInterface $default_renderer) : string
792    {
793        $tpl = $this->getTemplate("tpl.context_form.html", true, true);
794        $tpl_duration = $this->getTemplate("tpl.duration.html", true, true);
795
796        if ($input->getName()) {
797            $tpl->setVariable("NAME", $input->getName());
798        } else {
799            $tpl->setVariable("NAME", "");
800        }
801
802        $tpl->setVariable("LABEL", $input->getLabel());
803
804        if ($input->getByline() !== null) {
805            $tpl->setCurrentBlock("byline");
806            $tpl->setVariable("BYLINE", $input->getByline());
807            $tpl->parseCurrentBlock();
808        }
809
810        if ($input->isRequired()) {
811            $tpl->touchBlock("required");
812        }
813
814        if ($input->getError() !== null) {
815            $tpl->setCurrentBlock("error");
816            $tpl->setVariable("ERROR", $input->getError());
817            $tpl->parseCurrentBlock();
818        }
819
820        $input = $this->setSignals($input);
821        $input = $input->withAdditionalOnLoadCode(
822            function ($id) {
823                return "$(document).ready(function() {
824					il.UI.Input.duration.init('$id');
825				});";
826            }
827        );
828        $id = $this->bindJavaScript($input);
829        $tpl_duration->setVariable("ID", $id);
830
831        $input_html = '';
832        $inputs = $input->getInputs();
833
834        $inpt = array_shift($inputs); //from
835        $input_html .= $default_renderer->render($inpt);
836
837        $inpt = array_shift($inputs)->withAdditionalPickerconfig([ //until
838            'useCurrent' => false
839        ]);
840        $input_html .= $default_renderer->render($inpt);
841
842        $tpl_duration->setVariable('DURATION', $input_html);
843        $tpl->setVariable("INPUT", $tpl_duration->get());
844        return $tpl->get();
845    }
846
847    /**
848     * @inheritdoc
849     */
850    protected function getComponentInterfaceName()
851    {
852        return [
853            Component\Input\Field\Text::class,
854            Component\Input\Field\Numeric::class,
855            Component\Input\Field\Group::class,
856            Component\Input\Field\OptionalGroup::class,
857            Component\Input\Field\SwitchableGroup::class,
858            Component\Input\Field\Section::class,
859            Component\Input\Field\Checkbox::class,
860            Component\Input\Field\Tag::class,
861            Component\Input\Field\Password::class,
862            Component\Input\Field\Select::class,
863            Component\Input\Field\Radio::class,
864            Component\Input\Field\Textarea::class,
865            Component\Input\Field\MultiSelect::class,
866            Component\Input\Field\DateTime::class,
867            Component\Input\Field\Duration::class,
868            Component\Input\Field\File::class
869        ];
870    }
871
872
873    protected function renderFileInput(Component\Input\Field\File $input) : Component\Input\Field\File
874    {
875        $component = $this->setSignals($input);
876        /**
877         * @var $component File
878         */
879        $settings = new \stdClass();
880        $settings->upload_url = $component->getUploadHandler()->getUploadURL();
881        $settings->removal_url = $component->getUploadHandler()->getFileRemovalURL();
882        $settings->info_url = $component->getUploadHandler()->getExistingFileInfoURL();
883        $settings->file_identifier_key = $component->getUploadHandler()->getFileIdentifierParameterName();
884        $settings->accepted_files = implode(',', $component->getAcceptedMimeTypes());
885        $settings->existing_file_ids = $input->getValue();
886        $settings->existing_files = $component->getUploadHandler()->getInfoForExistingFiles($input->getValue() ?? []);
887        $settings->dictInvalidFileType = $this->txt('form_msg_file_wrong_file_type');
888
889        $input = $component->withAdditionalOnLoadCode(
890            function ($id) use ($settings) {
891                $settings = json_encode($settings);
892
893                return "$(document).ready(function() {
894					il.UI.Input.file.init('$id', '{$settings}');
895				});";
896            }
897        );
898
899        /**
900         * @var $input Component\Input\Field\File
901         */
902        return $input;
903    }
904}
905