Display::display_introduction_section()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 6
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
        $inputBase = self::tag('input', '', $attributes);
881
882
        if ('checkbox' === $type) {
883
            $isChecked = isset($attributes['checked']) && 'checked' === $attributes['checked'];
884
            $compontentCheckedClass = $isChecked ? 'p-checkbox-checked' : '';
885
886
            return <<<HTML
887
                <div class="p-checkbox p-component $compontentCheckedClass">
888
                    $inputBase
889
                    <div class="p-checkbox-box">
890
                        <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">
891
                            <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>
892
                        </svg>
893
                    </div>
894
                </div>
895
HTML;
896
        }
897
898
        if ('text' === $type) {
899
            $attributes['class'] = isset($attributes['class'])
900
                ? $attributes['class'].' p-inputtext p-component '
901
                : 'p-inputtext p-component ';
902
        }
903
904
        return self::tag('input', '', $attributes);
905
    }
906
907
    /**
908
     * Displays an HTML select tag.
909
     */
910
    public static function select(
911
        string $name,
912
        array $values,
913
        mixed $default = -1,
914
        array $extra_attributes = [],
915
        bool $show_blank_item = true,
916
        string $blank_item_text = ''
917
    ): string {
918
        $html = '';
919
        $extra = '';
920
        $default_id = 'id="'.$name.'" ';
921
        $extra_attributes = array_merge(
922
            ['class' => 'p-select p-component p-inputwrapper p-inputwrapper-filled'],
923
            $extra_attributes
924
        );
925
        foreach ($extra_attributes as $key => $parameter) {
926
            if ('id' == $key) {
927
                $default_id = '';
928
            }
929
            $extra .= $key.'="'.$parameter.'" ';
930
        }
931
        $html .= '<select name="'.$name.'" '.$default_id.' '.$extra.'>';
932
933
        if ($show_blank_item) {
934
            if (empty($blank_item_text)) {
935
                $blank_item_text = get_lang('Select');
936
            } else {
937
                $blank_item_text = Security::remove_XSS($blank_item_text);
938
            }
939
            $html .= self::tag(
940
                'option',
941
                '-- '.$blank_item_text.' --',
942
                ['value' => '-1']
943
            );
944
        }
945
        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...
946
            foreach ($values as $key => $value) {
947
                if (is_array($value) && isset($value['name'])) {
948
                    $value = $value['name'];
949
                }
950
                $html .= '<option value="'.$key.'"';
951
952
                if (is_array($default)) {
953
                    foreach ($default as $item) {
954
                        if ($item == $key) {
955
                            $html .= ' selected="selected"';
956
                            break;
957
                        }
958
                    }
959
                } else {
960
                    if ($default == $key) {
961
                        $html .= ' selected="selected"';
962
                    }
963
                }
964
965
                $html .= '>'.$value.'</option>';
966
            }
967
        }
968
        $html .= '</select>';
969
970
        return $html;
971
    }
972
973
    /**
974
     * @param $name
975
     * @param $value
976
     * @param array $attributes
977
     *
978
     * @return string
979
     */
980
    public static function button($name, $value, $attributes = [])
981
    {
982
        if (!empty($name)) {
983
            $attributes['name'] = $name;
984
        }
985
986
        return self::tag('button', $value, $attributes);
987
    }
988
989
    /**
990
     * Creates a tab menu
991
     * Requirements: declare the jquery, jquery-ui libraries + the jquery-ui.css
992
     * in the $htmlHeadXtra variable before the display_header
993
     * Add this script.
994
     *
995
     * @param array  $headers       list of the tab titles
996
     * @param array  $items
997
     * @param string $id            id of the container of the tab in the example "tabs"
998
     * @param array  $attributes    for the ul
999
     * @param array  $ul_attributes
1000
     * @param string $selected
1001
     *
1002
     * @return string
1003
     */
1004
    public static function tabs(
1005
        $headers,
1006
        $items,
1007
        $id = 'tabs',
1008
        $attributes = [],
1009
        $ul_attributes = [],
1010
        $selected = ''
1011
    ) {
1012
        if (empty($headers) || 0 === count($headers)) {
1013
            return '';
1014
        }
1015
1016
        // ---------------------------------------------------------------------
1017
        // Build tab headers
1018
        // ---------------------------------------------------------------------
1019
        $lis = '';
1020
        $i = 1;
1021
        foreach ($headers as $item) {
1022
            $isActive = (empty($selected) && 1 === $i) || (!empty($selected) && (int) $selected === $i);
1023
            $activeClass = $isActive ? ' active' : '';
1024
            $ariaSelected = $isActive ? 'true' : 'false';
1025
1026
            $item = self::tag(
1027
                'a',
1028
                $item,
1029
                [
1030
                    'href' => 'javascript:void(0)',
1031
                    'class' => 'nav-item nav-link text-primary'.$activeClass,
1032
                    '@click' => "openTab = $i",
1033
                    'id' => $id.$i.'-tab',
1034
                    'data-toggle' => 'tab',
1035
                    'role' => 'tab',
1036
                    'aria-controls' => $id.'-'.$i,
1037
                    'aria-selected' => $ariaSelected,
1038
                ]
1039
            );
1040
1041
            $lis .= $item;
1042
            $i++;
1043
        }
1044
1045
        $ul = self::tag(
1046
            'nav',
1047
            $lis,
1048
            [
1049
                'id' => 'nav_'.$id,
1050
                'class' => 'nav nav-tabs bg-white px-3 pt-3 border-bottom-0',
1051
                'role' => 'tablist',
1052
            ]
1053
        );
1054
1055
        // ---------------------------------------------------------------------
1056
        // Build tab contents
1057
        // ---------------------------------------------------------------------
1058
        $i = 1;
1059
        $divs = '';
1060
        foreach ($items as $content) {
1061
            $isActive = (empty($selected) && 1 === $i) || (!empty($selected) && (int) $selected === $i);
1062
            $panelClass = 'tab-panel';
1063
            if ($isActive) {
1064
                $panelClass .= ' is-active';
1065
            }
1066
1067
            $divs .= self::tag(
1068
                'div',
1069
                $content,
1070
                [
1071
                    'id' => $id.'-'.$i,
1072
                    'x-show' => "openTab === $i",
1073
                    'class' => $panelClass,
1074
                ]
1075
            );
1076
            $i++;
1077
        }
1078
1079
        // Wrapper for contents: white background, gray border, padding
1080
        $contentWrapper = self::tag(
1081
            'div',
1082
            $divs,
1083
            [
1084
                'class' => 'tab-content bg-white border border-gray-25 rounded-bottom px-3 py-3 mt-2',
1085
            ]
1086
        );
1087
1088
        // ---------------------------------------------------------------------
1089
        // Outer wrapper
1090
        // ---------------------------------------------------------------------
1091
        $attributes['id'] = (string) $id;
1092
        if (empty($attributes['class'])) {
1093
            $attributes['class'] = '';
1094
        }
1095
        // Shadow, rounded corners, small top margin
1096
        $attributes['class'] .= ' tab_wrapper shadow-sm rounded mt-3';
1097
1098
        $initialTab = !empty($selected) ? (int) $selected : 1;
1099
        $attributes['x-data'] = '{ openTab: '.$initialTab.' }';
1100
1101
        return self::tag(
1102
            'div',
1103
            $ul.$contentWrapper,
1104
            $attributes
1105
        );
1106
    }
1107
1108
    /**
1109
     * @param $headers
1110
     * @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...
1111
     *
1112
     * @return string
1113
     */
1114
    public static function tabsOnlyLink($headers, $selected = null, string $tabList = '')
1115
    {
1116
        $id = uniqid('tabs_');
1117
        $list = '';
1118
1119
        if ('integer' === gettype($selected)) {
1120
            $selected -= 1;
1121
        }
1122
1123
        foreach ($headers as $key => $item) {
1124
            $class = null;
1125
            if ($key == $selected) {
1126
                $class = 'active';
1127
            }
1128
            $item = self::tag(
1129
                'a',
1130
                $item['content'],
1131
                [
1132
                    'id' => $id.'-'.$key,
1133
                    'href' => $item['url'],
1134
                    'class' => 'nav-link '.$class,
1135
                ]
1136
            );
1137
            $list .= '<li class="nav-item">'.$item.'</li>';
1138
        }
1139
1140
        return self::div(
1141
            self::tag(
1142
                'ul',
1143
                $list,
1144
                ['class' => 'nav nav-tabs']
1145
            ),
1146
            ['class' => "ul-tablist $tabList"]
1147
        );
1148
    }
