Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
17
namespace core_course;
18
 
19
use advanced_testcase;
20
use backup_controller;
21
use backup;
22
use blog_entry;
23
use cache;
24
use calendar_event;
25
use coding_exception;
26
use comment;
27
use completion_criteria_date;
28
use completion_completion;
29
use context_course;
30
use context_module;
31
use context_system;
32
use context_coursecat;
33
use core\event\section_viewed;
34
use core_completion_external;
35
use core_external;
36
use core_tag_index_builder;
37
use core_tag_tag;
38
use course_capability_assignment;
39
use course_request;
40
use core_course_category;
41
use enrol_imsenterprise\imsenterprise_test;
42
use core_external\external_api;
43
use grade_item;
44
use grading_manager;
45
use moodle_exception;
46
use moodle_url;
47
use phpunit_util;
48
use rating_manager;
49
use restore_controller;
50
use stdClass;
51
use testing_data_generator;
52
 
53
defined('MOODLE_INTERNAL') or die();
54
 
55
// Require library globally because it's constants are used within dataProvider methods, executed before setUpBeforeClass.
56
global $CFG;
57
require_once($CFG->dirroot . '/course/lib.php');
58
 
59
/**
60
 * Course related unit tests
61
 *
62
 * @package    core_course
63
 * @category   test
64
 * @copyright  2012 Petr Skoda {@link http://skodak.org}
65
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
66
 */
1441 ariadna 67
final class courselib_test extends advanced_testcase {
1 efrain 68
 
69
    /**
70
     * Load required libraries and fixtures.
71
     */
72
    public static function setUpBeforeClass(): void {
73
        global $CFG;
74
 
75
        require_once($CFG->dirroot . '/course/tests/fixtures/course_capability_assignment.php');
76
        require_once($CFG->dirroot . '/enrol/imsenterprise/tests/imsenterprise_test.php');
1441 ariadna 77
        parent::setUpBeforeClass();
1 efrain 78
    }
79
 
80
    /**
81
     * Set forum specific test values for calling create_module().
82
     *
83
     * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
84
     */
85
    private function forum_create_set_values(&$moduleinfo) {
86
        // Completion specific to forum - optional.
87
        $moduleinfo->completionposts = 3;
88
        $moduleinfo->completiondiscussions = 1;
89
        $moduleinfo->completionreplies = 2;
90
 
91
        // Specific values to the Forum module.
92
        $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
93
        $moduleinfo->type = 'single';
94
        $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
95
        $moduleinfo->maxbytes = 10240;
96
        $moduleinfo->maxattachments = 2;
97
 
98
        // Post threshold for blocking - specific to forum.
99
        $moduleinfo->blockperiod = 60*60*24;
100
        $moduleinfo->blockafter = 10;
101
        $moduleinfo->warnafter = 5;
102
 
103
        // Grading of whole forum settings.
104
        $moduleinfo->grade_forum = 0;
105
    }
106
 
107
    /**
108
     * Execute test asserts on the saved DB data by create_module($forum).
109
     *
110
     * @param object $moduleinfo - the specific forum values that were used to create a forum.
111
     * @param object $dbmodinstance - the DB values of the created forum.
112
     */
113
    private function forum_create_run_asserts($moduleinfo, $dbmodinstance) {
114
        // Compare values specific to forums.
115
        $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
116
        $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
117
        $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
118
        $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
119
        $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
120
        $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
121
        $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
122
        $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
123
        $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
124
        $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
125
        $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
126
        $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
127
        $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
128
        $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
129
        $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
130
        $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
131
        $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
132
    }
133
 
134
    /**
135
     * Set assign module specific test values for calling create_module().
136
     *
137
     * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
138
     */
139
    private function assign_create_set_values(&$moduleinfo) {
140
        // Specific values to the Assign module.
141
        $moduleinfo->alwaysshowdescription = true;
142
        $moduleinfo->submissiondrafts = true;
143
        $moduleinfo->requiresubmissionstatement = true;
144
        $moduleinfo->sendnotifications = true;
145
        $moduleinfo->sendlatenotifications = true;
146
        $moduleinfo->duedate = time() + (7 * 24 * 3600);
147
        $moduleinfo->cutoffdate = time() + (7 * 24 * 3600);
148
        $moduleinfo->gradingduedate = time() + (7 * 24 * 3600);
149
        $moduleinfo->allowsubmissionsfromdate = time();
150
        $moduleinfo->teamsubmission = true;
151
        $moduleinfo->requireallteammemberssubmit = true;
152
        $moduleinfo->teamsubmissiongroupingid = true;
153
        $moduleinfo->blindmarking = true;
154
        $moduleinfo->markingworkflow = true;
155
        $moduleinfo->markingallocation = true;
156
        $moduleinfo->assignsubmission_onlinetext_enabled = true;
157
        $moduleinfo->assignsubmission_file_enabled = true;
158
        $moduleinfo->assignsubmission_file_maxfiles = 1;
159
        $moduleinfo->assignsubmission_file_maxsizebytes = 1000000;
160
        $moduleinfo->assignsubmission_comments_enabled = true;
161
        $moduleinfo->assignfeedback_comments_enabled = true;
162
        $moduleinfo->assignfeedback_offline_enabled = true;
163
        $moduleinfo->assignfeedback_file_enabled = true;
164
 
165
        // Advanced grading.
166
        $gradingmethods = grading_manager::available_methods();
167
        $moduleinfo->advancedgradingmethod_submissions = current(array_keys($gradingmethods));
168
    }
169
 
170
    /**
171
     * Execute test asserts on the saved DB data by create_module($assign).
172
     *
173
     * @param object $moduleinfo - the specific assign module values that were used to create an assign module.
174
     * @param object $dbmodinstance - the DB values of the created assign module.
175
     */
176
    private function assign_create_run_asserts($moduleinfo, $dbmodinstance) {
177
        global $DB;
178
 
179
        $this->assertEquals($moduleinfo->alwaysshowdescription, $dbmodinstance->alwaysshowdescription);
180
        $this->assertEquals($moduleinfo->submissiondrafts, $dbmodinstance->submissiondrafts);
181
        $this->assertEquals($moduleinfo->requiresubmissionstatement, $dbmodinstance->requiresubmissionstatement);
182
        $this->assertEquals($moduleinfo->sendnotifications, $dbmodinstance->sendnotifications);
183
        $this->assertEquals($moduleinfo->duedate, $dbmodinstance->duedate);
184
        $this->assertEquals($moduleinfo->cutoffdate, $dbmodinstance->cutoffdate);
185
        $this->assertEquals($moduleinfo->allowsubmissionsfromdate, $dbmodinstance->allowsubmissionsfromdate);
186
        $this->assertEquals($moduleinfo->teamsubmission, $dbmodinstance->teamsubmission);
187
        $this->assertEquals($moduleinfo->requireallteammemberssubmit, $dbmodinstance->requireallteammemberssubmit);
188
        $this->assertEquals($moduleinfo->teamsubmissiongroupingid, $dbmodinstance->teamsubmissiongroupingid);
189
        $this->assertEquals($moduleinfo->blindmarking, $dbmodinstance->blindmarking);
190
        $this->assertEquals($moduleinfo->markingworkflow, $dbmodinstance->markingworkflow);
191
        $this->assertEquals($moduleinfo->markingallocation, $dbmodinstance->markingallocation);
192
        // The goal not being to fully test assign_add_instance() we'll stop here for the assign tests - to avoid too many DB queries.
193
 
194
        // Advanced grading.
195
        $cm = get_coursemodule_from_instance('assign', $dbmodinstance->id);
196
        $contextmodule = context_module::instance($cm->id);
197
        $advancedgradingmethod = $DB->get_record('grading_areas',
198
            array('contextid' => $contextmodule->id,
199
                'activemethod' => $moduleinfo->advancedgradingmethod_submissions));
200
        $this->assertEquals($moduleinfo->advancedgradingmethod_submissions, $advancedgradingmethod);
201
    }
202
 
203
    /**
204
     * Run some asserts test for a specific module for the function create_module().
205
     *
206
     * The function has been created (and is called) for $this->test_create_module().
207
     * Note that the call to MODULE_create_set_values and MODULE_create_run_asserts are done after the common set values/run asserts.
208
     * So if you want, you can overwrite the default values/asserts in the respective functions.
209
     * @param string $modulename Name of the module ('forum', 'assign', 'book'...).
210
     */
211
    private function create_specific_module_test($modulename) {
212
        global $DB, $CFG;
213
 
214
        $this->resetAfterTest(true);
215
 
216
        $this->setAdminUser();
217
 
218
        // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
219
        require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
220
 
221
        // Enable avaibility.
222
        // If not enabled all conditional fields will be ignored.
223
        set_config('enableavailability', 1);
224
 
225
        // Enable course completion.
226
        // If not enabled all completion settings will be ignored.
227
        set_config('enablecompletion', COMPLETION_ENABLED);
228
 
229
        // Enable forum RSS feeds.
230
        set_config('enablerssfeeds', 1);
231
        set_config('forum_enablerssfeeds', 1);
232
 
233
        $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
234
           array('createsections'=>true));
235
 
236
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
237
 
238
        // Create assign module instance for test.
239
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
240
        $params['course'] = $course->id;
241
        $instance = $generator->create_instance($params);
242
        $assigncm = get_coursemodule_from_instance('assign', $instance->id);
243
 
244
        // Module test values.
245
        $moduleinfo = new stdClass();
246
 
247
        // Always mandatory generic values to any module.
248
        $moduleinfo->modulename = $modulename;
249
        $moduleinfo->section = 1; // This is the section number in the course. Not the section id in the database.
250
        $moduleinfo->course = $course->id;
251
        $moduleinfo->groupingid = $grouping->id;
252
        $moduleinfo->visible = true;
253
        $moduleinfo->visibleoncoursepage = true;
254
 
255
        // Sometimes optional generic values for some modules.
256
        $moduleinfo->name = 'My test module';
257
        $moduleinfo->showdescription = 1; // standard boolean
258
        require_once($CFG->libdir . '/gradelib.php');
259
        $gradecats = grade_get_categories_menu($moduleinfo->course, false);
260
        $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
261
        $moduleinfo->gradecat = $gradecatid;
262
        $moduleinfo->groupmode = VISIBLEGROUPS;
263
        $moduleinfo->cmidnumber = 'idnumber_XXX';
264
 
265
        // Completion common to all module.
266
        $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
267
        $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
268
        $moduleinfo->completiongradeitemnumber = 1;
269
        $moduleinfo->completionpassgrade = 0;
270
        $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
271
 
272
        // Conditional activity.
273
        $moduleinfo->availability = '{"op":"&","showc":[true,true],"c":[' .
274
                '{"type":"date","d":">=","t":' . time() . '},' .
275
                '{"type":"date","d":"<","t":' . (time() + (7 * 24 * 3600)) . '}' .
276
                ']}';
277
        $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
278
        $moduleinfo->conditiongradegroup = array(array('conditiongradeitemid' => $coursegradeitem->id, 'conditiongrademin' => 10, 'conditiongrademax' => 80));
279
        $moduleinfo->conditionfieldgroup = array(array('conditionfield' => 'email', 'conditionfieldoperator' => \availability_profile\condition::OP_CONTAINS, 'conditionfieldvalue' => '@'));
280
        $moduleinfo->conditioncompletiongroup = array(array('conditionsourcecmid' => $assigncm->id, 'conditionrequiredcompletion' => COMPLETION_COMPLETE)); // "conditionsourcecmid == 0" => none
281
 
282
        // Grading and Advanced grading.
283
        require_once($CFG->dirroot . '/rating/lib.php');
284
        $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
285
        $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
286
        $moduleinfo->assesstimestart = time();
287
        $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
288
 
289
        // RSS.
290
        $moduleinfo->rsstype = 2;
291
        $moduleinfo->rssarticles = 10;
292
 
293
        // Optional intro editor (depends of module).
294
        $draftid_editor = 0;
295
        file_prepare_draft_area($draftid_editor, null, null, null, null);
296
        $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
297
 
298
        // Following is the advanced grading method area called 'submissions' for the 'assign' module.
299
        if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
300
            $moduleinfo->grade = 100;
301
        }
302
 
303
        // Plagiarism form values.
304
        // No plagiarism plugin installed by default. Use this space to make your own test.
305
 
306
        // Values specific to the module.
307
        $modulesetvalues = $modulename.'_create_set_values';
308
        $this->$modulesetvalues($moduleinfo);
309
 
310
        // Create the module.
311
        $result = create_module($moduleinfo);
312
 
313
        // Retrieve the module info.
314
        $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
315
        $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
316
        // We passed the course section number to create_courses but $dbcm contain the section id.
317
        // We need to retrieve the db course section number.
318
        $section = $DB->get_record('course_sections', array('course' => $dbcm->course, 'id' => $dbcm->section));
319
        // Retrieve the grade item.
320
        $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
321
            'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
322
 
323
        // Compare the values common to all module instances.
324
        $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
325
        $this->assertEquals($moduleinfo->section, $section->section);
326
        $this->assertEquals($moduleinfo->course, $dbcm->course);
327
        $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
328
        $this->assertEquals($moduleinfo->visible, $dbcm->visible);
329
        $this->assertEquals($moduleinfo->completion, $dbcm->completion);
330
        $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
331
        $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
332
        $this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
333
        $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
334
        $this->assertEquals($moduleinfo->availability, $dbcm->availability);
335
        $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
336
        $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
337
        $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
338
        $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
339
 
340
        // Optional grade testing.
341
        if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
342
            $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
343
        }
344
 
345
        // Some optional (but quite common) to some module.
346
        $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
347
        $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
348
        $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
349
 
350
        // Test specific to the module.
351
        $modulerunasserts = $modulename.'_create_run_asserts';
352
        $this->$modulerunasserts($moduleinfo, $dbmodinstance);
353
        return $moduleinfo;
354
    }
355
 
356
    /**
357
     * Create module associated blog and tags.
358
     *
359
     * @param object $course Course.
360
     * @param object $modulecontext The context of the module.
361
     */
362
    private function create_module_asscociated_blog($course, $modulecontext) {
363
        global $DB, $CFG;
364
 
365
        // Create default group.
366
        $group = new stdClass();
367
        $group->courseid = $course->id;
368
        $group->name = 'Group';
369
        $group->id = $DB->insert_record('groups', $group);
370
 
371
        // Create default user.
372
        $user = $this->getDataGenerator()->create_user(array(
373
            'username' => 'testuser',
374
            'firstname' => 'Firsname',
375
            'lastname' => 'Lastname'
376
        ));
377
 
378
        // Create default post.
379
        $post = new stdClass();
380
        $post->userid = $user->id;
381
        $post->groupid = $group->id;
382
        $post->content = 'test post content text';
383
        $post->module = 'blog';
384
        $post->id = $DB->insert_record('post', $post);
385
 
386
        // Create default tag.
387
        $tag = $this->getDataGenerator()->create_tag(array('userid' => $user->id,
388
            'rawname' => 'Testtagname', 'isstandard' => 1));
389
        // Apply the tag to the blog.
390
        $DB->insert_record('tag_instance', array('tagid' => $tag->id, 'itemtype' => 'user',
391
            'component' => 'core', 'itemid' => $post->id, 'ordering' => 0));
392
 
393
        require_once($CFG->dirroot . '/blog/locallib.php');
394
        $blog = new blog_entry($post->id);
395
        $blog->add_association($modulecontext->id);
396
 
397
        return $blog;
398
    }
399
 
400
    /**
401
     * Test create_module() for multiple modules defined in the $modules array (first declaration of the function).
402
     */
11 efrain 403
    public function test_create_module(): void {
1 efrain 404
        // Add the module name you want to test here.
405
        // Create the match MODULENAME_create_set_values() and MODULENAME_create_run_asserts().
406
        $modules = array('forum', 'assign');
407
        // Run all tests.
408
        foreach ($modules as $modulename) {
409
            $this->create_specific_module_test($modulename);
410
        }
411
    }
412
 
413
    /**
414
     * Test update_module() for multiple modules defined in the $modules array (first declaration of the function).
415
     */
11 efrain 416
    public function test_update_module(): void {
1 efrain 417
        // Add the module name you want to test here.
418
        // Create the match MODULENAME_update_set_values() and MODULENAME_update_run_asserts().
419
        $modules = array('forum');
420
        // Run all tests.
421
        foreach ($modules as $modulename) {
422
            $this->update_specific_module_test($modulename);
423
        }
424
    }
425
 
426
    /**
427
     * Set forum specific test values for calling update_module().
428
     *
429
     * @param object $moduleinfo - the moduleinfo to add some specific values - passed in reference.
430
     */
431
    private function forum_update_set_values(&$moduleinfo) {
432
        // Completion specific to forum - optional.
433
        $moduleinfo->completionposts = 3;
434
        $moduleinfo->completiondiscussions = 1;
435
        $moduleinfo->completionreplies = 2;
436
 
437
        // Specific values to the Forum module.
438
        $moduleinfo->forcesubscribe = FORUM_INITIALSUBSCRIBE;
439
        $moduleinfo->type = 'single';
440
        $moduleinfo->trackingtype = FORUM_TRACKING_FORCED;
441
        $moduleinfo->maxbytes = 10240;
442
        $moduleinfo->maxattachments = 2;
443
 
444
        // Post threshold for blocking - specific to forum.
445
        $moduleinfo->blockperiod = 60*60*24;
446
        $moduleinfo->blockafter = 10;
447
        $moduleinfo->warnafter = 5;
448
 
449
        // Grading of whole forum settings.
450
        $moduleinfo->grade_forum = 0;
451
    }
452
 
453
    /**
454
     * Execute test asserts on the saved DB data by update_module($forum).
455
     *
456
     * @param object $moduleinfo - the specific forum values that were used to update a forum.
457
     * @param object $dbmodinstance - the DB values of the updated forum.
458
     */
459
    private function forum_update_run_asserts($moduleinfo, $dbmodinstance) {
460
        // Compare values specific to forums.
461
        $this->assertEquals($moduleinfo->forcesubscribe, $dbmodinstance->forcesubscribe);
462
        $this->assertEquals($moduleinfo->type, $dbmodinstance->type);
463
        $this->assertEquals($moduleinfo->assessed, $dbmodinstance->assessed);
464
        $this->assertEquals($moduleinfo->completionposts, $dbmodinstance->completionposts);
465
        $this->assertEquals($moduleinfo->completiondiscussions, $dbmodinstance->completiondiscussions);
466
        $this->assertEquals($moduleinfo->completionreplies, $dbmodinstance->completionreplies);
467
        $this->assertEquals($moduleinfo->scale, $dbmodinstance->scale);
468
        $this->assertEquals($moduleinfo->assesstimestart, $dbmodinstance->assesstimestart);
469
        $this->assertEquals($moduleinfo->assesstimefinish, $dbmodinstance->assesstimefinish);
470
        $this->assertEquals($moduleinfo->rsstype, $dbmodinstance->rsstype);
471
        $this->assertEquals($moduleinfo->rssarticles, $dbmodinstance->rssarticles);
472
        $this->assertEquals($moduleinfo->trackingtype, $dbmodinstance->trackingtype);
473
        $this->assertEquals($moduleinfo->maxbytes, $dbmodinstance->maxbytes);
474
        $this->assertEquals($moduleinfo->maxattachments, $dbmodinstance->maxattachments);
475
        $this->assertEquals($moduleinfo->blockperiod, $dbmodinstance->blockperiod);
476
        $this->assertEquals($moduleinfo->blockafter, $dbmodinstance->blockafter);
477
        $this->assertEquals($moduleinfo->warnafter, $dbmodinstance->warnafter);
478
    }
479
 
480
 
481
 
482
    /**
483
     * Test a specific type of module.
484
     *
485
     * @param string $modulename - the module name to test
486
     */
487
    private function update_specific_module_test($modulename) {
488
        global $DB, $CFG;
489
 
490
        $this->resetAfterTest(true);
491
 
492
        $this->setAdminUser();
493
 
494
        // Warnings: you'll need to change this line if ever you come to test a module not following Moodle standard.
495
        require_once($CFG->dirroot.'/mod/'. $modulename .'/lib.php');
496
 
497
        // Enable avaibility.
498
        // If not enabled all conditional fields will be ignored.
499
        set_config('enableavailability', 1);
500
 
501
        // Enable course completion.
502
        // If not enabled all completion settings will be ignored.
503
        set_config('enablecompletion', COMPLETION_ENABLED);
504
 
505
        // Enable forum RSS feeds.
506
        set_config('enablerssfeeds', 1);
507
        set_config('forum_enablerssfeeds', 1);
508
 
509
        $course = $this->getDataGenerator()->create_course(array('numsections'=>1, 'enablecompletion' => COMPLETION_ENABLED),
510
           array('createsections'=>true));
511
 
512
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
513
 
514
        // Create assign module instance for testing gradeitem.
515
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
516
        $params['course'] = $course->id;
517
        $instance = $generator->create_instance($params);
518
        $assigncm = get_coursemodule_from_instance('assign', $instance->id);
519
 
520
        // Create the test forum to update.
521
        $initvalues = new stdClass();
522
        $initvalues->introformat = FORMAT_HTML;
523
        $initvalues->course = $course->id;
524
        $forum = self::getDataGenerator()->create_module('forum', $initvalues);
525
 
526
        // Retrieve course module.
527
        $cm = get_coursemodule_from_instance('forum', $forum->id);
528
 
529
        // Module test values.
530
        $moduleinfo = new stdClass();
531
 
532
        // Always mandatory generic values to any module.
533
        $moduleinfo->coursemodule = $cm->id;
534
        $moduleinfo->modulename = $modulename;
535
        $moduleinfo->course = $course->id;
536
        $moduleinfo->groupingid = $grouping->id;
537
        $moduleinfo->visible = true;
538
        $moduleinfo->visibleoncoursepage = true;
539
 
540
        // Sometimes optional generic values for some modules.
541
        $moduleinfo->name = 'My test module';
542
        $moduleinfo->showdescription = 1; // standard boolean
543
        require_once($CFG->libdir . '/gradelib.php');
544
        $gradecats = grade_get_categories_menu($moduleinfo->course, false);
545
        $gradecatid = current(array_keys($gradecats)); // Retrieve the first key of $gradecats
546
        $moduleinfo->gradecat = $gradecatid;
547
        $moduleinfo->groupmode = VISIBLEGROUPS;
548
        $moduleinfo->cmidnumber = 'idnumber_XXX';
549
 
550
        // Completion common to all module.
551
        $moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
552
        $moduleinfo->completionview = COMPLETION_VIEW_REQUIRED;
553
        $moduleinfo->completiongradeitemnumber = 1;
554
        $moduleinfo->completionpassgrade = 0;
555
        $moduleinfo->completionexpected = time() + (7 * 24 * 3600);
556
        $moduleinfo->completionunlocked = 1;
557
 
558
        // Conditional activity.
559
        $coursegradeitem = grade_item::fetch_course_item($moduleinfo->course); //the activity will become available only when the user reach some grade into the course itself.
560
        $moduleinfo->availability = json_encode(\core_availability\tree::get_root_json(
561
                array(\availability_date\condition::get_json('>=', time()),
562
                \availability_date\condition::get_json('<', time() + (7 * 24 * 3600)),
563
                \availability_grade\condition::get_json($coursegradeitem->id, 10, 80),
564
                \availability_profile\condition::get_json(false, 'email', 'contains', '@'),
565
                \availability_completion\condition::get_json($assigncm->id, COMPLETION_COMPLETE)), '&'));
566
 
567
        // Grading and Advanced grading.
568
        require_once($CFG->dirroot . '/rating/lib.php');
569
        $moduleinfo->assessed = RATING_AGGREGATE_AVERAGE;
570
        $moduleinfo->scale = 10; // Note: it could be minus (for specific course scale). It is a signed number.
571
        $moduleinfo->assesstimestart = time();
572
        $moduleinfo->assesstimefinish = time() + (7 * 24 * 3600);
573
 
574
        // RSS.
575
        $moduleinfo->rsstype = 2;
576
        $moduleinfo->rssarticles = 10;
577
 
578
        // Optional intro editor (depends of module).
579
        $draftid_editor = 0;
580
        file_prepare_draft_area($draftid_editor, null, null, null, null);
581
        $moduleinfo->introeditor = array('text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftid_editor);
582
 
583
        // Following is the advanced grading method area called 'submissions' for the 'assign' module.
584
        if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
585
            $moduleinfo->grade = 100;
586
        }
587
        // Plagiarism form values.
588
        // No plagiarism plugin installed by default. Use this space to make your own test.
589
 
590
        // Values specific to the module.
591
        $modulesetvalues = $modulename.'_update_set_values';
592
        $this->$modulesetvalues($moduleinfo);
593
 
594
        // Create the module.
595
        $result = update_module($moduleinfo);
596
 
597
        // Retrieve the module info.
598
        $dbmodinstance = $DB->get_record($moduleinfo->modulename, array('id' => $result->instance));
599
        $dbcm = get_coursemodule_from_instance($moduleinfo->modulename, $result->instance);
600
        // Retrieve the grade item.
601
        $gradeitem = $DB->get_record('grade_items', array('courseid' => $moduleinfo->course,
602
            'iteminstance' => $dbmodinstance->id, 'itemmodule' => $moduleinfo->modulename));
603
 
604
        // Compare the values common to all module instances.
605
        $this->assertEquals($moduleinfo->modulename, $dbcm->modname);
606
        $this->assertEquals($moduleinfo->course, $dbcm->course);
607
        $this->assertEquals($moduleinfo->groupingid, $dbcm->groupingid);
608
        $this->assertEquals($moduleinfo->visible, $dbcm->visible);
609
        $this->assertEquals($moduleinfo->completion, $dbcm->completion);
610
        $this->assertEquals($moduleinfo->completionview, $dbcm->completionview);
611
        $this->assertEquals($moduleinfo->completiongradeitemnumber, $dbcm->completiongradeitemnumber);
612
        $this->assertEquals($moduleinfo->completionpassgrade, $dbcm->completionpassgrade);
613
        $this->assertEquals($moduleinfo->completionexpected, $dbcm->completionexpected);
614
        $this->assertEquals($moduleinfo->availability, $dbcm->availability);
615
        $this->assertEquals($moduleinfo->showdescription, $dbcm->showdescription);
616
        $this->assertEquals($moduleinfo->groupmode, $dbcm->groupmode);
617
        $this->assertEquals($moduleinfo->cmidnumber, $dbcm->idnumber);
618
        $this->assertEquals($moduleinfo->gradecat, $gradeitem->categoryid);
619
 
620
        // Optional grade testing.
621
        if (plugin_supports('mod', $modulename, FEATURE_GRADE_HAS_GRADE, false) && !plugin_supports('mod', $modulename, FEATURE_RATE, false)) {
622
            $this->assertEquals($moduleinfo->grade, $dbmodinstance->grade);
623
        }
624
 
625
        // Some optional (but quite common) to some module.
626
        $this->assertEquals($moduleinfo->name, $dbmodinstance->name);
627
        $this->assertEquals($moduleinfo->intro, $dbmodinstance->intro);
628
        $this->assertEquals($moduleinfo->introformat, $dbmodinstance->introformat);
629
 
630
        // Test specific to the module.
631
        $modulerunasserts = $modulename.'_update_run_asserts';
632
        $this->$modulerunasserts($moduleinfo, $dbmodinstance);
633
        return $moduleinfo;
634
   }
635
 
636
    /**
637
     * Data provider for course_delete module
638
     *
639
     * @return array An array of arrays contain test data
640
     */
1441 ariadna 641
    public static function provider_course_delete_module(): array {
1 efrain 642
        $data = array();
643
 
644
        $data['assign'] = array('assign', array('duedate' => time()));
645
        $data['quiz'] = array('quiz', array('duedate' => time()));
646
 
647
        return $data;
648
    }
649
 
650
    /**
651
     * Test the create_course function
652
     */
11 efrain 653
    public function test_create_course(): void {
1 efrain 654
        global $DB;
655
        $this->resetAfterTest(true);
656
        $defaultcategory = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
657
 
658
        $course = new stdClass();
659
        $course->fullname = 'Apu loves Unit Təsts';
660
        $course->shortname = 'Spread the lŭve';
661
        $course->idnumber = '123';
662
        $course->summary = 'Awesome!';
663
        $course->summaryformat = FORMAT_PLAIN;
664
        $course->format = 'topics';
665
        $course->newsitems = 0;
666
        $course->category = $defaultcategory;
667
        $original = (array) $course;
668
 
669
        $created = create_course($course);
670
        $context = context_course::instance($created->id);
671
 
672
        // Compare original and created.
673
        $this->assertEquals($original, array_intersect_key((array) $created, $original));
674
 
675
        // Ensure default section is created.
676
        $sectioncreated = $DB->record_exists('course_sections', array('course' => $created->id, 'section' => 0));
677
        $this->assertTrue($sectioncreated);
678
 
679
        // Ensure that the shortname isn't duplicated.
680
        try {
681
            $created = create_course($course);
682
            $this->fail('Exception expected');
683
        } catch (moodle_exception $e) {
684
            $this->assertSame(get_string('shortnametaken', 'error', $course->shortname), $e->getMessage());
685
        }
686
 
687
        // Ensure that the idnumber isn't duplicated.
688
        $course->shortname .= '1';
689
        try {
690
            $created = create_course($course);
691
            $this->fail('Exception expected');
692
        } catch (moodle_exception $e) {
693
            $this->assertSame(get_string('courseidnumbertaken', 'error', $course->idnumber), $e->getMessage());
694
        }
695
    }
696
 
11 efrain 697
    public function test_create_course_with_generator(): void {
1 efrain 698
        global $DB;
699
        $this->resetAfterTest(true);
700
        $course = $this->getDataGenerator()->create_course();
701
 
702
        // Ensure default section is created.
703
        $sectioncreated = $DB->record_exists('course_sections', array('course' => $course->id, 'section' => 0));
704
        $this->assertTrue($sectioncreated);
705
    }
706
 
11 efrain 707
    public function test_create_course_sections(): void {
1 efrain 708
        global $DB;
709
        $this->resetAfterTest(true);
710
 
711
        $numsections = 5;
712
        $course = $this->getDataGenerator()->create_course(
713
                array('shortname' => 'GrowingCourse',
714
                    'fullname' => 'Growing Course',
715
                    'numsections' => $numsections),
716
                array('createsections' => true));
717
 
718
        // Ensure all 6 (0-5) sections were created and course content cache works properly
719
        $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
720
        $this->assertEquals(range(0, $numsections), $sectionscreated);
721
 
722
        // this will do nothing, section already exists
723
        $this->assertFalse(course_create_sections_if_missing($course, $numsections));
724
 
725
        // this will create new section
726
        $this->assertTrue(course_create_sections_if_missing($course, $numsections + 1));
727
 
728
        // Ensure all 7 (0-6) sections were created and modinfo/sectioninfo cache works properly
729
        $sectionscreated = array_keys(get_fast_modinfo($course)->get_section_info_all());
730
        $this->assertEquals(range(0, $numsections + 1), $sectionscreated);
731
    }
732
 
11 efrain 733
    public function test_update_course(): void {
1 efrain 734
        global $DB;
735
 
736
        $this->resetAfterTest();
737
 
738
        $defaultcategory = $DB->get_field_select('course_categories', 'MIN(id)', 'parent = 0');
739
 
740
        $course = new stdClass();
741
        $course->fullname = 'Apu loves Unit Təsts';
742
        $course->shortname = 'test1';
743
        $course->idnumber = '1';
744
        $course->summary = 'Awesome!';
745
        $course->summaryformat = FORMAT_PLAIN;
746
        $course->format = 'topics';
747
        $course->newsitems = 0;
748
        $course->numsections = 5;
749
        $course->category = $defaultcategory;
750
 
751
        $created = create_course($course);
752
        // Ensure the checks only work on idnumber/shortname that are not already ours.
753
        update_course($created);
754
 
755
        $course->shortname = 'test2';
756
        $course->idnumber = '2';
757
 
758
        $created2 = create_course($course);
759
 
760
        // Test duplicate idnumber.
761
        $created2->idnumber = '1';
762
        try {
763
            update_course($created2);
764
            $this->fail('Expected exception when trying to update a course with duplicate idnumber');
765
        } catch (moodle_exception $e) {
766
            $this->assertEquals(get_string('courseidnumbertaken', 'error', $created2->idnumber), $e->getMessage());
767
        }
768
 
769
        // Test duplicate shortname.
770
        $created2->idnumber = '2';
771
        $created2->shortname = 'test1';
772
        try {
773
            update_course($created2);
774
            $this->fail('Expected exception when trying to update a course with a duplicate shortname');
775
        } catch (moodle_exception $e) {
776
            $this->assertEquals(get_string('shortnametaken', 'error', $created2->shortname), $e->getMessage());
777
        }
778
    }
779
 
11 efrain 780
    public function test_update_course_section_time_modified(): void {
1 efrain 781
        global $DB;
782
 
783
        $this->resetAfterTest();
784
 
785
        // Create the course with sections.
786
        $course = $this->getDataGenerator()->create_course(
787
            ['numsections' => 10],
788
            ['createsections' => true]
789
        );
790
        $sections = $DB->get_records('course_sections', ['course' => $course->id]);
791
 
792
        // Get the last section's time modified value.
793
        $section = array_pop($sections);
794
        $oldtimemodified = $section->timemodified;
795
 
796
        // Ensuring that the section update occurs at a different timestamp.
797
        $this->waitForSecond();
798
 
799
        // The timemodified should only be updated if the section is actually updated.
800
        course_update_section($course, $section, []);
801
        $sectionrecord = $DB->get_record('course_sections', ['id' => $section->id]);
802
        $this->assertEquals($oldtimemodified, $sectionrecord->timemodified);
803
 
804
        // Now update something to prove timemodified changes.
805
        course_update_section($course, $section, ['name' => 'New name']);
806
        $section = $DB->get_record('course_sections', ['id' => $section->id]);
807
        $newtimemodified = $section->timemodified;
808
        $this->assertGreaterThan($oldtimemodified, $newtimemodified);
809
    }
810
 
811
    /**
812
     * Relative dates mode settings provider for course creation.
813
     */
1441 ariadna 814
    public static function create_course_relative_dates_provider(): array {
1 efrain 815
        return [
816
            [0, 0, 0],
817
            [0, 1, 0],
818
            [1, 0, 0],
819
            [1, 1, 1],
820
        ];
821
    }
822
 
823
    /**
824
     * Test create_course by attempting to change the relative dates mode.
825
     *
826
     * @dataProvider create_course_relative_dates_provider
827
     * @param int $setting The value for the 'enablecourserelativedates' admin setting.
828
     * @param int $mode The value for the course's 'relativedatesmode' field.
829
     * @param int $expectedvalue The expected value of the 'relativedatesmode' field after course creation.
830
     */
11 efrain 831
    public function test_relative_dates_mode_for_course_creation($setting, $mode, $expectedvalue): void {
1 efrain 832
        global $DB;
833
 
834
        $this->resetAfterTest();
835
 
836
        set_config('enablecourserelativedates', $setting);
837
 
838
        // Generate a course with relative dates mode set to $mode.
839
        $course = $this->getDataGenerator()->create_course(['relativedatesmode' => $mode]);
840
 
841
        // Verify that the relative dates match what's expected.
842
        $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
843
        $this->assertEquals($expectedvalue, $relativedatesmode);
844
    }
845
 
846
    /**
847
     * Test update_course by attempting to change the relative dates mode.
848
     */
11 efrain 849
    public function test_relative_dates_mode_for_course_update(): void {
1 efrain 850
        global $DB;
851
 
852
        $this->resetAfterTest();
853
 
854
        set_config('enablecourserelativedates', 1);
855
 
856
        // Generate a course with relative dates mode set to 1.
857
        $course = $this->getDataGenerator()->create_course(['relativedatesmode' => 1]);
858
 
859
        // Attempt to update the course with a changed relativedatesmode.
860
        $course->relativedatesmode = 0;
861
        update_course($course);
862
 
863
        // Verify that the relative dates mode has not changed.
864
        $relativedatesmode = $DB->get_field('course', 'relativedatesmode', ['id' => $course->id]);
865
        $this->assertEquals(1, $relativedatesmode);
866
    }
867
 
11 efrain 868
    public function test_course_add_cm_to_section(): void {
1 efrain 869
        global $DB;
870
        $this->resetAfterTest(true);
871
 
872
        // Create course with 1 section.
873
        $course = $this->getDataGenerator()->create_course(
874
                array('shortname' => 'GrowingCourse',
875
                    'fullname' => 'Growing Course',
876
                    'numsections' => 1),
877
                array('createsections' => true));
878
 
879
        // Trash modinfo.
880
        rebuild_course_cache($course->id, true);
881
 
882
        // Create some cms for testing.
1441 ariadna 883
        $mods = $DB->get_records('modules');
884
        $mod = reset($mods);
1 efrain 885
        $cmids = array();
886
        for ($i=0; $i<4; $i++) {
1441 ariadna 887
            $cmids[$i] = $DB->insert_record('course_modules', ['course' => $course->id, 'module' => $mod->id]);
1 efrain 888
        }
889
 
890
        // Add it to section that exists.
1441 ariadna 891
        course_add_cm_to_section($course, $cmids[0], 1, null, $mod->name);
1 efrain 892
 
893
        // Check it got added to sequence.
894
        $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
895
        $this->assertEquals($cmids[0], $sequence);
896
 
897
        // Add a second, this time using courseid variant of parameters.
898
        $coursecacherev = $DB->get_field('course', 'cacherev', array('id' => $course->id));
1441 ariadna 899
        course_add_cm_to_section($course->id, $cmids[1], 1, null, $mod->name);
1 efrain 900
        $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 1));
901
        $this->assertEquals($cmids[0] . ',' . $cmids[1], $sequence);
902
 
903
        // Check that modinfo cache was reset but not rebuilt (important for performance if calling repeatedly).
904
        $newcacherev = $DB->get_field('course', 'cacherev', ['id' => $course->id]);
905
        $this->assertGreaterThan($coursecacherev, $newcacherev);
906
        $this->assertEmpty(cache::make('core', 'coursemodinfo')->get_versioned($course->id, $newcacherev));
907
 
908
        // Add one to section that doesn't exist (this might rebuild modinfo).
1441 ariadna 909
        course_add_cm_to_section($course, $cmids[2], 2, null, $mod->name);
1 efrain 910
        $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
911
        $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
912
        $this->assertEquals($cmids[2], $sequence);
913
 
914
        // Add using the 'before' option.
1441 ariadna 915
        course_add_cm_to_section($course, $cmids[3], 2, $cmids[2], $mod->name);
1 efrain 916
        $this->assertEquals(3, $DB->count_records('course_sections', array('course' => $course->id)));
