Issues (1886)

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
    {
904
        $options = [];
905
906
        if ($addNoneOption) {
907
            $options[0] = get_lang('None');
908
        }
909
910
        if (!empty($collection)) {
911
            foreach ($collection as $item) {
912
                $text = $item;
913
                if (!empty($textCallable)) {
914
                    $text = $item->$textCallable();
915
                }
916
                $options[$item->getId()] = $text;
917
            }
918
        }
919
920
        return $this->addElement('select', $name, $label, $options, $attributes);
921
    }
922
923
    public function addMultiSelect(string $name, $label, array $options, array $attributes = [])
924
    {
925
        $this->addElement('advmultiselect', $name, $label, $options, $attributes);
926
    }
927
928
    /**
929
     * @param string $label
930
     * @param string $text
931
     * @param bool $createElement
932
     *
933
     * @return HTML_QuickForm_Element
934
     */
935
    public function addLabel($label, $text, $createElement = false)
936
    {
937
        if ($createElement) {
938
            return $this->createElement(
939
                'label',
940
                $label,
941
                $text
942
            );
943
        }
944
945
        return $this->addElement('label', $label, $text);
946
    }
947
948
    /**
949
     * @param string $text
950
     */
951
    public function addHeader($text)
952
    {
953
        if (!empty($text)) {
954
            $this->addElement('header', $text);
955
        }
956
    }
957
958
    /**
959
     * @param string $name
960
     * @param string|array $label
961
     * @param array $attributes
962
     *
963
     * @return HTML_QuickForm_file
964
     * @throws Exception if the file doesn't have an id
965
     *
966
     */
967
    public function addFile($name, $label, $attributes = [])
968
    {
969
        try {
970
            $element = $this->addElement('file', $name, $label, $attributes);
971
            if (isset($attributes['crop_image'])) {
972
                $id = $element->getAttribute('id');
973
                if (empty($id)) {
974
                    throw new Exception('If you use the crop functionality the element must have an id');
975
                }
976
                $this->addHtml(
977
                    '
978
                <div class="form-group row" id="' . $id . '-form-group" style="display: none;">
979
                    <div class="offset-md-2 col-sm-8">
980
                        <div class="card-cropper">
981
                            <div id="' . $id . '_crop_image" class="cropCanvas">
982
                                <img id="' . $id . '_preview_image">
983
                            </div>
984
                            <button class="btn btn--primary" type="button" name="cropButton" id="' . $id . '_crop_button">
985
                                <em class="fa fa-crop"></em> ' . get_lang('Crop your picture') . '
986
                            </button>
987
                        </div>
988
                    </div>
989
                </div>'
990
                );
991
                $this->addHidden($id . '_crop_result', '');
992
                $this->addHidden($id . '_crop_result_for_resource', '');
993
                $this->addHidden($id . '_crop_image_base_64', '');
994
            }
995
        } catch (HTML_Quick|Form_Error $e) {
996
            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...
997
        }
998
999
        return $element;
1000
    }
1001
1002
    /**
1003
     * @param string $snippet
1004
     */
1005
    public function addHtml($snippet)
1006
    {
1007
        if (empty($snippet)) {
1008
            return false;
1009
        }
1010
        $this->addElement('html', $snippet);
1011
1012
        return true;
1013
    }
1014
1015
    public function addStartPanel(string $id, string $title, bool $open = false, $icon = null): void
1016
    {
1017
        // Same code as in Display::panelCollapse
1018
        $parent = null;
1019
        $javascript = '
1020
        <script>
1021
            document.addEventListener("DOMContentLoaded", function() {
1022
                const buttons = document.querySelectorAll("#card_' . $id . ' a");
1023
                const menus = document.querySelectorAll("#collapse_' . $id . '");
1024
1025
                buttons.forEach((button, index) => {
1026
                    button.addEventListener("click", function() {
1027
                        menus.forEach((menu, menuIndex) => {
1028
                            if (index === menuIndex) {
1029
                                button.setAttribute("aria-expanded", "true" === button.getAttribute("aria-expanded") ? "false" : "true")
1030
                                button.classList.toggle("mdi-chevron-down")
1031
                                button.classList.toggle("mdi-chevron-up")
1032
                                menu.classList.toggle("active");
1033
                            } else {
1034
                                menu.classList.remove("active");
1035
                            }
1036
                        });
1037
                    });
1038
                });
1039
            });
1040
        </script>';
1041
1042
        $this->addHtml($javascript);
1043
1044
        $htmlIcon = '';
1045
        if ($icon) {
1046
            $htmlIcon = Display::getMdiIcon($icon, 'ch-tool-icon', 'float:left;', ICON_SIZE_SMALL);
1047
        }
1048
        $html = '
1049
        <div class="display-panel-collapse field">
1050
            <div class="display-panel-collapse__header" id="card_' . $id . '">
1051
                <a role="button"
1052
                    class="mdi mdi-chevron-down"
1053
                    data-toggle="collapse"
1054
                    data-target="#collapse_' . $id . '"
1055
                    aria-expanded="' . (($open) ? 'true' : 'false') . '"
1056
                    aria-controls="collapse_' . $id . '"
1057
                >
1058
                    ' . $htmlIcon . '&nbsp;' . $title . '
1059
                </a>
1060
            </div>
1061
            <div
1062
                id="collapse_' . $id . '"
1063
                class="display-panel-collapse__collapsible ' . (($open) ? 'active' : '') . '"
1064
            >
1065
                <div id="collapse_contant_' . $id . '"  class="card-body ">';
1066
1067
        $this->addHtml($html);
1068
    }
