Display::url()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 3
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
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...
6
use Chamilo\CoreBundle\Entity\ExtraFieldValues;
7
use Chamilo\CoreBundle\Enums\ActionIcon;
8
use Chamilo\CoreBundle\Enums\ObjectIcon;
9
use Chamilo\CoreBundle\Enums\StateIcon;
10
use Chamilo\CoreBundle\Enums\ToolIcon;
11
use Chamilo\CoreBundle\Framework\Container;
12
use Chamilo\CoreBundle\Helpers\ThemeHelper;
13
use ChamiloSession as Session;
14
use Symfony\Component\HttpFoundation\Response;
15
16
/**
17
 * Class Display
18
 * Contains several public functions dealing with the display of
19
 * table data, messages, help topics, ...
20
 *
21
 * Include/require it in your code to use its public functionality.
22
 * There are also several display public functions in the main api library.
23
 *
24
 * All public functions static public functions inside a class called Display,
25
 * so you use them like this: e.g.
26
 * Display::return_message($message)
27
 */
28
class Display
29
{
30
    /** @var Template */
31
    public static $global_template;
32
    public static $preview_style = null;
33
    public static $legacyTemplate;
34
35
    public function __construct()
36
    {
37
    }
38
39
    /**
40
     * @return array
41
     */
42
    public static function toolList()
43
    {
44
        return [
45
            'group',
46
            'work',
47
            'glossary',
48
            'forum',
49
            'course_description',
50
            'gradebook',
51
            'attendance',
52
            'course_progress',
53
            'notebook',
54
        ];
55
    }
56
57
    /**
58
     * Displays the page header.
59
     *
60
     * @param string The name of the page (will be showed in the page title)
61
     * @param string Optional help file name
62
     * @param string $page_header
63
     */
64
    public static function display_header(
65
        $tool_name = '',
66
        $help = null,
67
        $page_header = null
68
    ) {
69
        global $interbreadcrumb;
70
        $interbreadcrumb[] = ['url' => '#', 'name' => $tool_name];
71
72
        ob_start();
73
74
        return true;
75
    }
76
77
    /**
78
     * Displays the reduced page header (without banner).
79
     */
80
    public static function display_reduced_header()
81
    {
82
        ob_start();
83
        self::$legacyTemplate = '@ChamiloCore/Layout/no_layout.html.twig';
84
85
        return true;
86
    }
87
88
    /**
89
     * Display no header.
90
     */
91
    public static function display_no_header()
92
    {
93
        global $tool_name, $show_learnpath;
94
        self::$global_template = new Template(
95
            $tool_name,
96
            false,
97
            false,
98
            $show_learnpath
99
        );
100
    }
101
102
    /**
103
     * Display the page footer.
104
     */
105
    public static function display_footer()
106
    {
107
        $contents = ob_get_contents();
108
        if (ob_get_length()) {
109
            ob_end_clean();
110
        }
111
        $tpl = '@ChamiloCore/Layout/layout_one_col.html.twig';
112
        if (!empty(self::$legacyTemplate)) {
113
            $tpl = self::$legacyTemplate;
114
        }
115
        $response = new Response();
116
        $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...
117
        global $interbreadcrumb, $htmlHeadXtra;
118
119
        $courseInfo = api_get_course_info();
120
        if (!empty($courseInfo)) {
121
            $url = $courseInfo['course_public_url'];
122
            $sessionId = api_get_session_id();
123
            if (!empty($sessionId)) {
124
                $url .= '?sid='.$sessionId;
125
            }
126
127
            if (!empty($interbreadcrumb)) {
128
                array_unshift(
129
                    $interbreadcrumb,
130
                    ['name' => $courseInfo['title'], 'url' => $url]
131
                );
132
            }
133
        }
134
135
        if (empty($interbreadcrumb)) {
136
            $interbreadcrumb = [];
137
        } else {
138
            $interbreadcrumb = array_filter(
139
                $interbreadcrumb,
140
                function ($item) {
141
                    return isset($item['name']) && !empty($item['name']);
142
                }
143
            );
144
        }
145
146
        $params['legacy_javascript'] = $htmlHeadXtra;
147
        $params['legacy_breadcrumb'] = json_encode(array_values($interbreadcrumb));
148
149
        Template::setVueParams($params);
150
        $content = Container::getTwig()->render($tpl, $params);
151
        $response->setContent($content);
152
        $response->send();
153
        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...
154
    }
155
156
    /**
157
     * Display the page footer.
158
     */
159
    public static function display_reduced_footer()
160
    {
161
        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...
162
    }
163
164
    /**
165
     * Displays the tool introduction of a tool.
166
     *
167
     * @author Patrick Cool <[email protected]>, Ghent University
168
     *
169
     * @param string $tool          these are the constants that are used for indicating the tools
170
     * @param array  $editor_config Optional configuration settings for the online editor.
171
     *                              return: $tool return a string array list with the "define" in main_api.lib
172
     *
173
     * @return string html code for adding an introduction
174
     */
175
    public static function display_introduction_section(
176
        $tool,
177
        $editor_config = null
178
    ) {
179
        // @todo replace introduction section with a vue page.
180
        return;
181
    }
182
183
    /**
184
     * @param string $tool
185
     * @param array  $editor_config
186
     */
187
    public static function return_introduction_section(
188
        $tool,
189
        $editor_config = null
190
    ) {
191
    }
192
193
    /**
194
     * Displays a table.
195
     *
196
     * @param array  $header          Titles for the table header
197
     *                                each item in this array can contain 3 values
198
     *                                - 1st element: the column title
199
     *                                - 2nd element: true or false (column sortable?)
200
     *                                - 3th element: additional attributes for
201
     *                                th-tag (eg for column-width)
202
     *                                - 4the element: additional attributes for the td-tags
203
     * @param array  $content         2D-array with the tables content
204
     * @param array  $sorting_options Keys are:
205
     *                                'column' = The column to use as sort-key
206
     *                                'direction' = SORT_ASC or SORT_DESC
207
     * @param array  $paging_options  Keys are:
208
     *                                'per_page_default' = items per page when switching from
209
     *                                full-    list to per-page-view
210
     *                                'per_page' = number of items to show per page
211
     *                                'page_nr' = The page to display
212
     * @param array  $query_vars      Additional variables to add in the query-string
213
     * @param array  $form_actions
214
     * @param string $style           The style that the table will show. You can set 'table' or 'grid'
215
     * @param string $tableName
216
     * @param string $tableId
217
     *
218
     * @author [email protected]
219
     */
220
    public static function display_sortable_table(
221
        $header,
222
        $content,
223
        $sorting_options = [],
224
        $paging_options = [],
225
        $query_vars = null,
226
        $form_actions = [],
227
        $style = 'table',
228
        $tableName = 'tablename',
229
        $tableId = ''
230
    ) {
231
        $column = $sorting_options['column'] ?? 0;
232
        $default_items_per_page = $paging_options['per_page'] ?? 20;
233
        $table = new SortableTableFromArray($content, $column, $default_items_per_page, $tableName, null, $tableId);
234
        if (is_array($query_vars)) {
235
            $table->set_additional_parameters($query_vars);
236
        }
237
        if ('table' === $style) {
238
            if (is_array($header) && count($header) > 0) {
239
                foreach ($header as $index => $header_item) {
240
                    $table->set_header(
241
                        $index,
242
                        isset($header_item[0]) ? $header_item[0] : null,
243
                        isset($header_item[1]) ? $header_item[1] : null,
244
                        isset($header_item[2]) ? $header_item[2] : null,
245
                        isset($header_item[3]) ? $header_item[3] : null
246
                    );
247
                }
248
            }
249
            $table->set_form_actions($form_actions);
250
            $table->display();
251
        } else {
252
            $table->display_grid();
253
        }
254
    }
255
256
    /**
257
     * Returns an HTML table with sortable column (through complete page refresh).
258
     *
259
     * @param array  $header
260
     * @param array  $content         Array of row arrays
261
     * @param array  $sorting_options
262
     * @param array  $paging_options
263
     * @param array  $query_vars
264
     * @param array  $form_actions
265
     * @param string $style
266
     *
267
     * @return string HTML string for array
268
     */
269
    public static function return_sortable_table(
270
        $header,
271
        $content,
272
        $sorting_options = [],
273
        $paging_options = [],
274
        $query_vars = null,
275
        $form_actions = [],
276
        $style = 'table'
277
    ) {
278
        ob_start();
279
        self::display_sortable_table(
280
            $header,
281
            $content,
282
            $sorting_options,
283
            $paging_options,
284
            $query_vars,
285
            $form_actions,
286
            $style
287
        );
288
        $content = ob_get_contents();
289
        ob_end_clean();
290
291
        return $content;
292
    }
293
294
    /**
295
     * Shows a nice grid.
296
     *
297
     * @param string grid name (important to create css)
298
     * @param array header content
299
     * @param array array with the information to show
300
     * @param array $paging_options Keys are:
301
     *                              'per_page_default' = items per page when switching from
302
     *                              full-    list to per-page-view
303
     *                              'per_page' = number of items to show per page
304
     *                              'page_nr' = The page to display
305
     *                              'hide_navigation' =  true to hide the navigation
306
     * @param array $query_vars     Additional variables to add in the query-string
307
     * @param mixed An array with bool values to know which columns show.
308
     * i.e: $visibility_options= array(true, false) we will only show the first column
309
     *                Can be also only a bool value. TRUE: show all columns, FALSE: show nothing
310
     */
311
    public static function display_sortable_grid(
312
        $name,
313
        $header,
314
        $content,
315
        $paging_options = [],
316
        $query_vars = null,
317
        $form_actions = [],
318
        $visibility_options = true,
319
        $sort_data = true,
320
        $grid_class = []
321
    ) {
322
        echo self::return_sortable_grid(
323
            $name,
324
            $header,
325
            $content,
326
            $paging_options,
327
            $query_vars,
328
            $form_actions,
329
            $visibility_options,
330
            $sort_data,
331
            $grid_class
332
        );
333
    }
334
335
    /**
336
     * Gets a nice grid in html string.
337
     *
338
     * @param string grid name (important to create css)
339
     * @param array header content
340
     * @param array array with the information to show
341
     * @param array $paging_options Keys are:
342
     *                              'per_page_default' = items per page when switching from
343
     *                              full-    list to per-page-view
344
     *                              'per_page' = number of items to show per page
345
     *                              'page_nr' = The page to display
346
     *                              'hide_navigation' =  true to hide the navigation
347
     * @param array $query_vars     Additional variables to add in the query-string
348
     * @param mixed An array with bool values to know which columns show. i.e:
349
     *  $visibility_options= array(true, false) we will only show the first column
350
     *    Can be also only a bool value. TRUE: show all columns, FALSE: show nothing
351
     * @param bool  true for sorting data or false otherwise
352
     * @param array grid classes
353
     *
354
     * @return string html grid
355
     */
356
    public static function return_sortable_grid(
357
        $name,
358
        $header,
359
        $content,
360
        $paging_options = [],
361
        $query_vars = null,
362
        $form_actions = [],
363
        $visibility_options = true,
364
        $sort_data = true,
365
        $grid_class = [],
366
        $elementCount = 0
367
    ) {
368
        $column = 0;
369
        $default_items_per_page = $paging_options['per_page'] ?? 20;
370
        $table = new SortableTableFromArray($content, $column, $default_items_per_page, $name);
371
        $table->total_number_of_items = (int) $elementCount;
372
        if (is_array($query_vars)) {
373
            $table->set_additional_parameters($query_vars);
374
        }
375
376
        return $table->display_simple_grid(
377
            $visibility_options,
378
            $paging_options['hide_navigation'],
379
            $default_items_per_page,
380
            $sort_data,
381
            $grid_class
382
        );
383
    }
384
385
    /**
386
     * Displays a table with a special configuration.
387
     *
388
     * @param array $header          Titles for the table header
389
     *                               each item in this array can contain 3 values
390
     *                               - 1st element: the column title
391
     *                               - 2nd element: true or false (column sortable?)
392
     *                               - 3th element: additional attributes for th-tag (eg for column-width)
393
     *                               - 4the element: additional attributes for the td-tags
394
     * @param array $content         2D-array with the tables content
395
     * @param array $sorting_options Keys are:
396
     *                               'column' = The column to use as sort-key
397
     *                               'direction' = SORT_ASC or SORT_DESC
398
     * @param array $paging_options  Keys are:
399
     *                               'per_page_default' = items per page when switching from full list to per-page-view
400
     *                               'per_page' = number of items to show per page
401
     *                               'page_nr' = The page to display
402
     * @param array $query_vars      Additional variables to add in the query-string
403
     * @param array $column_show     Array of binaries 1= show columns 0. hide a column
404
     * @param array $column_order    An array of integers that let us decide how the columns are going to be sort.
405
     *                               i.e:  $column_order=array('1''4','3','4'); The 2nd column will be order like the 4th column
406
     * @param array $form_actions    Set optional forms actions
407
     *
408
     * @author Julio Montoya
409
     */
410
    public static function display_sortable_config_table(
411
        $table_name,
412
        $header,
413
        $content,
414
        $sorting_options = [],
415
        $paging_options = [],
416
        $query_vars = null,
417
        $column_show = [],
418
        $column_order = [],
419
        $form_actions = []
420
    ) {
421
        $column = isset($sorting_options['column']) ? $sorting_options['column'] : 0;
422
        $default_items_per_page = isset($paging_options['per_page']) ? $paging_options['per_page'] : 20;
423
424
        $table = new SortableTableFromArrayConfig(
425
            $content,
426
            $column,
427
            $default_items_per_page,
428
            $table_name,
429
            $column_show,
430
            $column_order
431
        );
432
433
        if (is_array($query_vars)) {
434
            $table->set_additional_parameters($query_vars);
435
        }
436
        // Show or hide the columns header
437
        if (is_array($column_show)) {
438
            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...
439
                if (!empty($column_show[$i])) {
440
                    $val0 = isset($header[$i][0]) ? $header[$i][0] : null;
441
                    $val1 = isset($header[$i][1]) ? $header[$i][1] : null;
442
                    $val2 = isset($header[$i][2]) ? $header[$i][2] : null;
443
                    $val3 = isset($header[$i][3]) ? $header[$i][3] : null;
444
                    $table->set_header($i, $val0, $val1, $val2, $val3);
445
                }
446
            }
447
        }
448
        $table->set_form_actions($form_actions);
449
        $table->display();
450
    }