1149
1150
    /**
1151
     * In order to display a grid using jqgrid you have to:.
1152
     *
1153
     * @example
1154
     * After your Display::display_header function you have to add the nex javascript code:
1155
     * <script>
1156
     *   echo Display::grid_js('my_grid_name', $url,$columns, $column_model, $extra_params,[]);
1157
     *   // for more information of this function check the grid_js() function
1158
     * </script>
1159
     * //Then you have to call the grid_html
1160
     * echo Display::grid_html('my_grid_name');
1161
     * As you can see both function use the same "my_grid_name" this is very important otherwise nothing will work
1162
     *
1163
     * @param   string  the div id, this value must be the same with the first parameter of Display::grid_js()
1164
     *
1165
     * @return string html
1166
     */
1167
    public static function grid_html($div_id)
1168
    {
1169
        $table = self::tag('table', '', ['id' => $div_id]);
1170
        $table .= self::tag('div', '', ['id' => $div_id.'_pager']);
1171
1172
        return $table;
1173
    }
1174
1175
    /**
1176
     * This is a wrapper to use the jqgrid in Chamilo.
1177
     * For the other jqgrid options visit http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options
1178
     * This function need to be in the ready jquery function
1179
     * example --> $(function() { <?php echo Display::grid_js('grid' ...); ?> }
1180
     * In order to work this function needs the Display::grid_html function with the same div id.
1181
     *
1182
     * @param string $div_id       div id
1183
     * @param string $url          url where the jqgrid will ask for data (if datatype = json)
1184
     * @param array  $column_names Visible columns (you should use get_lang).
1185
     *                             An array in which we place the names of the columns.
1186
     *                             This is the text that appears in the head of the grid (Header layer).
1187
     *                             Example: colname   {name:'date',     index:'date',   width:120, align:'right'},
1188
     * @param array  $column_model the column model :  Array which describes the parameters of the columns.
1189
     *                             This is the most important part of the grid.
1190
     *                             For a full description of all valid values see colModel API. See the url above.
1191
     * @param array  $extra_params extra parameters
1192
     * @param array  $data         data that will be loaded
1193
     * @param string $formatter    A string that will be appended to the JSON returned
1194
     * @param bool   $fixed_width  not implemented yet
1195
     *
1196
     * @return string the js code
1197
     */
1198
    public static function grid_js(
1199
        $div_id,
1200
        $url,
1201
        $column_names,
1202
        $column_model,
1203
        $extra_params,
1204
        $data = [],
1205
        $formatter = '',
1206
        $fixed_width = false
1207
    ) {
1208
        $obj = new stdClass();
1209
        $obj->first = 'first';
1210
1211
        if (!empty($url)) {
1212
            $obj->url = $url;
1213
        }
1214
1215
        // Needed it in order to render the links/html in the grid
1216
        foreach ($column_model as &$columnModel) {
1217
            if (!isset($columnModel['formatter'])) {
1218
                $columnModel['formatter'] = '';
1219
            }
1220
        }
1221
1222
        //This line should only be used/modified in case of having characters
1223
        // encoding problems - see #6159
1224
        //$column_names = array_map("utf8_encode", $column_names);
1225
        $obj->colNames = $column_names;
1226
        $obj->colModel = $column_model;
1227
        $obj->pager = '#'.$div_id.'_pager';
1228
        $obj->datatype = 'json';
1229
        $obj->viewrecords = 'true';
1230
        $obj->guiStyle = 'bootstrap4';
1231
        $obj->iconSet = 'materialDesignIcons';
1232
        $all_value = 10000000;
1233
1234
        // Sets how many records we want to view in the grid
1235
        $obj->rowNum = 20;
1236
1237
        // Default row quantity
1238
        if (!isset($extra_params['rowList'])) {
1239
            $defaultRowList = [20, 50, 100, 500, 1000, $all_value];
1240
            $rowList = api_get_setting('display.table_row_list', true);
1241
            if (is_array($rowList) && isset($rowList['options']) && is_array($rowList['options'])) {
1242
                $rowList = $rowList['options'];
1243
                $rowList[] = $all_value;
1244
            } else {
1245
                $rowList = $defaultRowList;
1246
            }
1247
            $extra_params['rowList'] = $rowList;
1248
        }
1249
1250
        $defaultRow = (int) api_get_setting('display.table_default_row');
1251
        if ($defaultRow > 0) {
1252
            $obj->rowNum = $defaultRow;
1253
        }
1254
1255
        $json = '';
1256
        if (!empty($extra_params['datatype'])) {
1257
            $obj->datatype = $extra_params['datatype'];
1258
        }
1259
1260
        // Row even odd style.
1261
        $obj->altRows = true;
1262
        if (!empty($extra_params['altRows'])) {
1263
            $obj->altRows = $extra_params['altRows'];
1264
        }
1265
1266
        if (!empty($extra_params['sortname'])) {
1267
            $obj->sortname = $extra_params['sortname'];
1268
        }
1269
1270
        if (!empty($extra_params['sortorder'])) {
1271
            $obj->sortorder = $extra_params['sortorder'];
1272
        }
1273
1274
        if (!empty($extra_params['rowList'])) {
1275
            $obj->rowList = $extra_params['rowList'];
1276
        }
1277
1278
        if (!empty($extra_params['rowNum'])) {
1279
            $obj->rowNum = $extra_params['rowNum'];
1280
        } else {
1281
            // Try to load max rows from Session
1282
            $urlInfo = parse_url($url);
1283
            if (isset($urlInfo['query'])) {
1284
                parse_str($urlInfo['query'], $query);
1285
                if (isset($query['a'])) {
1286
                    $action = $query['a'];
1287
                    // This value is set in model.ajax.php
1288
                    $savedRows = Session::read('max_rows_'.$action);
1289
                    if (!empty($savedRows)) {
1290
                        $obj->rowNum = $savedRows;
1291
                    }
1292
                }
1293
            }
1294
        }
1295
1296
        if (!empty($extra_params['viewrecords'])) {
1297
            $obj->viewrecords = $extra_params['viewrecords'];
1298
        }
1299
1300
        $beforeSelectRow = null;
1301
        if (isset($extra_params['beforeSelectRow'])) {
1302
            $beforeSelectRow = 'beforeSelectRow: '.$extra_params['beforeSelectRow'].', ';
1303
            unset($extra_params['beforeSelectRow']);
1304
        }
1305
1306
        $beforeProcessing = '';
1307
        if (isset($extra_params['beforeProcessing'])) {
1308
            $beforeProcessing = 'beforeProcessing : function() { '.$extra_params['beforeProcessing'].' },';
1309
            unset($extra_params['beforeProcessing']);
1310
        }
1311
1312
        $beforeRequest = '';
1313
        if (isset($extra_params['beforeRequest'])) {
1314
            $beforeRequest = 'beforeRequest : function() { '.$extra_params['beforeRequest'].' },';
1315
            unset($extra_params['beforeRequest']);
1316
        }
1317
1318
        $gridComplete = '';
1319
        if (isset($extra_params['gridComplete'])) {
1320
            $gridComplete = 'gridComplete : function() { '.$extra_params['gridComplete'].' },';
1321
            unset($extra_params['gridComplete']);
1322
        }
1323
1324
        // Adding extra params
1325
        if (!empty($extra_params)) {
1326
            foreach ($extra_params as $key => $element) {
1327
                // the groupHeaders key gets a special treatment
1328
                if ('groupHeaders' != $key) {
1329
                    $obj->$key = $element;
1330
                }
1331
            }
1332
        }
1333
1334
        // Adding static data.
1335
        if (!empty($data)) {
1336
            $data_var = $div_id.'_data';
1337
            $json .= ' var '.$data_var.' = '.json_encode($data).';';
1338
            $obj->data = $data_var;
1339
            $obj->datatype = 'local';
1340
            $json .= "\n";
1341
        }
1342
1343
        $obj->end = 'end';
1344
1345
        $json_encode = json_encode($obj);
1346
1347
        if (!empty($data)) {
1348
            //Converts the "data":"js_variable" to "data":js_variable,
1349
            // otherwise it will not work
1350
            $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...
1351
        }
1352
1353
        // Fixing true/false js values that doesn't need the ""
1354
        $json_encode = str_replace(':"true"', ':true', $json_encode);
1355
        // wrap_cell is not a valid jqgrid attributes is a hack to wrap a text
1356
        $json_encode = str_replace('"wrap_cell":true', 'cellattr : function(rowId, value, rowObject, colModel, arrData) { return \'class = "jqgrid_whitespace"\'; }', $json_encode);
1357
        $json_encode = str_replace(':"false"', ':false', $json_encode);
1358
        $json_encode = str_replace('"formatter":"action_formatter"', 'formatter:action_formatter', $json_encode);
1359
        $json_encode = str_replace('"formatter":"extra_formatter"', 'formatter:extra_formatter', $json_encode);
1360
        $json_encode = str_replace(['{"first":"first",', '"end":"end"}'], '', $json_encode);
1361
1362
        if (('true' === api_get_setting('work.allow_compilatio_tool')) &&
1363
            (false !== strpos($_SERVER['REQUEST_URI'], 'work/work.php') ||
1364
             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...
1365
            )
1366
        ) {
1367
            $json_encode = str_replace('"function () { compilatioInit() }"',
1368
                'function () { compilatioInit() }',
1369
                $json_encode
1370
            );
1371
        }
1372
        // Creating the jqgrid element.
1373
        $json .= '$("#'.$div_id.'").jqGrid({';
1374
        $json .= "autowidth: true,";
1375
        //$json .= $beforeSelectRow;
1376
        $json .= $gridComplete;
1377
        $json .= $beforeProcessing;
1378
        $json .= $beforeRequest;
1379
        $json .= $json_encode;
1380
        $json .= '});';
1381
1382
        // Grouping headers option
1383
        if (isset($extra_params['groupHeaders'])) {
1384
            $groups = '';
1385
            foreach ($extra_params['groupHeaders'] as $group) {
1386
                //{ "startColumnName" : "courses", "numberOfColumns" : 1, "titleText" : "Order Info" },
1387
                $groups .= '{ "startColumnName" : "'.$group['startColumnName'].'", "numberOfColumns" : '.$group['numberOfColumns'].', "titleText" : "'.$group['titleText'].'" },';
1388
            }
1389
            $json .= '$("#'.$div_id.'").jqGrid("setGroupHeaders", {
1390
                "useColSpanStyle" : false,
1391
                "groupHeaders"    : [
1392
                    '.$groups.'
1393
                ]
1394
            });';
