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
/**
18
 * Unit tests for (some of) mod/assign/locallib.php.
19
 *
20
 * @package    mod_assign
21
 * @category   phpunit
22
 * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
23
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
namespace mod_assign;
26
 
27
use mod_assign_grade_form;
28
use mod_assign_test_generator;
29
use mod_assign_testable_assign;
30
 
31
defined('MOODLE_INTERNAL') || die();
32
 
33
global $CFG;
34
require_once($CFG->dirroot . '/mod/assign/locallib.php');
35
require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
36
 
37
/**
38
 * Unit tests for (some of) mod/assign/locallib.php.
39
 *
40
 * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
41
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42
 */
43
class locallib_test extends \advanced_testcase {
44
 
45
    // Use the generator helper.
46
    use mod_assign_test_generator;
47
 
48
    /** @var array */
49
    public $extrastudents;
50
 
51
    /** @var array */
52
    public $extrasuspendedstudents;
53
 
11 efrain 54
    public function test_return_links(): void {
1 efrain 55
        global $PAGE;
56
 
57
        $this->resetAfterTest();
58
        $course = $this->getDataGenerator()->create_course();
59
 
60
        $assign = $this->create_instance($course);
61
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
62
 
63
        $assign->register_return_link('RETURNACTION', ['param' => 1]);
64
        $this->assertEquals('RETURNACTION', $assign->get_return_action());
65
        $this->assertEquals(['param' => 1], $assign->get_return_params());
66
    }
67
 
11 efrain 68
    public function test_get_feedback_plugins(): void {
1 efrain 69
        $this->resetAfterTest();
70
        $course = $this->getDataGenerator()->create_course();
71
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
72
 
73
        $this->setUser($teacher);
74
        $assign = $this->create_instance($course);
75
        $installedplugins = array_keys(\core_component::get_plugin_list('assignfeedback'));
76
 
77
        foreach ($assign->get_feedback_plugins() as $plugin) {
78
            $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins');
79
        }
80
    }
81
 
11 efrain 82
    public function test_get_submission_plugins(): void {
1 efrain 83
        $this->resetAfterTest();
84
        $course = $this->getDataGenerator()->create_course();
85
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
86
 
87
        $this->setUser($teacher);
88
        $assign = $this->create_instance($course);
89
        $installedplugins = array_keys(\core_component::get_plugin_list('assignsubmission'));
90
 
91
        foreach ($assign->get_submission_plugins() as $plugin) {
92
            $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins');
93
        }
94
    }
95
 
11 efrain 96
    public function test_is_blind_marking(): void {
1 efrain 97
        $this->resetAfterTest();
98
        $course = $this->getDataGenerator()->create_course();
99
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
100
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
101
 
102
        $this->setUser($teacher);
103
        $assign = $this->create_instance($course, ['blindmarking' => 1]);
104
        $this->assertEquals(true, $assign->is_blind_marking());
105
 
106
        // Test cannot see student names.
107
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
108
        $output = $assign->get_renderer()->render($gradingtable);
109
        $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
110
 
111
        // Test students cannot reveal identities.
112
        $nopermission = false;
113
        $student->ignoresesskey = true;
114
        $this->setUser($student);
115
        $this->expectException('required_capability_exception');
116
        $assign->reveal_identities();
117
        $student->ignoresesskey = false;
118
 
119
        // Test teachers cannot reveal identities.
120
        $nopermission = false;
121
        $teacher->ignoresesskey = true;
122
        $this->setUser($teacher);
123
        $this->expectException('required_capability_exception');
124
        $assign->reveal_identities();
125
        $teacher->ignoresesskey = false;
126
 
127
        // Test sesskey is required.
128
        $this->setUser($teacher);
129
        $this->expectException('moodle_exception');
130
        $assign->reveal_identities();
131
 
132
        // Test editingteacher can reveal identities if sesskey is ignored.
133
        $teacher->ignoresesskey = true;
134
        $this->setUser($teacher);
135
        $assign->reveal_identities();
136
        $this->assertEquals(false, $assign->is_blind_marking());
137
        $teacher->ignoresesskey = false;
138
 
139
        // Test student names are visible.
140
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
141
        $output = $assign->get_renderer()->render($gradingtable);
142
        $this->assertEquals(false, strpos($output, get_string('hiddenuser', 'assign')));
143
 
144
        // Set this back to default.
145
        $teacher->ignoresesskey = false;
146
    }
147
 
148
    /**
149
     * Data provider for test_get_assign_perpage
150
     *
151
     * @return array Provider data
152
     */
153
    public function get_assign_perpage_provider() {
154
        return array(
155
            array(
156
                'maxperpage' => -1,
157
                'userprefs' => array(
158
                    -1 => -1,
159
                    10 => 10,
160
                    20 => 20,
161
                    50 => 50,
162
                ),
163
            ),
164
            array(
165
                'maxperpage' => 15,
166
                'userprefs' => array(
167
                    -1 => 15,
168
                    10 => 10,
169
                    20 => 15,
170
                    50 => 15,
171
                ),
172
            ),
173
        );
174
    }
175
 
176
    /**
177
     * Test maxperpage
178
     *
179
     * @dataProvider get_assign_perpage_provider
180
     * @param integer $maxperpage site config value
181
     * @param array $userprefs Array of user preferences and expected page sizes
182
     */
11 efrain 183
    public function test_get_assign_perpage($maxperpage, $userprefs): void {
1 efrain 184
        $this->resetAfterTest();
185
        $course = $this->getDataGenerator()->create_course();
186
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
187
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
188
 
189
        $this->setUser($teacher);
190
        $assign = $this->create_instance($course);
191
 
192
        set_config('maxperpage', $maxperpage, 'assign');
193
        set_user_preference('assign_perpage', null);
194
        $this->assertEquals(10, $assign->get_assign_perpage());
195
        foreach ($userprefs as $pref => $perpage) {
196
            set_user_preference('assign_perpage', $pref);
197
            $this->assertEquals($perpage, $assign->get_assign_perpage());
198
        }
199
    }
200
 
201
    /**
202
     * Test filter by requires grading.
203
     *
204
     * This is specifically checking an assignment with no grade to make sure we do not
205
     * get an exception thrown when rendering the grading table for this type of assignment.
206
     */
11 efrain 207
    public function test_gradingtable_filter_by_requiresgrading_no_grade(): void {
1 efrain 208
        global $PAGE;
209
 
210
        $this->resetAfterTest();
211
 
212
        $course = $this->getDataGenerator()->create_course();
213
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
214
        $this->setUser($teacher);
215
        $assign = $this->create_instance($course, [
216
                'assignsubmission_onlinetext_enabled' => 1,
217
                'assignfeedback_comments_enabled' => 0,
218
                'grade' => GRADE_TYPE_NONE
219
            ]);
220
 
221
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array(
222
            'id' => $assign->get_course_module()->id,
223
            'action' => 'grading',
224
        )));
225
 
226
        // Render the table with the requires grading filter.
227
        $gradingtable = new \assign_grading_table($assign, 1, ASSIGN_FILTER_REQUIRE_GRADING, 0, true);
228
        $output = $assign->get_renderer()->render($gradingtable);
229
 
230
        // Test that the filter function does not throw errors for assignments with no grade.
231
        $this->assertStringContainsString(get_string('nothingtodisplay'), $output);
232
    }
233
 
234
 
235
    /**
236
     * Test submissions with extension date.
237
     */
11 efrain 238
    public function test_gradingtable_extension_due_date(): void {
1 efrain 239
        global $PAGE;
240
 
241
        $this->resetAfterTest();
242
        $course = $this->getDataGenerator()->create_course();
243
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
244
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
245
 
246
        // Setup the assignment.
247
        $this->setUser($teacher);
248
        $time = time();
249
        $assign = $this->create_instance($course, [
250
                'assignsubmission_onlinetext_enabled' => 1,
251
                'duedate' => time() - (4 * DAYSECS),
252
            ]);
253
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array(
254
            'id' => $assign->get_course_module()->id,
255
            'action' => 'grading',
256
        )));
257
 
258
        // Check that the assignment is late.
259
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
260
        $output = $assign->get_renderer()->render($gradingtable);
261
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
262
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output);
263
 
264
        // Grant an extension.
265
        $extendedtime = $time + (2 * DAYSECS);
266
        $assign->testable_save_user_extension($student->id, $extendedtime);
267
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
268
        $output = $assign->get_renderer()->render($gradingtable);
269
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
270
        $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
271
 
272
        // Simulate a submission.
273
        $this->setUser($student);
274
        $submission = $assign->get_user_submission($student->id, true);
275
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
276
        $assign->testable_update_submission($submission, $student->id, true, false);
277
        $data = new \stdClass();
278
        $data->onlinetext_editor = [
279
            'itemid' => file_get_unused_draft_itemid(),
280
            'text' => 'Submission text',
281
            'format' => FORMAT_MOODLE,
282
        ];
283
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
284
        $plugin->save($submission, $data);
285
 
286
        // Verify output.
287
        $this->setUser($teacher);
288
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
289
        $output = $assign->get_renderer()->render($gradingtable);
290
        $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
291
        $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
292
    }
293
 
294
    /**
295
     * Test that late submissions with extension date calculate correctly.
296
     */
11 efrain 297
    public function test_gradingtable_extension_date_calculation_for_lateness(): void {
1 efrain 298
        global $PAGE;
299
 
300
        $this->resetAfterTest();
301
        $course = $this->getDataGenerator()->create_course();
302
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
303
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
304
 
305
        // Setup the assignment.
306
        $this->setUser($teacher);
307
        $time = time();
308
        $assign = $this->create_instance($course, [
309
                'assignsubmission_onlinetext_enabled' => 1,
310
                'duedate' => time() - (4 * DAYSECS),
311
            ]);
312
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array(
313
            'id' => $assign->get_course_module()->id,
314
            'action' => 'grading',
315
        )));
316
 
317
        // Check that the assignment is late.
318
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
319
        $output = $assign->get_renderer()->render($gradingtable);
320
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
321
        $difftime = time() - $time;
322
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
323
 
324
        // Grant an extension that is in the past.
325
        $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS));
326
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
327
        $output = $assign->get_renderer()->render($gradingtable);
328
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
329
        $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
330
        $difftime = time() - $time;
331
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
332
 
333
        // Simulate a submission.
334
        $this->setUser($student);
335
        $submission = $assign->get_user_submission($student->id, true);
336
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
337
        $assign->testable_update_submission($submission, $student->id, true, false);
338
        $data = new \stdClass();
339
        $data->onlinetext_editor = [
340
            'itemid' => file_get_unused_draft_itemid(),
341
            'text' => 'Submission text',
342
            'format' => FORMAT_MOODLE,
343
        ];
344
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
345
        $plugin->save($submission, $data);
346
        $submittedtime = time();
347
 
348
        // Verify output.
349
        $this->setUser($teacher);
350
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
351
        $output = $assign->get_renderer()->render($gradingtable);
352
        $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
353
        $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
354
 
355
        $difftime = $submittedtime - $time;
356
        $this->assertStringContainsString(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)),
357
            $output);
358
    }
359
 
11 efrain 360
    public function test_gradingtable_status_rendering(): void {
1 efrain 361
        global $PAGE;
362
 
363
        $this->resetAfterTest();
364
        $course = $this->getDataGenerator()->create_course();
365
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
366
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
367
 
368
        // Setup the assignment.
369
        $this->setUser($teacher);
370
        $time = time();
371
        $assign = $this->create_instance($course, [
372
            'assignsubmission_onlinetext_enabled' => 1,
373
            'duedate' => $time - (4 * DAYSECS),
374
         ]);
375
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array(
376
            'id' => $assign->get_course_module()->id,
377
            'action' => 'grading',
378
        )));
379
 
380
        // Check that the assignment is late.
381
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
382
        $output = $assign->get_renderer()->render($gradingtable);
383
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
384
        $difftime = time() - $time;
385
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
386
 
387
        // Simulate a student viewing the assignment without submitting.
388
        $this->setUser($student);
389
        $submission = $assign->get_user_submission($student->id, true);
390
        $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
391
        $assign->testable_update_submission($submission, $student->id, true, false);
392
        $submittedtime = time();
393
 
394
        // Verify output.
395
        $this->setUser($teacher);
396
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
397
        $output = $assign->get_renderer()->render($gradingtable);
398
        $difftime = $submittedtime - $time;
399
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
400
 
401
        $document = new \DOMDocument();
402
        @$document->loadHTML($output);
403
        $xpath = new \DOMXPath($document);
404
        $this->assertEmpty($xpath->evaluate('string(//td[@id="mod_assign_grading-' . $assign->get_context()->id. '_r0_c8"])'));
405
    }
406
 
407
    /**
408
     * Check that group submission information is rendered correctly in the
409
     * grading table.
410
     */
11 efrain 411
    public function test_gradingtable_group_submissions_rendering(): void {
1 efrain 412
        global $PAGE;
413
 
414
        $this->resetAfterTest();
415
        $course = $this->getDataGenerator()->create_course();
416
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
417
 
418
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
419
        groups_add_member($group, $teacher);
420
 
421
        $students = [];
422
 
423
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
424
        $students[] = $student;
425
        groups_add_member($group, $student);
426
 
427
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
428
        $students[] = $student;
429
        groups_add_member($group, $student);
430
 
431
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
432
        $students[] = $student;
433
        groups_add_member($group, $student);
434
 
435
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
436
        $students[] = $student;
437
        groups_add_member($group, $student);
438
 
439
        // Verify group assignments.
440
        $this->setUser($teacher);
441
        $assign = $this->create_instance($course, [
442
            'teamsubmission' => 1,
443
            'assignsubmission_onlinetext_enabled' => 1,
444
            'submissiondrafts' => 1,
445
            'requireallteammemberssubmit' => 0,
446
        ]);
447
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', array(
448
            'id' => $assign->get_course_module()->id,
449
            'action' => 'grading',
450
        )));
451
 
452
        // Add a submission.
453
        $this->setUser($student);
454
        $data = new \stdClass();
455
        $data->onlinetext_editor = [
456
            'itemid' => file_get_unused_draft_itemid(),
457
            'text' => 'Submission text',
458
            'format' => FORMAT_MOODLE,
459
        ];
460
        $notices = array();
461
        $assign->save_submission($data, $notices);
462
 
463
        $submission = $assign->get_group_submission($student->id, 0, true);
464
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
465
        $assign->testable_update_submission($submission, $student->id, true, true);
466
 
467
        // Check output.
468
        $this->setUser($teacher);
469
        $gradingtable = new \assign_grading_table($assign, 4, '', 0, true);
470
        $output = $assign->get_renderer()->render($gradingtable);
471
        $document = new \DOMDocument();
472
        @$document->loadHTML($output);
473
        $xpath = new \DOMXPath($document);
474
 
475
        // The XPath expression is based on the unique ID of the table.
476
        $xpathuniqueidroot = 'mod_assign_grading-' . $assign->get_context()->id;
477
 
478
        // Check status.
479
        $this->assertSame(get_string('submissionstatus_submitted', 'assign'),
480
            $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c4"]/div[@class="submissionstatussubmitted"])'));
481
        $this->assertSame(get_string('submissionstatus_submitted', 'assign'),
482
            $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c4"]/div[@class="submissionstatussubmitted"])'));
483
 
484
        // Check submission last modified date.
485
        $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c8"])')));
486
        $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c8"])')));
487
 
488
        // Check group.
489
        $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c5"])'));
490
        $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c5"])'));
491
 
492
        // Check submission text.
493
        $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c9"]/div/div)'));
494
        $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c9"]/div/div)'));
495
 
496
        // Check comments can be made.
497
        $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r0_c10"]//textarea)'));
498
        $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r3_c10"]//textarea)'));
499
    }
500
 
11 efrain 501
    public function test_show_intro(): void {
1 efrain 502
        $this->resetAfterTest();
503
        $course = $this->getDataGenerator()->create_course();
504
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
505
 
506
        // Test whether we are showing the intro at the correct times.
507
        $this->setUser($teacher);
508
        $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]);
509
 
510
        $this->assertEquals(true, $assign->testable_show_intro());
511
 
512
        $tomorrow = time() + DAYSECS;
513
 
514
        $assign = $this->create_instance($course, [
515
                'alwaysshowdescription' => 0,
516
                'allowsubmissionsfromdate' => $tomorrow,
517
            ]);
518
        $this->assertEquals(false, $assign->testable_show_intro());
519
        $yesterday = time() - DAYSECS;
520
        $assign = $this->create_instance($course, [
521
                'alwaysshowdescription' => 0,
522
                'allowsubmissionsfromdate' => $yesterday,
523
            ]);
524
        $this->assertEquals(true, $assign->testable_show_intro());
525
    }
526
 
11 efrain 527
    public function test_has_submissions_or_grades(): void {
1 efrain 528
        $this->resetAfterTest();
529
        $course = $this->getDataGenerator()->create_course();
530
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
531
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
532
 
533
        $this->setUser($teacher);
534
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
535
        $instance = $assign->get_instance();
536
 
537
        // Should start empty.
538
        $this->assertEquals(false, $assign->has_submissions_or_grades());
539
 
540
        // Simulate a submission.
541
        $this->setUser($student);
542
        $submission = $assign->get_user_submission($student->id, true);
543
 
544
        // The submission is still new.
545
        $this->assertEquals(false, $assign->has_submissions_or_grades());
546
 
547
        // Submit the submission.
548
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
549
        $assign->testable_update_submission($submission, $student->id, true, false);
550
        $data = new \stdClass();
551
        $data->onlinetext_editor = array(
552
            'itemid' => file_get_unused_draft_itemid(),
553
            'text' => 'Submission text',
554
            'format' => FORMAT_MOODLE);
555
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
556
        $plugin->save($submission, $data);
557
 
558
        // Now test again.
559
        $this->assertEquals(true, $assign->has_submissions_or_grades());
560
    }
561
 
11 efrain 562
    public function test_delete_grades(): void {
1 efrain 563
        $this->resetAfterTest();
564
        $course = $this->getDataGenerator()->create_course();
565
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
566
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
567
 
568
        $this->setUser($teacher);
569
        $assign = $this->create_instance($course);
570
 
571
        // Simulate adding a grade.
572
        $this->setUser($teacher);
573
        $data = new \stdClass();
574
        $data->grade = '50.0';
575
        $assign->testable_apply_grade_to_user($data, $student->id, 0);
576
 
577
        // Now see if the data is in the gradebook.
578
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
579
 
580
        $this->assertNotEquals(0, count($gradinginfo->items));
581
 
582
        $assign->testable_delete_grades();
583
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
584
 
585
        $this->assertEquals(0, count($gradinginfo->items));
586
    }
587
 
11 efrain 588
    public function test_delete_instance(): void {
1 efrain 589
        $this->resetAfterTest();
590
        $course = $this->getDataGenerator()->create_course();
591
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
592
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
593
 
594
        $this->setUser($teacher);
595
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
596
 
597
        // Simulate adding a grade.
598
        $this->setUser($teacher);
599
        $data = new \stdClass();
600
        $data->grade = '50.0';
601
        $assign->testable_apply_grade_to_user($data, $student->id, 0);
602
 
603
        // Simulate a submission.
604
        $this->add_submission($student, $assign);
605
 
606
        // Now try and delete.
607
        $this->setUser($teacher);
608
        $this->assertEquals(true, $assign->delete_instance());
609
    }
610
 
11 efrain 611
    public function test_reset_userdata(): void {
1 efrain 612
        global $DB;
613
 
614
        $this->resetAfterTest();
615
        $course = $this->getDataGenerator()->create_course();
616
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
617
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
618
 
619
        $now = time();
620
        $this->setUser($teacher);
621
        $assign = $this->create_instance($course, [
622
                'assignsubmission_onlinetext_enabled' => 1,
623
                'duedate' => $now,
624
            ]);
625
 
626
        // Simulate adding a grade.
627
        $this->add_submission($student, $assign);
628
        $this->submit_for_grading($student, $assign);
629
        $this->mark_submission($teacher, $assign, $student, 50.0);
630
 
631
        // Simulate a submission.
632
        $this->setUser($student);
633
        $submission = $assign->get_user_submission($student->id, true);
634
        $data = new \stdClass();
635
        $data->onlinetext_editor = array(
636
            'itemid' => file_get_unused_draft_itemid(),
637
            'text' => 'Submission text',
638
            'format' => FORMAT_MOODLE);
639
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
640
        $plugin->save($submission, $data);
641
 
642
        $this->assertEquals(true, $assign->has_submissions_or_grades());
643
        // Now try and reset.
644
        $data = new \stdClass();
645
        $data->reset_assign_submissions = 1;
646
        $data->reset_gradebook_grades = 1;
647
        $data->reset_assign_user_overrides = 1;
648
        $data->reset_assign_group_overrides = 1;
649
        $data->courseid = $course->id;
650
        $data->timeshift = DAYSECS;
651
        $this->setUser($teacher);
652
        $assign->reset_userdata($data);
653
        $this->assertEquals(false, $assign->has_submissions_or_grades());
654
 
655
        // Reload the instance data.
656
        $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
657
        $this->assertEquals($now + DAYSECS, $instance->duedate);
658
 
659
        // Test reset using assign_reset_userdata().
660
        $assignduedate = $instance->duedate; // Keep old updated value for comparison.
661
        $data->timeshift = (2 * DAYSECS);
662
        assign_reset_userdata($data);
663
        $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
664
        $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate);
