Passed
Push — master ( 841a58...400078 )
by Yannick
07:33
created

Display::actions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

// Bar.php
namespace OtherDir;

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

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

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

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

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

}

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

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

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

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

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

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

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

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

539
                $hmail .= '&subject='./** @scrutinizer ignore-type */ Security::remove_XSS(api_get_setting('siteName'));
Loading history...
540
            }
541
            if ($addExtraContent) {
542
                $content = '';
543
                if (!api_is_anonymous()) {
544
                    $userInfo = api_get_user_info();
545
                    $content .= get_lang('User').': '.$userInfo['complete_name']."\n";
546
547
                    $courseInfo = api_get_course_info();
548
                    if (!empty($courseInfo)) {
549
                        $content .= get_lang('Course').': ';
550
                        $content .= $courseInfo['name'];
551
                        $sessionInfo = api_get_session_info(api_get_session_id());
552
                        if (!empty($sessionInfo)) {
553
                            $content .= ' '.$sessionInfo['name'].' <br />';
554
                        }
555
                    }
556
                }
557
                $hmail .= '&body='.rawurlencode($content);
558
            }
559
        }
560
561
        $hclickable_text = '';
562
        // Encrypt clickable text if @ is present
563
        if (strpos($clickable_text, '@')) {
564
            for ($i = 0; $i < strlen($clickable_text); $i++) {
565
                $hclickable_text .= '&#'.ord($clickable_text[$i]).';';
566
            }
567
        } else {
568
            $hclickable_text = @htmlspecialchars(
569
                $clickable_text,
570
                ENT_QUOTES,
571
                api_get_system_encoding()
572
            );
573
        }
574
        // Return encrypted mailto hyperlink
575
        return '<a href="'.$hmail.'"'.$style_class.' class="clickable_email_link">'.$hclickable_text.'</a>';
576
    }
577
578
    /**
579
     * Prints an <option>-list with all letters (A-Z).
580
     *
581
     * @todo This is English language specific implementation.
582
     * It should be adapted for the other languages.
583
     *
584
     * @return string
585
     */
586
    public static function get_alphabet_options($selectedLetter = '')
587
    {
588
        $result = '';
589
        for ($i = 65; $i <= 90; $i++) {
590
            $letter = chr($i);
591
            $result .= '<option value="'.$letter.'"';
592
            if ($selectedLetter == $letter) {
593
                $result .= ' selected="selected"';
594
            }
595
            $result .= '>'.$letter.'</option>';
596
        }
597
598
        return $result;
599
    }
600
601
    /**
602
     * Get the options withing a select box within the given values.
603
     *
604
     * @param int   Min value
605
     * @param int   Max value
606
     * @param int   Default value
607
     *
608
     * @return string HTML select options
609
     */
610
    public static function get_numeric_options($min, $max, $selected_num = 0)
611
    {
612
        $result = '';
613
        for ($i = $min; $i <= $max; $i++) {
614
            $result .= '<option value="'.$i.'"';
615
            if (is_int($selected_num)) {
616
                if ($selected_num == $i) {
617
                    $result .= ' selected="selected"';
618
                }
619
            }
620
            $result .= '>'.$i.'</option>';
621
        }
622
623
        return $result;
624
    }
625
626
    /**
627
     * Gets the path of an icon.
628
     *
629
     * @param string $icon
630
     * @param int    $size
631
     *
632
     * @return string
633
     */
634
    public static function returnIconPath($icon, $size = ICON_SIZE_SMALL)
635
    {
636
        return self::return_icon($icon, null, null, $size, null, true, false);
637
    }
638
639
    /**
640
     * This public function returns the htmlcode for an icon.
641
     *
642
     * @param string   The filename of the file (in the main/img/ folder
643
     * @param string   The alt text (probably a language variable)
644
     * @param array    Additional attributes (for instance height, width, onclick, ...)
645
     * @param int  The wanted width of the icon (to be looked for in the corresponding img/icons/ folder)
646
     *
647
     * @return string An HTML string of the right <img> tag
648
     *
649
     * @author Patrick Cool <[email protected]>, Ghent University 2006
650
     * @author Julio Montoya 2010 Function improved, adding image constants
651
     * @author Yannick Warnier 2011 Added size handler
652
     *
653
     * @version Feb 2011
654
     */
