Passed
Push — master ( 7f557f...8c9b69 )
by Yannick
07:12 queued 13s
created

Display::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Component\Utils\ActionIcon;
6
use Chamilo\CoreBundle\Component\Utils\ObjectIcon;
7
use Chamilo\CoreBundle\Component\Utils\StateIcon;
8
use Chamilo\CoreBundle\Component\Utils\ToolIcon;
9
use Chamilo\CoreBundle\Entity\ExtraField;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, ExtraField. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use Chamilo\CoreBundle\Entity\ExtraFieldValues;
11
use Chamilo\CoreBundle\Framework\Container;
12
use ChamiloSession as Session;
13
use Symfony\Component\HttpFoundation\Response;
14
15
/**
16
 * Class Display
17
 * Contains several public functions dealing with the display of
18
 * table data, messages, help topics, ...
19
 *
20
 * Include/require it in your code to use its public functionality.
21
 * There are also several display public functions in the main api library.
22
 *
23
 * All public functions static public functions inside a class called Display,
24
 * so you use them like this: e.g.
25
 * Display::return_message($message)
26
 */
27
class Display
28
{
29
    /** @var Template */
30
    public static $global_template;
31
    public static $preview_style = null;
32
    public static $legacyTemplate;
33
34
    public function __construct()
35
    {
36
    }
37
38
    /**
39
     * @return array
40
     */
41
    public static function toolList()
42
    {
43
        return [
44
            'group',
45
            'work',
46
            'glossary',
47
            'forum',
48
            'course_description',
49
            'gradebook',
50
            'attendance',
51
            'course_progress',
52
            'notebook',
53
        ];
54
    }
55
56
    /**
57
     * Displays the page header.
58
     *
59
     * @param string The name of the page (will be showed in the page title)
60
     * @param string Optional help file name
61
     * @param string $page_header
62
     */
63
    public static function display_header(
64
        $tool_name = '',
65
        $help = null,
66
        $page_header = null
67
    ) {
68
        global $interbreadcrumb;
69
        $interbreadcrumb[] = ['url' => '#', 'name' => $tool_name];
70
71
        ob_start();
72
73
        return true;
74
    }
75
76
    /**
77
     * Displays the reduced page header (without banner).
78
     */
79
    public static function display_reduced_header()
80
    {
81
        ob_start();
82
        self::$legacyTemplate = '@ChamiloCore/Layout/no_layout.html.twig';
83
84
        return true;
85
    }
86
87
    /**
88
     * Display no header.
89
     */
90
    public static function display_no_header()
91
    {
92
        global $tool_name, $show_learnpath;
93
        self::$global_template = new Template(
94
            $tool_name,
95
            false,
96
            false,
97
            $show_learnpath
98
        );
99
    }
100
101
    /**
102
     * Display the page footer.
103
     */
104
    public static function display_footer()
105
    {
106
        $contents = ob_get_contents();
107
        if (ob_get_length()) {
108
            ob_end_clean();
109
        }
110
        $tpl = '@ChamiloCore/Layout/layout_one_col.html.twig';
111
        if (!empty(self::$legacyTemplate)) {
112
            $tpl = self::$legacyTemplate;
113
        }
114
        $response = new Response();
115
        $params['content'] = $contents;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
116
        global $interbreadcrumb, $htmlHeadXtra;
117
118
        $courseInfo = api_get_course_info();
119
        if (!empty($courseInfo)) {
120
            $url = $courseInfo['course_public_url'];
121
            $sessionId = api_get_session_id();
122
            if (!empty($sessionId)) {
123
                $url .= '?sid='.$sessionId;
124
            }
125
126
            if (!empty($interbreadcrumb)) {
127
                array_unshift(
128
                    $interbreadcrumb,
129
                    ['name' => $courseInfo['title'], 'url' => $url]
130
                );
131
            }
132
        }
133
134
        if (empty($interbreadcrumb)) {
135
            $interbreadcrumb = [];
136
        } else {
137
            $interbreadcrumb = array_filter(
138
                $interbreadcrumb,
139
                function ($item) {
140
                    return isset($item['name']) && !empty($item['name']);
141
                }
142
            );
143
        }
144
145
        $params['legacy_javascript'] = $htmlHeadXtra;
146
        $params['legacy_breadcrumb'] = json_encode($interbreadcrumb);
147
148
        Template::setVueParams($params);
149
        $content = Container::getTwig()->render($tpl, $params);
150
        $response->setContent($content);
151
        $response->send();
152
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
153
    }
154
155
    /**
156
     * Display the page footer.
157
     */
158
    public static function display_reduced_footer()
159
    {
160
        return self::display_footer();
0 ignored issues
show
Bug introduced by
Are you sure the usage of self::display_footer() targeting Display::display_footer() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
161
    }
162
163
    /**
164
     * Displays the tool introduction of a tool.
165
     *
166
     * @author Patrick Cool <[email protected]>, Ghent University
167
     *
168
     * @param string $tool          these are the constants that are used for indicating the tools
169
     * @param array  $editor_config Optional configuration settings for the online editor.
170
     *                              return: $tool return a string array list with the "define" in main_api.lib
171
     *
172
     * @return string html code for adding an introduction
173
     */
174
    public static function display_introduction_section(
175
        $tool,
176
        $editor_config = null
177
    ) {
178
        // @todo replace introduction section with a vue page.
179
        return;
180
    }
181
182
    /**
183
     * @param string $tool
184
     * @param array  $editor_config
185
     */
186
    public static function return_introduction_section(
187
        $tool,
188
        $editor_config = null
189
    ) {
190
    }
191
192
    /**
193
     * Displays a table.
194
     *
195
     * @param array  $header          Titles for the table header
196
     *                                each item in this array can contain 3 values
197
     *                                - 1st element: the column title
198
     *                                - 2nd element: true or false (column sortable?)
199
     *                                - 3th element: additional attributes for
200
     *                                th-tag (eg for column-width)
201
     *                                - 4the element: additional attributes for the td-tags
202
     * @param array  $content         2D-array with the tables content
203
     * @param array  $sorting_options Keys are:
204
     *                                'column' = The column to use as sort-key
205
     *                                'direction' = SORT_ASC or SORT_DESC
206
     * @param array  $paging_options  Keys are:
207
     *                                'per_page_default' = items per page when switching from
208
     *                                full-    list to per-page-view
209
     *                                'per_page' = number of items to show per page
210
     *                                'page_nr' = The page to display
211
     * @param array  $query_vars      Additional variables to add in the query-string
212
     * @param array  $form_actions
213
     * @param string $style           The style that the table will show. You can set 'table' or 'grid'
214
     * @param string $tableName
215
     * @param string $tableId
216
     *
217
     * @author [email protected]
218
     */
219
    public static function display_sortable_table(
220
        $header,
221
        $content,
222
        $sorting_options = [],
223
        $paging_options = [],
224
        $query_vars = null,
225
        $form_actions = [],
226
        $style = 'table',
227
        $tableName = 'tablename',
228
        $tableId = ''
229
    ) {
230
        $column = $sorting_options['column'] ?? 0;
231
        $default_items_per_page = $paging_options['per_page'] ?? 20;
232
        $table = new SortableTableFromArray($content, $column, $default_items_per_page, $tableName, null, $tableId);
233
        if (is_array($query_vars)) {
234
            $table->set_additional_parameters($query_vars);
235
        }
236
        if ('table' === $style) {
237
            if (is_array($header) && count($header) > 0) {
238
                foreach ($header as $index => $header_item) {
239
                    $table->set_header(
240
                        $index,
241
                        isset($header_item[0]) ? $header_item[0] : null,
242
                        isset($header_item[1]) ? $header_item[1] : null,
243
                        isset($header_item[2]) ? $header_item[2] : null,
244
                        isset($header_item[3]) ? $header_item[3] : null
245
                    );
246
                }
247
            }
248
            $table->set_form_actions($form_actions);
249
            $table->display();
250
        } else {
251
            $table->display_grid();
252
        }
253
    }
254
255
    /**
256
     * Returns an HTML table with sortable column (through complete page refresh).
257
     *
258
     * @param array  $header
259
     * @param array  $content         Array of row arrays
260
     * @param array  $sorting_options
261
     * @param array  $paging_options
262
     * @param array  $query_vars
263
     * @param array  $form_actions
264
     * @param string $style
265
     *
266
     * @return string HTML string for array
267
     */
268
    public static function return_sortable_table(
269
        $header,
270
        $content,
271
        $sorting_options = [],
272
        $paging_options = [],
273
        $query_vars = null,
274
        $form_actions = [],
275
        $style = 'table'
276
    ) {
277
        ob_start();
278
        self::display_sortable_table(
279
            $header,
280
            $content,
281
            $sorting_options,
282
            $paging_options,
283
            $query_vars,
284
            $form_actions,
285
            $style
286
        );
287
        $content = ob_get_contents();
288
        ob_end_clean();
289
290
        return $content;
291
    }
292
293
    /**
294
     * Shows a nice grid.
295
     *
296
     * @param string grid name (important to create css)
297
     * @param array header content
298
     * @param array array with the information to show
299
     * @param array $paging_options Keys are:
300
     *                              'per_page_default' = items per page when switching from
301
     *                              full-    list to per-page-view
302
     *                              'per_page' = number of items to show per page
303
     *                              'page_nr' = The page to display
304
     *                              'hide_navigation' =  true to hide the navigation
305
     * @param array $query_vars     Additional variables to add in the query-string
306
     * @param mixed An array with bool values to know which columns show.
307
     * i.e: $visibility_options= array(true, false) we will only show the first column
308
     *                Can be also only a bool value. TRUE: show all columns, FALSE: show nothing
309
     */
310
    public static function display_sortable_grid(
311
        $name,
312
        $header,
313
        $content,
314
        $paging_options = [],
315
        $query_vars = null,
316
        $form_actions = [],
317
        $visibility_options = true,
318
        $sort_data = true,
319
        $grid_class = []
320
    ) {
321
        echo self::return_sortable_grid(
322
            $name,
323
            $header,
324
            $content,
325
            $paging_options,
326
            $query_vars,
327
            $form_actions,
328
            $visibility_options,
329
            $sort_data,
330
            $grid_class
331
        );
332
    }
333
334
    /**
335
     * Gets a nice grid in html string.
336
     *
337
     * @param string grid name (important to create css)
338
     * @param array header content
339
     * @param array array with the information to show
340
     * @param array $paging_options Keys are:
341
     *                              'per_page_default' = items per page when switching from
342
     *                              full-    list to per-page-view
343
     *                              'per_page' = number of items to show per page
344
     *                              'page_nr' = The page to display
345
     *                              'hide_navigation' =  true to hide the navigation
346
     * @param array $query_vars     Additional variables to add in the query-string
347
     * @param mixed An array with bool values to know which columns show. i.e:
348
     *  $visibility_options= array(true, false) we will only show the first column
349
     *    Can be also only a bool value. TRUE: show all columns, FALSE: show nothing
350
     * @param bool  true for sorting data or false otherwise
351
     * @param array grid classes
352
     *
353
     * @return string html grid
354
     */
355
    public static function return_sortable_grid(
356
        $name,
357
        $header,
358
        $content,
359
        $paging_options = [],
360
        $query_vars = null,
361
        $form_actions = [],
362
        $visibility_options = true,
363
        $sort_data = true,
364
        $grid_class = [],
365
        $elementCount = 0
366
    ) {
367
        $column = 0;
368
        $default_items_per_page = $paging_options['per_page'] ?? 20;
369
        $table = new SortableTableFromArray($content, $column, $default_items_per_page, $name);
370
        $table->total_number_of_items = (int) $elementCount;
371
        if (is_array($query_vars)) {
372
            $table->set_additional_parameters($query_vars);
373
        }
374
375
        return $table->display_simple_grid(
376
            $visibility_options,
377
            $paging_options['hide_navigation'],
378
            $default_items_per_page,
379
            $sort_data,
380
            $grid_class
381
        );
382
    }
383
384
    /**
385
     * Displays a table with a special configuration.
386
     *
387
     * @param array $header          Titles for the table header
388
     *                               each item in this array can contain 3 values
389
     *                               - 1st element: the column title
390
     *                               - 2nd element: true or false (column sortable?)
391
     *                               - 3th element: additional attributes for th-tag (eg for column-width)
392
     *                               - 4the element: additional attributes for the td-tags
393
     * @param array $content         2D-array with the tables content
394
     * @param array $sorting_options Keys are:
395
     *                               'column' = The column to use as sort-key
396
     *                               'direction' = SORT_ASC or SORT_DESC
397
     * @param array $paging_options  Keys are:
398
     *                               'per_page_default' = items per page when switching from full list to per-page-view
399
     *                               'per_page' = number of items to show per page
400
     *                               'page_nr' = The page to display
401
     * @param array $query_vars      Additional variables to add in the query-string
402
     * @param array $column_show     Array of binaries 1= show columns 0. hide a column
403
     * @param array $column_order    An array of integers that let us decide how the columns are going to be sort.
404
     *                               i.e:  $column_order=array('1''4','3','4'); The 2nd column will be order like the 4th column
405
     * @param array $form_actions    Set optional forms actions
406
     *
407
     * @author Julio Montoya
408
     */
409
    public static function display_sortable_config_table(
410
        $table_name,
411
        $header,
412
        $content,
413
        $sorting_options = [],
414
        $paging_options = [],
415
        $query_vars = null,
416
        $column_show = [],
417
        $column_order = [],
418
        $form_actions = []
419
    ) {
420
        $column = isset($sorting_options['column']) ? $sorting_options['column'] : 0;
421
        $default_items_per_page = isset($paging_options['per_page']) ? $paging_options['per_page'] : 20;
422
423
        $table = new SortableTableFromArrayConfig(
424
            $content,
425
            $column,
426
            $default_items_per_page,
427
            $table_name,
428
            $column_show,
429
            $column_order
430
        );
431
432
        if (is_array($query_vars)) {
433
            $table->set_additional_parameters($query_vars);
434
        }
435
        // Show or hide the columns header
436
        if (is_array($column_show)) {
437
            for ($i = 0; $i < count($column_show); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
438
                if (!empty($column_show[$i])) {
439
                    $val0 = isset($header[$i][0]) ? $header[$i][0] : null;
440
                    $val1 = isset($header[$i][1]) ? $header[$i][1] : null;
441
                    $val2 = isset($header[$i][2]) ? $header[$i][2] : null;
442
                    $val3 = isset($header[$i][3]) ? $header[$i][3] : null;
443
                    $table->set_header($i, $val0, $val1, $val2, $val3);
444
                }
445
            }
446
        }
447
        $table->set_form_actions($form_actions);
448
        $table->display();
449
    }
450
451
    /**
452
     * Returns a div html string with.
453
     *
454
     * @param string $message
455
     * @param string $type    Example: confirm, normal, warning, error
456
     * @param bool   $filter  Whether to XSS-filter or not
457
     *
458
     * @return string Message wrapped into an HTML div
459
     */
460
    public static function return_message($message, $type = 'normal', $filter = true)
461
    {
462
        if (empty($message)) {
463
            return '';
464
        }
465
466
        if ($filter) {
467
            $message = api_htmlentities(
468
                $message,
469
                ENT_QUOTES
470
            );
471
        }
472
473
        $class = '';
474
        switch ($type) {
475
            case 'warning':
476
                $class .= 'alert alert-warning';
477
                break;
478
            case 'error':
479
                $class .= 'alert alert-danger';
480
                break;
481
            case 'confirmation':
482
            case 'confirm':
483
            case 'success':
484
                $class .= 'alert alert-success';
485
                break;
486
            case 'normal':
487
            case 'info':
488
            default:
489
                $class .= 'alert alert-info';
490
        }
491
492
        return self::div($message, ['class' => $class]);
493
    }
494
495
    /**
496
     * Returns an encrypted mailto hyperlink.
497
     *
498
     * @param string  e-mail
0 ignored issues
show
Documentation Bug introduced by
The doc comment e-mail at position 0 could not be parsed: Unknown type name 'e-mail' at position 0 in e-mail.
Loading history...
499
     * @param string  clickable text
500
     * @param string  optional, class from stylesheet
501
     * @param bool $addExtraContent
502
     *
503
     * @return string encrypted mailto hyperlink
504
     */
505
    public static function encrypted_mailto_link(
506
        $email,
507
        $clickable_text = null,
508
        $style_class = '',
509
        $addExtraContent = false
510
    ) {
511
        if (is_null($clickable_text)) {
512
            $clickable_text = $email;
513
        }
514
515
        // "mailto:" already present?
516
        if ('mailto:' !== substr($email, 0, 7)) {
517
            $email = 'mailto:'.$email;
518
        }
519
520
        // Class (stylesheet) defined?
521
        if ('' !== $style_class) {
522
            $style_class = ' class="'.$style_class.'"';
523
        }
524
525
        // Encrypt email
526
        $hmail = '';
527
        for ($i = 0; $i < strlen($email); $i++) {
528
            $hmail .= '&#'.ord($email[$i]).';';
529
        }
530
531
        $value = ('true' === api_get_setting('profile.add_user_course_information_in_mailto'));
532
533
        if ($value) {
534
            if ('false' === api_get_setting('allow_email_editor')) {
535
                $hmail .= '?';
536
            }
537
538
            if (!api_is_anonymous()) {
539
                $hmail .= '&subject='.Security::remove_XSS(api_get_setting('siteName'));
0 ignored issues
show
Bug introduced by
Are you sure Security::remove_XSS(api_get_setting('siteName')) of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

539
                $hmail .= '&subject='./** @scrutinizer ignore-type */ Security::remove_XSS(api_get_setting('siteName'));
Loading history...
540
            }
541
            if ($addExtraContent) {
542
                $content = '';
543
                if (!api_is_anonymous()) {
544
                    $userInfo = api_get_user_info();
545
                    $content .= get_lang('User').': '.$userInfo['complete_name']."\n";
546
547
                    $courseInfo = api_get_course_info();
548
                    if (!empty($courseInfo)) {
549
                        $content .= get_lang('Course').': ';
550
                        $content .= $courseInfo['name'];
551
                        $sessionInfo = api_get_session_info(api_get_session_id());
552
                        if (!empty($sessionInfo)) {
553
                            $content .= ' '.$sessionInfo['name'].' <br />';
554
                        }
555
                    }
556
                }
557
                $hmail .= '&body='.rawurlencode($content);
558
            }
559
        }
560
561
        $hclickable_text = '';
562
        // Encrypt clickable text if @ is present
563
        if (strpos($clickable_text, '@')) {
564
            for ($i = 0; $i < strlen($clickable_text); $i++) {
565
                $hclickable_text .= '&#'.ord($clickable_text[$i]).';';
566
            }
567
        } else {
568
            $hclickable_text = @htmlspecialchars(
569
                $clickable_text,
570
                ENT_QUOTES,
571
                api_get_system_encoding()
572
            );
573
        }
574
        // Return encrypted mailto hyperlink
575
        return '<a href="'.$hmail.'"'.$style_class.' class="clickable_email_link">'.$hclickable_text.'</a>';
576
    }
577
578
    /**
579
     * Prints an <option>-list with all letters (A-Z).
580
     *
581
     * @todo This is English language specific implementation.
582
     * It should be adapted for the other languages.
583
     *
584
     * @return string
585
     */
586
    public static function get_alphabet_options($selectedLetter = '')
587
    {
588
        $result = '';
589
        for ($i = 65; $i <= 90; $i++) {
590
            $letter = chr($i);
591
            $result .= '<option value="'.$letter.'"';
592
            if ($selectedLetter == $letter) {
593
                $result .= ' selected="selected"';
594
            }
595
            $result .= '>'.$letter.'</option>';
596
        }
597
598
        return $result;
599
    }
600
601
    /**
602
     * Get the options withing a select box within the given values.
603
     *
604
     * @param int   Min value
605
     * @param int   Max value
606
     * @param int   Default value
607
     *
608
     * @return string HTML select options
609
     */
610
    public static function get_numeric_options($min, $max, $selected_num = 0)
611
    {
612
        $result = '';
613
        for ($i = $min; $i <= $max; $i++) {
614
            $result .= '<option value="'.$i.'"';
615
            if (is_int($selected_num)) {
616
                if ($selected_num == $i) {
617
                    $result .= ' selected="selected"';
618
                }
619
            }
620
            $result .= '>'.$i.'</option>';
621
        }
622
623
        return $result;
624
    }
625
626
    /**
627
     * Gets the path of an icon.
628
     *
629
     * @param string $icon
630
     * @param int    $size
631
     *
632
     * @return string
633
     */
634
    public static function returnIconPath($icon, $size = ICON_SIZE_SMALL)
635
    {
636
        return self::return_icon($icon, null, null, $size, null, true, false);
637
    }
638
639
    /**
640
     * This public function returns the htmlcode for an icon.
641
     *
642
     * @param string   The filename of the file (in the main/img/ folder
643
     * @param string   The alt text (probably a language variable)
644
     * @param array    Additional attributes (for instance height, width, onclick, ...)
645
     * @param int  The wanted width of the icon (to be looked for in the corresponding img/icons/ folder)
646
     *
647
     * @return string An HTML string of the right <img> tag
648
     *
649
     * @author Patrick Cool <[email protected]>, Ghent University 2006
650
     * @author Julio Montoya 2010 Function improved, adding image constants
651
     * @author Yannick Warnier 2011 Added size handler
652
     *
653
     * @version Feb 2011
654
     */
655
    public static function return_icon(
656
        string $image,
657
        ?string $alt_text = '',
658
        ?array $additional_attributes = [],
659
        ?int $size = ICON_SIZE_SMALL,
660
        ?bool $show_text = true,
661
        ?bool $return_only_path = false,
662
        ?bool $loadThemeIcon = true
663
    ) {
664
        $code_path = api_get_path(SYS_PUBLIC_PATH);
665
        $w_code_path = api_get_path(WEB_PUBLIC_PATH);
666
        // The following path is checked to see if the file exist. It's
667
        // important to use the public path (i.e. web/css/) rather than the
668
        // internal path (/app/Resource/public/css/) because the path used
669
        // in the end must be the public path
670
        $alternateCssPath = api_get_path(SYS_PUBLIC_PATH).'css/';
671
        $alternateWebCssPath = api_get_path(WEB_PUBLIC_PATH).'css/';
672
673
        // Avoid issues with illegal string offset for legacy calls to this
674
        // method with an empty string rather than null or an empty array
675
        if (empty($additional_attributes)) {
676
            $additional_attributes = [];
677
        }
678
679
        $image = trim($image);
680
681
        if (isset($size)) {
682
            $size = (int) $size;
683
        } else {
684
            $size = ICON_SIZE_SMALL;
685
        }
686
687
        $size_extra = $size.'/';
688
        $icon = $w_code_path.'img/'.$image;
689
        $theme = 'themes/chamilo/icons/';
690
691
        if ($loadThemeIcon) {
692
            // @todo with chamilo 2 code
693
            $theme = 'themes/'.api_get_visual_theme().'/icons/';
694
            if (is_file($alternateCssPath.$theme.$image)) {
695
                $icon = $alternateWebCssPath.$theme.$image;
696
            }
697
            // Checking the theme icons folder example: app/Resources/public/css/themes/chamilo/icons/XXX
698
            if (is_file($alternateCssPath.$theme.$size_extra.$image)) {
699
                $icon = $alternateWebCssPath.$theme.$size_extra.$image;
700
            } elseif (is_file($code_path.'img/icons/'.$size_extra.$image)) {
701
                //Checking the main/img/icons/XXX/ folder
702
                $icon = $w_code_path.'img/icons/'.$size_extra.$image;
703
            }
704
        } else {
705
            if (is_file($code_path.'img/icons/'.$size_extra.$image)) {
706
                // Checking the main/img/icons/XXX/ folder
707
                $icon = $w_code_path.'img/icons/'.$size_extra.$image;
708
            }
709
        }
710
711
        // Special code to enable SVG - refs #7359 - Needs more work
712
        // The code below does something else to "test out" SVG: for each icon,
713
        // it checks if there is an SVG version. If so, it uses it.
714
        // When moving this to production, the return_icon() calls should
715
        // ask for the SVG version directly
716
        $svgIcons = api_get_setting('icons_mode_svg');
717
        if ('true' == $svgIcons && false == $return_only_path) {
718
            $svgImage = substr($image, 0, -3).'svg';
719
            if (is_file($code_path.$theme.'svg/'.$svgImage)) {
720
                $icon = $w_code_path.$theme.'svg/'.$svgImage;
721
            } elseif (is_file($code_path.'img/icons/svg/'.$svgImage)) {
722
                $icon = $w_code_path.'img/icons/svg/'.$svgImage;
723
            }
724
725
            if (empty($additional_attributes['height'])) {
726
                $additional_attributes['height'] = $size;
727
            }
728
            if (empty($additional_attributes['width'])) {
729
                $additional_attributes['width'] = $size;
730
            }
731
        }
732
733
        if ($return_only_path) {
734
            return $icon;
735
        }
736
737
        $img = self::img($icon, $alt_text, $additional_attributes);
738
        if (SHOW_TEXT_NEAR_ICONS == true && !empty($alt_text)) {
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...
739
            if ($show_text) {
740
                $img = "$img $alt_text";
741
            }
742
        }
743
744
        return $img;
745
    }
746
747
    /**
748
     * Returns the htmlcode for an image.
749
     *
750
     * @param string $image_path            the filename of the file (in the main/img/ folder
751
     * @param string $alt_text              the alt text (probably a language variable)
752
     * @param array  $additional_attributes (for instance height, width, onclick, ...)
753
     * @param bool   $filterPath            Optional. Whether filter the image path. Default is true
754
     *
755
     * @return string
756
     *
757
     * @author Julio Montoya 2010
758
     */
759
    public static function img(
760
        $image_path,
761
        $alt_text = '',
762
        $additional_attributes = null,
763
        $filterPath = true
764
    ) {
765
        if (empty($image_path)) {
766
            return '';
767
        }
768
        // Sanitizing the parameter $image_path
769
        if ($filterPath) {
770
            $image_path = Security::filter_img_path($image_path);
771
        }
772
773
        // alt text = the image name if there is none provided (for XHTML compliance)
774
        if ('' == $alt_text) {
775
            $alt_text = basename($image_path);
776
        }
777
778
        if (empty($additional_attributes)) {
779
            $additional_attributes = [];
780
        }
781
782
        $additional_attributes['src'] = $image_path;
783
784
        if (empty($additional_attributes['alt'])) {
785
            $additional_attributes['alt'] = $alt_text;
786
        }
787
        if (empty($additional_attributes['title'])) {
788
            $additional_attributes['title'] = $alt_text;
789
        }
790
791
        return self::tag('img', '', $additional_attributes);
792
    }
793
794
    /**
795
     * Returns the htmlcode for a tag (h3, h1, div, a, button), etc.
796
     *
797
     * @param string $tag                   the tag name
798
     * @param string $content               the tag's content
799
     * @param array  $additional_attributes (for instance height, width, onclick, ...)
800
     *
801
     * @return string
802
     *
803
     * @author Julio Montoya 2010
804
     */
805
    public static function tag($tag, $content, $additional_attributes = [])
806
    {
807
        $attribute_list = '';
808
        // Managing the additional attributes
809
        if (!empty($additional_attributes) && is_array($additional_attributes)) {
810
            $attribute_list = '';
811
            foreach ($additional_attributes as $key => &$value) {
812
                $attribute_list .= $key.'="'.$value.'" ';
813
            }
814
        }
815
        //some tags don't have this </XXX>
816
        if (in_array($tag, ['img', 'input', 'br'])) {
817
            $return_value = '<'.$tag.' '.$attribute_list.' />';
818
        } else {
819
            $return_value = '<'.$tag.' '.$attribute_list.' >'.$content.'</'.$tag.'>';
820
        }
821
822
        return $return_value;
823
    }
824
825
    /**
826
     * Creates a URL anchor.
827
     *
828
     * @param string $name
829
     * @param string $url
830
     * @param array  $attributes
831
     *
832
     * @return string
833
     */
834
    public static function url($name, $url, $attributes = [])
835
    {
836
        if (!empty($url)) {
837
            $url = preg_replace('#&amp;#', '&', $url);
838
            $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
839
            $attributes['href'] = $url;
840
        }
841
842
        return self::tag('a', $name, $attributes);
843
    }
844
845
    /**
846
     * Creates a div tag.
847
     *
848
     * @param string $content
849
     * @param array  $attributes
850
     *
851
     * @return string
852
     */
853
    public static function div($content, $attributes = [])
854
    {
855
        return self::tag('div', $content, $attributes);
856
    }
857
858
    /**
859
     * Creates a span tag.
860
     */
861
    public static function span($content, $attributes = [])
862
    {
863
        return self::tag('span', $content, $attributes);
864
    }
865
866
    /**
867
     * Displays an HTML input tag.
868
     */
869
    public static function input($type, $name, $value, $attributes = [])
870
    {
871
        if (isset($type)) {
872
            $attributes['type'] = $type;
873
        }
874
        if (isset($name)) {
875
            $attributes['name'] = $name;
876
        }
877
        if (isset($value)) {
878
            $attributes['value'] = $value;
879
        }
880
881
        return self::tag('input', '', $attributes);
882
    }
883
884
    /**
885
     * Displays an HTML select tag.
886
     */
887
    public static function select(
888
        string $name,
889
        array $values,
890
        mixed $default = -1,
891
        array $extra_attributes = [],
892
        bool $show_blank_item = true,
893
        string $blank_item_text = ''
894
    ): string {
895
        $html = '';
896
        $extra = '';
897
        $default_id = 'id="'.$name.'" ';
898
        $extra_attributes = array_merge(
899
            ['class' => 'p-dropdown p-component p-inputwrapper p-inputwrapper-filled'],
900
            $extra_attributes
901
        );
902
        foreach ($extra_attributes as $key => $parameter) {
903
            if ('id' == $key) {
904
                $default_id = '';
905
            }
906
            $extra .= $key.'="'.$parameter.'" ';
907
        }
908
        $html .= '<select name="'.$name.'" '.$default_id.' '.$extra.'>';
909
910
        if ($show_blank_item) {
911
            if (empty($blank_item_text)) {
912
                $blank_item_text = get_lang('Select');
913
            } else {
914
                $blank_item_text = Security::remove_XSS($blank_item_text);
915
            }
916
            $html .= self::tag(
917
                'option',
918
                '-- '.$blank_item_text.' --',
0 ignored issues
show
Bug introduced by
Are you sure $blank_item_text of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

918
                '-- './** @scrutinizer ignore-type */ $blank_item_text.' --',
Loading history...
919
                ['value' => '-1']
920
            );
921
        }
922
        if ($values) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $values of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
923
            foreach ($values as $key => $value) {
924
                if (is_array($value) && isset($value['name'])) {
925
                    $value = $value['name'];
926
                }
927
                $html .= '<option value="'.$key.'"';
928
929
                if (is_array($default)) {
930
                    foreach ($default as $item) {
931
                        if ($item == $key) {
932
                            $html .= ' selected="selected"';
933
                            break;
934
                        }
935
                    }
936
                } else {
937
                    if ($default == $key) {
938
                        $html .= ' selected="selected"';
939
                    }
940
                }
941
942
                $html .= '>'.$value.'</option>';
943
            }
944
        }
945
        $html .= '</select>';
946
947
        return $html;
948
    }