665
 
666
        // Create one more assignment and reset, make sure time shifted for previous assignment is not changed.
667
        $assign2 = $this->create_instance($course, [
668
                'assignsubmission_onlinetext_enabled' => 1,
669
                'duedate' => $now,
670
            ]);
671
        $assignduedate = $instance->duedate;
672
        $data->timeshift = 3 * DAYSECS;
673
        $assign2->reset_userdata($data);
674
        $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
675
        $this->assertEquals($assignduedate, $instance->duedate);
676
        $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
677
        $this->assertEquals($now + 3 * DAYSECS, $instance2->duedate);
678
 
679
        // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date.
680
        $assignduedate = $instance->duedate;
681
        $assign2duedate = $instance2->duedate;
682
        $data->timeshift = (4 * DAYSECS);
683
        assign_reset_userdata($data);
684
        $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
685
        $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate);
686
        $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
687
        $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate);
688
    }
689
 
11 efrain 690
    public function test_plugin_settings(): void {
1 efrain 691
        global $DB;
692
 
693
        $this->resetAfterTest();
694
 
695
        $course = $this->getDataGenerator()->create_course();
696
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
697
 
698
        $now = time();
699
        $this->setUser($teacher);
700
        $assign = $this->create_instance($course, [
701
                'assignsubmission_file_enabled' => 1,
702
                'assignsubmission_file_maxfiles' => 12,
703
                'assignsubmission_file_maxsizebytes' => 10,
704
            ]);
705
 
706
        $plugin = $assign->get_submission_plugin_by_type('file');
707
        $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
708
    }
709
 
11 efrain 710
    public function test_update_calendar(): void {
1 efrain 711
        global $DB;
712
 
713
        $this->resetAfterTest();
714
 
715
        $course = $this->getDataGenerator()->create_course();
716
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
717
 
718
        $this->setUser($teacher);
719
        $userctx = \context_user::instance($teacher->id)->id;
720
 
721
        // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey.
722
        $draftid = file_get_unused_draft_itemid();
723
        $_REQUEST['introeditor'] = $draftid;
724
        $_POST['introeditor'] = $draftid;
725
        $_POST['sesskey'] = sesskey();
726
 
727
        // Write links to a draft area.
728
        $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx,
729
            'user', 'draft', $draftid);
730
        $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx,
731
            'user', 'draft', $draftid);
732
 
733
        // Create a new \assignment with links to a draft area.
734
        $now = time();
735
        $assign = $this->create_instance($course, [
736
                'duedate' => $now,
737
                'intro' => $fakearealink1,
738
                'introformat' => FORMAT_HTML
739
            ]);
740
 
741
        // See if there is an event in the calendar.
742
        $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
743
        $event = $DB->get_record('event', $params);
744
        $this->assertNotEmpty($event);
745
        $this->assertSame('link', $event->description);     // The pluginfile links are removed.
746
 
747
        // Make sure the same works when updating the assignment.
748
        $instance = $assign->get_instance();
749
        $instance->instance = $instance->id;
750
        $instance->intro = $fakearealink2;
751
        $instance->introformat = FORMAT_HTML;
752
        $assign->update_instance($instance);
753
        $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
754
        $event = $DB->get_record('event', $params);
755
        $this->assertNotEmpty($event);
756
        $this->assertSame('new', $event->description);     // The pluginfile links are removed.
757
 
758
        // Create an assignment with a description that should be hidden.
759
        $assign = $this->create_instance($course, [
760
                'duedate' => $now + 160,
761
                'alwaysshowdescription' => false,
762
                'allowsubmissionsfromdate' => $now + 60,
763
                'intro' => 'Some text',
764
            ]);
765
 
766
        // Get the event from the calendar.
767
        $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
768
        $event = $DB->get_record('event', [
769
            'modulename' => 'assign',
770
            'instance' => $assign->get_instance()->id,
771
        ]);
772
 
773
        $this->assertEmpty($event->description);
774
 
775
        // Change the allowsubmissionfromdate to the past - do this directly in the DB
776
        // because if we call the assignment update method - it will update the calendar
777
        // and we want to test that this works from cron.
778
        $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id' => $assign->get_instance()->id));
779
        // Run cron to update the event in the calendar.
780
        \assign::cron();
781
        $event = $DB->get_record('event', $params);
782
 
783
        $this->assertStringContainsString('Some text', $event->description);
784
 
785
    }
786
 
11 efrain 787
    public function test_update_instance(): void {
1 efrain 788
        global $DB;
789
 
790
        $this->resetAfterTest();
791
 
792
        $course = $this->getDataGenerator()->create_course();
793
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
794
 
795
        $this->setUser($teacher);
796
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
797
 
798
        $now = time();
799
        $instance = $assign->get_instance();
800
        $instance->duedate = $now;
801
        $instance->instance = $instance->id;
802
        $instance->assignsubmission_onlinetext_enabled = 1;
803
 
804
        $assign->update_instance($instance);
805
 
806
        $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
807
        $this->assertEquals($now, $instance->duedate);
808
    }
809
 
11 efrain 810
    public function test_cannot_submit_empty(): void {
1 efrain 811
        global $PAGE;
812
 
813
        $this->resetAfterTest();
814
 
815
        $course = $this->getDataGenerator()->create_course();
816
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
817
 
818
        $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
819
 
820
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
821
 
822
        // Test you cannot see the submit button for an offline assignment regardless.
823
        $this->setUser($student);
824
        $output = $assign->view_student_summary($student, true);
825
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'),
826
            $output, 'Can submit empty offline assignment');
827
    }
828
 
11 efrain 829
    public function test_cannot_submit_empty_no_submission(): void {
1 efrain 830
        global $PAGE;
831
 
832
        $this->resetAfterTest();
833
 
834
        $course = $this->getDataGenerator()->create_course();
835
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
836
 
837
        $assign = $this->create_instance($course, [
838
            'submissiondrafts' => 1,
839
            'assignsubmission_onlinetext_enabled' => 1,
840
        ]);
841
 
842
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
843
 
844
        // Test you cannot see the submit button for an online text assignment with no submission.
845
        $this->setUser($student);
846
        $output = $assign->view_student_summary($student, true);
847
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'),
848
            $output, 'Cannot submit empty onlinetext assignment');
849
    }
850
 
11 efrain 851
    public function test_can_submit_with_submission(): void {
1 efrain 852
        global $PAGE;
853
 
854
        $this->resetAfterTest();
855
 
856
        $course = $this->getDataGenerator()->create_course();
857
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
858
 
859
        $assign = $this->create_instance($course, [
860
            'submissiondrafts' => 1,
861
            'assignsubmission_onlinetext_enabled' => 1,
862
        ]);
863
 
864
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
865
 
866
        // Add a draft.
867
        $this->add_submission($student, $assign);
868
 
869
        // Test you can see the submit button for an online text assignment with a submission.
870
        $this->setUser($student);
871
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
872
        $this->assertStringContainsString(get_string('submitassignment', 'assign'),
873
            $output, 'Can submit non empty onlinetext assignment');
874
    }
875
 
876
    /**
877
     * Test new_submission_empty
878
     *
879
     * We only test combinations of plugins here. Individual plugins are tested
880
     * in their respective test files.
881
     *
882
     * @dataProvider new_submission_empty_testcases
883
     * @param string $data The file submission data
884
     * @param bool $expected The expected return value
885
     */
11 efrain 886
    public function test_new_submission_empty($data, $expected): void {
1 efrain 887
        $this->resetAfterTest();
888
 
889
        $course = $this->getDataGenerator()->create_course();
890
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
891
 
892
        $assign = $this->create_instance($course, [
893
                'assignsubmission_file_enabled' => 1,
894
                'assignsubmission_file_maxfiles' => 12,
895
                'assignsubmission_file_maxsizebytes' => 10,
896
                'assignsubmission_onlinetext_enabled' => 1,
897
            ]);
898
        $this->setUser($student);
899
        $submission = new \stdClass();
900
 
901
        if ($data['file'] && isset($data['file']['filename'])) {
902
            $itemid = file_get_unused_draft_itemid();
903
            $submission->files_filemanager = $itemid;
904
            $data['file'] += ['contextid' => \context_user::instance($student->id)->id, 'itemid' => $itemid];
905
            $fs = get_file_storage();
906
            $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
907
        }
908
 
909
        if ($data['onlinetext']) {
910
            $submission->onlinetext_editor = ['text' => $data['onlinetext']];
911
        }
912
 
913
        $result = $assign->new_submission_empty($submission);
914
        $this->assertTrue($result === $expected);
915
    }
916
 
917
    /**
918
     * Dataprovider for the test_new_submission_empty testcase
919
     *
920
     * @return array of testcases
921
     */
922
    public function new_submission_empty_testcases() {
923
        return [
924
            'With file and onlinetext' => [
925
                [
926
                    'file' => [
927
                        'component' => 'user',
928
                        'filearea' => 'draft',
929
                        'filepath' => '/',
930
                        'filename' => 'not_a_virus.exe'
931
                    ],
932
                    'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
933
                ],
934
                false
935
            ]
936
        ];
937
    }
938
 
11 efrain 939
    public function test_list_participants(): void {
1 efrain 940
        global $CFG;
941
 
942
        $this->resetAfterTest();
943
 
944
        $course = $this->getDataGenerator()->create_course();
945
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
946
 
947
        // Create 10 students.
948
        for ($i = 0; $i < 10; $i++) {
949
            $this->getDataGenerator()->create_and_enrol($course, 'student');
950
        }
951
 
952
        $this->setUser($teacher);
953
        $assign = $this->create_instance($course, ['grade' => 100]);
954
 
955
        $this->assertCount(10, $assign->list_participants(null, true));
956
    }
957
 
11 efrain 958
    public function test_list_participants_activeenrol(): void {
1 efrain 959
        global $CFG, $DB;
960
 
961
        $this->resetAfterTest();
962
 
963
        $course = $this->getDataGenerator()->create_course();
964
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
965
 
966
        // Create 10 students.
967
        for ($i = 0; $i < 10; $i++) {
968
            $this->getDataGenerator()->create_and_enrol($course, 'student');
969
        }
970
 
971
        // Create 10 suspended students.
972
        for ($i = 0; $i < 10; $i++) {
973
            $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
974
        }
975
 
976
        $this->setUser($teacher);
977
        set_user_preference('grade_report_showonlyactiveenrol', false);
978
        $assign = $this->create_instance($course, ['grade' => 100]);
979
 
980
        $this->assertCount(10, $assign->list_participants(null, true));
981
    }
982
 
11 efrain 983
    public function test_list_participants_with_group_restriction(): void {
1 efrain 984
        global $CFG;
985
 
986
        $this->resetAfterTest();
987
 
988
        $course = $this->getDataGenerator()->create_course();
989
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
990
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
991
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
992
        $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
993
 
994
        // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group.
995
        $CFG->enableavailability = true;
996
 
997
        $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
998
        $assign = $this->create_instance($course, [
999
            'grade' => 100,
1000
            'availability' => json_encode(
1001
                \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
1002
            ),
1003
        ]);
1004
 
1005
        groups_add_member($specialgroup, $student);
1006
        groups_add_member($specialgroup, $otherstudent);
1007
        $this->assertEquals(2, count($assign->list_participants(null, true)));
1008
    }
1009
 
11 efrain 1010
    public function test_get_participant_user_not_exist(): void {
1 efrain 1011
        $this->resetAfterTest();
1012
        $course = $this->getDataGenerator()->create_course();
1013
 
1014
        $assign = $this->create_instance($course);
1015
        $this->assertNull($assign->get_participant('-1'));
1016
    }
1017
 
11 efrain 1018
    public function test_get_participant_not_enrolled(): void {
1 efrain 1019
        $this->resetAfterTest();
1020
        $course = $this->getDataGenerator()->create_course();
1021
        $assign = $this->create_instance($course);
1022
 
1023
        $user = $this->getDataGenerator()->create_user();
1024
        $this->assertNull($assign->get_participant($user->id));
1025
    }
1026
 
11 efrain 1027
    public function test_get_participant_no_submission(): void {
1 efrain 1028
        $this->resetAfterTest();
1029
        $course = $this->getDataGenerator()->create_course();
1030
        $assign = $this->create_instance($course);
1031
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1032
 
1033
        $participant = $assign->get_participant($student->id);
1034
 
1035
        $this->assertEquals($student->id, $participant->id);
1036
        $this->assertFalse($participant->submitted);
1037
        $this->assertFalse($participant->requiregrading);
1038
        $this->assertFalse($participant->grantedextension);
1039
    }
1040
 
11 efrain 1041
    public function test_get_participant_granted_extension(): void {
1 efrain 1042
        $this->resetAfterTest();
1043
        $course = $this->getDataGenerator()->create_course();
1044
        $assign = $this->create_instance($course);
1045
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1046
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1047
 
1048
        $this->setUser($teacher);
1049
        $assign->save_user_extension($student->id, time());
1050
        $participant = $assign->get_participant($student->id);
1051
 
1052
        $this->assertEquals($student->id, $participant->id);
1053
        $this->assertFalse($participant->submitted);
1054
        $this->assertFalse($participant->requiregrading);
1055
        $this->assertTrue($participant->grantedextension);
1056
    }
1057
 
11 efrain 1058
    public function test_get_participant_with_ungraded_submission(): void {
1 efrain 1059
        $this->resetAfterTest();
1060
        $course = $this->getDataGenerator()->create_course();
1061
        $assign = $this->create_instance($course);
1062
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1063
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1064
 
1065
        // Simulate a submission.
1066
        $this->add_submission($student, $assign);
1067
        $this->submit_for_grading($student, $assign);
1068
 
1069
        $participant = $assign->get_participant($student->id);
1070
 
1071
        $this->assertEquals($student->id, $participant->id);
1072
        $this->assertTrue($participant->submitted);
1073
        $this->assertTrue($participant->requiregrading);
1074
        $this->assertFalse($participant->grantedextension);
1075
    }
1076
 
1077
    /**
1078
     * Tests that if a student with no submission who can no longer submit is not a participant.
1079
     */
11 efrain 1080
    public function test_get_participant_with_no_submission_no_capability(): void {
1 efrain 1081
        global $DB;
1082
        $this->resetAfterTest();
1083
        $course = self::getDataGenerator()->create_course();
1084
        $coursecontext = \context_course::instance($course->id);
1085
        $assign = $this->create_instance($course);
1086
        $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher');
1087
        $student = self::getDataGenerator()->create_and_enrol($course, 'student');
1088
 
1089
        // Remove the students capability to submit.
1090
        $role = $DB->get_field('role', 'id', ['shortname' => 'student']);
1091
        assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext);
1092
 
1093
        $participant = $assign->get_participant($student->id);
1094
 
1095
        self::assertNull($participant);
1096
    }
1097
 
1098
    /**
1099
     * Tests that if a student that has submitted but can no longer submit is a participant.
1100
     */
11 efrain 1101
    public function test_get_participant_with_submission_no_capability(): void {
1 efrain 1102
        global $DB;
1103
        $this->resetAfterTest();
1104
        $course = self::getDataGenerator()->create_course();
1105
        $coursecontext = \context_course::instance($course->id);
1106
        $assign = $this->create_instance($course);
1107
        $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher');
1108
        $student = self::getDataGenerator()->create_and_enrol($course, 'student');
1109
 
1110
        // Simulate a submission.
1111
        $this->add_submission($student, $assign);
1112
        $this->submit_for_grading($student, $assign);
1113
 
1114
        // Remove the students capability to submit.
1115
        $role = $DB->get_field('role', 'id', ['shortname' => 'student']);
1116
        assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext);
1117
 
1118
        $participant = $assign->get_participant($student->id);
1119
 
1120
        self::assertNotNull($participant);
1121
        self::assertEquals($student->id, $participant->id);
1122
        self::assertTrue($participant->submitted);
1123
        self::assertTrue($participant->requiregrading);
1124
        self::assertFalse($participant->grantedextension);
1125
    }
1126
 
11 efrain 1127
    public function test_get_participant_with_graded_submission(): void {
1 efrain 1128
        $this->resetAfterTest();
1129
        $course = $this->getDataGenerator()->create_course();
1130
        $assign = $this->create_instance($course);
1131
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1132
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1133
 
1134
        // Simulate a submission.
1135
        $this->add_submission($student, $assign);
1136
        $this->submit_for_grading($student, $assign);
1137
 
1138
        $this->mark_submission($teacher, $assign, $student, 50.0);
1139
 
1140
        $data = new \stdClass();
1141
        $data->grade = '50.0';
1142
        $assign->testable_apply_grade_to_user($data, $student->id, 0);
1143
 
1144
        $participant = $assign->get_participant($student->id);
1145
 
1146
        $this->assertEquals($student->id, $participant->id);
1147
        $this->assertTrue($participant->submitted);
1148
        $this->assertFalse($participant->requiregrading);
1149
        $this->assertFalse($participant->grantedextension);
1150
    }
1151
 
1152
    /**
1153
     * No active group and non-group submissions disallowed => 2 groups.
1154
     */
11 efrain 1155
    public function test_count_teams_no_active_non_group_allowed(): void {
1 efrain 1156
        $this->resetAfterTest();
1157
 
1158
        $course = $this->getDataGenerator()->create_course();
1159
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1160
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1161
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1162
 
1163
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1164
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1165
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1166
        groups_add_member($group1, $student1);
1167
        groups_add_member($group2, $student2);
1168
 
1169
        $this->setUser($teacher);
1170
        $assign = $this->create_instance($course, ['teamsubmission' => 1]);
1171
 
1172
        $this->assertEquals(2, $assign->count_teams());
1173
    }
1174
 
1175
    /**
1176
     * No active group and non group submissions allowed => 2 groups + the default one.
1177
     */
11 efrain 1178
    public function test_count_teams_non_group_allowed(): void {
1 efrain 1179
        $this->resetAfterTest();
1180
 
1181
        $course = $this->getDataGenerator()->create_course();
1182
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1183
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1184
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1185
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1186
 
1187
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1188
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1189
 
1190
        $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1191
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1192
        $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1193
 
1194
        groups_add_member($group1, $student1);
1195
        groups_add_member($group2, $student2);
1196
 
1197
        $assign = $this->create_instance($course, [
1198
            'teamsubmission' => 1,
1199
            'teamsubmissiongroupingid' => $grouping->id,
1200
            'preventsubmissionnotingroup' => false,
1201
        ]);
1202
 
1203
        $this->setUser($teacher);
1204
        $this->assertEquals(3, $assign->count_teams());
1205
 
1206
        // Active group only.
1207
        $this->assertEquals(1, $assign->count_teams($group1->id));
1208
        $this->assertEquals(1, $assign->count_teams($group2->id));
1209
    }
1210
 
1211
    /**
1212
     * Active group => just selected one.
1213
     */
11 efrain 1214
    public function test_count_teams_no_active_group(): void {
1 efrain 1215
        $this->resetAfterTest();
1216
 
1217
        $course = $this->getDataGenerator()->create_course();
1218
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1219
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1220
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1221
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1222
 
1223
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1224
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1225
 
1226
        $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1227
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1228
        $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1229
 
1230
        groups_add_member($group1, $student1);
1231
        groups_add_member($group2, $student2);
1232
 
1233
        $assign = $this->create_instance($course, [
1234
            'teamsubmission' => 1,
1235
            'preventsubmissionnotingroup' => true,
1236
        ]);
1237
 
1238
        $this->setUser($teacher);
1239
        $this->assertEquals(2, $assign->count_teams());
1240
 
1241
        // Active group only.
1242
        $this->assertEquals(1, $assign->count_teams($group1->id));
1243
        $this->assertEquals(1, $assign->count_teams($group2->id));
1244
    }
1245
 
1246
    /**
1247
     * Active group => just selected one.
1248
     */