1069
1070
    public function addEndPanel(): void
1071
    {
1072
        $this->addHtml('</div></div></div>');
1073
    }
1074
1075
    /**
1076
     * Draws a panel of options see the course_info/infocours.php page.
1077
     *
1078
     * @param string $name internal name
1079
     * @param string $title visible title
1080
     * @param array $groupList list of group or elements
1081
     */
1082
    public function addPanelOption($name, $title, $groupList, $icon, $open)
1083
    {
1084
        $this->addStartPanel($name, $title, $open, $icon);
1085
1086
        foreach ($groupList as $groupName => $group) {
1087
            // Add group array
1088
            if (!empty($groupName) && is_array($group)) {
1089
                $this->addGroup($group, '', $groupName);
1090
            }
1091
            // Add element
1092
            if ($group instanceof HTML_QuickForm_element) {
1093
                $this->addElement($group);
1094
            }
1095
        }
1096
1097
        $this->addEndPanel();
1098
    }
1099
1100
    /**
1101
     * Adds a HTML-editor to the form.
1102
     *
1103
     * @param string $name
1104
     * @param string|array $label The label for the form-element
1105
     * @param bool $required (optional) Is the form-element required (default=true)
1106
     * @param bool $fullPage (optional) When it is true, the editor loads completed html code for a full page
1107
     * @param array $config (optional) Configuration settings for the online editor
1108
     * @param array $attributes
1109
     *
1110
     * @throws Exception
1111
     * @throws HTML_QuickForm_Error
1112
     */
1113
    public function addHtmlEditor(
1114
        $name,
1115
        $label,
1116
        $required = true,
1117
        $fullPage = false,
1118
        $config = [],
1119
        $attributes = []
1120
    )
1121
    {
1122
        $attributes['rows'] = $config['rows'] ?? 15;
1123
        $attributes['cols'] = $config['cols'] ?? 80;
1124
        $attributes['cols-size'] = $config['cols-size'] ?? [];
1125
        $attributes['class'] = $config['class'] ?? [];
1126
        $cleanName = str_replace(['[', ']', '#'], '', $name);
1127
1128
        if (empty($attributes['id'])) {
1129
            $attributes['id'] = $cleanName;
1130
        }
1131
1132
        //$attributes['id'] = $config['id'] ?? 'editor_'.$cleanName;
1133
1134
        $this->addElement('html_editor', $name, $label, $attributes, $config);
1135
        $this->applyFilter($name, 'trim');
1136
        $this->applyFilter($name, 'attr_on_filter');
1137
        if ($required) {
1138
            $this->addRule($name, get_lang('Required field'), 'required');
1139
        }
1140
1141
        /** @var HtmlEditor $element */
1142
        $element = $this->getElement($name);
1143
        $config['style'] = $config['style'] ?? false;
1144
        if ($fullPage) {
1145
            $config['fullPage'] = true;
1146
            // Adds editor_content.css in ckEditor
1147
            $config['style'] = true;
1148
        }
1149
1150
        if ($element->editor) {
1151
            $element->editor->processConfig($config);
1152
        }
1153
    }
1154
1155
    /**
1156
     * Prevent execution of event handlers in HTML elements.
1157
     *
1158
     * @param string $html
1159
     * @return string
1160
     */
1161
    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...
1162
    {
1163
        $pattern = '/\s+on\w+\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i';
1164
        return preg_replace($pattern, '', $html);
1165
    }
1166
1167
    /**
1168
     * Adds a Google Maps Geolocalization field to the form.
1169
     *
1170
     * @param      $name
1171
     * @param      $label
1172
     * @param bool $hideGeoLocalizationDetails
1173
     */
1174
    public function addGeoLocationMapField($name, $label, $dataValue, $hideGeoLocalizationDetails = false)
1175
    {
1176
        $gMapsPlugin = GoogleMapsPlugin::create();
1177
        $geolocalization = 'true' === $gMapsPlugin->get('enable_api');
1178
1179
        if ($geolocalization && false === $gMapsPlugin->javascriptIncluded) {
1180
            $gmapsApiKey = $gMapsPlugin->get('api_key');
1181
            $url = '//maps.googleapis.com/maps/api/js?key=' . $gmapsApiKey;
1182
            $this->addHtml('<script type="text/javascript" src="' . $url . '" ></script>');
1183
            $gMapsPlugin->javascriptIncluded = true;
1184
        }
1185
1186
        $this->addElement(
1187
            'text',
1188
            $name,
1189
            $label,
1190
            ['id' => $name]
1191
        );
1192
1193
        $this->addHidden(
1194
            $name . '_coordinates',
1195
            '',
1196
            ['id' => $name . '_coordinates']
1197
        );
1198
1199
        $this->applyFilter($name, 'stripslashes');
1200
        $this->applyFilter($name, 'trim');
1201
1202
        $this->addHtml(Extrafield::getLocalizationJavascript($name, $dataValue));
1203
1204
        if ($hideGeoLocalizationDetails) {
1205
            $this->addHtml('<div style="display:none">');
1206
        }
1207
1208
        $this->addHtml(
1209
            Extrafield::getLocalizationInput($name, $label)
1210
        );
1211
1212
        if ($hideGeoLocalizationDetails) {
1213
            $this->addHtml('</div>');
1214
        }
1215
    }