949
950
    /**
951
     * @param $name
952
     * @param $value
953
     * @param array $attributes
954
     *
955
     * @return string
956
     */
957
    public static function button($name, $value, $attributes = [])
958
    {
959
        if (!empty($name)) {
960
            $attributes['name'] = $name;
961
        }
962
963
        return self::tag('button', $value, $attributes);
964
    }
965
966
    /**
967
     * Creates a tab menu
968
     * Requirements: declare the jquery, jquery-ui libraries + the jquery-ui.css
969
     * in the $htmlHeadXtra variable before the display_header
970
     * Add this script.
971
     *
972
     * @param array  $headers       list of the tab titles
973
     * @param array  $items
974
     * @param string $id            id of the container of the tab in the example "tabs"
975
     * @param array  $attributes    for the ul
976
     * @param array  $ul_attributes
977
     * @param string $selected
978
     *
979
     * @return string
980
     */
981
    public static function tabs(
982
        $headers,
983
        $items,
984
        $id = 'tabs',
985
        $attributes = [],
986
        $ul_attributes = [],
987
        $selected = ''
988
    ) {
989
        if (empty($headers) || 0 === count($headers)) {
990
            return '';
991
        }
992
993
        $lis = '';
994
        $i = 1;
995
        foreach ($headers as $item) {
996
            $active = '';
997
            if (1 == $i) {
998
                $active = ' active';
999
            }
1000
1001
            if (!empty($selected)) {
1002
                $active = '';
1003
                if ($selected == $i) {
1004
                    $active = ' active';
1005
                }
1006
            }
1007
1008
            $item = self::tag(
1009
                'a',
1010
                $item,
1011
                [
1012
                    //'href' => '#'.$id.'-'.$i,
1013
                    'href' => 'javascript:void(0)',
1014
                    'class' => 'nav-item nav-link '.$active,
1015
                    '@click' => "openTab =  $i",
1016
                    'id' => $id.$i.'-tab',
1017
                    'data-toggle' => 'tab',
1018
                    'role' => 'tab',
1019
                    'aria-controls' => $id.'-'.$i,
1020
                    'aria-selected' => $selected,
1021
                ]
1022
            );
1023
            $lis .= $item;
1024
            $i++;
1025
        }
1026
1027
        $ul = self::tag(
1028
            'nav',
1029
            $lis,
1030
            [
1031
                'id' => 'nav_'.$id,
1032
                'class' => 'nav nav-tabs',
1033
                'role' => 'tablist',
1034
            ]
1035
        );
1036
1037
        $i = 1;
1038
        $divs = '';
1039
        foreach ($items as $content) {
1040
            $active = '';
1041
            if (1 == $i) {
1042
                $active = ' show active';
1043
            }
1044
1045
            if (!empty($selected)) {
1046
                $active = '';
1047
                if ($selected == $i) {
1048
                    $active = ' show active';
1049
                }
1050
            }
1051
1052
            $divs .= self::tag(
1053
                'div',
1054
                $content,
1055
                [
1056
                    'id' => $id.'-'.$i,
1057
                    'x-show' => "openTab === $i",
1058
                    //'class' => 'tab-pane fade '.$active,
1059
                    //'role' => 'tabpanel',
1060
                    //'aria-labelledby' => $id.$i.'-tab',
1061
                ]
1062
            );
1063
            $i++;
1064
        }
1065
1066
        $attributes['id'] = ''.$id;
1067
        if (empty($attributes['class'])) {
1068
            $attributes['class'] = '';
1069
        }
1070
        $attributes['class'] .= ' tab_wrapper ';
1071
        $attributes['x-data'] = ' { openTab: 1 } ';
1072
1073
        return self::tag(
1074
            'div',
1075
            $ul.
1076
            $divs,
1077
            $attributes
1078
        );
1079
    }