451
452
    /**
453
     * Returns a div html string with.
454
     *
455
     * @param string $message
456
     * @param string $type    Example: confirm, normal, warning, error
457
     * @param bool $filter  Whether to XSS-filter or not
458
     *
459
     * @return string Message wrapped into an HTML div
460
     */
461
    public static function return_message(string $message, string $type = 'normal', bool $filter = true): string
462
    {
463
        if (empty($message)) {
464
            return '';
465
        }
466
467
        if ($filter) {
468
            $message = api_htmlentities(
469
                $message,
470
                ENT_QUOTES
471
            );
472
        }
473
474
        $class = '';
475
        switch ($type) {
476
            case 'warning':
477
                $class .= 'alert alert-warning';
478
                break;
479
            case 'error':
480
                $class .= 'alert alert-danger';
481
                break;
482
            case 'confirmation':
483
            case 'confirm':
484
            case 'success':
485
                $class .= 'alert alert-success';
486
                break;
487
            case 'normal':
488
            case 'info':
489
            default:
490
                $class .= 'alert alert-info';
491
        }
492
493
        return self::div($message, ['class' => $class]);
494
    }
495
496
    /**
497
     * Returns an encrypted mailto hyperlink.
498
     *
499
     * @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...
500
     * @param string  clickable text
501
     * @param string  optional, class from stylesheet
502
     * @param bool $addExtraContent
503
     *
504
     * @return string encrypted mailto hyperlink
505
     */
506
    public static function encrypted_mailto_link(
507
        $email,
508
        $clickable_text = null,
509
        $style_class = '',
510
        $addExtraContent = false
511
    ) {
512
        if (is_null($clickable_text)) {
513
            $clickable_text = $email;
514
        }
515
516
        // "mailto:" already present?
517
        if ('mailto:' !== substr($email, 0, 7)) {
518
            $email = 'mailto:'.$email;
519
        }
520
521
        // Class (stylesheet) defined?
522
        if ('' !== $style_class) {
523
            $style_class = ' class="'.$style_class.'"';
524
        }
525
526
        // Encrypt email
527
        $hmail = '';
528
        for ($i = 0; $i < strlen($email); $i++) {
529
            $hmail .= '&#'.ord($email[$i]).';';
530
        }
531
532
        $value = ('true' === api_get_setting('profile.add_user_course_information_in_mailto'));
533
534
        if ($value) {
535
            if ('false' === api_get_setting('allow_email_editor')) {
536
                $hmail .= '?';
537
            }
538
539
            if (!api_is_anonymous()) {
540
                $hmail .= '&subject='.Security::remove_XSS(api_get_setting('siteName'));
541
            }
542
            if ($addExtraContent) {
543
                $content = '';
544
                if (!api_is_anonymous()) {
545
                    $userInfo = api_get_user_info();
546
                    $content .= get_lang('User').': '.$userInfo['complete_name']."\n";
547
548
                    $courseInfo = api_get_course_info();
549
                    if (!empty($courseInfo)) {
550
                        $content .= get_lang('Course').': ';
551
                        $content .= $courseInfo['name'];
552
                        $sessionInfo = api_get_session_info(api_get_session_id());
553
                        if (!empty($sessionInfo)) {
554
                            $content .= ' '.$sessionInfo['name'].' <br />';
555
                        }
556
                    }
557
                }
558
                $hmail .= '&body='.rawurlencode($content);
559
            }
560
        }
561
562
        $hclickable_text = '';
563
        // Encrypt clickable text if @ is present
564
        if (strpos($clickable_text, '@')) {
565
            for ($i = 0; $i < strlen($clickable_text); $i++) {
566
                $hclickable_text .= '&#'.ord($clickable_text[$i]).';';
567
            }
568
        } else {
569
            $hclickable_text = @htmlspecialchars(
570
                $clickable_text,
571
                ENT_QUOTES,
572
                api_get_system_encoding()
573
            );
574
        }
575
        // Return encrypted mailto hyperlink
576
        return '<a href="'.$hmail.'"'.$style_class.' class="clickable_email_link">'.$hclickable_text.'</a>';
577
    }
578
579
    /**
580
     * Prints an <option>-list with all letters (A-Z).
581
     *
582
     * @todo This is English language specific implementation.
583
     * It should be adapted for the other languages.
584
     *
585
     * @return string
586
     */
587
    public static function get_alphabet_options($selectedLetter = '')
588
    {
589
        $result = '';
590
        for ($i = 65; $i <= 90; $i++) {
591
            $letter = chr($i);
592
            $result .= '<option value="'.$letter.'"';
593
            if ($selectedLetter == $letter) {
594
                $result .= ' selected="selected"';
595
            }
596
            $result .= '>'.$letter.'</option>';
597
        }
598
599
        return $result;
600
    }
601
602
    /**
603
     * Get the options withing a select box within the given values.
604
     *
605
     * @param int   Min value
606
     * @param int   Max value
607
     * @param int   Default value
608
     *
609
     * @return string HTML select options
610
     */
611
    public static function get_numeric_options($min, $max, $selected_num = 0)
612
    {
613
        $result = '';
614
        for ($i = $min; $i <= $max; $i++) {
615
            $result .= '<option value="'.$i.'"';
616
            if (is_int($selected_num)) {
617
                if ($selected_num == $i) {
618
                    $result .= ' selected="selected"';
619
                }
620
            }
621
            $result .= '>'.$i.'</option>';
622
        }
623
624
        return $result;
625
    }
626
627
    /**
628
     * Gets the path of an icon.
629
     *
630
     * @param string $icon
631
     * @param int    $size
632
     *
633
     * @return string
634
     */
635
    public static function returnIconPath($icon, $size = ICON_SIZE_SMALL)
636
    {
637
        return self::return_icon($icon, null, null, $size, null, true, false);
638
    }
639
640
    /**
641
     * This public function returns the htmlcode for an icon.
642
     *
643
     * @param string   The filename of the file (in the main/img/ folder
644
     * @param string   The alt text (probably a language variable)
645
     * @param array    Additional attributes (for instance height, width, onclick, ...)
646
     * @param int  The wanted width of the icon (to be looked for in the corresponding img/icons/ folder)
647
     *
648
     * @return string An HTML string of the right <img> tag
649
     *
650
     * @author Patrick Cool <[email protected]>, Ghent University 2006
651
     * @author Julio Montoya 2010 Function improved, adding image constants
652
     * @author Yannick Warnier 2011 Added size handler
653
     *
654
     * @version Feb 2011
655
     */
656
    public static function return_icon(
657
        string $image,
658
        ?string $alt_text = '',
659
        ?array $additional_attributes = [],
660
        ?int $size = ICON_SIZE_SMALL,
661
        ?bool $show_text = true,
662
        ?bool $return_only_path = false,
663
        ?bool $loadThemeIcon = true
664
    ) {
665
        $code_path = api_get_path(SYS_PUBLIC_PATH);
666
        $w_code_path = api_get_path(WEB_PUBLIC_PATH);
667
        // The following path is checked to see if the file exists. It's
668
        // important to use the public path (i.e. web/css/) rather than the
669
        // internal path (/app/Resource/public/css/) because the path used
670
        // in the end must be the public path
671
        $alternateCssPath = api_get_path(SYS_PUBLIC_PATH).'css/';
672
        $alternateWebCssPath = api_get_path(WEB_PUBLIC_PATH).'css/';
673
674
        // Avoid issues with illegal string offset for legacy calls to this
675
        // method with an empty string rather than null or an empty array
676
        if (empty($additional_attributes)) {
677
            $additional_attributes = [];
678
        }
679
680
        $image = trim($image);
681
682
        if (isset($size)) {
683
            $size = (int) $size;
684
        } else {
685
            $size = ICON_SIZE_SMALL;
686
        }
687
688
        $size_extra = $size.'/';
689
        $icon = $w_code_path.'img/'.$image;
690
        $theme = 'themes/chamilo/icons/';
691
692
        if ($loadThemeIcon) {
693
            // @todo with chamilo 2 code
694
            $theme = 'themes/'.api_get_visual_theme().'/icons/';
695
            if (is_file($alternateCssPath.$theme.$image)) {
696
                $icon = $alternateWebCssPath.$theme.$image;
697
            }
698
            // Checking the theme icons folder example: var/themes/chamilo/icons/XXX
699
            if (is_file($alternateCssPath.$theme.$size_extra.$image)) {
700
                $icon = $alternateWebCssPath.$theme.$size_extra.$image;
701
            } elseif (is_file($code_path.'img/icons/'.$size_extra.$image)) {
702
                //Checking the main/img/icons/XXX/ folder
703
                $icon = $w_code_path.'img/icons/'.$size_extra.$image;
704
            }
705
        } else {
706
            if (is_file($code_path.'img/icons/'.$size_extra.$image)) {
707
                // Checking the main/img/icons/XXX/ folder
708
                $icon = $w_code_path.'img/icons/'.$size_extra.$image;
709
            }
710
        }
711
712
        if ($return_only_path) {
713
            $svgImage = substr($image, 0, -3).'svg';
714
            if (is_file($code_path.$theme.'svg/'.$svgImage)) {
715
                $icon = $w_code_path.$theme.'svg/'.$svgImage;
716
            } elseif (is_file($code_path.'img/icons/svg/'.$svgImage)) {
717
                $icon = $w_code_path.'img/icons/svg/'.$svgImage;
718
            }
719
720
            if (empty($additional_attributes['height'])) {
721
                $additional_attributes['height'] = $size;
722
            }
723
            if (empty($additional_attributes['width'])) {
724
                $additional_attributes['width'] = $size;
725
            }
726
        }
727
728
        if ($return_only_path) {
729
            return $icon;
730
        }
731
732
        $img = self::img($icon, $alt_text, $additional_attributes);
733
        if (SHOW_TEXT_NEAR_ICONS && !empty($alt_text)) {
734
            if ($show_text) {
735
                $img = "$img $alt_text";
736
            }
737
        }
738
739
        return $img;
740
    }
741
742
    /**
743
     * Returns the HTML code for an image.
744
     *
745
     * @param string $image_path            the filename of the file (in the main/img/ folder
746
     * @param ?string $alt_text              the alt text (probably a language variable)
747
     * @param ?array  $additional_attributes (for instance, height, width, onclick, ...)
748
     * @param ?bool   $filterPath            Optional. Whether filter the image path. Default is true
749
     *
750
     * @return string
751
     *
752
     * @author Julio Montoya 2010
753
     */
754
    public static function img(
755
        string $image_path,
756
        ?string $alt_text = '',
757
        ?array $additional_attributes = null,
758
        ?bool $filterPath = true
759
    ): string {
760
        if (empty($image_path)) {
761
            return '';
762
        }
763
        // Sanitizing the parameter $image_path
764
        if ($filterPath) {
765
            $image_path = Security::filter_img_path($image_path);
766
        }
767
768
        // alt text = the image name if there is none provided (for XHTML compliance)
769
        if ('' == $alt_text) {
770
            $alt_text = basename($image_path);
771
        }
772
773
        if (empty($additional_attributes)) {
774
            $additional_attributes = [];
775
        }
776
777
        $additional_attributes['src'] = $image_path;
778
779
        if (empty($additional_attributes['alt'])) {
780
            $additional_attributes['alt'] = $alt_text;
781
        }
782
        if (empty($additional_attributes['title'])) {
783
            $additional_attributes['title'] = $alt_text;
784
        }
785
786
        return self::tag('img', '', $additional_attributes);
787
    }
788
789
    /**
790
     * Returns the HTML code for a tag (h3, h1, div, a, button), etc.
791
     *
792
     * @param string $tag                   the tag name
793
     * @param string $content               the tag's content
794
     * @param array  $additional_attributes (for instance, height, width, onclick, ...)
795
     *
796
     * @return string
797
     *
798
     * @author Julio Montoya 2010
799
     */
800
    public static function tag($tag, $content, $additional_attributes = [])
801
    {
802
        $attribute_list = '';
803
        // Managing the additional attributes
804
        if (!empty($additional_attributes) && is_array($additional_attributes)) {
805
            $attribute_list = '';
806
            foreach ($additional_attributes as $key => &$value) {
807
                $attribute_list .= $key.'="'.$value.'" ';
808
            }
809
        }
810
        //some tags don't have this </XXX>
811
        if (in_array($tag, ['img', 'input', 'br'])) {
812
            $return_value = '<'.$tag.' '.$attribute_list.' />';
813
        } else {
814
            $return_value = '<'.$tag.' '.$attribute_list.' >'.$content.'</'.$tag.'>';
815
        }
816
817
        return $return_value;
818
    }
819
820
    /**
821
     * Creates a URL anchor.
822
     *
823
     * @param string $name
824
     * @param ?string $url
825
     * @param ?array  $attributes
826
     *
827
     * @return string
828
     */
829
    public static function url(string $name, ?string $url, ?array $attributes = []): string
830
    {
831
        if (!empty($url)) {
832
            $url = preg_replace('#&amp;#', '&', $url);
833
            $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
834
            $attributes['href'] = $url;
835
        }
836
837
        return self::tag('a', $name, $attributes);
838
    }
839
840
    /**
841
     * Creates a div tag.
842
     *
843
     * @param string $content
844
     * @param ?array  $attributes
845
     *
846
     * @return string
847
     */
848
    public static function div(string $content, ?array $attributes = []): string
849
    {
850
        return self::tag('div', $content, $attributes);
851
    }
852
853
    /**
854
     * Creates a span tag.
855
     */
856
    public static function span(string $content, ?array $attributes = []): string