1216
1217
    /**
1218
     * @param string $name
1219
     * @param string|array $label
1220
     *
1221
     * @return mixed
1222
     */
1223
    public function addButtonAdvancedSettings($name, $label = null)
1224
    {
1225
        $label = !empty($label) ? $label : get_lang('Advanced settings');
1226
1227
        return $this->addElement('advanced_settings', $name, $label);
1228
    }
1229
1230
    /**
1231
     * Adds a progress loading image to the form.
1232
     */
1233
    public function addProgress($delay = 2, $label = '')
1234
    {
1235
        if (empty($label)) {
1236
            $label = get_lang('Please stand by...');
1237
        }
1238
        $this->with_progress_bar = true;
1239
        $id = $this->getAttribute('id');
1240
1241
        $this->updateAttributes("onsubmit=\"javascript: addProgress('" . $id . "')\"");
1242
        $this->addHtml('<script language="javascript" src="' . api_get_path(WEB_LIBRARY_PATH) . 'javascript/upload.js" type="text/javascript"></script>');
1243
    }
1244
1245
    /**
1246
     * This function has been created for avoiding changes directly within QuickForm class.
1247
     * When we use it, the element is threated as 'required' to be dealt during validation.
1248
     *
1249
     * @param array $elements The array of elements
1250
     * @param string $message The message displayed
1251
     */
1252
    public function add_multiple_required_rule($elements, $message)
1253
    {
1254
        $this->_required[] = $elements[0];
1255
        $this->addRule($elements, $message, 'multiple_required');
1256
    }
1257
1258
    /**
1259
     * Displays the form.
1260
     * If an element in the form didn't validate, an error message is showed
1261
     * asking the user to complete the form.
1262
     */
1263
    public function display()
1264
    {
1265
        echo $this->returnForm();
1266
    }
1267
1268
    /**
1269
     * Returns the HTML code of the form.
1270
     *
1271
     * @return string $return_value HTML code of the form
1272
     */
1273
    public function returnForm()
1274
    {
1275
        $returnValue = '';
1276
        /** @var HTML_QuickForm_element $element */
1277
        foreach ($this->_elements as &$element) {
1278
            $element->setLayout($this->getLayout());
1279
            $elementError = parent::getElementError($element->getName());
1280
            if (!is_null($elementError)) {
1281
                $returnValue .= Display::return_message($elementError, 'warning') . '<br />';
1282
                break;
1283
            }
1284
        }
1285
1286
        $returnValue .= parent::toHtml();
1287
        // Add div-element which is to hold the progress bar
1288
        $id = $this->getAttribute('id');
1289
        if (isset($this->with_progress_bar) && $this->with_progress_bar) {
1290
            // @todo improve UI
1291
            $returnValue .= '<br />
1292
            <div id="loading_div_' . $id . '" class="loading_div" style="display:none;margin-left:40%; margin-top:10px; height:50px;">
1293
                <div class="wobblebar-loader"></div>
1294
            </div>
1295
            ';
1296
        }
1297
1298
        return $returnValue;
1299
    }
1300
1301
    /**
1302
     * Returns the HTML code of the form.
1303
     * If an element in the form didn't validate, an error message is showed
1304
     * asking the user to complete the form.
1305
     *
1306
     * @return string $return_value HTML code of the form
1307
     *
1308
     * @author Patrick Cool <[email protected]>, Ghent University, august 2006
1309
     * @author Julio Montoya
1310
     *
1311
     * @deprecated use returnForm()
1312
     */
1313
    public function return_form()
1314
    {
1315
        return $this->returnForm();
1316
    }
1317
1318
    /**
1319
     * @return HTML_QuickForm_Renderer_Default
1320
     */
1321
    public static function getDefaultRenderer()
1322
    {
1323
        return
1324
            isset($GLOBALS['_HTML_QuickForm_default_renderer']) ?
1325
                $GLOBALS['_HTML_QuickForm_default_renderer'] : null;
1326
    }
1327
1328
    /**
1329
     * Adds a input of type url to the form.
1330
     *
1331
     * @param string $name The label for the form-element
1332
     * @param string $label The element name
1333
     * @param bool $required Optional. Is the form-element required (default=true)
1334
     * @param array $attributes Optional. List of attributes for the form-element
1335
     */
1336
    public function addUrl($name, $label, $required = true, $attributes = [])