11 efrain 1249
    public function test_count_teams_groups_only(): void {
1 efrain 1250
        $this->resetAfterTest();
1251
 
1252
        $course = $this->getDataGenerator()->create_course();
1253
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
1254
 
1255
        $assign = $this->create_instance($course, [
1256
            'teamsubmission' => 1,
1257
            'teamsubmissiongroupingid' => $grouping->id,
1258
            'preventsubmissionnotingroup' => false,
1259
        ]);
1260
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1261
 
1262
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1263
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1264
        groups_add_member($group1, $student1);
1265
 
1266
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
1267
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1268
        groups_add_member($group2, $student2);
1269
 
1270
        $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
1271
        $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
1272
 
1273
        $this->setUser($teacher);
1274
 
1275
        $assign = $this->create_instance($course, [
1276
            'teamsubmission' => 1,
1277
            'preventsubmissionnotingroup' => true,
1278
        ]);
1279
        $this->assertEquals(2, $assign->count_teams());
1280
    }
1281
 
11 efrain 1282
    public function test_submit_to_default_group(): void {
1 efrain 1283
        global $DB, $SESSION;
1284
 
1285
        $this->resetAfterTest();
1286
 
1287
        $course = $this->getDataGenerator()->create_course();
1288
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1289
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1290
 
1291
        $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
1292
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1293
 
1294
        $assign = $this->create_instance($course, [
1295
            'teamsubmission' => 1,
1296
            'assignsubmission_onlinetext_enabled' => 1,
1297
            'submissiondrafts' => 0,
1298
            'groupmode' => VISIBLEGROUPS,
1299
        ]);
1300
 
1301
        $usergroup = $assign->get_submission_group($student->id);
1302
        $this->assertFalse($usergroup, 'New student is in default group');
1303
 
1304
        // Add a submission.
1305
        $this->add_submission($student, $assign);
1306
        $this->submit_for_grading($student, $assign);
1307
 
1308
        // Set active groups to all groups.
1309
        $this->setUser($teacher);
1310
        $SESSION->activegroup[$course->id]['aag'][0] = 0;
1311
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1312
 
1313
        // Set an active group.
1314
        $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
1315
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1316
    }
1317
 
11 efrain 1318
    public function test_count_submissions_no_draft(): void {
1 efrain 1319
        $this->resetAfterTest();
1320
 
1321
        $course = $this->getDataGenerator()->create_course();
1322
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1323
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1324
 
1325
        $assign = $this->create_instance($course, [
1326
            'assignsubmission_onlinetext_enabled' => 1,
1327
        ]);
1328
 
1329
        $assign->get_user_submission($student->id, true);
1330
 
1331
        // Note: Drafts count as a submission.
1332
        $this->assertEquals(0, $assign->count_grades());
1333
        $this->assertEquals(0, $assign->count_submissions());
1334
        $this->assertEquals(1, $assign->count_submissions(true));
1335
        $this->assertEquals(0, $assign->count_submissions_need_grading());
1336
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1337
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1338
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1339
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1340
    }
1341
 
11 efrain 1342
    public function test_count_submissions_draft(): void {
1 efrain 1343
        $this->resetAfterTest();
1344
 
1345
        $course = $this->getDataGenerator()->create_course();
1346
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1347
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1348
 
1349
        $assign = $this->create_instance($course, [
1350
            'assignsubmission_onlinetext_enabled' => 1,
1351
        ]);
1352
 
1353
        $this->add_submission($student, $assign);
1354
 
1355
        // Note: Drafts count as a submission.
1356
        $this->assertEquals(0, $assign->count_grades());
1357
        $this->assertEquals(1, $assign->count_submissions());
1358
        $this->assertEquals(1, $assign->count_submissions(true));
1359
        $this->assertEquals(0, $assign->count_submissions_need_grading());
1360
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1361
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1362
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1363
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1364
    }
1365
 
11 efrain 1366
    public function test_count_submissions_submitted(): void {
1 efrain 1367
        global $SESSION;
1368
 
1369
        $this->resetAfterTest();
1370
 
1371
        $course = $this->getDataGenerator()->create_course();
1372
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1373
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1374
 
1375
        $assign = $this->create_instance($course, [
1376
            'assignsubmission_onlinetext_enabled' => 1,
1377
        ]);
1378
 
1379
        $this->add_submission($student, $assign);
1380
        $this->submit_for_grading($student, $assign);
1381
 
1382
        $this->assertEquals(0, $assign->count_grades());
1383
        $this->assertEquals(1, $assign->count_submissions());
1384
        $this->assertEquals(1, $assign->count_submissions(true));
1385
        $this->assertEquals(1, $assign->count_submissions_need_grading());
1386
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1387
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1388
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1389
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1390
    }
1391
 
11 efrain 1392
    public function test_count_submissions_graded(): void {
1 efrain 1393
        $this->resetAfterTest();
1394
 
1395
        $course = $this->getDataGenerator()->create_course();
1396
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1397
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1398
 
1399
        $assign = $this->create_instance($course, [
1400
            'assignsubmission_onlinetext_enabled' => 1,
1401
        ]);
1402
 
1403
        $this->add_submission($student, $assign);
1404
        $this->submit_for_grading($student, $assign);
1405
        $this->mark_submission($teacher, $assign, $student, 50.0);
1406
 
1407
        // Although it has been graded, it is still marked as submitted.
1408
        $this->assertEquals(1, $assign->count_grades());
1409
        $this->assertEquals(1, $assign->count_submissions());
1410
        $this->assertEquals(1, $assign->count_submissions(true));
1411
        $this->assertEquals(0, $assign->count_submissions_need_grading());
1412
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
1413
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1414
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1415
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
1416
    }
1417
 
11 efrain 1418
    public function test_count_submissions_graded_group(): void {
1 efrain 1419
        global $SESSION;
1420
 
1421
        $this->resetAfterTest();
1422
 
1423
        $course = $this->getDataGenerator()->create_course();
1424
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1425
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1426
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1427
        $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1428
        groups_add_member($group, $student);
1429
 
1430
        $assign = $this->create_instance($course, [
1431
            'assignsubmission_onlinetext_enabled' => 1,
1432
            'groupmode' => VISIBLEGROUPS,
1433
        ]);
1434
 
1435
        $this->add_submission($student, $assign);
1436
        $this->submit_for_grading($student, $assign);
1437
 
1438
        // The user should still be listed when fetching all groups.
1439
        $this->setUser($teacher);
1440
        $SESSION->activegroup[$course->id]['aag'][0] = 0;
1441
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1442
 
1443
        // The user should still be listed when fetching just their group.
1444
        $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
1445
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1446
 
1447
        // The user should still be listed when fetching just their group.
1448
        $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
1449
        $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1450
    }
1451
 
1452
    // TODO
1453
    public function x_test_count_submissions_for_team() {
1454
        $this->resetAfterTest();
1455
 
1456
        $course = $this->getDataGenerator()->create_course();
1457
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1458
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1459
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1460
        $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1461
        groups_add_member($group, $student);
1462
 
1463
        $assign = $this->create_instance($course, [
1464
            'assignsubmission_onlinetext_enabled' => 1,
1465
            'teamsubmission' => 1,
1466
        ]);
1467
 
1468
        // Add a graded submission.
1469
        $this->add_submission($student, $assign);
1470
 
1471
        // Simulate adding a grade.
1472
        $this->setUser($teacher);
1473
        $data = new \stdClass();
1474
        $data->grade = '50.0';
1475
        $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
1476
 
1477
        // Simulate a submission.
1478
        $this->setUser($this->extrastudents[1]);
1479
        $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
1480
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1481
        $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
1482
        $data = new \stdClass();
1483
        $data->onlinetext_editor = array(
1484
            'itemid' => file_get_unused_draft_itemid(),
1485
            'text' => 'Submission text',
1486
            'format' => FORMAT_MOODLE);
1487
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1488
        $plugin->save($submission, $data);
1489
 
1490
        // Simulate a submission.
1491
        $this->setUser($this->extrastudents[2]);
1492
        $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
1493
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1494
        $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
1495
        $data = new \stdClass();
1496
        $data->onlinetext_editor = array(
1497
            'itemid' => file_get_unused_draft_itemid(),
1498
            'text' => 'Submission text',
1499
            'format' => FORMAT_MOODLE);
1500
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1501
        $plugin->save($submission, $data);
1502
 
1503
        // Simulate a submission.
1504
        $this->setUser($this->extrastudents[3]);
1505
        $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
1506
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1507
        $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
1508
        $data = new \stdClass();
1509
        $data->onlinetext_editor = array(
1510
            'itemid' => file_get_unused_draft_itemid(),
1511
            'text' => 'Submission text',
1512
            'format' => FORMAT_MOODLE);
1513
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
1514
        $plugin->save($submission, $data);
1515
 
1516
        // Simulate adding a grade.
1517
        $this->setUser($teacher);
1518
        $data = new \stdClass();
1519
        $data->grade = '50.0';
1520
        $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
1521
        $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
1522
 
1523
        // Create a new submission with status NEW.
1524
        $this->setUser($this->extrastudents[4]);
1525
        $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
1526
 
1527
        $this->assertEquals(2, $assign->count_grades());
1528
        $this->assertEquals(4, $assign->count_submissions());
1529
        $this->assertEquals(5, $assign->count_submissions(true));
1530
        $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
1531
        $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
1532
    }
1533
 
11 efrain 1534
    public function test_get_grading_userid_list_only_active(): void {
1 efrain 1535
        $this->resetAfterTest();
1536
 
1537
        $course = $this->getDataGenerator()->create_course();
1538
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1539
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1540
        $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1541
            $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1542
 
1543
        $this->setUser($teacher);
1544
 
1545
        $assign = $this->create_instance($course);
1546
        $this->assertCount(1, $assign->testable_get_grading_userid_list());
1547
    }
1548
 
11 efrain 1549
    public function test_get_grading_userid_list_all(): void {
1 efrain 1550
        $this->resetAfterTest();
1551
 
1552
        $course = $this->getDataGenerator()->create_course();
1553
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1554
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1555
        $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1556
            $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1557
 
1558
        $this->setUser($teacher);
1559
        set_user_preference('grade_report_showonlyactiveenrol', false);
1560
 
1561
        $assign = $this->create_instance($course);
1562
        $this->assertCount(2, $assign->testable_get_grading_userid_list());
1563
    }
1564
 
11 efrain 1565
    public function test_cron(): void {
1 efrain 1566
        global $PAGE;
1567
        $this->resetAfterTest();
1568
 
1569
        // First run cron so there are no messages waiting to be sent (from other tests).
1570
        \core\cron::setup_user();
1571
        \assign::cron();
1572
 
1573
        $course = $this->getDataGenerator()->create_course();
1574
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1575
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1576
 
1577
        // Now create an assignment and add some feedback.
1578
        $this->setUser($teacher);
1579
        $assign = $this->create_instance($course, [
1580
            'sendstudentnotifications' => 1,
1581
        ]);
1582
 
1583
        $this->add_submission($student, $assign);
1584
        $this->submit_for_grading($student, $assign);
1585
        $this->mark_submission($teacher, $assign, $student, 50.0);
1586
 
1587
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1588
        \core\cron::setup_user();
1589
        $sink = $this->redirectMessages();
1590
        \assign::cron();
1591
        $messages = $sink->get_messages();
1592
 
1593
        $this->assertEquals(1, count($messages));
1594
        $this->assertEquals(1, $messages[0]->notification);
1595
        $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1596
        // Test customdata.
1597
        $customdata = json_decode($messages[0]->customdata);
1598
        $this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
1599
        $this->assertEquals($assign->get_instance()->id, $customdata->instance);
1600
        $this->assertEquals('feedbackavailable', $customdata->messagetype);
1601
        $userpicture = new \user_picture($teacher);
1602
        $userpicture->size = 1; // Use f1 size.
1603
        $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
1604
        $this->assertEquals(0, $customdata->uniqueidforuser);   // Not used in this case.
1605
        $this->assertFalse($customdata->blindmarking);
1606
    }
1607
 
11 efrain 1608
    public function test_cron_without_notifications(): void {
1 efrain 1609
        $this->resetAfterTest();
1610
 
1611
        // First run cron so there are no messages waiting to be sent (from other tests).
1612
        \core\cron::setup_user();
1613
        \assign::cron();
1614
 
1615
        $course = $this->getDataGenerator()->create_course();
1616
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1617
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1618
 
1619
        // Now create an assignment and add some feedback.
1620
        $this->setUser($teacher);
1621
        $assign = $this->create_instance($course, [
1622
            'sendstudentnotifications' => 1,
1623
        ]);
1624
 
1625
        $this->add_submission($student, $assign);
1626
        $this->submit_for_grading($student, $assign);
1627
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1628
            'sendstudentnotifications' => 0,
1629
        ]);
1630
 
1631
        \core\cron::setup_user();
1632
        $sink = $this->redirectMessages();
1633
        \assign::cron();
1634
        $messages = $sink->get_messages();
1635
 
1636
        $this->assertEquals(0, count($messages));
1637
    }
1638
 
11 efrain 1639
    public function test_cron_regraded(): void {
1 efrain 1640
        $this->resetAfterTest();
1641
 
1642
        // First run cron so there are no messages waiting to be sent (from other tests).
1643
        \core\cron::setup_user();
1644
        \assign::cron();
1645
 
1646
        $course = $this->getDataGenerator()->create_course();
1647
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1648
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1649
 
1650
        // Now create an assignment and add some feedback.
1651
        $this->setUser($teacher);
1652
        $assign = $this->create_instance($course, [
1653
            'sendstudentnotifications' => 1,
1654
        ]);
1655
 
1656
        $this->add_submission($student, $assign);
1657
        $this->submit_for_grading($student, $assign);
1658
        $this->mark_submission($teacher, $assign, $student, 50.0);
1659
 
1660
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1661
        \core\cron::setup_user();
1662
        \assign::cron();
1663
 
1664
        // Regrade.
1665
        $this->mark_submission($teacher, $assign, $student, 50.0);
1666
 
1667
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1668
        \core\cron::setup_user();
1669
        $sink = $this->redirectMessages();
1670
        \assign::cron();
1671
        $messages = $sink->get_messages();
1672
 
1673
        $this->assertEquals(1, count($messages));
1674
        $this->assertEquals(1, $messages[0]->notification);
1675
        $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1676
    }
1677
 
1678
    /**
1679
     * Test delivery of grade notifications as controlled by marking workflow.
1680
     */
11 efrain 1681
    public function test_markingworkflow_cron(): void {
1 efrain 1682
        $this->resetAfterTest();
1683
 
1684
        // First run cron so there are no messages waiting to be sent (from other tests).
1685
        \core\cron::setup_user();
1686
        \assign::cron();
1687
 
1688
        $course = $this->getDataGenerator()->create_course();
1689
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1690
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1691
 
1692
        // Now create an assignment and add some feedback.
1693
        $this->setUser($teacher);
1694
        $assign = $this->create_instance($course, [
1695
            'sendstudentnotifications' => 1,
1696
            'markingworkflow' => 1,
1697
        ]);
1698
 
1699
        // Mark a submission but set the workflowstate to an unreleased state.
1700
        // This should not trigger a notification.
1701
        $this->add_submission($student, $assign);
1702
        $this->submit_for_grading($student, $assign);
1703
        $this->mark_submission($teacher, $assign, $student, 50.0, [
1704
            'sendstudentnotifications' => 1,
1705
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
1706
        ]);
1707
 
1708
        \core\cron::setup_user();
1709
        $sink = $this->redirectMessages();
1710
        \assign::cron();
1711
        $messages = $sink->get_messages();
1712
 
1713
        $this->assertEquals(0, count($messages));
1714
 
1715
        // Transition to the released state.
1716
        $this->setUser($teacher);
1717
        $submission = $assign->get_user_submission($student->id, true);
1718
        $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
1719
        $assign->testable_apply_grade_to_user($submission, $student->id, 0);
1720
 
1721
        // Now run cron and see that one message was sent.
1722
        \core\cron::setup_user();
1723
        $sink = $this->redirectMessages();
1724
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1725
        \assign::cron();
1726
        $messages = $sink->get_messages();
1727
 
1728
        $this->assertEquals(1, count($messages));
1729
        $this->assertEquals(1, $messages[0]->notification);
1730
        $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
1731
    }
1732
 
11 efrain 1733
    public function test_cron_message_includes_courseid(): void {
1 efrain 1734
        $this->resetAfterTest();
1735
 
1736
        // First run cron so there are no messages waiting to be sent (from other tests).
1737
        \core\cron::setup_user();
1738
        \assign::cron();
1739
 
1740
        $course = $this->getDataGenerator()->create_course();
1741
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1742
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1743
 
1744
        // Now create an assignment and add some feedback.
1745
        $this->setUser($teacher);
1746
        $assign = $this->create_instance($course, [
1747
            'sendstudentnotifications' => 1,
1748
        ]);
1749
 
1750
        // Mark a submission but set the workflowstate to an unreleased state.
1751
        // This should not trigger a notification.
1752
        $this->add_submission($student, $assign);
1753
        $this->submit_for_grading($student, $assign);
1754
        $this->mark_submission($teacher, $assign, $student);
1755
        \phpunit_util::stop_message_redirection();
1756
 
1757
        // Now run cron and see that one message was sent.
1758
        \core\cron::setup_user();
1759
        $this->preventResetByRollback();
1760
        $sink = $this->redirectEvents();
1761
        $this->expectOutputRegex('/Done processing 1 assignment submissions/');
1762
        \assign::cron();
1763
 
1764
        $events = $sink->get_events();
1765
        $event = reset($events);
1766
        $this->assertInstanceOf('\core\event\notification_sent', $event);
1767
        $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
1768
        $sink->close();
1769
    }
1770
 
11 efrain 1771
    public function test_is_graded(): void {
1 efrain 1772
        $this->resetAfterTest();
1773
 
1774
        $course = $this->getDataGenerator()->create_course();
1775
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1776
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1777
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1778
 
1779
        $assign = $this->create_instance($course);
1780
 
1781
        $this->add_submission($student, $assign);
1782
        $this->submit_for_grading($student, $assign);
1783
        $this->mark_submission($teacher, $assign, $student, 50.0);
1784
 
1785
        $this->setUser($teacher);
1786
        $this->assertEquals(true, $assign->testable_is_graded($student->id));
1787
        $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
1788
    }
1789
 
11 efrain 1790
    public function test_can_grade(): void {
1 efrain 1791
        global $DB;
1792
 
1793
        $this->resetAfterTest();
1794
 
1795
        $course = $this->getDataGenerator()->create_course();
1796
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1797
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1798
 
1799
        $assign = $this->create_instance($course);
1800
 
1801
        $this->setUser($student);
1802
        $this->assertEquals(false, $assign->can_grade());
1803
 
1804
        $this->setUser($teacher);
1805
        $this->assertEquals(true, $assign->can_grade());
1806
 
1807
        // Test the viewgrades capability for other users.
1808
        $this->setUser();
1809
        $this->assertTrue($assign->can_grade($teacher->id));
1810
        $this->assertFalse($assign->can_grade($student->id));
1811
 
1812
        // Test the viewgrades capability - without mod/assign:grade.
1813
        $this->setUser($student);
1814
 
1815
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1816
        assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1817
        $this->assertEquals(false, $assign->can_grade());
1818
    }
1819
 
11 efrain 1820
    public function test_can_view_submission(): void {
1 efrain 1821
        global $DB;
1822
 
1823
        $this->resetAfterTest();
1824
 
1825
        $course = $this->getDataGenerator()->create_course();
1826
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1827
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
1828
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1829
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1830
        $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
1831
            $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1832
 
1833
        $assign = $this->create_instance($course);
1834
 
1835
        $this->setUser($student);
1836
        $this->assertEquals(true, $assign->can_view_submission($student->id));
1837
        $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
1838
        $this->assertEquals(false, $assign->can_view_submission($teacher->id));
1839
 
1840
        $this->setUser($teacher);
1841
        $this->assertEquals(true, $assign->can_view_submission($student->id));
1842
        $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1843
        $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1844
        $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1845
 
1846
        $this->setUser($editingteacher);
1847
        $this->assertEquals(true, $assign->can_view_submission($student->id));
1848
        $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1849
        $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1850
        $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
1851
 
1852
        // Test the viewgrades capability - without mod/assign:grade.
1853
        $this->setUser($student);
1854
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
1855
        assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
1856
        $this->assertEquals(true, $assign->can_view_submission($student->id));
1857
        $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
1858
        $this->assertEquals(true, $assign->can_view_submission($teacher->id));
1859
        $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
1860
    }
1861
 
11 efrain 1862
    public function test_update_submission(): void {
1 efrain 1863
        $this->resetAfterTest();
1864
 
1865
        $course = $this->getDataGenerator()->create_course();
1866
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1867
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1868
 
1869
        $assign = $this->create_instance($course);
1870
 
1871
        $this->add_submission($student, $assign);
1872
        $submission = $assign->get_user_submission($student->id, 0);
1873
        $assign->testable_update_submission($submission, $student->id, true, true);
1874
 
1875
        $this->setUser($teacher);
1876
 
1877
        // Verify the gradebook update.
1878
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1879
 
1880
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1881
        $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1882
    }