857
    {
858
        return self::tag('span', $content, $attributes);
859
    }
860
861
    /**
862
     * Displays an HTML input tag.
863
     */
864
    public static function input($type, $name, $value, $attributes = [])
865
    {
866
        if (isset($type)) {
867
            $attributes['type'] = $type;
868
        }
869
        if (isset($name)) {
870
            $attributes['name'] = $name;
871
        }
872
        if (isset($value)) {
873
            $attributes['value'] = $value;
874
        }
875
876
        if ('checkbox' === $type) {
877
            $attributes['class'] ??= 'p-checkbox-input';
878
        }
879
880
        if ('radio' === $type) {
881
            $attributes['class'] ??= 'p-radiobutton-input';
882
            $attributes['onchange'] = "$('input[name=\'".(str_replace(['[', ']'], ['\\[', '\\]'], $attributes['name']))."\']').parent().removeClass('p-radiobutton-checked'); this.parentElement.classList.add('p-radiobutton-checked');";
883
        }
884
885
        $inputBase = self::tag('input', '', $attributes);
886
887
        if ('checkbox' === $type) {
888
            $isChecked = isset($attributes['checked']) && 'checked' === $attributes['checked'];
889
            $componentCheckedClass = $isChecked ? 'p-checkbox-checked' : '';
890
891
            return <<<HTML
892
                <div class="p-checkbox p-component $componentCheckedClass">
893
                    $inputBase
894
                    <div class="p-checkbox-box">
895
                        <svg class="p-icon p-checkbox-icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
896
                            <path d="M4.86199 11.5948C4.78717 11.5923 4.71366 11.5745 4.64596 11.5426C4.57826 11.5107 4.51779 11.4652 4.46827 11.4091L0.753985 7.69483C0.683167 7.64891 0.623706 7.58751 0.580092 7.51525C0.536478 7.44299 0.509851 7.36177 0.502221 7.27771C0.49459 7.19366 0.506156 7.10897 0.536046 7.03004C0.565935 6.95111 0.613367 6.88 0.674759 6.82208C0.736151 6.76416 0.8099 6.72095 0.890436 6.69571C0.970973 6.67046 1.05619 6.66385 1.13966 6.67635C1.22313 6.68886 1.30266 6.72017 1.37226 6.76792C1.44186 6.81567 1.4997 6.8786 1.54141 6.95197L4.86199 10.2503L12.6397 2.49483C12.7444 2.42694 12.8689 2.39617 12.9932 2.40745C13.1174 2.41873 13.2343 2.47141 13.3251 2.55705C13.4159 2.64268 13.4753 2.75632 13.4938 2.87973C13.5123 3.00315 13.4888 3.1292 13.4271 3.23768L5.2557 11.4091C5.20618 11.4652 5.14571 11.5107 5.07801 11.5426C5.01031 11.5745 4.9368 11.5923 4.86199 11.5948Z" fill="currentColor"></path>
897
                        </svg>
898
                    </div>
899
                </div>
900
HTML;
901
        }
902
903
        if ('radio' === $type) {
904
            $isChecked = isset($attributes['checked']) && 'checked' === $attributes['checked'];
905
            $componentCheckedClass = $isChecked ? 'p-radiobutton-checked' : '';
906
907
            return <<<HTML
908
                <div class="p-radiobutton p-component $componentCheckedClass">
909
                    $inputBase
910
                    <div class="p-radiobutton-box" >
911
                        <div class="p-radiobutton-icon"></div>
912
                    </div>
913
                </div>
914
HTML;
915
916
        }
917
918
        if ('text' === $type) {
919
            $attributes['class'] = isset($attributes['class'])
920
                ? $attributes['class'].' p-inputtext p-component '
921
                : 'p-inputtext p-component ';
922
        }
923
924
        return self::tag('input', '', $attributes);
925
    }
926
927
    /**
928
     * Displays an HTML select tag.
929
     */
930
    public static function select(
931
        string $name,
932
        array $values,
933
        mixed $default = -1,
934
        array $extra_attributes = [],
935
        bool $show_blank_item = true,
936
        string $blank_item_text = ''
937
    ): string {
938
        $html = '';
939
        $extra = '';
940
        $default_id = 'id="'.$name.'" ';
941
        $extra_attributes = array_merge(
942
            ['class' => 'p-select p-component p-inputwrapper p-inputwrapper-filled'],
943
            $extra_attributes
944
        );
945
        foreach ($extra_attributes as $key => $parameter) {
946
            if ('id' == $key) {
947
                $default_id = '';
948
            }
949
            $extra .= $key.'="'.$parameter.'" ';
950
        }
951
        $html .= '<select name="'.$name.'" '.$default_id.' '.$extra.'>';
952
953
        if ($show_blank_item) {
954
            if (empty($blank_item_text)) {
955
                $blank_item_text = get_lang('Select');
956
            } else {
957
                $blank_item_text = Security::remove_XSS($blank_item_text);
958
            }
959
            $html .= self::tag(
960
                'option',
961
                '-- '.$blank_item_text.' --',
962
                ['value' => '-1']
963
            );
964
        }
965
        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...
966
            foreach ($values as $key => $value) {
967
                if (is_array($value) && isset($value['name'])) {
968
                    $value = $value['name'];
969
                }
970
                $html .= '<option value="'.$key.'"';
971
972
                if (is_array($default)) {
973
                    foreach ($default as $item) {
974
                        if ($item == $key) {
975
                            $html .= ' selected="selected"';
976
                            break;
977
                        }
978
                    }
979
                } else {
980
                    if ($default == $key) {
981
                        $html .= ' selected="selected"';
982
                    }
983
                }
984
985
                $html .= '>'.$value.'</option>';
986
            }
987
        }
988
        $html .= '</select>';
989
990
        return $html;
991
    }
992
993
    /**
994
     * @param $name
995
     * @param $value
996
     * @param array $attributes
997
     *
998
     * @return string
999
     */
1000
    public static function button($name, $value, $attributes = [])
1001
    {
1002
        if (!empty($name)) {
1003
            $attributes['name'] = $name;
1004
        }
1005
1006
        return self::tag('button', $value, $attributes);
1007
    }
1008
1009
    /**
1010
     * Creates a tab menu
1011
     * Requirements: declare the jquery, jquery-ui libraries + the jquery-ui.css
1012
     * in the $htmlHeadXtra variable before the display_header
1013
     * Add this script.
1014
     *
1015
     * @param array  $headers       list of the tab titles
1016
     * @param array  $items
1017
     * @param string $id            id of the container of the tab in the example "tabs"
1018
     * @param array  $attributes    for the ul
1019
     * @param array  $ul_attributes
1020
     * @param string $selected
1021
     *
1022
     * @return string
1023
     */
1024
    public static function tabs(
1025
        $headers,
1026
        $items,
1027
        $id = 'tabs',
1028
        $attributes = [],
1029
        $ul_attributes = [],
1030
        $selected = ''
1031
    ) {
1032
        if (empty($headers) || 0 === count($headers)) {
1033
            return '';
1034
        }
1035
1036
        // ---------------------------------------------------------------------
1037
        // Build tab headers
1038
        // ---------------------------------------------------------------------
1039
        $lis = '';
1040
        $i = 1;
1041
        foreach ($headers as $item) {
1042
            $isActive = (empty($selected) && 1 === $i) || (!empty($selected) && (int) $selected === $i);
1043
            $activeClass = $isActive ? ' active' : '';
1044
            $ariaSelected = $isActive ? 'true' : 'false';
1045
1046
            $item = self::tag(
1047
                'a',
1048
                $item,
1049
                [
1050
                    'href' => 'javascript:void(0)',
1051
                    'class' => 'nav-item nav-link text-primary'.$activeClass,
1052
                    '@click' => "openTab = $i",
1053
                    'id' => $id.$i.'-tab',
1054
                    'data-toggle' => 'tab',
1055
                    'role' => 'tab',
1056
                    'aria-controls' => $id.'-'.$i,
1057
                    'aria-selected' => $ariaSelected,
1058
                ]
1059
            );
1060
1061
            $lis .= $item;
1062
            $i++;
1063
        }
1064
1065
        $ul = self::tag(
1066
            'nav',
1067
            $lis,
1068
            [
1069
                'id' => 'nav_'.$id,
1070
                'class' => 'nav nav-tabs bg-white px-3 pt-3 border-bottom-0',
1071
                'role' => 'tablist',
1072
            ]
1073
        );
1074
1075
        // ---------------------------------------------------------------------
1076
        // Build tab contents
1077
        // ---------------------------------------------------------------------
1078
        $i = 1;
1079
        $divs = '';
1080
        foreach ($items as $content) {
1081
            $isActive = (empty($selected) && 1 === $i) || (!empty($selected) && (int) $selected === $i);
1082
            $panelClass = 'tab-panel';
1083
            if ($isActive) {
1084
                $panelClass .= ' is-active';
1085
            }
1086
1087
            $divs .= self::tag(
1088
                'div',
1089
                $content,
1090
                [
1091
                    'id' => $id.'-'.$i,
1092
                    'x-show' => "openTab === $i",
1093
                    'class' => $panelClass,
1094
                ]
1095
            );
1096
            $i++;
1097
        }
1098
1099
        // Wrapper for contents: white background, gray border, padding
1100
        $contentWrapper = self::tag(
1101
            'div',
1102
            $divs,
1103
            [
1104
                'class' => 'tab-content bg-white border border-gray-25 rounded-bottom px-3 py-3 mt-2',
1105
            ]
1106
        );
1107
1108
        // ---------------------------------------------------------------------
1109
        // Outer wrapper
1110
        // ---------------------------------------------------------------------
1111
        $attributes['id'] = (string) $id;
1112
        if (empty($attributes['class'])) {
1113
            $attributes['class'] = '';
1114
        }
1115
        // Shadow, rounded corners, small top margin
1116
        $attributes['class'] .= ' tab_wrapper shadow-sm rounded mt-3';
1117
1118
        $initialTab = !empty($selected) ? (int) $selected : 1;
1119
        $attributes['x-data'] = '{ openTab: '.$initialTab.' }';
1120
1121
        return self::tag(
1122
            'div',
1123
            $ul.$contentWrapper,
1124
            $attributes
1125
        );
1126
    }
1127
1128
    /**
1129
     * @param $headers
1130
     * @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...
1131
     *
1132
     * @return string
1133
     */
1134
    public static function tabsOnlyLink($headers, $selected = null, string $tabList = '')
1135
    {
1136
        $id = uniqid('tabs_');
1137
        $list = '';
1138
1139
        if ('integer' === gettype($selected)) {
1140
            $selected -= 1;
1141
        }
1142
1143
        foreach ($headers as $key => $item) {
1144
            $class = null;
1145
            if ($key == $selected) {
1146
                $class = 'active';
1147
            }
1148
            $item = self::tag(
1149
                'a',
1150
                $item['content'],
1151
                [
1152
                    'id' => $id.'-'.$key,
1153
                    'href' => $item['url'],
1154
                    'class' => 'nav-link '.$class,
1155
                ]
1156
            );
1157
            $list .= '<li class="nav-item">'.$item.'</li>';
1158
        }
1159
1160
        return self::div(
1161
            self::tag(
1162
                'ul',
1163
                $list,
1164
                ['class' => 'nav nav-tabs']
1165
            ),
1166
            ['class' => "ul-tablist $tabList"]
1167
        );
1168
    }
1169
1170
    /**
1171
     * In order to display a grid using jqgrid you have to:.
1172
     *
1173
     * @example
1174
     * After your Display::display_header function you have to add the nex javascript code:
1175
     * <script>
1176
     *   echo Display::grid_js('my_grid_name', $url,$columns, $column_model, $extra_params,[]);
1177
     *   // for more information of this function check the grid_js() function
1178
     * </script>
1179
     * //Then you have to call the grid_html
1180
     * echo Display::grid_html('my_grid_name');
1181
     * As you can see both function use the same "my_grid_name" this is very important otherwise nothing will work
1182
     *
1183
     * @param   string  the div id, this value must be the same with the first parameter of Display::grid_js()
1184
     *
1185
     * @return string html
1186
     */
1187
    public static function grid_html($div_id)
1188
    {
1189
        $table = self::tag('table', '', ['id' => $div_id]);
1190
        $table .= self::tag('div', '', ['id' => $div_id.'_pager']);
1191
1192
        return $table;
1193
    }
1194
1195
    /**
1196
     * This is a wrapper to use the jqgrid in Chamilo.
1197
     * For the other jqgrid options visit http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options
1198
     * This function need to be in the ready jquery function
1199
     * example --> $(function() { <?php echo Display::grid_js('grid' ...); ?> }
1200
     * In order to work this function needs the Display::grid_html function with the same div id.
1201
     *
1202
     * @param string $div_id       div id
1203
     * @param string $url          url where the jqgrid will ask for data (if datatype = json)
1204
     * @param array  $column_names Visible columns (you should use get_lang).
1205
     *                             An array in which we place the names of the columns.
1206
     *                             This is the text that appears in the head of the grid (Header layer).
1207
     *                             Example: colname   {name:'date',     index:'date',   width:120, align:'right'},
1208
     * @param array  $column_model the column model :  Array which describes the parameters of the columns.
1209
     *                             This is the most important part of the grid.
1210
     *                             For a full description of all valid values see colModel API. See the url above.
1211
     * @param array  $extra_params extra parameters
1212
     * @param array  $data         data that will be loaded
1213
     * @param string $formatter    A string that will be appended to the JSON returned
1214
     * @param bool   $fixed_width  not implemented yet
1215
     *
1216
     * @return string the js code
1217
     */