917
        $sequence = $DB->get_field('course_sections', 'sequence', array('course' => $course->id, 'section' => 2));
918
        $this->assertEquals($cmids[3] . ',' . $cmids[2], $sequence);
919
    }
920
 
1441 ariadna 921
    /**
922
     * Module types that have FEATURE_CAN_DISPLAY flag set to false cannot be in any section other than 0.
923
     *
924
     * @return void
925
     * @covers ::course_add_cm_to_section()
926
     */
927
    public function test_add_non_display_types_to_cm_section(): void {
928
        global $DB;
929
 
930
        $this->resetAfterTest(true);
931
        $generator = self::getDataGenerator();
932
 
933
        // Create course with 1 section.
934
        $course = self::getDataGenerator()->create_course(
935
            [
936
                'shortname' => 'GrowingCourse',
937
                'fullname' => 'Growing Course',
938
                'numsections' => 1,
939
            ],
940
            ['createsections' => true]
941
        );
942
 
943
        // Create the module and assert in section 0.
944
        $sectionzero = $DB->get_record('course_sections', ['course' => $course->id, 'section' => 0], '*', MUST_EXIST);
945
        $module = $generator->create_module('qbank', ['course' => $course, 'section' => $sectionzero->section]);
946
 
947
        // Try to add to section 1.
948
        $this->expectExceptionMessage("Modules with FEATURE_CAN_DISPLAY set to false can not be moved from section 0");
949
 
950
        try {
951
            course_add_cm_to_section($course, $module->cmid, 1, null, 'qbank');
952
        } finally {
953
            // Assert still in section 0.
954
            $cm = $DB->get_record('course_modules', ['id' => $module->cmid]);
955
            $modsection = $DB->get_record('course_sections', ['id' => $cm->section]);
956
            $this->assertEquals($sectionzero->section, $modsection->section);
957
        }
958
    }
959
 
11 efrain 960
    public function test_reorder_sections(): void {
1 efrain 961
        global $DB;
962
        $this->resetAfterTest(true);
963
 
964
        $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
965
        $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
966
        $oldsections = array();
967
        $sections = array();
968
        foreach ($DB->get_records('course_sections', array('course'=>$course->id), 'id') as $section) {
969
            $oldsections[$section->section] = $section->id;
970
            $sections[$section->id] = $section->section;
971
        }
972
        ksort($oldsections);
973
 
974
        $neworder = reorder_sections($sections, 2, 4);
975
        $neworder = array_keys($neworder);
976
        $this->assertEquals($oldsections[0], $neworder[0]);
977
        $this->assertEquals($oldsections[1], $neworder[1]);
978
        $this->assertEquals($oldsections[2], $neworder[4]);
979
        $this->assertEquals($oldsections[3], $neworder[2]);
980
        $this->assertEquals($oldsections[4], $neworder[3]);
981
        $this->assertEquals($oldsections[5], $neworder[5]);
982
        $this->assertEquals($oldsections[6], $neworder[6]);
983
 
984
        $neworder = reorder_sections($sections, 4, 2);
985
        $neworder = array_keys($neworder);
986
        $this->assertEquals($oldsections[0], $neworder[0]);
987
        $this->assertEquals($oldsections[1], $neworder[1]);
988
        $this->assertEquals($oldsections[2], $neworder[3]);
989
        $this->assertEquals($oldsections[3], $neworder[4]);
990
        $this->assertEquals($oldsections[4], $neworder[2]);
991
        $this->assertEquals($oldsections[5], $neworder[5]);
992
        $this->assertEquals($oldsections[6], $neworder[6]);
993
 
994
        $neworder = reorder_sections(1, 2, 4);
995
        $this->assertFalse($neworder);
996
    }
997
 
11 efrain 998
    public function test_move_section_down(): void {
1 efrain 999
        global $DB;
1000
        $this->resetAfterTest(true);
1001
 
1002
        $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
1003
        $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
1004
        $oldsections = array();
1005
        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
1006
            $oldsections[$section->section] = $section->id;
1007
        }
1008
        ksort($oldsections);
1009
 
1010
        // Test move section down..
1011
        move_section_to($course, 2, 4);
1012
        $sections = array();
1013
        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
1014
            $sections[$section->section] = $section->id;
1015
        }
1016
        ksort($sections);
1017
 
1018
        $this->assertEquals($oldsections[0], $sections[0]);
1019
        $this->assertEquals($oldsections[1], $sections[1]);
1020
        $this->assertEquals($oldsections[2], $sections[4]);
1021
        $this->assertEquals($oldsections[3], $sections[2]);
1022
        $this->assertEquals($oldsections[4], $sections[3]);
1023
        $this->assertEquals($oldsections[5], $sections[5]);
1024
        $this->assertEquals($oldsections[6], $sections[6]);
1025
    }
1026
 
11 efrain 1027
    public function test_move_section_up(): void {
1 efrain 1028
        global $DB;
1029
        $this->resetAfterTest(true);
1030
 
1031
        $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
1032
        $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
1033
        $oldsections = array();
1034
        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
1035
            $oldsections[$section->section] = $section->id;
1036
        }
1037
        ksort($oldsections);
1038
 
1039
        // Test move section up..
1040
        move_section_to($course, 6, 4);
1041
        $sections = array();
1042
        foreach ($DB->get_records('course_sections', array('course'=>$course->id)) as $section) {
1043
            $sections[$section->section] = $section->id;
1044
        }
1045
        ksort($sections);
1046
 
1047
        $this->assertEquals($oldsections[0], $sections[0]);
1048
        $this->assertEquals($oldsections[1], $sections[1]);
1049
        $this->assertEquals($oldsections[2], $sections[2]);
1050
        $this->assertEquals($oldsections[3], $sections[3]);
1051
        $this->assertEquals($oldsections[4], $sections[5]);
1052
        $this->assertEquals($oldsections[5], $sections[6]);
1053
        $this->assertEquals($oldsections[6], $sections[4]);
1054
    }
1055
 
11 efrain 1056
    public function test_move_section_marker(): void {
1 efrain 1057
        global $DB;
1058
        $this->resetAfterTest(true);
1059
 
1060
        $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections'=>true));
1061
        $course = $this->getDataGenerator()->create_course(array('numsections'=>10), array('createsections'=>true));
1062
 
1063
        // Set course marker to the section we are going to move..
1064
        course_set_marker($course->id, 2);
1065
        // Verify that the course marker is set correctly.
1066
        $course = $DB->get_record('course', array('id' => $course->id));
1067
        $this->assertEquals(2, $course->marker);
1068
 
1069
        // Test move the marked section down..
1070
        move_section_to($course, 2, 4);
1071
 
1072
        // Verify that the course marker has been moved along with the section..
1073
        $course = $DB->get_record('course', array('id' => $course->id));
1074
        $this->assertEquals(4, $course->marker);
1075
 
1076
        // Test move the marked section up..
1077
        move_section_to($course, 4, 3);
1078
 
1079
        // Verify that the course marker has been moved along with the section..
1080
        $course = $DB->get_record('course', array('id' => $course->id));
1081
        $this->assertEquals(3, $course->marker);
1082
 
1083
        // Test moving a non-marked section above the marked section..
1084
        move_section_to($course, 4, 2);
1085
 
1086
        // Verify that the course marker has been moved down to accomodate..
1087
        $course = $DB->get_record('course', array('id' => $course->id));
1088
        $this->assertEquals(4, $course->marker);
1089
 
1090
        // Test moving a non-marked section below the marked section..
1091
        move_section_to($course, 3, 6);
1092
 
1093
        // Verify that the course marker has been up to accomodate..
1094
        $course = $DB->get_record('course', array('id' => $course->id));
1095
        $this->assertEquals(3, $course->marker);
1096
    }
1097
 
1098
    /**
1099
     * Test move_section_to method with caching
1100
     *
1101
     * @covers ::move_section_to
1102
     * @return void
1103
     */
1104
    public function test_move_section_with_section_cache(): void {
1105
        $this->resetAfterTest();
1106
        $this->setAdminUser();
1107
        $cache = cache::make('core', 'coursemodinfo');
1108
 
1109
        // Generate the course and pre-requisite module.
1110
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
1111
        // Reset course cache.
1112
        rebuild_course_cache($course->id, true);
1113
 
1114
        // Build course cache.
1115
        $modinfo = get_fast_modinfo($course->id);
1116
        // Get the course modinfo cache.
1117
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1118
        // Get the section cache.
1119
        $sectioncaches = $coursemodinfo->sectioncache;
1120
 
1121
        $numberedsections = $modinfo->get_section_info_all();
1122
 
1123
        // Make sure that we will have 4 section caches here.
1124
        $this->assertCount(4, $sectioncaches);
1125
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1126
        $this->assertArrayHasKey($numberedsections[1]->id, $sectioncaches);
1127
        $this->assertArrayHasKey($numberedsections[2]->id, $sectioncaches);
1128
        $this->assertArrayHasKey($numberedsections[3]->id, $sectioncaches);
1129
 
1130
        // Move section.
1131
        move_section_to($course, 2, 3);
1132
        // Get the course modinfo cache.
1133
        $coursemodinfo = $cache->get_versioned($course->id, $course->cacherev);
1134
        // Get the section cache.
1135
        $sectioncaches = $coursemodinfo->sectioncache;
1136
 
1137
        // Make sure that we will have 2 section caches left.
1138
        $this->assertCount(2, $sectioncaches);
1139
        $this->assertArrayHasKey($numberedsections[0]->id, $sectioncaches);
1140
        $this->assertArrayHasKey($numberedsections[1]->id, $sectioncaches);
1141
        $this->assertArrayNotHasKey($numberedsections[2]->id, $sectioncaches);
1142
        $this->assertArrayNotHasKey($numberedsections[3]->id, $sectioncaches);
1143
    }
1144
 
1145
    /**
1146
     * Test move_section_to method.
1147
     * Make sure that we only update the moving sections, not all the sections in the current course.
1148
     *
1149
     * @covers ::move_section_to
1150
     * @return void
1151
     */
1152
    public function test_move_section_to(): void {
1153
        global $DB, $CFG;
1154
        $this->resetAfterTest();
1155
        $this->setAdminUser();
1156
 
1157
        // Generate the course and pre-requisite module.
1158
        $course = $this->getDataGenerator()->create_course(['format' => 'topics', 'numsections' => 3], ['createsections' => true]);
1159
 
1160
        ob_start();
1161
        $DB->set_debug(true);
1162
        // Move section.
1163
        move_section_to($course, 2, 3);
1164
        $DB->set_debug(false);
1165
        $debuginfo = ob_get_contents();
1166
        ob_end_clean();
1167
        $sectionmovequerycount = substr_count($debuginfo, 'UPDATE ' . $CFG->phpunit_prefix . 'course_sections SET');
1168
        // We are updating the course_section table in steps to avoid breaking database uniqueness constraint.
1169
        // So the queries will be doubled. See: course/lib.php:1423
1170
        // Make sure that we only need 4 queries to update the position of section 2 and section 3.
1171
        $this->assertEquals(4, $sectionmovequerycount);
1172
    }
1173
 
11 efrain 1174
    public function test_course_can_delete_section(): void {
1 efrain 1175
        global $DB;
1176
        $this->resetAfterTest(true);
1177
 
1178
        $generator = $this->getDataGenerator();
1179
 
1180
        $courseweeks = $generator->create_course(
1181
            array('numsections' => 5, 'format' => 'weeks'),
1182
            array('createsections' => true));
1183
        $assign1 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 1));
1184
        $assign2 = $generator->create_module('assign', array('course' => $courseweeks, 'section' => 2));
1185
 
1186
        $coursetopics = $generator->create_course(
1187
            array('numsections' => 5, 'format' => 'topics'),
1188
            array('createsections' => true));
1189
 
1190
        $coursesingleactivity = $generator->create_course(
1191
            array('format' => 'singleactivity'),
1192
            array('createsections' => true));
1193
 
1194
        // Enrol student and teacher.
1195
        $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
1196
        $student = $generator->create_user();
1197
        $teacher = $generator->create_user();
1198
 
1199
        $generator->enrol_user($student->id, $courseweeks->id, $roleids['student']);
1200
        $generator->enrol_user($teacher->id, $courseweeks->id, $roleids['editingteacher']);
1201
 
1202
        $generator->enrol_user($student->id, $coursetopics->id, $roleids['student']);
1203
        $generator->enrol_user($teacher->id, $coursetopics->id, $roleids['editingteacher']);
1204
 
1205
        $generator->enrol_user($student->id, $coursesingleactivity->id, $roleids['student']);
1206
        $generator->enrol_user($teacher->id, $coursesingleactivity->id, $roleids['editingteacher']);
1207
 
1208
        // Teacher should be able to delete sections (except for 0) in topics and weeks format.
1209
        $this->setUser($teacher);
1210
 
1211
        // For topics and weeks formats will return false for section 0 and true for any other section.
1212
        $this->assertFalse(course_can_delete_section($courseweeks, 0));
1213
        $this->assertTrue(course_can_delete_section($courseweeks, 1));
1214
 
1215
        $this->assertFalse(course_can_delete_section($coursetopics, 0));
1216
        $this->assertTrue(course_can_delete_section($coursetopics, 1));
1217
 
1218
        // For singleactivity course format no section can be deleted.
1219
        $this->assertFalse(course_can_delete_section($coursesingleactivity, 0));
1220
        $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
1221
 
1222
        // Now let's revoke a capability from teacher to manage activity in section 1.
1223
        $modulecontext = context_module::instance($assign1->cmid);
1224
        assign_capability('moodle/course:manageactivities', CAP_PROHIBIT, $roleids['editingteacher'],
1225
            $modulecontext);
1226
        $this->assertFalse(course_can_delete_section($courseweeks, 1));
1227
        $this->assertTrue(course_can_delete_section($courseweeks, 2));
1228
 
1229
        // Student does not have permissions to delete sections.
1230
        $this->setUser($student);
1231
        $this->assertFalse(course_can_delete_section($courseweeks, 1));
1232
        $this->assertFalse(course_can_delete_section($coursetopics, 1));
1233
        $this->assertFalse(course_can_delete_section($coursesingleactivity, 1));
1234
    }
1235
 
11 efrain 1236
    public function test_course_delete_section(): void {
1 efrain 1237
        global $DB;
1238
        $this->resetAfterTest(true);
1239
 
1240
        $generator = $this->getDataGenerator();
1241
 
1242
        $course = $generator->create_course(array('numsections' => 6, 'format' => 'topics'),
1243
            array('createsections' => true));
1244
        $assign0 = $generator->create_module('assign', array('course' => $course, 'section' => 0));
1245
        $assign1 = $generator->create_module('assign', array('course' => $course, 'section' => 1));
1246
        $assign21 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
1247
        $assign22 = $generator->create_module('assign', array('course' => $course, 'section' => 2));
1248
        $assign3 = $generator->create_module('assign', array('course' => $course, 'section' => 3));
1249
        $assign5 = $generator->create_module('assign', array('course' => $course, 'section' => 5));
1250
        $assign6 = $generator->create_module('assign', array('course' => $course, 'section' => 6));
1251
 
1252
        $this->setAdminUser();
1253
 
1254
        // Attempt to delete non-existing section.
1255
        $this->assertFalse(course_delete_section($course, 10, false));
1256
        $this->assertFalse(course_delete_section($course, 9, true));
1257
 
1258
        // Attempt to delete 0-section.
1259
        $this->assertFalse(course_delete_section($course, 0, true));
1260
        $this->assertTrue($DB->record_exists('course_modules', array('id' => $assign0->cmid)));
1261
 
1262
        // Delete last section.
1263
        $this->assertTrue(course_delete_section($course, 6, true));
1264
        $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign6->cmid)));
1265
        $this->assertEquals(5, course_get_format($course)->get_last_section_number());
1266
 
1267
        // Delete empty section.
1268
        $this->assertTrue(course_delete_section($course, 4, false));
1269
        $this->assertEquals(4, course_get_format($course)->get_last_section_number());
1270
 
1271
        // Delete section in the middle (2).
1272
        $this->assertFalse(course_delete_section($course, 2, false));
1273
        $this->assertEquals(4, course_get_format($course)->get_last_section_number());
1274
        $this->assertTrue(course_delete_section($course, 2, true));
1275
        $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign21->cmid)));
1276
        $this->assertFalse($DB->record_exists('course_modules', array('id' => $assign22->cmid)));
1277
        $this->assertEquals(3, course_get_format($course)->get_last_section_number());
1278
        $this->assertEquals(array(0 => array($assign0->cmid),
1279
            1 => array($assign1->cmid),
1280
            2 => array($assign3->cmid),
1281
            3 => array($assign5->cmid)), get_fast_modinfo($course)->sections);
1282
 
1283
        // Remove marked section.
1284
        course_set_marker($course->id, 1);
1285
        $this->assertTrue(course_get_format($course)->is_section_current(1));
1286
        $this->assertTrue(course_delete_section($course, 1, true));
1287
        $this->assertFalse(course_get_format($course)->is_section_current(1));
1288
    }
1289
 
11 efrain 1290
    public function test_get_course_display_name_for_list(): void {
1 efrain 1291
        global $CFG;
1292
        $this->resetAfterTest(true);
1293
 
1294
        $course = $this->getDataGenerator()->create_course(array('shortname' => 'FROG101', 'fullname' => 'Introduction to pond life'));
1295
 
1296
        $CFG->courselistshortnames = 0;
1297
        $this->assertEquals('Introduction to pond life', get_course_display_name_for_list($course));
1298
 
1299
        $CFG->courselistshortnames = 1;
1300
        $this->assertEquals('FROG101 Introduction to pond life', get_course_display_name_for_list($course));
1301
    }
1302
 
11 efrain 1303
    public function test_move_module_in_course(): void {
1 efrain 1304
        global $DB;
1305
 
1306
        $this->resetAfterTest(true);
1307
        // Setup fixture
1308
        $course = $this->getDataGenerator()->create_course(array('numsections'=>5), array('createsections' => true));
1309
        $forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
1310
 
1311
        $cms = get_fast_modinfo($course)->get_cms();
1312
        $cm = reset($cms);
1313
 
1314
        $newsection = get_fast_modinfo($course)->get_section_info(3);
1315
        $oldsectionid = $cm->section;
1316
 
1317
        // Perform the move
1318
        moveto_module($cm, $newsection);
1319
 
1320
        $cms = get_fast_modinfo($course)->get_cms();
1321
        $cm = reset($cms);
1322
 
1323
        // Check that the cached modinfo contains the correct section info
1324
        $modinfo = get_fast_modinfo($course);
1325
        $this->assertTrue(empty($modinfo->sections[0]));
1326
        $this->assertFalse(empty($modinfo->sections[3]));
1327
 
1328
        // Check that the old section's sequence no longer contains this ID
1329
        $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
1330
        $oldsequences = explode(',', $newsection->sequence);
1331
        $this->assertFalse(in_array($cm->id, $oldsequences));
1332
 
1333
        // Check that the new section's sequence now contains this ID
1334
        $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
1335
        $newsequences = explode(',', $newsection->sequence);
1336
        $this->assertTrue(in_array($cm->id, $newsequences));
1337
 
1338
        // Check that the section number has been changed in the cm
1339
        $this->assertEquals($newsection->id, $cm->section);
1340
 
1341
 
1342
        // Perform a second move as some issues were only seen on the second move
1343
        $newsection = get_fast_modinfo($course)->get_section_info(2);
1344
        $oldsectionid = $cm->section;
1345
        moveto_module($cm, $newsection);
1346
 
1347
        $cms = get_fast_modinfo($course)->get_cms();
1348
        $cm = reset($cms);
1349
 
1350
        // Check that the cached modinfo contains the correct section info
1351
        $modinfo = get_fast_modinfo($course);
1352
        $this->assertTrue(empty($modinfo->sections[0]));
1353
        $this->assertFalse(empty($modinfo->sections[2]));
1354
 
1355
        // Check that the old section's sequence no longer contains this ID
1356
        $oldsection = $DB->get_record('course_sections', array('id' => $oldsectionid));
1357
        $oldsequences = explode(',', $newsection->sequence);
1358
        $this->assertFalse(in_array($cm->id, $oldsequences));
1359
 
1360
        // Check that the new section's sequence now contains this ID
1361
        $newsection = $DB->get_record('course_sections', array('id' => $newsection->id));
1362
        $newsequences = explode(',', $newsection->sequence);
1363
        $this->assertTrue(in_array($cm->id, $newsequences));
1364
    }
1365
 
1441 ariadna 1366
    /**
1367
     * Ensure that qbank module which has feature flag FEATURE_CAN_DISPLAY set to false cannot be moved from section 0.
1368
     *
1369
     * @return void
1370
     * @covers ::moveto_module()
1371
     */
1372
    public function test_move_feature_cannot_display(): void {
1373
        $this->resetAfterTest(true);
1374
        // Setup fixture.
1375
        $course = $this->getDataGenerator()->create_course(['numsections' => 5], ['createsections' => true]);
1376
        $qbank = $this->getDataGenerator()->create_module('qbank', ['course' => $course->id]);
1377
        $qbankcms = get_fast_modinfo($course)->get_instances_of('qbank');
1378
        $qbankcm = reset($qbankcms);
1379
 
1380
        // Check that mods with FEATURE_CAN_DISPLAY set to false cannot be moved from section 0.
1381
        $newsection = get_fast_modinfo($course)->get_section_info(3);
1382
 
1383
        $codingerror = "/ .* Modules with FEATURE_CAN_DISPLAY set to false can not be moved from section 0/";
1384
 
1385
        // Try to perform the move.
1386
        $this->expectExceptionMessageMatches($codingerror);
1387
        try {
1388
            moveto_module($qbankcm, $newsection);
1389
        } finally {
1390
            $qbankcms = get_fast_modinfo($course)->get_instances_of('qbank');
1391
            $qbankcm = reset($qbankcms);
1392
            $this->assertEquals(0, $qbankcm->sectionnum);
1393
        }
1394
    }
1395
 
11 efrain 1396
    public function test_module_visibility(): void {
1 efrain 1397
        $this->setAdminUser();
1398
        $this->resetAfterTest(true);
1399
 
1400
        // Create course and modules.
1401
        $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
1402
        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1403
        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(), 'course' => $course->id));
1404
        $modules = compact('forum', 'assign');
1405
 
1406
        // Hiding the modules.
1407
        foreach ($modules as $mod) {
1408
            set_coursemodule_visible($mod->cmid, 0);
1409
            $this->check_module_visibility($mod, 0, 0);
1410
        }
1411
 
1412
        // Showing the modules.
1413
        foreach ($modules as $mod) {
1414
            set_coursemodule_visible($mod->cmid, 1);
1415
            $this->check_module_visibility($mod, 1, 1);
1416
        }
1417
    }
1418
 
1419
    /**
1420
     * Test rebuildcache = false behaviour.
1421
     *
1422
     * When we pass rebuildcache = false to set_coursemodule_visible, the corusemodinfo cache will still contain
1423
     * the original visibility until we trigger a rebuild.
1424
     *
1425
     * @return void
1426
     * @covers ::set_coursemodule_visible
1427
     */
1428
    public function test_module_visibility_no_rebuild(): void {
1429
        $this->setAdminUser();
1430
        $this->resetAfterTest(true);
1431
 
1432
        // Create course and modules.
1433
        $course = $this->getDataGenerator()->create_course(['numsections' => 5]);
1434
        $forum = $this->getDataGenerator()->create_module('forum', ['course' => $course->id]);
1435
        $assign = $this->getDataGenerator()->create_module('assign', ['duedate' => time(), 'course' => $course->id]);
1436
        $modules = compact('forum', 'assign');
1437
 
1438
        // Hiding the modules.
1439
        foreach ($modules as $mod) {
1440
            set_coursemodule_visible($mod->cmid, 0, 1, false);
1441
            // The modinfo cache still has the original visibility until we manually trigger a rebuild.
1442
            $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
1443
            $this->assertEquals(1, $cm->visible);
1444
        }
1445
 
1446
        rebuild_course_cache($course->id);
1447
 
1448
        foreach ($modules as $mod) {
1449
            $this->check_module_visibility($mod, 0, 0);
1450
        }
1451
 
1452
        // Showing the modules.
1453
        foreach ($modules as $mod) {
1454
            set_coursemodule_visible($mod->cmid, 1, 1, false);
1455
            $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
1456
            $this->assertEquals(0, $cm->visible);
1457
        }
1458
 
1459
        rebuild_course_cache($course->id);
1460
 
1461
        foreach ($modules as $mod) {
1462
            $this->check_module_visibility($mod, 1, 1);
1463
        }
1464
    }
1465
 
11 efrain 1466
    public function test_section_visibility_events(): void {
1 efrain 1467
        $this->setAdminUser();
1468
        $this->resetAfterTest(true);
1469
 
1470
        $course = $this->getDataGenerator()->create_course(array('numsections' => 1), array('createsections' => true));
1471
        $sectionnumber = 1;
1472
        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1473
            array('section' => $sectionnumber));
1474
        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
1475
            'course' => $course->id), array('section' => $sectionnumber));
1476
        $sink = $this->redirectEvents();
1477
        set_section_visible($course->id, $sectionnumber, 0);
1478
        $events = $sink->get_events();
1479
 
1480
        // Extract the number of events related to what we are testing, other events
1481
        // such as course_section_updated could have been triggered.
1482
        $count = 0;
1483
        foreach ($events as $event) {
1484
            if ($event instanceof \core\event\course_module_updated) {
1485
                $count++;
1486
            }
1487
        }
1488
        $this->assertSame(2, $count);
1489
        $sink->close();
1490
    }
1491
 
11 efrain 1492
    public function test_section_visibility(): void {
1 efrain 1493
        $this->setAdminUser();
1494
        $this->resetAfterTest(true);
1495
 
1496
        // Create course.
1497
        $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
1498
 
1499
        $sink = $this->redirectEvents();
1500
 
1501
        // Testing an empty section.
1502
        $sectionnumber = 1;
1503
        set_section_visible($course->id, $sectionnumber, 0);
1504
        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1505
        $this->assertEquals($section_info->visible, 0);
1506
        set_section_visible($course->id, $sectionnumber, 1);
1507
        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1508
        $this->assertEquals($section_info->visible, 1);
1509
 
1510
        // Checking that an event was fired.
1511
        $events = $sink->get_events();
1512
        $this->assertInstanceOf('\core\event\course_section_updated', $events[0]);
1513
        $sink->close();
1514
 
1515
        // Testing a section with visible modules.
1516
        $sectionnumber = 2;
1517
        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1518
                array('section' => $sectionnumber));
1519
        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
1520
                'course' => $course->id), array('section' => $sectionnumber));
1521
        $modules = compact('forum', 'assign');
1522
        set_section_visible($course->id, $sectionnumber, 0);
1523
        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1524
        $this->assertEquals($section_info->visible, 0);
1525
        foreach ($modules as $mod) {
1526
            $this->check_module_visibility($mod, 0, 1);
1527
        }
1528
        set_section_visible($course->id, $sectionnumber, 1);
1529
        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1530
        $this->assertEquals($section_info->visible, 1);
1531
        foreach ($modules as $mod) {
1532
            $this->check_module_visibility($mod, 1, 1);
1533
        }
1534
 
1535
        // Testing a section with hidden modules, which should stay hidden.
1536
        $sectionnumber = 3;
1537
        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
1538
                array('section' => $sectionnumber));
1539
        $assign = $this->getDataGenerator()->create_module('assign', array('duedate' => time(),
1540
                'course' => $course->id), array('section' => $sectionnumber));
1541
        $modules = compact('forum', 'assign');
1542
        foreach ($modules as $mod) {
1543
            set_coursemodule_visible($mod->cmid, 0);
1544
            $this->check_module_visibility($mod, 0, 0);
1545
        }
1546
        set_section_visible($course->id, $sectionnumber, 0);
1547
        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1548
        $this->assertEquals($section_info->visible, 0);
1549
        foreach ($modules as $mod) {
1550
            $this->check_module_visibility($mod, 0, 0);
1551
        }
1552
        set_section_visible($course->id, $sectionnumber, 1);
1553
        $section_info = get_fast_modinfo($course->id)->get_section_info($sectionnumber);
1554
        $this->assertEquals($section_info->visible, 1);
1555
        foreach ($modules as $mod) {
1556
            $this->check_module_visibility($mod, 0, 0);
1557
        }
1558
    }
1559
 
1560
    /**
1561
     * Helper function to assert that a module has correctly been made visible, or hidden.
1562
     *
1563
     * @param stdClass $mod module information
1564
     * @param int $visibility the current state of the module
1565
     * @param int $visibleold the current state of the visibleold property
1566
     * @return void
1567
     */
1568
    public function check_module_visibility($mod, $visibility, $visibleold) {
1569
        global $DB;
1570
        $cm = get_fast_modinfo($mod->course)->get_cm($mod->cmid);
1571
        $this->assertEquals($visibility, $cm->visible);
1572
        $this->assertEquals($visibleold, $cm->visibleold);
1573
 
1574
        // Check the module grade items.
1575
        $grade_items = grade_item::fetch_all(array('itemtype' => 'mod', 'itemmodule' => $cm->modname,
1576
                'iteminstance' => $cm->instance, 'courseid' => $cm->course));
1577
        if ($grade_items) {
1578
            foreach ($grade_items as $grade_item) {
1579
                if ($visibility) {
1580
                    $this->assertFalse($grade_item->is_hidden(), "$cm->modname grade_item not visible");
1581
                } else {
1582
                    $this->assertTrue($grade_item->is_hidden(), "$cm->modname grade_item not hidden");
1583
                }
1584
            }
1585
        }
1586
 
1587
        // Check the events visibility.
1588
        if ($events = $DB->get_records('event', array('instance' => $cm->instance, 'modulename' => $cm->modname))) {
1589
            foreach ($events as $event) {
1590
                $calevent = new calendar_event($event);
1591
                $this->assertEquals($visibility, $calevent->visible, "$cm->modname calendar_event visibility");
1592
            }
1593
        }
1594
    }
1595
 
11 efrain 1596
    public function test_course_page_type_list(): void {
1 efrain 1597
        global $DB;
1598
        $this->resetAfterTest(true);
1599
 
1600
        // Create a category.
1601
        $category = new stdClass();
1602
        $category->name = 'Test Category';
1603
 
1604
        $testcategory = $this->getDataGenerator()->create_category($category);
1605
 
1606
        // Create a course.
1607
        $course = new stdClass();
1608
        $course->fullname = 'Apu loves Unit Təsts';
1609
        $course->shortname = 'Spread the lŭve';
1610
        $course->idnumber = '123';
1611
        $course->summary = 'Awesome!';
1612
        $course->summaryformat = FORMAT_PLAIN;
1613
        $course->format = 'topics';
1614
        $course->newsitems = 0;
1615
        $course->numsections = 5;
1616
        $course->category = $testcategory->id;
1617
 
1618
        $testcourse = $this->getDataGenerator()->create_course($course);
1619
 
1620
        // Create contexts.
1621
        $coursecontext = context_course::instance($testcourse->id);
1622
        $parentcontext = $coursecontext->get_parent_context(); // Not actually used.
1623
        $pagetype = 'page-course-x'; // Not used either.
1624
        $pagetypelist = course_page_type_list($pagetype, $parentcontext, $coursecontext);
1625
 
1626
        // Page type lists for normal courses.
1627
        $testpagetypelist1 = array();
1628
        $testpagetypelist1['*'] = 'Any page';
1629
        $testpagetypelist1['course-*'] = 'Any course page';
1630
        $testpagetypelist1['course-view-*'] = 'Any type of course main page';
1631
 
1632
        $this->assertEquals($testpagetypelist1, $pagetypelist);
1633
 
1634
        // Get the context for the front page course.
1635
        $sitecoursecontext = context_course::instance(SITEID);
1636
        $pagetypelist = course_page_type_list($pagetype, $parentcontext, $sitecoursecontext);
1637
 
1638
        // Page type list for the front page course.
1639
        $testpagetypelist2 = array('*' => 'Any page');
1640
        $this->assertEquals($testpagetypelist2, $pagetypelist);
1641
 
1642
        // Make sure that providing no current context to the function doesn't result in an error.
1643
        // Calls made from generate_page_type_patterns() may provide null values.
1644
        $pagetypelist = course_page_type_list($pagetype, null, null);
1645
        $this->assertEquals($pagetypelist, $testpagetypelist1);
1646
    }
1647
 
11 efrain 1648
    public function test_compare_activities_by_time_desc(): void {
1 efrain 1649
 
1650
        // Let's create some test data.
1651
        $activitiesivities = array();
1652
        $x = new stdClass();
1653
        $x->timestamp = null;
1654
        $activities[] = $x;
1655
 
1656
        $x = new stdClass();
1657
        $x->timestamp = 1;
1658
        $activities[] = $x;
1659
 
1660
        $x = new stdClass();
1661
        $x->timestamp = 3;
1662
        $activities[] = $x;
1663
 
1664
        $x = new stdClass();
1665
        $x->timestamp = 0;
1666
        $activities[] = $x;
1667
 
1668
        $x = new stdClass();
1669
        $x->timestamp = 5;
1670
        $activities[] = $x;
1671
 
1672
        $x = new stdClass();
1673
        $activities[] = $x;
1674
 
1675
        $x = new stdClass();
1676
        $x->timestamp = 5;
1677
        $activities[] = $x;
1678
 
1679
        // Do the sorting.
1680
        usort($activities, 'compare_activities_by_time_desc');
1681
 
1682
        // Let's check the result.
1683
        $last = 10;
1684
        foreach($activities as $activity) {
1685
            if (empty($activity->timestamp)) {
1686
                $activity->timestamp = 0;
1687
            }
1688
            $this->assertLessThanOrEqual($last, $activity->timestamp);
1689
        }
1690
    }
1691
 
11 efrain 1692
    public function test_compare_activities_by_time_asc(): void {
1 efrain 1693
 
1694
        // Let's create some test data.
1695
        $activities = array();
1696
        $x = new stdClass();
1697
        $x->timestamp = null;
1698
        $activities[] = $x;
1699
 
1700
        $x = new stdClass();
1701
        $x->timestamp = 1;
1702
        $activities[] = $x;
1703
 
1704
        $x = new stdClass();
1705
        $x->timestamp = 3;
1706
        $activities[] = $x;
1707
 
1708
        $x = new stdClass();
1709
        $x->timestamp = 0;
1710
        $activities[] = $x;
1711
 
1712
        $x = new stdClass();
1713
        $x->timestamp = 5;
1714
        $activities[] = $x;
1715
 
1716
        $x = new stdClass();
1717
        $activities[] = $x;
1718
 
1719
        $x = new stdClass();
1720
        $x->timestamp = 5;
1721
        $activities[] = $x;
1722
 
1723
        // Do the sorting.
1724
        usort($activities, 'compare_activities_by_time_asc');
1725
 
1726
        // Let's check the result.
1727
        $last = 0;
1728
        foreach($activities as $activity) {
1729
            if (empty($activity->timestamp)) {
1730
                $activity->timestamp = 0;
1731
            }
1732
            $this->assertGreaterThanOrEqual($last, $activity->timestamp);
1733
        }
1734
    }
1735
 
1736
    /**
1737
     * Tests moving a module between hidden/visible sections and
1738
     * verifies that the course/module visiblity seettings are
1739
     * retained.
1740
     */
11 efrain 1741
    public function test_moveto_module_between_hidden_sections(): void {
1 efrain 1742
        global $DB;
1743
 
1744
        $this->resetAfterTest(true);
1745
 
1746
        $course = $this->getDataGenerator()->create_course(array('numsections' => 4), array('createsections' => true));
1747
        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1748
        $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
1749
        $quiz= $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
1750
 
1751
        // Set the page as hidden
1752
        set_coursemodule_visible($page->cmid, 0);
1753
 
1754
        // Set sections 3 as hidden.
1755
        set_section_visible($course->id, 3, 0);
1756
 
1757
        $modinfo = get_fast_modinfo($course);
1758
 
1759
        $hiddensection = $modinfo->get_section_info(3);
1760
        // New section is definitely not visible:
1761
        $this->assertEquals($hiddensection->visible, 0);
1762
 
1763
        $forumcm = $modinfo->cms[$forum->cmid];
1764
        $pagecm = $modinfo->cms[$page->cmid];
1765
 
1766
        // Move the forum and the page to a hidden section, make sure moveto_module returns 0 as new visibility state.
1767
        $this->assertEquals(0, moveto_module($forumcm, $hiddensection));
1768
        $this->assertEquals(0, moveto_module($pagecm, $hiddensection));
1769
 
1770
        $modinfo = get_fast_modinfo($course);
1771
 
1772
        // Verify that forum and page have been moved to the hidden section and quiz has not.
1773
        $this->assertContainsEquals($forum->cmid, $modinfo->sections[3]);
1774
        $this->assertContainsEquals($page->cmid, $modinfo->sections[3]);
1775
        $this->assertNotContainsEquals($quiz->cmid, $modinfo->sections[3]);
1776
 
1777
        // Verify that forum has been made invisible.
1778
        $forumcm = $modinfo->cms[$forum->cmid];
1779
        $this->assertEquals($forumcm->visible, 0);
1780
        // Verify that old state has been retained.
1781
        $this->assertEquals($forumcm->visibleold, 1);
1782
 
1783
        // Verify that page has stayed invisible.
1784
        $pagecm = $modinfo->cms[$page->cmid];
1785
        $this->assertEquals($pagecm->visible, 0);
1786
        // Verify that old state has been retained.
1787
        $this->assertEquals($pagecm->visibleold, 0);
1788
 
1789
        // Verify that quiz has been unaffected.
1790
        $quizcm = $modinfo->cms[$quiz->cmid];
1791
        $this->assertEquals($quizcm->visible, 1);
1792
 
1793
        // Move forum and page back to visible section.
1794
        // Make sure the visibility is restored to the original value (visible for forum and hidden for page).
1795
        $visiblesection = $modinfo->get_section_info(2);
1796
        $this->assertEquals(1, moveto_module($forumcm, $visiblesection));
1797
        $this->assertEquals(0, moveto_module($pagecm, $visiblesection));
1798
 
1799
        $modinfo = get_fast_modinfo($course);
1800
 
1801
        // Double check that forum has been made visible.
1802
        $forumcm = $modinfo->cms[$forum->cmid];
1803
        $this->assertEquals($forumcm->visible, 1);
1804
 
1805
        // Double check that page has stayed invisible.
1806
        $pagecm = $modinfo->cms[$page->cmid];
1807
        $this->assertEquals($pagecm->visible, 0);
1808
 
1809
        // Move the page in the same section (this is what mod duplicate does).
1810
        // Visibility of page remains 0.
1811
        $this->assertEquals(0, moveto_module($pagecm, $visiblesection, $forumcm));
1812
 
1813
        // Double check that the the page is still hidden.
1814
        $modinfo = get_fast_modinfo($course);
1815
        $pagecm = $modinfo->cms[$page->cmid];
1816
        $this->assertEquals($pagecm->visible, 0);
1817
    }