1883
 
11 efrain 1884
    public function test_update_submission_team(): void {
1 efrain 1885
        $this->resetAfterTest();
1886
 
1887
        $course = $this->getDataGenerator()->create_course();
1888
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1889
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1890
 
1891
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1892
        groups_add_member($group, $student);
1893
 
1894
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1895
        groups_add_member($group, $otherstudent);
1896
 
1897
        $assign = $this->create_instance($course, [
1898
            'teamsubmission' => 1,
1899
        ]);
1900
 
1901
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1902
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1903
        $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1904
 
1905
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1906
        $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1907
        $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1908
 
1909
        $this->add_submission($student, $assign);
1910
        $submission = $assign->get_group_submission($student->id, 0, true);
1911
        $assign->testable_update_submission($submission, $student->id, true, true);
1912
 
1913
        // Verify the gradebook update for the student.
1914
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1915
 
1916
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1917
        $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1918
 
1919
        // Verify the gradebook update for the other student.
1920
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
1921
 
1922
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
1923
        $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
1924
    }
1925
 
11 efrain 1926
    public function test_update_submission_suspended(): void {
1 efrain 1927
        $this->resetAfterTest();
1928
 
1929
        $course = $this->getDataGenerator()->create_course();
1930
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1931
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
1932
 
1933
        $assign = $this->create_instance($course);
1934
 
1935
        $this->add_submission($student, $assign);
1936
        $submission = $assign->get_user_submission($student->id, 0);
1937
        $assign->testable_update_submission($submission, $student->id, true, false);
1938
 
1939
        $this->setUser($teacher);
1940
 
1941
        // Verify the gradebook update.
1942
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1943
 
1944
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1945
        $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
1946
    }
1947
 
11 efrain 1948
    public function test_update_submission_blind(): void {
1 efrain 1949
        $this->resetAfterTest();
1950
 
1951
        $course = $this->getDataGenerator()->create_course();
1952
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1953
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1954
 
1955
        $assign = $this->create_instance($course, [
1956
            'blindmarking' => 1,
1957
        ]);
1958
 
1959
        $this->add_submission($student, $assign);
1960
        $submission = $assign->get_user_submission($student->id, 0);
1961
        $assign->testable_update_submission($submission, $student->id, true, false);
1962
 
1963
        // Verify the gradebook update.
1964
        $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
1965
 
1966
        // The usermodified is not set because this is blind marked.
1967
        $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
1968
        $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
1969
    }
1970
 
11 efrain 1971
    public function test_group_submissions_submit_for_marking_requireallteammemberssubmit(): void {
1 efrain 1972
        global $PAGE;
1973
 
1974
        $this->resetAfterTest();
1975
 
1976
        $course = $this->getDataGenerator()->create_course();
1977
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
1978
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
1979
 
1980
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
1981
        groups_add_member($group, $student);
1982
 
1983
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
1984
        groups_add_member($group, $otherstudent);
1985
 
1986
        $assign = $this->create_instance($course, [
1987
            'teamsubmission' => 1,
1988
            'assignsubmission_onlinetext_enabled' => 1,
1989
            'submissiondrafts' => 1,
1990
            'requireallteammemberssubmit' => 1,
1991
        ]);
1992
 
1993
        // Now verify group assignments.
1994
        $this->setUser($teacher);
1995
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
1996
 
1997
        // Add a submission.
1998
        $this->add_submission($student, $assign);
1999
 
2000
        // Check we can see the submit button.
2001
        $this->setUser($student);
2002
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2003
        $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
2004
 
2005
        $submission = $assign->get_group_submission($student->id, 0, true);
2006
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2007
        $assign->testable_update_submission($submission, $student->id, true, true);
2008
 
2009
        // Check that the student does not see "Submit" button.
2010
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2011
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2012
 
2013
        // Change to another user in the same group.
2014
        $this->setUser($otherstudent);
2015
        $output = $assign->view_submission_action_bar($assign->get_instance(), $otherstudent);
2016
        $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
2017
 
2018
        $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2019
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2020
        $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2021
        $output = $assign->view_submission_action_bar($assign->get_instance(), $otherstudent);
2022
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2023
    }
2024
 
11 efrain 2025
    public function test_group_submissions_submit_for_marking(): void {
1 efrain 2026
        global $PAGE;
2027
 
2028
        $this->resetAfterTest();
2029
 
2030
        $course = $this->getDataGenerator()->create_course();
2031
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2032
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2033
 
2034
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2035
        groups_add_member($group, $student);
2036
 
2037
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2038
        groups_add_member($group, $otherstudent);
2039
 
2040
        // Now verify group assignments.
2041
        $this->setUser($teacher);
2042
        $time = time();
2043
        $assign = $this->create_instance($course, [
2044
            'teamsubmission' => 1,
2045
            'assignsubmission_onlinetext_enabled' => 1,
2046
            'submissiondrafts' => 1,
2047
            'requireallteammemberssubmit' => 0,
2048
            'duedate' => $time - (2 * DAYSECS),
2049
        ]);
2050
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2051
 
2052
        // Add a submission.
2053
        $this->add_submission($student, $assign);
2054
 
2055
        // Check we can see the submit button.
2056
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2057
        $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
2058
        $output = $assign->view_student_summary($student, true);
2059
        $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2060
        $difftime = time() - $time;
2061
        $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2062
 
2063
        $submission = $assign->get_group_submission($student->id, 0, true);
2064
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2065
        $assign->testable_update_submission($submission, $student->id, true, true);
2066
 
2067
        // Check that the student does not see "Submit" button.
2068
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2069
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2070
 
2071
        // Change to another user in the same group.
2072
        $this->setUser($otherstudent);
2073
        $output = $assign->view_submission_action_bar($assign->get_instance(), $otherstudent);
2074
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2075
 
2076
        // Check that time remaining is not overdue.
2077
        $output = $assign->view_student_summary($otherstudent, true);
2078
        $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
2079
        $difftime = time() - $time;
2080
        $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
2081
 
2082
        $submission = $assign->get_group_submission($otherstudent->id, 0, true);
2083
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2084
        $assign->testable_update_submission($submission, $otherstudent->id, true, true);
2085
        $output = $assign->view_submission_action_bar($assign->get_instance(), $otherstudent);
2086
        $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
2087
    }
2088
 
11 efrain 2089
    public function test_submissions_open(): void {
1 efrain 2090
        global $DB;
2091
 
2092
        $this->resetAfterTest();
2093
 
2094
        $course = $this->getDataGenerator()->create_course();
2095
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2096
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2097
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2098
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2099
        $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
2100
            $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
2101
 
2102
        $this->setAdminUser();
2103
 
2104
        $now = time();
2105
        $tomorrow = $now + DAYSECS;
2106
        $oneweek = $now + WEEKSECS;
2107
        $yesterday = $now - DAYSECS;
2108
 
2109
        $assign = $this->create_instance($course);
2110
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2111
 
2112
        $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
2113
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2114
 
2115
        $assign = $this->create_instance($course, ['duedate' => $yesterday]);
2116
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2117
 
2118
        $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
2119
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2120
 
2121
        $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
2122
        $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2123
 
2124
        $assign->testable_save_user_extension($student->id, $tomorrow);
2125
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2126
 
2127
        $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
2128
        $this->assertEquals(true, $assign->testable_submissions_open($student->id));
2129
 
2130
        $this->setUser($student);
2131
        $submission = $assign->get_user_submission($student->id, true);
2132
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
2133
        $assign->testable_update_submission($submission, $student->id, true, false);
2134
 
2135
        $this->setUser($teacher);
2136
        $this->assertEquals(false, $assign->testable_submissions_open($student->id));
2137
    }
2138
 
11 efrain 2139
    public function test_get_graders(): void {
1 efrain 2140
        global $DB;
2141
 
2142
        $this->resetAfterTest();
2143
 
2144
        $course = $this->getDataGenerator()->create_course();
2145
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2146
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2147
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2148
 
2149
        $this->setAdminUser();
2150
 
2151
        // Create an assignment with no groups.
2152
        $assign = $this->create_instance($course);
2153
        $this->assertCount(2, $assign->testable_get_graders($student->id));
2154
    }
2155
 
11 efrain 2156
    public function test_get_graders_separate_groups(): void {
1 efrain 2157
        global $DB;
2158
 
2159
        $this->resetAfterTest();
2160
 
2161
        $course = $this->getDataGenerator()->create_course();
2162
        $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2163
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2164
        $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2165
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2166
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2167
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2168
 
2169
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2170
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2171
        groups_add_member($group1, $student);
2172
 
2173
        $this->setAdminUser();
2174
 
2175
        // Force create an assignment with SEPARATEGROUPS.
2176
        $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2177
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2178
 
2179
        $assign = $this->create_instance($course, [
2180
            'groupingid' => $grouping->id,
2181
            'groupmode' => SEPARATEGROUPS,
2182
        ]);
2183
 
2184
        $this->assertCount(4, $assign->testable_get_graders($student->id));
2185
 
2186
        // Note the second student is in a group that is not in the grouping.
2187
        // This means that we get all graders that are not in a group in the grouping.
2188
        $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
2189
    }
2190
 
11 efrain 2191
    public function test_get_notified_users(): void {
1 efrain 2192
        global $CFG, $DB;
2193
 
2194
        $this->resetAfterTest();
2195
 
2196
        $course = $this->getDataGenerator()->create_course();
2197
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2198
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2199
        $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2200
 
2201
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2202
        groups_add_member($group1, $teacher);
2203
 
2204
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2205
        groups_add_member($group1, $editingteacher);
2206
 
2207
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2208
        groups_add_member($group1, $student);
2209
 
2210
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2211
        $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2212
 
2213
        $capability = 'mod/assign:receivegradernotifications';
2214
        $coursecontext = \context_course::instance($course->id);
2215
        $role = $DB->get_record('role', array('shortname' => 'teacher'));
2216
 
2217
        $this->setUser($teacher);
2218
 
2219
        // Create an assignment with no groups.
2220
        $assign = $this->create_instance($course);
2221
 
2222
        $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
2223
 
2224
        // Change nonediting teachers role to not receive grader notifications.
2225
        assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2226
 
2227
        // Only the editing teachers will be returned.
2228
        $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2229
 
2230
        // Note the second student is in a group that is not in the grouping.
2231
        // This means that we get all graders that are not in a group in the grouping.
2232
        $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2233
    }
2234
 
11 efrain 2235
    public function test_get_notified_users_in_grouping(): void {
1 efrain 2236
        global $CFG, $DB;
2237
 
2238
        $this->resetAfterTest();
2239
 
2240
        $course = $this->getDataGenerator()->create_course();
2241
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2242
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2243
        $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
2244
 
2245
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2246
        groups_add_member($group1, $teacher);
2247
 
2248
        $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2249
        groups_add_member($group1, $editingteacher);
2250
 
2251
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2252
        groups_add_member($group1, $student);
2253
 
2254
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2255
        $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2256
 
2257
        // Force create an assignment with SEPARATEGROUPS.
2258
        $assign = $this->create_instance($course, [
2259
            'groupingid' => $grouping->id,
2260
            'groupmode' => SEPARATEGROUPS,
2261
        ]);
2262
 
2263
        // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
2264
        $this->setUser($student);
2265
        $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
2266
 
2267
        // Note the second student is in a group that is not in the grouping.
2268
        // This means that we get all graders that are not in a group in the grouping.
2269
        $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
2270
 
2271
        // Change nonediting teachers role to not receive grader notifications.
2272
        $capability = 'mod/assign:receivegradernotifications';
2273
        $coursecontext = \context_course::instance($course->id);
2274
        $role = $DB->get_record('role', ['shortname' => 'teacher']);
2275
        assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
2276
 
2277
        // Only the editing teachers will be returned.
2278
        $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
2279
 
2280
        // Note the second student is in a group that is not in the grouping.
2281
        // This means that we get all graders that are not in a group in the grouping.
2282
        // Unfortunately there are no editing teachers who are not in a group.
2283
        $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
2284
    }
2285
 
11 efrain 2286
    public function test_group_members_only(): void {
1 efrain 2287
        global $CFG;
2288
 
2289
        $this->resetAfterTest();
2290
 
2291
        $course = $this->getDataGenerator()->create_course();
2292
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
2293
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2294
        $this->getDataGenerator()->create_grouping_group([
2295
            'groupid' => $group1->id,
2296
            'groupingid' => $grouping->id,
2297
        ]);
2298
 
2299
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2300
        $this->getDataGenerator()->create_grouping_group([
2301
            'groupid' => $group2->id,
2302
            'groupingid' => $grouping->id,
2303
        ]);
2304
 
2305
        $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
2306
 
2307
        // Add users in the following groups
2308
        // - Teacher - Group 1.
2309
        // - Student - Group 1.
2310
        // - Student - Group 2.
2311
        // - Student - Unrelated Group
2312
        // - Student - No group.
2313
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2314
        groups_add_member($group1, $teacher);
2315
 
2316
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2317
        groups_add_member($group1, $student);
2318
 
2319
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2320
        groups_add_member($group2, $otherstudent);
2321
 
2322
        $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
2323
        groups_add_member($group2, $otherstudent);
2324
 
2325
        $this->getDataGenerator()->create_and_enrol($course, 'student');
2326
 
2327
        $this->setAdminUser();
2328
 
2329
        $CFG->enableavailability = true;
2330
        $assign = $this->create_instance($course, [], [
2331
            'availability' => json_encode(
2332
                \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
2333
            ),
2334
            'groupingid' => $grouping->id,
2335
        ]);
2336
 
2337
        // The two students in groups should be returned, but not the teacher in the group, or the student not in the
2338
        // group, or the student in an unrelated group.
2339
        $this->setUser($teacher);
2340
        $participants = $assign->list_participants(0, true);
2341
        $this->assertCount(2, $participants);
2342
        $this->assertTrue(isset($participants[$student->id]));
2343
        $this->assertTrue(isset($participants[$otherstudent->id]));
2344
    }
2345
 
11 efrain 2346
    public function test_get_uniqueid_for_user(): void {
1 efrain 2347
        $this->resetAfterTest();
2348
 
2349
        $course = $this->getDataGenerator()->create_course();
2350
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2351
        $students = [];
2352
        for ($i = 0; $i < 10; $i++) {
2353
            $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2354
            $students[$student->id] = $student;
2355
        }
2356
 
2357
        $this->setUser($teacher);
2358
        $assign = $this->create_instance($course);
2359
 
2360
        foreach ($students as $student) {
2361
            $uniqueid = $assign->get_uniqueid_for_user($student->id);
2362
            $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
2363
        }
2364
    }
2365
 
11 efrain 2366
    public function test_show_student_summary(): void {
1 efrain 2367
        global $CFG, $PAGE;
2368
 
2369
        $this->resetAfterTest();
2370
 
2371
        $course = $this->getDataGenerator()->create_course();
2372
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2373
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2374
        $this->setUser($teacher);
2375
        $assign = $this->create_instance($course);
2376
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2377
 
2378
        // No feedback should be available because this student has not been graded.
2379
        $this->setUser($student);
2380
        $output = $assign->view_student_summary($student, true);
2381
        $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if there is no grade');
2382
 
2383
        // Simulate adding a grade.
2384
        $this->add_submission($student, $assign);
2385
        $this->submit_for_grading($student, $assign);
2386
        $this->mark_submission($teacher, $assign, $student);
2387
 
2388
        // Now we should see the feedback.
2389
        $this->setUser($student);
2390
        $output = $assign->view_student_summary($student, true);
2391
        $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
2392
 
2393
        // Now hide the grade in gradebook.
2394
        $this->setUser($teacher);
2395
        require_once($CFG->libdir.'/gradelib.php');
2396
        $gradeitem = new \grade_item(array(
2397
            'itemtype'      => 'mod',
2398
            'itemmodule'    => 'assign',
2399
            'iteminstance'  => $assign->get_instance()->id,
2400
            'courseid'      => $course->id));
2401
 
2402
        $gradeitem->set_hidden(1, false);
2403
 
2404
        // No feedback should be available because the grade is hidden.
2405
        $this->setUser($student);
2406
        $output = $assign->view_student_summary($student, true);
2407
        $this->assertDoesNotMatchRegularExpression('/Feedback/', $output,
2408
            'Do not show feedback if the grade is hidden in the gradebook');
2409
 
2410
        // Freeze the context.
2411
        $this->setAdminUser();
2412
        $context = $assign->get_context();
2413
        $CFG->contextlocking = true;
2414
        $context->set_locked(true);
2415
 
2416
        // No feedback should be available because the grade is hidden.
2417
        $this->setUser($student);
2418
        $output = $assign->view_student_summary($student, true);
2419
        $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
2420
 
2421
        // Show the feedback again - it should still be visible even in a frozen context.
2422
        $this->setUser($teacher);
2423
        $gradeitem->set_hidden(0, false);
2424
 
2425
        $this->setUser($student);
2426
        $output = $assign->view_student_summary($student, true);
2427
        $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
2428
    }
2429
 
11 efrain 2430
    public function test_show_student_summary_with_feedback(): void {
1 efrain 2431
        global $CFG, $PAGE;
2432
 
2433
        $this->resetAfterTest();
2434
 
2435
        $course = $this->getDataGenerator()->create_course();
2436
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2437
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2438
        $this->setUser($teacher);
2439
        $assign = $this->create_instance($course, [
2440
            'assignfeedback_comments_enabled' => 1
2441
        ]);
2442
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2443
 
2444
        // No feedback should be available because this student has not been graded.
2445
        $this->setUser($student);
2446
        $output = $assign->view_student_summary($student, true);
2447
        $this->assertDoesNotMatchRegularExpression('/Feedback/', $output);
2448
 
2449
        // Simulate adding a grade.
2450
        $this->add_submission($student, $assign);
2451
        $this->submit_for_grading($student, $assign);
2452
        $this->mark_submission($teacher, $assign, $student, null, [
2453
            'assignfeedbackcomments_editor' => [
2454
                'text' => 'Tomato sauce',
2455
                'format' => FORMAT_MOODLE,
2456
            ],
2457
        ]);
2458
 
2459
        // Should have feedback but no grade.
2460
        $this->setUser($student);
2461
        $output = $assign->view_student_summary($student, true);
2462
        $this->assertMatchesRegularExpression('/Feedback/', $output);
2463
        $this->assertMatchesRegularExpression('/Tomato sauce/', $output);
2464
        $this->assertDoesNotMatchRegularExpression('/Grade/', $output, 'Do not show grade when there is no grade.');
2465
        $this->assertDoesNotMatchRegularExpression('/Graded on/', $output, 'Do not show graded date when there is no grade.');
2466
 
2467
        // Add a grade now.
2468
        $this->mark_submission($teacher, $assign, $student, 50.0, [
2469
            'assignfeedbackcomments_editor' => [
2470
                'text' => 'Bechamel sauce',
2471
                'format' => FORMAT_MOODLE,
2472
            ],
2473
        ]);
2474
 
2475
        // Should have feedback but no grade.
2476
        $this->setUser($student);
2477
        $output = $assign->view_student_summary($student, true);
2478
        $this->assertDoesNotMatchRegularExpression('/Tomato sauce/', $output);
2479
        $this->assertMatchesRegularExpression('/Bechamel sauce/', $output);
2480
        $this->assertMatchesRegularExpression('/Grade/', $output);
2481
        $this->assertMatchesRegularExpression('/Graded on/', $output);
2482
 
2483
        // Now hide the grade in gradebook.
2484
        $this->setUser($teacher);
2485
        $gradeitem = new \grade_item(array(
2486
            'itemtype'      => 'mod',
2487
            'itemmodule'    => 'assign',
2488
            'iteminstance'  => $assign->get_instance()->id,
2489
            'courseid'      => $course->id));
2490
 
2491
        $gradeitem->set_hidden(1, false);
2492
 
2493
        // No feedback should be available because the grade is hidden.
2494
        $this->setUser($student);
2495
        $output = $assign->view_student_summary($student, true);
2496
        $this->assertDoesNotMatchRegularExpression('/Feedback/', $output,
2497
            'Do not show feedback if the grade is hidden in the gradebook');
2498
    }
2499
 
2500
    /**
2501
     * Test reopen behavior when in "Manual" mode.
2502
     */