1337
    {
1338
        $this->addElement('url', $name, $label, $attributes);
1339
        $this->applyFilter($name, 'trim');
1340
        $this->addRule($name, get_lang('Insert a valid URL'), 'url');
1341
1342
        if ($required) {
1343
            $this->addRule($name, get_lang('Required field'), 'required');
1344
        }
1345
    }
1346
1347
    /**
1348
     * Adds a text field for letters to the form.
1349
     * A trim-filter is attached to the field.
1350
     *
1351
     * @param string $name The element name
1352
     * @param string $label The label for the form-element
1353
     * @param bool $required Optional. Is the form-element required (default=true)
1354
     * @param array $attributes Optional. List of attributes for the form-element
1355
     */
1356
    public function addTextLettersOnly(
1357
        $name,
1358
        $label,
1359
        $required = false,
1360
        $attributes = []
1361
    )
1362
    {
1363
        $attributes = array_merge(
1364
            $attributes,
1365
            [
1366
                'pattern' => '[a-zA-ZñÑ]+',
1367
                'title' => get_lang('Only letters'),
1368
            ]
1369
        );
1370
1371
        $this->addElement(
1372
            'text',
1373
            $name,
1374
            [
1375
                $label,
1376
                get_lang('Only letters'),
1377
            ],
1378
            $attributes
1379
        );
1380
1381
        $this->applyFilter($name, 'trim');
1382
1383
        if ($required) {
1384
            $this->addRule($name, get_lang('Required field'), 'required');
1385
        }
1386
1387
        $this->addRule(
1388
            $name,
1389
            get_lang('Only letters'),
1390
            'regex',
1391
            '/^[a-zA-ZñÑ]+$/'
1392
        );
1393
    }
1394
1395
    /**
1396
     * @param string $name
1397
     * @param string $label
1398
     * @param array $attributes
1399
     * @param bool $required
1400
     *
1401
     * @return HTML_QuickForm_element
1402
     */
1403
    public function addNumeric($name, $label, $attributes = [], $required = false)
1404
    {
1405
        $element = $this->addElement('Number', $name, $label, $attributes);
1406
1407
        if ($required) {
1408
            $this->addRule($name, get_lang('Required field'), 'required');
1409
        }
1410
1411
        return $element;
1412
    }
1413
1414
    /**
1415
     * Adds a text field for alphanumeric characters to the form.
1416
     * A trim-filter is attached to the field.
1417
     *
1418
     * @param string $name The element name
1419
     * @param string $label The label for the form-element
1420
     * @param bool $required Optional. Is the form-element required (default=true)
1421
     * @param array $attributes Optional. List of attributes for the form-element
1422
     */
1423
    public function addTextAlphanumeric(
1424
        $name,
1425
        $label,
1426
        $required = false,
1427
        $attributes = []
1428
    )
1429
    {
1430
        $attributes = array_merge(
1431
            $attributes,
1432
            [
1433
                'pattern' => '[a-zA-Z0-9ñÑ]+',
1434
                'title' => get_lang('Only letters (a-z) and numbers (0-9)'),
1435
            ]
1436
        );
1437
1438
        $this->addElement(
1439
            'text',
1440
            $name,
1441
            [
1442
                $label,
1443
                get_lang('Only letters (a-z) and numbers (0-9)'),
1444
            ],
1445
            $attributes
1446
        );
1447
1448
        $this->applyFilter($name, 'trim');
1449
1450
        if ($required) {
1451
            $this->addRule($name, get_lang('Required field'), 'required');
1452
        }
1453
1454
        $this->addRule(
1455
            $name,
1456
            get_lang('Only letters (a-z) and numbers (0-9)'),
1457
            'regex',
1458
            '/^[a-zA-Z0-9ÑÑ]+$/'
1459
        );
1460
    }
1461
1462
    /**
1463
     * @param string $name
1464
     * @param $label
1465
     * @param bool $required
1466
     * @param array $attributes
1467
     * @param bool $allowNegative
1468
     * @param int $minValue
1469
     * @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...
1470
     */
1471
    public function addFloat(
1472
        $name,
1473
        $label,
1474
        $required = false,
1475
        $attributes = [],
1476
        $allowNegative = false,
1477
        $minValue = null,
1478
        $maxValue = null
1479
    )
1480
    {
1481
        $this->addElement(
1482
            'FloatNumber',
1483
            $name,
1484
            $label,
1485
            $attributes
1486
        );
1487
1488
        $this->applyFilter($name, 'trim');
1489
1490
        if ($required) {
1491
            $this->addRule($name, get_lang('Required field'), 'required');
1492
        }
1493
1494
        // Rule allows "," and "."
1495
        /*$this->addRule(
1496
            $name,
1497
            get_lang('Only numbers'),
1498
            'regex',
1499
            '/(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)|(^-?\d\d*\,\d*$)|(^-?\,\d\d*$)/'
1500
        );*/
1501
1502
        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...
1503
            $this->addRule(
1504
                $name,
1505
                get_lang('Negative value'),
1506
                'compare',
1507
                '>=',
1508
                'server',
1509
                false,
1510
                false,
1511
                0
1512
            );
1513
        }