1818
 
1819
    /**
1820
     * Tests moving a module around in the same section. moveto_module()
1821
     * is called this way in modduplicate.
1822
     */
11 efrain 1823
    public function test_moveto_module_in_same_section(): void {
1 efrain 1824
        global $DB;
1825
 
1826
        $this->resetAfterTest(true);
1827
 
1828
        $course = $this->getDataGenerator()->create_course(array('numsections' => 3), array('createsections' => true));
1829
        $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id));
1830
        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id));
1831
 
1832
        // Simulate inconsistent visible/visibleold values (MDL-38713).
1833
        $cm = $DB->get_record('course_modules', array('id' => $page->cmid), '*', MUST_EXIST);
1834
        $cm->visible = 0;
1835
        $cm->visibleold = 1;
1836
        $DB->update_record('course_modules', $cm);
1837
 
1838
        $modinfo = get_fast_modinfo($course);
1839
        $forumcm = $modinfo->cms[$forum->cmid];
1840
        $pagecm = $modinfo->cms[$page->cmid];
1841
 
1842
        // Verify that page is hidden.
1843
        $this->assertEquals($pagecm->visible, 0);
1844
 
1845
        // Verify section 0 is where all mods added.
1846
        $section = $modinfo->get_section_info(0);
1847
        $this->assertEquals($section->id, $forumcm->section);
1848
        $this->assertEquals($section->id, $pagecm->section);
1849
 
1850
 
1851
        // Move the page inside the hidden section. Make sure it is hidden.
1852
        $this->assertEquals(0, moveto_module($pagecm, $section, $forumcm));
1853
 
1854
        // Double check that the the page is still hidden.
1855
        $modinfo = get_fast_modinfo($course);
1856
        $pagecm = $modinfo->cms[$page->cmid];
1857
        $this->assertEquals($pagecm->visible, 0);
1858
    }
1859
 
1860
    /**
1861
     * Tests the function that deletes a course module
1862
     *
1863
     * @param string $type The type of module for the test
1864
     * @param array $options The options for the module creation
1865
     * @dataProvider provider_course_delete_module
1866
     */
11 efrain 1867
    public function test_course_delete_module($type, $options): void {
1 efrain 1868
        global $DB;
1869
 
1870
        $this->resetAfterTest(true);
1871
        $this->setAdminUser();
1872
 
1873
        // Create course and modules.
1874
        $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
1875
        $options['course'] = $course->id;
1876
 
1877
        // Generate an assignment with due date (will generate a course event).
1878
        $module = $this->getDataGenerator()->create_module($type, $options);
1879
 
1880
        // Get the module context.
1881
        $modcontext = context_module::instance($module->cmid);
1882
 
1883
        $assocblog = $this->create_module_asscociated_blog($course, $modcontext);
1884
 
1885
        // Verify context exists.
1886
        $this->assertInstanceOf('context_module', $modcontext);
1887
 
1888
        // Make module specific messes.
1889
        switch ($type) {
1890
            case 'assign':
1891
                // Add some tags to this assignment.
1892
                core_tag_tag::set_item_tags('mod_assign', 'assign', $module->id, $modcontext, array('Tag 1', 'Tag 2', 'Tag 3'));
1893
                core_tag_tag::set_item_tags('core', 'course_modules', $module->cmid, $modcontext, array('Tag 3', 'Tag 4', 'Tag 5'));
1894
 
1895
                // Confirm the tag instances were added.
1896
                $criteria = array('component' => 'mod_assign', 'itemtype' => 'assign', 'contextid' => $modcontext->id);
1897
                $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
1898
                $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
1899
                $this->assertEquals(3, $DB->count_records('tag_instance', $criteria));
1900
 
1901
                // Verify event assignment event has been generated.
1902
                $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
1903
                $this->assertEquals(1, $eventcount);
1904
 
1905
                break;
1906
            case 'quiz':
1907
                $qgen = $this->getDataGenerator()->get_plugin_generator('core_question');
1908
                $qcat = $qgen->create_question_category(array('contextid' => $modcontext->id));
1909
                $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
1910
                $qgen->create_question('shortanswer', null, array('category' => $qcat->id));
1911
                break;
1912
            default:
1913
                break;
1914
        }
1915
 
1916
        // Run delete..
1917
        course_delete_module($module->cmid);
1918
 
1919
        // Verify the context has been removed.
1920
        $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
1921
 
1922
        // Verify the course_module record has been deleted.
1923
        $cmcount = $DB->count_records('course_modules', array('id' => $module->cmid));
1924
        $this->assertEmpty($cmcount);
1925
 
1926
        // Verify the blog_association record has been deleted.
1927
        $this->assertCount(0, $DB->get_records('blog_association',
1928
                array('contextid' => $modcontext->id)));
1929
 
1930
        // Verify the blog post record has been deleted.
1931
        $this->assertCount(0, $DB->get_records('post',
1932
                array('id' => $assocblog->id)));
1933
 
1934
        // Verify the tag instance record has been deleted.
1935
        $this->assertCount(0, $DB->get_records('tag_instance',
1936
                array('itemid' => $assocblog->id)));
1937
 
1938
        // Test clean up of module specific messes.
1939
        switch ($type) {
1940
            case 'assign':
1941
                // Verify event assignment events have been removed.
1942
                $eventcount = $DB->count_records('event', array('instance' => $module->id, 'modulename' => $type));
1943
                $this->assertEmpty($eventcount);
1944
 
1945
                // Verify the tag instances were deleted.
1946
                $criteria = array('component' => 'mod_assign', 'contextid' => $modcontext->id);
1947
                $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
1948
 
1949
                $criteria = array('component' => 'core', 'itemtype' => 'course_modules', 'contextid' => $modcontext->id);
1950
                $this->assertEquals(0, $DB->count_records('tag_instance', $criteria));
1951
                break;
1952
            case 'quiz':
1953
                // Verify category deleted.
1954
                $criteria = array('contextid' => $modcontext->id);
1955
                $this->assertEquals(0, $DB->count_records('question_categories', $criteria));
1956
 
1957
                // Verify questions deleted.
1958
                $criteria = [$qcat->id];
1959
                $sql = 'SELECT COUNT(q.id)
1960
                          FROM {question} q
1961
                          JOIN {question_versions} qv ON qv.questionid = q.id
1962
                          JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid
1963
                          WHERE qbe.questioncategoryid = ?';
1964
                $this->assertEquals(0, $DB->count_records_sql($sql, $criteria));
1965
                break;
1966
            default:
1967
                break;
1968
        }
1969
    }
1970
 
1971
    /**
1972
     * Test that triggering a course_created event works as expected.
1973
     */
11 efrain 1974
    public function test_course_created_event(): void {
1 efrain 1975
        global $DB;
1976
 
1977
        $this->resetAfterTest();
1978
 
1979
        // Catch the events.
1980
        $sink = $this->redirectEvents();
1981
 
1982
        // Create the course with an id number which is used later when generating a course via the imsenterprise plugin.
1983
        $data = new stdClass();
1984
        $data->idnumber = 'idnumber';
1985
        $course = $this->getDataGenerator()->create_course($data);
1986
        // Get course from DB for comparison.
1987
        $course = $DB->get_record('course', array('id' => $course->id));
1988
 
1989
        // Capture the event.
1990
        $events = $sink->get_events();
1991
        $sink->close();
1992
 
1993
        // Validate the event.
1994
        $event = $events[0];
1995
        $this->assertInstanceOf('\core\event\course_created', $event);
1996
        $this->assertEquals('course', $event->objecttable);
1997
        $this->assertEquals($course->id, $event->objectid);
1998
        $this->assertEquals(context_course::instance($course->id), $event->get_context());
1999
        $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
2000
 
2001
        // Now we want to trigger creating a course via the imsenterprise.
2002
        // Delete the course we created earlier, as we want the imsenterprise plugin to create this.
2003
        // We do not want print out any of the text this function generates while doing this, which is why
2004
        // we are using ob_start() and ob_end_clean().
2005
        ob_start();
2006
        delete_course($course);
2007
        ob_end_clean();
2008
 
2009
        // Create the XML file we want to use.
2010
        $course->category = (array)$course->category;
1441 ariadna 2011
 
2012
        // Note: this is a violation of component communication principles.
2013
        // TODO MDL-83789.
2014
        $imstestcase = new imsenterprise_test('courselib_imsenterprise_test');
1 efrain 2015
        $imstestcase->imsplugin = enrol_get_plugin('imsenterprise');
2016
        $imstestcase->set_test_config();
2017
        $imstestcase->set_xml_file(false, array($course));
2018
 
2019
        // Capture the event.
2020
        $sink = $this->redirectEvents();
2021
        $imstestcase->imsplugin->cron();
2022
        $events = $sink->get_events();
2023
        $sink->close();
2024
        $event = null;
2025
        foreach ($events as $eventinfo) {
2026
            if ($eventinfo instanceof \core\event\course_created ) {
2027
                $event = $eventinfo;
2028
                break;
2029
            }
2030
        }
2031
 
2032
        // Validate the event triggered is \core\event\course_created. There is no need to validate the other values
2033
        // as they have already been validated in the previous steps. Here we only want to make sure that when the
2034
        // imsenterprise plugin creates a course an event is triggered.
2035
        $this->assertInstanceOf('\core\event\course_created', $event);
2036
        $this->assertEventContextNotUsed($event);
2037
    }
2038
 
2039
    /**
2040
     * Test that triggering a course_updated event works as expected.
2041
     */
11 efrain 2042
    public function test_course_updated_event(): void {
1 efrain 2043
        global $DB;
2044
 
2045
        $this->resetAfterTest();
2046
 
2047
        // Create a course.
2048
        $course = $this->getDataGenerator()->create_course();
2049
 
2050
        // Create a category we are going to move this course to.
2051
        $category = $this->getDataGenerator()->create_category();
2052
 
2053
        // Create a hidden category we are going to move this course to.
2054
        $categoryhidden = $this->getDataGenerator()->create_category(array('visible' => 0));
2055
 
2056
        // Update course and catch course_updated event.
2057
        $sink = $this->redirectEvents();
2058
        update_course($course);
2059
        $events = $sink->get_events();
2060
        $sink->close();
2061
 
2062
        // Get updated course information from the DB.
2063
        $updatedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
2064
        // Validate event.
2065
        $event = array_shift($events);
2066
        $this->assertInstanceOf('\core\event\course_updated', $event);
2067
        $this->assertEquals('course', $event->objecttable);
2068
        $this->assertEquals($updatedcourse->id, $event->objectid);
2069
        $this->assertEquals(context_course::instance($course->id), $event->get_context());
2070
        $url = new moodle_url('/course/edit.php', array('id' => $event->objectid));
2071
        $this->assertEquals($url, $event->get_url());
2072
        $this->assertEquals($updatedcourse, $event->get_record_snapshot('course', $event->objectid));
2073
 
2074
        // Move course and catch course_updated event.
2075
        $sink = $this->redirectEvents();
2076
        move_courses(array($course->id), $category->id);
2077
        $events = $sink->get_events();
2078
        $sink->close();
2079
 
2080
        // Return the moved course information from the DB.
2081
        $movedcourse = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
2082
        // Validate event.
2083
        $event = array_shift($events);
2084
        $this->assertInstanceOf('\core\event\course_updated', $event);
2085
        $this->assertEquals('course', $event->objecttable);
2086
        $this->assertEquals($movedcourse->id, $event->objectid);
2087
        $this->assertEquals(context_course::instance($course->id), $event->get_context());
2088
        $this->assertEquals($movedcourse, $event->get_record_snapshot('course', $movedcourse->id));
2089
 
2090
        // Move course to hidden category and catch course_updated event.
2091
        $sink = $this->redirectEvents();
2092
        move_courses(array($course->id), $categoryhidden->id);
2093
        $events = $sink->get_events();
2094
        $sink->close();
2095
 
2096
        // Return the moved course information from the DB.
2097
        $movedcoursehidden = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
2098
        // Validate event.
2099
        $event = array_shift($events);
2100
        $this->assertInstanceOf('\core\event\course_updated', $event);
2101
        $this->assertEquals('course', $event->objecttable);
2102
        $this->assertEquals($movedcoursehidden->id, $event->objectid);
2103
        $this->assertEquals(context_course::instance($course->id), $event->get_context());
2104
        $this->assertEquals($movedcoursehidden, $event->get_record_snapshot('course', $movedcoursehidden->id));
2105
        $this->assertEventContextNotUsed($event);
2106
    }
2107
 
2108
    /**
2109
     * Test that triggering a course_updated event logs changes.
2110
     */
11 efrain 2111
    public function test_course_updated_event_with_changes(): void {
1 efrain 2112
        global $DB;
2113
 
2114
        $this->resetAfterTest();
2115
 
2116
        // Create a course.
2117
        $course = $this->getDataGenerator()->create_course((object)['visible' => 1]);
2118
 
2119
        $editedcourse = $DB->get_record('course', ['id' => $course->id]);
2120
        $editedcourse->visible = 0;
2121
 
2122
        // Update course and catch course_updated event.
2123
        $sink = $this->redirectEvents();
2124
        update_course($editedcourse);
2125
        $events = $sink->get_events();
2126
        $sink->close();
2127
 
2128
        $event = array_shift($events);
2129
        $this->assertInstanceOf('\core\event\course_updated', $event);
2130
        $otherdata = [
2131
            'shortname' => $course->shortname,
2132
            'fullname' => $course->fullname,
2133
            'updatedfields' => [
2134
                'visible' => 0
2135
            ]
2136
        ];
2137
        $this->assertEquals($otherdata, $event->other);
2138
 
2139
    }
2140
 
2141
    /**
2142
     * Test that triggering a course_deleted event works as expected.
2143
     */
11 efrain 2144
    public function test_course_deleted_event(): void {
1 efrain 2145
        $this->resetAfterTest();
2146
 
2147
        // Create the course.
2148
        $course = $this->getDataGenerator()->create_course();
2149
 
2150
        // Save the course context before we delete the course.
2151
        $coursecontext = context_course::instance($course->id);
2152
 
2153
        // Catch the update event.
2154
        $sink = $this->redirectEvents();
2155
 
2156
        // Call delete_course() which will trigger the course_deleted event and the course_content_deleted
2157
        // event. This function prints out data to the screen, which we do not want during a PHPUnit test,
2158
        // so use ob_start and ob_end_clean to prevent this.
2159
        ob_start();
2160
        delete_course($course);
2161
        ob_end_clean();
2162
 
2163
        // Capture the event.
2164
        $events = $sink->get_events();
2165
        $sink->close();
2166
 
2167
        // Validate the event.
2168
        $event = array_pop($events);
2169
        $this->assertInstanceOf('\core\event\course_deleted', $event);
2170
        $this->assertEquals('course', $event->objecttable);
2171
        $this->assertEquals($course->id, $event->objectid);
2172
        $this->assertEquals($coursecontext->id, $event->contextid);
2173
        $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
2174
        $eventdata = $event->get_data();
2175
        $this->assertSame($course->idnumber, $eventdata['other']['idnumber']);
2176
        $this->assertSame($course->fullname, $eventdata['other']['fullname']);
2177
        $this->assertSame($course->shortname, $eventdata['other']['shortname']);
2178
 
2179
        $this->assertEventContextNotUsed($event);
2180
    }
2181
 
2182
    /**
2183
     * Test that triggering a course_content_deleted event works as expected.
2184
     */
11 efrain 2185
    public function test_course_content_deleted_event(): void {
1 efrain 2186
        global $DB;
2187
 
2188
        $this->resetAfterTest();
2189
 
2190
        // Create the course.
2191
        $course = $this->getDataGenerator()->create_course();
2192
 
2193
        // Get the course from the DB. The data generator adds some extra properties, such as
2194
        // numsections, to the course object which will fail the assertions later on.
2195
        $course = $DB->get_record('course', array('id' => $course->id), '*', MUST_EXIST);
2196
 
2197
        // Save the course context before we delete the course.
2198
        $coursecontext = context_course::instance($course->id);
2199
 
2200
        // Catch the update event.
2201
        $sink = $this->redirectEvents();
2202
 
2203
        remove_course_contents($course->id, false);
2204
 
2205
        // Capture the event.
2206
        $events = $sink->get_events();
2207
        $sink->close();
2208
 
2209
        // Validate the event.
2210
        $event = array_pop($events);
2211
        $this->assertInstanceOf('\core\event\course_content_deleted', $event);
2212
        $this->assertEquals('course', $event->objecttable);
2213
        $this->assertEquals($course->id, $event->objectid);
2214
        $this->assertEquals($coursecontext->id, $event->contextid);
2215
        $this->assertEquals($course, $event->get_record_snapshot('course', $course->id));
2216
        $this->assertEventContextNotUsed($event);
2217
    }
2218
 
2219
    /**
2220
     * Test that triggering a course_category_deleted event works as expected.
2221
     */
11 efrain 2222
    public function test_course_category_deleted_event(): void {
1 efrain 2223
        $this->resetAfterTest();
2224
 
2225
        // Create a category.
2226
        $category = $this->getDataGenerator()->create_category();
2227
 
2228
        // Save the original record/context before it is deleted.
2229
        $categoryrecord = $category->get_db_record();
2230
        $categorycontext = context_coursecat::instance($category->id);
2231
 
2232
        // Catch the update event.
2233
        $sink = $this->redirectEvents();
2234
 
2235
        // Delete the category.
2236
        $category->delete_full();
2237
 
2238
        // Capture the event.
2239
        $events = $sink->get_events();
2240
        $sink->close();
2241
 
2242
        // Validate the event.
2243
        $event = $events[0];
2244
        $this->assertInstanceOf('\core\event\course_category_deleted', $event);
2245
        $this->assertEquals('course_categories', $event->objecttable);
2246
        $this->assertEquals($category->id, $event->objectid);
2247
        $this->assertEquals($categorycontext->id, $event->contextid);
2248
        $this->assertEquals([
2249
            'name' => $category->name,
2250
        ], $event->other);
2251
        $this->assertEquals($categoryrecord, $event->get_record_snapshot($event->objecttable, $event->objectid));
2252
        $this->assertEquals(null, $event->get_url());
2253
 
2254
        // Create two categories.
2255
        $category = $this->getDataGenerator()->create_category();
2256
        $category2 = $this->getDataGenerator()->create_category();
2257
 
2258
        // Save the original record/context before it is moved and then deleted.
2259
        $category2record = $category2->get_db_record();
2260
        $category2context = context_coursecat::instance($category2->id);
2261
 
2262
        // Catch the update event.
2263
        $sink = $this->redirectEvents();
2264
 
2265
        // Move the category.
2266
        $category2->delete_move($category->id);
2267
 
2268
        // Capture the event.
2269
        $events = $sink->get_events();
2270
        $sink->close();
2271
 
2272
        // Validate the event.
2273
        $event = $events[0];
2274
        $this->assertInstanceOf('\core\event\course_category_deleted', $event);
2275
        $this->assertEquals('course_categories', $event->objecttable);
2276
        $this->assertEquals($category2->id, $event->objectid);
2277
        $this->assertEquals($category2context->id, $event->contextid);
2278
        $this->assertEquals([
2279
            'name' => $category2->name,
2280
            'contentmovedcategoryid' => $category->id,
2281
        ], $event->other);
2282
        $this->assertEquals($category2record, $event->get_record_snapshot($event->objecttable, $event->objectid));
2283
        $this->assertEventContextNotUsed($event);
2284
    }
2285
 
2286
    /**
2287
     * Test that triggering a course_backup_created event works as expected.
2288
     */
11 efrain 2289
    public function test_course_backup_created_event(): void {
1 efrain 2290
        global $CFG;
2291
 
2292
        // Get the necessary files to perform backup and restore.
2293
        require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
2294
        require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
2295
 
2296
        $this->resetAfterTest();
2297
 
2298
        // Set to admin user.
2299
        $this->setAdminUser();
2300
 
2301
        // The user id is going to be 2 since we are the admin user.
2302
        $userid = 2;
2303
 
2304
        // Create a course.
2305
        $course = $this->getDataGenerator()->create_course();
2306
 
2307
        // Create backup file and save it to the backup location.
2308
        $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
2309
            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
2310
        $sink = $this->redirectEvents();
2311
        $bc->execute_plan();
2312
 
2313
        // Capture the event.
2314
        $events = $sink->get_events();
2315
        $sink->close();
2316
 
2317
        // Validate the event.
2318
        $event = array_pop($events);
2319
        $this->assertInstanceOf('\core\event\course_backup_created', $event);
2320
        $this->assertEquals('course', $event->objecttable);
2321
        $this->assertEquals($bc->get_courseid(), $event->objectid);
2322
        $this->assertEquals(context_course::instance($bc->get_courseid())->id, $event->contextid);
2323
 
2324
        $url = new moodle_url('/course/view.php', array('id' => $event->objectid));
2325
        $this->assertEquals($url, $event->get_url());
2326
        $this->assertEventContextNotUsed($event);
2327
 
2328
        // Destroy the resource controller since we are done using it.
2329
        $bc->destroy();
2330
    }
2331
 
2332
    /**
2333
     * Test that triggering a course_restored event works as expected.
2334
     */
11 efrain 2335
    public function test_course_restored_event(): void {
1 efrain 2336
        global $CFG;
2337
 
2338
        // Get the necessary files to perform backup and restore.
2339
        require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
2340
        require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
2341
 
2342
        $this->resetAfterTest();
2343
 
2344
        // Set to admin user.
2345
        $this->setAdminUser();
2346
 
2347
        // The user id is going to be 2 since we are the admin user.
2348
        $userid = 2;
2349
 
2350
        // Create a course.
2351
        $course = $this->getDataGenerator()->create_course();
2352
 
2353
        // Create backup file and save it to the backup location.
2354
        $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
2355
            backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid);
2356
        $bc->execute_plan();
2357
        $results = $bc->get_results();
2358
        $file = $results['backup_destination'];
2359
        $fp = get_file_packer('application/vnd.moodle.backup');
2360
        $filepath = $CFG->dataroot . '/temp/backup/test-restore-course-event';
2361
        $file->extract_to_pathname($fp, $filepath);
2362
        $bc->destroy();
2363
 
2364
        // Now we want to catch the restore course event.
2365
        $sink = $this->redirectEvents();
2366
 
2367
        // Now restore the course to trigger the event.
2368
        $rc = new restore_controller('test-restore-course-event', $course->id, backup::INTERACTIVE_NO,
2369
            backup::MODE_GENERAL, $userid, backup::TARGET_NEW_COURSE);
2370
        $rc->execute_precheck();
2371
        $rc->execute_plan();
2372
 
2373
        // Capture the event.
2374
        $events = $sink->get_events();
2375
        $sink->close();
2376
 
2377
        // Validate the event.
2378
        $event = array_pop($events);
2379
        $this->assertInstanceOf('\core\event\course_restored', $event);
2380
        $this->assertEquals('course', $event->objecttable);
2381
        $this->assertEquals($rc->get_courseid(), $event->objectid);
2382
        $this->assertEquals(context_course::instance($rc->get_courseid())->id, $event->contextid);
2383
        $this->assertEventContextNotUsed($event);
2384
 
2385
        // Destroy the resource controller since we are done using it.
2386
        $rc->destroy();
2387
    }
2388
 
2389
    /**
2390
     * Test that triggering a course_section_updated event works as expected.
2391
     */
11 efrain 2392
    public function test_course_section_updated_event(): void {
1 efrain 2393
        global $DB;
2394
 
2395
        $this->resetAfterTest();
2396
 
2397
        // Create the course with sections.
2398
        $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
2399
        $sections = $DB->get_records('course_sections', array('course' => $course->id));
2400
 
2401
        $coursecontext = context_course::instance($course->id);
2402
 
2403
        $section = array_pop($sections);
2404
        $section->name = 'Test section';
2405
        $section->summary = 'Test section summary';
2406
        $DB->update_record('course_sections', $section);
2407
 
2408
        // Trigger an event for course section update.
2409
        $event = \core\event\course_section_updated::create(
2410
                array(
2411
                    'objectid' => $section->id,
2412
                    'courseid' => $course->id,
2413
                    'context' => context_course::instance($course->id),
2414
                    'other' => array(
2415
                        'sectionnum' => $section->section
2416
                    )
2417
                )
2418
            );
2419
        $event->add_record_snapshot('course_sections', $section);
2420
        // Trigger and catch event.
2421
        $sink = $this->redirectEvents();
2422
        $event->trigger();
2423
        $events = $sink->get_events();
2424
        $sink->close();
2425
 
2426
        // Validate the event.
2427
        $event = $events[0];
2428
        $this->assertInstanceOf('\core\event\course_section_updated', $event);
2429
        $this->assertEquals('course_sections', $event->objecttable);
2430
        $this->assertEquals($section->id, $event->objectid);
2431
        $this->assertEquals($course->id, $event->courseid);
2432
        $this->assertEquals($coursecontext->id, $event->contextid);
2433
        $this->assertEquals($section->section, $event->other['sectionnum']);
2434
        $expecteddesc = "The user with id '{$event->userid}' updated section number '{$event->other['sectionnum']}' for the course with id '{$event->courseid}'";
2435
        $this->assertEquals($expecteddesc, $event->get_description());
2436
        $url = new moodle_url('/course/editsection.php', array('id' => $event->objectid));
2437
        $this->assertEquals($url, $event->get_url());
2438
        $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
2439
        $id = $section->id;
2440
        $sectionnum = $section->section;
2441
        $this->assertEventContextNotUsed($event);
2442
    }
2443
 
2444
    /**
2445
     * Test that triggering a course_section_deleted event works as expected.
2446
     */
11 efrain 2447
    public function test_course_section_deleted_event(): void {
1 efrain 2448
        global $USER, $DB;
2449
        $this->resetAfterTest();
2450
        $sink = $this->redirectEvents();
2451
 
2452
        // Create the course with sections.
2453
        $course = $this->getDataGenerator()->create_course(array('numsections' => 10), array('createsections' => true));
2454
        $sections = $DB->get_records('course_sections', array('course' => $course->id), 'section');
2455
        $coursecontext = context_course::instance($course->id);
2456
        $section = array_pop($sections);
2457
        course_delete_section($course, $section);
2458
        $events = $sink->get_events();
2459
        $event = array_pop($events); // Delete section event.
2460
        $sink->close();
2461
 
2462
        // Validate event data.
2463
        $this->assertInstanceOf('\core\event\course_section_deleted', $event);
2464
        $this->assertEquals('course_sections', $event->objecttable);
2465
        $this->assertEquals($section->id, $event->objectid);
2466
        $this->assertEquals($course->id, $event->courseid);
2467
        $this->assertEquals($coursecontext->id, $event->contextid);
2468
        $this->assertEquals($section->section, $event->other['sectionnum']);
2469
        $expecteddesc = "The user with id '{$event->userid}' deleted section number '{$event->other['sectionnum']}' " .
2470
                "(section name '{$event->other['sectionname']}') for the course with id '{$event->courseid}'";
2471
        $this->assertEquals($expecteddesc, $event->get_description());
2472
        $this->assertEquals($section, $event->get_record_snapshot('course_sections', $event->objectid));
2473
        $this->assertNull($event->get_url());
2474
        $this->assertEventContextNotUsed($event);
2475
    }
2476
 
11 efrain 2477
    public function test_course_integrity_check(): void {
1 efrain 2478
        global $DB;
2479
 
2480
        $this->resetAfterTest(true);
2481
        $course = $this->getDataGenerator()->create_course(array('numsections' => 1),
2482
           array('createsections'=>true));
2483
 
2484
        $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id),
2485
                array('section' => 0));
2486
        $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id),
2487
                array('section' => 0));
2488
        $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id),
2489
                array('section' => 0));
2490
        $correctseq = join(',', array($forum->cmid, $page->cmid, $quiz->cmid));
2491
 
2492
        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2493
        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2494
        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2495
        $this->assertEquals($correctseq, $section0->sequence);
2496
        $this->assertEmpty($section1->sequence);
2497
        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2498
        $this->assertEquals($section0->id, $cms[$page->cmid]->section);
2499
        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2500
        $this->assertEmpty(course_integrity_check($course->id));
2501
 
2502
        // Now let's make manual change in DB and let course_integrity_check() fix it:
2503
 
2504
        // 1. Module appears twice in one section.
2505
        $DB->update_record('course_sections', array('id' => $section0->id, 'sequence' => $section0->sequence. ','. $page->cmid));
2506
        $this->assertEquals(
2507
                array('Failed integrity check for course ['. $course->id.
2508
                ']. Sequence for course section ['. $section0->id. '] is "'.
2509
                $section0->sequence. ','. $page->cmid. '", must be "'.
2510
                $section0->sequence. '"'),
2511
                course_integrity_check($course->id));
2512
        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2513
        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2514
        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2515
        $this->assertEquals($correctseq, $section0->sequence);
2516
        $this->assertEmpty($section1->sequence);
2517
        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2518
        $this->assertEquals($section0->id, $cms[$page->cmid]->section);
2519
        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2520
 
2521
        // 2. Module appears in two sections (last section wins).
2522
        $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''. $page->cmid));
2523
        // First message about double mentioning in sequence, second message about wrong section field for $page.
2524
        $this->assertEquals(array(
2525
            'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
2526
            '] must be removed from sequence of section ['. $section0->id.
2527
            '] because it is also present in sequence of section ['. $section1->id. ']',
2528
            'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
2529
            '] points to section ['. $section0->id. '] instead of ['. $section1->id. ']'),
2530
                course_integrity_check($course->id));
2531
        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2532
        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2533
        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2534
        $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
2535
        $this->assertEquals(''. $page->cmid, $section1->sequence);
2536
        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2537
        $this->assertEquals($section1->id, $cms[$page->cmid]->section);
2538
        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2539
 
2540
        // 3. Module id is not present in course_section.sequence (integrity check with $fullcheck = false).
2541
        $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
2542
        $this->assertEmpty(course_integrity_check($course->id)); // Not an error!
2543
        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2544
        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2545
        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2546
        $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
2547
        $this->assertEmpty($section1->sequence);
2548
        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2549
        $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
2550
        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2551
 
2552
        // 4. Module id is not present in course_section.sequence (integrity check with $fullcheck = true).
2553
        $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
2554
                $page->cmid. '] is missing from sequence of section ['. $section1->id. ']'),
2555
                course_integrity_check($course->id, null, null, true)); // Error!
2556
        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2557
        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2558
        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2559
        $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
2560
        $this->assertEquals(''. $page->cmid, $section1->sequence);  // Yay, module added to section.
2561
        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2562
        $this->assertEquals($section1->id, $cms[$page->cmid]->section); // Not changed.
2563
        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2564
 
2565
        // 5. Module id is not present in course_section.sequence and it's section is invalid (integrity check with $fullcheck = true).
2566
        $DB->update_record('course_modules', array('id' => $page->cmid, 'section' => 8765));
2567
        $DB->update_record('course_sections', array('id' => $section1->id, 'sequence' => ''));
2568
        $this->assertEquals(array(
2569
            'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
2570
            '] is missing from sequence of section ['. $section0->id. ']',
2571
            'Failed integrity check for course ['. $course->id. ']. Course module ['. $page->cmid.
2572
            '] points to section [8765] instead of ['. $section0->id. ']'),
2573
                course_integrity_check($course->id, null, null, true));
2574
        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2575
        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2576
        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2577
        $this->assertEquals($forum->cmid. ','. $quiz->cmid. ','. $page->cmid, $section0->sequence); // Module added to section.
2578
        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2579
        $this->assertEquals($section0->id, $cms[$page->cmid]->section); // Section changed to section0.
2580
        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2581
 
2582
        // 6. Module is deleted from course_modules but not deleted in sequence (integrity check with $fullcheck = true).
2583
        $DB->delete_records('course_modules', array('id' => $page->cmid));
2584
        $this->assertEquals(array('Failed integrity check for course ['. $course->id. ']. Course module ['.
2585
                $page->cmid. '] does not exist but is present in the sequence of section ['. $section0->id. ']'),
2586
                course_integrity_check($course->id, null, null, true));
2587
        $section0 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 0));
2588
        $section1 = $DB->get_record('course_sections', array('course' => $course->id, 'section' => 1));
2589
        $cms = $DB->get_records('course_modules', array('course' => $course->id), 'id', 'id,section');
2590
        $this->assertEquals($forum->cmid. ','. $quiz->cmid, $section0->sequence);
2591
        $this->assertEmpty($section1->sequence);
2592
        $this->assertEquals($section0->id, $cms[$forum->cmid]->section);
2593
        $this->assertEquals($section0->id, $cms[$quiz->cmid]->section);
2594
        $this->assertEquals(2, count($cms));
2595
    }
2596
 
2597
    /**
2598
     * Tests for event related to course module creation.
2599
     */
11 efrain 2600
    public function test_course_module_created_event(): void {
1 efrain 2601
        global $USER;
2602
 
2603
        $this->resetAfterTest();
2604
        $this->setAdminUser();
2605
 
2606
        // Create an assign module.
2607
        $sink = $this->redirectEvents();
2608
        $course = $this->getDataGenerator()->create_course();
2609
        $module = $this->getDataGenerator()->create_module('assign', ['course' => $course]);
2610
        $events = $sink->get_events();
2611
        $eventscount = 0;
2612
 
2613
        // Validate event data.
2614
        foreach ($events as $event) {
2615
            if ($event instanceof \core\event\course_module_created) {
2616
                $eventscount++;
2617
 
2618
                $this->assertEquals($module->cmid, $event->objectid);
2619
                $this->assertEquals($USER->id, $event->userid);
2620
                $this->assertEquals('course_modules', $event->objecttable);
2621
                $url = new moodle_url('/mod/assign/view.php', array('id' => $module->cmid));
2622
                $this->assertEquals($url, $event->get_url());
2623
                $this->assertEventContextNotUsed($event);
2624
            }
2625
        }
2626
        // Only one \core\event\course_module_created event should be triggered.
2627
        $this->assertEquals(1, $eventscount);
2628
 
2629
        // Let us see if duplicating an activity results in a nice course module created event.
2630
        $sink->clear();
2631
        $course = get_course($module->course);
2632
        $cm = get_coursemodule_from_id('assign', $module->cmid, 0, false, MUST_EXIST);
2633
        $newcm = duplicate_module($course, $cm);
2634
        $events = $sink->get_events();
2635
        $eventscount = 0;
2636
        $sink->close();
2637
 
2638
        foreach ($events as $event) {
2639
            if ($event instanceof \core\event\course_module_created) {
2640
                $eventscount++;
2641
                // Validate event data.
2642
                $this->assertInstanceOf('\core\event\course_module_created', $event);
2643
                $this->assertEquals($newcm->id, $event->objectid);
2644
                $this->assertEquals($USER->id, $event->userid);
2645
                $this->assertEquals($course->id, $event->courseid);
2646
                $url = new moodle_url('/mod/assign/view.php', array('id' => $newcm->id));
2647
                $this->assertEquals($url, $event->get_url());
2648
            }
2649
        }
2650
 
2651
        // Only one \core\event\course_module_created event should be triggered.
2652
        $this->assertEquals(1, $eventscount);
2653
    }
2654
 
2655
    /**
2656
     * Tests for event validations related to course module creation.
2657
     */