11 efrain 2503
    public function test_attempt_reopen_method_manual(): void {
1 efrain 2504
        global $PAGE;
2505
 
2506
        $this->resetAfterTest();
2507
        $course = $this->getDataGenerator()->create_course();
2508
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2509
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2510
 
2511
        $assign = $this->create_instance($course, [
2512
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
2513
            'maxattempts' => 3,
2514
            'submissiondrafts' => 1,
2515
            'assignsubmission_onlinetext_enabled' => 1,
2516
        ]);
2517
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2518
 
2519
        // Student should be able to see an add submission button.
2520
        $this->setUser($student);
2521
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2522
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2523
 
2524
        // Add a submission.
2525
        $this->add_submission($student, $assign);
2526
        $this->submit_for_grading($student, $assign);
2527
 
2528
        // Verify the student cannot make changes to the submission.
2529
        $output = $assign->view_student_summary($student, true);
2530
        $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2531
 
2532
        // Mark the submission.
2533
        $this->mark_submission($teacher, $assign, $student);
2534
 
2535
        // Check the student can see the grade.
2536
        $this->setUser($student);
2537
        $output = $assign->view_student_summary($student, true);
2538
        $this->assertNotEquals(false, strpos($output, '50.0'));
2539
 
2540
        // Allow the student another attempt.
2541
        $teacher->ignoresesskey = true;
2542
        $this->setUser($teacher);
2543
        $result = $assign->testable_process_add_attempt($student->id);
2544
        $this->assertEquals(true, $result);
2545
 
2546
        // Check that the previous attempt is now in the submission history table.
2547
        $this->setUser($student);
2548
        $output = $assign->view_student_summary($student, true);
2549
        // Need a better check.
2550
        $this->assertNotEquals(false, strpos($output, 'Submission text'), 'Contains: Submission text');
2551
 
2552
        // Check that the student now has a submission history.
2553
        $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2554
 
2555
        // Check that the student now does not have a button for Submit.
2556
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2557
        $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2558
 
2559
        // Check that the student now has a button for Add a new attempt".
2560
        $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2561
 
2562
        $this->setUser($teacher);
2563
        // Check that the grading table loads correctly and contains this user.
2564
        // This is also testing that we do not get duplicate rows in the grading table.
2565
        $gradingtable = new \assign_grading_table($assign, 100, '', 0, true);
2566
        $output = $assign->get_renderer()->render($gradingtable);
2567
        $this->assertEquals(true, strpos($output, $student->lastname));
2568
 
2569
        // Should be 1 not 2.
2570
        $this->assertEquals(1, $assign->count_submissions());
2571
        $this->assertEquals(1, $assign->count_submissions_with_status('reopened'));
2572
        $this->assertEquals(0, $assign->count_submissions_need_grading());
2573
        $this->assertEquals(1, $assign->count_grades());
2574
 
2575
        // Change max attempts to unlimited.
2576
        $formdata = clone($assign->get_instance());
2577
        $formdata->maxattempts = ASSIGN_UNLIMITED_ATTEMPTS;
2578
        $formdata->instance = $formdata->id;
2579
        $assign->update_instance($formdata);
2580
 
2581
        // Mark the submission again.
2582
        $this->mark_submission($teacher, $assign, $student, 60.0, [], 1);
2583
 
2584
        // Check the grade exists.
2585
        $this->setUser($teacher);
2586
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2587
        $this->assertEquals(60, (int) $grades[$student->id]->rawgrade);
2588
 
2589
        // Check we can reopen still.
2590
        $result = $assign->testable_process_add_attempt($student->id);
2591
        $this->assertEquals(true, $result);
2592
 
2593
        // Should no longer have a grade because there is no grade for the latest attempt.
2594
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2595
        $this->assertEmpty($grades);
2596
    }
2597
 
2598
    /**
2599
     * Test reopen behavior when in "Reopen until pass" mode.
2600
     */
11 efrain 2601
    public function test_attempt_reopen_method_untilpass(): void {
1 efrain 2602
        global $PAGE;
2603
 
2604
        $this->resetAfterTest();
2605
        $course = $this->getDataGenerator()->create_course();
2606
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2607
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2608
 
2609
        $assign = $this->create_instance($course, [
2610
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2611
            'maxattempts' => 3,
2612
            'submissiondrafts' => 1,
2613
            'assignsubmission_onlinetext_enabled' => 1,
2614
        ]);
2615
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2616
 
2617
        // Set grade to pass to 80.
2618
        $gradeitem = $assign->get_grade_item();
2619
        $gradeitem->gradepass = '80.0';
2620
        $gradeitem->update();
2621
 
2622
        // Student should be able to see an add submission button.
2623
        $this->setUser($student);
2624
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2625
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2626
 
2627
        // Add a submission.
2628
        $this->add_submission($student, $assign);
2629
        $this->submit_for_grading($student, $assign);
2630
 
2631
        // Verify the student cannot make a new attempt.
2632
        $output = $assign->view_student_summary($student, true);
2633
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2634
 
2635
        // Mark the submission as non-passing.
2636
        $this->mark_submission($teacher, $assign, $student, 50.0);
2637
 
2638
        // Check the student can see the grade.
2639
        $this->setUser($student);
2640
        $output = $assign->view_student_summary($student, true);
2641
        $this->assertNotEquals(false, strpos($output, '50.0'));
2642
 
2643
        // Check that the student now has a submission history.
2644
        $this->assertNotEquals(false, strpos($output, get_string('attempthistory', 'assign')));
2645
 
2646
        // Check that the student now does not have a button for Submit.
2647
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2648
        $this->assertEquals(false, strpos($output, get_string('submitassignment', 'assign')));
2649
 
2650
        // Check that the student now has a button for Add a new attempt.
2651
        $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2652
 
2653
        // Add a second submission.
2654
        $this->add_submission($student, $assign);
2655
        $this->submit_for_grading($student, $assign);
2656
 
2657
        // Mark the submission as passing.
2658
        $this->mark_submission($teacher, $assign, $student, 80.0);
2659
 
2660
        // Check that the student does not have a button for Add a new attempt.
2661
        $this->setUser($student);
2662
        $output = $assign->view_student_summary($student, true);
2663
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2664
 
2665
        // Re-mark the submission as not passing.
2666
        $this->mark_submission($teacher, $assign, $student, 40.0, [], 1);
2667
 
2668
        // Check that the student now has a button for Add a new attempt.
2669
        $this->setUser($student);
2670
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2671
        $this->assertMatchesRegularExpression('/' . get_string('addnewattempt', 'assign') . '/', $output);
2672
        $this->assertNotEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2673
    }
2674
 
11 efrain 2675
    public function test_attempt_reopen_method_untilpass_passing(): void {
1 efrain 2676
        global $PAGE;
2677
 
2678
        $this->resetAfterTest();
2679
        $course = $this->getDataGenerator()->create_course();
2680
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2681
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2682
 
2683
        $assign = $this->create_instance($course, [
2684
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2685
            'maxattempts' => 3,
2686
            'submissiondrafts' => 1,
2687
            'assignsubmission_onlinetext_enabled' => 1,
2688
        ]);
2689
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2690
 
2691
        // Set grade to pass to 80.
2692
        $gradeitem = $assign->get_grade_item();
2693
        $gradeitem->gradepass = '80.0';
2694
        $gradeitem->update();
2695
 
2696
        // Student should be able to see an add submission button.
2697
        $this->setUser($student);
2698
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2699
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2700
 
2701
        // Add a submission as a student.
2702
        $this->add_submission($student, $assign);
2703
        $this->submit_for_grading($student, $assign);
2704
 
2705
        // Mark the submission as passing.
2706
        $this->mark_submission($teacher, $assign, $student, 100.0);
2707
 
2708
        // Check the student can see the grade.
2709
        $this->setUser($student);
2710
        $output = $assign->view_student_summary($student, true);
2711
        $this->assertNotEquals(false, strpos($output, '100.0'));
2712
 
2713
        // Check that the student does not have a button for Add a new attempt.
2714
        $output = $assign->view_student_summary($student, true);
2715
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2716
    }
2717
 
11 efrain 2718
    public function test_attempt_reopen_method_untilpass_no_passing_requirement(): void {
1 efrain 2719
        global $PAGE;
2720
 
2721
        $this->resetAfterTest();
2722
        $course = $this->getDataGenerator()->create_course();
2723
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2724
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2725
 
2726
        $assign = $this->create_instance($course, [
2727
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS,
2728
            'maxattempts' => 3,
2729
            'submissiondrafts' => 1,
2730
            'assignsubmission_onlinetext_enabled' => 1,
2731
        ]);
2732
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2733
 
2734
        // Set grade to pass to 0, so that no attempts should reopen.
2735
        $gradeitem = $assign->get_grade_item();
2736
        $gradeitem->gradepass = '0';
2737
        $gradeitem->update();
2738
 
2739
        // Student should be able to see an add submission button.
2740
        $this->setUser($student);
2741
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
2742
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
2743
 
2744
        // Add a submission.
2745
        $this->add_submission($student, $assign);
2746
        $this->submit_for_grading($student, $assign);
2747
 
2748
        // Mark the submission with any grade.
2749
        $this->mark_submission($teacher, $assign, $student, 0.0);
2750
 
2751
        // Check the student can see the grade.
2752
        $this->setUser($student);
2753
        $output = $assign->view_student_summary($student, true);
2754
        $this->assertNotEquals(false, strpos($output, '0.0'));
2755
 
2756
        // Check that the student does not have a button for Add a new attempt.
2757
        $output = $assign->view_student_summary($student, true);
2758
        $this->assertEquals(false, strpos($output, get_string('addnewattempt', 'assign')));
2759
    }
2760
 
2761
    /**
2762
     * Test student visibility for each stage of the marking workflow.
2763
     */
11 efrain 2764
    public function test_markingworkflow(): void {
1 efrain 2765
        global $PAGE;
2766
 
2767
        $this->resetAfterTest();
2768
        $course = $this->getDataGenerator()->create_course();
2769
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2770
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2771
 
2772
        $assign = $this->create_instance($course, [
2773
            'markingworkflow' => 1,
2774
        ]);
2775
 
2776
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2777
 
2778
        // Mark the submission and set to notmarked.
2779
        $this->mark_submission($teacher, $assign, $student, 50.0,  [
2780
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED,
2781
        ]);
2782
 
2783
        // Check the student can't see the grade.
2784
        $this->setUser($student);
2785
        $output = $assign->view_student_summary($student, true);
2786
        $this->assertEquals(false, strpos($output, '50.0'));
2787
 
2788
        // Make sure the grade isn't pushed to the gradebook.
2789
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2790
        $this->assertEmpty($grades);
2791
 
2792
        // Mark the submission and set to inmarking.
2793
        $this->mark_submission($teacher, $assign, $student, 50.0,  [
2794
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INMARKING,
2795
        ]);
2796
 
2797
        // Check the student can't see the grade.
2798
        $this->setUser($student);
2799
        $output = $assign->view_student_summary($student, true);
2800
        $this->assertEquals(false, strpos($output, '50.0'));
2801
 
2802
        // Make sure the grade isn't pushed to the gradebook.
2803
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2804
        $this->assertEmpty($grades);
2805
 
2806
        // Mark the submission and set to readyforreview.
2807
        $this->mark_submission($teacher, $assign, $student, 50.0,  [
2808
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW,
2809
        ]);
2810
 
2811
        // Check the student can't see the grade.
2812
        $this->setUser($student);
2813
        $output = $assign->view_student_summary($student, true);
2814
        $this->assertEquals(false, strpos($output, '50.0'));
2815
 
2816
        // Make sure the grade isn't pushed to the gradebook.
2817
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2818
        $this->assertEmpty($grades);
2819
 
2820
        // Mark the submission and set to inreview.
2821
        $this->mark_submission($teacher, $assign, $student, 50.0,  [
2822
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW,
2823
        ]);
2824
 
2825
        // Check the student can't see the grade.
2826
        $this->setUser($student);
2827
        $output = $assign->view_student_summary($student, true);
2828
        $this->assertEquals(false, strpos($output, '50.0'));
2829
 
2830
        // Make sure the grade isn't pushed to the gradebook.
2831
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2832
        $this->assertEmpty($grades);
2833
 
2834
        // Mark the submission and set to readyforrelease.
2835
        $this->mark_submission($teacher, $assign, $student, 50.0,  [
2836
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
2837
        ]);
2838
 
2839
        // Check the student can't see the grade.
2840
        $this->setUser($student);
2841
        $output = $assign->view_student_summary($student, true);
2842
        $this->assertEquals(false, strpos($output, '50.0'));
2843
 
2844
        // Make sure the grade isn't pushed to the gradebook.
2845
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2846
        $this->assertEmpty($grades);
2847
 
2848
        // Mark the submission and set to released.
2849
        $this->mark_submission($teacher, $assign, $student, 50.0,  [
2850
            'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
2851
        ]);
2852
 
2853
        // Check the student can see the grade.
2854
        $this->setUser($student);
2855
        $output = $assign->view_student_summary($student, true);
2856
        $this->assertNotEquals(false, strpos($output, '50.0'));
2857
 
2858
        // Make sure the grade is pushed to the gradebook.
2859
        $grades = $assign->get_user_grades_for_gradebook($student->id);
2860
        $this->assertEquals(50, (int)$grades[$student->id]->rawgrade);
2861
    }
2862
 
2863
    /**
2864
     * Test that a student allocated a specific marker is only shown to that marker.
2865
     */
11 efrain 2866
    public function test_markerallocation(): void {
1 efrain 2867
        global $PAGE;
2868
 
2869
        $this->resetAfterTest();
2870
        $course = $this->getDataGenerator()->create_course();
2871
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2872
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2873
        $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
2874
 
2875
        $assign = $this->create_instance($course, [
2876
            'markingworkflow' => 1,
2877
            'markingallocation' => 1
2878
        ]);
2879
 
2880
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2881
 
2882
        // Allocate marker to submission.
2883
        $this->mark_submission($teacher, $assign, $student, null, [
2884
            'allocatedmarker' => $teacher->id,
2885
        ]);
2886
 
2887
        // Check the allocated marker can view the submission.
2888
        $this->setUser($teacher);
2889
        $users = $assign->list_participants(0, true);
2890
        $this->assertEquals(1, count($users));
2891
        $this->assertTrue(isset($users[$student->id]));
2892
 
2893
        $cm = get_coursemodule_from_instance('assign', $assign->get_instance()->id);
2894
        $context = \context_module::instance($cm->id);
2895
        $assign = new mod_assign_testable_assign($context, $cm, $course);
2896
 
2897
        // Check that other teachers can't view this submission.
2898
        $this->setUser($otherteacher);
2899
        $users = $assign->list_participants(0, true);
2900
        $this->assertEquals(0, count($users));
2901
    }
2902
 
2903
    /**
2904
     * Ensure that a teacher cannot submit for students as standard.
2905
     */
11 efrain 2906
    public function test_teacher_submit_for_student(): void {
1 efrain 2907
        global $PAGE;
2908
 
2909
        $this->resetAfterTest();
2910
        $course = $this->getDataGenerator()->create_course();
2911
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2912
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2913
 
2914
        $assign = $this->create_instance($course, [
2915
            'assignsubmission_onlinetext_enabled' => 1,
2916
            'submissiondrafts' => 1,
2917
        ]);
2918
 
2919
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2920
 
2921
        // Add a submission but do not submit.
2922
        $this->add_submission($student, $assign, 'Student submission text');
2923
 
2924
        $this->setUser($student);
2925
        $output = $assign->view_student_summary($student, true);
2926
        $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2927
 
2928
        // Check that a teacher can not edit the submission as they do not have the capability.
2929
        $this->setUser($teacher);
2930
        $this->expectException('moodle_exception');
2931
        $this->expectExceptionMessage('error/nopermission');
2932
        $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2933
    }
2934
 
2935
    /**
2936
     * Ensure that a teacher with the editothersubmission capability can submit on behalf of a student.
2937
     */
11 efrain 2938
    public function test_teacher_submit_for_student_with_capability(): void {
1 efrain 2939
        global $PAGE;
2940
 
2941
        $this->resetAfterTest();
2942
        $course = $this->getDataGenerator()->create_course();
2943
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
2944
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2945
        $otherteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
2946
 
2947
        $assign = $this->create_instance($course, [
2948
            'assignsubmission_onlinetext_enabled' => 1,
2949
            'submissiondrafts' => 1,
2950
        ]);
2951
 
2952
        // Add the required capability.
2953
        $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
2954
        assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
2955
        role_assign($roleid, $teacher->id, $assign->get_context()->id);
2956
        accesslib_clear_all_caches_for_unit_testing();
2957
 
2958
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
2959
 
2960
        // Add a submission but do not submit.
2961
        $this->add_submission($student, $assign, 'Student submission text');
2962
 
2963
        $this->setUser($student);
2964
        $output = $assign->view_student_summary($student, true);
2965
        $this->assertStringContainsString('Student submission text', $output, 'Contains student submission text');
2966
 
2967
        // Check that a teacher can edit the submission.
2968
        $this->setUser($teacher);
2969
        $this->add_submission($student, $assign, 'Teacher edited submission text', false);
2970
 
2971
        $this->setUser($student);
2972
        $output = $assign->view_student_summary($student, true);
2973
        $this->assertStringNotContainsString('Student submission text', $output, 'Contains student submission text');
2974
        $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains teacher edited submission text');
2975
 
2976
        // Check that the teacher can submit the students work.
2977
        $this->setUser($teacher);
2978
        $this->submit_for_grading($student, $assign, [], false);
2979
 
2980
        // Revert to draft so the student can edit it.
2981
        $assign->revert_to_draft($student->id);
2982
 
2983
        $this->setUser($student);
2984
 
2985
        // Check that the submission text was saved.
2986
        $output = $assign->view_student_summary($student, true);
2987
        $this->assertStringContainsString('Teacher edited submission text', $output, 'Contains student submission text');
2988
 
2989
        // Check that the student can submit their work.
2990
        $this->submit_for_grading($student, $assign, []);
2991
 
2992
        $output = $assign->view_student_summary($student, true);
2993
        $this->assertStringNotContainsString(get_string('addsubmission', 'assign'), $output);
2994
 
2995
        // An editing teacher without the extra role should still be able to revert to draft.
2996
        $this->setUser($otherteacher);
2997
 
2998
        // Revert to draft so the submission is editable.
2999
        $assign->revert_to_draft($student->id);
3000
    }
3001
 
3002
    /**
3003
     * Ensure that disabling submit after the cutoff date works as expected.
3004
     */
11 efrain 3005
    public function test_disable_submit_after_cutoff_date(): void {
1 efrain 3006
        global $PAGE;
3007
 
3008
        $this->resetAfterTest();
3009
        $course = $this->getDataGenerator()->create_course();
3010
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3011
 
3012
        $now = time();
3013
        $tomorrow = $now + DAYSECS;
3014
        $lastweek = $now - (7 * DAYSECS);
3015
        $yesterday = $now - DAYSECS;
3016
 
3017
        $this->setAdminUser();
3018
        $assign = $this->create_instance($course, [
3019
            'duedate' => $yesterday,
3020
            'cutoffdate' => $tomorrow,
3021
            'assignsubmission_onlinetext_enabled' => 1,
3022
        ]);
3023
 
3024
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
3025
 
3026
        // Student should be able to see an add submission button.
3027
        $this->setUser($student);
3028
        $output = $assign->view_submission_action_bar($assign->get_instance(), $student);
3029
        $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
3030
 
3031
        // Add a submission but don't submit now.
3032
        $this->add_submission($student, $assign);
3033
 
3034
        // Create another instance with cut-off and due-date already passed.
3035
        $this->setAdminUser();
3036
        $assign = $this->create_instance($course, [
3037
            'duedate' => $lastweek,
3038
            'cutoffdate' => $yesterday,
3039
            'assignsubmission_onlinetext_enabled' => 1,
3040
        ]);
3041
 
3042
        $this->setUser($student);
3043
        $output = $assign->view_student_summary($student, true);
3044
        $this->assertStringNotContainsString($output, get_string('editsubmission', 'assign'),
3045
            'Should not be able to edit after cutoff date.');
3046
        $this->assertStringNotContainsString($output, get_string('submitassignment', 'assign'),
3047
            'Should not be able to submit after cutoff date.');
3048
    }
3049
 
3050
    /**
3051
     * Testing for submission comment plugin settings.
3052
     *
3053
     * @dataProvider submission_plugin_settings_provider
3054
     * @param   bool    $globalenabled
3055
     * @param   array   $instanceconfig
3056
     * @param   bool    $isenabled
3057
     */
11 efrain 3058
    public function test_submission_comment_plugin_settings($globalenabled, $instanceconfig, $isenabled): void {
1 efrain 3059
        global $CFG;
3060
 
3061
        $this->resetAfterTest();
3062
        $course = $this->getDataGenerator()->create_course();
3063
 
3064
        $CFG->usecomments = $globalenabled;
3065
        $assign = $this->create_instance($course, $instanceconfig);
3066
        $plugin = $assign->get_submission_plugin_by_type('comments');
3067
        $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3068
    }