1395
        }
1396
1397
        $all_text = addslashes(get_lang('All'));
1398
        $json .= '$("'.$obj->pager.' option[value='.$all_value.']").text("'.$all_text.'");';
1399
        $json .= "\n";
1400
        // Adding edit/delete icons.
1401
        $json .= $formatter;
1402
1403
        return $json;
1404
    }
1405
1406
    /**
1407
     * @param array $headers
1408
     * @param array $rows
1409
     * @param array $attributes
1410
     *
1411
     * @return string
1412
     */
1413
    public static function table($headers, $rows, $attributes = [])
1414
    {
1415
        if (empty($attributes)) {
1416
            $attributes['class'] = 'data_table';
1417
        }
1418
        $table = new HTML_Table($attributes);
1419
        $row = 0;
1420
        $column = 0;
1421
1422
        // Course headers
1423
        if (!empty($headers)) {
1424
            foreach ($headers as $item) {
1425
                $table->setHeaderContents($row, $column, $item);
1426
                $column++;
1427
            }
1428
            $row = 1;
1429
            $column = 0;
1430
        }
1431
1432
        if (!empty($rows)) {
1433
            foreach ($rows as $content) {
1434
                $table->setCellContents($row, $column, $content);
1435
                $row++;
1436
            }
1437
        }
1438
1439
        return $table->toHtml();
1440
    }
1441
1442
    /**
1443
     * Get the session box details as an array.
1444
     *
1445
     * @todo check session visibility.
1446
     *
1447
     * @param int $session_id
1448
     *
1449
     * @return array Empty array or session array
1450
     *               ['title'=>'...','category'=>'','dates'=>'...','coach'=>'...','active'=>true/false,'session_category_id'=>int]
1451
     */
1452
    public static function getSessionTitleBox($session_id)
1453
    {
1454
        $session_info = api_get_session_info($session_id);
1455
        $generalCoachesNames = implode(
1456
            ' - ',
1457
            SessionManager::getGeneralCoachesNamesForSession($session_id)
1458
        );
1459
1460
        $session = [];
1461
        $session['category_id'] = $session_info['session_category_id'];
1462
        $session['title'] = $session_info['name'];
1463
        $session['dates'] = '';
1464
        $session['coach'] = '';
1465
        if ('true' === api_get_setting('show_session_coach') && $generalCoachesNames) {
1466
            $session['coach'] = get_lang('General coach').': '.$generalCoachesNames;
1467
        }
1468
        $active = false;
1469
        if (('0000-00-00 00:00:00' === $session_info['access_end_date'] &&
1470
            '0000-00-00 00:00:00' === $session_info['access_start_date']) ||
1471
            (empty($session_info['access_end_date']) && empty($session_info['access_start_date']))
1472
        ) {
1473
            if (isset($session_info['duration']) && !empty($session_info['duration'])) {
1474
                $daysLeft = SessionManager::getDayLeftInSession($session_info, api_get_user_id());
1475
                $session['duration'] = $daysLeft >= 0
1476
                    ? sprintf(get_lang('This session has a maximum duration. Only %s days to go.'), $daysLeft)
1477
                    : get_lang('You are already registered but your allowed access time has expired.');
1478
            }
1479
            $active = true;
1480
        } else {
1481
            $dates = SessionManager::parseSessionDates($session_info, true);
1482
            $session['dates'] = $dates['access'];
1483
            //$active = $date_start <= $now && $date_end >= $now;
1484
        }
1485
        $session['active'] = $active;
1486
        $session['session_category_id'] = $session_info['session_category_id'];
1487
        $session['visibility'] = $session_info['visibility'];
1488
        $session['num_users'] = $session_info['nbr_users'];
1489
        $session['num_courses'] = $session_info['nbr_courses'];
1490
        $session['description'] = $session_info['description'];
1491
        $session['show_description'] = $session_info['show_description'];
1492
        //$session['image'] = SessionManager::getSessionImage($session_info['id']);
1493
        $session['url'] = api_get_path(WEB_CODE_PATH).'session/index.php?session_id='.$session_info['id'];
1494
1495
        $entityManager = Database::getManager();
1496
        $fieldValuesRepo = $entityManager->getRepository(ExtraFieldValues::class);
1497
        $extraFieldValues = $fieldValuesRepo->getVisibleValues(
1498
            ExtraField::SESSION_FIELD_TYPE,
1499
            $session_id
1500
        );
1501
1502
        $session['extra_fields'] = [];
1503
        /** @var ExtraFieldValues $value */
1504
        foreach ($extraFieldValues as $value) {
1505
            if (empty($value)) {
1506
                continue;
1507
            }
1508
            $session['extra_fields'][] = [
1509
                'field' => [
1510
                    'variable' => $value->getField()->getVariable(),
1511
                    'display_text' => $value->getField()->getDisplayText(),
1512
                ],
1513
                'value' => $value->getFieldValue(),
1514
            ];
1515
        }
1516
1517
        return $session;
1518
    }
1519
1520
    /**
1521
     * Return the five star HTML.
1522
     *
1523
     * @param string $id              of the rating ul element
1524
     * @param string $url             that will be added (for jquery see hot_courses.tpl)
1525
     * @param array  $point_info      point info array see function CourseManager::get_course_ranking()
1526
     * @param bool   $add_div_wrapper add a div wrapper
1527
     *
1528
     * @return string
1529
     */
