MDL-55107 gradebook: Unit tests for the new setting check.
[moodle.git] / lib / tests / upgradelib_test.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * Unit tests for the lib/upgradelib.php library.
19  *
20  * @package   core
21  * @category  phpunit
22  * @copyright 2013 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
23  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir.'/upgradelib.php');
31 /**
32  * Tests various classes and functions in upgradelib.php library.
33  */
34 class core_upgradelib_testcase extends advanced_testcase {
36     /**
37      * Test the {@link upgrade_stale_php_files_present() function
38      */
39     public function test_upgrade_stale_php_files_present() {
40         // Just call the function, must return bool false always
41         // if there aren't any old files in the codebase.
42         $this->assertFalse(upgrade_stale_php_files_present());
43     }
45     /**
46      * Populate some fake grade items into the database with specified
47      * sortorder and course id.
48      *
49      * NOTE: This function doesn't make much attempt to respect the
50      * gradebook internals, its simply used to fake some data for
51      * testing the upgradelib function. Please don't use it for other
52      * purposes.
53      *
54      * @param int $courseid id of course
55      * @param int $sortorder numeric sorting order of item
56      * @return stdClass grade item object from the database.
57      */
58     private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
59         global $DB, $CFG;
60         require_once($CFG->libdir.'/gradelib.php');
62         $item = new stdClass();
63         $item->courseid = $courseid;
64         $item->sortorder = $sortorder;
65         $item->gradetype = GRADE_TYPE_VALUE;
66         $item->grademin = 30;
67         $item->grademax = 110;
68         $item->itemnumber = 1;
69         $item->iteminfo = '';
70         $item->timecreated = time();
71         $item->timemodified = time();
73         $item->id = $DB->insert_record('grade_items', $item);
75         return $DB->get_record('grade_items', array('id' => $item->id));
76     }
78     public function test_upgrade_fix_missing_root_folders_draft() {
79         global $DB, $SITE;
81         $this->resetAfterTest(true);
83         $user = $this->getDataGenerator()->create_user();
84         $usercontext = context_user::instance($user->id);
85         $this->setUser($user);
86         $resource1 = $this->getDataGenerator()->get_plugin_generator('mod_resource')
87             ->create_instance(array('course' => $SITE->id));
88         $context = context_module::instance($resource1->cmid);
89         $draftitemid = 0;
90         file_prepare_draft_area($draftitemid, $context->id, 'mod_resource', 'content', 0);
92         $queryparams = array(
93             'component' => 'user',
94             'contextid' => $usercontext->id,
95             'filearea' => 'draft',
96             'itemid' => $draftitemid,
97         );
99         // Make sure there are two records in files for the draft file area and one of them has filename '.'.
100         $records = $DB->get_records_menu('files', $queryparams, '', 'id, filename');
101         $this->assertEquals(2, count($records));
102         $this->assertTrue(in_array('.', $records));
103         $originalhash = $DB->get_field('files', 'pathnamehash', $queryparams + array('filename' => '.'));
105         // Delete record with filename '.' and make sure it does not exist any more.
106         $DB->delete_records('files', $queryparams + array('filename' => '.'));
108         $records = $DB->get_records_menu('files', $queryparams, '', 'id, filename');
109         $this->assertEquals(1, count($records));
110         $this->assertFalse(in_array('.', $records));
112         // Run upgrade script and make sure the record is restored.
113         upgrade_fix_missing_root_folders_draft();
115         $records = $DB->get_records_menu('files', $queryparams, '', 'id, filename');
116         $this->assertEquals(2, count($records));
117         $this->assertTrue(in_array('.', $records));
118         $newhash = $DB->get_field('files', 'pathnamehash', $queryparams + array('filename' => '.'));
119         $this->assertEquals($originalhash, $newhash);
120     }
122     /**
123      * Test upgrade minmaxgrade step.
124      */
125     public function test_upgrade_minmaxgrade() {
126         global $CFG, $DB;
127         require_once($CFG->libdir . '/gradelib.php');
128         $initialminmax = $CFG->grade_minmaxtouse;
129         $this->resetAfterTest();
131         $c1 = $this->getDataGenerator()->create_course();
132         $c2 = $this->getDataGenerator()->create_course();
133         $c3 = $this->getDataGenerator()->create_course();
134         $u1 = $this->getDataGenerator()->create_user();
135         $a1 = $this->getDataGenerator()->create_module('assign', array('course' => $c1, 'grade' => 100));
136         $a2 = $this->getDataGenerator()->create_module('assign', array('course' => $c2, 'grade' => 100));
137         $a3 = $this->getDataGenerator()->create_module('assign', array('course' => $c3, 'grade' => 100));
139         $cm1 = get_coursemodule_from_instance('assign', $a1->id);
140         $ctx1 = context_module::instance($cm1->id);
141         $assign1 = new assign($ctx1, $cm1, $c1);
143         $cm2 = get_coursemodule_from_instance('assign', $a2->id);
144         $ctx2 = context_module::instance($cm2->id);
145         $assign2 = new assign($ctx2, $cm2, $c2);
147         $cm3 = get_coursemodule_from_instance('assign', $a3->id);
148         $ctx3 = context_module::instance($cm3->id);
149         $assign3 = new assign($ctx3, $cm3, $c3);
151         // Give a grade to the student.
152         $ug = $assign1->get_user_grade($u1->id, true);
153         $ug->grade = 10;
154         $assign1->update_grade($ug);
156         $ug = $assign2->get_user_grade($u1->id, true);
157         $ug->grade = 20;
158         $assign2->update_grade($ug);
160         $ug = $assign3->get_user_grade($u1->id, true);
161         $ug->grade = 30;
162         $assign3->update_grade($ug);
165         // Run the upgrade.
166         upgrade_minmaxgrade();
168         // Nothing has happened.
169         $this->assertFalse($DB->record_exists('config', array('name' => 'show_min_max_grades_changed_' . $c1->id)));
170         $this->assertSame(false, grade_get_setting($c1->id, 'minmaxtouse', false, true));
171         $this->assertFalse($DB->record_exists('grade_items', array('needsupdate' => 1, 'courseid' => $c1->id)));
172         $this->assertFalse($DB->record_exists('config', array('name' => 'show_min_max_grades_changed_' . $c2->id)));
173         $this->assertSame(false, grade_get_setting($c2->id, 'minmaxtouse', false, true));
174         $this->assertFalse($DB->record_exists('grade_items', array('needsupdate' => 1, 'courseid' => $c2->id)));
175         $this->assertFalse($DB->record_exists('config', array('name' => 'show_min_max_grades_changed_' . $c3->id)));
176         $this->assertSame(false, grade_get_setting($c3->id, 'minmaxtouse', false, true));
177         $this->assertFalse($DB->record_exists('grade_items', array('needsupdate' => 1, 'courseid' => $c3->id)));
179         // Create inconsistency in c1 and c2.
180         $giparams = array('itemtype' => 'mod', 'itemmodule' => 'assign', 'iteminstance' => $a1->id,
181                 'courseid' => $c1->id, 'itemnumber' => 0);
182         $gi = grade_item::fetch($giparams);
183         $gi->grademin = 5;
184         $gi->update();
186         $giparams = array('itemtype' => 'mod', 'itemmodule' => 'assign', 'iteminstance' => $a2->id,
187                 'courseid' => $c2->id, 'itemnumber' => 0);
188         $gi = grade_item::fetch($giparams);
189         $gi->grademax = 50;
190         $gi->update();
193         // C1 and C2 should be updated, but the course setting should not be set.
194         $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_GRADE;
196         // Run the upgrade.
197         upgrade_minmaxgrade();
199         // C1 and C2 were partially updated.
200         $this->assertTrue($DB->record_exists('config', array('name' => 'show_min_max_grades_changed_' . $c1->id)));
201         $this->assertSame(false, grade_get_setting($c1->id, 'minmaxtouse', false, true));
202         $this->assertTrue($DB->record_exists('grade_items', array('needsupdate' => 1, 'courseid' => $c1->id)));
203         $this->assertTrue($DB->record_exists('config', array('name' => 'show_min_max_grades_changed_' . $c2->id)));
204         $this->assertSame(false, grade_get_setting($c2->id, 'minmaxtouse', false, true));
205         $this->assertTrue($DB->record_exists('grade_items', array('needsupdate' => 1, 'courseid' => $c2->id)));
207         // Nothing has happened for C3.
208         $this->assertFalse($DB->record_exists('config', array('name' => 'show_min_max_grades_changed_' . $c3->id)));
209         $this->assertSame(false, grade_get_setting($c3->id, 'minmaxtouse', false, true));
210         $this->assertFalse($DB->record_exists('grade_items', array('needsupdate' => 1, 'courseid' => $c3->id)));
213         // Course setting should not be set on a course that has the setting already.
214         $CFG->grade_minmaxtouse = GRADE_MIN_MAX_FROM_GRADE_ITEM;
215         grade_set_setting($c1->id, 'minmaxtouse', -1); // Sets different value than constant to check that it remained the same.
217         // Run the upgrade.
218         upgrade_minmaxgrade();
220         // C2 was updated.
221         $this->assertSame((string) GRADE_MIN_MAX_FROM_GRADE_GRADE, grade_get_setting($c2->id, 'minmaxtouse', false, true));
223         // Nothing has happened for C1.
224         $this->assertSame('-1', grade_get_setting($c1->id, 'minmaxtouse', false, true));
226         // Nothing has happened for C3.
227         $this->assertFalse($DB->record_exists('config', array('name' => 'show_min_max_grades_changed_' . $c3->id)));
228         $this->assertSame(false, grade_get_setting($c3->id, 'minmaxtouse', false, true));
229         $this->assertFalse($DB->record_exists('grade_items', array('needsupdate' => 1, 'courseid' => $c3->id)));
232         // Final check, this time we'll unset the default config.
233         unset($CFG->grade_minmaxtouse);
234         grade_set_setting($c1->id, 'minmaxtouse', null);
236         // Run the upgrade.
237         upgrade_minmaxgrade();
239         // C1 was updated.
240         $this->assertSame((string) GRADE_MIN_MAX_FROM_GRADE_GRADE, grade_get_setting($c1->id, 'minmaxtouse', false, true));
242         // Nothing has happened for C3.
243         $this->assertFalse($DB->record_exists('config', array('name' => 'show_min_max_grades_changed_' . $c3->id)));
244         $this->assertSame(false, grade_get_setting($c3->id, 'minmaxtouse', false, true));
245         $this->assertFalse($DB->record_exists('grade_items', array('needsupdate' => 1, 'courseid' => $c3->id)));
247         // Restore value.
248         $CFG->grade_minmaxtouse = $initialminmax;
249     }
251     public function test_upgrade_extra_credit_weightoverride() {
252         global $DB, $CFG;
254         $this->resetAfterTest(true);
256         require_once($CFG->libdir . '/db/upgradelib.php');
258         $c = array();
259         $a = array();
260         $gi = array();
261         for ($i=0; $i<5; $i++) {
262             $c[$i] = $this->getDataGenerator()->create_course();
263             $a[$i] = array();
264             $gi[$i] = array();
265             for ($j=0;$j<3;$j++) {
266                 $a[$i][$j] = $this->getDataGenerator()->create_module('assign', array('course' => $c[$i], 'grade' => 100));
267                 $giparams = array('itemtype' => 'mod', 'itemmodule' => 'assign', 'iteminstance' => $a[$i][$j]->id,
268                     'courseid' => $c[$i]->id, 'itemnumber' => 0);
269                 $gi[$i][$j] = grade_item::fetch($giparams);
270             }
271         }
273         // Case 1: Course $c[0] has aggregation method different from natural.
274         $coursecategory = grade_category::fetch_course_category($c[0]->id);
275         $coursecategory->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN;
276         $coursecategory->update();
277         $gi[0][1]->aggregationcoef = 1;
278         $gi[0][1]->update();
279         $gi[0][2]->weightoverride = 1;
280         $gi[0][2]->update();
282         // Case 2: Course $c[1] has neither extra credits nor overrides
284         // Case 3: Course $c[2] has extra credits but no overrides
285         $gi[2][1]->aggregationcoef = 1;
286         $gi[2][1]->update();
288         // Case 4: Course $c[3] has no extra credits and has overrides
289         $gi[3][2]->weightoverride = 1;
290         $gi[3][2]->update();
292         // Case 5: Course $c[4] has both extra credits and overrides
293         $gi[4][1]->aggregationcoef = 1;
294         $gi[4][1]->update();
295         $gi[4][2]->weightoverride = 1;
296         $gi[4][2]->update();
298         // Run the upgrade script and make sure only course $c[4] was marked as needed to be fixed.
299         upgrade_extra_credit_weightoverride();
301         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[0]->id}));
302         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[1]->id}));
303         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[2]->id}));
304         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[3]->id}));
305         $this->assertEquals(20150619, $CFG->{'gradebook_calculations_freeze_' . $c[4]->id});
307         set_config('gradebook_calculations_freeze_' . $c[4]->id, null);
309         // Run the upgrade script for a single course only.
310         upgrade_extra_credit_weightoverride($c[0]->id);
311         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[0]->id}));
312         upgrade_extra_credit_weightoverride($c[4]->id);
313         $this->assertEquals(20150619, $CFG->{'gradebook_calculations_freeze_' . $c[4]->id});
314     }
316     /**
317      * Test the upgrade function for flagging courses with calculated grade item problems.
318      */
319     public function test_upgrade_calculated_grade_items_freeze() {
320         global $DB, $CFG;
322         $this->resetAfterTest();
324         require_once($CFG->libdir . '/db/upgradelib.php');
326         // Create a user.
327         $user = $this->getDataGenerator()->create_user();
329         // Create a couple of courses.
330         $course1 = $this->getDataGenerator()->create_course();
331         $course2 = $this->getDataGenerator()->create_course();
332         $course3 = $this->getDataGenerator()->create_course();
334         // Enrol the user in the courses.
335         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
336         $maninstance1 = $DB->get_record('enrol', array('courseid' => $course1->id, 'enrol' => 'manual'), '*', MUST_EXIST);
337         $maninstance2 = $DB->get_record('enrol', array('courseid' => $course2->id, 'enrol' => 'manual'), '*', MUST_EXIST);
338         $maninstance3 = $DB->get_record('enrol', array('courseid' => $course3->id, 'enrol' => 'manual'), '*', MUST_EXIST);
339         $manual = enrol_get_plugin('manual');
340         $manual->enrol_user($maninstance1, $user->id, $studentrole->id);
341         $manual->enrol_user($maninstance2, $user->id, $studentrole->id);
342         $manual->enrol_user($maninstance3, $user->id, $studentrole->id);
344         // To create the data we need we freeze the grade book to use the old behaviour.
345         set_config('gradebook_calculations_freeze_' . $course1->id, 20150627);
346         set_config('gradebook_calculations_freeze_' . $course2->id, 20150627);
347         set_config('gradebook_calculations_freeze_' . $course3->id, 20150627);
348         $CFG->grade_minmaxtouse = 2;
350         // Creating a category for a grade item.
351         $gradecategory = new grade_category();
352         $gradecategory->fullname = 'calculated grade category';
353         $gradecategory->courseid = $course1->id;
354         $gradecategory->insert();
355         $gradecategoryid = $gradecategory->id;
357         // This is a manual grade item.
358         $gradeitem = new grade_item();
359         $gradeitem->itemname = 'grade item one';
360         $gradeitem->itemtype = 'manual';
361         $gradeitem->categoryid = $gradecategoryid;
362         $gradeitem->courseid = $course1->id;
363         $gradeitem->idnumber = 'gi1';
364         $gradeitem->insert();
366         // Changing the category into a calculated grade category.
367         $gradecategoryitem = grade_item::fetch(array('iteminstance' => $gradecategory->id));
368         $gradecategoryitem->calculation = '=##gi' . $gradeitem->id . '##/2';
369         $gradecategoryitem->update();
371         // Setting a grade for the student.
372         $grade = $gradeitem->get_grade($user->id, true);
373         $grade->finalgrade = 50;
374         $grade->update();
375         // Creating all the grade_grade items.
376         grade_regrade_final_grades($course1->id);
377         // Updating the grade category to a new grade max and min.
378         $gradecategoryitem->grademax = 50;
379         $gradecategoryitem->grademin = 5;
380         $gradecategoryitem->update();
382         // Different manual grade item for course 2. We are creating a course with a calculated grade item that has a grade max of
383         // 50. The grade_grade will have a rawgrademax of 100 regardless.
384         $gradeitem = new grade_item();
385         $gradeitem->itemname = 'grade item one';
386         $gradeitem->itemtype = 'manual';
387         $gradeitem->courseid = $course2->id;
388         $gradeitem->idnumber = 'gi1';
389         $gradeitem->grademax = 25;
390         $gradeitem->insert();
392         // Calculated grade item for course 2.
393         $calculatedgradeitem = new grade_item();
394         $calculatedgradeitem->itemname = 'calculated grade';
395         $calculatedgradeitem->itemtype = 'manual';
396         $calculatedgradeitem->courseid = $course2->id;
397         $calculatedgradeitem->calculation = '=##gi' . $gradeitem->id . '##*2';
398         $calculatedgradeitem->grademax = 50;
399         $calculatedgradeitem->insert();
401         // Assigning a grade for the user.
402         $grade = $gradeitem->get_grade($user->id, true);
403         $grade->finalgrade = 10;
404         $grade->update();
406         // Setting all of the grade_grade items.
407         grade_regrade_final_grades($course2->id);
409         // Different manual grade item for course 3. We are creating a course with a calculated grade item that has a grade max of
410         // 50. The grade_grade will have a rawgrademax of 100 regardless.
411         $gradeitem = new grade_item();
412         $gradeitem->itemname = 'grade item one';
413         $gradeitem->itemtype = 'manual';
414         $gradeitem->courseid = $course3->id;
415         $gradeitem->idnumber = 'gi1';
416         $gradeitem->grademax = 25;
417         $gradeitem->insert();
419         // Calculated grade item for course 2.
420         $calculatedgradeitem = new grade_item();
421         $calculatedgradeitem->itemname = 'calculated grade';
422         $calculatedgradeitem->itemtype = 'manual';
423         $calculatedgradeitem->courseid = $course3->id;
424         $calculatedgradeitem->calculation = '=##gi' . $gradeitem->id . '##*2';
425         $calculatedgradeitem->grademax = 50;
426         $calculatedgradeitem->insert();
428         // Assigning a grade for the user.
429         $grade = $gradeitem->get_grade($user->id, true);
430         $grade->finalgrade = 10;
431         $grade->update();
433         // Setting all of the grade_grade items.
434         grade_regrade_final_grades($course3->id);
435         // Need to do this first before changing the other courses, otherwise they will be flagged too early.
436         set_config('gradebook_calculations_freeze_' . $course3->id, null);
437         upgrade_calculated_grade_items($course3->id);
438         $this->assertEquals(20150627, $CFG->{'gradebook_calculations_freeze_' . $course3->id});
440         // Change the setting back to null.
441         set_config('gradebook_calculations_freeze_' . $course1->id, null);
442         set_config('gradebook_calculations_freeze_' . $course2->id, null);
443         // Run the upgrade.
444         upgrade_calculated_grade_items();
445         // The setting should be set again after the upgrade.
446         $this->assertEquals(20150627, $CFG->{'gradebook_calculations_freeze_' . $course1->id});
447         $this->assertEquals(20150627, $CFG->{'gradebook_calculations_freeze_' . $course2->id});
448     }
450     function test_upgrade_calculated_grade_items_regrade() {
451         global $DB, $CFG;
453         $this->resetAfterTest();
455         require_once($CFG->libdir . '/db/upgradelib.php');
457         // Create a user.
458         $user = $this->getDataGenerator()->create_user();
460         // Create a course.
461         $course = $this->getDataGenerator()->create_course();
463         // Enrol the user in the course.
464         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
465         $maninstance1 = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'), '*', MUST_EXIST);
466         $manual = enrol_get_plugin('manual');
467         $manual->enrol_user($maninstance1, $user->id, $studentrole->id);
469         set_config('upgrade_calculatedgradeitemsonlyregrade', 1);
471         // Creating a category for a grade item.
472         $gradecategory = new grade_category();
473         $gradecategory->fullname = 'calculated grade category';
474         $gradecategory->courseid = $course->id;
475         $gradecategory->insert();
476         $gradecategoryid = $gradecategory->id;
478         // This is a manual grade item.
479         $gradeitem = new grade_item();
480         $gradeitem->itemname = 'grade item one';
481         $gradeitem->itemtype = 'manual';
482         $gradeitem->categoryid = $gradecategoryid;
483         $gradeitem->courseid = $course->id;
484         $gradeitem->idnumber = 'gi1';
485         $gradeitem->insert();
487         // Changing the category into a calculated grade category.
488         $gradecategoryitem = grade_item::fetch(array('iteminstance' => $gradecategory->id));
489         $gradecategoryitem->calculation = '=##gi' . $gradeitem->id . '##/2';
490         $gradecategoryitem->grademax = 50;
491         $gradecategoryitem->grademin = 15;
492         $gradecategoryitem->update();
494         // Setting a grade for the student.
495         $grade = $gradeitem->get_grade($user->id, true);
496         $grade->finalgrade = 50;
497         $grade->update();
499         grade_regrade_final_grades($course->id);
500         $grade = grade_grade::fetch(array('itemid' => $gradecategoryitem->id, 'userid' => $user->id));
501         $grade->rawgrademax = 100;
502         $grade->rawgrademin = 0;
503         $grade->update();
504         $this->assertNotEquals($gradecategoryitem->grademax, $grade->rawgrademax);
505         $this->assertNotEquals($gradecategoryitem->grademin, $grade->rawgrademin);
507         // This is the function that we are testing. If we comment out this line, then the test fails because the grade items
508         // are not flagged for regrading.
509         upgrade_calculated_grade_items();
510         grade_regrade_final_grades($course->id);
512         $grade = grade_grade::fetch(array('itemid' => $gradecategoryitem->id, 'userid' => $user->id));
514         $this->assertEquals($gradecategoryitem->grademax, $grade->rawgrademax);
515         $this->assertEquals($gradecategoryitem->grademin, $grade->rawgrademin);
516     }
518     public function test_upgrade_course_tags() {
519         global $DB, $CFG;
521         $this->resetAfterTest();
523         require_once($CFG->libdir . '/db/upgradelib.php');
525         // Running upgrade script when there are no tags.
526         upgrade_course_tags();
527         $this->assertFalse($DB->record_exists('tag_instance', array()));
529         // No course entries.
530         $DB->insert_record('tag_instance', array('itemid' => 123, 'tagid' => 101, 'tiuserid' => 0,
531             'itemtype' => 'post', 'component' => 'core', 'contextid' => 1));
532         $DB->insert_record('tag_instance', array('itemid' => 333, 'tagid' => 103, 'tiuserid' => 1002,
533             'itemtype' => 'post', 'component' => 'core', 'contextid' => 1));
535         upgrade_course_tags();
536         $records = array_values($DB->get_records('tag_instance', array(), 'id', '*'));
537         $this->assertEquals(2, count($records));
538         $this->assertEquals(123, $records[0]->itemid);
539         $this->assertEquals(333, $records[1]->itemid);
541         // Imagine we have tags 101, 102, 103, ... and courses 1, 2, 3, ... and users 1001, 1002, ... .
542         $keys = array('itemid', 'tagid', 'tiuserid');
543         $valuesets = array(
544             array(1, 101, 0),
545             array(1, 102, 0),
547             array(2, 102, 0),
548             array(2, 103, 1001),
550             array(3, 103, 0),
551             array(3, 103, 1001),
553             array(3, 104, 1006),
554             array(3, 104, 1001),
555             array(3, 104, 1002),
556         );
558         foreach ($valuesets as $values) {
559             $DB->insert_record('tag_instance', array_combine($keys, $values) +
560                     array('itemtype' => 'course', 'component' => 'core', 'contextid' => 1));
561         }
563         upgrade_course_tags();
564         // There are 8 records in 'tag_instance' table and 7 of them do not have tiuserid (except for one 'post').
565         $records = array_values($DB->get_records('tag_instance', array(), 'id', '*'));
566         $this->assertEquals(8, count($records));
567         $this->assertEquals(7, $DB->count_records('tag_instance', array('tiuserid' => 0)));
568         // Course 1 is mapped to tags 101 and 102.
569         $this->assertEquals(array(101, 102), array_values($DB->get_fieldset_select('tag_instance', 'tagid',
570                 'itemtype = ? AND itemid = ? ORDER BY tagid', array('course', 1))));
571         // Course 2 is mapped to tags 102 and 103.
572         $this->assertEquals(array(102, 103), array_values($DB->get_fieldset_select('tag_instance', 'tagid',
573                 'itemtype = ? AND itemid = ? ORDER BY tagid', array('course', 2))));
574         // Course 1 is mapped to tags 101 and 102.
575         $this->assertEquals(array(103, 104), array_values($DB->get_fieldset_select('tag_instance', 'tagid',
576                 'itemtype = ? AND itemid = ? ORDER BY tagid', array('course', 3))));
577     }
579     /**
580      * Test that the upgrade script correctly flags courses to be frozen due to letter boundary problems.
581      */
582     public function test_upgrade_course_letter_boundary() {
583         global $CFG, $DB;
584         $this->resetAfterTest(true);
586         require_once($CFG->libdir . '/db/upgradelib.php');
588         // Create a user.
589         $user = $this->getDataGenerator()->create_user();
591         // Create some courses.
592         $courses = array();
593         $contexts = array();
594         for ($i = 0; $i < 45; $i++) {
595             $course = $this->getDataGenerator()->create_course();
596             $context = context_course::instance($course->id);
597             if (in_array($i, array(2, 5, 10, 13, 14, 19, 23, 25, 30, 34, 36))) {
598                 // Assign good letter boundaries.
599                 $this->assign_good_letter_boundary($context->id);
600             }
601             if (in_array($i, array(3, 6, 11, 15, 20, 24, 26, 31, 35))) {
602                 // Assign bad letter boundaries.
603                 $this->assign_bad_letter_boundary($context->id);
604             }
606             if (in_array($i, array(3, 9, 10, 11, 18, 19, 20, 29, 30, 31, 40))) {
607                 grade_set_setting($course->id, 'displaytype', '3');
608             } else if (in_array($i, array(8, 17, 28))) {
609                 grade_set_setting($course->id, 'displaytype', '2');
610             }
612             if (in_array($i, array(37, 43))) {
613                 // Show.
614                 grade_set_setting($course->id, 'report_user_showlettergrade', '1');
615             } else if (in_array($i, array(38, 42))) {
616                 // Hide.
617                 grade_set_setting($course->id, 'report_user_showlettergrade', '0');
618             }
620             $assignrow = $this->getDataGenerator()->create_module('assign', array('course' => $course->id, 'name' => 'Test!'));
621             $gi = grade_item::fetch(
622                     array('itemtype' => 'mod',
623                           'itemmodule' => 'assign',
624                           'iteminstance' => $assignrow->id,
625                           'courseid' => $course->id));
626             if (in_array($i, array(6, 13, 14, 15, 23, 24, 34, 35, 36, 41))) {
627                 grade_item::set_properties($gi, array('display' => 3));
628                 $gi->update();
629             } else if (in_array($i, array(12, 21, 32))) {
630                 grade_item::set_properties($gi, array('display' => 2));
631                 $gi->update();
632             }
633             $gradegrade = new grade_grade();
634             $gradegrade->itemid = $gi->id;
635             $gradegrade->userid = $user->id;
636             $gradegrade->rawgrade = 55.5563;
637             $gradegrade->finalgrade = 55.5563;
638             $gradegrade->rawgrademax = 100;
639             $gradegrade->rawgrademin = 0;
640             $gradegrade->timecreated = time();
641             $gradegrade->timemodified = time();
642             $gradegrade->insert();
644             $contexts[] = $context;
645             $courses[] = $course;
646         }
648         upgrade_course_letter_boundary();
650         // No system setting for grade letter boundaries.
651         // [0] A course with no letter boundaries.
652         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[0]->id}));
653         // [1] A course with letter boundaries which are default.
654         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[1]->id}));
655         // [2] A course with letter boundaries which are custom but not affected.
656         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[2]->id}));
657         // [3] A course with letter boundaries which are custom and will be affected.
658         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[3]->id});
659         // [4] A course with no letter boundaries, but with a grade item with letter boundaries which are default.
660         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[4]->id}));
661         // [5] A course with no letter boundaries, but with a grade item with letter boundaries which are not default, but not affected.
662         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[5]->id}));
663         // [6] A course with no letter boundaries, but with a grade item with letter boundaries which are not default which will be affected.
664         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[6]->id});
666         // System setting for grade letter boundaries (default).
667         set_config('grade_displaytype', '3');
668         for ($i = 0; $i < 45; $i++) {
669             unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
670         }
671         upgrade_course_letter_boundary();
673         // [7] A course with no grade display settings for the course or grade items.
674         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[7]->id}));
675         // [8] A course with grade display settings, but for something that isn't letters.
676         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[8]->id}));
677         // [9] A course with grade display settings of letters which are default.
678         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[9]->id}));
679         // [10] A course with grade display settings of letters which are not default, but not affected.
680         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[10]->id}));
681         // [11] A course with grade display settings of letters which are not default, which will be affected.
682         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[11]->id});
683         // [12] A grade item with display settings that are not letters.
684         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[12]->id}));
685         // [13] A grade item with display settings of letters which are default.
686         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[13]->id}));
687         // [14] A grade item with display settings of letters which are not default, but not affected.
688         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[14]->id}));
689         // [15] A grade item with display settings of letters which are not default, which will be affected.
690         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[15]->id});
692         // System setting for grade letter boundaries (custom with problem).
693         $systemcontext = context_system::instance();
694         $this->assign_bad_letter_boundary($systemcontext->id);
695         for ($i = 0; $i < 45; $i++) {
696             unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
697         }
698         upgrade_course_letter_boundary();
700         // [16] A course with no grade display settings for the course or grade items.
701         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[16]->id});
702         // [17] A course with grade display settings, but for something that isn't letters.
703         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[17]->id}));
704         // [18] A course with grade display settings of letters which are default.
705         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[18]->id});
706         // [19] A course with grade display settings of letters which are not default, but not affected.
707         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[19]->id}));
708         // [20] A course with grade display settings of letters which are not default, which will be affected.
709         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[20]->id});
710         // [21] A grade item with display settings which are not letters. Grade total will be affected so should be frozen.
711         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[21]->id});
712         // [22] A grade item with display settings of letters which are default.
713         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[22]->id});
714         // [23] A grade item with display settings of letters which are not default, but not affected. Course uses new letter boundary setting.
715         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[23]->id}));
716         // [24] A grade item with display settings of letters which are not default, which will be affected.
717         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[24]->id});
718         // [25] A course which is using the default grade display setting, but has updated the grade letter boundary (not 57) Should not be frozen.
719         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[25]->id}));
720         // [26] A course that is using the default display setting (letters) and altered the letter boundary with 57. Should be frozen.
721         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[26]->id});
723         // System setting not showing letters.
724         set_config('grade_displaytype', '2');
725         for ($i = 0; $i < 45; $i++) {
726             unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
727         }
728         upgrade_course_letter_boundary();
730         // [27] A course with no grade display settings for the course or grade items.
731         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[27]->id}));
732         // [28] A course with grade display settings, but for something that isn't letters.
733         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[28]->id}));
734         // [29] A course with grade display settings of letters which are default.
735         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[29]->id});
736         // [30] A course with grade display settings of letters which are not default, but not affected.
737         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[30]->id}));
738         // [31] A course with grade display settings of letters which are not default, which will be affected.
739         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[31]->id});
740         // [32] A grade item with display settings which are not letters.
741         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[32]->id}));
742         // [33] All system defaults.
743         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[33]->id}));
744         // [34] A grade item with display settings of letters which are not default, but not affected. Course uses new letter boundary setting.
745         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[34]->id}));
746         // [35] A grade item with display settings of letters which are not default, which will be affected.
747         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[35]->id});
748         // [36] A course with grade display settings of letters with modified and good boundary (not 57) Should not be frozen.
749         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[36]->id}));
751         // Previous site conditions still exist.
752         for ($i = 0; $i < 45; $i++) {
753             unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
754         }
755         upgrade_course_letter_boundary();
757         // [37] Site setting for not showing the letter column and course setting set to show (frozen).
758         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[37]->id});
759         // [38] Site setting for not showing the letter column and course setting set to hide.
760         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[38]->id}));
761         // [39] Site setting for not showing the letter column and course setting set to default.
762         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[39]->id}));
763         // [40] Site setting for not showing the letter column and course setting set to default. Course display set to letters (frozen).
764         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[40]->id});
765         // [41] Site setting for not showing the letter column and course setting set to default. Grade item display set to letters (frozen).
766         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[41]->id});
768         // Previous site conditions still exist.
769         for ($i = 0; $i < 45; $i++) {
770             unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
771         }
772         set_config('grade_report_user_showlettergrade', '1');
773         upgrade_course_letter_boundary();
775         // [42] Site setting for showing the letter column, but course setting set to hide.
776         $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[42]->id}));
777         // [43] Site setting for showing the letter column and course setting set to show (frozen).
778         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[43]->id});
779         // [44] Site setting for showing the letter column and course setting set to default (frozen).
780         $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[44]->id});
781     }
783     /**
784      * Test upgrade_letter_boundary_needs_freeze function.
785      */
786     public function test_upgrade_letter_boundary_needs_freeze() {
787         global $CFG;
789         $this->resetAfterTest();
791         require_once($CFG->libdir . '/db/upgradelib.php');
793         $courses = array();
794         $contexts = array();
795         for ($i = 0; $i < 3; $i++) {
796             $courses[] = $this->getDataGenerator()->create_course();
797             $contexts[] = context_course::instance($courses[$i]->id);
798         }
800         // Course one is not using a letter boundary.
801         $this->assertFalse(upgrade_letter_boundary_needs_freeze($contexts[0]));
803         // Let's make course 2 use the bad boundary.
804         $this->assign_bad_letter_boundary($contexts[1]->id);
805         $this->assertTrue(upgrade_letter_boundary_needs_freeze($contexts[1]));
806         // Course 3 has letter boundaries that are fine.
807         $this->assign_good_letter_boundary($contexts[2]->id);
808         $this->assertFalse(upgrade_letter_boundary_needs_freeze($contexts[2]));
809         // Try the system context not using a letter boundary.
810         $systemcontext = context_system::instance();
811         $this->assertFalse(upgrade_letter_boundary_needs_freeze($systemcontext));
812     }
814     /**
815      * Assigns letter boundaries with comparison problems.
816      *
817      * @param int $contextid Context ID.
818      */
819     private function assign_bad_letter_boundary($contextid) {
820         global $DB;
821         $newlettersscale = array(
822                 array('contextid' => $contextid, 'lowerboundary' => 90.00000, 'letter' => 'A'),
823                 array('contextid' => $contextid, 'lowerboundary' => 85.00000, 'letter' => 'A-'),
824                 array('contextid' => $contextid, 'lowerboundary' => 80.00000, 'letter' => 'B+'),
825                 array('contextid' => $contextid, 'lowerboundary' => 75.00000, 'letter' => 'B'),
826                 array('contextid' => $contextid, 'lowerboundary' => 70.00000, 'letter' => 'B-'),
827                 array('contextid' => $contextid, 'lowerboundary' => 65.00000, 'letter' => 'C+'),
828                 array('contextid' => $contextid, 'lowerboundary' => 57.00000, 'letter' => 'C'),
829                 array('contextid' => $contextid, 'lowerboundary' => 50.00000, 'letter' => 'C-'),
830                 array('contextid' => $contextid, 'lowerboundary' => 40.00000, 'letter' => 'D+'),
831                 array('contextid' => $contextid, 'lowerboundary' => 25.00000, 'letter' => 'D'),
832                 array('contextid' => $contextid, 'lowerboundary' => 0.00000, 'letter' => 'F'),
833             );
835         $DB->delete_records('grade_letters', array('contextid' => $contextid));
836         foreach ($newlettersscale as $record) {
837             // There is no API to do this, so we have to manually insert into the database.
838             $DB->insert_record('grade_letters', $record);
839         }
840     }
842     /**
843      * Assigns letter boundaries with no comparison problems.
844      *
845      * @param int $contextid Context ID.
846      */
847     private function assign_good_letter_boundary($contextid) {
848         global $DB;
849         $newlettersscale = array(
850                 array('contextid' => $contextid, 'lowerboundary' => 90.00000, 'letter' => 'A'),
851                 array('contextid' => $contextid, 'lowerboundary' => 85.00000, 'letter' => 'A-'),
852                 array('contextid' => $contextid, 'lowerboundary' => 80.00000, 'letter' => 'B+'),
853                 array('contextid' => $contextid, 'lowerboundary' => 75.00000, 'letter' => 'B'),
854                 array('contextid' => $contextid, 'lowerboundary' => 70.00000, 'letter' => 'B-'),
855                 array('contextid' => $contextid, 'lowerboundary' => 65.00000, 'letter' => 'C+'),
856                 array('contextid' => $contextid, 'lowerboundary' => 54.00000, 'letter' => 'C'),
857                 array('contextid' => $contextid, 'lowerboundary' => 50.00000, 'letter' => 'C-'),
858                 array('contextid' => $contextid, 'lowerboundary' => 40.00000, 'letter' => 'D+'),
859                 array('contextid' => $contextid, 'lowerboundary' => 25.00000, 'letter' => 'D'),
860                 array('contextid' => $contextid, 'lowerboundary' => 0.00000, 'letter' => 'F'),
861             );
863         $DB->delete_records('grade_letters', array('contextid' => $contextid));
864         foreach ($newlettersscale as $record) {
865             // There is no API to do this, so we have to manually insert into the database.
866             $DB->insert_record('grade_letters', $record);
867         }
868     }