3069
 
3070
    public function submission_plugin_settings_provider() {
3071
        return [
3072
            'CFG->usecomments true, empty config => Enabled by default' => [
3073
                true,
3074
                [],
3075
                true,
3076
            ],
3077
            'CFG->usecomments true, config enabled => Comments enabled' => [
3078
                true,
3079
                [
3080
                    'assignsubmission_comments_enabled' => 1,
3081
                ],
3082
                true,
3083
            ],
3084
            'CFG->usecomments true, config idisabled => Comments enabled' => [
3085
                true,
3086
                [
3087
                    'assignsubmission_comments_enabled' => 0,
3088
                ],
3089
                true,
3090
            ],
3091
            'CFG->usecomments false, empty config => Disabled by default' => [
3092
                false,
3093
                [],
3094
                false,
3095
            ],
3096
            'CFG->usecomments false, config enabled => Comments disabled' => [
3097
                false,
3098
                [
3099
                    'assignsubmission_comments_enabled' => 1,
3100
                ],
3101
                false,
3102
            ],
3103
            'CFG->usecomments false, config disabled => Comments disabled' => [
3104
                false,
3105
                [
3106
                    'assignsubmission_comments_enabled' => 0,
3107
                ],
3108
                false,
3109
            ],
3110
        ];
3111
    }
3112
 
3113
    /**
3114
     * Testing for comment inline settings
3115
     */
11 efrain 3116
    public function test_feedback_comment_commentinline(): void {
1 efrain 3117
        global $CFG, $USER;
3118
 
3119
        $this->resetAfterTest();
3120
        $course = $this->getDataGenerator()->create_course();
3121
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3122
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3123
 
3124
        $sourcetext = "Hello!
3125
 
3126
I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3127
 
3128
URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3129
Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
3130
 
3131
External link 1:<img src='https://moodle.org/logo/logo-240x60.gif' alt='Moodle'/>
3132
External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\"/>
3133
Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
3134
Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
3135
Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
3136
Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3137
";
3138
 
3139
        $this->setUser($teacher);
3140
        $assign = $this->create_instance($course, [
3141
            'assignsubmission_onlinetext_enabled' => 1,
3142
            'assignfeedback_comments_enabled' => 1,
3143
            'assignfeedback_comments_commentinline' => 1,
3144
        ]);
3145
 
3146
        $this->setUser($student);
3147
 
3148
        // Add a submission but don't submit now.
3149
        $this->add_submission($student, $assign, $sourcetext);
3150
 
3151
        $this->setUser($teacher);
3152
 
3153
        $data = new \stdClass();
3154
        require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3155
        $pagination = [
3156
            'userid' => $student->id,
3157
            'rownum' => 0,
3158
            'last' => true,
3159
            'useridlistid' => $assign->get_useridlist_key_id(),
3160
            'attemptnumber' => 0,
3161
        ];
3162
        $formparams = array($assign, $data, $pagination);
3163
        $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
3164
 
3165
        // We need to get the URL these will be transformed to.
3166
        $context = \context_user::instance($USER->id);
3167
        $itemid = $data->assignfeedbackcomments_editor['itemid'];
3168
        $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
3169
 
3170
        // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
3171
        $filteredtext = "Hello!
3172
 
3173
I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
3174
 
3175
URL outside a tag: https://moodle.org/logo/logo-240x60.gif
3176
Plugin url outside a tag: $url/logo-240x60.gif
3177
 
3178
External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
3179
External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
3180
Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
3181
Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
3182
Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
3183
Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
3184
";
3185
 
3186
        $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
3187
    }
3188
 
3189
    /**
3190
     * Testing for feedback comment plugin settings.
3191
     *
3192
     * @dataProvider feedback_plugin_settings_provider
3193
     * @param   array   $instanceconfig
3194
     * @param   bool    $isenabled
3195
     */
11 efrain 3196
    public function test_feedback_plugin_settings($instanceconfig, $isenabled): void {
1 efrain 3197
        $this->resetAfterTest();
3198
        $course = $this->getDataGenerator()->create_course();
3199
 
3200
        $assign = $this->create_instance($course, $instanceconfig);
3201
        $plugin = $assign->get_feedback_plugin_by_type('comments');
3202
        $this->assertEquals($isenabled, (bool) $plugin->is_enabled('enabled'));
3203
    }
3204
 
3205
    public function feedback_plugin_settings_provider() {
3206
        return [
3207
            'No configuration => disabled' => [
3208
                [],
3209
                false,
3210
            ],
3211
            'Actively disabled' => [
3212
                [
3213
                    'assignfeedback_comments_enabled' => 0,
3214
                ],
3215
                false,
3216
            ],
3217
            'Actively enabled' => [
3218
                [
3219
                    'assignfeedback_comments_enabled' => 1,
3220
                ],
3221
                true,
3222
            ],
3223
        ];
3224
    }
3225
 
3226
    /**
3227
     * Testing if gradebook feedback plugin is enabled.
3228
     */
11 efrain 3229
    public function test_is_gradebook_feedback_enabled(): void {
1 efrain 3230
        $this->resetAfterTest();
3231
        $course = $this->getDataGenerator()->create_course();
3232
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3233
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3234
 
3235
        $adminconfig = get_config('assign');
3236
        $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3237
 
3238
        // Create assignment with gradebook feedback enabled and grade = 0.
3239
        $assign = $this->create_instance($course, [
3240
            "{$gradebookplugin}_enabled" => 1,
3241
            'grades' => 0,
3242
        ]);
3243
 
3244
        // Get gradebook feedback plugin.
3245
        $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3246
        $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3247
        $this->assertEquals(1, $plugin->is_enabled('enabled'));
3248
        $this->assertEquals(1, $assign->is_gradebook_feedback_enabled());
3249
    }
3250
 
3251
    /**
3252
     * Testing if gradebook feedback plugin is disabled.
3253
     */
11 efrain 3254
    public function test_is_gradebook_feedback_disabled(): void {
1 efrain 3255
        $this->resetAfterTest();
3256
        $course = $this->getDataGenerator()->create_course();
3257
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3258
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3259
 
3260
        $adminconfig = get_config('assign');
3261
        $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
3262
 
3263
        // Create assignment with gradebook feedback disabled and grade = 0.
3264
        $assign = $this->create_instance($course, [
3265
            "{$gradebookplugin}_enabled" => 0,
3266
            'grades' => 0,
3267
        ]);
3268
 
3269
        $gradebookplugintype = str_replace('assignfeedback_', '', $gradebookplugin);
3270
        $plugin = $assign->get_feedback_plugin_by_type($gradebookplugintype);
3271
        $this->assertEquals(0, $plugin->is_enabled('enabled'));
3272
    }
3273
 
3274
    /**
3275
     * Testing can_edit_submission.
3276
     */
11 efrain 3277
    public function test_can_edit_submission(): void {
1 efrain 3278
        $this->resetAfterTest();
3279
        $course = $this->getDataGenerator()->create_course();
3280
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3281
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3282
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3283
 
3284
        $assign = $this->create_instance($course, [
3285
            'assignsubmission_onlinetext_enabled' => 1,
3286
            'submissiondrafts' => 1,
3287
        ]);
3288
 
3289
        // Check student can edit their own submission.
3290
        $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3291
 
3292
        // Check student cannot edit others submission.
3293
        $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3294
 
3295
        // Check teacher cannot (by default) edit a students submission.
3296
        $this->assertFalse($assign->can_edit_submission($student->id, $teacher->id));
3297
    }
3298
 
3299
    /**
3300
     * Testing can_edit_submission with the editothersubmission capability.
3301
     */
11 efrain 3302
    public function test_can_edit_submission_with_editothersubmission(): void {
1 efrain 3303
        $this->resetAfterTest();
3304
        $course = $this->getDataGenerator()->create_course();
3305
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3306
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3307
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3308
 
3309
        $assign = $this->create_instance($course, [
3310
            'assignsubmission_onlinetext_enabled' => 1,
3311
            'submissiondrafts' => 1,
3312
        ]);
3313
 
3314
        // Add the required capability to edit a student submission.
3315
        $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3316
        assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3317
        role_assign($roleid, $teacher->id, $assign->get_context()->id);
3318
        accesslib_clear_all_caches_for_unit_testing();
3319
 
3320
        // Check student can edit their own submission.
3321
        $this->assertTrue($assign->can_edit_submission($student->id, $student->id));
3322
 
3323
        // Check student cannot edit others submission.
3324
        $this->assertFalse($assign->can_edit_submission($otherstudent->id, $student->id));
3325
 
3326
        // Retest - should now have access.
3327
        $this->assertTrue($assign->can_edit_submission($student->id, $teacher->id));
3328
    }
3329
 
3330
    /**
3331
     * Testing can_edit_submission
3332
     */
11 efrain 3333
    public function test_can_edit_submission_separategroups(): void {
1 efrain 3334
        $this->resetAfterTest();
3335
        $course = $this->getDataGenerator()->create_course();
3336
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3337
 
3338
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3339
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3340
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3341
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3342
 
3343
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3344
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3345
        groups_assign_grouping($grouping->id, $group1->id);
3346
        groups_add_member($group1, $student1);
3347
        groups_add_member($group1, $student2);
3348
 
3349
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3350
        groups_assign_grouping($grouping->id, $group2->id);
3351
        groups_add_member($group2, $student3);
3352
        groups_add_member($group2, $student4);
3353
 
3354
        $assign = $this->create_instance($course, [
3355
            'assignsubmission_onlinetext_enabled' => 1,
3356
            'submissiondrafts' => 1,
3357
            'groupingid' => $grouping->id,
3358
            'groupmode' => SEPARATEGROUPS,
3359
        ]);
3360
 
3361
        // Verify a student does not have the ability to edit submissions for other users.
3362
        $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3363
        $this->assertFalse($assign->can_edit_submission($student2->id, $student1->id));
3364
        $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3365
        $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3366
    }
3367
 
3368
    /**
3369
     * Testing can_edit_submission
3370
     */
11 efrain 3371
    public function test_can_edit_submission_separategroups_with_editothersubmission(): void {
1 efrain 3372
        $this->resetAfterTest();
3373
        $course = $this->getDataGenerator()->create_course();
3374
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3375
 
3376
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3377
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3378
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3379
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3380
 
3381
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3382
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3383
        groups_assign_grouping($grouping->id, $group1->id);
3384
        groups_add_member($group1, $student1);
3385
        groups_add_member($group1, $student2);
3386
 
3387
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3388
        groups_assign_grouping($grouping->id, $group2->id);
3389
        groups_add_member($group2, $student3);
3390
        groups_add_member($group2, $student4);
3391
 
3392
        $assign = $this->create_instance($course, [
3393
            'assignsubmission_onlinetext_enabled' => 1,
3394
            'submissiondrafts' => 1,
3395
            'groupingid' => $grouping->id,
3396
            'groupmode' => SEPARATEGROUPS,
3397
        ]);
3398
 
3399
        // Add the capability to the new \assignment for student 1.
3400
        $roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
3401
        assign_capability('mod/assign:editothersubmission', CAP_ALLOW, $roleid, $assign->get_context()->id);
3402
        role_assign($roleid, $student1->id, $assign->get_context()->id);
3403
        accesslib_clear_all_caches_for_unit_testing();
3404
 
3405
        // Verify student1 has the ability to edit submissions for other users in their group, but not other groups.
3406
        $this->assertTrue($assign->can_edit_submission($student1->id, $student1->id));
3407
        $this->assertTrue($assign->can_edit_submission($student2->id, $student1->id));
3408
        $this->assertFalse($assign->can_edit_submission($student3->id, $student1->id));
3409
        $this->assertFalse($assign->can_edit_submission($student4->id, $student1->id));
3410
 
3411
        // Verify other students do not have the ability to edit submissions for other users.
3412
        $this->assertTrue($assign->can_edit_submission($student2->id, $student2->id));
3413
        $this->assertFalse($assign->can_edit_submission($student1->id, $student2->id));
3414
        $this->assertFalse($assign->can_edit_submission($student3->id, $student2->id));
3415
        $this->assertFalse($assign->can_edit_submission($student4->id, $student2->id));
3416
    }
3417
 
3418
    /**
3419
     * Test if the view blind details capability works
3420
     */
11 efrain 3421
    public function test_can_view_blind_details(): void {
1 efrain 3422
        global $DB;
3423
 
3424
        $this->resetAfterTest();
3425
        $course = $this->getDataGenerator()->create_course();
3426
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3427
        $manager = $this->getDataGenerator()->create_and_enrol($course, 'manager');
3428
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3429
 
3430
        $assign = $this->create_instance($course, [
3431
            'blindmarking' => 1,
3432
        ]);
3433
 
3434
        $this->assertTrue($assign->is_blind_marking());
3435
 
3436
        // Test student names are hidden to teacher.
3437
        $this->setUser($teacher);
3438
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
3439
        $output = $assign->get_renderer()->render($gradingtable);
3440
        $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));    // "Participant" is somewhere on the page.
3441
        $this->assertEquals(false, strpos($output, fullname($student)));    // Students full name doesn't appear.
3442
 
3443
        // Test student names are visible to manager.
3444
        $this->setUser($manager);
3445
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
3446
        $output = $assign->get_renderer()->render($gradingtable);
3447
        $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
3448
        $this->assertEquals(true, strpos($output, fullname($student)));
3449
    }
3450
 
3451
    /**
3452
     * Testing get_shared_group_members
3453
     */
11 efrain 3454
    public function test_get_shared_group_members(): void {
1 efrain 3455
        $this->resetAfterTest();
3456
        $course = $this->getDataGenerator()->create_course();
3457
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3458
 
3459
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3460
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3461
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3462
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3463
 
3464
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3465
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3466
        groups_assign_grouping($grouping->id, $group1->id);
3467
        groups_add_member($group1, $student1);
3468
        groups_add_member($group1, $student2);
3469
 
3470
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3471
        groups_assign_grouping($grouping->id, $group2->id);
3472
        groups_add_member($group2, $student3);
3473
        groups_add_member($group2, $student4);
3474
 
3475
        $assign = $this->create_instance($course, [
3476
            'groupingid' => $grouping->id,
3477
            'groupmode' => SEPARATEGROUPS,
3478
        ]);
3479
 
3480
        $cm = $assign->get_course_module();
3481
 
3482
        // Get shared group members for students 0 and 1.
3483
        $groupmembers = $assign->get_shared_group_members($cm, $student1->id);
3484
        $this->assertCount(2, $groupmembers);
3485
        $this->assertContainsEquals($student1->id, $groupmembers);
3486
        $this->assertContainsEquals($student2->id, $groupmembers);
3487
 
3488
        $groupmembers = $assign->get_shared_group_members($cm, $student2->id);
3489
        $this->assertCount(2, $groupmembers);
3490
        $this->assertContainsEquals($student1->id, $groupmembers);
3491
        $this->assertContainsEquals($student2->id, $groupmembers);
3492
 
3493
        $groupmembers = $assign->get_shared_group_members($cm, $student3->id);
3494
        $this->assertCount(2, $groupmembers);
3495
        $this->assertContainsEquals($student3->id, $groupmembers);
3496
        $this->assertContainsEquals($student4->id, $groupmembers);
3497
 
3498
        $groupmembers = $assign->get_shared_group_members($cm, $student4->id);
3499
        $this->assertCount(2, $groupmembers);
3500
        $this->assertContainsEquals($student3->id, $groupmembers);
3501
        $this->assertContainsEquals($student4->id, $groupmembers);
3502
    }
3503
 
3504
    /**
3505
     * Testing get_shared_group_members
3506
     */
11 efrain 3507
    public function test_get_shared_group_members_override(): void {
1 efrain 3508
        $this->resetAfterTest();
3509
        $course = $this->getDataGenerator()->create_course();
3510
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3511
 
3512
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3513
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3514
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3515
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3516
 
3517
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3518
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3519
        groups_assign_grouping($grouping->id, $group1->id);
3520
        groups_add_member($group1, $student1);
3521
        groups_add_member($group1, $student2);
3522
 
3523
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3524
        groups_assign_grouping($grouping->id, $group2->id);
3525
        groups_add_member($group2, $student3);
3526
        groups_add_member($group2, $student4);
3527
 
3528
        $assign = $this->create_instance($course, [
3529
            'groupingid' => $grouping->id,
3530
            'groupmode' => SEPARATEGROUPS,
3531
        ]);
3532
 
3533
        $cm = $assign->get_course_module();
3534
 
3535
        // Add the capability to access allgroups for one of the students.
3536
        $roleid = create_role('Access all groups role', 'accessallgroupsrole', '');
3537
        assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $roleid, $assign->get_context()->id);
3538
        role_assign($roleid, $student1->id, $assign->get_context()->id);
3539
        accesslib_clear_all_caches_for_unit_testing();
3540
 
3541
        // Get shared group members for students 0 and 1.
3542
        $groupmembers = $assign->get_shared_group_members($cm, $student1->id);
3543
        $this->assertCount(4, $groupmembers);
3544
        $this->assertContainsEquals($student1->id, $groupmembers);
3545
        $this->assertContainsEquals($student2->id, $groupmembers);
3546
        $this->assertContainsEquals($student3->id, $groupmembers);
3547
        $this->assertContainsEquals($student4->id, $groupmembers);
3548
 
3549
        $groupmembers = $assign->get_shared_group_members($cm, $student2->id);
3550
        $this->assertCount(2, $groupmembers);
3551
        $this->assertContainsEquals($student1->id, $groupmembers);
3552
        $this->assertContainsEquals($student2->id, $groupmembers);
3553
 
3554
        $groupmembers = $assign->get_shared_group_members($cm, $student3->id);
3555
        $this->assertCount(2, $groupmembers);
3556
        $this->assertContainsEquals($student3->id, $groupmembers);
3557
        $this->assertContainsEquals($student4->id, $groupmembers);
3558
 
3559
        $groupmembers = $assign->get_shared_group_members($cm, $student4->id);
3560
        $this->assertCount(2, $groupmembers);
3561
        $this->assertContainsEquals($student3->id, $groupmembers);
3562
        $this->assertContainsEquals($student4->id, $groupmembers);
3563
    }
3564
 
3565
    /**
3566
     * Test get plugins file areas
3567
     */
11 efrain 3568
    public function test_get_plugins_file_areas(): void {
1 efrain 3569
        global $DB;
3570
 
3571
        $this->resetAfterTest();
3572
        $course = $this->getDataGenerator()->create_course();
3573
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3574
 
3575
        $assign = $this->create_instance($course);
3576
 
3577
        // Test that all the submission and feedback plugins are returning the expected file aras.
3578
        $usingfilearea = 0;
3579
        $coreplugins = \core_plugin_manager::standard_plugins_list('assignsubmission');
3580
        foreach ($assign->get_submission_plugins() as $plugin) {
3581
            $type = $plugin->get_type();
3582
            if (!in_array($type, $coreplugins)) {
3583
                continue;
3584
            }
3585
            $fileareas = $plugin->get_file_areas();
3586
 
3587
            if ($type == 'onlinetext') {
3588
                $this->assertEquals(array('submissions_onlinetext' => 'Online text'), $fileareas);
3589
                $usingfilearea++;
3590
            } else if ($type == 'file') {
3591
                $this->assertEquals(array('submission_files' => 'File submissions'), $fileareas);
3592
                $usingfilearea++;
3593
            } else {
3594
                $this->assertEmpty($fileareas);
3595
            }
3596
        }
3597
        $this->assertEquals(2, $usingfilearea);
3598
 
3599
        $usingfilearea = 0;
3600
        $coreplugins = \core_plugin_manager::standard_plugins_list('assignfeedback');
3601
        foreach ($assign->get_feedback_plugins() as $plugin) {
3602
            $type = $plugin->get_type();
3603
            if (!in_array($type, $coreplugins)) {
3604
                continue;
3605
            }
3606
            $fileareas = $plugin->get_file_areas();
3607
 
3608
            if ($type == 'editpdf') {
3609
                $checkareas = [
3610
                    'download' => 'Annotate PDF',
3611
                    'combined' => 'Annotate PDF',
3612
                    'partial' => 'Annotate PDF',
3613
                    'importhtml' => 'Annotate PDF',
3614
                    'pages' => 'Annotate PDF',
3615
                    'readonlypages' => 'Annotate PDF',
3616
                    'stamps' => 'Annotate PDF',
3617
                    'tmp_jpg_to_pdf' => 'Annotate PDF',
3618
                    'tmp_rotated_jpg' => 'Annotate PDF'
3619
                ];
3620
                $this->assertEquals($checkareas, $fileareas);
3621
                $usingfilearea++;
3622
            } else if ($type == 'file') {
3623
                $this->assertEquals(array('feedback_files' => 'Feedback files'), $fileareas);
3624
                $usingfilearea++;
3625
            } else if ($type == 'comments') {
3626
                $this->assertEquals(array('feedback' => 'Feedback comments'), $fileareas);
3627
                $usingfilearea++;
3628
            } else {
3629
                $this->assertEmpty($fileareas);
3630
            }
3631
        }
3632
        $this->assertEquals(3, $usingfilearea);
3633
    }