1530
    public static function return_rating_system(
1531
        $id,
1532
        $url,
1533
        $point_info = [],
1534
        $add_div_wrapper = true
1535
    ) {
1536
        $number_of_users_who_voted = isset($point_info['users_who_voted']) ? $point_info['users_who_voted'] : null;
1537
        $percentage = isset($point_info['point_average']) ? $point_info['point_average'] : 0;
1538
1539
        if (!empty($percentage)) {
1540
            $percentage = $percentage * 125 / 100;
1541
        }
1542
        $accesses = isset($point_info['accesses']) ? $point_info['accesses'] : 0;
1543
        $star_label = sprintf(get_lang('%s stars out of 5'), $point_info['point_average_star']);
1544
1545
        $html = '<section class="rating-widget">';
1546
        $html .= '<div class="rating-stars"><ul id="stars">';
1547
        $html .= '<li class="star" data-link="'.$url.'&amp;star=1" title="Poor" data-value="1"><i class="fa fa-star fa-fw"></i></li>
1548
                 <li class="star" data-link="'.$url.'&amp;star=2" title="Fair" data-value="2"><i class="fa fa-star fa-fw"></i></li>
1549
                 <li class="star" data-link="'.$url.'&amp;star=3" title="Good" data-value="3"><i class="fa fa-star fa-fw"></i></li>
1550
                 <li class="star" data-link="'.$url.'&amp;star=4" title="Excellent" data-value="4"><i class="fa fa-star fa-fw"></i></li>
1551
                 <li class="star" data-link="'.$url.'&amp;star=5" title="WOW!!!" data-value="5"><i class="fa fa-star fa-fw"></i></li>
1552
        ';
1553
        $html .= '</ul></div>';
1554
        $html .= '</section>';
1555
        $labels = [];
1556
1557
        $labels[] = 1 == $number_of_users_who_voted ? $number_of_users_who_voted.' '.get_lang('Vote') : $number_of_users_who_voted.' '.get_lang('Votes');
1558
        $labels[] = 1 == $accesses ? $accesses.' '.get_lang('Visit') : $accesses.' '.get_lang('Visits');
1559
        $labels[] = $point_info['user_vote'] ? get_lang('Your vote').' ['.$point_info['user_vote'].']' : get_lang('Your vote').' [?] ';
1560
1561
        if (!$add_div_wrapper && api_is_anonymous()) {
1562
            $labels[] = self::tag('span', get_lang('Login to vote'), ['class' => 'error']);
1563
        }
1564
1565
        $html .= self::div(implode(' | ', $labels), ['id' => 'vote_label_'.$id, 'class' => 'vote_label_info']);
1566
        $html .= ' '.self::span(' ', ['id' => 'vote_label2_'.$id]);
1567
1568
        if ($add_div_wrapper) {
1569
            $html = self::div($html, ['id' => 'rating_wrapper_'.$id]);
1570
        }
1571
1572
        return $html;
1573
    }
1574
1575
    /**
1576
     * @param string $title
1577
     * @param string $second_title
1578
     * @param string $size
1579
     * @param bool   $filter
1580
     *
1581
     * @return string
1582
     */
1583
    public static function page_header($title, $second_title = null, $size = 'h2', $filter = true)
1584
    {
1585
        if ($filter) {
1586
            $title = Security::remove_XSS($title);
1587
        }
1588
1589
        if (!empty($second_title)) {
1590
            if ($filter) {
1591
                $second_title = Security::remove_XSS($second_title);
1592
            }
1593
            $title .= "<small> $second_title</small>";
1594
        }
1595
1596
        return '<div class="page-header section-header mb-6"><'.$size.' class="section-header__title">'.$title.'</'.$size.'></div>';
1597
    }
1598
1599
    public static function page_header_and_translate($title, $second_title = null)
1600
    {
1601
        $title = get_lang($title);
1602
1603
        return self::page_header($title, $second_title);
1604
    }
1605
1606
    public static function page_subheader($title, $second_title = null, $size = 'h3', $attributes = [])
1607
    {
1608
        if (!empty($second_title)) {
1609
            $second_title = Security::remove_XSS($second_title);
1610
            $title .= "<small> $second_title<small>";
1611
        }
1612
        $subTitle = self::tag($size, Security::remove_XSS($title), $attributes);
1613
1614
        return $subTitle;
1615
    }
1616
1617
    public static function page_subheader2($title, $second_title = null)
1618
    {
1619
        return self::page_header($title, $second_title, 'h4');
1620
    }
1621
1622
    public static function page_subheader3($title, $second_title = null)
1623
    {
1624
        return self::page_header($title, $second_title, 'h5');
1625
    }
1626
1627
    public static function description(array $list): string
1628
    {
1629
        $html = '';
1630
        if (!empty($list)) {
1631
            $html = '<dl class="dl-horizontal">';
1632
            foreach ($list as $item) {
1633
                $html .= '<dt>'.$item['title'].'</dt>';
1634
                $html .= '<dd>'.$item['content'].'</dd>';
1635
            }
1636
            $html .= '</dl>';
1637
        }
1638
1639
        return $html;
1640
    }
1641
1642
    /**
1643
     * @param int    $percentage      int value between 0 and 100
1644
     * @param bool   $show_percentage
1645
     * @param string $extra_info
1646
     * @param string $class           danger/success/infowarning
1647
     *
1648
     * @return string
1649
     */
1650
    public static function bar_progress($percentage, $show_percentage = true, $extra_info = '', $class = '')
1651
    {
1652
        $percentage = (int) $percentage;
1653
        $class = empty($class) ? '' : "progress-bar-$class";
1654
1655
        $div = '<div class="progress">
1656
                <div
1657
                    class="progress-bar progress-bar-striped '.$class.'"
1658
                    role="progressbar"
1659
                    aria-valuenow="'.$percentage.'"
1660
                    aria-valuemin="0"
1661
                    aria-valuemax="100"
1662
                    style="width: '.$percentage.'%;"
1663
                >';
1664
        if ($show_percentage) {
1665
            $div .= $percentage.'%';
1666
        } else {
1667
            if (!empty($extra_info)) {
1668
                $div .= $extra_info;
1669
            }
1670
        }
1671
        $div .= '</div></div>';
1672
1673
        return $div;
1674
    }
1675
1676
    /**
1677
     * @param array $badge_list
1678
     *
1679
     * @return string
1680
     */
1681
    public static function badgeGroup($list)
1682
    {
1683
        $html = '<div class="badge-group">';
1684
        foreach ($list as $badge) {
1685
            $html .= $badge;
1686
        }
1687
        $html .= '</div>';
1688
1689
        return $html;
1690
    }
1691
1692
    /**
1693
     * Return an HTML span element with the badge class and an additional bg-$type class
1694
     */
1695
    public static function label(string $content, string $type = 'default'): string
1696
    {
1697
        $html = '';
1698
        if (!empty($content)) {
1699
            $class = match ($type) {
1700
                'success' => 'success',
1701
                'warning' => 'warning',
1702
                'important', 'danger', 'error' => 'error',
1703
                'info' => 'info',
1704
                'primary' => 'primary',
1705
                default => 'secondary',
1706
            };
1707
1708
            $html = '<span class="badge badge--'.$class.'">';
1709
            $html .= $content;
1710
            $html .= '</span>';
1711
        }
1712
1713
        return $html;
1714
    }
1715
1716
    public static function actions(array $items): string
1717
    {
1718
        if (empty($items)) {
1719
            return '';
1720
        }
1721
1722
        $links = '';
1723
        foreach ($items as $value) {
1724
            $attributes = $value['url_attributes'] ?? [];
1725
            $links .= self::url($value['content'], $value['url'], $attributes);
1726
        }
1727
1728
        return self::toolbarAction(uniqid('toolbar', false), [$links]);
1729
    }
1730
1731
    /**
1732
     * Prints a tooltip.
1733
     *
1734
     * @param string $text
1735
     * @param string $tip
1736
     *
1737
     * @return string
1738
     */
1739
    public static function tip($text, $tip)
1740
    {
1741
        if (empty($tip)) {
1742
            return $text;
1743
        }
1744
1745
        return self::span(
1746
            $text,
1747
            ['class' => 'boot-tooltip', 'title' => strip_tags($tip)]
1748
        );
1749
    }
1750
1751
    /**
1752
     * @param array $buttons
1753
     *
1754
     * @return string
1755
     */
1756
    public static function groupButton($buttons)
1757
    {
1758
        $html = '<div class="btn-group" role="group">';
1759
        foreach ($buttons as $button) {
1760
            $html .= $button;
1761
        }
1762
        $html .= '</div>';
1763
1764
        return $html;
1765
    }
1766
1767
    /**
1768
     * @todo use twig
1769
     *
1770
     * @param string $title
1771
     * @param array  $elements
1772
     * @param bool   $alignToRight
1773
     *
1774
     * @return string
1775
     */