1218
    public static function grid_js(
1219
        $div_id,
1220
        $url,
1221
        $column_names,
1222
        $column_model,
1223
        $extra_params,
1224
        $data = [],
1225
        $formatter = '',
1226
        $fixed_width = false
1227
    ) {
1228
        $obj = new stdClass();
1229
        $obj->first = 'first';
1230
1231
        if (!empty($url)) {
1232
            $obj->url = $url;
1233
        }
1234
1235
        // Needed it in order to render the links/html in the grid
1236
        foreach ($column_model as &$columnModel) {
1237
            if (!isset($columnModel['formatter'])) {
1238
                $columnModel['formatter'] = '';
1239
            }
1240
        }
1241
1242
        //This line should only be used/modified in case of having characters
1243
        // encoding problems - see #6159
1244
        //$column_names = array_map("utf8_encode", $column_names);
1245
        $obj->colNames = $column_names;
1246
        $obj->colModel = $column_model;
1247
        $obj->pager = '#'.$div_id.'_pager';
1248
        $obj->datatype = 'json';
1249
        $obj->viewrecords = 'true';
1250
        $obj->guiStyle = 'bootstrap4';
1251
        $obj->iconSet = 'materialDesignIcons';
1252
        $all_value = 10000000;
1253
1254
        // Sets how many records we want to view in the grid
1255
        $obj->rowNum = 20;
1256
1257
        // Default row quantity
1258
        if (!isset($extra_params['rowList'])) {
1259
            $defaultRowList = [20, 50, 100, 500, 1000, $all_value];
1260
            $rowList = api_get_setting('display.table_row_list', true);
1261
            if (is_array($rowList) && isset($rowList['options']) && is_array($rowList['options'])) {
1262
                $rowList = $rowList['options'];
1263
                $rowList[] = $all_value;
1264
            } else {
1265
                $rowList = $defaultRowList;
1266
            }
1267
            $extra_params['rowList'] = $rowList;
1268
        }
1269
1270
        $defaultRow = (int) api_get_setting('display.table_default_row');
1271
        if ($defaultRow > 0) {
1272
            $obj->rowNum = $defaultRow;
1273
        }
1274
1275
        $json = '';
1276
        if (!empty($extra_params['datatype'])) {
1277
            $obj->datatype = $extra_params['datatype'];
1278
        }
1279
1280
        // Row even odd style.
1281
        $obj->altRows = true;
1282
        if (!empty($extra_params['altRows'])) {
1283
            $obj->altRows = $extra_params['altRows'];
1284
        }
1285
1286
        if (!empty($extra_params['sortname'])) {
1287
            $obj->sortname = $extra_params['sortname'];
1288
        }
1289
1290
        if (!empty($extra_params['sortorder'])) {
1291
            $obj->sortorder = $extra_params['sortorder'];
1292
        }
1293
1294
        if (!empty($extra_params['rowList'])) {
1295
            $obj->rowList = $extra_params['rowList'];
1296
        }
1297
1298
        if (!empty($extra_params['rowNum'])) {
1299
            $obj->rowNum = $extra_params['rowNum'];
1300
        } else {
1301
            // Try to load max rows from Session
1302
            $urlInfo = parse_url($url);
1303
            if (isset($urlInfo['query'])) {
1304
                parse_str($urlInfo['query'], $query);
1305
                if (isset($query['a'])) {
1306
                    $action = $query['a'];
1307
                    // This value is set in model.ajax.php
1308
                    $savedRows = Session::read('max_rows_'.$action);
1309
                    if (!empty($savedRows)) {
1310
                        $obj->rowNum = $savedRows;
1311
                    }
1312
                }
1313
            }
1314
        }
1315
1316
        if (!empty($extra_params['viewrecords'])) {
1317
            $obj->viewrecords = $extra_params['viewrecords'];
1318
        }
1319
1320
        $beforeSelectRow = null;
1321
        if (isset($extra_params['beforeSelectRow'])) {
1322
            $beforeSelectRow = 'beforeSelectRow: '.$extra_params['beforeSelectRow'].', ';
1323
            unset($extra_params['beforeSelectRow']);
1324
        }
1325
1326
        $beforeProcessing = '';
1327
        if (isset($extra_params['beforeProcessing'])) {
1328
            $beforeProcessing = 'beforeProcessing : function() { '.$extra_params['beforeProcessing'].' },';
1329
            unset($extra_params['beforeProcessing']);
1330
        }
1331
1332
        $beforeRequest = '';
1333
        if (isset($extra_params['beforeRequest'])) {
1334
            $beforeRequest = 'beforeRequest : function() { '.$extra_params['beforeRequest'].' },';
1335
            unset($extra_params['beforeRequest']);
1336
        }
1337
1338
        $gridComplete = '';
1339
        if (isset($extra_params['gridComplete'])) {
1340
            $gridComplete = 'gridComplete : function() { '.$extra_params['gridComplete'].' },';
1341
            unset($extra_params['gridComplete']);
1342
        }
1343
1344
        // Adding extra params
1345
        if (!empty($extra_params)) {
1346
            foreach ($extra_params as $key => $element) {
1347
                // the groupHeaders key gets a special treatment
1348
                if ('groupHeaders' != $key) {
1349
                    $obj->$key = $element;
1350
                }
1351
            }
1352
        }
1353
1354
        // Adding static data.
1355
        if (!empty($data)) {
1356
            $data_var = $div_id.'_data';
1357
            $json .= ' var '.$data_var.' = '.json_encode($data).';';
1358
            $obj->data = $data_var;
1359
            $obj->datatype = 'local';
1360
            $json .= "\n";
1361
        }
1362
1363
        $obj->end = 'end';
1364
1365
        $json_encode = json_encode($obj);
1366
1367
        if (!empty($data)) {
1368
            //Converts the "data":"js_variable" to "data":js_variable,
1369
            // otherwise it will not work
1370
            $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...
1371
        }
1372
1373
        // Fixing true/false js values that doesn't need the ""
1374
        $json_encode = str_replace(':"true"', ':true', $json_encode);
1375
        // wrap_cell is not a valid jqgrid attributes is a hack to wrap a text
1376
        $json_encode = str_replace('"wrap_cell":true', 'cellattr : function(rowId, value, rowObject, colModel, arrData) { return \'class = "jqgrid_whitespace"\'; }', $json_encode);
1377
        $json_encode = str_replace(':"false"', ':false', $json_encode);
1378
        $json_encode = str_replace('"formatter":"action_formatter"', 'formatter:action_formatter', $json_encode);
1379
        $json_encode = str_replace('"formatter":"extra_formatter"', 'formatter:extra_formatter', $json_encode);
1380
        $json_encode = str_replace(['{"first":"first",', '"end":"end"}'], '', $json_encode);
1381
1382
        if (('true' === api_get_setting('work.allow_compilatio_tool')) &&
1383
            (false !== strpos($_SERVER['REQUEST_URI'], 'work/work.php') ||
1384
             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...
1385
            )
1386
        ) {
1387
            $json_encode = str_replace('"function () { compilatioInit() }"',
1388
                'function () { compilatioInit() }',
1389
                $json_encode
1390
            );
1391
        }
1392
        // Creating the jqgrid element.
1393
        $json .= '$("#'.$div_id.'").jqGrid({';
1394
        $json .= "autowidth: true,";
1395
        //$json .= $beforeSelectRow;
1396
        $json .= $gridComplete;
1397
        $json .= $beforeProcessing;
1398
        $json .= $beforeRequest;
1399
        $json .= $json_encode;
1400
        $json .= '});';
1401
1402
        // Grouping headers option
1403
        if (isset($extra_params['groupHeaders'])) {
1404
            $groups = '';
1405
            foreach ($extra_params['groupHeaders'] as $group) {
1406
                //{ "startColumnName" : "courses", "numberOfColumns" : 1, "titleText" : "Order Info" },
1407
                $groups .= '{ "startColumnName" : "'.$group['startColumnName'].'", "numberOfColumns" : '.$group['numberOfColumns'].', "titleText" : "'.$group['titleText'].'" },';
1408
            }
1409
            $json .= '$("#'.$div_id.'").jqGrid("setGroupHeaders", {
1410
                "useColSpanStyle" : false,
1411
                "groupHeaders"    : [
1412
                    '.$groups.'
1413
                ]
1414
            });';
1415
        }
1416
1417
        $all_text = addslashes(get_lang('All'));
1418
        $json .= '$("'.$obj->pager.' option[value='.$all_value.']").text("'.$all_text.'");';
1419
        $json .= "\n";
1420
        // Adding edit/delete icons.
1421
        $json .= $formatter;
1422
1423
        return $json;
1424
    }
1425
1426
    /**
1427
     * @param array $headers
1428
     * @param array $rows
1429
     * @param array $attributes
1430
     *
1431
     * @return string
1432
     */
1433
    public static function table($headers, $rows, $attributes = [])
1434
    {
1435
        if (empty($attributes)) {
1436
            $attributes['class'] = 'data_table';
1437
        }
1438
        $table = new HTML_Table($attributes);
1439
        $row = 0;
1440
        $column = 0;
1441
1442
        // Course headers
1443
        if (!empty($headers)) {
1444
            foreach ($headers as $item) {
1445
                $table->setHeaderContents($row, $column, $item);
1446
                $column++;
1447
            }
1448
            $row = 1;
1449
            $column = 0;
1450
        }
1451
1452
        if (!empty($rows)) {
1453
            foreach ($rows as $content) {
1454
                $table->setCellContents($row, $column, $content);
1455
                $row++;
1456
            }
1457
        }
1458
1459
        return $table->toHtml();
1460
    }
1461
1462
    /**
1463
     * Get the session box details as an array.
1464
     *
1465
     * @todo check session visibility.
1466
     *
1467
     * @param int $session_id
1468
     *
1469
     * @return array Empty array or session array
1470
     *               ['title'=>'...','category'=>'','dates'=>'...','coach'=>'...','active'=>true/false,'session_category_id'=>int]
1471
     */
1472
    public static function getSessionTitleBox($session_id)
1473
    {
1474
        $session_info = api_get_session_info($session_id);
1475
        $generalCoachesNames = implode(
1476
            ' - ',
1477
            SessionManager::getGeneralCoachesNamesForSession($session_id)
1478
        );
1479
1480
        $session = [];
1481
        $session['category_id'] = $session_info['session_category_id'];
1482
        $session['title'] = $session_info['name'];
1483
        $session['dates'] = '';
1484
        $session['coach'] = '';
1485
        if ('true' === api_get_setting('show_session_coach') && $generalCoachesNames) {
1486
            $session['coach'] = get_lang('General coach').': '.$generalCoachesNames;
1487
        }
1488
        $active = false;
1489
        if (('0000-00-00 00:00:00' === $session_info['access_end_date'] &&
1490
            '0000-00-00 00:00:00' === $session_info['access_start_date']) ||
1491
            (empty($session_info['access_end_date']) && empty($session_info['access_start_date']))
1492
        ) {
1493
            if (isset($session_info['duration']) && !empty($session_info['duration'])) {
1494
                $daysLeft = SessionManager::getDayLeftInSession($session_info, api_get_user_id());
1495
                $session['duration'] = $daysLeft >= 0
1496
                    ? sprintf(get_lang('This session has a maximum duration. Only %s days to go.'), $daysLeft)
1497
                    : get_lang('You are already registered but your allowed access time has expired.');
1498
            }
1499
            $active = true;
1500
        } else {
1501
            $dates = SessionManager::parseSessionDates($session_info, true);
1502
            $session['dates'] = $dates['access'];
1503
            //$active = $date_start <= $now && $date_end >= $now;
1504
        }
1505
        $session['active'] = $active;
1506
        $session['session_category_id'] = $session_info['session_category_id'];
1507
        $session['visibility'] = $session_info['visibility'];
1508
        $session['num_users'] = $session_info['nbr_users'];
1509
        $session['num_courses'] = $session_info['nbr_courses'];
1510
        $session['description'] = $session_info['description'];
1511
        $session['show_description'] = $session_info['show_description'];
1512
        //$session['image'] = SessionManager::getSessionImage($session_info['id']);
1513
        $session['url'] = api_get_path(WEB_CODE_PATH).'session/index.php?session_id='.$session_info['id'];
1514
1515
        $entityManager = Database::getManager();
1516
        $fieldValuesRepo = $entityManager->getRepository(ExtraFieldValues::class);
1517
        $extraFieldValues = $fieldValuesRepo->getVisibleValues(
1518
            ExtraField::SESSION_FIELD_TYPE,
1519
            $session_id
1520
        );
1521
1522
        $session['extra_fields'] = [];
1523
        /** @var ExtraFieldValues $value */
1524
        foreach ($extraFieldValues as $value) {
1525
            if (empty($value)) {
1526
                continue;
1527
            }
1528
            $session['extra_fields'][] = [
1529
                'field' => [
1530
                    'variable' => $value->getField()->getVariable(),
1531
                    'display_text' => $value->getField()->getDisplayText(),
1532
                ],
1533
                'value' => $value->getFieldValue(),
1534
            ];
1535
        }
1536
1537
        return $session;
1538
    }
1539
1540
    /**
1541
     * Return the five star HTML.
1542
     *
1543
     * @param string $id              of the rating ul element
1544
     * @param string $url             that will be added (for jquery see hot_courses.tpl)
1545
     * @param array  $point_info      point info array see function CourseManager::get_course_ranking()
1546
     * @param bool   $add_div_wrapper add a div wrapper
1547
     *
1548
     * @return string
1549
     */
