Upgrade 1-11.38

This commit is contained in:
xesmyd
2026-03-30 14:10:30 +02:00
parent f2a7e6d1fc
commit ac648ef29d
24665 changed files with 69682 additions and 2205004 deletions
+79 -20
View File
@@ -162,26 +162,48 @@ function get_count_courses()
$drhLoaded = false;
if (api_is_drh()) {
if (api_drh_can_access_all_session_content()) {
if (empty($sessionId)) {
$count = SessionManager::getAllCoursesFollowedByUser(
$userId,
null,
null,
null,
null,
null,
true,
$keyword
);
} else {
$count = SessionManager::getCourseCountBySessionId(
$sessionId,
$keyword
);
if (empty($sessionId)) {
// Fetch full rows from both sources so we can deduplicate before
// counting, ensuring the count matches exactly what get_courses()
// will display (a simple addition of two COUNT()s would double-count
// courses assigned to the DRH both directly and through a session).
$coursesFromSessions = SessionManager::getAllCoursesFollowedByUser(
$userId,
null,
null,
null,
null,
null,
false,
$keyword
);
$directCourses = CourseManager::getCoursesFollowedByUser(
$userId,
DRH,
null,
null,
null,
null,
false,
$keyword,
0
);
$merged = $coursesFromSessions;
foreach ($directCourses as $course) {
$courseId = isset($course['real_id']) ? $course['real_id'] : $course['id'];
if (!isset($merged[$courseId])) {
$merged[$courseId] = $course;
}
}
$drhLoaded = true;
$count = count($merged);
} else {
$count = SessionManager::getCourseCountBySessionId(
$sessionId,
$keyword
);
}
$drhLoaded = true;
}
if ($drhLoaded == false) {
@@ -227,7 +249,44 @@ function get_courses($from, $limit, $column, $direction)
$follow = isset($_GET['follow']) ? true : false;
$drhLoaded = false;
if (api_is_drh()) {
if (api_drh_can_access_all_session_content()) {
if (empty($sessionId)) {
$coursesFromSessions = SessionManager::getAllCoursesFollowedByUser(
$userId,
null,
null,
null,
$column,
$direction,
false,
$keyword
);
$directCourses = CourseManager::getCoursesFollowedByUser(
$userId,
DRH,
null,
null,
$column,
$direction,
false,
$keyword,
0
);
$courses = $coursesFromSessions;
if (!empty($directCourses)) {
foreach ($directCourses as $course) {
$courseId = isset($course['real_id']) ? $course['real_id'] : $course['id'];
if (!isset($courses[$courseId])) {
$courses[$courseId] = $course;
}
}
}
if (!empty($courses)) {
$courses = array_slice($courses, (int) $from, (int) $limit, true);
}
} else {
$courses = SessionManager::getAllCoursesFollowedByUser(
$userId,
$sessionId,
@@ -238,8 +297,8 @@ function get_courses($from, $limit, $column, $direction)
false,
$keyword
);
$drhLoaded = true;
}
$drhLoaded = true;
}
if ($drhLoaded == false) {
+5 -5
View File
@@ -211,12 +211,12 @@ $headers = [
if (isset($_GET['export'])) {
global $charset;
$spreadsheet = new PHPExcel();
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$spreadsheet->setActiveSheetIndex(0);
$worksheet = $spreadsheet->getActiveSheet();
$line = 0;
$column = 0; //skip the first column (row titles)
$line = 1;
$column = 1;
foreach ($headers as $header) {
$worksheet->setCellValueByColumnAndRow($column, $line, $header);
@@ -224,7 +224,7 @@ if (isset($_GET['export'])) {
}
$line++;
foreach ($array as $row) {
$column = 0;
$column = 1;
foreach ($row as $item) {
$worksheet->setCellValueByColumnAndRow(
$column,
@@ -238,7 +238,7 @@ if (isset($_GET['export'])) {
$line++;
$file = api_get_path(SYS_ARCHIVE_PATH).api_replace_dangerous_char($filename);
$writer = new PHPExcel_Writer_Excel2007($spreadsheet);
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$writer->save($file);
DocumentManager::file_send_for_download($file, true, $filename);
exit;
+311
View File
@@ -0,0 +1,311 @@
<?php
/* For licensing terms, see /license.txt */
$cidReset = true;
require_once __DIR__.'/../inc/global.inc.php';
api_block_anonymous_users();
if (!api_is_platform_admin()) {
api_not_allowed(true);
}
$extraFields = MySpace::duGetUserExtraFields();
$defaultVar = array_key_exists('dni', $extraFields) ? 'dni'
: (array_key_exists('document', $extraFields) ? 'document' : (array_key_first($extraFields) ?: ''));
$selectedVar = isset($_REQUEST['field_var']) ? Security::remove_XSS($_REQUEST['field_var']) : $defaultVar;
$actionMode = (isset($_REQUEST['unify_mode']) && $_REQUEST['unify_mode'] === 'delete') ? 'delete' : 'deactivate';
$doSearch = isset($_REQUEST['do_search']) ? 1 : 0;
$doUnify = isset($_POST['do_unify']) ? 1 : 0;
$self = api_get_self();
if ($doUnify) {
// CSRF
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !Security::check_token()) {
$q = http_build_query([
'field_var' => $_POST['field_var'] ?? $selectedVar,
'unify_mode' => $_POST['unify_mode'] ?? $actionMode,
'do_search' => 1,
'flash' => 'csrf',
]);
header("Location: {$self}?{$q}");
exit;
}
$fieldVar = Security::remove_XSS($_POST['field_var'] ?? '');
$fieldInfo = MySpace::duGetUserExtraFieldByVariable($fieldVar);
$fieldId = (int) ($fieldInfo['id'] ?? 0);
$fieldValue = Security::remove_XSS($_POST['field_value'] ?? '');
$finalUserId = (int) ($_POST['final_user_id'] ?? 0);
$unifyMode = ($_POST['unify_mode'] ?? 'deactivate') === 'delete' ? 'delete' : 'deactivate';
$urlId = (int) api_get_current_access_url_id();
if ($fieldId && $finalUserId && $fieldValue !== '') {
$finalUserIsInGroup = false;
$usersInGroup = MySpace::duGetUsersByFieldValue($fieldId, $urlId, $fieldValue);
foreach ($usersInGroup as $uu) {
if ((int) $uu['user_id'] === $finalUserId) {
$finalUserIsInGroup = true;
break;
}
}
if (!$finalUserIsInGroup) {
$q = http_build_query([
'field_var' => $fieldVar,
'unify_mode' => $unifyMode,
'do_search' => 1,
'flash' => 'na',
]);
header("Location: {$self}?{$q}");
exit;
}
Database::query('START TRANSACTION');
$ok = true;
foreach ($usersInGroup as $u) {
$uid = (int) $u['user_id'];
if ($uid === $finalUserId) {
continue;
}
MySpace::duUpdateAllUserRefsList($uid, $finalUserId);
$ok = $ok && MySpace::duDisableOrDeleteUser($uid, $unifyMode);
}
if ($ok) {
Database::query('COMMIT');
Security::clear_token();
$q = http_build_query([
'field_var' => $fieldVar,
'unify_mode' => $unifyMode,
'do_search' => 1,
'flash' => 'ok',
'fv' => $fieldVar.'='.$fieldValue,
]);
header("Location: {$self}?{$q}");
exit;
} else {
Database::query('ROLLBACK');
$q = http_build_query([
'field_var' => $fieldVar,
'unify_mode' => $unifyMode,
'do_search' => 1,
'flash' => 'err',
'em' => get_lang('OperationFailedRollback'),
]);
header("Location: {$self}?{$q}");
exit;
}
} else {
$q = http_build_query([
'field_var' => $selectedVar,
'unify_mode' => $actionMode,
'do_search' => 1,
'flash' => 'na',
]);
header("Location: {$self}?{$q}");
exit;
}
}
$nameTools = get_lang('DuplicatedUsers');
Display::display_header($nameTools);
echo '<div class="actions">'.MySpace::getTopMenu().'</div>';
echo MySpace::getAdminActions();
if (isset($_GET['flash'])) {
$flash = $_GET['flash'];
if ($flash === 'ok') {
$msg = isset($_GET['fv']) ? get_lang('OperationCompleted').' ('.htmlspecialchars($_GET['fv']).')' : get_lang('OperationCompleted');
echo Display::return_message($msg, 'confirm');
} elseif ($flash === 'err') {
$msg = isset($_GET['em']) ? htmlspecialchars($_GET['em']) : get_lang('OperationFailedRollback');
echo Display::return_message($msg, 'error');
} elseif ($flash === 'na') {
echo Display::return_message(get_lang('NotAllowed'), 'error');
} elseif ($flash === 'csrf') {
echo Display::return_message(get_lang('NotAllowed').' (CSRF)', 'error');
}
}
echo '<div class="panel panel-default">';
echo ' <div class="panel-heading"><strong>'.get_lang('DuplicatedUsers').'</strong></div>';
echo ' <div class="panel-body">';
echo ' <form method="get" class="form-horizontal" action="">';
echo ' <div class="form-group">';
echo ' <label for="field_var" class="col-sm-3 control-label">'.get_lang('SelectExtraField').'</label>';
echo ' <div class="col-sm-6">';
echo ' <select name="field_var" id="field_var" class="form-control">';
if (!empty($extraFields)) {
foreach ($extraFields as $var => $label) {
$sel = $var === $selectedVar ? 'selected' : '';
echo ' <option value="'.htmlspecialchars($var).'" '.$sel.'>'.htmlspecialchars($label).'</option>';
}
}
echo ' </select>';
echo ' </div>';
echo ' </div>';
echo ' <div class="form-group">';
echo ' <label class="col-sm-3 control-label">'.get_lang('WhatToDoWithUnifiedUsers').'</label>';
echo ' <div class="col-sm-6">';
echo ' <div class="radio"><label>';
echo ' <input type="radio" name="unify_mode" value="deactivate" '.($actionMode === 'deactivate' ? 'checked' : '').'> '.get_lang('Deactivate');
echo ' </label></div>';
echo ' <div class="radio"><label>';
echo ' <input type="radio" name="unify_mode" value="delete" '.($actionMode === 'delete' ? 'checked' : '').'> '.get_lang('Delete');
echo ' </label></div>';
echo ' </div>';
echo ' </div>';
echo ' <div class="form-group">';
echo ' <div class="col-sm-offset-3 col-sm-6">';
echo ' <button type="submit" name="do_search" value="1" class="btn btn-primary">';
echo Display::return_icon('search.gif', get_lang('Search'), '').' '.get_lang('Search');
echo ' </button>';
echo ' </div>';
echo ' </div>';
echo ' </form>';
echo ' </div>';
echo '</div>';
if ($doSearch) {
$fieldInfo = MySpace::duGetUserExtraFieldByVariable($selectedVar);
if (empty($fieldInfo)) {
echo Display::return_message(get_lang('ExtraFieldNotFound').': '.htmlspecialchars($selectedVar), 'error');
Display::display_footer();
exit;
}
$fieldId = (int) $fieldInfo['id'];
$urlId = (int) api_get_current_access_url_id();
$dups = MySpace::duGetDuplicateValues($fieldId, $urlId);
echo "<div class='panel panel-default'>";
echo " <div class='panel-heading'><strong>".get_lang('SearchResultsFor').": </strong><code>".htmlspecialchars($selectedVar)."</code></div>";
echo " <div class='panel-body'>";
if (empty($dups)) {
echo Display::return_message(get_lang('NoDuplicatesFound'));
} else {
foreach ($dups as $g) {
$value = $g['the_value'];
$users = MySpace::duGetUsersByFieldValue($fieldId, $urlId, $value);
echo "<div class='panel panel-info mb-3'>";
echo " <div class='panel-heading'>";
echo " <strong>".htmlspecialchars($selectedVar)."</strong>: <code>".htmlspecialchars($value)."</code>";
echo " <span class='badge' style='margin-left:8px'>".count($users).' '.get_lang('Users')."</span>";
echo " </div>";
echo " <div class='panel-body'>";
echo "<div class='table-responsive'>";
echo "<table class='table table-striped table-hover table-condensed'>";
echo "<thead><tr>";
echo " <th>".htmlspecialchars($selectedVar)."</th>";
echo " <th>".get_lang('Username')."</th>";
echo " <th>".get_lang('FirstName')."</th>";
echo " <th>".get_lang('LastName')."</th>";
echo " <th>".get_lang('Email')."</th>";
echo " <th>".get_lang('UserId')."</th>";
echo " <th>".get_lang('RegistrationDate')."</th>";
echo " <th class='text-center'>".get_lang('UnifyToThisUser')."</th>";
echo "</tr></thead><tbody>";
foreach ($users as $u) {
$uid = (int) $u['user_id'];
echo "<tr>";
echo " <td>".htmlspecialchars($value)."</td>";
echo " <td>".htmlspecialchars($u['username'])."</td>";
echo " <td>".htmlspecialchars($u['firstname'])."</td>";
echo " <td>".htmlspecialchars($u['lastname'])."</td>";
echo " <td>".htmlspecialchars($u['email'])."</td>";
echo " <td>".$uid."</td>";
echo " <td>".htmlspecialchars($u['registration_date'])."</td>";
echo " <td class='text-center'>";
echo " <button type='button' class='btn btn-xs btn-danger'".
" data-toggle='modal' data-target='#confirmUnify'".
" data-finalid='".$uid."'".
" data-fieldvar='".htmlspecialchars($selectedVar, ENT_QUOTES)."'".
" data-fieldvalue='".htmlspecialchars($value, ENT_QUOTES)."'".
" data-count='".(count($users) - 1)."'".
" data-actionmode='".htmlspecialchars($actionMode, ENT_QUOTES)."'".
">".
Display::return_icon('save.png', get_lang('Unify'), '').' '.get_lang('Unify').
"</button>";
echo " </td>";
echo "</tr>";
}
echo "</tbody></table>";
echo "</div>";
echo " </div>";
echo "</div>";
}
}
echo " </div>";
echo "</div>";
}
?>
<div id="confirmUnify" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="confirmUnifyLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<form method="post" action="">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="<?php echo get_lang('Close'); ?>"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title" id="confirmUnifyLabel"><?php echo get_lang('Unify'); ?></h4>
</div>
<div class="modal-body">
<p><?php echo get_lang('AreYouSureToUnify'); ?></p>
<p><strong><?php echo get_lang('FinalUser'); ?>:</strong> <span id="mFinalUser">#</span></p>
<p id="mCount"></p>
<input type="hidden" name="field_var" id="mFieldVar">
<input type="hidden" name="field_value" id="mFieldValue">
<input type="hidden" name="final_user_id" id="mFinalId">
<input type="hidden" name="unify_mode" id="mUnifyMode" value="deactivate">
<input type="hidden" name="do_unify" value="1">
<?php echo Security::get_HTML_token(); ?>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"><?php echo get_lang('Cancel'); ?></button>
<button type="submit" class="btn btn-danger"><?php echo get_lang('Unify'); ?></button>
</div>
</form>
</div>
</div>
</div>
<script>
(function() {
var $modal = $('#confirmUnify');
$modal.on('show.bs.modal', function (e) {
var btn = $(e.relatedTarget);
var finalId = btn.data('finalid');
var fieldVar = btn.data('fieldvar');
var fieldValue = btn.data('fieldvalue');
var count = btn.data('count');
var mode = btn.data('actionmode');
$('#mFinalId').val(finalId);
$('#mFieldVar').val(fieldVar);
$('#mFieldValue').val(fieldValue);
$('#mUnifyMode').val(mode);
$('#mFinalUser').text('#' + finalId);
var txt = "<?php echo get_lang('WillMergeNUsers'); ?>";
$('#mCount').text(txt.replace('{n}', count));
});
})();
</script>
<?php
Display::display_footer();
+655 -114
View File
@@ -16,10 +16,15 @@ require_once '../work/work.lib.php';
api_block_anonymous_users();
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_PUBLIC_PATH)
.'assets/jquery.easy-pie-chart/dist/jquery.easypiechart.js"></script>';
$htmlHeadXtra[] = '<style>
.course-unsubscribed{background-color:#f5f5f5 !important;}
.course-unsubscribed td,.course-unsubscribed td a{color:#888 !important;}
.course-unsubscribed .details-icon{filter:grayscale(100%);opacity:0.5;}
</style>';
$export = isset($_GET['export']) ? $_GET['export'] : false;
$sessionId = isset($_GET['id_session']) ? (int) $_GET['id_session'] : 0;
$action = isset($_GET['action']) ? $_GET['action'] : '';
$sessionId = isset($_GET['id_session']) ? (int) $_GET['id_session'] : (isset($_POST['id_session']) ? (int) $_POST['id_session'] : 0);
$action = isset($_GET['action']) ? $_GET['action'] : (isset($_POST['action']) ? $_POST['action'] : '');
$origin = api_get_origin();
$course_code = isset($_GET['course']) ? Security::remove_XSS($_GET['course']) : '';
$courseInfo = api_get_course_info($course_code);
@@ -27,7 +32,7 @@ $courseCode = '';
if ($courseInfo) {
$courseCode = $courseInfo['code'];
}
$student_id = isset($_GET['student']) ? (int) $_GET['student'] : 0;
$student_id = isset($_GET['student']) ? (int) $_GET['student'] : (isset($_POST['student']) ? (int) $_POST['student'] : 0);
$coachId = isset($_GET['id_coach']) ? (int) $_GET['id_coach'] : 0;
$details = isset($_GET['details']) ? Security::remove_XSS($_GET['details']) : '';
$currentUrl = api_get_self().'?student='.$student_id.'&course='.$courseCode.'&id_session='.$sessionId
@@ -35,6 +40,7 @@ $currentUrl = api_get_self().'?student='.$student_id.'&course='.$courseCode.'&id
$allowMessages = api_get_configuration_value('private_messages_about_user');
$workingTime = api_get_configuration_value('considered_working_time');
$workingTimeEdit = api_get_configuration_value('allow_working_time_edition');
$subscriptionColumnEnabled = api_get_configuration_value('display_session_subscription_column');
$allowToQualify = api_is_allowed_to_edit(null, true) ||
api_is_course_tutor() ||
@@ -142,6 +148,119 @@ switch ($action) {
header('Location: '.$currentUrl);
exit;
break;
case 'subscribe_course':
if (false === $subscriptionColumnEnabled) {
break;
}
$canManageSubscriptions = api_is_platform_admin(true, true)
|| api_is_session_admin()
|| api_is_allowed_to_edit(null, true)
|| api_is_course_admin()
|| api_is_teacher()
|| api_is_coach();
if (!$canManageSubscriptions) {
api_not_allowed(true);
}
$courseCodeParam = isset($_GET['course_code']) ? Security::remove_XSS($_GET['course_code']) : '';
$sessionParam = isset($_GET['id_session']) ? (int) $_GET['id_session'] : 0;
if ('' !== $courseCodeParam && $sessionParam > 0) {
SessionManager::subscribe_users_to_session_course(
[$student_id],
$sessionParam,
$courseCodeParam
);
Display::addFlash(Display::return_message(get_lang('Updated')));
}
$redirectUrl = api_get_self().'?'.http_build_query([
'student' => $student_id,
'origin' => $origin,
'details' => $details,
'id_session' => $sessionParam,
]);
header('Location: '.$redirectUrl);
exit;
case 'unsubscribe_course':
if (false === $subscriptionColumnEnabled) {
break;
}
$canManageSubscriptions = api_is_platform_admin(true, true)
|| api_is_session_admin()
|| api_is_allowed_to_edit(null, true)
|| api_is_course_admin()
|| api_is_teacher()
|| api_is_coach();
if (!$canManageSubscriptions) {
api_not_allowed(true);
}
$courseCodeParam = isset($_GET['course_code']) ? Security::remove_XSS($_GET['course_code']) : '';
$sessionParam = isset($_GET['id_session']) ? (int) $_GET['id_session'] : 0;
if ('' !== $courseCodeParam && $sessionParam > 0) {
$courseInfoParam = api_get_course_info($courseCodeParam);
SessionManager::removeUsersFromCourseSession(
[$student_id],
$sessionParam,
$courseInfoParam
);
Display::addFlash(Display::return_message(get_lang('Updated')));
}
$redirectUrl = api_get_self().'?'.http_build_query([
'student' => $student_id,
'origin' => $origin,
'details' => $details,
'id_session' => $sessionParam,
]);
header('Location: '.$redirectUrl);
exit;
case 'bulk_session_course_subscription':
// Bulk subscribe/unsubscribe selected courses within a session
if (false === $subscriptionColumnEnabled) {
break;
}
if (!Security::check_token('post')) {
api_not_allowed(true);
}
$canManageSubscriptions = api_is_platform_admin(true, true)
|| api_is_session_admin()
|| api_is_allowed_to_edit(null, true)
|| api_is_course_admin()
|| api_is_teacher()
|| api_is_coach();
if (!$canManageSubscriptions) {
api_not_allowed(true);
}
$sessionParam = isset($_POST['id_session']) ? (int) $_POST['id_session'] : 0;
$courseCodes = isset($_POST['course_codes']) && is_array($_POST['course_codes']) ? $_POST['course_codes'] : [];
$bulkAction = isset($_POST['bulk_action']) ? Security::remove_XSS($_POST['bulk_action']) : '';
if ($sessionParam > 0 && !empty($courseCodes) && in_array($bulkAction, ['subscribe', 'unsubscribe'], true)) {
if ($bulkAction === 'subscribe') {
foreach ($courseCodes as $cc) {
$cc = Security::remove_XSS($cc);
if (!empty($cc)) {
SessionManager::subscribe_users_to_session_course([$student_id], $sessionParam, $cc);
}
}
} else { // unsubscribe
foreach ($courseCodes as $cc) {
$cc = Security::remove_XSS($cc);
if (!empty($cc)) {
$ci = api_get_course_info($cc);
SessionManager::removeUsersFromCourseSession([$student_id], $sessionParam, $ci);
}
}
}
Display::addFlash(Display::return_message(get_lang('Updated')));
Security::clear_token();
} else {
Display::addFlash(Display::return_message(get_lang('NoItemSelected'), 'warning'));
}
$redirectUrl = api_get_self().'?'.http_build_query([
'student' => $student_id,
'origin' => $origin,
'details' => $details,
'id_session' => $sessionParam,
]);
header('Location: '.$redirectUrl);
exit;
case 'export_one_session_row':
$sessionToExport = isset($_GET['session_to_export']) ? (int) $_GET['session_to_export'] : 0;
$exportList = Session::read('export_course_list');
@@ -605,6 +724,11 @@ $drh_can_access_all_courses = false;
if (api_is_drh() || api_is_platform_admin() || api_is_student_boss() || api_is_session_admin()) {
$drh_can_access_all_courses = true;
}
// Allow teachers to access tracking of all courses/sessions when enabled in configuration
$teacherCanAccessAll = api_get_configuration_value('teacher_access_all_tracking');
if (!$drh_can_access_all_courses && $teacherCanAccessAll && api_is_teacher()) {
$drh_can_access_all_courses = true;
}
$courses = CourseManager::get_course_list_of_user_as_course_admin(api_get_user_id());
$courses_in_session_by_coach = [];
@@ -654,39 +778,47 @@ while ($row = Database::fetch_array($rs)) {
}
$sessionTable = Database::get_main_table(TABLE_MAIN_SESSION);
$sessionCourseTable = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
$courseTable = Database::get_main_table(TABLE_MAIN_COURSE);
$sessionPositionOrder = '';
// Determine order like before for sessions
$allowOrder = api_get_configuration_value('session_list_order');
if ($allowOrder) {
$sessionPositionOrder = 's.position ASC, ';
}
$orderCondition = $allowOrder ? ' ORDER BY s.position ASC, s.display_end_date DESC' : ' ORDER BY s.display_end_date DESC';
// Use core helper to fetch sessions followed by user with the expected order
$sessionsForStudent = SessionManager::getSessionsFollowedByUser(
$student_id,
null,
null,
null,
false,
false,
false,
$orderCondition
);
// Get the list of sessions where the user is subscribed as student
$sql = 'SELECT DISTINCT sc.session_id, sc.c_id
FROM '.Database::get_main_table(TABLE_MAIN_SESSION_COURSE).' sc
INNER JOIN '.$sessionTable.' as s
ON (s.id = sc.session_id)
INNER JOIN '.Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER).' as scu
ON (scu.session_id = sc.session_id)
WHERE s.id = scu.session_id
AND user_id = '.$student_id.'
ORDER BY '.$sessionPositionOrder.'display_end_date DESC, sc.position ASC
';
$rs = Database::query($sql);
$tmp_sessions = [];
while ($row = Database::fetch_array($rs, 'ASSOC')) {
$tmp_sessions[] = $row['session_id'];
if ($drh_can_access_all_courses) {
if (in_array($row['session_id'], $tmp_sessions)) {
$courses_in_session[$row['session_id']][] = $row['c_id'];
}
} else {
if (isset($courses_in_session_by_coach[$row['session_id']])) {
if (in_array($row['session_id'], $tmp_sessions)) {
$courses_in_session[$row['session_id']][] = $row['c_id'];
}
foreach ($sessionsForStudent as $sessionItem) {
$sid = (int) $sessionItem['id'];
$tmp_sessions[] = $sid;
// Fetch all courses of the session ordered by sc.position ASC
$sqlCourses = 'SELECT sc.c_id, sc.position, c.code
FROM '.$sessionCourseTable.' sc
INNER JOIN '.$courseTable.' c ON (c.id = sc.c_id)
WHERE sc.session_id = '.$sid.'
ORDER BY sc.position ASC';
$rsCourses = Database::query($sqlCourses);
$cidList = [];
while ($rowC = Database::fetch_array($rsCourses, 'ASSOC')) {
$code = $rowC['code'];
// Respect coach restrictions when applicable
if ($drh_can_access_all_courses || !isset($courses_in_session_by_coach[$sid]) || isset($courses_in_session_by_coach[$sid][$code])) {
$cidList[] = (int) $rowC['c_id'];
}
}
// Always define the session key, even if empty, to render
$courses_in_session[$sid] = $cidList;
}
$isDrhOfCourse = CourseManager::isUserSubscribedInCourseAsDrh(api_get_user_id(), $courseInfo);
@@ -1195,6 +1327,178 @@ if (api_get_configuration_value('allow_career_users')) {
echo MyStudents::userCareersTable($student_id);
}
// Session progress section
if (api_get_configuration_value('improve_tracking_in_mystudent_php')) {
$orderCondition = null;
if (api_get_configuration_value('session_list_order')) {
$orderCondition = ' ORDER BY s.position ASC';
}
$sessions = SessionManager::getSessionsFollowedByUser(
$student_id,
null,
null,
null,
false,
false,
false,
$orderCondition
);
$sessionProgressTitle = get_lang('synthesis');
$sessionProgressHeading = '<h3 class="panel-title text-center"><strong>'.$sessionProgressTitle.'</strong></h3>';
$sessionProgressList = [];
$totalSessionsProgress = 0;
foreach ($sessions as $sessionItem) {
$courses = SessionManager::get_course_list_by_session_id($sessionItem['id']);
$courseProgressSum = 0;
$courseCount = 0;
foreach ($courses as $courseItem) {
$courseInfoItem = api_get_course_info_by_id($courseItem['real_id']);
$courseCodeItem = $courseInfoItem['code'];
if (CourseManager::is_user_subscribed_in_course($student_id, $courseCodeItem, true, $sessionItem['id'])) {
$progressValue = Tracking::get_avg_student_progress(
$student_id,
$courseCodeItem,
[],
$sessionItem['id']
);
if (is_numeric($progressValue)) {
$courseProgressSum += $progressValue;
}
$courseCount++;
}
}
$progress = $courseCount > 0 ? round($courseProgressSum / $courseCount, 2) : 0;
$sessionProgressList[] = [
'name' => $sessionItem['name'],
'progress' => $progress,
];
$totalSessionsProgress += $progress;
}
$avgSessionsProgress = !empty($sessionProgressList) ? round($totalSessionsProgress / count($sessionProgressList), 2) : 0;
// Calculate last week's time spent in courses using Tracking::generateReport
$aLastWeek = get_last_week();
$startWeek = date('Y-m-d', $aLastWeek[0]);
$endWeek = date('Y-m-d', $aLastWeek[6]);
$report = Tracking::generateReport('time_report', [$student_id], $startWeek, $endWeek);
$timeSeconds = 0;
foreach ($report['rows'] as $reportRow) {
$timeParts = explode(':', $reportRow[6]);
if (count($timeParts) === 3) {
[$hours, $minutes, $seconds] = array_map('intval', $timeParts);
$timeSeconds += ($hours * 3600) + ($minutes * 60) + $seconds;
}
}
$timeSpentLastWeek = api_time_to_hms($timeSeconds);
$detailsUrl = api_get_path(WEB_CODE_PATH)
.'mySpace/time_report_last_week.php?student='.$student_id
.'&start='.$startWeek.'&end='.$endWeek;
$timeContent = '<div class="text-center">';
$timeContent .= Display::return_icon('clock.png', get_lang('TimeSpentLastWeek'), [], ICON_SIZE_MEDIUM);
$timeContent .= '<div>'.$timeSpentLastWeek.'</div>';
$timeContent .= '<div>&nbsp;</div>';
$timeContent .= '<div><a href="'.$detailsUrl.'"'
.' onclick="window.open(this.href, \'timeReportDetails\','
.' \'width=800,height=600,scrollbars=yes\'); return false;">'
.get_lang('Details').'</a></div>';
$timeContent .= '<div>&nbsp;</div>';
$timeContent .= '<div>'
.'<a href="'.$detailsUrl.'&export=pdf">'
.Display::return_icon('pdf.png', get_lang('ExportPDF'), [], ICON_SIZE_MEDIUM)
.'</a> '
.'<a href="'.$detailsUrl.'&export=xls">'
.Display::return_icon('export_excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM)
.'</a></div>';
$timeContent .= '</div>';
$timePanel = Display::panel($timeContent, get_lang('TimeSpentInCoursesLastWeek'));
$avgProgressContent = '<div class="text-center">';
$avgProgressContent .= '<div id="avg-sessions-progress" class="easypiechart" data-percent="'.$avgSessionsProgress.'">';
$avgProgressContent .= '<span class="percent">'.$avgSessionsProgress.'%</span>';
$avgProgressContent .= '</div>';
$avgProgressContent .= '</div>';
$avgProgressContent .= "<script>\n $(function() {\n $('#avg-sessions-progress').easyPieChart({\n scaleColor: false,\n lineWidth: 8,\n barColor: '#3ba557',\n trackColor: '#f2f2f2'\n });\n });\n </script>";
$avgProgressPanel = Display::panel($avgProgressContent, get_lang('AverageProgressInSessions'));
$sessionBars = '';
foreach ($sessionProgressList as $item) {
$sessionBars .= '<p>'.Security::remove_XSS($item['name']).'</p>';
$sessionBars .= '<div class="progress">';
$sessionBars .= '<div class="progress-bar progress-bar-success" role="progressbar" style="width: '.$item['progress'].'%;">'.$item['progress'].'%</div>';
$sessionBars .= '</div>';
}
$sessionBarsAccordion = Display::panelCollapse(
get_lang('ProgressionInSessions'),
$sessionBars,
'panel-session-progress',
[],
'accordion-session-progress',
'collapse-session-progress',
false,
true
);
$sessionProgressHtml = '<div class="row session-progress-section" style="display:flex;flex-wrap:wrap;align-items:stretch;">';
$sessionProgressHtml .= '<div class="col-md-6 text-center" style="display:flex;"><div style="flex:1;">'.$avgProgressPanel.'</div></div>';
$sessionProgressHtml .= '<div class="col-md-6" style="display:flex;"><div style="flex:1;">'.$timePanel.'</div></div>';
$sessionProgressHtml .= '</div>';
$sessionProgressHtml .= $sessionBarsAccordion;
// Weekly time spent summary table - vertical layout displayed in 4 columns
$weeksToShow = 52;
$currentMonday = strtotime('monday this week');
$weekData = [];
for ($i = 1; $i <= $weeksToShow; $i++) {
$weekStart = strtotime('-'.$i.' week', $currentMonday);
$weekEnd = $weekStart + (6 * 86400);
$startDate = date('Y-m-d', $weekStart);
$endDate = date('Y-m-d', $weekEnd);
$reportWeek = Tracking::generateReport('time_report', [$student_id], $startDate, $endDate);
$weekSeconds = 0;
foreach ($reportWeek['rows'] as $reportRow) {
$parts = explode(':', $reportRow[6]);
if (count($parts) === 3) {
[$h, $m, $s] = array_map('intval', $parts);
$weekSeconds += ($h * 3600) + ($m * 60) + $s;
}
}
$label = date('Y', $weekStart).' - '.date('W', $weekStart);
$weekData[] = [
'label' => $label,
'time' => api_time_to_hms($weekSeconds),
];
}
$tablesHtml = '<div class="row">';
$tablesCount = 4;
$weeksPerTable = (int) ceil($weeksToShow / $tablesCount);
$index = 0;
for ($table = 0; $table < $tablesCount; $table++) {
$tableHtml = '<table class="table table-bordered table-condensed">';
$tableHtml .= '<thead><tr><th>'.get_lang('Week').'</th><th>'.get_lang('LatencyTimeSpent').'</th></tr></thead><tbody>';
for ($j = 0; $j < $weeksPerTable && $index < count($weekData); $j++, $index++) {
$label = Security::remove_XSS($weekData[$index]['label']);
$time = $weekData[$index]['time'];
$tableHtml .= '<tr><th class="text-center">'.$label.'</th><td class="text-right">'.$time.'</td></tr>';
}
$tableHtml .= '</tbody></table>';
$tablesHtml .= '<div class="col-md-3">'.$tableHtml.'</div>';
}
$tablesHtml .= '</div>';
$weeklySummaryPanel = Display::panelCollapse(
get_lang('WeeklyTimeSummary'),
$tablesHtml,
'panel-weekly-summary',
[],
'accordion-weekly-summary',
'collapse-weekly-summary',
false
);
$sessionProgressHtml .= $weeklySummaryPanel;
echo Display::panel($sessionProgressHtml, '', '', 'default', $sessionProgressHeading);
}
echo MyStudents::getBlockForSkills(
$student_id,
$courseInfo ? $courseInfo['real_id'] : 0,
@@ -1229,19 +1533,41 @@ echo '<div class="row"><div class="col-sm-5">';
echo MyStudents::getBlockForClasses($student_id);
echo '</div></div>';
$theoreticalTimeEnabled = api_get_configuration_value('display_theoretical_time');
$exportCourseList = [];
$lpIdList = [];
if (empty($details)) {
$csv_content[] = [];
$csv_content[] = [
get_lang('Session'),
get_lang('Course'),
get_lang('Time'),
get_lang('Progress'),
get_lang('Score'),
get_lang('AttendancesFaults'),
get_lang('Evaluations'),
];
if ($theoreticalTimeEnabled) {
$header = [
get_lang('Session'),
get_lang('Course'),
get_lang('Time'),
get_lang('Progress'),
get_lang('Score'),
get_lang('TheoreticalTime'),
];
if ($subscriptionColumnEnabled) {
$header[] = get_lang('Subscription');
}
$header[] = get_lang('AttendancesFaults');
$header[] = get_lang('Evaluations');
$csv_content[] = $header;
} else {
$header = [
get_lang('Session'),
get_lang('Course'),
get_lang('Time'),
get_lang('Progress'),
get_lang('Score'),
];
if ($subscriptionColumnEnabled) {
$header[] = get_lang('Subscription');
}
$header[] = get_lang('AttendancesFaults');
$header[] = get_lang('Evaluations');
$csv_content[] = $header;
}
$attendance = new Attendance();
$extraFieldValueSession = new ExtraFieldValue('session');
@@ -1272,33 +1598,96 @@ if (empty($details)) {
.' '.$session_name.($date_session ? ' ('.$date_session.')' : '');
}
// Permission for subscription actions (admin, session admin, teacher)
$canManageSubscriptions = api_is_platform_admin(true, true)
|| api_is_session_admin()
|| api_is_allowed_to_edit(null, true)
|| api_is_course_admin()
|| api_is_teacher()
|| api_is_coach();
// Courses
echo '<h3>'.$title.'</h3>';
// Determine columns count dynamically (including optional columns)
$hasBulk = ($subscriptionColumnEnabled && !empty($sId) && $canManageSubscriptions);
$columnsCount = 0;
// Checkbox bulk column
if ($hasBulk) {
$columnsCount++;
}
// Base columns
$columnsCount += 4; // Course, Time, Progress, Score
// Theoretical time
if ($theoreticalTimeEnabled) {
$columnsCount++;
}
// Subscription icon column (single action)
if ($subscriptionColumnEnabled && !empty($sId) && $canManageSubscriptions) {
$columnsCount++;
}
// Attendances, Evaluations, Details
$columnsCount += 3;
echo '<form method="post" action="'.api_get_self().'">';
echo '<div class="table-responsive">';
echo '<table class="table table-striped table-hover courses-tracking">';
echo '<thead>';
echo '<tr>
<th>'.get_lang('Course').'</th>
<th>'.get_lang('Time').'</th>
<th>'.get_lang('Progress').' '.Display::return_icon('info3.gif', get_lang('progressBasedOnVisiblesLPsInEachCourse'), [], ICON_SIZE_TINY).' </th>
<th>'.get_lang('Score').'</th>
<th>'.get_lang('AttendancesFaults').'</th>
<th>'.get_lang('Evaluations').'</th>
<th>'.get_lang('Details').'</th>
</tr>';
echo '<tr>';
if ($hasBulk) {
// Select all for this session's courses
$selectAllId = 'bulk-select-all-s'.$sId;
echo '<th style="width:20px;">'
.'<input type="checkbox" id="'.$selectAllId.'" class="bulk-select-all" data-target="s'.$sId.'" />'
.'</th>';
}
echo '<th>'.get_lang('Course').'</th>';
echo '<th>'.get_lang('Time').'</th>';
echo '<th>'.get_lang('Progress').' '.Display::return_icon('info3.gif', get_lang('progressBasedOnVisiblesLPsInEachCourse'), [], ICON_SIZE_TINY).' </th>';
echo '<th>'.get_lang('Score').'</th>';
if ($theoreticalTimeEnabled) {
echo '<th>'.get_lang('TheoreticalTime').'</th>';
}
if ($subscriptionColumnEnabled && !empty($sId) && $canManageSubscriptions) {
echo '<th>'.get_lang('Subscription').'</th>';
}
echo '<th>'.get_lang('AttendancesFaults').'</th>';
echo '<th>'.get_lang('Evaluations').'</th>';
echo '<th>'.get_lang('Details').'</th>';
echo '</tr>';
echo '</thead>';
echo '<tbody>';
$csvRow = [
'',
get_lang('Course'),
get_lang('Time'),
get_lang('Progress'),
get_lang('Score'),
get_lang('AttendancesFaults'),
get_lang('Evaluations'),
get_lang('Details'),
];
if ($theoreticalTimeEnabled) {
$csvRow = [
'',
get_lang('Course'),
get_lang('Time'),
get_lang('Progress'),
get_lang('Score'),
get_lang('TheoreticalTime'),
];
if ($subscriptionColumnEnabled && !empty($sId)) {
$csvRow[] = get_lang('Subscription');
}
$csvRow[] = get_lang('AttendancesFaults');
$csvRow[] = get_lang('Evaluations');
$csvRow[] = get_lang('Details');
} else {
$csvRow = [
'',
get_lang('Course'),
get_lang('Time'),
get_lang('Progress'),
get_lang('Score'),
];
if ($subscriptionColumnEnabled && !empty($sId)) {
$csvRow[] = get_lang('Subscription');
}
$csvRow[] = get_lang('AttendancesFaults');
$csvRow[] = get_lang('Evaluations');
$csvRow[] = get_lang('Details');
}
$exportCourseList[$sId][] = $csvRow;
@@ -1308,8 +1697,10 @@ if (empty($details)) {
$totalScore = 0;
$totalProgress = 0;
$gradeBookTotal = [0, 0];
$totalCourses = count($courses);
$totalCourses = 0;
$scoreDisplay = ScoreDisplay::instance();
$theoreticalTime = 0;
$totalTheoreticalTime = 0;
foreach ($courses as $courseId) {
$courseInfoItem = api_get_course_info_by_id($courseId);
@@ -1329,8 +1720,59 @@ if (empty($details)) {
$sId
);
}
$time_spent_on_course = api_time_to_hms(0);
$attendances_faults_avg = '0/0 (0%)';
$scoretotal_display = '0/0 (0%)';
$progress = '0%';
$score = '0%';
$subscriptionIcon = '';
$subscriptionCsv = '';
if ($subscriptionColumnEnabled && !empty($sId) && $canManageSubscriptions) {
$subscribeUrl = api_get_self().'?'.http_build_query([
'action' => 'subscribe_course',
'id_session' => $sId,
'student' => $student_id,
'course_code' => $courseCodeItem,
'origin' => $origin,
'details' => $details,
]);
$unsubscribeUrl = api_get_self().'?'.http_build_query([
'action' => 'unsubscribe_course',
'id_session' => $sId,
'student' => $student_id,
'course_code' => $courseCodeItem,
'origin' => $origin,
'details' => $details,
]);
$subscriptionIcon = Display::url(
Display::return_icon('add.png', get_lang('NotRegistered')),
$subscribeUrl
);
$subscriptionCsv = '+';
}
if ($theoreticalTimeEnabled) {
$theoreticalTime = CourseManager::get_course_extra_field_value('theoretical_time', $courseCodeItem);
if (is_numeric($theoreticalTime) && (float) $theoreticalTime != 0) {
if ($isSubscribed) {
$totalTheoreticalTime += (float) $theoreticalTime;
}
$hours = floor($theoreticalTime / 60);
$minutes = $theoreticalTime % 60;
$theoreticalTimeDisplay = sprintf('%02d:%02d', $hours, $minutes);
} else {
$theoreticalTimeDisplay = '00:00';
}
}
if ($isSubscribed) {
if ($subscriptionColumnEnabled && !empty($sId) && $canManageSubscriptions) {
$subscriptionIcon = Display::url(
Display::return_icon('delete.png', get_lang('Registered')),
$unsubscribeUrl
);
$subscriptionCsv = 'x';
}
$totalCourses++;
$timeInSeconds = Tracking::get_time_spent_on_the_course(
$student_id,
$courseId,
@@ -1346,7 +1788,6 @@ if (empty($details)) {
$sId
);
$attendances_faults_avg = '0/0 (0%)';
if (!empty($results_faults_avg['total'])) {
if (api_is_drh()) {
$attendances_faults_avg = Display::url(
@@ -1385,7 +1826,6 @@ if (empty($details)) {
}
}
$scoretotal_display = '0/0 (0%)';
if (!empty($scoretotal) && !empty($scoretotal[1])) {
$scoretotal_display =
round($scoretotal[0], 1).'/'.
@@ -1396,66 +1836,99 @@ if (empty($details)) {
$gradeBookTotal[1] += $scoretotal[1];
}
$progress = Tracking::get_avg_student_progress(
$progressVal = Tracking::get_avg_student_progress(
$student_id,
$courseCodeItem,
[],
$sId
);
$totalProgress += $progress;
$totalProgress += $progressVal;
$score = Tracking::get_avg_student_score(
$scoreVal = Tracking::get_avg_student_score(
$student_id,
$courseCodeItem,
[],
$sId
);
if (is_numeric($score)) {
$totalScore += $score;
if (is_numeric($scoreVal)) {
$totalScore += $scoreVal;
}
$progress = empty($progress) ? '0%' : $progress.'%';
$score = empty($score) ? '0%' : $score.'%';
$progress = empty($progressVal) ? '0%' : $progressVal.'%';
$score = empty($scoreVal) ? '0%' : $scoreVal.'%';
}
if ($theoreticalTimeEnabled) {
$csvRow = [
$session_name,
$courseInfoItem['title'],
$time_spent_on_course,
$progress,
$score,
$attendances_faults_avg,
$scoretotal_display,
$theoreticalTimeDisplay,
];
$csv_content[] = $csvRow;
$exportCourseList[$sId][] = $csvRow;
echo '<tr>
<td>
<a href="'.$courseInfoItem['course_public_url'].'?id_session='.$sId.'">'.
$courseInfoItem['title'].'
</a>
</td>
<td>'.$time_spent_on_course.'</td>
<td>'.$progress.'</td>
<td>'.$score.'</td>
<td>'.$attendances_faults_avg.'</td>
<td>'.$scoretotal_display.'</td>';
if (!empty($coachId)) {
echo '<td width="10"><a href="'.api_get_self().'?student='.$student_id
.'&details=true&course='.$courseInfoItem['code'].'&id_coach='.$coachId.'&origin='.$origin
.'&id_session='.$sId.'#infosStudent">'
.Display::return_icon('2rightarrow.png', get_lang('Details')).'</a></td>';
} else {
echo '<td width="10"><a href="'.api_get_self().'?student='.$student_id
.'&details=true&course='.$courseInfoItem['code'].'&origin='.$origin.'&id_session='.$sId
.'#infosStudent">'
.Display::return_icon('2rightarrow.png', get_lang('Details')).'</a></td>';
if ($subscriptionColumnEnabled && !empty($sId)) {
$csvRow[] = $subscriptionCsv;
}
echo '</tr>';
$csvRow[] = $attendances_faults_avg;
$csvRow[] = $scoretotal_display;
} else {
$csvRow = [
$session_name,
$courseInfoItem['title'],
$time_spent_on_course,
$progress,
$score,
];
if ($subscriptionColumnEnabled && !empty($sId)) {
$csvRow[] = $subscriptionCsv;
}
$csvRow[] = $attendances_faults_avg;
$csvRow[] = $scoretotal_display;
}
$csv_content[] = $csvRow;
$exportCourseList[$sId][] = $csvRow;
$rowClass = (!$isSubscribed && $subscriptionColumnEnabled) ? ' class="course-unsubscribed"' : '';
echo '<tr'.$rowClass.'>';
if ($hasBulk) {
echo '<td>'
.'<input type="checkbox" name="course_codes[]" value="'.Security::remove_XSS($courseCodeItem).'"'
.' class="bulk-course-checkbox bulk-course-checkbox-s'.$sId.'" />'
.'</td>';
}
echo '<td>',
'<a href="'.$courseInfoItem['course_public_url'].'?id_session='.$sId.'">'.
$courseInfoItem['title'].
'</a>',
'</td>',
'<td>'.$time_spent_on_course.'</td>',
'<td>'.$progress.'</td>',
'<td>'.$score.'</td>';
if ($theoreticalTimeEnabled) {
echo '<td>'.$theoreticalTimeDisplay.'</td>';
}
if ($subscriptionColumnEnabled && !empty($sId) && $canManageSubscriptions) {
echo '<td>'.$subscriptionIcon.'</td>';
}
echo '<td>'.$attendances_faults_avg.'</td>',
'<td>'.$scoretotal_display.'</td>';
if (!empty($coachId)) {
echo '<td width="10"><a href="'.api_get_self().'?student='.$student_id
.'&details=true&course='.$courseInfoItem['code'].'&id_coach='.$coachId.'&origin='.$origin
.'&id_session='.$sId.'#infosStudent">'
.Display::return_icon('2rightarrow.png', get_lang('Details'), ['class' => 'details-icon'])
.'</a></td>';
} else {
echo '<td width="10"><a href="'.api_get_self().'?student='.$student_id
.'&details=true&course='.$courseInfoItem['code'].'&origin='.$origin.'&id_session='.$sId
.'#infosStudent">'
.Display::return_icon('2rightarrow.png', get_lang('Details'), ['class' => 'details-icon'])
.'</a></td>';
}
echo '</tr>';
}
$totalAttendanceFormatted = $scoreDisplay->display_score($totalAttendance);
@@ -1466,26 +1939,58 @@ if (empty($details)) {
);
$totalEvaluations = $scoreDisplay->display_score($gradeBookTotal);
$totalTimeFormatted = api_time_to_hms($totalCourseTime);
echo '<tr>
<th>'.get_lang('Total').'</th>
echo '<tr>';
if ($hasBulk) {
echo '<th></th>';
}
echo '<th>'.get_lang('Total').'</th>
<th>'.$totalTimeFormatted.'</th>
<th>'.$totalProgressFormatted.'</th>
<th>'.$totalScoreFormatted.'</th>
<th>'.$totalAttendanceFormatted.'</th>
<th>'.$totalEvaluations.'</th>
<th></th>
</tr>';
<th>'.$totalScoreFormatted.'</th>';
if ($theoreticalTimeEnabled) {
$totalHours = floor($totalTheoreticalTime / 60);
$totalMinutes = $totalTheoreticalTime % 60;
$totalTheoreticalTimeDisplay = sprintf('%02d:%02d', $totalHours, $totalMinutes);
echo '<td>'.$totalTheoreticalTimeDisplay.'</td>';
}
if (!empty($sId)) {
echo '<th></th>';
}
echo '<th>'.$totalAttendanceFormatted.'</th>',
'<th>'.$totalEvaluations.'</th>',
'<th></th>',
'</tr>';
$csvRow = [
get_lang('Total'),
'',
$totalTimeFormatted,
$totalProgressFormatted,
$totalScoreFormatted,
$totalAttendanceFormatted,
$totalEvaluations,
'',
];
if ($theoreticalTimeEnabled) {
$csvRow = [
get_lang('Total'),
'',
$totalTimeFormatted,
$totalProgressFormatted,
$totalScoreFormatted,
$totalTheoreticalTimeDisplay,
];
if (!empty($sId)) {
$csvRow[] = '';
}
$csvRow[] = $totalAttendanceFormatted;
$csvRow[] = $totalEvaluations;
$csvRow[] = '';
} else {
$csvRow = [
get_lang('Total'),
'',
$totalTimeFormatted,
$totalProgressFormatted,
$totalScoreFormatted,
];
if (!empty($sId)) {
$csvRow[] = '';
}
$csvRow[] = $totalAttendanceFormatted;
$csvRow[] = $totalEvaluations;
$csvRow[] = '';
}
$csv_content[] = $csvRow;
$exportCourseList[$sId][] = $csvRow;
@@ -1590,12 +2095,48 @@ if (empty($details)) {
}
echo $sessionAction;
} else {
echo "<tr><td colspan='5'>".get_lang('NoCourse')."</td></tr>";
echo "<tr><td colspan='".$columnsCount."'>".get_lang('NoCourse')."</td></tr>";
}
Session::write('export_course_list', $exportCourseList);
echo '</tbody>';
echo '</table>';
echo '</div>';
if ($hasBulk) {
// Bulk action controls per session
echo '<div class="row" style="margin:10px 0;">'
.'<div class="col-sm-12">'
.'<div class="form-inline">'
.'<input type="hidden" name="action" value="bulk_session_course_subscription" />'
.'<input type="hidden" name="id_session" value="'.$sId.'" />'
.'<input type="hidden" name="student" value="'.$student_id.'" />'
.'<input type="hidden" name="origin" value="'.Security::remove_XSS($origin).'" />'
.'<input type="hidden" name="details" value="'.Security::remove_XSS($details).'" />'
.'<input type="hidden" name="sec_token" value="'.$token.'" />'
.'<label class="control-label" for="bulk-action-'.$sId.'" style="margin-right:8px;">'.get_lang('Action').'</label>'
.'<select id="bulk-action-'.$sId.'" name="bulk_action" class="form-control" style="margin-right:8px;">'
.'<option value="subscribe">'.get_lang('Subscribe').'</option>'
.'<option value="unsubscribe">'.get_lang('Unsubscribe').'</option>'
.'</select>'
.'<button type="submit" class="btn btn-primary">'.get_lang('Validate').'</button>'
.'</div>'
.'</div>'
.'</div>';
// Small JS to toggle all checkboxes per session
echo "<script>\n".
"(function(){\n".
" var el = document.getElementById('".$selectAllId."');\n".
" if (el) {\n".
" el.addEventListener('change', function(){\n".
" var target = this.getAttribute('data-target');\n".
" var boxes = document.querySelectorAll('.bulk-course-checkbox-' + target);\n".
" for (var i=0;i<boxes.length;i++){ boxes[i].checked = this.checked; }\n".
" });\n".
" }\n".
"})();\n".
"</script>";
}
echo '</form>';
}
} else {
$columnHeaders = [
+288 -153
View File
@@ -9,12 +9,21 @@ $cidReset = true;
require_once __DIR__.'/../inc/global.inc.php';
$this_section = SECTION_TRACKING;
// CSRF token for delete action.
$token = Security::get_token();
if (!api_is_allowed_to_create_course() && !api_is_drh()) {
api_not_allowed(true);
}
$allowCustomCertificate = 'true' === api_get_plugin_setting('customcertificate', 'enable_plugin_customcertificate');
$plugin = CustomCertificatePlugin::create();
/** @var CustomCertificatePlugin|null $plugin */
// Create plugin instance only when the plugin is enabled.
$plugin = null;
if ($allowCustomCertificate) {
$plugin = CustomCertificatePlugin::create();
}
$tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
$tblSession = Database::get_main_table(TABLE_MAIN_SESSION);
@@ -28,7 +37,7 @@ define('ALL_DATE_FILTER', 3);
$certificateList = [];
$urlParam = '';
$form = new FormValidator('search_user', 'GET', api_get_self());
$form = new FormValidator('search_user', 'POST', api_get_self());
$innerJoinSessionRelUser = '';
$whereCondictionDRH = '';
$whereCondictionMultiUrl = '';
@@ -39,7 +48,7 @@ if (api_is_drh()) {
$whereCondictionMultiUrl = " AND session_rel_user.user_id = ".api_get_user_id();
}
// Select of sessions.
// Select sessions the user can see.
$sql = "SELECT s.id, name FROM $tblSession s
$innerJoinSessionRelUser
$whereCondictionDRH
@@ -51,7 +60,7 @@ if (api_is_multiple_url_enabled()) {
if ($accessUrlId != -1) {
$sql = "SELECT s.id, name FROM $tblSession s
INNER JOIN $tblSessionRelAccessUrl as session_rel_url
ON (s.id = session_rel_url.session_id)
ON (s.id = session_rel_url.session_id)
$innerJoinSessionRelUser
WHERE access_url_id = $accessUrlId
$whereCondictionMultiUrl
@@ -60,13 +69,26 @@ if (api_is_multiple_url_enabled()) {
}
$result = Database::query($sql);
$Sessions = Database::store_result($result);
$options = [];
$options['0'] = '';
foreach ($Sessions as $enreg) {
$options[$enreg['id']] = $enreg['name'];
// Build choices array for the advanced multi-select.
$sessionChoices = [];
foreach ($Sessions as $row) {
$sessionChoices[$row['id']] = $row['name'];
}
$form->addElement('select', 'session_id', get_lang('SessionList'), $options, ['id' => 'session-id']);
$sessionMultiSelect = $form->addElement(
'advmultiselect',
'session_id',
null,
$sessionChoices
);
$sessionMultiSelect->setLabel([
get_lang('SessionList'),
get_lang('SearchSession'),
get_lang('MySessions'),
]);
$form->addDatePicker('date_begin', get_lang('DateStart'), ['id' => 'date-begin']);
$form->addDatePicker('date_end', get_lang('DateEnd'), ['id' => 'date-end']);
@@ -87,14 +109,48 @@ $returnParams = $extraField->addElements(
$form->addElement('hidden', 'formSent', 1);
$form->addButtonSearch(get_lang('Search'));
$form->addButtonExport(get_lang('ExportAsCSV'), 'export');
// We keep all export actions (CSV, PDF, ZIP) near the result table, so no form-level export button.
// Decide how to get values: normal search submit or CSV export icon.
$values = null;
$exportToCsv = false;
// Case 1: user clicked "Search" button (normal form submit).
if ($form->validate()) {
$values = $form->getSubmitValues();
$exportToCsv = isset($values['export']);
$sessionId = (int) $_REQUEST['session_id'];
$dateBegin = isset($_REQUEST['date_begin']) ? strtotime($_REQUEST['date_begin']) : null;
$dateEnd = isset($_REQUEST['date_end']) ? strtotime($_REQUEST['date_end'].' 23:59:59') : null;
$exportToCsv = false;
} elseif (isset($_GET['export'])) {
// Case 2: CSV export icon was clicked.
// Use raw GET parameters as the form values so filters are preserved.
$values = $_POST;
$exportToCsv = true;
}
// Process filters and build result list only when we have values (search or export).
if (!empty($values)) {
// Normalize session ids from the form to an integer array.
$sessionIds = [];
if (!empty($values['session_id'])) {
if (is_array($values['session_id'])) {
foreach ($values['session_id'] as $sessionIdValue) {
$sessionIdValue = (int) $sessionIdValue;
if ($sessionIdValue > 0) {
$sessionIds[] = $sessionIdValue;
}
}
} else {
$sessionIdValue = (int) $values['session_id'];
if ($sessionIdValue > 0) {
$sessionIds[] = $sessionIdValue;
}
}
}
$dateBeginRaw = isset($values['date_begin']) ? $values['date_begin'] : null;
$dateEndRaw = isset($values['date_end']) ? $values['date_end'] : null;
$dateBegin = !empty($dateBeginRaw) ? strtotime($dateBeginRaw) : null;
$dateEnd = !empty($dateEndRaw) ? strtotime($dateEndRaw.' 23:59:59') : null;
$filterDate = 0;
if (!empty($dateBegin)) {
@@ -104,91 +160,117 @@ if ($form->validate()) {
$filterDate += DATE_END_FILTER;
}
// Build extra-field filter list.
$filterCheckList = [];
$extraField = new ExtraField('user');
$extraFieldsAll = $extraField->get_all(['filter = ?' => 1], 'option_order');
foreach ($extraFieldsAll as $field) {
if (!empty($_REQUEST['extra_'.$field['variable']])) {
$fieldName = 'extra_'.$field['variable'];
if (!empty($values[$fieldName])) {
$filterCheckList[$field['id']] = $field;
}
}
$result = Database::select(
'c.id, c.code',
"$tbl_course c INNER JOIN $tblSessionRelCourse r ON c.id = r.c_id",
[
'where' => [
"r.session_id = ? " => [$sessionId],
],
]
);
foreach ($result as $value) {
$courseId = $value['id'];
$courseCode = $value['code'];
$cats = Category::load(
null,
null,
$courseCode,
null,
null,
$sessionId,
'ORDER BY id'
);
if (empty($cats)) {
// first time
$cats = Category::load(
0,
null,
$courseCode,
null,
null,
$sessionId,
'ORDER BY id'
// Build certificate list for all selected sessions.
$certificateList = [];
if (!empty($sessionIds)) {
foreach ($sessionIds as $sessionId) {
$courseResult = Database::select(
'c.id, c.code',
"$tbl_course c INNER JOIN $tblSessionRelCourse r ON c.id = r.c_id",
[
'where' => [
"r.session_id = ? " => [$sessionId],
],
]
);
}
$selectCat = (int) $cats[0]->get_id();
$certificateListAux = [];
if (!empty($selectCat)) {
$certificateListAux = GradebookUtils::get_list_users_certificates($selectCat);
}
if (empty($courseResult)) {
continue;
}
foreach ($certificateListAux as $value) {
$createdAt = strtotime(api_get_local_time($value['created_at']));
$value['category_id'] = $selectCat;
$value['c_id'] = $courseId;
$value['course_code'] = $courseCode;
switch ($filterDate) {
case NO_DATE_FILTER:
$certificateList[] = $value;
break;
case DATE_BEGIN_FILTER:
if ($createdAt >= $dateBegin) {
$certificateList[] = $value;
$sessionInfo = api_get_session_info($sessionId);
foreach ($courseResult as $course) {
$courseId = $course['id'];
$courseCode = $course['code'];
$cats = Category::load(
null,
null,
$courseCode,
null,
null,
$sessionId,
'ORDER BY id'
);
if (empty($cats)) {
// First time load with default category id = 0.
$cats = Category::load(
0,
null,
$courseCode,
null,
null,
$sessionId,
'ORDER BY id'
);
}
if (empty($cats)) {
continue;
}
$selectCat = (int) $cats[0]->get_id();
if ($selectCat <= 0) {
continue;
}
$certificateListAux = GradebookUtils::get_list_users_certificates($selectCat);
if (empty($certificateListAux)) {
continue;
}
foreach ($certificateListAux as $certificate) {
$createdAt = strtotime(api_get_local_time($certificate['created_at']));
$certificate['category_id'] = $selectCat;
$certificate['c_id'] = $courseId;
$certificate['course_code'] = $courseCode;
$certificate['session_id'] = $sessionId;
$certificate['session_name'] = isset($sessionInfo['name']) ? $sessionInfo['name'] : '';
$includeCertificate = false;
switch ($filterDate) {
case NO_DATE_FILTER:
$includeCertificate = true;
break;
case DATE_BEGIN_FILTER:
$includeCertificate = $createdAt >= $dateBegin;
break;
case DATE_END_FILTER:
$includeCertificate = $createdAt <= $dateEnd;
break;
case ALL_DATE_FILTER:
$includeCertificate = $createdAt >= $dateBegin && $createdAt <= $dateEnd;
break;
}
break;
case DATE_END_FILTER:
if ($createdAt <= $dateEnd) {
$certificateList[] = $value;
if ($includeCertificate) {
$certificateList[] = $certificate;
}
break;
case ALL_DATE_FILTER:
if ($createdAt >= $dateBegin && $createdAt <= $dateEnd) {
$certificateList[] = $value;
}
break;
}
}
}
}
// Filter extra field
foreach ($certificateList as $key => $value) {
// Filter by extra fields after building the global list.
if (!empty($filterCheckList) && !empty($certificateList)) {
foreach ($certificateList as $key => $certificate) {
foreach ($filterCheckList as $fieldId => $field) {
$extraFieldValue = new ExtraFieldValue('user');
$extraFieldValueData = $extraFieldValue->get_values_by_handler_and_field_id(
$value['user_id'],
$certificate['user_id'],
$fieldId
);
@@ -197,43 +279,78 @@ if ($form->validate()) {
break;
}
$fieldName = 'extra_'.$field['variable'];
switch ($field['field_type']) {
case ExtraField::FIELD_TYPE_TEXT:
case ExtraField::FIELD_TYPE_ALPHANUMERIC:
$pos = stripos($extraFieldValueData['value'], $_REQUEST['extra_'.$field['variable']]);
if ($pos === false) {
unset($certificateList[$key]);
$filterValue = isset($values[$fieldName]) ? $values[$fieldName] : '';
if ($filterValue !== '') {
$pos = stripos($extraFieldValueData['value'], (string) $filterValue);
if ($pos === false) {
unset($certificateList[$key]);
}
}
break;
case ExtraField::FIELD_TYPE_RADIO:
$valueRadio = $_REQUEST['extra_'.$field['variable']]['extra_'.$field['variable']];
if ($extraFieldValueData['value'] != $valueRadio) {
$filterValue = '';
if (isset($values[$fieldName][$fieldName])) {
$filterValue = $values[$fieldName][$fieldName];
}
if ($extraFieldValueData['value'] != $filterValue) {
unset($certificateList[$key]);
}
break;
case ExtraField::FIELD_TYPE_SELECT:
if ($extraFieldValueData['value'] != $_REQUEST['extra_'.$field['variable']]) {
$filterValue = isset($values[$fieldName]) ? $values[$fieldName] : null;
if ($filterValue !== null && $extraFieldValueData['value'] != $filterValue) {
unset($certificateList[$key]);
}
break;
}
}
}
}
// Reindex after unsetting items.
$certificateList = array_values($certificateList);
}
// Build URL parameters used by the export (PDF / ZIP / CSV) buttons.
$params = [];
if (!empty($sessionIds)) {
foreach ($sessionIds as $sessionId) {
$params['session_id'][] = $sessionId;
}
}
$params = [
'session_id' => (int) $_REQUEST['session_id'],
'date_begin' => Security::remove_XSS($_REQUEST['date_begin']),
'date_end' => Security::remove_XSS($_REQUEST['date_end']),
];
// Mark that form has been submitted, so filters can be reused.
$params['formSent'] = 1;
$params['date_begin'] = Security::remove_XSS((string) $dateBeginRaw);
$params['date_end'] = Security::remove_XSS((string) $dateEndRaw);
foreach ($filterCheckList as $field) {
$params['extra_'.$field['variable']] = Security::remove_XSS($_REQUEST['extra_'.$field['variable']]);
$fieldName = 'extra_'.$field['variable'];
if (!isset($values[$fieldName])) {
continue;
}
if (is_array($values[$fieldName])) {
$cleanArray = [];
foreach ($values[$fieldName] as $key => $val) {
$cleanArray[$key] = is_string($val) ? Security::remove_XSS($val) : $val;
}
$params[$fieldName] = $cleanArray;
} else {
$params[$fieldName] = Security::remove_XSS((string) $values[$fieldName]);
}
}
$urlParam = http_build_query($params);
$dataToExport = [];
// Build CSV data when export is requested.
if ($exportToCsv) {
$dataToExport = [];
$headers = [
get_lang('Session'),
get_lang('Course'),
@@ -249,7 +366,6 @@ if ($form->validate()) {
}
$dataToExport[] = $headers;
$sessionInfo = api_get_session_info($sessionId);
foreach ($certificateList as $index => $value) {
$categoryId = $value['category_id'];
$courseCode = $value['course_code'];
@@ -274,7 +390,7 @@ if ($form->validate()) {
$list = GradebookUtils::get_list_gradebook_certificates_by_user_id($value['user_id'], $categoryId);
foreach ($list as $valueCertificate) {
$item = [];
$item[] = $sessionInfo['name'];
$item[] = !empty($value['session_name']) ? $value['session_name'] : '';
$item[] = $courseInfo['title'];
$item[] = $value['firstname'];
$item[] = $value['lastname'];
@@ -285,79 +401,97 @@ if ($form->validate()) {
}
}
Export::arrayToCsv($dataToExport, 'export');
// Stop further HTML output when exporting CSV.
exit;
}
}
$htmlHeadXtra[] = "<script>
// Register JS only when the custom certificate plugin is enabled and available.
// JS only shows a confirmation dialog, it does not override URLs.
if ($allowCustomCertificate && $plugin) {
// Escape message to avoid breaking JS string.
$onlyCustomMsg = addslashes($plugin->get_lang('OnlyCustomCertificates'));
$htmlHeadXtra[] = "<script>
$(function () {
$('#export_pdf').click(function(e) {
e.preventDefault();
e.stopPropagation();
var session_id = $('#session-id').val();
var date_begin = $('#date-begin').val();
var date_end = $('#date-end').val();
if (confirm('".$plugin->get_lang('OnlyCustomCertificates')."')) {
var url = '".api_get_path(WEB_PLUGIN_PATH)."' +
'customcertificate/src/export_pdf_all_in_one.php?' +
'".$urlParam."&' +
'export_pdf=1';
$(location).attr('href',url);
$('#export_pdf').on('click', function(e) {
if (!confirm('".$onlyCustomMsg."')) {
e.preventDefault();
e.stopPropagation();
return false;
}
});
$('#export_zip').click(function(e) {
e.preventDefault();
e.stopPropagation();
var session_id = $('#session-id').val();
var date_begin = $('#date-begin').val();
var date_end = $('#date-end').val();
if (confirm('".$plugin->get_lang('OnlyCustomCertificates')."')) {
var url = '".api_get_path(WEB_PLUGIN_PATH)."' +
'customcertificate/src/export_pdf_all_in_one.php?' +
'".$urlParam."&' +
'export_zip=1';
$(location).attr('href',url);
$('#export_zip').on('click', function(e) {
if (!confirm('".$onlyCustomMsg."')) {
e.preventDefault();
e.stopPropagation();
return false;
}
});
});
</script>";
}
$interbreadcrumb[] = ['url' => 'index.php', 'name' => get_lang('MySpace')];
Display::display_header(get_lang('CertificatesSessions'));
echo Display::page_header(get_lang('CertificatesSessions'));
$actions = '';
$actions .= Display::url(
// Top toolbar: only back button, always visible.
$topActions = '';
$topActions .= Display::url(
Display::return_icon('back.png', get_lang('Back'), [], 32),
api_get_path(WEB_CODE_PATH).'mySpace'
);
echo Display::toolbarAction('actions', [$topActions]);
if ($allowCustomCertificate) {
$url = api_get_path(WEB_PLUGIN_PATH).'customcertificate/src/export_pdf_all_in_one.php';
$actions .= Display::url(
Display::return_icon('pdf.png', get_lang('ExportAllCertificatesToPDF'), [], ICON_SIZE_MEDIUM),
$url,
['id' => 'export_pdf']
);
$actions .= Display::url(
Display::return_icon('file_zip.png', get_lang('ExportAllCertificatesToZIP'), [], ICON_SIZE_MEDIUM),
$url,
['id' => 'export_zip']
);
}
echo Display::toolbarAction('actions', [$actions]);
// Show search form.
echo $form->returnForm();
if (0 == count($certificateList)) {
echo Display::return_message(get_lang('NoResultsAvailable'), 'warning');
} else {
echo '<table class="table table-hover table-striped data_table">';
// Actions related to the result list (CSV, PDF, ZIP) shown just above the table.
$resultActions = '';
// CSV export uses the same page with current filters.
$csvUrl = api_get_self().'?'.$urlParam.'&export=1';
$resultActions .= Display::url(
Display::return_icon('excel.png', get_lang('ExportAsCSV'), [], ICON_SIZE_MEDIUM),
$csvUrl
);
if ($allowCustomCertificate) {
$pluginUrl = api_get_path(WEB_PLUGIN_PATH).'customcertificate/src/export_pdf_all_in_one.php';
$pluginUrlWithParams = $pluginUrl.'?'.$urlParam;
// Open PDF export in a new tab so the filter form remains visible.
$resultActions .= Display::url(
Display::return_icon('pdf.png', get_lang('ExportAllCertificatesToPDF'), [], ICON_SIZE_MEDIUM),
$pluginUrlWithParams.'&export_pdf=1',
[
'id' => 'export_pdf',
'target' => '_blank',
]
);
// Open ZIP export in a new tab so the filter form remains visible.
$resultActions .= Display::url(
Display::return_icon('file_zip.png', get_lang('ExportAllCertificatesToZIP'), [], ICON_SIZE_MEDIUM),
$pluginUrlWithParams.'&export_zip=1',
[
'id' => 'export_zip',
'target' => '_blank',
]
);
}
// Render result actions toolbar right above the table.
echo Display::toolbarAction('result-actions', [$resultActions]);
// Render table with certificates.
echo '<table class="table table-hover table-striped data_table">';
echo '<tbody>';
foreach ($certificateList as $index => $value) {
$categoryId = $value['category_id'];
@@ -370,9 +504,9 @@ if (0 == count($certificateList)) {
echo '</td>';
echo '<td width="50%" class="actions">'.$courseInfo['title'].'</td>';
echo '</tr>';
echo '<tr><td colspan="2">
<table class="table table-hover table-striped data_table">
<tbody>';
echo '<tr><td colspan="2">';
echo '<table class="table table-hover table-striped data_table">';
echo '<tbody>';
$list = GradebookUtils::get_list_gradebook_certificates_by_user_id($value['user_id'], $categoryId);
foreach ($list as $valueCertificate) {
@@ -392,10 +526,10 @@ if (0 == count($certificateList)) {
);
echo $certificateUrl.PHP_EOL;
$url .= '&action=export';
$urlExport = $url.'&action=export';
$pdf = Display::url(
Display::return_icon('pdf.png', get_lang('Download')),
$url,
$urlExport,
['target' => '_blank']
);
echo $pdf.PHP_EOL;
@@ -405,9 +539,10 @@ if (0 == count($certificateList)) {
'&'.api_get_cidreq().
'&action=delete'.
'&cat_id='.$categoryId.
'&certificate_id='.$valueCertificate['id'].'">
'.Display::return_icon('delete.png', get_lang('Delete')).'
</a>'.PHP_EOL;
'&certificate_id='.$valueCertificate['id'].'">'.
Display::return_icon('delete.png', get_lang('Delete')).
'</a>'.PHP_EOL;
echo '</td></tr>';
}
echo '</tbody>';
+17 -2
View File
@@ -83,6 +83,13 @@ $reportTypeValues = [
];
$formValidator->addElement('select', 'report_type', get_lang('ReportType'), $reportTypeValues);
// Export format selector allows XLS or PDF output
$formatValues = [
'xls' => get_lang('ExportExcel'),
'pdf' => get_lang('ExportToPDF'),
];
$formValidator->addElement('select', 'export_format', get_lang('Format'), $formatValues);
// Button to generate the report
$formValidator->addButtonSend(get_lang('GenerateReport'));
@@ -91,6 +98,9 @@ $formValidator->addRule('start_date', get_lang('ThisFieldIsRequired'), 'required
$formValidator->addRule('end_date', get_lang('ThisFieldIsRequired'), 'required');
$formValidator->addRule('users', get_lang('ThisFieldIsRequired'), 'required');
$formValidator->addRule('report_type', get_lang('ThisFieldIsRequired'), 'required');
$formValidator->addRule('export_format', get_lang('ThisFieldIsRequired'), 'required');
$formValidator->setDefaults(['export_format' => 'xls']);
if ($formValidator->validate()) {
$values = $formValidator->exportValues();
@@ -98,7 +108,7 @@ if ($formValidator->validate()) {
$startDate = $values['start_date'];
$endDate = $values['end_date'];
$reportType = $values['report_type'];
$exportXls = isset($_POST['export']);
$format = $values['export_format'];
if (empty($users)) {
Display::addFlash(Display::return_message(get_lang('NoUsersSelected'), 'warning'));
@@ -111,7 +121,12 @@ if ($formValidator->validate()) {
$rows = $data['rows'];
array_unshift($rows, $headers);
$fileName = get_lang('Export').'-'.$reportTypeValues[$reportType].'_'.api_get_local_time();
Export::arrayToCsv($rows, $fileName);
if ($format === 'pdf') {
$html = Export::convert_array_to_html($rows);
Export::export_html_to_pdf($html, ['filename' => $fileName]);
} else {
Export::arrayToCsv($rows, $fileName);
}
}
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
/* For licensing terms, see /license.txt */
require_once __DIR__.'/../inc/global.inc.php';
api_block_anonymous_users(true);
if (api_is_student()) {
api_not_allowed(true);
}
$studentId = isset($_GET['student']) ? (int) $_GET['student'] : 0;
if (empty($studentId)) {
api_not_allowed(true);
}
$start = isset($_GET['start']) ? $_GET['start'] : '';
$end = isset($_GET['end']) ? $_GET['end'] : '';
if (empty($start) || empty($end)) {
$aLastWeek = get_last_week();
$start = date('Y-m-d', $aLastWeek[0]);
$end = date('Y-m-d', $aLastWeek[6]);
}
$report = Tracking::generateReport('time_report', [$studentId], $start, $end);
$rows = [];
if (!empty($report)) {
$rows = $report['rows'];
array_unshift($rows, $report['headers']);
}
$export = isset($_GET['export']) ? $_GET['export'] : '';
if ($export === 'xls') {
Export::arrayToXls($rows, 'time_report');
exit;
} elseif ($export === 'pdf') {
$params = ['filename' => 'time_report'];
Export::export_table_pdf($rows, $params);
exit;
}
$table = '';
if (!empty($rows)) {
$headers = array_shift($rows);
$colors = ['#cce5ff', '#ffe5b4'];
$colorIndex = 0;
// Group entries by starting date (column index 4)
$grouped = [];
foreach ($rows as $row) {
$dateKey = substr($row[4], 0, 10);
$grouped[$dateKey][] = $row;
}
$table = '<table class="data_table" style="border-collapse:collapse;border:1px solid #ccc;">';
$table .= '<tr>';
foreach ($headers as $header) {
$table .= '<th style="border:1px solid #ccc;padding-left:5px;">'.htmlspecialchars($header).'</th>';
}
$table .= '</tr>';
foreach ($grouped as $day => $dayRows) {
$totalSeconds = 0;
foreach ($dayRows as $row) {
$table .= '<tr style="background-color:'.$colors[$colorIndex].'">';
foreach ($row as $cell) {
$table .= '<td style="border:1px solid #ccc;padding-left:5px;">'.htmlspecialchars($cell).'</td>';
}
$table .= '</tr>';
$parts = explode(':', $row[6]);
if (count($parts) === 3) {
$totalSeconds += ($parts[0] * 3600) + ($parts[1] * 60) + $parts[2];
}
}
$table .= '<tr style="background-color:'.$colors[$colorIndex].';font-weight:bold;">'
.'<td colspan="'.(count($headers) - 1).'" style="text-align:right;border:1px solid #ccc;padding-left:5px;">'.get_lang('Total').'</td>'
.'<td style="border:1px solid #ccc;padding-left:5px;">'.gmdate('H:i:s', $totalSeconds).'</td>'
.'</tr>';
$colorIndex = 1 - $colorIndex;
}
$table .= '</table>';
}
$nameTools = get_lang('TimeReport');
Display::display_header($nameTools);
$baseUrl = api_get_self().'?student='.$studentId.'&start='.$start.'&end='.$end;
echo '<div>'
.'<a href="'.$baseUrl.'&export=pdf">'
.Display::return_icon('pdf.png', get_lang('ExportPDF'), [], ICON_SIZE_MEDIUM)
.'</a> '
.'<a href="'.$baseUrl.'&export=xls">'
.Display::return_icon('export_excel.png', get_lang('ExportAsXLS'), [], ICON_SIZE_MEDIUM)
.'</a></div>';
echo $table;
Display::display_footer();