Passed
Pull Request — master (#7151)
by
unknown
09:27
created

sort_by_order()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use Chamilo\CoreBundle\Enums\ActionIcon;
6
use Chamilo\CoreBundle\Enums\ObjectIcon;
7
use Chamilo\CoreBundle\Enums\StateIcon;
8
use Chamilo\CoreBundle\Enums\ToolIcon;
9
use Chamilo\CoreBundle\Framework\Container;
10
use Chamilo\CoreBundle\Helpers\ChamiloHelper;
11
use Chamilo\CourseBundle\Entity\CQuiz;
12
use ChamiloSession as Session;
13
14
require_once __DIR__.'/../inc/global.inc.php';
15
16
$current_course_tool = TOOL_TRACKING;
17
$course = api_get_course_entity();
18
if (null === $course) {
19
    api_not_allowed(true);
20
}
21
$courseInfo = api_get_course_info();
22
$sessionId = api_get_session_id();
23
$is_allowedToTrack = Tracking::isAllowToTrack($sessionId);
24
$session = api_get_session_entity($sessionId);
25
26
if (!$is_allowedToTrack) {
27
    api_not_allowed(true);
28
}
29
30
// Keep course_code form as it is loaded (global) by the table's get_user_data.
31
$courseCode = $course->getCode();
32
$courseId = $course->getId();
33
$parameters['cid'] = isset($_GET['cid']) ? (int) $_GET['cid'] : '';
34
$parameters['id_session'] = $sessionId;
35
$parameters['from'] = isset($_GET['myspace']) ? Security::remove_XSS($_GET['myspace']) : null;
36
$parameters['user_active'] = isset($_REQUEST['user_active']) && is_numeric($_REQUEST['user_active']) ? (int) $_REQUEST['user_active'] : null;
37
38
// PERSON_NAME_DATA_EXPORT is buggy.
39
$sortByFirstName = api_sort_by_first_name();
40
$from_myspace = false;
41
$from = $_GET['from'] ?? null;
42
$origin = api_get_origin();
43
$lpShowMaxProgress = 'true' === api_get_setting('lp.lp_show_max_progress_instead_of_average');
44
if ('true' === api_get_setting('lp.lp_show_max_progress_or_average_enable_course_level_redefinition')) {
45
    $lpShowProgressCourseSetting = api_get_course_setting('lp_show_max_or_average_progress');
46
    if (in_array($lpShowProgressCourseSetting, ['max', 'average'])) {
47
        $lpShowMaxProgress = ('max' === $lpShowProgressCourseSetting);
48
    }
49
}
50
51
// Starting the output buffering when we are exporting the information.
52
$export_csv = isset($_GET['export']) && 'csv' === $_GET['export'];
53
54
$this_section = SECTION_COURSES;
55
if ('myspace' === $from) {
56
    $from_myspace = true;
57
    $this_section = 'session_my_space';
58
}
59
60
// If the user is a HR director (drh).
61
if (api_is_drh()) {
62
    // Blocking course for drh.
63
    if (api_drh_can_access_all_session_content()) {
64
        // If the drh has been configured to be allowed to see all session content, give him access to the session courses.
65
        $coursesFromSession = SessionManager::getAllCoursesFollowedByUser(api_get_user_id(), null);
66
        $coursesFromSessionCodeList = [];
67
        if (!empty($coursesFromSession)) {
68
            foreach ($coursesFromSession as $courseItem) {
69
                $coursesFromSessionCodeList[$courseItem['code']] = $courseItem['code'];
70
            }
71
        }
72
73
        $coursesFollowedList = CourseManager::get_courses_followed_by_drh(api_get_user_id());
74
        if (!empty($coursesFollowedList)) {
75
            $coursesFollowedList = array_keys($coursesFollowedList);
76
        }
77
78
        if (!in_array($courseCode, $coursesFollowedList)) {
79
            if (!in_array($courseCode, $coursesFromSessionCodeList)) {
80
                api_not_allowed(true);
81
            }
82
        }
83
    } else {
84
        // If the drh has *not* been configured to be allowed to see all session content,
85
        // then check if he has also been given access to the corresponding courses.
86
        $coursesFollowedList = CourseManager::get_courses_followed_by_drh(api_get_user_id());
87
        $coursesFollowedList = array_keys($coursesFollowedList);
88
        if (!in_array($courseId, $coursesFollowedList)) {
89
            api_not_allowed(true);
90
        }
91
    }
92
}
93
94
$additionalParams = '';
95
if (isset($_GET['additional_profile_field'])) {
96
    foreach ($_GET['additional_profile_field'] as $fieldId) {
97
        $additionalParams .= '&additional_profile_field[]='.(int) $fieldId;
98
    }
99
}
100
101
if (isset($parameters['user_active'])) {
102
    $additionalParams .= '&user_active='.(int) $parameters['user_active'];
103
}
104
105
if ($export_csv || isset($_GET['csv'])) {
106
    if (!empty($sessionId)) {
107
        Session::write('id_session', $sessionId);
108
    }
109
    ob_start();
110
}
111
$columnsToHideFromSetting = api_get_setting('course.course_log_hide_columns', true);
112
$columnsToHide = [0, 8, 9, 10, 11];
113
if (!empty($columnsToHideFromSetting) && isset($columnsToHideFromSetting['columns'])) {
114
    $columnsToHide = $columnsToHideFromSetting['columns'];
115
}
116
$columnsToHide = json_encode($columnsToHide);
117
$csv_content = [];
118
$visibleIcon = Display::return_icon(
119
    'visible.png',
120
    get_lang('Hide column'),
121
    ['align' => 'absmiddle', 'hspace' => '3px']
122
);
123
124
$exportInactiveUsers = api_get_path(WEB_CODE_PATH).'tracking/courseLog.php?'.api_get_cidreq().'&'.$additionalParams;
125
126
// Scripts for reporting array hide/show columns.
127
$js = "<script>
128
    // Hide column and display the button to unhide it.
129
    function foldup(id) {
130
        var show = function () { \$(this).css('outline', '1px solid red') };
131
        var hide = function () { \$(this).css('outline', '1px solid gree') };
132
133
        \$('#reporting_table .data_table tr td:nth-child(' + (id + 1) + ')').toggle();
134
        \$('#reporting_table .data_table tr th:nth-child(' + (id + 1) + ')').toggle();
135
        \$('div#unhideButtons a:nth-child(' + (id + 1) + ')').toggle();
136
    }
137
138
    // Add the red cross on top of each column.
139
    function init_hide() {
140
        \$('#reporting_table .data_table tr th').each(
141
            function(index) {
142
                \$(this).prepend(
143
                    '<div style=\"cursor:pointer\" onclick=\"foldup(' + index + ')\">".Display::getMdiIcon(StateIcon::ACTIVE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Hide column'))."</div>'
144
                );
145
            }
146
        );
147
    }
148
149
    // Hide some column at startup.
150
    // Be sure that these columns always exist (see headers = array()).
151
    \$(function() {
152
        init_hide();
153
        var columnsToHide = ".$columnsToHide.";
154
        if (columnsToHide) {
155
            columnsToHide.forEach(function(id) {
156
                foldup(id);
157
            });
158
        }
159
        \$('#download-csv').on('click', function (e) {
160
            e.preventDefault();
161
            location.href = '".$exportInactiveUsers.'&csv=1&since='."'+\$('#reminder_form_since').val();
162
        });
163
    })
164
</script>";
165
$htmlHeadXtra[] = $js;
166
167
$htmlHeadXtra[] = <<<CSSJS
168
<style>
169
  /* Toolbar spacing and compact controls */
170
  #course_log {
171
    margin-top: 8px;
172
    margin-bottom: 8px;
173
  }
174
175
  #course_log .btn,
176
  #course_log .form-control,
177
  #course_log select {
178
    font-size: 13px;
179
  }
180
181
  /* Advanced search container */
182
  #advanced_search_options {
183
    background: #f9fafb;
184
    border: 1px solid #e5e7eb;
185
    border-radius: 8px;
186
    padding: 12px 14px;
187
    margin: 8px 0 12px;
188
  }
189
190
  #advanced_search_options .form-group {
191
    display: grid;
192
    grid-template-columns: 220px 1fr;
193
    gap: 8px;
194
    align-items: center;
195
    margin-bottom: 8px;
196
  }
197
198
  #advanced_search_options .form-group > label {
199
    font-weight: 600;
200
    margin: 0;
201
    font-size: 13px;
202
    color: #374151;
203
  }
204
205
  #advanced_search_options .form-control,
206
  #advanced_search_options select {
207
    max-width: 100%;
208
    font-size: 13px;
209
    padding: 4px 6px;
210
    height: auto;
211
  }
212
213
  #advanced_search_options .btn {
214
    font-size: 13px;
215
    padding: 4px 10px;
216
  }
217
218
  @media (max-width: 992px) {
219
    #advanced_search_options .form-group {
220
      grid-template-columns: 1fr;
221
    }
222
  }
