Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

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