diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index 20f29d5eca..716f097bb7 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -1,4 +1,4 @@
-name: PHP Composer
+name: Behat tests 1.11.x 🐞
on: [push, pull_request]
diff --git a/documentation/dependencies.html b/documentation/dependencies.html
index 073c853468..1184057f01 100755
--- a/documentation/dependencies.html
+++ b/documentation/dependencies.html
@@ -71,7 +71,7 @@ We recommend using HTML5-compatible technology.
Dependencies - server-side
- Apache 2+
- - PHP 5.6 (not recommended) with MySQL bindings or PHP 7.2 (recommended) or 7.3. PHP 7.0 is not supported by this version of Chamilo. PHP 7.1 should be supported but we lack elements of infrastructure to check it fully and thoroughly.
+ - PHP 7.2, 7.3 or 7.4 (recommended). PHP 5.6 and 7.0 are not supported by this version of Chamilo. PHP 7.1 should be supported but we lack elements of infrastructure to check it fully and thoroughly.
- MySQL 5.6+ or any version of MariaDB database server
- php-curl
- php-dom (usually available by default)
@@ -131,8 +131,7 @@ ensure all components are installed by an expert in the field.
- Audio recording (generate mp3 from wav)
- - libav-tools
- - libavcodec-extra-53
+ - libav-tools libavcodec-extra-53 *or* ffmpeg on more recent distributions
- libmp3lame0
diff --git a/main/admin/extra_field_workflow.php b/main/admin/extra_field_workflow.php
index 74fd4ecfb7..f4da6b8a3d 100755
--- a/main/admin/extra_field_workflow.php
+++ b/main/admin/extra_field_workflow.php
@@ -17,7 +17,7 @@ $interbreadcrumb[] = ['url' => 'index.php', 'name' => get_lang('PlatformAdmin')]
$tool_name = null;
$action = isset($_GET['action']) ? $_GET['action'] : null;
-$field_id = isset($_GET['field_id']) ? $_GET['field_id'] : null;
+$field_id = isset($_GET['field_id']) ? (int) $_GET['field_id'] : null;
if (empty($field_id)) {
api_not_allowed();
@@ -73,7 +73,7 @@ if ($action == 'add') {
];
}
-$roleId = isset($_REQUEST['roleId']) ? $_REQUEST['roleId'] : null;
+$roleId = isset($_REQUEST['roleId']) ? (int) $_REQUEST['roleId'] : null;
//jqgrid will use this URL to do the selects
$params = 'field_id='.$field_id.'&type='.$extraField->type.'&roleId='.$roleId;
diff --git a/main/exercise/pending.php b/main/exercise/pending.php
new file mode 100644
index 0000000000..e97150d025
--- /dev/null
+++ b/main/exercise/pending.php
@@ -0,0 +1,513 @@
+ $exercise['id'], 'text' => html_entity_decode($exercise['title'])];
+ }
+ }
+
+ echo json_encode($data);
+ exit;
+ break;
+}
+
+$userId = api_get_user_id();
+$origin = api_get_origin();
+
+$TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
+$TBL_TRACK_ATTEMPT = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
+$TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
+$TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
+$allowCoachFeedbackExercises = api_get_setting('allow_coach_feedback_exercises') === 'true';
+$documentPath = null;
+
+if (!empty($_GET['path'])) {
+ $parameters['path'] = Security::remove_XSS($_GET['path']);
+}
+
+if (!empty($_REQUEST['export_report']) && $_REQUEST['export_report'] == '1') {
+ if (api_is_platform_admin() || api_is_course_admin() ||
+ api_is_course_tutor() || api_is_session_general_coach()
+ ) {
+ $loadExtraData = false;
+ if (isset($_REQUEST['extra_data']) && $_REQUEST['extra_data'] == 1) {
+ $loadExtraData = true;
+ }
+
+ $includeAllUsers = false;
+ if (isset($_REQUEST['include_all_users']) &&
+ $_REQUEST['include_all_users'] == 1
+ ) {
+ $includeAllUsers = true;
+ }
+
+ $onlyBestAttempts = false;
+ if (isset($_REQUEST['only_best_attempts']) &&
+ $_REQUEST['only_best_attempts'] == 1
+ ) {
+ $onlyBestAttempts = true;
+ }
+
+ require_once 'exercise_result.class.php';
+ $export = new ExerciseResult();
+ $export->setIncludeAllUsers($includeAllUsers);
+ $export->setOnlyBestAttempts($onlyBestAttempts);
+
+ switch ($_GET['export_format']) {
+ case 'xls':
+ $export->exportCompleteReportXLS(
+ $documentPath,
+ null,
+ $loadExtraData,
+ null,
+ $exerciseId
+ );
+ exit;
+ break;
+ case 'csv':
+ default:
+ $export->exportCompleteReportCSV(
+ $documentPath,
+ null,
+ $loadExtraData,
+ null,
+ $exerciseId
+ );
+ exit;
+ break;
+ }
+ } else {
+ api_not_allowed(true);
+ }
+}
+
+Display::display_header(get_lang('PendingAttempts'));
+$token = Security::get_token();
+$extra = '';
+
+$extra .= '';
+$form = new FormValidator(
+ 'report',
+ 'post',
+ null,
+ null,
+ ['class' => 'form-vertical']
+);
+$form->addElement(
+ 'radio',
+ 'export_format',
+ null,
+ get_lang('ExportAsCSV'),
+ 'csv',
+ ['id' => 'export_format_csv_label']
+);
+$form->addElement(
+ 'radio',
+ 'export_format',
+ null,
+ get_lang('ExportAsXLS'),
+ 'xls',
+ ['id' => 'export_format_xls_label']
+);
+$form->addElement(
+ 'checkbox',
+ 'load_extra_data',
+ null,
+ get_lang('LoadExtraData'),
+ '0',
+ ['id' => 'export_format_xls_label']
+);
+$form->addElement(
+ 'checkbox',
+ 'include_all_users',
+ null,
+ get_lang('IncludeAllUsers'),
+ '0'
+);
+$form->addElement(
+ 'checkbox',
+ 'only_best_attempts',
+ null,
+ get_lang('OnlyBestAttempts'),
+ '0'
+);
+$form->setDefaults(['export_format' => 'csv']);
+$extra .= $form->returnForm();
+$extra .= '
';
+
+echo $extra;
+
+$courses = CourseManager::get_courses_list_by_user_id($userId, false, false, false);
+
+$form = new FormValidator('pending', 'GET');
+$courses = array_column($courses, 'title', 'real_id');
+$form->addSelect('course_id', get_lang('Course'), $courses, ['placeholder' => get_lang('All'), 'id' => 'course_id']);
+
+$form->addSelect(
+ 'exercise_id',
+ get_lang('Exercise'),
+ [],
+ [
+ 'placeholder' => get_lang('All'),
+ 'id' => 'exercise_id',
+ ]
+);
+
+$status = [
+ 1 => get_lang('All'),
+ 2 => get_lang('Validated'),
+ 3 => get_lang('NotValidated'),
+];
+
+$form->addSelect('status', get_lang('Status'), $status);
+$form->addButtonSearch(get_lang('Search'));
+$content = $form->returnForm();
+
+echo $content;
+
+if (empty($statusId)) {
+ Display::display_footer();
+ exit;
+}
+
+$url = api_get_path(WEB_AJAX_PATH).
+ 'model.ajax.php?a=get_exercise_pending_results&filter_by_user='.$filter_user.
+ '&course_id='.$courseId.'&exercise_id='.$exerciseId.'&status='.$statusId;
+$action_links = '';
+
+$officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
+
+// The order is important you need to check the the $column variable in the model.ajax.php file
+$columns = [
+ get_lang('Course'),
+ get_lang('Exercise'),
+ get_lang('FirstName'),
+ get_lang('LastName'),
+ get_lang('LoginName'),
+ get_lang('Duration').' ('.get_lang('MinMinute').')',
+ get_lang('StartDate'),
+ get_lang('EndDate'),
+ get_lang('Score'),
+ get_lang('IP'),
+ get_lang('Status'),
+ //get_lang('ToolLearnpath'),
+ get_lang('Actions'),
+];
+
+if ($officialCodeInList === 'true') {
+ $columns = array_merge([get_lang('OfficialCode')], $columns);
+}
+
+// Column config
+$column_model = [
+ ['name' => 'course', 'index' => 'course', 'width' => '50', 'align' => 'left', 'search' => 'false', 'sortable' => 'false'],
+ ['name' => 'exercise', 'index' => 'exercise', 'width' => '50', 'align' => 'left', 'search' => 'false', 'sortable' => 'false'],
+ ['name' => 'firstname', 'index' => 'firstname', 'width' => '50', 'align' => 'left', 'search' => 'true'],
+ [
+ 'name' => 'lastname',
+ 'index' => 'lastname',
+ 'width' => '50',
+ 'align' => 'left',
+ 'formatter' => 'action_formatter',
+ 'search' => 'true',
+ ],
+ [
+ 'name' => 'login',
+ 'index' => 'username',
+ 'width' => '40',
+ 'align' => 'left',
+ 'search' => 'true',
+ 'hidden' => api_get_configuration_value('exercise_attempts_report_show_username') ? 'false' : 'true',
+ ],
+ ['name' => 'duration', 'index' => 'exe_duration', 'width' => '30', 'align' => 'left', 'search' => 'true'],
+ ['name' => 'start_date', 'index' => 'start_date', 'width' => '60', 'align' => 'left', 'search' => 'true'],
+ ['name' => 'exe_date', 'index' => 'exe_date', 'width' => '60', 'align' => 'left', 'search' => 'true'],
+ ['name' => 'score', 'index' => 'exe_result', 'width' => '50', 'align' => 'center', 'search' => 'true'],
+ ['name' => 'ip', 'index' => 'user_ip', 'width' => '40', 'align' => 'center', 'search' => 'true'],
+ [
+ 'name' => 'status',
+ 'index' => 'revised',
+ 'width' => '40',
+ 'align' => 'left',
+ 'search' => 'false',
+ 'sortable' => 'false',
+ //'stype' => 'select',
+ //for the bottom bar
+ /*'searchoptions' => [
+ 'defaultValue' => '',
+ 'value' => ':'.get_lang('All').';1:'.get_lang('Validated').';0:'.get_lang('NotValidated'),
+ ],*/
+ //for the top bar
+ /*'editoptions' => [
+ 'value' => ':'.get_lang('All').';1:'.get_lang('Validated').';0:'.get_lang('NotValidated'),
+ ],*/
+ ],
+ //['name' => 'lp', 'index' => 'orig_lp_id', 'width' => '60', 'align' => 'left', 'search' => 'false'],
+ [
+ 'name' => 'actions',
+ 'index' => 'actions',
+ 'width' => '60',
+ 'align' => 'left',
+ 'search' => 'false',
+ 'sortable' => 'false',
+ ],
+];
+
+if ('true' === $officialCodeInList) {
+ $officialCodeRow = [
+ 'name' => 'official_code',
+ 'index' => 'official_code',
+ 'width' => '50',
+ 'align' => 'left',
+ 'search' => 'true',
+ ];
+ $column_model = array_merge([$officialCodeRow], $column_model);
+}
+
+$action_links = '
+// add username as title in lastname filed - ref 4226
+function action_formatter(cellvalue, options, rowObject) {
+ // rowObject is firstname,lastname,login,... get the third word
+ var loginx = "'.api_htmlentities(sprintf(get_lang('LoginX'), ':::'), ENT_QUOTES).'";
+ var tabLoginx = loginx.split(/:::/);
+ // tabLoginx[0] is before and tabLoginx[1] is after :::
+ // may be empty string but is defined
+ return ""+cellvalue+"";
+}';
+
+$extra_params['autowidth'] = 'true';
+$extra_params['height'] = 'auto';
+$gridJs = Display::grid_js(
+ 'results',
+ $url,
+ $columns,
+ $column_model,
+ $extra_params,
+ [],
+ $action_links,
+ true
+);
+
+?>
+
+
+';
$pageBottom .= Display::url(
get_lang('BackToAttemptList'),
- api_get_path(WEB_CODE_PATH).'exercise/overview.php?exerciseId='.$exercise_id.'&'.api_get_cidreq(),
+ api_get_path(WEB_CODE_PATH).'exercise/overview.php?exerciseId='.$exercise_id.'&'.api_get_cidreq().
+ "&learnpath_id=$lpId&learnpath_item_id=$lpItemId&learnpath_item_view_id=$lpViewId",
['class' => 'btn btn-primary']
);
$pageBottom .= '';
diff --git a/main/inc/ajax/model.ajax.php b/main/inc/ajax/model.ajax.php
index 0753ded465..6b361e2541 100755
--- a/main/inc/ajax/model.ajax.php
+++ b/main/inc/ajax/model.ajax.php
@@ -44,6 +44,7 @@ if (!in_array(
$action,
[
'get_exercise_results',
+ 'get_exercise_pending_results',
'get_exercise_results_report',
'get_work_student_list_overview',
'get_hotpotatoes_exercise_results',
@@ -630,6 +631,52 @@ switch ($action) {
null,
true
);
+ break;
+ case 'get_exercise_pending_results':
+ if (false === api_is_teacher()) {
+ exit;
+ }
+
+ $courseId = $_REQUEST['course_id'] ?? 0;
+ $exerciseId = $_REQUEST['exercise_id'] ?? 0;
+ $status = $_REQUEST['status'] ?? 0;
+ if (isset($_GET['filter_by_user']) && !empty($_GET['filter_by_user'])) {
+ $filter_user = (int) $_GET['filter_by_user'];
+ if (empty($whereCondition)) {
+ $whereCondition .= " te.exe_user_id = '$filter_user'";
+ } else {
+ $whereCondition .= " AND te.exe_user_id = '$filter_user'";
+ }
+ }
+
+ if (isset($_GET['group_id_in_toolbar']) && !empty($_GET['group_id_in_toolbar'])) {
+ $groupIdFromToolbar = (int) $_GET['group_id_in_toolbar'];
+ if (!empty($groupIdFromToolbar)) {
+ if (empty($whereCondition)) {
+ $whereCondition .= " te.group_id = '$groupIdFromToolbar'";
+ } else {
+ $whereCondition .= " AND group_id = '$groupIdFromToolbar'";
+ }
+ }
+ }
+
+ if (!empty($whereCondition)) {
+ $whereCondition = " AND $whereCondition";
+ }
+
+ if (!empty($courseId)) {
+ $whereCondition .= " AND te.c_id = $courseId";
+ }
+
+ $count = ExerciseLib::get_count_exam_results(
+ $exerciseId,
+ $whereCondition,
+ '',
+ false,
+ true,
+ $status
+ );
+
break;
case 'get_exercise_results':
$exercise_id = $_REQUEST['exerciseId'];
@@ -1491,6 +1538,46 @@ switch ($action) {
$whereCondition
);
}
+ break;
+ case 'get_exercise_pending_results':
+ $columns = [
+ 'course',
+ 'exercise',
+ 'firstname',
+ 'lastname',
+ 'username',
+ 'exe_duration',
+ 'start_date',
+ 'exe_date',
+ 'score',
+ 'user_ip',
+ 'status',
+ 'actions',
+ ];
+ $officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
+ if ($officialCodeInList === 'true') {
+ $columns = array_merge(['official_code'], $columns);
+ }
+
+ $result = ExerciseLib::get_exam_results_data(
+ $start,
+ $limit,
+ $sidx,
+ $sord,
+ $exerciseId,
+ $whereCondition,
+ false,
+ null,
+ false,
+ false,
+ [],
+ false,
+ false,
+ false,
+ true,
+ $status
+ );
+
break;
case 'get_exercise_results':
$is_allowedToEdit = api_is_allowed_to_edit(null, true) ||
@@ -2510,6 +2597,7 @@ $allowed_actions = [
'get_session_progress',
'get_exercise_progress',
'get_exercise_results',
+ 'get_exercise_pending_results',
'get_exercise_results_report',
'get_work_student_list_overview',
'get_hotpotatoes_exercise_results',
diff --git a/main/inc/lib/exercise.lib.php b/main/inc/lib/exercise.lib.php
index 8b88535ebf..359e116a70 100644
--- a/main/inc/lib/exercise.lib.php
+++ b/main/inc/lib/exercise.lib.php
@@ -981,7 +981,7 @@ class ExerciseLib
}
}
- list($answer) = explode('@@', $answer);
+ [$answer] = explode('@@', $answer);
// $correctAnswerList array of array with correct anwsers 0=> [0=>[\p] 1=>[plop]]
api_preg_match_all(
'/\[[^]]+\]/',
@@ -1909,11 +1909,19 @@ HOTSPOT;
* @param array $conditions
* @param string $courseCode
* @param bool $showSession
+ * @param bool $searchAllTeacherCourses
+ * @param int $status
*
* @return array
*/
- public static function get_count_exam_results($exerciseId, $conditions, $courseCode = '', $showSession = false)
- {
+ public static function get_count_exam_results(
+ $exerciseId,
+ $conditions,
+ $courseCode = '',
+ $showSession = false,
+ $searchAllTeacherCourses = false,
+ $status = 0
+ ) {
return self::get_exam_results_data(
null,
null,
@@ -1923,7 +1931,14 @@ HOTSPOT;
$conditions,
true,
$courseCode,
- $showSession
+ $showSession,
+ false,
+ [],
+ false,
+ false,
+ false,
+ $searchAllTeacherCourses,
+ $status
);
}
@@ -2087,7 +2102,7 @@ HOTSPOT;
* @param array $userExtraFieldsToAdd
* @param bool $useCommaAsDecimalPoint
* @param bool $roundValues
- * @param bool $getOnyIds
+ * @param bool $getOnlyIds
*
* @return array
*/
@@ -2105,21 +2120,21 @@ HOTSPOT;
$userExtraFieldsToAdd = [],
$useCommaAsDecimalPoint = false,
$roundValues = false,
- $getOnyIds = false
+ $getOnlyIds = false,
+ $searchAllTeacherCourses = false,
+ $status = 0
) {
//@todo replace all this globals
global $filter;
$courseCode = empty($courseCode) ? api_get_course_id() : $courseCode;
$courseInfo = api_get_course_info($courseCode);
-
- if (empty($courseInfo)) {
- return [];
- }
-
- $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
- $course_id = $courseInfo['real_id'];
+ $documentPath = '';
$sessionId = api_get_session_id();
- $exercise_id = (int) $exercise_id;
+ $courseId = 0;
+ if (!empty($courseInfo)) {
+ $courseId = $courseInfo['real_id'];
+ $documentPath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
+ }
$is_allowedToEdit =
api_is_allowed_to_edit(null, true) ||
@@ -2127,6 +2142,41 @@ HOTSPOT;
api_is_drh() ||
api_is_student_boss() ||
api_is_session_admin();
+
+ $courseCondition = "c_id = $courseId";
+ $statusCondition = '';
+
+ if (!empty($status)) {
+ switch ($status) {
+ case 2:
+ // validated
+ $statusCondition = ' AND revised = 1 ';
+ break;
+ case 3:
+ // not validated
+ $statusCondition = ' AND revised = 0 ';
+ break;
+ }
+ }
+
+ if (false === $searchAllTeacherCourses) {
+ if (empty($courseInfo)) {
+ return [];
+ }
+ } else {
+ $courses = CourseManager::get_courses_list_by_user_id(api_get_user_id(), false, false, false);
+
+ if (empty($courses)) {
+ return [];
+ }
+
+ $courses = array_column($courses, 'real_id');
+ $is_allowedToEdit = true;
+ $courseCondition = "c_id IN ('".implode("', '", $courses)."') ";
+ }
+
+ $exercise_id = (int) $exercise_id;
+
$TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
$TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
$TBL_GROUP_REL_USER = Database::get_course_table(TABLE_GROUP_USER);
@@ -2142,6 +2192,11 @@ HOTSPOT;
$sessionCondition = " AND ttte.session_id = $sessionId";
}
+ if ($searchAllTeacherCourses) {
+ $session_id_and = " AND te.session_id = 0 ";
+ $sessionCondition = " AND ttte.session_id = 0";
+ }
+
if (empty($sessionId) &&
api_get_configuration_value('show_exercise_session_attempts_in_base_course')
) {
@@ -2150,8 +2205,10 @@ HOTSPOT;
}
$exercise_where = '';
+ $exerciseFilter = '';
if (!empty($exercise_id)) {
$exercise_where .= ' AND te.exe_exo_id = '.$exercise_id.' ';
+ $exerciseFilter = " AND exe_exo_id = $exercise_id ";
}
$hotpotatoe_where = '';
@@ -2168,8 +2225,8 @@ HOTSPOT;
LEFT JOIN $TBL_TRACK_ATTEMPT_RECORDING tr
ON (ttte.exe_id = tr.exe_id)
WHERE
- c_id = $course_id AND
- exe_exo_id = $exercise_id
+ $courseCondition
+ $exerciseFilter
$sessionCondition
)";
@@ -2191,9 +2248,9 @@ HOTSPOT;
g.id as group_id
FROM $TBL_USER u
INNER JOIN $TBL_GROUP_REL_USER gru
- ON (gru.user_id = u.user_id AND gru.c_id= $course_id )
+ ON (gru.user_id = u.user_id AND gru.c_id= $courseId )
INNER JOIN $TBL_GROUP g
- ON (gru.group_id = g.id AND g.c_id= $course_id )
+ ON (gru.group_id = g.id AND g.c_id= $courseId )
)";
}
@@ -2254,9 +2311,9 @@ HOTSPOT;
g.id as group_id
FROM $TBL_USER u
LEFT OUTER JOIN $TBL_GROUP_REL_USER gru
- ON ( gru.user_id = u.user_id AND gru.c_id= $course_id )
+ ON ( gru.user_id = u.user_id AND gru.c_id = $courseId )
LEFT OUTER JOIN $TBL_GROUP g
- ON (gru.group_id = g.id AND g.c_id = $course_id )
+ ON (gru.group_id = g.id AND g.c_id = $courseId )
)";
}
@@ -2272,8 +2329,13 @@ HOTSPOT;
)";
}
- $sqlFromOption = " , $TBL_GROUP_REL_USER AS gru ";
- $sqlWhereOption = " AND gru.c_id = $course_id AND gru.user_id = user.user_id ";
+ $sqlFromOption = '';
+ $sqlWhereOption = '';
+ if (false === $searchAllTeacherCourses) {
+ $sqlFromOption = " , $TBL_GROUP_REL_USER AS gru ";
+ $sqlWhereOption = " AND gru.c_id = $courseId AND gru.user_id = user.user_id ";
+ }
+
$first_and_last_name = api_is_western_name_order() ? "firstname, lastname" : "lastname, firstname";
if ($get_count) {
@@ -2289,6 +2351,7 @@ HOTSPOT;
te.exe_weighting,
te.exe_date,
te.exe_id,
+ te.c_id,
te.session_id,
email as exemail,
te.start_date,
@@ -2312,11 +2375,13 @@ HOTSPOT;
INNER JOIN $sql_inner_join_tbl_user AS user
ON (user.user_id = exe_user_id)
WHERE
- te.c_id = $course_id $session_id_and AND
+ te.$courseCondition
+ $session_id_and AND
ce.active <> -1 AND
- ce.c_id = $course_id
+ ce.$courseCondition
$exercise_where
$extra_where_conditions
+ $statusCondition
";
// sql for hotpotatoes tests for teacher / tutor view
@@ -2339,11 +2404,11 @@ HOTSPOT;
$TBL_USER user
$sqlFromOption
WHERE
- user.user_id=tth.exe_user_id
- AND tth.c_id = $course_id
+ user.user_id=tth.exe_user_id AND
+ tth.$courseCondition
$hotpotatoe_where
- $sqlWhereOption
- AND user.status NOT IN (".api_get_users_status_ignored_in_reports('string').")
+ $sqlWhereOption AND
+ user.status NOT IN (".api_get_users_status_ignored_in_reports('string').")
ORDER BY tth.c_id ASC, tth.exe_date DESC ";
}
@@ -2358,11 +2423,13 @@ HOTSPOT;
return $rowx[0];
}
- $teacher_list = CourseManager::get_teacher_list_from_course_code($courseCode);
$teacher_id_list = [];
- if (!empty($teacher_list)) {
- foreach ($teacher_list as $teacher) {
- $teacher_id_list[] = $teacher['user_id'];
+ if (!empty($courseCode)) {
+ $teacher_list = CourseManager::get_teacher_list_from_course_code($courseCode);
+ if (!empty($teacher_list)) {
+ foreach ($teacher_list as $teacher) {
+ $teacher_id_list[] = $teacher['user_id'];
+ }
}
}
@@ -2386,27 +2453,32 @@ HOTSPOT;
$sql .= " ORDER BY $column $direction ";
}
- if (!$getOnyIds) {
+ if (!$getOnlyIds) {
$sql .= " LIMIT $from, $number_of_items";
}
$results = [];
+ error_log($sql);
$resx = Database::query($sql);
while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
$results[] = $rowx;
}
- $group_list = GroupManager::get_group_list(null, $courseInfo);
$clean_group_list = [];
- if (!empty($group_list)) {
- foreach ($group_list as $group) {
- $clean_group_list[$group['id']] = $group['name'];
+ $lp_list = [];
+
+ if (!empty($courseInfo)) {
+ $group_list = GroupManager::get_group_list(null, $courseInfo);
+ if (!empty($group_list)) {
+ foreach ($group_list as $group) {
+ $clean_group_list[$group['id']] = $group['name'];
+ }
}
- }
- $lp_list_obj = new LearnpathList(api_get_user_id());
- $lp_list = $lp_list_obj->get_flat_list();
- $oldIds = array_column($lp_list, 'lp_old_id', 'iid');
+ $lp_list_obj = new LearnpathList(api_get_user_id());
+ $lp_list = $lp_list_obj->get_flat_list();
+ $oldIds = array_column($lp_list, 'lp_old_id', 'iid');
+ }
if (is_array($results)) {
$users_array_id = [];
@@ -2417,18 +2489,31 @@ HOTSPOT;
$sizeof = count($results);
$locked = api_resource_is_locked_by_gradebook($exercise_id, LINK_EXERCISE);
$timeNow = strtotime(api_get_utc_datetime());
+ $courseItemList = [];
// Looping results
for ($i = 0; $i < $sizeof; $i++) {
- $revised = $results[$i]['revised'];
- $attemptSessionId = (int) $results[$i]['session_id'];
- $cidReq = api_get_cidreq(false).'&id_session='.$attemptSessionId;
+ $attempt = $results[$i];
+ $revised = $attempt['revised'];
+ $attemptSessionId = (int) $attempt['session_id'];
+ if (false === $searchAllTeacherCourses) {
+ $courseItemInfo = api_get_course_info();
+ $cidReq = api_get_cidreq(false).'&id_session='.$attemptSessionId;
+ } else {
+ if (isset($courseItemList[$attempt['c_id']])) {
+ $courseItemInfo = $courseItemList[$attempt['c_id']];
+ } else {
+ $courseItemInfo = api_get_course_info_by_id($attempt['c_id']);
+ $courseItemList[$attempt['c_id']] = $courseItemInfo;
+ }
+ $cidReq = 'cidReq='.$courseItemInfo['code'].'&id_session='.$attemptSessionId;
+ }
- if ('incomplete' === $results[$i]['completion_status']) {
+ if ('incomplete' === $attempt['completion_status']) {
// If the exercise was incomplete, we need to determine
// if it is still into the time allowed, or if its
// allowed time has expired and it can be closed
// (it's "unclosed")
- $minutes = $results[$i]['expired_time'];
+ $minutes = $attempt['expired_time'];
if ($minutes == 0) {
// There's no time limit, so obviously the attempt
// can still be "ongoing", but the teacher should
@@ -2437,32 +2522,31 @@ HOTSPOT;
$revised = 2;
} else {
$allowedSeconds = $minutes * 60;
- $timeAttemptStarted = strtotime($results[$i]['start_date']);
+ $timeAttemptStarted = strtotime($attempt['start_date']);
$secondsSinceStart = $timeNow - $timeAttemptStarted;
+ $revised = 3; // mark as "ongoing"
if ($secondsSinceStart > $allowedSeconds) {
$revised = 2; // mark as "unclosed"
- } else {
- $revised = 3; // mark as "ongoing"
}
}
}
if ($from_gradebook && ($is_allowedToEdit)) {
if (in_array(
- $results[$i]['username'].$results[$i]['firstname'].$results[$i]['lastname'],
+ $attempt['username'].$attempt['firstname'].$attempt['lastname'],
$users_array_id
)) {
continue;
}
- $users_array_id[] = $results[$i]['username'].$results[$i]['firstname'].$results[$i]['lastname'];
+ $users_array_id[] = $attempt['username'].$attempt['firstname'].$attempt['lastname'];
}
- $lp_obj = isset($results[$i]['orig_lp_id']) &&
- isset($lp_list[$results[$i]['orig_lp_id']]) ? $lp_list[$results[$i]['orig_lp_id']] : null;
+ $lp_obj = isset($attempt['orig_lp_id']) &&
+ isset($lp_list[$attempt['orig_lp_id']]) ? $lp_list[$attempt['orig_lp_id']] : null;
if (empty($lp_obj)) {
// Try to get the old id (id instead of iid)
- $lpNewId = isset($results[$i]['orig_lp_id']) &&
- isset($oldIds[$results[$i]['orig_lp_id']]) ? $oldIds[$results[$i]['orig_lp_id']] : null;
+ $lpNewId = isset($attempt['orig_lp_id']) &&
+ isset($oldIds[$attempt['orig_lp_id']]) ? $oldIds[$attempt['orig_lp_id']] : null;
if ($lpNewId) {
$lp_obj = isset($lp_list[$lpNewId]) ? $lp_list[$lpNewId] : null;
}
@@ -2470,7 +2554,7 @@ HOTSPOT;
$lp_name = null;
if ($lp_obj) {
$url = api_get_path(WEB_CODE_PATH).
- 'lp/lp_controller.php?'.$cidReq.'&action=view&lp_id='.$results[$i]['orig_lp_id'];
+ 'lp/lp_controller.php?'.$cidReq.'&action=view&lp_id='.$attempt['orig_lp_id'];
$lp_name = Display::url(
$lp_obj['lp_name'],
$url,
@@ -2483,7 +2567,7 @@ HOTSPOT;
if ($is_empty_sql_inner_join_tbl_user) {
$group_list = GroupManager::get_group_ids(
api_get_course_int_id(),
- $results[$i]['user_id']
+ $attempt['user_id']
);
foreach ($group_list as $id) {
@@ -2491,25 +2575,25 @@ HOTSPOT;
$group_name_list .= $clean_group_list[$id].'
';
}
}
- $results[$i]['group_name'] = $group_name_list;
+ $attempt['group_name'] = $group_name_list;
}
- $results[$i]['exe_duration'] = !empty($results[$i]['exe_duration']) ? round($results[$i]['exe_duration'] / 60) : 0;
- $id = $results[$i]['exe_id'];
- $dt = api_convert_and_format_date($results[$i]['exe_weighting']);
+ $attempt['exe_duration'] = !empty($attempt['exe_duration']) ? round($attempt['exe_duration'] / 60) : 0;
+ $id = $attempt['exe_id'];
+ $dt = api_convert_and_format_date($attempt['exe_weighting']);
// we filter the results if we have the permission to
$result_disabled = 0;
- if (isset($results[$i]['results_disabled'])) {
- $result_disabled = (int) $results[$i]['results_disabled'];
+ if (isset($attempt['results_disabled'])) {
+ $result_disabled = (int) $attempt['results_disabled'];
}
if ($result_disabled == 0) {
- $my_res = $results[$i]['exe_result'];
- $my_total = $results[$i]['exe_weighting'];
- $results[$i]['start_date'] = api_get_local_time($results[$i]['start_date']);
- $results[$i]['exe_date'] = api_get_local_time($results[$i]['exe_date']);
+ $my_res = $attempt['exe_result'];
+ $my_total = $attempt['exe_weighting'];
+ $attempt['start_date'] = api_get_local_time($attempt['start_date']);
+ $attempt['exe_date'] = api_get_local_time($attempt['exe_date']);
- if (!$results[$i]['propagate_neg'] && $my_res < 0) {
+ if (!$attempt['propagate_neg'] && $my_res < 0) {
$my_res = 0;
}
@@ -2529,7 +2613,7 @@ HOTSPOT;
if ($is_allowedToEdit) {
if (isset($teacher_id_list)) {
if (in_array(
- $results[$i]['exe_user_id'],
+ $attempt['exe_user_id'],
$teacher_id_list
)) {
$actions .= Display::return_icon('teacher.png', get_lang('Teacher'));
@@ -2607,7 +2691,7 @@ HOTSPOT;
// Admin can always delete the attempt
if (($locked == false || api_is_platform_admin()) && !api_is_student_boss()) {
$ip = Tracking::get_ip_from_user_event(
- $results[$i]['exe_user_id'],
+ $attempt['exe_user_id'],
api_get_utc_datetime(),
false
);
@@ -2620,14 +2704,14 @@ HOTSPOT;
http_build_query([
'id' => $id,
'exercise' => $exercise_id,
- 'user' => $results[$i]['exe_user_id'],
+ 'user' => $attempt['exe_user_id'],
]);
$actions .= Display::url(
Display::return_icon('reload.png', get_lang('RecalculateResults')),
$recalculateUrl,
[
'data-exercise' => $exercise_id,
- 'data-user' => $results[$i]['exe_user_id'],
+ 'data-user' => $attempt['exe_user_id'],
'data-id' => $id,
'class' => 'exercise-recalculate',
]
@@ -2638,7 +2722,7 @@ HOTSPOT;
href="exercise_report.php?'.$cidReq.'&filter_by_user='.$filterByUser.'&filter='.$filter.'&exerciseId='.$exercise_id.'&delete=delete&did='.$id.'"
onclick="javascript:if(!confirm(\''.sprintf(
addslashes(get_lang('DeleteAttempt')),
- $results[$i]['username'],
+ $attempt['username'],
$dt
).'\')) return false;">';
$delete_link .= Display::return_icon(
@@ -2658,7 +2742,7 @@ HOTSPOT;
$actions .= $delete_link;
}
} else {
- $attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'.$cidReq.'&id='.$results[$i]['exe_id'];
+ $attempt_url = api_get_path(WEB_CODE_PATH).'exercise/result.php?'.$cidReq.'&id='.$attempt['exe_id'];
$attempt_link = Display::url(
get_lang('Show'),
$attempt_url,
@@ -2675,17 +2759,17 @@ HOTSPOT;
foreach ($userExtraFieldsToAdd as $variable) {
$extraFieldValue = new ExtraFieldValue('user');
$values = $extraFieldValue->get_values_by_handler_and_field_variable(
- $results[$i]['user_id'],
+ $attempt['user_id'],
$variable
);
if (isset($values['value'])) {
- $results[$i][$variable] = $values['value'];
+ $attempt[$variable] = $values['value'];
}
}
}
- $exeId = $results[$i]['exe_id'];
- $results[$i]['id'] = $exeId;
+ $exeId = $attempt['exe_id'];
+ $attempt['id'] = $exeId;
$category_list = [];
if ($is_allowedToEdit) {
$sessionName = '';
@@ -2698,7 +2782,14 @@ HOTSPOT;
}
}
- $objExercise = new Exercise($course_id);
+ $courseId = $courseItemInfo['real_id'];
+
+ if ($searchAllTeacherCourses) {
+ $attempt['course'] = $courseItemInfo['title'];
+ $attempt['exercise'] = $attempt['title'];
+ }
+
+ $objExercise = new Exercise($courseId);
if ($showExerciseCategories) {
// Getting attempt info
$exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId);
@@ -2779,8 +2870,8 @@ HOTSPOT;
$thousandSeparator,
$roundValues
);
- $results[$i]['category_'.$categoryId] = $scoreToDisplay;
- $results[$i]['category_'.$categoryId.'_score_percentage'] = self::show_score(
+ $attempt['category_'.$categoryId] = $scoreToDisplay;
+ $attempt['category_'.$categoryId.'_score_percentage'] = self::show_score(
$result['score'],
$result['total'],
true,
@@ -2791,14 +2882,14 @@ HOTSPOT;
$thousandSeparator,
$roundValues
);
- $results[$i]['category_'.$categoryId.'_only_score'] = $result['score'];
- $results[$i]['category_'.$categoryId.'_total'] = $result['total'];
+ $attempt['category_'.$categoryId.'_only_score'] = $result['score'];
+ $attempt['category_'.$categoryId.'_total'] = $result['total'];
}
- $results[$i]['session'] = $sessionName;
- $results[$i]['session_access_start_date'] = $sessionStartAccessDate;
- $results[$i]['status'] = $revisedLabel;
- $results[$i]['score'] = $score;
- $results[$i]['score_percentage'] = self::show_score(
+ $attempt['session'] = $sessionName;
+ $attempt['session_access_start_date'] = $sessionStartAccessDate;
+ $attempt['status'] = $revisedLabel;
+ $attempt['score'] = $score;
+ $attempt['score_percentage'] = self::show_score(
$my_res,
$my_total,
true,
@@ -2827,7 +2918,7 @@ HOTSPOT;
);
}
- $results[$i]['only_score'] = $onlyScore;
+ $attempt['only_score'] = $onlyScore;
if ($roundValues) {
$whole = floor($my_total); // 1
@@ -2845,15 +2936,15 @@ HOTSPOT;
$thousandSeparator
);
}
- $results[$i]['total'] = $onlyTotal;
- $results[$i]['lp'] = $lp_name;
- $results[$i]['actions'] = $actions;
- $listInfo[] = $results[$i];
+ $attempt['total'] = $onlyTotal;
+ $attempt['lp'] = $lp_name;
+ $attempt['actions'] = $actions;
+ $listInfo[] = $attempt;
} else {
- $results[$i]['status'] = $revisedLabel;
- $results[$i]['score'] = $score;
- $results[$i]['actions'] = $actions;
- $listInfo[] = $results[$i];
+ $attempt['status'] = $revisedLabel;
+ $attempt['score'] = $score;
+ $attempt['actions'] = $actions;
+ $listInfo[] = $attempt;
}
}
}
diff --git a/main/inc/lib/tracking.lib.php b/main/inc/lib/tracking.lib.php
index 800a204bc7..ab37e9fdc4 100755
--- a/main/inc/lib/tracking.lib.php
+++ b/main/inc/lib/tracking.lib.php
@@ -157,21 +157,7 @@ class Tracking
}
$hideTime = api_get_configuration_value('hide_lp_time');
- $allowNewTracking = api_get_configuration_value('use_new_tracking_in_lp_item');
$lp_id = (int) $lp_id;
-
- if ($allowNewTracking) {
- $extraField = new ExtraFieldValue('lp');
- $result = $extraField->get_values_by_handler_and_field_variable($lp_id, 'track_lp_item');
- if (empty($result)) {
- $allowNewTracking = false;
- } else {
- if (isset($result['value']) && $result['value'] == 1) {
- $allowNewTracking = true;
- }
- }
- }
-
$lp_item_id = (int) $lp_item_id;
$user_id = (int) $user_id;
$session_id = (int) $session_id;
@@ -383,7 +369,7 @@ class Tracking
$time_for_total = 0;
$attemptResult = 0;
- if ($allowNewTracking && $timeCourse) {
+ if ($timeCourse) {
if (isset($timeCourse['learnpath_detailed']) &&
isset($timeCourse['learnpath_detailed'][$lp_id]) &&
isset($timeCourse['learnpath_detailed'][$lp_id][$my_item_id])
@@ -476,7 +462,7 @@ class Tracking
$extend_attempt_link = '';
$extend_this_attempt = 0;
- if ($allowNewTracking && $timeCourse) {
+ if ($timeCourse) {
//$attemptResult = 0;
if (isset($timeCourse['learnpath_detailed']) &&
isset($timeCourse['learnpath_detailed'][$lp_id]) &&
@@ -534,17 +520,14 @@ class Tracking
$attemptTime = $row['mytime'];
if ($minimumAvailable) {
- $lp_time = $timeCourse[TOOL_LEARNPATH];
+ /*$lp_time = $timeCourse[TOOL_LEARNPATH];
$lpTime = null;
if (isset($lp_time[$lp_id])) {
$lpTime = (int) $lp_time[$lp_id];
}
- $time_for_total = $lpTime;
-
- if ($allowNewTracking) {
- $time_for_total = (int) $attemptResult;
- $attemptTime = (int) $attemptResult;
- }
+ $time_for_total = $lpTime;*/
+ $time_for_total = (int) $attemptResult;
+ $attemptTime = (int) $attemptResult;
}
$time = learnpathItem::getScormTimeFromParameter('js', $attemptTime);
@@ -765,10 +748,6 @@ class Tracking
$subtotal_time += $tmp_row['mytime'];
}
- if ($allowNewTracking) {
- $subtotal_time = $attemptResult;
- }
-
$title = $row['mytitle'];
// Selecting the exe_id from stats attempts tables in order to look the max score value.
$sql = 'SELECT * FROM '.$tbl_stats_exercices.'
diff --git a/main/inc/lib/userportal.lib.php b/main/inc/lib/userportal.lib.php
index 0412ad64a9..011c1616f1 100755
--- a/main/inc/lib/userportal.lib.php
+++ b/main/inc/lib/userportal.lib.php
@@ -1129,12 +1129,22 @@ class IndexManager
];
}
- if (api_get_configuration_value('my_courses_show_pending_work') && api_is_teacher()) {
- $items[] = [
- 'icon' => Display::return_icon('work.png', get_lang('StudentPublicationToCorrect')),
- 'link' => api_get_path(WEB_CODE_PATH).'work/pending.php',
- 'title' => get_lang('StudentPublicationToCorrect'),
- ];
+ if (api_is_teacher()) {
+ if (api_get_configuration_value('my_courses_show_pending_work')) {
+ $items[] = [
+ 'icon' => Display::return_icon('work.png', get_lang('StudentPublicationToCorrect')),
+ 'link' => api_get_path(WEB_CODE_PATH).'work/pending.php',
+ 'title' => get_lang('StudentPublicationToCorrect'),
+ ];
+ }
+
+ if (api_get_configuration_value('my_courses_show_pending_exercise_attempts')) {
+ $items[] = [
+ 'icon' => Display::return_icon('quiz.png', get_lang('PendingAttempts')),
+ 'link' => api_get_path(WEB_CODE_PATH).'exercise/pending.php',
+ 'title' => get_lang('PendingAttempts'),
+ ];
+ }
}
return $items;
diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php
index 8928511583..9e05a39602 100755
--- a/main/install/configuration.dist.php
+++ b/main/install/configuration.dist.php
@@ -1159,10 +1159,6 @@ $_configuration['profile_fields_visibility'] = [
*/
//$_configuration['lp_minimum_time'] = false;
-// Track LP attempts using the new tracking system.
-// Requires to add an LP extra field called "track_lp_item" (checkbox) in order to use this feature.
-//$_configuration['use_new_tracking_in_lp_item'] = false;
-
// Add collapsable option for user course categories
// ALTER TABLE user_course_category ADD collapsed TINYINT(1) DEFAULT NULL;
// $_configuration['allow_user_course_category_collapsable'] = false;
@@ -1851,6 +1847,9 @@ ALTER TABLE gradebook_comment ADD CONSTRAINT FK_C3B70763AD3ED51C FOREIGN KEY (gr
// Show a link to the work/pending.php page in my courses (user_portal)
//$_configuration['my_courses_show_pending_work'] = true;
+// Show exercise report from all courses in a new page: exercise/pending.php
+//$_configuration['my_courses_show_pending_exercise_attempts'] = true;
+
// KEEP THIS AT THE END
// -------- Custom DB changes
// Add user activation by confirmation email
diff --git a/plugin/azure_active_directory/README.md b/plugin/azure_active_directory/README.md
index 39f2426f6c..258b838460 100644
--- a/plugin/azure_active_directory/README.md
+++ b/plugin/azure_active_directory/README.md
@@ -1,27 +1,30 @@
# The Azure Active Directory Plugin
Allow authentication (with OAuth2) with Microsoft's Azure Active Directory.
-This plugin add two extra fields for users:
+This plugin adds two extra fields for users:
- `organisationemail`, the email registered in Azure Active Directory for each user.
- `azure_id`, to save the internal ID for each user in Azure.
-> This plugin use the [`thenetworg/oauth2-azure`](https://github.com/TheNetworg/oauth2-azure) package.
+> This plugin uses the [`thenetworg/oauth2-azure`](https://github.com/TheNetworg/oauth2-azure) package.
+
+### Prerequisites
+This plugin will *not* work if you do not use HTTPS. Make sure your portal is in HTTPS before you configure this plugin.
### To configure Azure Active Directory
-* Create and configure an application.
-* In _Authentication_ section, set an _Reply URL_ with `https://{CHAMILO_URL}/plugin/azure_active_directory/src/callback.php`.
-* In _Certificates & secrets_, create a secret string (or application password). And keep copied.
+* Create and configure an application in your Azure panel (Azure Active Directory -> Applications registration -> New registration))
+* In the _Authentication_ section, set an _Reply URL_ with `https://{CHAMILO_URL}/plugin/azure_active_directory/src/callback.php`.
+* In _Certificates & secrets_, create a secret string (or application password). Keep the _Value_ field at hand.
### To configure this plugin
* _Enable_
-* _Application ID_: Enter the Application Id assinged to your app by the Azure portal.
-* _Application secret_: Enter the client secret created.
+* _Application ID_: Enter the Application Id assigned to your app by the Azure portal.
+* _Application secret_: Enter the client secret created in _Certificate & secrets_ above.
* _Block name_: (Optional) The name to show above the login button.
* _Force logout button_: (Optional) Add a button to force logout from Azure.
* _Management login_: (Optional) Disable the chamilo login and enable an alternative login page for users.
You will need copy the `/plugin/azure_active_directory/layout/login_form.tpl` file to `/main/template/overrides/layout/` directory.
-* _Name for the management login_: A name for the management login. By default is "Management Login".
-* And assign a region. Preferably `login_bottom`.
+* _Name for the management login_: A name for the manager login. By default, it is set to "Management Login".
+* Assign a region in which the login option will appear. Preferably `login_bottom`.
Also, you can configure the external login to work with the classic Chamilo form login.
Adding this line in `configuration.php` file.