223
224
  #advanced_search_options .has-long-list > div:last-child {
225
    max-height: 260px;
226
    overflow: auto;
227
    border: 1px solid #e5e7eb;
228
    border-radius: 8px;
229
    padding: 8px;
230
    background: #ffffff;
231
  }
232
233
  @media (min-width: 992px) {
234
    #advanced_search_options .has-long-list > div:last-child {
235
      column-count: 2;
236
      column-gap: 16px;
237
    }
238
239
    #advanced_search_options .has-long-list .radio,
240
    #advanced_search_options .has-long-list .checkbox {
241
      break-inside: avoid;
242
    }
243
  }
244
245
  /* Show hidden columns buttons */
246
  #unhideButtons {
247
    margin: 8px 0 4px;
248
    display: flex;
249
    flex-wrap: wrap;
250
    gap: 4px;
251
  }
252
253
  /* Main reporting table */
254
  #reporting_table {
255
    margin-top: 8px;
256
  }
257
258
  #reporting_table .data_table {
259
    border: 1px solid #e5e7eb;
260
    border-radius: 6px;
261
    overflow: hidden;
262
  }
263
264
  #reporting_table .data_table th,
265
  #reporting_table .data_table td {
266
    padding: 4px 6px;
267
    font-size: 13px;
268
    vertical-align: middle;
269
  }
270
271
  #reporting_table .data_table th {
272
    background: #f9fafb;
273
    border-bottom: 1px solid #e5e7eb;
274
  }
275
276
  #reporting_table .data_table tr:nth-child(even) td {
277
    background: #fdfdfd;
278
  }
279
280
  /* Free users anchor and button */
281
  #free-users {
282
    scroll-margin-top: 80px;
283
  }
284
285
  #free-users .btn {
286
    font-size: 13px;
287
    padding: 4px 10px;
288
  }
289
290
  /* Generic grey borders for detailed tables */
291
  .table.table-bordered > tbody > tr > td,
292
  .table.table-bordered > thead > tr > th {
293
    border-color: #e5e7eb;
294
  }
295
   .user-teacher,
296
  .user-coachs {
297
    list-style: none;
298
    padding-left: 0;
299
    margin: 4px 0 0;
300
  }
301
302
  .user-teacher li,
303
  .user-coachs li {
304
    display: flex;
305
    align-items: center;
306
    gap: 6px;
307
    font-size: 13px;
308
    padding: 2px 0;
309
  }
310
  .course-log-nav {
311
     display: flex;
312
      flex-wrap: wrap;
313
      gap: 6px;
314
      align-items: center;
315
  }
316
317
  .course-log-nav-link--active .course-log-nav-icon {
318
    color: #ddd;
319
  }
320
  .course-log-meta {
321
    display: flex;
322
    flex-wrap: wrap;
323
    gap: 16px;
324
    margin: 12px 0 4px;
325
  }
326
327
  .course-log-meta__column {
328
    flex: 1 1 260px;
329
    min-width: 260px;
330
  }
331
332
  .course-log-card {
333
    border: 1px solid #e5e7eb;
334
    border-radius: 8px;
335
    background: #ffffff;
336
    padding: 10px 12px;
337
  }
338
339
  .course-log-card__header {
340
    display: flex;
341
    align-items: center;
342
    gap: 6px;
343
    margin-bottom: 6px;
344
  }
345
346
  .course-log-card__icon {
347
    font-size: 18px;
348
  }
349
350
  .course-log-card__title {
351
    font-weight: 600;
352
    font-size: 14px;
353
    color: #111827;
354
  }
355
356
  .course-log-card__body {
357
    font-size: 13px;
358
  }
359
360
  .course-log-session-list {
361
    list-style: none;
362
    padding-left: 0;
363
    margin: 0;
364
  }
365
366
  .course-log-session-item {
367
    display: flex;
368
    align-items: center;
369
    gap: 6px;
370
    padding: 3px 0;
371
    font-size: 13px;
372
  }
373
</style>
374
<script>
375
  $(function () {
376
    // Mark long lists to render them in multiple columns.
377
    $('#advanced_search_options .form-group').each(function () {
378
      var inputs = $(this).find('input[type=checkbox], input[type=radio]');
379
      if (inputs.length > 6) {
380
        $(this).addClass('has-long-list');
381
      }
382
    });
383
  });
384
</script>
385
CSSJS;
386
$htmlHeadXtra[] = <<<CSSJS
387
<style>
388
389
  /* Extra fields form: grid layout and spacing */
390
#advanced_search_options #extra_fields {
391
  display: grid;
392
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
393
  gap: 12px 18px;
394
  align-items: flex-start;
395
  margin-top: 6px;
396
}
397
398
/* Each span behaves like a vertical field block */
399
#advanced_search_options #extra_fields > span {
400
  display: flex;
401
  flex-direction: column;
402
  gap: 6px;
403
}
404
405
/* Labels inside extra_fields */
406
#advanced_search_options #extra_fields label {
407
  font-weight: 600;
408
  font-size: 13px;
409
  color: #374151;
410
  margin-bottom: 2px;
411
}
412
413
#advanced_search_options #extra_hobbies {
414
  min-height: 80px;
415
}
416
#advanced_search_options #extra_fields .field-radiobutton {
417
  width: 50%;
418
  float: left;
419
}
420
421
/* Inputs and selects full width */
422
#advanced_search_options #extra_fields input[type="text"],
423
#advanced_search_options #extra_fields select,
424
#advanced_search_options #extra_fields .flatpickr-wrapper,
425
#advanced_search_options #extra_fields .p-inputtext,
426
#advanced_search_options #extra_fields .p-select {
427
  width: 100%;
428
}
429
430
/* Radio groups in a clean vertical list */
431
#advanced_search_options #extra_fields .field-radiobutton {
432
  display: flex;
433
  align-items: center;
434
  gap: 6px;
435
  margin-bottom: 4px;
436
}
437
438
#advanced_search_options #extra_fields .field-radiobutton label {
439
  margin: 0;
440
}
441
442
.tracking-box-title {
443
 font-size: 18px;
444
 text-align: center;
445
}
446
447
  /* Main panel: give some breathing room inside */
448
  #course-log-main-panel .panel-body {
449
    padding: 18px 22px;
450
  }
451
452
  #course-log-main-panel .card, .card {
453
    padding: 8px;
454
  }
455
456
  .card .field-checkbox, .card .field-radiobutton {
457
    justify-content: normal;
458
  }
459
460
  /* Toolbar spacing and compact controls */
461
  #course_log {
462
    margin-top: 8px;
463
    margin-bottom: 12px;
464
  }
465
466
  #course_log .btn,
467
  #course_log .form-control,
468
  #course_log select {
469
    font-size: 13px;
470
  }
471
472
  /* Advanced search container */
473
  #advanced_search_options {
474
    background: #f9fafb;
475
    border: 1px solid #e5e7eb;
476
    border-radius: 8px;
477
    padding: 16px 18px;
478
    margin: 12px 0 16px;
479
  }
480
481
  #advanced_search_options .form-horizontal {
482
    margin-bottom: 0;
483
  }
484
485
  #advanced_search_options .form-group {
486
    display: grid;
487
    grid-template-columns: 220px minmax(0, 1fr);
488
    gap: 6px 12px;
489
    align-items: flex-start;
490
    margin-bottom: 10px;
491
  }
492
493
  #advanced_search_options .form-group:last-child {
494
    margin-bottom: 0;
495
  }
496
497
  #advanced_search_options .control-label,
498
  #advanced_search_options label.control-label {
499
    font-weight: 600;
500
    margin: 0;
501
    font-size: 13px;
502
    color: #374151;
503
    padding-top: 4px;
504
  }
505
506
  #advanced_search_options .form-control,
507
  #advanced_search_options select,
508
  #advanced_search_options .select2-container {
509
    width: 100% !important;
510
    max-width: 100%;
511
    font-size: 13px;
512
    padding: 4px 6px;
513
    height: auto;
514
  }
515
516
  #advanced_search_options .btn {
517
    font-size: 13px;
518
    padding: 4px 10px;
519
  }
520
521
  /* Large radio / checkbox lists: scroll and columns */
522
  #advanced_search_options .has-long-list > div:last-child,
523
  #advanced_search_options .has-long-list .col-sm-9 {
524
    max-height: 260px;
525
    overflow: auto;
526
    border: 1px solid #e5e7eb;
527
    border-radius: 6px;
528
    padding: 6px 8px;
529
    background: #ffffff;
530
  }
531
532
  #advanced_search_options .has-long-list .radio,
533
  #advanced_search_options .has-long-list .checkbox {
534
    margin: 0 0 4px 0;
535
  }
536
537
  @media (min-width: 992px) {
538
    #advanced_search_options .has-long-list > div:last-child,
539
    #advanced_search_options .has-long-list .col-sm-9 {
540
      column-count: 2;
541
      column-gap: 16px;
542
    }
543
544
    #advanced_search_options .has-long-list .radio,