1080
1081
    /**
1082
     * @param $headers
1083
     * @param null $selected
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $selected is correct as it would always require null to be passed?
Loading history...
1084
     *
1085
     * @return string
1086
     */
1087
    public static function tabsOnlyLink($headers, $selected = null)
1088
    {
1089
        $id = uniqid('tabs_');
1090
        $i = 1;
1091
        $list = '';
1092
        foreach ($headers as $item) {
1093
            $class = null;
1094
            if ($i == $selected) {
1095
                $class = 'active';
1096
            }
1097
            $item = self::tag(
1098
                'a',
1099
                $item['content'],
1100
                [
1101
                    'id' => $id.'-'.$i,
1102
                    'href' => $item['url'],
1103
                    'class' => 'btn '.$class,
1104
                ]
1105
            );
1106
            $list .= $item;
1107
            $i++;
1108
        }
1109
1110
        return self::toolbarAction($id, [$list]);
1111
    }
1112
1113
    /**
1114
     * In order to display a grid using jqgrid you have to:.
1115
     *
1116
     * @example
1117
     * After your Display::display_header function you have to add the nex javascript code:
1118
     * <script>
1119
     *   echo Display::grid_js('my_grid_name', $url,$columns, $column_model, $extra_params,[]);
1120
     *   // for more information of this function check the grid_js() function
1121
     * </script>
1122
     * //Then you have to call the grid_html
1123
     * echo Display::grid_html('my_grid_name');
1124
     * As you can see both function use the same "my_grid_name" this is very important otherwise nothing will work
1125
     *
1126
     * @param   string  the div id, this value must be the same with the first parameter of Display::grid_js()
1127
     *
1128
     * @return string html
1129
     */