11 efrain 2658
    public function test_course_module_created_event_exceptions(): void {
1 efrain 2659
 
2660
        $this->resetAfterTest();
2661
 
2662
        // Generate data.
2663
        $modinfo = $this->create_specific_module_test('assign');
2664
        $context = context_module::instance($modinfo->coursemodule);
2665
 
2666
        // Test not setting instanceid.
2667
        try {
2668
            $event = \core\event\course_module_created::create(array(
2669
                'courseid' => $modinfo->course,
2670
                'context'  => $context,
2671
                'objectid' => $modinfo->coursemodule,
2672
                'other'    => array(
2673
                    'modulename' => 'assign',
2674
                    'name'       => 'My assignment',
2675
                )
2676
            ));
2677
            $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2678
                    other['instanceid']");
2679
        } catch (coding_exception $e) {
2680
            $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
2681
        }
2682
 
2683
        // Test not setting modulename.
2684
        try {
2685
            $event = \core\event\course_module_created::create(array(
2686
                'courseid' => $modinfo->course,
2687
                'context'  => $context,
2688
                'objectid' => $modinfo->coursemodule,
2689
                'other'    => array(
2690
                    'instanceid' => $modinfo->instance,
2691
                    'name'       => 'My assignment',
2692
                )
2693
            ));
2694
            $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2695
                    other['modulename']");
2696
        } catch (coding_exception $e) {
2697
            $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
2698
        }
2699
 
2700
        // Test not setting name.
2701
 
2702
        try {
2703
            $event = \core\event\course_module_created::create(array(
2704
                'courseid' => $modinfo->course,
2705
                'context'  => $context,
2706
                'objectid' => $modinfo->coursemodule,
2707
                'other'    => array(
2708
                    'modulename' => 'assign',
2709
                    'instanceid' => $modinfo->instance,
2710
                )
2711
            ));
2712
            $this->fail("Event validation should not allow \\core\\event\\course_module_created to be triggered without
2713
                    other['name']");
2714
        } catch (coding_exception $e) {
2715
            $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
2716
        }
2717
 
2718
    }
2719
 
2720
    /**
2721
     * Tests for event related to course module updates.
2722
     */
11 efrain 2723
    public function test_course_module_updated_event(): void {
1 efrain 2724
        global $USER, $DB;
2725
        $this->resetAfterTest();
2726
 
2727
        // Update a forum module.
2728
        $sink = $this->redirectEvents();
2729
        $modinfo = $this->update_specific_module_test('forum');
2730
        $events = $sink->get_events();
2731
        $eventscount = 0;
2732
        $sink->close();
2733
 
2734
        $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
2735
        $mod = $DB->get_record('forum', array('id' => $cm->instance), '*', MUST_EXIST);
2736
 
2737
        // Validate event data.
2738
        foreach ($events as $event) {
2739
            if ($event instanceof \core\event\course_module_updated) {
2740
                $eventscount++;
2741
 
2742
                $this->assertEquals($cm->id, $event->objectid);
2743
                $this->assertEquals($USER->id, $event->userid);
2744
                $this->assertEquals('course_modules', $event->objecttable);
2745
                $url = new moodle_url('/mod/forum/view.php', array('id' => $cm->id));
2746
                $this->assertEquals($url, $event->get_url());
2747
                $this->assertEventContextNotUsed($event);
2748
            }
2749
        }
2750
 
2751
        // Only one \core\event\course_module_updated event should be triggered.
2752
        $this->assertEquals(1, $eventscount);
2753
    }
2754
 
2755
    /**
2756
     * Tests for create_from_cm method.
2757
     */
11 efrain 2758
    public function test_course_module_create_from_cm(): void {
1 efrain 2759
        $this->resetAfterTest();
2760
        $this->setAdminUser();
2761
 
2762
        // Create course and modules.
2763
        $course = $this->getDataGenerator()->create_course(array('numsections' => 5));
2764
 
2765
        // Generate an assignment.
2766
        $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
2767
 
2768
        // Get the module context.
2769
        $modcontext = context_module::instance($assign->cmid);
2770
 
2771
        // Get course module.
2772
        $cm = get_coursemodule_from_id(null, $assign->cmid, $course->id, false, MUST_EXIST);
2773
 
2774
        // Create an event from course module.
2775
        $event = \core\event\course_module_updated::create_from_cm($cm, $modcontext);
2776
 
2777
        // Trigger the events.
2778
        $sink = $this->redirectEvents();
2779
        $event->trigger();
2780
        $events = $sink->get_events();
2781
        $event2 = array_pop($events);
2782
 
2783
        // Test event data.
2784
        $this->assertInstanceOf('\core\event\course_module_updated', $event);
2785
        $this->assertEquals($cm->id, $event2->objectid);
2786
        $this->assertEquals($modcontext, $event2->get_context());
2787
        $this->assertEquals($cm->modname, $event2->other['modulename']);
2788
        $this->assertEquals($cm->instance, $event2->other['instanceid']);
2789
        $this->assertEquals($cm->name, $event2->other['name']);
2790
        $this->assertEventContextNotUsed($event2);
2791
    }
2792
 
2793
    /**
2794
     * Tests for event validations related to course module update.
2795
     */
11 efrain 2796
    public function test_course_module_updated_event_exceptions(): void {
1 efrain 2797
 
2798
        $this->resetAfterTest();
2799
 
2800
        // Generate data.
2801
        $modinfo = $this->create_specific_module_test('assign');
2802
        $context = context_module::instance($modinfo->coursemodule);
2803
 
2804
        // Test not setting instanceid.
2805
        try {
2806
            $event = \core\event\course_module_updated::create(array(
2807
                'courseid' => $modinfo->course,
2808
                'context'  => $context,
2809
                'objectid' => $modinfo->coursemodule,
2810
                'other'    => array(
2811
                    'modulename' => 'assign',
2812
                    'name'       => 'My assignment',
2813
                )
2814
            ));
2815
            $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2816
                    other['instanceid']");
2817
        } catch (coding_exception $e) {
2818
            $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
2819
        }
2820
 
2821
        // Test not setting modulename.
2822
        try {
2823
            $event = \core\event\course_module_updated::create(array(
2824
                'courseid' => $modinfo->course,
2825
                'context'  => $context,
2826
                'objectid' => $modinfo->coursemodule,
2827
                'other'    => array(
2828
                    'instanceid' => $modinfo->instance,
2829
                    'name'       => 'My assignment',
2830
                )
2831
            ));
2832
            $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2833
                    other['modulename']");
2834
        } catch (coding_exception $e) {
2835
            $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
2836
        }
2837
 
2838
        // Test not setting name.
2839
 
2840
        try {
2841
            $event = \core\event\course_module_updated::create(array(
2842
                'courseid' => $modinfo->course,
2843
                'context'  => $context,
2844
                'objectid' => $modinfo->coursemodule,
2845
                'other'    => array(
2846
                    'modulename' => 'assign',
2847
                    'instanceid' => $modinfo->instance,
2848
                )
2849
            ));
2850
            $this->fail("Event validation should not allow \\core\\event\\course_module_updated to be triggered without
2851
                    other['name']");
2852
        } catch (coding_exception $e) {
2853
            $this->assertStringContainsString("The 'name' value must be set in other.", $e->getMessage());
2854
        }
2855
 
2856
    }
2857
 
2858
    /**
2859
     * Tests for event related to course module delete.
2860
     */
11 efrain 2861
    public function test_course_module_deleted_event(): void {
1 efrain 2862
        global $USER, $DB;
2863
        $this->resetAfterTest();
2864
 
2865
        // Create and delete a module.
2866
        $sink = $this->redirectEvents();
2867
        $modinfo = $this->create_specific_module_test('forum');
2868
        $cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
2869
        course_delete_module($modinfo->coursemodule);
2870
        $events = $sink->get_events();
2871
        $event = array_pop($events); // delete module event.;
2872
        $sink->close();
2873
 
2874
        // Validate event data.
2875
        $this->assertInstanceOf('\core\event\course_module_deleted', $event);
2876
        $this->assertEquals($cm->id, $event->objectid);
2877
        $this->assertEquals($USER->id, $event->userid);
2878
        $this->assertEquals('course_modules', $event->objecttable);
2879
        $this->assertEquals(null, $event->get_url());
2880
        $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $cm->id));
2881
    }
2882
 
2883
    /**
2884
     * Tests for event validations related to course module deletion.
2885
     */
11 efrain 2886
    public function test_course_module_deleted_event_exceptions(): void {
1 efrain 2887
 
2888
        $this->resetAfterTest();
2889
 
2890
        // Generate data.
2891
        $modinfo = $this->create_specific_module_test('assign');
2892
        $context = context_module::instance($modinfo->coursemodule);
2893
 
2894
        // Test not setting instanceid.
2895
        try {
2896
            $event = \core\event\course_module_deleted::create(array(
2897
                'courseid' => $modinfo->course,
2898
                'context'  => $context,
2899
                'objectid' => $modinfo->coursemodule,
2900
                'other'    => array(
2901
                    'modulename' => 'assign',
2902
                    'name'       => 'My assignment',
2903
                )
2904
            ));
2905
            $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
2906
                    other['instanceid']");
2907
        } catch (coding_exception $e) {
2908
            $this->assertStringContainsString("The 'instanceid' value must be set in other.", $e->getMessage());
2909
        }
2910
 
2911
        // Test not setting modulename.
2912
        try {
2913
            $event = \core\event\course_module_deleted::create(array(
2914
                'courseid' => $modinfo->course,
2915
                'context'  => $context,
2916
                'objectid' => $modinfo->coursemodule,
2917
                'other'    => array(
2918
                    'instanceid' => $modinfo->instance,
2919
                    'name'       => 'My assignment',
2920
                )
2921
            ));
2922
            $this->fail("Event validation should not allow \\core\\event\\course_module_deleted to be triggered without
2923
                    other['modulename']");
2924
        } catch (coding_exception $e) {
2925
            $this->assertStringContainsString("The 'modulename' value must be set in other.", $e->getMessage());
2926
        }
2927
    }
2928
 
2929
    /**
2930
     * Returns a user object and its assigned new role.
2931
     *
2932
     * @param testing_data_generator $generator
2933
     * @param $contextid
2934
     * @return array The user object and the role ID
2935
     */
2936
    protected function get_user_objects(testing_data_generator $generator, $contextid) {
2937
        global $USER;
2938
 
2939
        if (empty($USER->id)) {
2940
            $user  = $generator->create_user();
2941
            $this->setUser($user);
2942
        }
2943
        $roleid = create_role('Test role', 'testrole', 'Test role description');
2944
        if (!is_array($contextid)) {
2945
            $contextid = array($contextid);
2946
        }
2947
        foreach ($contextid as $cid) {
2948
            $assignid = role_assign($roleid, $user->id, $cid);
2949
        }
2950
        return array($user, $roleid);
2951
    }
2952
 
2953
    /**
2954
     * Test course move after course.
2955
     */
11 efrain 2956
    public function test_course_change_sortorder_after_course(): void {
1 efrain 2957
        global $DB;
2958
 
2959
        $this->resetAfterTest(true);
2960
 
2961
        $generator = $this->getDataGenerator();
2962
        $category = $generator->create_category();
2963
        $course3 = $generator->create_course(array('category' => $category->id));
2964
        $course2 = $generator->create_course(array('category' => $category->id));
2965
        $course1 = $generator->create_course(array('category' => $category->id));
2966
        $context = $category->get_context();
2967
 
2968
        list($user, $roleid) = $this->get_user_objects($generator, $context->id);
2969
        $caps = course_capability_assignment::allow('moodle/category:manage', $roleid, $context->id);
2970
 
2971
        $courses = $category->get_courses();
2972
        $this->assertIsArray($courses);
2973
        $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2974
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2975
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2976
 
2977
        // Test moving down.
2978
        $this->assertTrue(course_change_sortorder_after_course($course1->id, $course3->id));
2979
        $courses = $category->get_courses();
2980
        $this->assertIsArray($courses);
2981
        $this->assertEquals(array($course2->id, $course3->id, $course1->id), array_keys($courses));
2982
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2983
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2984
 
2985
        // Test moving up.
2986
        $this->assertTrue(course_change_sortorder_after_course($course1->id, $course2->id));
2987
        $courses = $category->get_courses();
2988
        $this->assertIsArray($courses);
2989
        $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
2990
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2991
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
2992
 
2993
        // Test moving to the top.
2994
        $this->assertTrue(course_change_sortorder_after_course($course1->id, 0));
2995
        $courses = $category->get_courses();
2996
        $this->assertIsArray($courses);
2997
        $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
2998
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
2999
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
3000
    }
3001
 
3002
    /**
3003
     * Tests changing the visibility of a course.
3004
     */
11 efrain 3005
    public function test_course_change_visibility(): void {
1 efrain 3006
        global $DB;
3007
 
3008
        $this->resetAfterTest(true);
3009
 
3010
        $generator = $this->getDataGenerator();
3011
        $category = $generator->create_category();
3012
        $course = $generator->create_course(array('category' => $category->id));
3013
 
3014
        $this->assertEquals('1', $course->visible);
3015
        $this->assertEquals('1', $course->visibleold);
3016
 
3017
        $this->assertTrue(course_change_visibility($course->id, false));
3018
        $course = $DB->get_record('course', array('id' => $course->id));
3019
        $this->assertEquals('0', $course->visible);
3020
        $this->assertEquals('0', $course->visibleold);
3021
 
3022
        $this->assertTrue(course_change_visibility($course->id, true));
3023
        $course = $DB->get_record('course', array('id' => $course->id));
3024
        $this->assertEquals('1', $course->visible);
3025
        $this->assertEquals('1', $course->visibleold);
3026
    }
3027
 
3028
    /**
3029
     * Tests moving the course up and down by one.
3030
     */
11 efrain 3031
    public function test_course_change_sortorder_by_one(): void {
1 efrain 3032
        global $DB;
3033
 
3034
        $this->resetAfterTest(true);
3035
 
3036
        $generator = $this->getDataGenerator();
3037
        $category = $generator->create_category();
3038
        $course3 = $generator->create_course(array('category' => $category->id));
3039
        $course2 = $generator->create_course(array('category' => $category->id));
3040
        $course1 = $generator->create_course(array('category' => $category->id));
3041
 
3042
        $courses = $category->get_courses();
3043
        $this->assertIsArray($courses);
3044
        $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
3045
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
3046
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
3047
 
3048
        // Test moving down.
3049
        $course1 = get_course($course1->id);
3050
        $this->assertTrue(course_change_sortorder_by_one($course1, false));
3051
        $courses = $category->get_courses();
3052
        $this->assertIsArray($courses);
3053
        $this->assertEquals(array($course2->id, $course1->id, $course3->id), array_keys($courses));
3054
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
3055
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
3056
 
3057
        // Test moving up.
3058
        $course1 = get_course($course1->id);
3059
        $this->assertTrue(course_change_sortorder_by_one($course1, true));
3060
        $courses = $category->get_courses();
3061
        $this->assertIsArray($courses);
3062
        $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
3063
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
3064
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
3065
 
3066
        // Test moving the top course up one.
3067
        $course1 = get_course($course1->id);
3068
        $this->assertFalse(course_change_sortorder_by_one($course1, true));
3069
        // Check nothing changed.
3070
        $courses = $category->get_courses();
3071
        $this->assertIsArray($courses);
3072
        $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
3073
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
3074
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
3075
 
3076
        // Test moving the bottom course up down.
3077
        $course3 = get_course($course3->id);
3078
        $this->assertFalse(course_change_sortorder_by_one($course3, false));
3079
        // Check nothing changed.
3080
        $courses = $category->get_courses();
3081
        $this->assertIsArray($courses);
3082
        $this->assertEquals(array($course1->id, $course2->id, $course3->id), array_keys($courses));
3083
        $dbcourses = $DB->get_records('course', array('category' => $category->id), 'sortorder', 'id');
3084
        $this->assertEquals(array_keys($dbcourses), array_keys($courses));
3085
    }
3086
 
11 efrain 3087
    public function test_view_resources_list(): void {
1 efrain 3088
        $this->resetAfterTest();
3089
 
3090
        $course = self::getDataGenerator()->create_course();
3091
        $coursecontext = context_course::instance($course->id);
3092
 
3093
        $event = \core\event\course_resources_list_viewed::create(array('context' => context_course::instance($course->id)));
3094
        $sink = $this->redirectEvents();
3095
        $event->trigger();
3096
        $events = $sink->get_events();
3097
        $sink->close();
3098
 
3099
        // Validate the event.
3100
        $event = $events[0];
3101
        $this->assertInstanceOf('\core\event\course_resources_list_viewed', $event);
3102
        $this->assertEquals(null, $event->objecttable);
3103
        $this->assertEquals(null, $event->objectid);
3104
        $this->assertEquals($course->id, $event->courseid);
3105
        $this->assertEquals($coursecontext->id, $event->contextid);
3106
        $this->assertEventContextNotUsed($event);
3107
    }
3108
 
3109
    /**
3110
     * Test duplicate_module()
3111
     */
11 efrain 3112
    public function test_duplicate_module(): void {
1 efrain 3113
        $this->setAdminUser();
3114
        $this->resetAfterTest();
3115
        $course = self::getDataGenerator()->create_course();
3116
        $res = self::getDataGenerator()->create_module('resource', array('course' => $course));
3117
        $cm = get_coursemodule_from_id('resource', $res->cmid, 0, false, MUST_EXIST);
3118
 
3119
        $newcm = duplicate_module($course, $cm);
3120
 
3121
        // Make sure they are the same, except obvious id changes.
3122
        foreach ($cm as $prop => $value) {
3123
            if ($prop == 'id' || $prop == 'url' || $prop == 'instance' || $prop == 'added') {
3124
                // Ignore obviously different properties.
3125
                continue;
3126
            }
3127
            if ($prop == 'name') {
3128
                // We expect ' (copy)' to be added to the original name since MDL-59227.
3129
                $value = get_string('duplicatedmodule', 'moodle', $value);
3130
            }
3131
            $this->assertEquals($value, $newcm->$prop);
3132
        }
3133
    }
3134
 
3135
    /**
11 efrain 3136
     * Test that permissions are duplicated correctly after duplicate_module().
3137
     * @covers ::duplicate_module
3138
     * @return void
3139
     */
3140
    public function test_duplicate_module_permissions(): void {
3141
        global $DB;
3142
        $this->setAdminUser();
3143
        $this->resetAfterTest();
3144
 
3145
        // Create course and course module.
3146
        $course = self::getDataGenerator()->create_course();
3147
        $res = self::getDataGenerator()->create_module('assign', ['course' => $course]);
3148
        $cm = get_coursemodule_from_id('assign', $res->cmid, 0, false, MUST_EXIST);
3149
        $cmcontext = \context_module::instance($cm->id);
3150
 
3151
        // Enrol student user.
3152
        $user = self::getDataGenerator()->create_user();
3153
        $roleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
3154
        self::getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
3155
 
3156
        // Add capability to original course module.
3157
        assign_capability('gradereport/grader:view', CAP_ALLOW, $roleid, $cmcontext->id);
3158
 
3159
        // Duplicate module.
3160
        $newcm = duplicate_module($course, $cm);
3161
        $newcmcontext = \context_module::instance($newcm->id);
3162
 
3163
        // Assert that user still has capability.
3164
        $this->assertTrue(has_capability('gradereport/grader:view', $newcmcontext, $user));
3165
 
3166
        // Assert that both modules contain the same count of overrides.
3167
        $overrides = $DB->get_records('role_capabilities', ['contextid' => $cmcontext->id]);
3168
        $newoverrides = $DB->get_records('role_capabilities', ['contextid' => $newcmcontext->id]);
3169
        $this->assertEquals(count($overrides), count($newoverrides));
3170
    }
3171
 
3172
    /**
3173
     * Test that locally assigned roles are duplicated correctly after duplicate_module().
3174
     * @covers ::duplicate_module
3175
     * @return void
3176
     */
3177
    public function test_duplicate_module_role_assignments(): void {
3178
        global $DB;
3179
        $this->setAdminUser();
3180
        $this->resetAfterTest();
3181
 
3182
        // Create course and course module.
3183
        $course = self::getDataGenerator()->create_course();
3184
        $res = self::getDataGenerator()->create_module('assign', ['course' => $course]);
3185
        $cm = get_coursemodule_from_id('assign', $res->cmid, 0, false, MUST_EXIST);
3186
        $cmcontext = \context_module::instance($cm->id);
3187
 
3188
        // Enrol student user.
3189
        $user = self::getDataGenerator()->create_user();
3190
        $roleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
3191
        self::getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
3192
 
3193
        // Assign user a new local role.
3194
        $newroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
3195
        role_assign($newroleid, $user->id, $cmcontext->id);
3196
 
3197
        // Duplicate module.
3198
        $newcm = duplicate_module($course, $cm);
3199
        $newcmcontext = \context_module::instance($newcm->id);
3200
 
3201
        // Assert that user still has role assigned.
3202
        $this->assertTrue(user_has_role_assignment($user->id, $newroleid, $newcmcontext->id));
3203
 
3204
        // Assert that both modules contain the same count of overrides.
3205
        $overrides = $DB->get_records('role_assignments', ['contextid' => $cmcontext->id]);
3206
        $newoverrides = $DB->get_records('role_assignments', ['contextid' => $newcmcontext->id]);
3207
        $this->assertEquals(count($overrides), count($newoverrides));
3208
    }
3209
 
3210
    /**
1441 ariadna 3211
     * Ensure that modules with the feature flag FEATURE_CAN_DISPLAY set to false cannot be duplicated into a section other than 0.
3212
     * @covers ::duplicate_module()
3213
     */
3214
    public function test_duplicate_cannot_display_mods(): void {
3215
        self::setAdminUser();
3216
        $this->resetAfterTest();
3217
        $course = self::getDataGenerator()->create_course(['numsections' => 2], ['createsections' => true]);
3218
        $res = self::getDataGenerator()->create_module('qbank', ['course' => $course]);
3219
        $cm = get_coursemodule_from_id('qbank', $res->cmid, 0, false, MUST_EXIST);
3220
        $sectionid = get_fast_modinfo($course)->get_section_info(1)->id;
3221
 
3222
        $this->expectExceptionMessage("Modules with FEATURE_CAN_DISPLAY set to false can not be moved from section 0");
3223
        duplicate_module($course, $cm, $sectionid);
3224
    }
3225
 
3226
    /**
1 efrain 3227
     * Tests that when creating or updating a module, if the availability settings
3228
     * are present but set to an empty tree, availability is set to null in
3229
     * database.
3230
     */
11 efrain 3231
    public function test_empty_availability_settings(): void {
1 efrain 3232
        global $DB;
3233
        $this->setAdminUser();
3234
        $this->resetAfterTest();
3235
 
3236
        // Enable availability.
3237
        set_config('enableavailability', 1);
3238
 
3239
        // Test add.
3240
        $emptyavailability = json_encode(\core_availability\tree::get_root_json(array()));
3241
        $course = self::getDataGenerator()->create_course();
3242
        $label = self::getDataGenerator()->create_module('label', array(
3243
                'course' => $course, 'availability' => $emptyavailability));
3244
        $this->assertNull($DB->get_field('course_modules', 'availability',
3245
                array('id' => $label->cmid)));
3246
 
3247
        // Test update.
3248
        $formdata = $DB->get_record('course_modules', array('id' => $label->cmid));
3249
        unset($formdata->availability);
3250
        $formdata->availabilityconditionsjson = $emptyavailability;
3251
        $formdata->modulename = 'label';
3252
        $formdata->coursemodule = $label->cmid;
3253
        $draftid = 0;
3254
        file_prepare_draft_area($draftid, context_module::instance($label->cmid)->id,
3255
                'mod_label', 'intro', 0);
3256
        $formdata->introeditor = array(
3257
            'itemid' => $draftid,
3258
            'text' => '<p>Yo</p>',
3259
            'format' => FORMAT_HTML);
3260
        update_module($formdata);
3261
        $this->assertNull($DB->get_field('course_modules', 'availability',
3262
                array('id' => $label->cmid)));
3263
    }
3264
 
3265
    /**
3266
     * Test update_inplace_editable()
3267
     */
11 efrain 3268
    public function test_update_module_name_inplace(): void {
1 efrain 3269
        global $CFG, $DB, $PAGE;
3270
        require_once($CFG->dirroot . '/lib/external/externallib.php');
3271
 
3272
        $this->setUser($this->getDataGenerator()->create_user());
3273
 
3274
        $this->resetAfterTest(true);
3275
        $course = $this->getDataGenerator()->create_course();
3276
        $forum = self::getDataGenerator()->create_module('forum', array('course' => $course->id, 'name' => 'forum name'));
3277
 
3278
        // Call service for core_course component without necessary permissions.
3279
        try {
3280
            core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
3281
            $this->fail('Exception expected');
3282
        } catch (moodle_exception $e) {
3283
            $this->assertEquals('Course or activity not accessible. (Not enrolled)',
3284
                $e->getMessage());
3285
        }
3286
 
3287
        // Change to admin user and make sure that cm name can be updated using web service update_inplace_editable().
3288
        $this->setAdminUser();
3289
        $res = core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, 'New forum name');
3290
        $res = external_api::clean_returnvalue(core_external::update_inplace_editable_returns(), $res);
3291
        $this->assertEquals('New forum name', $res['value']);
3292
        $this->assertEquals('New forum name', $DB->get_field('forum', 'name', array('id' => $forum->id)));
3293
    }
3294
 
3295
    /**
3296
     * Testing function course_get_tagged_course_modules - search tagged course modules
3297
     */
11 efrain 3298
    public function test_course_get_tagged_course_modules(): void {
1 efrain 3299
        global $DB;
3300
        $this->resetAfterTest();
3301
        $course3 = $this->getDataGenerator()->create_course();
3302
        $course2 = $this->getDataGenerator()->create_course();
3303
        $course1 = $this->getDataGenerator()->create_course();
3304
        $cm11 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
3305
            'tags' => 'Cat, Dog'));
3306
        $cm12 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
3307
            'tags' => 'Cat, Mouse', 'visible' => 0));
3308
        $cm13 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
3309
            'tags' => 'Cat, Mouse, Dog'));
3310
        $cm21 = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id,
3311
            'tags' => 'Cat, Mouse'));
3312
        $cm31 = $this->getDataGenerator()->create_module('forum', array('course' => $course3->id,
3313
            'tags' => 'Cat, Mouse'));
3314
 
3315
        // Admin is able to view everything.
3316
        $this->setAdminUser();
3317
        $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3318
                /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
3319
        $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
3320
        $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
3321
        $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
3322
        $this->assertMatchesRegularExpression('/'.$cm21->name.'/', $res->content);
3323
        $this->assertMatchesRegularExpression('/'.$cm31->name.'/', $res->content);
3324
        // Results from course1 are returned before results from course2.
3325
        $this->assertTrue(strpos($res->content, $cm11->name) < strpos($res->content, $cm21->name));
3326
 
3327
        // Ordinary user is not able to see anything.
3328
        $user = $this->getDataGenerator()->create_user();
3329
        $this->setUser($user);
3330
 
3331
        $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3332
                /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */0, /*$rec = */1, /*$page = */0);
3333
        $this->assertNull($res);
3334
 
3335
        // Enrol user as student in course1 and course2.
3336
        $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
3337
        $this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['student']);
3338
        $this->getDataGenerator()->enrol_user($user->id, $course2->id, $roleids['student']);
3339
        core_tag_index_builder::reset_caches();
3340
 
3341
        // Searching in the course context returns visible modules in this course.
3342
        $context = context_course::instance($course1->id);
3343
        $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3344
                /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
3345
        $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
3346
        $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
3347
        $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
3348
        $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
3349
        $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content);
3350
 
3351
        // Searching FROM the course context returns visible modules in all courses.
3352
        $context = context_course::instance($course2->id);
3353
        $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3354
                /*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
3355
        $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
3356
        $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
3357
        $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
3358
        $this->assertMatchesRegularExpression('/'.$cm21->name.'/', $res->content);
3359
        $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
3360
        // Results from course2 are returned before results from course1.
3361
        $this->assertTrue(strpos($res->content, $cm21->name) < strpos($res->content, $cm11->name));
3362
 
3363
        // Enrol user in course1 as a teacher - now he should be able to see hidden module.
3364
        $this->getDataGenerator()->enrol_user($user->id, $course1->id, $roleids['editingteacher']);
3365
        get_fast_modinfo(0,0,true);
3366
 
3367
        $context = context_course::instance($course1->id);
3368
        $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3369
                /*$exclusivemode = */false, /*$fromctx = */$context->id, /*$ctx = */0, /*$rec = */1, /*$page = */0);
3370
        $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
3371
 
3372
        // Create more modules and try pagination.
3373
        $cm14 = $this->getDataGenerator()->create_module('assign', array('course' => $course1->id,
3374
            'tags' => 'Cat, Dog'));
3375
        $cm15 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
3376
            'tags' => 'Cat, Mouse', 'visible' => 0));
3377
        $cm16 = $this->getDataGenerator()->create_module('page', array('course' => $course1->id,
3378
            'tags' => 'Cat, Mouse, Dog'));
3379
 
3380
        $context = context_course::instance($course1->id);
3381
        $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3382
                /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */0);
3383
        $this->assertMatchesRegularExpression('/'.$cm11->name.'/', $res->content);
3384
        $this->assertMatchesRegularExpression('/'.$cm12->name.'/', $res->content);
3385
        $this->assertMatchesRegularExpression('/'.$cm13->name.'/', $res->content);
3386
        $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
3387
        $this->assertMatchesRegularExpression('/'.$cm14->name.'/', $res->content);
3388
        $this->assertMatchesRegularExpression('/'.$cm15->name.'/', $res->content);
3389
        $this->assertDoesNotMatchRegularExpression('/'.$cm16->name.'/', $res->content);
3390
        $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
3391
        $this->assertEmpty($res->prevpageurl);
3392
        $this->assertNotEmpty($res->nextpageurl);
3393
 
3394
        $res = course_get_tagged_course_modules(core_tag_tag::get_by_name(0, 'Cat'),
3395
                /*$exclusivemode = */false, /*$fromctx = */0, /*$ctx = */$context->id, /*$rec = */1, /*$page = */1);
3396
        $this->assertDoesNotMatchRegularExpression('/'.$cm11->name.'/', $res->content);
3397
        $this->assertDoesNotMatchRegularExpression('/'.$cm12->name.'/', $res->content);
3398
        $this->assertDoesNotMatchRegularExpression('/'.$cm13->name.'/', $res->content);
3399
        $this->assertDoesNotMatchRegularExpression('/'.$cm21->name.'/', $res->content);
3400
        $this->assertDoesNotMatchRegularExpression('/'.$cm14->name.'/', $res->content);
3401
        $this->assertDoesNotMatchRegularExpression('/'.$cm15->name.'/', $res->content);
3402
        $this->assertMatchesRegularExpression('/'.$cm16->name.'/', $res->content);
3403
        $this->assertDoesNotMatchRegularExpression('/'.$cm31->name.'/', $res->content); // No access to course3.
3404
        $this->assertNotEmpty($res->prevpageurl);
3405
        $this->assertEmpty($res->nextpageurl);
3406
    }
3407
 
3408
    /**
3409
     * Test course_get_user_navigation_options for frontpage.
3410
     */
11 efrain 3411
    public function test_course_get_user_navigation_options_for_frontpage(): void {
1 efrain 3412
        global $CFG, $SITE, $DB;
3413
        $this->resetAfterTest();
3414
        $context = context_system::instance();
3415
        $course = clone $SITE;
3416
        $this->setAdminUser();
3417
 
3418
        $navoptions = course_get_user_navigation_options($context, $course);
3419
        $this->assertTrue($navoptions->blogs);
3420
        $this->assertTrue($navoptions->notes);
3421
        $this->assertTrue($navoptions->participants);
3422
        $this->assertTrue($navoptions->badges);
3423
        $this->assertTrue($navoptions->tags);
3424
        $this->assertFalse($navoptions->search);
3425
        $this->assertTrue($navoptions->competencies);
3426
 
3427
        // Enable global search now.
3428
        $CFG->enableglobalsearch = 1;
3429
        $navoptions = course_get_user_navigation_options($context, $course);
3430
        $this->assertTrue($navoptions->search);
3431
 
3432
        // Disable competencies.
3433
        $oldcompetencies = get_config('core_competency', 'enabled');
3434
        set_config('enabled', false, 'core_competency');
3435
        $navoptions = course_get_user_navigation_options($context, $course);
3436
        $this->assertFalse($navoptions->competencies);
3437
        set_config('enabled', $oldcompetencies, 'core_competency');
3438
 
3439
        // Now try with a standard user.
3440
        $user = $this->getDataGenerator()->create_user();
3441
        $this->setUser($user);
3442
        $navoptions = course_get_user_navigation_options($context, $course);
3443
        $this->assertTrue($navoptions->blogs);
3444
        $this->assertFalse($navoptions->notes);
3445
        $this->assertFalse($navoptions->participants);
3446
        $this->assertTrue($navoptions->badges);
3447
        $this->assertTrue($navoptions->tags);
3448
        $this->assertTrue($navoptions->search);
3449
    }
3450
 
3451
    /**
3452
     * Test course_get_user_navigation_options for managers in a normal course.
3453
     */
11 efrain 3454
    public function test_course_get_user_navigation_options_for_managers(): void {
1 efrain 3455
        global $CFG;
3456
        $this->resetAfterTest();
3457
        $course = $this->getDataGenerator()->create_course();
3458
        $context = context_course::instance($course->id);
3459
        $this->setAdminUser();
3460
 
3461
        $navoptions = course_get_user_navigation_options($context);
3462
        $this->assertTrue($navoptions->blogs);
3463
        $this->assertTrue($navoptions->notes);
3464
        $this->assertTrue($navoptions->participants);
3465
        $this->assertTrue($navoptions->badges);
3466
    }
3467
 
3468
    /**
3469
     * Test course_get_user_navigation_options for students in a normal course.
3470
     */
11 efrain 3471
    public function test_course_get_user_navigation_options_for_students(): void {
1 efrain 3472
        global $DB, $CFG;
3473
        $this->resetAfterTest();
3474
        $course = $this->getDataGenerator()->create_course();
3475
        $context = context_course::instance($course->id);
3476
 
3477
        $user = $this->getDataGenerator()->create_user();
3478
        $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
3479
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
3480
 
3481
        $this->setUser($user);
3482
 
3483
        $navoptions = course_get_user_navigation_options($context);
3484
        $this->assertTrue($navoptions->blogs);
3485
        $this->assertFalse($navoptions->notes);
3486
        $this->assertTrue($navoptions->participants);
3487
        $this->assertFalse($navoptions->badges);
3488
 
3489
        // Disable some options.
3490
        $CFG->badges_allowcoursebadges = 0;
3491
        $CFG->enableblogs = 0;
3492
        // Disable view participants capability.
3493
        assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $context);
3494
 
3495
        $navoptions = course_get_user_navigation_options($context);
3496
        $this->assertFalse($navoptions->blogs);
3497
        $this->assertFalse($navoptions->notes);
3498
        $this->assertFalse($navoptions->participants);
3499
        $this->assertFalse($navoptions->badges);
3500
 
3501
        // Re-enable some options to check badges are displayed as expected.
3502
        $CFG->badges_allowcoursebadges = 1;
3503
        assign_capability('moodle/badges:createbadge', CAP_ALLOW, $roleid, $context);
3504
 
3505
        $navoptions = course_get_user_navigation_options($context);
3506
        $this->assertTrue($navoptions->badges);
3507
    }
3508
 
3509
    /**
3510
     * Test course_get_user_administration_options for frontpage.
3511
     */
11 efrain 3512
    public function test_course_get_user_administration_options_for_frontpage(): void {
1 efrain 3513
        global $CFG, $SITE;
3514
        $this->resetAfterTest();
3515
        $course = clone $SITE;
3516
        $context = context_course::instance($course->id);
3517
        $this->setAdminUser();
3518
 
3519
        $adminoptions = course_get_user_administration_options($course, $context);
3520
        $this->assertTrue($adminoptions->update);
3521
        $this->assertTrue($adminoptions->filters);
3522
        $this->assertTrue($adminoptions->reports);
3523
        $this->assertTrue($adminoptions->backup);
3524
        $this->assertTrue($adminoptions->restore);
3525
        $this->assertFalse($adminoptions->files);
3526
        $this->assertFalse($adminoptions->tags);
3527
 
3528
        // Now try with a standard user.
3529
        $user = $this->getDataGenerator()->create_user();
3530
        $this->setUser($user);
3531
        $adminoptions = course_get_user_administration_options($course, $context);
3532
        $this->assertFalse($adminoptions->update);
3533
        $this->assertFalse($adminoptions->filters);
3534
        $this->assertFalse($adminoptions->reports);
3535
        $this->assertFalse($adminoptions->backup);
3536
        $this->assertFalse($adminoptions->restore);
3537
        $this->assertFalse($adminoptions->files);
3538
        $this->assertFalse($adminoptions->tags);
3539
 
3540
    }
3541
 
3542
    /**
3543
     * Test course_get_user_administration_options for managers in a normal course.
3544
     */
11 efrain 3545
    public function test_course_get_user_administration_options_for_managers(): void {
1 efrain 3546
        global $CFG;
3547
        $this->resetAfterTest();
3548
        $course = $this->getDataGenerator()->create_course();
3549
        $context = context_course::instance($course->id);
3550
        $this->setAdminUser();
3551
 
3552
        $adminoptions = course_get_user_administration_options($course, $context);
3553
        $this->assertTrue($adminoptions->update);
3554
        $this->assertTrue($adminoptions->filters);
3555
        $this->assertTrue($adminoptions->reports);
3556
        $this->assertTrue($adminoptions->backup);
3557
        $this->assertTrue($adminoptions->restore);
3558
        $this->assertFalse($adminoptions->files);
3559
        $this->assertTrue($adminoptions->tags);
3560
        $this->assertTrue($adminoptions->gradebook);
3561
        $this->assertFalse($adminoptions->outcomes);
3562
        $this->assertTrue($adminoptions->badges);
3563
        $this->assertTrue($adminoptions->import);
3564
        $this->assertTrue($adminoptions->reset);
3565
        $this->assertTrue($adminoptions->roles);
3566
    }
3567
 
3568
    /**
3569
     * Test course_get_user_administration_options for students in a normal course.
3570
     */
11 efrain 3571
    public function test_course_get_user_administration_options_for_students(): void {
1 efrain 3572
        global $DB, $CFG;
3573
        $this->resetAfterTest();
3574
        $course = $this->getDataGenerator()->create_course();
3575
        $context = context_course::instance($course->id);
3576
 
3577
        $user = $this->getDataGenerator()->create_user();
3578
        $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
3579
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
3580
 
3581
        $this->setUser($user);
3582
        $adminoptions = course_get_user_administration_options($course, $context);
3583
 
3584
        $this->assertFalse($adminoptions->update);
3585
        $this->assertFalse($adminoptions->filters);
3586
        $this->assertFalse($adminoptions->reports);
3587
        $this->assertFalse($adminoptions->backup);
3588
        $this->assertFalse($adminoptions->restore);
3589
        $this->assertFalse($adminoptions->files);
3590
        $this->assertFalse($adminoptions->tags);
3591
        $this->assertFalse($adminoptions->gradebook);
3592
        $this->assertFalse($adminoptions->outcomes);
3593
        $this->assertTrue($adminoptions->badges);
3594
        $this->assertFalse($adminoptions->import);
3595
        $this->assertFalse($adminoptions->reset);
3596
        $this->assertFalse($adminoptions->roles);
3597
 
3598
        $CFG->enablebadges = false;
3599
        $adminoptions = course_get_user_administration_options($course, $context);
3600
        $this->assertFalse($adminoptions->badges);
3601
    }
3602
 
3603
    /**
3604
     * Test test_update_course_frontpage_category.
3605
     */
11 efrain 3606
    public function test_update_course_frontpage_category(): void {
1 efrain 3607
        // Fetch front page course.
3608
        $course = get_course(SITEID);
3609
        // Test update information on front page course.
3610
        $course->category = 99;
3611
        $this->expectException('moodle_exception');
3612
        $this->expectExceptionMessage(get_string('invalidcourse', 'error'));
3613
        update_course($course);
3614
    }
3615
 
3616
    /**
3617
     * test_course_enddate
3618
     *
3619
     * @dataProvider course_enddate_provider
3620
     * @param int $startdate
3621
     * @param int $enddate
3622
     * @param string $errorcode
3623
     */
11 efrain 3624
    public function test_course_enddate($startdate, $enddate, $errorcode): void {
1 efrain 3625
 
3626
        $this->resetAfterTest(true);
3627
 
3628
        $record = array('startdate' => $startdate, 'enddate' => $enddate);
3629
        try {
3630
            $course1 = $this->getDataGenerator()->create_course($record);
3631
            if ($errorcode !== false) {
3632
                $this->fail('Expected exception with "' . $errorcode . '" error code in create_create');
3633
            }
3634
        } catch (moodle_exception $e) {
3635
            if ($errorcode === false) {
3636
                $this->fail('Got "' . $errorcode . '" exception error code and no exception was expected');
3637
            }
3638
            if ($e->errorcode != $errorcode) {
3639
                $this->fail('Got "' . $e->errorcode. '" exception error code and "' . $errorcode . '" was expected');
3640
            }
3641
            return;
3642
        }
3643
 
3644
        $this->assertEquals($startdate, $course1->startdate);
3645
        $this->assertEquals($enddate, $course1->enddate);
3646
    }
3647
 
3648
    /**
3649
     * Provider for test_course_enddate.
3650
     *
3651
     * @return array
3652
     */
1441 ariadna 3653
    public static function course_enddate_provider(): array {
1 efrain 3654
        // Each provided example contains startdate, enddate and the expected exception error code if there is any.
3655
        return [
3656
            [
3657
                111,
3658
                222,
3659
                false
3660
            ], [
3661
                222,
3662
                111,
3663
                'enddatebeforestartdate'
3664
            ], [
3665
                111,
3666
                0,
3667
                false
3668
            ], [
3669
                0,
3670
                222,
3671
                'nostartdatenoenddate'
3672
            ]
3673
        ];
3674
    }
