MDL-57696 mod_lesson: New WS mod_lesson_process_page

This commit is contained in:
Juan Leyva 2017-01-18 17:35:52 +01:00
parent 8d6748380c
commit e1f88fe7c4
5 changed files with 225 additions and 7 deletions

View File

@ -1395,4 +1395,146 @@ class mod_lesson_external extends external_api {
)
);
}
/**
* Describes the parameters for process_page.
*
* @return external_external_function_parameters
* @since Moodle 3.3
*/
public static function process_page_parameters() {
return new external_function_parameters (
array(
'lessonid' => new external_value(PARAM_INT, 'lesson instance id'),
'pageid' => new external_value(PARAM_INT, 'the page id'),
'data' => new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_RAW, 'data name'),
'value' => new external_value(PARAM_RAW, 'data value'),
)
), 'the data to be saved'
),
'password' => new external_value(PARAM_RAW, 'optional password (the lesson may be protected)', VALUE_DEFAULT, ''),
'review' => new external_value(PARAM_BOOL, 'if we want to review just after finishing (1 hour margin)',
VALUE_DEFAULT, false),
)
);
}
/**
* Processes page responses
*
* @param int $lessonid lesson instance id
* @param int $pageid page id
* @param array $data the data to be saved
* @param str $password optional password (the lesson may be protected)
* @param bool $review if we want to review just after finishing (1 hour margin)
* @return array of warnings and status result
* @since Moodle 3.3
* @throws moodle_exception
*/
public static function process_page($lessonid, $pageid, $data, $password = '', $review = false) {
global $USER;
$params = array('lessonid' => $lessonid, 'pageid' => $pageid, 'data' => $data, 'password' => $password,
'review' => $review);
$params = self::validate_parameters(self::process_page_parameters(), $params);
$warnings = array();
$pagecontent = $ongoingscore = '';
$progress = null;
list($lesson, $course, $cm, $context) = self::validate_lesson($params['lessonid']);
// Update timer so the validation can check the time restrictions.
$timer = $lesson->update_timer();
self::validate_attempt($lesson, $params);
// Create the $_POST object required by the lesson question engine.
$_POST = array();
foreach ($data as $element) {
// First check if we are handling editor fields like answer[text].
if (preg_match('/(.+)\[(.+)\]$/', $element['name'], $matches)) {
$_POST[$matches[1]][$matches[2]] = $element['value'];
} else {
$_POST[$element['name']] = $element['value'];
}
}
// Ignore sesskey (deep in some APIs), the request is already validated.
$USER->ignoresesskey = true;
// Process page.
$page = $lesson->load_page($params['pageid']);
$result = $lesson->process_page_responses($page);
// Prepare messages.
$reviewmode = $lesson->is_in_review_mode();
$lesson->add_messages_on_page_process($page, $result, $reviewmode);
// Additional lesson information.
if (!$lesson->can_manage()) {
if ($lesson->ongoing && !$reviewmode) {
$ongoingscore = $lesson->get_ongoing_score_message();
}
if ($lesson->progressbar) {
$progress = $lesson->calculate_progress();
}
}
// Check conditionally everything coming from result (except newpageid because is always set).
$result = array(
'newpageid' => (int) $result->newpageid,
'inmediatejump' => $result->inmediatejump,
'nodefaultresponse' => !empty($result->nodefaultresponse),
'feedback' => (isset($result->feedback)) ? $result->feedback : '',
'attemptsremaining' => (isset($result->attemptsremaining)) ? $result->attemptsremaining : null,
'correctanswer' => !empty($result->correctanswer),
'noanswer' => !empty($result->noanswer),
'isessayquestion' => !empty($result->isessayquestion),
'maxattemptsreached' => !empty($result->maxattemptsreached),
'response' => (isset($result->response)) ? $result->response : '',
'studentanswer' => (isset($result->studentanswer)) ? $result->studentanswer : '',
'userresponse' => (isset($result->userresponse)) ? $result->userresponse : '',
'reviewmode' => $reviewmode,
'ongoingscore' => $ongoingscore,
'progress' => $progress,
'displaymenu' => !empty(lesson_displayleftif($lesson)),
'messages' => self::format_lesson_messages($lesson),
'warnings' => $warnings,
);
return $result;
}
/**
* Describes the process_page return value.
*
* @return external_single_structure
* @since Moodle 3.3
*/
public static function process_page_returns() {
return new external_single_structure(
array(
'newpageid' => new external_value(PARAM_INT, 'New page id (if a jump was made).'),
'inmediatejump' => new external_value(PARAM_BOOL, 'Whether the page processing redirect directly to anoter page.'),
'nodefaultresponse' => new external_value(PARAM_BOOL, 'Whether there is not a default response.'),
'feedback' => new external_value(PARAM_RAW, 'The response feedback.'),
'attemptsremaining' => new external_value(PARAM_INT, 'Number of attempts remaining.'),
'correctanswer' => new external_value(PARAM_BOOL, 'Whether the answer is correct.'),
'noanswer' => new external_value(PARAM_BOOL, 'Whether there aren\'t answers.'),
'isessayquestion' => new external_value(PARAM_BOOL, 'Whether is a essay question.'),
'maxattemptsreached' => new external_value(PARAM_BOOL, 'Whether we reachered the max number of attempts.'),
'response' => new external_value(PARAM_RAW, 'The response.'),
'studentanswer' => new external_value(PARAM_RAW, 'The student answer.'),
'userresponse' => new external_value(PARAM_RAW, 'The user response.'),
'reviewmode' => new external_value(PARAM_BOOL, 'Whether the user is reviewing.'),
'ongoingscore' => new external_value(PARAM_TEXT, 'The ongoing message.'),
'progress' => new external_value(PARAM_INT, 'Progress percentage in the lesson.'),
'displaymenu' => new external_value(PARAM_BOOL, 'Whether we should display the menu or not in this page.'),
'messages' => self::external_messages(),
'warnings' => new external_warnings(),
)
);
}
}