545
    #advanced_search_options .has-long-list .checkbox {
546
      break-inside: avoid;
547
    }
548
  }
549
550
  @media (max-width: 991px) {
551
    #advanced_search_options .form-group {
552
      grid-template-columns: 1fr;
553
    }
554
  }
555
556
  /* Show hidden columns buttons */
557
  #unhideButtons {
558
    margin: 12px 0 6px;
559
    display: flex;
560
    flex-wrap: wrap;
561
    gap: 4px;
562
  }
563
564
  /* Main reporting table */
565
  #reporting_table {
566
    margin-top: 8px;
567
  }
568
569
  #reporting_table .data_table {
570
    border: 1px solid #e5e7eb;
571
    border-radius: 6px;
572
    overflow: hidden;
573
  }
574
575
  #reporting_table .data_table th,
576
  #reporting_table .data_table td {
577
    padding: 4px 6px;
578
    font-size: 13px;
579
    vertical-align: middle;
580
  }
581
582
  #reporting_table .data_table th {
583
    background: #f9fafb;
584
    border-bottom: 1px solid #e5e7eb;
585
  }
586
587
  #reporting_table .data_table tr:nth-child(even) td {
588
    background: #fdfdfd;
589
  }
590
591
  /* Free users anchor and button */
592
  #free-users {
593
    scroll-margin-top: 80px;
594
  }
595
596
  #free-users .btn {
597
    font-size: 13px;
598
    padding: 4px 10px;
599
  }
600
601
  /* Generic grey borders for detailed tables */
602
  .table.table-bordered > tbody > tr > td,
603
  .table.table-bordered > thead > tr > th {
604
    border-color: #e5e7eb;
605
  }
606
607
  /* Trainers / coaches lists inside cards */
608
  .user-teacher,
609
  .user-coachs {
610
    list-style: none;
611
    padding-left: 0;
612
    margin: 4px 0 0;
613
  }
614
615
  .user-teacher li,
616
  .user-coachs li {
617
    display: flex;
618
    align-items: center;
619
    gap: 6px;
620
    font-size: 13px;
621
    padding: 2px 0;
622
  }
623
624
  /* Top meta cards (Trainers / Session list) */
625
  .course-log-meta {
626
    display: flex;
627
    flex-wrap: wrap;
628
    gap: 16px;
629
    margin: 16px 0 8px;
630
  }
631
632
  .course-log-meta__column {
633
    flex: 1 1 280px;
634
    min-width: 260px;
635
  }
636
637
  .course-log-card {
638
    border: 1px solid #e5e7eb;
639
    border-radius: 10px;
640
    background: #ffffff;
641
    padding: 12px 14px;
642
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04);
643
  }
644
645
  .course-log-card__header {
646
    display: flex;
647
    align-items: center;
648
    gap: 6px;
649
    margin-bottom: 8px;
650
  }
651
652
  .course-log-card__icon {
653
    font-size: 18px;
654
  }
655
656
  .course-log-card__title {
657
    font-weight: 600;
658
    font-size: 14px;
659
    color: #111827;
660
  }
661
662
  .course-log-card__subsection-title {
663
    font-weight: 600;
664
    font-size: 13px;
665
    margin-top: 8px;
666
    margin-bottom: 2px;
667
    color: #4b5563;
668
  }
669
670
  .course-log-card__body {
671
    font-size: 13px;
672
  }
673
674
  .course-log-session-list {
675
    list-style: none;
676
    padding-left: 0;
677
    margin: 0;
678
  }
679
680
  .course-log-session-item {
681
    display: flex;
682
    align-items: center;
683
    gap: 6px;
684
    padding: 3px 0;
685
    font-size: 13px;
686
  }
687
</style>
688
<script>
689
  $(function () {
690
    // Mark long lists to render them in multiple columns.
691
    $('#advanced_search_options .form-group').each(function () {
692
      var inputs = $(this).find('input[type=checkbox], input[type=radio]');
693
      if (inputs.length > 6) {
694
        $(this).addClass('has-long-list');
695
      }
696
    });
697
  });
