Issues (2130)

main/inc/lib/formvalidator/FormValidator.class.php (1 issue)

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\HTMLPurifier\Filter\RemoveOnAttributes;
6
use Chamilo\UserBundle\Entity\User;
7
8
/**
9
 * Class FormValidator
10
 * create/manipulate/validate user input.
11
 */
12
class FormValidator extends HTML_QuickForm
13
{
14
    public const LAYOUT_HORIZONTAL = 'horizontal';
15
    public const LAYOUT_INLINE = 'inline';
16
    public const LAYOUT_BOX = 'box';
17
    public const LAYOUT_BOX_NO_LABEL = 'box-no-label';
18
    public const LAYOUT_GRID = 'grid';
19
20
    public const TIMEPICKER_INCREMENT_DEFAULT = 15;
21
22
    public $with_progress_bar = false;
23
    private $layout;
24
25
    /**
26
     * Constructor.
27
     *
28
     * @param string $name        Name of the form
29
     * @param string $method      (optional) Method ('post' (default) or 'get')
30
     * @param string $action      (optional) Action (default is $PHP_SELF)
31
     * @param string $target      (optional) Form's target defaults to '_self'
32
     * @param mixed  $attributes  (optional) Extra attributes for <form> tag
33
     * @param string $layout
34
     * @param bool   $trackSubmit (optional) Whether to track if the form was
35
     *                            submitted by adding a special hidden field (default = true)
36
     */
37
    public function __construct(
38
        $name,
39
        $method = 'post',
40
        $action = '',
41
        $target = '',
42
        $attributes = [],
43
        $layout = self::LAYOUT_HORIZONTAL,
44
        $trackSubmit = true
45
    ) {
46
        // Default form class.
47
        if (is_array($attributes) && !isset($attributes['class']) || empty($attributes)) {
48
            $attributes['class'] = 'form-horizontal';
49
        }
50
51
        if (isset($attributes['class']) && strpos($attributes['class'], 'form-search') !== false) {
52
            $layout = 'inline';
53
        }
54
55
        $this->setLayout($layout);
56
57
        // Form template
58
        $formTemplate = $this->getFormTemplate();
59
60
        switch ($layout) {
61
            case self::LAYOUT_HORIZONTAL:
62
                $attributes['class'] = 'form-horizontal';
63
                break;
64
            case self::LAYOUT_INLINE:
65
                $attributes['class'] = 'form-inline';
66
                break;
67
            case self::LAYOUT_BOX:
68
                $attributes['class'] = 'form-inline-box';
69
                break;
70
            case self::LAYOUT_GRID:
71
                $attributes['class'] = 'form-grid';
72
                $formTemplate = $this->getGridFormTemplate();
73
                break;
74
        }
75
76
        parent::__construct($name, $method, $action, $target, $attributes, $trackSubmit);
77
78
        // Modify the default templates
79
        $renderer = &$this->defaultRenderer();
80
81
        $renderer->setFormTemplate($formTemplate);
82
83
        // Element template
84
        if (isset($attributes['class']) && $attributes['class'] == 'form-inline') {
85
            $elementTemplate = ' {label}  {element} ';
86
            $renderer->setElementTemplate($elementTemplate);
87
        } elseif (isset($attributes['class']) && $attributes['class'] == 'form-search') {
88
            $elementTemplate = ' {label}  {element} ';
89
            $renderer->setElementTemplate($elementTemplate);
90
        } else {
91
            $renderer->setElementTemplate($this->getDefaultElementTemplate());
92
93
            // Display a gray div in the buttons
94
            $templateSimple = '<div class="form-actions">{label} {element}</div>';
95
            $renderer->setElementTemplate($templateSimple, 'submit_in_actions');
96
97
            //Display a gray div in the buttons + makes the button available when scrolling
98
            $templateBottom = '<div class="form-actions bottom_actions bg-form">{label} {element}</div>';
99
            $renderer->setElementTemplate($templateBottom, 'submit_fixed_in_bottom');
100
            $renderer->setElementTemplate($templateSimple, 'buttons_in_action');
101
102
            $templateSimpleRight = '<div class="form-actions"> <div class="pull-right">{label} {element}</div></div>';
103
            $renderer->setElementTemplate($templateSimpleRight, 'buttons_in_action_right');
104
        }
105
106
        //Set Header template
107
        $renderer->setHeaderTemplate('<legend>{header}</legend>');
108
109
        //Set required field template
110
        $this->setRequiredNote(
111
            '<span class="form_required">*</span> <small>'.get_lang('ThisFieldIsRequired').'</small>'
112
        );
113
114
        $noteTemplate = <<<EOT
115
	<div class="form-group">
116
		<div class="col-sm-offset-2 col-sm-10">{requiredNote}</div>
117
	</div>
118
EOT;
119
        $renderer->setRequiredNoteTemplate($noteTemplate);
120
    }
121
122
    /**
123
     * @return string
124
     */
125
    public function getFormTemplate()
126
    {
127
        return '<form{attributes}>
128
        <fieldset>
129
            {content}
130
        </fieldset>
131
        {hidden}
132
        </form>';
133
    }
134
135
    /**
136
     * @return string
137
     */
138
    public function getGridFormTemplate()
139
    {
140
        return '
141
        <style>
142
143
        </style>
144
        <form{attributes}>
145
            <div class="form_list">
146
                {content}
147
            </div>
148
        {hidden}
149
        </form>';
150
    }
151
152
    /**
153
     * @todo this function should be added in the element class
154
     *
155
     * @return string
156
     */
157
    public function getDefaultElementTemplate()
158
    {
159
        return '
160
            <div class="form-group {error_class}">
161
                <label {label-for} class="col-sm-2 control-label {extra_label_class}" >
162
                    <!-- BEGIN required --><span class="form_required">*</span><!-- END required -->
163
                    {label}
164
                </label>
165
                <div class="col-sm-8">
166
                    {icon}
167
                    {element}
168
                    <!-- BEGIN label_2 -->
169
                        <p class="help-block">{label_2}</p>
170
                    <!-- END label_2 -->
171
172
                    <!-- BEGIN error -->
173
                        <span class="help-inline help-block">{error}</span>
174
                    <!-- END error -->
175
                </div>
176
                <div class="col-sm-2">
177
                    <!-- BEGIN label_3 -->
178
                        {label_3}
179
                    <!-- END label_3 -->
180
                </div>
181
            </div>';
182
    }
183
184
    /**
185
     * @return string
186
     */
187
    public function getLayout()
188
    {
189
        return $this->layout;
190
    }
191
192
    /**
193
     * @param string $layout
194
     */
195
    public function setLayout($layout)
196
    {
197
        $this->layout = $layout;
198
    }
199
200
    /**
201
     * Adds a text field to the form.
202
     * A trim-filter is attached to the field.
203
     *
204
     * @param string|array $label      The label for the form-element
205
     * @param string       $name       The element name
206
     * @param bool         $required   (optional)    Is the form-element required (default=true)
207
     * @param array        $attributes (optional)    List of attributes for the form-element
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
        $this->applyFilter($name, 'attr_on_filter');
222
223
        if ($required) {
224
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
225
        }
226
227
        return $element;
228
    }
229
230
    /**
231
     * Adds a text field to the form to be used as internal url (URL without the domain part).
232
     * A trim-filter is attached to the field.
233
     *
234
     * @param string|array $label      The label for the form-element
235
     * @param string       $name       The element name
236
     * @param bool         $required   (optional)    Is the form-element required (default=true)
237
     * @param array        $attributes (optional)    List of attributes for the form-element
238
     *
239
     * @return HTML_QuickForm_text
240
     */
241
    public function addInternalUrl($name, $label, $required = true, $attributes = [], $createElement = false)
242
    {
243
        if ($createElement) {
244
            $element = $this->createElement('text', $name, $label, $attributes);
245
        } else {
246
            $element = $this->addElement('text', $name, $label, $attributes);
247
        }
248
249
        $this->applyFilter($name, 'trim');
250
        $this->applyFilter($name, 'plain_url_filter');
251
        $this->addRule($name, get_lang('InsertAValidUrl'), 'internal_url');
252
253
        if ($required) {
254
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
255
        }
256
257
        return $element;
258
    }
259
260
    /**
261
     * Add hidden course params.
262
     */
263
    public function addCourseHiddenParams()
264
    {
265
        $this->addHidden('cidReq', api_get_course_id());
266
        $this->addHidden('id_session', api_get_session_id());
267
    }
268
269
    /**
270
     * The "date_range_picker" element creates 2 hidden fields
271
     * "elementName" + "_start"  and "elementName" + "_end"
272
     * For example if the name is "range", you will have 2 new fields
273
     * when executing $form->getSubmitValues()
274
     * "range_start" and "range_end".
275
     *
276
     * @param string $name
277
     * @param string $label
278
     * @param bool   $required
279
     * @param array  $attributes
280
     */
281
    public function addDateRangePicker($name, $label, $required = true, $attributes = [])
282
    {
283
        $this->addElement('date_range_picker', $name, $label, $attributes);
284
        $this->addElement('hidden', $name.'_start');
285
        $this->addElement('hidden', $name.'_end');
286
287
        if ($required) {
288
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
289
        }
290
    }
291
292
    /**
293
     * @param string $name
294
     * @param string $label
295
     * @param array  $attributes
296
     *
297
     * @return DatePicker
298
     */
299
    public function addDatePicker($name, $label, $attributes = [])
300
    {
301
        return $this->addElement('DatePicker', $name, $label, $attributes);
302
    }
303
304
    /**
305
     * @param string $name
306
     * @param string $label
307
     * @param array  $attributes
308
     *
309
     * @return mixed
310
     */
311
    public function addSelectLanguage($name, $label, $options = [], $attributes = [])
312
    {
313
        return $this->addElement('SelectLanguage', $name, $label, $options, $attributes);
314
    }
315
316
    /**
317
     * @param string $name
318
     * @param string $label
319
     * @param array  $options
320
     * @param array  $attributes
321
     *
322
     * @throws Exception
323
     *
324
     * @return HTML_QuickForm_element
325
     */
326
    public function addSelectAjax($name, $label, $options = [], $attributes = [])
327
    {
328
        if (!isset($attributes['url'])) {
329
            throw new \Exception('select_ajax needs an URL');
330
        }
331
332
        return $this->addElement(
333
            'select_ajax',
334
            $name,
335
            $label,
336
            $options,
337
            $attributes
338
        );
339
    }
340
341
    /**
342
     * @param string       $name
343
     * @param string|array $label
344
     * @param array        $attributes
345
     *
346
     * @return DateTimePicker
347
     */
348
    public function addDateTimePicker($name, $label, $attributes = [])
349
    {
350
        return $this->addElement('DateTimePicker', $name, $label, $attributes);
351
    }
352
353
    /**
354
     * @param string       $name
355
     * @param string|array $label
356
     * @param array        $attributes
357
     *
358
     * @return DateTimeRangePicker
359
     */
360
    public function addDateTimeRangePicker($name, $label, $attributes = [])
361
    {
362
        return $this->addElement('DateTimeRangePicker', $name, $label, $attributes);
363
    }
364
365
    /**
366
     * @param string $name
367
     * @param string $value
368
     * @param array  $attributes
369
     */
370
    public function addHidden($name, $value, $attributes = [])
371
    {
372
        $this->addElement('hidden', $name, $value, $attributes);
373
    }
374
375
    /**
376
     * @param string       $name
377
     * @param string|array $label
378
     * @param array        $attributes
379
     * @param bool         $required
380
     *
381
     * @return HTML_QuickForm_textarea
382
     */
383
    public function addTextarea($name, $label, $attributes = [], $required = false)
384
    {
385
        $element = $this->addElement('textarea', $name, $label, $attributes);
386
387
        if ($required) {
388
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
389
        }
390
        $this->applyFilter($name, 'attr_on_filter');
391
392
        return $element;
393
    }
394
395
    /**
396
     * @param string $name
397
     * @param string $label
398
     * @param string $icon          font-awesome
399
     * @param string $style         default|primary|success|info|warning|danger|link
400
     * @param string $size          large|default|small|extra-small
401
     * @param string $class         Example plus is transformed to icon fa fa-plus
402
     * @param array  $attributes
403
     * @param bool   $createElement
404
     *
405
     * @return HTML_QuickForm_button
406
     */
407
    public function addButton(
408
        $name,
409
        $label,
410
        $icon = 'check',
411
        $style = 'default',
412
        $size = 'default',
413
        $class = null,
414
        $attributes = [],
415
        $createElement = false
416
    ) {
417
        if ($createElement) {
418
            return $this->createElement(
419
                'button',
420
                $name,
421
                $label,
422
                $icon,
423
                $style,
424
                $size,
425
                $class,
426
                $attributes
427
            );
428
        }
429
430
        return $this->addElement(
431
            'button',
432
            $name,
433
            $label,
434
            $icon,
435
            $style,
436
            $size,
437
            $class,
438
            $attributes
439
        );
440
    }
441
442
    /**
443
     * Returns a button with the primary color and a check mark.
444
     *
445
     * @param string $label         Text appearing on the button
446
     * @param string $name          Element name (for form treatment purposes)
447
     * @param bool   $createElement Whether to use the create or add method
448
     *
449
     * @return HTML_QuickForm_button
450
     */
451
    public function addButtonSave($label, $name = 'submit', $createElement = false)
452
    {
453
        return $this->addButton(
454
            $name,
455
            $label,
456
            'check',
457
            'primary',
458
            null,
459
            null,
460
            [],
461
            $createElement
462
        );
463
    }
464
465
    /**
466
     * Returns a cancel button.
467
     *
468
     * @param string $label         Text appearing on the button
469
     * @param string $name          Element name (for form treatment purposes)
470
     * @param bool   $createElement Whether to use the create or add method
471
     *
472
     * @return HTML_QuickForm_button
473
     */
474
    public function addButtonCancel($label, $name = 'submit', $createElement = false)
475
    {
476
        return $this->addButton(
477
            $name,
478
            $label,
479
            'times',
480
            'danger',
481
            null,
482
            null,
483
            [],
484
            $createElement
485
        );
486
    }
487
488
    /**
489
     * Returns a button with the primary color and a "plus" icon.
490
     *
491
     * @param string $label         Text appearing on the button
492
     * @param string $name          Element name (for form treatment purposes)
493
     * @param bool   $createElement Whether to use the create or add method
494
     * @param array  $attributes    Additional attributes
495
     *
496
     * @return HTML_QuickForm_button
497
     */
498
    public function addButtonCreate($label, $name = 'submit', $createElement = false, $attributes = [])
499
    {
500
        return $this->addButton(
501
            $name,
502
            $label,
503
            'plus',
504
            'primary',
505
            null,
506
            null,
507
            $attributes,
508
            $createElement
509
        );
510
    }
511
512
    /**
513
     * Returns a button with the primary color and a pencil icon.
514
     *
515
     * @param string $label         Text appearing on the button
516
     * @param string $name          Element name (for form treatment purposes)
517
     * @param bool   $createElement Whether to use the create or add method
518
     *
519
     * @return HTML_QuickForm_button
520
     */
521
    public function addButtonUpdate($label, $name = 'submit', $createElement = false)
522
    {
523
        return $this->addButton(
524
            $name,
525
            $label,
526
            'pencil',
527
            'primary',
528
            null,
529
            null,
530
            [],
531
            $createElement
532
        );
533
    }
534
535
    /**
536
     * Returns a button with the danger color and a trash icon.
537
     *
538
     * @param string $label         Text appearing on the button
539
     * @param string $name          Element name (for form treatment purposes)
540
     * @param bool   $createElement Whether to use the create or add method
541
     *
542
     * @return HTML_QuickForm_button
543
     */
544
    public function addButtonDelete($label, $name = 'submit', $createElement = false)
545
    {
546
        return $this->addButton(
547
            $name,
548
            $label,
549
            'trash',
550
            'danger',
551
            null,
552
            null,
553
            [],
554
            $createElement
555
        );
556
    }
557
558
    /**
559
     * Returns a move style button.
560
     *
561
     * @param string $label         Text appearing on the button
562
     * @param string $name          Element name (for form treatment purposes)
563
     * @param bool   $createElement Whether to use the create or add method
564
     *
565
     * @return HTML_QuickForm_button
566
     */
567
    public function addButtonMove($label, $name = 'submit', $createElement = false)
568
    {
569
        return $this->addButton(
570
            $name,
571
            $label,
572
            'arrow-circle-right',
573
            'primary',
574
            null,
575
            null,
576
            [],
577
            $createElement
578
        );
579
    }
580
581
    /**
582
     * Returns a button with the primary color and a paper-plane icon.
583
     *
584
     * @param string $label         Text appearing on the button
585
     * @param string $name          Element name (for form treatment purposes)
586
     * @param bool   $createElement Whether to use the create or add method
587
     * @param array  $attributes
588
     * @param string $size
589
     * @param string $class
590
     *
591
     * @return HTML_QuickForm_button
592
     */
593
    public function addButtonSend(
594
        $label,
595
        $name = 'submit',
596
        $createElement = false,
597
        $attributes = [],
598
        $size = 'default',
599
        $class = ''
600
    ) {
601
        return $this->addButton(
602
            $name,
603
            $label,
604
            'paper-plane',
605
            'primary',
606
            $size,
607
            $class,
608
            $attributes,
609
            $createElement
610
        );
611
    }
612
613
    /**
614
     * Returns a button with the default (grey?) color and a magnifier icon.
615
     *
616
     * @param string $label Text appearing on the button
617
     * @param string $name  Element name (for form treatment purposes)
618
     *
619
     * @return HTML_QuickForm_button
620
     */
621
    public function addButtonSearch($label = null, $name = 'submit')
622
    {
623
        if (empty($label)) {
624
            $label = get_lang('Search');
625
        }
626
627
        return $this->addButton($name, $label, 'search', 'default');
628
    }
629
630
    /**
631
     * Returns a button with the primary color and a right-pointing arrow icon.
632
     *
633
     * @param string $label      Text appearing on the button
634
     * @param string $name       Element name (for form treatment purposes)
635
     * @param array  $attributes Additional attributes
636
     *
637
     * @return HTML_QuickForm_button
638
     */
639
    public function addButtonNext($label, $name = 'submit', $attributes = [])
640
    {
641
        return $this->addButton(
642
            $name,
643
            $label,
644
            'arrow-right',
645
            'primary',
646
            null,
647
            null,
648
            $attributes
649
        );
650
    }
651
652
    /**
653
     * Returns a button with the primary color and a check mark icon.
654
     *
655
     * @param string $label         Text appearing on the button
656
     * @param string $name          Element name (for form treatment purposes)
657
     * @param bool   $createElement Whether to use the create or add method
658
     *
659
     * @return HTML_QuickForm_button
660
     */
661
    public function addButtonImport($label, $name = 'submit', $createElement = false)
662
    {
663
        return $this->addButton(
664
            $name,
665
            $label,
666
            'check',
667
            'primary',
668
            null,
669
            null,
670
            [],
671
            $createElement
672
        );
673
    }
674
675
    /**
676
     * Returns a button with the primary color and a check-mark icon.
677
     *
678
     * @param string $label         Text appearing on the button
679
     * @param string $name          Element name (for form treatment purposes)
680
     * @param bool   $createElement Whether to use the create or add method
681
     *
682
     * @return HTML_QuickForm_button
683
     */
684
    public function addButtonExport($label, $name = 'submit', $createElement = false)
685
    {
686
        return $this->addButton(
687
            $name,
688
            $label,
689
            'check',
690
            'primary',
691
            null,
692
            null,
693
            [],
694
            $createElement
695
        );
696
    }
697
698
    /**
699
     * Shortcut to filter button.
700
     *
701
     * @param string $label         Text appearing on the button
702
     * @param string $name          Element name (for form treatment purposes)
703
     * @param bool   $createElement Whether to use the create or add method
704
     *
705
     * @return HTML_QuickForm_button
706
     */
707
    public function addButtonFilter($label, $name = 'submit', $createElement = false)
708
    {
709
        return $this->addButton(
710
            $name,
711
            $label,
712
            'filter',
713
            'primary',
714
            null,
715
            null,
716
            [],
717
            $createElement
718
        );
719
    }
720
721
    /**
722
     * Shortcut to reset button.
723
     *
724
     * @param string $label         Text appearing on the button
725
     * @param string $name          Element name (for form treatment purposes)
726
     * @param bool   $createElement Whether to use the create or add method
727
     *
728
     * @return HTML_QuickForm_button
729
     */
730
    public function addButtonReset($label, $name = 'reset', $createElement = false)
731
    {
732
        $icon = 'eraser';
733
        $style = 'default';
734
        $size = 'default';
735
        $class = null;
736
        $attributes = [];
737
738
        if ($createElement) {
739
            return $this->createElement(
740
                'reset',
741
                $name,
742
                $label,
743
                $icon,
744
                $style,
745
                $size,
746
                $class,
747
                $attributes
748
            );
749
        }
750
751
        return $this->addElement(
752
            'reset',
753
            $name,
754
            $label,
755
            $icon,
756
            $style,
757
            $size,
758
            $class,
759
            $attributes
760
        );
761
    }
762
763
    /**
764
     * Returns a button with the primary color and an upload icon.
765
     *
766
     * @param string $label         Text appearing on the button
767
     * @param string $name          Element name (for form treatment purposes)
768
     * @param bool   $createElement Whether to use the create or add method
769
     *
770
     * @return HTML_QuickForm_button
771
     */
772
    public function addButtonUpload($label, $name = 'submit', $createElement = false)
773
    {
774
        return $this->addButton(
775
            $name,
776
            $label,
777
            'upload',
778
            'primary',
779
            null,
780
            null,
781
            [],
782
            $createElement
783
        );
784
    }
785
786
    /**
787
     * Returns a button with the primary color and a download icon.
788
     *
789
     * @param string $label         Text appearing on the button
790
     * @param string $name          Element name (for form treatment purposes)
791
     * @param bool   $createElement Whether to use the create or add method
792
     *
793
     * @return HTML_QuickForm_button
794
     */
795
    public function addButtonDownload($label, $name = 'submit', $createElement = false)
796
    {
797
        return $this->addButton(
798
            $name,
799
            $label,
800
            'download',
801
            'primary',
802
            null,
803
            null,
804
            [],
805
            $createElement
806
        );
807
    }
808
809
    /**
810
     * Returns a button with the primary color and a magnifier icon.
811
     *
812
     * @param string $label         Text appearing on the button
813
     * @param string $name          Element name (for form treatment purposes)
814
     * @param bool   $createElement Whether to use the create or add method
815
     *
816
     * @return HTML_QuickForm_button
817
     */
818
    public function addButtonPreview($label, $name = 'submit', $createElement = false)
819
    {
820
        return $this->addButton(
821
            $name,
822
            $label,
823
            'search',
824
            'primary',
825
            null,
826
            null,
827
            [],
828
            $createElement
829
        );
830
    }
831
832
    /**
833
     * Returns a button with the primary color and a copy (double sheet) icon.
834
     *
835
     * @param string $label         Text appearing on the button
836
     * @param string $name          Element name (for form treatment purposes)
837
     * @param bool   $createElement Whether to use the create or add method
838
     *
839
     * @return HTML_QuickForm_button
840
     */
841
    public function addButtonCopy($label, $name = 'submit', $createElement = false)
842
    {
843
        return $this->addButton(
844
            $name,
845
            $label,
846
            'copy',
847
            'primary',
848
            null,
849
            null,
850
            [],
851
            $createElement
852
        );
853
    }
854
855
    /**
856
     * @param string $name
857
     * @param string $label
858
     * @param string $text
859
     * @param array  $attributes
860
     *
861
     * @return HTML_QuickForm_checkbox
862
     */
863
    public function addCheckBox($name, $label, $text = '', $attributes = [])
864
    {
865
        return $this->addElement('checkbox', $name, $label, $text, $attributes);
866
    }
867
868
    /**
869
     * @param string $name
870
     * @param string $label
871
     * @param array  $options
872
     * @param array  $attributes
873
     *
874
     * @return HTML_QuickForm_group
875
     */
876
    public function addCheckBoxGroup($name, $label, $options = [], $attributes = [])
877
    {
878
        $group = [];
879
        foreach ($options as $value => $text) {
880
            $attributes['value'] = $value;
881
            $group[] = $this->createElement(
882
                'checkbox',
883
                $value,
884
                null,
885
                $text,
886
                $attributes
887
            );
888
        }
889
890
        return $this->addGroup($group, $name, $label);
891
    }
892
893
    /**
894
     * @param string $name
895
     * @param string $label
896
     * @param array  $options
897
     * @param array  $attributes
898
     *
899
     * @return HTML_QuickForm_group
900
     */
901
    public function addRadio($name, $label, $options = [], $attributes = [])
902
    {
903
        $group = [];
904
        $counter = 1;
905
        foreach ($options as $key => $value) {
906
            $attributes['data-order'] = $counter;
907
            $group[] = $this->createElement('radio', null, null, $value, $key, $attributes);
908
            $counter++;
909
        }
910
911
        return $this->addGroup($group, $name, $label);
912
    }
913
914
    /**
915
     * @param string $name
916
     * @param mixed  $label      String, or array if form element with a comment
917
     * @param array  $options
918
     * @param array  $attributes
919
     *
920
     * @return HTML_QuickForm_select
921
     */
922
    public function addSelect($name, $label, $options = [], $attributes = [])
923
    {
924
        return $this->addElement('select', $name, $label, $options, $attributes);
925
    }
926
927
    /**
928
     * @param $name
929
     * @param $label
930
     * @param $collection
931
     * @param array  $attributes
932
     * @param bool   $addNoneOption
933
     * @param string $textCallable  set a function getStringValue() by default __toString()
934
     *
935
     * @return HTML_QuickForm_element
936
     */
937
    public function addSelectFromCollection(
938
        $name,
939
        $label,
940
        $collection,
941
        $attributes = [],
942
        $addNoneOption = false,
943
        $textCallable = ''
944
    ) {
945
        $options = [];
946
947
        if ($addNoneOption) {
948
            $options[0] = get_lang('None');
949
        }
950
951
        if (!empty($collection)) {
952
            foreach ($collection as $item) {
953
                $text = $item;
954
                if (!empty($textCallable)) {
955
                    $text = $item->$textCallable();
956
                }
957
                $options[$item->getId()] = $text;
958
            }
959
        }
960
961
        return $this->addElement('select', $name, $label, $options, $attributes);
962
    }
963
964
    /**
965
     * @param string $label
966
     * @param string $text
967
     * @param bool   $createElement
968
     *
969
     * @return HTML_QuickForm_Element
970
     */
971
    public function addLabel($label, $text, $createElement = false)
972
    {
973
        if ($createElement) {
974
            return $this->createElement(
975
                'label',
976
                $label,
977
                $text
978
            );
979
        }
980
981
        return $this->addElement('label', $label, $text);
982
    }
983
984
    /**
985
     * @param string $text
986
     */
987
    public function addHeader($text)
988
    {
989
        if (!empty($text)) {
990
            $this->addElement('header', $text);
991
        }
992
    }
993
994
    /**
995
     * @param string $name
996
     * @param string $label
997
     * @param array  $attributes
998
     *
999
     * @throws Exception if the file doesn't have an id
1000
     *
1001
     * @return HTML_QuickForm_file
1002
     */
1003
    public function addFile($name, $label, $attributes = [])
1004
    {
1005
        $element = $this->addElement('file', $name, $label, $attributes);
1006
        if (isset($attributes['crop_image'])) {
1007
            $id = $element->getAttribute('id');
1008
            if (empty($id)) {
1009
                throw new Exception('If you use the crop functionality the element must have an id');
1010
            }
1011
            $this->addHtml(
1012
                '
1013
                <div class="form-group" id="'.$id.'-form-group" style="display: none;">
1014
                    <div class="col-sm-offset-2 col-sm-8">
1015
                        <div id="'.$id.'_crop_image" class="cropCanvas thumbnail">
1016
                            <img id="'.$id.'_preview_image">
1017
                        </div>
1018
                        <button class="btn btn-primary" type="button" name="cropButton" id="'.$id.'_crop_button">
1019
                            <em class="fa fa-crop"></em> '.get_lang('CropYourPicture').'
1020
                        </button>
1021
                    </div>
1022
                </div>'
1023
            );
1024
            $this->addHidden($id.'_crop_result', '');
1025
            $this->addHidden($id.'_crop_image_base_64', '');
1026
        }
1027
1028
        return $element;
1029
    }
1030
1031
    /**
1032
     * @param string $snippet
1033
     */
1034
    public function addHtml($snippet)
1035
    {
1036
        if (empty($snippet)) {
1037
            return false;
1038
        }
1039
        $this->addElement('html', $snippet);
1040
1041
        return true;
1042
    }
1043
1044
    /**
1045
     * Draws a panel of options see the course_info/infocours.php page.
1046
     *
1047
     * @param string $name      internal name
1048
     * @param string $title     visible title
1049
     * @param array  $groupList list of group or elements
1050
     */
1051
    public function addPanelOption($name, $title, $groupList)
1052
    {
1053
        $this->addHtml('<div class="panel panel-default">');
1054
        $this->addHtml(
1055
            '
1056
            <div class="panel-heading" role="tab" id="heading-'.$name.'-settings">
1057
                <h4 class="panel-title">
1058
                    <a class="collapsed" role="button" data-toggle="collapse" data-parent="#accordion"
1059
                       href="#collapse-'.$name.'-settings" aria-expanded="false" aria-controls="collapse-'.$name.'-settings">
1060
        '
1061
        );
1062
        $this->addHtml($title);
1063
        $this->addHtml('</a></h4></div>');
1064
        $this->addHtml('<div id="collapse-'.$name.'-settings" class="panel-collapse collapse" role="tabpanel"
1065
             aria-labelledby="heading-'.$name.'-settings">
1066
            <div class="panel-body">
1067
        ');
1068
1069
        foreach ($groupList as $groupName => $group) {
1070
            // Add group array
1071
            if (!empty($groupName) && is_array($group)) {
1072
                $this->addGroup($group, '', $groupName);
1073
            }
1074
            // Add element
1075
            if ($group instanceof HTML_QuickForm_element) {
1076
                $this->addElement($group);
1077
            }
1078
        }
1079
1080
        $this->addHtml('</div></div>');
1081
        $this->addHtml('</div>');
1082
    }
1083
1084
    /**
1085
     * Adds a HTML-editor to the form.
1086
     *
1087
     * @param string       $name
1088
     * @param string|array $label    The label for the form-element
1089
     * @param bool         $required (optional) Is the form-element required (default=true)
1090
     * @param bool         $fullPage (optional) When it is true, the editor loads completed html code for a full page
1091
     * @param array        $config   (optional) Configuration settings for the online editor
1092
     */
1093
    public function addHtmlEditor(
1094
        $name,
1095
        $label,
1096
        $required = true,
1097
        $fullPage = false,
1098
        $config = []
1099
    ) {
1100
        $attributes = [];
1101
        $attributes['rows'] = isset($config['rows']) ? $config['rows'] : 15;
1102
        $attributes['cols'] = isset($config['cols']) ? $config['cols'] : 80;
1103
        $attributes['cols-size'] = isset($config['cols-size']) ? $config['cols-size'] : [];
1104
        $attributes['class'] = isset($config['class']) ? $config['class'] : [];
1105
        $attributes['id'] = isset($config['id']) ? $config['id'] : '';
1106
1107
        if (empty($attributes['id'])) {
1108
            $attributes['id'] = $name;
1109
        }
1110
1111
        $this->addElement('html_editor', $name, $label, $attributes, $config);
1112
        $this->applyFilter($name, 'trim');
1113
        $this->applyFilter($name, 'attr_on_filter');
1114
        if ($required) {
1115
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
1116
        }
1117
1118
        /** @var HtmlEditor $element */
1119
        $element = $this->getElement($name);
1120
        $config['style'] = isset($config['style']) ? $config['style'] : false;
1121
        if ($fullPage) {
1122
            $config['fullPage'] = true;
1123
            // Adds editor_content.css in ckEditor
1124
            $config['style'] = true;
1125
        }
1126
1127
        if ($element->editor) {
1128
            $element->editor->processConfig($config);
1129
        }
1130
    }
1131
1132
    /**
1133
     * Adds a Google Maps Geolocalization field to the form.
1134
     *
1135
     * @param      $name
1136
     * @param      $label
1137
     * @param bool $hideGeoLocalizationDetails
1138
     */
1139
    public function addGeoLocationMapField($name, $label, $dataValue, $hideGeoLocalizationDetails = false)
1140
    {
1141
        $gMapsPlugin = GoogleMapsPlugin::create();
1142
        $geolocalization = $gMapsPlugin->get('enable_api') === 'true';
1143
1144
        if ($geolocalization && $gMapsPlugin->javascriptIncluded === false) {
1145
            $gmapsApiKey = $gMapsPlugin->get('api_key');
1146
            $url = '//maps.googleapis.com/maps/api/js?key='.$gmapsApiKey;
1147
            $this->addHtml('<script type="text/javascript" src="'.$url.'" ></script>');
1148
            $gMapsPlugin->javascriptIncluded = true;
1149
        }
1150
1151
        $this->addElement(
1152
            'text',
1153
            $name,
1154
            $label,
1155
            ['id' => $name]
1156
        );
1157
1158
        $this->addHidden(
1159
            $name.'_coordinates',
1160
            '',
1161
            ['id' => $name.'_coordinates']
1162
        );
1163
1164
        $this->applyFilter($name, 'stripslashes');
1165
        $this->applyFilter($name, 'trim');
1166
1167
        $this->addHtml(Extrafield::getLocalizationJavascript($name, $dataValue));
1168
1169
        if ($hideGeoLocalizationDetails) {
1170
            $this->addHtml('<div style="display:none">');
1171
        }
1172
1173
        $this->addHtml(
1174
            Extrafield::getLocalizationInput($name, $label)
1175
        );
1176
1177
        if ($hideGeoLocalizationDetails) {
1178
            $this->addHtml('</div>');
1179
        }
1180
    }
1181
1182
    /**
1183
     * @param string $name
1184
     * @param string $label
1185
     *
1186
     * @return mixed
1187
     */
1188
    public function addButtonAdvancedSettings($name, $label = '')
1189
    {
1190
        $label = !empty($label) ? $label : get_lang('AdvancedParameters');
1191
1192
        return $this->addElement('advanced_settings', $name, $label);
1193
    }
1194
1195
    /**
1196
     * Adds a progress loading image to the form.
1197
     */
1198
    public function addProgress($delay = 2, $label = '')
1199
    {
1200
        if (empty($label)) {
1201
            $label = get_lang('PleaseStandBy');
1202
        }
1203
        $this->with_progress_bar = true;
1204
        $id = $this->getAttribute('id');
1205
1206
        $this->updateAttributes("onsubmit=\"javascript: addProgress('".$id."')\"");
1207
        $this->addHtml('<script language="javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/upload.js" type="text/javascript"></script>');
1208
    }
1209
1210
    /**
1211
     * This function has been created for avoiding changes directly within QuickForm class.
1212
     * When we use it, the element is threated as 'required' to be dealt during validation.
1213
     *
1214
     * @param array  $elements The array of elements
1215
     * @param string $message  The message displayed
1216
     */
1217
    public function add_multiple_required_rule($elements, $message)
1218
    {
1219
        $this->_required[] = $elements[0];
1220
        $this->addRule($elements, $message, 'multiple_required');
1221
    }
1222
1223
    /**
1224
     * Displays the form.
1225
     * If an element in the form didn't validate, an error message is showed
1226
     * asking the user to complete the form.
1227
     */
1228
    public function display()
1229
    {
1230
        echo $this->returnForm();
1231
    }
1232
1233
    /**
1234
     * Returns the HTML code of the form.
1235
     *
1236
     * @return string $return_value HTML code of the form
1237
     */
1238
    public function returnForm()
1239
    {
1240
        $returnValue = '';
1241
1242
        /** @var HTML_QuickForm_element $element */
1243
        foreach ($this->_elements as &$element) {
1244
            $element->setLayout($this->getLayout());
1245
            $elementError = parent::getElementError($element->getName());
1246
            if (!is_null($elementError)) {
1247
                $returnValue .= Display::return_message($elementError, 'warning').'<br />';
1248
                break;
1249
            }
1250
        }
1251
1252
        $returnValue .= parent::toHtml();
1253
        // Add div-element which is to hold the progress bar
1254
        $id = $this->getAttribute('id');
1255
        if (isset($this->with_progress_bar) && $this->with_progress_bar) {
1256
            // @todo improve UI
1257
            $returnValue .= '<br />
1258
            <div id="loading_div_'.$id.'" class="loading_div" style="display:none;margin-left:40%; margin-top:10px; height:50px;">
1259
                <div class="wobblebar-loader"></div>
1260
            </div>
1261
            ';
1262
        }
1263
1264
        return $returnValue;
1265
    }
1266
1267
    /**
1268
     * Returns the HTML code of the form.
1269
     * If an element in the form didn't validate, an error message is showed
1270
     * asking the user to complete the form.
1271
     *
1272
     * @return string $return_value HTML code of the form
1273
     *
1274
     * @author Patrick Cool <[email protected]>, Ghent University, august 2006
1275
     * @author Julio Montoya
1276
     *
1277
     * @deprecated use returnForm()
1278
     */
1279
    public function return_form()
1280
    {
1281
        return $this->returnForm();
1282
    }
1283
1284
    /**
1285
     * @return HTML_QuickForm_Renderer_Default
1286
     */
1287
    public static function getDefaultRenderer()
1288
    {
1289
        return
1290
            isset($GLOBALS['_HTML_QuickForm_default_renderer']) ?
1291
                $GLOBALS['_HTML_QuickForm_default_renderer'] : null;
1292
    }
1293
1294
    /**
1295
     * Adds a input of type url to the form.
1296
     *
1297
     * @param string $name       The label for the form-element
1298
     * @param string $label      The element name
1299
     * @param bool   $required   Optional. Is the form-element required (default=true)
1300
     * @param array  $attributes Optional. List of attributes for the form-element
1301
     */
1302
    public function addUrl($name, $label, $required = true, $attributes = [])
1303
    {
1304
        $this->addElement('url', $name, $label, $attributes);
1305
        $this->applyFilter($name, 'trim');
1306
1307
        $this->addRule($name, get_lang('InsertAValidUrl'), 'url');
1308
1309
        if ($required) {
1310
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
1311
        }
1312
    }
1313
1314
    /**
1315
     * Adds a text field for letters to the form.
1316
     * A trim-filter is attached to the field.
1317
     *
1318
     * @param string $name       The element name
1319
     * @param string $label      The label for the form-element
1320
     * @param bool   $required   Optional. Is the form-element required (default=true)
1321
     * @param array  $attributes Optional. List of attributes for the form-element
1322
     */
1323
    public function addTextLettersOnly(
1324
        $name,
1325
        $label,
1326
        $required = false,
1327
        $attributes = []
1328
    ) {
1329
        $attributes = array_merge(
1330
            $attributes,
1331
            [
1332
                'pattern' => '[a-zA-ZñÑ]+',
1333
                'title' => get_lang('OnlyLetters'),
1334
            ]
1335
        );
1336
1337
        $this->addElement(
1338
            'text',
1339
            $name,
1340
            [
1341
                $label,
1342
                get_lang('OnlyLetters'),
1343
            ],
1344
            $attributes
1345
        );
1346
1347
        $this->applyFilter($name, 'trim');
1348
1349
        if ($required) {
1350
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
1351
        }
1352
1353
        $this->addRule(
1354
            $name,
1355
            get_lang('OnlyLetters'),
1356
            'regex',
1357
            '/^[a-zA-ZñÑ]+$/'
1358
        );
1359
    }
1360
1361
    /**
1362
     * @param string $name
1363
     * @param string $label
1364
     * @param array  $attributes
1365
     * @param bool   $required
1366
     *
1367
     * @return HTML_QuickForm_element
1368
     */
1369
    public function addNumeric($name, $label, $attributes = [], $required = false)
1370
    {
1371
        $element = $this->addElement('Number', $name, $label, $attributes);
1372
1373
        if ($required) {
1374
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
1375
        }
1376
1377
        return $element;
1378
    }
1379
1380
    /**
1381
     * Adds a text field for alphanumeric characters to the form.
1382
     * A trim-filter is attached to the field.
1383
     *
1384
     * @param string $name       The element name
1385
     * @param string $label      The label for the form-element
1386
     * @param bool   $required   Optional. Is the form-element required (default=true)
1387
     * @param array  $attributes Optional. List of attributes for the form-element
1388
     */
1389
    public function addTextAlphanumeric(
1390
        $name,
1391
        $label,
1392
        $required = false,
1393
        $attributes = []
1394
    ) {
1395
        $attributes = array_merge(
1396
            $attributes,
1397
            [
1398
                'pattern' => '[a-zA-Z0-9ñÑ]+',
1399
                'title' => get_lang('OnlyLettersAndNumbers'),
1400
            ]
1401
        );
1402
1403
        $this->addElement(
1404
            'text',
1405
            $name,
1406
            [
1407
                $label,
1408
                get_lang('OnlyLettersAndNumbers'),
1409
            ],
1410
            $attributes
1411
        );
1412
1413
        $this->applyFilter($name, 'trim');
1414
1415
        if ($required) {
1416
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
1417
        }
1418
1419
        $this->addRule(
1420
            $name,
1421
            get_lang('OnlyLettersAndNumbers'),
1422
            'regex',
1423
            '/^[a-zA-Z0-9ÑÑ]+$/'
1424
        );
1425
    }
1426
1427
    /**
1428
     * @param string $name
1429
     * @param $label
1430
     * @param bool  $required
1431
     * @param array $attributes
1432
     * @param bool  $allowNegative
1433
     * @param int   $minValue
1434
     * @param null  $maxValue
1435
     */
1436
    public function addFloat(
1437
        $name,
1438
        $label,
1439
        $required = false,
1440
        $attributes = [],
1441
        $allowNegative = false,
1442
        $minValue = null,
1443
        $maxValue = null
1444
    ) {
1445
        $this->addElement(
1446
            'FloatNumber',
1447
            $name,
1448
            $label,
1449
            $attributes
1450
        );
1451
1452
        $this->applyFilter($name, 'trim');
1453
1454
        if ($required) {
1455
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
1456
        }
1457
1458
        // Rule allows "," and "."
1459
        /*$this->addRule(
1460
            $name,
1461
            get_lang('OnlyNumbers'),
1462
            'regex',
1463
            '/(^-?\d\d*\.\d*$)|(^-?\d\d*$)|(^-?\.\d\d*$)|(^-?\d\d*\,\d*$)|(^-?\,\d\d*$)/'
1464
        );*/
1465
1466
        if ($allowNegative == false) {
1467
            $this->addRule(
1468
                $name,
1469
                get_lang('NegativeValue'),
1470
                'compare',
1471
                '>=',
1472
                'server',
1473
                false,
1474
                false,
1475
                0
1476
            );
1477
        }
1478
1479
        if (!is_null($minValue)) {
1480
            $this->addRule(
1481
                $name,
1482
                get_lang('UnderMin'),
1483
                'compare',
1484
                '>=',
1485
                'server',
1486
                false,
1487
                false,
1488
                $minValue
1489
            );
1490
        }
1491
1492
        if (!is_null($maxValue)) {
1493
            $this->addRule(
1494
                $name,
1495
                get_lang('OverMax'),
1496
                'compare',
1497
                '<=',
1498
                'server',
1499
                false,
1500
                false,
1501
                $maxValue
1502
            );
1503
        }
1504
    }
1505
1506
    /**
1507
     * Adds a text field for letters and spaces to the form.
1508
     * A trim-filter is attached to the field.
1509
     *
1510
     * @param string $name       The element name
1511
     * @param string $label      The label for the form-element
1512
     * @param bool   $required   Optional. Is the form-element required (default=true)
1513
     * @param array  $attributes Optional. List of attributes for the form-element
1514
     */
1515
    public function addTextLettersAndSpaces(
1516
        $name,
1517
        $label,
1518
        $required = false,
1519
        $attributes = []
1520
    ) {
1521
        $attributes = array_merge(
1522
            $attributes,
1523
            [
1524
                'pattern' => '[a-zA-ZñÑ\s]+',
1525
                'title' => get_lang('OnlyLettersAndSpaces'),
1526
            ]
1527
        );
1528
1529
        $this->addElement(
1530
            'text',
1531
            $name,
1532
            [
1533
                $label,
1534
                get_lang('OnlyLettersAndSpaces'),
1535
            ],
1536
            $attributes
1537
        );
1538
1539
        $this->applyFilter($name, 'trim');
1540
1541
        if ($required) {
1542
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
1543
        }
1544
1545
        $this->addRule(
1546
            $name,
1547
            get_lang('OnlyLettersAndSpaces'),
1548
            'regex',
1549
            '/^[a-zA-ZñÑ\s]+$/'
1550
        );
1551
    }
1552
1553
    /**
1554
     * Adds a text field for alphanumeric and spaces characters to the form.
1555
     * A trim-filter is attached to the field.
1556
     *
1557
     * @param string $name       The element name
1558
     * @param string $label      The label for the form-element
1559
     * @param bool   $required   Optional. Is the form-element required (default=true)
1560
     * @param array  $attributes Optional. List of attributes for the form-element
1561
     */
1562
    public function addTextAlphanumericAndSpaces(
1563
        $name,
1564
        $label,
1565
        $required = false,
1566
        $attributes = []
1567
    ) {
1568
        $attributes = array_merge(
1569
            $attributes,
1570
            [
1571
                'pattern' => '[a-zA-Z0-9ñÑ\s]+',
1572
                'title' => get_lang('OnlyLettersAndNumbersAndSpaces'),
1573
            ]
1574
        );
1575
1576
        $this->addElement(
1577
            'text',
1578
            $name,
1579
            [
1580
                $label,
1581
                get_lang('OnlyLettersAndNumbersAndSpaces'),
1582
            ],
1583
            $attributes
1584
        );
1585
1586
        $this->applyFilter($name, 'trim');
1587
1588
        if ($required) {
1589
            $this->addRule($name, get_lang('ThisFieldIsRequired'), 'required');
1590
        }
1591
1592
        $this->addRule(
1593
            $name,
1594
            get_lang('OnlyLettersAndNumbersAndSpaces'),
1595
            'regex',
1596
            '/^[a-zA-Z0-9ñÑ\s]+$/'
1597
        );
1598
    }
1599
1600
    /**
1601
     * @param string $url
1602
     * @param string $urlToRedirect after upload redirect to this page
1603
     */
1604
    public function addMultipleUpload($url, $urlToRedirect = '')
1605
    {
1606
        $inputName = 'input_file_upload';
1607
        $this->addMultipleUploadJavascript($url, $inputName, $urlToRedirect);
1608
1609
        $this->addHtml('
1610
            <div class="description-upload">
1611
            '.get_lang('ClickToSelectOrDragAndDropMultipleFilesOnTheUploadField').'
1612
            </div>
1613
            <span class="btn btn-success fileinput-button">
1614
                <i class="glyphicon glyphicon-plus"></i>
1615
                <span>'.get_lang('AddFiles').'</span>
1616
                <!-- The file input field used as target for the file upload widget -->
1617
                <input id="'.$inputName.'" type="file" name="files[]" multiple>
1618
            </span>
1619
            <div id="dropzone">
1620
                <div class="button-load">
1621
                '.get_lang('UploadFiles').'
1622
                </div>
1623
            </div>
1624
            <br />
1625
            <!-- The global progress bar -->
1626
            <div id="progress" class="progress">
1627
                <div class="progress-bar progress-bar-success"></div>
1628
            </div>
1629
            <div id="files" class="files"></div>
1630
        ');
1631
    }
1632
1633
    /**
1634
     * @throws Exception
1635
     */
1636
    public function addNoSamePasswordRule(string $elementName, User $user)
1637
    {
1638
        $passwordRequirements = api_get_configuration_value('password_requirements');
1639
1640
        if (!empty($passwordRequirements) && $passwordRequirements['force_different_password']) {
1641
            $this->addRule(
1642
                $elementName,
1643
                get_lang('NewPasswordCannotBeSameAsCurrent'),
1644
                'no_same_current_password',
1645
                $user
1646
            );
1647
        }
1648
    }
1649
1650
    /**
1651
     * @param string $elementName
1652
     * @param string $groupName   if element is inside a group
1653
     *
1654
     * @throws Exception
1655
     */
1656
    public function addPasswordRule($elementName, $groupName = '')
1657
    {
1658
        // Constant defined in old config/profile.conf.php
1659
        if (CHECK_PASS_EASY_TO_FIND !== true) {
1660
            return;
1661
        }
1662
1663
        $message = get_lang('PassTooEasy').': '.api_generate_password();
1664
1665
        if (empty($groupName)) {
1666
            $this->addRule(
1667
                $elementName,
1668
                $message,
1669
                'callback',
1670
                'api_check_password'
1671
            );
1672
1673
            return;
1674
        }
1675
1676
        $groupObj = $this->getElement($groupName);
1677
1678
        if ($groupObj instanceof HTML_QuickForm_group) {
1679
            $elementName = $groupObj->getElementName($elementName);
1680
1681
            if ($elementName === false) {
1682
                throw new Exception("The $groupName doesn't have the element $elementName");
1683
            }
1684
1685
            $this->_rules[$elementName][] = [
1686
                'type' => 'callback',
1687
                'format' => 'api_check_password',
1688
                'message' => $message,
1689
                'validation' => '',
1690
                'reset' => false,
1691
                'group' => $groupName,
1692
            ];
1693
        }
1694
    }
1695
1696
    /**
1697
     * Add an element with user ID and avatar to the form.
1698
     * It needs a Chamilo\UserBundle\Entity\User as value. The exported value is the Chamilo\UserBundle\Entity\User ID.
1699
     *
1700
     * @see \UserAvatar
1701
     *
1702
     * @param string $name
1703
     * @param string $label
1704
     * @param string $imageSize Optional. Small, medium or large image
1705
     * @param string $subtitle  Optional. The subtitle for the field
1706
     *
1707
     * @return \UserAvatar
1708
     */
1709
    public function addUserAvatar($name, $label, $imageSize = 'small', $subtitle = '')
1710
    {
1711
        return $this->addElement('UserAvatar', $name, $label, ['image_size' => $imageSize, 'sub_title' => $subtitle]);
1712
    }
1713
1714
    public function addCaptcha()
1715
    {
1716
        $ajax = api_get_path(WEB_AJAX_PATH).'form.ajax.php?a=get_captcha';
1717
        $options = [
1718
            'width' => 220,
1719
            'height' => 90,
1720
            'callback' => $ajax.'&var='.basename(__FILE__, '.php'),
1721
            'sessionVar' => basename(__FILE__, '.php'),
1722
            'imageOptions' => [
1723
                'font_size' => 20,
1724
                'font_path' => api_get_path(SYS_FONTS_PATH).'opensans/',
1725
                'font_file' => 'OpenSans-Regular.ttf',
1726
                //'output' => 'gif'
1727
            ],
1728
        ];
1729
1730
        $captcha_question = $this->addElement(
1731
            'CAPTCHA_Image',
1732
            'captcha_question',
1733
            '',
1734
            $options
1735
        );
1736
        $this->addElement('static', null, null, get_lang('ClickOnTheImageForANewOne'));
1737
1738
        $this->addElement(
1739
            'text',
1740
            'captcha',
1741
            get_lang('EnterTheLettersYouSee'),
1742
            ['size' => 40]
1743
        );
1744
        $this->addRule(
1745
            'captcha',
1746
            get_lang('EnterTheCharactersYouReadInTheImage'),
1747
            'required',
1748
            null,
1749
            'client'
1750
        );
1751
        $this->addRule(
1752
            'captcha',
1753
            get_lang('TheTextYouEnteredDoesNotMatchThePicture'),
1754
            'CAPTCHA',
1755
            $captcha_question
1756
        );
1757
    }
1758
1759
    /**
1760
     * @param array $typeList
1761
     */
1762
    public function addEmailTemplate($typeList)
1763
    {
1764
        $mailManager = new MailTemplateManager();
1765
        foreach ($typeList as $type) {
1766
            $list = $mailManager->get_all(
1767
                ['where' => ['type = ? AND url_id = ?' => [$type, api_get_current_access_url_id()]]]
1768
            );
1769
1770
            $options = [get_lang('Select')];
1771
            $name = $type;
1772
            $defaultId = '';
1773
            foreach ($list as $item) {
1774
                $options[$item['id']] = $item['name'];
1775
                $name = $item['name'];
1776
                if (empty($defaultId)) {
1777
                    $defaultId = $item['default_template'] == 1 ? $item['id'] : '';
1778
                }
1779
            }
1780
1781
            $url = api_get_path(WEB_AJAX_PATH).'mail.ajax.php?a=select_option';
1782
            $typeNoDots = 'email_template_option_'.str_replace('.tpl', '', $type);
1783
            $this->addSelect(
1784
                'email_template_option['.$type.']',
1785
                $name,
1786
                $options,
1787
                ['id' => $typeNoDots]
1788
            );
1789
1790
            $templateNoDots = 'email_template_'.str_replace('.tpl', '', $type);
1791
            $templateNoDotsBlock = 'email_template_block_'.str_replace('.tpl', '', $type);
1792
            $this->addHtml('<div id="'.$templateNoDotsBlock.'" style="display:none">');
1793
            $this->addTextarea(
1794
                $templateNoDots,
1795
                get_lang('Preview'),
1796
                ['disabled' => 'disabled ', 'id' => $templateNoDots, 'rows' => '5']
1797
            );
1798
            $this->addHtml('</div>');
1799
1800
            $this->addHtml("<script>
1801
            $(function() {
1802
                var defaultValue = '$defaultId';
1803
                $('#$typeNoDots').val(defaultValue);
1804
                $('#$typeNoDots').selectpicker('render');
1805
                if (defaultValue != '') {
1806
                    var selected = $('#$typeNoDots option:selected').val();
1807
                    $.ajax({
1808
                        url: '$url' + '&id=' + selected+ '&template_name=$type',
1809
                        success: function (data) {
1810
                            $('#$templateNoDots').html(data);
1811
                            $('#$templateNoDotsBlock').show();
1812
                            return;
1813
                        },
1814
                    });
1815
                }
1816
1817
                $('#$typeNoDots').on('change', function(){
1818
                    var selected = $('#$typeNoDots option:selected').val();
1819
                    $.ajax({
1820
                        url: '$url' + '&id=' + selected,
1821
                        success: function (data) {
1822
                            $('#$templateNoDots').html(data);
1823
                            $('#$templateNoDotsBlock').show();
1824
                            return;
1825
                        },
1826
                    });
1827
                });
1828
            });
1829
            </script>");
1830
        }
1831
    }
1832
1833
    public static function getTimepickerIncrement(): int
1834
    {
1835
        $customIncrement = api_get_configuration_value('timepicker_increment');
1836
1837
        if (false !== $customIncrement) {
1838
            return (int) $customIncrement;
1839
        }
1840
1841
        return self::TIMEPICKER_INCREMENT_DEFAULT;
1842
    }
1843
1844
    /**
1845
     * @param string $url           page that will handle the upload
1846
     * @param string $inputName
1847
     * @param string $urlToRedirect
1848
     */
1849
    private function addMultipleUploadJavascript($url, $inputName, $urlToRedirect = '')
1850
    {
1851
        $target = '_blank';
1852
        if (!empty($_SESSION['oLP']->lti_launch_id)) {
1853
            $target = '_self';
1854
        }
1855
        $redirectCondition = '';
1856
        if (!empty($urlToRedirect)) {
1857
            $redirectCondition = "window.location.replace('$urlToRedirect'); ";
1858
        }
1859
        $maxFileSize = getIniMaxFileSizeInBytes();
1860
        $icon = Display::return_icon('file_txt.gif');
1861
        $errorUploadMessage = get_lang('FileSizeIsTooBig').' '.get_lang('MaxFileSize').' : '.getIniMaxFileSizeInBytes(true);
1862
        $this->addHtml("
1863
        <script>
1864
        $(function () {
1865
            'use strict';
1866
            $('#".$this->getAttribute('id')."').submit(function() {
1867
                return false;
1868
            });
1869
1870
            $('#dropzone').on('click', function() {
1871
                $('#".$inputName."').click();
1872
            });
1873
1874
            var url = '".$url."';
1875
            var uploadButton = $('<button/>')
1876
                .addClass('btn btn-primary')
1877
                .prop('disabled', true)
1878
                .text('".addslashes(get_lang('Loading'))."')
1879
                .on('click', function () {
1880
                    var \$this = $(this),
1881
                    data = \$this.data();
1882
                    \$this
1883
                        .off('click')
1884
                        .text('".addslashes(get_lang('Cancel'))."')
1885
                        .on('click', function () {
1886
                            \$this.remove();
1887
                            data.abort();
1888
                        });
1889
                    data.submit().always(function () {
1890
                        \$this.remove();
1891
                    });
1892
                });
1893
1894
            var maxFileSize = parseInt('".$maxFileSize."');
1895
            var counter = 0,
1896
                total = 0;
1897
            $('#".$inputName."').fileupload({
1898
                url: url,
1899
                dataType: 'json',
1900
                // Enable image resizing, except for Android and Opera,
1901
                // which actually support image resizing, but fail to
1902
                // send Blob objects via XHR requests:
1903
                disableImageResize: /Android(?!.*Chrome)|Opera/.test(window.navigator.userAgent),
1904
                previewMaxWidth: 300,
1905
                previewMaxHeight: 169,
1906
                previewCrop: true,
1907
                dropZone: $('#dropzone'),
1908
                maxChunkSize: 10000000, // 10 MB
1909
                sequentialUploads: true,
1910
            }).on('fileuploadchunksend', function (e, data) {
1911
                console.log('fileuploadchunkbeforesend');
1912
                console.log(data);
1913
                data.url = url + '&chunkAction=send';
1914
            }).on('fileuploadchunkdone', function (e, data) {
1915
                console.log('fileuploadchunkdone');
1916
                console.log(data);
1917
                if (data.uploadedBytes >= data.total) {
1918
                    data.url = url + '&chunkAction=done';
1919
                    data.submit();
1920
                }
1921
            }).on('fileuploadchunkfail', function (e, data) {
1922
                console.log('fileuploadchunkfail');
1923
                console.log(data);
1924
1925
            }).on('fileuploadadd', function (e, data) {
1926
                data.context = $('<div class=\"row\" />').appendTo('#files');
1927
                var errs = [];
1928
                $.each(data.files, function (index, file) {
1929
                    // check size
1930
                    if (maxFileSize > 0 && data.files[index]['size'] > maxFileSize) {
1931
                        errs.push(\"".$errorUploadMessage."\");
1932
                    } else {
1933
                        // array for all errors
1934
                        var node = $('<div class=\"col-sm-5 file_name\">').text(file.name);
1935
                        node.appendTo(data.context);
1936
                        var iconLoading = $('<div class=\"col-sm-3\">').html(
1937
                            $('<span id=\"image-loading'+index+'\"/>').html('".Display::return_icon('loading1.gif', get_lang('Uploading'), [], ICON_SIZE_MEDIUM)."')
1938
                        );
1939
                        $(data.context.children()[index]).parent().append(iconLoading);
1940
                        total++;
1941
                    }
1942
                });
1943
1944
                // Output errors or submit data
1945
                if (errs.length > 0) {
1946
                    alert(\"".get_lang('AnErrorOccured')."\\n\" + errs.join(' '));
1947
                    return false;
1948
                } else {
1949
                    data.submit();
1950
                }
1951
1952
            }).on('fileuploadprocessalways', function (e, data) {
1953
                var index = data.index,
1954
                    file = data.files[index],
1955
                    node = $(data.context.children()[index]);
1956
1957
                if (maxFileSize > 0 && data.files[index]['size'] > maxFileSize) {
1958
                    return false;
1959
                }
1960
                if (file.preview) {
1961
                    data.context.prepend($('<div class=\"col-sm-4\">').html(file.preview));
1962
                } else {
1963
                    data.context.prepend($('<div class=\"col-sm-4\">').html('".$icon."'));
1964
                }
1965
                if (index + 1 === data.files.length) {
1966
                    data.context.find('button')
1967
                        .text('Upload')
1968
                        .prop('disabled', !!data.files.error);
1969
                }
1970
            }).on('fileuploadprogressall', function (e, data) {
1971
                var progress = parseInt(data.loaded / data.total * 100, 10) - 2;
1972
                $('#progress .progress-bar').css(
1973
                    'width',
1974
                    progress + '%'
1975
                );
1976
                $('#progress .progress-bar').text(progress + '%');
1977
            }).on('fileuploaddone', function (e, data) {
1978
                $.each(data.result.files, function (index, file) {
1979
                    if (file.error) {
1980
                        var link = $('<div>')
1981
                            .attr({class : 'panel-image'})                            ;
1982
                        $(data.context.children()[index]).parent().wrap(link);
1983
                        // Update file name with new one from Chamilo
1984
                        $(data.context.children()[index]).parent().find('.file_name').html(file.name);
1985
                        var message = $('<div class=\"col-sm-3\">').html(
1986
                            $('<span class=\"message-image-danger\"/>').text(file.error)
1987
                        );
1988
                        $(data.context.children()[index]).parent().append(message);
1989
1990
                        return;
1991
                    }
1992
                    if (file.url) {
1993
                        var link = $('<a>')
1994
                            .attr({target: '".$target."', class : 'panel-image'})
1995
                            .prop('href', file.url);
1996
                        $(data.context.children()[index]).parent().wrap(link);
1997
                    }
1998
                    // Update file name with new one from Chamilo
1999
                    $(data.context.children()[index]).parent().find('.file_name').html(file.name);
2000
                    $('#image-loading'+index).remove();
2001
                    var message = $('<div class=\"col-sm-3\">').html(
2002
                        $('<span class=\"message-image-success\"/>').text('".addslashes(get_lang('UplUploadSucceeded'))."')
2003
                    );
2004
                    $(data.context.children()[index]).parent().append(message);
2005
                    counter++;
2006
                });
2007
                if (counter == total) {
2008
                    $('#progress .progress-bar').css('width', '100%');
2009
                    $('#progress .progress-bar').text('100%');
2010
                }
2011
                $('#dropzone').removeClass('hover');
2012
                ".$redirectCondition."
2013
            }).on('fileuploadfail', function (e, data) {
2014
                $.each(data.files, function (index) {
2015
                    var failedMessage = '".addslashes(get_lang('UplUploadFailed'))."';
2016
                    var error = $('<div class=\"col-sm-3\">').html(
2017
                        $('<span class=\"alert alert-danger\"/>').text(failedMessage)
2018
                    );
2019
                    $(data.context.children()[index]).parent().append(error);
2020
                });
2021
                $('#dropzone').removeClass('hover');
2022
            }).prop('disabled', !$.support.fileInput).parent().addClass($.support.fileInput ? undefined : 'disabled');
2023
2024
            $('#dropzone').on('dragover', function (e) {
2025
                // dragleave callback implementation
2026
                $('#dropzone').addClass('hover');
2027
            });
2028
2029
            $('#dropzone').on('dragleave', function (e) {
2030
                $('#dropzone').removeClass('hover');
2031
            });
2032
            $('.fileinput-button').hide();
2033
        });
2034
        </script>");
2035
    }
2036
}
2037
2038
/**
2039
 * Cleans HTML text filter.
2040
 *
2041
 * @param string $html HTML to clean
2042
 * @param int    $mode (optional)
2043
 *
2044
 * @return string The cleaned HTML
2045
 */
2046
function html_filter($html, $mode = NO_HTML)
2047
{
2048
    $allowed_tags = HTML_QuickForm_Rule_HTML::get_allowed_tags($mode);
2049
    $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

2049
    $cleaned_html = /** @scrutinizer ignore-call */ kses($html, $allowed_tags);
Loading history...
2050
2051
    return $cleaned_html;
2052
}
2053
2054
function html_filter_teacher($html)
2055
{
2056
    return html_filter($html, TEACHER_HTML);
2057
}
2058
2059
function html_filter_student($html)
2060
{
2061
    return html_filter($html, STUDENT_HTML);
2062
}
2063
2064
function html_filter_teacher_fullpage($html)
2065
{
2066
    return html_filter($html, TEACHER_HTML_FULLPAGE);
2067
}
2068
2069
function html_filter_student_fullpage($html)
2070
{
2071
    return html_filter($html, STUDENT_HTML_FULLPAGE);
2072
}
2073
2074
/**
2075
 * Cleans mobile phone number text.
2076
 *
2077
 * @param string $mobilePhoneNumber Mobile phone number to clean
2078
 *
2079
 * @return string The cleaned mobile phone number
2080
 */
2081
function mobile_phone_number_filter($mobilePhoneNumber)
2082
{
2083
    $mobilePhoneNumber = str_replace(['+', '(', ')'], '', $mobilePhoneNumber);
2084
2085
    return ltrim($mobilePhoneNumber, '0');
2086
}
2087
2088
/**
2089
 * Cleans JS from a URL.
2090
 *
2091
 * @param string $html URL to clean
2092
 * @param int    $mode (optional)
2093
 *
2094
 * @return string The cleaned URL
2095
 */
2096
function plain_url_filter($html, $mode = NO_HTML)
2097
{
2098
    $allowed_tags = HTML_QuickForm_Rule_HTML::get_allowed_tags($mode);
2099
    $html = kses_no_null($html);
2100
    $html = kses_js_entities($html);
2101
    $allowed_html_fixed = kses_array_lc($allowed_tags);
2102
2103
    return kses_split($html, $allowed_html_fixed, ['http', 'https']);
2104
}
2105
2106
/**
2107
 * Prevent execution of event handlers in HTML elements.
2108
 */
2109
function attr_on_filter(string $html): string
2110
{
2111
    return RemoveOnAttributes::filter($html);
2112
}
2113