1130
    public static function grid_html($div_id)
1131
    {
1132
        $table = self::tag('table', '', ['id' => $div_id]);
1133
        $table .= self::tag('div', '', ['id' => $div_id.'_pager']);
1134
1135
        return $table;
1136
    }
1137
1138
    /**
1139
     * This is a wrapper to use the jqgrid in Chamilo.
1140
     * For the other jqgrid options visit http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options
1141
     * This function need to be in the ready jquery function
1142
     * example --> $(function() { <?php echo Display::grid_js('grid' ...); ?> }
1143
     * In order to work this function needs the Display::grid_html function with the same div id.
1144
     *
1145
     * @param string $div_id       div id
1146
     * @param string $url          url where the jqgrid will ask for data (if datatype = json)
1147
     * @param array  $column_names Visible columns (you should use get_lang).
1148
     *                             An array in which we place the names of the columns.
1149
     *                             This is the text that appears in the head of the grid (Header layer).
1150
     *                             Example: colname   {name:'date',     index:'date',   width:120, align:'right'},
1151
     * @param array  $column_model the column model :  Array which describes the parameters of the columns.
1152
     *                             This is the most important part of the grid.
1153
     *                             For a full description of all valid values see colModel API. See the url above.
1154
     * @param array  $extra_params extra parameters
1155
     * @param array  $data         data that will be loaded
1156
     * @param string $formatter    A string that will be appended to the JSON returned
1157
     * @param bool   $fixed_width  not implemented yet
1158
     *
1159
     * @return string the js code
1160
     */
1161
    public static function grid_js(
1162
        $div_id,
1163
        $url,
1164
        $column_names,
1165
        $column_model,
1166
        $extra_params,
1167
        $data = [],
1168
        $formatter = '',
1169
        $fixed_width = false
1170
    ) {
1171
        $obj = new stdClass();
1172
        $obj->first = 'first';
1173
1174
        if (!empty($url)) {
1175
            $obj->url = $url;
1176
        }
1177
1178
        // Needed it in order to render the links/html in the grid
1179
        foreach ($column_model as &$columnModel) {
1180
            if (!isset($columnModel['formatter'])) {
1181
                $columnModel['formatter'] = '';
1182
            }
1183
        }
1184
1185
        //This line should only be used/modified in case of having characters
1186
        // encoding problems - see #6159
1187
        //$column_names = array_map("utf8_encode", $column_names);
1188
        $obj->colNames = $column_names;
1189
        $obj->colModel = $column_model;
1190
        $obj->pager = '#'.$div_id.'_pager';
1191
        $obj->datatype = 'json';
1192
        $obj->viewrecords = 'true';
1193
        $obj->guiStyle = 'bootstrap4';
1194
        $obj->iconSet = 'materialDesignIcons';
1195
        $all_value = 10000000;
1196
1197
        // Sets how many records we want to view in the grid
1198
        $obj->rowNum = 20;
1199
1200
        // Default row quantity
1201
        if (!isset($extra_params['rowList'])) {
1202
            $extra_params['rowList'] = [20, 50, 100, 500, 1000, $all_value];
1203
            $rowList = api_get_setting('platform.table_row_list', true);
1204
            if (!empty($rowList) && isset($rowList['options'])) {
1205
                $rowList = $rowList['options'];
1206
                $rowList[] = $all_value;
1207
            }
1208
            $extra_params['rowList'] = $rowList;
1209
        }
1210
1211
        $defaultRow = (int) api_get_setting('platform.table_default_row');
1212
        if ($defaultRow > 0) {
1213
            $obj->rowNum = $defaultRow;
1214
        }
1215
1216
        $json = '';
1217
        if (!empty($extra_params['datatype'])) {
1218
            $obj->datatype = $extra_params['datatype'];
1219
        }
1220
1221
        // Row even odd style.
1222
        $obj->altRows = true;
1223
        if (!empty($extra_params['altRows'])) {
1224
            $obj->altRows = $extra_params['altRows'];
1225
        }
1226
1227
        if (!empty($extra_params['sortname'])) {
1228
            $obj->sortname = $extra_params['sortname'];
1229
        }
1230
1231
        if (!empty($extra_params['sortorder'])) {
1232
            $obj->sortorder = $extra_params['sortorder'];
1233
        }
1234
1235
        if (!empty($extra_params['rowList'])) {
1236
            $obj->rowList = $extra_params['rowList'];
1237
        }
1238
1239
        if (!empty($extra_params['rowNum'])) {
1240
            $obj->rowNum = $extra_params['rowNum'];
1241
        } else {
1242
            // Try to load max rows from Session
1243
            $urlInfo = parse_url($url);
1244
            if (isset($urlInfo['query'])) {
1245
                parse_str($urlInfo['query'], $query);
1246
                if (isset($query['a'])) {
1247
                    $action = $query['a'];
1248
                    // This value is set in model.ajax.php
1249
                    $savedRows = Session::read('max_rows_'.$action);
1250
                    if (!empty($savedRows)) {
1251
                        $obj->rowNum = $savedRows;
1252
                    }
1253
                }
1254
            }
1255
        }
1256
1257
        if (!empty($extra_params['viewrecords'])) {
1258
            $obj->viewrecords = $extra_params['viewrecords'];
1259
        }
1260
1261
        $beforeSelectRow = null;
1262
        if (isset($extra_params['beforeSelectRow'])) {
1263
            $beforeSelectRow = 'beforeSelectRow: '.$extra_params['beforeSelectRow'].', ';
1264
            unset($extra_params['beforeSelectRow']);
1265
        }
1266
1267
        $beforeProcessing = '';
1268
        if (isset($extra_params['beforeProcessing'])) {
1269
            $beforeProcessing = 'beforeProcessing : function() { '.$extra_params['beforeProcessing'].' },';
1270
            unset($extra_params['beforeProcessing']);
1271
        }
1272
1273
        $beforeRequest = '';
1274
        if (isset($extra_params['beforeRequest'])) {
1275
            $beforeRequest = 'beforeRequest : function() { '.$extra_params['beforeRequest'].' },';
1276
            unset($extra_params['beforeRequest']);
1277
        }
1278
1279
        $gridComplete = '';
1280
        if (isset($extra_params['gridComplete'])) {
1281
            $gridComplete = 'gridComplete : function() { '.$extra_params['gridComplete'].' },';
1282
            unset($extra_params['gridComplete']);
1283
        }
1284
1285
        // Adding extra params
1286
        if (!empty($extra_params)) {
1287
            foreach ($extra_params as $key => $element) {
1288
                // the groupHeaders key gets a special treatment
1289
                if ('groupHeaders' != $key) {
1290
                    $obj->$key = $element;
1291
                }
1292
            }
1293
        }
1294
1295
        // Adding static data.
1296
        if (!empty($data)) {
1297
            $data_var = $div_id.'_data';
1298
            $json .= ' var '.$data_var.' = '.json_encode($data).';';
1299
            $obj->data = $data_var;
1300
            $obj->datatype = 'local';
1301
            $json .= "\n";
1302
        }
1303
1304
        $obj->end = 'end';
1305
1306
        $json_encode = json_encode($obj);
1307
1308
        if (!empty($data)) {
1309
            //Converts the "data":"js_variable" to "data":js_variable,
1310
            // otherwise it will not work
1311
            $json_encode = str_replace('"data":"'.$data_var.'"', '"data":'.$data_var.'', $json_encode);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data_var does not seem to be defined for all execution paths leading up to this point.
Loading history...
1312
        }
1313
1314
        // Fixing true/false js values that doesn't need the ""
1315
        $json_encode = str_replace(':"true"', ':true', $json_encode);
1316
        // wrap_cell is not a valid jqgrid attributes is a hack to wrap a text
1317
        $json_encode = str_replace('"wrap_cell":true', 'cellattr : function(rowId, value, rowObject, colModel, arrData) { return \'class = "jqgrid_whitespace"\'; }', $json_encode);
1318
        $json_encode = str_replace(':"false"', ':false', $json_encode);
1319
        $json_encode = str_replace('"formatter":"action_formatter"', 'formatter:action_formatter', $json_encode);
1320
        $json_encode = str_replace('"formatter":"extra_formatter"', 'formatter:extra_formatter', $json_encode);
1321
        $json_encode = str_replace(['{"first":"first",', '"end":"end"}'], '', $json_encode);
1322
1323
        if (('true' === api_get_setting('document.allow_compilatio_tool')) &&
1324
            (false !== strpos($_SERVER['REQUEST_URI'], 'work/work.php') ||
1325
             false != strpos($_SERVER['REQUEST_URI'], 'work/work_list_all.php')
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing strpos($_SERVER['REQUEST...ork/work_list_all.php') of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
1326
            )
1327
        ) {
1328
            $json_encode = str_replace('"function () { compilatioInit() }"',
1329
                'function () { compilatioInit() }',
1330
                $json_encode
1331
            );
1332
        }
1333
        // Creating the jqgrid element.
1334
        $json .= '$("#'.$div_id.'").jqGrid({';
1335
        $json .= "autowidth: true,";
1336
        //$json .= $beforeSelectRow;
1337
        $json .= $gridComplete;
1338
        $json .= $beforeProcessing;
1339
        $json .= $beforeRequest;
1340
        $json .= $json_encode;
1341
        $json .= '});';
1342
1343
        // Grouping headers option
1344
        if (isset($extra_params['groupHeaders'])) {
1345
            $groups = '';
1346
            foreach ($extra_params['groupHeaders'] as $group) {
1347
                //{ "startColumnName" : "courses", "numberOfColumns" : 1, "titleText" : "Order Info" },
1348
                $groups .= '{ "startColumnName" : "'.$group['startColumnName'].'", "numberOfColumns" : '.$group['numberOfColumns'].', "titleText" : "'.$group['titleText'].'" },';
1349
            }
1350
            $json .= '$("#'.$div_id.'").jqGrid("setGroupHeaders", {
1351
                "useColSpanStyle" : false,
1352
                "groupHeaders"    : [
1353
                    '.$groups.'
1354
                ]
1355
            });';
1356
        }