3675
 
3676
 
3677
    /**
3678
     * test_course_dates_reset
3679
     *
3680
     * @dataProvider course_dates_reset_provider
3681
     * @param int $startdate
3682
     * @param int $enddate
3683
     * @param int $resetstartdate
3684
     * @param int $resetenddate
3685
     * @param int $resultingstartdate
3686
     * @param int $resultingenddate
3687
     */
11 efrain 3688
    public function test_course_dates_reset($startdate, $enddate, $resetstartdate, $resetenddate, $resultingstartdate, $resultingenddate): void {
1 efrain 3689
        global $CFG, $DB;
3690
 
3691
        require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
3692
 
3693
        $this->resetAfterTest(true);
3694
 
3695
        $this->setAdminUser();
3696
 
3697
        $CFG->enablecompletion = true;
3698
 
3699
        $this->setTimezone('UTC');
3700
 
3701
        $record = array('startdate' => $startdate, 'enddate' => $enddate, 'enablecompletion' => 1);
3702
        $originalcourse = $this->getDataGenerator()->create_course($record);
3703
        $coursecriteria = new completion_criteria_date(array('course' => $originalcourse->id, 'timeend' => $startdate + DAYSECS));
3704
        $coursecriteria->insert();
3705
 
3706
        $activitycompletiondate = $startdate + DAYSECS;
3707
        $data = $this->getDataGenerator()->create_module('data', array('course' => $originalcourse->id),
3708
                        array('completion' => 1, 'completionexpected' => $activitycompletiondate));
3709
 
3710
        $resetdata = new stdClass();
3711
        $resetdata->id = $originalcourse->id;
3712
        $resetdata->reset_start_date_old = $originalcourse->startdate;
3713
        $resetdata->reset_start_date = $resetstartdate;
3714
        $resetdata->reset_end_date = $resetenddate;
3715
        $resetdata->reset_end_date_old = $record['enddate'];
3716
        reset_course_userdata($resetdata);
3717
 
3718
        $course = $DB->get_record('course', array('id' => $originalcourse->id));
3719
 
3720
        $this->assertEquals($resultingstartdate, $course->startdate);
3721
        $this->assertEquals($resultingenddate, $course->enddate);
3722
 
3723
        $coursecompletioncriteria = completion_criteria_date::fetch(array('course' => $originalcourse->id));
3724
        $this->assertEquals($resultingstartdate + DAYSECS, $coursecompletioncriteria->timeend);
3725
 
3726
        $this->assertEquals($resultingstartdate + DAYSECS, $DB->get_field('course_modules', 'completionexpected',
3727
            array('id' => $data->cmid)));
3728
    }
3729
 
3730
    /**
3731
     * Provider for test_course_dates_reset.
3732
     *
3733
     * @return array
3734
     */
1441 ariadna 3735
    public static function course_dates_reset_provider(): array {
1 efrain 3736
 
3737
        // Each example contains the following:
3738
        // - course startdate
3739
        // - course enddate
3740
        // - startdate to reset to (false if not reset)
3741
        // - enddate to reset to (false if not reset)
3742
        // - resulting startdate
3743
        // - resulting enddate
3744
        $time = 1445644800;
3745
        return [
3746
            // No date changes.
3747
            [
3748
                $time,
3749
                $time + DAYSECS,
3750
                false,
3751
                false,
3752
                $time,
3753
                $time + DAYSECS
3754
            ],
3755
            // End date changes to a valid value.
3756
            [
3757
                $time,
3758
                $time + DAYSECS,
3759
                false,
3760
                $time + DAYSECS + 111,
3761
                $time,
3762
                $time + DAYSECS + 111
3763
            ],
3764
            // Start date changes to a valid value. End date does not get updated because it does not have value.
3765
            [
3766
                $time,
3767
                0,
3768
                $time + DAYSECS,
3769
                false,
3770
                $time + DAYSECS,
3771
 
3772
            ],
3773
            // Start date changes to a valid value. End date gets updated accordingly.
3774
            [
3775
                $time,
3776
                $time + DAYSECS,
3777
                $time + WEEKSECS,
3778
                false,
3779
                $time + WEEKSECS,
3780
                $time + WEEKSECS + DAYSECS
3781
            ],
3782
            // Start date and end date change to a valid value.
3783
            [
3784
                $time,
3785
                $time + DAYSECS,
3786
                $time + WEEKSECS,
3787
                $time + YEARSECS,
3788
                $time + WEEKSECS,
3789
                $time + YEARSECS
1441 ariadna 3790
            ],
3791
            // Time shift is between exact times, not midnight(s) (MDL-65233).
3792
            [
3793
                $time + HOURSECS,
3794
                $time + DAYSECS,
3795
                $time + WEEKSECS + HOURSECS,
3796
                false,
3797
                $time + WEEKSECS + HOURSECS,
3798
                $time + WEEKSECS + DAYSECS,
3799
            ],
1 efrain 3800
        ];
3801
    }
3802
 
3803
    /**
3804
     * Test reset_course_userdata()
3805
     *    - with reset_roles_overrides enabled
3806
     *    - with selective role unenrolments
3807
     */
11 efrain 3808
    public function test_course_roles_reset(): void {
1 efrain 3809
        global $DB;
3810
 
3811
        $this->resetAfterTest(true);
3812
 
3813
        $generator = $this->getDataGenerator();
3814
 
3815
        // Create test course and user, enrol one in the other.
3816
        $course = $generator->create_course();
3817
        $user = $generator->create_user();
3818
        $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
3819
        $generator->enrol_user($user->id, $course->id, $roleid);
3820
 
3821
        // Test case with reset_roles_overrides enabled.
3822
        // Override course so it does NOT allow students 'mod/forum:viewdiscussion'.
3823
        $coursecontext = context_course::instance($course->id);
3824
        assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $roleid, $coursecontext->id);
3825
 
3826
        // Check expected capabilities so far.
3827
        $this->assertFalse(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
3828
 
3829
        // Oops, preventing student from viewing forums was a mistake, let's reset the course.
3830
        $resetdata = new stdClass();
3831
        $resetdata->id = $course->id;
3832
        $resetdata->reset_roles_overrides = true;
3833
        reset_course_userdata($resetdata);
3834
 
3835
        // Check new expected capabilities - override at the course level should be reset.
3836
        $this->assertTrue(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
3837
 
3838
        // Test case with selective role unenrolments.
3839
        $roles = array();
3840
        $roles['student'] = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
3841
        $roles['teacher'] = $DB->get_field('role', 'id', array('shortname' => 'teacher'), MUST_EXIST);
3842
 
3843
        // We enrol a user with student and teacher roles.
3844
        $generator->enrol_user($user->id, $course->id, $roles['student']);
3845
        $generator->enrol_user($user->id, $course->id, $roles['teacher']);
3846
 
3847
        // When we reset only student role, we expect to keep teacher role.
3848
        $resetdata = new stdClass();
3849
        $resetdata->id = $course->id;
3850
        $resetdata->unenrol_users = array($roles['student']);
3851
        reset_course_userdata($resetdata);
3852
 
3853
        $usersroles = enrol_get_course_users_roles($course->id);
3854
        $this->assertArrayHasKey($user->id, $usersroles);
3855
        $this->assertArrayHasKey($roles['teacher'], $usersroles[$user->id]);
3856
        $this->assertArrayNotHasKey($roles['student'], $usersroles[$user->id]);
3857
        $this->assertCount(1, $usersroles[$user->id]);
3858
 
3859
        // We reenrol user as student.
3860
        $generator->enrol_user($user->id, $course->id, $roles['student']);
3861
 
3862
        // When we reset student and teacher roles, we expect no roles left.
3863
        $resetdata = new stdClass();
3864
        $resetdata->id = $course->id;
3865
        $resetdata->unenrol_users = array($roles['student'], $roles['teacher']);
3866
        reset_course_userdata($resetdata);
3867
 
3868
        $usersroles = enrol_get_course_users_roles($course->id);
3869
        $this->assertEmpty($usersroles);
3870
    }
3871
 
11 efrain 3872
    public function test_course_check_module_updates_since(): void {
1 efrain 3873
        global $CFG, $DB, $USER;
3874
        require_once($CFG->dirroot . '/mod/glossary/lib.php');
3875
        require_once($CFG->dirroot . '/rating/lib.php');
3876
        require_once($CFG->dirroot . '/comment/lib.php');
3877
 
3878
        $this->resetAfterTest(true);
3879
 
3880
        $CFG->enablecompletion = true;
3881
        $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
3882
        $glossary = $this->getDataGenerator()->create_module('glossary', array(
3883
            'course' => $course->id,
3884
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
3885
            'completionview' => 1,
3886
            'allowcomments' => 1,
3887
            'assessed' => RATING_AGGREGATE_AVERAGE,
3888
            'scale' => 100
3889
        ));
3890
        $glossarygenerator = $this->getDataGenerator()->get_plugin_generator('mod_glossary');
3891
        $context = context_module::instance($glossary->cmid);
3892
        $modinfo = get_fast_modinfo($course);
3893
        $cm = $modinfo->get_cm($glossary->cmid);
3894
        $user = $this->getDataGenerator()->create_user();
3895
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
3896
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
3897
        $from = time();
3898
 
3899
        $teacher = $this->getDataGenerator()->create_user();
3900
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
3901
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
3902
 
3903
        assign_capability('mod/glossary:viewanyrating', CAP_ALLOW, $studentrole->id, $context->id, true);
3904
 
3905
        // Check nothing changed right now.
3906
        $updates = course_check_module_updates_since($cm, $from);
3907
        $this->assertFalse($updates->configuration->updated);
3908
        $this->assertFalse($updates->completion->updated);
3909
        $this->assertFalse($updates->gradeitems->updated);
3910
        $this->assertFalse($updates->comments->updated);
3911
        $this->assertFalse($updates->ratings->updated);
3912
        $this->assertFalse($updates->introfiles->updated);
3913
        $this->assertFalse($updates->outcomes->updated);
3914
 
3915
        $this->waitForSecond();
3916
 
3917
        // Do some changes.
3918
        $this->setUser($user);
3919
        $entry = $glossarygenerator->create_content($glossary);
3920
 
3921
        $this->setUser($teacher);
3922
        // Name.
3923
        set_coursemodule_name($glossary->cmid, 'New name');
3924
 
3925
        // Add some ratings.
3926
        $rm = new rating_manager();
3927
        $result = $rm->add_rating($cm, $context, 'mod_glossary', 'entry', $entry->id, 100, 50, $user->id, RATING_AGGREGATE_AVERAGE);
3928
 
3929
        // Change grades.
3930
        $glossary->cmidnumber = $glossary->cmid;
3931
        glossary_update_grades($glossary, $user->id);
3932
 
3933
        $this->setUser($user);
3934
        // Completion status.
3935
        glossary_view($glossary, $course, $cm, $context, 'letter');
3936
 
3937
        // Add one comment.
3938
        $args = new stdClass;
3939
        $args->context   = $context;
3940
        $args->course    = $course;
3941
        $args->cm        = $cm;
3942
        $args->area      = 'glossary_entry';
3943
        $args->itemid    = $entry->id;
3944
        $args->client_id = 1;
3945
        $args->component = 'mod_glossary';
3946
        $manager = new comment($args);
3947
        $manager->add('blah blah blah');
3948
 
3949
        // Check upgrade status.
3950
        $updates = course_check_module_updates_since($cm, $from);
3951
        $this->assertTrue($updates->configuration->updated);
3952
        $this->assertTrue($updates->completion->updated);
3953
        $this->assertTrue($updates->gradeitems->updated);
3954
        $this->assertTrue($updates->comments->updated);
3955
        $this->assertTrue($updates->ratings->updated);
3956
        $this->assertFalse($updates->introfiles->updated);
3957
        $this->assertFalse($updates->outcomes->updated);
3958
    }
3959
 
11 efrain 3960
    public function test_async_module_deletion_hook_implemented(): void {
1 efrain 3961
        // Async module deletion depends on the 'true' being returned by at least one plugin implementing the hook,
3962
        // 'course_module_adhoc_deletion_recommended'. In core, is implemented by the course recyclebin, which will only return
3963
        // true if the recyclebin plugin is enabled. To make sure async deletion occurs, this test force-enables the recyclebin.
3964
        global $DB, $USER;
3965
        $this->resetAfterTest(true);
3966
        $this->setAdminUser();
3967
 
3968
        // Ensure recyclebin is enabled.
3969
        set_config('coursebinenable', true, 'tool_recyclebin');
3970
 
3971
        // Create course, module and context.
3972
        $course = $this->getDataGenerator()->create_course(['numsections' => 5]);
3973
        $module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
3974
        $modcontext = context_module::instance($module->cmid);
3975
 
3976
        // Verify context exists.
3977
        $this->assertInstanceOf('context_module', $modcontext);
3978
 
3979
        // Check events generated on the course_delete_module call.
3980
        $sink = $this->redirectEvents();
3981
 
3982
        // Try to delete the module using the async flag.
3983
        course_delete_module($module->cmid, true); // Try to delete the module asynchronously.
3984
 
3985
        // Verify that no event has been generated yet.
3986
        $events = $sink->get_events();
3987
        $event = array_pop($events);
3988
        $sink->close();
3989
        $this->assertEmpty($event);
3990
 
3991
        // Grab the record, in it's final state before hard deletion, for comparison with the event snapshot.
3992
        // We need to do this because the 'deletioninprogress' flag has changed from '0' to '1'.
3993
        $cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST);
3994
 
3995
        // Verify the course_module is marked as 'deletioninprogress'.
3996
        $this->assertNotEquals($cm, false);
3997
        $this->assertEquals($cm->deletioninprogress, '1');
3998
 
3999
        // Verify the context has not yet been removed.
4000
        $this->assertEquals($modcontext, context_module::instance($module->cmid, IGNORE_MISSING));
4001
 
4002
        // Set up a sink to catch the 'course_module_deleted' event.
4003
        $sink = $this->redirectEvents();
4004
 
4005
        // Now, run the adhoc task which performs the hard deletion.
4006
        phpunit_util::run_all_adhoc_tasks();
4007
 
4008
        // Fetch and validate the event data.
4009
        $events = $sink->get_events();
4010
        $event = array_pop($events);
4011
        $sink->close();
4012
        $this->assertInstanceOf('\core\event\course_module_deleted', $event);
4013
        $this->assertEquals($module->cmid, $event->objectid);
4014
        $this->assertEquals($USER->id, $event->userid);
4015
        $this->assertEquals('course_modules', $event->objecttable);
4016
        $this->assertEquals(null, $event->get_url());
4017
        $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
4018
 
4019
        // Verify the context has been removed.
4020
        $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
4021
 
4022
        // Verify the course_module record has been deleted.
4023
        $cmcount = $DB->count_records('course_modules', ['id' => $module->cmid]);
4024
        $this->assertEmpty($cmcount);
4025
    }
4026
 
11 efrain 4027
    public function test_async_module_deletion_hook_not_implemented(): void {
1 efrain 4028
        // Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns
4029
        // 'true' from the 'course_module_adhoc_deletion_recommended' hook.
4030
        // In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it.
4031
        global $DB, $USER;
4032
        $this->resetAfterTest(true);
4033
        $this->setAdminUser();
4034
        set_config('coursebinenable', false, 'tool_recyclebin');
4035
 
4036
        // Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test.
4037
        // If at least one plugin still returns true, then skip this test.
4038
        if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
4039
            foreach ($pluginsfunction as $plugintype => $plugins) {
4040
                foreach ($plugins as $pluginfunction) {
4041
                    if ($pluginfunction()) {
4042
                        $this->markTestSkipped();
4043
                    }
4044
                }
4045
            }
4046
        }
4047
 
4048
        // Create course, module and context.
4049
        $course = $this->getDataGenerator()->create_course(['numsections' => 5]);
4050
        $module = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
4051
        $modcontext = context_module::instance($module->cmid);
4052
        $cm = $DB->get_record('course_modules', ['id' => $module->cmid], '*', MUST_EXIST);
4053
 
4054
        // Verify context exists.
4055
        $this->assertInstanceOf('context_module', $modcontext);
4056
 
4057
        // Check events generated on the course_delete_module call.
4058
        $sink = $this->redirectEvents();
4059
 
4060
        // Try to delete the module using the async flag.
4061
        course_delete_module($module->cmid, true); // Try to delete the module asynchronously.
4062
 
4063
        // Fetch and validate the event data.
4064
        $events = $sink->get_events();
4065
        $event = array_pop($events);
4066
        $sink->close();
4067
        $this->assertInstanceOf('\core\event\course_module_deleted', $event);
4068
        $this->assertEquals($module->cmid, $event->objectid);
4069
        $this->assertEquals($USER->id, $event->userid);
4070
        $this->assertEquals('course_modules', $event->objecttable);
4071
        $this->assertEquals(null, $event->get_url());
4072
        $this->assertEquals($cm, $event->get_record_snapshot('course_modules', $module->cmid));
4073
 
4074
        // Verify the context has been removed.
4075
        $this->assertFalse(context_module::instance($module->cmid, IGNORE_MISSING));
4076
 
4077
        // Verify the course_module record has been deleted.
4078
        $cmcount = $DB->count_records('course_modules', ['id' => $module->cmid]);
4079
        $this->assertEmpty($cmcount);
4080
    }
4081
 
11 efrain 4082
    public function test_async_section_deletion_hook_implemented(): void {
1 efrain 4083
        // Async section deletion (provided section contains modules), depends on the 'true' being returned by at least one plugin
4084
        // implementing the 'course_module_adhoc_deletion_recommended' hook. In core, is implemented by the course recyclebin,
4085
        // which will only return true if the plugin is enabled. To make sure async deletion occurs, this test enables recyclebin.
4086
        global $DB, $USER;
4087
        $this->resetAfterTest(true);
4088
        $this->setAdminUser();
4089
 
4090
        // Ensure recyclebin is enabled.
4091
        set_config('coursebinenable', true, 'tool_recyclebin');
4092
 
4093
        // Create course, module and context.
4094
        $generator = $this->getDataGenerator();
4095
        $course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]);
4096
        $assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
4097
        $assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
4098
        $assign2 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
4099
        $assign3 = $generator->create_module('assign', ['course' => $course, 'section' => 0]);
4100
 
4101
        // Delete empty section. No difference from normal, synchronous behaviour.
4102
        $this->assertTrue(course_delete_section($course, 4, false, true));
4103
        $this->assertEquals(3, course_get_format($course)->get_last_section_number());
4104
 
4105
        // Delete a module in section 2 (using async). Need to verify this doesn't generate two tasks when we delete
4106
        // the section in the next step.
4107
        course_delete_module($assign2->cmid, true);
4108
 
4109
        // Confirm that the module is pending deletion in its current section.
4110
        $section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison.
4111
        $this->assertEquals(true, $DB->record_exists('course_modules', ['id' => $assign2->cmid, 'deletioninprogress' => 1,
4112
                                                     'section' => $section->id]));
4113
 
4114
        // Now, delete section 2.
4115
        $this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change.
4116
 
4117
        $sink = $this->redirectEvents(); // To capture the event.
4118
        $this->assertTrue(course_delete_section($course, 2, true, true));
4119
 
4120
        // Now, confirm that:
4121
        // a) the section's modules have been flagged for deletion and moved to section 0 and;
4122
        // b) the section has been deleted and;
4123
        // c) course_section_deleted event has been fired. The course_module_deleted events will only fire once they have been
4124
        // removed from section 0 via the adhoc task.
4125
 
4126
        // Modules should have been flagged for deletion and moved to section 0.
4127
        $sectionid = $DB->get_field('course_sections', 'id', ['course' => $course->id, 'section' => 0]);
4128
        $this->assertEquals(3, $DB->count_records('course_modules', ['section' => $sectionid, 'deletioninprogress' => 1]));
4129
 
4130
        // Confirm the section has been deleted.
4131
        $this->assertEquals(2, course_get_format($course)->get_last_section_number());
4132
 
4133
        // Check event fired.
4134
        $events = $sink->get_events();
4135
        $event = array_pop($events);
4136
        $sink->close();
4137
        $this->assertInstanceOf('\core\event\course_section_deleted', $event);
4138
        $this->assertEquals($section->id, $event->objectid);
4139
        $this->assertEquals($USER->id, $event->userid);
4140
        $this->assertEquals('course_sections', $event->objecttable);
4141
        $this->assertEquals(null, $event->get_url());
4142
        $this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id));
4143
 
4144
        // Now, run the adhoc task to delete the modules from section 0.
4145
        $sink = $this->redirectEvents(); // To capture the events.
4146
        phpunit_util::run_all_adhoc_tasks();
4147
 
4148
        // Confirm the modules have been deleted.
4149
        list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid, $assign2->cmid]);
4150
        $cmcount = $DB->count_records_select('course_modules', 'id ' . $insql,  $assignids);
4151
        $this->assertEmpty($cmcount);
4152
 
4153
        // Confirm other modules in section 0 still remain.
4154
        $this->assertEquals(1, $DB->count_records('course_modules', ['id' => $assign3->cmid]));
4155
 
4156
        // Confirm that events were generated for all 3 of the modules.
4157
        $events = $sink->get_events();
4158
        $sink->close();
4159
        $count = 0;
4160
        while (!empty($events)) {
4161
            $event = array_pop($events);
4162
            if ($event instanceof \core\event\course_module_deleted &&
4163
                in_array($event->objectid, [$assign0->cmid, $assign1->cmid, $assign2->cmid])) {
4164
                $count++;
4165
            }
4166
        }
4167
        $this->assertEquals(3, $count);
4168
    }
4169
 
11 efrain 4170
    public function test_async_section_deletion_hook_not_implemented(): void {
1 efrain 4171
        // If no plugins advocate async removal, then normal synchronous removal will take place.
4172
        // Only proceed if we are sure that no plugin is going to advocate async removal of a module. I.e. no plugin returns
4173
        // 'true' from the 'course_module_adhoc_deletion_recommended' hook.
4174
        // In the case of core, only recyclebin implements this hook, and it will only return true if enabled, so disable it.
4175
        global $DB, $USER;
4176
        $this->resetAfterTest(true);
4177
        $this->setAdminUser();
4178
        set_config('coursebinenable', false, 'tool_recyclebin');
4179
 
4180
        // Non-core plugins might implement the 'course_module_adhoc_deletion_recommended' hook and spoil this test.
4181
        // If at least one plugin still returns true, then skip this test.
4182
        if ($pluginsfunction = get_plugins_with_function('course_module_background_deletion_recommended')) {
4183
            foreach ($pluginsfunction as $plugintype => $plugins) {
4184
                foreach ($plugins as $pluginfunction) {
4185
                    if ($pluginfunction()) {
4186
                        $this->markTestSkipped();
4187
                    }
4188
                }
4189
            }
4190
        }
4191
 
4192
        // Create course, module and context.
4193
        $generator = $this->getDataGenerator();
4194
        $course = $generator->create_course(['numsections' => 4, 'format' => 'topics'], ['createsections' => true]);
4195
        $assign0 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
4196
        $assign1 = $generator->create_module('assign', ['course' => $course, 'section' => 2]);
4197
 
4198
        // Delete empty section. No difference from normal, synchronous behaviour.
4199
        $this->assertTrue(course_delete_section($course, 4, false, true));
4200
        $this->assertEquals(3, course_get_format($course)->get_last_section_number());
4201
 
4202
        // Delete section in the middle (2).
4203
        $section = $DB->get_record('course_sections', ['course' => $course->id, 'section' => '2']); // For event comparison.
4204
        $this->assertFalse(course_delete_section($course, 2, false, true)); // Non-empty section, no forcedelete, so no change.
4205
 
4206
        $sink = $this->redirectEvents(); // To capture the event.
4207
        $this->assertTrue(course_delete_section($course, 2, true, true));
4208
 
4209
        // Now, confirm that:
4210
        // a) The section's modules have deleted and;
4211
        // b) the section has been deleted and;
4212
        // c) course_section_deleted event has been fired and;
4213
        // d) course_module_deleted events have both been fired.
4214
 
4215
        // Confirm modules have been deleted.
4216
        list($insql, $assignids) = $DB->get_in_or_equal([$assign0->cmid, $assign1->cmid]);
4217
        $cmcount = $DB->count_records_select('course_modules', 'id ' . $insql, $assignids);
4218
        $this->assertEmpty($cmcount);
4219
 
4220
        // Confirm the section has been deleted.
4221
        $this->assertEquals(2, course_get_format($course)->get_last_section_number());
4222
 
4223
        // Confirm the course_section_deleted event has been generated.
4224
        $events = $sink->get_events();
4225
        $event = array_pop($events);
4226
        $sink->close();
4227
        $this->assertInstanceOf('\core\event\course_section_deleted', $event);
4228
        $this->assertEquals($section->id, $event->objectid);
4229
        $this->assertEquals($USER->id, $event->userid);
4230
        $this->assertEquals('course_sections', $event->objecttable);
4231
        $this->assertEquals(null, $event->get_url());
4232
        $this->assertEquals($section, $event->get_record_snapshot('course_sections', $section->id));
4233
 
4234
        // Confirm that the course_module_deleted events have both been generated.
4235
        $count = 0;
4236
        while (!empty($events)) {
4237
            $event = array_pop($events);
4238
            if ($event instanceof \core\event\course_module_deleted &&
4239
                in_array($event->objectid, [$assign0->cmid, $assign1->cmid])) {
4240
                $count++;
4241
            }
4242
        }
4243
        $this->assertEquals(2, $count);
4244
    }
4245
 
11 efrain 4246
    public function test_classify_course_for_timeline(): void {
1 efrain 4247
        global $DB, $CFG;
4248
 
4249
        require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
4250
 
4251
        set_config('enablecompletion', COMPLETION_ENABLED);
4252
        set_config('coursegraceperiodbefore', 0);
4253
        set_config('coursegraceperiodafter', 0);
4254
 
4255
        $this->resetAfterTest(true);
4256
        $this->setAdminUser();
4257
 
4258
        // Create courses for testing.
4259
        $generator = $this->getDataGenerator();
4260
        $future = time() + 3600;
4261
        $past = time() - 3600;
4262
        $futurecourse = $generator->create_course(['startdate' => $future]);
4263
        $pastcourse = $generator->create_course(['startdate' => $past - 60, 'enddate' => $past]);
4264
        $completedcourse = $generator->create_course(['enablecompletion' => COMPLETION_ENABLED]);
4265
        $inprogresscourse = $generator->create_course();
4266
 
4267
        // Set completion rules.
4268
        $criteriadata = new stdClass();
4269
        $criteriadata->id = $completedcourse->id;
4270
 
4271
        // Self completion.
4272
        $criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF;
4273
        $class = 'completion_criteria_self';
4274
        $criterion = new $class();
4275
        $criterion->update_config($criteriadata);
4276
 
4277
        $user = $this->getDataGenerator()->create_user();
4278
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
4279
        $this->getDataGenerator()->enrol_user($user->id, $futurecourse->id, $studentrole->id);
4280
        $this->getDataGenerator()->enrol_user($user->id, $pastcourse->id, $studentrole->id);
4281
        $this->getDataGenerator()->enrol_user($user->id, $completedcourse->id, $studentrole->id);
4282
        $this->getDataGenerator()->enrol_user($user->id, $inprogresscourse->id, $studentrole->id);
4283
 
4284
        $this->setUser($user);
4285
        core_completion_external::mark_course_self_completed($completedcourse->id);
4286
        $ccompletion = new completion_completion(array('course' => $completedcourse->id, 'userid' => $user->id));
4287
        $ccompletion->mark_complete();
4288
 
4289
        // Aggregate the completions.
4290
        $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($pastcourse));
4291
        $this->assertEquals(COURSE_TIMELINE_FUTURE, course_classify_for_timeline($futurecourse));
4292
        $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse));
4293
        $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse));
4294
 
4295
        // Test grace period.
4296
        set_config('coursegraceperiodafter', 1);
4297
        set_config('coursegraceperiodbefore', 1);
4298
        $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($pastcourse));
4299
        $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($futurecourse));
4300
        $this->assertEquals(COURSE_TIMELINE_PAST, course_classify_for_timeline($completedcourse));
4301
        $this->assertEquals(COURSE_TIMELINE_INPROGRESS, course_classify_for_timeline($inprogresscourse));
4302
    }
4303
 
4304
    /**
4305
     * Test the main function for updating all calendar events for a module.
4306
     */
11 efrain 4307
    public function test_course_module_calendar_event_update_process(): void {
1 efrain 4308
        global $DB;
4309
 
4310
        $this->resetAfterTest();
4311
        $this->setAdminUser();
4312
 
4313
        $completionexpected = time();
4314
        $duedate = time();
4315
 
4316
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
4317
        $assign = $this->getDataGenerator()->create_module('assign', [
4318
                    'course' => $course,
4319
                    'completionexpected' => $completionexpected,
4320
                    'duedate' => $duedate
4321
                ]);
4322
 
4323
        $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
4324
        $events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]);
4325
        // Check that both events are using the expected dates.
4326
        foreach ($events as $event) {
4327
            if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
4328
                $this->assertEquals($completionexpected, $event->timestart);
4329
            }
4330
            if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
4331
                $this->assertEquals($duedate, $event->timestart);
4332
            }
4333
        }
4334
 
4335
        // We have to manually update the module and the course module.
4336
        $newcompletionexpected = time() + DAYSECS * 60;
4337
        $newduedate = time() + DAYSECS * 45;
4338
        $newmodulename = 'Assign - new name';
4339
 
4340
        $moduleobject = (object)array('id' => $assign->id, 'duedate' => $newduedate, 'name' => $newmodulename);
4341
        $DB->update_record('assign', $moduleobject);
4342
        $cmobject = (object)array('id' => $cm->id, 'completionexpected' => $newcompletionexpected);
4343
        $DB->update_record('course_modules', $cmobject);
4344
 
4345
        $assign = $DB->get_record('assign', ['id' => $assign->id]);
4346
        $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
4347
 
4348
        course_module_calendar_event_update_process($assign, $cm);
4349
 
4350
        $events = $DB->get_records('event', ['courseid' => $course->id, 'instance' => $assign->id]);
4351
        // Now check that the details have been updated properly from the function.
4352
        foreach ($events as $event) {
4353
            if ($event->eventtype == \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED) {
4354
                $this->assertEquals($newcompletionexpected, $event->timestart);
4355
                $this->assertEquals(get_string('completionexpectedfor', 'completion', (object)['instancename' => $newmodulename]),
4356
                        $event->name);
4357
            }
4358
            if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
4359
                $this->assertEquals($newduedate, $event->timestart);
4360
                $this->assertEquals(get_string('calendardue', 'assign', $newmodulename), $event->name);
4361
            }
4362
        }
4363
    }
4364
 
4365
    /**
4366
     * Test the higher level checks for updating calendar events for an instance.
4367
     */
11 efrain 4368
    public function test_course_module_update_calendar_events(): void {
1 efrain 4369
        $this->resetAfterTest();
4370
        $this->setAdminUser();
4371
 
4372
        $completionexpected = time();
4373
        $duedate = time();
4374
 
4375
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
4376
        $assign = $this->getDataGenerator()->create_module('assign', [
4377
                    'course' => $course,
4378
                    'completionexpected' => $completionexpected,
4379
                    'duedate' => $duedate
4380
                ]);
4381
 
4382
        $cm = get_coursemodule_from_instance('assign', $assign->id, $course->id);
4383
 
4384
        // Both the instance and cm objects are missing.
4385
        $this->assertFalse(course_module_update_calendar_events('assign'));
4386
        // Just using the assign instance.
4387
        $this->assertTrue(course_module_update_calendar_events('assign', $assign));
4388
        // Just using the course module object.
4389
        $this->assertTrue(course_module_update_calendar_events('assign', null, $cm));
4390
        // Using both the assign instance and the course module object.
4391
        $this->assertTrue(course_module_update_calendar_events('assign', $assign, $cm));
4392
    }
4393
 
4394
    /**
4395
     * Test the higher level checks for updating calendar events for a module.
4396
     */
11 efrain 4397
    public function test_course_module_bulk_update_calendar_events(): void {
1 efrain 4398
        $this->resetAfterTest();
4399
        $this->setAdminUser();
4400
 
4401
        $completionexpected = time();
4402
        $duedate = time();
4403
 
4404
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
4405
        $course2 = $this->getDataGenerator()->create_course(['enablecompletion' => COMPLETION_ENABLED]);
4406
        $assign = $this->getDataGenerator()->create_module('assign', [
4407
                    'course' => $course,
4408
                    'completionexpected' => $completionexpected,
4409
                    'duedate' => $duedate
4410
                ]);
4411
 
4412
        // No assign instances in this course.
4413
        $this->assertFalse(course_module_bulk_update_calendar_events('assign', $course2->id));
4414
        // No book instances for the site.
4415
        $this->assertFalse(course_module_bulk_update_calendar_events('book'));
4416
        // Update all assign instances.
4417
        $this->assertTrue(course_module_bulk_update_calendar_events('assign'));
4418
        // Update the assign instances for this course.
4419
        $this->assertTrue(course_module_bulk_update_calendar_events('assign', $course->id));
4420
    }
4421
 
4422
    /**
4423
     * Test that a student can view participants in a course they are enrolled in.
4424
     */
11 efrain 4425
    public function test_course_can_view_participants_as_student(): void {
1 efrain 4426
        $this->resetAfterTest();
4427
 
4428
        $course = $this->getDataGenerator()->create_course();
4429
        $coursecontext = context_course::instance($course->id);
4430
 
4431
        $user = $this->getDataGenerator()->create_user();
4432
        $this->getDataGenerator()->enrol_user($user->id, $course->id);
4433
 
4434
        $this->setUser($user);
4435
 
4436
        $this->assertTrue(course_can_view_participants($coursecontext));
4437
    }
4438
 
4439
    /**
4440
     * Test that a student in a course can not view participants on the site.
4441
     */
11 efrain 4442
    public function test_course_can_view_participants_as_student_on_site(): void {
1 efrain 4443
        $this->resetAfterTest();
4444
 
4445
        $course = $this->getDataGenerator()->create_course();
4446
 
4447
        $user = $this->getDataGenerator()->create_user();
4448
        $this->getDataGenerator()->enrol_user($user->id, $course->id);
4449
 
4450
        $this->setUser($user);
4451
 
4452
        $this->assertFalse(course_can_view_participants(context_system::instance()));
4453
    }
4454
 
4455
    /**
4456
     * Test that an admin can view participants on the site.
4457
     */
11 efrain 4458
    public function test_course_can_view_participants_as_admin_on_site(): void {
1 efrain 4459
        $this->resetAfterTest();
4460
 
4461
        $this->setAdminUser();
4462
 
4463
        $this->assertTrue(course_can_view_participants(context_system::instance()));
4464
    }
4465
 
4466
    /**
4467
     * Test teachers can view participants in a course they are enrolled in.
4468
     */
11 efrain 4469
    public function test_course_can_view_participants_as_teacher(): void {
1 efrain 4470
        global $DB;
4471
 
4472
        $this->resetAfterTest();
4473
 
4474
        $course = $this->getDataGenerator()->create_course();
4475
        $coursecontext = context_course::instance($course->id);
4476
 
4477
        $user = $this->getDataGenerator()->create_user();
4478
        $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
4479
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
4480
 
4481
        $this->setUser($user);
4482
 
4483
        $this->assertTrue(course_can_view_participants($coursecontext));
4484
    }
4485
 
4486
    /**
4487
     * Check the teacher can still view the participants page without the 'viewparticipants' cap.
4488
     */
11 efrain 4489
    public function test_course_can_view_participants_as_teacher_without_view_participants_cap(): void {
1 efrain 4490
        global $DB;
4491
 
4492
        $this->resetAfterTest();
4493
 
4494
        $course = $this->getDataGenerator()->create_course();
4495
        $coursecontext = context_course::instance($course->id);
4496
 
4497
        $user = $this->getDataGenerator()->create_user();
4498
        $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
4499
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
4500
 
4501
        $this->setUser($user);
4502
 
4503
        // Disable one of the capabilties.
4504
        assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $coursecontext);
4505
 
4506
        // Should still be able to view the page as they have the 'moodle/course:enrolreview' cap.
4507
        $this->assertTrue(course_can_view_participants($coursecontext));
4508
    }
4509
 
4510
    /**
4511
     * Check the teacher can still view the participants page without the 'moodle/course:enrolreview' cap.
4512
     */
11 efrain 4513
    public function test_course_can_view_participants_as_teacher_without_enrol_review_cap(): void {
1 efrain 4514
        global $DB;
4515
 
4516
        $this->resetAfterTest();
4517
 
4518
        $course = $this->getDataGenerator()->create_course();
4519
        $coursecontext = context_course::instance($course->id);
4520
 
4521
        $user = $this->getDataGenerator()->create_user();
4522
        $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
4523
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
4524
 
4525
        $this->setUser($user);
4526
 
4527
        // Disable one of the capabilties.
4528
        assign_capability('moodle/course:enrolreview', CAP_PROHIBIT, $roleid, $coursecontext);
4529
 
4530
        // Should still be able to view the page as they have the 'moodle/course:viewparticipants' cap.
4531
        $this->assertTrue(course_can_view_participants($coursecontext));
4532
    }
4533
 
4534
    /**
4535
     * Check the teacher can not view the participants page without the required caps.
4536
     */
11 efrain 4537
    public function test_course_can_view_participants_as_teacher_without_required_caps(): void {
1 efrain 4538
        global $DB;
4539
 
4540
        $this->resetAfterTest();
4541
 
4542
        $course = $this->getDataGenerator()->create_course();
4543
        $coursecontext = context_course::instance($course->id);
4544
 
4545
        $user = $this->getDataGenerator()->create_user();
4546
        $roleid = $DB->get_field('role', 'id', array('shortname' => 'editingteacher'));
4547
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $roleid);
4548
 
4549
        $this->setUser($user);
4550
 
4551
        // Disable the capabilities.
4552
        assign_capability('moodle/course:viewparticipants', CAP_PROHIBIT, $roleid, $coursecontext);
4553
        assign_capability('moodle/course:enrolreview', CAP_PROHIBIT, $roleid, $coursecontext);
4554
 
4555
        $this->assertFalse(course_can_view_participants($coursecontext));
4556
    }
4557
 
4558
    /**
4559
     * Check that an exception is not thrown if we can view the participants page.
4560
     */
11 efrain 4561
    public function test_course_require_view_participants(): void {
1 efrain 4562
        $this->resetAfterTest();
4563
 
4564
        $course = $this->getDataGenerator()->create_course();
4565
        $coursecontext = context_course::instance($course->id);
4566
 
4567
        $user = $this->getDataGenerator()->create_user();
4568
        $this->getDataGenerator()->enrol_user($user->id, $course->id);
4569
 
4570
        $this->setUser($user);
4571
 
4572
        course_require_view_participants($coursecontext);
4573
    }
