From dfcabd3b3321b6607e9641aa0ed354dcc1d44e75 Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Wed, 18 Jan 2017 19:58:10 +0100 Subject: [PATCH 1/2] MDL-57724 mod_lesson: Move EOL code to lib methods --- mod/lesson/locallib.php | 192 ++++++++++++++++++++++++++++++++++++++++ mod/lesson/renderer.php | 95 +++++++++++++++++++- mod/lesson/view.php | 175 +----------------------------------- 3 files changed, 286 insertions(+), 176 deletions(-) diff --git a/mod/lesson/locallib.php b/mod/lesson/locallib.php index a2ae35d4324..2269f9f0ff4 100644 --- a/mod/lesson/locallib.php +++ b/mod/lesson/locallib.php @@ -2790,6 +2790,198 @@ class lesson extends lesson_base { $this->add_message(get_string('attemptsremaining', 'lesson', $result->attemptsremaining)); } } + + /** + * Process and return all the information for the end of lesson page. + * + * @param string $outoftime used to check to see if the student ran out of time + * @return stdclass an object with all the page data ready for rendering + * @since Moodle 3.3 + */ + public function process_eol_page($outoftime) { + global $DB, $USER; + + $course = $this->get_courserecord(); + $cm = $this->get_cm(); + $canmanage = $this->can_manage(); + + // Init all the possible fields and values. + $data = (object) array( + 'gradelesson' => true, + 'notenoughtimespent' => false, + 'numberofpagesviewed' => false, + 'youshouldview' => false, + 'numberofcorrectanswers' => false, + 'displayscorewithessays' => false, + 'displayscorewithoutessays' => false, + 'yourcurrentgradeisoutof' => false, + 'eolstudentoutoftimenoanswers' => false, + 'welldone' => false, + 'progressbar' => false, + 'displayofgrade' => false, + 'reviewlesson' => false, + 'modattemptsnoteacher' => false, + 'activitylink' => false, + ); + + $ntries = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id)); + if (isset($USER->modattempts[$this->properties->id])) { + $ntries--; // Need to look at the old attempts :). + } + + $gradeinfo = lesson_grade($this, $ntries); + $data->gradeinfo = $gradeinfo; + if ($this->properties->custom && !$canmanage) { + // Before we calculate the custom score make sure they answered the minimum + // number of questions. We only need to do this for custom scoring as we can + // not get the miniumum score the user should achieve. If we are not using + // custom scoring (so all questions are valued as 1) then we simply check if + // they answered more than the minimum questions, if not, we mark it out of the + // number specified in the minimum questions setting - which is done in lesson_grade(). + // Get the number of answers given. + if ($gradeinfo->nquestions < $this->properties->minquestions) { + $data->gradelesson = false; + $a = new stdClass; + $a->nquestions = $gradeinfo->nquestions; + $a->minquestions = $this->properties->minquestions; + $this->add_message(get_string('numberofpagesviewednotice', 'lesson', $a)); + } + } + + if (!$canmanage) { + if ($data->gradelesson) { + // Store this now before any modifications to pages viewed. + $progresscompleted = $this->calculate_progress(); + + // Update the clock / get time information for this user. + $this->stop_timer(); + + // Update completion state. + $completion = new completion_info($course); + if ($completion->is_enabled($cm) && $this->properties->completionendreached) { + $completion->update_state($cm, COMPLETION_COMPLETE); + } + + if ($this->properties->completiontimespent > 0) { + $duration = $DB->get_field_sql( + "SELECT SUM(lessontime - starttime) + FROM {lesson_timer} + WHERE lessonid = :lessonid + AND userid = :userid", + array('userid' => $USER->id, 'lessonid' => $this->properties->id)); + if (!$duration) { + $duration = 0; + } + + // If student has not spend enough time in the lesson, display a message. + if ($duration < $this->properties->completiontimespent) { + $a = new stdClass; + $a->timespentraw = $duration; + $a->timespent = format_time($duration); + $a->timerequiredraw = $this->properties->completiontimespent; + $a->timerequired = format_time($this->properties->completiontimespent); + $data->notenoughtimespent = $a; + } + } + + if ($gradeinfo->attempts) { + if (!$this->properties->custom) { + $data->numberofpagesviewed = $gradeinfo->nquestions; + if ($this->properties->minquestions) { + if ($gradeinfo->nquestions < $this->properties->minquestions) { + $data->youshouldview = $this->properties->minquestions; + } + } + $data->numberofcorrectanswers = $gradeinfo->earned; + } + $a = new stdClass; + $a->score = $gradeinfo->earned; + $a->grade = $gradeinfo->total; + if ($gradeinfo->nmanual) { + $a->tempmaxgrade = $gradeinfo->total - $gradeinfo->manualpoints; + $a->essayquestions = $gradeinfo->nmanual; + $data->displayscorewithessays = $a; + } else { + $data->displayscorewithoutessays = $a; + } + if ($this->properties->grade != GRADE_TYPE_NONE) { + $a = new stdClass; + $a->grade = number_format($gradeinfo->grade * $this->properties->grade / 100, 1); + $a->total = $this->properties->grade; + $data->yourcurrentgradeisoutof = $a; + } + + $grade = new stdClass(); + $grade->lessonid = $this->properties->id; + $grade->userid = $USER->id; + $grade->grade = $gradeinfo->grade; + $grade->completed = time(); + if (isset($USER->modattempts[$this->properties->id])) { // If reviewing, make sure update old grade record. + if (!$grades = $DB->get_records("lesson_grades", + array("lessonid" => $this->properties->id, "userid" => $USER->id), "completed DESC", '*', 0, 1)) { + throw new moodle_exception('cannotfindgrade', 'lesson'); + } + $oldgrade = array_shift($grades); + $grade->id = $oldgrade->id; + $DB->update_record("lesson_grades", $grade); + } else { + $newgradeid = $DB->insert_record("lesson_grades", $grade); + } + } else { + if ($this->properties->timelimit) { + if ($outoftime == 'normal') { + $grade = new stdClass(); + $grade->lessonid = $this->properties->id; + $grade->userid = $USER->id; + $grade->grade = 0; + $grade->completed = time(); + $newgradeid = $DB->insert_record("lesson_grades", $grade); + $data->eolstudentoutoftimenoanswers = true; + } + } else { + $data->welldone = true; + } + } + + // Update central gradebook. + lesson_update_grades($this, $USER->id); + $data->progresscompleted = $progresscompleted; + } + } else { + // Display for teacher. + if ($this->properties->grade != GRADE_TYPE_NONE) { + $data->displayofgrade = true; + } + } + + if ($this->properties->modattempts && !$canmanage) { + // Make sure if the student is reviewing, that he/she sees the same pages/page path that he/she saw the first time + // look at the attempt records to find the first QUESTION page that the user answered, then use that page id + // to pass to view again. This is slick cause it wont call the empty($pageid) code + // $ntries is decremented above. + if (!$attempts = $this->get_attempts($ntries)) { + $attempts = array(); + $url = new moodle_url('/mod/lesson/view.php', array('id' => $cm->id)); + } else { + $firstattempt = current($attempts); + $pageid = $firstattempt->pageid; + // If the student wishes to review, need to know the last question page that the student answered. + // This will help to make sure that the student can leave the lesson via pushing the continue button. + $lastattempt = end($attempts); + $USER->modattempts[$this->properties->id] = $lastattempt->pageid; + + $url = new moodle_url('/mod/lesson/view.php', array('id' => $cm->id, 'pageid' => $pageid)); + } + $data->reviewlesson = $url; + } else if ($this->properties->modattempts && $canmanage) { + $data->modattemptsnoteacher = true; + } + + if ($this->properties->activitylink) { + $data->activitylink = $this->link_for_activitylink(); + } + return $data; + } } diff --git a/mod/lesson/renderer.php b/mod/lesson/renderer.php index f3f60f2a4d0..ab78fcd7346 100644 --- a/mod/lesson/renderer.php +++ b/mod/lesson/renderer.php @@ -484,9 +484,10 @@ class mod_lesson_renderer extends plugin_renderer_base { * Returns HTML to display a progress bar of progression through a lesson * * @param lesson $lesson + * @param int $progress optional, if empty it will be calculated * @return string */ - public function progress_bar(lesson $lesson) { + public function progress_bar(lesson $lesson, $progress = null) { $context = context_module::instance($this->page->cm->id); // lesson setting to turn progress bar on or off @@ -499,8 +500,9 @@ class mod_lesson_renderer extends plugin_renderer_base { return $this->output->notification(get_string('progressbarteacherwarning2', 'lesson')); } - // Check if the user is reviewing the attempt. - $progress = $lesson->calculate_progress(); + if ($progress === null) { + $progress = $lesson->calculate_progress(); + } // print out the Progress Bar. Attempted to put as much as possible in the style sheets. $content = '
' . html_writer::tag('div', $progress . '%', array('class' => 'progress_bar_completed', 'style' => 'width: '. $progress . '%;')); @@ -541,4 +543,91 @@ class mod_lesson_renderer extends plugin_renderer_base { $output = html_writer::tag('p', $contents, $attributes); return $output; } + + /** + * Returns the HTML for displaying the end of lesson page. + * + * @param lesson $lesson lesson instance + * @param stdclass $data lesson data to be rendered + * @return string HTML contents + */ + public function display_eol_page(lesson $lesson, $data) { + + $output = ''; + $canmanage = $lesson->can_manage(); + $course = $lesson->courserecord; + + if ($lesson->custom && !$canmanage && (($data->gradeinfo->nquestions < $lesson->minquestions))) { + $output .= $this->box_start('generalbox boxaligncenter'); + } + + if ($data->gradelesson) { + // We are using level 3 header because the page title is a sub-heading of lesson title (MDL-30911). + $output .= $this->heading(get_string("congratulations", "lesson"), 3); + $output .= $this->box_start('generalbox boxaligncenter'); + } + + if ($data->notenoughtimespent !== false) { + $output .= $this->paragraph(get_string("notenoughtimespent", "lesson", $data->notenoughtimespent), 'center'); + } + + if ($data->numberofpagesviewed !== false) { + $output .= $this->paragraph(get_string("numberofpagesviewed", "lesson", $data->numberofpagesviewed), 'center'); + } + if ($data->youshouldview !== false) { + $output .= $this->paragraph(get_string("youshouldview", "lesson", $data->youshouldview), 'center'); + } + if ($data->numberofcorrectanswers !== false) { + $output .= $this->paragraph(get_string("numberofcorrectanswers", "lesson", $data->numberofcorrectanswers), 'center'); + } + + if ($data->displayscorewithessays !== false) { + $output .= $this->box(get_string("displayscorewithessays", "lesson", $data->displayscorewithessays), 'center'); + } else if ($data->displayscorewithoutessays !== false) { + $output .= $this->box(get_string("displayscorewithoutessays", "lesson", $data->displayscorewithoutessays), 'center'); + } + + if ($data->yourcurrentgradeisoutof !== false) { + $output .= $this->paragraph(get_string("yourcurrentgradeisoutof", "lesson", $data->yourcurrentgradeisoutof), 'center'); + } + if ($data->eolstudentoutoftimenoanswers !== false) { + $output .= $this->paragraph(get_string("eolstudentoutoftimenoanswers", "lesson")); + } + if ($data->welldone !== false) { + $output .= $this->paragraph(get_string("welldone", "lesson")); + } + + if ($data->progresscompleted !== false) { + $output .= $this->progress_bar($lesson, $data->progresscompleted); + } + + if ($data->displayofgrade !== false) { + $output .= $this->paragraph(get_string("displayofgrade", "lesson"), 'center'); + } + + $output .= $this->box_end(); // End of Lesson button to Continue. + + if ($data->reviewlesson !== false) { + $output .= html_writer::link($data->reviewlesson, get_string('reviewlesson', 'lesson'), array('class' => 'centerpadded lessonbutton standardbutton p-r-1')); + } + if ($data->modattemptsnoteacher !== false) { + $output .= $this->paragraph(get_string("modattemptsnoteacher", "lesson"), 'centerpadded'); + } + + if ($data->activitylink !== false) { + $output .= $data->activitylink; + } + + $url = new moodle_url('/course/view.php', array('id' => $course->id)); + $output .= html_writer::link($url, get_string('returnto', 'lesson', format_string($course->fullname, true)), + array('class' => 'centerpadded lessonbutton standardbutton p-r-1')); + + if (has_capability('gradereport/user:view', context_course::instance($course->id)) + && $course->showgrades && $lesson->grade != 0 && !$lesson->practice) { + $url = new moodle_url('/grade/index.php', array('id' => $course->id)); + $output .= html_writer::link($url, get_string('viewgrades', 'lesson'), + array('class' => 'centerpadded lessonbutton standardbutton p-r-1')); + } + return $output; + } } diff --git a/mod/lesson/view.php b/mod/lesson/view.php index 785b9201591..b308cec5c69 100644 --- a/mod/lesson/view.php +++ b/mod/lesson/view.php @@ -233,183 +233,12 @@ if ($pageid != LESSON_EOL) { } else { - $lessoncontent = ''; // End of lesson reached work out grade. // Used to check to see if the student ran out of time. $outoftime = optional_param('outoftime', '', PARAM_ALPHA); - $ntries = $DB->count_records("lesson_grades", array("lessonid"=>$lesson->id, "userid"=>$USER->id)); - if (isset($USER->modattempts[$lesson->id])) { - $ntries--; // need to look at the old attempts :) - } - $gradelesson = true; - $gradeinfo = lesson_grade($lesson, $ntries); - if ($lesson->custom && !$canmanage) { - // Before we calculate the custom score make sure they answered the minimum - // number of questions. We only need to do this for custom scoring as we can - // not get the miniumum score the user should achieve. If we are not using - // custom scoring (so all questions are valued as 1) then we simply check if - // they answered more than the minimum questions, if not, we mark it out of the - // number specified in the minimum questions setting - which is done in lesson_grade(). - // Get the number of answers given. - if ($gradeinfo->nquestions < $lesson->minquestions) { - $gradelesson = false; - $a = new stdClass; - $a->nquestions = $gradeinfo->nquestions; - $a->minquestions = $lesson->minquestions; - $lessoncontent .= $OUTPUT->box_start('generalbox boxaligncenter'); - $lesson->add_message(get_string('numberofpagesviewednotice', 'lesson', $a)); - } - } - if ($gradelesson) { - // We are using level 3 header because the page title is a sub-heading of lesson title (MDL-30911). - $lessoncontent .= $OUTPUT->heading(get_string("congratulations", "lesson"), 3); - $lessoncontent .= $OUTPUT->box_start('generalbox boxaligncenter'); - } - if (!$canmanage) { - if ($gradelesson) { - // Store this now before any modifications to pages viewed. - $progressbar = $lessonoutput->progress_bar($lesson); - // Update the clock / get time information for this user. - $lesson->stop_timer(); - - // Update completion state. - $completion = new completion_info($course); - if ($completion->is_enabled($cm) && $lesson->completionendreached) { - $completion->update_state($cm, COMPLETION_COMPLETE); - } - - if ($lesson->completiontimespent > 0) { - $duration = $DB->get_field_sql( - "SELECT SUM(lessontime - starttime) - FROM {lesson_timer} - WHERE lessonid = :lessonid - AND userid = :userid", - array('userid' => $USER->id, 'lessonid' => $lesson->id)); - if (!$duration) { - $duration = 0; - } - - // If student has not spend enough time in the lesson, display a message. - if ($duration < $lesson->completiontimespent) { - $a = new stdClass; - $a->timespent = format_time($duration); - $a->timerequired = format_time($lesson->completiontimespent); - $lessoncontent .= $lessonoutput->paragraph(get_string("notenoughtimespent", "lesson", $a), 'center'); - } - } - - - if ($gradeinfo->attempts) { - if (!$lesson->custom) { - $lessoncontent .= $lessonoutput->paragraph(get_string("numberofpagesviewed", "lesson", $gradeinfo->nquestions), 'center'); - if ($lesson->minquestions) { - if ($gradeinfo->nquestions < $lesson->minquestions) { - // print a warning and set nviewed to minquestions - $lessoncontent .= $lessonoutput->paragraph(get_string("youshouldview", "lesson", $lesson->minquestions), 'center'); - } - } - $lessoncontent .= $lessonoutput->paragraph(get_string("numberofcorrectanswers", "lesson", $gradeinfo->earned), 'center'); - } - $a = new stdClass; - $a->score = $gradeinfo->earned; - $a->grade = $gradeinfo->total; - if ($gradeinfo->nmanual) { - $a->tempmaxgrade = $gradeinfo->total - $gradeinfo->manualpoints; - $a->essayquestions = $gradeinfo->nmanual; - $lessoncontent .= $OUTPUT->box(get_string("displayscorewithessays", "lesson", $a), 'center'); - } else { - $lessoncontent .= $OUTPUT->box(get_string("displayscorewithoutessays", "lesson", $a), 'center'); - } - if ($lesson->grade != GRADE_TYPE_NONE) { - $a = new stdClass; - $a->grade = number_format($gradeinfo->grade * $lesson->grade / 100, 1); - $a->total = $lesson->grade; - $lessoncontent .= $lessonoutput->paragraph(get_string("yourcurrentgradeisoutof", "lesson", $a), 'center'); - } - - $grade = new stdClass(); - $grade->lessonid = $lesson->id; - $grade->userid = $USER->id; - $grade->grade = $gradeinfo->grade; - $grade->completed = time(); - if (isset($USER->modattempts[$lesson->id])) { // If reviewing, make sure update old grade record. - if (!$grades = $DB->get_records("lesson_grades", - array("lessonid" => $lesson->id, "userid" => $USER->id), "completed DESC", '*', 0, 1)) { - print_error('cannotfindgrade', 'lesson'); - } - $oldgrade = array_shift($grades); - $grade->id = $oldgrade->id; - $DB->update_record("lesson_grades", $grade); - } else { - $newgradeid = $DB->insert_record("lesson_grades", $grade); - } - } else { - if ($lesson->timelimit) { - if ($outoftime == 'normal') { - $grade = new stdClass(); - $grade->lessonid = $lesson->id; - $grade->userid = $USER->id; - $grade->grade = 0; - $grade->completed = time(); - $newgradeid = $DB->insert_record("lesson_grades", $grade); - $lessoncontent .= $lessonoutput->paragraph(get_string("eolstudentoutoftimenoanswers", "lesson")); - } - } else { - $lessoncontent .= $lessonoutput->paragraph(get_string("welldone", "lesson")); - } - } - - // update central gradebook - lesson_update_grades($lesson, $USER->id); - $lessoncontent .= $progressbar; - } - } else { - // display for teacher - if ($lesson->grade != GRADE_TYPE_NONE) { - $lessoncontent .= $lessonoutput->paragraph(get_string("displayofgrade", "lesson"), 'center'); - } - } - $lessoncontent .= $OUTPUT->box_end(); //End of Lesson button to Continue. - - if ($lesson->modattempts && !$canmanage) { - // make sure if the student is reviewing, that he/she sees the same pages/page path that he/she saw the first time - // look at the attempt records to find the first QUESTION page that the user answered, then use that page id - // to pass to view again. This is slick cause it wont call the empty($pageid) code - // $ntries is decremented above - if (!$attempts = $lesson->get_attempts($ntries)) { - $attempts = array(); - $url = new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id)); - } else { - $firstattempt = current($attempts); - $pageid = $firstattempt->pageid; - // IF the student wishes to review, need to know the last question page that the student answered. This will help to make - // sure that the student can leave the lesson via pushing the continue button. - $lastattempt = end($attempts); - $USER->modattempts[$lesson->id] = $lastattempt->pageid; - - $url = new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id, 'pageid'=>$pageid)); - } - $lessoncontent .= html_writer::link($url, get_string('reviewlesson', 'lesson'), - array('class' => 'centerpadded lessonbutton standardbutton p-r-1')); - } elseif ($lesson->modattempts && $canmanage) { - $lessoncontent .= $lessonoutput->paragraph(get_string("modattemptsnoteacher", "lesson"), 'centerpadded'); - } - - if ($lesson->activitylink) { - $lessoncontent .= $lesson->link_for_activitylink(); - } - - $url = new moodle_url('/course/view.php', array('id'=>$course->id)); - $lessoncontent .= html_writer::link($url, get_string('returnto', 'lesson', format_string($course->fullname, true)), - array('class' => 'centerpadded lessonbutton standardbutton p-r-1')); - - if (has_capability('gradereport/user:view', context_course::instance($course->id)) - && $course->showgrades && $lesson->grade != 0 && !$lesson->practice) { - $url = new moodle_url('/grade/index.php', array('id' => $course->id)); - $lessoncontent .= html_writer::link($url, get_string('viewgrades', 'lesson'), - array('class' => 'centerpadded lessonbutton standardbutton p-r-1')); - } + $data = $lesson->process_eol_page($outoftime); + $lessoncontent = $lessonoutput->display_eol_page($lesson, $data); lesson_add_fake_blocks($PAGE, $cm, $lesson, $timer); echo $lessonoutput->header($lesson, $cm, $currenttab, $extraeditbuttons, $lessonpageid, get_string("congratulations", "lesson")); From 5f3351d7471951862679c02b0ccb84adf9b5141c Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Thu, 19 Jan 2017 11:10:00 +0100 Subject: [PATCH 2/2] MDL-57724 mod_lesson: New Web Service mod_lesson_finish_attempt --- mod/lesson/classes/external.php | 113 +++++++++++++++++++++++++++++ mod/lesson/db/services.php | 8 ++ mod/lesson/tests/external_test.php | 89 +++++++++++++++++++++++ mod/lesson/version.php | 2 +- 4 files changed, 211 insertions(+), 1 deletion(-) diff --git a/mod/lesson/classes/external.php b/mod/lesson/classes/external.php index d5a4f882d22..034a01d335e 100644 --- a/mod/lesson/classes/external.php +++ b/mod/lesson/classes/external.php @@ -1537,4 +1537,117 @@ class mod_lesson_external extends external_api { ) ); } + + /** + * Describes the parameters for finish_attempt. + * + * @return external_external_function_parameters + * @since Moodle 3.3 + */ + public static function finish_attempt_parameters() { + return new external_function_parameters ( + array( + 'lessonid' => new external_value(PARAM_INT, 'Lesson instance id.'), + 'password' => new external_value(PARAM_RAW, 'Optional password (the lesson may be protected).', VALUE_DEFAULT, ''), + 'outoftime' => new external_value(PARAM_BOOL, 'If the user run out of time.', VALUE_DEFAULT, false), + 'review' => new external_value(PARAM_BOOL, 'If we want to review just after finishing (1 hour margin).', + VALUE_DEFAULT, false), + ) + ); + } + + /** + * Finishes the current attempt. + * + * @param int $lessonid lesson instance id + * @param str $password optional password (the lesson may be protected) + * @param bool $outoftime optional if the user run out of time + * @param bool $review if we want to review just after finishing (1 hour margin) + * @return array of warnings and information about the finished attempt + * @since Moodle 3.3 + * @throws moodle_exception + */ + public static function finish_attempt($lessonid, $password = '', $outoftime = false, $review = false) { + + $params = array('lessonid' => $lessonid, 'password' => $password, 'outoftime' => $outoftime, 'review' => $review); + $params = self::validate_parameters(self::finish_attempt_parameters(), $params); + + $warnings = array(); + + list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']); + + // Update timer so the validation can check the time restrictions. + $timer = $lesson->update_timer(); + + // Return the validation to avoid exceptions in case the user is out of time. + $params['pageid'] = LESSON_EOL; + $validation = self::validate_attempt($lesson, $params, true); + + if (array_key_exists('eolstudentoutoftime', $validation)) { + // Maybe we run out of time just now. + $params['outoftime'] = true; + unset($validation['eolstudentoutoftime']); + } + // Check if there are more errors. + if (!empty($validation)) { + reset($validation); + throw new moodle_exception(key($validation), 'lesson', '', current($validation)); // Throw first error. + } + + $result = $lesson->process_eol_page($params['outoftime']); + + // Return the data. + $validmessages = array( + 'notenoughtimespent', 'numberofpagesviewed', 'youshouldview', 'numberofcorrectanswers', + 'displayscorewithessays', 'displayscorewithoutessays', 'yourcurrentgradeisoutof', 'eolstudentoutoftimenoanswers', + 'welldone', 'displayofgrade', 'reviewlesson', 'modattemptsnoteacher', 'progresscompleted'); + + $data = array(); + foreach ($result as $el => $value) { + if ($value !== false) { + $message = ''; + if (in_array($el, $validmessages)) { // Check if the data comes with an informative message. + $a = (is_bool($value)) ? null : $value; + $message = get_string($el, 'lesson', $a); + } + // Return the data. + $data[] = array( + 'name' => $el, + 'value' => (is_bool($value)) ? 1 : json_encode($value), // The data can be a php object. + 'message' => $message + ); + } + } + + $result = array( + 'data' => $data, + 'messages' => self::format_lesson_messages($lesson), + 'warnings' => $warnings, + ); + return $result; + } + + /** + * Describes the finish_attempt return value. + * + * @return external_single_structure + * @since Moodle 3.3 + */ + public static function finish_attempt_returns() { + return new external_single_structure( + array( + 'data' => new external_multiple_structure( + new external_single_structure( + array( + 'name' => new external_value(PARAM_ALPHANUMEXT, 'Data name.'), + 'value' => new external_value(PARAM_RAW, 'Data value.'), + 'message' => new external_value(PARAM_RAW, 'Data message (translated string).'), + ) + ), 'The EOL page information data.' + ), + 'messages' => self::external_messages(), + 'warnings' => new external_warnings(), + ) + ); + } } diff --git a/mod/lesson/db/services.php b/mod/lesson/db/services.php index 09224df9c81..4e1c87456e8 100644 --- a/mod/lesson/db/services.php +++ b/mod/lesson/db/services.php @@ -124,4 +124,12 @@ $functions = array( 'capabilities' => 'mod/lesson:view', 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) ), + 'mod_lesson_finish_attempt' => array( + 'classname' => 'mod_lesson_external', + 'methodname' => 'finish_attempt', + 'description' => 'Finishes the current attempt.', + 'type' => 'write', + 'capabilities' => 'mod/lesson:view', + 'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE) + ), ); diff --git a/mod/lesson/tests/external_test.php b/mod/lesson/tests/external_test.php index 625aa130ec2..7a98c609eca 100644 --- a/mod/lesson/tests/external_test.php +++ b/mod/lesson/tests/external_test.php @@ -1042,4 +1042,93 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase { $this->assertFalse($result['maxattemptsreached']); // Still one attempt. $this->assertEquals(50, $result['progress']); } + + /** + * Test finish attempt not doing anything. + */ + public function test_finish_attempt_not_doing_anything() { + + $this->setUser($this->student); + // First we need to launch the lesson so the timer is on. + mod_lesson_external::launch_attempt($this->lesson->id); + + $result = mod_lesson_external::finish_attempt($this->lesson->id); + $result = external_api::clean_returnvalue(mod_lesson_external::finish_attempt_returns(), $result); + + $this->assertCount(0, $result['warnings']); + $returneddata = []; + foreach ($result['data'] as $data) { + $returneddata[$data['name']] = $data['value']; + } + $this->assertEquals(1, $returneddata['gradelesson']); // Graded lesson. + $this->assertEquals(1, $returneddata['welldone']); // Finished correctly (even without grades). + $gradeinfo = json_decode($returneddata['gradeinfo']); + $expectedgradeinfo = (object) [ + 'nquestions' => 0, + 'attempts' => 0, + 'total' => 0, + 'earned' => 0, + 'grade' => 0, + 'nmanual' => 0, + 'manualpoints' => 0, + ]; + } + + /** + * Test finish attempt with correct answer. + */ + public function test_finish_attempt_with_correct_answer() { + global $DB; + + $this->setUser($this->student); + // First we need to launch the lesson so the timer is on. + mod_lesson_external::launch_attempt($this->lesson->id); + + // Attempt a question, correct answer. + $DB->set_field('lesson', 'custom', 0, array('id' => $this->lesson->id)); + $DB->set_field('lesson', 'progressbar', 1, array('id' => $this->lesson->id)); + + $answercorrect = 0; + $p2answers = $DB->get_records('lesson_answers', array('lessonid' => $this->lesson->id, 'pageid' => $this->page2->id), 'id'); + foreach ($p2answers as $answer) { + if ($answer->jumpto != 0) { + $answercorrect = $answer->id; + } + } + + $data = array( + array( + 'name' => 'answerid', + 'value' => $answercorrect, + ), + array( + 'name' => '_qf__lesson_display_answer_form_truefalse', + 'value' => 1, + ) + ); + $result = mod_lesson_external::process_page($this->lesson->id, $this->page2->id, $data); + $result = external_api::clean_returnvalue(mod_lesson_external::process_page_returns(), $result); + + $result = mod_lesson_external::finish_attempt($this->lesson->id); + $result = external_api::clean_returnvalue(mod_lesson_external::finish_attempt_returns(), $result); + + $this->assertCount(0, $result['warnings']); + $returneddata = []; + foreach ($result['data'] as $data) { + $returneddata[$data['name']] = $data['value']; + } + $this->assertEquals(1, $returneddata['gradelesson']); // Graded lesson. + $this->assertEquals(1, $returneddata['numberofpagesviewed']); + $this->assertEquals(1, $returneddata['numberofcorrectanswers']); + $gradeinfo = json_decode($returneddata['gradeinfo']); + $expectedgradeinfo = (object) [ + 'nquestions' => 1, + 'attempts' => 1, + 'total' => 1, + 'earned' => 1, + 'grade' => 100, + 'nmanual' => 0, + 'manualpoints' => 0, + ]; + } } diff --git a/mod/lesson/version.php b/mod/lesson/version.php index d7b95f6c320..dc0ab41da2e 100644 --- a/mod/lesson/version.php +++ b/mod/lesson/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2016120511; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2016120512; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2016112900; // Requires this Moodle version $plugin->component = 'mod_lesson'; // Full name of the plugin (used for diagnostics) $plugin->cron = 0;