1357
1358
        $all_text = addslashes(get_lang('All'));
1359
        $json .= '$("'.$obj->pager.' option[value='.$all_value.']").text("'.$all_text.'");';
1360
        $json .= "\n";
1361
        // Adding edit/delete icons.
1362
        $json .= $formatter;
1363
1364
        return $json;
1365
    }
1366
1367
    /**
1368
     * @param array $headers
1369
     * @param array $rows
1370
     * @param array $attributes
1371
     *
1372
     * @return string
1373
     */
1374
    public static function table($headers, $rows, $attributes = [])
1375
    {
1376
        if (empty($attributes)) {
1377
            $attributes['class'] = 'data_table';
1378
        }
1379
        $table = new HTML_Table($attributes);
1380
        $row = 0;
1381
        $column = 0;
1382
1383
        // Course headers
1384
        if (!empty($headers)) {
1385
            foreach ($headers as $item) {
1386
                $table->setHeaderContents($row, $column, $item);
1387
                $column++;
1388
            }
1389
            $row = 1;
1390
            $column = 0;
1391
        }
1392
1393
        if (!empty($rows)) {
1394
            foreach ($rows as $content) {
1395
                $table->setCellContents($row, $column, $content);
1396
                $row++;
1397
            }
1398
        }
1399
1400
        return $table->toHtml();
1401
    }
1402
1403
    /**
1404
     * Get the session box details as an array.
1405
     *
1406
     * @todo check session visibility.
1407
     *
1408
     * @param int $session_id
1409
     *
1410
     * @return array Empty array or session array
1411
     *               ['title'=>'...','category'=>'','dates'=>'...','coach'=>'...','active'=>true/false,'session_category_id'=>int]
1412
     */
1413
    public static function getSessionTitleBox($session_id)
1414
    {
1415
        $session_info = api_get_session_info($session_id);
1416
        $generalCoachesNames = implode(
1417
            ' - ',
1418
            SessionManager::getGeneralCoachesNamesForSession($session_id)
1419
        );
1420
1421
        $session = [];
1422
        $session['category_id'] = $session_info['session_category_id'];
1423
        $session['title'] = $session_info['name'];
1424
        $session['dates'] = '';
1425
        $session['coach'] = '';
1426
        if ('true' === api_get_setting('show_session_coach') && $generalCoachesNames) {
1427
            $session['coach'] = get_lang('General coach').': '.$generalCoachesNames;
1428
        }
1429
        $active = false;
1430
        if (('0000-00-00 00:00:00' === $session_info['access_end_date'] &&
1431
            '0000-00-00 00:00:00' === $session_info['access_start_date']) ||
1432
            (empty($session_info['access_end_date']) && empty($session_info['access_start_date']))
1433
        ) {
1434
            if (isset($session_info['duration']) && !empty($session_info['duration'])) {
1435
                $daysLeft = SessionManager::getDayLeftInSession($session_info, api_get_user_id());
1436
                $session['duration'] = $daysLeft >= 0
1437
                    ? sprintf(get_lang('This session has a maximum duration. Only %s days to go.'), $daysLeft)
1438
                    : get_lang('You are already registered but your allowed access time has expired.');
1439
            }
1440
            $active = true;
1441
        } else {
1442
            $dates = SessionManager::parseSessionDates($session_info, true);
1443
            $session['dates'] = $dates['access'];
1444
            //$active = $date_start <= $now && $date_end >= $now;
1445
        }
1446
        $session['active'] = $active;
1447
        $session['session_category_id'] = $session_info['session_category_id'];
1448
        $session['visibility'] = $session_info['visibility'];
1449
        $session['num_users'] = $session_info['nbr_users'];
1450
        $session['num_courses'] = $session_info['nbr_courses'];
1451
        $session['description'] = $session_info['description'];
1452
        $session['show_description'] = $session_info['show_description'];
1453
        //$session['image'] = SessionManager::getSessionImage($session_info['id']);
1454
        $session['url'] = api_get_path(WEB_CODE_PATH).'session/index.php?session_id='.$session_info['id'];
1455
1456
        $entityManager = Database::getManager();
1457
        $fieldValuesRepo = $entityManager->getRepository(ExtraFieldValues::class);
1458
        $extraFieldValues = $fieldValuesRepo->getVisibleValues(
1459
            ExtraField::SESSION_FIELD_TYPE,
1460
            $session_id
1461
        );
1462
1463
        $session['extra_fields'] = [];
1464
        /** @var ExtraFieldValues $value */
1465
        foreach ($extraFieldValues as $value) {
1466
            if (empty($value)) {
1467
                continue;
1468
            }
1469
            $session['extra_fields'][] = [
1470
                'field' => [
1471
                    'variable' => $value->getField()->getVariable(),
1472
                    'display_text' => $value->getField()->getDisplayText(),
1473
                ],
1474
                'value' => $value->getFieldValue(),
1475
            ];
1476
        }
1477
1478
        return $session;
1479
    }
1480
1481
    /**
1482
     * Return the five star HTML.
1483
     *
1484
     * @param string $id              of the rating ul element
1485
     * @param string $url             that will be added (for jquery see hot_courses.tpl)
1486
     * @param array  $point_info      point info array see function CourseManager::get_course_ranking()
1487
     * @param bool   $add_div_wrapper add a div wrapper
1488
     *
1489
     * @return string
1490
     */
1491
    public static function return_rating_system(
1492
        $id,
1493
        $url,
1494
        $point_info = [],
1495
        $add_div_wrapper = true
1496
    ) {
1497
        $number_of_users_who_voted = isset($point_info['users_who_voted']) ? $point_info['users_who_voted'] : null;
1498
        $percentage = isset($point_info['point_average']) ? $point_info['point_average'] : 0;
1499
1500
        if (!empty($percentage)) {
1501
            $percentage = $percentage * 125 / 100;
1502
        }
1503
        $accesses = isset($point_info['accesses']) ? $point_info['accesses'] : 0;
1504
        $star_label = sprintf(get_lang('%s stars out of 5'), $point_info['point_average_star']);
1505
1506
        $html = '<section class="rating-widget">';
1507
        $html .= '<div class="rating-stars"><ul id="stars">';
1508
        $html .= '<li class="star" data-link="'.$url.'&amp;star=1" title="Poor" data-value="1"><i class="fa fa-star fa-fw"></i></li>
1509
                 <li class="star" data-link="'.$url.'&amp;star=2" title="Fair" data-value="2"><i class="fa fa-star fa-fw"></i></li>
1510
                 <li class="star" data-link="'.$url.'&amp;star=3" title="Good" data-value="3"><i class="fa fa-star fa-fw"></i></li>
1511
                 <li class="star" data-link="'.$url.'&amp;star=4" title="Excellent" data-value="4"><i class="fa fa-star fa-fw"></i></li>
1512
                 <li class="star" data-link="'.$url.'&amp;star=5" title="WOW!!!" data-value="5"><i class="fa fa-star fa-fw"></i></li>
1513
        ';
1514
        $html .= '</ul></div>';
1515
        $html .= '</section>';
1516
        $labels = [];
1517
1518
        $labels[] = 1 == $number_of_users_who_voted ? $number_of_users_who_voted.' '.get_lang('Vote') : $number_of_users_who_voted.' '.get_lang('Votes');
1519
        $labels[] = 1 == $accesses ? $accesses.' '.get_lang('Visit') : $accesses.' '.get_lang('Visits');
1520
        $labels[] = $point_info['user_vote'] ? get_lang('Your vote').' ['.$point_info['user_vote'].']' : get_lang('Your vote').' [?] ';
1521
1522
        if (!$add_div_wrapper && api_is_anonymous()) {
1523
            $labels[] = self::tag('span', get_lang('Login to vote'), ['class' => 'error']);
1524
        }
1525
1526
        $html .= self::div(implode(' | ', $labels), ['id' => 'vote_label_'.$id, 'class' => 'vote_label_info']);
1527
        $html .= ' '.self::span(' ', ['id' => 'vote_label2_'.$id]);
1528
1529
        if ($add_div_wrapper) {
1530
            $html = self::div($html, ['id' => 'rating_wrapper_'.$id]);
1531
        }
1532
1533
        return $html;
1534
    }
1535
1536
    /**
1537
     * @param string $title
1538
     * @param string $second_title
1539
     * @param string $size
1540
     * @param bool   $filter
1541
     *
1542
     * @return string
1543
     */
1544
    public static function page_header($title, $second_title = null, $size = 'h2', $filter = true)
1545
    {
1546
        if ($filter) {
1547
            $title = Security::remove_XSS($title);
1548
        }
1549
1550
        if (!empty($second_title)) {
1551
            if ($filter) {
1552
                $second_title = Security::remove_XSS($second_title);
1553
            }
1554
            $title .= "<small> $second_title</small>";
1555
        }
1556
1557
        return '<'.$size.' class="page-header">'.$title.'</'.$size.'>';
0 ignored issues
show
Bug introduced by
Are you sure $title of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

1557
        return '<'.$size.' class="page-header">'./** @scrutinizer ignore-type */ $title.'</'.$size.'>';
Loading history...
1558
    }
1559
1560
    public static function page_header_and_translate($title, $second_title = null)
1561
    {
1562
        $title = get_lang($title);
1563
1564
        return self::page_header($title, $second_title);
1565
    }
1566
1567
    public static function page_subheader($title, $second_title = null, $size = 'h3', $attributes = [])
1568
    {
1569
        if (!empty($second_title)) {
1570
            $second_title = Security::remove_XSS($second_title);
1571
            $title .= "<small> $second_title<small>";
1572
        }
1573
        $subTitle = self::tag($size, Security::remove_XSS($title), $attributes);
1574
1575
        return $subTitle;
1576
    }
1577
1578
    public static function page_subheader2($title, $second_title = null)
1579
    {
1580
        return self::page_header($title, $second_title, 'h4');
1581
    }
1582
1583
    public static function page_subheader3($title, $second_title = null)
1584
    {
1585
        return self::page_header($title, $second_title, 'h5');
1586
    }
1587
1588
    public static function description(array $list): string
1589
    {
1590
        $html = '';
1591
        if (!empty($list)) {
1592
            $html = '<dl class="dl-horizontal">';
1593
            foreach ($list as $item) {
1594
                $html .= '<dt>'.$item['title'].'</dt>';
1595
                $html .= '<dd>'.$item['content'].'</dd>';
1596
            }
1597
            $html .= '</dl>';
1598
        }
1599
1600
        return $html;
1601
    }
1602
1603
    /**
1604
     * @param int    $percentage      int value between 0 and 100
1605
     * @param bool   $show_percentage
1606
     * @param string $extra_info
1607
     * @param string $class           danger/success/infowarning
1608
     *
1609
     * @return string
1610
     */
1611
    public static function bar_progress($percentage, $show_percentage = true, $extra_info = '', $class = '')
1612
    {
1613
        $percentage = (int) $percentage;
1614
        $class = empty($class) ? '' : "progress-bar-$class";
1615
1616
        $div = '<div class="progress">
1617
                <div
1618
                    class="progress-bar progress-bar-striped '.$class.'"
1619
                    role="progressbar"
1620
                    aria-valuenow="'.$percentage.'"
1621
                    aria-valuemin="0"
1622
                    aria-valuemax="100"
1623
                    style="width: '.$percentage.'%;"
1624
                >';
1625
        if ($show_percentage) {
1626
            $div .= $percentage.'%';
1627
        } else {
1628
            if (!empty($extra_info)) {
1629
                $div .= $extra_info;
1630
            }
1631
        }
1632
        $div .= '</div></div>';
1633
1634
        return $div;
1635
    }