4574
 
4575
    /**
4576
     * Check that an exception is thrown if we can't view the participants page.
4577
     */
11 efrain 4578
    public function test_course_require_view_participants_as_student_on_site(): void {
1 efrain 4579
        $this->resetAfterTest();
4580
 
4581
        $course = $this->getDataGenerator()->create_course();
4582
 
4583
        $user = $this->getDataGenerator()->create_user();
4584
        $this->getDataGenerator()->enrol_user($user->id, $course->id);
4585
 
4586
        $this->setUser($user);
4587
 
4588
        $this->expectException('required_capability_exception');
4589
        course_require_view_participants(context_system::instance());
4590
    }
4591
 
4592
    /**
4593
     *  Testing the can_download_from_backup_filearea fn.
4594
     */
11 efrain 4595
    public function test_can_download_from_backup_filearea(): void {
1 efrain 4596
        global $DB;
4597
        $this->resetAfterTest();
4598
        $course = $this->getDataGenerator()->create_course();
4599
        $context = context_course::instance($course->id);
4600
        $user = $this->getDataGenerator()->create_user();
4601
        $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
4602
        $this->getDataGenerator()->enrol_user($user->id, $course->id, $teacherrole->id);
4603
 
4604
        // The 'automated' backup area. Downloading from this area requires two capabilities.
4605
        // If the user has only the 'backup:downloadfile' capability.
4606
        unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
4607
        assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
4608
        $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
4609
 
4610
        // If the user has only the 'restore:userinfo' capability.
4611
        unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
4612
        assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context);
4613
        $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
4614
 
4615
        // If the user has both capabilities.
4616
        assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
4617
        assign_capability('moodle/restore:userinfo', CAP_ALLOW, $teacherrole->id, $context);
4618
        $this->assertTrue(can_download_from_backup_filearea('automated', $context, $user));
4619
 
4620
        // Is the user has neither of the capabilities.
4621
        unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
4622
        unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
4623
        $this->assertFalse(can_download_from_backup_filearea('automated', $context, $user));
4624
 
4625
        // The 'course ' and 'backup' backup file areas. These are governed by the same download capability.
4626
        // User has the capability.
4627
        unassign_capability('moodle/restore:userinfo', $teacherrole->id, $context);
4628
        assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
4629
        $this->assertTrue(can_download_from_backup_filearea('course', $context, $user));
4630
        $this->assertTrue(can_download_from_backup_filearea('backup', $context, $user));
4631
 
4632
        // User doesn't have the capability.
4633
        unassign_capability('moodle/backup:downloadfile', $teacherrole->id, $context);
4634
        $this->assertFalse(can_download_from_backup_filearea('course', $context, $user));
4635
        $this->assertFalse(can_download_from_backup_filearea('backup', $context, $user));
4636
 
4637
        // A file area that doesn't exist. No permissions, regardless of capabilities.
4638
        assign_capability('moodle/backup:downloadfile', CAP_ALLOW, $teacherrole->id, $context);
4639
        $this->assertFalse(can_download_from_backup_filearea('testing', $context, $user));
4640
    }
4641
 
4642
    /**
4643
     * Test cases for the course_classify_courses_for_timeline test.
4644
     */
1441 ariadna 4645
    public static function get_course_classify_courses_for_timeline_test_cases(): array {
1 efrain 4646
        $now = time();
4647
        $day = 86400;
4648
 
4649
        return [
4650
            'no courses' => [
4651
                'coursesdata' => [],
4652
                'expected' => [
4653
                    COURSE_TIMELINE_PAST => [],
4654
                    COURSE_TIMELINE_FUTURE => [],
4655
                    COURSE_TIMELINE_INPROGRESS => []
4656
                ]
4657
            ],
4658
            'only past' => [
4659
                'coursesdata' => [
4660
                    [
4661
                        'shortname' => 'past1',
4662
                        'startdate' => $now - ($day * 2),
4663
                        'enddate' => $now - $day
4664
                    ],
4665
                    [
4666
                        'shortname' => 'past2',
4667
                        'startdate' => $now - ($day * 2),
4668
                        'enddate' => $now - $day
4669
                    ]
4670
                ],
4671
                'expected' => [
4672
                    COURSE_TIMELINE_PAST => ['past1', 'past2'],
4673
                    COURSE_TIMELINE_FUTURE => [],
4674
                    COURSE_TIMELINE_INPROGRESS => []
4675
                ]
4676
            ],
4677
            'only in progress' => [
4678
                'coursesdata' => [
4679
                    [
4680
                        'shortname' => 'inprogress1',
4681
                        'startdate' => $now - $day,
4682
                        'enddate' => $now + $day
4683
                    ],
4684
                    [
4685
                        'shortname' => 'inprogress2',
4686
                        'startdate' => $now - $day,
4687
                        'enddate' => $now + $day
4688
                    ]
4689
                ],
4690
                'expected' => [
4691
                    COURSE_TIMELINE_PAST => [],
4692
                    COURSE_TIMELINE_FUTURE => [],
4693
                    COURSE_TIMELINE_INPROGRESS => ['inprogress1', 'inprogress2']
4694
                ]
4695
            ],
4696
            'only future' => [
4697
                'coursesdata' => [
4698
                    [
4699
                        'shortname' => 'future1',
4700
                        'startdate' => $now + $day
4701
                    ],
4702
                    [
4703
                        'shortname' => 'future2',
4704
                        'startdate' => $now + $day
4705
                    ]
4706
                ],
4707
                'expected' => [
4708
                    COURSE_TIMELINE_PAST => [],
4709
                    COURSE_TIMELINE_FUTURE => ['future1', 'future2'],
4710
                    COURSE_TIMELINE_INPROGRESS => []
4711
                ]
4712
            ],
4713
            'combination' => [
4714
                'coursesdata' => [
4715
                    [
4716
                        'shortname' => 'past1',
4717
                        'startdate' => $now - ($day * 2),
4718
                        'enddate' => $now - $day
4719
                    ],
4720
                    [
4721
                        'shortname' => 'past2',
4722
                        'startdate' => $now - ($day * 2),
4723
                        'enddate' => $now - $day
4724
                    ],
4725
                    [
4726
                        'shortname' => 'inprogress1',
4727
                        'startdate' => $now - $day,
4728
                        'enddate' => $now + $day
4729
                    ],
4730
                    [
4731
                        'shortname' => 'inprogress2',
4732
                        'startdate' => $now - $day,
4733
                        'enddate' => $now + $day
4734
                    ],
4735
                    [
4736
                        'shortname' => 'future1',
4737
                        'startdate' => $now + $day
4738
                    ],
4739
                    [
4740
                        'shortname' => 'future2',
4741
                        'startdate' => $now + $day
4742
                    ]
4743
                ],
4744
                'expected' => [
4745
                    COURSE_TIMELINE_PAST => ['past1', 'past2'],
4746
                    COURSE_TIMELINE_FUTURE => ['future1', 'future2'],
4747
                    COURSE_TIMELINE_INPROGRESS => ['inprogress1', 'inprogress2']
4748
                ]
4749
            ],
4750
        ];
4751
    }
4752
 
4753
    /**
4754
     * Test the course_classify_courses_for_timeline function.
4755
     *
1441 ariadna 4756
     * @dataProvider get_course_classify_courses_for_timeline_test_cases
1 efrain 4757
     * @param array $coursesdata Courses to create
4758
     * @param array $expected Expected test results.
4759
     */
11 efrain 4760
    public function test_course_classify_courses_for_timeline($coursesdata, $expected): void {
1 efrain 4761
        $this->resetAfterTest();
4762
        $generator = $this->getDataGenerator();
4763
 
4764
        $courses = array_map(function($coursedata) use ($generator) {
4765
            return $generator->create_course($coursedata);
4766
        }, $coursesdata);
4767
 
4768
        sort($expected[COURSE_TIMELINE_PAST]);
4769
        sort($expected[COURSE_TIMELINE_FUTURE]);
4770
        sort($expected[COURSE_TIMELINE_INPROGRESS]);
4771
 
4772
        $results = course_classify_courses_for_timeline($courses);
4773
 
4774
        $actualpast = array_map(function($result) {
4775
            return $result->shortname;
4776
        }, $results[COURSE_TIMELINE_PAST]);
4777
 
4778
        $actualfuture = array_map(function($result) {
4779
            return $result->shortname;
4780
        }, $results[COURSE_TIMELINE_FUTURE]);
4781
 
4782
        $actualinprogress = array_map(function($result) {
4783
            return $result->shortname;
4784
        }, $results[COURSE_TIMELINE_INPROGRESS]);
4785
 
4786
        sort($actualpast);
4787
        sort($actualfuture);
4788
        sort($actualinprogress);
4789
 
4790
        $this->assertEquals($expected[COURSE_TIMELINE_PAST], $actualpast);
4791
        $this->assertEquals($expected[COURSE_TIMELINE_FUTURE], $actualfuture);
4792
        $this->assertEquals($expected[COURSE_TIMELINE_INPROGRESS], $actualinprogress);
4793
    }
4794
 
4795
    /**
4796
     * Test cases for the course_get_enrolled_courses_for_logged_in_user tests.
4797
     */
1441 ariadna 4798
    public static function get_course_get_enrolled_courses_for_logged_in_user_test_cases(): array {
1 efrain 4799
        $buildexpectedresult = function($limit, $offset) {
4800
            $result = [];
4801
            for ($i = $offset; $i < $offset + $limit; $i++) {
4802
                $result[] = "testcourse{$i}";
4803
            }
4804
            return $result;
4805
        };
4806
 
4807
        return [
4808
            'zero records' => [
4809
                'dbquerylimit' => 3,
4810
                'totalcourses' => 0,
4811
                'limit' => 0,
4812
                'offset' => 0,
4813
                'expecteddbqueries' => 4,
4814
                'expectedresult' => $buildexpectedresult(0, 0)
4815
            ],
4816
            'less than query limit' => [
4817
                'dbquerylimit' => 3,
4818
                'totalcourses' => 2,
4819
                'limit' => 0,
4820
                'offset' => 0,
4821
                'expecteddbqueries' => 2,
4822
                'expectedresult' => $buildexpectedresult(2, 0)
4823
            ],
4824
            'more than query limit' => [
4825
                'dbquerylimit' => 3,
4826
                'totalcourses' => 7,
4827
                'limit' => 0,
4828
                'offset' => 0,
4829
                'expecteddbqueries' => 4,
4830
                'expectedresult' => $buildexpectedresult(7, 0)
4831
            ],
4832
            'limit less than query limit' => [
4833
                'dbquerylimit' => 3,
4834
                'totalcourses' => 7,
4835
                'limit' => 2,
4836
                'offset' => 0,
4837
                'expecteddbqueries' => 2,
4838
                'expectedresult' => $buildexpectedresult(2, 0)
4839
            ],
4840
            'limit less than query limit with offset' => [
4841
                'dbquerylimit' => 3,
4842
                'totalcourses' => 7,
4843
                'limit' => 2,
4844
                'offset' => 2,
4845
                'expecteddbqueries' => 2,
4846
                'expectedresult' => $buildexpectedresult(2, 2)
4847
            ],
4848
            'limit less than total' => [
4849
                'dbquerylimit' => 3,
4850
                'totalcourses' => 9,
4851
                'limit' => 6,
4852
                'offset' => 0,
4853
                'expecteddbqueries' => 3,
4854
                'expectedresult' => $buildexpectedresult(6, 0)
4855
            ],
4856
            'less results than limit' => [
4857
                'dbquerylimit' => 4,
4858
                'totalcourses' => 9,
4859
                'limit' => 20,
4860
                'offset' => 0,
4861
                'expecteddbqueries' => 4,
4862
                'expectedresult' => $buildexpectedresult(9, 0)
4863
            ],
4864
            'less results than limit exact divisible' => [
4865
                'dbquerylimit' => 3,
4866
                'totalcourses' => 9,
4867
                'limit' => 20,
4868
                'offset' => 0,
4869
                'expecteddbqueries' => 5,
4870
                'expectedresult' => $buildexpectedresult(9, 0)
4871
            ],
4872
            'less results than limit with offset' => [
4873
                'dbquerylimit' => 3,
4874
                'totalcourses' => 9,
4875
                'limit' => 10,
4876
                'offset' => 5,
4877
                'expecteddbqueries' => 3,
4878
                'expectedresult' => $buildexpectedresult(4, 5)
4879
            ],
4880
        ];
4881
    }
4882
 
4883
    /**
4884
     * Test the course_get_enrolled_courses_for_logged_in_user function.
4885
     *
1441 ariadna 4886
     * @dataProvider get_course_get_enrolled_courses_for_logged_in_user_test_cases
1 efrain 4887
     * @param int $dbquerylimit Number of records to load per DB request
4888
     * @param int $totalcourses Number of courses to create
4889
     * @param int $limit Maximum number of results to get.
4890
     * @param int $offset Skip this number of results from the start of the result set.
4891
     * @param int $expecteddbqueries The number of DB queries expected during the test.
4892
     * @param array $expectedresult Expected test results.
4893
     */
4894
    public function test_course_get_enrolled_courses_for_logged_in_user(
4895
        $dbquerylimit,
4896
        $totalcourses,
4897
        $limit,
4898
        $offset,
4899
        $expecteddbqueries,
4900
        $expectedresult
11 efrain 4901
    ): void {
1 efrain 4902
        global $DB;
4903
 
4904
        $this->resetAfterTest();
4905
        $generator = $this->getDataGenerator();
4906
        $student = $generator->create_user();
4907
 
4908
        for ($i = 0; $i < $totalcourses; $i++) {
4909
            $shortname = "testcourse{$i}";
4910
            $course = $generator->create_course(['shortname' => $shortname]);
4911
            $generator->enrol_user($student->id, $course->id, 'student');
4912
        }
4913
 
4914
        $this->setUser($student);
4915
 
4916
        $initialquerycount = $DB->perf_get_queries();
4917
        $courses = course_get_enrolled_courses_for_logged_in_user($limit, $offset, 'shortname ASC', 'shortname', $dbquerylimit);
4918
 
4919
        // Loop over the result set to force the lazy loading to kick in so that we can check the
4920
        // number of DB queries.
4921
        $actualresult = array_map(function($course) {
4922
            return $course->shortname;
4923
        }, iterator_to_array($courses, false));
4924
 
4925
        sort($expectedresult);
4926
 
4927
        $this->assertEquals($expectedresult, $actualresult);
4928
        $this->assertLessThanOrEqual($expecteddbqueries, $DB->perf_get_queries() - $initialquerycount);
4929
    }
4930
 
4931
    /**
4932
     * Test cases for the course_filter_courses_by_timeline_classification tests.
4933
     */
1441 ariadna 4934
    public static function get_course_filter_courses_by_timeline_classification_test_cases(): array {
1 efrain 4935
        $now = time();
4936
        $day = 86400;
4937
 
4938
        $coursedata = [
4939
            [
4940
                'shortname' => 'apast',
4941
                'startdate' => $now - ($day * 2),
4942
                'enddate' => $now - $day
4943
            ],
4944
            [
4945
                'shortname' => 'bpast',
4946
                'startdate' => $now - ($day * 2),
4947
                'enddate' => $now - $day
4948
            ],
4949
            [
4950
                'shortname' => 'cpast',
4951
                'startdate' => $now - ($day * 2),
4952
                'enddate' => $now - $day
4953
            ],
4954
            [
4955
                'shortname' => 'dpast',
4956
                'startdate' => $now - ($day * 2),
4957
                'enddate' => $now - $day
4958
            ],
4959
            [
4960
                'shortname' => 'epast',
4961
                'startdate' => $now - ($day * 2),
4962
                'enddate' => $now - $day
4963
            ],
4964
            [
4965
                'shortname' => 'ainprogress',
4966
                'startdate' => $now - $day,
4967
                'enddate' => $now + $day
4968
            ],
4969
            [
4970
                'shortname' => 'binprogress',
4971
                'startdate' => $now - $day,
4972
                'enddate' => $now + $day
4973
            ],
4974
            [
4975
                'shortname' => 'cinprogress',
4976
                'startdate' => $now - $day,
4977
                'enddate' => $now + $day
4978
            ],
4979
            [
4980
                'shortname' => 'dinprogress',
4981
                'startdate' => $now - $day,
4982
                'enddate' => $now + $day
4983
            ],
4984
            [
4985
                'shortname' => 'einprogress',
4986
                'startdate' => $now - $day,
4987
                'enddate' => $now + $day
4988
            ],
4989
            [
4990
                'shortname' => 'afuture',
4991
                'startdate' => $now + $day
4992
            ],
4993
            [
4994
                'shortname' => 'bfuture',
4995
                'startdate' => $now + $day
4996
            ],
4997
            [
4998
                'shortname' => 'cfuture',
4999
                'startdate' => $now + $day
5000
            ],
5001
            [
5002
                'shortname' => 'dfuture',
5003
                'startdate' => $now + $day
5004
            ],
5005
            [
5006
                'shortname' => 'efuture',
5007
                'startdate' => $now + $day
5008
            ]
5009
        ];
5010
 
5011
        // Raw enrolled courses result set should be returned in this order:
5012
        // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast,
5013
        // dfuture, dinprogress, dpast, efuture, einprogress, epast
5014
        //
5015
        // By classification the offset values for each record should be:
5016
        // COURSE_TIMELINE_FUTURE
5017
        // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture)
5018
        // COURSE_TIMELINE_INPROGRESS
5019
        // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress)
5020
        // COURSE_TIMELINE_PAST
5021
        // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast).
5022
        return [
5023
            'empty set' => [
5024
                'coursedata' => [],
5025
                'classification' => COURSE_TIMELINE_FUTURE,
5026
                'limit' => 2,
5027
                'offset' => 0,
5028
                'expectedcourses' => [],
5029
                'expectedprocessedcount' => 0
5030
            ],
5031
            // COURSE_TIMELINE_FUTURE.
5032
            'future not limit no offset' => [
5033
                'coursedata' => $coursedata,
5034
                'classification' => COURSE_TIMELINE_FUTURE,
5035
                'limit' => 0,
5036
                'offset' => 0,
5037
                'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
5038
                'expectedprocessedcount' => 15
5039
            ],
5040
            'future no offset' => [
5041
                'coursedata' => $coursedata,
5042
                'classification' => COURSE_TIMELINE_FUTURE,
5043
                'limit' => 2,
5044
                'offset' => 0,
5045
                'expectedcourses' => ['afuture', 'bfuture'],
5046
                'expectedprocessedcount' => 4
5047
            ],
5048
            'future offset' => [
5049
                'coursedata' => $coursedata,
5050
                'classification' => COURSE_TIMELINE_FUTURE,
5051
                'limit' => 2,
5052
                'offset' => 2,
5053
                'expectedcourses' => ['bfuture', 'cfuture'],
5054
                'expectedprocessedcount' => 5
5055
            ],
5056
            'future exact limit' => [
5057
                'coursedata' => $coursedata,
5058
                'classification' => COURSE_TIMELINE_FUTURE,
5059
                'limit' => 5,
5060
                'offset' => 0,
5061
                'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
5062
                'expectedprocessedcount' => 13
5063
            ],
5064
            'future limit less results' => [
5065
                'coursedata' => $coursedata,
5066
                'classification' => COURSE_TIMELINE_FUTURE,
5067
                'limit' => 10,
5068
                'offset' => 0,
5069
                'expectedcourses' => ['afuture', 'bfuture', 'cfuture', 'dfuture', 'efuture'],
5070
                'expectedprocessedcount' => 15
5071
            ],
5072
            'future limit less results with offset' => [
5073
                'coursedata' => $coursedata,
5074
                'classification' => COURSE_TIMELINE_FUTURE,
5075
                'limit' => 10,
5076
                'offset' => 5,
5077
                'expectedcourses' => ['cfuture', 'dfuture', 'efuture'],
5078
                'expectedprocessedcount' => 10
5079
            ],
5080
        ];
5081
    }
5082
 
5083
    /**
5084
     * Test the course_get_enrolled_courses_for_logged_in_user_from_search function.
5085
     */
11 efrain 5086
    public function test_course_get_enrolled_courses_for_logged_in_user_from_search(): void {
1 efrain 5087
        global $DB;
5088
 
5089
        // Set up.
5090
 
5091
        $this->resetAfterTest();
5092
        $generator = $this->getDataGenerator();
5093
        $student = $generator->create_user();
5094
 
5095
        $cat1 = core_course_category::create(['name' => 'Cat1']);
5096
        $cat2 = core_course_category::create(['name' => 'Cat2', 'parent' => $cat1->id]);
5097
        $c1 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 3', 'summary' => 'Magic', 'idnumber' => 'ID3']);
5098
        $c2 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 1', 'summary' => 'Magic']);
5099
        $c3 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Математика', 'summary' => ' Test Magic']);
5100
        $c4 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'fullname' => 'Test 4', 'summary' => 'Magic', 'idnumber' => 'ID4']);
5101
 
5102
        $c5 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 5', 'summary' => 'Magic']);
5103
        $c6 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Дискретная Математика', 'summary' => 'Magic']);
5104
        $c7 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 7', 'summary' => 'Magic']);
5105
        $c8 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'fullname' => 'Test 8', 'summary' => 'Magic']);
5106
 
5107
        for ($i = 1; $i < 9; $i++) {
5108
            $generator->enrol_user($student->id, ${"c$i"}->id, 'student');
5109
        }
5110
 
5111
        $this->setUser($student);
5112
 
5113
        $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
5114
            0,
5115
            0,
5116
            'id ASC',
5117
            null,
5118
            COURSE_DB_QUERY_LIMIT,
5119
            ['search' => 'test'],
5120
            ['idonly' => true]
5121
        );
5122
 
5123
        $actualresult = array_map(function($course) {
5124
            return $course->id;
5125
        }, iterator_to_array($returnedcourses, false));
5126
 
5127
        $this->assertEquals([$c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c7->id, $c8->id], $actualresult);
5128
 
5129
        // Test no courses matching the search.
5130
        $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
5131
            0,
5132
            0,
5133
            'id ASC',
5134
            null,
5135
            COURSE_DB_QUERY_LIMIT,
5136
            ['search' => 'foobar'],
5137
            ['idonly' => true]
5138
        );
5139
 
5140
        $actualresult = array_map(function($course) {
5141
            return $course->id;
5142
        }, iterator_to_array($returnedcourses, false));
5143
 
5144
        $this->assertEquals([], $actualresult);
5145
 
5146
        // Test returning all courses that have a mutual summary.
5147
        $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
5148
            0,
5149
            0,
5150
            'id ASC',
5151
            null,
5152
            COURSE_DB_QUERY_LIMIT,
5153
            ['search' => 'Magic'],
5154
            ['idonly' => true]
5155
        );
5156
 
5157
        $actualresult = array_map(function($course) {
5158
            return $course->id;
5159
        }, iterator_to_array($returnedcourses, false));
5160
 
5161
        $this->assertEquals([$c1->id, $c2->id, $c3->id, $c4->id, $c5->id, $c6->id, $c7->id, $c8->id], $actualresult);
5162
 
5163
        // Test returning a unique course.
5164
        $returnedcourses = course_get_enrolled_courses_for_logged_in_user_from_search(
5165
            0,
5166
            0,
5167
            'id ASC',
5168
            null,
5169
            COURSE_DB_QUERY_LIMIT,
5170
            ['search' => 'Дискретная'],
5171
            ['idonly' => true]
5172
        );
5173
 
5174
        $actualresult = array_map(function($course) {
5175
            return $course->id;
5176
        }, iterator_to_array($returnedcourses, false));
5177
 
5178
        $this->assertEquals([$c6->id], $actualresult);
5179
    }
5180
 
5181
    /**
5182
     * Test the course_filter_courses_by_timeline_classification function.
5183
     *
1441 ariadna 5184
     * @dataProvider get_course_filter_courses_by_timeline_classification_test_cases
1 efrain 5185
     * @param array $coursedata Course test data to create.
5186
     * @param string $classification Timeline classification.
5187
     * @param int $limit Maximum number of results to return.
5188
     * @param int $offset Results to skip at the start of the result set.
5189
     * @param string[] $expectedcourses Expected courses in results.
5190
     * @param int $expectedprocessedcount Expected number of course records to be processed.
5191
     */
5192
    public function test_course_filter_courses_by_timeline_classification(
5193
        $coursedata,
5194
        $classification,
5195
        $limit,
5196
        $offset,
5197
        $expectedcourses,
5198
        $expectedprocessedcount
11 efrain 5199
    ): void {
1 efrain 5200
        $this->resetAfterTest();
5201
        $generator = $this->getDataGenerator();
5202
 
5203
        $courses = array_map(function($coursedata) use ($generator) {
5204
            return $generator->create_course($coursedata);
5205
        }, $coursedata);
5206
 
5207
        $student = $generator->create_user();
5208
 
5209
        foreach ($courses as $course) {
5210
            $generator->enrol_user($student->id, $course->id, 'student');
5211
        }
5212
 
5213
        $this->setUser($student);
5214
 
5215
        $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
5216
        list($result, $processedcount) = course_filter_courses_by_timeline_classification(
5217
            $coursesgenerator,
5218
            $classification,
5219
            $limit
5220
        );
5221
 
5222
        $actual = array_map(function($course) {
5223
            return $course->shortname;
5224
        }, $result);
5225
 
5226
        $this->assertEquals($expectedcourses, $actual);
5227
        $this->assertEquals($expectedprocessedcount, $processedcount);
5228
    }
5229
 
5230
    /**
5231
     * Test cases for the course_filter_courses_by_timeline_classification tests.
5232
     */
1441 ariadna 5233
    public static function get_course_filter_courses_by_customfield_test_cases(): array {
1 efrain 5234
        global $CFG;
5235
        require_once($CFG->dirroot.'/blocks/myoverview/lib.php');
5236
        $coursedata = [
5237
            [
5238
                'shortname' => 'C1',
5239
                'customfield_checkboxfield' => 1,
5240
                'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'),
5241
                'customfield_selectfield' => 1,
5242
                'customfield_textfield' => 'fish',
5243
            ],
5244
            [
5245
                'shortname' => 'C2',
5246
                'customfield_checkboxfield' => 0,
5247
                'customfield_datefield' => strtotime('1980-08-05T13:00:00Z'),
5248
            ],
5249
            [
5250
                'shortname' => 'C3',
5251
                'customfield_checkboxfield' => 0,
5252
                'customfield_datefield' => strtotime('2001-02-01T12:00:00Z'),
5253
                'customfield_selectfield' => 2,
5254
                'customfield_textfield' => 'dog',
5255
            ],
5256
            [
5257
                'shortname' => 'C4',
5258
                'customfield_checkboxfield' => 1,
5259
                'customfield_selectfield' => 3,
5260
                'customfield_textfield' => 'cat',
5261
            ],
5262
            [
5263
                'shortname' => 'C5',
5264
                'customfield_datefield' => strtotime('1980-08-06T13:00:00Z'),
5265
                'customfield_selectfield' => 2,
5266
                'customfield_textfield' => 'fish',
5267
            ],
5268
        ];
5269
 
5270
        return [
5271
            'empty set' => [
5272
                'coursedata' => [],
5273
                'customfield' => 'checkboxfield',
5274
                'customfieldvalue' => 1,
5275
                'limit' => 10,
5276
                'offset' => 0,
5277
                'expectedcourses' => [],
5278
                'expectedprocessedcount' => 0
5279
            ],
5280
            'checkbox yes' => [
5281
                'coursedata' => $coursedata,
5282
                'customfield' => 'checkboxfield',
5283
                'customfieldvalue' => 1,
5284
                'limit' => 10,
5285
                'offset' => 0,
5286
                'expectedcourses' => ['C1', 'C4'],
5287
                'expectedprocessedcount' => 5
5288
            ],
5289
            'checkbox no' => [
5290
                'coursedata' => $coursedata,
5291
                'customfield' => 'checkboxfield',
5292
                'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
5293
                'limit' => 10,
5294
                'offset' => 0,
5295
                'expectedcourses' => ['C2', 'C3', 'C5'],
5296
                'expectedprocessedcount' => 5
5297
            ],
5298
            'date 1 Feb 2001' => [
5299
                'coursedata' => $coursedata,
5300
                'customfield' => 'datefield',
5301
                'customfieldvalue' => strtotime('2001-02-01T12:00:00Z'),
5302
                'limit' => 10,
5303
                'offset' => 0,
5304
                'expectedcourses' => ['C1', 'C3'],
5305
                'expectedprocessedcount' => 5
5306
            ],
5307
            'date 6 Aug 1980' => [
5308
                'coursedata' => $coursedata,
5309
                'customfield' => 'datefield',
5310
                'customfieldvalue' => strtotime('1980-08-06T13:00:00Z'),
5311
                'limit' => 10,
5312
                'offset' => 0,
5313
                'expectedcourses' => ['C5'],
5314
                'expectedprocessedcount' => 5
5315
            ],
5316
            'date no date' => [
5317
                'coursedata' => $coursedata,
5318
                'customfield' => 'datefield',
5319
                'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
5320
                'limit' => 10,
5321
                'offset' => 0,
5322
                'expectedcourses' => ['C4'],
5323
                'expectedprocessedcount' => 5
5324
            ],
5325
            'select Option 1' => [
5326
                'coursedata' => $coursedata,
5327
                'customfield' => 'selectfield',
5328
                'customfieldvalue' => 1,
5329
                'limit' => 10,
5330
                'offset' => 0,
5331
                'expectedcourses' => ['C1'],
5332
                'expectedprocessedcount' => 5
5333
            ],
5334
            'select Option 2' => [
5335
                'coursedata' => $coursedata,
5336
                'customfield' => 'selectfield',
5337
                'customfieldvalue' => 2,
5338
                'limit' => 10,
5339
                'offset' => 0,
5340
                'expectedcourses' => ['C3', 'C5'],
5341
                'expectedprocessedcount' => 5
5342
            ],
5343
            'select no select' => [
5344
                'coursedata' => $coursedata,
5345
                'customfield' => 'selectfield',
5346
                'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
5347
                'limit' => 10,
5348
                'offset' => 0,
5349
                'expectedcourses' => ['C2'],
5350
                'expectedprocessedcount' => 5
5351
            ],
5352
            'text fish' => [
5353
                'coursedata' => $coursedata,
5354
                'customfield' => 'textfield',
5355
                'customfieldvalue' => 'fish',
5356
                'limit' => 10,
5357
                'offset' => 0,
5358
                'expectedcourses' => ['C1', 'C5'],
5359
                'expectedprocessedcount' => 5
5360
            ],
5361
            'text dog' => [
5362
                'coursedata' => $coursedata,
5363
                'customfield' => 'textfield',
5364
                'customfieldvalue' => 'dog',
5365
                'limit' => 10,
5366
                'offset' => 0,
5367
                'expectedcourses' => ['C3'],
5368
                'expectedprocessedcount' => 5
5369
            ],
5370
            'text no text' => [
5371
                'coursedata' => $coursedata,
5372
                'customfield' => 'textfield',
5373
                'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
5374
                'limit' => 10,
5375
                'offset' => 0,
5376
                'expectedcourses' => ['C2'],
5377
                'expectedprocessedcount' => 5
5378
            ],
5379
            'checkbox limit no' => [
5380
                'coursedata' => $coursedata,
5381
                'customfield' => 'checkboxfield',
5382
                'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
5383
                'limit' => 2,
5384
                'offset' => 0,
5385
                'expectedcourses' => ['C2', 'C3'],
5386
                'expectedprocessedcount' => 3
5387
            ],
5388
            'checkbox limit offset no' => [
5389
                'coursedata' => $coursedata,
5390
                'customfield' => 'checkboxfield',
5391
                'customfieldvalue' => BLOCK_MYOVERVIEW_CUSTOMFIELD_EMPTY,
5392
                'limit' => 2,
5393
                'offset' => 3,
5394
                'expectedcourses' => ['C5'],
5395
                'expectedprocessedcount' => 2
5396
            ],
5397
        ];
5398
    }
5399
 
5400
    /**
5401
     * Test the course_filter_courses_by_customfield function.
5402
     *
1441 ariadna 5403
     * @dataProvider get_course_filter_courses_by_customfield_test_cases
1 efrain 5404
     * @param array $coursedata Course test data to create.
5405
     * @param string $customfield Shortname of the customfield.
5406
     * @param string $customfieldvalue the value to filter by.
5407
     * @param int $limit Maximum number of results to return.
5408
     * @param int $offset Results to skip at the start of the result set.
5409
     * @param string[] $expectedcourses Expected courses in results.
5410
     * @param int $expectedprocessedcount Expected number of course records to be processed.
5411
     */
5412
    public function test_course_filter_courses_by_customfield(
5413
        $coursedata,
5414
        $customfield,
5415
        $customfieldvalue,
5416
        $limit,
5417
        $offset,
5418
        $expectedcourses,
5419
        $expectedprocessedcount
11 efrain 5420
    ): void {
1 efrain 5421
        $this->resetAfterTest();
5422
        $generator = $this->getDataGenerator();
5423
 
5424
        // Create the custom fields.
5425
        $generator->create_custom_field_category([
5426
            'name' => 'Course fields',
5427
            'component' => 'core_course',
5428
            'area' => 'course',
5429
            'itemid' => 0,
5430
        ]);
5431
        $generator->create_custom_field([
5432
            'name' => 'Checkbox field',
5433
            'category' => 'Course fields',
5434
            'type' => 'checkbox',
5435
            'shortname' => 'checkboxfield',
5436
        ]);
5437
        $generator->create_custom_field([
5438
            'name' => 'Date field',
5439
            'category' => 'Course fields',
5440
            'type' => 'date',
5441
            'shortname' => 'datefield',
5442
            'configdata' => '{"mindate":0, "maxdate":0}',
5443
        ]);
5444
        $generator->create_custom_field([
5445
            'name' => 'Select field',
5446
            'category' => 'Course fields',
5447
            'type' => 'select',
5448
            'shortname' => 'selectfield',
5449
            'configdata' => '{"options":"Option 1\nOption 2\nOption 3\nOption 4"}',
5450
        ]);
5451
        $generator->create_custom_field([
5452
            'name' => 'Text field',
5453
            'category' => 'Course fields',
5454
            'type' => 'text',
5455
            'shortname' => 'textfield',
5456
        ]);
5457
 
5458
        $courses = array_map(function($coursedata) use ($generator) {
5459
            return $generator->create_course($coursedata);
5460
        }, $coursedata);
5461
 
5462
        $student = $generator->create_user();
5463
 
5464
        foreach ($courses as $course) {
5465
            $generator->enrol_user($student->id, $course->id, 'student');
5466
        }
5467
 
5468
        $this->setUser($student);
5469
 
5470
        $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
5471
        list($result, $processedcount) = course_filter_courses_by_customfield(
5472
            $coursesgenerator,
5473
            $customfield,
5474
            $customfieldvalue,
5475
            $limit
5476
        );
5477
 
5478
        $actual = array_map(function($course) {
5479
            return $course->shortname;
5480
        }, $result);
5481
 
5482
        $this->assertEquals($expectedcourses, $actual);
5483
        $this->assertEquals($expectedprocessedcount, $processedcount);
5484
    }
5485
 
5486
    /**
5487
     * Test cases for the course_filter_courses_by_timeline_classification w/ hidden courses tests.
5488
     */
1441 ariadna 5489
    public static function get_course_filter_courses_by_timeline_classification_hidden_courses_test_cases(): array {
1 efrain 5490
        $now = time();
5491
        $day = 86400;
5492
 
5493
        $coursedata = [
5494
            [
5495
                'shortname' => 'apast',
5496
                'startdate' => $now - ($day * 2),
5497
                'enddate' => $now - $day
5498
            ],
5499
            [
5500
                'shortname' => 'bpast',
5501
                'startdate' => $now - ($day * 2),
5502
                'enddate' => $now - $day
5503
            ],
5504
            [
5505
                'shortname' => 'cpast',
5506
                'startdate' => $now - ($day * 2),
5507
                'enddate' => $now - $day
5508
            ],
5509
            [
5510
                'shortname' => 'dpast',
5511
                'startdate' => $now - ($day * 2),
5512
                'enddate' => $now - $day
5513
            ],
5514
            [
5515
                'shortname' => 'epast',
5516
                'startdate' => $now - ($day * 2),
5517
                'enddate' => $now - $day
5518
            ],
5519
            [
5520
                'shortname' => 'ainprogress',
5521
                'startdate' => $now - $day,
5522
                'enddate' => $now + $day
5523
            ],
5524
            [
5525
                'shortname' => 'binprogress',
5526
                'startdate' => $now - $day,
5527
                'enddate' => $now + $day
5528
            ],
5529
            [
5530
                'shortname' => 'cinprogress',
5531
                'startdate' => $now - $day,
5532
                'enddate' => $now + $day
5533
            ],
5534
            [
5535
                'shortname' => 'dinprogress',
5536
                'startdate' => $now - $day,
5537
                'enddate' => $now + $day
5538
            ],
5539
            [
5540
                'shortname' => 'einprogress',
5541
                'startdate' => $now - $day,
5542
                'enddate' => $now + $day
5543
            ],
5544
            [
5545
                'shortname' => 'afuture',
5546
                'startdate' => $now + $day
5547
            ],
5548
            [
5549
                'shortname' => 'bfuture',
5550
                'startdate' => $now + $day
5551
            ],
5552
            [
5553
                'shortname' => 'cfuture',
5554
                'startdate' => $now + $day
5555
            ],
5556
            [
5557
                'shortname' => 'dfuture',
5558
                'startdate' => $now + $day
5559
            ],
5560
            [
5561
                'shortname' => 'efuture',
5562
                'startdate' => $now + $day
5563
            ]
5564
        ];
5565
 
5566
        // Raw enrolled courses result set should be returned in this order:
5567
        // afuture, ainprogress, apast, bfuture, binprogress, bpast, cfuture, cinprogress, cpast,
5568
        // dfuture, dinprogress, dpast, efuture, einprogress, epast
5569
        //
5570
        // By classification the offset values for each record should be:
5571
        // COURSE_TIMELINE_FUTURE
5572
        // 0 (afuture), 3 (bfuture), 6 (cfuture), 9 (dfuture), 12 (efuture)
5573
        // COURSE_TIMELINE_INPROGRESS
5574
        // 1 (ainprogress), 4 (binprogress), 7 (cinprogress), 10 (dinprogress), 13 (einprogress)
5575
        // COURSE_TIMELINE_PAST
5576
        // 2 (apast), 5 (bpast), 8 (cpast), 11 (dpast), 14 (epast).
5577
        return [
5578
            'empty set' => [
5579
                'coursedata' => [],
5580
                'classification' => COURSE_TIMELINE_FUTURE,
5581
                'limit' => 2,
5582
                'offset' => 0,
5583
                'expectedcourses' => [],
5584
                'expectedprocessedcount' => 0,
5585
                'hiddencourse' => ''
5586
            ],
5587
            // COURSE_TIMELINE_FUTURE.
5588
            'future not limit no offset' => [
5589
                'coursedata' => $coursedata,
5590
                'classification' => COURSE_TIMELINE_FUTURE,
5591
                'limit' => 0,
5592
                'offset' => 0,
5593
                'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
5594
                'expectedprocessedcount' => 15,
5595
                'hiddencourse' => 'bfuture'
5596
            ],
5597
            'future no offset' => [
5598
                'coursedata' => $coursedata,
5599
                'classification' => COURSE_TIMELINE_FUTURE,
5600
                'limit' => 2,
5601
                'offset' => 0,
5602
                'expectedcourses' => ['afuture', 'cfuture'],
5603
                'expectedprocessedcount' => 7,
5604
                'hiddencourse' => 'bfuture'
5605
            ],
5606
            'future offset' => [
5607
                'coursedata' => $coursedata,
5608
                'classification' => COURSE_TIMELINE_FUTURE,
5609
                'limit' => 2,
5610
                'offset' => 2,
5611
                'expectedcourses' => ['bfuture', 'dfuture'],
5612
                'expectedprocessedcount' => 8,
5613
                'hiddencourse' => 'cfuture'
5614
            ],
5615
            'future exact limit' => [
5616
                'coursedata' => $coursedata,
5617
                'classification' => COURSE_TIMELINE_FUTURE,
5618
                'limit' => 5,
5619
                'offset' => 0,
5620
                'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
5621
                'expectedprocessedcount' => 15,
5622
                'hiddencourse' => 'bfuture'
5623
            ],
5624
            'future limit less results' => [
5625
                'coursedata' => $coursedata,
5626
                'classification' => COURSE_TIMELINE_FUTURE,
5627
                'limit' => 10,
5628
                'offset' => 0,
5629
                'expectedcourses' => ['afuture', 'cfuture', 'dfuture', 'efuture'],
5630
                'expectedprocessedcount' => 15,
5631
                'hiddencourse' => 'bfuture'
5632
            ],
5633
            'future limit less results with offset' => [
5634
                'coursedata' => $coursedata,
5635
                'classification' => COURSE_TIMELINE_FUTURE,
5636
                'limit' => 10,
5637
                'offset' => 5,
5638
                'expectedcourses' => ['cfuture', 'efuture'],
5639
                'expectedprocessedcount' => 10,
5640
                'hiddencourse' => 'dfuture'
5641
            ],
5642
        ];
5643
    }