1550
    public static function return_rating_system(
1551
        $id,
1552
        $url,
1553
        $point_info = [],
1554
        $add_div_wrapper = true
1555
    ) {
1556
        $number_of_users_who_voted = isset($point_info['users_who_voted']) ? $point_info['users_who_voted'] : null;
1557
        $percentage = isset($point_info['point_average']) ? $point_info['point_average'] : 0;
1558
1559
        if (!empty($percentage)) {
1560
            $percentage = $percentage * 125 / 100;
1561
        }
1562
        $accesses = isset($point_info['accesses']) ? $point_info['accesses'] : 0;
1563
        $star_label = sprintf(get_lang('%s stars out of 5'), $point_info['point_average_star']);
1564
1565
        $html = '<section class="rating-widget">';
1566
        $html .= '<div class="rating-stars"><ul id="stars">';
1567
        $html .= '<li class="star" data-link="'.$url.'&amp;star=1" title="Poor" data-value="1"><i class="fa fa-star fa-fw"></i></li>
1568
                 <li class="star" data-link="'.$url.'&amp;star=2" title="Fair" data-value="2"><i class="fa fa-star fa-fw"></i></li>
1569
                 <li class="star" data-link="'.$url.'&amp;star=3" title="Good" data-value="3"><i class="fa fa-star fa-fw"></i></li>
1570
                 <li class="star" data-link="'.$url.'&amp;star=4" title="Excellent" data-value="4"><i class="fa fa-star fa-fw"></i></li>
1571
                 <li class="star" data-link="'.$url.'&amp;star=5" title="WOW!!!" data-value="5"><i class="fa fa-star fa-fw"></i></li>
1572
        ';
1573
        $html .= '</ul></div>';
1574
        $html .= '</section>';
1575
        $labels = [];
1576
1577
        $labels[] = 1 == $number_of_users_who_voted ? $number_of_users_who_voted.' '.get_lang('Vote') : $number_of_users_who_voted.' '.get_lang('Votes');
1578
        $labels[] = 1 == $accesses ? $accesses.' '.get_lang('Visit') : $accesses.' '.get_lang('Visits');
1579
        $labels[] = $point_info['user_vote'] ? get_lang('Your vote').' ['.$point_info['user_vote'].']' : get_lang('Your vote').' [?] ';
1580
1581
        if (!$add_div_wrapper && api_is_anonymous()) {
1582
            $labels[] = self::tag('span', get_lang('Login to vote'), ['class' => 'error']);
1583
        }
1584
1585
        $html .= self::div(implode(' | ', $labels), ['id' => 'vote_label_'.$id, 'class' => 'vote_label_info']);
1586
        $html .= ' '.self::span(' ', ['id' => 'vote_label2_'.$id]);
1587
1588
        if ($add_div_wrapper) {
1589
            $html = self::div($html, ['id' => 'rating_wrapper_'.$id]);
1590
        }
1591
1592
        return $html;
1593
    }
1594
1595
    /**
1596
     * @param string $title
1597
     * @param string $second_title
1598
     * @param string $size
1599
     * @param bool   $filter
1600
     *
1601
     * @return string
1602
     */
1603
    public static function page_header($title, $second_title = null, $size = 'h2', $filter = true)
1604
    {
1605
        if ($filter) {
1606
            $title = Security::remove_XSS($title);
1607
        }
1608
1609
        if (!empty($second_title)) {
1610
            if ($filter) {
1611
                $second_title = Security::remove_XSS($second_title);
1612
            }
1613
            $title .= "<small> $second_title</small>";
1614
        }
1615
1616
        return '<div class="page-header section-header mb-6"><'.$size.' class="section-header__title">'.$title.'</'.$size.'></div>';
1617
    }
1618
1619
    public static function page_header_and_translate($title, $second_title = null)
1620
    {
1621
        $title = get_lang($title);
1622
1623
        return self::page_header($title, $second_title);
1624
    }
1625
1626
    public static function page_subheader($title, $second_title = null, $size = 'h3', $attributes = [])
1627
    {
1628
        if (!empty($second_title)) {
1629
            $second_title = Security::remove_XSS($second_title);
1630
            $title .= "<small> $second_title<small>";
1631
        }
1632
        $subTitle = self::tag($size, Security::remove_XSS($title), $attributes);
1633
1634
        return $subTitle;
1635
    }
1636
1637
    public static function page_subheader2($title, $second_title = null)
1638
    {
1639
        return self::page_header($title, $second_title, 'h4');
1640
    }
1641
1642
    public static function page_subheader3($title, $second_title = null)
1643
    {
1644
        return self::page_header($title, $second_title, 'h5');
1645
    }
1646
1647
    public static function description(array $list): string
1648
    {
1649
        $html = '';
1650
        if (!empty($list)) {
1651
            $html = '<dl class="dl-horizontal">';
1652
            foreach ($list as $item) {
1653
                $html .= '<dt>'.$item['title'].'</dt>';
1654
                $html .= '<dd>'.$item['content'].'</dd>';
1655
            }
1656
            $html .= '</dl>';
1657
        }
1658
1659
        return $html;
1660
    }
1661
1662
    /**
1663
     * @param int    $percentage      int value between 0 and 100
1664
     * @param bool   $show_percentage
1665
     * @param string $extra_info
1666
     * @param string $class           danger/success/infowarning
1667
     *
1668
     * @return string
1669
     */
1670
    public static function bar_progress($percentage, $show_percentage = true, $extra_info = '', $class = '')
1671
    {
1672
        $percentage = (int) $percentage;
1673
        $class = empty($class) ? '' : "progress-bar-$class";
1674
1675
        $div = '<div class="progress">
1676
                <div
1677
                    class="progress-bar progress-bar-striped '.$class.'"
1678
                    role="progressbar"
1679
                    aria-valuenow="'.$percentage.'"
1680
                    aria-valuemin="0"
1681
                    aria-valuemax="100"
1682
                    style="width: '.$percentage.'%;"
1683
                >';
1684
        if ($show_percentage) {
1685
            $div .= $percentage.'%';
1686
        } else {
1687
            if (!empty($extra_info)) {
1688
                $div .= $extra_info;
1689
            }
1690
        }
1691
        $div .= '</div></div>';
1692
1693
        return $div;
1694
    }
1695
1696
    /**
1697
     * @param array $badge_list
1698
     *
1699
     * @return string
1700
     */
1701
    public static function badgeGroup($list)
1702
    {
1703
        $html = '<div class="badge-group">';
1704
        foreach ($list as $badge) {
1705
            $html .= $badge;
1706
        }
1707
        $html .= '</div>';
1708
1709
        return $html;
1710
    }
1711
1712
    /**
1713
     * Return an HTML span element with the badge class and an additional bg-$type class
1714
     */
1715
    public static function label(string $content, string $type = 'default'): string
1716
    {
1717
        $html = '';
1718
        if (!empty($content)) {
1719
            $class = match ($type) {
1720
                'success' => 'success',
1721
                'warning' => 'warning',
1722
                'important', 'danger', 'error' => 'error',
1723
                'info' => 'info',
1724
                'primary' => 'primary',
1725
                default => 'secondary',
1726
            };
1727
1728
            $html = '<span class="badge badge--'.$class.'">';
1729
            $html .= $content;
1730
            $html .= '</span>';
1731
        }
1732
1733
        return $html;
1734
    }
1735
1736
    public static function actions(array $items): string
1737
    {
1738
        if (empty($items)) {
1739
            return '';
1740
        }
1741
1742
        $links = '';
1743
        foreach ($items as $value) {
1744
            $attributes = $value['url_attributes'] ?? [];
1745
            $links .= self::url($value['content'], $value['url'], $attributes);
1746
        }
1747
1748
        return self::toolbarAction(uniqid('toolbar', false), [$links]);
1749
    }
1750
1751
    /**
1752
     * Prints a tooltip.
1753
     *
1754
     * @param string $text
1755
     * @param string $tip
1756
     *
1757
     * @return string
1758
     */
1759
    public static function tip($text, $tip)
1760
    {
1761
        if (empty($tip)) {
1762
            return $text;
1763
        }
1764
1765
        return self::span(
1766
            $text,
1767
            ['class' => 'boot-tooltip', 'title' => strip_tags($tip)]
1768
        );
1769
    }
1770
1771
    /**
1772
     * @param array $buttons
1773
     *
1774
     * @return string
1775
     */
1776
    public static function groupButton($buttons)
1777
    {
1778
        $html = '<div class="btn-group" role="group">';
1779
        foreach ($buttons as $button) {
1780
            $html .= $button;
1781
        }
1782
        $html .= '</div>';
1783
1784
        return $html;
1785
    }
1786
1787
    /**
1788
     * @todo use twig
1789
     *
1790
     * @param string $title
1791
     * @param array  $elements
1792
     * @param bool   $alignToRight
1793
     *
1794
     * @return string
1795
     */
1796
    public static function groupButtonWithDropDown($title, $elements, $alignToRight = false)
1797
    {
1798
        $id = uniqid('dropdown', false);
1799
        $html = '
1800
        <div class="dropdown inline-block relative">
1801
            <button
1802
                id="'.$id.'"
1803
                type="button"
1804
                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"
1805
                aria-expanded="false"
1806
                aria-haspopup="true"
1807
                onclick="document.querySelector(\'#'.$id.'_menu\').classList.toggle(\'hidden\')"
1808
            >
1809
              '.$title.'
1810
              <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">
1811
                <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" />
1812
              </svg>
1813
            </button>
1814
            <div
1815
                id="'.$id.'_menu"
1816
                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"
1817
                role="menu"
1818
                aria-orientation="vertical"
1819
                aria-labelledby="menu-button"
1820
                tabindex="-1"
1821
            >
1822
            <div class="py-1" role="none">';
1823
        foreach ($elements as $item) {
1824
            $html .= self::url(
1825
                    $item['title'],
1826
                    $item['href'],
1827
                    [
1828
                        'class' => 'text-gray-700 block px-4 py-2 text-sm',
1829
                        'role' => 'menuitem',
1830
                        'onclick' => $item['onclick'] ?? '',
1831
                        'data-action' => $item['data-action'] ?? '',
1832
                        'data-confirm' => $item['data-confirm'] ?? '',
1833
                    ]
1834
                );
1835
        }
1836
        $html .= '
1837
            </div>
1838
            </div>
1839
            </div>
1840
        ';
1841
1842
        return $html;
1843
    }
1844
1845
    /**
1846
     * @param string $file
1847
     * @param array  $params
1848
     *
1849
     * @return string|null
1850
     */
1851
    public static function getMediaPlayer($file, $params = [])
1852
    {
1853
        $fileInfo = pathinfo($file);
1854
1855
        $autoplay = isset($params['autoplay']) && 'true' === $params['autoplay'] ? 'autoplay' : '';
1856
        $id = isset($params['id']) ? $params['id'] : $fileInfo['basename'];
1857
        $width = isset($params['width']) ? 'width="'.$params['width'].'"' : null;
1858
        $class = isset($params['class']) ? ' class="'.$params['class'].'"' : null;
1859
1860
        switch ($fileInfo['extension']) {
1861
            case 'mp3':
1862
            case 'webm':
1863
                $html = '<audio id="'.$id.'" '.$class.' controls '.$autoplay.' '.$width.' src="'.$params['url'].'" >';
1864
                $html .= '<object width="'.$width.'" height="50" type="application/x-shockwave-flash" data="'.api_get_path(WEB_LIBRARY_PATH).'javascript/mediaelement/flashmediaelement.swf">
1865
                            <param name="movie" value="'.api_get_path(WEB_LIBRARY_PATH).'javascript/mediaelement/flashmediaelement.swf" />
1866
                            <param name="flashvars" value="controls=true&file='.$params['url'].'" />
1867
                          </object>';
1868
                $html .= '</audio>';
1869
1870
                return $html;
1871
                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...
1872
            case 'wav':
1873
            case 'ogg':
1874
                $html = '<audio width="300px" controls id="'.$id.'" '.$autoplay.' src="'.$params['url'].'" >';
1875
1876
                return $html;
1877
                break;
1878
        }
1879
1880
        return null;
1881
    }
1882
1883
    /**
1884
     * @param int    $nextValue
1885
     * @param array  $list
1886
     * @param int    $current
1887
     * @param int    $fixedValue
1888
     * @param array  $conditions
1889
     * @param string $link
1890
     * @param bool   $isMedia
1891
     * @param bool   $addHeaders
1892
     * @param array  $linkAttributes
1893
     *
1894
     * @return string
1895
     */
1896
    public static function progressPaginationBar(
1897
        $nextValue,
1898
        $list,
1899
        $current,
1900
        $fixedValue = null,
1901
        $conditions = [],
1902
        $link = null,
1903
        $isMedia = false,
1904
        $addHeaders = true,
1905
        $linkAttributes = []
1906
    ) {
1907
        if ($addHeaders) {
1908
            $pagination_size = 'pagination-mini';
1909
            $html = '<div class="exercise_pagination pagination '.$pagination_size.'"><ul>';
1910
        } else {
1911
            $html = null;
1912
        }
1913
        $affectAllItems = false;
1914
        if ($isMedia && isset($fixedValue) && ($nextValue + 1 == $current)) {
1915
            $affectAllItems = true;
1916
        }
1917
        $localCounter = 0;
1918
        foreach ($list as $itemId) {
1919
            $isCurrent = false;
1920
            if ($affectAllItems) {
1921
                $isCurrent = true;
1922
            } else {
1923
                if (!$isMedia) {
1924
                    $isCurrent = $current == ($localCounter + $nextValue + 1) ? true : false;
1925
                }
1926
            }
1927
            $html .= self::parsePaginationItem(
1928
                $itemId,
1929
                $isCurrent,
1930
                $conditions,
1931
                $link,
1932
                $nextValue,
1933
                $isMedia,
1934
                $localCounter,
1935
                $fixedValue,
1936
                $linkAttributes
1937
            );
1938
            $localCounter++;
1939
        }
1940
        if ($addHeaders) {
1941
            $html .= '</ul></div>';
1942
        }
1943
1944
        return $html;
1945
    }
1946
1947
    /**
1948
     * @param int    $itemId
1949
     * @param bool   $isCurrent
1950
     * @param array  $conditions
1951
     * @param string $link
1952
     * @param int    $nextValue
1953
     * @param bool   $isMedia
1954
     * @param int    $localCounter
1955
     * @param int    $fixedValue
1956
     * @param array  $linkAttributes
1957
     *
1958
     * @return string
1959
     */
