diff --git a/README.md b/README.md
index bd6fa3b..907939b 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,11 @@ Enrich your videos by the powerful features of the social video player. Create p
# Changelog
+### v1.16
+* added Timing Mode
+* bug fixes
+* ``ep5 version 2.6``
+
### v1.15
* added Gradebook integration
* added Multiple-choice questions to Quiz-mode
diff --git a/amd/src/ivs_activity_settings_page.js b/amd/src/ivs_activity_settings_page.js
index 6b5c455..db5f55f 100644
--- a/amd/src/ivs_activity_settings_page.js
+++ b/amd/src/ivs_activity_settings_page.js
@@ -22,13 +22,13 @@ define(['jquery', 'core/notification', 'core/custom_interaction_events', 'core/m
set_display_option('#id_annotations_enabled_value', '#id_mod_ivsnotification');
set_display_option('#id_exam_mode_enabled_value', '#id_mod_ivsgrades');
- set_display_option_timing_mode( '#id_match_question_enabled_value', '#fgroup_id_show_videotest_feedback');
- set_display_option_timing_mode('#id_match_question_enabled_value', '#fgroup_id_show_videotest_solution');
+ set_display_option_timing_mode( '#id_match_question_enabled_value', '#fgroup_id_show_realtime_results');
+ set_display_option_timing_mode('#id_match_question_enabled_value', '#fgroup_id_show_timing_take_summary');
$('#id_match_question_enabled_value').change(function () {
//Show timing mode checkboxes
- set_display_option_timing_mode( $('#id_match_question_enabled_value'), '#fgroup_id_show_videotest_feedback');
- set_display_option_timing_mode($('#id_match_question_enabled_value'), '#fgroup_id_show_videotest_solution');
+ set_display_option_timing_mode( $('#id_match_question_enabled_value'), '#fgroup_id_show_realtime_results');
+ set_display_option_timing_mode($('#id_match_question_enabled_value'), '#fgroup_id_show_timing_take_summary');
return;
});
diff --git a/classes/MoodleMatchController.php b/classes/MoodleMatchController.php
index 5323824..00a5607 100644
--- a/classes/MoodleMatchController.php
+++ b/classes/MoodleMatchController.php
@@ -26,6 +26,7 @@
use ltiservice_gradebookservices\local\service\gradebookservices;
use mod_ivs\gradebook\GradebookService;
+use mod_ivs\ivs_match\MatchTimingType;
use mod_ivs\ivs_match\question\QuestionSummary;
use mod_ivs\ivs_match\AssessmentConfig;
use mod_ivs\ivs_match\IvsMatchControllerBase;
@@ -37,8 +38,11 @@
use mod_ivs\ivs_match\IIvsMatch;
use mod_ivs\ivs_match\MatchConfig;
use mod_ivs\ivs_match\MatchTake;
+use mod_ivs\ivs_match\timing\MatchTimingTakeResult;
use mod_ivs\IvsHelper;
use mod_ivs\output\match\question_click_answer_view;
+use mod_ivs\output\match\timing_question_answer_view;
+use mod_ivs\output\match\timing_type_answer_view;
use mod_ivs\output\match\question_single_choice_answer_view;
use mod_ivs\output\match\question_text_answer_view;
use mod_ivs\settings\SettingsDefinition;
@@ -106,7 +110,8 @@ public function match_question_get_db($questionid, $skipaccess = false) {
throw new MatchQuestionNotFoundException();
}
- return $this->record_to_player_question((array) $questionfromdb);
+ $questionfromdb = $this->record_to_player_question((array) $questionfromdb);
+ return $questionfromdb;
}
@@ -129,6 +134,8 @@ public function match_questions_get_by_video_db($videoid, $order = 'timecode', $
$questions[$question->id] = $this->record_to_player_question((array) $question);
}
+ $questions = $this->match_questions_reformat_timing_questions($videoid, $questions);
+
return $questions;
}
@@ -156,6 +163,9 @@ public function match_questions_get_by_video_db_order($videoid, $order = 'timeco
foreach ($records as $question) {
$questions[$question->id] = $this->record_to_player_question((array) $question);
}
+
+ $questions = $this->match_questions_reformat_timing_questions($videoid, $questions);
+
return $questions;
}
@@ -389,6 +399,7 @@ public function match_question_answers_get_by_question_and_user_db($questionid,
foreach ($record as $answer) {
$answers[$answer->id] = $this->record_to_player_answer((array) $answer);
}
+
return $answers;
}
@@ -439,6 +450,53 @@ public function match_question_answers_get_by_question_and_user_for_reporting($q
return $detail;
}
+ /**
+ * Gets all necessary data for reporting
+ *
+ * @param int $questionid
+ * @param int $userid
+ * @param bool $skipaccess
+ * @return array
+ * @throws \mod_ivs\ivs_match\exception\MatchQuestionAccessDeniedException
+ * @throws \mod_ivs\ivs_match\exception\MatchQuestionNotFoundException
+ */
+ public function match_question_answers_get_by_timing_type_and_user_for_reporting($currenttimingtype, $userid, $videoid, $skipaccess = false) {
+
+ //get all answers by timing type
+ $timingtypeanswers = $this->match_question_answers_get_by_timing_type_and_user_db($currenttimingtype->id, $userid, $videoid, $skipaccess);
+ $detail = [];
+
+ $counterfirst = 0;
+ $counterlast = 0;
+
+ foreach ($timingtypeanswers as $questionid => $answers){
+ if (!empty($answers)){
+ $answers = array_values($answers);
+ if ($answers[0]['is_correct']){
+ $counterfirst++;
+ }
+ if (end($answers)['is_correct']){
+ $counterlast++;
+ }
+ }
+ }
+
+ $detail = array(
+
+ 'userid' => $userid,
+ 'type' => 'timing_question',
+ 'videoid' => $videoid,
+ 'question' => [
+ 'nid' => $currenttimingtype->id,
+ 'title' => $currenttimingtype->title,
+ 'question_body' => $currenttimingtype->description,
+ ],
+ 'answers' => [$counterfirst, $counterlast]
+ );
+
+ return $detail;
+ }
+
/**
* Get a collection of answers by video and user keyed by question_id
*
@@ -888,7 +946,7 @@ public function get_assessment_type_options(){
return[
AssessmentConfig::ASSESSMENT_TYPE_NONE => get_string('ivs_match_config_assessment_mode_none', 'ivs'),
AssessmentConfig::ASSESSMENT_TYPE_QUIZ => get_string('ivs_match_config_assessment_mode_quiz', 'ivs'),
- //AssessmentConfig::ASSESSMENT_TYPE_TIMING => get_string('ivs_match_config_assessment_mode_timing', 'ivs'),
+ AssessmentConfig::ASSESSMENT_TYPE_TIMING => get_string('ivs_match_config_assessment_mode_timing', 'ivs'),
];
}
@@ -903,7 +961,6 @@ public function get_assessment_type_options(){
*/
public function assessment_config_get_by_user_and_video($userid, $videoid, $includesimulation = false) {
- $assessmentconfig = [];
global $DB;
$ivs = $DB->get_record('ivs', array('id' => $videoid), '*', MUST_EXIST);
$moodlematchcontroller = new MoodleMatchController();
@@ -1052,6 +1109,16 @@ public function get_match_question_title($question) {
return !empty($question['title']) ? $question['title'] : shorten_text($question['question_body']);
}
+ /**
+ * Get the title from a match question
+ * @param array $question
+ *
+ * @return mixed
+ */
+ public function get_match_question_timing_title($timingtype) {
+ return !empty($timingtype->description) ? ($timingtype->title . ': ' . shorten_text($timingtype->description)) : $timingtype->label;
+ }
+
/**
* Get question summary raw data
*
@@ -1149,6 +1216,15 @@ public function get_question_summary_formated($question, $coursestudents) {
round($questionsummary->last_attempt_correct * 100 / $questionsummary->num_students_participation, 0) .
'%';
break;
+ case 'timing_question':
+ $data->question_type = get_string('ivs_match_question_summary_question_type_timing', 'ivs');
+ $data->question_first_try = $questionsummary->num_students_participation == 0 ? '0%' :
+ round($questionsummary->first_attempt_correct * 100 / $questionsummary->num_students_participation, 0) .
+ '%';
+ $data->question_last_try = $questionsummary->num_students_participation == 0 ? '0%' :
+ round($questionsummary->last_attempt_correct * 100 / $questionsummary->num_students_participation, 0) .
+ '%';
+ break;
}
$data->question_answered = $questionsummary->num_students_participation . ' / ' . $questionsummary->num_students_total;
@@ -1233,6 +1309,8 @@ public function get_question_answers_data($detailarray, $questions, $cmid, $vide
$data->text_question = false;
$data->single_choice_question = false;
$data->click_question = false;
+ $data->timing_question = false;
+ $timing_types = false;
for ($i = $offset; $i < $offset + $perpage; $i++) {
if ($i == $totalcount) {
@@ -1256,6 +1334,18 @@ public function get_question_answers_data($detailarray, $questions, $cmid, $vide
$data->question_type = get_string('ivs_match_question_summary_question_type_single', 'ivs');
$data->single_choice_question = true;
$renderable = new question_single_choice_answer_view($answer, $answerusers[$i]);
+ break;
+ case 'timing_question':
+ $data->question_type = get_string('ivs_match_question_summary_question_type_timing', 'ivs');
+ $data->timing_question = true;
+ $url = $_SERVER['REQUEST_URI'];
+ if (strpos($url, 'question_answers.php')){
+ $renderable = new timing_question_answer_view($answer, $answerusers[$i]);
+ $timing_types = true;
+ }else{
+ $renderable = new timing_type_answer_view($answer, $answerusers[$i]);
+ }
+
break;
}
@@ -1273,14 +1363,28 @@ public function get_question_answers_data($detailarray, $questions, $cmid, $vide
}
if (!empty($output)) {
// Render all Questions in Dropdown.
- foreach ($questions as $question) {
+ if ($timing_types){
+ foreach ($questions as $question) {
+
+ $label = $controller->get_match_question_title($question);
- $label = $controller->get_match_question_title($question);
+ $questionurl = new moodle_url('/mod/ivs/question_answers.php?id=' . $cmid . '&vid=' . $videoid . '&qid=' .
+ $question['nid'] . '&perpage=10');
+ $selected = required_param('qid', PARAM_INT) == $question['nid'] ? 'selected' : '';
+ $data->dropdown_options[] = '';
+ }
+ }else{
+ $timingtypes = $controller->match_timing_type_get_db($videoid);
+ foreach ($timingtypes as $timingtype) {
- $questionurl = new moodle_url('/mod/ivs/question_answers.php?id=' . $cmid . '&vid=' . $videoid . '&qid=' .
- $question['nid'] . '&perpage=10');
- $selected = required_param('qid', PARAM_INT) == $question['nid'] ? 'selected' : '';
- $data->dropdown_options[] = '';
+ $label = $controller->get_match_question_timing_title($timingtype);
+
+ $questionurl = new moodle_url('/mod/ivs/question_type_answers.php?id=' . $cmid . '&vid=' . $videoid . '&qid=' .
+ $timingtype->id . '&perpage=10');
+ $qid = required_param('qid', PARAM_ALPHANUMEXT);
+ $selected = $qid == $timingtype->id ? 'selected' : '';
+ $data->dropdown_options[] = '';
+ }
}
// Render Pager Options in Dropdown.
@@ -1310,7 +1414,8 @@ public function get_question_answers_data($detailarray, $questions, $cmid, $vide
$data->single_choice_correct = get_string("ivs_match_question_answer_menu_label_single_choice_correct", 'ivs');
$data->single_choice_selected_answer =
get_string("ivs_match_question_answer_menu_label_last_single_choice_selected_answer", 'ivs');
-
+ $data->first_timing_answer = get_string("ivs_match_question_answer_menu_label_first_timing_answer", 'ivs');
+ $data->last_timing_answer = get_string("ivs_match_question_answer_menu_label_last_timing_answer", 'ivs');
return $data;
}
@@ -1428,6 +1533,105 @@ public function get_question_answers_data_text_question($answer, $courseuser) {
return $data;
}
+ /**
+ * Get all answers for a timing questions
+ * @param array $answer
+ * @param \stdClass $courseuser
+ *
+ * @return \stdClass
+ */
+ public function get_question_answers_data_timing_type($answer, $courseuser) {
+ $data = new \stdClass;
+
+ $user = IvsHelper::get_user($courseuser->id);
+ $controller = $this;
+
+ $data->fullname = $user['fullname'];
+ $data->id = $courseuser->id;
+
+ foreach ($answer as $key => $value) {
+
+ $questions = $controller->match_questions_get_by_video_db($value['videoid']);
+ $timingquestioncount = 0;
+ foreach($questions as $question){
+ if ($value['question']['nid'] == $question['type_data']['timing_type_id']){
+ $timingquestioncount++;
+ }
+ }
+
+
+ $data->first = '0/' . $timingquestioncount;
+ $data->last = '0/' . $timingquestioncount;
+ $data->retries = '-';
+
+ $takes = $controller->match_takes_get_by_user_and_video_db($user['user']->id, $value['videoid'], $value['videoid']);
+ $numtakes = count($takes);
+
+
+
+ if ($numtakes > 0) {
+ $data->retries = $numtakes - 1;
+ if ($value['userid'] === $courseuser->id) {
+ $data->first = $value['answers'][0] . '/' . $timingquestioncount;
+ $data->last = $value['answers'][1] . '/' . $timingquestioncount;
+ break;
+ }
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * Get all answers for a timing questions
+ * @param array $answer
+ * @param \stdClass $courseuser
+ *
+ * @return \stdClass
+ */
+ public function get_question_answers_data_timing_question($answer, $courseuser) {
+ $data = new \stdClass;
+
+ $user = IvsHelper::get_user($courseuser->id);
+
+ $data->fullname = $user['fullname'];
+ $data->id = $courseuser->id;
+
+ $controller = $this;
+
+ foreach ($answer as $key => $value) {
+
+ $data->first = '-';
+ $data->last = '-';
+ $data->retries = '-';
+ if (!empty($value['answers'][1]) && $value['answers'][1]['user_id'] === $courseuser->id) {
+
+ $userid = $courseuser->id;
+ $answers = $controller->match_question_answers_get_by_question_and_user_db($value['question']['nid'], $userid);
+ $numanswers = count($answers);
+
+ if ($numanswers > 0) {
+ $data->retries = $numanswers - 1;
+
+ $lastanswer = $value['answers'][1];
+ if (!empty($value['answers'][0])) {
+ $firstanswer = $value['answers'][0];
+ } else {
+ $firstanswer = $value['answers'][1];
+ }
+
+ $data->first = !empty($firstanswer['is_correct']) ? 1 : 0;
+ $data->last = !empty($lastanswer['is_correct']) ? 1 : 0;
+
+ }
+
+ break;
+
+ }
+ }
+
+ return $data;
+ }
+
private function get_formative_assessment_config($userid, $ivs) {
$videoid = $ivs->id;
@@ -1468,6 +1672,9 @@ private function get_videotest_assessment_config_by_user($userid, $ivs) {
$settingscontroller = new SettingsService();
$activitysettings = $settingscontroller->get_settings_for_activity($videoid, $ivs->course);
+ $gradebookservice = new GradebookService();
+ $grade_methods = $gradebookservice->ivs_get_grade_method_options();
+
if ($this->has_edit_access($videoid)) {
$assconf = new AssessmentConfig();
@@ -1488,10 +1695,16 @@ private function get_videotest_assessment_config_by_user($userid, $ivs) {
$assconf->matchConfig = $this->match_video_get_config_db($videoid);
$assconf->takes_left = $activitysettings['exam_mode_enabled']->value ? $this->get_remaining_attempts($userid, $videoid, $contextid) : 1;
$assconf->takes = $this->match_takes_get_by_user_and_video_db($userid, $videoid, $contextid);
+ $assconf->grade_method = $grade_methods[$activitysettings[SettingsDefinition::SETTING_PLAYER_VIDEOTEST_GRADE_METHOD]->value];
+ $assconf->exam_enabled = (int)$activitysettings[SettingsDefinition::SETTING_PLAYER_EXAM_ENABLED]->value;
$num_takes = count($assconf->takes);
$already_passed = FALSE;
+ if ($assconf->matchConfig->is_timing_mode()){
+ $assconf->matchConfig->rate .= $this->get_success_rate_points_label($videoid, $assconf);
+ }
+
if ($num_takes == 0) {
$assconf->status = AssessmentConfig::TAKES_LEFT_NEW;
$assconf->status_description = get_string("ivs_match_config_status_not_started_label", 'ivs');
@@ -1508,35 +1721,18 @@ private function get_videotest_assessment_config_by_user($userid, $ivs) {
}
}
- $gradebookservice = new GradebookService();
- $scoreinfo = $gradebookservice->ivs_gradebook_get_score_info_by_takes($assconf->takes, $ivs);
+ $score = $gradebookservice->ivs_gradebook_get_score_by_takes($assconf->takes, $ivs);
- if ($scoreinfo['score'] >= $assconf->matchConfig->rate){
+ if ($score >= $assconf->matchConfig->rate){
$already_passed = TRUE;
}
- if ($already_passed) {
- $assconf->status_description = get_string("ivs_match_config_status_passed_label", 'ivs') . $scoreinfo['desc'] . $scoreinfo['score'] . '%';
- if($assconf->takes_left > 0) {
- $assconf->status = AssessmentConfig::TAKES_LEFT_COMPLETED_SUCCESS;
- }else{
- if($assconf->matchConfig->assessment_type == AssessmentConfig::ASSESSMENT_TYPE_TIMING){
- $assconf->status = AssessmentConfig::NO_TAKES_LEFT_COMPLETED_SUCCESS_NO_SUMMARY;
- }else{
- $assconf->status = AssessmentConfig::NO_TAKES_LEFT_COMPLETED_SUCCESS;
- }
- }
+ if ($assconf->matchConfig->is_quiz_mode()){
+ $this->get_quiz_status($assconf, $already_passed, $score, $take_in_progress);
}
- elseif ($assconf->takes_left == 0) {
- $assconf->status = AssessmentConfig::NO_TAKES_LEFT_COMPLETED_FAILED;
- $assconf->status_description = get_string("ivs_match_config_status_failed_label", 'ivs') . $scoreinfo['desc'] . $scoreinfo['score'] . '%';
- }else{
- $assconf->status = AssessmentConfig::TAKES_LEFT_PROGRESS;
- if($take_in_progress) {
- $assconf->status_description = get_string("ivs_match_config_status_progress_label", 'ivs');
- }else{
- $assconf->status_description = get_string("ivs_match_config_status_not_passed_label", 'ivs') . $scoreinfo['desc'] . $scoreinfo['score']. '%';
- }
+
+ if ($assconf->matchConfig->is_timing_mode()){
+ $this->get_timing_status($assconf, $already_passed, $ivs);
}
}
@@ -1545,6 +1741,51 @@ private function get_videotest_assessment_config_by_user($userid, $ivs) {
return $assessmentconfig;
}
+ private function get_quiz_status(&$assconf, $already_passed, $score, $take_in_progress) {
+
+ if ($already_passed) {
+ $assconf->status_description = get_string("ivs_match_config_status_passed_label", 'ivs') . $score . '%';
+ $assconf->status = AssessmentConfig::NO_TAKES_LEFT_COMPLETED_SUCCESS;
+ if($assconf->takes_left > 0 || $assconf->matchConfig->attempts == 0) {
+ $assconf->status = AssessmentConfig::TAKES_LEFT_COMPLETED_SUCCESS;
+ }
+ }
+ elseif ($assconf->takes_left == 0) {
+ $assconf->status = AssessmentConfig::NO_TAKES_LEFT_COMPLETED_FAILED;
+ $assconf->status_description = get_string("ivs_match_config_status_failed_label", 'ivs') . $score. '%';
+ }else{
+ $assconf->status = AssessmentConfig::TAKES_LEFT_PROGRESS;
+ if($take_in_progress) {
+ $assconf->status_description = get_string("ivs_match_config_status_progress_label", 'ivs');
+ }else{
+ $assconf->status_description = get_string("ivs_match_config_status_not_passed_label", 'ivs') . $score . '%';
+ }
+ }
+ }
+
+ private function get_timing_status(&$assconf, $already_passed, $ivs) {
+ $gradebookservice = new GradebookService();
+
+ if ($already_passed) {
+ $assconf->status_description = get_string("ivs_match_config_timing_status_passed_label", 'ivs');
+ $assconf->status = AssessmentConfig::NO_TAKES_LEFT_COMPLETED_SUCCESS_NO_SUMMARY;
+ if($assconf->takes_left > 0 || $assconf->matchConfig->attempts == 0) {
+ $assconf->status = AssessmentConfig::TAKES_LEFT_COMPLETED_SUCCESS;
+ }
+ }
+ elseif ($assconf->takes_left == 0) {
+ $assconf->status = AssessmentConfig::NO_TAKES_LEFT_COMPLETED_FAILED;
+ $assconf->status_description = get_string("ivs_match_config_timing_status_not_passed_label", 'ivs');
+ }else{
+ $assconf->status = AssessmentConfig::TAKES_LEFT_PROGRESS;
+ $assconf->status_description = get_string("ivs_match_config_timing_status_not_passed_label", 'ivs');
+ }
+
+ $assconf->status_description .= $gradebookservice->get_rendered_timing_take_summary($assconf->takes, $ivs);
+ }
+
+
+
private function get_quiz_match_config($ivs) {
global $DB;
$gradebookservice = new GradebookService();
@@ -1599,8 +1840,8 @@ private function get_timing_match_config($ivs) {
$mc->player_controls_enabled = (int) $activitysettings['player_controls_enabled']->value;
$mc->rate = !empty($gradesettings) ? (int)$gradesettings->gradepass : 100;
$mc->attempts = $activitysettings[SettingsDefinition::SETTING_PLAYER_VIDEOTEST_ATTEMPTS]->value;
- $mc->show_feedback = $activitysettings[SettingsDefinition::SETTING_PLAYER_SHOW_VIDEOTEST_FEEDBACK]->value;
- $mc->show_solution = $activitysettings[SettingsDefinition::SETTING_PLAYER_SHOW_VIDEOTEST_SOLUTION]->value;
+ $mc->show_feedback = $activitysettings[SettingsDefinition::SETTING_PLAYER_SHOW_REALTIME_RESULTS]->value;
+ $mc->show_solution = false;
return $mc;
}
@@ -1623,18 +1864,21 @@ private function get_ivs_videotest_context_label($ivs) {
}
- public function match_timing_type_get_db($ivs, $skip_access = FALSE) {
+ public function match_timing_type_get_db($videoid, $skip_access = FALSE) {
+ global $DB;
+ $ivs = $DB->get_record('ivs', array('id' => $videoid), '*', MUST_EXIST);
+ $timingtypes = [];
if(!empty($ivs->match_config)) {
- $data = json_decode($ivs->match_config, TRUE);
+ $matchconfig = json_decode($ivs->match_config, TRUE);
- if(!empty($data['timing_types'])) {
- return $data['timing_types'];
+ foreach ($matchconfig['timing_types'] as $timingtype){
+ $timingtypes[] = new MatchTimingType($timingtype);
}
}
- return [];
+ return $timingtypes;
}
public function match_timing_type_insert_db($videoid, $data, $user_id = NULL, $skip_access = FALSE) {
@@ -1663,25 +1907,15 @@ public function match_timing_type_delete_db($videoid, $timing_type_id, $skip_acc
throw new MatchQuestionAccessDeniedException(null, "Access denied");
}
- if(empty($ivs->match_config)) {
- $match_settings = [];
- }else {
- $match_settings = json_decode($ivs->match_config, TRUE);
- }
+ $matchtimingtypes = $this->match_timing_type_get_db($videoid, TRUE);
//check if command id exists
- foreach ($match_settings['timing_types'] as $k => $c) {
- if ($c['id'] === $timing_type_id) {
- unset($match_settings['timing_types'][$k]);
+ foreach ($matchtimingtypes as $i => $matchtimingtype) {
+ if ($matchtimingtype->id === $timing_type_id) {
+ unset($matchtimingtypes[$i]);
}
}
-
- //normalize keys
- $match_settings['timing_types'] = array_values($match_settings['timing_types']);
-
- $ivs->match_config = json_encode((array) $match_settings);
- $DB->insert_record('ivs', $ivs);
-
+ $this->update_timing_types($ivs, $matchtimingtypes);
}
protected function saveTimingType($post_data, $ivs, $skip_access = FALSE) {
@@ -1693,11 +1927,8 @@ protected function saveTimingType($post_data, $ivs, $skip_access = FALSE) {
throw new MatchQuestionAccessDeniedException(null, "Access denied");
}
- $timing_types = $this->match_timing_type_get_db($ivs, $skip_access);
$post_data = (object) $post_data;
-
-
//parse data
$id = $post_data->id;
@@ -1707,18 +1938,18 @@ protected function saveTimingType($post_data, $ivs, $skip_access = FALSE) {
}
- $timing_type['type'] = $post_data->type;
+ $timing_type_array['type'] = $post_data->type;
- $timing_type['timestamp'] = $post_data->timestamp;
- $timing_type['duration'] = $post_data->duration;
- $timing_type['title'] = $post_data->title;
+ $timing_type_array['timestamp'] = $post_data->timestamp;
+ $timing_type_array['duration'] = $post_data->duration;
+ $timing_type_array['title'] = $post_data->title;
$position = explode(',', $post_data->btn['position']);
$new_pos = [];
foreach ($position as $pos){
$new_pos[] = $pos;
}
- $timing_type = [
+ $timing_type_array = [
'title' => $post_data->title,
'duration' => $post_data->duration,
'weight' => $post_data->weight,
@@ -1731,37 +1962,144 @@ protected function saveTimingType($post_data, $ivs, $skip_access = FALSE) {
'description' => $post_data->btn['description']
]
];
+ $timing_type_array['id'] = $id;
- $timing_type['id'] = $id;
-
+ $matchtimingtype = new MatchTimingType($timing_type_array);
+ $timing_types = $this->match_timing_type_get_db($videoid, $skip_access);
- //check existing id
$is_new = TRUE;
- foreach ($timing_types as $k => $c) {
- if ($c['id'] === $id) {
- $timing_types[$k] = $timing_type;
+ foreach ($timing_types as &$timing_type) {
+ if ($timing_type->id == $matchtimingtype->id) {
+ $timing_type = $matchtimingtype;
$is_new = FALSE;
}
}
+
if ($is_new) {
- $timing_types[] = $timing_type;
+
+ $timing_types[] = $matchtimingtype;
}
- //save node
+ $this->update_timing_types($ivs, $timing_types);
+
+ return $matchtimingtype;
+
+ }
+ private function update_timing_types($ivs, $matchtimingtypes = []) {
+
+ global $DB;
if(empty($ivs->match_config)) {
$match_settings = [];
}else {
$match_settings = json_decode($ivs->match_config, TRUE);
}
- $match_settings['timing_types'] = $timing_types;
+ $matchtimingtypesjson = array_map( function ($timingtype){
+ return $timingtype->to_player_json();
+ }, $matchtimingtypes);
- $ivs->match_config = json_encode((array) $match_settings);
+ $match_settings['timing_types'] = $matchtimingtypesjson;
+ $ivs->match_config = json_encode($match_settings);
$DB->update_record('ivs', $ivs);
- return $timing_type;
}
+
+ private function get_success_rate_points_label($videoid, $assconf) {
+ $successratelabel = '';
+ $pointstotal = 0;
+
+ global $DB;
+ $matchcontroller = new MoodleMatchController();
+ $matchquestions = $DB->get_records('ivs_matchquestion', array('video_id' => $videoid));
+ $timingtypes = $matchcontroller->match_timing_type_get_db($videoid);
+ foreach ($matchquestions as $matchquestion) {
+ $type_data = unserialize($matchquestion->type_data);
+ $timingtype = MatchTimingTakeResult::find_object_by_id($type_data['timing_type_id'], $timingtypes);
+ $pointstotal += $timingtype->score;
+ }
+
+
+ $pointstosuccess = $pointstotal / 100 * $assconf->matchConfig->rate;
+ $successratelabel = '% (' . $pointstosuccess . ' ' . get_string('ivs_grademethod_timing_take_summary_points', 'ivs') . ')';
+ return $successratelabel;
+ }
+
+ private function match_questions_reformat_timing_questions($videoid, $questions) {
+
+ $timingtypes = $this->match_timing_type_get_db($videoid);
+ $formatedquestions = [];
+
+ foreach($questions as $question){
+ if ($question['type'] === 'timing_question'){
+
+ foreach($timingtypes as $timingtype){
+ if ($timingtype->id === $question['type_data']['timing_type_id']){
+ $question['title'] = $this->match_format_question_time($question['timestamp']) . ' - ' . $this->match_format_question_time($question['timestamp'] + $question['duration']) . ' ' . $timingtype->title;
+ $question['question_body'] = $timingtype->description;
+ break;
+ }
+ }
+ }
+ $formatedquestions[$question['nid']] = $question;
+ }
+ return $formatedquestions;
+ }
+
+ private function match_format_question_time($milliseconds) {
+ return sprintf('%02d:%02d', floor(($milliseconds % 3600000) / 60000), floor(($milliseconds % 60000) / 1000));
+ }
+
+ private function match_question_answers_get_by_timing_type_and_user_db( $timingtypeid, $userid, $videoid, $skipaccess) {
+
+
+ $matchquestions = $this->match_questions_get_by_video_db($videoid);
+
+ foreach ($matchquestions as $questionId => $question){
+ if ($question['type'] !== 'timing_question'){
+ unset($matchquestions[$questionId]);
+ continue;
+ }
+
+ if ($question['type_data']['timing_type_id'] !== $timingtypeid){
+ unset($matchquestions[$questionId]);
+ }
+ }
+
+
+ global $DB;
+ $timingtypeanswers = [];
+ foreach($matchquestions as $id => $matchquestion){
+
+
+ $record = $DB->get_records('ivs_matchanswer', array(
+ 'question_id' => $id,
+ 'user_id' => $userid
+ ));
+
+ $answers = [];
+ foreach ($record as $answer) {
+ $answers[$answer->id] = $this->record_to_player_answer((array) $answer);
+ }
+ $timingtypeanswers[$id] = $answers;
+ }
+
+ return $timingtypeanswers;
+ }
+
+ public function match_timing_get_current_timing_type($instance, $qid)
+ {
+ //Get current timing type
+ $timingtypes = $this->match_timing_type_get_db($instance);
+ $currenttimingtype = null;
+ foreach ($timingtypes as $timingtype){
+ if ($timingtype->id === $qid){
+ $currenttimingtype = $timingtype;
+ break;
+ }
+ }
+ return $currenttimingtype;
+ }
}
diff --git a/classes/StatisticsService.php b/classes/StatisticsService.php
index c7f6512..8ca2221 100644
--- a/classes/StatisticsService.php
+++ b/classes/StatisticsService.php
@@ -170,6 +170,8 @@ private function numIVSMatchQuestionsTypes() {
$this->database->count_records_sql("SELECT COUNT(id) FROM {ivs_matchquestion} WHERE type='click_question'");
$questiontypes['text_question'] =
$this->database->count_records_sql("SELECT COUNT(id) FROM {ivs_matchquestion} WHERE type='text_question'");
+ $questiontypes['timing_question'] =
+ $this->database->count_records_sql("SELECT COUNT(id) FROM {ivs_matchquestion} WHERE type='timing_question'");
return $questiontypes;
}
diff --git a/classes/UpdateService.php b/classes/UpdateService.php
index 8cc5d0f..8c4a64d 100755
--- a/classes/UpdateService.php
+++ b/classes/UpdateService.php
@@ -78,7 +78,7 @@ public function settingInvertUpdate() {
* @return void
*/
public function alterVideocommentTableForCommentType() {
- $this->database->execute("ALTER TABLE {ivs_videocomment} ADD comment_type VARCHAR(255) DEFAULT 'comment'");
+ $this->database->execute("ALTER TABLE {ivs_videocomment} ADD COLUMN IF NOT EXISTS comment_type VARCHAR(255) DEFAULT 'comment'");
$allcomments = $this->database->get_records_sql("SELECT id FROM {ivs_videocomment}");
foreach ($allcomments as $comment) {
diff --git a/classes/admin_setting_configtext_ivs_custom.php b/classes/admin_setting_configtext_ivs_custom.php
index 9ef21df..5fa1496 100644
--- a/classes/admin_setting_configtext_ivs_custom.php
+++ b/classes/admin_setting_configtext_ivs_custom.php
@@ -27,6 +27,7 @@ public function validate($data) {
if ($data > IVS_SETTING_PLAYER_ANNOTATION_AUDIO_MAX_DURATION || $data < 0) {
return get_string('ivs_setting_annotation_audio_max_duration_validation', 'mod_ivs');
}
+
return true;
}
-}
\ No newline at end of file
+}
diff --git a/classes/admin_setting_configtext_ivs_custom_with_lock.php b/classes/admin_setting_configtext_ivs_custom_with_lock.php
index 39cce64..678a302 100644
--- a/classes/admin_setting_configtext_ivs_custom_with_lock.php
+++ b/classes/admin_setting_configtext_ivs_custom_with_lock.php
@@ -18,6 +18,7 @@
use admin_setting_configtext;
use admin_setting_flag;
+use mod_ivs\settings\SettingsDefinition;
class admin_setting_configtext_ivs_custom_with_lock extends admin_setting_configtext {
/**
@@ -36,13 +37,28 @@ public function __construct($name, $visiblename, $description, $defaultsetting,
}
public function validate($data) {
- if (!is_numeric($data)) {
- return get_string('ivs_setting_annotation_audio_max_duration_validation', 'mod_ivs');
+
+
+ if ($this->name == SettingsDefinition::SETTING_PLAYER_ANNOTATION_AUDIO_MAX_DURATION){
+ if (!is_numeric($data)) {
+ return get_string('ivs_setting_annotation_audio_max_duration_validation', 'mod_ivs');
+ }
+
+ if ($data > IVS_SETTING_PLAYER_ANNOTATION_AUDIO_MAX_DURATION || $data < 0) {
+ return get_string('ivs_setting_annotation_audio_max_duration_validation', 'mod_ivs');
+ }
}
- if ($data > IVS_SETTING_PLAYER_ANNOTATION_AUDIO_MAX_DURATION || $data < 0) {
- return get_string('ivs_setting_annotation_audio_max_duration_validation', 'mod_ivs');
+ if ($this->name == SettingsDefinition::SETTING_PLAYER_VIDEOTEST_GRADE_TO_PASS){
+ if (!is_numeric($data)) {
+ return get_string('ivs_setting_grade_to_pass_validation', 'mod_ivs');
+ }
+
+ if ($data > 100 || $data < 0) {
+ return get_string('ivs_setting_grade_to_pass_validation', 'mod_ivs');
+ }
}
+
return true;
}
-}
\ No newline at end of file
+}
diff --git a/classes/gradebook/GradebookService.php b/classes/gradebook/GradebookService.php
index 202b8be..ea728b3 100644
--- a/classes/gradebook/GradebookService.php
+++ b/classes/gradebook/GradebookService.php
@@ -26,9 +26,12 @@
namespace mod_ivs\gradebook;
use core_course\analytics\target\course_gradetopass;
+use core_table\local\filter\string_filter;
use enrol_self\self_test;
use Helper\MoodleHelper;
+use mod_ivs\exception\ivs_exception;
use mod_ivs\ivs_match\AssessmentConfig;
+use mod_ivs\ivs_match\timing\MatchTimingTakeResult;
use mod_ivs\MoodleMatchController;
use mod_ivs\settings\SettingsDefinition;
use mod_ivs\settings\SettingsService;
@@ -106,10 +109,9 @@ public function ivs_get_attempt_options() {
* @param $takes
* @return mixed|null
*/
- private function get_grade_item_best_attempt($takes) {
+ public function get_best_score_by_takes($takes) {
$score = null;
-
foreach ($takes as $take) {
if (isset($take->score) && ($score === null || $take->score > $score)) {
$score = $take->score;
@@ -124,7 +126,7 @@ private function get_grade_item_best_attempt($takes) {
* @param $takes
* @return float|int
*/
- private function get_grade_item_average($takes) {
+ private function get_average_score_by_takes($takes) {
$total = 0;
$count = 0;
@@ -146,8 +148,9 @@ private function get_grade_item_average($takes) {
* @param $takes
* @return int
*/
- private function get_grade_item_first_attempt($takes) {
- return $takes[0]->score ?? 0;
+ private function get_first_score_by_takes($takes) {
+ $score = $takes[0]->score ?? 0;
+ return $score;
}
/**
@@ -155,9 +158,10 @@ private function get_grade_item_first_attempt($takes) {
* @param $takes
* @return int
*/
- private function get_grade_item_last_attempt($takes) {
+ private function get_last_score_by_takes($takes) {
$take = end($takes);
- return $take->score ?? 0;
+ $score = $take->score ?? 0;
+ return $score;
}
/**
@@ -266,36 +270,165 @@ public function ivs_get_grade_settings($ivs){
* Returns score and description for ivs activity quiz view
* @param $takes
* @param $ivs
- * @return array
+ * @return float
* @throws \coding_exception
*/
- public function ivs_gradebook_get_score_info_by_takes($takes, $ivs) {
+ public function ivs_gradebook_get_score_by_takes($takes, $ivs) {
+ $settingsservice = new SettingsService();
+ $activitysettings = $settingsservice->get_settings_for_activity($ivs->id, $ivs->course);
+ $gradingmethod = $activitysettings[SettingsDefinition::SETTING_PLAYER_VIDEOTEST_GRADE_METHOD]->value;
+
+ switch ($gradingmethod) {
+ case self::GRADE_METHOD_BEST_ATTEMPT:
+ $score = $this->get_best_score_by_takes($takes);
+ break;
+ case self::GRADE_METHOD_AVERAGE:
+ $score = $this->get_average_score_by_takes($takes);
+ break;
+ case self::GRADE_METHOD_FIRST_ATTEMPT:
+ $score = $this->get_first_score_by_takes($takes);
+ break;
+ case self::GRADE_METHOD_LAST_ATTEMPT:
+ $score = $this->get_last_score_by_takes($takes);
+ break;
+ }
+
+ return round($score, 2 );
+ }
+
+ public function ivs_gradebook_get_timing_take_summary_data_by_grade_method($takes, $ivs){
$settingsservice = new SettingsService();
$activitysettings = $settingsservice->get_settings_for_activity($ivs->id, $ivs->course);
- $score = 0;
$gradingmethod = $activitysettings[SettingsDefinition::SETTING_PLAYER_VIDEOTEST_GRADE_METHOD]->value;
switch ($gradingmethod) {
case self::GRADE_METHOD_BEST_ATTEMPT:
- $score = $this->get_grade_item_best_attempt($takes);
- $desc = get_string('ivs_match_config_grade_mode_best_score_label', 'ivs');
+ $matchtimingresult = $this->get_best_timing_take_summary_by_takes($takes);
break;
case self::GRADE_METHOD_AVERAGE:
- $score = $this->get_grade_item_average($takes);
- $desc = get_string('ivs_match_config_grade_mode_average_score_label', 'ivs');
+ $matchtimingresult = $this->get_average_timing_take_summary_by_takes($takes);
break;
case self::GRADE_METHOD_FIRST_ATTEMPT:
- $score = $this->get_grade_item_first_attempt($takes);
- $desc = get_string('ivs_match_config_grade_mode_first_attempt_score_label', 'ivs');
+ $matchtimingresult = $this->get_first_timing_take_summary_by_takes($takes);
break;
case self::GRADE_METHOD_LAST_ATTEMPT:
- $score = $this->get_grade_item_last_attempt($takes);
- $desc = get_string('ivs_match_config_grade_mode_last_attempt_score_label', 'ivs');
+ $matchtimingresult = $this->get_last_timing_take_summary_by_takes($takes);
break;
}
- return ['score' => round($score, 2 ), 'desc' => $desc];
+
+ return $matchtimingresult;
+ }
+
+ private function get_best_timing_take_summary_by_takes($takes){
+ $score = null;
+ foreach ($takes as $take) {
+ if (isset($take->score) && ($score === null || $take->score > $score)) {
+ $score = $take->score;
+ $besttake = $take;
+ }
+ }
+
+ $matchtimingresult = $this->get_evaluated_timing_type_result_by_take($besttake);
+
+ return $matchtimingresult;
+
+ }
+
+
+
+ private function get_average_timing_take_summary_by_takes($takes){
+
+ $numtakes = count($takes);
+ $matchtimingtakeresultavg = new MatchTimingTakeResult();
+
+ foreach ($takes as $take) {
+ $matchtimingtakeresult = $this->get_evaluated_timing_type_result_by_take($take);
+
+ $matchtimingtakeresultavg->pointsuser += $matchtimingtakeresult->pointsuser;
+ $matchtimingtakeresultavg->pointstotal += $matchtimingtakeresult->pointstotal;
+
+ foreach($matchtimingtakeresult->summary as $k => $v){
+ $matchtimingtakeresultavg->summary[$k]['timing_type'] = $v['timing_type'];
+ if (array_key_exists('sum_points', $matchtimingtakeresultavg->summary[$k])){
+ $matchtimingtakeresultavg->summary[$k]['sum_points'] += $v['sum_points'];
+ }else{
+ $matchtimingtakeresultavg->summary[$k]['sum_points'] = $v['sum_points'];
+ }
+
+ if (array_key_exists('num_correct', $matchtimingtakeresultavg->summary[$k])){
+ $matchtimingtakeresultavg->summary[$k]['num_correct'] += $v['num_correct'];
+ }else{
+ $matchtimingtakeresultavg->summary[$k]['num_correct'] = $v['num_correct'];
+ }
+ }
+
+ }
+
+ $matchtimingtakeresultavg->pointsuser = $matchtimingtakeresultavg->pointsuser / $numtakes;
+ $matchtimingtakeresultavg->pointstotal = $matchtimingtakeresultavg->pointstotal / $numtakes;
+ foreach($matchtimingtakeresultavg->summary as $k => $v){
+
+ $matchtimingtakeresultavg->summary[$k]['num_correct'] = $v['num_correct'] / $numtakes;
+ $matchtimingtakeresultavg->summary[$k]['sum_points'] = $v['num_correct'] * $v['timing_type']->score / $numtakes;
+ }
+
+ $matchtimingtakeresultavg->calculate_score();
+
+ return $matchtimingtakeresultavg;
+
+ }
+
+ private function get_first_timing_take_summary_by_takes($takes){
+ $firsttake = $takes[0];
+ $matchtimingresult = $this->get_evaluated_timing_type_result_by_take($firsttake);
+ return $matchtimingresult;
+ }
+
+ private function get_last_timing_take_summary_by_takes($takes){
+ $lasttake = end($takes);
+ $matchtimingresult = $this->get_evaluated_timing_type_result_by_take($lasttake);
+ return $matchtimingresult;
+ }
+
+ private function get_evaluated_timing_type_result_by_take($take){
+ $matchcontroller = new MoodleMatchController();
+
+ $matchtake = $matchcontroller->match_take_get_db($take->id);
+ $takeanswers = $matchcontroller->match_question_answers_get_by_take($take->id);
+ $questions = $matchcontroller->match_questions_get_by_video_db($matchtake->videoid, 'timecode', true);
+ $timingtypes = $matchcontroller->match_timing_type_get_db($matchtake->videoid);
+ $matchtimingresult = MatchTimingTakeResult::evaluate_take($timingtypes, $questions,$takeanswers);
+
+ return $matchtimingresult;
+ }
+
+ public function get_rendered_timing_take_summary($takes, $ivs) {
+ global $DB;
+ $course = $DB->get_record('course', array('id' => $ivs->course), '*', MUST_EXIST);
+ $settingscontroller = new SettingsService();
+ $activitysettings = $settingscontroller->get_settings_for_activity($ivs->id, $course->id);
+ $timingtakesummaryenabled = $activitysettings['show_timing_take_summary']->value;
+
+ if ($timingtakesummaryenabled) {
+
+ $timingtakesummary = '
';
+ $matchtimingresult = $this->ivs_gradebook_get_timing_take_summary_data_by_grade_method($takes, $ivs);
+
+ foreach ($matchtimingresult->summary as $k => $v) {
+ $timingtakesummary .= '- ' . $v['timing_type']->label . ':
' . $v['num_correct'] . ' ' . get_string('ivs_grademethod_timing_take_summary_korrekt', 'ivs') . ' (' . $v['sum_points'] . ' ' . get_string('ivs_grademethod_timing_take_summary_points', 'ivs') . ') ';
+ }
+ $timingtakesummary .= '- ' . get_string('ivs_grademethod_timing_take_summary_pointsuser', 'ivs') . ' ' . $matchtimingresult->score . '% (' . $matchtimingresult->pointsuser . ' ' . get_string('ivs_grademethod_timing_take_summary_points', 'ivs') . ')
';
+ $timingtakesummary .= '
';
+
+ return $timingtakesummary;
+
+ }else{
+ $timingtakesummary = '
' . get_string('ivs_grademethod_timing_take_summary_thanks', 'ivs') . '';
+ return $timingtakesummary;
+ }
}
+
}
diff --git a/classes/ivs_match/IIvsMatch.php b/classes/ivs_match/IIvsMatch.php
index 62f4e3c..342a59f 100644
--- a/classes/ivs_match/IIvsMatch.php
+++ b/classes/ivs_match/IIvsMatch.php
@@ -242,7 +242,7 @@ public function permission_match_question($op, $videoid, $contextid = null, $use
*/
public function assessment_config_get_by_user_and_video($userid, $videoid, $includesimulation = false);
- public function match_timing_type_get_db($ivs, $skip_access = FALSE);
+ public function match_timing_type_get_db($videoid, $skip_access = FALSE);
public function match_timing_type_insert_db($videoid, $data, $user_id = NULL, $skip_access = FALSE);
public function match_timing_type_update_db($videoid, $data, $user_id = NULL, $skip_access = FALSE);
public function match_timing_type_delete_db($videoid, $timingtypeid, $skip_access = FALSE);
diff --git a/classes/ivs_match/IvsMatchControllerBase.php b/classes/ivs_match/IvsMatchControllerBase.php
index 798ef57..9c3632a 100644
--- a/classes/ivs_match/IvsMatchControllerBase.php
+++ b/classes/ivs_match/IvsMatchControllerBase.php
@@ -33,6 +33,7 @@
use mod_ivs\ivs_match\exception\MatchQuestionNotFoundException;
use mod_ivs\ivs_match\exception\MatchTakeException;
use mod_ivs\ivs_match\exception\MatchTakeNoRemainingAttemptsException;
+use mod_ivs\ivs_match\timing\MatchTimingTakeResult;
use mod_ivs\settings\SettingsService;
/**
@@ -189,7 +190,6 @@ protected function handle_context_requests($patharguments, $method, $postdata) {
return new \mod_ivs\ivs_match\MatchResponse($take);
}elseif ($action == 'finish') {
- $this->finish_match_take_for_user($uid, $videonid, $contextid, $postdata);
$assessmentconfig = $this->ivsmatchinterface->assessment_config_get_by_user_and_video($uid, $videonid);
return new MatchResponse($assessmentconfig);
@@ -210,10 +210,16 @@ protected function handleTimingTypeRequests($patharguments, $method, $postdata)
switch (strtoupper($method)) {
case "GET":
- $ivs = $DB->get_record('ivs', array('id' => $videoid), '*', MUST_EXIST);
- $assessmentconfig = $this->ivsmatchinterface->match_timing_type_get_db($ivs);
- return new MatchResponse($assessmentconfig);
+ $timingtypes = $this->ivsmatchinterface->match_timing_type_get_db($videoid);
+
+ $response = array_map(/**
+ * @param MatchTimingType $timingtype
+ */ function ($timingtype){
+ return $timingtype->to_player_json();
+ }, $timingtypes);
+
+ return new MatchResponse($response);
case "POST":
try {
$response = $this->ivsmatchinterface->match_timing_type_insert_db($videoid, $postdata);
@@ -340,6 +346,8 @@ public function evaluate_take($takeid) {
$questions = $this->ivsmatchinterface->match_questions_get_by_video_db($matchtake->videoid, 'timecode', true);
+
+
$numanswered = 0;
$numcorrect = 0;
$numquestions = count($questions);
@@ -354,9 +362,21 @@ public function evaluate_take($takeid) {
}
}
+ //when all questions are answered, eveluate and score the take
if ($numanswered === $numquestions) {
+
+ if($matchconf->is_timing_mode()) {
+
+ $timingtypes = $this->ivsmatchinterface->match_timing_type_get_db($matchtake->videoid);
+ $matchtimingresult = MatchTimingTakeResult::evaluate_take($timingtypes, $questions,$takeanswers);
+ $matchtake->score = $matchtimingresult->score;
+
+
+ }else{
+ $matchtake->score = $numcorrect * 100 / $numanswered;
+ }
// Score.
- $matchtake->score = $numcorrect * 100 / $numanswered;
+
$matchtake->completed = time();
$matchtake->status = $matchconf->haspassed($matchtake->score) ? MatchTake::STATUS_PASSED : MatchTake::STATUS_FAILED;
@@ -374,28 +394,6 @@ public function evaluate_take($takeid) {
}
- /**
- * Calculate succeeded match timing interaction and update finished match take
- * @param $user_id
- * @param $video_id
- * @param $context_id
- * @param $post_data
- * @throws \edubreak_match\exception\MatchNoConfigException
- */
- public function finish_match_take_for_user($user_id, $video_id, $context_id, $post_data) {
-
- $take_id = $post_data['take_id'];
-
- $match_take = $this->ivsmatchinterface->match_take_get_db($take_id);
- $match_conf = $this->ivsmatchinterface->match_video_get_config_db($context_id, $video_id);
-
- $match_take->score = 100 / ($post_data['correct'] + $post_data['incorrect']) * $post_data['correct'];
- $match_take->completed = time();
- $match_take->status = $match_conf->hasPassed($match_take->score ) ? MatchTake::STATUS_PASSED : MatchTake::STATUS_FAILED;
-
- $this->ivsmatchinterface->match_take_update_db($match_take);
-
- }
/**
* Add the question to the player
@@ -484,7 +482,6 @@ public function evaluate_answer(&$answerdata, $addsolution = true, $skipaccessch
$answerdata['is_correct'] = true;
$answerdata['is_evaluated'] = false;
break;
-
}
}
diff --git a/classes/ivs_match/MatchConfig.php b/classes/ivs_match/MatchConfig.php
index 47ea395..8212b94 100644
--- a/classes/ivs_match/MatchConfig.php
+++ b/classes/ivs_match/MatchConfig.php
@@ -81,4 +81,13 @@ public function has_unlimited_attempts() {
public function hasPassed(float $score) {
return $score >= $this->rate;
}
+
+ public function is_timing_mode()
+ {
+ return $this->assessment_type === 'TIMING_TAKES';
+ }
+
+ public function is_quiz_mode(){
+ return $this->assessment_type === 'TAKES';
+ }
}
diff --git a/classes/ivs_match/MatchTimingType.php b/classes/ivs_match/MatchTimingType.php
new file mode 100644
index 0000000..1efe17b
--- /dev/null
+++ b/classes/ivs_match/MatchTimingType.php
@@ -0,0 +1,52 @@
+title = $values['title'] ?? NULL;
+ $this->id = $values['id'] ?? NULL;
+ $this->label = $values['btn']['label'] ?? NULL;
+ $this->score = $values['btn']['score'] ?? NULL;
+ $this->style = $values['btn']['style'] ?? NULL;
+ $this->description = $values['btn']['description'] ?? NULL;
+ $this->shortcut = $values['btn']['shortcut'] ?? NULL;
+ $this->position = $values['btn']['position'] ?? NULL;
+ $this->duration = $values['duration'] ?? NULL;
+ $this->weight = $values['weight'] ?? NULL;
+ }
+
+ public function to_player_json()
+ {
+ return [
+ 'btn' => [
+ 'label' => $this->label,
+ 'score' => $this->score,
+ 'style' => $this->style,
+ 'description' => $this->description,
+ 'shortcut' => $this->shortcut,
+ 'position' => $this->position,
+ ],
+ 'title' => $this->title,
+ 'id' => $this->id,
+ 'duration' => $this->duration,
+ 'weight' => $this->weight
+ ];
+ }
+}
diff --git a/classes/ivs_match/timing/MatchTimingTakeResult.php b/classes/ivs_match/timing/MatchTimingTakeResult.php
new file mode 100644
index 0000000..28bd852
--- /dev/null
+++ b/classes/ivs_match/timing/MatchTimingTakeResult.php
@@ -0,0 +1,84 @@
+pointstotal = 0;
+ $matchtimingtakeresult->pointsuser = 0;
+ $matchtimingtakeresult->score = 0;
+
+
+
+ foreach ($matchquestions as $matchquestion) {
+ $timingtype = self::find_object_by_id($matchquestion['type_data']['timing_type_id'], $matchtimingtypes);
+
+
+ if (empty($matchtimingtakeresult->summary[$timingtype->id])) {
+ $matchtimingtakeresult->summary[$timingtype->id] = [
+ 'timing_type' => $timingtype,
+ 'num_correct' => 0,
+ 'sum_points' => 0
+ ];
+ }
+ $matchtimingtakeresult->pointstotal += $timingtype->score;
+ }
+
+ foreach ($matchanswers as $questionid => $matchanswer) {
+ $question = $matchquestions[$questionid];
+
+
+ /** @var MatchTimingType $timingtype */
+ $timingtype = self::find_object_by_id($question['type_data']['timing_type_id'], $matchtimingtypes);
+
+
+
+ if (empty($timingtype)) {
+ continue;
+ }
+ if ($matchanswer['is_correct']) {
+
+ $matchtimingtakeresult->pointsuser += $timingtype->score;
+ $matchtimingtakeresult->summary[$timingtype->id]['num_correct']++;
+ $matchtimingtakeresult->summary[$timingtype->id]['sum_points'] += $timingtype->score;
+ }
+ }
+
+ $matchtimingtakeresult->calculate_score();
+
+ return $matchtimingtakeresult;
+
+ }
+
+ public function calculate_score() {
+
+ if ($this->pointstotal > 0){
+ $this->score = round($this->pointsuser / $this->pointstotal * 100, 2);
+ }
+
+ }
+
+ public static function find_object_by_id($id, $objectarray) {
+ $result = array_filter($objectarray, function ($item) use ($id) {
+ return $item->id == $id;
+ });
+
+ return end($result);
+
+ }
+
+}
diff --git a/classes/output/match/question_type_answers_view.php b/classes/output/match/question_type_answers_view.php
new file mode 100644
index 0000000..1ad8ecf
--- /dev/null
+++ b/classes/output/match/question_type_answers_view.php
@@ -0,0 +1,117 @@
+.
+
+/**
+ * Output class for rendering questions answers
+ * @package mod_ivs
+ * @author Ghostthinker GmbH
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright (C) 2017 onwards Ghostthinker GmbH (https://ghostthinker.de/)
+ */
+
+// Standard GPL and phpdocs.
+namespace mod_ivs\output\match;
+
+use mod_ivs\MoodleMatchController;
+use moodle_url;
+use renderable;
+use renderer_base;
+use templatable;
+use stdClass;
+use tool_langimport\controller;
+
+/**
+ * Class question_type_answers_view
+ *
+ */
+class question_type_answers_view implements renderable, templatable {
+
+ /**
+ * @var array|null
+ */
+ public $detailarray = null;
+
+ /**
+ * @var array|null
+ */
+ public $questions = null;
+
+ /**
+ * @var int|null
+ */
+ public $cmid = null;
+
+ /**
+ * @var int
+ */
+ protected $videoid;
+
+ /**
+ * @var array
+ */
+ protected $courseusers;
+
+ /**
+ * @var int
+ */
+ protected $totalcount;
+
+ /**
+ * question_type_answers_view constructor.
+ *
+ * @param array $array
+ * @param array $questions
+ * @param int $cmid
+ * @param int $videoid
+ * @param array $courseusers
+ * @param int $totalcount
+ */
+ public function __construct($array, $questions, $cmid, $videoid, $courseusers, $totalcount) {
+ $this->detailarray = $array;
+ $this->questions = $questions;
+ $this->cmid = $cmid;
+ $this->videoid = $videoid;
+ $this->courseusers = $courseusers;
+ $this->totalcount = $totalcount;
+ }
+
+ /**
+ * Render mustache template
+ * @param \renderer_base $output
+ *
+ * @return \stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+
+ $instance = $this->videoid;
+
+ $controller = new MoodleMatchController();
+ $data = $controller->get_question_answers_data($this->detailarray, $this->questions, $this->cmid, $this->videoid,
+ $this->courseusers, $this->totalcount, $output);
+
+ if (empty($data)) {
+ return null;
+ }
+
+ $qid = $data->id;
+ $data->download_options = $output->download_dataformat_selector(get_string('ivs_match_download_summary_label', 'ivs'),
+ 'question_type_answers_download.php', 'download',
+ array('qid' => $qid, 'cmid' => $this->cmid, 'instance_id' => $instance,
+ 'total_count' => $this->totalcount));
+
+ return $data;
+ }
+}
diff --git a/classes/output/match/question_type_overview.php b/classes/output/match/question_type_overview.php
new file mode 100644
index 0000000..63e9452
--- /dev/null
+++ b/classes/output/match/question_type_overview.php
@@ -0,0 +1,106 @@
+.
+
+/**
+ * Output class for rendering the question overview
+ * @package mod_ivs
+ * @author Ghostthinker GmbH
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright (C) 2017 onwards Ghostthinker GmbH (https://ghostthinker.de/)
+ */
+
+// Standard GPL and phpdocs.
+namespace mod_ivs\output\match;
+
+use renderable;
+use renderer_base;
+use templatable;
+use stdClass;
+
+/**
+ * Class question_type_overview
+ *
+ */
+class question_type_overview implements renderable, templatable {
+
+ /**
+ * @var null
+ */
+ public $timingtype = null;
+
+ /**
+ * @var null
+ */
+ public $module = null;
+
+ /**
+ * question_overview constructor.
+ *
+ * @param array $timingtype
+ * @param stdClass $module
+ */
+ public function __construct($timingtype, $module) {
+ $this->timingtype = $timingtype;
+ $this->module = $module;
+ }
+
+ /**
+ * Render mustache template
+ * @param \renderer_base $output
+ *
+ * @return \stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+ $data = new stdClass();
+ $data->id = $this->timingtype->id;
+
+ $this->timingtype->title = str_replace('\[', '$$', $this->timingtype->title);
+ $this->timingtype->title = str_replace('\]', '$$', $this->timingtype->title);
+ $this->timingtype->title = str_replace('\(', '$', $this->timingtype->title);
+ $this->timingtype->title = str_replace('\)', '$', $this->timingtype->title);
+
+ $this->timingtype->description = str_replace('\[', '$$', $this->timingtype->description);
+ $this->timingtype->description = str_replace('\]', '$$', $this->timingtype->description);
+ $this->timingtype->description = str_replace('\(', '$', $this->timingtype->description);
+ $this->timingtype->description = str_replace('\)', '$', $this->timingtype->description);
+
+ // We need this, because he dont apply mathjax when no $$ exists.
+ if (!strpos($this->timingtype->title, '$$')) {
+ $this->timingtype->title .= ' $$ $$';
+ }
+
+ // We need this, because he dont apply mathjax when no $$ exists.
+ if (!strpos($this->timingtype->description, '$$')) {
+ $this->timingtype->description .= ' $$ $$';
+ }
+
+ $data->question = format_text($this->timingtype->title, FORMAT_MARKDOWN);
+ if (strlen($this->timingtype->title) > 0) {
+ $data->question = ''. format_text($this->timingtype->title, FORMAT_MARKDOWN) . ': ' . format_text($this->timingtype->description, FORMAT_MARKDOWN) . '
';
+ }
+
+ $data->link = new \moodle_url('/mod/ivs/question_type_answers.php',
+ array(
+ 'id' => $this->module->id,
+ 'vid' => $this->module->id,
+ 'qid' => $data->id,
+ 'perpage' => 10
+ )
+ );
+
+ return $data;
+ }
+}
diff --git a/classes/output/match/timing_question_answer_view.php b/classes/output/match/timing_question_answer_view.php
new file mode 100644
index 0000000..74ca86e
--- /dev/null
+++ b/classes/output/match/timing_question_answer_view.php
@@ -0,0 +1,70 @@
+.
+
+/**
+ * Output class for rendering the click answers
+ * @package mod_ivs
+ * @author Ghostthinker GmbH
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright (C) 2017 onwards Ghostthinker GmbH (https://ghostthinker.de/)
+ */
+
+// Standard GPL and phpdocs.
+namespace mod_ivs\output\match;
+
+use mod_ivs\IvsHelper;
+use mod_ivs\MoodleMatchController;
+use renderable;
+use renderer_base;
+use templatable;
+use stdClass;
+use tool_langimport\controller;
+
+/**
+ * Class timing_question_answer_view
+ *
+ */
+class timing_question_answer_view implements renderable, templatable {
+
+ /**
+ * @var array|null
+ */
+ public $answer = null;
+
+ /**
+ * timing_type_answer_view constructor.
+ *
+ * @param array $answer
+ * @param stdClass $courseuser
+ */
+ public function __construct($answer, $courseuser) {
+ $this->answer = $answer;
+ $this->course_user = $courseuser;
+ }
+
+ /**
+ * Render mustache template
+ * @param \renderer_base $output
+ *
+ * @return \stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+
+ $controller = new MoodleMatchController();
+
+ return $controller->get_question_answers_data_timing_question($this->answer, $this->course_user);
+ }
+}
diff --git a/classes/output/match/timing_type_answer_view.php b/classes/output/match/timing_type_answer_view.php
new file mode 100644
index 0000000..5d2aa19
--- /dev/null
+++ b/classes/output/match/timing_type_answer_view.php
@@ -0,0 +1,70 @@
+.
+
+/**
+ * Output class for rendering the click answers
+ * @package mod_ivs
+ * @author Ghostthinker GmbH
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright (C) 2017 onwards Ghostthinker GmbH (https://ghostthinker.de/)
+ */
+
+// Standard GPL and phpdocs.
+namespace mod_ivs\output\match;
+
+use mod_ivs\IvsHelper;
+use mod_ivs\MoodleMatchController;
+use renderable;
+use renderer_base;
+use templatable;
+use stdClass;
+use tool_langimport\controller;
+
+/**
+ * Class timing_type_answer_view
+ *
+ */
+class timing_type_answer_view implements renderable, templatable {
+
+ /**
+ * @var array|null
+ */
+ public $answer = null;
+
+ /**
+ * timing_type_answer_view constructor.
+ *
+ * @param array $answer
+ * @param stdClass $courseuser
+ */
+ public function __construct($answer, $courseuser) {
+ $this->answer = $answer;
+ $this->course_user = $courseuser;
+ }
+
+ /**
+ * Render mustache template
+ * @param \renderer_base $output
+ *
+ * @return \stdClass
+ */
+ public function export_for_template(renderer_base $output) {
+
+ $controller = new MoodleMatchController();
+
+ return $controller->get_question_answers_data_timing_type($this->answer, $this->course_user);
+ }
+}
diff --git a/classes/output/renderer.php b/classes/output/renderer.php
index 9852297..5ca8cc5 100644
--- a/classes/output/renderer.php
+++ b/classes/output/renderer.php
@@ -78,6 +78,17 @@ public function render_question_overview($page) {
return parent::render_from_template('ivs/question_view', $data);
}
+ /**
+ * Define renderer for template
+ * @param index_page $page
+ *
+ * @return mixed
+ */
+ public function render_question_type_overview($page) {
+ $data = $page->export_for_template($this);
+ return parent::render_from_template('ivs/question_view', $data);
+ }
+
/**
* Define renderer for template
* @param index_page $page
@@ -89,6 +100,16 @@ public function render_question_answers_view($page) {
return parent::render_from_template('ivs/question_answers_view', $data);
}
+ /**
+ * Define renderer for template
+ * @param index_page $page
+ *
+ * @return mixed
+ */
+ public function render_question_type_answers_view($page) {
+ $data = $page->export_for_template($this);
+ return parent::render_from_template('ivs/question_type_answers_view', $data);
+ }
/**
* Define renderer for template
* @param index_page $page
@@ -122,6 +143,28 @@ public function render_question_single_choice_answer_view($page) {
return parent::render_from_template('ivs/question_single_choice_answer_view', $data);
}
+ /**
+ * Define renderer for template
+ * @param index_page $page
+ *
+ * @return mixed
+ */
+ public function render_timing_type_answer_view($page) {
+ $data = $page->export_for_template($this);
+ return parent::render_from_template('ivs/timing_type_answer_view', $data);
+ }
+
+ /**
+ * Define renderer for template
+ * @param index_page $page
+ *
+ * @return mixed
+ */
+ public function render_timing_question_answer_view($page) {
+ $data = $page->export_for_template($this);
+ return parent::render_from_template('ivs/timing_question_answer_view', $data);
+ }
+
/**
* Define renderer for template
* @param index_page $page
diff --git a/classes/output/statistics_view.php b/classes/output/statistics_view.php
index 1d4c831..0422da1 100644
--- a/classes/output/statistics_view.php
+++ b/classes/output/statistics_view.php
@@ -72,7 +72,9 @@ public function export_for_template(renderer_base $output) {
$statisticdataobject->num_ivs_match_questions_types_click_question_label = 'Click';
$statisticdataobject->num_ivs_match_questions_types_click_question = $statisticdata['num_ivs_match_questions_types']['click_question'];
$statisticdataobject->num_ivs_match_questions_types_text_question_label = 'Text';
- $statisticdataobject->num_ivs_match_questions_types_text_question = $statisticdata['num_ivs_match_questions_types']['text_question'];
+ $statisticdataobject->num_ivs_match_questions_types_text_question_label = 'Timing';
+ $statisticdataobject->num_ivs_match_questions_types_timing_question = $statisticdata['num_ivs_match_questions_types']['timing_question'];
+ $statisticdataobject->num_ivs_match_questions_types_timing_question = $statisticdata['num_ivs_match_questions_types']['timing_question'];
$statisticdataobject->num_ivs_match_takes_label = get_string('ivs_match_takes_label', 'ivs');
$statisticdataobject->num_ivs_match_takes = $statisticdata['num_ivs_match_takes'];
$statisticdataobject->num_ivs_videohosts_label = get_string('ivs_videohosts_label', 'ivs');
diff --git a/classes/settings/SettingsDefinition.php b/classes/settings/SettingsDefinition.php
index 26c7cd9..052ca24 100644
--- a/classes/settings/SettingsDefinition.php
+++ b/classes/settings/SettingsDefinition.php
@@ -90,12 +90,12 @@ class SettingsDefinition {
* @var string
*/
- const SETTING_PLAYER_SHOW_VIDEOTEST_FEEDBACK = 'show_videotest_feedback';
+ const SETTING_PLAYER_SHOW_REALTIME_RESULTS = 'show_realtime_results';
/**
* @var string
*/
- const SETTING_PLAYER_SHOW_VIDEOTEST_SOLUTION = 'show_videotest_solution';
+ const SETTING_PLAYER_SHOW_TIMING_TAKE_SUMMARY = 'show_timing_take_summary';
/**
* @var string
diff --git a/classes/settings/SettingsService.php b/classes/settings/SettingsService.php
index d3dc05b..892dfee 100644
--- a/classes/settings/SettingsService.php
+++ b/classes/settings/SettingsService.php
@@ -240,17 +240,17 @@ public static function ivs_get_player_advanced_match_settings() {
true,
true);
$settings[] = new SettingsDefinition(
- SettingsDefinition::SETTING_PLAYER_SHOW_VIDEOTEST_FEEDBACK,
- get_string('ivs_setting_player_show_videotest_feedback', 'ivs'),
- 'ivs_setting_player_show_videotest_feedback',
+ SettingsDefinition::SETTING_PLAYER_SHOW_REALTIME_RESULTS,
+ get_string('ivs_setting_player_show_realtime_results', 'ivs'),
+ 'ivs_setting_player_show_realtime_results',
'checkbox',
0,
true,
true);
$settings[] = new SettingsDefinition(
- SettingsDefinition::SETTING_PLAYER_SHOW_VIDEOTEST_SOLUTION,
- get_string('ivs_setting_player_show_videotest_solution', 'ivs'),
- 'ivs_setting_player_show_videotest_solution',
+ SettingsDefinition::SETTING_PLAYER_SHOW_TIMING_TAKE_SUMMARY,
+ get_string('ivs_setting_player_show_timing_take_summary', 'ivs'),
+ 'ivs_setting_player_show_timing_take_summary',
'checkbox',
1,
true,
diff --git a/db/upgrade.php b/db/upgrade.php
index 3550e18..a6d2d23 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -112,6 +112,15 @@ function xmldb_ivs_upgrade($oldversion) {
upgrade_mod_savepoint(true, 2022110800, 'ivs');
}
+ // Change setting score type from int to varchar.
+ if ($oldversion < 2023022000) {
+ $table = new xmldb_table('ivs_matchtake');
+ $field = new xmldb_field('score', XMLDB_TYPE_FLOAT, null, null, null, null, 0, null);
+ $dbman->change_field_type($table, $field);
+
+ upgrade_mod_savepoint(true, 2023022000, 'ivs');
+ }
+
return true;
}
diff --git a/grade.php b/grade.php
index 66a647a..02f686b 100644
--- a/grade.php
+++ b/grade.php
@@ -34,7 +34,7 @@
$moodlematchcontroller = new \mod_ivs\MoodleMatchController();
if ($moodlematchcontroller->has_edit_access($cm->instance)){
- redirect(new moodle_url('/mod/ivs/questions.php', array('id' => $id), 'question-types'));
+ redirect(new moodle_url('/mod/ivs/questions.php', array('id' => $id), 'questions'));
}
redirect('view.php?id=' . $id);
diff --git a/lang/de/ivs.php b/lang/de/ivs.php
index c7b45b6..c3b55dc 100644
--- a/lang/de/ivs.php
+++ b/lang/de/ivs.php
@@ -175,7 +175,7 @@
$string['ivs_setting_match_question_help'] =
"Im Quiz-Modus verwenden Sie verschiedene Fragetypen (u.a. Single-/Multiple-Choice-Fragen und Freitext-Fragen), um das Vorwissen und das Verständnis der Lernenden direkt im Video zu testen. Wenn die Frage erscheint, wird das Video angehalten und die Lernenden können erst fortfahren, wenn die Frage (richtig) beantwortet wurde.
-Im Timing-Modus werden Kenntnis und Reaktion zu den gezeigten Videoszenen getestet. Diese können über Schaltflächen durch die Lernenden zum richtigen Zeitpunkt idenzifiziert werden.";
+Im Timing-Modus werden Kenntnis und Reaktion zu den gezeigten Videoszenen getestet. Diese können über Schaltflächen durch die Lernenden zum richtigen Zeitpunkt identifiziert werden.";
$string['ivs:edit_match_questions'] = "Video Test bearbeiten";
$string['ivs:create_match_answers'] = "Video Test-Antworten erstellen";
$string['ivs:access_match_reports'] = "Video Test-Reports einsehen";
@@ -395,7 +395,7 @@
$string['ivs_grademethod_last_attempt'] = 'Letzter Versuch';
$string['ivs_gradepass'] = 'Bestehensgrenze';
$string['ivs_attempts'] = 'Erlaubte Versuche';
-$string['ivs_gradepass_help'] = 'Diese Option legt die erforderliche Punktzahl fest, die für das Bestehen erreicht werden müssen. Der Wert wird beim Aktivtäts- und Kursabschluss verwendet';
+$string['ivs_gradepass_help'] = 'Diese Option legt die erforderliche prozentualen Anteil fest, der für das Bestehen erreicht werden muss. Der Wert wird beim Aktivtäts- und Kursabschluss verwendet';
$string['ivs_attempts_help'] = 'Wie oft dürfen die Nutzer:innen den Video-Test durchführen?';
$string['ivs_grademethod_help'] = 'Wenn mehrere Versuche erlaubt sind, sind die folgenden Methoden zur Berechnung der endgültigen Testbewertung verfügbar:
@@ -407,19 +407,28 @@
$string['ivs_match_config_assessment_mode_none'] = "Deaktiviert";
$string['ivs_match_config_assessment_mode_quiz'] = "Quiz-Modus";
$string['ivs_match_config_assessment_mode_timing'] = "Timing-Modus";
-$string['ivs_match_config_grade_mode_best_score_label'] = "Bestes Ergebnis ist ";
-$string['ivs_match_config_grade_mode_average_score_label'] = "Durchschnittliches Ergebnis ist ";
-$string['ivs_match_config_grade_mode_first_attempt_score_label'] = "Ergebnis des ersten Durchgangs ist ";
-$string['ivs_match_config_grade_mode_last_attempt_score_label'] = "Ergebnis des letzten Durchgangs ist ";
$string['ivs_match_config_status_passed_label'] = "Bestanden - ";
$string['ivs_match_config_status_not_started_label'] = "Nicht gestartet";
$string['ivs_match_config_status_failed_label'] = "Fehlgeschlagen, keine Versuche übrig - ";
-$string['ivs_match_config_status_progress_label'] = "Laufender Versuch";
+$string['ivs_match_config_status_progress_label'] = "Versuch läuft";
$string['ivs_match_config_status_not_passed_label'] = "Nicht bestanden - ";
$string['ivs_setting_player_controls_enabled'] = "Erlaube Navigieren im Video-Test";
$string['ivs_setting_player_controls_enabled_help'] = "Festlegen, ob Nutzer:innen innerhalb des Videos vor- oder zurücknavigieren dürfen.";
-$string['ivs_setting_player_show_videotest_feedback'] = "Ergebnisse für Lernende direkt anzeigen";
-$string['ivs_setting_player_show_videotest_feedback_help'] = "Ergebnisse für Lernende direkt anzeigen wenn eine Antwort abgegeben wurde.";
-$string['ivs_setting_player_show_videotest_solution'] = "Zusammenfassung der Ergebnisse für Lernende am Ende anzeigen";
-$string['ivs_setting_player_show_videotest_solution_help'] = "Zusammenfassung der Ergebnisse für Lernende am Ende anzeigen";
-
+$string['ivs_setting_player_show_realtime_results'] = "Ergebnisse für Lernende direkt anzeigen";
+$string['ivs_setting_player_show_realtime_results_help'] = "Ergebnisse für Lernende direkt anzeigen wenn eine Antwort abgegeben wurde.";
+$string['ivs_setting_player_show_timing_take_summary'] = "Zusammenfassung der Ergebnisse für Lernende am Ende anzeigen";
+$string['ivs_setting_player_show_timing_take_summary_help'] = "Zusammenfassung der Ergebnisse für Lernende am Ende anzeigen";
+$string['ivs_match_config_timing_status_not_passed_label'] = "Nicht bestanden";
+$string['ivs_match_config_timing_status_passed_label'] = "Bestanden";
+$string['ivs_setting_grade_to_pass_validation'] = "Bitte nur Werte zwischen 0 und 100 eingeben";
+$string['ivs_grademethod_timing_take_summary_korrekt'] = "mal korrekt";
+$string['ivs_grademethod_timing_take_summary_points'] = "Punkte";
+$string['ivs_grademethod_timing_take_summary_pointsuser'] = "Ergebnis:";
+$string['ivs_grademethod_timing_take_summary_thanks'] = "Vielen Dank für Ihre Teilnahme";
+$string['ivs_match_question_summary_question_type_timing'] = "Timing Frage";
+$string['ivs_match_question_answer_menu_label_elements_per_questions_types'] = "Fragen Typen";
+$string['ivs_match_download_question_type_details_label'] = "Alle Fragentypresultate herunterladen";
+$string['ivs_match_question_answer_menu_label_first_timing_answer'] = "Erster Versuch: Korrekt / Gesamt";
+$string['ivs_match_question_answer_menu_label_last_timing_answer'] = "Letzer Versuch: Korrekt / Gesamt";
+$string['ivs_match_question_timing_type_id'] = "Timing Typ ID";
+$string['ivs_match_question_timing_type_id_label'] = "Timing Typ";
diff --git a/lang/en/ivs.php b/lang/en/ivs.php
index 40b740c..25ace74 100644
--- a/lang/en/ivs.php
+++ b/lang/en/ivs.php
@@ -417,10 +417,6 @@
$string['ivs_match_config_assessment_mode_none'] = "Deactivated";
$string['ivs_match_config_assessment_mode_quiz'] = "Quiz-Mode";
$string['ivs_match_config_assessment_mode_timing'] = "Timing-Mode";
-$string['ivs_match_config_grade_mode_best_score_label'] = "Best score is ";
-$string['ivs_match_config_grade_mode_average_score_label'] = "Average score is ";
-$string['ivs_match_config_grade_mode_first_attempt_score_label'] = "First attempt score is ";
-$string['ivs_match_config_grade_mode_last_attempt_score_label'] = "Last attempt score is ";
$string['ivs_match_config_status_passed_label'] = "Passed - ";
$string['ivs_match_config_status_not_started_label'] = "Not startet";
$string['ivs_match_config_status_failed_label'] = "Failed, no attempts left - ";
@@ -428,7 +424,20 @@
$string['ivs_match_config_status_not_passed_label'] = "Not passed - ";
$string['ivs_setting_player_controls_enabled'] = "Allow navigation in the video test";
$string['ivs_setting_player_controls_enabled_help'] = "Specify whether users can navigate forwards or backwards within the video.";
-$string['ivs_setting_player_show_videotest_feedback'] = "Show results for learners directly";
-$string['ivs_setting_player_show_videotest_feedback_help'] = "Show results for learners directly after answering a video test question.";
-$string['ivs_setting_player_show_videotest_solution'] = "Show summary of results for learners at the end";
-$string['ivs_setting_player_show_videotest_solution_help'] = "Show summary of results for learners at the end";
+$string['ivs_setting_player_show_realtime_results'] = "Show results for learners directly";
+$string['ivs_setting_player_show_realtime_results_help'] = "Show results for learners directly after answering a video test question.";
+$string['ivs_setting_player_show_timing_take_summary'] = "Show summary of results for learners at the end";
+$string['ivs_setting_player_show_timing_take_summary_help'] = "Show summary of results for learners at the end";
+$string['ivs_match_config_timing_status_not_passed_label'] = "Not passed";
+$string['ivs_match_config_timing_status_passed_label'] = "passed";
+$string['ivs_setting_grade_to_pass_validation'] = "Only values between 0 and 100 are allowed";
+$string['ivs_grademethod_timing_take_summary_korrekt'] = "times correct";
+$string['ivs_grademethod_timing_take_summary_points'] = "Points";
+$string['ivs_grademethod_timing_take_summary_pointsuser'] = "Result:";
+$string['ivs_grademethod_timing_take_summary_thanks'] = "Thank you for your participation";
+$string['ivs_match_question_summary_question_type_timing'] = "Timing Question";
+$string['ivs_match_question_answer_menu_label_elements_per_questions_types'] = "Question types";
+$string['ivs_match_question_answer_menu_label_first_timing_answer'] = "First attempt: correct / total";
+$string['ivs_match_question_answer_menu_label_last_timing_answer'] = "Last attempt: correct / total";
+$string['ivs_match_question_timing_type_id'] = "Timing Type ID";
+$string['ivs_match_question_timing_type_id_label'] = "Timing Type";
diff --git a/lib.php b/lib.php
index 3755ffd..59e1522 100644
--- a/lib.php
+++ b/lib.php
@@ -741,11 +741,11 @@ function ivs_update_grades($ivs, $take, $nullifnone=true){
$takes = $moodlematchcontroller->match_takes_get_by_user_and_video_db($take->userid, $take->videoid, $take->videoid);
if ($takes) {
- $scoreinfo = $gradebookservice->ivs_gradebook_get_score_info_by_takes($takes, $ivs);
+ $score = $gradebookservice->ivs_gradebook_get_score_by_takes($takes, $ivs);
$grade = new stdClass();
$grade->userid = $take->userid;
- $grade->rawgrade = $scoreinfo['score'];
+ $grade->rawgrade = $score;
ivs_grade_item_update($ivs, $grade);
}
diff --git a/mod_form.php b/mod_form.php
index 0b95da8..ae7c218 100644
--- a/mod_form.php
+++ b/mod_form.php
@@ -251,6 +251,10 @@ public function validation($data, $files) {
}
}
+ if ($data['videotest_grade_to_pass']['value'] > 100 || $data['videotest_grade_to_pass']['value'] < 0) {
+ $errors['range'] = get_string('ivs_setting_grade_to_pass_validation', 'mod_ivs');
+ \core\notification::error(get_string('ivs_setting_grade_to_pass_validation', 'mod_ivs'));
+ }
return $errors;
}
diff --git a/question_answers_download.php b/question_answers_download.php
index d3724c9..a04edce 100644
--- a/question_answers_download.php
+++ b/question_answers_download.php
@@ -147,6 +147,31 @@
);
}
break;
+ case get_string('ivs_match_question_summary_question_type_timing', 'ivs'):
+
+ $data[] = array(
+ 'col_1' => get_string("ivs_match_question_answer_menu_label_name", 'ivs'),
+ 'col_2' => get_string("ivs_match_question_answer_menu_label_user_id", 'ivs'),
+ 'col_3' => get_string("ivs_match_question_answer_menu_label_first_click_answer", 'ivs'),
+ 'col_4' => get_string("ivs_match_question_answer_menu_label_click_retries", 'ivs'),
+ 'col_5' => get_string("ivs_match_question_answer_menu_label_last_click_answer", 'ivs'),
+ );
+
+ foreach ($answersdata->answers as $answer) {
+
+ $answerdetails = $controller->get_question_answers_data_timing_question($answer->answer, $answer->course_user);
+
+ $data[] = array(
+ 'col_1' => $answerdetails->fullname,
+ 'col_2' => $answerdetails->id,
+ 'col_3' => strip_tags($answerdetails->first),
+ 'col_4' => $answerdetails->retries,
+ 'col_5' => strip_tags($answerdetails->last),
+ );
+ }
+
+ break;
+
}
$filename = clean_filename($course->shortname . get_string('ivs_match_question_export_question_filename', 'ivs') . $questionid);
diff --git a/question_type_answers.php b/question_type_answers.php
new file mode 100644
index 0000000..53b8ba2
--- /dev/null
+++ b/question_type_answers.php
@@ -0,0 +1,111 @@
+.
+
+/**
+ * Render all answers from match takes.
+ *
+ * @package mod_ivs
+ * @author Ghostthinker GmbH
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright (C) 2017 onwards Ghostthinker GmbH (https://ghostthinker.de/)
+ */
+
+require_once('../../config.php');
+
+define('DEFAULT_PAGE_SIZE', 10);
+
+// Pager, sort and settings.
+$page = optional_param('page', 0, PARAM_INT); // Which page to show.
+$perpage = required_param('perpage', PARAM_INT);
+
+$qid = required_param('qid', PARAM_ALPHANUMEXT);
+$cmid = required_param('id', PARAM_INT);
+$cm = get_coursemodule_from_id('ivs', $cmid, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+require_login($course, true, $cm);
+
+$ivs = $DB->get_record('ivs', array('id' => $cm->instance), '*', MUST_EXIST);
+
+$context = \context_module::instance($cmid);
+
+require_login($course, true, $cm);
+
+// Only allow if permission is correct.
+if (!has_capability('mod/ivs:access_match_reports', $context)) {
+ throw new moodle_exception('accessdenied', 'admin');
+}
+
+$PAGE->set_url('/mod/ivs/question_type_answers.php',
+ array('id' => $cm->id, 'qid' => $qid, 'vid' => $cm->instance, 'perpage' => $perpage));
+
+$heading = get_string('ivs:view:question_overview', 'ivs');
+
+$PAGE->set_title($heading);
+$PAGE->set_heading($course->fullname);
+
+$PAGE->requires->css(new moodle_url($CFG->httpswwwroot . '/mod/ivs/templates/question_answers_view.css'));
+$PAGE->requires->jquery();
+
+$controller = new \mod_ivs\MoodleMatchController();
+$courseservice = new \mod_ivs\CourseService();
+$rolestudent = $DB->get_record('role', array('shortname' => 'student'));
+$coursestudents = $courseservice->get_course_membersby_role($course->id, $rolestudent->id);
+
+// Breadcrumb.
+$PAGE->navbar->add(get_string('ivs:view:question_overview', 'ivs'), new moodle_url('/mod/ivs/questions.php?id=' . $cm->id));
+$currenttimingtype = $controller->match_timing_get_current_timing_type($cm->instance, $qid);
+
+$PAGE->navbar->add($currenttimingtype->title .($currenttimingtype->description ? ': ' . $currenttimingtype->description : '') ,
+ new moodle_url('/mod/ivs/question_type_answers.php?id=' . $cm->id . '&vid=' . $cm->instance . '&qid=' . $qid . '&perpage=' .
+ $perpage));
+
+echo $OUTPUT->header();
+
+$offset = $page * $perpage;
+
+$renderer = $PAGE->get_renderer('ivs');
+
+echo '';
+echo '
' . $heading . '
';
+
+$useranswers = [];
+foreach ($coursestudents as $user) {
+ $useranswers[] = $controller->match_question_answers_get_by_timing_type_and_user_for_reporting($currenttimingtype, $user->id, $cm->instance);
+}
+
+$totalcount = count($coursestudents);
+
+$questions = $controller->match_questions_get_by_video_db($cm->instance);
+?>
+
+
+ id, $cm->instance,
+ $coursestudents, $totalcount);
+ echo $renderer->render($renderable);
+ ?>
+
+
+ $perpage) {
+ echo $OUTPUT->paging_bar($totalcount, $page, $perpage, $PAGE->url);
+}
+
+echo $OUTPUT->footer();
diff --git a/question_type_answers_download.php b/question_type_answers_download.php
new file mode 100644
index 0000000..14e3e9d
--- /dev/null
+++ b/question_type_answers_download.php
@@ -0,0 +1,184 @@
+.
+
+/**
+ * Download the questions answers
+ * @package mod_ivs
+ * @author Ghostthinker GmbH
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright (C) 2017 onwards Ghostthinker GmbH (https://ghostthinker.de/)
+ */
+
+require_once('../../config.php');
+require_once($CFG->libdir . '/dataformatlib.php');
+
+use mod_ivs\MoodleMatchController;
+use mod_ivs\CourseService;
+
+$dataformat = optional_param('download', '', PARAM_ALPHA);
+
+$qid = optional_param('qid', '', PARAM_ALPHANUM);
+$cmid = optional_param('cmid', '', PARAM_INT);
+$instanceid = optional_param('instance_id', '', PARAM_ALPHANUM);
+$totalcount = optional_param('total_count', '', PARAM_ALPHANUM);
+
+$cm = get_coursemodule_from_id('ivs', $cmid, 0, false, MUST_EXIST);
+$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST);
+
+require_login($course, true, $cm);
+
+$courseservice = new CourseService();
+$rolestudent = $DB->get_record('role', array('shortname' => 'student'));
+$coursestudents = $courseservice->get_course_membersby_role($course->id, $rolestudent->id);
+
+$controller = new MoodleMatchController();
+$currenttimingtype = $controller->match_timing_get_current_timing_type($cm->instance, $qid);
+
+$useranswers = [];
+foreach ($coursestudents as $user) {
+ $useranswers[] = $controller->match_question_answers_get_by_timing_type_and_user_for_reporting($currenttimingtype, $user->id, $instanceid);
+}
+
+$questions = $controller->match_questions_get_by_video_db_order($instanceid);
+
+$answersdata = $controller->get_question_answers_data(array_values($useranswers), $questions, $cmid, $instanceid, $coursestudents,
+ $totalcount, null);
+
+$columns = array(
+ 'col_1' => get_string("ivs_match_question_header_id_label", 'ivs') . $answersdata->id,
+ 'col_2' => get_string("ivs_match_question_header_type_label", 'ivs') . $answersdata->question_type,
+ 'col_3' => get_string("ivs_match_question_header_title_label", 'ivs') . strip_tags($answersdata->label)
+);
+
+$data[] = array(
+ 'col_1' => get_string("ivs_match_question_header_question_label", 'ivs') . strip_tags($answersdata->question),
+);
+
+switch ($answersdata->question_type) {
+ case get_string('ivs_match_question_summary_question_type_single', 'ivs'):
+
+ $data[] = array(
+ 'col_1' => get_string("ivs_match_question_answer_menu_label_name", 'ivs'),
+ 'col_2' => get_string("ivs_match_question_answer_menu_label_user_id", 'ivs'),
+ 'col_3' => get_string("ivs_match_question_answer_menu_label_first_single_choice_answer", 'ivs'),
+ 'col_4' => get_string("ivs_match_question_answer_menu_label_first_single_choice_answer", 'ivs'),
+ 'col_5' => get_string("ivs_match_question_answer_menu_label_single_choice_retries", 'ivs'),
+ 'col_6' => get_string("ivs_match_question_answer_menu_label_last_single_choice_answer", 'ivs'),
+ );
+
+ $data[] = array(
+ 'col_1' => '',
+ 'col_2' => '',
+ 'col_3' => get_string("ivs_match_question_answer_menu_label_single_choice_correct", 'ivs'),
+ 'col_4' => get_string("ivs_match_question_answer_menu_label_last_single_choice_selected_answer", 'ivs'),
+ 'col_5' => '',
+ 'col_6' => '',
+ );
+
+ foreach ($answersdata->answers as $answer) {
+
+ $answerdetails = $controller->get_question_answers_data_single_choice_question($answer->answer, $answer->course_user);
+
+ $data[] = array(
+ 'col_1' => $answerdetails->fullname,
+ 'col_2' => $answerdetails->id,
+ 'col_3' => $answerdetails->correct,
+ 'col_4' => strip_tags($answerdetails->selected_answer),
+ 'col_5' => $answerdetails->retries,
+ 'col_6' => strip_tags($answerdetails->last),
+ );
+ }
+
+ break;
+ case get_string('ivs_match_question_summary_question_type_click', 'ivs'):
+
+ $data[] = array(
+ 'col_1' => get_string("ivs_match_question_answer_menu_label_name", 'ivs'),
+ 'col_2' => get_string("ivs_match_question_answer_menu_label_user_id", 'ivs'),
+ 'col_3' => get_string("ivs_match_question_answer_menu_label_first_click_answer", 'ivs'),
+ 'col_4' => get_string("ivs_match_question_answer_menu_label_click_retries", 'ivs'),
+ 'col_5' => get_string("ivs_match_question_answer_menu_label_last_click_answer", 'ivs'),
+ );
+
+ foreach ($answersdata->answers as $answer) {
+
+ $answerdetails = $controller->get_question_answers_data_click_question($answer->answer, $answer->course_user);
+
+ $data[] = array(
+ 'col_1' => $answerdetails->fullname,
+ 'col_2' => $answerdetails->id,
+ 'col_3' => strip_tags($answerdetails->first),
+ 'col_4' => $answerdetails->retries,
+ 'col_5' => strip_tags($answerdetails->last),
+ );
+ }
+
+ break;
+ case get_string('ivs_match_question_summary_question_type_text', 'ivs'):
+
+ $data[] = array(
+ 'col_1' => get_string("ivs_match_question_answer_menu_label_name", 'ivs'),
+ 'col_2' => get_string("ivs_match_question_answer_menu_label_user_id", 'ivs'),
+ 'col_3' => get_string("ivs_match_question_answer_menu_label_first_text_answer", 'ivs'),
+ 'col_4' => get_string("ivs_match_question_answer_menu_label_last_text_answer", 'ivs'),
+ );
+
+ foreach ($answersdata->answers as $answer) {
+
+ $answerdetails = $controller->get_question_answers_data_text_question($answer->answer, $answer->course_user);
+
+ $data[] = array(
+ 'col_1' => $answerdetails->fullname,
+ 'col_2' => $answerdetails->id,
+ 'col_3' => strip_tags($answerdetails->first),
+ 'col_4' => strip_tags($answerdetails->last),
+ );
+ }
+ break;
+ case get_string('ivs_match_question_summary_question_type_timing', 'ivs'):
+
+ $data[] = array(
+ 'col_1' => get_string("ivs_match_question_answer_menu_label_name", 'ivs'),
+ 'col_2' => get_string("ivs_match_question_answer_menu_label_user_id", 'ivs'),
+ 'col_3' => get_string("ivs_match_question_answer_menu_label_first_timing_answer", 'ivs'),
+ 'col_4' => get_string("ivs_match_question_answer_menu_label_click_retries", 'ivs'),
+ 'col_5' => get_string("ivs_match_question_answer_menu_label_last_timing_answer", 'ivs'),
+ );
+
+ foreach ($answersdata->answers as $answer) {
+
+ $answerdetails = $controller->get_question_answers_data_timing_type($answer->answer, $answer->course_user);
+
+ $data[] = array(
+ 'col_1' => $answerdetails->fullname,
+ 'col_2' => $answerdetails->id,
+ 'col_3' => strip_tags($answerdetails->first),
+ 'col_4' => $answerdetails->retries,
+ 'col_5' => strip_tags($answerdetails->last),
+ );
+ }
+
+ break;
+
+}
+
+$filename = clean_filename($course->shortname . get_string('ivs_match_question_export_question_filename', 'ivs') . $qid);
+
+if (class_exists ( '\core\dataformat' )) {
+ \core\dataformat::download_data($filename, $dataformat, $columns, $data);
+} else {
+ download_as_dataformat($filename, $dataformat, $columns, $data);
+}
diff --git a/questions.php b/questions.php
index 2b08e9b..103a657 100644
--- a/questions.php
+++ b/questions.php
@@ -64,6 +64,7 @@
$controller = new \mod_ivs\MoodleMatchController();
$questions = $controller->match_questions_get_by_video_db_order($ivs->id);
+$timingtypes = $controller->match_timing_type_get_db($ivs->id);
$renderer = $PAGE->get_renderer('ivs');
@@ -88,9 +89,13 @@
role="tab">
-
-
+ -
+
+
@@ -102,20 +107,45 @@
?>
-
+
+
+
+
+ |
+ |
+
+
+
+ render($renderable);
+ }
+ ?>
+
+
- render($renderable);
- }
- ?>
-
+
+
+
+
+
+
+ |
+ |
+
+
+
download_dataformat_selector(get_string("ivs_match_download_summary_details_label", 'ivs'),
- 'question_overview_details_download.php', 'download', array('player_id' => $ivs->id, 'cmid' => $cmid));
+ foreach ($timingtypes as $timingtype) {
+ $renderable = new \mod_ivs\output\match\question_type_overview($timingtype, $cm);
+ echo $renderer->render($renderable);
+ }
?>
-
+
+
+
+
diff --git a/templates/question_answers_view.mustache b/templates/question_answers_view.mustache
index 5356329..b561a2c 100644
--- a/templates/question_answers_view.mustache
+++ b/templates/question_answers_view.mustache
@@ -110,6 +110,36 @@
{{/single_choice_question}}
+
+ {{#timing_question}}
+
+
+
+
+ {{id_label}} |
+ {{type_label}} |
+ {{title_label}} |
+
+
+ {{question_label}} |
+
+
+ {{name}} |
+ {{user_id}} |
+ {{first_click_answer}} |
+ {{click_retries}} |
+ {{last_click_answer}} |
+
+
+
+ {{#answers}}
+ {{{.}}}
+ {{/answers}}
+
+
+
+ {{/timing_question}}
+