5644
 
5645
    /**
5646
     * Test the course_filter_courses_by_timeline_classification function hidden courses.
5647
     *
1441 ariadna 5648
     * @dataProvider get_course_filter_courses_by_timeline_classification_hidden_courses_test_cases
1 efrain 5649
     * @param array $coursedata Course test data to create.
5650
     * @param string $classification Timeline classification.
5651
     * @param int $limit Maximum number of results to return.
5652
     * @param int $offset Results to skip at the start of the result set.
5653
     * @param string[] $expectedcourses Expected courses in results.
5654
     * @param int $expectedprocessedcount Expected number of course records to be processed.
5655
     * @param int $hiddencourse The course to hide as part of this process
5656
     */
5657
    public function test_course_filter_courses_by_timeline_classification_with_hidden_courses(
5658
        $coursedata,
5659
        $classification,
5660
        $limit,
5661
        $offset,
5662
        $expectedcourses,
5663
        $expectedprocessedcount,
5664
        $hiddencourse
11 efrain 5665
    ): void {
1 efrain 5666
        $this->resetAfterTest();
5667
        $generator = $this->getDataGenerator();
5668
        $student = $generator->create_user();
5669
        $this->setUser($student);
5670
 
5671
        $courses = array_map(function($coursedata) use ($generator, $hiddencourse) {
5672
            $course = $generator->create_course($coursedata);
5673
            if ($course->shortname == $hiddencourse) {
5674
                set_user_preference('block_myoverview_hidden_course_' . $course->id, true);
5675
            }
5676
            return $course;
5677
        }, $coursedata);
5678
 
5679
        foreach ($courses as $course) {
5680
            $generator->enrol_user($student->id, $course->id, 'student');
5681
        }
5682
 
5683
        $coursesgenerator = course_get_enrolled_courses_for_logged_in_user(0, $offset, 'shortname ASC', 'shortname');
5684
        list($result, $processedcount) = course_filter_courses_by_timeline_classification(
5685
            $coursesgenerator,
5686
            $classification,
5687
            $limit
5688
        );
5689
 
5690
        $actual = array_map(function($course) {
5691
            return $course->shortname;
5692
        }, $result);
5693
 
5694
        $this->assertEquals($expectedcourses, $actual);
5695
        $this->assertEquals($expectedprocessedcount, $processedcount);
5696
    }
5697
 
5698
 
5699
    /**
5700
     * Testing core_course_core_calendar_get_valid_event_timestart_range when the course has no end date.
5701
     */
11 efrain 5702
    public function test_core_course_core_calendar_get_valid_event_timestart_range_no_enddate(): void {
1 efrain 5703
        global $CFG;
5704
        require_once($CFG->dirroot . "/calendar/lib.php");
5705
 
5706
        $this->resetAfterTest(true);
5707
        $this->setAdminUser();
5708
        $generator = $this->getDataGenerator();
5709
        $now = time();
5710
        $course = $generator->create_course(['startdate' => $now - 86400]);
5711
 
5712
        // Create a course event.
5713
        $event = new \calendar_event([
5714
            'name' => 'Test course event',
5715
            'eventtype' => 'course',
5716
            'courseid' => $course->id,
5717
        ]);
5718
 
5719
        list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course);
5720
        $this->assertEquals($course->startdate, $min[0]);
5721
        $this->assertNull($max);
5722
    }
5723
 
5724
    /**
5725
     * Testing core_course_core_calendar_get_valid_event_timestart_range when the course has end date.
5726
     */
11 efrain 5727
    public function test_core_course_core_calendar_get_valid_event_timestart_range_with_enddate(): void {
1 efrain 5728
        global $CFG;
5729
        require_once($CFG->dirroot . "/calendar/lib.php");
5730
 
5731
        $this->resetAfterTest(true);
5732
        $this->setAdminUser();
5733
        $generator = $this->getDataGenerator();
5734
        $now = time();
5735
        $course = $generator->create_course(['startdate' => $now - 86400, 'enddate' => $now + 86400]);
5736
 
5737
        // Create a course event.
5738
        $event = new \calendar_event([
5739
            'name' => 'Test course event',
5740
            'eventtype' => 'course',
5741
            'courseid' => $course->id,
5742
        ]);
5743
 
5744
        list ($min, $max) = core_course_core_calendar_get_valid_event_timestart_range($event, $course);
5745
        $this->assertEquals($course->startdate, $min[0]);
5746
        $this->assertNull($max);
5747
    }
5748
 
5749
    /**
5750
     * Test the course_get_recent_courses function.
5751
     */
11 efrain 5752
    public function test_course_get_recent_courses(): void {
1 efrain 5753
        global $DB;
5754
 
5755
        $this->resetAfterTest();
5756
        $generator = $this->getDataGenerator();
5757
 
5758
        $courses = array();
5759
        for ($i = 1; $i < 4; $i++) {
5760
            $courses[]  = $generator->create_course();
5761
        };
5762
 
5763
        $student = $generator->create_user();
5764
 
5765
        foreach ($courses as $course) {
5766
            $generator->enrol_user($student->id, $course->id, 'student');
5767
        }
5768
 
5769
        $this->setUser($student);
5770
 
5771
        $result = course_get_recent_courses($student->id);
5772
 
5773
        // No course accessed.
5774
        $this->assertCount(0, $result);
5775
 
5776
        $time = time();
5777
        foreach ($courses as $course) {
5778
            $context = context_course::instance($course->id);
5779
            course_view($context);
5780
            $DB->set_field('user_lastaccess', 'timeaccess', $time, [
5781
                'userid' => $student->id,
5782
                'courseid' => $course->id,
5783
                ]);
5784
            $time++;
5785
        }
5786
 
5787
        // Every course accessed.
5788
        $result = course_get_recent_courses($student->id);
5789
        $this->assertCount(3, $result);
5790
 
5791
        // Every course accessed, result limited to 2 courses.
5792
        $result = course_get_recent_courses($student->id, 2);
5793
        $this->assertCount(2, $result);
5794
 
5795
        // Every course accessed, with limit and offset should return the first course.
5796
        $result = course_get_recent_courses($student->id, 3, 2);
5797
        $this->assertCount(1, $result);
5798
        $this->assertArrayHasKey($courses[0]->id, $result);
5799
 
5800
        // Every course accessed, order by shortname DESC. The last create course ($course[2]) should have the greater shortname.
5801
        $result = course_get_recent_courses($student->id, 0, 0, 'shortname DESC');
5802
        $this->assertCount(3, $result);
5803
        $this->assertEquals($courses[2]->id, array_values($result)[0]->id);
5804
        $this->assertEquals($courses[1]->id, array_values($result)[1]->id);
5805
        $this->assertEquals($courses[0]->id, array_values($result)[2]->id);
5806
 
5807
        // Every course accessed, order by shortname ASC.
5808
        $result = course_get_recent_courses($student->id, 0, 0, 'shortname ASC');
5809
        $this->assertCount(3, $result);
5810
        $this->assertEquals($courses[0]->id, array_values($result)[0]->id);
5811
        $this->assertEquals($courses[1]->id, array_values($result)[1]->id);
5812
        $this->assertEquals($courses[2]->id, array_values($result)[2]->id);
5813
 
5814
        $guestcourse = $generator->create_course(
5815
            (object)array('shortname' => 'guestcourse',
5816
                'enrol_guest_status_0' => ENROL_INSTANCE_ENABLED,
5817
                'enrol_guest_password_0' => ''));
5818
        $context = context_course::instance($guestcourse->id);
5819
        course_view($context);
5820
 
5821
        // Every course accessed, even the not enrolled one.
5822
        $result = course_get_recent_courses($student->id);
5823
        $this->assertCount(4, $result);
5824
 
5825
        // Suspended student.
5826
        $this->getDataGenerator()->enrol_user($student->id, $courses[0]->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
5827
 
5828
        // The course with suspended enrolment is not returned by the function.
5829
        $result = course_get_recent_courses($student->id);
5830
        $this->assertCount(3, $result);
5831
        $this->assertArrayNotHasKey($courses[0]->id, $result);
5832
    }
5833
 
5834
    /**
5835
     * Test the validation of the sort value in course_get_recent_courses().
5836
     *
5837
     * @dataProvider course_get_recent_courses_sort_validation_provider
5838
     * @param string $sort The sort value
5839
     * @param string $expectedexceptionmsg The expected exception message
5840
     */
11 efrain 5841
    public function test_course_get_recent_courses_sort_validation(string $sort, string $expectedexceptionmsg): void {
1 efrain 5842
        $this->resetAfterTest();
5843
 
5844
        $user = $this->getDataGenerator()->create_user();
5845
 
5846
        if (!empty($expectedexceptionmsg)) {
5847
            $this->expectException('invalid_parameter_exception');
5848
            $this->expectExceptionMessage($expectedexceptionmsg);
5849
        }
5850
        course_get_recent_courses($user->id, 0, 0, $sort);
5851
    }
5852
 
5853
    /**
5854
     * Data provider for test_course_get_recent_courses_sort_validation().
5855
     *
5856
     * @return array
5857
     */
1441 ariadna 5858
    public static function course_get_recent_courses_sort_validation_provider(): array {
1 efrain 5859
        return [
5860
            'Invalid sort format (SQL injection attempt)' =>
5861
                [
5862
                    'shortname DESC LIMIT 1--',
5863
                    'Invalid structure of the sort parameter, allowed structure: fieldname [ASC|DESC].',
5864
                ],
5865
            'Sort uses \'sort by\' field that does not exist' =>
5866
                [
5867
                    'shortname DESC, xyz ASC',
5868
                    'Invalid field in the sort parameter, allowed fields: id, idnumber, summary, summaryformat, ' .
5869
                    'startdate, enddate, category, shortname, fullname, timeaccess, component, visible, ' .
5870
                    'showactivitydates, showcompletionconditions, pdfexportfont.',
5871
            ],
5872
            'Sort uses invalid value for the sorting direction' =>
5873
                [
5874
                    'shortname xyz, lastaccess',
5875
                    'Invalid sort direction in the sort parameter, allowed values: asc, desc.',
5876
                ],
5877
            'Valid sort format' =>
5878
                [
5879
                    'shortname asc, timeaccess',
5880
                    ''
5881
                ]
5882
        ];
5883
    }
5884
 
5885
    /**
5886
     * Test the course_get_recent_courses function.
5887
     */
11 efrain 5888
    public function test_course_get_recent_courses_with_guest(): void {
1 efrain 5889
        global $DB;
5890
        $this->resetAfterTest(true);
5891
 
5892
        $student = $this->getDataGenerator()->create_user();
5893
 
5894
        // Course 1 with guest access and no direct enrolment.
5895
        $course1 = $this->getDataGenerator()->create_course();
5896
        $context1 = context_course::instance($course1->id);
5897
        $record = $DB->get_record('enrol', ['courseid' => $course1->id, 'enrol' => 'guest']);
5898
        enrol_get_plugin('guest')->update_status($record, ENROL_INSTANCE_ENABLED);
5899
 
5900
        // Course 2 where student is enrolled with two enrolment methods.
5901
        $course2 = $this->getDataGenerator()->create_course();
5902
        $context2 = context_course::instance($course2->id);
5903
        $record = $DB->get_record('enrol', ['courseid' => $course2->id, 'enrol' => 'self']);
5904
        enrol_get_plugin('guest')->update_status($record, ENROL_INSTANCE_ENABLED);
5905
        $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student', 'manual', 0, 0, ENROL_USER_ACTIVE);
5906
        $this->getDataGenerator()->enrol_user($student->id, $course2->id, 'student', 'self', 0, 0, ENROL_USER_ACTIVE);
5907
 
5908
        // Course 3.
5909
        $course3 = $this->getDataGenerator()->create_course();
5910
        $context3 = context_course::instance($course3->id);
5911
 
5912
        // Student visits first two courses, course_get_recent_courses returns two courses.
5913
        $this->setUser($student);
5914
        course_view($context1);
5915
        course_view($context2);
5916
 
5917
        $result = course_get_recent_courses($student->id);
5918
        $this->assertEqualsCanonicalizing([$course2->id, $course1->id], array_column($result, 'id'));
5919
 
5920
        // Admin visits all three courses. Only the one with guest access is returned.
5921
        $this->setAdminUser();
5922
        course_view($context1);
5923
        course_view($context2);
5924
        course_view($context3);
5925
        $result = course_get_recent_courses(get_admin()->id);
5926
        $this->assertEqualsCanonicalizing([$course1->id], array_column($result, 'id'));
5927
    }
5928
 
5929
    /**
5930
     * Test cases for the course_get_course_dates_for_user_ids tests.
5931
     */
1441 ariadna 5932
    public static function get_course_get_course_dates_for_user_ids_test_cases(): array {
1 efrain 5933
        $now = time();
5934
        $pastcoursestart = $now - 100;
5935
        $futurecoursestart = $now + 100;
5936
 
5937
        return [
5938
            'future course start fixed no users enrolled' => [
5939
                'relativedatemode' => false,
5940
                'coursestart' => $futurecoursestart,
5941
                'usercount' => 2,
5942
                'enrolmentmethods' => [
5943
                    ['manual', ENROL_INSTANCE_ENABLED],
5944
                    ['self', ENROL_INSTANCE_ENABLED]
5945
                ],
5946
                'enrolled' => [[], []],
5947
                'expected' => [
5948
                    [
5949
                        'start' => $futurecoursestart,
5950
                        'startoffset' => 0
5951
                    ],
5952
                    [
5953
                        'start' => $futurecoursestart,
5954
                        'startoffset' => 0
5955
                    ]
5956
                ]
5957
            ],
5958
            'future course start fixed 1 users enrolled future' => [
5959
                'relativedatemode' => false,
5960
                'coursestart' => $futurecoursestart,
5961
                'usercount' => 2,
5962
                'enrolmentmethods' => [
5963
                    ['manual', ENROL_INSTANCE_ENABLED],
5964
                    ['self', ENROL_INSTANCE_ENABLED]
5965
                ],
5966
                'enrolled' => [
5967
                    // User 1.
5968
                    ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
5969
                    // User 2.
5970
                    []
5971
                ],
5972
                'expected' => [
5973
                    [
5974
                        'start' => $futurecoursestart,
5975
                        'startoffset' => 0
5976
                    ],
5977
                    [
5978
                        'start' => $futurecoursestart,
5979
                        'startoffset' => 0
5980
                    ]
5981
                ]
5982
            ],
5983
            'future course start fixed 1 users enrolled past' => [
5984
                'relativedatemode' => false,
5985
                'coursestart' => $futurecoursestart,
5986
                'usercount' => 2,
5987
                'enrolmentmethods' => [
5988
                    ['manual', ENROL_INSTANCE_ENABLED],
5989
                    ['self', ENROL_INSTANCE_ENABLED]
5990
                ],
5991
                'enrolled' => [
5992
                    // User 1.
5993
                    ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
5994
                    // User 2.
5995
                    []
5996
                ],
5997
                'expected' => [
5998
                    [
5999
                        'start' => $futurecoursestart,
6000
                        'startoffset' => 0
6001
                    ],
6002
                    [
6003
                        'start' => $futurecoursestart,
6004
                        'startoffset' => 0
6005
                    ]
6006
                ]
6007
            ],
6008
            'future course start fixed 2 users enrolled future' => [
6009
                'relativedatemode' => false,
6010
                'coursestart' => $futurecoursestart,
6011
                'usercount' => 2,
6012
                'enrolmentmethods' => [
6013
                    ['manual', ENROL_INSTANCE_ENABLED],
6014
                    ['self', ENROL_INSTANCE_ENABLED]
6015
                ],
6016
                'enrolled' => [
6017
                    // User 1.
6018
                    ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
6019
                    // User 2.
6020
                    ['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]]
6021
                ],
6022
                'expected' => [
6023
                    [
6024
                        'start' => $futurecoursestart,
6025
                        'startoffset' => 0
6026
                    ],
6027
                    [
6028
                        'start' => $futurecoursestart,
6029
                        'startoffset' => 0
6030
                    ]
6031
                ]
6032
            ],
6033
            'future course start fixed 2 users enrolled past' => [
6034
                'relativedatemode' => false,
6035
                'coursestart' => $futurecoursestart,
6036
                'usercount' => 2,
6037
                'enrolmentmethods' => [
6038
                    ['manual', ENROL_INSTANCE_ENABLED],
6039
                    ['self', ENROL_INSTANCE_ENABLED]
6040
                ],
6041
                'enrolled' => [
6042
                    // User 1.
6043
                    ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
6044
                    // User 2.
6045
                    ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
6046
                ],
6047
                'expected' => [
6048
                    [
6049
                        'start' => $futurecoursestart,
6050
                        'startoffset' => 0
6051
                    ],
6052
                    [
6053
                        'start' => $futurecoursestart,
6054
                        'startoffset' => 0
6055
                    ]
6056
                ]
6057
            ],
6058
            'future course start fixed 2 users enrolled mixed' => [
6059
                'relativedatemode' => false,
6060
                'coursestart' => $futurecoursestart,
6061
                'usercount' => 2,
6062
                'enrolmentmethods' => [
6063
                    ['manual', ENROL_INSTANCE_ENABLED],
6064
                    ['self', ENROL_INSTANCE_ENABLED]
6065
                ],
6066
                'enrolled' => [
6067
                    // User 1.
6068
                    ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
6069
                    // User 2.
6070
                    ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
6071
                ],
6072
                'expected' => [
6073
                    [
6074
                        'start' => $futurecoursestart,
6075
                        'startoffset' => 0
6076
                    ],
6077
                    [
6078
                        'start' => $futurecoursestart,
6079
                        'startoffset' => 0
6080
                    ]
6081
                ]
6082
            ],
6083
            'future course start fixed 2 users enrolled 2 methods' => [
6084
                'relativedatemode' => false,
6085
                'coursestart' => $futurecoursestart,
6086
                'usercount' => 2,
6087
                'enrolmentmethods' => [
6088
                    ['manual', ENROL_INSTANCE_ENABLED],
6089
                    ['self', ENROL_INSTANCE_ENABLED]
6090
                ],
6091
                'enrolled' => [
6092
                    // User 1.
6093
                    [
6094
                        'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
6095
                        'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
6096
                    ],
6097
                    // User 2.
6098
                    [
6099
                        'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
6100
                        'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
6101
                    ]
6102
                ],
6103
                'expected' => [
6104
                    [
6105
                        'start' => $futurecoursestart,
6106
                        'startoffset' => 0
6107
                    ],
6108
                    [
6109
                        'start' => $futurecoursestart,
6110
                        'startoffset' => 0
6111
                    ]
6112
                ]
6113
            ],
6114
            'future course start fixed 2 users enrolled 2 methods 1 disabled' => [
6115
                'relativedatemode' => false,
6116
                'coursestart' => $futurecoursestart,
6117
                'usercount' => 2,
6118
                'enrolmentmethods' => [
6119
                    ['manual', ENROL_INSTANCE_DISABLED],
6120
                    ['self', ENROL_INSTANCE_ENABLED]
6121
                ],
6122
                'enrolled' => [
6123
                    // User 1.
6124
                    [
6125
                        'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
6126
                        'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
6127
                    ],
6128
                    // User 2.
6129
                    [
6130
                        'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
6131
                        'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
6132
                    ]
6133
                ],
6134
                'expected' => [
6135
                    [
6136
                        'start' => $futurecoursestart,
6137
                        'startoffset' => 0
6138
                    ],
6139
                    [
6140
                        'start' => $futurecoursestart,
6141
                        'startoffset' => 0
6142
                    ]
6143
                ]
6144
            ],
6145
            'future course start fixed 2 users enrolled 2 methods 2 disabled' => [
6146
                'relativedatemode' => false,
6147
                'coursestart' => $futurecoursestart,
6148
                'usercount' => 2,
6149
                'enrolmentmethods' => [
6150
                    ['manual', ENROL_INSTANCE_DISABLED],
6151
                    ['self', ENROL_INSTANCE_DISABLED]
6152
                ],
6153
                'enrolled' => [
6154
                    // User 1.
6155
                    [
6156
                        'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
6157
                        'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
6158
                    ],
6159
                    // User 2.
6160
                    [
6161
                        'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
6162
                        'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
6163
                    ]
6164
                ],
6165
                'expected' => [
6166
                    [
6167
                        'start' => $futurecoursestart,
6168
                        'startoffset' => 0
6169
                    ],
6170
                    [
6171
                        'start' => $futurecoursestart,
6172
                        'startoffset' => 0
6173
                    ]
6174
                ]
6175
            ],
6176
            'future course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
6177
                'relativedatemode' => false,
6178
                'coursestart' => $futurecoursestart,
6179
                'usercount' => 2,
6180
                'enrolmentmethods' => [
6181
                    ['manual', ENROL_INSTANCE_ENABLED],
6182
                    ['self', ENROL_INSTANCE_ENABLED]
6183
                ],
6184
                'enrolled' => [
6185
                    // User 1.
6186
                    [
6187
                        'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
6188
                        'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
6189
                    ],
6190
                    // User 2.
6191
                    [
6192
                        'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
6193
                        'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
6194
                    ]
6195
                ],
6196
                'expected' => [
6197
                    [
6198
                        'start' => $futurecoursestart,
6199
                        'startoffset' => 0
6200
                    ],
6201
                    [
6202
                        'start' => $futurecoursestart,
6203
                        'startoffset' => 0
6204
                    ]
6205
                ]
6206
            ],
6207
            'future course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
6208
                'relativedatemode' => false,
6209
                'coursestart' => $futurecoursestart,
6210
                'usercount' => 2,
6211
                'enrolmentmethods' => [
6212
                    ['manual', ENROL_INSTANCE_ENABLED],
6213
                    ['self', ENROL_INSTANCE_ENABLED]
6214
                ],
6215
                'enrolled' => [
6216
                    // User 1.
6217
                    [
6218
                        'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
6219
                        'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED]
6220
                    ],
6221
                    // User 2.
6222
                    [
6223
                        'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
6224
                        'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED]
6225
                    ]
6226
                ],
6227
                'expected' => [
6228
                    [
6229
                        'start' => $futurecoursestart,
6230
                        'startoffset' => 0
6231
                    ],
6232
                    [
6233
                        'start' => $futurecoursestart,
6234
                        'startoffset' => 0
6235
                    ]
6236
                ]
6237
            ],
6238
            'future course start relative no users enrolled' => [
6239
                'relativedatemode' => true,
6240
                'coursestart' => $futurecoursestart,
6241
                'usercount' => 2,
6242
                'enrolmentmethods' => [
6243
                    ['manual', ENROL_INSTANCE_ENABLED],
6244
                    ['self', ENROL_INSTANCE_ENABLED]
6245
                ],
6246
                'enrolled' => [[], []],
6247
                'expected' => [
6248
                    [
6249
                        'start' => $futurecoursestart,
6250
                        'startoffset' => 0
6251
                    ],
6252
                    [
6253
                        'start' => $futurecoursestart,
6254
                        'startoffset' => 0
6255
                    ]
6256
                ]
6257
            ],
6258
            'future course start relative 1 users enrolled future' => [
6259
                'relativedatemode' => true,
6260
                'coursestart' => $futurecoursestart,
6261
                'usercount' => 2,
6262
                'enrolmentmethods' => [
6263
                    ['manual', ENROL_INSTANCE_ENABLED],
6264
                    ['self', ENROL_INSTANCE_ENABLED]
6265
                ],
6266
                'enrolled' => [
6267
                    // User 1.
6268
                    ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
6269
                    // User 2.
6270
                    []
6271
                ],
6272
                'expected' => [
6273
                    [
6274
                        'start' => $futurecoursestart + 10,
6275
                        'startoffset' => 10
6276
                    ],
6277
                    [
6278
                        'start' => $futurecoursestart,
6279
                        'startoffset' => 0
6280
                    ]
6281
                ]
6282
            ],
6283
            'future course start relative 1 users enrolled past' => [
6284
                'relativedatemode' => true,
6285
                'coursestart' => $futurecoursestart,
6286
                'usercount' => 2,
6287
                'enrolmentmethods' => [
6288
                    ['manual', ENROL_INSTANCE_ENABLED],
6289
                    ['self', ENROL_INSTANCE_ENABLED]
6290
                ],
6291
                'enrolled' => [
6292
                    // User 1.
6293
                    ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
6294
                    // User 2.
6295
                    []
6296
                ],
6297
                'expected' => [
6298
                    [
6299
                        'start' => $futurecoursestart,
6300
                        'startoffset' => 0
6301
                    ],
6302
                    [
6303
                        'start' => $futurecoursestart,
6304
                        'startoffset' => 0
6305
                    ]
6306
                ]
6307
            ],
6308
            'future course start relative 2 users enrolled future' => [
6309
                'relativedatemode' => true,
6310
                'coursestart' => $futurecoursestart,
6311
                'usercount' => 2,
6312
                'enrolmentmethods' => [
6313
                    ['manual', ENROL_INSTANCE_ENABLED],
6314
                    ['self', ENROL_INSTANCE_ENABLED]
6315
                ],
6316
                'enrolled' => [
6317
                    // User 1.
6318
                    ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
6319
                    // User 2.
6320
                    ['manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]]
6321
                ],
6322
                'expected' => [
6323
                    [
6324
                        'start' => $futurecoursestart + 10,
6325
                        'startoffset' => 10
6326
                    ],
6327
                    [
6328
                        'start' => $futurecoursestart + 20,
6329
                        'startoffset' => 20
6330
                    ]
6331
                ]
6332
            ],
6333
            'future course start relative 2 users enrolled past' => [
6334
                'relativedatemode' => true,
6335
                'coursestart' => $futurecoursestart,
6336
                'usercount' => 2,
6337
                'enrolmentmethods' => [
6338
                    ['manual', ENROL_INSTANCE_ENABLED],
6339
                    ['self', ENROL_INSTANCE_ENABLED]
6340
                ],
6341
                'enrolled' => [
6342
                    // User 1.
6343
                    ['manual' => [$futurecoursestart - 10, ENROL_USER_ACTIVE]],
6344
                    // User 2.
6345
                    ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
6346
                ],
6347
                'expected' => [
6348
                    [
6349
                        'start' => $futurecoursestart,
6350
                        'startoffset' => 0
6351
                    ],
6352
                    [
6353
                        'start' => $futurecoursestart,
6354
                        'startoffset' => 0
6355
                    ]
6356
                ]
6357
            ],
6358
            'future course start relative 2 users enrolled mixed' => [
6359
                'relativedatemode' => true,
6360
                'coursestart' => $futurecoursestart,
6361
                'usercount' => 2,
6362
                'enrolmentmethods' => [
6363
                    ['manual', ENROL_INSTANCE_ENABLED],
6364
                    ['self', ENROL_INSTANCE_ENABLED]
6365
                ],
6366
                'enrolled' => [
6367
                    // User 1.
6368
                    ['manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]],
6369
                    // User 2.
6370
                    ['manual' => [$futurecoursestart - 20, ENROL_USER_ACTIVE]]
6371
                ],
6372
                'expected' => [
6373
                    [
6374
                        'start' => $futurecoursestart + 10,
6375
                        'startoffset' => 10
6376
                    ],
6377
                    [
6378
                        'start' => $futurecoursestart,
6379
                        'startoffset' => 0
6380
                    ]
6381
                ]
6382
            ],
6383
            'future course start relative 2 users enrolled 2 methods' => [
6384
                'relativedatemode' => true,
6385
                'coursestart' => $futurecoursestart,
6386
                'usercount' => 2,
6387
                'enrolmentmethods' => [
6388
                    ['manual', ENROL_INSTANCE_ENABLED],
6389
                    ['self', ENROL_INSTANCE_ENABLED]
6390
                ],
6391
                'enrolled' => [
6392
                    // User 1.
6393
                    [
6394
                        'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
6395
                        'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
6396
                    ],
6397
                    // User 2.
6398
                    [
6399
                        'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
6400
                        'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
6401
                    ]
6402
                ],
6403
                'expected' => [
6404
                    [
6405
                        'start' => $futurecoursestart + 10,
6406
                        'startoffset' => 10
6407
                    ],
6408
                    [
6409
                        'start' => $futurecoursestart + 10,
6410
                        'startoffset' => 10
6411
                    ]
6412
                ]
6413
            ],
6414
            'future course start relative 2 users enrolled 2 methods 1 disabled' => [
6415
                'relativedatemode' => true,
6416
                'coursestart' => $futurecoursestart,
6417
                'usercount' => 2,
6418
                'enrolmentmethods' => [
6419
                    ['manual', ENROL_INSTANCE_DISABLED],
6420
                    ['self', ENROL_INSTANCE_ENABLED]
6421
                ],
6422
                'enrolled' => [
6423
                    // User 1.
6424
                    [
6425
                        'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
6426
                        'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
6427
                    ],
6428
                    // User 2.
6429
                    [
6430
                        'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
6431
                        'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
6432
                    ]
6433
                ],
6434
                'expected' => [
6435
                    [
6436
                        'start' => $futurecoursestart + 20,
6437
                        'startoffset' => 20
6438
                    ],
6439
                    [
6440
                        'start' => $futurecoursestart + 10,
6441
                        'startoffset' => 10
6442
                    ]
6443
                ]
6444
            ],
6445
            'future course start relative 2 users enrolled 2 methods 2 disabled' => [
6446
                'relativedatemode' => true,
6447
                'coursestart' => $futurecoursestart,
6448
                'usercount' => 2,
6449
                'enrolmentmethods' => [
6450
                    ['manual', ENROL_INSTANCE_DISABLED],
6451
                    ['self', ENROL_INSTANCE_DISABLED]
6452
                ],
6453
                'enrolled' => [
6454
                    // User 1.
6455
                    [
6456
                        'manual' => [$futurecoursestart + 10, ENROL_USER_ACTIVE],
6457
                        'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
6458
                    ],
6459
                    // User 2.
6460
                    [
6461
                        'manual' => [$futurecoursestart + 20, ENROL_USER_ACTIVE],
6462
                        'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
6463
                    ]
6464
                ],
6465
                'expected' => [
6466
                    [
6467
                        'start' => $futurecoursestart,
6468
                        'startoffset' => 0
6469
                    ],
6470
                    [
6471
                        'start' => $futurecoursestart,
6472
                        'startoffset' => 0
6473
                    ]
6474
                ]
6475
            ],
6476
            'future course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
6477
                'relativedatemode' => true,
6478
                'coursestart' => $futurecoursestart,
6479
                'usercount' => 2,
6480
                'enrolmentmethods' => [
6481
                    ['manual', ENROL_INSTANCE_ENABLED],
6482
                    ['self', ENROL_INSTANCE_ENABLED]
6483
                ],
6484
                'enrolled' => [
6485
                    // User 1.
6486
                    [
6487
                        'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
6488
                        'self' => [$futurecoursestart + 20, ENROL_USER_ACTIVE]
6489
                    ],
6490
                    // User 2.
6491
                    [
6492
                        'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
6493
                        'self' => [$futurecoursestart + 10, ENROL_USER_ACTIVE]
6494
                    ]
6495
                ],
6496
                'expected' => [
6497
                    [
6498
                        'start' => $futurecoursestart + 20,
6499
                        'startoffset' => 20
6500
                    ],
6501
                    [
6502
                        'start' => $futurecoursestart + 10,
6503
                        'startoffset' => 10
6504
                    ]
6505
                ]
6506
            ],
6507
            'future course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
6508
                'relativedatemode' => true,
6509
                'coursestart' => $futurecoursestart,
6510
                'usercount' => 2,
6511
                'enrolmentmethods' => [
6512
                    ['manual', ENROL_INSTANCE_ENABLED],
6513
                    ['self', ENROL_INSTANCE_ENABLED]
6514
                ],
6515
                'enrolled' => [
6516
                    // User 1.
6517
                    [
6518
                        'manual' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED],
6519
                        'self' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED]
6520
                    ],
6521
                    // User 2.
6522
                    [
6523
                        'manual' => [$futurecoursestart + 20, ENROL_USER_SUSPENDED],
6524
                        'self' => [$futurecoursestart + 10, ENROL_USER_SUSPENDED]
6525
                    ]
6526
                ],
6527
                'expected' => [
6528
                    [
6529
                        'start' => $futurecoursestart,
6530
                        'startoffset' => 0
6531
                    ],
6532
                    [
6533
                        'start' => $futurecoursestart,
6534
                        'startoffset' => 0
6535
                    ]
6536
                ]
6537
            ],
6538
 
6539
            // Course start date in the past.
6540
            'past course start fixed no users enrolled' => [
6541
                'relativedatemode' => false,
6542
                'coursestart' => $pastcoursestart,
6543
                'usercount' => 2,
6544
                'enrolmentmethods' => [
6545
                    ['manual', ENROL_INSTANCE_ENABLED],
6546
                    ['self', ENROL_INSTANCE_ENABLED]
6547
                ],
6548
                'enrolled' => [[], []],
6549
                'expected' => [
6550
                    [
6551
                        'start' => $pastcoursestart,
6552
                        'startoffset' => 0
6553
                    ],
6554
                    [
6555
                        'start' => $pastcoursestart,
6556
                        'startoffset' => 0
6557
                    ]
6558
                ]
6559
            ],
6560
            'past course start fixed 1 users enrolled future' => [
6561
                'relativedatemode' => false,
6562
                'coursestart' => $pastcoursestart,
6563
                'usercount' => 2,
6564
                'enrolmentmethods' => [
6565
                    ['manual', ENROL_INSTANCE_ENABLED],
6566
                    ['self', ENROL_INSTANCE_ENABLED]
6567
                ],
6568
                'enrolled' => [
6569
                    // User 1.
6570
                    ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
6571
                    // User 2.
6572
                    []
6573
                ],
6574
                'expected' => [
6575
                    [
6576
                        'start' => $pastcoursestart,
6577
                        'startoffset' => 0
6578
                    ],
6579
                    [
6580
                        'start' => $pastcoursestart,
6581
                        'startoffset' => 0
6582
                    ]
6583
                ]
6584
            ],
6585
            'past course start fixed 1 users enrolled past' => [
6586
                'relativedatemode' => false,
6587
                'coursestart' => $pastcoursestart,
6588
                'usercount' => 2,
6589
                'enrolmentmethods' => [
6590
                    ['manual', ENROL_INSTANCE_ENABLED],
6591
                    ['self', ENROL_INSTANCE_ENABLED]
6592
                ],
6593
                'enrolled' => [
6594
                    // User 1.
6595
                    ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
6596
                    // User 2.
6597
                    []
6598
                ],
6599
                'expected' => [
6600
                    [
6601
                        'start' => $pastcoursestart,
6602
                        'startoffset' => 0
6603
                    ],
6604
                    [
6605
                        'start' => $pastcoursestart,
6606
                        'startoffset' => 0
6607
                    ]
6608
                ]
6609
            ],