655
    public static function return_icon(
656
        string $image,
657
        ?string $alt_text = '',
658
        ?array $additional_attributes = [],
659
        ?int $size = ICON_SIZE_SMALL,
660
        ?bool $show_text = true,
661
        ?bool $return_only_path = false,
662
        ?bool $loadThemeIcon = true
663
    ) {
664
        $code_path = api_get_path(SYS_PUBLIC_PATH);
665
        $w_code_path = api_get_path(WEB_PUBLIC_PATH);
666
        // The following path is checked to see if the file exist. It's
667
        // important to use the public path (i.e. web/css/) rather than the
668
        // internal path (/app/Resource/public/css/) because the path used
669
        // in the end must be the public path
670
        $alternateCssPath = api_get_path(SYS_PUBLIC_PATH).'css/';
671
        $alternateWebCssPath = api_get_path(WEB_PUBLIC_PATH).'css/';
672
673
        // Avoid issues with illegal string offset for legacy calls to this
674
        // method with an empty string rather than null or an empty array
675
        if (empty($additional_attributes)) {
676
            $additional_attributes = [];
677
        }
678
679
        $image = trim($image);
680
681
        if (isset($size)) {
682
            $size = (int) $size;
683
        } else {
684
            $size = ICON_SIZE_SMALL;
685
        }
686
687
        $size_extra = $size.'/';
688
        $icon = $w_code_path.'img/'.$image;
689
        $theme = 'themes/chamilo/icons/';
690
691
        if ($loadThemeIcon) {
692
            // @todo with chamilo 2 code
693
            $theme = 'themes/'.api_get_visual_theme().'/icons/';
694
            if (is_file($alternateCssPath.$theme.$image)) {
695
                $icon = $alternateWebCssPath.$theme.$image;
696
            }
697
            // Checking the theme icons folder example: app/Resources/public/css/themes/chamilo/icons/XXX
698
            if (is_file($alternateCssPath.$theme.$size_extra.$image)) {
699
                $icon = $alternateWebCssPath.$theme.$size_extra.$image;
700
            } elseif (is_file($code_path.'img/icons/'.$size_extra.$image)) {
701
                //Checking the main/img/icons/XXX/ folder
702
                $icon = $w_code_path.'img/icons/'.$size_extra.$image;
703
            }
704
        } else {
705
            if (is_file($code_path.'img/icons/'.$size_extra.$image)) {
706
                // Checking the main/img/icons/XXX/ folder
707
                $icon = $w_code_path.'img/icons/'.$size_extra.$image;
708
            }
709
        }
710
711
        // Special code to enable SVG - refs #7359 - Needs more work
712
        // The code below does something else to "test out" SVG: for each icon,
713
        // it checks if there is an SVG version. If so, it uses it.
714
        // When moving this to production, the return_icon() calls should
715
        // ask for the SVG version directly
716
        $svgIcons = api_get_setting('icons_mode_svg');
717
        if ('true' == $svgIcons && false == $return_only_path) {
718
            $svgImage = substr($image, 0, -3).'svg';
719
            if (is_file($code_path.$theme.'svg/'.$svgImage)) {
720
                $icon = $w_code_path.$theme.'svg/'.$svgImage;
721
            } elseif (is_file($code_path.'img/icons/svg/'.$svgImage)) {
722
                $icon = $w_code_path.'img/icons/svg/'.$svgImage;
723
            }
724
725
            if (empty($additional_attributes['height'])) {
726
                $additional_attributes['height'] = $size;
727
            }
728
            if (empty($additional_attributes['width'])) {
729
                $additional_attributes['width'] = $size;
730
            }
731
        }
732
733
        if ($return_only_path) {
734
            return $icon;
735
        }
736
737
        $img = self::img($icon, $alt_text, $additional_attributes);
738
        if (SHOW_TEXT_NEAR_ICONS == true && !empty($alt_text)) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
739
            if ($show_text) {
740
                $img = "$img $alt_text";
741
            }
742
        }
743
744
        return $img;
745
    }
746
747
    /**
748
     * Returns the htmlcode for an image.
749
     *
750
     * @param string $image_path            the filename of the file (in the main/img/ folder
751
     * @param string $alt_text              the alt text (probably a language variable)
752
     * @param array  $additional_attributes (for instance height, width, onclick, ...)
753
     * @param bool   $filterPath            Optional. Whether filter the image path. Default is true
754
     *
755
     * @return string
756
     *
757
     * @author Julio Montoya 2010
758
     */