View File

@ -116,4 +116,12 @@ $functions = array(
'capabilities' => 'mod/lesson:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
'mod_lesson_process_page' => array(
'classname' => 'mod_lesson_external',
'methodname' => 'process_page',
'description' => 'Processes page responses.',
'type' => 'write',
'capabilities' => 'mod/lesson:view',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE)
),
);

View File

@ -2703,13 +2703,13 @@ class lesson extends lesson_base {
return $result;
} else if (isset($USER->modattempts[$this->properties->id])) {
// Make sure if the student is reviewing, that he/she sees the same pages/page path that he/she saw the first time.
if ($USER->modattempts[$lesson->id]->pageid == $page->id && $page->nextpageid == 0) {
if ($USER->modattempts[$this->properties->id]->pageid == $page->id && $page->nextpageid == 0) {
// Remember, this session variable holds the pageid of the last page that the user saw.
$result->newpageid = LESSON_EOL;
} else {
$nretakes = $DB->count_records("lesson_grades", array("lessonid" => $lesson->id, "userid" => $USER->id));
$nretakes = $DB->count_records("lesson_grades", array("lessonid" => $this->properties->id, "userid" => $USER->id));
$nretakes--; // Make sure we are looking at the right try.
$attempts = $DB->get_records("lesson_attempts", array("lessonid" => $lesson->id, "userid" => $USER->id, "retry" => $nretakes), "timeseen", "id, pageid");
$attempts = $DB->get_records("lesson_attempts", array("lessonid" => $this->properties->id, "userid" => $USER->id, "retry" => $nretakes), "timeseen", "id, pageid");
$found = false;
$temppageid = 0;
// Make sure that the newpageid always defaults to something valid.
@ -2730,7 +2730,7 @@ class lesson extends lesson_base {
// Going to check to see if the page that the user is going to view next, is a cluster page.
// If so, dont display, go into the cluster.
// The $result->newpageid > 0 is used to filter out all of the negative code jumps.
$newpage = $lesson->load_page($result->newpageid);
$newpage = $this->load_page($result->newpageid);
if ($newpageid = $newpage->override_next_page($result->newpageid)) {
$result->newpageid = $newpageid;
}
@ -2742,12 +2742,12 @@ class lesson extends lesson_base {
$result->newpageid = $page->nextpageid;
}
} else {
$result->newpageid = lesson_unseen_question_jump($lesson, $USER->id, $page->id);
$result->newpageid = lesson_unseen_question_jump($this, $USER->id, $page->id);
}
} else if ($result->newpageid == LESSON_PREVIOUSPAGE) {
$result->newpageid = $page->prevpageid;
} else if ($result->newpageid == LESSON_RANDOMPAGE) {
$result->newpageid = lesson_random_question_jump($lesson, $page->id);
$result->newpageid = lesson_random_question_jump($this, $page->id);
} else if ($result->newpageid == LESSON_CLUSTERJUMP) {
if ($canmanage) {
if ($page->nextpageid == 0) { // If teacher, go to next page.

View File

@ -974,4 +974,72 @@ class mod_lesson_external_testcase extends externallib_advanced_testcase {
$this->setExpectedException('moodle_exception');
$result = mod_lesson_external::get_page_data($this->lesson->id, $this->page2->id, '', false, true);
}
/**
* Test process_page
*/
public function test_process_page() {
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);
// Configure the lesson to return feedback and avoid custom scoring.
$DB->set_field('lesson', 'feedback', 1, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'progressbar', 1, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'custom', 0, array('id' => $this->lesson->id));
$DB->set_field('lesson', 'maxattempts', 3, array('id' => $this->lesson->id));
// Now, we can directly launch mocking the data.
// First incorrect response.
$answerincorrect = 0;
$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) {
$answerincorrect = $answer->id;
} else {
$answercorrect = $answer->id;
}
}
$data = array(
array(
'name' => 'answerid',
'value' => $answerincorrect,
),
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);
$this->assertEquals($this->page2->id, $result['newpageid']); // Same page, since the answer was incorrect.
$this->assertFalse($result['correctanswer']); // Incorrect answer.
$this->assertEquals(50, $result['progress']);
// Correct response.
$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);
$this->assertEquals($this->page1->id, $result['newpageid']); // Next page, the answer was correct.
$this->assertTrue($result['correctanswer']); // Correct response.
$this->assertFalse($result['maxattemptsreached']); // Still one attempt.
$this->assertEquals(50, $result['progress']);
}
}

View File

@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2016120510; // The current module version (Date: YYYYMMDDXX)
$plugin->version = 2016120511; // 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;