6610
            'past course start fixed 2 users enrolled future' => [
6611
                'relativedatemode' => false,
6612
                'coursestart' => $pastcoursestart,
6613
                'usercount' => 2,
6614
                'enrolmentmethods' => [
6615
                    ['manual', ENROL_INSTANCE_ENABLED],
6616
                    ['self', ENROL_INSTANCE_ENABLED]
6617
                ],
6618
                'enrolled' => [
6619
                    // User 1.
6620
                    ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
6621
                    // User 2.
6622
                    ['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]]
6623
                ],
6624
                'expected' => [
6625
                    [
6626
                        'start' => $pastcoursestart,
6627
                        'startoffset' => 0
6628
                    ],
6629
                    [
6630
                        'start' => $pastcoursestart,
6631
                        'startoffset' => 0
6632
                    ]
6633
                ]
6634
            ],
6635
            'past course start fixed 2 users enrolled past' => [
6636
                'relativedatemode' => false,
6637
                'coursestart' => $pastcoursestart,
6638
                'usercount' => 2,
6639
                'enrolmentmethods' => [
6640
                    ['manual', ENROL_INSTANCE_ENABLED],
6641
                    ['self', ENROL_INSTANCE_ENABLED]
6642
                ],
6643
                'enrolled' => [
6644
                    // User 1.
6645
                    ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
6646
                    // User 2.
6647
                    ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
6648
                ],
6649
                'expected' => [
6650
                    [
6651
                        'start' => $pastcoursestart,
6652
                        'startoffset' => 0
6653
                    ],
6654
                    [
6655
                        'start' => $pastcoursestart,
6656
                        'startoffset' => 0
6657
                    ]
6658
                ]
6659
            ],
6660
            'past course start fixed 2 users enrolled mixed' => [
6661
                'relativedatemode' => false,
6662
                'coursestart' => $pastcoursestart,
6663
                'usercount' => 2,
6664
                'enrolmentmethods' => [
6665
                    ['manual', ENROL_INSTANCE_ENABLED],
6666
                    ['self', ENROL_INSTANCE_ENABLED]
6667
                ],
6668
                'enrolled' => [
6669
                    // User 1.
6670
                    ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
6671
                    // User 2.
6672
                    ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
6673
                ],
6674
                'expected' => [
6675
                    [
6676
                        'start' => $pastcoursestart,
6677
                        'startoffset' => 0
6678
                    ],
6679
                    [
6680
                        'start' => $pastcoursestart,
6681
                        'startoffset' => 0
6682
                    ]
6683
                ]
6684
            ],
6685
            'past course start fixed 2 users enrolled 2 methods' => [
6686
                'relativedatemode' => false,
6687
                'coursestart' => $pastcoursestart,
6688
                'usercount' => 2,
6689
                'enrolmentmethods' => [
6690
                    ['manual', ENROL_INSTANCE_ENABLED],
6691
                    ['self', ENROL_INSTANCE_ENABLED]
6692
                ],
6693
                'enrolled' => [
6694
                    // User 1.
6695
                    [
6696
                        'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
6697
                        'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
6698
                    ],
6699
                    // User 2.
6700
                    [
6701
                        'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
6702
                        'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
6703
                    ]
6704
                ],
6705
                'expected' => [
6706
                    [
6707
                        'start' => $pastcoursestart,
6708
                        'startoffset' => 0
6709
                    ],
6710
                    [
6711
                        'start' => $pastcoursestart,
6712
                        'startoffset' => 0
6713
                    ]
6714
                ]
6715
            ],
6716
            'past course start fixed 2 users enrolled 2 methods 1 disabled' => [
6717
                'relativedatemode' => false,
6718
                'coursestart' => $pastcoursestart,
6719
                'usercount' => 2,
6720
                'enrolmentmethods' => [
6721
                    ['manual', ENROL_INSTANCE_DISABLED],
6722
                    ['self', ENROL_INSTANCE_ENABLED]
6723
                ],
6724
                'enrolled' => [
6725
                    // User 1.
6726
                    [
6727
                        'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
6728
                        'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
6729
                    ],
6730
                    // User 2.
6731
                    [
6732
                        'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
6733
                        'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
6734
                    ]
6735
                ],
6736
                'expected' => [
6737
                    [
6738
                        'start' => $pastcoursestart,
6739
                        'startoffset' => 0
6740
                    ],
6741
                    [
6742
                        'start' => $pastcoursestart,
6743
                        'startoffset' => 0
6744
                    ]
6745
                ]
6746
            ],
6747
            'past course start fixed 2 users enrolled 2 methods 2 disabled' => [
6748
                'relativedatemode' => false,
6749
                'coursestart' => $pastcoursestart,
6750
                'usercount' => 2,
6751
                'enrolmentmethods' => [
6752
                    ['manual', ENROL_INSTANCE_DISABLED],
6753
                    ['self', ENROL_INSTANCE_DISABLED]
6754
                ],
6755
                'enrolled' => [
6756
                    // User 1.
6757
                    [
6758
                        'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
6759
                        'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
6760
                    ],
6761
                    // User 2.
6762
                    [
6763
                        'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
6764
                        'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
6765
                    ]
6766
                ],
6767
                'expected' => [
6768
                    [
6769
                        'start' => $pastcoursestart,
6770
                        'startoffset' => 0
6771
                    ],
6772
                    [
6773
                        'start' => $pastcoursestart,
6774
                        'startoffset' => 0
6775
                    ]
6776
                ]
6777
            ],
6778
            'past course start fixed 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
6779
                'relativedatemode' => false,
6780
                'coursestart' => $pastcoursestart,
6781
                'usercount' => 2,
6782
                'enrolmentmethods' => [
6783
                    ['manual', ENROL_INSTANCE_ENABLED],
6784
                    ['self', ENROL_INSTANCE_ENABLED]
6785
                ],
6786
                'enrolled' => [
6787
                    // User 1.
6788
                    [
6789
                        'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
6790
                        'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
6791
                    ],
6792
                    // User 2.
6793
                    [
6794
                        'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
6795
                        'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
6796
                    ]
6797
                ],
6798
                'expected' => [
6799
                    [
6800
                        'start' => $pastcoursestart,
6801
                        'startoffset' => 0
6802
                    ],
6803
                    [
6804
                        'start' => $pastcoursestart,
6805
                        'startoffset' => 0
6806
                    ]
6807
                ]
6808
            ],
6809
            'past course start fixed 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
6810
                'relativedatemode' => false,
6811
                'coursestart' => $pastcoursestart,
6812
                'usercount' => 2,
6813
                'enrolmentmethods' => [
6814
                    ['manual', ENROL_INSTANCE_ENABLED],
6815
                    ['self', ENROL_INSTANCE_ENABLED]
6816
                ],
6817
                'enrolled' => [
6818
                    // User 1.
6819
                    [
6820
                        'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
6821
                        'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED]
6822
                    ],
6823
                    // User 2.
6824
                    [
6825
                        'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
6826
                        'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED]
6827
                    ]
6828
                ],
6829
                'expected' => [
6830
                    [
6831
                        'start' => $pastcoursestart,
6832
                        'startoffset' => 0
6833
                    ],
6834
                    [
6835
                        'start' => $pastcoursestart,
6836
                        'startoffset' => 0
6837
                    ]
6838
                ]
6839
            ],
6840
            'past course start relative no users enrolled' => [
6841
                'relativedatemode' => true,
6842
                'coursestart' => $pastcoursestart,
6843
                'usercount' => 2,
6844
                'enrolmentmethods' => [
6845
                    ['manual', ENROL_INSTANCE_ENABLED],
6846
                    ['self', ENROL_INSTANCE_ENABLED]
6847
                ],
6848
                'enrolled' => [[], []],
6849
                'expected' => [
6850
                    [
6851
                        'start' => $pastcoursestart,
6852
                        'startoffset' => 0
6853
                    ],
6854
                    [
6855
                        'start' => $pastcoursestart,
6856
                        'startoffset' => 0
6857
                    ]
6858
                ]
6859
            ],
6860
            'past course start relative 1 users enrolled future' => [
6861
                'relativedatemode' => true,
6862
                'coursestart' => $pastcoursestart,
6863
                'usercount' => 2,
6864
                'enrolmentmethods' => [
6865
                    ['manual', ENROL_INSTANCE_ENABLED],
6866
                    ['self', ENROL_INSTANCE_ENABLED]
6867
                ],
6868
                'enrolled' => [
6869
                    // User 1.
6870
                    ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
6871
                    // User 2.
6872
                    []
6873
                ],
6874
                'expected' => [
6875
                    [
6876
                        'start' => $pastcoursestart + 10,
6877
                        'startoffset' => 10
6878
                    ],
6879
                    [
6880
                        'start' => $pastcoursestart,
6881
                        'startoffset' => 0
6882
                    ]
6883
                ]
6884
            ],
6885
            'past course start relative 1 users enrolled past' => [
6886
                'relativedatemode' => true,
6887
                'coursestart' => $pastcoursestart,
6888
                'usercount' => 2,
6889
                'enrolmentmethods' => [
6890
                    ['manual', ENROL_INSTANCE_ENABLED],
6891
                    ['self', ENROL_INSTANCE_ENABLED]
6892
                ],
6893
                'enrolled' => [
6894
                    // User 1.
6895
                    ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
6896
                    // User 2.
6897
                    []
6898
                ],
6899
                'expected' => [
6900
                    [
6901
                        'start' => $pastcoursestart,
6902
                        'startoffset' => 0
6903
                    ],
6904
                    [
6905
                        'start' => $pastcoursestart,
6906
                        'startoffset' => 0
6907
                    ]
6908
                ]
6909
            ],
6910
            'past course start relative 2 users enrolled future' => [
6911
                'relativedatemode' => true,
6912
                'coursestart' => $pastcoursestart,
6913
                'usercount' => 2,
6914
                'enrolmentmethods' => [
6915
                    ['manual', ENROL_INSTANCE_ENABLED],
6916
                    ['self', ENROL_INSTANCE_ENABLED]
6917
                ],
6918
                'enrolled' => [
6919
                    // User 1.
6920
                    ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
6921
                    // User 2.
6922
                    ['manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]]
6923
                ],
6924
                'expected' => [
6925
                    [
6926
                        'start' => $pastcoursestart + 10,
6927
                        'startoffset' => 10
6928
                    ],
6929
                    [
6930
                        'start' => $pastcoursestart + 20,
6931
                        'startoffset' => 20
6932
                    ]
6933
                ]
6934
            ],
6935
            'past course start relative 2 users enrolled past' => [
6936
                'relativedatemode' => true,
6937
                'coursestart' => $pastcoursestart,
6938
                'usercount' => 2,
6939
                'enrolmentmethods' => [
6940
                    ['manual', ENROL_INSTANCE_ENABLED],
6941
                    ['self', ENROL_INSTANCE_ENABLED]
6942
                ],
6943
                'enrolled' => [
6944
                    // User 1.
6945
                    ['manual' => [$pastcoursestart - 10, ENROL_USER_ACTIVE]],
6946
                    // User 2.
6947
                    ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
6948
                ],
6949
                'expected' => [
6950
                    [
6951
                        'start' => $pastcoursestart,
6952
                        'startoffset' => 0
6953
                    ],
6954
                    [
6955
                        'start' => $pastcoursestart,
6956
                        'startoffset' => 0
6957
                    ]
6958
                ]
6959
            ],
6960
            'past course start relative 2 users enrolled mixed' => [
6961
                'relativedatemode' => true,
6962
                'coursestart' => $pastcoursestart,
6963
                'usercount' => 2,
6964
                'enrolmentmethods' => [
6965
                    ['manual', ENROL_INSTANCE_ENABLED],
6966
                    ['self', ENROL_INSTANCE_ENABLED]
6967
                ],
6968
                'enrolled' => [
6969
                    // User 1.
6970
                    ['manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]],
6971
                    // User 2.
6972
                    ['manual' => [$pastcoursestart - 20, ENROL_USER_ACTIVE]]
6973
                ],
6974
                'expected' => [
6975
                    [
6976
                        'start' => $pastcoursestart + 10,
6977
                        'startoffset' => 10
6978
                    ],
6979
                    [
6980
                        'start' => $pastcoursestart,
6981
                        'startoffset' => 0
6982
                    ]
6983
                ]
6984
            ],
6985
            'past course start relative 2 users enrolled 2 methods' => [
6986
                'relativedatemode' => true,
6987
                'coursestart' => $pastcoursestart,
6988
                'usercount' => 2,
6989
                'enrolmentmethods' => [
6990
                    ['manual', ENROL_INSTANCE_ENABLED],
6991
                    ['self', ENROL_INSTANCE_ENABLED]
6992
                ],
6993
                'enrolled' => [
6994
                    // User 1.
6995
                    [
6996
                        'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
6997
                        'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
6998
                    ],
6999
                    // User 2.
7000
                    [
7001
                        'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
7002
                        'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
7003
                    ]
7004
                ],
7005
                'expected' => [
7006
                    [
7007
                        'start' => $pastcoursestart + 10,
7008
                        'startoffset' => 10
7009
                    ],
7010
                    [
7011
                        'start' => $pastcoursestart + 10,
7012
                        'startoffset' => 10
7013
                    ]
7014
                ]
7015
            ],
7016
            'past course start relative 2 users enrolled 2 methods 1 disabled' => [
7017
                'relativedatemode' => true,
7018
                'coursestart' => $pastcoursestart,
7019
                'usercount' => 2,
7020
                'enrolmentmethods' => [
7021
                    ['manual', ENROL_INSTANCE_DISABLED],
7022
                    ['self', ENROL_INSTANCE_ENABLED]
7023
                ],
7024
                'enrolled' => [
7025
                    // User 1.
7026
                    [
7027
                        'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
7028
                        'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
7029
                    ],
7030
                    // User 2.
7031
                    [
7032
                        'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
7033
                        'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
7034
                    ]
7035
                ],
7036
                'expected' => [
7037
                    [
7038
                        'start' => $pastcoursestart + 20,
7039
                        'startoffset' => 20
7040
                    ],
7041
                    [
7042
                        'start' => $pastcoursestart + 10,
7043
                        'startoffset' => 10
7044
                    ]
7045
                ]
7046
            ],
7047
            'past course start relative 2 users enrolled 2 methods 2 disabled' => [
7048
                'relativedatemode' => true,
7049
                'coursestart' => $pastcoursestart,
7050
                'usercount' => 2,
7051
                'enrolmentmethods' => [
7052
                    ['manual', ENROL_INSTANCE_DISABLED],
7053
                    ['self', ENROL_INSTANCE_DISABLED]
7054
                ],
7055
                'enrolled' => [
7056
                    // User 1.
7057
                    [
7058
                        'manual' => [$pastcoursestart + 10, ENROL_USER_ACTIVE],
7059
                        'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
7060
                    ],
7061
                    // User 2.
7062
                    [
7063
                        'manual' => [$pastcoursestart + 20, ENROL_USER_ACTIVE],
7064
                        'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
7065
                    ]
7066
                ],
7067
                'expected' => [
7068
                    [
7069
                        'start' => $pastcoursestart,
7070
                        'startoffset' => 0
7071
                    ],
7072
                    [
7073
                        'start' => $pastcoursestart,
7074
                        'startoffset' => 0
7075
                    ]
7076
                ]
7077
            ],
7078
            'past course start relative 2 users enrolled 2 methods 0 disabled 1 user suspended' => [
7079
                'relativedatemode' => true,
7080
                'coursestart' => $pastcoursestart,
7081
                'usercount' => 2,
7082
                'enrolmentmethods' => [
7083
                    ['manual', ENROL_INSTANCE_ENABLED],
7084
                    ['self', ENROL_INSTANCE_ENABLED]
7085
                ],
7086
                'enrolled' => [
7087
                    // User 1.
7088
                    [
7089
                        'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
7090
                        'self' => [$pastcoursestart + 20, ENROL_USER_ACTIVE]
7091
                    ],
7092
                    // User 2.
7093
                    [
7094
                        'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
7095
                        'self' => [$pastcoursestart + 10, ENROL_USER_ACTIVE]
7096
                    ]
7097
                ],
7098
                'expected' => [
7099
                    [
7100
                        'start' => $pastcoursestart + 20,
7101
                        'startoffset' => 20
7102
                    ],
7103
                    [
7104
                        'start' => $pastcoursestart + 10,
7105
                        'startoffset' => 10
7106
                    ]
7107
                ]
7108
            ],
7109
            'past course start relative 2 users enrolled 2 methods 0 disabled 2 user suspended' => [
7110
                'relativedatemode' => true,
7111
                'coursestart' => $pastcoursestart,
7112
                'usercount' => 2,
7113
                'enrolmentmethods' => [
7114
                    ['manual', ENROL_INSTANCE_ENABLED],
7115
                    ['self', ENROL_INSTANCE_ENABLED]
7116
                ],
7117
                'enrolled' => [
7118
                    // User 1.
7119
                    [
7120
                        'manual' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED],
7121
                        'self' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED]
7122
                    ],
7123
                    // User 2.
7124
                    [
7125
                        'manual' => [$pastcoursestart + 20, ENROL_USER_SUSPENDED],
7126
                        'self' => [$pastcoursestart + 10, ENROL_USER_SUSPENDED]
7127
                    ]
7128
                ],
7129
                'expected' => [
7130
                    [
7131
                        'start' => $pastcoursestart,
7132
                        'startoffset' => 0
7133
                    ],
7134
                    [
7135
                        'start' => $pastcoursestart,
7136
                        'startoffset' => 0
7137
                    ]
7138
                ]
7139
            ]
7140
        ];
7141
    }
7142
 
7143
    /**
7144
     * Test the course_get_course_dates_for_user_ids function.
7145
     *
1441 ariadna 7146
     * @dataProvider get_course_get_course_dates_for_user_ids_test_cases
1 efrain 7147
     * @param bool $relativedatemode Set the course to relative dates mode
7148
     * @param int $coursestart Course start date
7149
     * @param int $usercount Number of users to create
7150
     * @param array $enrolmentmethods Enrolment methods to set for the course
7151
     * @param array $enrolled Enrolment config for to set for the users
7152
     * @param array $expected Expected output
7153
     */
7154
    public function test_course_get_course_dates_for_user_ids(
7155
        $relativedatemode,
7156
        $coursestart,
7157
        $usercount,
7158
        $enrolmentmethods,
7159
        $enrolled,
7160
        $expected
11 efrain 7161
    ): void {
1 efrain 7162
        global $DB;
7163
        $this->resetAfterTest();
7164
 
7165
        $generator = $this->getDataGenerator();
7166
        $course  = $generator->create_course(['startdate' => $coursestart]);
7167
        $course->relativedatesmode = $relativedatemode;
7168
        $users = [];
7169
 
7170
        for ($i = 0; $i < $usercount; $i++) {
7171
            $users[] = $generator->create_user();
7172
        }
7173
 
7174
        foreach ($enrolmentmethods as [$type, $status]) {
7175
            $record = $DB->get_record('enrol', ['courseid' => $course->id, 'enrol' => $type]);
7176
            $plugin = enrol_get_plugin($type);
7177
            if ($record->status != $status) {
7178
                $plugin->update_status($record, $status);
7179
            }
7180
        }
7181
 
7182
        foreach ($enrolled as $index => $enrolconfig) {
7183
            $user = $users[$index];
7184
            foreach ($enrolconfig as $type => [$starttime, $status]) {
7185
                $generator->enrol_user($user->id, $course->id, 'student', $type, $starttime, 0, $status);
7186
            }
7187
        }
7188
 
7189
        $userids = array_map(function($user) {
7190
            return $user->id;
7191
        }, $users);
7192
        $actual = course_get_course_dates_for_user_ids($course, $userids);
7193
 
7194
        foreach ($expected as $index => $exp) {
7195
            $userid = $userids[$index];
7196
            $act = $actual[$userid];
7197
 
7198
            $this->assertEquals($exp['start'], $act['start']);
7199
            $this->assertEquals($exp['startoffset'], $act['startoffset']);
7200
        }
7201
    }
7202
 
7203
    /**
7204
     * Test that calling course_get_course_dates_for_user_ids multiple times in the
7205
     * same request fill fetch the correct data for the user.
7206
     */
11 efrain 7207
    public function test_course_get_course_dates_for_user_ids_multiple_calls(): void {
1 efrain 7208
        $this->resetAfterTest();
7209
 
7210
        $generator = $this->getDataGenerator();
7211
        $now = time();
7212
        $coursestart = $now - 1000;
7213
        $course  = $generator->create_course(['startdate' => $coursestart]);
7214
        $course->relativedatesmode = true;
7215
        $user1 = $generator->create_user();
7216
        $user2 = $generator->create_user();
7217
        $user1start = $coursestart + 100;
7218
        $user2start = $coursestart + 200;
7219
 
7220
        $generator->enrol_user($user1->id, $course->id, 'student', 'manual', $user1start);
7221
        $generator->enrol_user($user2->id, $course->id, 'student', 'manual', $user2start);
7222
 
7223
        $result = course_get_course_dates_for_user_ids($course, [$user1->id]);
7224
        $this->assertEquals($user1start, $result[$user1->id]['start']);
7225
 
7226
        $result = course_get_course_dates_for_user_ids($course, [$user1->id, $user2->id]);
7227
        $this->assertEquals($user1start, $result[$user1->id]['start']);
7228
        $this->assertEquals($user2start, $result[$user2->id]['start']);
7229
 
7230
        $result = course_get_course_dates_for_user_ids($course, [$user2->id]);
7231
        $this->assertEquals($user2start, $result[$user2->id]['start']);
7232
    }
7233
 
7234
    /**
7235
     * Data provider for test_course_modules_pending_deletion.
7236
     *
7237
     * @return array An array of arrays contain test data
7238
     */
1441 ariadna 7239
    public static function provider_course_modules_pending_deletion(): array {
1 efrain 7240
        return [
7241
            'Non-gradable activity, check all'              => [['forum'], 0, false, true],
7242
            'Gradable activity, check all'                  => [['assign'], 0, false, true],
7243
            'Non-gradable activity, check gradables'        => [['forum'], 0, true, false],
7244
            'Gradable activity, check gradables'            => [['assign'], 0, true, true],
7245
            'Non-gradable within multiple, check all'       => [['quiz', 'forum', 'assign'], 1, false, true],
7246
            'Non-gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 1, true, false],
7247
            'Gradable within multiple, check all'           => [['quiz', 'forum', 'assign'], 2, false, true],
7248
            'Gradable within multiple, check gradables'     => [['quiz', 'forum', 'assign'], 2, true, true],
7249
        ];
7250
    }
7251
 
7252
    /**
7253
     * Tests the function course_modules_pending_deletion.
7254
     *
7255
     * @param string[] $modules A complete list aff all available modules before deletion
7256
     * @param int $indextodelete The index of the module in the $modules array that we want to test with
7257
     * @param bool $gradable The value to pass to the gradable argument of the course_modules_pending_deletion function
7258
     * @param bool $expected The expected result
7259
     * @dataProvider provider_course_modules_pending_deletion
7260
     */
11 efrain 7261
    public function test_course_modules_pending_deletion(array $modules, int $indextodelete, bool $gradable, bool $expected): void {
1 efrain 7262
        $this->resetAfterTest();
7263
 
7264
        // Ensure recyclebin is enabled.
7265
        set_config('coursebinenable', true, 'tool_recyclebin');
7266
 
7267
        // Create course and modules.
7268
        $generator = $this->getDataGenerator();
7269
        $course = $generator->create_course();
7270
 
7271
        $moduleinstances = [];
7272
        foreach ($modules as $module) {
7273
            $moduleinstances[] = $generator->create_module($module, array('course' => $course->id));
7274
        }
7275
 
7276
        course_delete_module($moduleinstances[$indextodelete]->cmid, true); // Try to delete the instance asynchronously.
7277
        $this->assertEquals($expected, course_modules_pending_deletion($course->id, $gradable));
7278
    }
7279
 
7280
    /**
7281
     * Tests for the course_request::can_request
7282
     */
11 efrain 7283
    public function test_can_request_course(): void {
1 efrain 7284
        global $CFG, $DB;
7285
        $this->resetAfterTest();
7286
 
7287
        $user = $this->getDataGenerator()->create_user();
7288
        $cat1 = $CFG->defaultrequestcategory;
7289
        $cat2 = $this->getDataGenerator()->create_category()->id;
7290
        $cat3 = $this->getDataGenerator()->create_category()->id;
7291
        $context1 = context_coursecat::instance($cat1);
7292
        $context2 = context_coursecat::instance($cat2);
7293
        $context3 = context_coursecat::instance($cat3);
7294
        $this->setUser($user);
7295
 
7296
        // By default users don't have capability to request courses.
7297
        $this->assertFalse(course_request::can_request(context_system::instance()));
7298
        $this->assertFalse(course_request::can_request($context1));
7299
        $this->assertFalse(course_request::can_request($context2));
7300
        $this->assertFalse(course_request::can_request($context3));
7301
 
7302
        // Allow for the 'user' role the capability to request courses.
7303
        $userroleid = $DB->get_field('role', 'id', ['shortname' => 'user']);
7304
        assign_capability('moodle/course:request', CAP_ALLOW, $userroleid,
7305
            context_system::instance()->id);
7306
        accesslib_clear_all_caches_for_unit_testing();
7307
 
7308
        // Lock category selection.
7309
        $CFG->lockrequestcategory = 1;
7310
 
7311
        // Now user can only request course in the default category or in system context.
7312
        $this->assertTrue(course_request::can_request(context_system::instance()));
7313
        $this->assertTrue(course_request::can_request($context1));
7314
        $this->assertFalse(course_request::can_request($context2));
7315
        $this->assertFalse(course_request::can_request($context3));
7316
 
7317
        // Enable category selection. User can request course anywhere.
7318
        $CFG->lockrequestcategory = 0;
7319
        $this->assertTrue(course_request::can_request(context_system::instance()));
7320
        $this->assertTrue(course_request::can_request($context1));
7321
        $this->assertTrue(course_request::can_request($context2));
7322
        $this->assertTrue(course_request::can_request($context3));
7323
 
7324
        // Remove cap from cat2.
7325
        $roleid = create_role('Test role', 'testrole', 'Test role description');
7326
        assign_capability('moodle/course:request', CAP_PROHIBIT, $roleid,
7327
            $context2->id, true);
7328
        role_assign($roleid, $user->id, $context2->id);
7329
        accesslib_clear_all_caches_for_unit_testing();
7330
 
7331
        $this->assertTrue(course_request::can_request(context_system::instance()));
7332
        $this->assertTrue(course_request::can_request($context1));
7333
        $this->assertFalse(course_request::can_request($context2));
7334
        $this->assertTrue(course_request::can_request($context3));
7335
 
7336
        // Disable course request functionality.
7337
        $CFG->enablecourserequests = false;
7338
        $this->assertFalse(course_request::can_request(context_system::instance()));
7339
        $this->assertFalse(course_request::can_request($context1));
7340
        $this->assertFalse(course_request::can_request($context2));
7341
        $this->assertFalse(course_request::can_request($context3));
7342
    }
7343
 
7344
    /**
7345
     * Tests for the course_request::can_approve
7346
     */
11 efrain 7347
    public function test_can_approve_course_request(): void {
1 efrain 7348
        global $CFG;
7349
        $this->resetAfterTest();
7350
 
7351
        $requestor = $this->getDataGenerator()->create_user();
7352
        $user = $this->getDataGenerator()->create_user();
7353
        $cat1 = $CFG->defaultrequestcategory;
7354
        $cat2 = $this->getDataGenerator()->create_category()->id;
7355
        $cat3 = $this->getDataGenerator()->create_category()->id;
7356
 
7357
        // Enable course requests. Default 'user' role has capability to request courses.
7358
        $CFG->enablecourserequests = true;
7359
        $CFG->lockrequestcategory = 0;
7360
        $this->setUser($requestor);
7361
        $requestdata = ['summary_editor' => ['text' => '', 'format' => 0], 'name' => 'Req', 'reason' => 'test'];
7362
        $request1 = course_request::create((object)($requestdata));
7363
        $request2 = course_request::create((object)($requestdata + ['category' => $cat2]));
7364
        $request3 = course_request::create((object)($requestdata + ['category' => $cat3]));
7365
 
7366
        $this->setUser($user);
7367
        // Add capability to approve courses.
7368
        $roleid = create_role('Test role', 'testrole', 'Test role description');
7369
        assign_capability('moodle/site:approvecourse', CAP_ALLOW, $roleid,
7370
            context_system::instance()->id, true);
7371
        role_assign($roleid, $user->id, context_coursecat::instance($cat2)->id);
7372
        accesslib_clear_all_caches_for_unit_testing();
7373
 
7374
        $this->assertFalse($request1->can_approve());
7375
        $this->assertTrue($request2->can_approve());
7376
        $this->assertFalse($request3->can_approve());
7377
 
7378
        // Delete category where course was requested. Now only site-wide manager can approve it.
7379
        core_course_category::get($cat2, MUST_EXIST, true)->delete_full(false);
7380
        $this->assertFalse($request2->can_approve());
7381
 
7382
        $this->setAdminUser();
7383
        $this->assertTrue($request2->can_approve());
7384
    }
7385
 
7386
    /**
7387
     * Test the course allowed module method.
7388
     */
11 efrain 7389
    public function test_course_allowed_module(): void {
1 efrain 7390
        $this->resetAfterTest();
7391
        global $DB;
7392
 
7393
        $course = $this->getDataGenerator()->create_course();
7394
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
7395
        $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
7396
 
7397
        $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
7398
        assign_capability('mod/assign:addinstance', CAP_PROHIBIT, $teacherrole->id, \context_course::instance($course->id));
7399
 
7400
        // Global user (teacher) has no permissions in this course.
7401
        $this->setUser($teacher);
7402
        $this->assertFalse(course_allowed_module($course, 'assign'));
7403
 
7404
        // Manager has permissions.
7405
        $this->assertTrue(course_allowed_module($course, 'assign', $manager));
7406
    }
7407
 
7408
    /**
7409
     * Test the {@link average_number_of_participants()} function.
7410
     */
11 efrain 7411
    public function test_average_number_of_participants(): void {
1 efrain 7412
        global $DB;
7413
        $this->resetAfterTest(true);
7414
 
7415
        $generator = $this->getDataGenerator();
7416
        $now = time();
7417
 
7418
        // If there are no courses, expect zero number of participants per course.
7419
        $this->assertEquals(0, average_number_of_participants());
7420
 
7421
        $c1 = $generator->create_course();
7422
        $c2 = $generator->create_course();
7423
 
7424
        // If there are no users, expect zero number of participants per course.
7425
        $this->assertEquals(0, average_number_of_participants());
7426
 
7427
        $t1 = $generator->create_user(['lastlogin' => $now]);
7428
        $s1 = $generator->create_user(['lastlogin' => $now]);
7429
        $s2 = $generator->create_user(['lastlogin' => $now - WEEKSECS]);
7430
        $s3 = $generator->create_user(['lastlogin' => $now - WEEKSECS]);
7431
        $s4 = $generator->create_user(['lastlogin' => $now - YEARSECS]);
7432
 
7433
        // We have courses, we have users, but no enrolments yet.
7434
        $this->assertEquals(0, average_number_of_participants());
7435
 
7436
        // Front page enrolments are ignored.
7437
        $generator->enrol_user($t1->id, SITEID, 'teacher');
7438
        $this->assertEquals(0, average_number_of_participants());
7439
 
7440
        // The teacher enrolled into one of the two courses.
7441
        $generator->enrol_user($t1->id, $c1->id, 'editingteacher');
7442
        $this->assertEquals(0.5, average_number_of_participants());
7443
 
7444
        // The teacher enrolled into both courses.
7445
        $generator->enrol_user($t1->id, $c2->id, 'editingteacher');
7446
        $this->assertEquals(1, average_number_of_participants());
7447
 
7448
        // Student 1 enrolled in the Course 1 only.
7449
        $generator->enrol_user($s1->id, $c1->id, 'student');
7450
        $this->assertEquals(1.5, average_number_of_participants());
7451
 
7452
        // Student 2 enrolled in both courses, but the enrolment in the Course 2 not active yet (enrolment starts in the future).
7453
        $generator->enrol_user($s2->id, $c1->id, 'student');
7454
        $generator->enrol_user($s2->id, $c2->id, 'student', 'manual', $now + WEEKSECS);
7455
        $this->assertEquals(2.5, average_number_of_participants());
7456
        $this->assertEquals(2, average_number_of_participants(true));
7457
 
7458
        // Student 3 enrolled in the Course 1, but the enrolment already expired.
7459
        $generator->enrol_user($s3->id, $c1->id, 'student', 'manual', 0, $now - YEARSECS);
7460
        $this->assertEquals(3, average_number_of_participants());
7461
        $this->assertEquals(2, average_number_of_participants(true));
7462
 
7463
        // Student 4 enrolled in both courses, but the enrolment has been suspended.
7464
        $generator->enrol_user($s4->id, $c1->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
7465
        $generator->enrol_user($s4->id, $c2->id, 'student', 'manual', $now - DAYSECS, $now + YEARSECS, ENROL_USER_SUSPENDED);
7466
        $this->assertEquals(4, average_number_of_participants());
7467
        $this->assertEquals(2, average_number_of_participants(true));
7468
 
7469
        // Consider only t1 and s1 who logged in recently.
7470
        $this->assertEquals(1.5, average_number_of_participants(false, $now - DAYSECS));
7471
 
7472
        // Consider only t1, s1, s2 and s3 who logged in in recent weeks.
7473
        $this->assertEquals(3, average_number_of_participants(false, $now - 4 * WEEKSECS));
7474
 
7475
        // Hidden courses are excluded from stats.
7476
        $DB->set_field('course', 'visible', 0, ['id' => $c1->id]);
7477
        $this->assertEquals(3, average_number_of_participants());
7478
        $this->assertEquals(1, average_number_of_participants(true));
7479
    }
7480
 
7481
    /**
7482
     * Test the set_downloadcontent() function.
7483
     */
11 efrain 7484
    public function test_set_downloadcontent(): void {
1 efrain 7485
        $this->resetAfterTest();
7486
 
7487
        $generator = $this->getDataGenerator();
7488
        $course = $generator->create_course();
7489
        $page = $generator->create_module('page', ['course' => $course]);
7490
 
7491
        // Test the module 'downloadcontent' field is set to enabled.
7492
        set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_ENABLED);
7493
        $modinfo = get_fast_modinfo($course)->get_cm($page->cmid);
7494
        $this->assertEquals(DOWNLOAD_COURSE_CONTENT_ENABLED, $modinfo->downloadcontent);
7495
 
7496
        // Now let's test the 'downloadcontent' value is updated to disabled.
7497
        set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_DISABLED);
7498
        $modinfo = get_fast_modinfo($course)->get_cm($page->cmid);
7499
        $this->assertEquals(DOWNLOAD_COURSE_CONTENT_DISABLED, $modinfo->downloadcontent);
7500
 
7501
        // Nothing to update, the download course content value is the same, it should return false.
7502
        $this->assertFalse(set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_DISABLED));
7503
 
7504
        // The download course content value has changed, it should return true in this case.
7505
        $this->assertTrue(set_downloadcontent($page->cmid, DOWNLOAD_COURSE_CONTENT_ENABLED));
7506
    }
7507
 
7508
    /**
7509
     * Test for course_get_courseimage.
7510
     *
7511
     * @covers ::course_get_courseimage
7512
     */
7513
    public function test_course_get_courseimage(): void {
7514
        global $CFG;
7515
 
7516
        $this->resetAfterTest();
7517
        $generator = $this->getDataGenerator();
7518
        $course = $generator->create_course();
7519
 
7520
        $this->assertNull(course_get_courseimage($course));
7521
 
7522
        $fs = get_file_storage();
7523
        $file = $fs->create_file_from_pathname((object) [
7524
            'contextid' => \core\context\course::instance($course->id)->id,
7525
            'component' => 'course',
7526
            'filearea' => 'overviewfiles',
7527
            'itemid' => 0,
7528
            'filepath' => '/',
7529
            'filename' => 'logo.png',
7530
        ], "{$CFG->dirroot}/lib/tests/fixtures/gd-logo.png");
7531
 
7532
        $image = course_get_courseimage($course);
7533
        $this->assertInstanceOf(\stored_file::class, $image);
7534
        $this->assertEquals(
7535
            $file->get_id(),
7536
            $image->get_id(),
7537
        );
7538
    }
7539
 
7540
    /**
7541
     * Test the course_get_communication_instance_data() function.
7542
     *
7543
     * @covers ::course_get_communication_instance_data
7544
     */
7545
    public function test_course_get_communication_instance_data(): void {
7546
        $this->resetAfterTest();
7547
        $course = $this->getDataGenerator()->create_course();
7548
 
7549
        // Set admin user as a valid enrolment will be checked in the callback function.
7550
        $this->setAdminUser();
7551
 
7552
        // Use the callback function and return the data.
7553
        list($instance, $context, $heading, $returnurl) = component_callback(
7554
            'core_course',
7555
            'get_communication_instance_data',
7556
            [$course->id]
7557
        );
7558
 
7559
        // Check the url is as expected.
7560
        $expectedreturnurl = new moodle_url('/course/view.php', ['id' => $course->id]);
7561
        $this->assertEquals($expectedreturnurl, $returnurl);
7562
 
7563
        // Check the context is as expected.
7564
        $expectedcontext = context_course::instance($course->id);
7565
        $this->assertEquals($expectedcontext, $context);
7566
 
7567
        // Check the instance id is as expected.
7568
        $this->assertEquals($course->id, $instance->id);
7569
 
7570
        // Check the heading is as expected.
7571
        $this->assertEquals($course->fullname, $heading);
7572
    }
7573
 
7574
    /**
7575
     * Test course_section_view() function
7576
     *
7577
     * @covers ::course_section_view
7578
     */
7579
    public function test_course_section_view(): void {
7580
 
7581
        $this->resetAfterTest();
7582
 
7583
        // Course without sections.
7584
        $course = $this->getDataGenerator()->create_course(['numsections' => 5], ['createsections' => true]);
7585
        $coursecontext = context_course::instance($course->id);
7586
        $format = course_get_format($course->id);
7587
        $sections = $format->get_sections();
7588
        $section = reset($sections);
7589
 
7590
        // Redirect events to the sink, so we can recover them later.
7591
        $sink = $this->redirectEvents();
7592
 
7593
        course_section_view($coursecontext, $section->id);
7594
 
7595
        $events = $sink->get_events();
7596
        $event = reset($events);
7597
 
7598
        // Check the event details are correct.
7599
        $this->assertInstanceOf('\core\event\section_viewed', $event);
7600
        $this->assertEquals(context_course::instance($course->id), $event->get_context());
7601
        $this->assertEquals('course_sections', $event->objecttable);
7602
        $this->assertEquals($section->id, $event->objectid);
7603
    }
1441 ariadna 7604
 
7605
    /**
7606
     * Tests get_sorted_course_formats returns plugins in cases where plugins are
7607
     * installed previously but no longer exist, or not installed yet.
7608
     *
7609
     * @covers ::get_sorted_course_formats()
7610
     */
7611
    public function test_get_sorted_course_formats_installed_or_not(): void {
7612
        global $DB;
7613
 
7614
        $this->resetAfterTest();
7615
 
7616
        // If there is an extra format installed that no longer exists, include in list (at end).
7617
        $DB->insert_record('config_plugins', [
7618
            'plugin' => 'format_frogs',
7619
            'name' => 'version',
7620
            'value' => '20240916',
7621
        ]);
7622
        \core\plugin_manager::reset_caches();
7623
        $formats = get_sorted_course_formats();
7624
        $this->assertContains('frogs', $formats);
7625
 
7626
        // If one of the formats is not installed yet, we still return it.
7627
        $DB->delete_records('config_plugins', ['plugin' => 'format_weeks']);
7628
        \core\plugin_manager::reset_caches();
7629
        $formats = get_sorted_course_formats();
7630
        $this->assertContains('weeks', $formats);
7631
    }
1 efrain 7632
}