1636
1637
    /**
1638
     * @param array $badge_list
1639
     *
1640
     * @return string
1641
     */
1642
    public static function badgeGroup($list)
1643
    {
1644
        $html = '<div class="badge-group">';
1645
        foreach ($list as $badge) {
1646
            $html .= $badge;
1647
        }
1648
        $html .= '</div>';
1649
1650
        return $html;
1651
    }
1652
1653
    /**
1654
     * Return an HTML span element with the badge class and an additional bg-$type class
1655
     */
1656
    public static function label(string $content, string $type = 'default'): string
1657
    {
1658
        $html = '';
1659
        if (!empty($content)) {
1660
            $class = match ($type) {
1661
                'success' => 'success',
1662
                'warning' => 'warning',
1663
                'important', 'danger', 'error' => 'error',
1664
                'info' => 'info',
1665
                'primary' => 'primary',
1666
                default => 'secondary',
1667
            };
1668
1669
            $html = '<span class="badge badge--'.$class.'">';
1670
            $html .= $content;
1671
            $html .= '</span>';
1672
        }
1673
1674
        return $html;
1675
    }
1676
1677
    public static function actions(array $items): string
1678
    {
1679
        if (empty($items)) {
1680
            return '';
1681
        }
1682
1683
        $links = '';
1684
        foreach ($items as $value) {
1685
            $attributes = $value['url_attributes'] ?? [];
1686
            $links .= self::url($value['content'], $value['url'], $attributes);
1687
        }
1688
1689
        return self::toolbarAction(uniqid('toolbar', false), [$links]);
1690
    }
1691
1692
    /**
1693
     * Prints a tooltip.
1694
     *
1695
     * @param string $text
1696
     * @param string $tip
1697
     *
1698
     * @return string
1699
     */
1700
    public static function tip($text, $tip)
1701
    {
1702
        if (empty($tip)) {
1703
            return $text;
1704
        }
1705
1706
        return self::span(
1707
            $text,
1708
            ['class' => 'boot-tooltip', 'title' => strip_tags($tip)]
1709
        );
1710
    }
1711
1712
    /**
1713
     * @param array $buttons
1714
     *
1715
     * @return string
1716
     */
1717
    public static function groupButton($buttons)
1718
    {
1719
        $html = '<div class="btn-group" role="group">';
1720
        foreach ($buttons as $button) {
1721
            $html .= $button;
1722
        }
1723
        $html .= '</div>';
1724
1725
        return $html;
1726
    }
1727
1728
    /**
1729
     * @todo use twig
1730
     *
1731
     * @param string $title
1732
     * @param array  $elements
1733
     * @param bool   $alignToRight
1734
     *
1735
     * @return string
1736
     */
1737
    public static function groupButtonWithDropDown($title, $elements, $alignToRight = false)
1738
    {
1739
        $id = uniqid('dropdown', false);
1740
        $html = '
1741
        <div class="dropdown inline-block relative">
1742
            <button
1743
                id="'.$id.'"
1744
                type="button"
1745
                class="inline-flex justify-center w-full rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-indigo-500"
1746
                aria-expanded="false"
1747
                aria-haspopup="true"
1748
                onclick="document.querySelector(\'#'.$id.'_menu\').classList.toggle(\'hidden\')"
1749
            >
1750
              '.$title.'
1751
              <svg class="-mr-1 ml-2 h-5 w-5" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
1752
                <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
1753
              </svg>
1754
            </button>
1755
            <div
1756
                id="'.$id.'_menu"
1757
                class=" dropdown-menu hidden origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
1758
                role="menu"
1759
                aria-orientation="vertical"
1760
                aria-labelledby="menu-button"
1761
                tabindex="-1"
1762
            >
1763
            <div class="py-1" role="none">';
1764
        foreach ($elements as $item) {
1765
            $html .= self::url(
1766
                    $item['title'],
1767
                    $item['href'],
1768
                    [
1769
                        'class' => 'text-gray-700 block px-4 py-2 text-sm',
1770
                        'role' => 'menuitem',
1771
                        'onclick' => $item['onclick'] ?? '',
1772
                        'data-action' => $item['data-action'] ?? '',
1773
                    ]
1774
                );
1775
        }
1776
        $html .= '
1777
            </div>
1778
            </div>
1779
            </div>
1780
        ';
1781
1782
        return $html;
1783
    }
1784
1785
    /**
1786
     * @param string $file
1787
     * @param array  $params
1788
     *
1789
     * @return string|null
1790
     */
1791
    public static function getMediaPlayer($file, $params = [])
1792
    {
1793
        $fileInfo = pathinfo($file);
1794
1795
        $autoplay = isset($params['autoplay']) && 'true' === $params['autoplay'] ? 'autoplay' : '';
1796
        $id = isset($params['id']) ? $params['id'] : $fileInfo['basename'];
1797
        $width = isset($params['width']) ? 'width="'.$params['width'].'"' : null;
1798
        $class = isset($params['class']) ? ' class="'.$params['class'].'"' : null;
1799
1800
        switch ($fileInfo['extension']) {
1801
            case 'mp3':
1802
            case 'webm':
1803
                $html = '<audio id="'.$id.'" '.$class.' controls '.$autoplay.' '.$width.' src="'.$params['url'].'" >';
1804
                $html .= '<object width="'.$width.'" height="50" type="application/x-shockwave-flash" data="'.api_get_path(WEB_LIBRARY_PATH).'javascript/mediaelement/flashmediaelement.swf">
1805
                            <param name="movie" value="'.api_get_path(WEB_LIBRARY_PATH).'javascript/mediaelement/flashmediaelement.swf" />
1806
                            <param name="flashvars" value="controls=true&file='.$params['url'].'" />
1807
                          </object>';
1808
                $html .= '</audio>';
1809
1810
                return $html;
1811
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
1812
            case 'wav':
1813
            case 'ogg':
1814
                $html = '<audio width="300px" controls id="'.$id.'" '.$autoplay.' src="'.$params['url'].'" >';
1815
1816
                return $html;
1817
                break;
1818
        }
1819
1820
        return null;
1821
    }
1822
1823
    /**
1824
     * @param int    $nextValue
1825
     * @param array  $list
1826
     * @param int    $current
1827
     * @param int    $fixedValue
1828
     * @param array  $conditions
1829
     * @param string $link
1830
     * @param bool   $isMedia
1831
     * @param bool   $addHeaders
1832
     * @param array  $linkAttributes
1833
     *
1834
     * @return string
1835
     */
1836
    public static function progressPaginationBar(
1837
        $nextValue,
1838
        $list,
1839
        $current,
1840
        $fixedValue = null,
1841
        $conditions = [],
1842
        $link = null,
1843
        $isMedia = false,
1844
        $addHeaders = true,
1845
        $linkAttributes = []
1846
    ) {
1847
        if ($addHeaders) {
1848
            $pagination_size = 'pagination-mini';
1849
            $html = '<div class="exercise_pagination pagination '.$pagination_size.'"><ul>';
1850
        } else {
1851
            $html = null;
1852
        }
1853
        $affectAllItems = false;
1854
        if ($isMedia && isset($fixedValue) && ($nextValue + 1 == $current)) {
1855
            $affectAllItems = true;
1856
        }
1857
        $localCounter = 0;
1858
        foreach ($list as $itemId) {
1859
            $isCurrent = false;
1860
            if ($affectAllItems) {
1861
                $isCurrent = true;
1862
            } else {
1863
                if (!$isMedia) {
1864
                    $isCurrent = $current == ($localCounter + $nextValue + 1) ? true : false;
1865
                }
1866
            }
1867
            $html .= self::parsePaginationItem(
1868
                $itemId,
1869
                $isCurrent,
1870
                $conditions,
1871
                $link,
1872
                $nextValue,
1873
                $isMedia,
1874
                $localCounter,
1875
                $fixedValue,
1876
                $linkAttributes
1877
            );
1878
            $localCounter++;
1879
        }
1880
        if ($addHeaders) {
1881
            $html .= '</ul></div>';
1882
        }
1883
1884
        return $html;
1885
    }
1886
1887
    /**
1888
     * @param int    $itemId
1889
     * @param bool   $isCurrent
1890
     * @param array  $conditions
1891
     * @param string $link
1892
     * @param int    $nextValue
1893
     * @param bool   $isMedia
1894
     * @param int    $localCounter
1895
     * @param int    $fixedValue
1896
     * @param array  $linkAttributes
1897
     *
1898
     * @return string
1899
     */
1900
    public static function parsePaginationItem(
1901
        $itemId,
1902
        $isCurrent,
1903
        $conditions,
1904
        $link,
1905
        $nextValue = 0,
1906
        $isMedia = false,
1907
        $localCounter = null,
1908
        $fixedValue = null,
1909
        $linkAttributes = []
1910
    ) {
1911
        $defaultClass = 'before';
1912
        $class = $defaultClass;
1913
        foreach ($conditions as $condition) {
1914
            $array = isset($condition['items']) ? $condition['items'] : [];
1915
            $class_to_applied = $condition['class'];
1916
            $type = isset($condition['type']) ? $condition['type'] : 'positive';
1917
            $mode = isset($condition['mode']) ? $condition['mode'] : 'add';
1918
            switch ($type) {
1919
                case 'positive':
1920
                    if (in_array($itemId, $array)) {
1921
                        if ('overwrite' == $mode) {
1922
                            $class = " $defaultClass $class_to_applied";
1923
                        } else {
1924
                            $class .= " $class_to_applied";
1925
                        }
1926
                    }
1927
                    break;
1928
                case 'negative':
1929
                    if (!in_array($itemId, $array)) {
1930
                        if ('overwrite' == $mode) {
1931
                            $class = " $defaultClass $class_to_applied";
1932
                        } else {
1933
                            $class .= " $class_to_applied";
1934
                        }
1935
                    }
1936
                    break;
1937
            }
1938
        }
1939
        if ($isCurrent) {
1940
            $class = 'before current';
1941
        }
1942
        if ($isMedia && $isCurrent) {
1943
            $class = 'before current';
1944
        }
1945
        if (empty($link)) {
1946
            $link_to_show = '#';
1947
        } else {
1948
            $link_to_show = $link.($nextValue + $localCounter);
1949
        }
1950
        $label = $nextValue + $localCounter + 1;
1951
        if ($isMedia) {
1952
            $label = ($fixedValue + 1).' '.chr(97 + $localCounter);
1953
            $link_to_show = $link.$fixedValue.'#questionanchor'.$itemId;
1954
        }
1955
        $link = self::url($label.' ', $link_to_show, $linkAttributes);
1956
1957
        return '<li class = "'.$class.'">'.$link.'</li>';
1958
    }
1959
1960
    /**
1961
     * @param int $current
1962
     * @param int $total
1963
     *
1964
     * @return string
1965
     */
1966
    public static function paginationIndicator($current, $total)
1967
    {
1968
        $html = null;
1969
        if (!empty($current) && !empty($total)) {
1970
            $label = sprintf(get_lang('%s of %s'), $current, $total);
1971
            $html = self::url($label, '#', ['class' => 'btn disabled']);
1972
        }
1973
1974
        return $html;
1975
    }
1976
1977
    /**
1978
     * @param $url
1979
     * @param $currentPage
1980
     * @param $pagesCount
1981
     * @param $totalItems
1982
     *
1983
     * @return string
1984
     */
1985
    public static function getPagination($url, $currentPage, $pagesCount, $totalItems)