1960
    public static function parsePaginationItem(
1961
        $itemId,
1962
        $isCurrent,
1963
        $conditions,
1964
        $link,
1965
        $nextValue = 0,
1966
        $isMedia = false,
1967
        $localCounter = null,
1968
        $fixedValue = null,
1969
        $linkAttributes = []
1970
    ) {
1971
        $defaultClass = 'before';
1972
        $class = $defaultClass;
1973
        foreach ($conditions as $condition) {
1974
            $array = isset($condition['items']) ? $condition['items'] : [];
1975
            $class_to_applied = $condition['class'];
1976
            $type = isset($condition['type']) ? $condition['type'] : 'positive';
1977
            $mode = isset($condition['mode']) ? $condition['mode'] : 'add';
1978
            switch ($type) {
1979
                case 'positive':
1980
                    if (in_array($itemId, $array)) {
1981
                        if ('overwrite' == $mode) {
1982
                            $class = " $defaultClass $class_to_applied";
1983
                        } else {
1984
                            $class .= " $class_to_applied";
1985
                        }
1986
                    }
1987
                    break;
1988
                case 'negative':
1989
                    if (!in_array($itemId, $array)) {
1990
                        if ('overwrite' == $mode) {
1991
                            $class = " $defaultClass $class_to_applied";
1992
                        } else {
1993
                            $class .= " $class_to_applied";
1994
                        }
1995
                    }
1996
                    break;
1997
            }
1998
        }
1999
        if ($isCurrent) {
2000
            $class = 'before current';
2001
        }
2002
        if ($isMedia && $isCurrent) {
2003
            $class = 'before current';
2004
        }
2005
        if (empty($link)) {
2006
            $link_to_show = '#';
2007
        } else {
2008
            $link_to_show = $link.($nextValue + $localCounter);
2009
        }
2010
        $label = $nextValue + $localCounter + 1;
2011
        if ($isMedia) {
2012
            $label = ($fixedValue + 1).' '.chr(97 + $localCounter);
2013
            $link_to_show = $link.$fixedValue.'#questionanchor'.$itemId;
2014
        }
2015
        $link = self::url($label.' ', $link_to_show, $linkAttributes);
2016
2017
        return '<li class = "'.$class.'">'.$link.'</li>';
2018
    }
2019
2020
    /**
2021
     * @param int $current
2022
     * @param int $total
2023
     *
2024
     * @return string
2025
     */
2026
    public static function paginationIndicator($current, $total)
2027
    {
2028
        $html = null;
2029
        if (!empty($current) && !empty($total)) {
2030
            $label = sprintf(get_lang('%s of %s'), $current, $total);
2031
            $html = self::url($label, '#', ['class' => 'btn disabled']);
2032
        }
2033
2034
        return $html;
2035
    }
2036
2037
    /**
2038
     * @param $url
2039
     * @param $currentPage
2040
     * @param $pagesCount
2041
     * @param $totalItems
2042
     *
2043
     * @return string
2044
     */
2045
    public static function getPagination($url, $currentPage, $pagesCount, $totalItems)
2046
    {
2047
        $pagination = '';
2048
        if ($totalItems > 1 && $pagesCount > 1) {
2049
            $pagination .= '<ul class="pagination">';
2050
            for ($i = 0; $i < $pagesCount; $i++) {
2051
                $newPage = $i + 1;
2052
                if ($currentPage == $newPage) {
2053
                    $pagination .= '<li class="active"><a href="'.$url.'&page='.$newPage.'">'.$newPage.'</a></li>';
2054
                } else {
2055
                    $pagination .= '<li><a href="'.$url.'&page='.$newPage.'">'.$newPage.'</a></li>';
2056
                }
2057
            }
2058
            $pagination .= '</ul>';
2059
        }
2060
2061
        return $pagination;
2062
    }
2063
2064
    /**
2065
     * Adds a legacy message in the queue.
2066
     *
2067
     * @param string $message
2068
     */
2069
    public static function addFlash($message)
2070
    {
2071
        // Detect type of message.
2072
        $parts = preg_match('/alert-([a-z]*)/', $message, $matches);
2073
        $type = 'primary';
2074
        if ($parts && isset($matches[1]) && $matches[1]) {
2075
            $type = $matches[1];
2076
        }
2077
        // Detect legacy content of message.
2078
        $result = preg_match('/<div(.*?)\>(.*?)\<\/div>/s', $message, $matches);
2079
        if ($result && isset($matches[2])) {
2080
            Container::getSession()->getFlashBag()->add($type, $matches[2]);
2081
        }
2082
    }
2083
2084
    /**
2085
     * Get the profile edition link for a user.
2086
     *
2087
     * @param int  $userId  The user id
2088
     * @param bool $asAdmin Optional. Whether get the URL for the platform admin
2089
     *
2090
     * @return string The link
2091
     */
2092
    public static function getProfileEditionLink($userId, $asAdmin = false)
2093
    {
2094
        $editProfileUrl = api_get_path(WEB_CODE_PATH).'auth/profile.php';
2095
        if ($asAdmin) {
2096
            $editProfileUrl = api_get_path(WEB_CODE_PATH)."admin/user_edit.php?user_id=".intval($userId);
2097
        }
2098
2099
        return $editProfileUrl;
2100
    }
2101
2102
    /**
2103
     * Get the vCard for a user.
2104
     *
2105
     * @param int $userId The user id
2106
     *
2107
     * @return string *.*vcf file
2108
     */
2109
    public static function getVCardUserLink($userId)
2110
    {
2111
        return api_get_path(WEB_PATH).'main/social/vcard_export.php?userId='.intval($userId);
2112
    }
2113
2114
    /**
2115
     * @param string $content
2116
     * @param string $title
2117
     * @param string $footer
2118
     * @param string $type        primary|success|info|warning|danger
2119
     * @param string $extra
2120
     * @param string $id
2121
     * @param string $customColor
2122
     * @param string $rightAction
2123
     *
2124
     * @return string
2125
     */
2126
    public static function panel(
2127
        $content,
2128
        $title = '',
2129
        $footer = '',
2130
        $type = 'default',
2131
        $extra = '',
2132
        $id = '',
2133
        $customColor = '',
2134
        $rightAction = ''
2135
    ) {
2136
        $headerStyle = '';
2137
        if (!empty($customColor)) {
2138
            $headerStyle = 'style = "color: white; background-color: '.$customColor.'" ';
2139
        }
2140
2141
        $footer = !empty($footer) ? '<p class="card-text"><small class="text-muted">'.$footer.'</small></p>' : '';
2142
        $typeList = ['primary', 'success', 'info', 'warning', 'danger'];
2143
        $style = !in_array($type, $typeList) ? 'default' : $type;
2144
2145
        if (!empty($id)) {
2146
            $id = " id='$id'";
2147
        }
2148
        $cardBody = $title.' '.self::contentPanel($content).' '.$footer;
2149
2150
        return "
2151
            <div $id class=card>
2152
                <div class='flex justify-between items-center py-2'>
2153
                    <div class='relative mt-1 flex'>
2154
                        $title
2155
                    </div>
2156
                    <div>
2157
                        $rightAction
2158
                    </div>
2159
                </div>
2160
2161
                $content
2162
                $footer
2163
            </div>"
2164
        ;
2165
    }
2166
2167
    /**
2168
     * @param string $content
2169
     */
2170
    public static function contentPanel($content): string
2171
    {
2172
        if (empty($content)) {
2173
            return '';
2174
        }
2175
2176
        return '<div class="card-text">'.$content.'</div>';
2177
    }
2178
2179
    /**
2180
     * Get the button HTML with an Awesome Font icon.
2181
     *
2182
     * @param string $text        The button content
2183
     * @param string $url         The url to button
2184
     * @param string $icon        The Awesome Font class for icon
2185
     * @param string $type        Optional. The button Bootstrap class. Default 'default' class
2186
     * @param array  $attributes  The additional attributes
2187
     * @param bool   $includeText
2188
     *
2189
     * @return string The button HTML
2190
     */
2191
    public static function toolbarButton(
2192
        $text,
2193
        $url,
2194
        $icon = 'check',
2195
        $type = null,
2196
        array $attributes = [],
2197
        $includeText = true
2198
    ) {
2199
        $buttonClass = "btn btn--secondary-outline";
2200
        if (!empty($type)) {
2201
            $buttonClass = "btn btn--$type";
2202
        }
2203
        //$icon = self::tag('i', null, ['class' => "fa fa-$icon fa-fw", 'aria-hidden' => 'true']);
2204
        $icon = self::getMdiIcon($icon);
2205
        $attributes['class'] = isset($attributes['class']) ? "$buttonClass {$attributes['class']}" : $buttonClass;
2206
        $attributes['title'] = $attributes['title'] ?? $text;
2207
2208
        if (!$includeText) {
2209
            $text = '<span class="sr-only">'.$text.'</span>';
2210
        }
2211
2212
        return self::url("$icon $text", $url, $attributes);
2213
    }
2214
2215
    /**
2216
     * Generate an HTML "p-toolbar" div element with the given id attribute.
2217
     * @param string $id The HTML div's "id" attribute to set
2218
     * @param array  $contentList Array of left-center-right elements for the toolbar. If only 2 elements are defined, this becomes left-right (no center)
2219
     * @return string HTML div for the toolbar
2220
     */
2221
    public static function toolbarAction(string $id, array $contentList): string
2222
    {
2223
        $contentListPurged = array_filter($contentList);
2224
2225
        if (empty($contentListPurged)) {
2226
            return '';
2227
        }
2228
2229
        $count = count($contentList);
2230
2231
        $start = $contentList[0];
2232
        $center = '';
2233
        $end = '';
2234
2235
        if (2 === $count) {
2236
            $end = $contentList[1];
2237
        } elseif (3 === $count) {
2238
            $center = $contentList[1];
2239
            $end = $contentList[2];
2240
        }
2241
2242
        return '<div id="'.$id.'" class="toolbar-action p-toolbar p-component flex items-center justify-between flex-wrap w-full" role="toolbar">
2243
            <div class="p-toolbar-group-start p-toolbar-group-left">'.$start.'</div>
2244
            <div class="p-toolbar-group-center">'.$center.'</div>
2245
            <div class="p-toolbar-group-end p-toolbar-group-right">'.$end.'</div>
2246
        </div>';
2247
    }
2248
2249
    /**
2250
     * @param array  $content
2251
     * @param array  $colsWidth Optional. Columns width
2252
     *
2253
     * @return string
2254
     */
2255
    public static function toolbarGradeAction($content, $colsWidth = [])
2256
    {
2257
        $col = count($content);
2258
2259
        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...
2260
            $width = 8 / $col;
2261
            array_walk($content, function () use ($width, &$colsWidth) {
2262
                $colsWidth[] = $width;
2263
            });
2264
        }
2265
2266
        $html = '<div id="grade" class="p-toolbar p-component flex items-center justify-between flex-wrap" role="toolbar">';
2267
        for ($i = 0; $i < $col; $i++) {
2268
            $class = 'col-sm-'.$colsWidth[$i];
2269
            if ($col > 1) {
2270
                if ($i > 0 && $i < count($content) - 1) {
2271
                    $class .= ' text-center';
2272
                } elseif ($i === count($content) - 1) {
2273
                    $class .= ' text-right';
2274
                }
2275
            }
2276
            $html .= '<div class="'.$class.'">'.$content[$i].'</div>';
2277
        }
2278
        $html .= '</div>';
2279
2280
        return $html;
2281
    }
2282
2283
    /**
2284
     * The auto-translated title version of getMdiIconSimple()
2285
     * Shortcut method to getMdiIcon, to be used from Twig (see ChamiloExtension.php)
2286
     * using acceptable default values
2287
     * @param string $name The icon name or a string representing the icon in our *Icon Enums
2288
     * @param int|null $size The icon size
2289
     * @param string|null $additionalClass Additional CSS class to add to the icon
2290
     * @param string|null $title A title for the icon
2291
     * @return string
2292
     * @throws InvalidArgumentException
2293
     * @throws ReflectionException
2294
     */
2295
    public static function getMdiIconTranslate(
2296
        string $name,
2297
        ?int $size = ICON_SIZE_SMALL,
2298
        ?string $additionalClass = 'ch-tool-icon',
2299
        ?string $title = null
2300
    ): string
2301
    {
2302
        if (!empty($title)) {
2303
            $title = get_lang($title);
2304
        }
2305
2306
        return self::getMdiIconSimple($name, $size, $additionalClass, $title);
2307
    }
2308
    /**
2309
     * Shortcut method to getMdiIcon, to be used from Twig (see ChamiloExtension.php)
2310
     * using acceptable default values
2311
     * @param string $name The icon name or a string representing the icon in our *Icon Enums
2312
     * @param int|null $size The icon size
2313
     * @param string|null $additionalClass Additional CSS class to add to the icon
2314
     * @param string|null $title A title for the icon
2315
     * @return string
2316
     * @throws InvalidArgumentException
2317
     * @throws ReflectionException
2318
     */
2319
    public static function getMdiIconSimple(
2320
        string $name,
2321
        ?int $size = ICON_SIZE_SMALL,
2322
        ?string $additionalClass = 'ch-tool-icon',
2323
        ?string $title = null
2324
    ): string
2325
    {
2326
        // If the string contains '::', we assume it is a reference to one of the icon Enum classes in src/CoreBundle/Enums/
2327
        $matches = [];
2328
        if (preg_match('/(\w*)::(\w*)/', $name, $matches)) {
2329
            if (count($matches) != 3) {
2330
                throw new InvalidArgumentException('Invalid enum case string format. Expected format is "EnumClass::CASE".');
2331
            }
2332
            $enum = $matches[1];
2333
            $case = $matches[2];
2334
            if (!class_exists('Chamilo\CoreBundle\Enums\\'.$enum)) {
2335
                throw new InvalidArgumentException("Class {$enum} does not exist.");
2336
            }
2337
            $reflection = new ReflectionEnum('Chamilo\CoreBundle\Enums\\'.$enum);
2338
            // Check if the case exists in the Enum class
2339
            if (!$reflection->hasCase($case)) {
2340
                throw new InvalidArgumentException("Case {$case} does not exist in enum class {$enum}.");
2341
            }
2342
            // Get the Enum case
2343
            /* @var ReflectionEnumUnitCase $enumUnitCaseObject */
2344
            $enumUnitCaseObject = $reflection->getCase($case);
2345
            $enumValue = $enumUnitCaseObject->getValue();
2346
            $name = $enumValue->value;
2347
2348
        }
2349
2350
        return self::getMdiIcon($name, $additionalClass, null, $size, $title);
2351
    }