698
</script>
699
CSSJS;
700
701
702
// Database table definitions.
703
// @todo remove these calls.
704
$TABLETRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
705
$TABLECOURSUSER = Database::get_main_table(TABLE_MAIN_COURSE_USER);
706
$TABLECOURSE = Database::get_main_table(TABLE_MAIN_COURSE);
707
$table_user = Database::get_main_table(TABLE_MAIN_USER);
708
$TABLEQUIZ = Database::get_course_table(TABLE_QUIZ_TEST);
709
710
$userEditionExtraFieldToCheck = 'true' === api_get_setting('workflows.user_edition_extra_field_to_check');
711
712
// Breadcrumbs.
713
if ('resume_session' === $origin) {
714
    $interbreadcrumb[] = [
715
        'url' => '../admin/index.php',
716
        'name' => get_lang('Administration'),
717
    ];
718
    $interbreadcrumb[] = [
719
        'url' => '../session/session_list.php',
720
        'name' => get_lang('Session list'),
721
    ];
722
    $interbreadcrumb[] = [
723
        'url' => '../session/resume_session.php?id_session='.$sessionId,
724
        'name' => get_lang('Session overview'),
725
    ];
726
}
727
728
$view = isset($_REQUEST['view']) ? $_REQUEST['view'] : '';
729
$nameTools = get_lang('Reporting');
730
731
$tpl = new Template($nameTools);
732
733
// Getting all the students of the course.
734
if (empty($sessionId)) {
735
    // Registered students in a course outside session.
736
    $studentList = CourseManager::get_student_list_from_course_code($courseCode);
737
} else {
738
    // Registered students in session.
739
    $studentList = CourseManager::get_student_list_from_course_code($courseCode, true, $sessionId);
740
}
741
742
$nbStudents = count($studentList);
743
$user_ids = array_keys($studentList);
744
$extra_info = [];
745
$userProfileInfo = [];
746
747
// Getting all the additional information of an additional profile field.
748
if (isset($_GET['additional_profile_field'])) {
749
    $user_array = [];
750
    foreach ($studentList as $key => $item) {
751
        $user_array[] = $key;
752
    }
753
754
    $extraField = new ExtraField('user');
755
    foreach ($_GET['additional_profile_field'] as $fieldId) {
756
        // Fetching only the users that are loaded, not all users in the portal.
757
        $userProfileInfo[$fieldId] = TrackingCourseLog::getAdditionalProfileInformationOfFieldByUser(
758
            $fieldId,
759
            $user_array
760
        );
761
        $extra_info[$fieldId] = $extraField->getFieldInfoByFieldId($fieldId);
762
    }
763
}
764
765
Session::write('additional_user_profile_info', $userProfileInfo);
766
Session::write('extra_field_info', $extra_info);
767
768
$defaultExtraFields = [];
769
$defaultExtraFieldsFromSettings = api_get_setting('course.course_log_default_extra_fields', true);
770
if (!empty($defaultExtraFieldsFromSettings) && isset($defaultExtraFieldsFromSettings['extra_fields'])) {
771
    $defaultExtraFields = $defaultExtraFieldsFromSettings['extra_fields'];
772
    $defaultExtraInfo = [];
773
    $defaultUserProfileInfo = [];
774
775
    foreach ($defaultExtraFields as $fieldName) {
776
        $extraFieldInfo = UserManager::get_extra_field_information_by_name($fieldName);
777
778
        if (!empty($extraFieldInfo)) {
779
            // Fetching only the users that are loaded, not all users in the portal.
780
            $defaultUserProfileInfo[$extraFieldInfo['id']] = TrackingCourseLog::getAdditionalProfileInformationOfFieldByUser(
781
                $extraFieldInfo['id'],
782
                $user_ids
783
            );
784
            $defaultExtraInfo[$extraFieldInfo['id']] = $extraFieldInfo;
785
        }
786
    }
787
788
    Session::write('default_additional_user_profile_info', $defaultUserProfileInfo);
789
    Session::write('default_extra_field_info', $defaultExtraInfo);
790
}
791
792
Display::display_header($nameTools, 'Tracking');
793
794
$actionsLeft = TrackingCourseLog::actionsLeft('users', $sessionId, false);
795
796
$actionsRight = '<a href="javascript: void(0);" onclick="javascript: window.print();">'.
797
    Display::getMdiIcon(ActionIcon::PRINT, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Print')).'</a>';
798
799
$users_tracking_per_page = '';
800
if (isset($_GET['users_tracking_per_page'])) {
801
    $users_tracking_per_page = '&users_tracking_per_page='.intval($_GET['users_tracking_per_page']);
802
}
803
804
$showNonRegistered = isset($_GET['show_non_registered']) ? (int) $_GET['show_non_registered'] : 0;
805
$freeUsers = [];
806
if ($showNonRegistered) {
807
    $freeUsers = Statistics::getNonRegisteredActiveUsersInCourse($courseId, (int) $sessionId);
808
}
809
810
$actionsRight .= '<a
811
    href="'.api_get_self().'?'.api_get_cidreq().'&export=csv&'.$additionalParams.$users_tracking_per_page.'">
812
     '.Display::getMdiIcon(ActionIcon::EXPORT_CSV, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('CSV export')).'</a>';
813
814
// Create a search-box.
815
$form_search = new FormValidator(
816
    'search_simple',
817
    'GET',
818
    api_get_path(WEB_CODE_PATH).'tracking/courseLog.php?'.api_get_cidreq(),
819
    '',
820
    [],
821
    FormValidator::LAYOUT_INLINE
822
);
823
$renderer = $form_search->defaultRenderer();
824
$renderer->setCustomElementTemplate('<span>{element}</span>');
825
$form_search->addHidden('from', Security::remove_XSS($from));
826
$form_search->addHidden('sessionId', $sessionId);
827
$form_search->addHidden('sid', $sessionId);
828
$form_search->addHidden('cid', $courseId);
829
$form_search->addElement('text', 'user_keyword');
830
$form_search->addButtonSearch(get_lang('Search users'));
831
echo Display::toolbarAction(
832
    'course_log',
833
    [$actionsLeft, $form_search->returnForm(), $actionsRight]
834
);
835
836
$course_name = get_lang('Course').' '.$course->getTitle();
837
838
if ($sessionId) {
839
    $titleSession = Display::getMdiIcon(
840
            ObjectIcon::SESSION,
841
            'ch-tool-icon',
842
            null,
843
            ICON_SIZE_SMALL,
844
            get_lang('Session')
845
        ).' '.api_get_session_name($sessionId);
846
847
    $titleCourse = Display::getMdiIcon(
848
            ObjectIcon::COURSE,
849
            'ch-tool-icon',
850
            null,
851
            ICON_SIZE_SMALL,
852
            get_lang('Course')
853
        ).' '.$course_name;
854
} else {
855
    // When there is no session, show only course info
856
    $titleSession = Display::getMdiIcon(
857
            ObjectIcon::COURSE,
858
            'ch-tool-icon',
859
            null,
860
            ICON_SIZE_SMALL,
861
            get_lang('Course')
862
        ).' '.$course->getTitle();
863
    $titleCourse = '';
864
}
865
866
$panelIcon = Display::getMdiIcon(
867
    ToolIcon::TRACKING,
868
    'ch-tool-icon',
869
    null,
870
    ICON_SIZE_SMALL,
871
    get_lang('Reporting')
872
);
873
874
// Panel title: tracking icon + course (if any) + session
875
if ($sessionId) {
876
    $panelTitle = $panelIcon.' '.$titleCourse.' &raquo; '.$titleSession;
877
} else {
878
    $panelTitle = $panelIcon.' '.$titleSession;
879
}
880
881
$teacherList = CourseManager::getTeacherListFromCourseCodeToString(
882
    $courseCode,
883
    ',',
884
    true,  // Add link to profile
885
    true   // Render as <ul> list
886
);
887
888
$coaches = null;
889
if (!empty($sessionId)) {
890
    $coaches = CourseManager::get_coachs_from_course_to_string(
891
        $sessionId,
892
        $courseId,
893
        ',',
894
        true,  // Add link to profile
895
        true   // Render as <ul> list
896
    );
897
}
898
899
/** @var string[] $sessionLinks */
900
$sessionLinks = [];
901
$showReporting = ('false' === api_get_setting('session.hide_reporting_session_list'));
902
903
if ($showReporting) {
904
    $sessionList = SessionManager::get_session_by_course($courseId);
905
906
    if (!empty($sessionList)) {
907
        $icon = Display::getMdiIcon(
908
            ObjectIcon::SESSION,
909
            'ch-tool-icon course-log-session-icon',
910
            null,
911
            ICON_SIZE_TINY
912
        );
913
914
        $urlWebCode = api_get_path(WEB_CODE_PATH);
915
        $isAdmin = api_is_platform_admin();
916
917
        foreach ($sessionList as $sessionItem) {
918
            if (!$isAdmin) {
919
                // Respect session visibility for non-admin users.
920
                $visibility = api_get_session_visibility($sessionItem['id'], $courseId);
921
                if (SESSION_INVISIBLE === $visibility) {
922
                    continue;
923
                }
924
925
                // Only show sessions where user is coach.
926
                $isCoach = api_is_coach($sessionItem['id'], $courseId);
927
                if (!$isCoach) {
928
                    continue;
929
                }
930
            }
931
932
            $url = $urlWebCode.'tracking/courseLog.php?cid='.$courseId.'&sid='.$sessionItem['id'].'&gid=0';
933
934
            $sessionLinks[] =
935
                '<li class="course-log-session-item">'.
936
                $icon.
937
                Display::url(Security::remove_XSS($sessionItem['title']), $url).
938
                '</li>';
939
        }
940
    }
941
}
942
943
$html = '';
944
945
if (!empty($teacherList) || !empty($coaches) || !empty($sessionLinks)) {
946
    $html .= '<div class="course-log-meta">';
947
948
    // Column: trainers / coaches
949
    if (!empty($teacherList) || !empty($coaches)) {
950
        $html .= '<div class="course-log-meta__column">';
951
        $html .= '<div class="course-log-card">';
952
953
        $html .= '<div class="course-log-card__header">';
954
        $html .= Display::getMdiIcon(
955
            'account-tie-outline',
956
            'ch-tool-icon course-log-card__icon',
957
            null,
958
            ICON_SIZE_SMALL,
959
            get_lang('Trainers')
960
        );
961
        $html .= '<span class="course-log-card__title">'.get_lang('Trainers').'</span>';
962
        $html .= '</div>';
963
964
        $html .= '<div class="course-log-card__body">';
965
966
        // Teacher list (returns an UL when orderList = true)
967
        if (!empty($teacherList)) {
968
            $html .= $teacherList;
969
        }
970
971
        // Coaches list
972
        if (!empty($coaches)) {
973
            $html .= '<div class="course-log-card__subsection-title">'.get_lang('Coaches').'</div>';
974
            $html .= $coaches;
975
        }
976
977
        $html .= '</div>'; // .course-log-card__body
978
        $html .= '</div>'; // .course-log-card
979
        $html .= '</div>'; // .course-log-meta__column
980
    }
981
982
    // Column: session list
983
    if (!empty($sessionLinks)) {
984
        $html .= '<div class="course-log-meta__column">';
985
        $html .= '<div class="course-log-card">';
986
987
        $html .= '<div class="course-log-card__header">';
988
        $html .= Display::getMdiIcon(
989
            ObjectIcon::SESSION,
990
            'ch-tool-icon course-log-card__icon',
991
            null,
992
            ICON_SIZE_SMALL,
993
            get_lang('Session list')
994
        );
995
        $html .= '<span class="course-log-card__title">'.get_lang('Session list').'</span>';
996
        $html .= '</div>';
997
998
        $html .= '<div class="course-log-card__body">';
999
        $html .= '<ul class="course-log-session-list">'.implode('', $sessionLinks).'</ul>';
1000
        $html .= '</div>'; // .course-log-card__body
1001
1002
        $html .= '</div>'; // .course-log-card
1003
        $html .= '</div>'; // .course-log-meta__column
1004
    }
1005
1006
    $html .= '</div>'; // .course-log-meta
1007
}
1008
1009
1010
$trackingColumn = $_GET['users_tracking_column'] ?? null;
1011
$trackingDirection = $_GET['users_tracking_direction'] ?? null;
1012
$hideReports = api_get_configuration_value('hide_course_report_graph');
1013
$conditions = [];
1014
1015
$groupList = GroupManager::get_group_list(null, $course, 1, $sessionId);
1016
$class = new UserGroupModel();
1017
//$options['where'] = [' usergroup.course_id = ? ' => $courseId];
1018
//$classes = $class->getUserGroupInCourse($options);
1019
$classes = $class->get_all();
1020
1021
$bestScoreLabel = get_lang('Score').' - '.get_lang('Best attempt');
1022
1023
// Show the charts part only if there are students subscribed to this course/session.
1024
if ($nbStudents > 0 || isset($parameters['user_active'])) {
1025
    // Classes.
1026
    $formClass = new FormValidator(
1027
        'classes',
1028
        'get',
1029
        api_get_self().'?'.api_get_cidreq().'&'.$additionalParams
1030
    );
1031
    $formClass->addHidden('cid', $courseId);
1032
    $formClass->addHidden('sid', $sessionId);
1033
    $groupIdList = ['--'];
1034
    $select = $formClass->addSelect('class_id', get_lang('Class').'/'.get_lang('Group'), $groupIdList);
1035
    $groupIdList = [];
1036
    foreach ($classes as $class) {
1037
        //$groupIdList['class_'.$class['id']] = $class['title'];
1038
        $groupIdList[] = ['text' => $class['title'], 'value' => 'class_'.$class['id']];
1039
    }
1040
    $select->addOptGroup($groupIdList, get_lang('Class'));
1041
    $groupIdList = [];
1042
    foreach ($groupList as $group) {
1043
        $groupIdList[] = ['text' => $group['title'], 'value' => 'group_'.$group['iid']];
1044
    }
1045
    $select->addOptGroup($groupIdList, get_lang('Group'));
1046
    $formClass->addButtonSearch(get_lang('Search'));
1047
1048
    // Extra fields.
1049
    $formExtraField = new FormValidator(
1050
        'extra_fields',
1051
        'get',
1052
        api_get_self().'?'.api_get_cidreq().'&'.$additionalParams
1053
    );
1054
    $formExtraField->addHidden('cid', $courseId);
1055
    $formExtraField->addHidden('sid', $sessionId);
1056
    if (isset($_GET['additional_profile_field'])) {
1057
        foreach ($_GET['additional_profile_field'] as $fieldId) {
1058
            $fieldId = Security::remove_XSS($fieldId);
1059
            $formExtraField->addHidden('additional_profile_field[]', $fieldId);
1060
            $formClass->addHidden('additional_profile_field[]', $fieldId);
1061
        }
1062
    }
1063
1064
    $extraField = new ExtraField('user');
1065
    $extraField->addElements($formExtraField, 0, [], true);
1066
    $formExtraField->addButtonSearch(get_lang('Search'));
1067
1068
    $numberStudentsCompletedLP = 0;
1069
    $averageStudentsTestScore = 0;
1070
    $scoresDistribution = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
1071
    $userScoreList = [];
1072
    $listStudentIds = [];
1073
    $timeStudent = [];
1074
    $certificateCount = 0;
1075
    $category = Category::load(
1076
        null,
1077
        null,
1078
        $courseId,
1079
        null,
1080
        null,
1081
        $sessionId
1082
    );
1083
1084
    $conditions = [];
1085
    $fields = [];
1086
1087
    if ($formClass->validate()) {
1088
        $classId = null;
1089
        $groupId = null;
1090
1091
        $part = $formClass->getSubmitValue('class_id');
1092
        $item = explode('_', $part);
1093
        if (isset($item[0]) && isset($item[1])) {
1094
            if ('class' === $item[0]) {
1095
                $classId = (int) $item[1];
1096
            } else {
1097
                $groupId = (int) $item[1];
1098
            }
1099
        }
1100
1101
        if (!empty($classId)) {
1102
            $whereCondition = " AND gu.usergroup_id = $classId ";
1103
            $tableGroup = Database::get_main_table(TABLE_USERGROUP_REL_USER);
1104
            $joins = " INNER JOIN $tableGroup gu ON (user.id = gu.user_id) ";
1105
            $conditions = ['where' => $whereCondition, 'inject_joins' => $joins];
1106
        }
1107
1108
        if (!empty($groupId)) {
1109
            $whereCondition = " AND gu.group_id = $groupId ";
1110
            $tableGroup = Database::get_course_table(TABLE_GROUP_USER);
1111
            $joins = " INNER JOIN $tableGroup gu ON (user.id = gu.user_id) ";
1112
            $conditions = ['where' => $whereCondition, 'inject_joins' => $joins];
1113
        }
1114
    }
1115
1116
    if ($formExtraField->validate()) {
1117
        $extraResult = $extraField->processExtraFieldSearch($_REQUEST, $formExtraField, 'user');
1118
        if (!empty($extraResult)) {
1119
            $conditions = $extraResult['condition'];
1120
            $fields = $extraResult['fields'];
1121
        }
1122
    }
1123
1124
    if (false === $hideReports) {
1125
        $conditions['course_id'] = $courseId;
1126
        $conditions['include_invited_users'] = false;
1127
        $usersTracking = TrackingCourseLog::getUserData(
1128
            0,
1129
            $nbStudents,
1130
            $trackingColumn,
1131
            $trackingDirection,
1132
            $conditions,
1133
            true,
1134
            false,
1135
            null,
1136
            (int) $sessionId,
1137
            $export_csv,
1138
            $user_ids
1139
        );
1140
        $userRepo = Container::getUserRepository();
1141
        foreach ($usersTracking as $userTracking) {
1142
            $user = $userRepo->findOneBy(['username' => $userTracking[3]]);
1143
            if (empty($user)) {
1144
                continue;
1145
            }
1146
            $userId = $user->getId();
1147
            if ('100%' === $userTracking[5]) {
1148
                $numberStudentsCompletedLP++;
1149
            }
1150
            $averageStudentTestScore = substr($userTracking[7], 0, -1);
1151
            $averageStudentsTestScore .= $averageStudentTestScore;
1152
1153
            $reducedAverage = ('100' === $averageStudentTestScore) ? 9 : floor((float) $averageStudentTestScore / 10);
1154
            if (isset($scoresDistribution[$reducedAverage])) {
1155
                $scoresDistribution[$reducedAverage]++;
1156
            }
1157
1158
            $scoreStudent = (float) substr($userTracking[5], 0, -1) + (float) substr($userTracking[7], 0, -1);
1159
            [$hours, $minutes, $seconds] = preg_split('/:/', $userTracking[4]);
1160
            $minutes = round((3600 * (int) $hours + 60 * (int) $minutes + (int) $seconds) / 60);
1161
1162
            $certificate = false;
1163
            if (isset($category[0]) && $category[0]->is_certificate_available($userId)) {
1164
                $certificate = true;
1165
                $certificateCount++;
1166
            }
1167
1168
            $listStudent = [
1169
                'id' => $userId,
1170
                'user' => $user,
1171
                'fullname' => UserManager::formatUserFullName($user),
1172
                'score' => floor($scoreStudent / 2),
1173
                'total_time' => $minutes,
1174
                'certicate' => $certificate,
1175
            ];
1176
            $listStudentIds[] = $userId;
1177
            $userScoreList[] = $listStudent;
1178
        }
1179
1180
        uasort($userScoreList, 'sort_by_order');
1181
        $averageStudentsTestScore = round($averageStudentsTestScore / $nbStudents);
1182
1183
        $colors = ChamiloHelper::getColorPalette(true, true, 10);
1184
        $tpl->assign('chart_colors', json_encode($colors));
1185
        $tpl->assign('certificate_count', $certificateCount);
1186
        $tpl->assign('score_distribution', json_encode($scoresDistribution));
1187
        $tpl->assign('json_time_student', json_encode($userScoreList));
1188
        $tpl->assign('students_test_score', $averageStudentsTestScore);
1189
        $tpl->assign('students_completed_lp', $numberStudentsCompletedLP);
1190
        $tpl->assign('number_students', $nbStudents);
1191
        $tpl->assign('top_students', $userScoreList);
1192
1193
        echo $tpl->fetch($tpl->get_template('tracking/tracking_course_log.tpl'));
1194
    }
1195
}
1196
1197
$html .= '<div style="margin-top: 16px;"></div>';
1198
$html .= Display::page_subheader2(
1199
    Display::getMdiIcon(
1200
        'account-multiple-outline',
1201
        'ch-tool-icon',
1202
        null,
1203
        ICON_SIZE_TINY,
1204
        get_lang('Learner list')
1205
    ).' '.get_lang('Learner list')
1206
);
1207
1208
$bestScoreLabel = get_lang('Score').' - '.get_lang('Only best attempts');
1209
if ($nbStudents > 0) {
1210
    $mainForm = new FormValidator(
1211
        'filter',
1212
        'get',
1213
        api_get_self().'?'.api_get_cidreq().'&'.$additionalParams
1214
    );
1215
    $mainForm->addButtonAdvancedSettings(
1216
        'advanced_search',
1217
        [get_lang('Advanced search')]
1218
    );
1219
    $mainForm->addHtml('<div id="advanced_search_options" style="display:none;">');
1220
    $mainForm->addHtml($formClass->returnForm());
1221
    $mainForm->addHtml($formExtraField->returnForm());
1222
    $mainForm->addHtml('</div>');
1223
1224
    $html .= $mainForm->returnForm();
1225
1226
    $getLangXDays = get_lang('%s days');
1227
    $form = new FormValidator(
1228
        'reminder_form',
1229
        'get',
1230
        api_get_path(WEB_CODE_PATH).'announcements/announcements.php?'.api_get_cidreq(),
1231
        null,
1232
        ['style' => 'margin-bottom: 10px'],
1233
        FormValidator::LAYOUT_INLINE
1234
    );
1235
    $options = [
1236
        2 => sprintf($getLangXDays, 2),
1237
        3 => sprintf($getLangXDays, 3),
1238
        4 => sprintf($getLangXDays, 4),
1239
        5 => sprintf($getLangXDays, 5),
1240
        6 => sprintf($getLangXDays, 6),
1241
        7 => sprintf($getLangXDays, 7),
1242
        15 => sprintf($getLangXDays, 15),
1243
        30 => sprintf($getLangXDays, 30),
1244
        'never' => get_lang('Never'),
1245
    ];
1246
    $el = $form->addSelect(
1247
        'since',
1248
        Display::getMdiIcon(StateIcon::WARNING, 'ch-tool-icon', null, ICON_SIZE_SMALL).get_lang('Remind learners inactive since'),
1249
        $options,
1250
        ['disable_js' => true, 'class' => 'col-sm-3']
1251
    );
1252
    $el->setSelected(7);
1253
    $form->addElement('hidden', 'action', 'add');
1254
    $form->addElement('hidden', 'remindallinactives', 'true');
1255
    $form->addElement('hidden', 'cid', api_get_course_int_id());
1256
    $form->addElement('hidden', 'sid', api_get_session_id());
1257
    $form->addButtonSend(get_lang('Notify'));
1258
1259
    $extraFieldSelect = TrackingCourseLog::displayAdditionalProfileFields();
1260
    if (!empty($extraFieldSelect)) {
1261
        $html .= $extraFieldSelect;
1262
    }
1263
1264
    $html .= $form->returnForm();
1265
1266
    if ($export_csv) {
1267
        $csv_content = [];
1268
        // Override the SortableTable "per page" limit if CSV.
1269
        $_GET['users_tracking_per_page'] = 1000000;
1270
    }
1271
1272
    if (false === $hideReports) {
1273
        $table = new SortableTableFromArray(
1274
            $usersTracking,
1275
            1,
1276
            20,
1277
            'users_tracking'
1278
        );
1279
        $table->total_number_of_items = $nbStudents;
1280
    } else {
1281
        $conditions['include_invited_users'] = true;
1282
        $conditions['course_id'] = $courseId;
1283
        $table = new SortableTable(
1284
            'users_tracking',
1285
            ['TrackingCourseLog', 'get_number_of_users'],
1286
            ['TrackingCourseLog', 'get_user_data'],
1287
            1,
1288
            20
1289
        );
1290
        $table->setDataFunctionParams($conditions);
1291
    }
1292
1293
    $parameters['cid'] = isset($_GET['cid']) ? Security::remove_XSS($_GET['cid']) : '';
1294
    $parameters['sid'] = $sessionId;
1295
    $parameters['from'] = isset($_GET['myspace']) ? Security::remove_XSS($_GET['myspace']) : null;
1296
1297
    $headerCounter = 0;
1298
    $headers = [];
1299
    // Tab of header texts.
1300
    $table->set_header($headerCounter++, get_lang('Code'), true);
1301
    $headers['official_code'] = get_lang('Code');
1302
    if ($sortByFirstName) {
1303
        $table->set_header($headerCounter++, get_lang('First name'), true);
1304
        $table->set_header($headerCounter++, get_lang('Last name'), true);
1305
        $headers['firstname'] = get_lang('First name');
1306
        $headers['lastname'] = get_lang('Last name');
1307
    } else {
1308
        $table->set_header($headerCounter++, get_lang('Last name'), true);
1309
        $table->set_header($headerCounter++, get_lang('First name'), true);
1310
        $headers['lastname'] = get_lang('Last name');
1311
        $headers['firstname'] = get_lang('First name');
1312
    }
1313
    $table->set_header($headerCounter++, get_lang('Login'), false);
1314
    $headers['login'] = get_lang('Login');
1315
1316
    $table->set_header(
1317
        $headerCounter++,
1318
        get_lang('Time').'&nbsp;'.
1319
        Display::getMdiIcon('clock-outline', 'ch-tool-icon', null, ICON_SIZE_TINY, get_lang('Time spent in the course')),
1320
        false
1321
    );
1322
    $headers['training_time'] = get_lang('Time');
1323
    $table->set_header(
1324
        $headerCounter++,
1325
        get_lang('Progress').'&nbsp;'.
1326
        Display::getMdiIcon(ObjectIcon::COURSE_PROGRESS, 'ch-tool-icon', null, ICON_SIZE_TINY, get_lang('Average progress in courses')),
1327
        false
1328
    );
1329
    $headers['course_progress'] = get_lang('Course progress');
1330
1331
    $table->set_header(
1332
        $headerCounter++,
1333
        get_lang('Exercise progress').'&nbsp;'.
1334
        Display::getMdiIcon(ObjectIcon::COURSE_PROGRESS, 'ch-tool-icon', null, ICON_SIZE_TINY, get_lang('Progress of exercises taken by the student')),
1335
        false
1336
    );
1337
    $headers['exercise_progress'] = get_lang('Exercise progress');
1338
    $table->set_header(
1339
        $headerCounter++,
1340
        get_lang('Exercise average').'&nbsp;'.
1341
        Display::getMdiIcon('format-annotation-plus', 'ch-tool-icon', null, ICON_SIZE_TINY, get_lang('Average of best grades of each exercise attempt')),
1342
        false
1343
    );
1344
    $headers['exercise_average'] = get_lang('Exercise average');
1345
    $table->set_header(
1346
        $headerCounter++,
1347
        get_lang('Score').'&nbsp;'.
1348
        Display::getMdiIcon('format-annotation-plus', 'ch-tool-icon', null, ICON_SIZE_TINY, get_lang('Average of tests in Learning Paths')),
1349
        false
1350
    );
1351
    $headers['score'] = get_lang('Score');
1352
    $table->set_header(
1353
        $headerCounter++,
1354
        $bestScoreLabel.'&nbsp;'.
1355
        Display::getMdiIcon('format-annotation-plus', 'ch-tool-icon', null, ICON_SIZE_TINY, get_lang('Average of tests in Learning Paths')),
1356
        false
1357
    );
1358
    $headers['score_best'] = $bestScoreLabel;
1359
1360
    $addExerciseOption = api_get_setting('exercise.add_exercise_best_attempt_in_report', true);
1361
    $exerciseResultHeaders = [];
1362
    if (!empty($addExerciseOption) && isset($addExerciseOption['courses']) && isset($addExerciseOption['courses'][$courseCode])) {
1363
        foreach ($addExerciseOption['courses'][$courseCode] as $exerciseId) {
1364
            $exercise = new Exercise();
1365
            $exercise->read($exerciseId);
1366
            if ($exercise->iId) {
1367
                $title = get_lang('Test').': '.$exercise->get_formated_title();
1368
                $table->set_header($headerCounter++, $title, false);
1369
                $exerciseResultHeaders[] = $title;
1370
                $headers['exercise_'.$exercise->iId] = $title;
1371
            }
1372
        }
1373
    }
1374
1375
    $table->set_header($headerCounter++, get_lang('Assignments'), false);
1376
    $headers['student_publication'] = get_lang('Assignments');
1377
    $table->set_header($headerCounter++, get_lang('Messages'), false);
1378
    $headers['messages'] = get_lang('Messages');
1379
    $table->set_header($headerCounter++, get_lang('Classes'));
1380
    $headers['classes'] = get_lang('Classes');
1381
1382
    if (empty($sessionId)) {
1383
        $table->set_header($headerCounter++, get_lang('Survey'), false);
1384
        $headers['survey'] = get_lang('Survey');
1385
    } else {
1386
        $table->set_header($headerCounter++, get_lang('Registered date'), false);
1387
        $headers['registered_at'] = get_lang('Registered date');
1388
    }
1389
    $table->set_header($headerCounter++, get_lang('First access to course'), false);
1390
    $headers['first_login'] = get_lang('First access to course');
1391
    $table->set_header($headerCounter++, get_lang('Latest access in course'), false);
1392
    $headers['latest_login'] = get_lang('Latest access in course');
1393
    $table->set_header($headerCounter++, get_lang("Last lp's finalization date"), false);
1394
    $headers['lp_finalization_date'] = get_lang("Last lp's finalization date");
1395
    $table->set_header($headerCounter++, get_lang('Last quiz finalization date'), false);
1396
    $headers['quiz_finalization_date'] = get_lang('Last quiz finalization date');
1397
1398
    $counter = $headerCounter;
1399
    if ('true' === api_get_setting('show_email_addresses')) {
1400
        $table->set_header($counter, get_lang('E-mail'), false);
1401
        $headers['email'] = get_lang('E-mail');
1402
        $counter++;
1403
    }
1404
    if (isset($_GET['additional_profile_field'])) {
1405
        foreach ($_GET['additional_profile_field'] as $fieldId) {
1406
            $table->set_header($counter, $extra_info[$fieldId]['display_text'], false);
1407
            $headers[$extra_info[$fieldId]['variable']] = $extra_info[$fieldId]['display_text'];
1408
            $counter++;
1409
            $parameters['additional_profile_field'] = $fieldId;
1410
        }
1411
    }
1412
    if (isset($defaultExtraFields)) {
1413
        if (!empty($defaultExtraInfo)) {
1414
            foreach ($defaultExtraInfo as $field) {
1415
                $table->set_header($counter, $field['display_text'], false);
1416
                $headers[$field['variable']] = $field['display_text'];
1417
                $counter++;
1418
            }
1419
        }
1420
    }
1421
    $table->set_header($counter, get_lang('Details'), false);
1422
    $headers['Details'] = get_lang('Details');
1423
1424
    if (!empty($fields)) {
1425
        foreach ($fields as $key => $value) {
1426
            $key = Security::remove_XSS($key);
1427
            $value = Security::remove_XSS($value);
1428
            $parameters[$key] = $value;
1429
        }
1430
    }
1431
    $parameters['cid'] = api_get_course_int_id();
1432
    $parameters['sid'] = $sessionId;
1433
    $table->set_additional_parameters($parameters);
1434
    // Display buttons to unhide hidden columns.
1435
    $html .= '<div id="unhideButtons" class="btn-toolbar">';
1436
    $index = 0;
1437
    $getLangDisplayColumn = get_lang('Show column');
1438
    foreach ($headers as $header) {
1439
        $html .= Display::toolbarButton(
1440
            $header,
1441
            '#',
1442
            'arrow-right',
1443
            'plain-outline',
1444
            [
1445
                'title' => htmlentities("$getLangDisplayColumn \"$header\"", ENT_QUOTES),
1446
                'style' => 'display: none;',
1447
                'onclick' => "foldup($index); return false;",
1448
            ]
1449
        );
1450
        $index++;
1451
    }
1452
    $html .= '</div>';
1453
1454
    $html .= '<div id="reporting_table">';
1455
    $html .= $table->return_table();
1456
    $html .= '</div>';
1457
} else {
1458
    if (empty($freeUsers)) {
1459
        $html .= Display::return_message(get_lang('No users in course'), 'warning', true);
1460
    }
1461
}
1462
1463
// Small top margin between charts (tpl) and info panel
1464
// Small top margin between charts (tpl) and info panel
1465
echo '<div id="course-log-main-panel" style="margin-top: 16px;">';
1466
echo Display::panel($html, $panelTitle);
1467
echo '</div>';
1468
1469
1470
$freeAnchor = 'free-users';
1471
$toggleUrl  = api_get_self().'?'.api_get_cidreq().'&show_non_registered='.($showNonRegistered ? 0 : 1).'#'.$freeAnchor;
1472
$toggleLbl  = $showNonRegistered
1473
    ? get_lang('Hide free users (not enrolled)')
1474
    : get_lang('Show free users (not enrolled)');
1475
1476
echo '<div id="'.$freeAnchor.'" class="mb-2" style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">'
1477
    .'<a class="btn btn--info" href="'.$toggleUrl.'">'.Security::remove_XSS($toggleLbl).'</a>'
1478
    .'</div>';
1479
1480
$groupTable = new HTML_Table(['class' => 'table table-hover table-striped table-bordered data_table']);
1481
$column = 0;
1482
$groupTable->setHeaderContents(0, $column++, get_lang('Name'));
1483
$groupTable->setHeaderContents(0, $column++, get_lang('Time'));
1484
$groupTable->setHeaderContents(0, $column++, get_lang('Average time in the course'));
1485
$groupTable->setHeaderContents(0, $column++, get_lang('Course progress'));
1486
$groupTable->setHeaderContents(0, $column++, get_lang('Exercise average'));
1487
1488
$exerciseList = [];
1489
$session = api_get_session_entity($sessionId);
1490
$qb = Container::getQuizRepository()->findAllByCourse($course, $session, null, 2, false);
1491
/** @var CQuiz[] $exercises */
1492
$exercises = $qb->getQuery()->getResult();
1493
1494
$percentVal = static function ($v): float {
1495
    if ($v === '' || $v === null) {
1496
        return 0.0;
1497
    }
1498
    if (is_numeric($v)) {
1499
        return (float) $v;
1500
    }
1501
1502
    return (float) str_replace('%', '', trim((string) $v));
1503
};
1504
1505
if (!empty($groupList)) {
1506
    $row = 1;
1507
    $totalSeconds = 0;
1508
    $totalUsers   = 0;
1509
    $sumProgress  = 0.0;
1510
    $sumBest      = 0.0;
1511
1512
    foreach ($groupList as $groupInfo) {
1513
        $col = 0;
1514
        $groupTable->setCellContents($row, $col++, $groupInfo['title']);
1515
1516
        $timeStr = $avgTimeStr = $progStr = $bestStr = '';
1517
1518
        $usersInGroup = GroupManager::getStudents($groupInfo['iid']);
1519
        if (!empty($usersInGroup)) {
1520
            $uids = array_column($usersInGroup, 'user_id');
1521
            $count = count($uids);
1522
1523
            $secs = Tracking::get_time_spent_on_the_course($uids, $courseId, $sessionId);
1524
            if ($secs) {
1525
                $timeStr    = api_time_to_hms($secs);
1526
                $avgTimeStr = api_time_to_hms($secs / $count);
1527
            }
1528
1529
            $groupProgressNum = $percentVal(
1530
                Tracking::get_avg_student_progress($uids, $course, [], $session)
1531
            );
1532
            $progStr = round($groupProgressNum, 2).' %';
1533
1534
            $groupBestNum = 0.0;
1535
            if (!empty($exercises) && $count) {
1536
                $bestSum = 0.0;
1537
                foreach ($exercises as $exerciseData) {
1538
                    foreach ($uids as $u) {
1539
                        $results = Event::get_best_exercise_results_by_user(
1540
                            $exerciseData->getIid(),
1541
                            $courseId,
1542
                            0, // No-session in this query.
1543
                            $u
1544
                        );
1545
                        $best = 0.0;
1546
                        if (!empty($results)) {
1547
                            foreach ($results as $r) {
1548
                                if (!empty($r['max_score'])) {
1549
                                    $sc = $r['score'] / $r['max_score'];
1550
                                    if ($sc > $best) {
1551
                                        $best = $sc;
1552
                                    }
1553
                                }
1554
                            }
1555
                        }
1556
                        $bestSum += $best;
1557
                    }
1558
                }
1559
                $groupBestNum = round(($bestSum / max(count($exercises), 1)) * 100 / $count, 2);
1560
            }
1561
            $bestStr = $groupBestNum ? ($groupBestNum.' %') : '';
1562
1563
            $totalSeconds += $secs;
1564
            $totalUsers   += $count;
1565
            $sumProgress  += $groupProgressNum * $count;
1566
            $sumBest      += $groupBestNum     * $count;
1567
        }
1568
1569
        $groupTable->setCellContents($row, $col++, $timeStr);
1570
        $groupTable->setCellContents($row, $col++, $avgTimeStr);
1571
        $groupTable->setCellContents($row, $col++, $progStr);
1572
        $groupTable->setCellContents($row, $col++, $bestStr);
1573
        $row++;
1574
    }
1575
1576
    if ($showNonRegistered && !empty($freeUsers)) {
1577
        foreach ($freeUsers as $fu) {
1578
            $col = 0;
1579
            $uid  = (int) ($fu['id'] ?? 0);
1580
            $name = Security::remove_XSS(trim(($fu['firstname'] ?? '').' '.($fu['lastname'] ?? ''))).' (free)';
1581
1582
            $secs = Tracking::get_time_spent_on_the_course([$uid], $courseId, $sessionId);
1583
            $timeStr    = $secs ? api_time_to_hms($secs) : '';
1584
            $avgTimeStr = $timeStr;
1585
1586
            $lpNum = $percentVal(Tracking::get_avg_student_progress($uid, $course, [], $session));
1587
            $progStr = $lpNum !== null ? round($lpNum, 2).' %' : '';
1588
1589
            $bestNum = 0.0;
1590
            if (!empty($exercises)) {
1591
                $sumBestFree = 0.0;
1592
                $countE = 0;
1593
                foreach ($exercises as $exerciseData) {
1594
                    $results = Event::get_best_exercise_results_by_user(
1595
                        $exerciseData->getIid(),
1596
                        $courseId,
1597
                        $sessionId,
1598
                        $uid
1599
                    );
1600
                    $best = 0.0;
1601
                    if (!empty($results)) {
1602
                        foreach ($results as $r) {
1603
                            if (!empty($r['max_score'])) {
1604
                                $sc = $r['score'] / $r['max_score'];
1605
                                if ($sc > $best) {
1606
                                    $best = $sc;
1607
                                }
1608
                            }
1609
                        }
1610
                    }
1611
                    $sumBestFree += $best;
1612
                    $countE++;
1613
                }
1614
                if ($countE > 0) {
1615
                    $bestNum = round(($sumBestFree / $countE) * 100, 2);
1616
                }
1617
            }
1618
            $bestStr = $bestNum ? ($bestNum.' %') : '';
1619
1620
            $totalSeconds += $secs;
1621
            $totalUsers   += 1;
1622
            $sumProgress  += $lpNum;
1623
            $sumBest      += $bestNum;
1624
1625
            $groupTable->setCellContents($row, $col++, $name);
1626
            $groupTable->setCellContents($row, $col++, $timeStr);
1627
            $groupTable->setCellContents($row, $col++, $avgTimeStr);
1628
            $groupTable->setCellContents($row, $col++, $progStr);
1629
            $groupTable->setCellContents($row, $col++, $bestStr);
1630
            $row++;
1631
        }
1632
    }
1633
1634
    $avgSecondsAll   = $totalUsers ? ($totalSeconds / $totalUsers) : 0;
1635
    $totalAvgTimeStr = api_time_to_hms($avgSecondsAll);
1636
    $totalProgStr    = $totalUsers ? (round($sumProgress / $totalUsers, 2).' %') : '';
1637
    $totalBestStr    = $totalUsers ? (round($sumBest     / $totalUsers, 2).' %') : '';
1638
1639
    $col = 0;
1640
    $groupTable->setCellContents($row, $col++, get_lang('Total'));
1641
    $groupTable->setCellContents($row, $col++, api_time_to_hms($totalSeconds));
1642
    $groupTable->setCellContents($row, $col++, $totalAvgTimeStr);
1643
    $groupTable->setCellContents($row, $col++, $totalProgStr);
1644
    $groupTable->setCellContents($row, $col++, $totalBestStr);
1645
1646
} else {
1647
    $studentIdList = Session::read('user_id_list');
1648
    $studentIdList = !empty($studentIdList) ? $studentIdList : array_column($studentList, 'user_id');
1649
1650
    if ($showNonRegistered && !empty($freeUsers)) {
1651
        foreach ($freeUsers as $fu) {
1652
            $studentIdList[] = (int) ($fu['id'] ?? 0);
1653
        }
1654
    }
1655
1656
    $nbAll = count($studentIdList);
1657
    $totalSeconds   = Tracking::get_time_spent_on_the_course($studentIdList, $courseId, $sessionId);
1658
    $avgSecondsAll  = $nbAll ? $totalSeconds / $nbAll : 0;
1659
1660
    $sumProgress = 0.0;
1661
    foreach ($studentIdList as $uid) {
1662
        $sumProgress += $percentVal(Tracking::get_avg_student_progress($uid, $course, [], $session));
1663
    }
1664
    $avgProgress = $nbAll ? round($sumProgress / $nbAll, 2) : 0.0;
1665
1666
    $bestSum = 0.0;
1667
    if (!empty($exercises)) {
1668
        foreach ($studentIdList as $uid) {
1669
            $sumE = 0.0;
1670
            $countE = 0;
1671
            foreach ($exercises as $exerciseData) {
1672
                $results = Event::get_best_exercise_results_by_user(
1673
                    $exerciseData->getIid(),
1674
                    $courseId,
1675
                    $sessionId,
1676
                    $uid
1677
                );
1678
                $best = 0.0;
1679
                if (!empty($results)) {
1680
                    foreach ($results as $r) {
1681
                        if (!empty($r['max_score'])) {
1682
                            $sc = $r['score'] / $r['max_score'];
1683
                            if ($sc > $best) {
1684
                                $best = $sc;
1685
                            }
1686
                        }
1687
                    }
1688
                }
1689
                $sumE += $best;
1690
                $countE++;
1691
            }
1692
            if ($countE > 0) {
1693
                $bestSum += ($sumE / $countE) * 100;
1694
            }
1695
        }
1696
    }
1697
    $avgBest = $nbAll ? round($bestSum / $nbAll, 2) : 0.0;
1698
1699
    $row = 1;
1700
    if ($showNonRegistered && !empty($freeUsers)) {
1701
        foreach ($freeUsers as $fu) {
1702
            $col = 0;
1703
            $uid  = (int) ($fu['id'] ?? 0);
1704
            $name = Security::remove_XSS(trim(($fu['firstname'] ?? '').' '.($fu['lastname'] ?? ''))).' (free)';
1705
1706
            $secs = Tracking::get_time_spent_on_the_course([$uid], $courseId, $sessionId);
1707
            $timeU    = $secs ? api_time_to_hms($secs) : '';
1708
            $avgTimeU = $timeU;
1709
1710
            $lpU = $percentVal(Tracking::get_avg_student_progress($uid, $course, [], $session));
1711
            $lpU = round($lpU, 2).' %';
1712
1713
            $bestAvgU = '';
1714
            if (!empty($exercises)) {
1715
                $sumBestU = 0.0;
1716
                $countE = 0;
1717
                foreach ($exercises as $exerciseData) {
1718
                    $results = Event::get_best_exercise_results_by_user(
1719
                        $exerciseData->getIid(),
1720
                        $courseId,
1721
                        $sessionId,
1722
                        $uid
1723
                    );
1724
                    $best = 0.0;
1725
                    if (!empty($results)) {
1726
                        foreach ($results as $r) {
1727
                            if (!empty($r['max_score'])) {
1728
                                $sc = $r['score'] / $r['max_score'];
1729
                                if ($sc > $best) {
1730
                                    $best = $sc;
1731
                                }
1732
                            }
1733
                        }
1734
                    }
1735
                    $sumBestU += $best;
1736
                    $countE++;
1737
                }
1738
                if ($countE > 0) {
1739
                    $bestAvgU = round(($sumBestU / $countE) * 100, 2).' %';
1740
                }
1741
            }
1742
1743
            $groupTable->setCellContents($row, $col++, $name);
1744
            $groupTable->setCellContents($row, $col++, $timeU);
1745
            $groupTable->setCellContents($row, $col++, $avgTimeU);
1746
            $groupTable->setCellContents($row, $col++, $lpU);
1747
            $groupTable->setCellContents($row, $col++, $bestAvgU);
1748
            $row++;
1749
        }
1750
    }
1751
1752
    $col = 0;
1753
    $groupTable->setCellContents($row, $col++, get_lang('Total'));
1754
    $groupTable->setCellContents($row, $col++, api_time_to_hms($totalSeconds));
1755
    $groupTable->setCellContents($row, $col++, api_time_to_hms($avgSecondsAll));
1756
    $groupTable->setCellContents($row, $col++, $avgProgress.' %');
1757
    $groupTable->setCellContents($row, $col++, $avgBest.' %');
1758
}
1759
1760
$groupPanelTitle = Display::getMdiIcon(
1761
        'chart-bar',
1762
        'ch-tool-icon',
1763
        null,
1764
        ICON_SIZE_TINY,
1765
        get_lang('Group statistics')
1766
    ).' '.get_lang('Group statistics');
1767
1768
echo Display::panel($groupTable->toHtml(), $groupPanelTitle);
1769
1770
// Send the csv file if asked.
1771
if ($export_csv) {
1772
    $csv_headers = [];
1773
    $csv_headers[] = get_lang('Code');
1774
    if ($sortByFirstName) {
1775
        $csv_headers[] = get_lang('First name');
1776
        $csv_headers[] = get_lang('Last name');
1777
    } else {
1778
        $csv_headers[] = get_lang('Last name');
1779
        $csv_headers[] = get_lang('First name');
1780
    }
1781
    $csv_headers[] = get_lang('Login');
1782
    $csv_headers[] = get_lang('Time');
1783
    $csv_headers[] = get_lang('Progress');
1784
    $csv_headers[] = get_lang('Exercise progress');
1785
    $csv_headers[] = get_lang('Exercise average');
1786
    $csv_headers[] = get_lang('Score');
1787
    $csv_headers[] = $bestScoreLabel;
1788
    if (!empty($exerciseResultHeaders)) {
1789
        foreach ($exerciseResultHeaders as $exerciseLabel) {
1790
            $csv_headers[] = $exerciseLabel;
1791
        }
1792
    }
1793
    $csv_headers[] = get_lang('Assignments');
1794
    $csv_headers[] = get_lang('Messages');
1795
1796
    if (empty($sessionId)) {
1797
        $csv_headers[] = get_lang('Survey');
1798
    } else {
1799
        $csv_headers[] = get_lang('Registration date');
1800
    }
1801
1802
    $csv_headers[] = get_lang('First access to course');
1803
    $csv_headers[] = get_lang('Latest access in course');
1804
    $csv_headers[] = get_lang("Last lp's finalization date");
1805
    $csv_headers[] = get_lang('Last quiz finalization date');
1806
1807
    if (isset($_GET['additional_profile_field'])) {
1808
        foreach ($_GET['additional_profile_field'] as $fieldId) {
1809
            $csv_headers[] = $extra_info[$fieldId]['display_text'];
1810
        }
1811
    }
1812
    ob_end_clean();
1813
1814
    $csvContentInSession = Session::read('csv_content', []);
1815
    array_unshift($csvContentInSession, $csv_headers);
1816
1817
    if ($sessionId) {
1818
        $session = api_get_session_entity($sessionId);
1819
        $sessionDates = SessionManager::parseSessionDates($session);
1820
1821
        array_unshift($csvContentInSession, [get_lang('Date'), $sessionDates['access']]);
1822
        array_unshift($csvContentInSession, [get_lang('Session name'), $session->getTitle()]);
1823
    }
1824
1825
    Export::arrayToCsv($csvContentInSession, 'reporting_student_list');
1826
    exit;
1827
}
1828
1829
Display::display_footer();
1830
1831
function sort_by_order($a, $b)
1832
{
1833
    return $a['score'] <= $b['score'];
1834
}
1835