1776
    public static function groupButtonWithDropDown($title, $elements, $alignToRight = false)
1777
    {
1778
        $id = uniqid('dropdown', false);
1779
        $html = '
1780
        <div class="dropdown inline-block relative">
1781
            <button
1782
                id="'.$id.'"
1783
                type="button"
1784
                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"
1785
                aria-expanded="false"
1786
                aria-haspopup="true"
1787
                onclick="document.querySelector(\'#'.$id.'_menu\').classList.toggle(\'hidden\')"
1788
            >
1789
              '.$title.'
1790
              <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">
1791
                <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" />
1792
              </svg>
1793
            </button>
1794
            <div
1795
                id="'.$id.'_menu"
1796
                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"
1797
                role="menu"
1798
                aria-orientation="vertical"
1799
                aria-labelledby="menu-button"
1800
                tabindex="-1"
1801
            >
1802
            <div class="py-1" role="none">';
1803
        foreach ($elements as $item) {
1804
            $html .= self::url(
1805
                    $item['title'],
1806
                    $item['href'],
1807
                    [
1808
                        'class' => 'text-gray-700 block px-4 py-2 text-sm',
1809
                        'role' => 'menuitem',
1810
                        'onclick' => $item['onclick'] ?? '',
1811
                        'data-action' => $item['data-action'] ?? '',
1812
                        'data-confirm' => $item['data-confirm'] ?? '',
1813
                    ]
1814
                );
1815
        }
1816
        $html .= '
1817
            </div>
1818
            </div>
1819
            </div>
1820
        ';
1821
1822
        return $html;
1823
    }
1824
1825
    /**
1826
     * @param string $file
1827
     * @param array  $params
1828
     *
1829
     * @return string|null
1830
     */
1831
    public static function getMediaPlayer($file, $params = [])
1832
    {
1833
        $fileInfo = pathinfo($file);
1834
1835
        $autoplay = isset($params['autoplay']) && 'true' === $params['autoplay'] ? 'autoplay' : '';
1836
        $id = isset($params['id']) ? $params['id'] : $fileInfo['basename'];
1837
        $width = isset($params['width']) ? 'width="'.$params['width'].'"' : null;
1838
        $class = isset($params['class']) ? ' class="'.$params['class'].'"' : null;
1839
1840
        switch ($fileInfo['extension']) {
1841
            case 'mp3':
1842
            case 'webm':
1843
                $html = '<audio id="'.$id.'" '.$class.' controls '.$autoplay.' '.$width.' src="'.$params['url'].'" >';
1844
                $html .= '<object width="'.$width.'" height="50" type="application/x-shockwave-flash" data="'.api_get_path(WEB_LIBRARY_PATH).'javascript/mediaelement/flashmediaelement.swf">
1845
                            <param name="movie" value="'.api_get_path(WEB_LIBRARY_PATH).'javascript/mediaelement/flashmediaelement.swf" />
1846
                            <param name="flashvars" value="controls=true&file='.$params['url'].'" />
1847
                          </object>';
1848
                $html .= '</audio>';
1849
1850
                return $html;
1851
                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...
1852
            case 'wav':
1853
            case 'ogg':
1854
                $html = '<audio width="300px" controls id="'.$id.'" '.$autoplay.' src="'.$params['url'].'" >';
1855
1856
                return $html;
1857
                break;
1858
        }
1859
1860
        return null;
1861
    }
1862
1863
    /**
1864
     * @param int    $nextValue
1865
     * @param array  $list
1866
     * @param int    $current
1867
     * @param int    $fixedValue
1868
     * @param array  $conditions
1869
     * @param string $link
1870
     * @param bool   $isMedia
1871
     * @param bool   $addHeaders
1872
     * @param array  $linkAttributes
1873
     *
1874
     * @return string
1875
     */
1876
    public static function progressPaginationBar(
1877
        $nextValue,
1878
        $list,
1879
        $current,
1880
        $fixedValue = null,
1881
        $conditions = [],
1882
        $link = null,
1883
        $isMedia = false,
1884
        $addHeaders = true,
1885
        $linkAttributes = []
1886
    ) {
1887
        if ($addHeaders) {
1888
            $pagination_size = 'pagination-mini';
1889
            $html = '<div class="exercise_pagination pagination '.$pagination_size.'"><ul>';
1890
        } else {
1891
            $html = null;
1892
        }
1893
        $affectAllItems = false;
1894
        if ($isMedia && isset($fixedValue) && ($nextValue + 1 == $current)) {
1895
            $affectAllItems = true;
1896
        }
1897
        $localCounter = 0;
1898
        foreach ($list as $itemId) {
1899
            $isCurrent = false;
1900
            if ($affectAllItems) {
1901
                $isCurrent = true;
1902
            } else {
1903
                if (!$isMedia) {
1904
                    $isCurrent = $current == ($localCounter + $nextValue + 1) ? true : false;
1905
                }
1906
            }
1907
            $html .= self::parsePaginationItem(
1908
                $itemId,
1909
                $isCurrent,
1910
                $conditions,
1911
                $link,
1912
                $nextValue,
1913
                $isMedia,
1914
                $localCounter,
1915
                $fixedValue,
1916
                $linkAttributes
1917
            );
1918
            $localCounter++;
1919
        }
1920
        if ($addHeaders) {
1921
            $html .= '</ul></div>';
1922
        }
1923
1924
        return $html;
1925
    }
1926
1927
    /**
1928
     * @param int    $itemId
1929
     * @param bool   $isCurrent
1930
     * @param array  $conditions
1931
     * @param string $link
1932
     * @param int    $nextValue
1933
     * @param bool   $isMedia
1934
     * @param int    $localCounter
1935
     * @param int    $fixedValue
1936
     * @param array  $linkAttributes
1937
     *
1938
     * @return string
1939
     */
1940
    public static function parsePaginationItem(
1941
        $itemId,
1942
        $isCurrent,
1943
        $conditions,
1944
        $link,
1945
        $nextValue = 0,
1946
        $isMedia = false,
1947
        $localCounter = null,
1948
        $fixedValue = null,
1949
        $linkAttributes = []
1950
    ) {
1951
        $defaultClass = 'before';
1952
        $class = $defaultClass;
1953
        foreach ($conditions as $condition) {
1954
            $array = isset($condition['items']) ? $condition['items'] : [];
1955
            $class_to_applied = $condition['class'];
1956
            $type = isset($condition['type']) ? $condition['type'] : 'positive';
1957
            $mode = isset($condition['mode']) ? $condition['mode'] : 'add';
1958
            switch ($type) {
1959
                case 'positive':
1960
                    if (in_array($itemId, $array)) {
1961
                        if ('overwrite' == $mode) {
1962
                            $class = " $defaultClass $class_to_applied";
1963
                        } else {
1964
                            $class .= " $class_to_applied";
1965
                        }
1966
                    }
1967
                    break;
1968
                case 'negative':
1969
                    if (!in_array($itemId, $array)) {
1970
                        if ('overwrite' == $mode) {
1971
                            $class = " $defaultClass $class_to_applied";
1972
                        } else {
1973
                            $class .= " $class_to_applied";
1974
                        }
1975
                    }
1976
                    break;
1977
            }
1978
        }
1979
        if ($isCurrent) {
1980
            $class = 'before current';
1981
        }
1982
        if ($isMedia && $isCurrent) {
1983
            $class = 'before current';
1984
        }
1985
        if (empty($link)) {
1986
            $link_to_show = '#';
1987
        } else {
1988
            $link_to_show = $link.($nextValue + $localCounter);
1989
        }
1990
        $label = $nextValue + $localCounter + 1;
1991
        if ($isMedia) {
1992
            $label = ($fixedValue + 1).' '.chr(97 + $localCounter);
1993
            $link_to_show = $link.$fixedValue.'#questionanchor'.$itemId;
1994
        }
1995
        $link = self::url($label.' ', $link_to_show, $linkAttributes);
1996
1997
        return '<li class = "'.$class.'">'.$link.'</li>';
1998
    }
1999
2000
    /**
2001
     * @param int $current
2002
     * @param int $total
2003
     *
2004
     * @return string
2005
     */
2006
    public static function paginationIndicator($current, $total)
2007
    {
2008
        $html = null;
2009
        if (!empty($current) && !empty($total)) {
2010
            $label = sprintf(get_lang('%s of %s'), $current, $total);
2011
            $html = self::url($label, '#', ['class' => 'btn disabled']);
2012
        }
2013
2014
        return $html;
2015
    }