1986
    {
1987
        $pagination = '';
1988
        if ($totalItems > 1 && $pagesCount > 1) {
1989
            $pagination .= '<ul class="pagination">';
1990
            for ($i = 0; $i < $pagesCount; $i++) {
1991
                $newPage = $i + 1;
1992
                if ($currentPage == $newPage) {
1993
                    $pagination .= '<li class="active"><a href="'.$url.'&page='.$newPage.'">'.$newPage.'</a></li>';
1994
                } else {
1995
                    $pagination .= '<li><a href="'.$url.'&page='.$newPage.'">'.$newPage.'</a></li>';
1996
                }
1997
            }
1998
            $pagination .= '</ul>';
1999
        }
2000
2001
        return $pagination;
2002
    }
2003
2004
    /**
2005
     * Adds a legacy message in the queue.
2006
     *
2007
     * @param string $message
2008
     */
2009
    public static function addFlash($message)
2010
    {
2011
        // Detect type of message.
2012
        $parts = preg_match('/alert-([a-z]*)/', $message, $matches);
2013
        $type = 'primary';
2014
        if ($parts && isset($matches[1]) && $matches[1]) {
2015
            $type = $matches[1];
2016
        }
2017
        // Detect legacy content of message.
2018
        $result = preg_match('/<div(.*?)\>(.*?)\<\/div>/s', $message, $matches);
2019
        if ($result && isset($matches[2])) {
2020
            Container::getSession()->getFlashBag()->add($type, $matches[2]);
2021
        }
2022
    }
2023
2024
    /**
2025
     * Get the profile edition link for a user.
2026
     *
2027
     * @param int  $userId  The user id
2028
     * @param bool $asAdmin Optional. Whether get the URL for the platform admin
2029
     *
2030
     * @return string The link
2031
     */
2032
    public static function getProfileEditionLink($userId, $asAdmin = false)
2033
    {
2034
        $editProfileUrl = api_get_path(WEB_CODE_PATH).'auth/profile.php';
2035
        if ($asAdmin) {
2036
            $editProfileUrl = api_get_path(WEB_CODE_PATH)."admin/user_edit.php?user_id=".intval($userId);
2037
        }
2038
2039
        return $editProfileUrl;
2040
    }
2041
2042
    /**
2043
     * Get the vCard for a user.
2044
     *
2045
     * @param int $userId The user id
2046
     *
2047
     * @return string *.*vcf file
2048
     */
2049
    public static function getVCardUserLink($userId)
2050
    {
2051
        return api_get_path(WEB_PATH).'main/social/vcard_export.php?userId='.intval($userId);
2052
    }
2053
2054
    /**
2055
     * @param string $content
2056
     * @param string $title
2057
     * @param string $footer
2058
     * @param string $type        primary|success|info|warning|danger
2059
     * @param string $extra
2060
     * @param string $id
2061
     * @param string $customColor
2062
     * @param string $rightAction
2063
     *
2064
     * @return string
2065
     */
2066
    public static function panel(
2067
        $content,
2068
        $title = '',
2069
        $footer = '',
2070
        $type = 'default',
2071
        $extra = '',
2072
        $id = '',
2073
        $customColor = '',
2074
        $rightAction = ''
2075
    ) {
2076
        $headerStyle = '';
2077
        if (!empty($customColor)) {
2078
            $headerStyle = 'style = "color: white; background-color: '.$customColor.'" ';
2079
        }
2080
2081
        $footer = !empty($footer) ? '<p class="card-text"><small class="text-muted">'.$footer.'</small></p>' : '';
2082
        $typeList = ['primary', 'success', 'info', 'warning', 'danger'];
2083
        $style = !in_array($type, $typeList) ? 'default' : $type;
2084
2085
        if (!empty($id)) {
2086
            $id = " id='$id'";
2087
        }
2088
        $cardBody = $title.' '.self::contentPanel($content).' '.$footer;
2089
2090
        return "
2091
            <div $id class=card>
2092
                <div class='flex justify-between items-center py-2'>
2093
                    <div class='relative mt-1 flex'>
2094
                        $title
2095
                    </div>
2096
                    <div>
2097
                        $rightAction
2098
                    </div>
2099
                </div>
2100
2101
                $content
2102
                $footer
2103
            </div>"
2104
        ;
2105
    }
2106
2107
    /**
2108
     * @param string $content
2109
     */
2110
    public static function contentPanel($content): string
2111
    {
2112
        if (empty($content)) {
2113
            return '';
2114
        }
2115
2116
        return '<div class="card-text">'.$content.'</div>';
2117
    }
2118
2119
    /**
2120
     * Get the button HTML with an Awesome Font icon.
2121
     *
2122
     * @param string $text        The button content
2123
     * @param string $url         The url to button
2124
     * @param string $icon        The Awesome Font class for icon
2125
     * @param string $type        Optional. The button Bootstrap class. Default 'default' class
2126
     * @param array  $attributes  The additional attributes
2127
     * @param bool   $includeText
2128
     *
2129
     * @return string The button HTML
2130
     */
2131
    public static function toolbarButton(
2132
        $text,
2133
        $url,
2134
        $icon = 'check',
2135
        $type = null,
2136
        array $attributes = [],
2137
        $includeText = true
2138
    ) {
2139
        $buttonClass = "btn btn--secondary-outline";
2140
        if (!empty($type)) {
2141
            $buttonClass = "btn btn--$type";
2142
        }
2143
        //$icon = self::tag('i', null, ['class' => "fa fa-$icon fa-fw", 'aria-hidden' => 'true']);
2144
        $icon = self::getMdiIcon($icon);
2145
        $attributes['class'] = isset($attributes['class']) ? "$buttonClass {$attributes['class']}" : $buttonClass;
2146
        $attributes['title'] = $attributes['title'] ?? $text;
2147
2148
        if (!$includeText) {
2149
            $text = '<span class="sr-only">'.$text.'</span>';
2150
        }
2151
2152
        return self::url("$icon $text", $url, $attributes);
2153
    }
2154
2155
    /**
2156
     * Generate an HTML "p-toolbar" div element with the given id attribute.
2157
     * @param string $id The HTML div's "id" attribute to set
2158
     * @param array  $contentList Array of left-center-right elements for the toolbar. If only 2 elements are defined, this becomes left-right (no center)
2159
     * @return string HTML div for the toolbar
2160
     */
2161
    public static function toolbarAction(string $id, array $contentList): string
2162
    {
2163
        $contentListPurged = array_filter($contentList);
2164
2165
        if (empty($contentListPurged)) {
2166
            return '';
2167
        }
2168
2169
        $count = count($contentList);
2170
2171
        $start = $contentList[0];
2172
        $center = '';
2173
        $end = '';
2174
2175
        if (2 === $count) {
2176
            $end = $contentList[1];
2177
        } elseif (3 === $count) {
2178
            $center = $contentList[1];
2179
            $end = $contentList[2];
2180
        }
2181
2182
2183
        return '<div id="'.$id.'" class="p-toolbar p-component flex items-center justify-between flex-wrap" role="toolbar">
2184
                <div class="p-toolbar-group-start p-toolbar-group-left">'.$start.'</div>
2185
                <div class="p-toolbar-group-center">'.$center.'</div>
2186
                <div class="p-toolbar-group-end p-toolbar-group-right">'.$end.'</div>
2187
            </div>
2188
        ';
2189
    }
2190
2191
    /**
2192
     * @param array  $content
2193
     * @param array  $colsWidth Optional. Columns width
2194
     *
2195
     * @return string
2196
     */
2197
    public static function toolbarGradeAction($content, $colsWidth = [])
2198
    {
2199
        $col = count($content);
2200
2201
        if (!$colsWidth) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $colsWidth of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2202
            $width = 8 / $col;
2203
            array_walk($content, function () use ($width, &$colsWidth) {
2204
                $colsWidth[] = $width;
2205
            });
2206
        }
2207
2208
        $html = '<div id="grade" class="p-toolbar p-component flex items-center justify-between flex-wrap" role="toolbar">';
2209
        for ($i = 0; $i < $col; $i++) {
2210
            $class = 'col-sm-'.$colsWidth[$i];
2211
            if ($col > 1) {
2212
                if ($i > 0 && $i < count($content) - 1) {
2213
                    $class .= ' text-center';
2214
                } elseif ($i === count($content) - 1) {
2215
                    $class .= ' text-right';
2216
                }
2217
            }
2218
            $html .= '<div class="'.$class.'">'.$content[$i].'</div>';
2219
        }
2220
        $html .= '</div>';
2221
2222
        return $html;
2223
    }
2224
2225
    /**
2226
     * Shortcut method to getMdiIcon, to be used from Twig (see ChamiloExtension.php)
2227
     * using acceptable default values
2228
     * @param string $name The icon name or a string representing the icon in our *Icon Enums
2229
     * @param int|null $size The icon size
2230
     * @param string|null $additionalClass Additional CSS class to add to the icon
2231
     * @param string|null $title A title for the icon
2232
     * @return string
2233
     * @throws InvalidArgumentException
2234
     * @throws ReflectionException
2235
     */
2236
    public static function getMdiIconSimple(
2237
        string $name,
2238
        ?int $size = ICON_SIZE_SMALL,
2239
        ?string $additionalClass = 'ch-tool-icon',
2240
        ?string $title = null
2241
    ): string
2242
    {
2243
        // If the string contains '::', we assume it is a reference to one of the icon Enum classes in src/CoreBundle/Component/Utils/
2244
        $matches = [];
2245
        if (preg_match('/(\w*)::(\w*)/', $name, $matches)) {
2246
            if (count($matches) != 3) {
2247
                throw new InvalidArgumentException('Invalid enum case string format. Expected format is "EnumClass::CASE".');
2248
            }
2249
            $enum = $matches[1];
2250
            $case = $matches[2];
2251
            if (!class_exists('Chamilo\CoreBundle\Component\Utils\\'.$enum)) {
2252
                throw new InvalidArgumentException("Class {$enum} does not exist.");
2253
            }
2254
            $reflection = new ReflectionEnum('Chamilo\CoreBundle\Component\Utils\\'.$enum);
2255
            // Check if the case exists in the Enum class
2256
            if (!$reflection->hasCase($case)) {
2257
                throw new InvalidArgumentException("Case {$case} does not exist in enum class {$enum}.");
2258
            }
2259
            // Get the Enum case
2260
            /* @var ReflectionEnumUnitCase $enumUnitCaseObject */
2261
            $enumUnitCaseObject = $reflection->getCase($case);
2262
            $enumValue = $enumUnitCaseObject->getValue();
2263
            $name = $enumValue->value;
2264
2265
        }
2266
        if (!empty($title)) {
2267
            $title = get_lang($title);
2268
        }
2269
2270
        return self::getMdiIcon($name, $additionalClass, null, $size, $title);
2271
    }
2272
2273
    /**
2274
     * Get a full HTML <i> tag for an icon from the Material Design Icons set
2275
     * @param string|ActionIcon|ToolIcon|ObjectIcon|StateIcon $name
2276
     * @param string|null                                     $additionalClass
2277
     * @param string|null                                     $style
2278
     * @param int|null                                        $pixelSize
2279
     * @param string|null                                     $title
2280
     * @param array|null                                      $additionalAttributes
2281
     * @return string
2282
     */
2283
    public static function getMdiIcon(string|ActionIcon|ToolIcon|ObjectIcon|StateIcon $name, string $additionalClass = null, string $style = null, int $pixelSize = null, string $title = null, array $additionalAttributes = null): string