2352
2353
    /**
2354
     * Get a full HTML <i> tag for an icon from the Material Design Icons set
2355
     * @param string|ActionIcon|ToolIcon|ObjectIcon|StateIcon $name
2356
     * @param string|null                                     $additionalClass
2357
     * @param string|null                                     $style
2358
     * @param int|null                                        $pixelSize
2359
     * @param string|null                                     $title
2360
     * @param array|null                                      $additionalAttributes
2361
     * @return string
2362
     */
2363
    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
2364
    {
2365
        $sizeString = '';
2366
        if (!empty($pixelSize)) {
2367
            $sizeString = 'font-size: '.$pixelSize.'px; width: '.$pixelSize.'px; height: '.$pixelSize.'px; ';
2368
        }
2369
        if (empty($style)) {
2370
            $style = '';
2371
        }
2372
2373
        $additionalAttributes['class'] = 'mdi mdi-';
2374
2375
        if ($name instanceof ActionIcon
2376
            || $name instanceof ToolIcon
2377
            || $name instanceof ObjectIcon
2378
            || $name instanceof StateIcon
2379
        ) {
2380
            $additionalAttributes['class'] .= $name->value;
2381
        } else {
2382
            $additionalAttributes['class'] .= $name;
2383
        }
2384
2385
        $additionalAttributes['class'] .= " $additionalClass";
2386
        $additionalAttributes['style'] = $sizeString.$style;
2387
        $additionalAttributes['aria-hidden'] = 'true';
2388
2389
        if (!empty($title)) {
2390
            $additionalAttributes['title'] = htmlentities($title);
2391
        }
2392
2393
        return self::tag(
2394
            'i',
2395
            '',
2396
            $additionalAttributes
2397
        );
2398
    }
2399
2400
    /**
2401
     * Get a HTML code for a icon by Font Awesome.
2402
     *
2403
     * @param string     $name            The icon name. Example: "mail-reply"
2404
     * @param int|string $size            Optional. The size for the icon. (Example: lg, 2, 3, 4, 5)
2405
     * @param bool       $fixWidth        Optional. Whether add the fw class
2406
     * @param string     $additionalClass Optional. Additional class
2407
     *
2408
     * @return string
2409
     * @deprecated Use getMdiIcon() instead
2410
     */
2411
    public static function returnFontAwesomeIcon(
2412
        $name,
2413
        $size = '',
2414
        $fixWidth = false,
2415
        $additionalClass = ''
2416
    ) {
2417
        $className = "mdi mdi-$name";
2418
2419
        if ($fixWidth) {
2420
            $className .= ' fa-fw';
2421
        }
2422
2423
        switch ($size) {
2424
            case 'xs':
2425
            case 'sm':
2426
            case 'lg':
2427
                $className .= " fa-{$size}";
2428
                break;
2429
            case 2:
2430
            case 3:
2431
            case 4:
2432
            case 5:
2433
                $className .= " fa-{$size}x";
2434
                break;
2435
        }
2436
2437
        if (!empty($additionalClass)) {
2438
            $className .= " $additionalClass";
2439
        }
2440
2441
        $icon = self::tag('em', null, ['class' => $className]);
2442
2443
        return "$icon ";
2444
    }
2445
2446
    public static function returnPrimeIcon(
2447
        $name,
2448
        $size = '',
2449
        $fixWidth = false,
2450
        $additionalClass = ''
2451
    ) {
2452
        $className = "pi pi-$name";
2453
2454
        if ($fixWidth) {
2455
            $className .= ' pi-fw';
2456
        }
2457
2458
        if ($size) {
2459
            $className .= " pi-$size";
2460
        }
2461
2462
        if (!empty($additionalClass)) {
2463
            $className .= " $additionalClass";
2464
        }
2465
2466
        $icon = self::tag('i', null, ['class' => $className]);
2467
2468
        return "$icon ";
2469
    }
2470
2471
    /**
2472
     * @param string     $title
2473
     * @param string     $content
2474
     * @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...
2475
     * @param array      $params
2476
     * @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...
2477
     * @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...
2478
     * @param bool|true  $open
2479
     * @param bool|false $fullClickable
2480
     *
2481
     * @return string
2482
     *
2483
     * @todo rework function to easy use
2484
     */
2485
    public static function panelCollapse(
2486
        $title,
2487
        $content,
2488
        $id = null,
2489
        $params = [],
2490
        $idAccordion = null,
2491
        $idCollapse = null,
2492
        $open = true,
2493
        $fullClickable = false
2494
    ) {
2495
        $javascript = '';
2496
        if (!empty($idAccordion)) {
2497
            $javascript = '
2498
        <script>
2499
            document.addEventListener("DOMContentLoaded", function() {
2500
                const buttons = document.querySelectorAll("#card_'.$idAccordion.' a");
2501
                const menus = document.querySelectorAll("#collapse_'.$idAccordion.'");
2502
                buttons.forEach((button, index) => {
2503
                    button.addEventListener("click", function() {
2504
                        menus.forEach((menu, menuIndex) => {
2505
                            if (index === menuIndex) {
2506
                                button.setAttribute("aria-expanded", "true" === button.getAttribute("aria-expanded") ? "false" : "true")
2507
                                button.classList.toggle("mdi-chevron-down")
2508
                                button.classList.toggle("mdi-chevron-up")
2509
                                menu.classList.toggle("active");
2510
                            } else {
2511
                                menu.classList.remove("active");
2512
                            }
2513
                        });
2514
                    });
2515
                });
2516
            });
2517
        </script>';
2518
            $html = '
2519
        <div class="display-panel-collapse mb-2">
2520
            <div class="display-panel-collapse__header" id="card_'.$idAccordion.'">
2521
                <a role="button"
2522
                    class="mdi mdi-chevron-down"
2523
                    data-toggle="collapse"
2524
                    data-target="#collapse_'.$idAccordion.'"
2525
                    aria-expanded="'.(($open) ? 'true' : 'false').'"
2526
                    aria-controls="collapse_'.$idAccordion.'"
2527
                >
2528
                    '.$title.'
2529
                </a>
2530
            </div>
2531
            <div
2532
                id="collapse_'.$idAccordion.'"
2533
                class="display-panel-collapse__collapsible '.(($open) ? 'active' : '').'"
2534
            >
2535
                <div id="collapse_contant_'.$idAccordion.'">';
2536
2537
            $html .= $content;
2538
            $html .= '</div></div></div>';
2539
2540
        } else {
2541
            if (!empty($id)) {
2542
                $params['id'] = $id;
2543
            }
2544
            $params['class'] = 'v-card bg-white mx-2';
2545
            $html = '';
2546
            if (!empty($title)) {
2547
                $html .= '<div class="v-card-header text-h5 my-2">'.$title.'</div>'.PHP_EOL;
2548
            }
2549
            $html .= '<div class="v-card-text">'.$content.'</div>'.PHP_EOL;
2550
            $html = self::div($html, $params);
2551
        }
2552
2553
        return $javascript.$html;
2554
    }
2555
2556
    /**
2557
     * Returns the string "1 day ago" with a link showing the exact date time.
2558
     *
2559
     * @param string|DateTime $dateTime in UTC or a DateTime in UTC
2560
     *
2561
     * @throws Exception
2562
     *
2563
     * @return string
2564
     */
2565
    public static function dateToStringAgoAndLongDate(string|DateTime $dateTime): string
2566
    {
2567
        if (empty($dateTime) || '0000-00-00 00:00:00' === $dateTime) {
2568
            return '';
2569
        }
2570
2571
        if (is_string($dateTime)) {
2572
            $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));
2573
        }
2574
2575
        return self::tip(
2576
            date_to_str_ago($dateTime),
2577
            api_convert_and_format_date($dateTime, DATE_TIME_FORMAT_LONG)
2578
        );
2579
    }
2580
2581
    /**
2582
     * @param array  $userInfo
2583
     * @param string $status
2584
     * @param string $toolbar
2585
     *
2586
     * @return string
2587
     */
2588
    public static function getUserCard($userInfo, $status = '', $toolbar = '')
2589
    {
2590
        if (empty($userInfo)) {
2591
            return '';
2592
        }
2593
2594
        if (!empty($status)) {
2595
            $status = '<div class="items-user-status">'.$status.'</div>';
2596
        }
2597
2598
        if (!empty($toolbar)) {
2599
            $toolbar = '<div class="btn-group pull-right">'.$toolbar.'</div>';
2600
        }
2601
2602
        return '<div id="user_card_'.$userInfo['id'].'" class="card d-flex flex-row">
2603
                    <img src="'.$userInfo['avatar'].'" class="rounded" />
2604
                    <h3 class="card-title">'.$userInfo['complete_name'].'</h3>
2605
                    <div class="card-body">
2606
                       <div class="card-title">
2607
                       '.$status.'
2608
                       '.$toolbar.'
2609
                       </div>
2610
                    </div>
2611
                    <hr />
2612
              </div>';
2613
    }
2614
2615
    /**
2616
     * @param string $fileName
2617
     * @param string $fileUrl
2618
     *
2619
     * @return string
2620
     */
2621
    public static function fileHtmlGuesser($fileName, $fileUrl)
2622
    {
2623
        $data = pathinfo($fileName);
2624
2625
        //$content = self::url($data['basename'], $fileUrl);
2626
        $content = '';
2627
        switch ($data['extension']) {
2628
            case 'webm':
2629
            case 'mp4':
2630
            case 'ogg':
2631
                $content = '<video style="width: 400px; height:100%;" src="'.$fileUrl.'"></video>';
2632
                // Allows video to play when loading during an ajax call
2633
                $content .= "<script>jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer();</script>";
2634
                break;
2635
            case 'jpg':
2636
            case 'jpeg':
2637
            case 'gif':
2638
            case 'png':
2639
                $content = '<img class="img-responsive" src="'.$fileUrl.'" />';
2640
                break;
2641
            default:
2642
                //$html = self::url($data['basename'], $fileUrl);
2643
                break;
2644
        }
2645
        //$html = self::url($content, $fileUrl, ['ajax']);
2646
2647
        return $content;
2648
    }
2649
2650
    /**
2651
     * @param string $image
2652
     * @param int    $size
2653
     *
2654
     * @return string
2655
     */
2656
    public static function get_icon_path($image, $size = ICON_SIZE_SMALL)
2657
    {
2658
        return self::return_icon($image, '', [], $size, false, true);
2659
    }
2660
2661
    /**
2662
     * @param $id
2663
     *
2664
     * @return array|mixed
2665
     */
2666
    public static function randomColor($id)
2667
    {
2668
        static $colors = [];
2669
2670
        if (!empty($colors[$id])) {
2671
            return $colors[$id];
2672
        } else {
2673
            $color = substr(md5(time() * $id), 0, 6);
2674
            $c1 = hexdec(substr($color, 0, 2));
2675
            $c2 = hexdec(substr($color, 2, 2));
2676
            $c3 = hexdec(substr($color, 4, 2));
2677
            $luminosity = $c1 + $c2 + $c3;
2678
2679
            $type = '#000000';
2680
            if ($luminosity < (255 + 255 + 255) / 2) {
2681
                $type = '#FFFFFF';
2682
            }
2683
2684
            $result = [
2685
                'color' => '#'.$color,
2686
                'luminosity' => $type,
2687
            ];
2688
            $colors[$id] = $result;
2689
2690
            return $result; // example: #fc443a
2691
        }
2692
    }
2693
2694
    public static function noDataView(string $title, string $icon, string $buttonTitle, string $url): string
2695
    {
2696
        $content = '<div id="no-data-view">';
2697
        $content .= '<h3>'.$title.'</h3>';
2698
        $content .= $icon;
2699
        $content .= '<div class="controls">';
2700
        $content .= self::url(
2701
            '<em class="fa fa-plus"></em> '.$buttonTitle,
2702
            $url,
2703
            ['class' => 'btn btn--primary']
2704
        );
2705
        $content .= '</div>';
2706
        $content .= '</div>';
2707
2708
        return $content;
2709
    }
2710
2711
    public static function prose(string $contents): string
2712
    {
2713
        return "
2714
    <div class='w-full my-8'>
2715
      <div class='prose prose-blue max-w-none px-6 py-4 bg-white rounded-lg shadow'>
2716
        $contents
2717
      </div>
2718
    </div>
2719
    ";
2720
    }
2721
2722
    public static function getFrameReadyBlock(
2723
        string $frameName,
2724
        string $itemType = '',
2725
        string $jsConditionalFunction = 'function () { return false; }'
2726
    ): string {
2727
2728
        if (in_array($itemType, ['link', 'sco', 'xapi', 'quiz', 'h5p', 'forum'])) {
2729
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the type-hinted return string.
Loading history...
2730
        }
2731
2732
        $themeHelper = Container::$container->get(ThemeHelper::class);
0 ignored issues
show
Bug introduced by
The method get() does not exist on null. ( Ignorable by Annotation )

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

2732
        /** @scrutinizer ignore-call */ 
2733
        $themeHelper = Container::$container->get(ThemeHelper::class);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2733
2734
        $themeColorsUrl = $themeHelper->getThemeAssetUrl('colors.css');
2735
2736
        $colorThemeItem = $themeColorsUrl
2737
            ? '{ type: "stylesheet", src: "'.$themeColorsUrl.'" },'
2738
            : '';
2739
2740
        return '$.frameReady(function() {},
2741
            "'.$frameName.'",
2742
            [
2743
                { type: "script", src: "/build/runtime.js" },
2744
                { type: "script", src: "/build/legacy_framereadyloader.js" },
2745
                { type: "stylesheet", src: "/build/legacy_framereadyloader.css" },
2746
                '.$colorThemeItem.'
2747
            ],
2748
            '.$jsConditionalFunction
2749
            .');';
2750
    }