1514
1515
        if (!is_null($minValue)) {
1516
            $this->addRule(
1517
                $name,
1518
                get_lang('Under the minimum.'),
1519
                'compare',
1520
                '>=',
1521
                'server',
1522
                false,
1523
                false,
1524
                $minValue
1525
            );
1526
        }
1527
1528
        if (!is_null($maxValue)) {
1529
            $this->addRule(
1530
                $name,
1531
                get_lang('Value exceeds score.'),
1532
                'compare',
1533
                '<=',
1534
                'server',
1535
                false,
1536
                false,
1537
                $maxValue
1538
            );
1539
        }
1540
    }
1541
1542
    /**
1543
     * Adds a text field for letters and spaces to the form.
1544
     * A trim-filter is attached to the field.
1545
     *
1546
     * @param string $name The element name
1547
     * @param string $label The label for the form-element
1548
     * @param bool $required Optional. Is the form-element required (default=true)
1549
     * @param array $attributes Optional. List of attributes for the form-element
1550
     */
1551
    public function addTextLettersAndSpaces(
1552
        $name,
1553
        $label,
1554
        $required = false,
1555
        $attributes = []
1556
    )
1557
    {
1558
        $attributes = array_merge(
1559
            $attributes,
1560
            [
1561
                'pattern' => '[a-zA-ZñÑ\s]+',
1562
                'title' => get_lang('Only letters and spaces'),
1563
            ]
1564
        );
1565
1566
        $this->addElement(
1567
            'text',
1568
            $name,
1569
            [
1570
                $label,
1571
                get_lang('Only letters and spaces'),
1572
            ],
1573
            $attributes
1574
        );
1575
1576
        $this->applyFilter($name, 'trim');
1577
1578
        if ($required) {
1579
            $this->addRule($name, get_lang('Required field'), 'required');
1580
        }
1581
1582
        $this->addRule(
1583
            $name,
1584
            get_lang('Only letters and spaces'),
1585
            'regex',
1586
            '/^[a-zA-ZñÑ\s]+$/'
1587
        );
1588
    }
1589
1590
    /**
1591
     * Adds a text field for alphanumeric and spaces characters to the form.
1592
     * A trim-filter is attached to the field.
1593
     *
1594
     * @param string $name The element name
1595
     * @param string $label The label for the form-element
1596
     * @param bool $required Optional. Is the form-element required (default=true)
1597
     * @param array $attributes Optional. List of attributes for the form-element
1598
     */
1599
    public function addTextAlphanumericAndSpaces(
1600
        $name,
1601
        $label,
1602
        $required = false,
1603
        $attributes = []
1604
    )
1605
    {
1606
        $attributes = array_merge(
1607
            $attributes,
1608
            [
1609
                'pattern' => '[a-zA-Z0-9ñÑ\s]+',
1610
                'title' => get_lang('Only letters, numbers and spaces'),
1611
            ]
1612
        );
1613
1614
        $this->addElement(
1615
            'text',
1616
            $name,
1617
            [
1618
                $label,
1619
                get_lang('Only letters, numbers and spaces'),
1620
            ],
1621
            $attributes
1622
        );
1623
1624
        $this->applyFilter($name, 'trim');
1625
1626
        if ($required) {
1627
            $this->addRule($name, get_lang('Required field'), 'required');
1628
        }
1629
1630
        $this->addRule(
1631
            $name,
1632
            get_lang('Only letters, numbers and spaces'),
1633
            'regex',
1634
            '/^[a-zA-Z0-9ñÑ\s]+$/'
1635
        );
1636
    }
1637
1638
    /**
1639
     * @param string $url
1640
     * @param string $urlToRedirect after upload redirect to this page
1641
     */
1642
    public function addMultipleUpload(string $url, string $urlToRedirect = ''): void