3634
 
3635
    /**
3636
     * Test override exists
3637
     *
3638
     * This function needs to obey the group override logic as per the assign grading table and
3639
     * the overview block.
3640
     */
11 efrain 3641
    public function test_override_exists(): void {
1 efrain 3642
        global $DB;
3643
 
3644
        $this->resetAfterTest();
3645
        $course = $this->getDataGenerator()->create_course();
3646
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3647
 
3648
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3649
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3650
        $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3651
 
3652
        // Data:
3653
        // - student1 => group A only
3654
        // - student2 => group B only
3655
        // - student3 => Group A + Group B (No user override)
3656
        // - student4 => Group A + Group B (With user override)
3657
        // - student4 => No groups.
3658
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3659
        groups_add_member($group1, $student1);
3660
 
3661
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3662
        groups_add_member($group2, $student2);
3663
 
3664
        $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3665
        groups_add_member($group1, $student3);
3666
        groups_add_member($group2, $student3);
3667
 
3668
        $student4 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3669
        groups_add_member($group1, $student4);
3670
        groups_add_member($group2, $student4);
3671
 
3672
        $student5 = $this->getDataGenerator()->create_and_enrol($course, 'student');
3673
 
3674
        $assign = $this->create_instance($course);
3675
        $instance = $assign->get_instance();
3676
 
3677
        // Overrides for each of the groups, and a user override.
3678
        $overrides = [
3679
            (object) [
3680
                // Override for group 1, highest priority (numerically lowest sortorder).
3681
                'assignid' => $instance->id,
3682
                'groupid' => $group1->id,
3683
                'userid' => null,
3684
                'sortorder' => 1,
3685
                'allowsubmissionsfromdate' => 1,
3686
                'duedate' => 2,
3687
                'cutoffdate' => 3,
3688
                'timelimit' => null
3689
            ],
3690
            (object) [
3691
                // Override for group 2, lower priority (numerically higher sortorder).
3692
                'assignid' => $instance->id,
3693
                'groupid' => $group2->id,
3694
                'userid' => null,
3695
                'sortorder' => 2,
3696
                'allowsubmissionsfromdate' => 5,
3697
                'duedate' => 6,
3698
                'cutoffdate' => 6,
3699
                'timelimit' => null
3700
            ],
3701
            (object) [
3702
                // User override.
3703
                'assignid' => $instance->id,
3704
                'groupid' => null,
3705
                'userid' => $student3->id,
3706
                'sortorder' => null,
3707
                'allowsubmissionsfromdate' => 7,
3708
                'duedate' => 8,
3709
                'cutoffdate' => 9,
3710
                'timelimit' => null
3711
            ],
3712
        ];
3713
 
3714
        foreach ($overrides as &$override) {
3715
            $override->id = $DB->insert_record('assign_overrides', $override);
3716
        }
3717
 
3718
        // User only in group 1 should see the group 1 override.
3719
        $this->assertEquals($overrides[0], $assign->override_exists($student1->id));
3720
 
3721
        // User only in group 2 should see the group 2 override.
3722
        $this->assertEquals($overrides[1], $assign->override_exists($student2->id));
3723
 
3724
        // User only in both groups with an override should see the user override as it has higher priority.
3725
        $this->assertEquals($overrides[2], $assign->override_exists($student3->id));
3726
 
3727
        // User only in both groups with no override should see the group 1 override as it has higher priority.
3728
        $this->assertEquals($overrides[0], $assign->override_exists($student4->id));
3729
 
3730
        // User with no overrides shoudl get nothing.
3731
        $override = $assign->override_exists($student5->id);
3732
        $this->assertNull($override->duedate);
3733
        $this->assertNull($override->cutoffdate);
3734
        $this->assertNull($override->allowsubmissionsfromdate);
3735
    }
3736
 
3737
    /**
3738
     * Test the quicksave grades processor
3739
     */
11 efrain 3740
    public function test_process_save_quick_grades(): void {
1 efrain 3741
        $this->resetAfterTest();
3742
        $course = $this->getDataGenerator()->create_course();
3743
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3744
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3745
 
3746
        $teacher->ignoresesskey = true;
3747
        $this->setUser($teacher);
3748
        $assign = $this->create_instance($course, [
3749
                'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
3750
            ]);
3751
 
3752
        // Initially grade the user.
3753
        $grade = (object) [
3754
            'attemptnumber' => '',
3755
            'timemodified' => '',
3756
        ];
3757
        $data = [
3758
            "grademodified_{$student->id}" => $grade->timemodified,
3759
            "gradeattempt_{$student->id}" => $grade->attemptnumber,
3760
            "quickgrade_{$student->id}" => '60.0',
3761
        ];
3762
 
3763
        $result = $assign->testable_process_save_quick_grades($data);
3764
        $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
3765
        $grade = $assign->get_user_grade($student->id, false);
3766
        $this->assertEquals(60.0, $grade->grade);
3767
 
3768
        // Attempt to grade with a past attempts grade info.
3769
        $assign->testable_process_add_attempt($student->id);
3770
        $data = array(
3771
            'grademodified_' . $student->id => $grade->timemodified,
3772
            'gradeattempt_' . $student->id => $grade->attemptnumber,
3773
            'quickgrade_' . $student->id => '50.0'
3774
        );
3775
        $result = $assign->testable_process_save_quick_grades($data);
3776
        $this->assertStringContainsString(get_string('errorrecordmodified', 'assign'), $result);
3777
        $grade = $assign->get_user_grade($student->id, false);
3778
        $this->assertFalse($grade);
3779
 
3780
        // Attempt to grade a the attempt.
3781
        $submission = $assign->get_user_submission($student->id, false);
3782
        $data = array(
3783
            'grademodified_' . $student->id => '',
3784
            'gradeattempt_' . $student->id => $submission->attemptnumber,
3785
            'quickgrade_' . $student->id => '40.0'
3786
        );
3787
        $result = $assign->testable_process_save_quick_grades($data);
3788
        $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
3789
        $grade = $assign->get_user_grade($student->id, false);
3790
        $this->assertEquals(40.0, $grade->grade);
3791
 
3792
        // Catch grade update conflicts.
3793
        // Save old data for later.
3794
        $pastdata = $data;
3795
        // Update the grade the 'good' way.
3796
        $data = array(
3797
            'grademodified_' . $student->id => $grade->timemodified,
3798
            'gradeattempt_' . $student->id => $grade->attemptnumber,
3799
            'quickgrade_' . $student->id => '30.0'
3800
        );
3801
        $result = $assign->testable_process_save_quick_grades($data);
3802
        $this->assertStringContainsString(get_string('quickgradingchangessaved', 'assign'), $result);
3803
        $grade = $assign->get_user_grade($student->id, false);
3804
        $this->assertEquals(30.0, $grade->grade);
3805
 
3806
        // Now update using 'old' data. Should fail.
3807
        $result = $assign->testable_process_save_quick_grades($pastdata);
3808
        $this->assertStringContainsString(get_string('errorrecordmodified', 'assign'), $result);
3809
        $grade = $assign->get_user_grade($student->id, false);
3810
        $this->assertEquals(30.0, $grade->grade);
3811
    }
3812
 
3813
    /**
3814
     * Test updating activity completion when submitting an assessment.
3815
     */
11 efrain 3816
    public function test_update_activity_completion_records_solitary_submission(): void {
1 efrain 3817
        $this->resetAfterTest();
3818
 
3819
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
3820
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3821
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3822
 
3823
        $this->setUser($teacher);
3824
        $assign = $this->create_instance($course, [
3825
            'grade' => 100,
3826
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
3827
            'requireallteammemberssubmit' => 0,
3828
        ]);
3829
        $cm = $assign->get_course_module();
3830
 
3831
        // Submit the assignment as the student.
3832
        $this->add_submission($student, $assign);
3833
 
3834
        // Check that completion is not met yet.
3835
        $completion = new \completion_info($course);
3836
        $completiondata = $completion->get_data($cm, false, $student->id);
3837
        $this->assertEquals(0, $completiondata->completionstate);
3838
 
3839
        // Update to mark as complete.
3840
        $submission = $assign->get_user_submission($student->id, true);
3841
        $assign->testable_update_activity_completion_records(0, 0, $submission,
3842
            $student->id, COMPLETION_COMPLETE, $completion);
3843
 
3844
        // Completion should now be met.
3845
        $completiondata = $completion->get_data($cm, false, $student->id);
3846
        $this->assertEquals(1, $completiondata->completionstate);
3847
    }
3848
 
3849
    /**
3850
     * Test updating activity completion when submitting an assessment.
3851
     */
11 efrain 3852
    public function test_update_activity_completion_records_team_submission(): void {
1 efrain 3853
        $this->resetAfterTest();
3854
 
3855
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
3856
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3857
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3858
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3859
 
3860
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3861
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3862
 
3863
        groups_add_member($group1, $student);
3864
        groups_add_member($group1, $otherstudent);
3865
 
3866
        $assign = $this->create_instance($course, [
3867
            'grade' => 100,
3868
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
3869
            'teamsubmission' => 1,
3870
        ]);
3871
 
3872
        $cm = $assign->get_course_module();
3873
 
3874
        $this->add_submission($student, $assign);
3875
        $this->submit_for_grading($student, $assign, ['groupid' => $group1->id]);
3876
 
3877
        $completion = new \completion_info($course);
3878
 
3879
        // Check that completion is not met yet.
3880
        $completiondata = $completion->get_data($cm, false, $student->id);
3881
        $this->assertEquals(0, $completiondata->completionstate);
3882
 
3883
        $completiondata = $completion->get_data($cm, false, $otherstudent->id);
3884
        $this->assertEquals(0, $completiondata->completionstate);
3885
 
3886
        $submission = $assign->get_user_submission($student->id, true);
3887
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
3888
        $submission->groupid = $group1->id;
3889
 
3890
        $assign->testable_update_activity_completion_records(1, 0, $submission, $student->id, COMPLETION_COMPLETE, $completion);
3891
 
3892
        // Completion should now be met.
3893
        $completiondata = $completion->get_data($cm, false, $student->id);
3894
        $this->assertEquals(1, $completiondata->completionstate);
3895
 
3896
        $completiondata = $completion->get_data($cm, false, $otherstudent->id);
3897
        $this->assertEquals(1, $completiondata->completionstate);
3898
    }
3899
 
3900
    /**
3901
     * Test updating activity completion when submitting an assessment for MDL-67126.
3902
     */
11 efrain 3903
    public function test_update_activity_completion_records_team_submission_new(): void {
1 efrain 3904
        $this->resetAfterTest();
3905
 
3906
        $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
3907
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3908
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3909
        $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
3910
 
3911
        $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
3912
        $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
3913
 
3914
        groups_add_member($group1, $student);
3915
        groups_add_member($group1, $otherstudent);
3916
 
3917
        $assign = $this->create_instance($course, [
3918
            'submissiondrafts' => 0,
3919
            'completion' => COMPLETION_TRACKING_AUTOMATIC,
3920
            'completionsubmit' => 1,
3921
            'teamsubmission' => 1,
3922
            'assignsubmission_onlinetext_enabled' => 1
3923
        ]);
3924
 
3925
        $cm = $assign->get_course_module();
3926
 
3927
        $this->add_submission($student, $assign);
3928
 
3929
        $completion = new \completion_info($course);
3930
 
3931
        // Completion should now be met.
3932
        $completiondata = $completion->get_data($cm, false, $student->id);
3933
        $this->assertEquals(1, $completiondata->completionstate);
3934
 
3935
        $completiondata = $completion->get_data($cm, false, $otherstudent->id);
3936
        $this->assertEquals(1, $completiondata->completionstate);
3937
    }
3938
 
3939
    /**
3940
     * Data provider for test_fix_null_grades
3941
     * @return array[] Test data for test_fix_null_grades. Each element should contain grade, expectedcount and gradebookvalue
3942
     */
3943
    public function fix_null_grades_provider() {
3944
        return [
3945
            'Negative less than one is errant' => [
3946
                'grade' => -0.64,
3947
                'gradebookvalue' => null,
3948
            ],
3949
            'Negative more than one is errant' => [
3950
                'grade' => -30.18,
3951
                'gradebookvalue' => null,
3952
            ],
3953
            'Negative one exactly is not errant, but shouldn\'t be pushed to gradebook' => [
3954
                'grade' => ASSIGN_GRADE_NOT_SET,
3955
                'gradebookvalue' => null,
3956
            ],
3957
            'Positive grade is not errant' => [
3958
                'grade' => 1,
3959
                'gradebookvalue' => 1,
3960
            ],
3961
            'Large grade is not errant' => [
3962
                'grade' => 100,
3963
                'gradebookvalue' => 100,
3964
            ],
3965
            'Zero grade is not errant' => [
3966
                'grade' => 0,
3967
                'gradebookvalue' => 0,
3968
            ],
3969
        ];
3970
    }
3971
 
3972
    /**
3973
     * Test fix_null_grades
3974
     * @param number $grade The grade we should set in the assign grading table.
3975
     * @param number $expectedcount The finalgrade we expect in the gradebook after fixing the grades.
3976
     * @dataProvider fix_null_grades_provider
3977
     */
11 efrain 3978
    public function test_fix_null_grades($grade, $gradebookvalue): void {
1 efrain 3979
        global $DB;
3980
 
3981
        $this->resetAfterTest();
3982
 
3983
        $course = $this->getDataGenerator()->create_course();
3984
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
3985
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
3986
 
3987
        $this->setUser($teacher);
3988
        $assign = $this->create_instance($course);
3989
 
3990
        // Try getting a student's grade. This will give a grade of -1.
3991
        // Then we can override it with a bad negative grade.
3992
        $assign->get_user_grade($student->id, true);
3993
 
3994
        // Set the grade to something errant.
3995
        // We don't set the grader here, so we expect it to be -1 as a result.
3996
        $DB->set_field(
3997
            'assign_grades',
3998
            'grade',
3999
            $grade,
4000
            [
4001
                'userid' => $student->id,
4002
                'assignment' => $assign->get_instance()->id,
4003
            ]
4004
        );
4005
        $assign->grade = $grade;
4006
        $assigntemp = clone $assign->get_instance();
4007
        $assigntemp->cmidnumber = $assign->get_course_module()->idnumber;
4008
        assign_update_grades($assigntemp);
4009
 
4010
        // Check that the gradebook was updated with the assign grade. So we can guarentee test results later on.
4011
        $expectedgrade = $grade == -1 ? null : $grade; // Assign sends null to the gradebook for -1 grades.
4012
        $gradegrade = \grade_grade::fetch(array('userid' => $student->id, 'itemid' => $assign->get_grade_item()->id));
4013
        $this->assertEquals(-1, $gradegrade->usermodified);
4014
        $this->assertEquals($expectedgrade, $gradegrade->rawgrade);
4015
 
4016
        // Call fix_null_grades().
4017
        $method = new \ReflectionMethod(\assign::class, 'fix_null_grades');
4018
        $result = $method->invoke($assign);
4019
 
4020
        $this->assertSame(true, $result);
4021
 
4022
        $gradegrade = \grade_grade::fetch(array('userid' => $student->id, 'itemid' => $assign->get_grade_item()->id));
4023
 
4024
        $this->assertEquals(-1, $gradegrade->usermodified);
4025
        $this->assertEquals($gradebookvalue, $gradegrade->finalgrade);
4026
 
4027
        // Check that the grade was updated in the gradebook by fix_null_grades.
4028
        $this->assertEquals($gradebookvalue, $gradegrade->finalgrade);
4029
    }
4030
 
4031
    /**
4032
     * Test grade override displays 'Graded' for students
4033
     */
11 efrain 4034
    public function test_grade_submission_override(): void {
1 efrain 4035
        global $DB, $PAGE, $OUTPUT;
4036
 
4037
        $this->resetAfterTest();
4038
 
4039
        $course = $this->getDataGenerator()->create_course();
4040
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4041
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
4042
 
4043
        $this->setUser($teacher);
4044
        $assign = $this->create_instance($course, [
4045
            'assignsubmission_onlinetext_enabled' => 1,
4046
        ]);
4047
 
4048
        // Simulate adding a grade.
4049
        $this->setUser($teacher);
4050
        $data = new \stdClass();
4051
        $data->grade = '50.0';
4052
        $assign->testable_apply_grade_to_user($data, $student->id, 0);
4053
 
4054
        // Set grade override.
4055
        $gradegrade = \grade_grade::fetch([
4056
            'userid' => $student->id,
4057
            'itemid' => $assign->get_grade_item()->id,
4058
        ]);
4059
 
4060
        // Check that grade submission is not overridden yet.
4061
        $this->assertEquals(false, $gradegrade->is_overridden());
4062
 
4063
        // Simulate a submission.
4064
        $this->setUser($student);
4065
        $submission = $assign->get_user_submission($student->id, true);
4066
 
4067
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
4068
 
4069
        // Set override grade grade, and check that grade submission has been overridden.
4070
        $gradegrade->set_overridden(true);
4071
        $this->assertEquals(true, $gradegrade->is_overridden());
4072
 
4073
        // Check that submissionslocked message 'This assignment is not accepting submissions' does not appear for student.
4074
        $gradingtable = new \assign_grading_table($assign, 1, '', 0, true);
4075
        $output = $assign->get_renderer()->render($gradingtable);
4076
        $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
4077
 
4078
        $assignsubmissionstatus = $assign->get_assign_submission_status_renderable($student, true);
4079
        $output2 = $assign->get_renderer()->render($assignsubmissionstatus);
4080
 
4081
        // Check that submissionslocked 'This assignment is not accepting submissions' message does not appear for student.
4082
        $this->assertStringNotContainsString(get_string('submissionslocked', 'assign'), $output2);
4083
        // Check that submissionstatus_marked 'Graded' message does appear for student.
4084
        $this->assertStringContainsString(get_string('submissionstatus_marked', 'assign'), $output2);
4085
    }
4086
 
4087
    /**
4088
     * Test the result of get_filters is consistent.
4089
     */
11 efrain 4090
    public function test_get_filters(): void {
1 efrain 4091
        $this->resetAfterTest();
4092
 
4093
        $course = $this->getDataGenerator()->create_course();
4094
        $assign = $this->create_instance($course);
4095
        $valid = $assign->get_filters();
4096
 
4097
        $this->assertEquals(count($valid), 6);
4098
    }
4099
 
4100
    /**
4101
     * Test assign->get_instance() for a number of cases, as defined in the data provider.
4102
     *
4103
     * @dataProvider assign_get_instance_provider
4104
     * @param array $courseconfig the config to use when creating the course.
4105
     * @param array $assignconfig the config to use when creating the assignment.
4106
     * @param array $enrolconfig the config to use when enrolling the user (this will be the active user).
4107
     * @param array $expectedproperties an map containing the expected names and values for the assign instance data.
4108
     */
4109
    public function test_assign_get_instance(array $courseconfig, array $assignconfig, array $enrolconfig,
11 efrain 4110
            array $expectedproperties): void {
1 efrain 4111
        $this->resetAfterTest();
4112
 
4113
        set_config('enablecourserelativedates', true); // Enable relative dates at site level.
4114
 
4115
        $course = $this->getDataGenerator()->create_course($courseconfig);
4116
        $assign = $this->create_instance($course, $assignconfig);
4117
        $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));
4118
 
4119
        $instance = $assign->get_instance($user->id);
4120
 
4121
        foreach ($expectedproperties as $propertyname => $propertyval) {
4122
            $this->assertEquals($propertyval, $instance->$propertyname);
4123
        }
4124
    }
4125
 
4126
    /**
4127
     * The test_assign_get_instance data provider.
4128
     */
4129
    public function assign_get_instance_provider() {
4130
        $timenow = time();
4131
 
4132
        // The get_default_instance() method shouldn't calculate any properties per-user. It should just return the record data.
4133
        // We'll confirm this works for a few different user types anyway, just like we do for get_instance().
4134
        return [
4135
            'Teacher whose enrolment starts after the course start date, relative dates mode enabled' => [
4136
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4137
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4138
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4139
                    'startdate' => $timenow - 8 * DAYSECS],
4140
                'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS]
4141
            ],
4142
            'Teacher whose enrolment starts before the course start date, relative dates mode enabled' => [
4143
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4144
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4145
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4146
                    'startdate' => $timenow - 12 * DAYSECS],
4147
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4148
            ],