759
    public static function img(
760
        $image_path,
761
        $alt_text = '',
762
        $additional_attributes = null,
763
        $filterPath = true
764
    ) {
765
        if (empty($image_path)) {
766
            return '';
767
        }
768
        // Sanitizing the parameter $image_path
769
        if ($filterPath) {
770
            $image_path = Security::filter_img_path($image_path);
771
        }
772
773
        // alt text = the image name if there is none provided (for XHTML compliance)
774
        if ('' == $alt_text) {
775
            $alt_text = basename($image_path);
776
        }
777
778
        if (empty($additional_attributes)) {
779
            $additional_attributes = [];
780
        }
781
782
        $additional_attributes['src'] = $image_path;
783
784
        if (empty($additional_attributes['alt'])) {
785
            $additional_attributes['alt'] = $alt_text;
786
        }
787
        if (empty($additional_attributes['title'])) {
788
            $additional_attributes['title'] = $alt_text;
789
        }
790
791
        return self::tag('img', '', $additional_attributes);
792
    }
793
794
    /**
795
     * Returns the htmlcode for a tag (h3, h1, div, a, button), etc.
796
     *
797
     * @param string $tag                   the tag name
798
     * @param string $content               the tag's content
799
     * @param array  $additional_attributes (for instance height, width, onclick, ...)
800
     *
801
     * @return string
802
     *
803
     * @author Julio Montoya 2010
804
     */
805
    public static function tag($tag, $content, $additional_attributes = [])
806
    {
807
        $attribute_list = '';
808
        // Managing the additional attributes
809
        if (!empty($additional_attributes) && is_array($additional_attributes)) {
810
            $attribute_list = '';
811
            foreach ($additional_attributes as $key => &$value) {
812
                $attribute_list .= $key.'="'.$value.'" ';
813
            }
814
        }
815
        //some tags don't have this </XXX>
816
        if (in_array($tag, ['img', 'input', 'br'])) {
817
            $return_value = '<'.$tag.' '.$attribute_list.' />';
818
        } else {
819
            $return_value = '<'.$tag.' '.$attribute_list.' >'.$content.'</'.$tag.'>';
820
        }
821
822
        return $return_value;
823
    }
824
825
    /**
826
     * Creates a URL anchor.
827
     *
828
     * @param string $name
829
     * @param string $url
830
     * @param array  $attributes
831
     *
832
     * @return string
833
     */
834
    public static function url($name, $url, $attributes = [])
835
    {
836
        if (!empty($url)) {
837
            $url = preg_replace('#&amp;#', '&', $url);
838
            $url = htmlspecialchars($url, ENT_QUOTES, 'UTF-8');
839
            $attributes['href'] = $url;
840
        }
841
842
        return self::tag('a', $name, $attributes);
843
    }
844
845
    /**
846
     * Creates a div tag.
847
     *
848
     * @param string $content
849
     * @param array  $attributes
850
     *
851
     * @return string
852
     */
853
    public static function div($content, $attributes = [])
854
    {
855
        return self::tag('div', $content, $attributes);
856
    }
857
858
    /**
859
     * Creates a span tag.
860
     */
861
    public static function span($content, $attributes = [])
862
    {
863
        return self::tag('span', $content, $attributes);
864
    }
865
866
    /**
867
     * Displays an HTML input tag.
868
     */
869
    public static function input($type, $name, $value, $attributes = [])
870
    {
871
        if (isset($type)) {
872
            $attributes['type'] = $type;
873
        }
874
        if (isset($name)) {
875
            $attributes['name'] = $name;
876
        }
877
        if (isset($value)) {
878
            $attributes['value'] = $value;
879
        }
880
881
        return self::tag('input', '', $attributes);
882
    }
883
884
    /**
885
     * Displays an HTML select tag.
886
     */
887
    public static function select(
888
        string $name,
889
        array $values,
890
        mixed $default = -1,
891
        array $extra_attributes = [],
892
        bool $show_blank_item = true,
893
        string $blank_item_text = ''
894
    ): string {
895
        $html = '';
896
        $extra = '';
897
        $default_id = 'id="'.$name.'" ';
898
        $extra_attributes = array_merge(
899
            ['class' => 'p-dropdown p-component p-inputwrapper p-inputwrapper-filled'],
900
            $extra_attributes
901
        );
902
        foreach ($extra_attributes as $key => $parameter) {
903
            if ('id' == $key) {
904
                $default_id = '';
905
            }
906
            $extra .= $key.'="'.$parameter.'" ';
907
        }
908
        $html .= '<select name="'.$name.'" '.$default_id.' '.$extra.'>';
909
910
        if ($show_blank_item) {
911
            if (empty($blank_item_text)) {
912
                $blank_item_text = get_lang('Select');
913
            } else {
914
                $blank_item_text = Security::remove_XSS($blank_item_text);
915
            }
916
            $html .= self::tag(
917
                'option',
918
                '-- '.$blank_item_text.' --',
0 ignored issues
show
Bug introduced by
Are you sure $blank_item_text of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

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

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

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

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

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

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

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

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

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

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

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

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

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