2016
2017
    /**
2018
     * @param $url
2019
     * @param $currentPage
2020
     * @param $pagesCount
2021
     * @param $totalItems
2022
     *
2023
     * @return string
2024
     */
2025
    public static function getPagination($url, $currentPage, $pagesCount, $totalItems)
2026
    {
2027
        $pagination = '';
2028
        if ($totalItems > 1 && $pagesCount > 1) {
2029
            $pagination .= '<ul class="pagination">';
2030
            for ($i = 0; $i < $pagesCount; $i++) {
2031
                $newPage = $i + 1;
2032
                if ($currentPage == $newPage) {
2033
                    $pagination .= '<li class="active"><a href="'.$url.'&page='.$newPage.'">'.$newPage.'</a></li>';
2034
                } else {
2035
                    $pagination .= '<li><a href="'.$url.'&page='.$newPage.'">'.$newPage.'</a></li>';
2036
                }
2037
            }
2038
            $pagination .= '</ul>';
2039
        }
2040
2041
        return $pagination;
2042
    }
2043
2044
    /**
2045
     * Adds a legacy message in the queue.
2046
     *
2047
     * @param string $message
2048
     */
2049
    public static function addFlash($message)
2050
    {
2051
        // Detect type of message.
2052
        $parts = preg_match('/alert-([a-z]*)/', $message, $matches);
2053
        $type = 'primary';
2054
        if ($parts && isset($matches[1]) && $matches[1]) {
2055
            $type = $matches[1];
2056
        }
2057
        // Detect legacy content of message.
2058
        $result = preg_match('/<div(.*?)\>(.*?)\<\/div>/s', $message, $matches);
2059
        if ($result && isset($matches[2])) {
2060
            Container::getSession()->getFlashBag()->add($type, $matches[2]);
2061
        }
2062
    }
2063
2064
    /**
2065
     * Get the profile edition link for a user.
2066
     *
2067
     * @param int  $userId  The user id
2068
     * @param bool $asAdmin Optional. Whether get the URL for the platform admin
2069
     *
2070
     * @return string The link
2071
     */
2072
    public static function getProfileEditionLink($userId, $asAdmin = false)
2073
    {
2074
        $editProfileUrl = api_get_path(WEB_CODE_PATH).'auth/profile.php';
2075
        if ($asAdmin) {
2076
            $editProfileUrl = api_get_path(WEB_CODE_PATH)."admin/user_edit.php?user_id=".intval($userId);
2077
        }
2078
2079
        return $editProfileUrl;
2080
    }
2081
2082
    /**
2083
     * Get the vCard for a user.
2084
     *
2085
     * @param int $userId The user id
2086
     *
2087
     * @return string *.*vcf file
2088
     */
2089
    public static function getVCardUserLink($userId)
2090
    {
2091
        return api_get_path(WEB_PATH).'main/social/vcard_export.php?userId='.intval($userId);
2092
    }
2093
2094
    /**
2095
     * @param string $content
2096
     * @param string $title
2097
     * @param string $footer
2098
     * @param string $type        primary|success|info|warning|danger
2099
     * @param string $extra
2100
     * @param string $id
2101
     * @param string $customColor
2102
     * @param string $rightAction
2103
     *
2104
     * @return string
2105
     */
2106
    public static function panel(
2107
        $content,
2108
        $title = '',
2109
        $footer = '',
2110
        $type = 'default',
2111
        $extra = '',
2112
        $id = '',
2113
        $customColor = '',
2114
        $rightAction = ''
2115
    ) {
2116
        $headerStyle = '';
2117
        if (!empty($customColor)) {
2118
            $headerStyle = 'style = "color: white; background-color: '.$customColor.'" ';
2119
        }
2120
2121
        $footer = !empty($footer) ? '<p class="card-text"><small class="text-muted">'.$footer.'</small></p>' : '';
2122
        $typeList = ['primary', 'success', 'info', 'warning', 'danger'];
2123
        $style = !in_array($type, $typeList) ? 'default' : $type;
2124
2125
        if (!empty($id)) {
2126
            $id = " id='$id'";
2127
        }
2128
        $cardBody = $title.' '.self::contentPanel($content).' '.$footer;
2129
2130
        return "
2131
            <div $id class=card>
2132
                <div class='flex justify-between items-center py-2'>
2133
                    <div class='relative mt-1 flex'>
2134
                        $title
2135
                    </div>
2136
                    <div>
2137
                        $rightAction
2138
                    </div>
2139
                </div>
2140
2141
                $content
2142
                $footer
2143
            </div>"
2144
        ;
2145
    }
2146
2147
    /**
2148
     * @param string $content
2149
     */
2150
    public static function contentPanel($content): string
2151
    {
2152
        if (empty($content)) {
2153
            return '';
2154
        }
2155
2156
        return '<div class="card-text">'.$content.'</div>';
2157
    }
2158
2159
    /**
2160
     * Get the button HTML with an Awesome Font icon.
2161
     *
2162
     * @param string $text        The button content
2163
     * @param string $url         The url to button
2164
     * @param string $icon        The Awesome Font class for icon
2165
     * @param string $type        Optional. The button Bootstrap class. Default 'default' class
2166
     * @param array  $attributes  The additional attributes
2167
     * @param bool   $includeText
2168
     *
2169
     * @return string The button HTML
2170
     */
2171
    public static function toolbarButton(
2172
        $text,
2173
        $url,
2174
        $icon = 'check',
2175
        $type = null,
2176
        array $attributes = [],
2177
        $includeText = true
2178
    ) {
2179
        $buttonClass = "btn btn--secondary-outline";
2180
        if (!empty($type)) {
2181
            $buttonClass = "btn btn--$type";
2182
        }
2183
        //$icon = self::tag('i', null, ['class' => "fa fa-$icon fa-fw", 'aria-hidden' => 'true']);
2184
        $icon = self::getMdiIcon($icon);
2185
        $attributes['class'] = isset($attributes['class']) ? "$buttonClass {$attributes['class']}" : $buttonClass;
2186
        $attributes['title'] = $attributes['title'] ?? $text;
2187
2188
        if (!$includeText) {
2189
            $text = '<span class="sr-only">'.$text.'</span>';
2190
        }
2191
2192
        return self::url("$icon $text", $url, $attributes);
2193
    }
2194
2195
    /**
2196
     * Generate an HTML "p-toolbar" div element with the given id attribute.
2197
     * @param string $id The HTML div's "id" attribute to set
2198
     * @param array  $contentList Array of left-center-right elements for the toolbar. If only 2 elements are defined, this becomes left-right (no center)
2199
     * @return string HTML div for the toolbar
2200
     */
2201
    public static function toolbarAction(string $id, array $contentList): string
2202
    {
2203
        $contentListPurged = array_filter($contentList);
2204
2205
        if (empty($contentListPurged)) {
2206
            return '';
2207
        }
2208
2209
        $count = count($contentList);
2210
2211
        $start = $contentList[0];
2212
        $center = '';
2213
        $end = '';
2214
2215
        if (2 === $count) {
2216
            $end = $contentList[1];
2217
        } elseif (3 === $count) {
2218
            $center = $contentList[1];
2219
            $end = $contentList[2];
2220
        }
2221
2222
        return '<div id="'.$id.'" class="toolbar-action p-toolbar p-component flex items-center justify-between flex-wrap w-full" role="toolbar">
2223
            <div class="p-toolbar-group-start p-toolbar-group-left">'.$start.'</div>
2224
            <div class="p-toolbar-group-center">'.$center.'</div>
2225
            <div class="p-toolbar-group-end p-toolbar-group-right">'.$end.'</div>
2226
        </div>';
2227
    }
2228
2229
    /**
2230
     * @param array  $content
2231
     * @param array  $colsWidth Optional. Columns width
2232
     *
2233
     * @return string
2234
     */
2235
    public static function toolbarGradeAction($content, $colsWidth = [])
2236
    {
2237
        $col = count($content);
2238
2239
        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...
2240
            $width = 8 / $col;
2241
            array_walk($content, function () use ($width, &$colsWidth) {
2242
                $colsWidth[] = $width;
2243
            });
2244
        }
2245
2246
        $html = '<div id="grade" class="p-toolbar p-component flex items-center justify-between flex-wrap" role="toolbar">';