2751
2752
    /**
2753
     * Renders and consumes messages stored with Display::addFlash().
2754
     * Returns HTML ready to inject into the view (alert-*).
2755
     *
2756
     * @return html string with all Flash messages (or '' if none)
2757
     */
2758
    public static function getFlash(): string
2759
    {
2760
        $html = '';
2761
2762
        try {
2763
            $flashBag = Container::getSession()->getFlashBag();
2764
            $all = $flashBag->all();
2765
2766
            foreach ($all as $type => $messages) {
2767
                switch ($type) {
2768
                    case 'success':
2769
                        $displayType = 'success';
2770
                        break;
2771
                    case 'warning':
2772
                        $displayType = 'warning';
2773
                        break;
2774
                    case 'danger':
2775
                    case 'error':
2776
                        $displayType = 'error';
2777
                        break;
2778
                    case 'info':
2779
                    case 'primary':
2780
                    default:
2781
                        $displayType = 'info';
2782
                        break;
2783
                }
2784
2785
                foreach ((array) $messages as $msg) {
2786
                    if (is_string($msg) && $msg !== '') {
2787
                        $html .= self::return_message($msg, $displayType, false);
2788
                    }
2789
                }
2790
            }
2791
        } catch (\Throwable $e) {
2792
            error_log('Display::getFlash error: '.$e->getMessage());
2793
        }
2794
2795
        return $html;
2796
    }
2797
2798
    /**
2799
     * Build the common MySpace / tracking menu (left part of the toolbar).
2800
     *
2801
     * $current can be:
2802
     *  - overview
2803
     *  - students
2804
     *  - teachers
2805
     *  - admin_view
2806
     *  - exams
2807
     *  - current_courses
2808
     *  - courses
2809
     *  - sessions
2810
     *  - company_reports
2811
     *  - company_reports_resumed
2812
     */
2813
    public static function mySpaceMenu(?string $current = 'overview'): string
2814
    {
2815
        $base = api_get_path(WEB_CODE_PATH).'my_space/';
2816
        $items = [];
2817
2818
        $isPlatformAdmin = api_is_platform_admin();
2819
        $isDrh = api_is_drh();
2820
        if ('yourstudents' === $current) {
2821
            $current = 'students';
2822
        }
2823
2824
        if ($isDrh) {
2825
            // DRH menu.
2826
            $items = [
2827
                'students' => [
2828
                    'icon' => ObjectIcon::USER,
2829
                    'title' => get_lang('Learners'),
2830
                    'url' => $base.'student.php',
2831
                ],
2832
                'teachers' => [
2833
                    'icon' => ObjectIcon::TEACHER,
2834
                    'title' => get_lang('Teachers'),
2835
                    'url' => $base.'teachers.php',
2836
                ],
2837
                'courses' => [
2838
                    'icon' => ObjectIcon::COURSE,
2839
                    'title' => get_lang('Courses'),
2840
                    'url' => $base.'course.php',
2841
                ],
2842
                'sessions' => [
2843
                    'icon' => ObjectIcon::SESSION,
2844
                    'title' => get_lang('Course sessions'),
2845
                    'url' => $base.'session.php',
2846
                ],
2847
                'company_reports' => [
2848
                    'icon' => ObjectIcon::REPORT,
2849
                    'title' => get_lang('Corporate report'),
2850
                    'url' => $base.'company_reports.php',
2851
                ],
2852
                'company_reports_resumed' => [
2853
                    'icon' => 'chart-box-outline',
2854
                    'title' => get_lang('Corporate report, short version'),
2855
                    'url' => $base.'company_reports_resumed.php',
2856
                ],
2857
            ];
2858
        } else {
2859
            // Teacher / platform admin menu.
2860
            $items = [
2861
                'overview' => [
2862
                    'icon' => 'chart-bar',
2863
                    'title' => get_lang('Global view'),
2864
                    'url' => $base.'index.php',
2865
                ],
2866
                'students' => [
2867
                    'icon' => 'account-star',
2868
                    'title' => get_lang('Learners'),
2869
                    'url' => $base.'student.php',
2870
                ],
2871
                'teachers' => [
2872
                    'icon' => 'human-male-board',
2873
                    'title' => get_lang('Teachers'),
2874
                    'url' => $base.'teachers.php',
2875
                ],
2876
            ];
2877
2878
            if ($isPlatformAdmin) {
2879
                $items['admin_view'] = [
2880
                    'icon' => 'star-outline',
2881
                    'title' => get_lang('Admin view'),
2882
                    'url' => $base.'admin_view.php',
2883
                ];
2884
                $items['exams'] = [
2885
                    'icon' => 'order-bool-ascending-variant',
2886
                    'title' => get_lang('Exam tracking'),
2887
                    'url' => api_get_path(WEB_CODE_PATH).'tracking/exams.php',
2888
                ];
2889
                $items['current_courses'] = [
2890
                    'icon' => 'book-open-page-variant',
2891
                    'title' => get_lang('Current courses report'),
2892
                    'url' => $base.'current_courses.php',
2893
                ];
2894
                $items['certificate_report'] = [
2895
                    'icon' => 'certificate',
2896
                    'title' => get_lang('See list of learner certificates'),
2897
                    'url' => api_get_path(WEB_CODE_PATH).'gradebook/certificate_report.php',
2898
                ];
2899
            }
2900
        }
2901
2902
        $html = '';
2903
2904
        foreach ($items as $name => $item) {
2905
            $iconClass = $name === $current ? 'ch-tool-icon-disabled' : 'ch-tool-icon';
2906
            $url = $name === $current ? '' : $item['url'];
2907
2908
            $html .= self::url(
2909
                self::getMdiIcon(
2910
                    $item['icon'],
2911
                    $iconClass,
2912
                    null,
2913
                    32,
2914
                    $item['title']
2915
                ),
2916
                $url
2917
            );
2918
        }
2919
2920
        return $html;
2921
    }
2922
2923
    /**
2924
     * Reusable collapsible wrapper for "advanced parameters" sections.
2925
     */
2926
    public static function advancedPanelStart(
2927
        string $id,
2928
        string $title,
2929
        bool $open = false,
2930
        array $options = []
2931
    ): string {
2932
        $safeId = preg_replace('/[^a-zA-Z0-9\-_:.]/', '_', $id);
2933
        $safeTitle = Security::remove_XSS($title);
2934
2935
        $openAttr = $open ? ' open' : '';
2936
2937
        $detailsClass = $options['details_class']
2938
            ?? 'display-advanced-panel mb-4';
2939
2940
        // Button-like summary, fit to text (no full width)
2941
        $summaryClass = $options['summary_class']
2942
            ?? 'inline-flex w-fit items-center gap-2 cursor-pointer select-none whitespace-nowrap
2943
            bg-primary text-white
2944
            border border-primary rounded px-3 py-2 shadow-sm font-semibold
2945
            hover:opacity-90 transition
2946
            focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2';
2947
2948
        $contentClass = $options['content_class']
2949
            ?? 'bg-white border border-gray-25 rounded mt-3 px-3 py-3';
2950
2951
        return '
2952
            <details id="'.$safeId.'" class="'.$detailsClass.'"'.$openAttr.'>
2953
              <summary class="'.$summaryClass.'">
2954
                <span class="display-advanced-panel__chevron" aria-hidden="true">▸</span>
2955
                <span>'.$safeTitle.'</span>
2956
              </summary>
2957
              <div class="'.$contentClass.'">
2958
            ';
2959
    }
2960
2961
    public static function advancedPanelEnd(): string
2962
    {
2963
        return '
2964
          </div>
2965
        </details>
2966
        ';
2967
    }
2968
2969
    /**
2970
     * Render a consistent "Subscribe users to this session" header + tabs layout
2971
     * reused across legacy admin pages (add users, usergroups, etc).
2972
     *
2973
     * $activeTab: users|classes|teachers|students
2974
     * $options:
2975
     *  - return_to: string (used to preserve the "Back" destination in the Classes tab)
2976
     *  - header_title: string (defaults to get_lang('Subscribe users to this session'))
2977
     *  - users_url/classes_url/teachers_url/students_url: override URLs if needed
2978
     *  - wrapper_class: string
2979
     *  - card_class: string
2980
     *  - tabs_bar_class: string
2981
     *  - content_class: string
2982
     */
2983
    public static function sessionSubscriptionPage(
2984
        int $sessionId,
2985
        string $sessionTitle,
2986
        string $backUrl,
2987
        string $activeTab,
2988
        string $contentHtml,
2989
        array $options = []
2990
    ): string {
2991
        $safeTitle = htmlspecialchars($sessionTitle, ENT_QUOTES, api_get_system_encoding());
2992
2993
        $headerTitle = $options['header_title'] ?? get_lang('Subscribe users to this session');
2994
2995
        $returnTo = (string) ($options['return_to'] ?? '');
2996
2997
        $usersUrl = $options['users_url']
2998
            ?? api_get_path(WEB_CODE_PATH).'session/add_users_to_session.php?id_session='.$sessionId.'&add=true';
2999
3000
        $classesUrl = $options['classes_url']
3001
            ?? api_get_path(WEB_CODE_PATH).'admin/usergroups.php?from_session='.$sessionId
3002
            .(!empty($returnTo) ? '&return_to='.rawurlencode($returnTo) : '');
3003
3004
        $teachersUrl = $options['teachers_url']
3005
            ?? api_get_path(WEB_CODE_PATH).'session/add_teachers_to_session.php?id='.$sessionId;
3006
3007
        $studentsUrl = $options['students_url']
3008
            ?? api_get_path(WEB_CODE_PATH).'session/add_students_to_session.php?id='.$sessionId;
3009
3010
        $wrapperClass = $options['wrapper_class'] ?? 'mx-auto w-full p-4 space-y-4';
3011
        $cardClass = $options['card_class'] ?? 'rounded-lg border border-gray-30 bg-white shadow-sm';
3012
        $tabsBarClass = $options['tabs_bar_class'] ?? 'flex flex-wrap items-center gap-2 border-b border-gray-20 px-3 py-2';
3013
        $contentClass = $options['content_class'] ?? 'p-4';
3014
3015
        $btnNeutral = 'inline-flex items-center gap-2 rounded-md border border-gray-30 bg-white px-3 py-1.5 text-sm font-medium text-gray-90 shadow-sm hover:bg-gray-10';
3016
3017
        $tabBase = 'inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm';
3018
        $tabActive = 'font-semibold bg-gray-10 text-gray-90';
3019
        $tabIdle = 'font-medium text-gray-90 hover:bg-gray-10';
3020
3021
        $tabs = [
3022
            'users' => [
3023
                'url' => $usersUrl,
3024
                'label' => get_lang('Users'),
3025
                'icon' => \Chamilo\CoreBundle\Enums\ObjectIcon::USER,
3026
            ],
3027
            'classes' => [
3028
                'url' => $classesUrl,
3029
                'label' => get_lang('Enrolment by classes'),
3030
                'icon' => \Chamilo\CoreBundle\Enums\ObjectIcon::MULTI_ELEMENT,
3031
            ],
3032
            'teachers' => [
3033
                'url' => $teachersUrl,
3034
                'label' => get_lang('Enroll trainers from existing sessions'),
3035
                'icon' => \Chamilo\CoreBundle\Enums\ObjectIcon::TEACHER,
3036
            ],
3037
            'students' => [
3038
                'url' => $studentsUrl,
3039
                'label' => get_lang('Enroll students from existing sessions'),
3040
                'icon' => \Chamilo\CoreBundle\Enums\ObjectIcon::USER,
3041
            ],
3042
        ];
3043
3044
        if (!isset($tabs[$activeTab])) {
3045
            $activeTab = 'users';
3046
        }
3047
3048
        $html = '';
3049
        $html .= '<div class="'.$wrapperClass.'">';
3050
3051
        // Header card
3052
        $html .= '  <div class="'.$cardClass.' p-4">';
3053
        $html .= '    <div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">';
3054
        $html .= '      <div class="min-w-0">';
3055
        $html .= '        <h1 class="text-lg font-semibold text-gray-90">'.htmlspecialchars($headerTitle, ENT_QUOTES, api_get_system_encoding()).'</h1>';
3056
        $html .= '        <p class="text-sm text-gray-50">'.$safeTitle.'</p>';
3057
        $html .= '      </div>';
3058
        $html .= '      <div class="flex items-center gap-2">';
3059
        $html .= '        <a href="'.htmlspecialchars($backUrl, ENT_QUOTES, api_get_system_encoding()).'" class="'.$btnNeutral.'">'.get_lang('Back').'</a>';
3060
        $html .= '      </div>';
3061
        $html .= '    </div>';
3062
        $html .= '  </div>';
3063
3064
        // Tabs + content in the SAME card (so it looks like real tabs)
3065
        $html .= '  <div class="'.$cardClass.'">';
3066
        $html .= '    <div class="'.$tabsBarClass.'">';
3067
3068
        foreach ($tabs as $key => $tab) {
3069
            $cls = $tabBase.' '.($key === $activeTab ? $tabActive : $tabIdle);
3070
            $ariaCurrent = $key === $activeTab ? ' aria-current="page"' : '';
3071
3072
            $html .= '      <a href="'.htmlspecialchars($tab['url'], ENT_QUOTES, api_get_system_encoding()).'" class="'.$cls.'"'.$ariaCurrent.'>';
3073
            $html .=            self::getMdiIcon($tab['icon'], 'ch-tool-icon', null, ICON_SIZE_SMALL, $tab['label']);
3074
            $html .= '        <span>'.$tab['label'].'</span>';
3075
            $html .= '      </a>';
3076
        }
3077
3078
        $html .= '    </div>';
3079
        $html .= '    <div class="'.$contentClass.'">'.$contentHtml.'</div>';
3080
        $html .= '  </div>';
3081
3082
        $html .= '</div>';
3083
3084
        return $html;
3085
    }
3086
}
3087