2284
    {
2285
        $sizeString = '';
2286
        if (!empty($pixelSize)) {
2287
            $sizeString = 'font-size: '.$pixelSize.'px; width: '.$pixelSize.'px; height: '.$pixelSize.'px; ';
2288
        }
2289
        if (empty($style)) {
2290
            $style = '';
2291
        }
2292
2293
        $additionalAttributes['class'] = 'mdi mdi-';
2294
2295
        if ($name instanceof ActionIcon
2296
            || $name instanceof ToolIcon
2297
            || $name instanceof ObjectIcon
2298
            || $name instanceof StateIcon
2299
        ) {
2300
            $additionalAttributes['class'] .= $name->value;
2301
        } else {
2302
            $additionalAttributes['class'] .= $name;
2303
        }
2304
2305
        $additionalAttributes['class'] .= " $additionalClass";
2306
        $additionalAttributes['style'] = $sizeString.$style;
2307
        $additionalAttributes['aria-hidden'] = 'true';
2308
2309
        if (!empty($title)) {
2310
            $additionalAttributes['title'] = htmlentities($title);
2311
        }
2312
2313
        return self::tag(
2314
            'i',
2315
            '',
2316
            $additionalAttributes
2317
        );
2318
    }
2319
2320
    /**
2321
     * Get a HTML code for a icon by Font Awesome.
2322
     *
2323
     * @param string     $name            The icon name. Example: "mail-reply"
2324
     * @param int|string $size            Optional. The size for the icon. (Example: lg, 2, 3, 4, 5)
2325
     * @param bool       $fixWidth        Optional. Whether add the fw class
2326
     * @param string     $additionalClass Optional. Additional class
2327
     *
2328
     * @return string
2329
     * @deprecated Use getMdiIcon() instead
2330
     */
2331
    public static function returnFontAwesomeIcon(
2332
        $name,
2333
        $size = '',
2334
        $fixWidth = false,
2335
        $additionalClass = ''
2336
    ) {
2337
        $className = "mdi mdi-$name";
2338
2339
        if ($fixWidth) {
2340
            $className .= ' fa-fw';
2341
        }
2342
2343
        switch ($size) {
2344
            case 'xs':
2345
            case 'sm':
2346
            case 'lg':
2347
                $className .= " fa-{$size}";
2348
                break;
2349
            case 2:
2350
            case 3:
2351
            case 4:
2352
            case 5:
2353
                $className .= " fa-{$size}x";
2354
                break;
2355
        }
2356
2357
        if (!empty($additionalClass)) {
2358
            $className .= " $additionalClass";
2359
        }
2360
2361
        $icon = self::tag('em', null, ['class' => $className]);
2362
2363
        return "$icon ";
2364
    }
2365
2366
    public static function returnPrimeIcon(
2367
        $name,
2368
        $size = '',
2369
        $fixWidth = false,
2370
        $additionalClass = ''
2371
    ) {
2372
        $className = "pi pi-$name";
2373
2374
        if ($fixWidth) {
2375
            $className .= ' pi-fw';
2376
        }
2377
2378
        if ($size) {
2379
            $className .= " pi-$size";
2380
        }
2381
2382
        if (!empty($additionalClass)) {
2383
            $className .= " $additionalClass";
2384
        }
2385
2386
        $icon = self::tag('i', null, ['class' => $className]);
2387
2388
        return "$icon ";
2389
    }
2390
2391
    /**
2392
     * @param string     $title
2393
     * @param string     $content
2394
     * @param null       $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
2395
     * @param array      $params
2396
     * @param null       $idAccordion
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $idAccordion is correct as it would always require null to be passed?
Loading history...
2397
     * @param null       $idCollapse
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $idCollapse is correct as it would always require null to be passed?
Loading history...
2398
     * @param bool|true  $open
2399
     * @param bool|false $fullClickable
2400
     *
2401
     * @return string
2402
     *
2403
     * @todo rework function to easy use
2404
     */
2405
    public static function panelCollapse(
2406
        $title,
2407
        $content,
2408
        $id = null,
2409
        $params = [],
2410
        $idAccordion = null,
2411
        $idCollapse = null,
2412
        $open = true,
2413
        $fullClickable = false
2414
    ) {
2415
        $javascript = '';
2416
        if (!empty($idAccordion)) {
2417
            $javascript = '
2418
        <script>
2419
            document.addEventListener("DOMContentLoaded", function() {
2420
                const buttons = document.querySelectorAll("#card_'.$idAccordion.' a");
2421
                const menus = document.querySelectorAll("#collapse_'.$idAccordion.'");
2422
                buttons.forEach((button, index) => {
2423
                    button.addEventListener("click", function() {
2424
                        menus.forEach((menu, menuIndex) => {
2425
                            if (index === menuIndex) {
2426
                                menu.classList.toggle("active");
2427
                            } else {
2428
                                menu.classList.remove("active");
2429
                            }
2430
                        });
2431
                    });
2432
                });
2433
            });
2434
        </script>';
2435
            $html = '
2436
        <div class="mt-4 rounded-lg bg-gray-50 p-2">
2437
            <div class="px-4 bg-gray-100 border border-gray-50" id="card_'.$idAccordion.'">
2438
                <h5>
2439
                    <a role="button"
2440
                        class="cursor-pointer"
2441
                        data-toggle="collapse"
2442
                        data-target="#collapse_'.$idAccordion.'"
2443
                        aria-expanded="'.(($open) ? 'true' : 'false').'"
2444
                        aria-controls="collapse_'.$idAccordion.'"
2445
                    >
2446
                        '.$title.'
2447
                    </a>
2448
                </h5>
2449
            </div>
2450
            <div
2451
                id="collapse_'.$idAccordion.'"
2452
                class="px-4 border border-gray-50 bg-white collapse custom-collapse '.(($open) ? 'active' : '').'"
2453
            >
2454
                <div id="collapse_contant_'.$idAccordion.'"  class="card-body ">';
2455
2456
            $html .= $content;
2457
            $html .= '</div></div></div>';
2458
2459
        } else {
2460
            if (!empty($id)) {
2461
                $params['id'] = $id;
2462
            }
2463
            $params['class'] = 'v-card bg-white mx-2';
2464
            $html = '';
2465
            if (!empty($title)) {
2466
                $html .= '<div class="v-card-header text-h5 my-2">'.$title.'</div>'.PHP_EOL;
2467
            }
2468
            $html .= '<div class="v-card-text">'.$content.'</div>'.PHP_EOL;
2469
            $html = self::div($html, $params);
2470
        }
2471
2472
        return $javascript.$html;
2473
    }
2474
2475
    /**
2476
     * Returns the string "1 day ago" with a link showing the exact date time.
2477
     *
2478
     * @param string|DateTime $dateTime in UTC or a DateTime in UTC
2479
     *
2480
     * @throws Exception
2481
     *
2482
     * @return string
2483
     */
2484
    public static function dateToStringAgoAndLongDate(string|DateTime $dateTime): string
2485
    {
2486
        if (empty($dateTime) || '0000-00-00 00:00:00' === $dateTime) {
2487
            return '';
2488
        }
2489
2490
        if (is_string($dateTime)) {
2491
            $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));
2492
        }
2493
2494
        return self::tip(
2495
            date_to_str_ago($dateTime),
2496
            api_convert_and_format_date($dateTime, DATE_TIME_FORMAT_LONG)
2497
        );
2498
    }
2499
2500
    /**
2501
     * @param array  $userInfo
2502
     * @param string $status
2503
     * @param string $toolbar
2504
     *
2505
     * @return string
2506
     */
2507
    public static function getUserCard($userInfo, $status = '', $toolbar = '')
2508
    {
2509
        if (empty($userInfo)) {
2510
            return '';
2511
        }
2512
2513
        if (!empty($status)) {
2514
            $status = '<div class="items-user-status">'.$status.'</div>';
2515
        }
2516
2517
        if (!empty($toolbar)) {
2518
            $toolbar = '<div class="btn-group pull-right">'.$toolbar.'</div>';
2519
        }
2520
2521
        return '<div id="user_card_'.$userInfo['id'].'" class="card d-flex flex-row">
2522
                    <img src="'.$userInfo['avatar'].'" class="rounded" />
2523
                    <h3 class="card-title">'.$userInfo['complete_name'].'</h3>
2524
                    <div class="card-body">
2525
                       <div class="card-title">
2526
                       '.$status.'
2527
                       '.$toolbar.'
2528
                       </div>
2529
                    </div>
2530
                    <hr />
2531
              </div>';
2532
    }
2533
2534
    /**
2535
     * @param string $fileName
2536
     * @param string $fileUrl
2537
     *
2538
     * @return string
2539
     */
2540
    public static function fileHtmlGuesser($fileName, $fileUrl)
2541
    {
2542
        $data = pathinfo($fileName);
2543
2544
        //$content = self::url($data['basename'], $fileUrl);
2545
        $content = '';
2546
        switch ($data['extension']) {
2547
            case 'webm':
2548
            case 'mp4':
2549
            case 'ogg':
2550
                $content = '<video style="width: 400px; height:100%;" src="'.$fileUrl.'"></video>';
2551
                // Allows video to play when loading during an ajax call
2552
                $content .= "<script>jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer();</script>";
2553
                break;
2554
            case 'jpg':
2555
            case 'jpeg':
2556
            case 'gif':
2557
            case 'png':
2558
                $content = '<img class="img-responsive" src="'.$fileUrl.'" />';
2559
                break;
2560
            default:
2561
                //$html = self::url($data['basename'], $fileUrl);
2562
                break;
2563
        }
2564
        //$html = self::url($content, $fileUrl, ['ajax']);
2565
2566
        return $content;
2567
    }
2568
2569
    /**
2570
     * @param string $image
2571
     * @param int    $size
2572
     *
2573
     * @return string
2574
     */
2575
    public static function get_icon_path($image, $size = ICON_SIZE_SMALL)
2576
    {
2577
        return self::return_icon($image, '', [], $size, false, true);
2578
    }
2579
2580
    /**
2581
     * @param $id
2582
     *
2583
     * @return array|mixed
2584
     */
2585
    public static function randomColor($id)
2586
    {
2587
        static $colors = [];
2588
2589
        if (!empty($colors[$id])) {
2590
            return $colors[$id];
2591
        } else {
2592
            $color = substr(md5(time() * $id), 0, 6);
2593
            $c1 = hexdec(substr($color, 0, 2));
2594
            $c2 = hexdec(substr($color, 2, 2));
2595
            $c3 = hexdec(substr($color, 4, 2));
2596
            $luminosity = $c1 + $c2 + $c3;
2597
2598
            $type = '#000000';
2599
            if ($luminosity < (255 + 255 + 255) / 2) {
2600
                $type = '#FFFFFF';
2601
            }
2602
2603
            $result = [
2604
                'color' => '#'.$color,
2605
                'luminosity' => $type,
2606
            ];
2607
            $colors[$id] = $result;
2608
2609
            return $result; // example: #fc443a
2610
        }
2611
    }
2612
2613
    public static function noDataView(string $title, string $icon, string $buttonTitle, string $url): string
2614
    {
2615
        $content = '<div id="no-data-view">';
2616
        $content .= '<h3>'.$title.'</h3>';
2617
        $content .= $icon;
2618
        $content .= '<div class="controls">';
2619
        $content .= self::url(
2620
            '<em class="fa fa-plus"></em> '.$buttonTitle,
2621
            $url,
2622
            ['class' => 'btn btn--primary']
2623
        );
2624
        $content .= '</div>';
2625
        $content .= '</div>';
2626
2627
        return $content;
2628
    }
2629
2630
    public static function prose(string $contents): string
2631
    {
2632
        return "
2633
            <div class=''>
2634
                <div class='prose prose-blue'>
2635
                $contents
2636
                </div>
2637
            </div>
2638
            ";
2639
    }
2640
}
2641