Issues (1870)

main/inc/lib/formvalidator/FormValidator.class.php (9 issues)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\HTMLPurifier\Filter\RemoveOnAttributes;
6
7
/**
8
 * Class FormValidator
9
 * create/manipulate/validate user input.
10
 */
11
class FormValidator extends HTML_QuickForm
12
{
13
    public const LAYOUT_HORIZONTAL = 'horizontal';
14
    public const LAYOUT_INLINE = 'inline';
15
    public const LAYOUT_BOX = 'box';
16
    public const LAYOUT_BOX_NO_LABEL = 'box-no-label';
17
    public const LAYOUT_BOX_SEARCH = 'box-search';
18
    public const LAYOUT_GRID = 'grid';
19
20
    public $with_progress_bar = false;
21
    private $layout;
22
23
    /**
24
     * @param string      $name        Name of the form
25
     * @param string      $method      (optional) Method ('post' (default) or 'get')
26
     * @param string      $action      (optional) Action (default is $PHP_SELF)
27
     * @param string|null $target      (optional) Form's target defaults to '_self'
28
     * @param mixed       $attributes  (optional) Extra attributes for <form> tag
29
     * @param bool        $trackSubmit Whether to track if the form was submitted by adding a special hidden field
30
     */
31
    public function __construct(
32
        string $name,
33
        ?string $method = 'post',
34
        ?string $action = '',
35
        ?string $target = '',
36
        ?array $attributes = [],
37
        string $layout = self::LAYOUT_HORIZONTAL,
38
        bool $trackSubmit = true
39
    ) {
40
        if (null === $attributes) {
41
            $attributes = [];
42
        }
43
44
        if (isset($attributes['class']) && str_contains($attributes['class'], 'form-search')) {
45
            $layout = self::LAYOUT_INLINE;
46
        }
47
48
        $this->setLayout($layout);
49
50
        // Form template
51
        $formTemplate = $this->getFormTemplate();
52
53
        switch ($layout) {
54
            case self::LAYOUT_BOX_SEARCH:
55
            case self::LAYOUT_INLINE:
56
                $attributes['class'] = 'flex flex-row gap-3 items-center ';
57
                break;
58
            case self::LAYOUT_BOX:
59
                $attributes['class'] = 'ch flex gap-1 ';
60
                break;
61
            case self::LAYOUT_GRID:
62
                $attributes['class'] = 'ch form-grid';
63
                $formTemplate = $this->getGridFormTemplate();
64
                break;
65
        }
66
67
        parent::__construct($name, $method, $action, $target, $attributes, $trackSubmit);
68
69
        // Modify the default templates
70
        $renderer = &$this->defaultRenderer();
71
        $renderer->setFormTemplate($formTemplate);
72
73
        // Element template
74
        if ((isset($attributes['class']) && 'form--inline' === $attributes['class']) ||
75
            (self::LAYOUT_INLINE === $layout || self::LAYOUT_BOX_SEARCH === $layout)
76
        ) {
77
            $elementTemplate = ' {label}  {element} ';
78
            $renderer->setElementTemplate($elementTemplate);
79
        } elseif (isset($attributes['class']) && 'form-search' === $attributes['class']) {
80
            $elementTemplate = ' {label}  {element} ';
81
            $renderer->setElementTemplate($elementTemplate);
82
        } else {
83
            $renderer->setElementTemplate($this->getDefaultElementTemplate());
84
85
            // Display a gray div in the buttons
86
            $templateSimple = '<div class="form-actions">{label} {element}</div>';
87
            $renderer->setElementTemplate($templateSimple, 'submit_in_actions');
88
89
            //Display a gray div in the buttons + makes the button available when scrolling
90
            $templateBottom = '<div class="form-actions bottom_actions bg-form">{label} {element}</div>';
91
            $renderer->setElementTemplate($templateBottom, 'submit_fixed_in_bottom');
92
            $renderer->setElementTemplate($templateSimple, 'buttons_in_action');
93
94
            $templateSimpleRight = '<div class="form-actions"> <div class="pull-right">{label} {element}</div></div>';
95
            $renderer->setElementTemplate($templateSimpleRight, 'buttons_in_action_right');
96
        }
97
98
        //Set Header template
99
        $renderer->setHeaderTemplate(' <h1 class="text-h3 font-small text-gray-800 mb-4">{header}<hr /></h1>');
100
101
        $required = '<span class="form_required">*</span> <small>'.get_lang('Required field').'</small>';
102
        if ((self::LAYOUT_INLINE === $layout || self::LAYOUT_BOX_SEARCH === $layout)) {
103
            $required = '';
104
        }
105
        // Set required field template
106
        $this->setRequiredNote($required);
107
108
        if (self::LAYOUT_BOX_SEARCH !== $layout) {
109
            $noteTemplate = <<<EOT
110
	<div class="form-group">
111
		<div class="col-sm-offset-2 col-sm-10">{requiredNote}</div>
112
	</div>
113
EOT;
114
            $renderer->setRequiredNoteTemplate($noteTemplate);
115
        }
116
    }
117
118
    public function getFormTemplate(): string
119
    {
120
        if (self::LAYOUT_BOX_SEARCH == $this->layout) {
121
            return '<form {attributes}>
122
                    <div class="form__group form__group--inline p-inputgroup">
123
                        {content}
124
                        {hidden}
125
                    </div>
126
                </form>';
127
        }
128
129
        return '<form{attributes}>
130
                {content}
131
                {hidden}
132
            </form>';
133
    }
134
135
    public function getGridFormTemplate(): string
136
    {
137
        return '
138
        <style>
139
            .form_list {
140
                display: grid;
141
                grid-template-columns:  repeat(auto-fill, minmax(300px, 1fr));;
142
                grid-gap: 10px 30px;
143
                gap: 10px 30px;
144
            }
145
            .form_list .input-group {
146
                display:block;
147
            }
148
        </style>
149
        <form{attributes}>
150
            <div class="form_list">
151
                {content}
152
            </div>
153
        {hidden}
154
        </form>';
155
    }
156
157
    /**
158
     * @todo this function should be added in the element class
159
     */
160
    public function getDefaultElementTemplate(): string
161
    {
162
        return '
163
            <div class="row mb-3 {error_class}">
164
                <label {label-for} class="col-sm-2 col-form-label {extra_label_class}" >
165
                    <!-- BEGIN required --><span class="form_required">*</span><!-- END required -->
166
                    {label}
167
                </label>
168
                <div class="col-sm-8">
169
                    {icon}
170
                    {element}
171
                    <!-- BEGIN label_2 -->
172
                        <p class="help-block">{label_2}</p>
173
                    <!-- END label_2 -->
174
175
                    <!-- BEGIN error -->
176
                        <span class="help-inline help-block">{error}</span>
177
                    <!-- END error -->
178
                </div>
179
                <div class="col-sm-2">
180
                    <!-- BEGIN label_3 -->
181
                        {label_3}
182
                    <!-- END label_3 -->
183
                </div>
184
            </div>';
185
    }
186
187
    public function getLayout(): string
188
    {
189
        return $this->layout;
190
    }
191
192
    public function setLayout(string $layout)
193
    {
194
        $this->layout = $layout;
195
    }
196
197
    /**
198
     * Adds a text field to the form.
199
     * A trim-filter is attached to the field.
200
     *
201
     * @param string       $name       The element name
202
     * @param string|array $label      The label for the form-element
203
     * @param bool         $required   (optional)    Is the form-element required (default=true)
204
     * @param array        $attributes (optional)    List of attributes for the form-element
205
     * @param bool         $createElement
206
     *
207
     * @throws Exception
208
     *
209
     * @return HTML_QuickForm_text
210
     */
211
    public function addText($name, $label, $required = true, $attributes = [], $createElement = false)
212
    {
213
        if ($createElement) {
214
            $element = $this->createElement('text', $name, $label, $attributes);
215
        } else {
216
            $element = $this->addElement('text', $name, $label, $attributes);
217
        }
218
219
        $this->applyFilter($name, 'trim');
220
        $this->applyFilter($name, 'html_filter');
221
222
        if ($required) {
223
            $this->addRule($name, get_lang('Required field'), 'required');
224
        }
225
226
        return $element;
227
    }
228
229
    /**
230
     * Add hidden course params.
231
     */
232
    public function addCourseHiddenParams()
233
    {
234
        $this->addHidden('cid', api_get_course_int_id());
235
        $this->addHidden('sid', api_get_session_id());
236
    }
237
238
    /**
239
     * The "date_range_picker" element creates 2 hidden fields
240
     * "elementName" + "_start"  and "elementName" + "_end"
241
     * For example if the name is "range", you will have 2 new fields
242
     * when executing $form->getSubmitValues()
243
     * "range_start" and "range_end".
244
     *
245
     * @param string $name
246
     * @param string $label
247
     * @param bool   $required
248
     * @param array  $attributes
249
     */
250
    public function addDateRangePicker($name, $label, $required = true, $attributes = [])
251
    {
252
        $this->addElement('date_range_picker', $name, $label, $attributes);
253
        $this->addElement('hidden', $name.'_start');
254
        $this->addElement('hidden', $name.'_end');
255
256
        if ($required) {
257
            $this->addRule($name, get_lang('Required field'), 'required');
258
        }
259
    }
260
261
    /**
262
     * @param string $name
263
     * @param string $label
264
     * @param array  $attributes
265
     *
266
     * @return mixed
267
     */
268
    public function addSelectLanguage($name, $label, $options = [], $attributes = [])
269
    {
270
        return $this->addElement('SelectLanguage', $name, $label, $options, $attributes);
271
    }
272
273
    public function addSelectTheme($name, $label, $options = [], $attributes = [])
274
    {
275
        return $this->addElement('SelectTheme', $name, $label, $options, $attributes);
276
    }
277
278
    /**
279
     * @param string       $name
280
     * @param string|array $label
281
     * @param array        $options
282
     * @param array        $attributes
283
     *
284
     * @return SelectAjax
285
     */
286
    public function addSelectAjax($name, $label, $options = [], $attributes = [])
287
    {
288
        if (!isset($attributes['url'])) {
289
            throw new \Exception('select_ajax needs an URL');
290
        }
291
292
        return $this->addElement(
293
            'select_ajax',
294
            $name,
295
            $label,
296
            $options,
297
            $attributes
298
        );
299
    }
300
301
    /**
302
     * @param string $name
303
     * @param string $label
304
     * @param array  $attributes
305
     *
306
     * @return DatePicker
307
     */
308
    public function addDatePicker($name, $label, $attributes = [])
309
    {
310
        return $this->addElement('DatePicker', $name, $label, $attributes);
311
    }
312
313
    /**
314
     * @param string       $name
315
     * @param string|array $label
316
     * @param array        $attributes
317
     *
318
     * @return DateTimePicker
319
     */
320
    public function addDateTimePicker($name, $label, $attributes = [])
321
    {
322
        return $this->addElement('DateTimePicker', $name, $label, $attributes);
323
    }
324
325
    /**
326
     * @param string       $name
327
     * @param string|array $label
328
     * @param array        $attributes
329
     *
330
     * @return DateTimeRangePicker
331
     */
332
    public function addDateTimeRangePicker($name, $label, $attributes = [])
333
    {
334
        return $this->addElement('DateTimeRangePicker', $name, $label, $attributes);
335
    }
336
337
    /**
338
     * @param string $name
339
     * @param string|mixed $value
340
     * @param array  $attributes
341
     */
342
    public function addHidden($name, $value, $attributes = [])
343
    {
344
        $this->addElement('hidden', $name, $value, $attributes);
345
    }
346
347
    /**
348
     * @param string       $name
349
     * @param string|array $label
350
     * @param array        $attributes
351
     * @param bool         $required
352
     *
353
     * @return HTML_QuickForm_textarea
354
     */
355
    public function addTextarea($name, $label, $attributes = [], $required = false)
356
    {
357
        $element = $this->addElement('textarea', $name, $label, $attributes);
358
359
        if ($required) {
360
            $this->addRule($name, get_lang('Required field'), 'required');
361
        }
362
363
        return $element;
364
    }
365
366
    /**
367
     * @param string $name
368
     * @param string $label
369
     * @param string $icon          font-awesome
370
     * @param string $style         default|primary|success|info|warning|danger|link
371
     * @param string $size          large|default|small|extra-small
372
     * @param string $class         Example plus is transformed to icon fa fa-plus
373
     * @param array  $attributes
374
     * @param bool   $createElement
375
     *
376
     * @return HTML_QuickForm_button
377
     */
378
    public function addButton(
379
        $name,
380
        $label,
381
        $icon = 'check',
382
        $style = 'default',
383
        $size = 'default',
384
        $class = null,
385
        $attributes = [],
386
        $createElement = false
387
    ) {
388
        if ($createElement) {
389
            return $this->createElement(
390
                'button',
391
                $name,
392
                $label,
393
                $icon,
394
                $style,
395
                $size,
396
                $class,
397
                $attributes
398
            );
399
        }
400
401
        return $this->addElement(
402
            'button',
403
            $name,
404
            $label,
405
            $icon,
406
            $style,
407
            $size,
408
            $class,
409
            $attributes
410
        );
411
    }
412
413
    /**
414
     * Returns a button with the primary color and a check mark.
415
     *
416
     * @param string $label         Text appearing on the button
417
     * @param string $name          Element name (for form treatment purposes)
418
     * @param bool   $createElement Whether to use the create or add method
419
     * @param array  $attributes
420
     *
421
     * @return HTML_QuickForm_button
422
     */
423
    public function addButtonSave($label, $name = 'submit', $createElement = false, $attributes = [])
424
    {
425
        return $this->addButton(
426
            $name,
427
            $label,
428
            'check',
429
            'primary',
430
            null,
431
            null,
432
            $attributes,
433
            $createElement
434
        );
435
    }
436
437
    /**
438
     * Returns a cancel button.
439
     *
440
     * @param string $label         Text appearing on the button
441
     * @param string $name          Element name (for form treatment purposes)
442
     * @param bool   $createElement Whether to use the create or add method
443
     *
444
     * @return HTML_QuickForm_button
445
     */
446
    public function addButtonCancel($label, $name = 'submit', $createElement = false)
447
    {
448
        return $this->addButton(
449
            $name,
450
            $label,
451
            'close',
452
            'danger',
453
            null,
454
            null,
455
            [],
456
            $createElement
457
        );
458
    }
459
460
    /**
461
     * Returns a button with the primary color and a "plus" icon.
462
     *
463
     * @param string $label         Text appearing on the button
464
     * @param string $name          Element name (for form treatment purposes)
465
     * @param bool   $createElement Whether to use the create or add method
466
     * @param array  $attributes    Additional attributes
467
     *
468
     * @return HTML_QuickForm_button
469
     */
470
    public function addButtonCreate($label, $name = 'submit', $createElement = false, $attributes = [])
471
    {
472
        return $this->addButton(
473
            $name,
474
            $label,
475
            'plus',
476
            'primary',
477
            null,
478
            null,
479
            $attributes,
480
            $createElement
481
        );
482
    }
483
484
    /**
485
     * Returns a button with the primary color and a pencil icon.
486
     *
487
     * @param string $label         Text appearing on the button
488
     * @param string $name          Element name (for form treatment purposes)
489
     * @param bool   $createElement Whether to use the create or add method
490
     *
491
     * @return HTML_QuickForm_button
492
     */
493
    public function addButtonUpdate($label, $name = 'submit', $createElement = false)
494
    {
495
        return $this->addButton(
496
            $name,
497
            $label,
498
            'pencil',
499
            'primary',
500
            null,
501
            null,
502
            [],
503
            $createElement
504
        );
505
    }
506
507
    /**
508
     * Returns a button with the danger color and a trash icon.
509
     *
510
     * @param string $label         Text appearing on the button
511
     * @param string $name          Element name (for form treatment purposes)
512
     * @param bool   $createElement Whether to use the create or add method
513
     *
514
     * @return HTML_QuickForm_button
515
     */
516
    public function addButtonDelete($label, $name = 'submit', $createElement = false)
517
    {
518
        return $this->addButton(
519
            $name,
520
            $label,
521
            'delete',
522
            'danger',
523
            null,
524
            null,
525
            [],
526
            $createElement
527
        );
528
    }
529
530
    /**
531
     * Returns a move style button.
532
     *
533
     * @param string $label         Text appearing on the button
534
     * @param string $name          Element name (for form treatment purposes)
535
     * @param bool   $createElement Whether to use the create or add method
536
     *
537
     * @return HTML_QuickForm_button
538
     */
539
    public function addButtonMove($label, $name = 'submit', $createElement = false)
540
    {
541
        return $this->addButton(
542
            $name,
543
            $label,
544
            'arrow-right-bold-circle',
545
            'primary',
546
            null,
547
            null,
548
            [],
549
            $createElement
550
        );
551
    }
552
553
    /**
554
     * Returns a button with the primary color and a paper-plane icon.
555
     *
556
     * @param string $label         Text appearing on the button
557
     * @param string $name          Element name (for form treatment purposes)
558
     * @param bool   $createElement Whether to use the create or add method
559
     * @param array  $attributes
560
     *
561
     * @return HTML_QuickForm_button
562
     */
563
    public function addButtonSend($label, $name = 'submit', $createElement = false, $attributes = [])
564
    {
565
        return $this->addButton(
566
            $name,
567
            $label,
568
            'send',
569
            'primary',
570
            null,
571
            null,
572
            $attributes,
573
            $createElement
574
        );
575
    }
576
577
    /**
578
     * Returns a button with the default (grey?) color and a magnifier icon.
579
     *
580
     * @param string $label Text appearing on the button
581
     * @param string $name  Element name (for form treatment purposes)
582
     *
583
     * @return HTML_QuickForm_button
584
     */
585
    public function addButtonSearch($label = null, $name = 'submit')
586
    {
587
        if (empty($label)) {
588
            $label = get_lang('Search');
589
        }
590
591
        return $this->addButton($name, $label, 'magnify', 'primary');
592
    }
593
594
    /**
595
     * Returns a button with the primary color and a right-pointing arrow icon.
596
     *
597
     * @param string $label      Text appearing on the button
598
     * @param string $name       Element name (for form treatment purposes)
599
     * @param array  $attributes Additional attributes
600
     *
601
     * @return HTML_QuickForm_button
602
     */
603
    public function addButtonNext($label, $name = 'submit', $attributes = [])
604
    {
605
        return $this->addButton(
606
            $name,
607
            $label,
608
            'arrow-right',
609
            'primary',
610
            null,
611
            null,
612
            $attributes
613
        );
614
    }
615
616
    /**
617
     * Returns a button with the primary color and a check mark icon.
618
     *
619
     * @param string $label         Text appearing on the button
620
     * @param string $name          Element name (for form treatment purposes)
621
     * @param bool   $createElement Whether to use the create or add method
622
     *
623
     * @return HTML_QuickForm_button
624
     */
625
    public function addButtonImport($label, $name = 'submit', $createElement = false)
626
    {
627
        return $this->addButton(
628
            $name,
629
            $label,
630
            'check',
631
            'primary',
632
            null,
633
            null,
634
            [],
635
            $createElement
636
        );
637
    }
638
639
    /**
640
     * Returns a button with the primary color and a check-mark icon.
641
     *
642
     * @param string $label         Text appearing on the button
643
     * @param string $name          Element name (for form treatment purposes)
644
     * @param bool   $createElement Whether to use the create or add method
645
     *
646
     * @return HTML_QuickForm_button
647
     */
648
    public function addButtonExport($label, $name = 'submit', $createElement = false)
649
    {
650
        return $this->addButton(
651
            $name,
652
            $label,
653
            'check',
654
            'primary',
655
            null,
656
            null,
657
            [],
658
            $createElement
659
        );
660
    }
661
662
    /**
663
     * Shortcut to filter button.
664
     *
665
     * @param string $label         Text appearing on the button
666
     * @param string $name          Element name (for form treatment purposes)
667
     * @param bool   $createElement Whether to use the create or add method
668
     *
669
     * @return HTML_QuickForm_button
670
     */
671
    public function addButtonFilter($label, $name = 'submit', $createElement = false)
672
    {
673
        return $this->addButton(
674
            $name,
675
            $label,
676
            'filter',
677
            'primary',
678
            null,
679
            null,
680
            [],
681
            $createElement
682
        );
683
    }
684
685
    /**
686
     * Shortcut to reset button.
687
     *
688
     * @param string $label         Text appearing on the button
689
     * @param string $name          Element name (for form treatment purposes)
690
     * @param bool   $createElement Whether to use the create or add method
691
     *
692
     * @return HTML_QuickForm_button
693
     */
694
    public function addButtonReset($label, $name = 'reset', $createElement = false)
695
    {
696
        $icon = 'eraser';
697
        $style = 'default';
698
        $size = 'default';
699
        $class = null;
700
        $attributes = [];
701
702
        if ($createElement) {
703
            return $this->createElement(
704
                'reset',
705
                $name,
706
                $label,
707
                $icon,
708
                $style,
709
                $size,
710
                $class,
711
                $attributes
712
            );
713
        }
714
715
        return $this->addElement(
716
            'reset',
717
            $name,
718
            $label,
719
            $icon,
720
            $style,
721
            $size,
722
            $class,
723
            $attributes
724
        );
725
    }
726
727
    /**
728
     * Returns a button with the primary color and an upload icon.
729
     *
730
     * @param string $label         Text appearing on the button
731
     * @param string $name          Element name (for form treatment purposes)
732
     * @param bool   $createElement Whether to use the create or add method
733
     *
734
     * @return HTML_QuickForm_button
735
     */
736
    public function addButtonUpload($label, $name = 'submit', $createElement = false)
737
    {
738
        return $this->addButton(
739
            $name,
740
            $label,
741
            'upload',
742
            'primary',
743
            null,
744
            null,
745
            [],
746
            $createElement
747
        );
748
    }
749
750
    /**
751
     * Returns a button with the primary color and a download icon.
752
     *
753
     * @param string $label         Text appearing on the button
754
     * @param string $name          Element name (for form treatment purposes)
755
     * @param bool   $createElement Whether to use the create or add method
756
     *
757
     * @return HTML_QuickForm_button
758
     */
759
    public function addButtonDownload($label, $name = 'submit', $createElement = false)
760
    {
761
        return $this->addButton(
762
            $name,
763
            $label,
764
            'download',
765
            'primary',
766
            null,
767
            null,
768
            [],
769
            $createElement
770
        );
771
    }
772
773
    /**
774
     * Returns a button with the primary color and a magnifier icon.
775
     *
776
     * @param string $label         Text appearing on the button
777
     * @param string $name          Element name (for form treatment purposes)
778
     * @param bool   $createElement Whether to use the create or add method
779
     *
780
     * @return HTML_QuickForm_button
781
     */
782
    public function addButtonPreview($label, $name = 'submit', $createElement = false)
783
    {
784
        return $this->addButton(
785
            $name,
786
            $label,
787
            'magnify',
788
            'primary',
789
            null,
790
            null,
791
            [],
792
            $createElement
793
        );
794
    }
795
796
    /**
797
     * Returns a button with the primary color and a copy (double sheet) icon.
798
     *
799
     * @param string $label         Text appearing on the button
800
     * @param string $name          Element name (for form treatment purposes)
801
     * @param bool   $createElement Whether to use the create or add method
802
     *
803
     * @return HTML_QuickForm_button
804
     */
805
    public function addButtonCopy($label, $name = 'submit', $createElement = false)
806
    {
807
        return $this->addButton(
808
            $name,
809
            $label,
810
            'copy',
811
            'primary',
812
            null,
813
            null,
814
            [],
815
            $createElement
816
        );
817
    }
818
819
    /**
820
     * @param string $name
821
     * @param string $label
822
     * @param string $text
823
     * @param array  $attributes
824
     *
825
     * @return HTML_QuickForm_checkbox
826
     */
827
    public function addCheckBox($name, $label, $text = '', $attributes = [])
828
    {
829
        return $this->addElement('checkbox', $name, $label, $text, $attributes);
830
    }
831
832
    /**
833
     * @param string $name
834
     * @param string $label
835
     * @param array  $options
836
     * @param array  $attributes
837
     *
838
     * @return HTML_QuickForm_group
839
     */
840
    public function addCheckBoxGroup($name, $label, $options = [], $attributes = [])
841
    {
842
        $group = [];
843
        foreach ($options as $value => $text) {
844
            $attributes['value'] = $value;
845
            $group[] = $this->createElement(
846
                'checkbox',
847
                $value,
848
                null,
849
                $text,
850
                $attributes
851
            );
852
        }
853
854
        return $this->addGroup($group, $name, $label);
855
    }
856
857
    /**
858
     * @param string $name
859
     * @param string $label
860
     * @param array  $options
861
     * @param array  $attributes
862
     *
863
     * @return HTML_QuickForm_group
864
     */
865
    public function addRadio($name, $label, $options = [], $attributes = [])
866
    {
867
        $group = [];
868
        foreach ($options as $key => $value) {
869
            $group[] = $this->createElement('radio', null, null, $value, $key, $attributes);
870
        }
871
872
        return $this->addGroup($group, $name, $label);
873
    }
874
875
    /**
876
     * @param string|array $label
877
     *
878
     * @return HTML_QuickForm_select
879
     */
880
    public function addSelect(string $name, $label, ?array $options = [], array $attributes = [])
881
    {
882
        return $this->addElement('select', $name, $label, $options, $attributes);
883
    }
884
885
    /**
886
     * @param $name
887
     * @param $label
888
     * @param $collection
889
     * @param array  $attributes
890
     * @param bool   $addNoneOption
891
     * @param string $textCallable  set a function getStringValue() by default __toString()
892
     *
893
     * @return HTML_QuickForm_element
894
     */
895
    public function addSelectFromCollection(
896
        $name,
897
        $label,
898
        $collection,
899
        $attributes = [],
900
        $addNoneOption = false,
901
        $textCallable = ''
902
    ) {
903
        $options = [];
904
905
        if ($addNoneOption) {
906
            $options[0] = get_lang('None');
907
        }
908
909
        if (!empty($collection)) {
910
            foreach ($collection as $item) {
911
                $text = $item;
912
                if (!empty($textCallable)) {
913
                    $text = $item->$textCallable();
914
                }
915
                $options[$item->getId()] = $text;
916
            }
917
        }
918
919
        return $this->addElement('select', $name, $label, $options, $attributes);
920
    }
921
922
    public function addMultiSelect(string $name, $label, array $options, array $attributes = [])
923
    {
924
        $this->addElement('advmultiselect', $name, $label, $options, $attributes);
925
    }
926
927
    /**
928
     * @param string $label
929
     * @param string $text
930
     * @param bool   $createElement
931
     *
932
     * @return HTML_QuickForm_Element
933
     */
934
    public function addLabel($label, $text, $createElement = false)
935
    {
936
        if ($createElement) {
937
            return $this->createElement(
938
                'label',
939
                $label,
940
                $text
941
            );
942
        }
943
944
        return $this->addElement('label', $label, $text);
945
    }
946
947
    /**
948
     * @param string $text
949
     */
950
    public function addHeader($text)
951
    {
952
        if (!empty($text)) {
953
            $this->addElement('header', $text);
954
        }
955
    }
956
957
    /**
958
     * @param string       $name
959
     * @param string|array $label
960
     * @param array        $attributes
961
     *
962
     * @throws Exception if the file doesn't have an id
963
     *
964
     * @return HTML_QuickForm_file
965
     */
966
    public function addFile($name, $label, $attributes = [])
967
    {
968
        try {
969
            $element = $this->addElement('file', $name, $label, $attributes);
970
            if (isset($attributes['crop_image'])) {
971
                $id = $element->getAttribute('id');
972
                if (empty($id)) {
973
                    throw new Exception('If you use the crop functionality the element must have an id');
974
                }
975
                $this->addHtml(
976
                    '
977
                <div class="form-group row" id="'.$id.'-form-group" style="display: none;">
978
                    <div class="offset-md-2 col-sm-8">
979
                        <div class="card-cropper">
980
                            <div id="'.$id.'_crop_image" class="cropCanvas">
981
                                <img id="'.$id.'_preview_image">
982
                            </div>
983
                            <button class="btn btn--primary" type="button" name="cropButton" id="'.$id.'_crop_button">
984
                                <em class="fa fa-crop"></em> '.get_lang('Crop your picture').'
985
                            </button>
986
                        </div>
987
                    </div>
988
                </div>'
989
                );
990
                $this->addHidden($id.'_crop_result', '');
991
                $this->addHidden($id.'_crop_result_for_resource', '');
992
                $this->addHidden($id.'_crop_image_base_64', '');
993
            }
994
        } catch (HTML_Quick | Form_Error $e) {
995
            var_dump($e->getMessage());
0 ignored issues
show
Security Debugging Code introduced by
var_dump($e->getMessage()) looks like debug code. Are you sure you do not want to remove it?
Loading history...
996
        }
997
998
        return $element;
999
    }
1000
1001
    /**
1002
     * @param string $snippet
1003
     */
1004
    public function addHtml($snippet)
1005
    {
1006
        if (empty($snippet)) {
1007
            return false;
1008
        }
1009
        $this->addElement('html', $snippet);
1010
1011
        return true;
1012
    }
1013
1014
    public function addStartPanel(string $id, string $title, bool $open = false, $icon = null): void
1015
    {
1016
        // Same code as in Display::panelCollapse
1017
        $parent = null;
1018
        $javascript = '
1019
        <script>
1020
            document.addEventListener("DOMContentLoaded", function() {
1021
                const buttons = document.querySelectorAll("#card_'.$id.' a");
1022
                const menus = document.querySelectorAll("#collapse_'.$id.'");
1023
1024
                buttons.forEach((button, index) => {
1025
                    button.addEventListener("click", function() {
1026
                        menus.forEach((menu, menuIndex) => {
1027
                            if (index === menuIndex) {
1028
                                button.setAttribute("aria-expanded", "true" === button.getAttribute("aria-expanded") ? "false" : "true")
1029
                                button.classList.toggle("mdi-chevron-down")
1030
                                button.classList.toggle("mdi-chevron-up")
1031
                                menu.classList.toggle("active");
1032
                            } else {
1033
                                menu.classList.remove("active");
1034
                            }
1035
                        });
1036
                    });
1037
                });
1038
            });
1039
        </script>';
1040
1041
        $this->addHtml($javascript);
1042
1043
        $htmlIcon = '';
1044
        if ($icon) {
1045
            $htmlIcon = Display::getMdiIcon($icon, 'ch-tool-icon', 'float:left;', ICON_SIZE_SMALL);
1046
        }
1047
        $html = '
1048
        <div class="display-panel-collapse field">
1049
            <div class="display-panel-collapse__header" id="card_'.$id.'">
1050
                <a role="button"
1051
                    class="mdi mdi-chevron-down"
1052
                    data-toggle="collapse"
1053
                    data-target="#collapse_'.$id.'"
1054
                    aria-expanded="'.(($open) ? 'true' : 'false').'"
1055
                    aria-controls="collapse_'.$id.'"
1056
                >
1057
                    '.$htmlIcon.'&nbsp;'.$title.'
1058
                </a>
1059
            </div>
1060
            <div
1061
                id="collapse_'.$id.'"
1062
                class="display-panel-collapse__collapsible '.(($open) ? 'active' : '').'"
1063
            >
1064
                <div id="collapse_contant_'.$id.'"  class="card-body ">';
1065
1066
        $this->addHtml($html);
1067
    }
1068
1069
    public function addEndPanel(): void
1070
    {
1071
        $this->addHtml('</div></div></div>');
1072
    }
1073
1074
    /**
1075
     * Draws a panel of options see the course_info/infocours.php page.
1076
     *
1077
     * @param string $name      internal name
1078
     * @param string $title     visible title
1079
     * @param array  $groupList list of group or elements
1080
     */
1081
    public function addPanelOption($name, $title, $groupList, $icon, $open)
1082
    {
1083
        $this->addStartPanel($name, $title, $open, $icon);
1084
1085
        foreach ($groupList as $groupName => $group) {
1086
            // Add group array
1087
            if (!empty($groupName) && is_array($group)) {
1088
                $this->addGroup($group, '', $groupName);
1089
            }
1090
            // Add element
1091
            if ($group instanceof HTML_QuickForm_element) {
1092
                $this->addElement($group);
1093
            }
1094
        }
1095
1096
        $this->addEndPanel();
1097
    }
1098
1099
    /**
1100
     * Adds a HTML-editor to the form.
1101
     *
1102
     * @param string       $name
1103
     * @param string|array $label      The label for the form-element
1104
     * @param bool         $required   (optional) Is the form-element required (default=true)
1105
     * @param bool         $fullPage   (optional) When it is true, the editor loads completed html code for a full page
1106
     * @param array        $config     (optional) Configuration settings for the online editor
1107
     * @param array        $attributes
1108
     *
1109
     * @throws Exception
1110
     * @throws HTML_QuickForm_Error
1111
     */
1112
    public function addHtmlEditor(
1113
        $name,
1114
        $label,
1115
        $required = true,
1116
        $fullPage = false,
1117
        $config = [],
1118
        $attributes = []
1119
    ) {
1120
        $attributes['rows'] = $config['rows'] ?? 15;
1121
        $attributes['cols'] = $config['cols'] ?? 80;
1122
        $attributes['cols-size'] = $config['cols-size'] ?? [];
1123
        $attributes['class'] = $config['class'] ?? [];
1124
        $cleanName = str_replace(['[', ']', '#'], '', $name);
1125
1126
        if (empty($attributes['id'])) {
1127
            $attributes['id'] = $cleanName;
1128
        }
1129
1130
        //$attributes['id'] = $config['id'] ?? 'editor_'.$cleanName;
1131
1132
        $this->addElement('html_editor', $name, $label, $attributes, $config);
1133
        $this->applyFilter($name, 'trim');
1134
        $this->applyFilter($name, 'attr_on_filter');
1135
        if ($required) {
1136
            $this->addRule($name, get_lang('Required field'), 'required');
1137
        }
1138
1139
        /** @var HtmlEditor $element */
1140
        $element = $this->getElement($name);
1141
        $config['style'] = $config['style'] ?? false;
1142
        if ($fullPage) {
1143
            $config['fullPage'] = true;
1144
            // Adds editor_content.css in ckEditor
1145
            $config['style'] = true;
1146
        }
1147
1148
        if ($element->editor) {
1149
            $element->editor->processConfig($config);
1150
        }
1151
    }
1152
1153
    /**
1154
     * Prevent execution of event handlers in HTML elements.
1155
     *
1156
     * @param string $html
1157
     * @return string
1158
     */
1159
    function attr_on_filter($html) {
0 ignored issues
show
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1160
        $pattern = '/\s+on\w+\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i';
1161
        return preg_replace($pattern, '', $html);
1162
    }
1163
1164
    /**
1165
     * Adds a Google Maps Geolocalization field to the form.
1166
     *
1167
     * @param      $name
1168
     * @param      $label
1169
     * @param bool $hideGeoLocalizationDetails
1170
     */
1171
    public function addGeoLocationMapField($name, $label, $dataValue, $hideGeoLocalizationDetails = false)
1172
    {
1173
        $gMapsPlugin = GoogleMapsPlugin::create();
1174
        $geolocalization = 'true' === $gMapsPlugin->get('enable_api');
1175
1176
        if ($geolocalization && false === $gMapsPlugin->javascriptIncluded) {
1177
            $gmapsApiKey = $gMapsPlugin->get('api_key');
1178
            $url = '//maps.googleapis.com/maps/api/js?key='.$gmapsApiKey;
1179
            $this->addHtml('<script type="text/javascript" src="'.$url.'" ></script>');
1180
            $gMapsPlugin->javascriptIncluded = true;
1181
        }
1182
1183
        $this->addElement(
1184
            'text',
1185
            $name,
1186
            $label,
1187
            ['id' => $name]
1188
        );
1189
1190
        $this->addHidden(
1191
            $name.'_coordinates',
1192
            '',
1193
            ['id' => $name.'_coordinates']
1194
        );
1195
1196
        $this->applyFilter($name, 'stripslashes');
1197
        $this->applyFilter($name, 'trim');
1198
1199
        $this->addHtml(Extrafield::getLocalizationJavascript($name, $dataValue));
1200
1201
        if ($hideGeoLocalizationDetails) {
1202
            $this->addHtml('<div style="display:none">');
1203
        }
1204
1205
        $this->addHtml(
1206
            Extrafield::getLocalizationInput($name, $label)
1207
        );
1208
1209
        if ($hideGeoLocalizationDetails) {
1210
            $this->addHtml('</div>');
1211
        }
1212
    }
1213
1214
    /**
1215
     * @param string       $name
1216
     * @param string|array $label
1217
     *
1218
     * @return mixed
1219
     */
1220
    public function addButtonAdvancedSettings($name, $label = null)
1221
    {
1222
        $label = !empty($label) ? $label : get_lang('Advanced settings');
1223
1224
        return $this->addElement('advanced_settings', $name, $label);
1225
    }
1226
1227
    /**
1228
     * Adds a progress loading image to the form.
1229
     */
1230
    public function addProgress($delay = 2, $label = '')
1231
    {
1232
        if (empty($label)) {
1233
            $label = get_lang('Please stand by...');
1234
        }
1235
        $this->with_progress_bar = true;
1236
        $id = $this->getAttribute('id');
1237
1238
        $this->updateAttributes("onsubmit=\"javascript: addProgress('".$id."')\"");
1239
        $this->addHtml('<script language="javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/upload.js" type="text/javascript"></script>');
1240
    }
1241
1242
    /**
1243
     * This function has been created for avoiding changes directly within QuickForm class.
1244
     * When we use it, the element is threated as 'required' to be dealt during validation.
1245
     *
1246
     * @param array  $elements The array of elements
1247
     * @param string $message  The message displayed
1248
     */
1249
    public function add_multiple_required_rule($elements, $message)
1250
    {
1251
        $this->_required[] = $elements[0];
1252
        $this->addRule($elements, $message, 'multiple_required');
1253
    }
1254
1255
    /**
1256
     * Displays the form.
1257
     * If an element in the form didn't validate, an error message is showed
1258
     * asking the user to complete the form.
1259
     */
1260
    public function display()
1261
    {
1262
        echo $this->returnForm();
1263
    }
1264
1265
    /**
1266
     * Returns the HTML code of the form.
1267
     *
1268
     * @return string $return_value HTML code of the form
1269
     */
1270
    public function returnForm()
1271
    {
1272
        $returnValue = '';
1273
        /** @var HTML_QuickForm_element $element */
1274
        foreach ($this->_elements as &$element) {
1275
            $element->setLayout($this->getLayout());
1276
            $elementError = parent::getElementError($element->getName());
1277
            if (!is_null($elementError)) {
1278
                $returnValue .= Display::return_message($elementError, 'warning').'<br />';
1279
                break;
1280
            }
1281
        }
1282
1283
        $returnValue .= parent::toHtml();
1284
        // Add div-element which is to hold the progress bar
1285
        $id = $this->getAttribute('id');
1286
        if (isset($this->with_progress_bar) && $this->with_progress_bar) {
1287
            // @todo improve UI
1288
            $returnValue .= '<br />
1289
            <div id="loading_div_'.$id.'" class="loading_div" style="display:none;margin-left:40%; margin-top:10px; height:50px;">
1290
                <div class="wobblebar-loader"></div>
1291
            </div>
1292
            ';
1293
        }
1294
1295
        return $returnValue;
1296
    }
1297
1298
    /**
1299
     * Returns the HTML code of the form.
1300
     * If an element in the form didn't validate, an error message is showed
1301
     * asking the user to complete the form.
1302
     *
1303
     * @return string $return_value HTML code of the form
1304
     *
1305
     * @author Patrick Cool <[email protected]>, Ghent University, august 2006
1306
     * @author Julio Montoya
1307
     *
1308
     * @deprecated use returnForm()
1309
     */
1310
    public function return_form()
1311
    {
1312
        return $this->returnForm();
1313
    }
1314
1315
    /**
1316
     * @return HTML_QuickForm_Renderer_Default
1317
     */
1318
    public static function getDefaultRenderer()
1319
    {
1320
        return
1321
            isset($GLOBALS['_HTML_QuickForm_default_renderer']) ?
1322
                $GLOBALS['_HTML_QuickForm_default_renderer'] : null;
1323
    }
1324
1325
    /**
1326
     * Adds a input of type url to the form.
1327
     *
1328
     * @param string $name       The label for the form-element
1329
     * @param string $label      The element name
1330
     * @param bool   $required   Optional. Is the form-element required (default=true)
1331
     * @param array  $attributes Optional. List of attributes for the form-element
1332
     */
1333
    public function addUrl($name, $label, $required = true, $attributes = [])
1334
    {
1335
        $this->addElement('url', $name, $label, $attributes);
1336
        $this->applyFilter($name, 'trim');
1337
        $this->addRule($name, get_lang('Insert a valid URL'), 'url');
1338
1339
        if ($required) {
1340
            $this->addRule($name, get_lang('Required field'), 'required');
1341
        }
1342
    }
1343
1344
    /**
1345
     * Adds a text field for letters to the form.
1346
     * A trim-filter is attached to the field.
1347
     *
1348
     * @param string $name       The element name
1349
     * @param string $label      The label for the form-element
1350
     * @param bool   $required   Optional. Is the form-element required (default=true)
1351
     * @param array  $attributes Optional. List of attributes for the form-element
1352
     */
1353
    public function addTextLettersOnly(
1354
        $name,
1355
        $label,
1356
        $required = false,
1357
        $attributes = []
1358
    ) {
1359
        $attributes = array_merge(
1360
            $attributes,
1361
            [
1362
                'pattern' => '[a-zA-ZñÑ]+',
1363
                'title' => get_lang('Only letters'),
1364
            ]
1365
        );
1366
1367
        $this->addElement(
1368
            'text',
1369
            $name,
1370
            [
1371
                $label,
1372
                get_lang('Only letters'),
1373
            ],
1374
            $attributes
1375
        );
1376
1377
        $this->applyFilter($name, 'trim');
1378
1379
        if ($required) {
1380
            $this->addRule($name, get_lang('Required field'), 'required');
1381
        }
1382
1383
        $this->addRule(
1384
            $name,
1385
            get_lang('Only letters'),
1386
            'regex',
1387
            '/^[a-zA-ZñÑ]+$/'
1388
        );
1389
    }
1390
1391
    /**
1392
     * @param string $name
1393
     * @param string $label
1394
     * @param array  $attributes
1395
     * @param bool   $required
1396
     *
1397
     * @return HTML_QuickForm_element
1398
     */
1399
    public function addNumeric($name, $label, $attributes = [], $required = false)
1400
    {
1401
        $element = $this->addElement('Number', $name, $label, $attributes);
1402
1403
        if ($required) {
1404
            $this->addRule($name, get_lang('Required field'), 'required');
1405
        }
1406
1407
        return $element;
1408
    }
1409
1410
    /**
1411
     * Adds a text field for alphanumeric characters to the form.
1412
     * A trim-filter is attached to the field.
1413
     *
1414
     * @param string $name       The element name
1415
     * @param string $label      The label for the form-element
1416
     * @param bool   $required   Optional. Is the form-element required (default=true)
1417
     * @param array  $attributes Optional. List of attributes for the form-element
1418
     */
1419
    public function addTextAlphanumeric(
1420
        $name,
1421
        $label,
1422
        $required = false,
1423
        $attributes = []
1424
    ) {
1425
        $attributes = array_merge(
1426
            $attributes,
1427
            [
1428
                'pattern' => '[a-zA-Z0-9ñÑ]+',
1429
                'title' => get_lang('Only lettersAndNumbers'),
1430
            ]
1431
        );
1432
1433
        $this->addElement(
1434
            'text',
1435
            $name,
1436
            [
1437
                $label,
1438
                get_lang('Only lettersAndNumbers'),
1439
            ],
1440
            $attributes
1441
        );
1442
1443
        $this->applyFilter($name, 'trim');
1444
1445
        if ($required) {
1446
            $this->addRule($name, get_lang('Required field'), 'required');
1447
        }
1448
1449
        $this->addRule(
1450
            $name,
1451
            get_lang('Only lettersAndNumbers'),
1452
            'regex',
1453
            '/^[a-zA-Z0-9ÑÑ]+$/'
1454
        );
1455
    }
1456
1457
    /**
1458
     * @param string $name
1459
     * @param $label
1460
     * @param bool  $required
1461
     * @param array $attributes
1462
     * @param bool  $allowNegative
1463
     * @param int   $minValue
1464
     * @param null  $maxValue
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $maxValue is correct as it would always require null to be passed?
Loading history...
1465
     */
1466
    public function addFloat(
1467
        $name,
1468
        $label,
1469
        $required = false,
1470
        $attributes = [],
1471
        $allowNegative = false,
1472
        $minValue = null,
1473
        $maxValue = null
1474
    ) {
1475
        $this->addElement(
1476
            'FloatNumber',
1477
            $name,
1478
            $label,
1479
            $attributes
1480
        );
1481
1482
        $this->applyFilter($name, 'trim');
1483
1484
        if ($required) {
1485
            $this->addRule($name, get_lang('Required field'), 'required');
1486
        }
1487
1488
        // Rule allows "," and "."
1489
        /*$this->addRule(
1490
            $name,
1491
            get_lang('Only numbers'),
1492
            'regex',
1493
            '/(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)|(^-?\d\d*\,\d*$)|(^-?\,\d\d*$)/'
1494
        );*/
1495
1496
        if (false == $allowNegative) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1497
            $this->addRule(
1498
                $name,
1499
                get_lang('Negative value'),
1500
                'compare',
1501
                '>=',
1502
                'server',
1503
                false,
1504
                false,
1505
                0
1506
            );
1507
        }
1508
1509
        if (!is_null($minValue)) {
1510
            $this->addRule(
1511
                $name,
1512
                get_lang('Under the minimum.'),
1513
                'compare',
1514
                '>=',
1515
                'server',
1516
                false,
1517
                false,
1518
                $minValue
1519
            );
1520
        }
1521
1522
        if (!is_null($maxValue)) {
1523
            $this->addRule(
1524
                $name,
1525
                get_lang('Value exceeds score.'),
1526
                'compare',
1527
                '<=',
1528
                'server',
1529
                false,
1530
                false,
1531
                $maxValue
1532
            );
1533
        }
1534
    }
