Proyectos de Subversion Moodle

Rev

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