From 25345cb4cd2366be97264f7479aff774ae5397b4 Mon Sep 17 00:00:00 2001 From: Jean-Michel Vedrine Date: Sat, 22 Nov 2014 15:46:04 +0100 Subject: [PATCH] MDL-48244 Lesson: add custom end of lesson reached completion rule --- .../backup/moodle2/backup_lesson_stepslib.php | 17 ++--- .../moodle2/restore_lesson_stepslib.php | 10 ++- mod/lesson/db/install.xml | 2 + mod/lesson/db/upgrade.php | 22 ++++++ mod/lesson/lang/en/lesson.php | 2 + mod/lesson/lib.php | 28 ++++++++ mod/lesson/locallib.php | 5 +- mod/lesson/mod_form.php | 23 ++++++ .../completion_condition_end_reached.feature | 70 +++++++++++++++++++ mod/lesson/upgrade.txt | 8 +++ mod/lesson/version.php | 2 +- mod/lesson/view.php | 6 ++ 12 files changed, 183 insertions(+), 12 deletions(-) create mode 100644 mod/lesson/tests/behat/completion_condition_end_reached.feature create mode 100644 mod/lesson/upgrade.txt diff --git a/mod/lesson/backup/moodle2/backup_lesson_stepslib.php b/mod/lesson/backup/moodle2/backup_lesson_stepslib.php index 2ff5ef6f5ce..cfb746d0b05 100644 --- a/mod/lesson/backup/moodle2/backup_lesson_stepslib.php +++ b/mod/lesson/backup/moodle2/backup_lesson_stepslib.php @@ -69,13 +69,14 @@ class backup_lesson_activity_structure_step extends backup_activity_structure_st // alot goes into it but nothing relational other than course when will // need to be corrected upon restore $lesson = new backup_nested_element('lesson', array('id'), array( - 'course','name','practice','modattempts','usepassword','password', - 'dependency','conditions','grade','custom','ongoing','usemaxgrade', - 'maxanswers','maxattempts','review','nextpagedefault','feedback', - 'minquestions','maxpages','timed','maxtime','retake','activitylink', - 'mediafile','mediaheight','mediawidth','mediaclose','slideshow', - 'width','height','bgcolor','displayleft','displayleftif','progressbar', - 'showhighscores','maxhighscores','available','deadline','timemodified' + 'course', 'name', 'practice', 'modattempts', 'usepassword', 'password', + 'dependency', 'conditions', 'grade', 'custom', 'ongoing', 'usemaxgrade', + 'maxanswers', 'maxattempts', 'review', 'nextpagedefault', 'feedback', + 'minquestions', 'maxpages', 'timed', 'maxtime', 'retake', 'activitylink', + 'mediafile', 'mediaheight', 'mediawidth', 'mediaclose', 'slideshow', + 'width', 'height', 'bgcolor', 'displayleft', 'displayleftif', 'progressbar', + 'showhighscores', 'maxhighscores', 'available', 'deadline', 'timemodified', + 'completionendreached' )); // Tell the lesson element about the showhighscores elements mapping to the highscores // database field. @@ -140,7 +141,7 @@ class backup_lesson_activity_structure_step extends backup_activity_structure_st // Grouped by a `timers` element this is relational to the lesson and user. $timers = new backup_nested_element('timers'); $timer = new backup_nested_element('timer', array('id'), array( - 'userid','starttime','lessontime' + 'userid', 'starttime', 'lessontime', 'completed' )); // Now that we have all of the elements created we've got to put them diff --git a/mod/lesson/backup/moodle2/restore_lesson_stepslib.php b/mod/lesson/backup/moodle2/restore_lesson_stepslib.php index 34e8371370c..8be645e9bf0 100644 --- a/mod/lesson/backup/moodle2/restore_lesson_stepslib.php +++ b/mod/lesson/backup/moodle2/restore_lesson_stepslib.php @@ -72,6 +72,11 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ unset($data->showhighscores); } + // Supply item that maybe missing from previous versions. + if (!isset($data->completionendreached)) { + $data->completionendreached = 0; + } + // insert the lesson record $newitemid = $DB->insert_record('lesson', $data); // immediately after inserting "activity" record, call this @@ -175,7 +180,10 @@ class restore_lesson_activity_structure_step extends restore_activity_structure_ $data->userid = $this->get_mappingid('user', $data->userid); $data->starttime = $this->apply_date_offset($data->starttime); $data->lessontime = $this->apply_date_offset($data->lessontime); - + // Supply item that maybe missing from previous versions. + if (!isset($data->completed)) { + $data->completed = 0; + } $newitemid = $DB->insert_record('lesson_timer', $data); } diff --git a/mod/lesson/db/install.xml b/mod/lesson/db/install.xml index dc77a6daf9d..470a172d5ae 100644 --- a/mod/lesson/db/install.xml +++ b/mod/lesson/db/install.xml @@ -48,6 +48,7 @@ + @@ -145,6 +146,7 @@ + diff --git a/mod/lesson/db/upgrade.php b/mod/lesson/db/upgrade.php index 6531a2299e7..3d78bf107dd 100644 --- a/mod/lesson/db/upgrade.php +++ b/mod/lesson/db/upgrade.php @@ -102,5 +102,27 @@ function xmldb_lesson_upgrade($oldversion) { // Moodle v2.8.0 release upgrade line. // Put any upgrade step following this. + if ($oldversion < 2014112300) { + + // Define field completionendreached to be added to lesson. + $table = new xmldb_table('lesson'); + $field = new xmldb_field('completionendreached', XMLDB_TYPE_INTEGER, '1', null, null, null, '0', 'timemodified'); + + // Conditionally launch add field completionendreached. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field completed to be added to lesson_timer. + $table = new xmldb_table('lesson_timer'); + $field = new xmldb_field('completed', XMLDB_TYPE_INTEGER, '1', null, null, null, '0', 'lessontime'); + + // Conditionally launch add field completed. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + // Lesson savepoint reached. + upgrade_mod_savepoint(true, 2014112300, 'lesson'); + } return true; } diff --git a/mod/lesson/lang/en/lesson.php b/mod/lesson/lang/en/lesson.php index 850813d782c..b234315591e 100644 --- a/mod/lesson/lang/en/lesson.php +++ b/mod/lesson/lang/en/lesson.php @@ -94,6 +94,8 @@ $string['comments'] = 'Your comments'; $string['completed'] = 'Completed'; $string['completederror'] = 'Complete the lesson'; $string['completethefollowingconditions'] = 'You must complete the following condition(s) in {$a} lesson before you can proceed.'; +$string['completionendreached'] = 'Require end reached'; +$string['completionendreached_desc'] = 'Student must reach the end of lesson page to complete this activity'; $string['conditionsfordependency'] = 'Condition(s) for the dependency'; $string['configactionaftercorrectanswer'] = 'The default action to take after a correct answer'; $string['configmaxanswers'] = 'Default maximum number of answers per page'; diff --git a/mod/lesson/lib.php b/mod/lesson/lib.php index 7bab80f8d5c..776be81207e 100644 --- a/mod/lesson/lib.php +++ b/mod/lesson/lib.php @@ -750,6 +750,8 @@ function lesson_supports($feature) { return true; case FEATURE_GRADE_HAS_GRADE: return true; + case FEATURE_COMPLETION_HAS_RULES: + return true; case FEATURE_GRADE_OUTCOMES: return true; case FEATURE_BACKUP_MOODLE2: @@ -761,6 +763,32 @@ function lesson_supports($feature) { } } +/** + * Obtains the automatic completion state for this lesson based on any conditions + * in lesson settings. + * + * @param object $course Course + * @param object $cm course-module + * @param int $userid User ID + * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) + * @return bool True if completed, false if not, $type if conditions not set. + */ +function lesson_get_completion_state($course, $cm, $userid, $type) { + global $CFG, $DB; + + // Get lesson details. + $lesson = $DB->get_record('lesson', array('id' => $cm->instance), '*', + MUST_EXIST); + + // If completion option is enabled, evaluate it and return true/false. + if ($lesson->completionendreached) { + return $DB->record_exists('lesson_timer', array( + 'lessonid' => $lesson->id, 'userid' => $userid, 'completed' => 1)); + } else { + // Completion option is not enabled so just return $type. + return $type; + } +} /** * This function extends the settings navigation block for the site. * diff --git a/mod/lesson/locallib.php b/mod/lesson/locallib.php index dc018522604..568970a3e40 100644 --- a/mod/lesson/locallib.php +++ b/mod/lesson/locallib.php @@ -1251,7 +1251,7 @@ class lesson extends lesson_base { * will continue from a previous attempt * @return stdClass The new timer */ - public function update_timer($restart=false, $continue=false) { + public function update_timer($restart=false, $continue=false, $endreached =false) { global $USER, $DB; // clock code // get time information for this user @@ -1272,6 +1272,7 @@ class lesson extends lesson_base { } $timer->lessontime = time(); + $timer->completed = $endreached; $DB->update_record('lesson_timer', $timer); return $timer; } @@ -1295,7 +1296,7 @@ class lesson extends lesson_base { )); $event->trigger(); - return $this->update_timer(false, false); + return $this->update_timer(false, false, true); } /** diff --git a/mod/lesson/mod_form.php b/mod/lesson/mod_form.php index 3d739c3f913..8475ddc37c5 100644 --- a/mod/lesson/mod_form.php +++ b/mod/lesson/mod_form.php @@ -343,5 +343,28 @@ class mod_lesson_mod_form extends moodleform_mod { return $errors; } + + /** + * Display module-specific activity completion rules. + * Part of the API defined by moodleform_mod + * @return array Array of string IDs of added items, empty array if none + */ + public function add_completion_rules() { + $mform = $this->_form; + + $mform->addElement('checkbox', 'completionendreached', get_string('completionendreached', 'lesson'), + get_string('completionendreached_desc', 'lesson')); + return array('completionendreached'); + } + + /** + * Called during validation. Indicates whether a module-specific completion rule is selected. + * + * @param array $data Input data (not yet validated) + * @return bool True if one or more rules is enabled, false if none are. + */ + public function completion_rule_enabled($data) { + return !empty($data['completionendreached']); + } } diff --git a/mod/lesson/tests/behat/completion_condition_end_reached.feature b/mod/lesson/tests/behat/completion_condition_end_reached.feature new file mode 100644 index 00000000000..feae2dc8deb --- /dev/null +++ b/mod/lesson/tests/behat/completion_condition_end_reached.feature @@ -0,0 +1,70 @@ +@mod @mod_lesson +Feature: Set end of lesson reached as a completion condition for a lesson + In order to ensure students really see all lesson pages + As a teacher + I need to set end of lesson reached to mark the lesson activity as completed + + @javascript + Scenario: Set end reached as a condition + Given the following "users" exist: + | username | firstname | lastname | email | + | student1 | Student | 1 | student1@asd.com | + | teacher1 | Teacher | 1 | teacher1@asd.com | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + And I log in as "admin" + And I set the following administration settings values: + | Enable completion tracking | 1 | + And I log out + And I log in as "teacher1" + And I follow "Course 1" + And I turn editing mode on + And I click on "Edit settings" "link" in the "Administration" "block" + And I set the following fields to these values: + | Enable completion tracking | Yes | + And I press "Save and display" + And I add a "Lesson" to section "1" and I fill the form with: + | Name | Test lesson | + | Description | Test lesson description | + | Completion tracking | Show activity as complete when conditions are met | + | completionendreached | 1 | + And I follow "Test lesson" + And I follow "Add a content page" + And I set the following fields to these values: + | Page title | First page name | + | Page contents | First page contents | + | id_answer_editor_0 | Next page | + | id_jumpto_0 | Next page | + And I press "Save page" + And I set the field "qtype" to "Add a content page" + And I set the following fields to these values: + | Page title | Second page name | + | Page contents | Second page contents | + | id_answer_editor_0 | Previous page | + | id_jumpto_0 | Previous page | + | id_answer_editor_1 | Next page | + | id_jumpto_1 | Next page | + And I press "Save page" + And I log out + When I log in as "student1" + And I follow "Course 1" + Then I hover "//li[contains(concat(' ', normalize-space(@class), ' '), ' modtype_lesson ')]/descendant::img[@alt='Not completed: Test lesson']" "xpath_element" + And I follow "Test lesson" + And I press "Next page" + And I follow "Course 1" + And I hover "//li[contains(concat(' ', normalize-space(@class), ' '), ' modtype_lesson ')]/descendant::img[@alt='Not completed: Test lesson']" "xpath_element" + And I follow "Course 1" + And I follow "Test lesson" + And I press "Next page" + And I press "Next page" + And I follow "Course 1" + And I hover "//li[contains(concat(' ', normalize-space(@class), ' '), ' modtype_lesson ')]/descendant::img[@alt='Completed: Test lesson']" "xpath_element" + And I log out + And I log in as "teacher1" + And I follow "Course 1" + And "Student 1" user has completed "Test lesson" activity diff --git a/mod/lesson/upgrade.txt b/mod/lesson/upgrade.txt new file mode 100644 index 00000000000..5fd45b49d96 --- /dev/null +++ b/mod/lesson/upgrade.txt @@ -0,0 +1,8 @@ +This files describes API changes in the lesson code. + +=== 2.9 === +A third optional boolean parameter $endreached was added to lesson::update_timer to indicate that end of lesson was reached. This is used by 'completionendreached' custom completion rule. + +=== Earlier changes === + +* Were not documented in this way. Sorry. diff --git a/mod/lesson/version.php b/mod/lesson/version.php index 74cbcacac27..ac10eb593a2 100644 --- a/mod/lesson/version.php +++ b/mod/lesson/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2014111000; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2014112300; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2014110400; // Requires this Moodle version $plugin->component = 'mod_lesson'; // Full name of the plugin (used for diagnostics) $plugin->cron = 0; diff --git a/mod/lesson/view.php b/mod/lesson/view.php index 097a93cdb11..6a31b53cee4 100644 --- a/mod/lesson/view.php +++ b/mod/lesson/view.php @@ -434,6 +434,12 @@ if ($pageid != LESSON_EOL) { $lesson->stop_timer(); $gradeinfo = lesson_grade($lesson, $ntries); + // Update completion state. + $completion = new completion_info($course); + if ($completion->is_enabled($cm) && $lesson->completionendreached) { + $completion->update_state($cm, COMPLETION_COMPLETE); + } + if ($gradeinfo->attempts) { if (!$lesson->custom) { $lessoncontent .= $lessonoutput->paragraph(get_string("numberofpagesviewed", "lesson", $gradeinfo->nquestions), 'center');