2247
        for ($i = 0; $i < $col; $i++) {
2248
            $class = 'col-sm-'.$colsWidth[$i];
2249
            if ($col > 1) {
2250
                if ($i > 0 && $i < count($content) - 1) {
2251
                    $class .= ' text-center';
2252
                } elseif ($i === count($content) - 1) {
2253
                    $class .= ' text-right';
2254
                }
2255
            }
2256
            $html .= '<div class="'.$class.'">'.$content[$i].'</div>';
2257
        }
2258
        $html .= '</div>';
2259
2260
        return $html;
2261
    }
2262
2263
    /**
2264
     * The auto-translated title version of getMdiIconSimple()
2265
     * Shortcut method to getMdiIcon, to be used from Twig (see ChamiloExtension.php)
2266
     * using acceptable default values
2267
     * @param string $name The icon name or a string representing the icon in our *Icon Enums
2268
     * @param int|null $size The icon size
2269
     * @param string|null $additionalClass Additional CSS class to add to the icon
2270
     * @param string|null $title A title for the icon
2271
     * @return string
2272
     * @throws InvalidArgumentException
2273
     * @throws ReflectionException
2274
     */
2275
    public static function getMdiIconTranslate(
2276
        string $name,
2277
        ?int $size = ICON_SIZE_SMALL,
2278
        ?string $additionalClass = 'ch-tool-icon',
2279
        ?string $title = null
2280
    ): string
2281
    {
2282
        if (!empty($title)) {
2283
            $title = get_lang($title);
2284
        }
2285
2286
        return self::getMdiIconSimple($name, $size, $additionalClass, $title);
2287
    }
2288
    /**
2289
     * Shortcut method to getMdiIcon, to be used from Twig (see ChamiloExtension.php)
2290
     * using acceptable default values
2291
     * @param string $name The icon name or a string representing the icon in our *Icon Enums
2292
     * @param int|null $size The icon size
2293
     * @param string|null $additionalClass Additional CSS class to add to the icon
2294
     * @param string|null $title A title for the icon
2295
     * @return string
2296
     * @throws InvalidArgumentException
2297
     * @throws ReflectionException
2298
     */
2299
    public static function getMdiIconSimple(
2300
        string $name,
2301
        ?int $size = ICON_SIZE_SMALL,
2302
        ?string $additionalClass = 'ch-tool-icon',
2303
        ?string $title = null
2304
    ): string
2305
    {
2306
        // If the string contains '::', we assume it is a reference to one of the icon Enum classes in src/CoreBundle/Enums/
2307
        $matches = [];
2308
        if (preg_match('/(\w*)::(\w*)/', $name, $matches)) {
2309
            if (count($matches) != 3) {
2310
                throw new InvalidArgumentException('Invalid enum case string format. Expected format is "EnumClass::CASE".');
2311
            }
2312
            $enum = $matches[1];
2313
            $case = $matches[2];
2314
            if (!class_exists('Chamilo\CoreBundle\Enums\\'.$enum)) {
2315
                throw new InvalidArgumentException("Class {$enum} does not exist.");
2316
            }
2317
            $reflection = new ReflectionEnum('Chamilo\CoreBundle\Enums\\'.$enum);
2318
            // Check if the case exists in the Enum class
2319
            if (!$reflection->hasCase($case)) {
2320
                throw new InvalidArgumentException("Case {$case} does not exist in enum class {$enum}.");
2321
            }
2322
            // Get the Enum case
2323
            /* @var ReflectionEnumUnitCase $enumUnitCaseObject */
2324
            $enumUnitCaseObject = $reflection->getCase($case);
2325
            $enumValue = $enumUnitCaseObject->getValue();
2326
            $name = $enumValue->value;
2327
2328
        }
2329
2330
        return self::getMdiIcon($name, $additionalClass, null, $size, $title);
2331
    }
2332
2333
    /**
2334
     * Get a full HTML <i> tag for an icon from the Material Design Icons set
2335
     * @param string|ActionIcon|ToolIcon|ObjectIcon|StateIcon $name
2336
     * @param string|null                                     $additionalClass
2337
     * @param string|null                                     $style
2338
     * @param int|null                                        $pixelSize
2339
     * @param string|null                                     $title
2340
     * @param array|null                                      $additionalAttributes
2341
     * @return string
2342
     */
2343
    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
2344
    {
2345
        $sizeString = '';
2346
        if (!empty($pixelSize)) {
2347
            $sizeString = 'font-size: '.$pixelSize.'px; width: '.$pixelSize.'px; height: '.$pixelSize.'px; ';
2348
        }
2349
        if (empty($style)) {
2350
            $style = '';
2351
        }
2352
2353
        $additionalAttributes['class'] = 'mdi mdi-';
2354
2355
        if ($name instanceof ActionIcon
2356
            || $name instanceof ToolIcon
2357
            || $name instanceof ObjectIcon
2358
            || $name instanceof StateIcon
2359
        ) {
2360
            $additionalAttributes['class'] .= $name->value;
2361
        } else {
2362
            $additionalAttributes['class'] .= $name;
2363
        }
2364
2365
        $additionalAttributes['class'] .= " $additionalClass";
2366
        $additionalAttributes['style'] = $sizeString.$style;
2367
        $additionalAttributes['aria-hidden'] = 'true';
2368
2369
        if (!empty($title)) {
2370
            $additionalAttributes['title'] = htmlentities($title);
2371
        }
2372
2373
        return self::tag(
2374
            'i',
2375
            '',
2376
            $additionalAttributes
2377
        );
2378
    }
2379
2380
    /**
2381
     * Get a HTML code for a icon by Font Awesome.
2382
     *
2383
     * @param string     $name            The icon name. Example: "mail-reply"
2384
     * @param int|string $size            Optional. The size for the icon. (Example: lg, 2, 3, 4, 5)
2385
     * @param bool       $fixWidth        Optional. Whether add the fw class
2386
     * @param string     $additionalClass Optional. Additional class
2387
     *
2388
     * @return string
2389
     * @deprecated Use getMdiIcon() instead
2390
     */
2391
    public static function returnFontAwesomeIcon(
2392
        $name,
2393
        $size = '',
2394
        $fixWidth = false,
2395
        $additionalClass = ''
2396
    ) {
2397
        $className = "mdi mdi-$name";
2398
2399
        if ($fixWidth) {
2400
            $className .= ' fa-fw';
2401
        }
2402
2403
        switch ($size) {
2404
            case 'xs':
2405
            case 'sm':
2406
            case 'lg':
2407
                $className .= " fa-{$size}";
2408
                break;
2409
            case 2:
2410
            case 3:
2411
            case 4:
2412
            case 5:
2413
                $className .= " fa-{$size}x";
2414
                break;
2415
        }
2416
2417
        if (!empty($additionalClass)) {
2418
            $className .= " $additionalClass";
2419
        }
2420
2421
        $icon = self::tag('em', null, ['class' => $className]);
2422
2423
        return "$icon ";
2424
    }
2425
2426
    public static function returnPrimeIcon(
2427
        $name,
2428
        $size = '',
2429
        $fixWidth = false,
2430
        $additionalClass = ''
2431
    ) {
2432
        $className = "pi pi-$name";
2433
2434
        if ($fixWidth) {
2435
            $className .= ' pi-fw';
2436
        }
2437
2438
        if ($size) {
2439
            $className .= " pi-$size";
2440
        }
2441
2442
        if (!empty($additionalClass)) {
2443
            $className .= " $additionalClass";
2444
        }
2445
2446
        $icon = self::tag('i', null, ['class' => $className]);
2447
2448
        return "$icon ";
2449
    }
2450
2451
    /**
2452
     * @param string     $title
2453
     * @param string     $content
2454
     * @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...
2455
     * @param array      $params
2456
     * @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...
2457
     * @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...
2458
     * @param bool|true  $open
2459
     * @param bool|false $fullClickable
2460
     *
2461
     * @return string
2462
     *
2463
     * @todo rework function to easy use
2464
     */