4149
            'Teacher whose enrolment starts after the course start date, relative dates mode disabled' => [
4150
                'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4151
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4152
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4153
                    'startdate' => $timenow - 8 * DAYSECS],
4154
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4155
            ],
4156
            'Student whose enrolment starts after the course start date, relative dates mode enabled' => [
4157
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4158
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4159
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4160
                    'startdate' => $timenow - 8 * DAYSECS],
4161
                'expectedproperties' => ['duedate' => $timenow + 6 * DAYSECS]
4162
            ],
4163
            'Student whose enrolment starts before the course start date, relative dates mode enabled' => [
4164
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4165
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4166
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4167
                    'startdate' => $timenow - 12 * DAYSECS],
4168
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4169
            ],
4170
            'Student whose enrolment starts after the course start date, relative dates mode disabled' => [
4171
                'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4172
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4173
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4174
                    'startdate' => $timenow - 8 * DAYSECS],
4175
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4176
            ],
4177
        ];
4178
    }
4179
 
4180
    /**
4181
     * Test assign->get_default_instance() for a number of cases, as defined in the date provider.
4182
     *
4183
     * @dataProvider assign_get_default_instance_provider
4184
     * @param array $courseconfig the config to use when creating the course.
4185
     * @param array $assignconfig the config to use when creating the assignment.
4186
     * @param array $enrolconfig the config to use when enrolling the user (this will be the active user).
4187
     * @param array $expectedproperties an map containing the expected names and values for the assign instance data.
4188
     */
4189
    public function test_assign_get_default_instance(array $courseconfig, array $assignconfig, array $enrolconfig,
11 efrain 4190
            array $expectedproperties): void {
1 efrain 4191
        $this->resetAfterTest();
4192
 
4193
        set_config('enablecourserelativedates', true); // Enable relative dates at site level.
4194
 
4195
        $course = $this->getDataGenerator()->create_course($courseconfig);
4196
        $assign = $this->create_instance($course, $assignconfig);
4197
        $user = $this->getDataGenerator()->create_and_enrol($course, ...array_values($enrolconfig));
4198
 
4199
        $this->setUser($user);
4200
        $defaultinstance = $assign->get_default_instance();
4201
 
4202
        foreach ($expectedproperties as $propertyname => $propertyval) {
4203
            $this->assertEquals($propertyval, $defaultinstance->$propertyname);
4204
        }
4205
    }
4206
 
4207
    /**
4208
     * The test_assign_get_default_instance data provider.
4209
     */
4210
    public function assign_get_default_instance_provider() {
4211
        $timenow = time();
4212
 
4213
        // The get_default_instance() method shouldn't calculate any properties per-user. It should just return the record data.
4214
        // We'll confirm this works for a few different user types anyway, just like we do for get_instance().
4215
        return [
4216
            'Teacher whose enrolment starts after the course start date, relative dates mode enabled' => [
4217
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4218
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4219
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4220
                    'startdate' => $timenow - 8 * DAYSECS],
4221
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4222
            ],
4223
            'Teacher whose enrolment starts before the course start date, relative dates mode enabled' => [
4224
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4225
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4226
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4227
                    'startdate' => $timenow - 12 * DAYSECS],
4228
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4229
            ],
4230
            'Teacher whose enrolment starts after the course start date, relative dates mode disabled' => [
4231
                'courseconfig' => ['relativedatesmode' => false, 'startdate' => $timenow - 10 * DAYSECS],
4232
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4233
                'enrolconfig' => ['shortname' => 'teacher', 'userparams' => null, 'method' => 'manual',
4234
                    'startdate' => $timenow - 8 * DAYSECS],
4235
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4236
            ],
4237
            'Student whose enrolment starts after the course start date, relative dates mode enabled' => [
4238
                'courseconfig' => ['relativedatesmode' => true, 'startdate' => $timenow - 10 * DAYSECS],
4239
                'assignconfig' => ['duedate' => $timenow + 4 * DAYSECS],
4240
                'enrolconfig' => ['shortname' => 'student', 'userparams' => null, 'method' => 'manual',
4241
                    'startdate' => $timenow - 8 * DAYSECS],
4242
                'expectedproperties' => ['duedate' => $timenow + 4 * DAYSECS]
4243
            ],
4244
        ];
4245
    }
4246
 
4247
    /**
4248
     * Test that cron task uses task API to get its last run time.
4249
     */
11 efrain 4250
    public function test_cron_use_task_api_to_get_lastruntime(): void {
1 efrain 4251
        global $DB;
4252
        $this->resetAfterTest();
4253
        $course = $this->getDataGenerator()->create_course();
4254
 
4255
        // Create an assignment which allows submissions from 3 days ago.
4256
        $assign1 = $this->create_instance($course, [
4257
            'duedate' => time() + DAYSECS,
4258
            'alwaysshowdescription' => 0,
4259
            'allowsubmissionsfromdate' => time() - 3 * DAYSECS,
4260
            'intro' => 'This one should not be re-created',
4261
        ]);
4262
 
4263
        // Create an assignment which allows submissions from 1 day ago.
4264
        $assign2 = $this->create_instance($course, [
4265
            'duedate' => time() + DAYSECS,
4266
            'alwaysshowdescription' => 0,
4267
            'allowsubmissionsfromdate' => time() - DAYSECS,
4268
            'intro' => 'This one should be re-created',
4269
        ]);
4270
 
4271
        // Set last run time 2 days ago.
4272
        $DB->set_field('task_scheduled', 'lastruntime', time() - 2 * DAYSECS, ['classname' => '\mod_assign\task\cron_task']);
4273
 
4274
        // Remove events to make sure cron will update calendar and re-create one of them.
4275
        $params = array('modulename' => 'assign', 'instance' => $assign1->get_instance()->id);
4276
        $DB->delete_records('event', $params);
4277
        $params = array('modulename' => 'assign', 'instance' => $assign2->get_instance()->id);
4278
        $DB->delete_records('event', $params);
4279
 
4280
        // Run cron.
4281
        \assign::cron();
4282
 
4283
        // Assert that calendar hasn't been updated for the first assignment as it's supposed to be
4284
        // updated as part of previous cron runs (allowsubmissionsfromdate is less than lastruntime).
4285
        $params = array('modulename' => 'assign', 'instance' => $assign1->get_instance()->id);
4286
        $event1 = $DB->get_record('event', $params);
4287
        $this->assertEmpty($event1);
4288
 
4289
        // Assert that calendar has been updated for the second assignment
4290
        // because its allowsubmissionsfromdate is greater than lastruntime.
4291
        $params = array('modulename' => 'assign', 'instance' => $assign2->get_instance()->id);
4292
        $event2 = $DB->get_record('event', $params);
4293
        $this->assertNotEmpty($event2);
4294
        $this->assertSame('This one should be re-created', $event2->description);
4295
    }
4296
 
4297
    /**
4298
     * Test submissions that need grading output after one ungraded submission
4299
     */
11 efrain 4300
    public function test_submissions_need_grading(): void {
1 efrain 4301
        global $PAGE;
4302
 
4303
        $this->resetAfterTest();
4304
        $course = $this->getDataGenerator()->create_course();
4305
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4306
        $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
4307
 
4308
        // Setup the assignment.
4309
        $this->setUser($teacher);
4310
        $time = time();
4311
        $assign = $this->create_instance($course, [
4312
                'assignsubmission_onlinetext_enabled' => 1,
4313
            ]);
4314
        $PAGE->set_url(new \moodle_url('/mod/assign/view.php', [
4315
            'id' => $assign->get_course_module()->id,
4316
            'action' => 'grading',
4317
        ]));
4318
 
4319
        // Check for 0 submissions.
4320
        $summary = $assign->view('viewcourseindex');
4321
 
4322
        $this->assertStringContainsString('/mod/assign/view.php?id=' .
4323
            $assign->get_course_module()->id . '&amp;action=grading">' .
4324
            get_string('numberofsubmissionsneedgradinglabel', 'assign', 0) . '</a>', $summary);
4325
 
4326
        // Simulate an assignment submission.
4327
        $this->setUser($student);
4328
        $submission = $assign->get_user_submission($student->id, true);
4329
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
4330
        $assign->testable_update_submission($submission, $student->id, true, false);
4331
        $data = new \stdClass();
4332
        $data->onlinetext_editor = [
4333
            'itemid' => file_get_unused_draft_itemid(),
4334
            'text' => 'Submission text',
4335
            'format' => FORMAT_MOODLE,
4336
        ];
4337
        $plugin = $assign->get_submission_plugin_by_type('onlinetext');
4338
        $plugin->save($submission, $data);
4339
 
4340
        // Check for 1 ungraded submission.
4341
        $this->setUser($teacher);
4342
 
4343
        $summary = $assign->view('viewcourseindex');
4344
 
4345
        $this->assertStringContainsString('/mod/assign/view.php?id=' .
4346
            $assign->get_course_module()->id .  '&amp;action=grading">' .
4347
            get_string('numberofsubmissionsneedgradinglabel', 'assign', 1) . '</a>', $summary);
4348
    }
4349
 
4350
    /**
4351
     * Test that attachments should not be provided if \assign->show_intro returns false.
4352
     *
4353
     * @covers \assign::should_provide_intro_attachments
4354
     */
11 efrain 4355
    public function test_should_provide_intro_attachments_with_show_intro_disabled(): void {
1 efrain 4356
        $this->resetAfterTest();
4357
        $futuredate = time() + 300;
4358
        list($assign, $instance, $student) = $this->create_submission([
4359
            'alwaysshowdescription' => '0',
4360
            'allowsubmissionsfromdate' => $futuredate,
4361
        ]);
4362
        $this->assertFalse($assign->should_provide_intro_attachments($student->id));
4363
    }
4364
 
4365
    /**
4366
     * Test that attachments should be provided if user has capability to manage activity.
4367
     *
4368
     * @covers \assign::should_provide_intro_attachments
4369
     */
11 efrain 4370
    public function test_should_provide_intro_attachments_with_bypass_capability(): void {
1 efrain 4371
        $this->resetAfterTest();
4372
        list($assign, $instance, $student) = $this->create_submission([
4373
            'submissionattachments' => 1,
4374
        ]);
4375
        // Provide teaching role to student1 so they are able to bypass time limit restrictions on viewing attachments.
4376
        $this->getDataGenerator()->enrol_user($student->id, $instance->course, 'editingteacher');
4377
        $this->assertTrue($assign->should_provide_intro_attachments($student->id));
4378
    }
4379
 
4380
    /**
4381
     * Test that attachments should be provided if submissionattachments is disabled.
4382
     *
4383
     * @covers \assign::should_provide_intro_attachments
4384
     */
11 efrain 4385
    public function test_should_provide_intro_attachments_with_submissionattachments_disabled(): void {
1 efrain 4386
        $this->resetAfterTest();
4387
        list($assign, $instance, $student) = $this->create_submission();
4388
        $this->assertTrue($assign->should_provide_intro_attachments($student->id));
4389
    }
4390
 
4391
    /**
4392
     * Test that attachments should not be provided if submissionattachments is enabled with no open submission.
4393
     *
4394
     * @covers \assign::should_provide_intro_attachments
4395
     */
11 efrain 4396
    public function test_should_provide_intro_attachments_with_submissionattachments_enabled_and_submissions_closed(): void {
1 efrain 4397
        $this->resetAfterTest();
4398
        // Set cut-off date to the past.
4399
        list($assign, $instance, $student) = $this->create_submission([
4400
            'timelimit' => '300',
4401
            'submissionattachments' => 1,
4402
            'cutoffdate' => time() - 300,
4403
        ]);
4404
        $this->assertFalse($assign->should_provide_intro_attachments($student->id));
4405
    }
4406
 
4407
    /**
4408
     * Test that attachments should be provided if submissionattachments is enabled with an open submission.
4409
     *
4410
     * @covers \assign::should_provide_intro_attachments
4411
     */
11 efrain 4412
    public function test_should_provide_intro_attachments_submissionattachments_enabled_and_an_open_submission(): void {
1 efrain 4413
        $this->resetAfterTest();
4414
        set_config('enabletimelimit', '1', 'assign');
4415
        list($assign, $instance, $student) = $this->create_submission([
4416
            'timelimit' => '300',
4417
            'submissionattachments' => 1,
4418
        ]);
4419
 
4420
        // Open a submission.
4421
        $assign->get_user_submission($student->id, true);
4422
 
4423
        $this->assertTrue($assign->should_provide_intro_attachments($student->id));
4424
    }
4425
 
4426
    /**
4427
     * Test that a submission using a time limit is currently open.
4428
     *
4429
     * @covers \assign::is_attempt_in_progress
4430
     */
11 efrain 4431
    public function test_is_attempt_in_progress_with_open_submission(): void {
1 efrain 4432
        global $DB;
4433
        $this->resetAfterTest();
4434
        set_config('enabletimelimit', '1', 'assign');
4435
        list($assign, $instance, $student) = $this->create_submission([
4436
            'timelimit' => '300',
4437
        ]);
4438
        $submission = $assign->get_user_submission($student->id, true);
4439
        // Set a timestarted.
4440
        $submission->timestarted = time() - 300;
4441
        $DB->update_record('assign_submission', $submission);
4442
        $this->assertTrue($assign->is_attempt_in_progress());
4443
    }
4444
 
4445
    /**
4446
     * Test that a submission using a time limit is started without a start time.
4447
     *
4448
     * @covers \assign::is_attempt_in_progress
4449
     */
11 efrain 4450
    public function test_is_attempt_in_progress_with_open_submission_and_no_timestarted(): void {
1 efrain 4451
        $this->resetAfterTest();
4452
        set_config('enabletimelimit', '1', 'assign');
4453
        list($assign, $instance, $student) = $this->create_submission([
4454
            'timelimit' => '300',
4455
        ]);
4456
        $assign->get_user_submission($student->id, true);
4457
        $this->assertFalse($assign->is_attempt_in_progress());
4458
    }
4459
 
4460
    /**
4461
     * Test that a submission using a time limit is currently not open.
4462
     *
4463
     * @covers \assign::is_attempt_in_progress
4464
     */
11 efrain 4465
    public function test_is_attempt_in_progress_with_no_open_submission(): void {
1 efrain 4466
        global $DB;
4467
        $this->resetAfterTest();
4468
        set_config('enabletimelimit', '1', 'assign');
4469
        list($assign, $instance, $student) = $this->create_submission([
4470
            'timelimit' => '300',
4471
        ]);
4472
        // Clear all current submissions.
4473
        $DB->delete_records('assign_submission', ['assignment' => $instance->id]);
4474
        $this->assertFalse($assign->is_attempt_in_progress());
4475
    }
4476
 
4477
    /**
4478
     * Create a submission for testing.
4479
     * @param  array $params Optional params to use for creating assignment instance.
4480
     * @return array an array containing all the required data for testing
4481
     */
4482
    protected function create_submission(array $params = []) {
4483
        global $DB;
4484
 
4485
        // Create a course and assignment and users.
4486
        $course = self::getDataGenerator()->create_course(array('groupmode' => SEPARATEGROUPS, 'groupmodeforce' => 1));
4487
 
4488
        $generator = $this->getDataGenerator()->get_plugin_generator('mod_assign');
4489
        $params = array_merge(array(
4490
            'course' => $course->id,
4491
            'assignsubmission_file_maxfiles' => 1,
4492
            'assignsubmission_file_maxsizebytes' => 1024 * 1024,
4493
            'assignsubmission_onlinetext_enabled' => 1,
4494
            'assignsubmission_file_enabled' => 1,
4495
            'submissiondrafts' => 1,
4496
            'assignfeedback_file_enabled' => 1,
4497
            'assignfeedback_comments_enabled' => 1,
4498
            'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
4499
            'sendnotifications' => 0
4500
        ), $params);
4501
 
4502
        set_config('submissionreceipts', 0, 'assign');
4503
 
4504
        $instance = $generator->create_instance($params);
4505
        $cm = get_coursemodule_from_instance('assign', $instance->id);
4506
        $context = \context_module::instance($cm->id);
4507
 
4508
        $assign = new \mod_assign_testable_assign($context, $cm, $course);
4509
 
4510
        $student = self::getDataGenerator()->create_user();
4511
        $studentrole = $DB->get_record('role', array('shortname' => 'student'));
4512
        $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
4513
 
4514
        $this->setUser($student);
4515
 
4516
        // Create a student1 with an online text submission.
4517
        // Simulate a submission.
4518
        $submission = $assign->get_user_submission($student->id, true);
4519
 
4520
        $data = new \stdClass();
4521
        $data->onlinetext_editor = array(
4522
            'itemid' => file_get_unused_draft_itemid(),
4523
            'text' => 'Submission text with a <a href="@@PLUGINFILE@@/intro.txt">link</a>',
4524
            'format' => FORMAT_MOODLE);
4525
 
4526
        $draftidfile = file_get_unused_draft_itemid();
4527
        $usercontext = \context_user::instance($student->id);
4528
        $filerecord = array(
4529
            'contextid' => $usercontext->id,
4530
            'component' => 'user',
4531
            'filearea'  => 'draft',
4532
            'itemid'    => $draftidfile,
4533
            'filepath'  => '/',
4534
            'filename'  => 't.txt',
4535
        );
4536
        $fs = get_file_storage();
4537
        $fs->create_file_from_string($filerecord, 'text contents');
4538
 
4539
        $data->files_filemanager = $draftidfile;
4540
 
4541
        $notices = array();
4542
        $assign->save_submission($data, $notices);
4543
 
4544
        return array($assign, $instance, $student);
4545
    }
4546
 
4547
    /**
4548
     * Test user filtering by First name, Last name and Submission status.
4549
     *
4550
     * @covers \assign::is_userid_filtered
4551
     */
11 efrain 4552
    public function test_is_userid_filtered(): void {
1 efrain 4553
        $this->resetAfterTest();
4554
 
4555
        // Generate data and simulate student submissions.
4556
        $course = $this->getDataGenerator()->create_course();
4557
        $params1 = ['firstname' => 'Valentin', 'lastname' => 'Ivanov'];
4558
        $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student', $params1);
4559
        $params2 = ['firstname' => 'Nikolay', 'lastname' => 'Petrov'];
4560
        $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student', $params2);
4561
        $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
4562
        $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
4563
        $this->setUser($student1);
4564
        $submission = $assign->get_user_submission($student1->id, true);
4565
        $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
4566
        $assign->testable_update_submission($submission, $student1->id, true, false);
4567
        $this->setUser($student2);
4568
        $submission = $assign->get_user_submission($student2->id, true);
4569
        $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
4570
        $assign->testable_update_submission($submission, $student2->id, true, false);
4571
        $this->setUser($teacher);
4572
 
4573
        // By default, both users should match filters.
4574
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4575
        $this->AssertTrue($assign->is_userid_filtered($student2->id));
4576
 
4577
        // Filter by First name starting with V.
4578
        $_GET['tifirst'] = 'V';
4579
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4580
        $this->AssertFalse($assign->is_userid_filtered($student2->id));
4581
 
4582
        // Add Last name to filter out both users.
4583
        $_GET['tilast'] = 'G';
4584
        $this->AssertFalse($assign->is_userid_filtered($student1->id));
4585
        $this->AssertFalse($assign->is_userid_filtered($student2->id));
4586
 
4587
        // Unsetting variables doesn't change behaviour because filters are stored in user preferences.
4588
        unset($_GET['tifirst']);
4589
        unset($_GET['tilast']);
4590
        $this->AssertFalse($assign->is_userid_filtered($student1->id));
4591
        $this->AssertFalse($assign->is_userid_filtered($student2->id));
4592
 
4593
        // Reset table preferences.
4594
        $_GET['treset'] = '1';
4595
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4596
        $this->AssertTrue($assign->is_userid_filtered($student2->id));
4597
 
4598
        // Display users with submitted submissions only.
4599
        set_user_preference('assign_filter', ASSIGN_SUBMISSION_STATUS_SUBMITTED);
4600
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4601
        $this->AssertFalse($assign->is_userid_filtered($student2->id));
4602
 
4603
        // Display users with drafts.
4604
        set_user_preference('assign_filter', ASSIGN_SUBMISSION_STATUS_DRAFT);
4605
        $this->AssertFalse($assign->is_userid_filtered($student1->id));
4606
        $this->AssertTrue($assign->is_userid_filtered($student2->id));
4607
 
4608
        // Reset the filter.
4609
        set_user_preference('assign_filter', '');
4610
        $this->AssertTrue($assign->is_userid_filtered($student1->id));
4611
        $this->AssertTrue($assign->is_userid_filtered($student2->id));
4612
    }
4613
}