1535
1536
    /**
1537
     * Adds a text field for letters and spaces to the form.
1538
     * A trim-filter is attached to the field.
1539
     *
1540
     * @param string $name       The element name
1541
     * @param string $label      The label for the form-element
1542
     * @param bool   $required   Optional. Is the form-element required (default=true)
1543
     * @param array  $attributes Optional. List of attributes for the form-element
1544
     */
1545
    public function addTextLettersAndSpaces(
1546
        $name,
1547
        $label,
1548
        $required = false,
1549
        $attributes = []
1550
    ) {
1551
        $attributes = array_merge(
1552
            $attributes,
1553
            [
1554
                'pattern' => '[a-zA-ZñÑ\s]+',
1555
                'title' => get_lang('Only lettersAndSpaces'),
1556
            ]
1557
        );
1558
1559
        $this->addElement(
1560
            'text',
1561
            $name,
1562
            [
1563
                $label,
1564
                get_lang('Only lettersAndSpaces'),
1565
            ],
1566
            $attributes
1567
        );
1568
1569
        $this->applyFilter($name, 'trim');
1570
1571
        if ($required) {
1572
            $this->addRule($name, get_lang('Required field'), 'required');
1573
        }
1574
1575
        $this->addRule(
1576
            $name,
1577
            get_lang('Only lettersAndSpaces'),
1578
            'regex',
1579
            '/^[a-zA-ZñÑ\s]+$/'
1580
        );
1581
    }