2465
    public static function panelCollapse(
2466
        $title,
2467
        $content,
2468
        $id = null,
2469
        $params = [],
2470
        $idAccordion = null,
2471
        $idCollapse = null,
2472
        $open = true,
2473
        $fullClickable = false
2474
    ) {
2475
        $javascript = '';
2476
        if (!empty($idAccordion)) {
2477
            $javascript = '
2478
        <script>
2479
            document.addEventListener("DOMContentLoaded", function() {
2480
                const buttons = document.querySelectorAll("#card_'.$idAccordion.' a");
2481
                const menus = document.querySelectorAll("#collapse_'.$idAccordion.'");
2482
                buttons.forEach((button, index) => {
2483
                    button.addEventListener("click", function() {
2484
                        menus.forEach((menu, menuIndex) => {
2485
                            if (index === menuIndex) {
2486
                                button.setAttribute("aria-expanded", "true" === button.getAttribute("aria-expanded") ? "false" : "true")
2487
                                button.classList.toggle("mdi-chevron-down")
2488
                                button.classList.toggle("mdi-chevron-up")
2489
                                menu.classList.toggle("active");
2490
                            } else {
2491
                                menu.classList.remove("active");
2492
                            }
2493
                        });
2494
                    });
2495
                });
2496
            });
2497
        </script>';
2498
            $html = '
2499
        <div class="display-panel-collapse mb-2">
2500
            <div class="display-panel-collapse__header" id="card_'.$idAccordion.'">
2501
                <a role="button"
2502
                    class="mdi mdi-chevron-down"
2503
                    data-toggle="collapse"
2504
                    data-target="#collapse_'.$idAccordion.'"
2505
                    aria-expanded="'.(($open) ? 'true' : 'false').'"
2506
                    aria-controls="collapse_'.$idAccordion.'"
2507
                >
2508
                    '.$title.'
2509
                </a>
2510
            </div>
2511
            <div
2512
                id="collapse_'.$idAccordion.'"
2513
                class="display-panel-collapse__collapsible '.(($open) ? 'active' : '').'"
2514
            >
2515
                <div id="collapse_contant_'.$idAccordion.'">';
2516
2517
            $html .= $content;
2518
            $html .= '</div></div></div>';
2519
2520
        } else {
2521
            if (!empty($id)) {
2522
                $params['id'] = $id;
2523
            }
2524
            $params['class'] = 'v-card bg-white mx-2';
2525
            $html = '';
2526
            if (!empty($title)) {
2527
                $html .= '<div class="v-card-header text-h5 my-2">'.$title.'</div>'.PHP_EOL;
2528
            }
2529
            $html .= '<div class="v-card-text">'.$content.'</div>'.PHP_EOL;
2530
            $html = self::div($html, $params);
2531
        }
2532
2533
        return $javascript.$html;
2534
    }
2535
2536
    /**
2537
     * Returns the string "1 day ago" with a link showing the exact date time.
2538
     *
2539
     * @param string|DateTime $dateTime in UTC or a DateTime in UTC
2540
     *
2541
     * @throws Exception
2542
     *
2543
     * @return string
2544
     */
2545
    public static function dateToStringAgoAndLongDate(string|DateTime $dateTime): string
2546
    {
2547
        if (empty($dateTime) || '0000-00-00 00:00:00' === $dateTime) {
2548
            return '';
2549
        }
2550
2551
        if (is_string($dateTime)) {
2552
            $dateTime = new \DateTime($dateTime, new \DateTimeZone('UTC'));
2553
        }
2554
2555
        return self::tip(
2556
            date_to_str_ago($dateTime),
2557
            api_convert_and_format_date($dateTime, DATE_TIME_FORMAT_LONG)
2558
        );
2559
    }
2560
2561
    /**
2562
     * @param array  $userInfo
2563
     * @param string $status
2564
     * @param string $toolbar
2565
     *
2566
     * @return string
2567
     */
2568
    public static function getUserCard($userInfo, $status = '', $toolbar = '')
2569
    {
2570
        if (empty($userInfo)) {
2571
            return '';
2572
        }
2573
2574
        if (!empty($status)) {
2575
            $status = '<div class="items-user-status">'.$status.'</div>';
2576
        }
2577
2578
        if (!empty($toolbar)) {
2579
            $toolbar = '<div class="btn-group pull-right">'.$toolbar.'</div>';
2580
        }
2581
2582
        return '<div id="user_card_'.$userInfo['id'].'" class="card d-flex flex-row">
2583
                    <img src="'.$userInfo['avatar'].'" class="rounded" />
2584
                    <h3 class="card-title">'.$userInfo['complete_name'].'</h3>
2585
                    <div class="card-body">
2586
                       <div class="card-title">
2587
                       '.$status.'
2588
                       '.$toolbar.'
2589
                       </div>
2590
                    </div>
2591
                    <hr />
2592
              </div>';
2593
    }
2594
2595
    /**
2596
     * @param string $fileName
2597
     * @param string $fileUrl
2598
     *
2599
     * @return string
2600
     */
2601
    public static function fileHtmlGuesser($fileName, $fileUrl)
2602
    {
2603
        $data = pathinfo($fileName);
2604
2605
        //$content = self::url($data['basename'], $fileUrl);
2606
        $content = '';
2607
        switch ($data['extension']) {
2608
            case 'webm':
2609
            case 'mp4':
2610
            case 'ogg':
2611
                $content = '<video style="width: 400px; height:100%;" src="'.$fileUrl.'"></video>';
2612
                // Allows video to play when loading during an ajax call
2613
                $content .= "<script>jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer();</script>";
2614
                break;
2615
            case 'jpg':
2616
            case 'jpeg':
2617
            case 'gif':
2618
            case 'png':
2619
                $content = '<img class="img-responsive" src="'.$fileUrl.'" />';
2620
                break;
2621
            default:
2622
                //$html = self::url($data['basename'], $fileUrl);
2623
                break;
2624
        }
2625
        //$html = self::url($content, $fileUrl, ['ajax']);
2626
2627
        return $content;
2628
    }
2629
2630
    /**
2631
     * @param string $image
2632
     * @param int    $size
2633
     *
2634
     * @return string
2635
     */
2636
    public static function get_icon_path($image, $size = ICON_SIZE_SMALL)
2637
    {
2638
        return self::return_icon($image, '', [], $size, false, true);
2639
    }
2640
2641
    /**
2642
     * @param $id
2643
     *
2644
     * @return array|mixed
2645
     */
2646
    public static function randomColor($id)
2647
    {
2648
        static $colors = [];
2649
2650
        if (!empty($colors[$id])) {
2651
            return $colors[$id];
2652
        } else {
2653
            $color = substr(md5(time() * $id), 0, 6);
2654
            $c1 = hexdec(substr($color, 0, 2));
2655
            $c2 = hexdec(substr($color, 2, 2));
2656
            $c3 = hexdec(substr($color, 4, 2));
2657
            $luminosity = $c1 + $c2 + $c3;
2658
2659
            $type = '#000000';
2660
            if ($luminosity < (255 + 255 + 255) / 2) {
2661
                $type = '#FFFFFF';
2662
            }
2663
2664
            $result = [
2665
                'color' => '#'.$color,
2666
                'luminosity' => $type,
2667
            ];
2668
            $colors[$id] = $result;
2669
2670
            return $result; // example: #fc443a
2671
        }
2672
    }
2673
2674
    public static function noDataView(string $title, string $icon, string $buttonTitle, string $url): string
2675
    {
2676
        $content = '<div id="no-data-view">';
2677
        $content .= '<h3>'.$title.'</h3>';
2678
        $content .= $icon;
2679
        $content .= '<div class="controls">';
2680
        $content .= self::url(
2681
            '<em class="fa fa-plus"></em> '.$buttonTitle,
2682
            $url,
2683
            ['class' => 'btn btn--primary']
2684
        );
2685
        $content .= '</div>';
2686
        $content .= '</div>';
2687
2688
        return $content;
2689
    }
2690
2691
    public static function prose(string $contents): string
2692
    {
2693
        return "
2694
    <div class='w-full my-8'>
2695
      <div class='prose prose-blue max-w-none px-6 py-4 bg-white rounded-lg shadow'>
2696
        $contents
2697
      </div>
2698
    </div>
2699
    ";
2700
    }
2701
2702
    public static function getFrameReadyBlock(
2703
        string $frameName,
2704
        string $itemType = '',
2705
        string $jsConditionalFunction = 'function () { return false; }'
2706
    ): string {
2707
2708
        if (in_array($itemType, ['link', 'sco', 'xapi', 'quiz', 'h5p', 'forum'])) {
2709
            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...
2710
        }
2711
2712
        $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

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