1643
    {
1644
        $inputName = 'input_file_upload';
1645
        $this->addMultipleUploadJavascript($url, $inputName, $urlToRedirect);
1646
1647
        $this->addHtml('
1648
        <div class="description-upload text-sm text-gray-600 mb-3">
1649
            Click on the box below to select files from your computer (you can use CTRL + click to select multiple files), or drag and drop files directly over the box below.
1650
        </div>
1651
1652
        <span class="btn btn--success fileinput-button mb-3">
1653
            <i class="glyphicon glyphicon-plus"></i>
1654
            <span>Add files</span>
1655
            <input id="' . $inputName . '" type="file" name="files[]" multiple>
1656
        </span>
1657
1658
        <div id="dropzone" class="rounded-lg border border-dashed border-gray-300 bg-white hover:bg-gray-20 transition p-6 text-center cursor-pointer">
1659
            <div class="button-load text-gray-700">Click or drag and drop files here to upload</div>
1660
        </div>
1661
1662
        <div id="upload-status" class="hidden mt-4">
1663
            <div class="flex items-center gap-2 text-sm text-gray-600">
1664
                <svg class="animate-spin h-4 w-4 text-gray-500" viewBox="0 0 24 24" fill="none">
1665
                    <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
1666
                    <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4A4 4 0 008 12H4z"></path>
1667
                </svg>
1668
                <span id="upload-status-text">Uploading…</span>
1669
                <span id="upload-percent" class="ml-1 font-medium">0%</span>
1670
            </div>
1671
        </div>
1672
1673
        <div id="progress" class="progress w-full h-2 bg-gray-200 rounded mt-2 hidden">
1674
            <div class="progress-bar progress-bar-success h-2 w-0"></div>
1675
        </div>
1676
1677
        <div id="files" class="files mt-4"></div>
1678
    ');
1679
    }
1680
1681
    private function addMultipleUploadJavascript(string $url, string $inputName, string $urlToRedirect = ''): void
1682
    {
1683
        $icon = Display::getMdiIcon('text-box-outline', 'ch-tool-icon', null, ICON_SIZE_SMALL);
1684
1685
        $this->addHtml("
1686
    <script>
1687
    $(function () {
1688
        'use strict';
1689
1690
        // Prevent default form submit
1691
        $('#" . $this->getAttribute('id') . "').on('submit', function() { return false; });
1692
1693
        // Click-to-open input
1694
        $('#dropzone').on('click', function() { $('#" . $inputName . "').click(); });
1695
1696
        var url = '" . $url . "';
1697
1698
        // Optional manual upload button (kept for compatibility)
1699
        var uploadButton = $('<button/>')
1700
            .addClass('btn btn--primary')
1701
            .prop('disabled', true)
1702
            .text('Uploading…')
1703
            .on('click', function () {
1704
                var \$this = $(this), data = \$this.data();
1705
                \$this
1706
                    .off('click')
1707
                    .text('Cancel')
1708
                    .on('click', function () {
1709
                        \$this.remove();
1710
                        data.abort();
1711
                    });
1712
                data.submit().always(function () { \$this.remove(); });
1713
            });
1714
1715
        var \$input = $('#" . $inputName . "');
1716
1717
        function showUploadingUI() {
1718
            $('#upload-status').removeClass('hidden');
1719
            $('#progress').removeClass('hidden');
1720
            $('#upload-status-text').text('Uploading…');
1721
        }
1722
1723
        function setProgress(pct) {
1724
            $('#progress .progress-bar').css('width', pct + '%');
1725
            $('#upload-percent').text(pct + '%');
1726
        }
1727
1728
        function finishUploadingUI(success) {
1729
            $('#upload-status-text').text(success ? 'Upload complete' : 'Upload failed');
1730
            if (success) {
1731
                setProgress(100);
1732
            }
1733
            setTimeout(function () {
1734
                $('#upload-status').addClass('hidden');
1735
                $('#progress').addClass('hidden');
1736
                setProgress(0);
1737
            }, 1500);
1738
        }
1739
1740
        \$input.fileupload({
1741
            url: url,
1742
            dataType: 'json',
1743
            disableImageResize: /Android(?!.*Chrome)|Opera/.test(window.navigator.userAgent),
1744
            previewMaxWidth: 300,
1745
            previewMaxHeight: 169,
1746
            previewCrop: true,
1747
            dropzone: $('#dropzone')
1748
        })
1749
        .on('fileuploadadd', function (e, data) {
1750
            setProgress(0);
1751
            showUploadingUI();
1752
            data.context = $('<div class=\"row\" />').appendTo('#files');
1753
            $.each(data.files, function (index, file) {
1754
                var node = $('<div class=\"col-sm-5 file_name truncate\">').text(file.name);
1755
                node.appendTo(data.context);
1756
            });
1757
        })
1758
        .on('fileuploadstart', function () {
1759
            showUploadingUI();
1760
        })
1761
        .on('fileuploadprocessalways', function (e, data) {
1762
            var index = data.index;
1763
            var file = data.files[index];
1764
            var node = $(data.context.children()[index]);
1765
            if (file.preview) {
1766
                data.context.prepend($('<div class=\"col-sm-4\">').html(file.preview));
1767
            } else {
1768
                data.context.prepend($('<div class=\"col-sm-4\">').html('" . $icon . "'));
1769
            }
1770
            if (index + 1 === data.files.length) {
1771
                data.context.find('button').text('Upload').prop('disabled', !!data.files.error);
1772
            }
1773
        })
1774
        .on('fileuploadprogressall', function (e, data) {
1775
            var progress = parseInt(data.loaded / data.total * 100, 10);
1776
            setProgress(progress);
1777
        })
1778
        .on('fileuploaddone', function (e, data) {
1779
            $.each(data.result.files, function (index, file) {
1780
                if (file.error) {
1781
                    var link = $('<div>').attr({class : 'panel-image'});
1782
                    $(data.context.children()[index]).parent().wrap(link);
1783
                    $(data.context.children()[index]).parent().find('.file_name').html(file.name);
1784
                    var message = $('<div class=\"col-sm-3\">').html(
1785
                        $('<span class=\"alert alert-danger\"/>').text(file.error)
1786
                    );
1787
                    $(data.context.children()[index]).parent().append(message);
1788
                    return;
1789
                }
1790
                if (file.url) {
1791
                    var link = $('<a>').attr({target: '_blank', class : 'panel-image'}).prop('href', file.url);
1792
                    $(data.context.children()[index]).parent().wrap(link);
1793
                }
1794
                $(data.context.children()[index]).parent().find('.file_name').html(file.name);
1795
                var message = $('<div class=\"col-sm-3\">').html(
1796
                    $('<span class=\"alert alert-success\"/>').text('File uploaded')
1797
                );
1798
                $(data.context.children()[index]).parent().append(message);
1799
            });
1800
            $('#dropzone').removeClass('hover');
1801
        })
1802
        .on('fileuploadstop', function () {
1803
            finishUploadingUI(true);
1804
        })
1805
        .on('fileuploadfail', function (e, data) {
1806
            $.each(data.files, function (index) {
1807
                var error = $('<div class=\"col-sm-3\">').html(
1808
                    $('<span class=\"alert alert-danger\"/>').text('The file upload has failed.')
1809
                );
1810
                $(data.context.children()[index]).parent().append(error);
1811
            });
1812
            $('#dropzone').removeClass('hover');
1813
            finishUploadingUI(false);
1814
        })
1815
        .prop('disabled', !$.support.fileInput)
1816
        .parent()
1817
        .addClass($.support.fileInput ? undefined : 'disabled');
1818
1819
        $('#dropzone').on('dragover', function () { $('#dropzone').addClass('hover'); });
1820
        $('#dropzone').on('dragleave', function () { $('#dropzone').removeClass('hover'); });
1821
1822
        // Hide the legacy button if desired
1823
        $('.fileinput-button').hide();
1824
    });
1825
    </script>");
1826
    }
1827
1828
1829
    /**
1830
     * @param string $elementName
1831
     * @param string $groupName if element is inside a group
1832
     *
1833
     * @throws Exception
1834
     */
1835
    public function addPasswordRule($elementName, $groupName = '')
1836
    {
1837
        if ('true' == api_get_setting('security.check_password')) {
1838
            $message = get_lang('this password  is too simple. Use a pass like this') . ': ' . api_generate_password();
1839
1840
            if (!empty($groupName)) {
1841
                $groupObj = $this->getElement($groupName);
1842
1843
                if ($groupObj instanceof HTML_QuickForm_group) {
1844
                    $elementName = $groupObj->getElementName($elementName);
1845
1846
                    if (false === $elementName) {
1847
                        throw new Exception("The $groupName doesn't have the element $elementName");
1848
                    }
1849
1850
                    $this->_rules[$elementName][] = [
1851
                        'type' => 'callback',
1852
                        'format' => 'api_check_password',
1853
                        'message' => $message,
1854
                        'validation' => '',
1855
                        'reset' => false,
1856
                        'group' => $groupName,
1857
                    ];
1858
                }
1859
            } else {
1860
                $this->addRule(
1861
                    $elementName,
1862
                    $message,
1863
                    'callback',
1864
                    'api_check_password'
1865
                );
1866
            }
1867
        }
1868
    }
1869
1870
    /**
1871
     * Add an element with user ID and avatar to the form.
1872
     * It needs a Chamilo\CoreBundle\Entity\User as value. The exported value is the Chamilo\CoreBundle\Entity\User ID.
1873
     *
1874
     * @param string $name
1875
     * @param string $label
1876
     * @param string $imageSize Optional. Small, medium or large image
1877
     * @param string $subtitle Optional. The subtitle for the field
1878
     *
1879
     * @return \UserAvatar
1880
     * @see \UserAvatar
1881
     *
1882
     */
1883
    public function addUserAvatar($name, $label, $imageSize = 'small', $subtitle = '')
1884
    {
1885
        return $this->addElement('UserAvatar', $name, $label, ['image_size' => $imageSize, 'sub_title' => $subtitle]);
1886
    }
1887
1888
    /**
1889
     * @param array $typeList
1890
     */
1891
    public function addEmailTemplate($typeList)
1892
    {
1893
        $mailManager = new MailTemplateManager();
1894
        foreach ($typeList as $type) {
1895
            $list = $mailManager->get_all(
1896
                ['where' => ['type = ? AND url_id = ?' => [$type, api_get_current_access_url_id()]]]
1897
            );
1898
1899
            $options = [get_lang('Select')];
1900
            $name = $type;
1901
            $defaultId = '';
1902
            foreach ($list as $item) {
1903
                $options[$item['id']] = $item['name'];
1904
                $name = $item['name'];
1905
                if (empty($defaultId)) {
1906
                    $defaultId = 1 == $item['default_template'] ? $item['id'] : '';
1907
                }
1908
            }
1909
1910
            $url = api_get_path(WEB_AJAX_PATH) . 'mail.ajax.php?a=select_option';
1911
            $typeNoDots = 'email_template_option_' . str_replace('.tpl', '', $type);
1912
            $this->addSelect(
1913
                'email_template_option[' . $type . ']',
1914
                $name,
1915
                $options,
1916
                ['id' => $typeNoDots]
1917
            );
1918
1919
            $templateNoDots = 'email_template_' . str_replace('.tpl', '', $type);
1920
            $templateNoDotsBlock = 'email_template_block_' . str_replace('.tpl', '', $type);
1921
            $this->addHtml('<div id="' . $templateNoDotsBlock . '" style="display:none">');
1922
            $this->addTextarea(
1923
                $templateNoDots,
1924
                get_lang('Preview'),
1925
                ['disabled' => 'disabled ', 'id' => $templateNoDots, 'rows' => '5']
1926
            );
1927
            $this->addHtml('</div>');
1928
1929
            $this->addHtml("<script>
1930
            $(function() {
1931
                var defaultValue = '$defaultId';
1932
                $('#$typeNoDots').val(defaultValue);
1933
                //$('#$typeNoDots').selectpicker('render');
1934
                if (defaultValue != '') {
1935
                    var selected = $('#$typeNoDots option:selected').val();
1936
                    $.ajax({
1937
                        url: '$url' + '&id=' + selected+ '&template_name=$type',
1938
                        success: function (data) {
1939
                            $('#$templateNoDots').html(data);
1940
                            $('#$templateNoDotsBlock').show();
1941
                            return;
1942
                        },
1943
                    });
1944
                }
1945
1946
                $('#$typeNoDots').on('change', function(){
1947
                    var selected = $('#$typeNoDots option:selected').val();
1948
                    $.ajax({
1949
                        url: '$url' + '&id=' + selected,
1950
                        success: function (data) {
1951
                            $('#$templateNoDots').html(data);
1952
                            $('#$templateNoDotsBlock').show();
1953
                            return;
1954
                        },
1955
                    });
1956
                });
1957
            });
1958
            </script>");
1959
        }
1960
    }
1961
1962
    /**
1963
     * Add email rule for an element.
1964
     */
1965
    public function addEmailRule(string $element)
1966
    {
1967
        $this->addRule(
1968
            $element,
1969
            get_lang('The email address is not complete or contains some invalid characters'),
1970
            'email'
1971
        );
1972
    }
1973
}
1974
1975
/**
1976
 * Cleans HTML text filter.
1977
 *
1978
 * @param string $html HTML to clean
1979
 * @param int    $mode (optional)
1980
 *
1981
 * @return string The cleaned HTML
1982
 */
1983
function html_filter($html, $mode = NO_HTML)
1984
{
1985
    $allowed_tags = HTML_QuickForm_Rule_HTML::get_allowed_tags($mode);
1986
    $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

1986
    $cleaned_html = /** @scrutinizer ignore-call */ kses($html, $allowed_tags);
Loading history...
1987
1988
    return $cleaned_html;
1989
}
1990
1991
function html_filter_teacher($html)
1992
{
1993
    return html_filter($html, TEACHER_HTML);
1994
}
1995
1996
function html_filter_student($html)
1997
{
1998
    return html_filter($html, STUDENT_HTML);
1999
}
2000
2001
function html_filter_teacher_fullpage($html)
2002
{
2003
    return html_filter($html, TEACHER_HTML_FULLPAGE);
2004
}
2005
2006
function html_filter_student_fullpage($html)
2007
{
2008
    return html_filter($html, STUDENT_HTML_FULLPAGE);
2009
}
2010
2011
/**
2012
 * Cleans mobile phone number text.
2013
 *
2014
 * @param string $mobilePhoneNumber Mobile phone number to clean
2015
 *
2016
 * @return string The cleaned mobile phone number
2017
 */
2018
function mobile_phone_number_filter($mobilePhoneNumber)
2019
{
2020
    $mobilePhoneNumber = str_replace(['+', '(', ')'], '', $mobilePhoneNumber);
2021
2022
    return ltrim($mobilePhoneNumber, '0');
2023
}
2024
2025
/**
2026
 * Cleans JS from a URL.
2027
 *
2028
 * @param string $html URL to clean
2029
 * @param int    $mode (optional)
2030
 *
2031
 * @return string The cleaned URL
2032
 */
2033
function plain_url_filter($html, $mode = NO_HTML)
2034
{
2035
    $allowed_tags = HTML_QuickForm_Rule_HTML::get_allowed_tags($mode);
2036
    $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

2036
    $html = /** @scrutinizer ignore-call */ kses_no_null($html);
Loading history...
2037
    $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

2037
    $html = /** @scrutinizer ignore-call */ kses_js_entities($html);
Loading history...
2038
    $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

2038
    $allowed_html_fixed = /** @scrutinizer ignore-call */ kses_array_lc($allowed_tags);
Loading history...
2039
2040
    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

2040
    return /** @scrutinizer ignore-call */ kses_split($html, $allowed_html_fixed, ['http', 'https']);
Loading history...
2041
}
2042
2043
/**
2044
 * Prevent execution of event handlers in HTML elements.
2045
 */
2046
function attr_on_filter(string $html): string
2047
{
2048
    return RemoveOnAttributes::filter($html);
2049
}
2050
2051