1582
1583
    /**
1584
     * Adds a text field for alphanumeric and spaces characters to the form.
1585
     * A trim-filter is attached to the field.
1586
     *
1587
     * @param string $name       The element name
1588
     * @param string $label      The label for the form-element
1589
     * @param bool   $required   Optional. Is the form-element required (default=true)
1590
     * @param array  $attributes Optional. List of attributes for the form-element
1591
     */
1592
    public function addTextAlphanumericAndSpaces(
1593
        $name,
1594
        $label,
1595
        $required = false,
1596
        $attributes = []
1597
    ) {
1598
        $attributes = array_merge(
1599
            $attributes,
1600
            [
1601
                'pattern' => '[a-zA-Z0-9ñÑ\s]+',
1602
                'title' => get_lang('Only lettersAndNumbersAndSpaces'),
1603
            ]
1604
        );
1605
1606
        $this->addElement(
1607
            'text',
1608
            $name,
1609
            [
1610
                $label,
1611
                get_lang('Only lettersAndNumbersAndSpaces'),
1612
            ],
1613
            $attributes
1614
        );
1615
1616
        $this->applyFilter($name, 'trim');
1617
1618
        if ($required) {
1619
            $this->addRule($name, get_lang('Required field'), 'required');
1620
        }
1621
1622
        $this->addRule(
1623
            $name,
1624
            get_lang('Only lettersAndNumbersAndSpaces'),
1625
            'regex',
1626
            '/^[a-zA-Z0-9ñÑ\s]+$/'
1627
        );
1628
    }
