mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00

The versioning changes will require some major changes in the backup and restore of question bank and its elements. This change introduces those changes to make it compatible with the new world of versioning in question bank. This commit also removes quiz_slots fields and quiz_slot_tags table. This commit will also introduce the versioning db structure and some major changes to the quiz and quiz attempts for the question, random question and the view. This commit implements the behat changes for versioning in core question and associated locations.
7272 lines
309 KiB
PHP
7272 lines
309 KiB
PHP
<?php
|
|
// This file is part of Moodle - http://moodle.org/
|
|
//
|
|
// Moodle is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Moodle is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
/**
|
|
* Course related unit tests
|
|
*
|
|
* @package core
|
|
* @category phpunit
|
|
* @copyright 2012 Petr Skoda {@link http://skodak.org}
|
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
|
*/
|
|
|
|
defined('MOODLE_INTERNAL') || die();
|
|
|
|
global $CFG;
|
|
require_once($CFG->dirroot . '/course/lib.php');
|
|
require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php');
|
|
require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php');
|
|
|
|
class core_course_courselib_testcase extends advanced_testcase {
|
|
|
|
/**
|
|
* Set forum specific test values for calling create_module().
|
|
*
|
|
* @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
|
|
*/
|
|
private function forum_create_set_values(&$moduleinfo) {
|
|
// Completion specific to forum - optional.
|
|
$moduleinfo->completionposts = 3;
|
|
$moduleinfo->completiondiscussions = 1;
|
|
$moduleinfo->completionreplies = 2;
|
|
|
|
// Specific values to the Forum module.
|
|
$moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
|
|
$moduleinfo->type = 'single';
|
|
$moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
|
|
$moduleinfo->maxbytes = 10240;
|
|
$moduleinfo->maxattachments = 2;
|
|
|
|
// Post threshold for blocking - specific to forum.
|
|
$moduleinfo->blockperiod = 60*60*24;
|
|
$moduleinfo->blockafter = 10;
|
|
$moduleinfo->warnafter = 5;
|
|
|
|
// Grading of whole forum settings.
|
|
$moduleinfo->grade_forum = 0;
|
|
}
|
|
|
|
/**
|
|
* Execute test asserts on the saved DB data by create_module($forum).
|
|
*
|
|
* @param object $moduleinfo - the specific forum values that were used to create a forum.
|
|
* @param object $dbmodinstance - the DB values of the created forum.
|
|
*/
|
|
private function forum_create_run_asserts($moduleinfo, $dbmodinstance) {
|
|
// Compare values specific to forums.
|
|
$this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
|
|
$this->assertEquals($moduleinfo->type, $dbmodinstance->type);
|
|
$this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
|
|
$this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
|
|
$this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
|
|
$this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
|
|
$this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
|
|
$this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
|
|
$this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
|
|
$this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
|
|
$this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
|
|
$this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
|
|
$this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
|
|
$this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
|
|
$this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
|
|
$this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
|
|
$this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
|
|
}
|
|
|
|
/**
|
|
* Set assign module specific test values for calling create_module().
|
|
*
|
|
* @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
|
|
*/
|
|
private function assign_create_set_values(&$moduleinfo) {
|
|
// Specific values to the Assign module.
|
|
$moduleinfo->alwaysshowdescription = true;
|
|
$moduleinfo->submissiondrafts = true;
|
|
$moduleinfo->requiresubmissionstatement = true;
|
|
$moduleinfo->sendnotifications = true;
|
|
$moduleinfo->sendlatenotifications = true;
|
|
$moduleinfo->duedate = time() + (7 * 24 * 3600);
|
|
$moduleinfo->cutoffdate = time() + (7 * 24 * 3600);
|
|
$moduleinfo->gradingduedate = time() + (7 * 24 * 3600);
|
|
$moduleinfo->allowsubmissionsfromdate = time();
|
|
$moduleinfo->teamsubmission = true;
|
|
$moduleinfo->requireallteammemberssubmit = true;
|
|
$moduleinfo->teamsubmissiongroupingid = true;
|
|
$moduleinfo->blindmarking = true;
|
|
$moduleinfo->markingworkflow = true;
|
|
$moduleinfo->markingallocation = true;
|
|
$moduleinfo->assignsubmission_onlinetext_enabled = true;
|
|
$moduleinfo->assignsubmission_file_enabled = true;
|
|
$moduleinfo->assignsubmission_file_maxfiles = 1;
|
|
$moduleinfo->assignsubmission_file_maxsizebytes = 1000000;
|
|
$moduleinfo->assignsubmission_comments_enabled = true;
|
|
$moduleinfo->assignfeedback_comments_enabled = true;
|
|
$moduleinfo->assignfeedback_offline_enabled = true;
|
|
$moduleinfo->assignfeedback_file_enabled = true;
|
|
|
|
// Advanced grading.
|
|
$gradingmethods = grading_manager::available_methods();
|
|
$moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods));
|
|
}
|
|
|
|
/**
|
|
* Execute test asserts on the saved DB data by create_module($assign).
|
|
*
|
|
* @param object $moduleinfo - the specific assign module values that were used to create an assign module.
|
|
* @param object $dbmodinstance - the DB values of the created assign module.
|
|
*/
|
|
private function assign_create_run_asserts($moduleinfo, $dbmodinstance) {
|
|
global $DB;
|
|
|
|
$this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription);
|
|
$this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts);
|
|
$this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement);
|
|
$this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications);
|
|
$this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate);
|
|
$this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate);
|
|
$this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate);
|
|
$this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission);
|
|
$this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit);
|
|
$this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid);
|
|
$this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking);
|
|
$this->assertEquals($moduleinfo->markingworkflow, $dbmodinstance->markingworkflow);
|
|
$this->assertEquals($moduleinfo->markingallocation, $dbmodinstance->markingallocation);
|
|
// The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries.
|
|
|
|
// Advanced grading.
|
|
$cm = get_coursemodule_from_instance('assign', $dbmodinstance->id);
|
|
$contextmodule = context_module::instance($cm->id);
|
|
$advancedgradingmethod = $DB->get_record('grading_areas',
|
|
array('contextid' => $contextmodule->id,
|
|
'activemethod' => $moduleinfo->advancedgradingmethod_submissions));
|
|
$this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod);
|
|
}
|
|
|
|
/**
|
|
* Run some asserts test for a specific module for the function create_module().
|
|
*
|
|
* The function has been created (and is called) for $this->test_create_module().
|
|
* Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts.
|
|
* So if you want, you can overwrite the default values/asserts in the respective functions.
|
|
* @param string $modulename Name of the module ('forum', 'assign', 'book'...).
|
|
*/
|
|
private function create_specific_module_test($modulename) {
|
|
global $DB, $CFG;
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$this->setAdminUser();
|
|
|
|
// Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
|
|
require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
|
|
|
|
// Enable avaibility.
|
|
// If not enabled all conditional fields will be ignored.
|
|
set_config('enableavailability', 1);
|
|
|
|
// Enable course completion.
|
|
// If not enabled all completion settings will be ignored.
|
|
set_config('enablecompletion', COMPLETION_ENABLED);
|
|
|
|
// Enable forum RSS feeds.
|
|
set_config('enablerssfeeds', 1);
|
|
set_config('forum_enablerssfeeds', 1);
|
|
|
|
$course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
|
|
array('createsections'=>true));
|
|
|
|
$grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
|
|
|
|
// Create assign module instance for test.
|
|
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
|
|
$params['course'] = $course->id;
|
|
$instance = $generator->create_instance($params);
|
|
$assigncm = get_coursemodule_from_instance('assign', $instance->id);
|
|
|
|
// Module test values.
|
|
$moduleinfo = new stdClass();
|
|
|
|
// Always mandatory generic values to any module.
|
|
$moduleinfo->modulename = $modulename;
|
|
$moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database.
|
|
$moduleinfo->course = $course->id;
|
|
$moduleinfo->groupingid = $grouping->id;
|
|
$moduleinfo->visible = true;
|
|
$moduleinfo->visibleoncoursepage = true;
|
|
|
|
// Sometimes optional generic values for some modules.
|
|
$moduleinfo->name = 'My test module';
|
|
$moduleinfo->showdescription = 1; // standard boolean
|
|
require_once($CFG->libdir . '/gradelib.php');
|
|
$gradecats = grade_get_categories_menu($moduleinfo->course, false);
|
|
$gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
|
|
$moduleinfo->gradecat = $gradecatid;
|
|
$moduleinfo->groupmode = VISIBLEGROUPS;
|
|
$moduleinfo->cmidnumber = 'idnumber_XXX';
|
|
|
|
// Completion common to all module.
|
|
$moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
|
|
$moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
|
|
$moduleinfo->completiongradeitemnumber = 1;
|
|
$moduleinfo->completionpassgrade = 0;
|
|
$moduleinfo->completionexpected = time() + (7 * 24 * 3600);
|
|
|
|
// Conditional activity.
|
|
$moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' .
|
|
'{"type":"date","d":">=","t":' . time() . '},' .
|
|
'{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' .
|
|
']}';
|
|
$coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
|
|
$moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
|
|
$moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@'));
|
|
$moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
|
|
|
|
// Grading and Advanced grading.
|
|
require_once($CFG->dirroot . '/rating/lib.php');
|
|
$moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
|
|
$moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
|
|
$moduleinfo->assesstimestart = time();
|
|
$moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
|
|
|
|
// RSS.
|
|
$moduleinfo->rsstype = 2;
|
|
$moduleinfo->rssarticles = 10;
|
|
|
|
// Optional intro editor (depends of module).
|
|
$draftid_editor = 0;
|
|
file_prepare_draft_area($draftid_editor, null, null, null, null);
|
|
$moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
|
|
|
|
// Following is the advanced grading method area called 'submissions' for the 'assign' module.
|
|
if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
|
|
$moduleinfo->grade = 100;
|
|
}
|
|
|
|
// Plagiarism form values.
|
|
// No plagiarism plugin installed by default. Use this space to make your own test.
|
|
|
|
// Values specific to the module.
|
|
$modulesetvalues = $modulename.'_create_set_values';
|
|
$this->$modulesetvalues($moduleinfo);
|
|
|
|
// Create the module.
|
|
$result = create_module($moduleinfo);
|
|
|
|
// Retrieve the module info.
|
|
$dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
|
|
$dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
|
|
// We passed the course section number to create_courses but $dbcm contain the section id.
|
|
// We need to retrieve the db course section number.
|
|
$section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section));
|
|
// Retrieve the grade item.
|
|
$gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
|
|
'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
|
|
|
|
// Compare the values common to all module instances.
|
|
$this->assertEquals($moduleinfo->modulename, $dbcm->modname);
|
|
$this->assertEquals($moduleinfo->section, $section->section);
|
|
$this->assertEquals($moduleinfo->course, $dbcm->course);
|
|
$this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
|
|
$this->assertEquals($moduleinfo->visible, $dbcm->visible);
|
|
$this->assertEquals($moduleinfo->completion, $dbcm->completion);
|
|
$this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
|
|
$this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
|
|
$this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
|
|
$this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
|
|
$this->assertEquals($moduleinfo->availability, $dbcm->availability);
|
|
$this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
|
|
$this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
|
|
$this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
|
|
$this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
|
|
|
|
// Optional grade testing.
|
|
if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
|
|
$this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
|
|
}
|
|
|
|
// Some optional (but quite common) to some module.
|
|
$this->assertEquals($moduleinfo->name, $dbmodinstance->name);
|
|
$this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
|
|
$this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
|
|
|
|
// Test specific to the module.
|
|
$modulerunasserts = $modulename.'_create_run_asserts';
|
|
$this->$modulerunasserts($moduleinfo, $dbmodinstance);
|
|
return $moduleinfo;
|
|
}
|
|
|
|
/**
|
|
* Create module associated blog and tags.
|
|
*
|
|
* @param object $course Course.
|
|
* @param object $modulecontext The context of the module.
|
|
*/
|
|
private function create_module_asscociated_blog($course, $modulecontext) {
|
|
global $DB, $CFG;
|
|
|
|
// Create default group.
|
|
$group = new stdClass();
|
|
$group->courseid = $course->id;
|
|
$group->name = 'Group';
|
|
$group->id = $DB->insert_record('groups', $group);
|
|
|
|
// Create default user.
|
|
$user = $this->getDataGenerator()->create_user(array(
|
|
'username' => 'testuser',
|
|
'firstname' => 'Firsname',
|
|
'lastname' => 'Lastname'
|
|
));
|
|
|
|
// Create default post.
|
|
$post = new stdClass();
|
|
$post->userid = $user->id;
|
|
$post->groupid = $group->id;
|
|
$post->content = 'test post content text';
|
|
$post->module = 'blog';
|
|
$post->id = $DB->insert_record('post', $post);
|
|
|
|
// Create default tag.
|
|
$tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
|
|
'rawname' => 'Testtagname', 'isstandard' => 1));
|
|
// Apply the tag to the blog.
|
|
$DB->insert_record('tag_instance', array('tagid' => $tag->id, 'itemtype' => 'user',
|
|
'component' => 'core', 'itemid' => $post->id, 'ordering' => 0));
|
|
|
|
require_once($CFG->dirroot . '/blog/locallib.php');
|
|
$blog = new blog_entry($post->id);
|
|
$blog->add_association($modulecontext->id);
|
|
|
|
return $blog;
|
|
}
|
|
|
|
/**
|
|
* Test create_module() for multiple modules defined in the $modules array (first declaration of the function).
|
|
*/
|
|
public function test_create_module() {
|
|
// Add the module name you want to test here.
|
|
// Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts().
|
|
$modules = array('forum', 'assign');
|
|
// Run all tests.
|
|
foreach ($modules as $modulename) {
|
|
$this->create_specific_module_test($modulename);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test update_module() for multiple modules defined in the $modules array (first declaration of the function).
|
|
*/
|
|
public function test_update_module() {
|
|
// Add the module name you want to test here.
|
|
// Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts().
|
|
$modules = array('forum');
|
|
// Run all tests.
|
|
foreach ($modules as $modulename) {
|
|
$this->update_specific_module_test($modulename);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set forum specific test values for calling update_module().
|
|
*
|
|
* @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
|
|
*/
|
|
private function forum_update_set_values(&$moduleinfo) {
|
|
// Completion specific to forum - optional.
|
|
$moduleinfo->completionposts = 3;
|
|
$moduleinfo->completiondiscussions = 1;
|
|
$moduleinfo->completionreplies = 2;
|
|
|
|
// Specific values to the Forum module.
|
|
$moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
|
|
$moduleinfo->type = 'single';
|
|
$moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
|
|
$moduleinfo->maxbytes = 10240;
|
|
$moduleinfo->maxattachments = 2;
|
|
|
|
// Post threshold for blocking - specific to forum.
|
|
$moduleinfo->blockperiod = 60*60*24;
|
|
$moduleinfo->blockafter = 10;
|
|
$moduleinfo->warnafter = 5;
|
|
|
|
// Grading of whole forum settings.
|
|
$moduleinfo->grade_forum = 0;
|
|
}
|
|
|
|
/**
|
|
* Execute test asserts on the saved DB data by update_module($forum).
|
|
*
|
|
* @param object $moduleinfo - the specific forum values that were used to update a forum.
|
|
* @param object $dbmodinstance - the DB values of the updated forum.
|
|
*/
|
|
private function forum_update_run_asserts($moduleinfo, $dbmodinstance) {
|
|
// Compare values specific to forums.
|
|
$this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
|
|
$this->assertEquals($moduleinfo->type, $dbmodinstance->type);
|
|
$this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
|
|
$this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
|
|
$this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
|
|
$this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
|
|
$this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
|
|
$this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
|
|
$this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
|
|
$this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
|
|
$this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
|
|
$this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
|
|
$this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
|
|
$this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
|
|
$this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
|
|
$this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
|
|
$this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Test a specific type of module.
|
|
*
|
|
* @param string $modulename - the module name to test
|
|
*/
|
|
private function update_specific_module_test($modulename) {
|
|
global $DB, $CFG;
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$this->setAdminUser();
|
|
|
|
// Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
|
|
require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
|
|
|
|
// Enable avaibility.
|
|
// If not enabled all conditional fields will be ignored.
|
|
set_config('enableavailability', 1);
|
|
|
|
// Enable course completion.
|
|
// If not enabled all completion settings will be ignored.
|
|
set_config('enablecompletion', COMPLETION_ENABLED);
|
|
|
|
// Enable forum RSS feeds.
|
|
set_config('enablerssfeeds', 1);
|
|
set_config('forum_enablerssfeeds', 1);
|
|
|
|
$course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
|
|
array('createsections'=>true));
|
|
|
|
$grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
|
|
|
|
// Create assign module instance for testing gradeitem.
|
|
$generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
|
|
$params['course'] = $course->id;
|
|
$instance = $generator->create_instance($params);
|
|
$assigncm = get_coursemodule_from_instance('assign', $instance->id);
|
|
|
|
// Create the test forum to update.
|
|
$initvalues = new stdClass();
|
|
$initvalues->introformat = FORMAT_HTML;
|
|
$initvalues->course = $course->id;
|
|
$forum = self::getDataGenerator()->create_module('forum', $initvalues);
|
|
|
|
// Retrieve course module.
|
|
$cm = get_coursemodule_from_instance('forum', $forum->id);
|
|
|
|
// Module test values.
|
|
$moduleinfo = new stdClass();
|
|
|
|
// Always mandatory generic values to any module.
|
|
$moduleinfo->coursemodule = $cm->id;
|
|
$moduleinfo->modulename = $modulename;
|
|
$moduleinfo->course = $course->id;
|
|
$moduleinfo->groupingid = $grouping->id;
|
|
$moduleinfo->visible = true;
|
|
$moduleinfo->visibleoncoursepage = true;
|
|
|
|
// Sometimes optional generic values for some modules.
|
|
$moduleinfo->name = 'My test module';
|
|
$moduleinfo->showdescription = 1; // standard boolean
|
|
require_once($CFG->libdir . '/gradelib.php');
|
|
$gradecats = grade_get_categories_menu($moduleinfo->course, false);
|
|
$gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
|
|
$moduleinfo->gradecat = $gradecatid;
|
|
$moduleinfo->groupmode = VISIBLEGROUPS;
|
|
$moduleinfo->cmidnumber = 'idnumber_XXX';
|
|
|
|
// Completion common to all module.
|
|
$moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
|
|
$moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
|
|
$moduleinfo->completiongradeitemnumber = 1;
|
|
$moduleinfo->completionpassgrade = 0;
|
|
$moduleinfo->completionexpected = time() + (7 * 24 * 3600);
|
|
$moduleinfo->completionunlocked = 1;
|
|
|
|
// Conditional activity.
|
|
$coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
|
|
$moduleinfo->availability = json_encode(\core_availability\tree::get_root_json(
|
|
array(\availability_date\condition::get_json('>=', time()),
|
|
\availability_date\condition::get_json('<', time() + (7 * 24 * 3600)),
|
|
\availability_grade\condition::get_json($coursegradeitem->id, 10, 80),
|
|
\availability_profile\condition::get_json(false, 'email', 'contains', '@'),
|
|
\availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&'));
|
|
|
|
// Grading and Advanced grading.
|
|
require_once($CFG->dirroot . '/rating/lib.php');
|
|
$moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
|
|
$moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
|
|
$moduleinfo->assesstimestart = time();
|
|
$moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
|
|
|
|
// RSS.
|
|
$moduleinfo->rsstype = 2;
|
|
$moduleinfo->rssarticles = 10;
|
|
|
|
// Optional intro editor (depends of module).
|
|
$draftid_editor = 0;
|
|
file_prepare_draft_area($draftid_editor, null, null, null, null);
|
|
$moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
|
|
|
|
// Following is the advanced grading method area called 'submissions' for the 'assign' module.
|
|
if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
|
|
$moduleinfo->grade = 100;
|
|
}
|
|
// Plagiarism form values.
|
|
// No plagiarism plugin installed by default. Use this space to make your own test.
|
|
|
|
// Values specific to the module.
|
|
$modulesetvalues = $modulename.'_update_set_values';
|
|
$this->$modulesetvalues($moduleinfo);
|
|
|
|
// Create the module.
|
|
$result = update_module($moduleinfo);
|
|
|
|
// Retrieve the module info.
|
|
$dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
|
|
$dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
|
|
// Retrieve the grade item.
|
|
$gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
|
|
'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
|
|
|
|
// Compare the values common to all module instances.
|
|
$this->assertEquals($moduleinfo->modulename, $dbcm->modname);
|
|
$this->assertEquals($moduleinfo->course, $dbcm->course);
|
|
$this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
|
|
$this->assertEquals($moduleinfo->visible, $dbcm->visible);
|
|
$this->assertEquals($moduleinfo->completion, $dbcm->completion);
|
|
$this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
|
|
$this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
|
|
$this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
|
|
$this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
|
|
$this->assertEquals($moduleinfo->availability, $dbcm->availability);
|
|
$this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
|
|
$this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
|
|
$this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
|
|
$this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
|
|
|
|
// Optional grade testing.
|
|
if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
|
|
$this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
|
|
}
|
|
|
|
// Some optional (but quite common) to some module.
|
|
$this->assertEquals($moduleinfo->name, $dbmodinstance->name);
|
|
$this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
|
|
$this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
|
|
|
|
// Test specific to the module.
|
|
$modulerunasserts = $modulename.'_update_run_asserts';
|
|
$this->$modulerunasserts($moduleinfo, $dbmodinstance);
|
|
return $moduleinfo;
|
|
}
|
|
|
|
/**
|
|
* Data provider for course_delete module
|
|
*
|
|
* @return array An array of arrays contain test data
|
|
*/
|
|
public function provider_course_delete_module() {
|
|
$data = array();
|
|
|
|
$data['assign'] = array('assign', array('duedate' => time()));
|
|
$data['quiz'] = array('quiz', array('duedate' => time()));
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Test the create_course function
|
|
*/
|
|
public function test_create_course() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
$defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
|
|
|
|
$course = new stdClass();
|
|
$course->fullname = 'Apu loves Unit Təsts';
|
|
$course->shortname = 'Spread the lŭve';
|
|
$course->idnumber = '123';
|
|
$course->summary = 'Awesome!';
|
|
$course->summaryformat = FORMAT_PLAIN;
|
|
$course->format = 'topics';
|
|
$course->newsitems = 0;
|
|
$course->category = $defaultcategory;
|
|
$original = (array) $course;
|
|
|
|
$created = create_course($course);
|
|
$context = context_course::instance($created->id);
|
|
|
|
// Compare original and created.
|
|
$this->assertEquals($original, array_intersect_key((array) $created, $original));
|
|
|
|
// Ensure default section is created.
|
|
$sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0));
|
|
$this->assertTrue($sectioncreated);
|
|
|
|
// Ensure that the shortname isn't duplicated.
|
|
try {
|
|
$created = create_course($course);
|
|
$this->fail('Exception expected');
|
|
} catch (moodle_exception $e) {
|
|
$this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage());
|
|
}
|
|
|
|
// Ensure that the idnumber isn't duplicated.
|
|
$course->shortname .= '1';
|
|
try {
|
|
$created = create_course($course);
|
|
$this->fail('Exception expected');
|
|
} catch (moodle_exception $e) {
|
|
$this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function test_create_course_with_generator() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
$course = $this->getDataGenerator()->create_course();
|
|
|
|
// Ensure default section is created.
|
|
$sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0));
|
|
$this->assertTrue($sectioncreated);
|
|
}
|
|
|
|
public function test_create_course_sections() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$numsections = 5;
|
|
$course = $this->getDataGenerator()->create_course(
|
|
array('shortname' => 'GrowingCourse',
|
|
'fullname' => 'Growing Course',
|
|
'numsections' => $numsections),
|
|
array('createsections' => true));
|
|
|
|
// Ensure all 6 (0-5) sections were created and course content cache works properly
|
|
$sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
|
|
$this->assertEquals(range(0, $numsections), $sectionscreated);
|
|
|
|
// this will do nothing, section already exists
|
|
$this->assertFalse(course_create_sections_if_missing($course, $numsections));
|
|
|
|
// this will create new section
|
|
$this->assertTrue(course_create_sections_if_missing($course, $numsections + 1));
|
|
|
|
// Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly
|
|
$sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
|
|
$this->assertEquals(range(0, $numsections + 1), $sectionscreated);
|
|
}
|
|
|
|
public function test_update_course() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0');
|
|
|
|
$course = new stdClass();
|
|
$course->fullname = 'Apu loves Unit Təsts';
|
|
$course->shortname = 'test1';
|
|
$course->idnumber = '1';
|
|
$course->summary = 'Awesome!';
|
|
$course->summaryformat = FORMAT_PLAIN;
|
|
$course->format = 'topics';
|
|
$course->newsitems = 0;
|
|
$course->numsections = 5;
|
|
$course->category = $defaultcategory;
|
|
|
|
$created = create_course($course);
|
|
// Ensure the checks only work on idnumber/shortname that are not already ours.
|
|
update_course($created);
|
|
|
|
$course->shortname = 'test2';
|
|
$course->idnumber = '2';
|
|
|
|
$created2 = create_course($course);
|
|
|
|
// Test duplicate idnumber.
|
|
$created2->idnumber = '1';
|
|
try {
|
|
update_course($created2);
|
|
$this->fail('Expected exception when trying to update a course with duplicate idnumber');
|
|
} catch (moodle_exception $e) {
|
|
$this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage());
|
|
}
|
|
|
|
// Test duplicate shortname.
|
|
$created2->idnumber = '2';
|
|
$created2->shortname = 'test1';
|
|
try {
|
|
update_course($created2);
|
|
$this->fail('Expected exception when trying to update a course with a duplicate shortname');
|
|
} catch (moodle_exception $e) {
|
|
$this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function test_update_course_section_time_modified() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Create the course with sections.
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
|
|
$sections = $DB->get_records('course_sections', array('course' => $course->id));
|
|
|
|
// Get the last section's time modified value.
|
|
$section = array_pop($sections);
|
|
$oldtimemodified = $section->timemodified;
|
|
|
|
// Update the section.
|
|
$this->waitForSecond(); // Ensuring that the section update occurs at a different timestamp.
|
|
course_update_section($course, $section, array());
|
|
|
|
// Check that the time has changed.
|
|
$section = $DB->get_record('course_sections', array('id' => $section->id));
|
|
$newtimemodified = $section->timemodified;
|
|
$this->assertGreaterThan($oldtimemodified, $newtimemodified);
|
|
}
|
|
|
|
/**
|
|
* Relative dates mode settings provider for course creation.
|
|
*/
|
|
public function create_course_relative_dates_provider() {
|
|
return [
|
|
[0, 0, 0],
|
|
[0, 1, 0],
|
|
[1, 0, 0],
|
|
[1, 1, 1],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test create_course by attempting to change the relative dates mode.
|
|
*
|
|
* @dataProvider create_course_relative_dates_provider
|
|
* @param int $setting The value for the 'enablecourserelativedates' admin setting.
|
|
* @param int $mode The value for the course's 'relativedatesmode' field.
|
|
* @param int $expectedvalue The expected value of the 'relativedatesmode' field after course creation.
|
|
*/
|
|
public function test_relative_dates_mode_for_course_creation($setting, $mode, $expectedvalue) {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
set_config('enablecourserelativedates', $setting);
|
|
|
|
// Generate a course with relative dates mode set to $mode.
|
|
$course = $this->getDataGenerator()->create_course(['relativedatesmode' => $mode]);
|
|
|
|
// Verify that the relative dates match what's expected.
|
|
$relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
|
|
$this->assertEquals($expectedvalue, $relativedatesmode);
|
|
}
|
|
|
|
/**
|
|
* Test update_course by attempting to change the relative dates mode.
|
|
*/
|
|
public function test_relative_dates_mode_for_course_update() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
set_config('enablecourserelativedates', 1);
|
|
|
|
// Generate a course with relative dates mode set to 1.
|
|
$course = $this->getDataGenerator()->create_course(['relativedatesmode' => 1]);
|
|
|
|
// Attempt to update the course with a changed relativedatesmode.
|
|
$course->relativedatesmode = 0;
|
|
update_course($course);
|
|
|
|
// Verify that the relative dates mode has not changed.
|
|
$relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
|
|
$this->assertEquals(1, $relativedatesmode);
|
|
}
|
|
|
|
public function test_course_add_cm_to_section() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
// Create course with 1 section.
|
|
$course = $this->getDataGenerator()->create_course(
|
|
array('shortname' => 'GrowingCourse',
|
|
'fullname' => 'Growing Course',
|
|
'numsections' => 1),
|
|
array('createsections' => true));
|
|
|
|
// Trash modinfo.
|
|
rebuild_course_cache($course->id, true);
|
|
|
|
// Create some cms for testing.
|
|
$cmids = array();
|
|
for ($i=0; $i<4; $i++) {
|
|
$cmids[$i] = $DB->insert_record('course_modules', array('course' => $course->id));
|
|
}
|
|
|
|
// Add it to section that exists.
|
|
course_add_cm_to_section($course, $cmids[0], 1);
|
|
|
|
// Check it got added to sequence.
|
|
$sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
|
|
$this->assertEquals($cmids[0], $sequence);
|
|
|
|
// Add a second, this time using courseid variant of parameters.
|
|
$coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
|
|
course_add_cm_to_section($course->id, $cmids[1], 1);
|
|
$sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
|
|
$this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
|
|
|
|
// Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly).
|
|
$this->assertGreaterThan($coursecacherev, $DB->get_field('course', 'cacherev', array('id' => $course->id)));
|
|
$this->assertEmpty(cache::make('core', 'coursemodinfo')->get($course->id));
|
|
|
|
// Add one to section that doesn't exist (this might rebuild modinfo).
|
|
course_add_cm_to_section($course, $cmids[2], 2);
|
|
$this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
|
|
$sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
|
|
$this->assertEquals($cmids[2], $sequence);
|
|
|
|
// Add using the 'before' option.
|
|
course_add_cm_to_section($course, $cmids[3], 2, $cmids[2]);
|
|
$this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
|
|
$sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
|
|
$this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence);
|
|
}
|
|
|
|
public function test_reorder_sections() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
|
|
$course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
|
|
$oldsections = array();
|
|
$sections = array();
|
|
foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
|
|
$oldsections[$section->section] = $section->id;
|
|
$sections[$section->id] = $section->section;
|
|
}
|
|
ksort($oldsections);
|
|
|
|
$neworder = reorder_sections($sections, 2, 4);
|
|
$neworder = array_keys($neworder);
|
|
$this->assertEquals($oldsections[0], $neworder[0]);
|
|
$this->assertEquals($oldsections[1], $neworder[1]);
|
|
$this->assertEquals($oldsections[2], $neworder[4]);
|
|
$this->assertEquals($oldsections[3], $neworder[2]);
|
|
$this->assertEquals($oldsections[4], $neworder[3]);
|
|
$this->assertEquals($oldsections[5], $neworder[5]);
|
|
$this->assertEquals($oldsections[6], $neworder[6]);
|
|
|
|
$neworder = reorder_sections($sections, 4, 2);
|
|
$neworder = array_keys($neworder);
|
|
$this->assertEquals($oldsections[0], $neworder[0]);
|
|
$this->assertEquals($oldsections[1], $neworder[1]);
|
|
$this->assertEquals($oldsections[2], $neworder[3]);
|
|
$this->assertEquals($oldsections[3], $neworder[4]);
|
|
$this->assertEquals($oldsections[4], $neworder[2]);
|
|
$this->assertEquals($oldsections[5], $neworder[5]);
|
|
$this->assertEquals($oldsections[6], $neworder[6]);
|
|
|
|
$neworder = reorder_sections(1, 2, 4);
|
|
$this->assertFalse($neworder);
|
|
}
|
|
|
|
public function test_move_section_down() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
|
|
$course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
|
|
$oldsections = array();
|
|
foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
|
|
$oldsections[$section->section] = $section->id;
|
|
}
|
|
ksort($oldsections);
|
|
|
|
// Test move section down..
|
|
move_section_to($course, 2, 4);
|
|
$sections = array();
|
|
foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
|
|
$sections[$section->section] = $section->id;
|
|
}
|
|
ksort($sections);
|
|
|
|
$this->assertEquals($oldsections[0], $sections[0]);
|
|
$this->assertEquals($oldsections[1], $sections[1]);
|
|
$this->assertEquals($oldsections[2], $sections[4]);
|
|
$this->assertEquals($oldsections[3], $sections[2]);
|
|
$this->assertEquals($oldsections[4], $sections[3]);
|
|
$this->assertEquals($oldsections[5], $sections[5]);
|
|
$this->assertEquals($oldsections[6], $sections[6]);
|
|
}
|
|
|
|
public function test_move_section_up() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
|
|
$course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
|
|
$oldsections = array();
|
|
foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
|
|
$oldsections[$section->section] = $section->id;
|
|
}
|
|
ksort($oldsections);
|
|
|
|
// Test move section up..
|
|
move_section_to($course, 6, 4);
|
|
$sections = array();
|
|
foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
|
|
$sections[$section->section] = $section->id;
|
|
}
|
|
ksort($sections);
|
|
|
|
$this->assertEquals($oldsections[0], $sections[0]);
|
|
$this->assertEquals($oldsections[1], $sections[1]);
|
|
$this->assertEquals($oldsections[2], $sections[2]);
|
|
$this->assertEquals($oldsections[3], $sections[3]);
|
|
$this->assertEquals($oldsections[4], $sections[5]);
|
|
$this->assertEquals($oldsections[5], $sections[6]);
|
|
$this->assertEquals($oldsections[6], $sections[4]);
|
|
}
|
|
|
|
public function test_move_section_marker() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
|
|
$course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
|
|
|
|
// Set course marker to the section we are going to move..
|
|
course_set_marker($course->id, 2);
|
|
// Verify that the course marker is set correctly.
|
|
$course = $DB->get_record('course', array('id' => $course->id));
|
|
$this->assertEquals(2, $course->marker);
|
|
|
|
// Test move the marked section down..
|
|
move_section_to($course, 2, 4);
|
|
|
|
// Verify that the course marker has been moved along with the section..
|
|
$course = $DB->get_record('course', array('id' => $course->id));
|
|
$this->assertEquals(4, $course->marker);
|
|
|
|
// Test move the marked section up..
|
|
move_section_to($course, 4, 3);
|
|
|
|
// Verify that the course marker has been moved along with the section..
|
|
$course = $DB->get_record('course', array('id' => $course->id));
|
|
$this->assertEquals(3, $course->marker);
|
|
|
|
// Test moving a non-marked section above the marked section..
|
|
move_section_to($course, 4, 2);
|
|
|
|
// Verify that the course marker has been moved down to accomodate..
|
|
$course = $DB->get_record('course', array('id' => $course->id));
|
|
$this->assertEquals(4, $course->marker);
|
|
|
|
// Test moving a non-marked section below the marked section..
|
|
move_section_to($course, 3, 6);
|
|
|
|
// Verify that the course marker has been up to accomodate..
|
|
$course = $DB->get_record('course', array('id' => $course->id));
|
|
$this->assertEquals(3, $course->marker);
|
|
}
|
|
|
|
public function test_course_can_delete_section() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$generator = $this->getDataGenerator();
|
|
|
|
$courseweeks = $generator->create_course(
|
|
array('numsections' => 5, 'format' => 'weeks'),
|
|
array('createsections' => true));
|
|
$assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1));
|
|
$assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2));
|
|
|
|
$coursetopics = $generator->create_course(
|
|
array('numsections' => 5, 'format' => 'topics'),
|
|
array('createsections' => true));
|
|
|
|
$coursesingleactivity = $generator->create_course(
|
|
array('format' => 'singleactivity'),
|
|
array('createsections' => true));
|
|
|
|
// Enrol student and teacher.
|
|
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
|
|
$student = $generator->create_user();
|
|
$teacher = $generator->create_user();
|
|
|
|
$generator->enrol_user($student->id, $courseweeks->id, $roleids['student']);
|
|
$generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']);
|
|
|
|
$generator->enrol_user($student->id, $coursetopics->id, $roleids['student']);
|
|
$generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']);
|
|
|
|
$generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']);
|
|
$generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']);
|
|
|
|
// Teacher should be able to delete sections (except for 0) in topics and weeks format.
|
|
$this->setUser($teacher);
|
|
|
|
// For topics and weeks formats will return false for section 0 and true for any other section.
|
|
$this->assertFalse(course_can_delete_section($courseweeks, 0));
|
|
$this->assertTrue(course_can_delete_section($courseweeks, 1));
|
|
|
|
$this->assertFalse(course_can_delete_section($coursetopics, 0));
|
|
$this->assertTrue(course_can_delete_section($coursetopics, 1));
|
|
|
|
// For singleactivity course format no section can be deleted.
|
|
$this->assertFalse(course_can_delete_section($coursesingleactivity, 0));
|
|
$this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
|
|
|
|
// Now let's revoke a capability from teacher to manage activity in section 1.
|
|
$modulecontext = context_module::instance($assign1->cmid);
|
|
assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'],
|
|
$modulecontext);
|
|
$this->assertFalse(course_can_delete_section($courseweeks, 1));
|
|
$this->assertTrue(course_can_delete_section($courseweeks, 2));
|
|
|
|
// Student does not have permissions to delete sections.
|
|
$this->setUser($student);
|
|
$this->assertFalse(course_can_delete_section($courseweeks, 1));
|
|
$this->assertFalse(course_can_delete_section($coursetopics, 1));
|
|
$this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
|
|
}
|
|
|
|
public function test_course_delete_section() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$generator = $this->getDataGenerator();
|
|
|
|
$course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'),
|
|
array('createsections' => true));
|
|
$assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0));
|
|
$assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1));
|
|
$assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
|
|
$assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
|
|
$assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3));
|
|
$assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5));
|
|
$assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6));
|
|
|
|
$this->setAdminUser();
|
|
|
|
// Attempt to delete non-existing section.
|
|
$this->assertFalse(course_delete_section($course, 10, false));
|
|
$this->assertFalse(course_delete_section($course, 9, true));
|
|
|
|
// Attempt to delete 0-section.
|
|
$this->assertFalse(course_delete_section($course, 0, true));
|
|
$this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid)));
|
|
|
|
// Delete last section.
|
|
$this->assertTrue(course_delete_section($course, 6, true));
|
|
$this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid)));
|
|
$this->assertEquals(5, course_get_format($course)->get_last_section_number());
|
|
|
|
// Delete empty section.
|
|
$this->assertTrue(course_delete_section($course, 4, false));
|
|
$this->assertEquals(4, course_get_format($course)->get_last_section_number());
|
|
|
|
// Delete section in the middle (2).
|
|
$this->assertFalse(course_delete_section($course, 2, false));
|
|
$this->assertTrue(course_delete_section($course, 2, true));
|
|
$this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid)));
|
|
$this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid)));
|
|
$this->assertEquals(3, course_get_format($course)->get_last_section_number());
|
|
$this->assertEquals(array(0 => array($assign0->cmid),
|
|
1 => array($assign1->cmid),
|
|
2 => array($assign3->cmid),
|
|
3 => array($assign5->cmid)), get_fast_modinfo($course)->sections);
|
|
|
|
// Remove marked section.
|
|
course_set_marker($course->id, 1);
|
|
$this->assertTrue(course_get_format($course)->is_section_current(1));
|
|
$this->assertTrue(course_delete_section($course, 1, true));
|
|
$this->assertFalse(course_get_format($course)->is_section_current(1));
|
|
}
|
|
|
|
public function test_get_course_display_name_for_list() {
|
|
global $CFG;
|
|
$this->resetAfterTest(true);
|
|
|
|
$course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life'));
|
|
|
|
$CFG->courselistshortnames = 0;
|
|
$this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course));
|
|
|
|
$CFG->courselistshortnames = 1;
|
|
$this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
|
|
}
|
|
|
|
public function test_move_module_in_course() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
// Setup fixture
|
|
$course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true));
|
|
$forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
|
|
|
|
$cms = get_fast_modinfo($course)->get_cms();
|
|
$cm = reset($cms);
|
|
|
|
$newsection = get_fast_modinfo($course)->get_section_info(3);
|
|
$oldsectionid = $cm->section;
|
|
|
|
// Perform the move
|
|
moveto_module($cm, $newsection);
|
|
|
|
$cms = get_fast_modinfo($course)->get_cms();
|
|
$cm = reset($cms);
|
|
|
|
// Check that the cached modinfo contains the correct section info
|
|
$modinfo = get_fast_modinfo($course);
|
|
$this->assertTrue(empty($modinfo->sections[0]));
|
|
$this->assertFalse(empty($modinfo->sections[3]));
|
|
|
|
// Check that the old section's sequence no longer contains this ID
|
|
$oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
|
|
$oldsequences = explode(',', $newsection->sequence);
|
|
$this->assertFalse(in_array($cm->id, $oldsequences));
|
|
|
|
// Check that the new section's sequence now contains this ID
|
|
$newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
|
|
$newsequences = explode(',', $newsection->sequence);
|
|
$this->assertTrue(in_array($cm->id, $newsequences));
|
|
|
|
// Check that the section number has been changed in the cm
|
|
$this->assertEquals($newsection->id, $cm->section);
|
|
|
|
|
|
// Perform a second move as some issues were only seen on the second move
|
|
$newsection = get_fast_modinfo($course)->get_section_info(2);
|
|
$oldsectionid = $cm->section;
|
|
moveto_module($cm, $newsection);
|
|
|
|
$cms = get_fast_modinfo($course)->get_cms();
|
|
$cm = reset($cms);
|
|
|
|
// Check that the cached modinfo contains the correct section info
|
|
$modinfo = get_fast_modinfo($course);
|
|
$this->assertTrue(empty($modinfo->sections[0]));
|
|
$this->assertFalse(empty($modinfo->sections[2]));
|
|
|
|
// Check that the old section's sequence no longer contains this ID
|
|
$oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
|
|
$oldsequences = explode(',', $newsection->sequence);
|
|
$this->assertFalse(in_array($cm->id, $oldsequences));
|
|
|
|
// Check that the new section's sequence now contains this ID
|
|
$newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
|
|
$newsequences = explode(',', $newsection->sequence);
|
|
$this->assertTrue(in_array($cm->id, $newsequences));
|
|
}
|
|
|
|
public function test_module_visibility() {
|
|
$this->setAdminUser();
|
|
$this->resetAfterTest(true);
|
|
|
|
// Create course and modules.
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 5));
|
|
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
|
|
$assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
|
|
$modules = compact('forum', 'assign');
|
|
|
|
// Hiding the modules.
|
|
foreach ($modules as $mod) {
|
|
set_coursemodule_visible($mod->cmid, 0);
|
|
$this->check_module_visibility($mod, 0, 0);
|
|
}
|
|
|
|
// Showing the modules.
|
|
foreach ($modules as $mod) {
|
|
set_coursemodule_visible($mod->cmid, 1);
|
|
$this->check_module_visibility($mod, 1, 1);
|
|
}
|
|
}
|
|
|
|
public function test_section_visibility_events() {
|
|
$this->setAdminUser();
|
|
$this->resetAfterTest(true);
|
|
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 1), array('createsections' => true));
|
|
$sectionnumber = 1;
|
|
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
|
array('section' => $sectionnumber));
|
|
$assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
|
|
'course' => $course->id), array('section' => $sectionnumber));
|
|
$sink = $this->redirectEvents();
|
|
set_section_visible($course->id, $sectionnumber, 0);
|
|
$events = $sink->get_events();
|
|
|
|
// Extract the number of events related to what we are testing, other events
|
|
// such as course_section_updated could have been triggered.
|
|
$count = 0;
|
|
foreach ($events as $event) {
|
|
if ($event instanceof \core\event\course_module_updated) {
|
|
$count++;
|
|
}
|
|
}
|
|
$this->assertSame(2, $count);
|
|
$sink->close();
|
|
}
|
|
|
|
public function test_section_visibility() {
|
|
$this->setAdminUser();
|
|
$this->resetAfterTest(true);
|
|
|
|
// Create course.
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
|
|
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Testing an empty section.
|
|
$sectionnumber = 1;
|
|
set_section_visible($course->id, $sectionnumber, 0);
|
|
$section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
|
|
$this->assertEquals($section_info->visible, 0);
|
|
set_section_visible($course->id, $sectionnumber, 1);
|
|
$section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
|
|
$this->assertEquals($section_info->visible, 1);
|
|
|
|
// Checking that an event was fired.
|
|
$events = $sink->get_events();
|
|
$this->assertInstanceOf('\core\event\course_section_updated', $events[0]);
|
|
$sink->close();
|
|
|
|
// Testing a section with visible modules.
|
|
$sectionnumber = 2;
|
|
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
|
array('section' => $sectionnumber));
|
|
$assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
|
|
'course' => $course->id), array('section' => $sectionnumber));
|
|
$modules = compact('forum', 'assign');
|
|
set_section_visible($course->id, $sectionnumber, 0);
|
|
$section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
|
|
$this->assertEquals($section_info->visible, 0);
|
|
foreach ($modules as $mod) {
|
|
$this->check_module_visibility($mod, 0, 1);
|
|
}
|
|
set_section_visible($course->id, $sectionnumber, 1);
|
|
$section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
|
|
$this->assertEquals($section_info->visible, 1);
|
|
foreach ($modules as $mod) {
|
|
$this->check_module_visibility($mod, 1, 1);
|
|
}
|
|
|
|
// Testing a section with hidden modules, which should stay hidden.
|
|
$sectionnumber = 3;
|
|
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
|
array('section' => $sectionnumber));
|
|
$assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
|
|
'course' => $course->id), array('section' => $sectionnumber));
|
|
$modules = compact('forum', 'assign');
|
|
foreach ($modules as $mod) {
|
|
set_coursemodule_visible($mod->cmid, 0);
|
|
$this->check_module_visibility($mod, 0, 0);
|
|
}
|
|
set_section_visible($course->id, $sectionnumber, 0);
|
|
$section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
|
|
$this->assertEquals($section_info->visible, 0);
|
|
foreach ($modules as $mod) {
|
|
$this->check_module_visibility($mod, 0, 0);
|
|
}
|
|
set_section_visible($course->id, $sectionnumber, 1);
|
|
$section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
|
|
$this->assertEquals($section_info->visible, 1);
|
|
foreach ($modules as $mod) {
|
|
$this->check_module_visibility($mod, 0, 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to assert that a module has correctly been made visible, or hidden.
|
|
*
|
|
* @param stdClass $mod module information
|
|
* @param int $visibility the current state of the module
|
|
* @param int $visibleold the current state of the visibleold property
|
|
* @return void
|
|
*/
|
|
public function check_module_visibility($mod, $visibility, $visibleold) {
|
|
global $DB;
|
|
$cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
|
|
$this->assertEquals($visibility, $cm->visible);
|
|
$this->assertEquals($visibleold, $cm->visibleold);
|
|
|
|
// Check the module grade items.
|
|
$grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname,
|
|
'iteminstance' => $cm->instance, 'courseid' => $cm->course));
|
|
if ($grade_items) {
|
|
foreach ($grade_items as $grade_item) {
|
|
if ($visibility) {
|
|
$this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible");
|
|
} else {
|
|
$this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check the events visibility.
|
|
if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) {
|
|
foreach ($events as $event) {
|
|
$calevent = new calendar_event($event);
|
|
$this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility");
|
|
}
|
|
}
|
|
}
|
|
|
|
public function test_course_page_type_list() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
// Create a category.
|
|
$category = new stdClass();
|
|
$category->name = 'Test Category';
|
|
|
|
$testcategory = $this->getDataGenerator()->create_category($category);
|
|
|
|
// Create a course.
|
|
$course = new stdClass();
|
|
$course->fullname = 'Apu loves Unit Təsts';
|
|
$course->shortname = 'Spread the lŭve';
|
|
$course->idnumber = '123';
|
|
$course->summary = 'Awesome!';
|
|
$course->summaryformat = FORMAT_PLAIN;
|
|
$course->format = 'topics';
|
|
$course->newsitems = 0;
|
|
$course->numsections = 5;
|
|
$course->category = $testcategory->id;
|
|
|
|
$testcourse = $this->getDataGenerator()->create_course($course);
|
|
|
|
// Create contexts.
|
|
$coursecontext = context_course::instance($testcourse->id);
|
|
$parentcontext = $coursecontext->get_parent_context(); // Not actually used.
|
|
$pagetype = 'page-course-x'; // Not used either.
|
|
$pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext);
|
|
|
|
// Page type lists for normal courses.
|
|
$testpagetypelist1 = array();
|
|
$testpagetypelist1['*'] = 'Any page';
|
|
$testpagetypelist1['course-*'] = 'Any course page';
|
|
$testpagetypelist1['course-view-*'] = 'Any type of course main page';
|
|
|
|
$this->assertEquals($testpagetypelist1, $pagetypelist);
|
|
|
|
// Get the context for the front page course.
|
|
$sitecoursecontext = context_course::instance(SITEID);
|
|
$pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext);
|
|
|
|
// Page type list for the front page course.
|
|
$testpagetypelist2 = array('*' => 'Any page');
|
|
$this->assertEquals($testpagetypelist2, $pagetypelist);
|
|
|
|
// Make sure that providing no current context to the function doesn't result in an error.
|
|
// Calls made from generate_page_type_patterns() may provide null values.
|
|
$pagetypelist = course_page_type_list($pagetype, null, null);
|
|
$this->assertEquals($pagetypelist, $testpagetypelist1);
|
|
}
|
|
|
|
public function test_compare_activities_by_time_desc() {
|
|
|
|
// Let's create some test data.
|
|
$activitiesivities = array();
|
|
$x = new stdClass();
|
|
$x->timestamp = null;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 1;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 3;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 0;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 5;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 5;
|
|
$activities[] = $x;
|
|
|
|
// Do the sorting.
|
|
usort($activities, 'compare_activities_by_time_desc');
|
|
|
|
// Let's check the result.
|
|
$last = 10;
|
|
foreach($activities as $activity) {
|
|
if (empty($activity->timestamp)) {
|
|
$activity->timestamp = 0;
|
|
}
|
|
$this->assertLessThanOrEqual($last, $activity->timestamp);
|
|
}
|
|
}
|
|
|
|
public function test_compare_activities_by_time_asc() {
|
|
|
|
// Let's create some test data.
|
|
$activities = array();
|
|
$x = new stdClass();
|
|
$x->timestamp = null;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 1;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 3;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 0;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 5;
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$activities[] = $x;
|
|
|
|
$x = new stdClass();
|
|
$x->timestamp = 5;
|
|
$activities[] = $x;
|
|
|
|
// Do the sorting.
|
|
usort($activities, 'compare_activities_by_time_asc');
|
|
|
|
// Let's check the result.
|
|
$last = 0;
|
|
foreach($activities as $activity) {
|
|
if (empty($activity->timestamp)) {
|
|
$activity->timestamp = 0;
|
|
}
|
|
$this->assertGreaterThanOrEqual($last, $activity->timestamp);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests moving a module between hidden/visible sections and
|
|
* verifies that the course/module visiblity seettings are
|
|
* retained.
|
|
*/
|
|
public function test_moveto_module_between_hidden_sections() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true));
|
|
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
|
|
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
|
|
$quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
|
|
|
|
// Set the page as hidden
|
|
set_coursemodule_visible($page->cmid, 0);
|
|
|
|
// Set sections 3 as hidden.
|
|
set_section_visible($course->id, 3, 0);
|
|
|
|
$modinfo = get_fast_modinfo($course);
|
|
|
|
$hiddensection = $modinfo->get_section_info(3);
|
|
// New section is definitely not visible:
|
|
$this->assertEquals($hiddensection->visible, 0);
|
|
|
|
$forumcm = $modinfo->cms[$forum->cmid];
|
|
$pagecm = $modinfo->cms[$page->cmid];
|
|
|
|
// Move the forum and the page to a hidden section, make sure moveto_module returns 0 as new visibility state.
|
|
$this->assertEquals(0, moveto_module($forumcm, $hiddensection));
|
|
$this->assertEquals(0, moveto_module($pagecm, $hiddensection));
|
|
|
|
$modinfo = get_fast_modinfo($course);
|
|
|
|
// Verify that forum and page have been moved to the hidden section and quiz has not.
|
|
$this->assertContainsEquals($forum->cmid, $modinfo->sections[3]);
|
|
$this->assertContainsEquals($page->cmid, $modinfo->sections[3]);
|
|
$this->assertNotContainsEquals($quiz->cmid, $modinfo->sections[3]);
|
|
|
|
// Verify that forum has been made invisible.
|
|
$forumcm = $modinfo->cms[$forum->cmid];
|
|
$this->assertEquals($forumcm->visible, 0);
|
|
// Verify that old state has been retained.
|
|
$this->assertEquals($forumcm->visibleold, 1);
|
|
|
|
// Verify that page has stayed invisible.
|
|
$pagecm = $modinfo->cms[$page->cmid];
|
|
$this->assertEquals($pagecm->visible, 0);
|
|
// Verify that old state has been retained.
|
|
$this->assertEquals($pagecm->visibleold, 0);
|
|
|
|
// Verify that quiz has been unaffected.
|
|
$quizcm = $modinfo->cms[$quiz->cmid];
|
|
$this->assertEquals($quizcm->visible, 1);
|
|
|
|
// Move forum and page back to visible section.
|
|
// Make sure the visibility is restored to the original value (visible for forum and hidden for page).
|
|
$visiblesection = $modinfo->get_section_info(2);
|
|
$this->assertEquals(1, moveto_module($forumcm, $visiblesection));
|
|
$this->assertEquals(0, moveto_module($pagecm, $visiblesection));
|
|
|
|
$modinfo = get_fast_modinfo($course);
|
|
|
|
// Double check that forum has been made visible.
|
|
$forumcm = $modinfo->cms[$forum->cmid];
|
|
$this->assertEquals($forumcm->visible, 1);
|
|
|
|
// Double check that page has stayed invisible.
|
|
$pagecm = $modinfo->cms[$page->cmid];
|
|
$this->assertEquals($pagecm->visible, 0);
|
|
|
|
// Move the page in the same section (this is what mod duplicate does).
|
|
// Visibility of page remains 0.
|
|
$this->assertEquals(0, moveto_module($pagecm, $visiblesection, $forumcm));
|
|
|
|
// Double check that the the page is still hidden.
|
|
$modinfo = get_fast_modinfo($course);
|
|
$pagecm = $modinfo->cms[$page->cmid];
|
|
$this->assertEquals($pagecm->visible, 0);
|
|
}
|
|
|
|
/**
|
|
* Tests moving a module around in the same section. moveto_module()
|
|
* is called this way in modduplicate.
|
|
*/
|
|
public function test_moveto_module_in_same_section() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
|
|
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
|
|
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
|
|
|
|
// Simulate inconsistent visible/visibleold values (MDL-38713).
|
|
$cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST);
|
|
$cm->visible = 0;
|
|
$cm->visibleold = 1;
|
|
$DB->update_record('course_modules', $cm);
|
|
|
|
$modinfo = get_fast_modinfo($course);
|
|
$forumcm = $modinfo->cms[$forum->cmid];
|
|
$pagecm = $modinfo->cms[$page->cmid];
|
|
|
|
// Verify that page is hidden.
|
|
$this->assertEquals($pagecm->visible, 0);
|
|
|
|
// Verify section 0 is where all mods added.
|
|
$section = $modinfo->get_section_info(0);
|
|
$this->assertEquals($section->id, $forumcm->section);
|
|
$this->assertEquals($section->id, $pagecm->section);
|
|
|
|
|
|
// Move the page inside the hidden section. Make sure it is hidden.
|
|
$this->assertEquals(0, moveto_module($pagecm, $section, $forumcm));
|
|
|
|
// Double check that the the page is still hidden.
|
|
$modinfo = get_fast_modinfo($course);
|
|
$pagecm = $modinfo->cms[$page->cmid];
|
|
$this->assertEquals($pagecm->visible, 0);
|
|
}
|
|
|
|
/**
|
|
* Tests the function that deletes a course module
|
|
*
|
|
* @param string $type The type of module for the test
|
|
* @param array $options The options for the module creation
|
|
* @dataProvider provider_course_delete_module
|
|
*/
|
|
public function test_course_delete_module($type, $options) {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
$this->setAdminUser();
|
|
|
|
// Create course and modules.
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 5));
|
|
$options['course'] = $course->id;
|
|
|
|
// Generate an assignment with due date (will generate a course event).
|
|
$module = $this->getDataGenerator()->create_module($type, $options);
|
|
|
|
// Get the module context.
|
|
$modcontext = context_module::instance($module->cmid);
|
|
|
|
$assocblog = $this->create_module_asscociated_blog($course, $modcontext);
|
|
|
|
// Verify context exists.
|
|
$this->assertInstanceOf('context_module', $modcontext);
|
|
|
|
// Make module specific messes.
|
|
switch ($type) {
|
|
case 'assign':
|
|
// Add some tags to this assignment.
|
|
core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3'));
|
|
core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, array('Tag 3', 'Tag 4', 'Tag 5'));
|
|
|
|
// Confirm the tag instances were added.
|
|
$criteria = array('component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id);
|
|
$this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
|
|
$criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
|
|
$this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
|
|
|
|
// Verify event assignment event has been generated.
|
|
$eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
|
|
$this->assertEquals(1, $eventcount);
|
|
|
|
break;
|
|
case 'quiz':
|
|
$qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
|
|
$qcat = $qgen->create_question_category(array('contextid' => $modcontext->id));
|
|
$qgen->create_question('shortanswer', null, array('category' => $qcat->id));
|
|
$qgen->create_question('shortanswer', null, array('category' => $qcat->id));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Run delete..
|
|
course_delete_module($module->cmid);
|
|
|
|
// Verify the context has been removed.
|
|
$this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
|
|
|
|
// Verify the course_module record has been deleted.
|
|
$cmcount = $DB->count_records('course_modules', array('id' => $module->cmid));
|
|
$this->assertEmpty($cmcount);
|
|
|
|
// Verify the blog_association record has been deleted.
|
|
$this->assertCount(0, $DB->get_records('blog_association',
|
|
array('contextid' => $modcontext->id)));
|
|
|
|
// Verify the blog post record has been deleted.
|
|
$this->assertCount(0, $DB->get_records('post',
|
|
array('id' => $assocblog->id)));
|
|
|
|
// Verify the tag instance record has been deleted.
|
|
$this->assertCount(0, $DB->get_records('tag_instance',
|
|
array('itemid' => $assocblog->id)));
|
|
|
|
// Test clean up of module specific messes.
|
|
switch ($type) {
|
|
case 'assign':
|
|
// Verify event assignment events have been removed.
|
|
$eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
|
|
$this->assertEmpty($eventcount);
|
|
|
|
// Verify the tag instances were deleted.
|
|
$criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
|
|
$this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
|
|
|
|
$criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
|
|
$this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
|
|
break;
|
|
case 'quiz':
|
|
// Verify category deleted.
|
|
$criteria = array('contextid' => $modcontext->id);
|
|
$this->assertEquals(0, $DB->count_records('question_categories', $criteria));
|
|
|
|
// Verify questions deleted.
|
|
$criteria = [$qcat->id];
|
|
$sql = 'SELECT COUNT(q.id)
|
|
FROM {question} q
|
|
JOIN {question_versions} qv ON qv.questionid = q.id
|
|
JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
|
|
WHERE qbe.questioncategoryid = ?';
|
|
$this->assertEquals(0, $DB->count_records_sql($sql, $criteria));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_created event works as expected.
|
|
*/
|
|
public function test_course_created_event() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Catch the events.
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Create the course with an id number which is used later when generating a course via the imsenterprise plugin.
|
|
$data = new stdClass();
|
|
$data->idnumber = 'idnumber';
|
|
$course = $this->getDataGenerator()->create_course($data);
|
|
// Get course from DB for comparison.
|
|
$course = $DB->get_record('course', array('id' => $course->id));
|
|
|
|
// Capture the event.
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = $events[0];
|
|
$this->assertInstanceOf('\core\event\course_created', $event);
|
|
$this->assertEquals('course', $event->objecttable);
|
|
$this->assertEquals($course->id, $event->objectid);
|
|
$this->assertEquals(context_course::instance($course->id), $event->get_context());
|
|
$this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
|
|
$this->assertEquals('course_created', $event->get_legacy_eventname());
|
|
$this->assertEventLegacyData($course, $event);
|
|
$expectedlog = array(SITEID, 'course', 'new', 'view.php?id=' . $course->id, $course->fullname . ' (ID ' . $course->id . ')');
|
|
$this->assertEventLegacyLogData($expectedlog, $event);
|
|
|
|
// Now we want to trigger creating a course via the imsenterprise.
|
|
// Delete the course we created earlier, as we want the imsenterprise plugin to create this.
|
|
// We do not want print out any of the text this function generates while doing this, which is why
|
|
// we are using ob_start() and ob_end_clean().
|
|
ob_start();
|
|
delete_course($course);
|
|
ob_end_clean();
|
|
|
|
// Create the XML file we want to use.
|
|
$course->category = (array)$course->category;
|
|
$imstestcase = new enrol_imsenterprise_testcase();
|
|
$imstestcase->imsplugin = enrol_get_plugin('imsenterprise');
|
|
$imstestcase->set_test_config();
|
|
$imstestcase->set_xml_file(false, array($course));
|
|
|
|
// Capture the event.
|
|
$sink = $this->redirectEvents();
|
|
$imstestcase->imsplugin->cron();
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
$event = null;
|
|
foreach ($events as $eventinfo) {
|
|
if ($eventinfo instanceof \core\event\course_created ) {
|
|
$event = $eventinfo;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Validate the event triggered is \core\event\course_created. There is no need to validate the other values
|
|
// as they have already been validated in the previous steps. Here we only want to make sure that when the
|
|
// imsenterprise plugin creates a course an event is triggered.
|
|
$this->assertInstanceOf('\core\event\course_created', $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_updated event works as expected.
|
|
*/
|
|
public function test_course_updated_event() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Create a course.
|
|
$course = $this->getDataGenerator()->create_course();
|
|
|
|
// Create a category we are going to move this course to.
|
|
$category = $this->getDataGenerator()->create_category();
|
|
|
|
// Create a hidden category we are going to move this course to.
|
|
$categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
|
|
|
|
// Update course and catch course_updated event.
|
|
$sink = $this->redirectEvents();
|
|
update_course($course);
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Get updated course information from the DB.
|
|
$updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
|
|
// Validate event.
|
|
$event = array_shift($events);
|
|
$this->assertInstanceOf('\core\event\course_updated', $event);
|
|
$this->assertEquals('course', $event->objecttable);
|
|
$this->assertEquals($updatedcourse->id, $event->objectid);
|
|
$this->assertEquals(context_course::instance($course->id), $event->get_context());
|
|
$url = new moodle_url('/course/edit.php', array('id' => $event->objectid));
|
|
$this->assertEquals($url, $event->get_url());
|
|
$this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid));
|
|
$this->assertEquals('course_updated', $event->get_legacy_eventname());
|
|
$this->assertEventLegacyData($updatedcourse, $event);
|
|
$expectedlog = array($updatedcourse->id, 'course', 'update', 'edit.php?id=' . $course->id, $course->id);
|
|
$this->assertEventLegacyLogData($expectedlog, $event);
|
|
|
|
// Move course and catch course_updated event.
|
|
$sink = $this->redirectEvents();
|
|
move_courses(array($course->id), $category->id);
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Return the moved course information from the DB.
|
|
$movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
|
|
// Validate event.
|
|
$event = array_shift($events);
|
|
$this->assertInstanceOf('\core\event\course_updated', $event);
|
|
$this->assertEquals('course', $event->objecttable);
|
|
$this->assertEquals($movedcourse->id, $event->objectid);
|
|
$this->assertEquals(context_course::instance($course->id), $event->get_context());
|
|
$this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
|
|
$this->assertEquals('course_updated', $event->get_legacy_eventname());
|
|
$this->assertEventLegacyData($movedcourse, $event);
|
|
$expectedlog = array($movedcourse->id, 'course', 'move', 'edit.php?id=' . $movedcourse->id, $movedcourse->id);
|
|
$this->assertEventLegacyLogData($expectedlog, $event);
|
|
|
|
// Move course to hidden category and catch course_updated event.
|
|
$sink = $this->redirectEvents();
|
|
move_courses(array($course->id), $categoryhidden->id);
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Return the moved course information from the DB.
|
|
$movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
|
|
// Validate event.
|
|
$event = array_shift($events);
|
|
$this->assertInstanceOf('\core\event\course_updated', $event);
|
|
$this->assertEquals('course', $event->objecttable);
|
|
$this->assertEquals($movedcoursehidden->id, $event->objectid);
|
|
$this->assertEquals(context_course::instance($course->id), $event->get_context());
|
|
$this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
|
|
$this->assertEquals('course_updated', $event->get_legacy_eventname());
|
|
$this->assertEventLegacyData($movedcoursehidden, $event);
|
|
$expectedlog = array($movedcoursehidden->id, 'course', 'move', 'edit.php?id=' . $movedcoursehidden->id, $movedcoursehidden->id);
|
|
$this->assertEventLegacyLogData($expectedlog, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_updated event logs changes.
|
|
*/
|
|
public function test_course_updated_event_with_changes() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Create a course.
|
|
$course = $this->getDataGenerator()->create_course((object)['visible' => 1]);
|
|
|
|
$editedcourse = $DB->get_record('course', ['id' => $course->id]);
|
|
$editedcourse->visible = 0;
|
|
|
|
// Update course and catch course_updated event.
|
|
$sink = $this->redirectEvents();
|
|
update_course($editedcourse);
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
$event = array_shift($events);
|
|
$this->assertInstanceOf('\core\event\course_updated', $event);
|
|
$otherdata = [
|
|
'shortname' => $course->shortname,
|
|
'fullname' => $course->fullname,
|
|
'updatedfields' => [
|
|
'visible' => 0
|
|
]
|
|
];
|
|
$this->assertEquals($otherdata, $event->other);
|
|
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_deleted event works as expected.
|
|
*/
|
|
public function test_course_deleted_event() {
|
|
$this->resetAfterTest();
|
|
|
|
// Create the course.
|
|
$course = $this->getDataGenerator()->create_course();
|
|
|
|
// Save the course context before we delete the course.
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
// Catch the update event.
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Call delete_course() which will trigger the course_deleted event and the course_content_deleted
|
|
// event. This function prints out data to the screen, which we do not want during a PHPUnit test,
|
|
// so use ob_start and ob_end_clean to prevent this.
|
|
ob_start();
|
|
delete_course($course);
|
|
ob_end_clean();
|
|
|
|
// Capture the event.
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = array_pop($events);
|
|
$this->assertInstanceOf('\core\event\course_deleted', $event);
|
|
$this->assertEquals('course', $event->objecttable);
|
|
$this->assertEquals($course->id, $event->objectid);
|
|
$this->assertEquals($coursecontext->id, $event->contextid);
|
|
$this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
|
|
$this->assertEquals('course_deleted', $event->get_legacy_eventname());
|
|
$eventdata = $event->get_data();
|
|
$this->assertSame($course->idnumber, $eventdata['other']['idnumber']);
|
|
$this->assertSame($course->fullname, $eventdata['other']['fullname']);
|
|
$this->assertSame($course->shortname, $eventdata['other']['shortname']);
|
|
|
|
// The legacy data also passed the context in the course object and substitutes timemodified with the current date.
|
|
$expectedlegacy = clone($course);
|
|
$expectedlegacy->context = $coursecontext;
|
|
$expectedlegacy->timemodified = $event->timecreated;
|
|
$this->assertEventLegacyData($expectedlegacy, $event);
|
|
|
|
// Validate legacy log data.
|
|
$expectedlog = array(SITEID, 'course', 'delete', 'view.php?id=' . $course->id, $course->fullname . '(ID ' . $course->id . ')');
|
|
$this->assertEventLegacyLogData($expectedlog, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_content_deleted event works as expected.
|
|
*/
|
|
public function test_course_content_deleted_event() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Create the course.
|
|
$course = $this->getDataGenerator()->create_course();
|
|
|
|
// Get the course from the DB. The data generator adds some extra properties, such as
|
|
// numsections, to the course object which will fail the assertions later on.
|
|
$course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
|
|
|
|
// Save the course context before we delete the course.
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
// Catch the update event.
|
|
$sink = $this->redirectEvents();
|
|
|
|
remove_course_contents($course->id, false);
|
|
|
|
// Capture the event.
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = array_pop($events);
|
|
$this->assertInstanceOf('\core\event\course_content_deleted', $event);
|
|
$this->assertEquals('course', $event->objecttable);
|
|
$this->assertEquals($course->id, $event->objectid);
|
|
$this->assertEquals($coursecontext->id, $event->contextid);
|
|
$this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
|
|
$this->assertEquals('course_content_removed', $event->get_legacy_eventname());
|
|
// The legacy data also passed the context and options in the course object.
|
|
$course->context = $coursecontext;
|
|
$course->options = array();
|
|
$this->assertEventLegacyData($course, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_category_deleted event works as expected.
|
|
*/
|
|
public function test_course_category_deleted_event() {
|
|
$this->resetAfterTest();
|
|
|
|
// Create a category.
|
|
$category = $this->getDataGenerator()->create_category();
|
|
|
|
// Save the original record/context before it is deleted.
|
|
$categoryrecord = $category->get_db_record();
|
|
$categorycontext = context_coursecat::instance($category->id);
|
|
|
|
// Catch the update event.
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Delete the category.
|
|
$category->delete_full();
|
|
|
|
// Capture the event.
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = $events[0];
|
|
$this->assertInstanceOf('\core\event\course_category_deleted', $event);
|
|
$this->assertEquals('course_categories', $event->objecttable);
|
|
$this->assertEquals($category->id, $event->objectid);
|
|
$this->assertEquals($categorycontext->id, $event->contextid);
|
|
$this->assertEquals([
|
|
'name' => $category->name,
|
|
], $event->other);
|
|
$this->assertEquals($categoryrecord, $event->get_record_snapshot($event->objecttable, $event->objectid));
|
|
$this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
|
|
$this->assertEquals(null, $event->get_url());
|
|
$this->assertEventLegacyData($category, $event);
|
|
$expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category->name . '(ID ' . $category->id . ')');
|
|
$this->assertEventLegacyLogData($expectedlog, $event);
|
|
|
|
// Create two categories.
|
|
$category = $this->getDataGenerator()->create_category();
|
|
$category2 = $this->getDataGenerator()->create_category();
|
|
|
|
// Save the original record/context before it is moved and then deleted.
|
|
$category2record = $category2->get_db_record();
|
|
$category2context = context_coursecat::instance($category2->id);
|
|
|
|
// Catch the update event.
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Move the category.
|
|
$category2->delete_move($category->id);
|
|
|
|
// Capture the event.
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = $events[0];
|
|
$this->assertInstanceOf('\core\event\course_category_deleted', $event);
|
|
$this->assertEquals('course_categories', $event->objecttable);
|
|
$this->assertEquals($category2->id, $event->objectid);
|
|
$this->assertEquals($category2context->id, $event->contextid);
|
|
$this->assertEquals([
|
|
'name' => $category2->name,
|
|
'contentmovedcategoryid' => $category->id,
|
|
], $event->other);
|
|
$this->assertEquals($category2record, $event->get_record_snapshot($event->objecttable, $event->objectid));
|
|
$this->assertEquals('course_category_deleted', $event->get_legacy_eventname());
|
|
$this->assertEventLegacyData($category2, $event);
|
|
$expectedlog = array(SITEID, 'category', 'delete', 'index.php', $category2->name . '(ID ' . $category2->id . ')');
|
|
$this->assertEventLegacyLogData($expectedlog, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_backup_created event works as expected.
|
|
*/
|
|
public function test_course_backup_created_event() {
|
|
global $CFG;
|
|
|
|
// Get the necessary files to perform backup and restore.
|
|
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
|
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Set to admin user.
|
|
$this->setAdminUser();
|
|
|
|
// The user id is going to be 2 since we are the admin user.
|
|
$userid = 2;
|
|
|
|
// Create a course.
|
|
$course = $this->getDataGenerator()->create_course();
|
|
|
|
// Create backup file and save it to the backup location.
|
|
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
|
|
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
|
|
$sink = $this->redirectEvents();
|
|
$bc->execute_plan();
|
|
|
|
// Capture the event.
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = array_pop($events);
|
|
$this->assertInstanceOf('\core\event\course_backup_created', $event);
|
|
$this->assertEquals('course', $event->objecttable);
|
|
$this->assertEquals($bc->get_courseid(), $event->objectid);
|
|
$this->assertEquals(context_course::instance($bc->get_courseid())->id, $event->contextid);
|
|
|
|
$url = new moodle_url('/course/view.php', array('id' => $event->objectid));
|
|
$this->assertEquals($url, $event->get_url());
|
|
$this->assertEventContextNotUsed($event);
|
|
|
|
// Destroy the resource controller since we are done using it.
|
|
$bc->destroy();
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_restored event works as expected.
|
|
*/
|
|
public function test_course_restored_event() {
|
|
global $CFG;
|
|
|
|
// Get the necessary files to perform backup and restore.
|
|
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
|
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Set to admin user.
|
|
$this->setAdminUser();
|
|
|
|
// The user id is going to be 2 since we are the admin user.
|
|
$userid = 2;
|
|
|
|
// Create a course.
|
|
$course = $this->getDataGenerator()->create_course();
|
|
|
|
// Create backup file and save it to the backup location.
|
|
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
|
|
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
|
|
$bc->execute_plan();
|
|
$results = $bc->get_results();
|
|
$file = $results['backup_destination'];
|
|
$fp = get_file_packer('application/vnd.moodle.backup');
|
|
$filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
|
|
$file->extract_to_pathname($fp, $filepath);
|
|
$bc->destroy();
|
|
|
|
// Now we want to catch the restore course event.
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Now restore the course to trigger the event.
|
|
$rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO,
|
|
backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE);
|
|
$rc->execute_precheck();
|
|
$rc->execute_plan();
|
|
|
|
// Capture the event.
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = array_pop($events);
|
|
$this->assertInstanceOf('\core\event\course_restored', $event);
|
|
$this->assertEquals('course', $event->objecttable);
|
|
$this->assertEquals($rc->get_courseid(), $event->objectid);
|
|
$this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid);
|
|
$this->assertEquals('course_restored', $event->get_legacy_eventname());
|
|
$legacydata = (object) array(
|
|
'courseid' => $rc->get_courseid(),
|
|
'userid' => $rc->get_userid(),
|
|
'type' => $rc->get_type(),
|
|
'target' => $rc->get_target(),
|
|
'mode' => $rc->get_mode(),
|
|
'operation' => $rc->get_operation(),
|
|
'samesite' => $rc->is_samesite()
|
|
);
|
|
$url = new moodle_url('/course/view.php', array('id' => $event->objectid));
|
|
$this->assertEquals($url, $event->get_url());
|
|
$this->assertEventLegacyData($legacydata, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
|
|
// Destroy the resource controller since we are done using it.
|
|
$rc->destroy();
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_section_updated event works as expected.
|
|
*/
|
|
public function test_course_section_updated_event() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Create the course with sections.
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
|
|
$sections = $DB->get_records('course_sections', array('course' => $course->id));
|
|
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
$section = array_pop($sections);
|
|
$section->name = 'Test section';
|
|
$section->summary = 'Test section summary';
|
|
$DB->update_record('course_sections', $section);
|
|
|
|
// Trigger an event for course section update.
|
|
$event = \core\event\course_section_updated::create(
|
|
array(
|
|
'objectid' => $section->id,
|
|
'courseid' => $course->id,
|
|
'context' => context_course::instance($course->id),
|
|
'other' => array(
|
|
'sectionnum' => $section->section
|
|
)
|
|
)
|
|
);
|
|
$event->add_record_snapshot('course_sections', $section);
|
|
// Trigger and catch event.
|
|
$sink = $this->redirectEvents();
|
|
$event->trigger();
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = $events[0];
|
|
$this->assertInstanceOf('\core\event\course_section_updated', $event);
|
|
$this->assertEquals('course_sections', $event->objecttable);
|
|
$this->assertEquals($section->id, $event->objectid);
|
|
$this->assertEquals($course->id, $event->courseid);
|
|
$this->assertEquals($coursecontext->id, $event->contextid);
|
|
$this->assertEquals($section->section, $event->other['sectionnum']);
|
|
$expecteddesc = "The user with id '{$event->userid}' updated section number '{$event->other['sectionnum']}' for the course with id '{$event->courseid}'";
|
|
$this->assertEquals($expecteddesc, $event->get_description());
|
|
$url = new moodle_url('/course/editsection.php', array('id' => $event->objectid));
|
|
$this->assertEquals($url, $event->get_url());
|
|
$this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
|
|
$id = $section->id;
|
|
$sectionnum = $section->section;
|
|
$expectedlegacydata = array($course->id, "course", "editsection", 'editsection.php?id=' . $id, $sectionnum);
|
|
$this->assertEventLegacyLogData($expectedlegacydata, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
|
|
/**
|
|
* Test that triggering a course_section_deleted event works as expected.
|
|
*/
|
|
public function test_course_section_deleted_event() {
|
|
global $USER, $DB;
|
|
$this->resetAfterTest();
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Create the course with sections.
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
|
|
$sections = $DB->get_records('course_sections', array('course' => $course->id), 'section');
|
|
$coursecontext = context_course::instance($course->id);
|
|
$section = array_pop($sections);
|
|
course_delete_section($course, $section);
|
|
$events = $sink->get_events();
|
|
$event = array_pop($events); // Delete section event.
|
|
$sink->close();
|
|
|
|
// Validate event data.
|
|
$this->assertInstanceOf('\core\event\course_section_deleted', $event);
|
|
$this->assertEquals('course_sections', $event->objecttable);
|
|
$this->assertEquals($section->id, $event->objectid);
|
|
$this->assertEquals($course->id, $event->courseid);
|
|
$this->assertEquals($coursecontext->id, $event->contextid);
|
|
$this->assertEquals($section->section, $event->other['sectionnum']);
|
|
$expecteddesc = "The user with id '{$event->userid}' deleted section number '{$event->other['sectionnum']}' " .
|
|
"(section name '{$event->other['sectionname']}') for the course with id '{$event->courseid}'";
|
|
$this->assertEquals($expecteddesc, $event->get_description());
|
|
$this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
|
|
$this->assertNull($event->get_url());
|
|
|
|
// Test legacy data.
|
|
$sectionnum = $section->section;
|
|
$expectedlegacydata = array($course->id, "course", "delete section", 'view.php?id=' . $course->id, $sectionnum);
|
|
$this->assertEventLegacyLogData($expectedlegacydata, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
|
|
public function test_course_integrity_check() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 1),
|
|
array('createsections'=>true));
|
|
|
|
$forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
|
|
array('section' => 0));
|
|
$page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
|
|
array('section' => 0));
|
|
$quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id),
|
|
array('section' => 0));
|
|
$correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid));
|
|
|
|
$section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
|
|
$section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
|
|
$cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
|
|
$this->assertEquals($correctseq, $section0->sequence);
|
|
$this->assertEmpty($section1->sequence);
|
|
$this->assertEquals($section0->id, $cms[$forum->cmid]->section);
|
|
$this->assertEquals($section0->id, $cms[$page->cmid]->section);
|
|
$this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
|
|
$this->assertEmpty(course_integrity_check($course->id));
|
|
|
|
// Now let's make manual change in DB and let course_integrity_check() fix it:
|
|
|
|
// 1. Module appears twice in one section.
|
|
$DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid));
|
|
$this->assertEquals(
|
|
array('Failed integrity check for course ['. $course->id.
|
|
']. Sequence for course section ['. $section0->id. '] is "'.
|
|
$section0->sequence. ','. $page->cmid. '", must be "'.
|
|
$section0->sequence. '"'),
|
|
course_integrity_check($course->id));
|
|
$section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
|
|
$section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
|
|
$cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
|
|
$this->assertEquals($correctseq, $section0->sequence);
|
|
$this->assertEmpty($section1->sequence);
|
|
$this->assertEquals($section0->id, $cms[$forum->cmid]->section);
|
|
$this->assertEquals($section0->id, $cms[$page->cmid]->section);
|
|
$this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
|
|
|
|
// 2. Module appears in two sections (last section wins).
|
|
$DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid));
|
|
// First message about double mentioning in sequence, second message about wrong section field for $page.
|
|
$this->assertEquals(array(
|
|
'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
|
|
'] must be removed from sequence of section ['. $section0->id.
|
|
'] because it is also present in sequence of section ['. $section1->id. ']',
|
|
'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
|
|
'] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'),
|
|
course_integrity_check($course->id));
|
|
$section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
|
|
$section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
|
|
$cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
|
|
$this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
|
|
$this->assertEquals(''. $page->cmid, $section1->sequence);
|
|
$this->assertEquals($section0->id, $cms[$forum->cmid]->section);
|
|
$this->assertEquals($section1->id, $cms[$page->cmid]->section);
|
|
$this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
|
|
|
|
// 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false).
|
|
$DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
|
|
$this->assertEmpty(course_integrity_check($course->id)); // Not an error!
|
|
$section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
|
|
$section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
|
|
$cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
|
|
$this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
|
|
$this->assertEmpty($section1->sequence);
|
|
$this->assertEquals($section0->id, $cms[$forum->cmid]->section);
|
|
$this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
|
|
$this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
|
|
|
|
// 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true).
|
|
$this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
|
|
$page->cmid. '] is missing from sequence of section ['. $section1->id. ']'),
|
|
course_integrity_check($course->id, null, null, true)); // Error!
|
|
$section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
|
|
$section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
|
|
$cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
|
|
$this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
|
|
$this->assertEquals(''. $page->cmid, $section1->sequence); // Yay, module added to section.
|
|
$this->assertEquals($section0->id, $cms[$forum->cmid]->section);
|
|
$this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
|
|
$this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
|
|
|
|
// 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true).
|
|
$DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765));
|
|
$DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
|
|
$this->assertEquals(array(
|
|
'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
|
|
'] is missing from sequence of section ['. $section0->id. ']',
|
|
'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
|
|
'] points to section [8765] instead of ['. $section0->id. ']'),
|
|
course_integrity_check($course->id, null, null, true));
|
|
$section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
|
|
$section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
|
|
$cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
|
|
$this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section.
|
|
$this->assertEquals($section0->id, $cms[$forum->cmid]->section);
|
|
$this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0.
|
|
$this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
|
|
|
|
// 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true).
|
|
$DB->delete_records('course_modules', array('id' => $page->cmid));
|
|
$this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
|
|
$page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'),
|
|
course_integrity_check($course->id, null, null, true));
|
|
$section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
|
|
$section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
|
|
$cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
|
|
$this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
|
|
$this->assertEmpty($section1->sequence);
|
|
$this->assertEquals($section0->id, $cms[$forum->cmid]->section);
|
|
$this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
|
|
$this->assertEquals(2, count($cms));
|
|
}
|
|
|
|
/**
|
|
* Tests for event related to course module creation.
|
|
*/
|
|
public function test_course_module_created_event() {
|
|
global $USER;
|
|
|
|
$this->resetAfterTest();
|
|
$this->setAdminUser();
|
|
|
|
// Create an assign module.
|
|
$sink = $this->redirectEvents();
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
|
|
$events = $sink->get_events();
|
|
$eventscount = 0;
|
|
|
|
// Validate event data.
|
|
foreach ($events as $event) {
|
|
if ($event instanceof \core\event\course_module_created) {
|
|
$eventscount++;
|
|
|
|
$this->assertEquals($module->cmid, $event->objectid);
|
|
$this->assertEquals($USER->id, $event->userid);
|
|
$this->assertEquals('course_modules', $event->objecttable);
|
|
$url = new moodle_url('/mod/assign/view.php', array('id' => $module->cmid));
|
|
$this->assertEquals($url, $event->get_url());
|
|
|
|
// Test legacy data.
|
|
$this->assertSame('mod_created', $event->get_legacy_eventname());
|
|
$eventdata = new stdClass();
|
|
$eventdata->modulename = 'assign';
|
|
$eventdata->name = $module->name;
|
|
$eventdata->cmid = $module->cmid;
|
|
$eventdata->courseid = $module->course;
|
|
$eventdata->userid = $USER->id;
|
|
$this->assertEventLegacyData($eventdata, $event);
|
|
|
|
$arr = array(
|
|
array($module->course, "course", "add mod", "../mod/assign/view.php?id=$module->cmid", "assign $module->id"),
|
|
array($module->course, "assign", "add", "view.php?id=$module->cmid", $module->id, $module->cmid)
|
|
);
|
|
$this->assertEventLegacyLogData($arr, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
}
|
|
// Only one \core\event\course_module_created event should be triggered.
|
|
$this->assertEquals(1, $eventscount);
|
|
|
|
// Let us see if duplicating an activity results in a nice course module created event.
|
|
$sink->clear();
|
|
$course = get_course($module->course);
|
|
$cm = get_coursemodule_from_id('assign', $module->cmid, 0, false, MUST_EXIST);
|
|
$newcm = duplicate_module($course, $cm);
|
|
$events = $sink->get_events();
|
|
$eventscount = 0;
|
|
$sink->close();
|
|
|
|
foreach ($events as $event) {
|
|
if ($event instanceof \core\event\course_module_created) {
|
|
$eventscount++;
|
|
// Validate event data.
|
|
$this->assertInstanceOf('\core\event\course_module_created', $event);
|
|
$this->assertEquals($newcm->id, $event->objectid);
|
|
$this->assertEquals($USER->id, $event->userid);
|
|
$this->assertEquals($course->id, $event->courseid);
|
|
$url = new moodle_url('/mod/assign/view.php', array('id' => $newcm->id));
|
|
$this->assertEquals($url, $event->get_url());
|
|
}
|
|
}
|
|
|
|
// Only one \core\event\course_module_created event should be triggered.
|
|
$this->assertEquals(1, $eventscount);
|
|
}
|
|
|
|
/**
|
|
* Tests for event validations related to course module creation.
|
|
*/
|
|
public function test_course_module_created_event_exceptions() {
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Generate data.
|
|
$modinfo = $this->create_specific_module_test('assign');
|
|
$context = context_module::instance($modinfo->coursemodule);
|
|
|
|
// Test not setting instanceid.
|
|
try {
|
|
$event = \core\event\course_module_created::create(array(
|
|
'courseid' => $modinfo->course,
|
|
'context' => $context,
|
|
'objectid' => $modinfo->coursemodule,
|
|
'other' => array(
|
|
'modulename' => 'assign',
|
|
'name' => 'My assignment',
|
|
)
|
|
));
|
|
$this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
|
|
other['instanceid']");
|
|
} catch (coding_exception $e) {
|
|
$this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
|
|
}
|
|
|
|
// Test not setting modulename.
|
|
try {
|
|
$event = \core\event\course_module_created::create(array(
|
|
'courseid' => $modinfo->course,
|
|
'context' => $context,
|
|
'objectid' => $modinfo->coursemodule,
|
|
'other' => array(
|
|
'instanceid' => $modinfo->instance,
|
|
'name' => 'My assignment',
|
|
)
|
|
));
|
|
$this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
|
|
other['modulename']");
|
|
} catch (coding_exception $e) {
|
|
$this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
|
|
}
|
|
|
|
// Test not setting name.
|
|
|
|
try {
|
|
$event = \core\event\course_module_created::create(array(
|
|
'courseid' => $modinfo->course,
|
|
'context' => $context,
|
|
'objectid' => $modinfo->coursemodule,
|
|
'other' => array(
|
|
'modulename' => 'assign',
|
|
'instanceid' => $modinfo->instance,
|
|
)
|
|
));
|
|
$this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
|
|
other['name']");
|
|
} catch (coding_exception $e) {
|
|
$this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Tests for event related to course module updates.
|
|
*/
|
|
public function test_course_module_updated_event() {
|
|
global $USER, $DB;
|
|
$this->resetAfterTest();
|
|
|
|
// Update a forum module.
|
|
$sink = $this->redirectEvents();
|
|
$modinfo = $this->update_specific_module_test('forum');
|
|
$events = $sink->get_events();
|
|
$eventscount = 0;
|
|
$sink->close();
|
|
|
|
$cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
|
|
$mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST);
|
|
|
|
// Validate event data.
|
|
foreach ($events as $event) {
|
|
if ($event instanceof \core\event\course_module_updated) {
|
|
$eventscount++;
|
|
|
|
$this->assertEquals($cm->id, $event->objectid);
|
|
$this->assertEquals($USER->id, $event->userid);
|
|
$this->assertEquals('course_modules', $event->objecttable);
|
|
$url = new moodle_url('/mod/forum/view.php', array('id' => $cm->id));
|
|
$this->assertEquals($url, $event->get_url());
|
|
|
|
// Test legacy data.
|
|
$this->assertSame('mod_updated', $event->get_legacy_eventname());
|
|
$eventdata = new stdClass();
|
|
$eventdata->modulename = 'forum';
|
|
$eventdata->name = $mod->name;
|
|
$eventdata->cmid = $cm->id;
|
|
$eventdata->courseid = $cm->course;
|
|
$eventdata->userid = $USER->id;
|
|
$this->assertEventLegacyData($eventdata, $event);
|
|
|
|
$arr = array(
|
|
array($cm->course, "course", "update mod", "../mod/forum/view.php?id=$cm->id", "forum $cm->instance"),
|
|
array($cm->course, "forum", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
|
|
);
|
|
$this->assertEventLegacyLogData($arr, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
}
|
|
|
|
// Only one \core\event\course_module_updated event should be triggered.
|
|
$this->assertEquals(1, $eventscount);
|
|
}
|
|
|
|
/**
|
|
* Tests for create_from_cm method.
|
|
*/
|
|
public function test_course_module_create_from_cm() {
|
|
$this->resetAfterTest();
|
|
$this->setAdminUser();
|
|
|
|
// Create course and modules.
|
|
$course = $this->getDataGenerator()->create_course(array('numsections' => 5));
|
|
|
|
// Generate an assignment.
|
|
$assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
|
|
|
|
// Get the module context.
|
|
$modcontext = context_module::instance($assign->cmid);
|
|
|
|
// Get course module.
|
|
$cm = get_coursemodule_from_id(null, $assign->cmid, $course->id, false, MUST_EXIST);
|
|
|
|
// Create an event from course module.
|
|
$event = \core\event\course_module_updated::create_from_cm($cm, $modcontext);
|
|
|
|
// Trigger the events.
|
|
$sink = $this->redirectEvents();
|
|
$event->trigger();
|
|
$events = $sink->get_events();
|
|
$event2 = array_pop($events);
|
|
|
|
// Test event data.
|
|
$this->assertInstanceOf('\core\event\course_module_updated', $event);
|
|
$this->assertEquals($cm->id, $event2->objectid);
|
|
$this->assertEquals($modcontext, $event2->get_context());
|
|
$this->assertEquals($cm->modname, $event2->other['modulename']);
|
|
$this->assertEquals($cm->instance, $event2->other['instanceid']);
|
|
$this->assertEquals($cm->name, $event2->other['name']);
|
|
$this->assertEventContextNotUsed($event2);
|
|
$this->assertSame('mod_updated', $event2->get_legacy_eventname());
|
|
$arr = array(
|
|
array($cm->course, "course", "update mod", "../mod/assign/view.php?id=$cm->id", "assign $cm->instance"),
|
|
array($cm->course, "assign", "update", "view.php?id=$cm->id", $cm->instance, $cm->id)
|
|
);
|
|
$this->assertEventLegacyLogData($arr, $event);
|
|
}
|
|
|
|
/**
|
|
* Tests for event validations related to course module update.
|
|
*/
|
|
public function test_course_module_updated_event_exceptions() {
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Generate data.
|
|
$modinfo = $this->create_specific_module_test('assign');
|
|
$context = context_module::instance($modinfo->coursemodule);
|
|
|
|
// Test not setting instanceid.
|
|
try {
|
|
$event = \core\event\course_module_updated::create(array(
|
|
'courseid' => $modinfo->course,
|
|
'context' => $context,
|
|
'objectid' => $modinfo->coursemodule,
|
|
'other' => array(
|
|
'modulename' => 'assign',
|
|
'name' => 'My assignment',
|
|
)
|
|
));
|
|
$this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
|
|
other['instanceid']");
|
|
} catch (coding_exception $e) {
|
|
$this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
|
|
}
|
|
|
|
// Test not setting modulename.
|
|
try {
|
|
$event = \core\event\course_module_updated::create(array(
|
|
'courseid' => $modinfo->course,
|
|
'context' => $context,
|
|
'objectid' => $modinfo->coursemodule,
|
|
'other' => array(
|
|
'instanceid' => $modinfo->instance,
|
|
'name' => 'My assignment',
|
|
)
|
|
));
|
|
$this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
|
|
other['modulename']");
|
|
} catch (coding_exception $e) {
|
|
$this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
|
|
}
|
|
|
|
// Test not setting name.
|
|
|
|
try {
|
|
$event = \core\event\course_module_updated::create(array(
|
|
'courseid' => $modinfo->course,
|
|
'context' => $context,
|
|
'objectid' => $modinfo->coursemodule,
|
|
'other' => array(
|
|
'modulename' => 'assign',
|
|
'instanceid' => $modinfo->instance,
|
|
)
|
|
));
|
|
$this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
|
|
other['name']");
|
|
} catch (coding_exception $e) {
|
|
$this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Tests for event related to course module delete.
|
|
*/
|
|
public function test_course_module_deleted_event() {
|
|
global $USER, $DB;
|
|
$this->resetAfterTest();
|
|
|
|
// Create and delete a module.
|
|
$sink = $this->redirectEvents();
|
|
$modinfo = $this->create_specific_module_test('forum');
|
|
$cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
|
|
course_delete_module($modinfo->coursemodule);
|
|
$events = $sink->get_events();
|
|
$event = array_pop($events); // delete module event.;
|
|
$sink->close();
|
|
|
|
// Validate event data.
|
|
$this->assertInstanceOf('\core\event\course_module_deleted', $event);
|
|
$this->assertEquals($cm->id, $event->objectid);
|
|
$this->assertEquals($USER->id, $event->userid);
|
|
$this->assertEquals('course_modules', $event->objecttable);
|
|
$this->assertEquals(null, $event->get_url());
|
|
$this->assertEquals($cm, $event->get_record_snapshot('course_modules', $cm->id));
|
|
|
|
// Test legacy data.
|
|
$this->assertSame('mod_deleted', $event->get_legacy_eventname());
|
|
$eventdata = new stdClass();
|
|
$eventdata->modulename = 'forum';
|
|
$eventdata->cmid = $cm->id;
|
|
$eventdata->courseid = $cm->course;
|
|
$eventdata->userid = $USER->id;
|
|
$this->assertEventLegacyData($eventdata, $event);
|
|
|
|
$arr = array($cm->course, 'course', "delete mod", "view.php?id=$cm->course", "forum $cm->instance", $cm->id);
|
|
$this->assertEventLegacyLogData($arr, $event);
|
|
|
|
}
|
|
|
|
/**
|
|
* Tests for event validations related to course module deletion.
|
|
*/
|
|
public function test_course_module_deleted_event_exceptions() {
|
|
|
|
$this->resetAfterTest();
|
|
|
|
// Generate data.
|
|
$modinfo = $this->create_specific_module_test('assign');
|
|
$context = context_module::instance($modinfo->coursemodule);
|
|
|
|
// Test not setting instanceid.
|
|
try {
|
|
$event = \core\event\course_module_deleted::create(array(
|
|
'courseid' => $modinfo->course,
|
|
'context' => $context,
|
|
'objectid' => $modinfo->coursemodule,
|
|
'other' => array(
|
|
'modulename' => 'assign',
|
|
'name' => 'My assignment',
|
|
)
|
|
));
|
|
$this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
|
|
other['instanceid']");
|
|
} catch (coding_exception $e) {
|
|
$this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
|
|
}
|
|
|
|
// Test not setting modulename.
|
|
try {
|
|
$event = \core\event\course_module_deleted::create(array(
|
|
'courseid' => $modinfo->course,
|
|
'context' => $context,
|
|
'objectid' => $modinfo->coursemodule,
|
|
'other' => array(
|
|
'instanceid' => $modinfo->instance,
|
|
'name' => 'My assignment',
|
|
)
|
|
));
|
|
$this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
|
|
other['modulename']");
|
|
} catch (coding_exception $e) {
|
|
$this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a user object and its assigned new role.
|
|
*
|
|
* @param testing_data_generator $generator
|
|
* @param $contextid
|
|
* @return array The user object and the role ID
|
|
*/
|
|
protected function get_user_objects(testing_data_generator $generator, $contextid) {
|
|
global $USER;
|
|
|
|
if (empty($USER->id)) {
|
|
$user = $generator->create_user();
|
|
$this->setUser($user);
|
|
}
|
|
$roleid = create_role('Test role', 'testrole', 'Test role description');
|
|
if (!is_array($contextid)) {
|
|
$contextid = array($contextid);
|
|
}
|
|
foreach ($contextid as $cid) {
|
|
$assignid = role_assign($roleid, $user->id, $cid);
|
|
}
|
|
return array($user, $roleid);
|
|
}
|
|
|
|
/**
|
|
* Test course move after course.
|
|
*/
|
|
public function test_course_change_sortorder_after_course() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$generator = $this->getDataGenerator();
|
|
$category = $generator->create_category();
|
|
$course3 = $generator->create_course(array('category' => $category->id));
|
|
$course2 = $generator->create_course(array('category' => $category->id));
|
|
$course1 = $generator->create_course(array('category' => $category->id));
|
|
$context = $category->get_context();
|
|
|
|
list($user, $roleid) = $this->get_user_objects($generator, $context->id);
|
|
$caps = course_capability_assignment::allow('moodle/category:manage', $roleid, $context->id);
|
|
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
|
|
// Test moving down.
|
|
$this->assertTrue(course_change_sortorder_after_course($course1->id, $course3->id));
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
|
|
// Test moving up.
|
|
$this->assertTrue(course_change_sortorder_after_course($course1->id, $course2->id));
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
|
|
// Test moving to the top.
|
|
$this->assertTrue(course_change_sortorder_after_course($course1->id, 0));
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
}
|
|
|
|
/**
|
|
* Tests changing the visibility of a course.
|
|
*/
|
|
public function test_course_change_visibility() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$generator = $this->getDataGenerator();
|
|
$category = $generator->create_category();
|
|
$course = $generator->create_course(array('category' => $category->id));
|
|
|
|
$this->assertEquals('1', $course->visible);
|
|
$this->assertEquals('1', $course->visibleold);
|
|
|
|
$this->assertTrue(course_change_visibility($course->id, false));
|
|
$course = $DB->get_record('course', array('id' => $course->id));
|
|
$this->assertEquals('0', $course->visible);
|
|
$this->assertEquals('0', $course->visibleold);
|
|
|
|
$this->assertTrue(course_change_visibility($course->id, true));
|
|
$course = $DB->get_record('course', array('id' => $course->id));
|
|
$this->assertEquals('1', $course->visible);
|
|
$this->assertEquals('1', $course->visibleold);
|
|
}
|
|
|
|
/**
|
|
* Tests moving the course up and down by one.
|
|
*/
|
|
public function test_course_change_sortorder_by_one() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$generator = $this->getDataGenerator();
|
|
$category = $generator->create_category();
|
|
$course3 = $generator->create_course(array('category' => $category->id));
|
|
$course2 = $generator->create_course(array('category' => $category->id));
|
|
$course1 = $generator->create_course(array('category' => $category->id));
|
|
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
|
|
// Test moving down.
|
|
$course1 = get_course($course1->id);
|
|
$this->assertTrue(course_change_sortorder_by_one($course1, false));
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
|
|
// Test moving up.
|
|
$course1 = get_course($course1->id);
|
|
$this->assertTrue(course_change_sortorder_by_one($course1, true));
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
|
|
// Test moving the top course up one.
|
|
$course1 = get_course($course1->id);
|
|
$this->assertFalse(course_change_sortorder_by_one($course1, true));
|
|
// Check nothing changed.
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
|
|
// Test moving the bottom course up down.
|
|
$course3 = get_course($course3->id);
|
|
$this->assertFalse(course_change_sortorder_by_one($course3, false));
|
|
// Check nothing changed.
|
|
$courses = $category->get_courses();
|
|
$this->assertIsArray($courses);
|
|
$this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
|
|
$dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
|
|
$this->assertEquals(array_keys($dbcourses), array_keys($courses));
|
|
}
|
|
|
|
public function test_view_resources_list() {
|
|
$this->resetAfterTest();
|
|
|
|
$course = self::getDataGenerator()->create_course();
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
$event = \core\event\course_resources_list_viewed::create(array('context' => context_course::instance($course->id)));
|
|
$event->set_legacy_logdata(array('book', 'page', 'resource'));
|
|
$sink = $this->redirectEvents();
|
|
$event->trigger();
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
|
|
// Validate the event.
|
|
$event = $events[0];
|
|
$this->assertInstanceOf('\core\event\course_resources_list_viewed', $event);
|
|
$this->assertEquals(null, $event->objecttable);
|
|
$this->assertEquals(null, $event->objectid);
|
|
$this->assertEquals($course->id, $event->courseid);
|
|
$this->assertEquals($coursecontext->id, $event->contextid);
|
|
$expectedlegacydata = array(
|
|
array($course->id, "book", "view all", 'index.php?id=' . $course->id, ''),
|
|
array($course->id, "page", "view all", 'index.php?id=' . $course->id, ''),
|
|
array($course->id, "resource", "view all", 'index.php?id=' . $course->id, ''),
|
|
);
|
|
$this->assertEventLegacyLogData($expectedlegacydata, $event);
|
|
$this->assertEventContextNotUsed($event);
|
|
}
|
|
|
|
/**
|
|
* Test duplicate_module()
|
|
*/
|
|
public function test_duplicate_module() {
|
|
$this->setAdminUser();
|
|
$this->resetAfterTest();
|
|
$course = self::getDataGenerator()->create_course();
|
|
$res = self::getDataGenerator()->create_module('resource', array('course' => $course));
|
|
$cm = get_coursemodule_from_id('resource', $res->cmid, 0, false, MUST_EXIST);
|
|
|
|
$newcm = duplicate_module($course, $cm);
|
|
|
|
// Make sure they are the same, except obvious id changes.
|
|
foreach ($cm as $prop => $value) {
|
|
if ($prop == 'id' || $prop == 'url' || $prop == 'instance' || $prop == 'added') {
|
|
// Ignore obviously different properties.
|
|
continue;
|
|
}
|
|
if ($prop == 'name') {
|
|
// We expect ' (copy)' to be added to the original name since MDL-59227.
|
|
$value = get_string('duplicatedmodule', 'moodle', $value);
|
|
}
|
|
$this->assertEquals($value, $newcm->$prop);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tests that when creating or updating a module, if the availability settings
|
|
* are present but set to an empty tree, availability is set to null in
|
|
* database.
|
|
*/
|
|
public function test_empty_availability_settings() {
|
|
global $DB;
|
|
$this->setAdminUser();
|
|
$this->resetAfterTest();
|
|
|
|
// Enable availability.
|
|
set_config('enableavailability', 1);
|
|
|
|
// Test add.
|
|
$emptyavailability = json_encode(\core_availability\tree::get_root_json(array()));
|
|
$course = self::getDataGenerator()->create_course();
|
|
$label = self::getDataGenerator()->create_module('label', array(
|
|
'course' => $course, 'availability' => $emptyavailability));
|
|
$this->assertNull($DB->get_field('course_modules', 'availability',
|
|
array('id' => $label->cmid)));
|
|
|
|
// Test update.
|
|
$formdata = $DB->get_record('course_modules', array('id' => $label->cmid));
|
|
unset($formdata->availability);
|
|
$formdata->availabilityconditionsjson = $emptyavailability;
|
|
$formdata->modulename = 'label';
|
|
$formdata->coursemodule = $label->cmid;
|
|
$draftid = 0;
|
|
file_prepare_draft_area($draftid, context_module::instance($label->cmid)->id,
|
|
'mod_label', 'intro', 0);
|
|
$formdata->introeditor = array(
|
|
'itemid' => $draftid,
|
|
'text' => '<p>Yo</p>',
|
|
'format' => FORMAT_HTML);
|
|
update_module($formdata);
|
|
$this->assertNull($DB->get_field('course_modules', 'availability',
|
|
array('id' => $label->cmid)));
|
|
}
|
|
|
|
/**
|
|
* Test update_inplace_editable()
|
|
*/
|
|
public function test_update_module_name_inplace() {
|
|
global $CFG, $DB, $PAGE;
|
|
require_once($CFG->dirroot . '/lib/external/externallib.php');
|
|
|
|
$this->setUser($this->getDataGenerator()->create_user());
|
|
|
|
$this->resetAfterTest(true);
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$forum = self::getDataGenerator()->create_module('forum', array('course' => $course->id, 'name' => 'forum name'));
|
|
|
|
// Call service for core_course component without necessary permissions.
|
|
try {
|
|
core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
|
|
$this->fail('Exception expected');
|
|
} catch (moodle_exception $e) {
|
|
$this->assertEquals('Course or activity not accessible. (Not enrolled)',
|
|
$e->getMessage());
|
|
}
|
|
|
|
// Change to admin user and make sure that cm name can be updated using web service update_inplace_editable().
|
|
$this->setAdminUser();
|
|
$res = core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
|
|
$res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
|
|
$this->assertEquals('New forum name', $res['value']);
|
|
$this->assertEquals('New forum name', $DB->get_field('forum', 'name', array('id' => $forum->id)));
|
|
}
|
|
|
|
/**
|
|
* Testing function course_get_tagged_course_modules - search tagged course modules
|
|
*/
|
|
public function test_course_get_tagged_course_modules() {
|
|
global $DB;
|
|
$this->resetAfterTest();
|
|
$course3 = $this->getDataGenerator()->create_course();
|
|
$course2 = $this->getDataGenerator()->create_course();
|
|
$course1 = $this->getDataGenerator()->create_course();
|
|
$cm11 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
|
|
'tags' => 'Cat, Dog'));
|
|
$cm12 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
|
|
'tags' => 'Cat, Mouse', 'visible' => 0));
|
|
$cm13 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
|
|
'tags' => 'Cat, Mouse, Dog'));
|
|
$cm21 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id,
|
|
'tags' => 'Cat, Mouse'));
|
|
$cm31 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id,
|
|
'tags' => 'Cat, Mouse'));
|
|
|
|
// Admin is able to view everything.
|
|
$this->setAdminUser();
|
|
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
|
|
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
|
|
$this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm21->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm31->name.'/', $res->content);
|
|
// Results from course1 are returned before results from course2.
|
|
$this->assertTrue(strpos($res->content, $cm11->name) < strpos($res->content, $cm21->name));
|
|
|
|
// Ordinary user is not able to see anything.
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->setUser($user);
|
|
|
|
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
|
|
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
|
|
$this->assertNull($res);
|
|
|
|
// Enrol user as student in course1 and course2.
|
|
$roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
|
|
$this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['student']);
|
|
$this->getDataGenerator()->enrol_user($user->id, $course2->id, $roleids['student']);
|
|
core_tag_index_builder::reset_caches();
|
|
|
|
// Searching in the course context returns visible modules in this course.
|
|
$context = context_course::instance($course1->id);
|
|
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
|
|
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
|
|
$this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content);
|
|
|
|
// Searching FROM the course context returns visible modules in all courses.
|
|
$context = context_course::instance($course2->id);
|
|
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
|
|
/*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
|
|
$this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm21->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
|
|
// Results from course2 are returned before results from course1.
|
|
$this->assertTrue(strpos($res->content, $cm21->name) < strpos($res->content, $cm11->name));
|
|
|
|
// Enrol user in course1 as a teacher - now he should be able to see hidden module.
|
|
$this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['editingteacher']);
|
|
get_fast_modinfo(0,0,true);
|
|
|
|
$context = context_course::instance($course1->id);
|
|
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
|
|
/*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
|
|
$this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
|
|
|
|
// Create more modules and try pagination.
|
|
$cm14 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
|
|
'tags' => 'Cat, Dog'));
|
|
$cm15 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
|
|
'tags' => 'Cat, Mouse', 'visible' => 0));
|
|
$cm16 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
|
|
'tags' => 'Cat, Mouse, Dog'));
|
|
|
|
$context = context_course::instance($course1->id);
|
|
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
|
|
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
|
|
$this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm14->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm15->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm16->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
|
|
$this->assertEmpty($res->prevpageurl);
|
|
$this->assertNotEmpty($res->nextpageurl);
|
|
|
|
$res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
|
|
/*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */1);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm11->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm13->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm14->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm15->name.'/', $res->content);
|
|
$this->assertMatchesRegularExpression('/'.$cm16->name.'/', $res->content);
|
|
$this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
|
|
$this->assertNotEmpty($res->prevpageurl);
|
|
$this->assertEmpty($res->nextpageurl);
|
|
}
|
|
|
|
/**
|
|
* Test course_get_user_navigation_options for frontpage.
|
|
*/
|
|
public function test_course_get_user_navigation_options_for_frontpage() {
|
|
global $CFG, $SITE, $DB;
|
|
$this->resetAfterTest();
|
|
$context = context_system::instance();
|
|
$course = clone $SITE;
|
|
$this->setAdminUser();
|
|
|
|
$navoptions = course_get_user_navigation_options($context, $course);
|
|
$this->assertTrue($navoptions->blogs);
|
|
$this->assertTrue($navoptions->notes);
|
|
$this->assertTrue($navoptions->participants);
|
|
$this->assertTrue($navoptions->badges);
|
|
$this->assertTrue($navoptions->tags);
|
|
$this->assertFalse($navoptions->search);
|
|
$this->assertTrue($navoptions->competencies);
|
|
|
|
// Enable global search now.
|
|
$CFG->enableglobalsearch = 1;
|
|
$navoptions = course_get_user_navigation_options($context, $course);
|
|
$this->assertTrue($navoptions->search);
|
|
|
|
// Disable competencies.
|
|
$oldcompetencies = get_config('core_competency', 'enabled');
|
|
set_config('enabled', false, 'core_competency');
|
|
$navoptions = course_get_user_navigation_options($context, $course);
|
|
$this->assertFalse($navoptions->competencies);
|
|
set_config('enabled', $oldcompetencies, 'core_competency');
|
|
|
|
// Now try with a standard user.
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->setUser($user);
|
|
$navoptions = course_get_user_navigation_options($context, $course);
|
|
$this->assertTrue($navoptions->blogs);
|
|
$this->assertFalse($navoptions->notes);
|
|
$this->assertFalse($navoptions->participants);
|
|
$this->assertTrue($navoptions->badges);
|
|
$this->assertTrue($navoptions->tags);
|
|
$this->assertTrue($navoptions->search);
|
|
}
|
|
|
|
/**
|
|
* Test course_get_user_navigation_options for managers in a normal course.
|
|
*/
|
|
public function test_course_get_user_navigation_options_for_managers() {
|
|
global $CFG;
|
|
$this->resetAfterTest();
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$context = context_course::instance($course->id);
|
|
$this->setAdminUser();
|
|
|
|
$navoptions = course_get_user_navigation_options($context);
|
|
$this->assertTrue($navoptions->blogs);
|
|
$this->assertTrue($navoptions->notes);
|
|
$this->assertTrue($navoptions->participants);
|
|
$this->assertTrue($navoptions->badges);
|
|
}
|
|
|
|
/**
|
|
* Test course_get_user_navigation_options for students in a normal course.
|
|
*/
|
|
public function test_course_get_user_navigation_options_for_students() {
|
|
global $DB, $CFG;
|
|
$this->resetAfterTest();
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$context = context_course::instance($course->id);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$roleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
|
|
|
|
$this->setUser($user);
|
|
|
|
$navoptions = course_get_user_navigation_options($context);
|
|
$this->assertTrue($navoptions->blogs);
|
|
$this->assertFalse($navoptions->notes);
|
|
$this->assertTrue($navoptions->participants);
|
|
$this->assertFalse($navoptions->badges);
|
|
|
|
// Disable some options.
|
|
$CFG->badges_allowcoursebadges = 0;
|
|
$CFG->enableblogs = 0;
|
|
// Disable view participants capability.
|
|
assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $context);
|
|
|
|
$navoptions = course_get_user_navigation_options($context);
|
|
$this->assertFalse($navoptions->blogs);
|
|
$this->assertFalse($navoptions->notes);
|
|
$this->assertFalse($navoptions->participants);
|
|
$this->assertFalse($navoptions->badges);
|
|
|
|
// Re-enable some options to check badges are displayed as expected.
|
|
$CFG->badges_allowcoursebadges = 1;
|
|
assign_capability('moodle/badges:createbadge', CAP_ALLOW, $roleid, $context);
|
|
|
|
$navoptions = course_get_user_navigation_options($context);
|
|
$this->assertTrue($navoptions->badges);
|
|
}
|
|
|
|
/**
|
|
* Test course_get_user_administration_options for frontpage.
|
|
*/
|
|
public function test_course_get_user_administration_options_for_frontpage() {
|
|
global $CFG, $SITE;
|
|
$this->resetAfterTest();
|
|
$course = clone $SITE;
|
|
$context = context_course::instance($course->id);
|
|
$this->setAdminUser();
|
|
|
|
$adminoptions = course_get_user_administration_options($course, $context);
|
|
$this->assertTrue($adminoptions->update);
|
|
$this->assertTrue($adminoptions->filters);
|
|
$this->assertTrue($adminoptions->reports);
|
|
$this->assertTrue($adminoptions->backup);
|
|
$this->assertTrue($adminoptions->restore);
|
|
$this->assertFalse($adminoptions->files);
|
|
$this->assertFalse($adminoptions->tags);
|
|
|
|
// Now try with a standard user.
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->setUser($user);
|
|
$adminoptions = course_get_user_administration_options($course, $context);
|
|
$this->assertFalse($adminoptions->update);
|
|
$this->assertFalse($adminoptions->filters);
|
|
$this->assertFalse($adminoptions->reports);
|
|
$this->assertFalse($adminoptions->backup);
|
|
$this->assertFalse($adminoptions->restore);
|
|
$this->assertFalse($adminoptions->files);
|
|
$this->assertFalse($adminoptions->tags);
|
|
|
|
}
|
|
|
|
/**
|
|
* Test course_get_user_administration_options for managers in a normal course.
|
|
*/
|
|
public function test_course_get_user_administration_options_for_managers() {
|
|
global $CFG;
|
|
$this->resetAfterTest();
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$context = context_course::instance($course->id);
|
|
$this->setAdminUser();
|
|
|
|
$adminoptions = course_get_user_administration_options($course, $context);
|
|
$this->assertTrue($adminoptions->update);
|
|
$this->assertTrue($adminoptions->filters);
|
|
$this->assertTrue($adminoptions->reports);
|
|
$this->assertTrue($adminoptions->backup);
|
|
$this->assertTrue($adminoptions->restore);
|
|
$this->assertFalse($adminoptions->files);
|
|
$this->assertTrue($adminoptions->tags);
|
|
$this->assertTrue($adminoptions->gradebook);
|
|
$this->assertFalse($adminoptions->outcomes);
|
|
$this->assertTrue($adminoptions->badges);
|
|
$this->assertTrue($adminoptions->import);
|
|
$this->assertTrue($adminoptions->reset);
|
|
$this->assertTrue($adminoptions->roles);
|
|
}
|
|
|
|
/**
|
|
* Test course_get_user_administration_options for students in a normal course.
|
|
*/
|
|
public function test_course_get_user_administration_options_for_students() {
|
|
global $DB, $CFG;
|
|
$this->resetAfterTest();
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$context = context_course::instance($course->id);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$roleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
|
|
|
|
$this->setUser($user);
|
|
$adminoptions = course_get_user_administration_options($course, $context);
|
|
|
|
$this->assertFalse($adminoptions->update);
|
|
$this->assertFalse($adminoptions->filters);
|
|
$this->assertFalse($adminoptions->reports);
|
|
$this->assertFalse($adminoptions->backup);
|
|
$this->assertFalse($adminoptions->restore);
|
|
$this->assertFalse($adminoptions->files);
|
|
$this->assertFalse($adminoptions->tags);
|
|
$this->assertFalse($adminoptions->gradebook);
|
|
$this->assertFalse($adminoptions->outcomes);
|
|
$this->assertTrue($adminoptions->badges);
|
|
$this->assertFalse($adminoptions->import);
|
|
$this->assertFalse($adminoptions->reset);
|
|
$this->assertFalse($adminoptions->roles);
|
|
|
|
$CFG->enablebadges = false;
|
|
$adminoptions = course_get_user_administration_options($course, $context);
|
|
$this->assertFalse($adminoptions->badges);
|
|
}
|
|
|
|
/**
|
|
* Test test_update_course_frontpage_category.
|
|
*/
|
|
public function test_update_course_frontpage_category() {
|
|
// Fetch front page course.
|
|
$course = get_course(SITEID);
|
|
// Test update information on front page course.
|
|
$course->category = 99;
|
|
$this->expectException('moodle_exception');
|
|
$this->expectExceptionMessage(get_string('invalidcourse', 'error'));
|
|
update_course($course);
|
|
}
|
|
|
|
/**
|
|
* test_course_enddate
|
|
*
|
|
* @dataProvider course_enddate_provider
|
|
* @param int $startdate
|
|
* @param int $enddate
|
|
* @param string $errorcode
|
|
*/
|
|
public function test_course_enddate($startdate, $enddate, $errorcode) {
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$record = array('startdate' => $startdate, 'enddate' => $enddate);
|
|
try {
|
|
$course1 = $this->getDataGenerator()->create_course($record);
|
|
if ($errorcode !== false) {
|
|
$this->fail('Expected exception with "' . $errorcode . '" error code in create_create');
|
|
}
|
|
} catch (moodle_exception $e) {
|
|
if ($errorcode === false) {
|
|
$this->fail('Got "' . $errorcode . '" exception error code and no exception was expected');
|
|
}
|
|
if ($e->errorcode != $errorcode) {
|
|
$this->fail('Got "' . $e->errorcode. '" exception error code and "' . $errorcode . '" was expected');
|
|
}
|
|
return;
|
|
}
|
|
|
|
$this->assertEquals($startdate, $course1->startdate);
|
|
$this->assertEquals($enddate, $course1->enddate);
|
|
}
|
|
|
|
/**
|
|
* Provider for test_course_enddate.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function course_enddate_provider() {
|
|
// Each provided example contains startdate, enddate and the expected exception error code if there is any.
|
|
return [
|
|
[
|
|
111,
|
|
222,
|
|
false
|
|
], [
|
|
222,
|
|
111,
|
|
'enddatebeforestartdate'
|
|
], [
|
|
111,
|
|
0,
|
|
false
|
|
], [
|
|
0,
|
|
222,
|
|
'nostartdatenoenddate'
|
|
]
|
|
];
|
|
}
|
|
|
|
|
|
/**
|
|
* test_course_dates_reset
|
|
*
|
|
* @dataProvider course_dates_reset_provider
|
|
* @param int $startdate
|
|
* @param int $enddate
|
|
* @param int $resetstartdate
|
|
* @param int $resetenddate
|
|
* @param int $resultingstartdate
|
|
* @param int $resultingenddate
|
|
*/
|
|
public function test_course_dates_reset($startdate, $enddate, $resetstartdate, $resetenddate, $resultingstartdate, $resultingenddate) {
|
|
global $CFG, $DB;
|
|
|
|
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$this->setAdminUser();
|
|
|
|
$CFG->enablecompletion = true;
|
|
|
|
$this->setTimezone('UTC');
|
|
|
|
$record = array('startdate' => $startdate, 'enddate' => $enddate, 'enablecompletion' => 1);
|
|
$originalcourse = $this->getDataGenerator()->create_course($record);
|
|
$coursecriteria = new completion_criteria_date(array('course' => $originalcourse->id, 'timeend' => $startdate + DAYSECS));
|
|
$coursecriteria->insert();
|
|
|
|
$activitycompletiondate = $startdate + DAYSECS;
|
|
$data = $this->getDataGenerator()->create_module('data', array('course' => $originalcourse->id),
|
|
array('completion' => 1, 'completionexpected' => $activitycompletiondate));
|
|
|
|
$resetdata = new stdClass();
|
|
$resetdata->id = $originalcourse->id;
|
|
$resetdata->reset_start_date_old = $originalcourse->startdate;
|
|
$resetdata->reset_start_date = $resetstartdate;
|
|
$resetdata->reset_end_date = $resetenddate;
|
|
$resetdata->reset_end_date_old = $record['enddate'];
|
|
reset_course_userdata($resetdata);
|
|
|
|
$course = $DB->get_record('course', array('id' => $originalcourse->id));
|
|
|
|
$this->assertEquals($resultingstartdate, $course->startdate);
|
|
$this->assertEquals($resultingenddate, $course->enddate);
|
|
|
|
$coursecompletioncriteria = completion_criteria_date::fetch(array('course' => $originalcourse->id));
|
|
$this->assertEquals($resultingstartdate + DAYSECS, $coursecompletioncriteria->timeend);
|
|
|
|
$this->assertEquals($resultingstartdate + DAYSECS, $DB->get_field('course_modules', 'completionexpected',
|
|
array('id' => $data->cmid)));
|
|
}
|
|
|
|
/**
|
|
* Provider for test_course_dates_reset.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function course_dates_reset_provider() {
|
|
|
|
// Each example contains the following:
|
|
// - course startdate
|
|
// - course enddate
|
|
// - startdate to reset to (false if not reset)
|
|
// - enddate to reset to (false if not reset)
|
|
// - resulting startdate
|
|
// - resulting enddate
|
|
$time = 1445644800;
|
|
return [
|
|
// No date changes.
|
|
[
|
|
$time,
|
|
$time + DAYSECS,
|
|
false,
|
|
false,
|
|
$time,
|
|
$time + DAYSECS
|
|
],
|
|
// End date changes to a valid value.
|
|
[
|
|
$time,
|
|
$time + DAYSECS,
|
|
false,
|
|
$time + DAYSECS + 111,
|
|
$time,
|
|
$time + DAYSECS + 111
|
|
],
|
|
// Start date changes to a valid value. End date does not get updated because it does not have value.
|
|
[
|
|
$time,
|
|
0,
|
|
$time + DAYSECS,
|
|
false,
|
|
$time + DAYSECS,
|
|
0
|
|
],
|
|
// Start date changes to a valid value. End date gets updated accordingly.
|
|
[
|
|
$time,
|
|
$time + DAYSECS,
|
|
$time + WEEKSECS,
|
|
false,
|
|
$time + WEEKSECS,
|
|
$time + WEEKSECS + DAYSECS
|
|
],
|
|
// Start date and end date change to a valid value.
|
|
[
|
|
$time,
|
|
$time + DAYSECS,
|
|
$time + WEEKSECS,
|
|
$time + YEARSECS,
|
|
$time + WEEKSECS,
|
|
$time + YEARSECS
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test reset_course_userdata()
|
|
* - with reset_roles_overrides enabled
|
|
* - with selective role unenrolments
|
|
*/
|
|
public function test_course_roles_reset() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$generator = $this->getDataGenerator();
|
|
|
|
// Create test course and user, enrol one in the other.
|
|
$course = $generator->create_course();
|
|
$user = $generator->create_user();
|
|
$roleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
|
|
$generator->enrol_user($user->id, $course->id, $roleid);
|
|
|
|
// Test case with reset_roles_overrides enabled.
|
|
// Override course so it does NOT allow students 'mod/forum:viewdiscussion'.
|
|
$coursecontext = context_course::instance($course->id);
|
|
assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $roleid, $coursecontext->id);
|
|
|
|
// Check expected capabilities so far.
|
|
$this->assertFalse(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
|
|
|
|
// Oops, preventing student from viewing forums was a mistake, let's reset the course.
|
|
$resetdata = new stdClass();
|
|
$resetdata->id = $course->id;
|
|
$resetdata->reset_roles_overrides = true;
|
|
reset_course_userdata($resetdata);
|
|
|
|
// Check new expected capabilities - override at the course level should be reset.
|
|
$this->assertTrue(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
|
|
|
|
// Test case with selective role unenrolments.
|
|
$roles = array();
|
|
$roles['student'] = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
|
|
$roles['teacher'] = $DB->get_field('role', 'id', array('shortname' => 'teacher'), MUST_EXIST);
|
|
|
|
// We enrol a user with student and teacher roles.
|
|
$generator->enrol_user($user->id, $course->id, $roles['student']);
|
|
$generator->enrol_user($user->id, $course->id, $roles['teacher']);
|
|
|
|
// When we reset only student role, we expect to keep teacher role.
|
|
$resetdata = new stdClass();
|
|
$resetdata->id = $course->id;
|
|
$resetdata->unenrol_users = array($roles['student']);
|
|
reset_course_userdata($resetdata);
|
|
|
|
$usersroles = enrol_get_course_users_roles($course->id);
|
|
$this->assertArrayHasKey($user->id, $usersroles);
|
|
$this->assertArrayHasKey($roles['teacher'], $usersroles[$user->id]);
|
|
$this->assertArrayNotHasKey($roles['student'], $usersroles[$user->id]);
|
|
$this->assertCount(1, $usersroles[$user->id]);
|
|
|
|
// We reenrol user as student.
|
|
$generator->enrol_user($user->id, $course->id, $roles['student']);
|
|
|
|
// When we reset student and teacher roles, we expect no roles left.
|
|
$resetdata = new stdClass();
|
|
$resetdata->id = $course->id;
|
|
$resetdata->unenrol_users = array($roles['student'], $roles['teacher']);
|
|
reset_course_userdata($resetdata);
|
|
|
|
$usersroles = enrol_get_course_users_roles($course->id);
|
|
$this->assertEmpty($usersroles);
|
|
}
|
|
|
|
public function test_course_check_module_updates_since() {
|
|
global $CFG, $DB, $USER;
|
|
require_once($CFG->dirroot . '/mod/glossary/lib.php');
|
|
require_once($CFG->dirroot . '/rating/lib.php');
|
|
require_once($CFG->dirroot . '/comment/lib.php');
|
|
|
|
$this->resetAfterTest(true);
|
|
|
|
$CFG->enablecompletion = true;
|
|
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
|
|
$glossary = $this->getDataGenerator()->create_module('glossary', array(
|
|
'course' => $course->id,
|
|
'completion' => COMPLETION_TRACKING_AUTOMATIC,
|
|
'completionview' => 1,
|
|
'allowcomments' => 1,
|
|
'assessed' => RATING_AGGREGATE_AVERAGE,
|
|
'scale' => 100
|
|
));
|
|
$glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
|
|
$context = context_module::instance($glossary->cmid);
|
|
$modinfo = get_fast_modinfo($course);
|
|
$cm = $modinfo->get_cm($glossary->cmid);
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
|
|
$from = time();
|
|
|
|
$teacher = $this->getDataGenerator()->create_user();
|
|
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
|
|
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
|
|
|
|
assign_capability('mod/glossary:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id, true);
|
|
|
|
// Check nothing changed right now.
|
|
$updates = course_check_module_updates_since($cm, $from);
|
|
$this->assertFalse($updates->configuration->updated);
|
|
$this->assertFalse($updates->completion->updated);
|
|
$this->assertFalse($updates->gradeitems->updated);
|
|
$this->assertFalse($updates->comments->updated);
|
|
$this->assertFalse($updates->ratings->updated);
|
|
$this->assertFalse($updates->introfiles->updated);
|
|
$this->assertFalse($updates->outcomes->updated);
|
|
|
|
$this->waitForSecond();
|
|
|
|
// Do some changes.
|
|
$this->setUser($user);
|
|
$entry = $glossarygenerator->create_content($glossary);
|
|
|
|
$this->setUser($teacher);
|
|
// Name.
|
|
set_coursemodule_name($glossary->cmid, 'New name');
|
|
|
|
// Add some ratings.
|
|
$rm = new rating_manager();
|
|
$result = $rm->add_rating($cm, $context, 'mod_glossary', 'entry', $entry->id, 100, 50, $user->id, RATING_AGGREGATE_AVERAGE);
|
|
|
|
// Change grades.
|
|
$glossary->cmidnumber = $glossary->cmid;
|
|
glossary_update_grades($glossary, $user->id);
|
|
|
|
$this->setUser($user);
|
|
// Completion status.
|
|
glossary_view($glossary, $course, $cm, $context, 'letter');
|
|
|
|
// Add one comment.
|
|
$args = new stdClass;
|
|
$args->context = $context;
|
|
$args->course = $course;
|
|
$args->cm = $cm;
|
|
$args->area = 'glossary_entry';
|
|
$args->itemid = $entry->id;
|
|
$args->client_id = 1;
|
|
$args->component = 'mod_glossary';
|
|
$manager = new comment($args);
|
|
$manager->add('blah blah blah');
|
|
|
|
// Check upgrade status.
|
|
$updates = course_check_module_updates_since($cm, $from);
|
|
$this->assertTrue($updates->configuration->updated);
|
|
$this->assertTrue($updates->completion->updated);
|
|
$this->assertTrue($updates->gradeitems->updated);
|
|
$this->assertTrue($updates->comments->updated);
|
|
$this->assertTrue($updates->ratings->updated);
|
|
$this->assertFalse($updates->introfiles->updated);
|
|
$this->assertFalse($updates->outcomes->updated);
|
|
}
|
|
|
|
public function test_async_module_deletion_hook_implemented() {
|
|
// Async module deletion depends on the 'true' being returned by at least one plugin implementing the hook,
|
|
// 'course_module_adhoc_deletion_recommended'. In core, is implemented by the course recyclebin, which will only return
|
|
// true if the recyclebin plugin is enabled. To make sure async deletion occurs, this test force-enables the recyclebin.
|
|
global $DB, $USER;
|
|
$this->resetAfterTest(true);
|
|
$this->setAdminUser();
|
|
|
|
// Ensure recyclebin is enabled.
|
|
set_config('coursebinenable', true, 'tool_recyclebin');
|
|
|
|
// Create course, module and context.
|
|
$course = $this->getDataGenerator()->create_course(['numsections' => 5]);
|
|
$module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
|
|
$modcontext = context_module::instance($module->cmid);
|
|
|
|
// Verify context exists.
|
|
$this->assertInstanceOf('context_module', $modcontext);
|
|
|
|
// Check events generated on the course_delete_module call.
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Try to delete the module using the async flag.
|
|
course_delete_module($module->cmid, true); // Try to delete the module asynchronously.
|
|
|
|
// Verify that no event has been generated yet.
|
|
$events = $sink->get_events();
|
|
$event = array_pop($events);
|
|
$sink->close();
|
|
$this->assertEmpty($event);
|
|
|
|
// Grab the record, in it's final state before hard deletion, for comparison with the event snapshot.
|
|
// We need to do this because the 'deletioninprogress' flag has changed from '0' to '1'.
|
|
$cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST);
|
|
|
|
// Verify the course_module is marked as 'deletioninprogress'.
|
|
$this->assertNotEquals($cm, false);
|
|
$this->assertEquals($cm->deletioninprogress, '1');
|
|
|
|
// Verify the context has not yet been removed.
|
|
$this->assertEquals($modcontext, context_module::instance($module->cmid, IGNORE_MISSING));
|
|
|
|
// Set up a sink to catch the 'course_module_deleted' event.
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Now, run the adhoc task which performs the hard deletion.
|
|
phpunit_util::run_all_adhoc_tasks();
|
|
|
|
// Fetch and validate the event data.
|
|
$events = $sink->get_events();
|
|
$event = array_pop($events);
|
|
$sink->close();
|
|
$this->assertInstanceOf('\core\event\course_module_deleted', $event);
|
|
$this->assertEquals($module->cmid, $event->objectid);
|
|
$this->assertEquals($USER->id, $event->userid);
|
|
$this->assertEquals('course_modules', $event->objecttable);
|
|
$this->assertEquals(null, $event->get_url());
|
|
$this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
|
|
|
|
// Verify the context has been removed.
|
|
$this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
|
|
|
|
// Verify the course_module record has been deleted.
|
|
$cmcount = $DB->count_records('course_modules', ['id' => $module->cmid]);
|
|
$this->assertEmpty($cmcount);
|
|
}
|
|
|
|
public function test_async_module_deletion_hook_not_implemented() {
|
|
// Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns
|
|
// 'true' from the 'course_module_adhoc_deletion_recommended' hook.
|
|
// In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it.
|
|
global $DB, $USER;
|
|
$this->resetAfterTest(true);
|
|
$this->setAdminUser();
|
|
set_config('coursebinenable', false, 'tool_recyclebin');
|
|
|
|
// Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test.
|
|
// If at least one plugin still returns true, then skip this test.
|
|
if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
|
|
foreach ($pluginsfunction as $plugintype => $plugins) {
|
|
foreach ($plugins as $pluginfunction) {
|
|
if ($pluginfunction()) {
|
|
$this->markTestSkipped();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create course, module and context.
|
|
$course = $this->getDataGenerator()->create_course(['numsections' => 5]);
|
|
$module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
|
|
$modcontext = context_module::instance($module->cmid);
|
|
$cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST);
|
|
|
|
// Verify context exists.
|
|
$this->assertInstanceOf('context_module', $modcontext);
|
|
|
|
// Check events generated on the course_delete_module call.
|
|
$sink = $this->redirectEvents();
|
|
|
|
// Try to delete the module using the async flag.
|
|
course_delete_module($module->cmid, true); // Try to delete the module asynchronously.
|
|
|
|
// Fetch and validate the event data.
|
|
$events = $sink->get_events();
|
|
$event = array_pop($events);
|
|
$sink->close();
|
|
$this->assertInstanceOf('\core\event\course_module_deleted', $event);
|
|
$this->assertEquals($module->cmid, $event->objectid);
|
|
$this->assertEquals($USER->id, $event->userid);
|
|
$this->assertEquals('course_modules', $event->objecttable);
|
|
$this->assertEquals(null, $event->get_url());
|
|
$this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
|
|
|
|
// Verify the context has been removed.
|
|
$this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
|
|
|
|
// Verify the course_module record has been deleted.
|
|
$cmcount = $DB->count_records('course_modules', ['id' => $module->cmid]);
|
|
$this->assertEmpty($cmcount);
|
|
}
|
|
|
|
public function test_async_section_deletion_hook_implemented() {
|
|
// Async section deletion (provided section contains modules), depends on the 'true' being returned by at least one plugin
|
|
// implementing the 'course_module_adhoc_deletion_recommended' hook. In core, is implemented by the course recyclebin,
|
|
// which will only return true if the plugin is enabled. To make sure async deletion occurs, this test enables recyclebin.
|
|
global $DB, $USER;
|
|
$this->resetAfterTest(true);
|
|
$this->setAdminUser();
|
|
|
|
// Ensure recyclebin is enabled.
|
|
set_config('coursebinenable', true, 'tool_recyclebin');
|
|
|
|
// Create course, module and context.
|
|
$generator = $this->getDataGenerator();
|
|
$course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]);
|
|
$assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
|
|
$assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
|
|
$assign2 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
|
|
$assign3 = $generator->create_module('assign', ['course' => $course, 'section' => 0]);
|
|
|
|
// Delete empty section. No difference from normal, synchronous behaviour.
|
|
$this->assertTrue(course_delete_section($course, 4, false, true));
|
|
$this->assertEquals(3, course_get_format($course)->get_last_section_number());
|
|
|
|
// Delete a module in section 2 (using async). Need to verify this doesn't generate two tasks when we delete
|
|
// the section in the next step.
|
|
course_delete_module($assign2->cmid, true);
|
|
|
|
// Confirm that the module is pending deletion in its current section.
|
|
$section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison.
|
|
$this->assertEquals(true, $DB->record_exists('course_modules', ['id' => $assign2->cmid, 'deletioninprogress' => 1,
|
|
'section' => $section->id]));
|
|
|
|
// Now, delete section 2.
|
|
$this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change.
|
|
|
|
$sink = $this->redirectEvents(); // To capture the event.
|
|
$this->assertTrue(course_delete_section($course, 2, true, true));
|
|
|
|
// Now, confirm that:
|
|
// a) the section's modules have been flagged for deletion and moved to section 0 and;
|
|
// b) the section has been deleted and;
|
|
// c) course_section_deleted event has been fired. The course_module_deleted events will only fire once they have been
|
|
// removed from section 0 via the adhoc task.
|
|
|
|
// Modules should have been flagged for deletion and moved to section 0.
|
|
$sectionid = $DB->get_field('course_sections', 'id', ['course' => $course->id, 'section' => 0]);
|
|
$this->assertEquals(3, $DB->count_records('course_modules', ['section' => $sectionid, 'deletioninprogress' => 1]));
|
|
|
|
// Confirm the section has been deleted.
|
|
$this->assertEquals(2, course_get_format($course)->get_last_section_number());
|
|
|
|
// Check event fired.
|
|
$events = $sink->get_events();
|
|
$event = array_pop($events);
|
|
$sink->close();
|
|
$this->assertInstanceOf('\core\event\course_section_deleted', $event);
|
|
$this->assertEquals($section->id, $event->objectid);
|
|
$this->assertEquals($USER->id, $event->userid);
|
|
$this->assertEquals('course_sections', $event->objecttable);
|
|
$this->assertEquals(null, $event->get_url());
|
|
$this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id));
|
|
|
|
// Now, run the adhoc task to delete the modules from section 0.
|
|
$sink = $this->redirectEvents(); // To capture the events.
|
|
phpunit_util::run_all_adhoc_tasks();
|
|
|
|
// Confirm the modules have been deleted.
|
|
list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid, $assign2->cmid]);
|
|
$cmcount = $DB->count_records_select('course_modules', 'id ' . $insql, $assignids);
|
|
$this->assertEmpty($cmcount);
|
|
|
|
// Confirm other modules in section 0 still remain.
|
|
$this->assertEquals(1, $DB->count_records('course_modules', ['id' => $assign3->cmid]));
|
|
|
|
// Confirm that events were generated for all 3 of the modules.
|
|
$events = $sink->get_events();
|
|
$sink->close();
|
|
$count = 0;
|
|
while (!empty($events)) {
|
|
$event = array_pop($events);
|
|
if ($event instanceof \core\event\course_module_deleted &&
|
|
in_array($event->objectid, [$assign0->cmid, $assign1->cmid, $assign2->cmid])) {
|
|
$count++;
|
|
}
|
|
}
|
|
$this->assertEquals(3, $count);
|
|
}
|
|
|
|
public function test_async_section_deletion_hook_not_implemented() {
|
|
// If no plugins advocate async removal, then normal synchronous removal will take place.
|
|
// Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns
|
|
// 'true' from the 'course_module_adhoc_deletion_recommended' hook.
|
|
// In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it.
|
|
global $DB, $USER;
|
|
$this->resetAfterTest(true);
|
|
$this->setAdminUser();
|
|
set_config('coursebinenable', false, 'tool_recyclebin');
|
|
|
|
// Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test.
|
|
// If at least one plugin still returns true, then skip this test.
|
|
if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
|
|
foreach ($pluginsfunction as $plugintype => $plugins) {
|
|
foreach ($plugins as $pluginfunction) {
|
|
if ($pluginfunction()) {
|
|
$this->markTestSkipped();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create course, module and context.
|
|
$generator = $this->getDataGenerator();
|
|
$course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]);
|
|
$assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
|
|
$assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
|
|
|
|
// Delete empty section. No difference from normal, synchronous behaviour.
|
|
$this->assertTrue(course_delete_section($course, 4, false, true));
|
|
$this->assertEquals(3, course_get_format($course)->get_last_section_number());
|
|
|
|
// Delete section in the middle (2).
|
|
$section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison.
|
|
$this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change.
|
|
|
|
$sink = $this->redirectEvents(); // To capture the event.
|
|
$this->assertTrue(course_delete_section($course, 2, true, true));
|
|
|
|
// Now, confirm that:
|
|
// a) The section's modules have deleted and;
|
|
// b) the section has been deleted and;
|
|
// c) course_section_deleted event has been fired and;
|
|
// d) course_module_deleted events have both been fired.
|
|
|
|
// Confirm modules have been deleted.
|
|
list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid]);
|
|
$cmcount = $DB->count_records_select('course_modules', 'id ' . $insql, $assignids);
|
|
$this->assertEmpty($cmcount);
|
|
|
|
// Confirm the section has been deleted.
|
|
$this->assertEquals(2, course_get_format($course)->get_last_section_number());
|
|
|
|
// Confirm the course_section_deleted event has been generated.
|
|
$events = $sink->get_events();
|
|
$event = array_pop($events);
|
|
$sink->close();
|
|
$this->assertInstanceOf('\core\event\course_section_deleted', $event);
|
|
$this->assertEquals($section->id, $event->objectid);
|
|
$this->assertEquals($USER->id, $event->userid);
|
|
$this->assertEquals('course_sections', $event->objecttable);
|
|
$this->assertEquals(null, $event->get_url());
|
|
$this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id));
|
|
|
|
// Confirm that the course_module_deleted events have both been generated.
|
|
$count = 0;
|
|
while (!empty($events)) {
|
|
$event = array_pop($events);
|
|
if ($event instanceof \core\event\course_module_deleted &&
|
|
in_array($event->objectid, [$assign0->cmid, $assign1->cmid])) {
|
|
$count++;
|
|
}
|
|
}
|
|
$this->assertEquals(2, $count);
|
|
}
|
|
|
|
public function test_classify_course_for_timeline() {
|
|
global $DB, $CFG;
|
|
|
|
require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
|
|
|
|
set_config('enablecompletion', COMPLETION_ENABLED);
|
|
set_config('coursegraceperiodbefore', 0);
|
|
set_config('coursegraceperiodafter', 0);
|
|
|
|
$this->resetAfterTest(true);
|
|
$this->setAdminUser();
|
|
|
|
// Create courses for testing.
|
|
$generator = $this->getDataGenerator();
|
|
$future = time() + 3600;
|
|
$past = time() - 3600;
|
|
$futurecourse = $generator->create_course(['startdate' => $future]);
|
|
$pastcourse = $generator->create_course(['startdate' => $past - 60, 'enddate' => $past]);
|
|
$completedcourse = $generator->create_course(['enablecompletion' => COMPLETION_ENABLED]);
|
|
$inprogresscourse = $generator->create_course();
|
|
|
|
// Set completion rules.
|
|
$criteriadata = new stdClass();
|
|
$criteriadata->id = $completedcourse->id;
|
|
|
|
// Self completion.
|
|
$criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF;
|
|
$class = 'completion_criteria_self';
|
|
$criterion = new $class();
|
|
$criterion->update_config($criteriadata);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $futurecourse->id, $studentrole->id);
|
|
$this->getDataGenerator()->enrol_user($user->id, $pastcourse->id, $studentrole->id);
|
|
$this->getDataGenerator()->enrol_user($user->id, $completedcourse->id, $studentrole->id);
|
|
$this->getDataGenerator()->enrol_user($user->id, $inprogresscourse->id, $studentrole->id);
|
|
|
|
$this->setUser($user);
|
|
core_completion_external::mark_course_self_completed($completedcourse->id);
|
|
$ccompletion = new completion_completion(array('course' => $completedcourse->id, 'userid' => $user->id));
|
|
$ccompletion->mark_complete();
|
|
|
|
// Aggregate the completions.
|
|
$this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($pastcourse));
|
|
$this->assertEquals(COURSE_TIMELINE_FUTURE, course_classify_for_timeline($futurecourse));
|
|
$this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse));
|
|
$this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse));
|
|
|
|
// Test grace period.
|
|
set_config('coursegraceperiodafter', 1);
|
|
set_config('coursegraceperiodbefore', 1);
|
|
$this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($pastcourse));
|
|
$this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($futurecourse));
|
|
$this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse));
|
|
$this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse));
|
|
}
|
|
|
|
/**
|
|
* Test the main function for updating all calendar events for a module.
|
|
*/
|
|
public function test_course_module_calendar_event_update_process() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
$this->setAdminUser();
|
|
|
|
$completionexpected = time();
|
|
$duedate = time();
|
|
|
|
$course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
|
|
$assign = $this->getDataGenerator()->create_module('assign', [
|
|
'course' => $course,
|
|
'completionexpected' => $completionexpected,
|
|
'duedate' => $duedate
|
|
]);
|
|
|
|
$cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
|
|
$events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]);
|
|
// Check that both events are using the expected dates.
|
|
foreach ($events as $event) {
|
|
if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
|
|
$this->assertEquals($completionexpected, $event->timestart);
|
|
}
|
|
if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
|
|
$this->assertEquals($duedate, $event->timestart);
|
|
}
|
|
}
|
|
|
|
// We have to manually update the module and the course module.
|
|
$newcompletionexpected = time() + DAYSECS * 60;
|
|
$newduedate = time() + DAYSECS * 45;
|
|
$newmodulename = 'Assign - new name';
|
|
|
|
$moduleobject = (object)array('id' => $assign->id, 'duedate' => $newduedate, 'name' => $newmodulename);
|
|
$DB->update_record('assign', $moduleobject);
|
|
$cmobject = (object)array('id' => $cm->id, 'completionexpected' => $newcompletionexpected);
|
|
$DB->update_record('course_modules', $cmobject);
|
|
|
|
$assign = $DB->get_record('assign', ['id' => $assign->id]);
|
|
$cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
|
|
|
|
course_module_calendar_event_update_process($assign, $cm);
|
|
|
|
$events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]);
|
|
// Now check that the details have been updated properly from the function.
|
|
foreach ($events as $event) {
|
|
if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
|
|
$this->assertEquals($newcompletionexpected, $event->timestart);
|
|
$this->assertEquals(get_string('completionexpectedfor', 'completion', (object)['instancename' => $newmodulename]),
|
|
$event->name);
|
|
}
|
|
if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
|
|
$this->assertEquals($newduedate, $event->timestart);
|
|
$this->assertEquals(get_string('calendardue', 'assign', $newmodulename), $event->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test the higher level checks for updating calendar events for an instance.
|
|
*/
|
|
public function test_course_module_update_calendar_events() {
|
|
$this->resetAfterTest();
|
|
$this->setAdminUser();
|
|
|
|
$completionexpected = time();
|
|
$duedate = time();
|
|
|
|
$course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
|
|
$assign = $this->getDataGenerator()->create_module('assign', [
|
|
'course' => $course,
|
|
'completionexpected' => $completionexpected,
|
|
'duedate' => $duedate
|
|
]);
|
|
|
|
$cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
|
|
|
|
// Both the instance and cm objects are missing.
|
|
$this->assertFalse(course_module_update_calendar_events('assign'));
|
|
// Just using the assign instance.
|
|
$this->assertTrue(course_module_update_calendar_events('assign', $assign));
|
|
// Just using the course module object.
|
|
$this->assertTrue(course_module_update_calendar_events('assign', null, $cm));
|
|
// Using both the assign instance and the course module object.
|
|
$this->assertTrue(course_module_update_calendar_events('assign', $assign, $cm));
|
|
}
|
|
|
|
/**
|
|
* Test the higher level checks for updating calendar events for a module.
|
|
*/
|
|
public function test_course_module_bulk_update_calendar_events() {
|
|
$this->resetAfterTest();
|
|
$this->setAdminUser();
|
|
|
|
$completionexpected = time();
|
|
$duedate = time();
|
|
|
|
$course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
|
|
$course2 = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
|
|
$assign = $this->getDataGenerator()->create_module('assign', [
|
|
'course' => $course,
|
|
'completionexpected' => $completionexpected,
|
|
'duedate' => $duedate
|
|
]);
|
|
|
|
// No assign instances in this course.
|
|
$this->assertFalse(course_module_bulk_update_calendar_events('assign', $course2->id));
|
|
// No book instances for the site.
|
|
$this->assertFalse(course_module_bulk_update_calendar_events('book'));
|
|
// Update all assign instances.
|
|
$this->assertTrue(course_module_bulk_update_calendar_events('assign'));
|
|
// Update the assign instances for this course.
|
|
$this->assertTrue(course_module_bulk_update_calendar_events('assign', $course->id));
|
|
}
|
|
|
|
/**
|
|
* Test that a student can view participants in a course they are enrolled in.
|
|
*/
|
|
public function test_course_can_view_participants_as_student() {
|
|
$this->resetAfterTest();
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id);
|
|
|
|
$this->setUser($user);
|
|
|
|
$this->assertTrue(course_can_view_participants($coursecontext));
|
|
}
|
|
|
|
/**
|
|
* Test that a student in a course can not view participants on the site.
|
|
*/
|
|
public function test_course_can_view_participants_as_student_on_site() {
|
|
$this->resetAfterTest();
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id);
|
|
|
|
$this->setUser($user);
|
|
|
|
$this->assertFalse(course_can_view_participants(context_system::instance()));
|
|
}
|
|
|
|
/**
|
|
* Test that an admin can view participants on the site.
|
|
*/
|
|
public function test_course_can_view_participants_as_admin_on_site() {
|
|
$this->resetAfterTest();
|
|
|
|
$this->setAdminUser();
|
|
|
|
$this->assertTrue(course_can_view_participants(context_system::instance()));
|
|
}
|
|
|
|
/**
|
|
* Test teachers can view participants in a course they are enrolled in.
|
|
*/
|
|
public function test_course_can_view_participants_as_teacher() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
|
|
|
|
$this->setUser($user);
|
|
|
|
$this->assertTrue(course_can_view_participants($coursecontext));
|
|
}
|
|
|
|
/**
|
|
* Check the teacher can still view the participants page without the 'viewparticipants' cap.
|
|
*/
|
|
public function test_course_can_view_participants_as_teacher_without_view_participants_cap() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
|
|
|
|
$this->setUser($user);
|
|
|
|
// Disable one of the capabilties.
|
|
assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $coursecontext);
|
|
|
|
// Should still be able to view the page as they have the 'moodle/course:enrolreview' cap.
|
|
$this->assertTrue(course_can_view_participants($coursecontext));
|
|
}
|
|
|
|
/**
|
|
* Check the teacher can still view the participants page without the 'moodle/course:enrolreview' cap.
|
|
*/
|
|
public function test_course_can_view_participants_as_teacher_without_enrol_review_cap() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
|
|
|
|
$this->setUser($user);
|
|
|
|
// Disable one of the capabilties.
|
|
assign_capability('moodle/course:enrolreview', CAP_PROHIBIT, $roleid, $coursecontext);
|
|
|
|
// Should still be able to view the page as they have the 'moodle/course:viewparticipants' cap.
|
|
$this->assertTrue(course_can_view_participants($coursecontext));
|
|
}
|
|
|
|
/**
|
|
* Check the teacher can not view the participants page without the required caps.
|
|
*/
|
|
public function test_course_can_view_participants_as_teacher_without_required_caps() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
|
|
|
|
$this->setUser($user);
|
|
|
|
// Disable the capabilities.
|
|
assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $coursecontext);
|
|
assign_capability('moodle/course:enrolreview', CAP_PROHIBIT, $roleid, $coursecontext);
|
|
|
|
$this->assertFalse(course_can_view_participants($coursecontext));
|
|
}
|
|
|
|
/**
|
|
* Check that an exception is not thrown if we can view the participants page.
|
|
*/
|
|
public function test_course_require_view_participants() {
|
|
$this->resetAfterTest();
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$coursecontext = context_course::instance($course->id);
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id);
|
|
|
|
$this->setUser($user);
|
|
|
|
course_require_view_participants($coursecontext);
|
|
}
|
|
|
|
/**
|
|
* Check that an exception is thrown if we can't view the participants page.
|
|
*/
|
|
public function test_course_require_view_participants_as_student_on_site() {
|
|
$this->resetAfterTest();
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id);
|
|
|
|
$this->setUser($user);
|
|
|
|
$this->expectException('required_capability_exception');
|
|
course_require_view_participants(context_system::instance());
|
|
}
|
|
|
|
/**
|
|
* Testing the can_download_from_backup_filearea fn.
|
|
*/
|
|
public function test_can_download_from_backup_filearea() {
|
|
global $DB;
|
|
$this->resetAfterTest();
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$context = context_course::instance($course->id);
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
|
|
$this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
|
|
|
|
// The 'automated' backup area. Downloading from this area requires two capabilities.
|
|
// If the user has only the 'backup:downloadfile' capability.
|
|
unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
|
|
assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
|
|
$this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
|
|
|
|
// If the user has only the 'restore:userinfo' capability.
|
|
unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
|
|
assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context);
|
|
$this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
|
|
|
|
// If the user has both capabilities.
|
|
assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
|
|
assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context);
|
|
$this->assertTrue(can_download_from_backup_filearea('automated', $context, $user));
|
|
|
|
// Is the user has neither of the capabilities.
|
|
unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
|
|
unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
|
|
$this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
|
|
|
|
// The 'course ' and 'backup' backup file areas. These are governed by the same download capability.
|
|
// User has the capability.
|
|
unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
|
|
assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
|
|
$this->assertTrue(can_download_from_backup_filearea('course', $context, $user));
|
|
$this->assertTrue(can_download_from_backup_filearea('backup', $context, $user));
|
|
|
|
// User doesn't have the capability.
|
|
unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
|
|
$this->assertFalse(can_download_from_backup_filearea('course', $context, $user));
|
|
$this->assertFalse(can_download_from_backup_filearea('backup', $context, $user));
|
|
|
|
// A file area that doesn't exist. No permissions, regardless of capabilities.
|
|
assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
|
|
$this->assertFalse(can_download_from_backup_filearea('testing', $context, $user));
|
|
}
|
|
|
|
/**
|
|
* Test cases for the course_classify_courses_for_timeline test.
|
|
*/
|
|
public function get_course_classify_courses_for_timeline_test_cases() {
|
|
$now = time();
|
|
$day = 86400;
|
|
|
|
return [
|
|
'no courses' => [
|
|
'coursesdata' => [],
|
|
'expected' => [
|
|
COURSE_TIMELINE_PAST => [],
|
|
COURSE_TIMELINE_FUTURE => [],
|
|
COURSE_TIMELINE_INPROGRESS => []
|
|
]
|
|
],
|
|
'only past' => [
|
|
'coursesdata' => [
|
|
[
|
|
'shortname' => 'past1',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'past2',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
]
|
|
],
|
|
'expected' => [
|
|
COURSE_TIMELINE_PAST => ['past1', 'past2'],
|
|
COURSE_TIMELINE_FUTURE => [],
|
|
COURSE_TIMELINE_INPROGRESS => []
|
|
]
|
|
],
|
|
'only in progress' => [
|
|
'coursesdata' => [
|
|
[
|
|
'shortname' => 'inprogress1',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'inprogress2',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
]
|
|
],
|
|
'expected' => [
|
|
COURSE_TIMELINE_PAST => [],
|
|
COURSE_TIMELINE_FUTURE => [],
|
|
COURSE_TIMELINE_INPROGRESS => ['inprogress1', 'inprogress2']
|
|
]
|
|
],
|
|
'only future' => [
|
|
'coursesdata' => [
|
|
[
|
|
'shortname' => 'future1',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'future2',
|
|
'startdate' => $now + $day
|
|
]
|
|
],
|
|
'expected' => [
|
|
COURSE_TIMELINE_PAST => [],
|
|
COURSE_TIMELINE_FUTURE => ['future1', 'future2'],
|
|
COURSE_TIMELINE_INPROGRESS => []
|
|
]
|
|
],
|
|
'combination' => [
|
|
'coursesdata' => [
|
|
[
|
|
'shortname' => 'past1',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'past2',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'inprogress1',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'inprogress2',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'future1',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'future2',
|
|
'startdate' => $now + $day
|
|
]
|
|
],
|
|
'expected' => [
|
|
COURSE_TIMELINE_PAST => ['past1', 'past2'],
|
|
COURSE_TIMELINE_FUTURE => ['future1', 'future2'],
|
|
COURSE_TIMELINE_INPROGRESS => ['inprogress1', 'inprogress2']
|
|
]
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test the course_classify_courses_for_timeline function.
|
|
*
|
|
* @dataProvider get_course_classify_courses_for_timeline_test_cases()
|
|
* @param array $coursesdata Courses to create
|
|
* @param array $expected Expected test results.
|
|
*/
|
|
public function test_course_classify_courses_for_timeline($coursesdata, $expected) {
|
|
$this->resetAfterTest();
|
|
$generator = $this->getDataGenerator();
|
|
|
|
$courses = array_map(function($coursedata) use ($generator) {
|
|
return $generator->create_course($coursedata);
|
|
}, $coursesdata);
|
|
|
|
sort($expected[COURSE_TIMELINE_PAST]);
|
|
sort($expected[COURSE_TIMELINE_FUTURE]);
|
|
sort($expected[COURSE_TIMELINE_INPROGRESS]);
|
|
|
|
$results = course_classify_courses_for_timeline($courses);
|
|
|
|
$actualpast = array_map(function($result) {
|
|
return $result->shortname;
|
|
}, $results[COURSE_TIMELINE_PAST]);
|
|
|
|
$actualfuture = array_map(function($result) {
|
|
return $result->shortname;
|
|
}, $results[COURSE_TIMELINE_FUTURE]);
|
|
|
|
$actualinprogress = array_map(function($result) {
|
|
return $result->shortname;
|
|
}, $results[COURSE_TIMELINE_INPROGRESS]);
|
|
|
|
sort($actualpast);
|
|
sort($actualfuture);
|
|
sort($actualinprogress);
|
|
|
|
$this->assertEquals($expected[COURSE_TIMELINE_PAST], $actualpast);
|
|
$this->assertEquals($expected[COURSE_TIMELINE_FUTURE], $actualfuture);
|
|
$this->assertEquals($expected[COURSE_TIMELINE_INPROGRESS], $actualinprogress);
|
|
}
|
|
|
|
/**
|
|
* Test cases for the course_get_enrolled_courses_for_logged_in_user tests.
|
|
*/
|
|
public function get_course_get_enrolled_courses_for_logged_in_user_test_cases() {
|
|
$buildexpectedresult = function($limit, $offset) {
|
|
$result = [];
|
|
for ($i = $offset; $i < $offset + $limit; $i++) {
|
|
$result[] = "testcourse{$i}";
|
|
}
|
|
return $result;
|
|
};
|
|
|
|
return [
|
|
'zero records' => [
|
|
'dbquerylimit' => 3,
|
|
'totalcourses' => 0,
|
|
'limit' => 0,
|
|
'offset' => 0,
|
|
'expecteddbqueries' => 4,
|
|
'expectedresult' => $buildexpectedresult(0, 0)
|
|
],
|
|
'less than query limit' => [
|
|
'dbquerylimit' => 3,
|
|
'totalcourses' => 2,
|
|
'limit' => 0,
|
|
'offset' => 0,
|
|
'expecteddbqueries' => 2,
|
|
'expectedresult' => $buildexpectedresult(2, 0)
|
|
],
|
|
'more than query limit' => [
|
|
'dbquerylimit' => 3,
|
|
'totalcourses' => 7,
|
|
'limit' => 0,
|
|
'offset' => 0,
|
|
'expecteddbqueries' => 4,
|
|
'expectedresult' => $buildexpectedresult(7, 0)
|
|
],
|
|
'limit less than query limit' => [
|
|
'dbquerylimit' => 3,
|
|
'totalcourses' => 7,
|
|
'limit' => 2,
|
|
'offset' => 0,
|
|
'expecteddbqueries' => 2,
|
|
'expectedresult' => $buildexpectedresult(2, 0)
|
|
],
|
|
'limit less than query limit with offset' => [
|
|
'dbquerylimit' => 3,
|
|
'totalcourses' => 7,
|
|
'limit' => 2,
|
|
'offset' => 2,
|
|
'expecteddbqueries' => 2,
|
|
'expectedresult' => $buildexpectedresult(2, 2)
|
|
],
|
|
'limit less than total' => [
|
|
'dbquerylimit' => 3,
|
|
'totalcourses' => 9,
|
|
'limit' => 6,
|
|
'offset' => 0,
|
|
'expecteddbqueries' => 3,
|
|
'expectedresult' => $buildexpectedresult(6, 0)
|
|
],
|
|
'less results than limit' => [
|
|
'dbquerylimit' => 4,
|
|
'totalcourses' => 9,
|
|
'limit' => 20,
|
|
'offset' => 0,
|
|
'expecteddbqueries' => 4,
|
|
'expectedresult' => $buildexpectedresult(9, 0)
|
|
],
|
|
'less results than limit exact divisible' => [
|
|
'dbquerylimit' => 3,
|
|
'totalcourses' => 9,
|
|
'limit' => 20,
|
|
'offset' => 0,
|
|
'expecteddbqueries' => 5,
|
|
'expectedresult' => $buildexpectedresult(9, 0)
|
|
],
|
|
'less results than limit with offset' => [
|
|
'dbquerylimit' => 3,
|
|
'totalcourses' => 9,
|
|
'limit' => 10,
|
|
'offset' => 5,
|
|
'expecteddbqueries' => 3,
|
|
'expectedresult' => $buildexpectedresult(4, 5)
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test the course_get_enrolled_courses_for_logged_in_user function.
|
|
*
|
|
* @dataProvider get_course_get_enrolled_courses_for_logged_in_user_test_cases()
|
|
* @param int $dbquerylimit Number of records to load per DB request
|
|
* @param int $totalcourses Number of courses to create
|
|
* @param int $limit Maximum number of results to get.
|
|
* @param int $offset Skip this number of results from the start of the result set.
|
|
* @param int $expecteddbqueries The number of DB queries expected during the test.
|
|
* @param array $expectedresult Expected test results.
|
|
*/
|
|
public function test_course_get_enrolled_courses_for_logged_in_user(
|
|
$dbquerylimit,
|
|
$totalcourses,
|
|
$limit,
|
|
$offset,
|
|
$expecteddbqueries,
|
|
$expectedresult
|
|
) {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
$generator = $this->getDataGenerator();
|
|
$student = $generator->create_user();
|
|
|
|
for ($i = 0; $i < $totalcourses; $i++) {
|
|
$shortname = "testcourse{$i}";
|
|
$course = $generator->create_course(['shortname' => $shortname]);
|
|
$generator->enrol_user($student->id, $course->id, 'student');
|
|
}
|
|
|
|
$this->setUser($student);
|
|
|
|
$initialquerycount = $DB->perf_get_queries();
|
|
$courses = course_get_enrolled_courses_for_logged_in_user($limit, $offset, 'shortname ASC', 'shortname', $dbquerylimit);
|
|
|
|
// Loop over the result set to force the lazy loading to kick in so that we can check the
|
|
// number of DB queries.
|
|
$actualresult = array_map(function($course) {
|
|
return $course->shortname;
|
|
}, iterator_to_array($courses, false));
|
|
|
|
sort($expectedresult);
|
|
|
|
$this->assertEquals($expectedresult, $actualresult);
|
|
$this->assertLessThanOrEqual($expecteddbqueries, $DB->perf_get_queries() - $initialquerycount);
|
|
}
|
|
|
|
/**
|
|
* Test cases for the course_filter_courses_by_timeline_classification tests.
|
|
*/
|
|
public function get_course_filter_courses_by_timeline_classification_test_cases() {
|
|
$now = time();
|
|
$day = 86400;
|
|
|
|
$coursedata = [
|
|
[
|
|
'shortname' => 'apast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'bpast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'cpast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'dpast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'epast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'ainprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'binprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'cinprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'dinprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'einprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'afuture',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'bfuture',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'cfuture',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'dfuture',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'efuture',
|
|
'startdate' => $now + $day
|
|
]
|
|
];
|
|
|
|
// Raw enrolled courses result set should be returned in this order:
|
|
// afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast,
|
|
// dfuture, dinprogress, dpast, efuture, einprogress, epast
|
|
//
|
|
// By classification the offset values for each record should be:
|
|
// COURSE_TIMELINE_FUTURE
|
|
// 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture)
|
|
// COURSE_TIMELINE_INPROGRESS
|
|
// 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress)
|
|
// COURSE_TIMELINE_PAST
|
|
// 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast).
|
|
return [
|
|
'empty set' => [
|
|
'coursedata' => [],
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 2,
|
|
'offset' => 0,
|
|
'expectedcourses' => [],
|
|
'expectedprocessedcount' => 0
|
|
],
|
|
// COURSE_TIMELINE_FUTURE.
|
|
'future not limit no offset' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 0,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
|
|
'expectedprocessedcount' => 15
|
|
],
|
|
'future no offset' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 2,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['afuture', 'bfuture'],
|
|
'expectedprocessedcount' => 4
|
|
],
|
|
'future offset' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 2,
|
|
'offset' => 2,
|
|
'expectedcourses' => ['bfuture', 'cfuture'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'future exact limit' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 5,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
|
|
'expectedprocessedcount' => 13
|
|
],
|
|
'future limit less results' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
|
|
'expectedprocessedcount' => 15
|
|
],
|
|
'future limit less results with offset' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 10,
|
|
'offset' => 5,
|
|
'expectedcourses' => ['cfuture', 'dfuture', 'efuture'],
|
|
'expectedprocessedcount' => 10
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test the course_get_enrolled_courses_for_logged_in_user_from_search function.
|
|
*/
|
|
public function test_course_get_enrolled_courses_for_logged_in_user_from_search() {
|
|
global $DB;
|
|
|
|
// Set up.
|
|
|
|
$this->resetAfterTest();
|
|
$generator = $this->getDataGenerator();
|
|
$student = $generator->create_user();
|
|
|
|
$cat1 = core_course_category::create(['name' => 'Cat1']);
|
|
$cat2 = core_course_category::create(['name' => 'Cat2', 'parent' => $cat1->id]);
|
|
$c1 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 3', 'summary' => 'Magic', 'idnumber' => 'ID3']);
|
|
$c2 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 1', 'summary' => 'Magic']);
|
|
$c3 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Математика', 'summary' => ' Test Magic']);
|
|
$c4 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 4', 'summary' => 'Magic', 'idnumber' => 'ID4']);
|
|
|
|
$c5 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 5', 'summary' => 'Magic']);
|
|
$c6 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Дискретная Математика', 'summary' => 'Magic']);
|
|
$c7 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 7', 'summary' => 'Magic']);
|
|
$c8 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 8', 'summary' => 'Magic']);
|
|
|
|
for ($i = 1; $i < 9; $i++) {
|
|
$generator->enrol_user($student->id, ${"c$i"}->id, 'student');
|
|
}
|
|
|
|
$this->setUser($student);
|
|
|
|
$returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
|
|
0,
|
|
0,
|
|
'id ASC',
|
|
null,
|
|
COURSE_DB_QUERY_LIMIT,
|
|
['search' => 'test'],
|
|
['idonly' => true]
|
|
);
|
|
|
|
$actualresult = array_map(function($course) {
|
|
return $course->id;
|
|
}, iterator_to_array($returnedcourses, false));
|
|
|
|
$this->assertEquals([$c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c7->id, $c8->id], $actualresult);
|
|
|
|
// Test no courses matching the search.
|
|
$returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
|
|
0,
|
|
0,
|
|
'id ASC',
|
|
null,
|
|
COURSE_DB_QUERY_LIMIT,
|
|
['search' => 'foobar'],
|
|
['idonly' => true]
|
|
);
|
|
|
|
$actualresult = array_map(function($course) {
|
|
return $course->id;
|
|
}, iterator_to_array($returnedcourses, false));
|
|
|
|
$this->assertEquals([], $actualresult);
|
|
|
|
// Test returning all courses that have a mutual summary.
|
|
$returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
|
|
0,
|
|
0,
|
|
'id ASC',
|
|
null,
|
|
COURSE_DB_QUERY_LIMIT,
|
|
['search' => 'Magic'],
|
|
['idonly' => true]
|
|
);
|
|
|
|
$actualresult = array_map(function($course) {
|
|
return $course->id;
|
|
}, iterator_to_array($returnedcourses, false));
|
|
|
|
$this->assertEquals([$c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c6->id, $c7->id, $c8->id], $actualresult);
|
|
|
|
// Test returning a unique course.
|
|
$returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
|
|
0,
|
|
0,
|
|
'id ASC',
|
|
null,
|
|
COURSE_DB_QUERY_LIMIT,
|
|
['search' => 'Дискретная'],
|
|
['idonly' => true]
|
|
);
|
|
|
|
$actualresult = array_map(function($course) {
|
|
return $course->id;
|
|
}, iterator_to_array($returnedcourses, false));
|
|
|
|
$this->assertEquals([$c6->id], $actualresult);
|
|
}
|
|
|
|
/**
|
|
* Test the course_filter_courses_by_timeline_classification function.
|
|
*
|
|
* @dataProvider get_course_filter_courses_by_timeline_classification_test_cases()
|
|
* @param array $coursedata Course test data to create.
|
|
* @param string $classification Timeline classification.
|
|
* @param int $limit Maximum number of results to return.
|
|
* @param int $offset Results to skip at the start of the result set.
|
|
* @param string[] $expectedcourses Expected courses in results.
|
|
* @param int $expectedprocessedcount Expected number of course records to be processed.
|
|
*/
|
|
public function test_course_filter_courses_by_timeline_classification(
|
|
$coursedata,
|
|
$classification,
|
|
$limit,
|
|
$offset,
|
|
$expectedcourses,
|
|
$expectedprocessedcount
|
|
) {
|
|
$this->resetAfterTest();
|
|
$generator = $this->getDataGenerator();
|
|
|
|
$courses = array_map(function($coursedata) use ($generator) {
|
|
return $generator->create_course($coursedata);
|
|
}, $coursedata);
|
|
|
|
$student = $generator->create_user();
|
|
|
|
foreach ($courses as $course) {
|
|
$generator->enrol_user($student->id, $course->id, 'student');
|
|
}
|
|
|
|
$this->setUser($student);
|
|
|
|
$coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
|
|
list($result, $processedcount) = course_filter_courses_by_timeline_classification(
|
|
$coursesgenerator,
|
|
$classification,
|
|
$limit
|
|
);
|
|
|
|
$actual = array_map(function($course) {
|
|
return $course->shortname;
|
|
}, $result);
|
|
|
|
$this->assertEquals($expectedcourses, $actual);
|
|
$this->assertEquals($expectedprocessedcount, $processedcount);
|
|
}
|
|
|
|
/**
|
|
* Test cases for the course_filter_courses_by_timeline_classification tests.
|
|
*/
|
|
public function get_course_filter_courses_by_customfield_test_cases() {
|
|
global $CFG;
|
|
require_once($CFG->dirroot.'/blocks/myoverview/lib.php');
|
|
$coursedata = [
|
|
[
|
|
'shortname' => 'C1',
|
|
'customfield_checkboxfield' => 1,
|
|
'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'),
|
|
'customfield_selectfield' => 1,
|
|
'customfield_textfield' => 'fish',
|
|
],
|
|
[
|
|
'shortname' => 'C2',
|
|
'customfield_checkboxfield' => 0,
|
|
'customfield_datefield' => strtotime('1980-08-05T13:00:00Z'),
|
|
],
|
|
[
|
|
'shortname' => 'C3',
|
|
'customfield_checkboxfield' => 0,
|
|
'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'),
|
|
'customfield_selectfield' => 2,
|
|
'customfield_textfield' => 'dog',
|
|
],
|
|
[
|
|
'shortname' => 'C4',
|
|
'customfield_checkboxfield' => 1,
|
|
'customfield_selectfield' => 3,
|
|
'customfield_textfield' => 'cat',
|
|
],
|
|
[
|
|
'shortname' => 'C5',
|
|
'customfield_datefield' => strtotime('1980-08-06T13:00:00Z'),
|
|
'customfield_selectfield' => 2,
|
|
'customfield_textfield' => 'fish',
|
|
],
|
|
];
|
|
|
|
return [
|
|
'empty set' => [
|
|
'coursedata' => [],
|
|
'customfield' => 'checkboxfield',
|
|
'customfieldvalue' => 1,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => [],
|
|
'expectedprocessedcount' => 0
|
|
],
|
|
'checkbox yes' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'checkboxfield',
|
|
'customfieldvalue' => 1,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C1', 'C4'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'checkbox no' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'checkboxfield',
|
|
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C2', 'C3', 'C5'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'date 1 Feb 2001' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'datefield',
|
|
'customfieldvalue' => strtotime('2001-02-01T12:00:00Z'),
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C1', 'C3'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'date 6 Aug 1980' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'datefield',
|
|
'customfieldvalue' => strtotime('1980-08-06T13:00:00Z'),
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C5'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'date no date' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'datefield',
|
|
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C4'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'select Option 1' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'selectfield',
|
|
'customfieldvalue' => 1,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C1'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'select Option 2' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'selectfield',
|
|
'customfieldvalue' => 2,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C3', 'C5'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'select no select' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'selectfield',
|
|
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C2'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'text fish' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'textfield',
|
|
'customfieldvalue' => 'fish',
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C1', 'C5'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'text dog' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'textfield',
|
|
'customfieldvalue' => 'dog',
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C3'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'text no text' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'textfield',
|
|
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C2'],
|
|
'expectedprocessedcount' => 5
|
|
],
|
|
'checkbox limit no' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'checkboxfield',
|
|
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
|
|
'limit' => 2,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['C2', 'C3'],
|
|
'expectedprocessedcount' => 3
|
|
],
|
|
'checkbox limit offset no' => [
|
|
'coursedata' => $coursedata,
|
|
'customfield' => 'checkboxfield',
|
|
'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
|
|
'limit' => 2,
|
|
'offset' => 3,
|
|
'expectedcourses' => ['C5'],
|
|
'expectedprocessedcount' => 2
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test the course_filter_courses_by_customfield function.
|
|
*
|
|
* @dataProvider get_course_filter_courses_by_customfield_test_cases()
|
|
* @param array $coursedata Course test data to create.
|
|
* @param string $customfield Shortname of the customfield.
|
|
* @param string $customfieldvalue the value to filter by.
|
|
* @param int $limit Maximum number of results to return.
|
|
* @param int $offset Results to skip at the start of the result set.
|
|
* @param string[] $expectedcourses Expected courses in results.
|
|
* @param int $expectedprocessedcount Expected number of course records to be processed.
|
|
*/
|
|
public function test_course_filter_courses_by_customfield(
|
|
$coursedata,
|
|
$customfield,
|
|
$customfieldvalue,
|
|
$limit,
|
|
$offset,
|
|
$expectedcourses,
|
|
$expectedprocessedcount
|
|
) {
|
|
$this->resetAfterTest();
|
|
$generator = $this->getDataGenerator();
|
|
|
|
// Create the custom fields.
|
|
$generator->create_custom_field_category([
|
|
'name' => 'Course fields',
|
|
'component' => 'core_course',
|
|
'area' => 'course',
|
|
'itemid' => 0,
|
|
]);
|
|
$generator->create_custom_field([
|
|
'name' => 'Checkbox field',
|
|
'category' => 'Course fields',
|
|
'type' => 'checkbox',
|
|
'shortname' => 'checkboxfield',
|
|
]);
|
|
$generator->create_custom_field([
|
|
'name' => 'Date field',
|
|
'category' => 'Course fields',
|
|
'type' => 'date',
|
|
'shortname' => 'datefield',
|
|
'configdata' => '{"mindate":0, "maxdate":0}',
|
|
]);
|
|
$generator->create_custom_field([
|
|
'name' => 'Select field',
|
|
'category' => 'Course fields',
|
|
'type' => 'select',
|
|
'shortname' => 'selectfield',
|
|
'configdata' => '{"options":"Option 1\nOption 2\nOption 3\nOption 4"}',
|
|
]);
|
|
$generator->create_custom_field([
|
|
'name' => 'Text field',
|
|
'category' => 'Course fields',
|
|
'type' => 'text',
|
|
'shortname' => 'textfield',
|
|
]);
|
|
|
|
$courses = array_map(function($coursedata) use ($generator) {
|
|
return $generator->create_course($coursedata);
|
|
}, $coursedata);
|
|
|
|
$student = $generator->create_user();
|
|
|
|
foreach ($courses as $course) {
|
|
$generator->enrol_user($student->id, $course->id, 'student');
|
|
}
|
|
|
|
$this->setUser($student);
|
|
|
|
$coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
|
|
list($result, $processedcount) = course_filter_courses_by_customfield(
|
|
$coursesgenerator,
|
|
$customfield,
|
|
$customfieldvalue,
|
|
$limit
|
|
);
|
|
|
|
$actual = array_map(function($course) {
|
|
return $course->shortname;
|
|
}, $result);
|
|
|
|
$this->assertEquals($expectedcourses, $actual);
|
|
$this->assertEquals($expectedprocessedcount, $processedcount);
|
|
}
|
|
|
|
/**
|
|
* Test cases for the course_filter_courses_by_timeline_classification w/ hidden courses tests.
|
|
*/
|
|
public function get_course_filter_courses_by_timeline_classification_hidden_courses_test_cases() {
|
|
$now = time();
|
|
$day = 86400;
|
|
|
|
$coursedata = [
|
|
[
|
|
'shortname' => 'apast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'bpast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'cpast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'dpast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'epast',
|
|
'startdate' => $now - ($day * 2),
|
|
'enddate' => $now - $day
|
|
],
|
|
[
|
|
'shortname' => 'ainprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'binprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'cinprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'dinprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'einprogress',
|
|
'startdate' => $now - $day,
|
|
'enddate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'afuture',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'bfuture',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'cfuture',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'dfuture',
|
|
'startdate' => $now + $day
|
|
],
|
|
[
|
|
'shortname' => 'efuture',
|
|
'startdate' => $now + $day
|
|
]
|
|
];
|
|
|
|
// Raw enrolled courses result set should be returned in this order:
|
|
// afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast,
|
|
// dfuture, dinprogress, dpast, efuture, einprogress, epast
|
|
//
|
|
// By classification the offset values for each record should be:
|
|
// COURSE_TIMELINE_FUTURE
|
|
// 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture)
|
|
// COURSE_TIMELINE_INPROGRESS
|
|
// 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress)
|
|
// COURSE_TIMELINE_PAST
|
|
// 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast).
|
|
return [
|
|
'empty set' => [
|
|
'coursedata' => [],
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 2,
|
|
'offset' => 0,
|
|
'expectedcourses' => [],
|
|
'expectedprocessedcount' => 0,
|
|
'hiddencourse' => ''
|
|
],
|
|
// COURSE_TIMELINE_FUTURE.
|
|
'future not limit no offset' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 0,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
|
|
'expectedprocessedcount' => 15,
|
|
'hiddencourse' => 'bfuture'
|
|
],
|
|
'future no offset' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 2,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['afuture', 'cfuture'],
|
|
'expectedprocessedcount' => 7,
|
|
'hiddencourse' => 'bfuture'
|
|
],
|
|
'future offset' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 2,
|
|
'offset' => 2,
|
|
'expectedcourses' => ['bfuture', 'dfuture'],
|
|
'expectedprocessedcount' => 8,
|
|
'hiddencourse' => 'cfuture'
|
|
],
|
|
'future exact limit' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 5,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
|
|
'expectedprocessedcount' => 15,
|
|
'hiddencourse' => 'bfuture'
|
|
],
|
|
'future limit less results' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 10,
|
|
'offset' => 0,
|
|
'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
|
|
'expectedprocessedcount' => 15,
|
|
'hiddencourse' => 'bfuture'
|
|
],
|
|
'future limit less results with offset' => [
|
|
'coursedata' => $coursedata,
|
|
'classification' => COURSE_TIMELINE_FUTURE,
|
|
'limit' => 10,
|
|
'offset' => 5,
|
|
'expectedcourses' => ['cfuture', 'efuture'],
|
|
'expectedprocessedcount' => 10,
|
|
'hiddencourse' => 'dfuture'
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test the course_filter_courses_by_timeline_classification function hidden courses.
|
|
*
|
|
* @dataProvider get_course_filter_courses_by_timeline_classification_hidden_courses_test_cases()
|
|
* @param array $coursedata Course test data to create.
|
|
* @param string $classification Timeline classification.
|
|
* @param int $limit Maximum number of results to return.
|
|
* @param int $offset Results to skip at the start of the result set.
|
|
* @param string[] $expectedcourses Expected courses in results.
|
|
* @param int $expectedprocessedcount Expected number of course records to be processed.
|
|
* @param int $hiddencourse The course to hide as part of this process
|
|
*/
|
|
public function test_course_filter_courses_by_timeline_classification_with_hidden_courses(
|
|
$coursedata,
|
|
$classification,
|
|
$limit,
|
|
$offset,
|
|
$expectedcourses,
|
|
$expectedprocessedcount,
|
|
$hiddencourse
|
|
) {
|
|
$this->resetAfterTest();
|
|
$generator = $this->getDataGenerator();
|
|
$student = $generator->create_user();
|
|
$this->setUser($student);
|
|
|
|
$courses = array_map(function($coursedata) use ($generator, $hiddencourse) {
|
|
$course = $generator->create_course($coursedata);
|
|
if ($course->shortname == $hiddencourse) {
|
|
set_user_preference('block_myoverview_hidden_course_' . $course->id, true);
|
|
}
|
|
return $course;
|
|
}, $coursedata);
|
|
|
|
foreach ($courses as $course) {
|
|
$generator->enrol_user($student->id, $course->id, 'student');
|
|
}
|
|
|
|
$coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
|
|
list($result, $processedcount) = course_filter_courses_by_timeline_classification(
|
|
$coursesgenerator,
|
|
$classification,
|
|
$limit
|
|
);
|
|
|
|
$actual = array_map(function($course) {
|
|
return $course->shortname;
|
|
}, $result);
|
|
|
|
$this->assertEquals($expectedcourses, $actual);
|
|
$this->assertEquals($expectedprocessedcount, $processedcount);
|
|
}
|
|
|
|
|
|
/**
|
|
* Testing core_course_core_calendar_get_valid_event_timestart_range when the course has no end date.
|
|
*/
|
|
public function test_core_course_core_calendar_get_valid_event_timestart_range_no_enddate() {
|
|
global $CFG;
|
|
require_once($CFG->dirroot . "/calendar/lib.php");
|
|
|
|
$this->resetAfterTest(true);
|
|
$this->setAdminUser();
|
|
$generator = $this->getDataGenerator();
|
|
$now = time();
|
|
$course = $generator->create_course(['startdate' => $now - 86400]);
|
|
|
|
// Create a course event.
|
|
$event = new \calendar_event([
|
|
'name' => 'Test course event',
|
|
'eventtype' => 'course',
|
|
'courseid' => $course->id,
|
|
]);
|
|
|
|
list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course);
|
|
$this->assertEquals($course->startdate, $min[0]);
|
|
$this->assertNull($max);
|
|
}
|
|
|
|
/**
|
|
* Testing core_course_core_calendar_get_valid_event_timestart_range when the course has end date.
|
|
*/
|
|
public function test_core_course_core_calendar_get_valid_event_timestart_range_with_enddate() {
|
|
global $CFG;
|
|
require_once($CFG->dirroot . "/calendar/lib.php");
|
|
|
|
$this->resetAfterTest(true);
|
|
$this->setAdminUser();
|
|
$generator = $this->getDataGenerator();
|
|
$now = time();
|
|
$course = $generator->create_course(['startdate' => $now - 86400, 'enddate' => $now + 86400]);
|
|
|
|
// Create a course event.
|
|
$event = new \calendar_event([
|
|
'name' => 'Test course event',
|
|
'eventtype' => 'course',
|
|
'courseid' => $course->id,
|
|
]);
|
|
|
|
list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course);
|
|
$this->assertEquals($course->startdate, $min[0]);
|
|
$this->assertNull($max);
|
|
}
|
|
|
|
/**
|
|
* Test the course_get_recent_courses function.
|
|
*/
|
|
public function test_course_get_recent_courses() {
|
|
global $DB;
|
|
|
|
$this->resetAfterTest();
|
|
$generator = $this->getDataGenerator();
|
|
|
|
$courses = array();
|
|
for ($i = 1; $i < 4; $i++) {
|
|
$courses[] = $generator->create_course();
|
|
};
|
|
|
|
$student = $generator->create_user();
|
|
|
|
foreach ($courses as $course) {
|
|
$generator->enrol_user($student->id, $course->id, 'student');
|
|
}
|
|
|
|
$this->setUser($student);
|
|
|
|
$result = course_get_recent_courses($student->id);
|
|
|
|
// No course accessed.
|
|
$this->assertCount(0, $result);
|
|
|
|
$time = time();
|
|
foreach ($courses as $course) {
|
|
$context = context_course::instance($course->id);
|
|
course_view($context);
|
|
$DB->set_field('user_lastaccess', 'timeaccess', $time, [
|
|
'userid' => $student->id,
|
|
'courseid' => $course->id,
|
|
]);
|
|
$time++;
|
|
}
|
|
|
|
// Every course accessed.
|
|
$result = course_get_recent_courses($student->id);
|
|
$this->assertCount(3, $result);
|
|
|
|
// Every course accessed, result limited to 2 courses.
|
|
$result = course_get_recent_courses($student->id, 2);
|
|
$this->assertCount(2, $result);
|
|
|
|
// Every course accessed, with limit and offset should return the first course.
|
|
$result = course_get_recent_courses($student->id, 3, 2);
|
|
$this->assertCount(1, $result);
|
|
$this->assertArrayHasKey($courses[0]->id, $result);
|
|
|
|
// Every course accessed, order by shortname DESC. The last create course ($course[2]) should have the greater shortname.
|
|
$result = course_get_recent_courses($student->id, 0, 0, 'shortname DESC');
|
|
$this->assertCount(3, $result);
|
|
$this->assertEquals($courses[2]->id, array_values($result)[0]->id);
|
|
$this->assertEquals($courses[1]->id, array_values($result)[1]->id);
|
|
$this->assertEquals($courses[0]->id, array_values($result)[2]->id);
|
|
|
|
// Every course accessed, order by shortname ASC.
|
|
$result = course_get_recent_courses($student->id, 0, 0, 'shortname ASC');
|
|
$this->assertCount(3, $result);
|
|
$this->assertEquals($courses[0]->id, array_values($result)[0]->id);
|
|
$this->assertEquals($courses[1]->id, array_values($result)[1]->id);
|
|
$this->assertEquals($courses[2]->id, array_values($result)[2]->id);
|
|
|
|
$guestcourse = $generator->create_course(
|
|
(object)array('shortname' => 'guestcourse',
|
|
'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
|
|
'enrol_guest_password_0' => ''));
|
|
$context = context_course::instance($guestcourse->id);
|
|
course_view($context);
|
|
|
|
// Every course accessed, even the not enrolled one.
|
|
$result = course_get_recent_courses($student->id);
|
|
$this->assertCount(4, $result);
|
|
|
|
// Suspended student.
|
|
$this->getDataGenerator()->enrol_user($student->id, $courses[0]->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
|
|
|
|
// The course with suspended enrolment is not returned by the function.
|
|
$result = course_get_recent_courses($student->id);
|
|
$this->assertCount(3, $result);
|
|
$this->assertArrayNotHasKey($courses[0]->id, $result);
|
|
}
|
|
|
|
/**
|
|
* Test the validation of the sort value in course_get_recent_courses().
|
|
*
|
|
* @dataProvider course_get_recent_courses_sort_validation_provider
|
|
* @param string $sort The sort value
|
|
* @param string $expectedexceptionmsg The expected exception message
|
|
*/
|
|
public function test_course_get_recent_courses_sort_validation(string $sort, string $expectedexceptionmsg) {
|
|
$this->resetAfterTest();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
|
|
if (!empty($expectedexceptionmsg)) {
|
|
$this->expectException('invalid_parameter_exception');
|
|
$this->expectExceptionMessage($expectedexceptionmsg);
|
|
}
|
|
course_get_recent_courses($user->id, 0, 0, $sort);
|
|
}
|
|
|
|
/**
|
|
* Data provider for test_course_get_recent_courses_sort_validation().
|
|
*
|
|
* @return array
|
|
*/
|
|
function course_get_recent_courses_sort_validation_provider() {
|
|
return [
|
|
'Invalid sort format (SQL injection attempt)' =>
|
|
[
|
|
'shortname DESC LIMIT 1--',
|
|
'Invalid structure of the sort parameter, allowed structure: fieldname [ASC|DESC].',
|
|
],
|
|
'Sort uses \'sort by\' field that does not exist' =>
|
|
[
|
|
'shortname DESC, xyz ASC',
|
|
'Invalid field in the sort parameter, allowed fields: id, idnumber, summary, summaryformat, ' .
|
|
'startdate, enddate, category, shortname, fullname, timeaccess, component, visible, ' .
|
|
'showactivitydates, showcompletionconditions.',
|
|
],
|
|
'Sort uses invalid value for the sorting direction' =>
|
|
[
|
|
'shortname xyz, lastaccess',
|
|
'Invalid sort direction in the sort parameter, allowed values: asc, desc.',
|
|
],
|
|
'Valid sort format' =>
|
|
[
|
|
'shortname asc, timeaccess',
|
|
''
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test the course_get_recent_courses function.
|
|
*/
|
|
public function test_course_get_recent_courses_with_guest() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$student = $this->getDataGenerator()->create_user();
|
|
|
|
// Course 1 with guest access and no direct enrolment.
|
|
$course1 = $this->getDataGenerator()->create_course();
|
|
$context1 = context_course::instance($course1->id);
|
|
$record = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'guest']);
|
|
enrol_get_plugin('guest')->update_status($record, ENROL_INSTANCE_ENABLED);
|
|
|
|
// Course 2 where student is enrolled with two enrolment methods.
|
|
$course2 = $this->getDataGenerator()->create_course();
|
|
$context2 = context_course::instance($course2->id);
|
|
$record = $DB->get_record('enrol', ['courseid' => $course2->id, 'enrol' => 'self']);
|
|
enrol_get_plugin('guest')->update_status($record, ENROL_INSTANCE_ENABLED);
|
|
$this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student', 'manual', 0, 0, ENROL_USER_ACTIVE);
|
|
$this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student', 'self', 0, 0, ENROL_USER_ACTIVE);
|
|
|
|
// Course 3.
|
|
$course3 = $this->getDataGenerator()->create_course();
|
|
$context3 = context_course::instance($course3->id);
|
|
|
|
// Student visits first two courses, course_get_recent_courses returns two courses.
|
|
$this->setUser($student);
|
|
course_view($context1);
|
|
course_view($context2);
|
|
|
|
$result = course_get_recent_courses($student->id);
|
|
$this->assertEqualsCanonicalizing([$course2->id, $course1->id], array_column($result, 'id'));
|
|
|
|
// Admin visits all three courses. Only the one with guest access is returned.
|
|
$this->setAdminUser();
|
|
course_view($context1);
|
|
course_view($context2);
|
|
course_view($context3);
|
|
$result = course_get_recent_courses(get_admin()->id);
|
|
$this->assertEqualsCanonicalizing([$course1->id], array_column($result, 'id'));
|
|
}
|
|
|
|
/**
|
|
* Test cases for the course_get_course_dates_for_user_ids tests.
|
|
*/
|
|
public function get_course_get_course_dates_for_user_ids_test_cases() {
|
|
$now = time();
|
|
$pastcoursestart = $now - 100;
|
|
$futurecoursestart = $now + 100;
|
|
|
|
return [
|
|
'future course start fixed no users enrolled' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [[], []],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 1 users enrolled future' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
[]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 1 users enrolled past' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
[]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 2 users enrolled future' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 2 users enrolled past' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 2 users enrolled mixed' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 2 users enrolled 2 methods' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 2 users enrolled 2 methods 1 disabled' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_DISABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 2 users enrolled 2 methods 2 disabled' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_DISABLED],
|
|
['self', ENROL_INSTANCE_DISABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start relative no users enrolled' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [[], []],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 1 users enrolled future' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
[]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart + 10,
|
|
'startoffset' => 10
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 1 users enrolled past' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
[]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 2 users enrolled future' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart + 10,
|
|
'startoffset' => 10
|
|
],
|
|
[
|
|
'start' => $futurecoursestart + 20,
|
|
'startoffset' => 20
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 2 users enrolled past' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 2 users enrolled mixed' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart + 10,
|
|
'startoffset' => 10
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 2 users enrolled 2 methods' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart + 10,
|
|
'startoffset' => 10
|
|
],
|
|
[
|
|
'start' => $futurecoursestart + 10,
|
|
'startoffset' => 10
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 2 users enrolled 2 methods 1 disabled' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_DISABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart + 20,
|
|
'startoffset' => 20
|
|
],
|
|
[
|
|
'start' => $futurecoursestart + 10,
|
|
'startoffset' => 10
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 2 users enrolled 2 methods 2 disabled' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_DISABLED],
|
|
['self', ENROL_INSTANCE_DISABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart + 20,
|
|
'startoffset' => 20
|
|
],
|
|
[
|
|
'start' => $futurecoursestart + 10,
|
|
'startoffset' => 10
|
|
]
|
|
]
|
|
],
|
|
'future course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $futurecoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
|
|
'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
|
|
'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $futurecoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
|
|
// Course start date in the past.
|
|
'past course start fixed no users enrolled' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [[], []],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 1 users enrolled future' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
[]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 1 users enrolled past' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
[]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 2 users enrolled future' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 2 users enrolled past' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 2 users enrolled mixed' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 2 users enrolled 2 methods' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 2 users enrolled 2 methods 1 disabled' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_DISABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 2 users enrolled 2 methods 2 disabled' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_DISABLED],
|
|
['self', ENROL_INSTANCE_DISABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
|
|
'relativedatemode' => false,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start relative no users enrolled' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [[], []],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 1 users enrolled future' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
[]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart + 10,
|
|
'startoffset' => 10
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 1 users enrolled past' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
[]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 2 users enrolled future' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart + 10,
|
|
'startoffset' => 10
|
|
],
|
|
[
|
|
'start' => $pastcoursestart + 20,
|
|
'startoffset' => 20
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 2 users enrolled past' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 2 users enrolled mixed' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
|
|
// User 2.
|
|
['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart + 10,
|
|
'startoffset' => 10
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 2 users enrolled 2 methods' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart + 10,
|
|
'startoffset' => 10
|
|
],
|
|
[
|
|
'start' => $pastcoursestart + 10,
|
|
'startoffset' => 10
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 2 users enrolled 2 methods 1 disabled' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_DISABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart + 20,
|
|
'startoffset' => 20
|
|
],
|
|
[
|
|
'start' => $pastcoursestart + 10,
|
|
'startoffset' => 10
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 2 users enrolled 2 methods 2 disabled' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_DISABLED],
|
|
['self', ENROL_INSTANCE_DISABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart + 20,
|
|
'startoffset' => 20
|
|
],
|
|
[
|
|
'start' => $pastcoursestart + 10,
|
|
'startoffset' => 10
|
|
]
|
|
]
|
|
],
|
|
'past course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
|
|
'relativedatemode' => true,
|
|
'coursestart' => $pastcoursestart,
|
|
'usercount' => 2,
|
|
'enrolmentmethods' => [
|
|
['manual', ENROL_INSTANCE_ENABLED],
|
|
['self', ENROL_INSTANCE_ENABLED]
|
|
],
|
|
'enrolled' => [
|
|
// User 1.
|
|
[
|
|
'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
|
|
'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED]
|
|
],
|
|
// User 2.
|
|
[
|
|
'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
|
|
'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED]
|
|
]
|
|
],
|
|
'expected' => [
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
],
|
|
[
|
|
'start' => $pastcoursestart,
|
|
'startoffset' => 0
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Test the course_get_course_dates_for_user_ids function.
|
|
*
|
|
* @dataProvider get_course_get_course_dates_for_user_ids_test_cases()
|
|
* @param bool $relativedatemode Set the course to relative dates mode
|
|
* @param int $coursestart Course start date
|
|
* @param int $usercount Number of users to create
|
|
* @param array $enrolmentmethods Enrolment methods to set for the course
|
|
* @param array $enrolled Enrolment config for to set for the users
|
|
* @param array $expected Expected output
|
|
*/
|
|
public function test_course_get_course_dates_for_user_ids(
|
|
$relativedatemode,
|
|
$coursestart,
|
|
$usercount,
|
|
$enrolmentmethods,
|
|
$enrolled,
|
|
$expected
|
|
) {
|
|
global $DB;
|
|
$this->resetAfterTest();
|
|
|
|
$generator = $this->getDataGenerator();
|
|
$course = $generator->create_course(['startdate' => $coursestart]);
|
|
$course->relativedatesmode = $relativedatemode;
|
|
$users = [];
|
|
|
|
for ($i = 0; $i < $usercount; $i++) {
|
|
$users[] = $generator->create_user();
|
|
}
|
|
|
|
foreach ($enrolmentmethods as [$type, $status]) {
|
|
$record = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => $type]);
|
|
$plugin = enrol_get_plugin($type);
|
|
if ($record->status != $status) {
|
|
$plugin->update_status($record, $status);
|
|
}
|
|
}
|
|
|
|
foreach ($enrolled as $index => $enrolconfig) {
|
|
$user = $users[$index];
|
|
foreach ($enrolconfig as $type => [$starttime, $status]) {
|
|
$generator->enrol_user($user->id, $course->id, 'student', $type, $starttime, 0, $status);
|
|
}
|
|
}
|
|
|
|
$userids = array_map(function($user) {
|
|
return $user->id;
|
|
}, $users);
|
|
$actual = course_get_course_dates_for_user_ids($course, $userids);
|
|
|
|
foreach ($expected as $index => $exp) {
|
|
$userid = $userids[$index];
|
|
$act = $actual[$userid];
|
|
|
|
$this->assertEquals($exp['start'], $act['start']);
|
|
$this->assertEquals($exp['startoffset'], $act['startoffset']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test that calling course_get_course_dates_for_user_ids multiple times in the
|
|
* same request fill fetch the correct data for the user.
|
|
*/
|
|
public function test_course_get_course_dates_for_user_ids_multiple_calls() {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = $this->getDataGenerator();
|
|
$now = time();
|
|
$coursestart = $now - 1000;
|
|
$course = $generator->create_course(['startdate' => $coursestart]);
|
|
$course->relativedatesmode = true;
|
|
$user1 = $generator->create_user();
|
|
$user2 = $generator->create_user();
|
|
$user1start = $coursestart + 100;
|
|
$user2start = $coursestart + 200;
|
|
|
|
$generator->enrol_user($user1->id, $course->id, 'student', 'manual', $user1start);
|
|
$generator->enrol_user($user2->id, $course->id, 'student', 'manual', $user2start);
|
|
|
|
$result = course_get_course_dates_for_user_ids($course, [$user1->id]);
|
|
$this->assertEquals($user1start, $result[$user1->id]['start']);
|
|
|
|
$result = course_get_course_dates_for_user_ids($course, [$user1->id, $user2->id]);
|
|
$this->assertEquals($user1start, $result[$user1->id]['start']);
|
|
$this->assertEquals($user2start, $result[$user2->id]['start']);
|
|
|
|
$result = course_get_course_dates_for_user_ids($course, [$user2->id]);
|
|
$this->assertEquals($user2start, $result[$user2->id]['start']);
|
|
}
|
|
|
|
/**
|
|
* Data provider for test_course_modules_pending_deletion.
|
|
*
|
|
* @return array An array of arrays contain test data
|
|
*/
|
|
public function provider_course_modules_pending_deletion() {
|
|
return [
|
|
'Non-gradable activity, check all' => [['forum'], 0, false, true],
|
|
'Gradable activity, check all' => [['assign'], 0, false, true],
|
|
'Non-gradable activity, check gradables' => [['forum'], 0, true, false],
|
|
'Gradable activity, check gradables' => [['assign'], 0, true, true],
|
|
'Non-gradable within multiple, check all' => [['quiz', 'forum', 'assign'], 1, false, true],
|
|
'Non-gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 1, true, false],
|
|
'Gradable within multiple, check all' => [['quiz', 'forum', 'assign'], 2, false, true],
|
|
'Gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 2, true, true],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Tests the function course_modules_pending_deletion.
|
|
*
|
|
* @param string[] $modules A complete list aff all available modules before deletion
|
|
* @param int $indextodelete The index of the module in the $modules array that we want to test with
|
|
* @param bool $gradable The value to pass to the gradable argument of the course_modules_pending_deletion function
|
|
* @param bool $expected The expected result
|
|
* @dataProvider provider_course_modules_pending_deletion
|
|
*/
|
|
public function test_course_modules_pending_deletion(array $modules, int $indextodelete, bool $gradable, bool $expected) {
|
|
$this->resetAfterTest();
|
|
|
|
// Ensure recyclebin is enabled.
|
|
set_config('coursebinenable', true, 'tool_recyclebin');
|
|
|
|
// Create course and modules.
|
|
$generator = $this->getDataGenerator();
|
|
$course = $generator->create_course();
|
|
|
|
$moduleinstances = [];
|
|
foreach ($modules as $module) {
|
|
$moduleinstances[] = $generator->create_module($module, array('course' => $course->id));
|
|
}
|
|
|
|
course_delete_module($moduleinstances[$indextodelete]->cmid, true); // Try to delete the instance asynchronously.
|
|
$this->assertEquals($expected, course_modules_pending_deletion($course->id, $gradable));
|
|
}
|
|
|
|
/**
|
|
* Tests for the course_request::can_request
|
|
*/
|
|
public function test_can_request_course() {
|
|
global $CFG, $DB;
|
|
$this->resetAfterTest();
|
|
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$cat1 = $CFG->defaultrequestcategory;
|
|
$cat2 = $this->getDataGenerator()->create_category()->id;
|
|
$cat3 = $this->getDataGenerator()->create_category()->id;
|
|
$context1 = context_coursecat::instance($cat1);
|
|
$context2 = context_coursecat::instance($cat2);
|
|
$context3 = context_coursecat::instance($cat3);
|
|
$this->setUser($user);
|
|
|
|
// By default users don't have capability to request courses.
|
|
$this->assertFalse(course_request::can_request(context_system::instance()));
|
|
$this->assertFalse(course_request::can_request($context1));
|
|
$this->assertFalse(course_request::can_request($context2));
|
|
$this->assertFalse(course_request::can_request($context3));
|
|
|
|
// Allow for the 'user' role the capability to request courses.
|
|
$userroleid = $DB->get_field('role', 'id', ['shortname' => 'user']);
|
|
assign_capability('moodle/course:request', CAP_ALLOW, $userroleid,
|
|
context_system::instance()->id);
|
|
accesslib_clear_all_caches_for_unit_testing();
|
|
|
|
// Lock category selection.
|
|
$CFG->lockrequestcategory = 1;
|
|
|
|
// Now user can only request course in the default category or in system context.
|
|
$this->assertTrue(course_request::can_request(context_system::instance()));
|
|
$this->assertTrue(course_request::can_request($context1));
|
|
$this->assertFalse(course_request::can_request($context2));
|
|
$this->assertFalse(course_request::can_request($context3));
|
|
|
|
// Enable category selection. User can request course anywhere.
|
|
$CFG->lockrequestcategory = 0;
|
|
$this->assertTrue(course_request::can_request(context_system::instance()));
|
|
$this->assertTrue(course_request::can_request($context1));
|
|
$this->assertTrue(course_request::can_request($context2));
|
|
$this->assertTrue(course_request::can_request($context3));
|
|
|
|
// Remove cap from cat2.
|
|
$roleid = create_role('Test role', 'testrole', 'Test role description');
|
|
assign_capability('moodle/course:request', CAP_PROHIBIT, $roleid,
|
|
$context2->id, true);
|
|
role_assign($roleid, $user->id, $context2->id);
|
|
accesslib_clear_all_caches_for_unit_testing();
|
|
|
|
$this->assertTrue(course_request::can_request(context_system::instance()));
|
|
$this->assertTrue(course_request::can_request($context1));
|
|
$this->assertFalse(course_request::can_request($context2));
|
|
$this->assertTrue(course_request::can_request($context3));
|
|
|
|
// Disable course request functionality.
|
|
$CFG->enablecourserequests = false;
|
|
$this->assertFalse(course_request::can_request(context_system::instance()));
|
|
$this->assertFalse(course_request::can_request($context1));
|
|
$this->assertFalse(course_request::can_request($context2));
|
|
$this->assertFalse(course_request::can_request($context3));
|
|
}
|
|
|
|
/**
|
|
* Tests for the course_request::can_approve
|
|
*/
|
|
public function test_can_approve_course_request() {
|
|
global $CFG;
|
|
$this->resetAfterTest();
|
|
|
|
$requestor = $this->getDataGenerator()->create_user();
|
|
$user = $this->getDataGenerator()->create_user();
|
|
$cat1 = $CFG->defaultrequestcategory;
|
|
$cat2 = $this->getDataGenerator()->create_category()->id;
|
|
$cat3 = $this->getDataGenerator()->create_category()->id;
|
|
|
|
// Enable course requests. Default 'user' role has capability to request courses.
|
|
$CFG->enablecourserequests = true;
|
|
$CFG->lockrequestcategory = 0;
|
|
$this->setUser($requestor);
|
|
$requestdata = ['summary_editor' => ['text' => '', 'format' => 0], 'name' => 'Req', 'reason' => 'test'];
|
|
$request1 = course_request::create((object)($requestdata));
|
|
$request2 = course_request::create((object)($requestdata + ['category' => $cat2]));
|
|
$request3 = course_request::create((object)($requestdata + ['category' => $cat3]));
|
|
|
|
$this->setUser($user);
|
|
// Add capability to approve courses.
|
|
$roleid = create_role('Test role', 'testrole', 'Test role description');
|
|
assign_capability('moodle/site:approvecourse', CAP_ALLOW, $roleid,
|
|
context_system::instance()->id, true);
|
|
role_assign($roleid, $user->id, context_coursecat::instance($cat2)->id);
|
|
accesslib_clear_all_caches_for_unit_testing();
|
|
|
|
$this->assertFalse($request1->can_approve());
|
|
$this->assertTrue($request2->can_approve());
|
|
$this->assertFalse($request3->can_approve());
|
|
|
|
// Delete category where course was requested. Now only site-wide manager can approve it.
|
|
core_course_category::get($cat2, MUST_EXIST, true)->delete_full(false);
|
|
$this->assertFalse($request2->can_approve());
|
|
|
|
$this->setAdminUser();
|
|
$this->assertTrue($request2->can_approve());
|
|
}
|
|
|
|
/**
|
|
* Test the course allowed module method.
|
|
*/
|
|
public function test_course_allowed_module() {
|
|
$this->resetAfterTest();
|
|
global $DB;
|
|
|
|
$course = $this->getDataGenerator()->create_course();
|
|
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
|
|
$manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
|
|
|
|
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
|
|
assign_capability('mod/assign:addinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
|
|
|
|
// Global user (teacher) has no permissions in this course.
|
|
$this->setUser($teacher);
|
|
$this->assertFalse(course_allowed_module($course, 'assign'));
|
|
|
|
// Manager has permissions.
|
|
$this->assertTrue(course_allowed_module($course, 'assign', $manager));
|
|
}
|
|
|
|
/**
|
|
* Test the {@link average_number_of_participants()} function.
|
|
*/
|
|
public function test_average_number_of_participants() {
|
|
global $DB;
|
|
$this->resetAfterTest(true);
|
|
|
|
$generator = $this->getDataGenerator();
|
|
$now = time();
|
|
|
|
// If there are no courses, expect zero number of participants per course.
|
|
$this->assertEquals(0, average_number_of_participants());
|
|
|
|
$c1 = $generator->create_course();
|
|
$c2 = $generator->create_course();
|
|
|
|
// If there are no users, expect zero number of participants per course.
|
|
$this->assertEquals(0, average_number_of_participants());
|
|
|
|
$t1 = $generator->create_user(['lastlogin' => $now]);
|
|
$s1 = $generator->create_user(['lastlogin' => $now]);
|
|
$s2 = $generator->create_user(['lastlogin' => $now - WEEKSECS]);
|
|
$s3 = $generator->create_user(['lastlogin' => $now - WEEKSECS]);
|
|
$s4 = $generator->create_user(['lastlogin' => $now - YEARSECS]);
|
|
|
|
// We have courses, we have users, but no enrolments yet.
|
|
$this->assertEquals(0, average_number_of_participants());
|
|
|
|
// Front page enrolments are ignored.
|
|
$generator->enrol_user($t1->id, SITEID, 'teacher');
|
|
$this->assertEquals(0, average_number_of_participants());
|
|
|
|
// The teacher enrolled into one of the two courses.
|
|
$generator->enrol_user($t1->id, $c1->id, 'editingteacher');
|
|
$this->assertEquals(0.5, average_number_of_participants());
|
|
|
|
// The teacher enrolled into both courses.
|
|
$generator->enrol_user($t1->id, $c2->id, 'editingteacher');
|
|
$this->assertEquals(1, average_number_of_participants());
|
|
|
|
// Student 1 enrolled in the Course 1 only.
|
|
$generator->enrol_user($s1->id, $c1->id, 'student');
|
|
$this->assertEquals(1.5, average_number_of_participants());
|
|
|
|
// Student 2 enrolled in both courses, but the enrolment in the Course 2 not active yet (enrolment starts in the future).
|
|
$generator->enrol_user($s2->id, $c1->id, 'student');
|
|
$generator->enrol_user($s2->id, $c2->id, 'student', 'manual', $now + WEEKSECS);
|
|
$this->assertEquals(2.5, average_number_of_participants());
|
|
$this->assertEquals(2, average_number_of_participants(true));
|
|
|
|
// Student 3 enrolled in the Course 1, but the enrolment already expired.
|
|
$generator->enrol_user($s3->id, $c1->id, 'student', 'manual', 0, $now - YEARSECS);
|
|
$this->assertEquals(3, average_number_of_participants());
|
|
$this->assertEquals(2, average_number_of_participants(true));
|
|
|
|
// Student 4 enrolled in both courses, but the enrolment has been suspended.
|
|
$generator->enrol_user($s4->id, $c1->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
|
|
$generator->enrol_user($s4->id, $c2->id, 'student', 'manual', $now - DAYSECS, $now + YEARSECS, ENROL_USER_SUSPENDED);
|
|
$this->assertEquals(4, average_number_of_participants());
|
|
$this->assertEquals(2, average_number_of_participants(true));
|
|
|
|
// Consider only t1 and s1 who logged in recently.
|
|
$this->assertEquals(1.5, average_number_of_participants(false, $now - DAYSECS));
|
|
|
|
// Consider only t1, s1, s2 and s3 who logged in in recent weeks.
|
|
$this->assertEquals(3, average_number_of_participants(false, $now - 4 * WEEKSECS));
|
|
|
|
// Hidden courses are excluded from stats.
|
|
$DB->set_field('course', 'visible', 0, ['id' => $c1->id]);
|
|
$this->assertEquals(3, average_number_of_participants());
|
|
$this->assertEquals(1, average_number_of_participants(true));
|
|
}
|
|
|
|
/**
|
|
* Test the set_downloadcontent() function.
|
|
*/
|
|
public function test_set_downloadcontent() {
|
|
$this->resetAfterTest();
|
|
|
|
$generator = $this->getDataGenerator();
|
|
$course = $generator->create_course();
|
|
$page = $generator->create_module('page', ['course' => $course]);
|
|
|
|
// Test the module 'downloadcontent' field is set to enabled.
|
|
set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_ENABLED);
|
|
$modinfo = get_fast_modinfo($course)->get_cm($page->cmid);
|
|
$this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $modinfo->downloadcontent);
|
|
|
|
// Now let's test the 'downloadcontent' value is updated to disabled.
|
|
set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_DISABLED);
|
|
$modinfo = get_fast_modinfo($course)->get_cm($page->cmid);
|
|
$this->assertEquals(DOWNLOAD_COURSE_CONTENT_DISABLED, $modinfo->downloadcontent);
|
|
|
|
// Nothing to update, the download course content value is the same, it should return false.
|
|
$this->assertFalse(set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_DISABLED));
|
|
|
|
// The download course content value has changed, it should return true in this case.
|
|
$this->assertTrue(set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_ENABLED));
|
|
}
|
|
}
|