1629
1630
    /**
1631
     * @param string $url
1632
     * @param string $urlToRedirect after upload redirect to this page
1633
     */
1634
    public function addMultipleUpload($url, $urlToRedirect = '')
1635
    {
1636
        $inputName = 'input_file_upload';
1637
        $this->addMultipleUploadJavascript($url, $inputName, $urlToRedirect);
1638
1639
        $this->addHtml('
1640
            <div class="description-upload">
1641
            '.get_lang('Click on the box below to select files from your computer (you can use CTRL + clic to select various files at a time), or drag and drop some files from your desktop directly over the box below. The system will handle the rest!').'
1642
            </div>
1643
            <span class="btn btn--success fileinput-button">
1644
                <i class="glyphicon glyphicon-plus"></i>
1645
                <span>'.get_lang('Add files').'</span>
1646
                <!-- The file input field used as target for the file upload widget -->
1647
                <input id="'.$inputName.'" type="file" name="files[]" multiple>
1648
            </span>
1649
            <div id="dropzone">
1650
                <div class="button-load">
1651
                '.get_lang('Click or drag and drop files here to upload them').'
1652
                </div>
1653
            </div>
1654
            <br />
1655
            <!-- The global progress bar -->
1656
            <div id="progress" class="progress">
1657
                <div class="progress-bar progress-bar-success"></div>
1658
            </div>
1659
            <div id="files" class="files"></div>
1660
        ');
1661
    }
1662
1663
    /**
1664
     * @param string $elementName
1665
     * @param string $groupName   if element is inside a group
1666
     *
1667
     * @throws Exception
1668
     */
1669
    public function addPasswordRule($elementName, $groupName = '')
1670
    {
1671
        if ('true' == api_get_setting('security.check_password')) {
1672
            $message = get_lang('this password  is too simple. Use a pass like this').': '.api_generate_password();
1673
1674
            if (!empty($groupName)) {
1675
                $groupObj = $this->getElement($groupName);
1676
1677
                if ($groupObj instanceof HTML_QuickForm_group) {
1678
                    $elementName = $groupObj->getElementName($elementName);
1679
1680
                    if (false === $elementName) {
1681
                        throw new Exception("The $groupName doesn't have the element $elementName");
1682
                    }
1683
1684
                    $this->_rules[$elementName][] = [
1685
                        'type' => 'callback',
1686
                        'format' => 'api_check_password',
1687
                        'message' => $message,
1688
                        'validation' => '',
1689
                        'reset' => false,
1690
                        'group' => $groupName,
1691
                    ];
1692
                }
1693
            } else {
1694
                $this->addRule(
1695
                    $elementName,
1696
                    $message,
1697
                    'callback',
1698
                    'api_check_password'
1699
                );
1700
            }
1701
        }
1702
    }
1703
1704
    /**
1705
     * Add an element with user ID and avatar to the form.
1706
     * It needs a Chamilo\CoreBundle\Entity\User as value. The exported value is the Chamilo\CoreBundle\Entity\User ID.
1707
     *
1708
     * @see \UserAvatar
1709
     *
1710
     * @param string $name
1711
     * @param string $label
1712
     * @param string $imageSize Optional. Small, medium or large image
1713
     * @param string $subtitle  Optional. The subtitle for the field
1714
     *
1715
     * @return \UserAvatar
1716
     */
1717
    public function addUserAvatar($name, $label, $imageSize = 'small', $subtitle = '')
1718
    {
1719
        return $this->addElement('UserAvatar', $name, $label, ['image_size' => $imageSize, 'sub_title' => $subtitle]);
1720
    }
1721
1722
    /**
1723
     * @param array $typeList
1724
     */
1725
    public function addEmailTemplate($typeList)
1726
    {
1727
        $mailManager = new MailTemplateManager();
1728
        foreach ($typeList as $type) {
1729
            $list = $mailManager->get_all(
1730
                ['where' => ['type = ? AND url_id = ?' => [$type, api_get_current_access_url_id()]]]
1731
            );
1732
1733
            $options = [get_lang('Select')];
1734
            $name = $type;
1735
            $defaultId = '';
1736
            foreach ($list as $item) {
1737
                $options[$item['id']] = $item['name'];
1738
                $name = $item['name'];
1739
                if (empty($defaultId)) {
1740
                    $defaultId = 1 == $item['default_template'] ? $item['id'] : '';
1741
                }
1742
            }
1743
1744
            $url = api_get_path(WEB_AJAX_PATH).'mail.ajax.php?a=select_option';
1745
            $typeNoDots = 'email_template_option_'.str_replace('.tpl', '', $type);
1746
            $this->addSelect(
1747
                'email_template_option['.$type.']',
1748
                $name,
1749
                $options,
1750
                ['id' => $typeNoDots]
1751
            );
1752
1753
            $templateNoDots = 'email_template_'.str_replace('.tpl', '', $type);
1754
            $templateNoDotsBlock = 'email_template_block_'.str_replace('.tpl', '', $type);
1755
            $this->addHtml('<div id="'.$templateNoDotsBlock.'" style="display:none">');
1756
            $this->addTextarea(
1757
                $templateNoDots,
1758
                get_lang('Preview'),
1759
                ['disabled' => 'disabled ', 'id' => $templateNoDots, 'rows' => '5']
1760
            );
1761
            $this->addHtml('</div>');
1762
1763
            $this->addHtml("<script>
1764
            $(function() {
1765
                var defaultValue = '$defaultId';
1766
                $('#$typeNoDots').val(defaultValue);
1767
                //$('#$typeNoDots').selectpicker('render');
1768
                if (defaultValue != '') {
1769
                    var selected = $('#$typeNoDots option:selected').val();
1770
                    $.ajax({
1771
                        url: '$url' + '&id=' + selected+ '&template_name=$type',
1772
                        success: function (data) {
1773
                            $('#$templateNoDots').html(data);
1774
                            $('#$templateNoDotsBlock').show();
1775
                            return;
1776
                        },
1777
                    });
1778
                }
1779
1780
                $('#$typeNoDots').on('change', function(){
1781
                    var selected = $('#$typeNoDots option:selected').val();
1782
                    $.ajax({
1783
                        url: '$url' + '&id=' + selected,
1784
                        success: function (data) {
1785
                            $('#$templateNoDots').html(data);
1786
                            $('#$templateNoDotsBlock').show();
1787
                            return;
1788
                        },
1789
                    });
1790
                });
1791
            });
1792
            </script>");
1793
        }
1794
    }
1795
1796
    /**
1797
     * Add email rule for an element.
1798
     */
1799
    public function addEmailRule(string $element)
1800
    {
1801
        $this->addRule(
1802
            $element,
1803
            get_lang('The email address is not complete or contains some invalid characters'),
1804
            'email'
1805
        );
1806
    }
1807
1808
    /**
1809
     * @param string $url           page that will handle the upload
1810
     * @param string $inputName
1811
     * @param string $urlToRedirect
1812
     */
1813
    private function addMultipleUploadJavascript($url, $inputName, $urlToRedirect = '')
1814
    {
1815
        $redirectCondition = '';
1816
        if (!empty($urlToRedirect)) {
1817
            $redirectCondition = "window.location.replace('$urlToRedirect'); ";
1818
        }
1819
        $icon = Display::getMdiIcon('text-box-outline', 'ch-tool-icon', null, ICON_SIZE_SMALL);
1820
        $this->addHtml("
1821
        <script>
1822
        $(function () {
1823
            'use strict';
1824
            $('#".$this->getAttribute('id')."').submit(function() {
1825
                return false;
1826
            });
1827
1828
            $('#dropzone').on('click', function() {
1829
                $('#".$inputName."').click();
1830
            });
1831
1832
            var url = '".$url."';
1833
            var uploadButton = $('<button/>')
1834
                .addClass('btn btn--primary')
1835
                .prop('disabled', true)
1836
                .text('".addslashes(get_lang('Loading'))."')
1837
                .on('click', function () {
1838
                    var \$this = $(this),
1839
                    data = \$this.data();
1840
                    \$this
1841
                        .off('click')
1842
                        .text('".addslashes(get_lang('Cancel'))."')
1843
                        .on('click', function () {
1844
                            \$this.remove();
1845
                            data.abort();
1846
                        });
1847
                    data.submit().always(function () {
1848
                        \$this.remove();
1849
                    });
1850
                });
1851
1852
            $('#".$inputName."').fileupload({
1853
                url: url,
1854
                dataType: 'json',
1855
                // Enable image resizing, except for Android and Opera,
1856
                // which actually support image resizing, but fail to
1857
                // send Blob objects via XHR requests:
1858
                disableImageResize: /Android(?!.*Chrome)|Opera/.test(window.navigator.userAgent),
1859
                previewMaxWidth: 300,
1860
                previewMaxHeight: 169,
1861
                previewCrop: true,
1862
                dropzone: $('#dropzone'),
1863
            }).on('fileuploadadd', function (e, data) {
1864
                data.context = $('<div class=\"row\" />').appendTo('#files');
1865
                $.each(data.files, function (index, file) {
1866
                    var node = $('<div class=\"col-sm-5 file_name\">').text(file.name);
1867
                    node.appendTo(data.context);
1868
                });
1869
            }).on('fileuploadprocessalways', function (e, data) {
1870
                var index = data.index,
1871
                    file = data.files[index],
1872
                    node = $(data.context.children()[index]);
1873
                if (file.preview) {
1874
                    data.context.prepend($('<div class=\"col-sm-4\">').html(file.preview));
1875
                } else {
1876
                    data.context.prepend($('<div class=\"col-sm-4\">').html('".$icon."'));
1877
                }
1878
                if (index + 1 === data.files.length) {
1879
                    data.context.find('button')
1880
                        .text('Upload')
1881
                        .prop('disabled', !!data.files.error);
1882
                }
1883
            }).on('fileuploadprogressall', function (e, data) {
1884
                var progress = parseInt(data.loaded / data.total * 100, 10);
1885
                $('#progress .progress-bar').css(
1886
                    'width',
1887
                    progress + '%'
1888
                );
1889
            }).on('fileuploaddone', function (e, data) {
1890
                $.each(data.result.files, function (index, file) {
1891
                    if (file.error) {
1892
                        var link = $('<div>')
1893
                            .attr({class : 'panel-image'})                            ;
1894
                        $(data.context.children()[index]).parent().wrap(link);
1895
                        // Update file name with new one from Chamilo
1896
                        $(data.context.children()[index]).parent().find('.file_name').html(file.name);
1897
                        var message = $('<div class=\"col-sm-3\">').html(
1898
                            $('<span class=\"message-image-danger\"/>').text(file.error)
1899
                        );
1900
                        $(data.context.children()[index]).parent().append(message);
1901
1902
                        return;
1903
                    }
1904
                    if (file.url) {
1905
                        var link = $('<a>')
1906
                            .attr({target: '_blank', class : 'panel-image'})
1907
                            .prop('href', file.url);
1908
                        $(data.context.children()[index]).parent().wrap(link);
1909
                    }
1910
                    // Update file name with new one from Chamilo
1911
                    $(data.context.children()[index]).parent().find('.file_name').html(file.name);
1912
                    var message = $('<div class=\"col-sm-3\">').html(
1913
                        $('<span class=\"alert alert-success\"/>').text('".addslashes(get_lang('File upload succeeded!'))."')
1914
                    );
1915
                    $(data.context.children()[index]).parent().append(message);
1916
                });
1917
                $('#dropzone').removeClass('hover');
1918
                ".$redirectCondition."
1919
            }).on('fileuploadfail', function (e, data) {
1920
                $.each(data.files, function (index) {
1921
                    var failedMessage = '".addslashes(get_lang('The file upload has failed.'))."';
1922
                    var error = $('<div class=\"col-sm-3\">').html(
1923
                        $('<span class=\"alert alert-danger\"/>').text(failedMessage)
1924
                    );
1925
                    $(data.context.children()[index]).parent().append(error);
1926
                });
1927
                $('#dropzone').removeClass('hover');
1928
            }).prop('disabled', !$.support.fileInput).parent().addClass($.support.fileInput ? undefined : 'disabled');
1929
1930
            $('#dropzone').on('dragover', function (e) {
1931
                // dragleave callback implementation
1932
                $('#dropzone').addClass('hover');
1933
            });
1934
1935
            $('#dropzone').on('dragleave', function (e) {
1936
                $('#dropzone').removeClass('hover');
1937
            });
1938
            $('.fileinput-button').hide();
1939
        });
1940
        </script>");
1941
    }
1942
}
1943
1944
/**
1945
 * Cleans HTML text filter.
1946
 *
1947
 * @param string $html HTML to clean
1948
 * @param int    $mode (optional)
1949
 *
1950
 * @return string The cleaned HTML
1951
 */
1952
function html_filter($html, $mode = NO_HTML)
1953
{
1954
    $allowed_tags = HTML_QuickForm_Rule_HTML::get_allowed_tags($mode);
1955
    $cleaned_html = kses($html, $allowed_tags);
0 ignored issues
show
The function kses was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1955
    $cleaned_html = /** @scrutinizer ignore-call */ kses($html, $allowed_tags);
Loading history...
1956
1957
    return $cleaned_html;
1958
}
1959
1960
function html_filter_teacher($html)
1961
{
1962
    return html_filter($html, TEACHER_HTML);
1963
}
1964
1965
function html_filter_student($html)
1966
{
1967
    return html_filter($html, STUDENT_HTML);
1968
}
1969
1970
function html_filter_teacher_fullpage($html)
1971
{
1972
    return html_filter($html, TEACHER_HTML_FULLPAGE);
1973
}
1974
1975
function html_filter_student_fullpage($html)
1976
{
1977
    return html_filter($html, STUDENT_HTML_FULLPAGE);
1978
}
1979
1980
/**
1981
 * Cleans mobile phone number text.
1982
 *
1983
 * @param string $mobilePhoneNumber Mobile phone number to clean
1984
 *
1985
 * @return string The cleaned mobile phone number
1986
 */
1987
function mobile_phone_number_filter($mobilePhoneNumber)
1988
{
1989
    $mobilePhoneNumber = str_replace(['+', '(', ')'], '', $mobilePhoneNumber);
1990
1991
    return ltrim($mobilePhoneNumber, '0');
1992
}
1993
1994
/**
1995
 * Cleans JS from a URL.
1996
 *
1997
 * @param string $html URL to clean
1998
 * @param int    $mode (optional)
1999
 *
2000
 * @return string The cleaned URL
2001
 */
2002
function plain_url_filter($html, $mode = NO_HTML)
2003
{
2004
    $allowed_tags = HTML_QuickForm_Rule_HTML::get_allowed_tags($mode);
2005
    $html = kses_no_null($html);
0 ignored issues
show
The function kses_no_null was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2005
    $html = /** @scrutinizer ignore-call */ kses_no_null($html);
Loading history...
2006
    $html = kses_js_entities($html);
0 ignored issues
show
The function kses_js_entities was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2006
    $html = /** @scrutinizer ignore-call */ kses_js_entities($html);
Loading history...
2007
    $allowed_html_fixed = kses_array_lc($allowed_tags);
0 ignored issues
show
The function kses_array_lc was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2007
    $allowed_html_fixed = /** @scrutinizer ignore-call */ kses_array_lc($allowed_tags);
Loading history...
2008
2009
    return kses_split($html, $allowed_html_fixed, ['http', 'https']);
0 ignored issues
show
The function kses_split was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

2009
    return /** @scrutinizer ignore-call */ kses_split($html, $allowed_html_fixed, ['http', 'https']);
Loading history...
2010
}
2011
2012
/**
2013
 * Prevent execution of event handlers in HTML elements.
2014
 */
2015
function attr_on_filter(string $html): string
2016
{
2017
    return RemoveOnAttributes::filter($html);
2018
}
2019
2020