Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 9 May 2022 (12 months).
  • Bug fixes for security issues in 3.11.x will end 14 November 2022 (18 months).
  • 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 35 and 311] [Versions 36 and 311] [Versions 37 and 311] [Versions 38 and 311] [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  
      26  
      27  defined('MOODLE_INTERNAL') || die();
      28  
      29  global $CFG;
      30  require_once($CFG->dirroot . '/mod/assign/locallib.php');
      31  require_once($CFG->dirroot . '/mod/assign/upgradelib.php');
      32  require_once($CFG->dirroot . '/mod/assign/tests/generator.php');
      33  
      34  /**
      35   * Unit tests for (some of) mod/assign/locallib.php.
      36   *
      37   * @copyright  1999 onwards Martin Dougiamas  {@link http://moodle.com}
      38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
      39   */
      40  class mod_assign_locallib_testcase extends advanced_testcase {
      41  
      42      // Use the generator helper.
      43      use mod_assign_test_generator;
      44  
      45      public function test_return_links() {
      46          global $PAGE;
      47  
      48          $this->resetAfterTest();
      49          $course = $this->getDataGenerator()->create_course();
      50  
      51          $assign = $this->create_instance($course);
      52          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
      53  
      54          $assign->register_return_link('RETURNACTION', ['param' => 1]);
      55          $this->assertEquals('RETURNACTION', $assign->get_return_action());
      56          $this->assertEquals(['param' => 1], $assign->get_return_params());
      57      }
      58  
      59      public function test_get_feedback_plugins() {
      60          $this->resetAfterTest();
      61          $course = $this->getDataGenerator()->create_course();
      62          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
      63  
      64          $this->setUser($teacher);
      65          $assign = $this->create_instance($course);
      66          $installedplugins = array_keys(core_component::get_plugin_list('assignfeedback'));
      67  
      68          foreach ($assign->get_feedback_plugins() as $plugin) {
      69              $this->assertContains($plugin->get_type(), $installedplugins, 'Feedback plugin not in list of installed plugins');
      70          }
      71      }
      72  
      73      public function test_get_submission_plugins() {
      74          $this->resetAfterTest();
      75          $course = $this->getDataGenerator()->create_course();
      76          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
      77  
      78          $this->setUser($teacher);
      79          $assign = $this->create_instance($course);
      80          $installedplugins = array_keys(core_component::get_plugin_list('assignsubmission'));
      81  
      82          foreach ($assign->get_submission_plugins() as $plugin) {
      83              $this->assertContains($plugin->get_type(), $installedplugins, 'Submission plugin not in list of installed plugins');
      84          }
      85      }
      86  
      87      public function test_is_blind_marking() {
      88          $this->resetAfterTest();
      89          $course = $this->getDataGenerator()->create_course();
      90          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
      91          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
      92  
      93          $this->setUser($teacher);
      94          $assign = $this->create_instance($course, ['blindmarking' => 1]);
      95          $this->assertEquals(true, $assign->is_blind_marking());
      96  
      97          // Test cannot see student names.
      98          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
      99          $output = $assign->get_renderer()->render($gradingtable);
     100          $this->assertEquals(true, strpos($output, get_string('hiddenuser', 'assign')));
     101  
     102          // Test students cannot reveal identities.
     103          $nopermission = false;
     104          $student->ignoresesskey = true;
     105          $this->setUser($student);
     106          $this->expectException('required_capability_exception');
     107          $assign->reveal_identities();
     108          $student->ignoresesskey = false;
     109  
     110          // Test teachers cannot reveal identities.
     111          $nopermission = false;
     112          $teacher->ignoresesskey = true;
     113          $this->setUser($teacher);
     114          $this->expectException('required_capability_exception');
     115          $assign->reveal_identities();
     116          $teacher->ignoresesskey = false;
     117  
     118          // Test sesskey is required.
     119          $this->setUser($teacher);
     120          $this->expectException('moodle_exception');
     121          $assign->reveal_identities();
     122  
     123          // Test editingteacher can reveal identities if sesskey is ignored.
     124          $teacher->ignoresesskey = true;
     125          $this->setUser($teacher);
     126          $assign->reveal_identities();
     127          $this->assertEquals(false, $assign->is_blind_marking());
     128          $teacher->ignoresesskey = false;
     129  
     130          // Test student names are visible.
     131          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     132          $output = $assign->get_renderer()->render($gradingtable);
     133          $this->assertEquals(false, strpos($output, get_string('hiddenuser', 'assign')));
     134  
     135          // Set this back to default.
     136          $teacher->ignoresesskey = false;
     137      }
     138  
     139      /**
     140       * Data provider for test_get_assign_perpage
     141       *
     142       * @return array Provider data
     143       */
     144      public function get_assign_perpage_provider() {
     145          return array(
     146              array(
     147                  'maxperpage' => -1,
     148                  'userprefs' => array(
     149                      -1 => -1,
     150                      10 => 10,
     151                      20 => 20,
     152                      50 => 50,
     153                  ),
     154              ),
     155              array(
     156                  'maxperpage' => 15,
     157                  'userprefs' => array(
     158                      -1 => 15,
     159                      10 => 10,
     160                      20 => 15,
     161                      50 => 15,
     162                  ),
     163              ),
     164          );
     165      }
     166  
     167      /**
     168       * Test maxperpage
     169       *
     170       * @dataProvider get_assign_perpage_provider
     171       * @param integer $maxperpage site config value
     172       * @param array $userprefs Array of user preferences and expected page sizes
     173       */
     174      public function test_get_assign_perpage($maxperpage, $userprefs) {
     175          $this->resetAfterTest();
     176          $course = $this->getDataGenerator()->create_course();
     177          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     178          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     179  
     180          $this->setUser($teacher);
     181          $assign = $this->create_instance($course);
     182  
     183          set_config('maxperpage', $maxperpage, 'assign');
     184          set_user_preference('assign_perpage', null);
     185          $this->assertEquals(10, $assign->get_assign_perpage());
     186          foreach ($userprefs as $pref => $perpage) {
     187              set_user_preference('assign_perpage', $pref);
     188              $this->assertEquals($perpage, $assign->get_assign_perpage());
     189          }
     190      }
     191  
     192      /**
     193       * Test filter by requires grading.
     194       *
     195       * This is specifically checking an assignment with no grade to make sure we do not
     196       * get an exception thrown when rendering the grading table for this type of assignment.
     197       */
     198      public function test_gradingtable_filter_by_requiresgrading_no_grade() {
     199          global $PAGE;
     200  
     201          $this->resetAfterTest();
     202  
     203          $course = $this->getDataGenerator()->create_course();
     204          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     205          $this->setUser($teacher);
     206          $assign = $this->create_instance($course, [
     207                  'assignsubmission_onlinetext_enabled' => 1,
     208                  'assignfeedback_comments_enabled' => 0,
     209                  'grade' => GRADE_TYPE_NONE
     210              ]);
     211  
     212          $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
     213              'id' => $assign->get_course_module()->id,
     214              'action' => 'grading',
     215          )));
     216  
     217          // Render the table with the requires grading filter.
     218          $gradingtable = new assign_grading_table($assign, 1, ASSIGN_FILTER_REQUIRE_GRADING, 0, true);
     219          $output = $assign->get_renderer()->render($gradingtable);
     220  
     221          // Test that the filter function does not throw errors for assignments with no grade.
     222          $this->assertStringContainsString(get_string('nothingtodisplay'), $output);
     223      }
     224  
     225  
     226      /**
     227       * Test submissions with extension date.
     228       */
     229      public function test_gradingtable_extension_due_date() {
     230          global $PAGE;
     231  
     232          $this->resetAfterTest();
     233          $course = $this->getDataGenerator()->create_course();
     234          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     235          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     236  
     237          // Setup the assignment.
     238          $this->setUser($teacher);
     239          $time = time();
     240          $assign = $this->create_instance($course, [
     241                  'assignsubmission_onlinetext_enabled' => 1,
     242                  'duedate' => time() - (4 * DAYSECS),
     243              ]);
     244          $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
     245              'id' => $assign->get_course_module()->id,
     246              'action' => 'grading',
     247          )));
     248  
     249          // Check that the assignment is late.
     250          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     251          $output = $assign->get_renderer()->render($gradingtable);
     252          $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
     253          $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS))), $output);
     254  
     255          // Grant an extension.
     256          $extendedtime = $time + (2 * DAYSECS);
     257          $assign->testable_save_user_extension($student->id, $extendedtime);
     258          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     259          $output = $assign->get_renderer()->render($gradingtable);
     260          $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
     261          $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
     262  
     263          // Simulate a submission.
     264          $this->setUser($student);
     265          $submission = $assign->get_user_submission($student->id, true);
     266          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
     267          $assign->testable_update_submission($submission, $student->id, true, false);
     268          $data = new stdClass();
     269          $data->onlinetext_editor = [
     270              'itemid' => file_get_unused_draft_itemid(),
     271              'text' => 'Submission text',
     272              'format' => FORMAT_MOODLE,
     273          ];
     274          $plugin = $assign->get_submission_plugin_by_type('onlinetext');
     275          $plugin->save($submission, $data);
     276  
     277          // Verify output.
     278          $this->setUser($teacher);
     279          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     280          $output = $assign->get_renderer()->render($gradingtable);
     281          $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
     282          $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($extendedtime)), $output);
     283      }
     284  
     285      /**
     286       * Test that late submissions with extension date calculate correctly.
     287       */
     288      public function test_gradingtable_extension_date_calculation_for_lateness() {
     289          global $PAGE;
     290  
     291          $this->resetAfterTest();
     292          $course = $this->getDataGenerator()->create_course();
     293          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     294          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     295  
     296          // Setup the assignment.
     297          $this->setUser($teacher);
     298          $time = time();
     299          $assign = $this->create_instance($course, [
     300                  'assignsubmission_onlinetext_enabled' => 1,
     301                  'duedate' => time() - (4 * DAYSECS),
     302              ]);
     303          $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
     304              'id' => $assign->get_course_module()->id,
     305              'action' => 'grading',
     306          )));
     307  
     308          // Check that the assignment is late.
     309          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     310          $output = $assign->get_renderer()->render($gradingtable);
     311          $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
     312          $difftime = time() - $time;
     313          $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
     314  
     315          // Grant an extension that is in the past.
     316          $assign->testable_save_user_extension($student->id, $time - (2 * DAYSECS));
     317          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     318          $output = $assign->get_renderer()->render($gradingtable);
     319          $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
     320          $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
     321          $difftime = time() - $time;
     322          $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
     323  
     324          // Simulate a submission.
     325          $this->setUser($student);
     326          $submission = $assign->get_user_submission($student->id, true);
     327          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
     328          $assign->testable_update_submission($submission, $student->id, true, false);
     329          $data = new stdClass();
     330          $data->onlinetext_editor = [
     331              'itemid' => file_get_unused_draft_itemid(),
     332              'text' => 'Submission text',
     333              'format' => FORMAT_MOODLE,
     334          ];
     335          $plugin = $assign->get_submission_plugin_by_type('onlinetext');
     336          $plugin->save($submission, $data);
     337          $submittedtime = time();
     338  
     339          // Verify output.
     340          $this->setUser($teacher);
     341          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     342          $output = $assign->get_renderer()->render($gradingtable);
     343          $this->assertStringContainsString(get_string('submissionstatus_submitted', 'assign'), $output);
     344          $this->assertStringContainsString(get_string('userextensiondate', 'assign', userdate($time - (2 * DAYSECS))), $output);
     345  
     346          $difftime = $submittedtime - $time;
     347          $this->assertStringContainsString(get_string('submittedlateshort', 'assign', format_time((2 * DAYSECS) + $difftime)),
     348              $output);
     349      }
     350  
     351      public function test_gradingtable_status_rendering() {
     352          global $PAGE;
     353  
     354          $this->resetAfterTest();
     355          $course = $this->getDataGenerator()->create_course();
     356          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     357          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     358  
     359          // Setup the assignment.
     360          $this->setUser($teacher);
     361          $time = time();
     362          $assign = $this->create_instance($course, [
     363              'assignsubmission_onlinetext_enabled' => 1,
     364              'duedate' => $time - (4 * DAYSECS),
     365           ]);
     366          $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
     367              'id' => $assign->get_course_module()->id,
     368              'action' => 'grading',
     369          )));
     370  
     371          // Check that the assignment is late.
     372          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     373          $output = $assign->get_renderer()->render($gradingtable);
     374          $this->assertStringContainsString(get_string('submissionstatus_', 'assign'), $output);
     375          $difftime = time() - $time;
     376          $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
     377  
     378          // Simulate a student viewing the assignment without submitting.
     379          $this->setUser($student);
     380          $submission = $assign->get_user_submission($student->id, true);
     381          $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
     382          $assign->testable_update_submission($submission, $student->id, true, false);
     383          $submittedtime = time();
     384  
     385          // Verify output.
     386          $this->setUser($teacher);
     387          $gradingtable = new assign_grading_table($assign, 1, '', 0, true);
     388          $output = $assign->get_renderer()->render($gradingtable);
     389          $difftime = $submittedtime - $time;
     390          $this->assertStringContainsString(get_string('overdue', 'assign', format_time((4 * DAYSECS) + $difftime)), $output);
     391  
     392          $document = new DOMDocument();
     393          @$document->loadHTML($output);
     394          $xpath = new DOMXPath($document);
     395          $this->assertEmpty($xpath->evaluate('string(//td[@id="mod_assign_grading-' . $assign->get_context()->id. '_r0_c8"])'));
     396      }
     397  
     398      /**
     399       * Check that group submission information is rendered correctly in the
     400       * grading table.
     401       */
     402      public function test_gradingtable_group_submissions_rendering() {
     403          global $PAGE;
     404  
     405          $this->resetAfterTest();
     406          $course = $this->getDataGenerator()->create_course();
     407          $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
     408  
     409          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     410          groups_add_member($group, $teacher);
     411  
     412          $students = [];
     413  
     414          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     415          $students[] = $student;
     416          groups_add_member($group, $student);
     417  
     418          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     419          $students[] = $student;
     420          groups_add_member($group, $student);
     421  
     422          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     423          $students[] = $student;
     424          groups_add_member($group, $student);
     425  
     426          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     427          $students[] = $student;
     428          groups_add_member($group, $student);
     429  
     430          // Verify group assignments.
     431          $this->setUser($teacher);
     432          $assign = $this->create_instance($course, [
     433              'teamsubmission' => 1,
     434              'assignsubmission_onlinetext_enabled' => 1,
     435              'submissiondrafts' => 1,
     436              'requireallteammemberssubmit' => 0,
     437          ]);
     438          $PAGE->set_url(new moodle_url('/mod/assign/view.php', array(
     439              'id' => $assign->get_course_module()->id,
     440              'action' => 'grading',
     441          )));
     442  
     443          // Add a submission.
     444          $this->setUser($student);
     445          $data = new stdClass();
     446          $data->onlinetext_editor = [
     447              'itemid' => file_get_unused_draft_itemid(),
     448              'text' => 'Submission text',
     449              'format' => FORMAT_MOODLE,
     450          ];
     451          $notices = array();
     452          $assign->save_submission($data, $notices);
     453  
     454          $submission = $assign->get_group_submission($student->id, 0, true);
     455          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
     456          $assign->testable_update_submission($submission, $student->id, true, true);
     457  
     458          // Check output.
     459          $this->setUser($teacher);
     460          $gradingtable = new assign_grading_table($assign, 4, '', 0, true);
     461          $output = $assign->get_renderer()->render($gradingtable);
     462          $document = new DOMDocument();
     463          @$document->loadHTML($output);
     464          $xpath = new DOMXPath($document);
     465  
     466          // The XPath expression is based on the unique ID of the table.
     467          $xpathuniqueidroot = 'mod_assign_grading-' . $assign->get_context()->id;
     468  
     469          // Check status.
     470          $this->assertSame(get_string('submissionstatus_submitted', 'assign'),
     471              $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c4"]/div[@class="submissionstatussubmitted"])'));
     472          $this->assertSame(get_string('submissionstatus_submitted', 'assign'),
     473              $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c4"]/div[@class="submissionstatussubmitted"])'));
     474  
     475          // Check submission last modified date.
     476          $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c8"])')));
     477          $this->assertGreaterThan(0, strtotime($xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c8"])')));
     478  
     479          // Check group.
     480          $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c5"])'));
     481          $this->assertSame($group->name, $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c5"])'));
     482  
     483          // Check submission text.
     484          $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r0_c9"]/div/div)'));
     485          $this->assertSame('Submission text', $xpath->evaluate('string(//td[@id="' . $xpathuniqueidroot . '_r3_c9"]/div/div)'));
     486  
     487          // Check comments can be made.
     488          $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r0_c10"]//textarea)'));
     489          $this->assertEquals(1, $xpath->evaluate('count(//td[@id="' . $xpathuniqueidroot . '_r3_c10"]//textarea)'));
     490      }
     491  
     492      public function test_show_intro() {
     493          $this->resetAfterTest();
     494          $course = $this->getDataGenerator()->create_course();
     495          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     496  
     497          // Test whether we are showing the intro at the correct times.
     498          $this->setUser($teacher);
     499          $assign = $this->create_instance($course, ['alwaysshowdescription' => 1]);
     500  
     501          $this->assertEquals(true, $assign->testable_show_intro());
     502  
     503          $tomorrow = time() + DAYSECS;
     504  
     505          $assign = $this->create_instance($course, [
     506                  'alwaysshowdescription' => 0,
     507                  'allowsubmissionsfromdate' => $tomorrow,
     508              ]);
     509          $this->assertEquals(false, $assign->testable_show_intro());
     510          $yesterday = time() - DAYSECS;
     511          $assign = $this->create_instance($course, [
     512                  'alwaysshowdescription' => 0,
     513                  'allowsubmissionsfromdate' => $yesterday,
     514              ]);
     515          $this->assertEquals(true, $assign->testable_show_intro());
     516      }
     517  
     518      public function test_has_submissions_or_grades() {
     519          $this->resetAfterTest();
     520          $course = $this->getDataGenerator()->create_course();
     521          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     522          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     523  
     524          $this->setUser($teacher);
     525          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
     526          $instance = $assign->get_instance();
     527  
     528          // Should start empty.
     529          $this->assertEquals(false, $assign->has_submissions_or_grades());
     530  
     531          // Simulate a submission.
     532          $this->setUser($student);
     533          $submission = $assign->get_user_submission($student->id, true);
     534  
     535          // The submission is still new.
     536          $this->assertEquals(false, $assign->has_submissions_or_grades());
     537  
     538          // Submit the submission.
     539          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
     540          $assign->testable_update_submission($submission, $student->id, true, false);
     541          $data = new stdClass();
     542          $data->onlinetext_editor = array(
     543              'itemid' => file_get_unused_draft_itemid(),
     544              'text' => 'Submission text',
     545              'format' => FORMAT_MOODLE);
     546          $plugin = $assign->get_submission_plugin_by_type('onlinetext');
     547          $plugin->save($submission, $data);
     548  
     549          // Now test again.
     550          $this->assertEquals(true, $assign->has_submissions_or_grades());
     551      }
     552  
     553      public function test_delete_grades() {
     554          $this->resetAfterTest();
     555          $course = $this->getDataGenerator()->create_course();
     556          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     557          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     558  
     559          $this->setUser($teacher);
     560          $assign = $this->create_instance($course);
     561  
     562          // Simulate adding a grade.
     563          $this->setUser($teacher);
     564          $data = new stdClass();
     565          $data->grade = '50.0';
     566          $assign->testable_apply_grade_to_user($data, $student->id, 0);
     567  
     568          // Now see if the data is in the gradebook.
     569          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
     570  
     571          $this->assertNotEquals(0, count($gradinginfo->items));
     572  
     573          $assign->testable_delete_grades();
     574          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id);
     575  
     576          $this->assertEquals(0, count($gradinginfo->items));
     577      }
     578  
     579      public function test_delete_instance() {
     580          $this->resetAfterTest();
     581          $course = $this->getDataGenerator()->create_course();
     582          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     583          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     584  
     585          $this->setUser($teacher);
     586          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
     587  
     588          // Simulate adding a grade.
     589          $this->setUser($teacher);
     590          $data = new stdClass();
     591          $data->grade = '50.0';
     592          $assign->testable_apply_grade_to_user($data, $student->id, 0);
     593  
     594          // Simulate a submission.
     595          $this->add_submission($student, $assign);
     596  
     597          // Now try and delete.
     598          $this->setUser($teacher);
     599          $this->assertEquals(true, $assign->delete_instance());
     600      }
     601  
     602      public function test_reset_userdata() {
     603          global $DB;
     604  
     605          $this->resetAfterTest();
     606          $course = $this->getDataGenerator()->create_course();
     607          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     608          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     609  
     610          $now = time();
     611          $this->setUser($teacher);
     612          $assign = $this->create_instance($course, [
     613                  'assignsubmission_onlinetext_enabled' => 1,
     614                  'duedate' => $now,
     615              ]);
     616  
     617          // Simulate adding a grade.
     618          $this->add_submission($student, $assign);
     619          $this->submit_for_grading($student, $assign);
     620          $this->mark_submission($teacher, $assign, $student, 50.0);
     621  
     622          // Simulate a submission.
     623          $this->setUser($student);
     624          $submission = $assign->get_user_submission($student->id, true);
     625          $data = new stdClass();
     626          $data->onlinetext_editor = array(
     627              'itemid' => file_get_unused_draft_itemid(),
     628              'text' => 'Submission text',
     629              'format' => FORMAT_MOODLE);
     630          $plugin = $assign->get_submission_plugin_by_type('onlinetext');
     631          $plugin->save($submission, $data);
     632  
     633          $this->assertEquals(true, $assign->has_submissions_or_grades());
     634          // Now try and reset.
     635          $data = new stdClass();
     636          $data->reset_assign_submissions = 1;
     637          $data->reset_gradebook_grades = 1;
     638          $data->reset_assign_user_overrides = 1;
     639          $data->reset_assign_group_overrides = 1;
     640          $data->courseid = $course->id;
     641          $data->timeshift = DAYSECS;
     642          $this->setUser($teacher);
     643          $assign->reset_userdata($data);
     644          $this->assertEquals(false, $assign->has_submissions_or_grades());
     645  
     646          // Reload the instance data.
     647          $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
     648          $this->assertEquals($now + DAYSECS, $instance->duedate);
     649  
     650          // Test reset using assign_reset_userdata().
     651          $assignduedate = $instance->duedate; // Keep old updated value for comparison.
     652          $data->timeshift = (2 * DAYSECS);
     653          assign_reset_userdata($data);
     654          $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
     655          $this->assertEquals($assignduedate + (2 * DAYSECS), $instance->duedate);
     656  
     657          // Create one more assignment and reset, make sure time shifted for previous assignment is not changed.
     658          $assign2 = $this->create_instance($course, [
     659                  'assignsubmission_onlinetext_enabled' => 1,
     660                  'duedate' => $now,
     661              ]);
     662          $assignduedate = $instance->duedate;
     663          $data->timeshift = 3 * DAYSECS;
     664          $assign2->reset_userdata($data);
     665          $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
     666          $this->assertEquals($assignduedate, $instance->duedate);
     667          $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
     668          $this->assertEquals($now + 3 * DAYSECS, $instance2->duedate);
     669  
     670          // Reset both assignments using assign_reset_userdata() and make sure both assignments have same date.
     671          $assignduedate = $instance->duedate;
     672          $assign2duedate = $instance2->duedate;
     673          $data->timeshift = (4 * DAYSECS);
     674          assign_reset_userdata($data);
     675          $instance = $DB->get_record('assign', array('id' => $assign->get_instance()->id));
     676          $this->assertEquals($assignduedate + (4 * DAYSECS), $instance->duedate);
     677          $instance2 = $DB->get_record('assign', array('id' => $assign2->get_instance()->id));
     678          $this->assertEquals($assign2duedate + (4 * DAYSECS), $instance2->duedate);
     679      }
     680  
     681      public function test_plugin_settings() {
     682          global $DB;
     683  
     684          $this->resetAfterTest();
     685  
     686          $course = $this->getDataGenerator()->create_course();
     687          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     688  
     689          $now = time();
     690          $this->setUser($teacher);
     691          $assign = $this->create_instance($course, [
     692                  'assignsubmission_file_enabled' => 1,
     693                  'assignsubmission_file_maxfiles' => 12,
     694                  'assignsubmission_file_maxsizebytes' => 10,
     695              ]);
     696  
     697          $plugin = $assign->get_submission_plugin_by_type('file');
     698          $this->assertEquals('12', $plugin->get_config('maxfilesubmissions'));
     699      }
     700  
     701      public function test_update_calendar() {
     702          global $DB;
     703  
     704          $this->resetAfterTest();
     705  
     706          $course = $this->getDataGenerator()->create_course();
     707          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     708  
     709          $this->setUser($teacher);
     710          $userctx = context_user::instance($teacher->id)->id;
     711  
     712          // Hack to pretend that there was an editor involved. We need both $_POST and $_REQUEST, and a sesskey.
     713          $draftid = file_get_unused_draft_itemid();
     714          $_REQUEST['introeditor'] = $draftid;
     715          $_POST['introeditor'] = $draftid;
     716          $_POST['sesskey'] = sesskey();
     717  
     718          // Write links to a draft area.
     719          $fakearealink1 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">link</a>', 'draftfile.php', $userctx,
     720              'user', 'draft', $draftid);
     721          $fakearealink2 = file_rewrite_pluginfile_urls('<a href="@@PLUGINFILE@@/pic.gif">new</a>', 'draftfile.php', $userctx,
     722              'user', 'draft', $draftid);
     723  
     724          // Create a new assignment with links to a draft area.
     725          $now = time();
     726          $assign = $this->create_instance($course, [
     727                  'duedate' => $now,
     728                  'intro' => $fakearealink1,
     729                  'introformat' => FORMAT_HTML
     730              ]);
     731  
     732          // See if there is an event in the calendar.
     733          $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
     734          $event = $DB->get_record('event', $params);
     735          $this->assertNotEmpty($event);
     736          $this->assertSame('link', $event->description);     // The pluginfile links are removed.
     737  
     738          // Make sure the same works when updating the assignment.
     739          $instance = $assign->get_instance();
     740          $instance->instance = $instance->id;
     741          $instance->intro = $fakearealink2;
     742          $instance->introformat = FORMAT_HTML;
     743          $assign->update_instance($instance);
     744          $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
     745          $event = $DB->get_record('event', $params);
     746          $this->assertNotEmpty($event);
     747          $this->assertSame('new', $event->description);     // The pluginfile links are removed.
     748  
     749          // Create an assignment with a description that should be hidden.
     750          $assign = $this->create_instance($course, [
     751                  'duedate' => $now + 160,
     752                  'alwaysshowdescription' => false,
     753                  'allowsubmissionsfromdate' => $now + 60,
     754                  'intro' => 'Some text',
     755              ]);
     756  
     757          // Get the event from the calendar.
     758          $params = array('modulename' => 'assign', 'instance' => $assign->get_instance()->id);
     759          $event = $DB->get_record('event', [
     760              'modulename' => 'assign',
     761              'instance' => $assign->get_instance()->id,
     762          ]);
     763  
     764          $this->assertEmpty($event->description);
     765  
     766          // Change the allowsubmissionfromdate to the past - do this directly in the DB
     767          // because if we call the assignment update method - it will update the calendar
     768          // and we want to test that this works from cron.
     769          $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id' => $assign->get_instance()->id));
     770          // Run cron to update the event in the calendar.
     771          assign::cron();
     772          $event = $DB->get_record('event', $params);
     773  
     774          $this->assertStringContainsString('Some text', $event->description);
     775  
     776      }
     777  
     778      public function test_update_instance() {
     779          global $DB;
     780  
     781          $this->resetAfterTest();
     782  
     783          $course = $this->getDataGenerator()->create_course();
     784          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     785  
     786          $this->setUser($teacher);
     787          $assign = $this->create_instance($course, ['assignsubmission_onlinetext_enabled' => 1]);
     788  
     789          $now = time();
     790          $instance = $assign->get_instance();
     791          $instance->duedate = $now;
     792          $instance->instance = $instance->id;
     793          $instance->assignsubmission_onlinetext_enabled = 1;
     794  
     795          $assign->update_instance($instance);
     796  
     797          $instance = $DB->get_record('assign', ['id' => $assign->get_instance()->id]);
     798          $this->assertEquals($now, $instance->duedate);
     799      }
     800  
     801      public function test_cannot_submit_empty() {
     802          global $PAGE;
     803  
     804          $this->resetAfterTest();
     805  
     806          $course = $this->getDataGenerator()->create_course();
     807          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     808  
     809          $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
     810  
     811          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
     812  
     813          // Test you cannot see the submit button for an offline assignment regardless.
     814          $this->setUser($student);
     815          $output = $assign->view_student_summary($student, true);
     816          $this->assertStringNotContainsString(get_string('submitassignment', 'assign'),
     817              $output, 'Can submit empty offline assignment');
     818      }
     819  
     820      public function test_cannot_submit_empty_no_submission() {
     821          global $PAGE;
     822  
     823          $this->resetAfterTest();
     824  
     825          $course = $this->getDataGenerator()->create_course();
     826          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     827  
     828          $assign = $this->create_instance($course, [
     829              'submissiondrafts' => 1,
     830              'assignsubmission_onlinetext_enabled' => 1,
     831          ]);
     832  
     833          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
     834  
     835          // Test you cannot see the submit button for an online text assignment with no submission.
     836          $this->setUser($student);
     837          $output = $assign->view_student_summary($student, true);
     838          $this->assertStringNotContainsString(get_string('submitassignment', 'assign'),
     839              $output, 'Cannot submit empty onlinetext assignment');
     840      }
     841  
     842      public function test_can_submit_with_submission() {
     843          global $PAGE;
     844  
     845          $this->resetAfterTest();
     846  
     847          $course = $this->getDataGenerator()->create_course();
     848          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     849  
     850          $assign = $this->create_instance($course, [
     851              'submissiondrafts' => 1,
     852              'assignsubmission_onlinetext_enabled' => 1,
     853          ]);
     854  
     855          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
     856  
     857          // Add a draft.
     858          $this->add_submission($student, $assign);
     859  
     860          // Test you can see the submit button for an online text assignment with a submission.
     861          $this->setUser($student);
     862          $output = $assign->view_student_summary($student, true);
     863          $this->assertStringContainsString(get_string('submitassignment', 'assign'),
     864              $output, 'Can submit non empty onlinetext assignment');
     865      }
     866  
     867      /**
     868       * Test new_submission_empty
     869       *
     870       * We only test combinations of plugins here. Individual plugins are tested
     871       * in their respective test files.
     872       *
     873       * @dataProvider test_new_submission_empty_testcases
     874       * @param string $data The file submission data
     875       * @param bool $expected The expected return value
     876       */
     877      public function test_new_submission_empty($data, $expected) {
     878          $this->resetAfterTest();
     879  
     880          $course = $this->getDataGenerator()->create_course();
     881          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     882  
     883          $assign = $this->create_instance($course, [
     884                  'assignsubmission_file_enabled' => 1,
     885                  'assignsubmission_file_maxfiles' => 12,
     886                  'assignsubmission_file_maxsizebytes' => 10,
     887                  'assignsubmission_onlinetext_enabled' => 1,
     888              ]);
     889          $this->setUser($student);
     890          $submission = new stdClass();
     891  
     892          if ($data['file'] && isset($data['file']['filename'])) {
     893              $itemid = file_get_unused_draft_itemid();
     894              $submission->files_filemanager = $itemid;
     895              $data['file'] += ['contextid' => context_user::instance($student->id)->id, 'itemid' => $itemid];
     896              $fs = get_file_storage();
     897              $fs->create_file_from_string((object)$data['file'], 'Content of ' . $data['file']['filename']);
     898          }
     899  
     900          if ($data['onlinetext']) {
     901              $submission->onlinetext_editor = ['text' => $data['onlinetext']];
     902          }
     903  
     904          $result = $assign->new_submission_empty($submission);
     905          $this->assertTrue($result === $expected);
     906      }
     907  
     908      /**
     909       * Dataprovider for the test_new_submission_empty testcase
     910       *
     911       * @return array of testcases
     912       */
     913      public function test_new_submission_empty_testcases() {
     914          return [
     915              'With file and onlinetext' => [
     916                  [
     917                      'file' => [
     918                          'component' => 'user',
     919                          'filearea' => 'draft',
     920                          'filepath' => '/',
     921                          'filename' => 'not_a_virus.exe'
     922                      ],
     923                      'onlinetext' => 'Balin Fundinul Uzbadkhazaddumu'
     924                  ],
     925                  false
     926              ]
     927          ];
     928      }
     929  
     930      public function test_list_participants() {
     931          global $CFG;
     932  
     933          $this->resetAfterTest();
     934  
     935          $course = $this->getDataGenerator()->create_course();
     936          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     937  
     938          // Create 10 students.
     939          for ($i = 0; $i < 10; $i++) {
     940              $this->getDataGenerator()->create_and_enrol($course, 'student');
     941          }
     942  
     943          $this->setUser($teacher);
     944          $assign = $this->create_instance($course, ['grade' => 100]);
     945  
     946          $this->assertCount(10, $assign->list_participants(null, true));
     947      }
     948  
     949      public function test_list_participants_activeenrol() {
     950          global $CFG, $DB;
     951  
     952          $this->resetAfterTest();
     953  
     954          $course = $this->getDataGenerator()->create_course();
     955          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     956  
     957          // Create 10 students.
     958          for ($i = 0; $i < 10; $i++) {
     959              $this->getDataGenerator()->create_and_enrol($course, 'student');
     960          }
     961  
     962          // Create 10 suspended students.
     963          for ($i = 0; $i < 10; $i++) {
     964              $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
     965          }
     966  
     967          $this->setUser($teacher);
     968          set_user_preference('grade_report_showonlyactiveenrol', false);
     969          $assign = $this->create_instance($course, ['grade' => 100]);
     970  
     971          $this->assertCount(10, $assign->list_participants(null, true));
     972      }
     973  
     974      public function test_list_participants_with_group_restriction() {
     975          global $CFG;
     976  
     977          $this->resetAfterTest();
     978  
     979          $course = $this->getDataGenerator()->create_course();
     980          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
     981          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
     982          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
     983          $unrelatedstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
     984  
     985          // Turn on availability and a group restriction, and check that it doesn't show users who aren't in the group.
     986          $CFG->enableavailability = true;
     987  
     988          $specialgroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
     989          $assign = $this->create_instance($course, [
     990              'grade' => 100,
     991              'availability' => json_encode(
     992                  \core_availability\tree::get_root_json([\availability_group\condition::get_json($specialgroup->id)])
     993              ),
     994          ]);
     995  
     996          groups_add_member($specialgroup, $student);
     997          groups_add_member($specialgroup, $otherstudent);
     998          $this->assertEquals(2, count($assign->list_participants(null, true)));
     999      }
    1000  
    1001      public function test_get_participant_user_not_exist() {
    1002          $this->resetAfterTest();
    1003          $course = $this->getDataGenerator()->create_course();
    1004  
    1005          $assign = $this->create_instance($course);
    1006          $this->assertNull($assign->get_participant('-1'));
    1007      }
    1008  
    1009      public function test_get_participant_not_enrolled() {
    1010          $this->resetAfterTest();
    1011          $course = $this->getDataGenerator()->create_course();
    1012          $assign = $this->create_instance($course);
    1013  
    1014          $user = $this->getDataGenerator()->create_user();
    1015          $this->assertNull($assign->get_participant($user->id));
    1016      }
    1017  
    1018      public function test_get_participant_no_submission() {
    1019          $this->resetAfterTest();
    1020          $course = $this->getDataGenerator()->create_course();
    1021          $assign = $this->create_instance($course);
    1022          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1023  
    1024          $participant = $assign->get_participant($student->id);
    1025  
    1026          $this->assertEquals($student->id, $participant->id);
    1027          $this->assertFalse($participant->submitted);
    1028          $this->assertFalse($participant->requiregrading);
    1029          $this->assertFalse($participant->grantedextension);
    1030      }
    1031  
    1032      public function test_get_participant_granted_extension() {
    1033          $this->resetAfterTest();
    1034          $course = $this->getDataGenerator()->create_course();
    1035          $assign = $this->create_instance($course);
    1036          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1037          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1038  
    1039          $this->setUser($teacher);
    1040          $assign->save_user_extension($student->id, time());
    1041          $participant = $assign->get_participant($student->id);
    1042  
    1043          $this->assertEquals($student->id, $participant->id);
    1044          $this->assertFalse($participant->submitted);
    1045          $this->assertFalse($participant->requiregrading);
    1046          $this->assertTrue($participant->grantedextension);
    1047      }
    1048  
    1049      public function test_get_participant_with_ungraded_submission() {
    1050          $this->resetAfterTest();
    1051          $course = $this->getDataGenerator()->create_course();
    1052          $assign = $this->create_instance($course);
    1053          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1054          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1055  
    1056          // Simulate a submission.
    1057          $this->add_submission($student, $assign);
    1058          $this->submit_for_grading($student, $assign);
    1059  
    1060          $participant = $assign->get_participant($student->id);
    1061  
    1062          $this->assertEquals($student->id, $participant->id);
    1063          $this->assertTrue($participant->submitted);
    1064          $this->assertTrue($participant->requiregrading);
    1065          $this->assertFalse($participant->grantedextension);
    1066      }
    1067  
    1068      /**
    1069       * Tests that if a student with no submission who can no longer submit is not a participant.
    1070       */
    1071      public function test_get_participant_with_no_submission_no_capability() {
    1072          global $DB;
    1073          $this->resetAfterTest();
    1074          $course = self::getDataGenerator()->create_course();
    1075          $coursecontext = context_course::instance($course->id);
    1076          $assign = $this->create_instance($course);
    1077          $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher');
    1078          $student = self::getDataGenerator()->create_and_enrol($course, 'student');
    1079  
    1080          // Remove the students capability to submit.
    1081          $role = $DB->get_field('role', 'id', ['shortname' => 'student']);
    1082          assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext);
    1083  
    1084          $participant = $assign->get_participant($student->id);
    1085  
    1086          self::assertNull($participant);
    1087      }
    1088  
    1089      /**
    1090       * Tests that if a student that has submitted but can no longer submit is a participant.
    1091       */
    1092      public function test_get_participant_with_submission_no_capability() {
    1093          global $DB;
    1094          $this->resetAfterTest();
    1095          $course = self::getDataGenerator()->create_course();
    1096          $coursecontext = context_course::instance($course->id);
    1097          $assign = $this->create_instance($course);
    1098          $teacher = self::getDataGenerator()->create_and_enrol($course, 'teacher');
    1099          $student = self::getDataGenerator()->create_and_enrol($course, 'student');
    1100  
    1101          // Simulate a submission.
    1102          $this->add_submission($student, $assign);
    1103          $this->submit_for_grading($student, $assign);
    1104  
    1105          // Remove the students capability to submit.
    1106          $role = $DB->get_field('role', 'id', ['shortname' => 'student']);
    1107          assign_capability('mod/assign:submit', CAP_PROHIBIT, $role, $coursecontext);
    1108  
    1109          $participant = $assign->get_participant($student->id);
    1110  
    1111          self::assertNotNull($participant);
    1112          self::assertEquals($student->id, $participant->id);
    1113          self::assertTrue($participant->submitted);
    1114          self::assertTrue($participant->requiregrading);
    1115          self::assertFalse($participant->grantedextension);
    1116      }
    1117  
    1118      public function test_get_participant_with_graded_submission() {
    1119          $this->resetAfterTest();
    1120          $course = $this->getDataGenerator()->create_course();
    1121          $assign = $this->create_instance($course);
    1122          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1123          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1124  
    1125          // Simulate a submission.
    1126          $this->add_submission($student, $assign);
    1127          $this->submit_for_grading($student, $assign);
    1128  
    1129          $this->mark_submission($teacher, $assign, $student, 50.0);
    1130  
    1131          $data = new stdClass();
    1132          $data->grade = '50.0';
    1133          $assign->testable_apply_grade_to_user($data, $student->id, 0);
    1134  
    1135          $participant = $assign->get_participant($student->id);
    1136  
    1137          $this->assertEquals($student->id, $participant->id);
    1138          $this->assertTrue($participant->submitted);
    1139          $this->assertFalse($participant->requiregrading);
    1140          $this->assertFalse($participant->grantedextension);
    1141      }
    1142  
    1143      /**
    1144       * No active group and non-group submissions disallowed => 2 groups.
    1145       */
    1146      public function test_count_teams_no_active_non_group_allowed() {
    1147          $this->resetAfterTest();
    1148  
    1149          $course = $this->getDataGenerator()->create_course();
    1150          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1151          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1152          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1153  
    1154          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    1155          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1156          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1157          groups_add_member($group1, $student1);
    1158          groups_add_member($group2, $student2);
    1159  
    1160          $this->setUser($teacher);
    1161          $assign = $this->create_instance($course, ['teamsubmission' => 1]);
    1162  
    1163          $this->assertEquals(2, $assign->count_teams());
    1164      }
    1165  
    1166      /**
    1167       * No active group and non group submissions allowed => 2 groups + the default one.
    1168       */
    1169      public function test_count_teams_non_group_allowed() {
    1170          $this->resetAfterTest();
    1171  
    1172          $course = $this->getDataGenerator()->create_course();
    1173          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1174          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1175          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1176          $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1177  
    1178          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    1179          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1180  
    1181          $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
    1182          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1183          $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
    1184  
    1185          groups_add_member($group1, $student1);
    1186          groups_add_member($group2, $student2);
    1187  
    1188          $assign = $this->create_instance($course, [
    1189              'teamsubmission' => 1,
    1190              'teamsubmissiongroupingid' => $grouping->id,
    1191              'preventsubmissionnotingroup' => false,
    1192          ]);
    1193  
    1194          $this->setUser($teacher);
    1195          $this->assertEquals(3, $assign->count_teams());
    1196  
    1197          // Active group only.
    1198          $this->assertEquals(1, $assign->count_teams($group1->id));
    1199          $this->assertEquals(1, $assign->count_teams($group2->id));
    1200      }
    1201  
    1202      /**
    1203       * Active group => just selected one.
    1204       */
    1205      public function test_count_teams_no_active_group() {
    1206          $this->resetAfterTest();
    1207  
    1208          $course = $this->getDataGenerator()->create_course();
    1209          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1210          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1211          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1212          $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1213  
    1214          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    1215          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1216  
    1217          $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
    1218          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1219          $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
    1220  
    1221          groups_add_member($group1, $student1);
    1222          groups_add_member($group2, $student2);
    1223  
    1224          $assign = $this->create_instance($course, [
    1225              'teamsubmission' => 1,
    1226              'preventsubmissionnotingroup' => true,
    1227          ]);
    1228  
    1229          $this->setUser($teacher);
    1230          $this->assertEquals(2, $assign->count_teams());
    1231  
    1232          // Active group only.
    1233          $this->assertEquals(1, $assign->count_teams($group1->id));
    1234          $this->assertEquals(1, $assign->count_teams($group2->id));
    1235      }
    1236  
    1237      /**
    1238       * Active group => just selected one.
    1239       */
    1240      public function test_count_teams_groups_only() {
    1241          $this->resetAfterTest();
    1242  
    1243          $course = $this->getDataGenerator()->create_course();
    1244          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    1245  
    1246          $assign = $this->create_instance($course, [
    1247              'teamsubmission' => 1,
    1248              'teamsubmissiongroupingid' => $grouping->id,
    1249              'preventsubmissionnotingroup' => false,
    1250          ]);
    1251          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1252  
    1253          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1254          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1255          groups_add_member($group1, $student1);
    1256  
    1257          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1258          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1259          groups_add_member($group2, $student2);
    1260  
    1261          $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
    1262          $this->getDataGenerator()->create_grouping_group(array('groupid' => $group2->id, 'groupingid' => $grouping->id));
    1263  
    1264          $this->setUser($teacher);
    1265  
    1266          $assign = $this->create_instance($course, [
    1267              'teamsubmission' => 1,
    1268              'preventsubmissionnotingroup' => true,
    1269          ]);
    1270          $this->assertEquals(2, $assign->count_teams());
    1271      }
    1272  
    1273      public function test_submit_to_default_group() {
    1274          global $DB, $SESSION;
    1275  
    1276          $this->resetAfterTest();
    1277  
    1278          $course = $this->getDataGenerator()->create_course();
    1279          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1280          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1281  
    1282          $grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
    1283          $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1284  
    1285          $assign = $this->create_instance($course, [
    1286              'teamsubmission' => 1,
    1287              'assignsubmission_onlinetext_enabled' => 1,
    1288              'submissiondrafts' => 0,
    1289              'groupmode' => VISIBLEGROUPS,
    1290          ]);
    1291  
    1292          $usergroup = $assign->get_submission_group($student->id);
    1293          $this->assertFalse($usergroup, 'New student is in default group');
    1294  
    1295          // Add a submission.
    1296          $this->add_submission($student, $assign);
    1297          $this->submit_for_grading($student, $assign);
    1298  
    1299          // Set active groups to all groups.
    1300          $this->setUser($teacher);
    1301          $SESSION->activegroup[$course->id]['aag'][0] = 0;
    1302          $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1303  
    1304          // Set an active group.
    1305          $SESSION->activegroup[$course->id]['aag'][0] = (int) $group->id;
    1306          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1307      }
    1308  
    1309      public function test_count_submissions_no_draft() {
    1310          $this->resetAfterTest();
    1311  
    1312          $course = $this->getDataGenerator()->create_course();
    1313          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1314          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1315  
    1316          $assign = $this->create_instance($course, [
    1317              'assignsubmission_onlinetext_enabled' => 1,
    1318          ]);
    1319  
    1320          $assign->get_user_submission($student->id, true);
    1321  
    1322          // Note: Drafts count as a submission.
    1323          $this->assertEquals(0, $assign->count_grades());
    1324          $this->assertEquals(0, $assign->count_submissions());
    1325          $this->assertEquals(1, $assign->count_submissions(true));
    1326          $this->assertEquals(0, $assign->count_submissions_need_grading());
    1327          $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
    1328          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
    1329          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1330          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
    1331      }
    1332  
    1333      public function test_count_submissions_draft() {
    1334          $this->resetAfterTest();
    1335  
    1336          $course = $this->getDataGenerator()->create_course();
    1337          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1338          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1339  
    1340          $assign = $this->create_instance($course, [
    1341              'assignsubmission_onlinetext_enabled' => 1,
    1342          ]);
    1343  
    1344          $this->add_submission($student, $assign);
    1345  
    1346          // Note: Drafts count as a submission.
    1347          $this->assertEquals(0, $assign->count_grades());
    1348          $this->assertEquals(1, $assign->count_submissions());
    1349          $this->assertEquals(1, $assign->count_submissions(true));
    1350          $this->assertEquals(0, $assign->count_submissions_need_grading());
    1351          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
    1352          $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
    1353          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1354          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
    1355      }
    1356  
    1357      public function test_count_submissions_submitted() {
    1358          global $SESSION;
    1359  
    1360          $this->resetAfterTest();
    1361  
    1362          $course = $this->getDataGenerator()->create_course();
    1363          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1364          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1365  
    1366          $assign = $this->create_instance($course, [
    1367              'assignsubmission_onlinetext_enabled' => 1,
    1368          ]);
    1369  
    1370          $this->add_submission($student, $assign);
    1371          $this->submit_for_grading($student, $assign);
    1372  
    1373          $this->assertEquals(0, $assign->count_grades());
    1374          $this->assertEquals(1, $assign->count_submissions());
    1375          $this->assertEquals(1, $assign->count_submissions(true));
    1376          $this->assertEquals(1, $assign->count_submissions_need_grading());
    1377          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
    1378          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
    1379          $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1380          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
    1381      }
    1382  
    1383      public function test_count_submissions_graded() {
    1384          $this->resetAfterTest();
    1385  
    1386          $course = $this->getDataGenerator()->create_course();
    1387          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1388          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1389  
    1390          $assign = $this->create_instance($course, [
    1391              'assignsubmission_onlinetext_enabled' => 1,
    1392          ]);
    1393  
    1394          $this->add_submission($student, $assign);
    1395          $this->submit_for_grading($student, $assign);
    1396          $this->mark_submission($teacher, $assign, $student, 50.0);
    1397  
    1398          // Although it has been graded, it is still marked as submitted.
    1399          $this->assertEquals(1, $assign->count_grades());
    1400          $this->assertEquals(1, $assign->count_submissions());
    1401          $this->assertEquals(1, $assign->count_submissions(true));
    1402          $this->assertEquals(0, $assign->count_submissions_need_grading());
    1403          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_NEW));
    1404          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
    1405          $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1406          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_REOPENED));
    1407      }
    1408  
    1409      public function test_count_submissions_graded_group() {
    1410          global $SESSION;
    1411  
    1412          $this->resetAfterTest();
    1413  
    1414          $course = $this->getDataGenerator()->create_course();
    1415          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1416          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1417          $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1418          $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1419          groups_add_member($group, $student);
    1420  
    1421          $assign = $this->create_instance($course, [
    1422              'assignsubmission_onlinetext_enabled' => 1,
    1423              'groupmode' => VISIBLEGROUPS,
    1424          ]);
    1425  
    1426          $this->add_submission($student, $assign);
    1427          $this->submit_for_grading($student, $assign);
    1428  
    1429          // The user should still be listed when fetching all groups.
    1430          $this->setUser($teacher);
    1431          $SESSION->activegroup[$course->id]['aag'][0] = 0;
    1432          $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1433  
    1434          // The user should still be listed when fetching just their group.
    1435          $SESSION->activegroup[$course->id]['aag'][0] = $group->id;
    1436          $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1437  
    1438          // The user should still be listed when fetching just their group.
    1439          $SESSION->activegroup[$course->id]['aag'][0] = $othergroup->id;
    1440          $this->assertEquals(0, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1441      }
    1442  
    1443      // TODO
    1444      public function x_test_count_submissions_for_team() {
    1445          $this->resetAfterTest();
    1446  
    1447          $course = $this->getDataGenerator()->create_course();
    1448          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1449          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1450          $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1451          $othergroup = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1452          groups_add_member($group, $student);
    1453  
    1454          $assign = $this->create_instance($course, [
    1455              'assignsubmission_onlinetext_enabled' => 1,
    1456              'teamsubmission' => 1,
    1457          ]);
    1458  
    1459          // Add a graded submission.
    1460          $this->add_submission($student, $assign);
    1461  
    1462          // Simulate adding a grade.
    1463          $this->setUser($teacher);
    1464          $data = new stdClass();
    1465          $data->grade = '50.0';
    1466          $assign->testable_apply_grade_to_user($data, $this->extrastudents[0]->id, 0);
    1467  
    1468          // Simulate a submission.
    1469          $this->setUser($this->extrastudents[1]);
    1470          $submission = $assign->get_group_submission($this->extrastudents[1]->id, $groupid, true);
    1471          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
    1472          $assign->testable_update_submission($submission, $this->extrastudents[1]->id, true, false);
    1473          $data = new stdClass();
    1474          $data->onlinetext_editor = array(
    1475              'itemid' => file_get_unused_draft_itemid(),
    1476              'text' => 'Submission text',
    1477              'format' => FORMAT_MOODLE);
    1478          $plugin = $assign->get_submission_plugin_by_type('onlinetext');
    1479          $plugin->save($submission, $data);
    1480  
    1481          // Simulate a submission.
    1482          $this->setUser($this->extrastudents[2]);
    1483          $submission = $assign->get_group_submission($this->extrastudents[2]->id, $groupid, true);
    1484          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
    1485          $assign->testable_update_submission($submission, $this->extrastudents[2]->id, true, false);
    1486          $data = new stdClass();
    1487          $data->onlinetext_editor = array(
    1488              'itemid' => file_get_unused_draft_itemid(),
    1489              'text' => 'Submission text',
    1490              'format' => FORMAT_MOODLE);
    1491          $plugin = $assign->get_submission_plugin_by_type('onlinetext');
    1492          $plugin->save($submission, $data);
    1493  
    1494          // Simulate a submission.
    1495          $this->setUser($this->extrastudents[3]);
    1496          $submission = $assign->get_group_submission($this->extrastudents[3]->id, $groupid, true);
    1497          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
    1498          $assign->testable_update_submission($submission, $this->extrastudents[3]->id, true, false);
    1499          $data = new stdClass();
    1500          $data->onlinetext_editor = array(
    1501              'itemid' => file_get_unused_draft_itemid(),
    1502              'text' => 'Submission text',
    1503              'format' => FORMAT_MOODLE);
    1504          $plugin = $assign->get_submission_plugin_by_type('onlinetext');
    1505          $plugin->save($submission, $data);
    1506  
    1507          // Simulate adding a grade.
    1508          $this->setUser($teacher);
    1509          $data = new stdClass();
    1510          $data->grade = '50.0';
    1511          $assign->testable_apply_grade_to_user($data, $this->extrastudents[3]->id, 0);
    1512          $assign->testable_apply_grade_to_user($data, $this->extrasuspendedstudents[0]->id, 0);
    1513  
    1514          // Create a new submission with status NEW.
    1515          $this->setUser($this->extrastudents[4]);
    1516          $submission = $assign->get_group_submission($this->extrastudents[4]->id, $groupid, true);
    1517  
    1518          $this->assertEquals(2, $assign->count_grades());
    1519          $this->assertEquals(4, $assign->count_submissions());
    1520          $this->assertEquals(5, $assign->count_submissions(true));
    1521          $this->assertEquals(3, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED));
    1522          $this->assertEquals(1, $assign->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT));
    1523      }
    1524  
    1525      public function test_get_grading_userid_list_only_active() {
    1526          $this->resetAfterTest();
    1527  
    1528          $course = $this->getDataGenerator()->create_course();
    1529          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1530          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1531          $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
    1532              $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
    1533  
    1534          $this->setUser($teacher);
    1535  
    1536          $assign = $this->create_instance($course);
    1537          $this->assertCount(1, $assign->testable_get_grading_userid_list());
    1538      }
    1539  
    1540      public function test_get_grading_userid_list_all() {
    1541          $this->resetAfterTest();
    1542  
    1543          $course = $this->getDataGenerator()->create_course();
    1544          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1545          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1546          $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
    1547              $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
    1548  
    1549          $this->setUser($teacher);
    1550          set_user_preference('grade_report_showonlyactiveenrol', false);
    1551  
    1552          $assign = $this->create_instance($course);
    1553          $this->assertCount(2, $assign->testable_get_grading_userid_list());
    1554      }
    1555  
    1556      public function test_cron() {
    1557          global $PAGE;
    1558          $this->resetAfterTest();
    1559  
    1560          // First run cron so there are no messages waiting to be sent (from other tests).
    1561          cron_setup_user();
    1562          assign::cron();
    1563  
    1564          $course = $this->getDataGenerator()->create_course();
    1565          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1566          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1567  
    1568          // Now create an assignment and add some feedback.
    1569          $this->setUser($teacher);
    1570          $assign = $this->create_instance($course, [
    1571              'sendstudentnotifications' => 1,
    1572          ]);
    1573  
    1574          $this->add_submission($student, $assign);
    1575          $this->submit_for_grading($student, $assign);
    1576          $this->mark_submission($teacher, $assign, $student, 50.0);
    1577  
    1578          $this->expectOutputRegex('/Done processing 1 assignment submissions/');
    1579          cron_setup_user();
    1580          $sink = $this->redirectMessages();
    1581          assign::cron();
    1582          $messages = $sink->get_messages();
    1583  
    1584          $this->assertEquals(1, count($messages));
    1585          $this->assertEquals(1, $messages[0]->notification);
    1586          $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
    1587          // Test customdata.
    1588          $customdata = json_decode($messages[0]->customdata);
    1589          $this->assertEquals($assign->get_course_module()->id, $customdata->cmid);
    1590          $this->assertEquals($assign->get_instance()->id, $customdata->instance);
    1591          $this->assertEquals('feedbackavailable', $customdata->messagetype);
    1592          $userpicture = new user_picture($teacher);
    1593          $userpicture->size = 1; // Use f1 size.
    1594          $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl);
    1595          $this->assertEquals(0, $customdata->uniqueidforuser);   // Not used in this case.
    1596          $this->assertFalse($customdata->blindmarking);
    1597      }
    1598  
    1599      public function test_cron_without_notifications() {
    1600          $this->resetAfterTest();
    1601  
    1602          // First run cron so there are no messages waiting to be sent (from other tests).
    1603          cron_setup_user();
    1604          assign::cron();
    1605  
    1606          $course = $this->getDataGenerator()->create_course();
    1607          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1608          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1609  
    1610          // Now create an assignment and add some feedback.
    1611          $this->setUser($teacher);
    1612          $assign = $this->create_instance($course, [
    1613              'sendstudentnotifications' => 1,
    1614          ]);
    1615  
    1616          $this->add_submission($student, $assign);
    1617          $this->submit_for_grading($student, $assign);
    1618          $this->mark_submission($teacher, $assign, $student, 50.0, [
    1619              'sendstudentnotifications' => 0,
    1620          ]);
    1621  
    1622          cron_setup_user();
    1623          $sink = $this->redirectMessages();
    1624          assign::cron();
    1625          $messages = $sink->get_messages();
    1626  
    1627          $this->assertEquals(0, count($messages));
    1628      }
    1629  
    1630      public function test_cron_regraded() {
    1631          $this->resetAfterTest();
    1632  
    1633          // First run cron so there are no messages waiting to be sent (from other tests).
    1634          cron_setup_user();
    1635          assign::cron();
    1636  
    1637          $course = $this->getDataGenerator()->create_course();
    1638          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1639          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1640  
    1641          // Now create an assignment and add some feedback.
    1642          $this->setUser($teacher);
    1643          $assign = $this->create_instance($course, [
    1644              'sendstudentnotifications' => 1,
    1645          ]);
    1646  
    1647          $this->add_submission($student, $assign);
    1648          $this->submit_for_grading($student, $assign);
    1649          $this->mark_submission($teacher, $assign, $student, 50.0);
    1650  
    1651          $this->expectOutputRegex('/Done processing 1 assignment submissions/');
    1652          cron_setup_user();
    1653          assign::cron();
    1654  
    1655          // Regrade.
    1656          $this->mark_submission($teacher, $assign, $student, 50.0);
    1657  
    1658          $this->expectOutputRegex('/Done processing 1 assignment submissions/');
    1659          cron_setup_user();
    1660          $sink = $this->redirectMessages();
    1661          assign::cron();
    1662          $messages = $sink->get_messages();
    1663  
    1664          $this->assertEquals(1, count($messages));
    1665          $this->assertEquals(1, $messages[0]->notification);
    1666          $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
    1667      }
    1668  
    1669      /**
    1670       * Test delivery of grade notifications as controlled by marking workflow.
    1671       */
    1672      public function test_markingworkflow_cron() {
    1673          $this->resetAfterTest();
    1674  
    1675          // First run cron so there are no messages waiting to be sent (from other tests).
    1676          cron_setup_user();
    1677          assign::cron();
    1678  
    1679          $course = $this->getDataGenerator()->create_course();
    1680          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1681          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1682  
    1683          // Now create an assignment and add some feedback.
    1684          $this->setUser($teacher);
    1685          $assign = $this->create_instance($course, [
    1686              'sendstudentnotifications' => 1,
    1687              'markingworkflow' => 1,
    1688          ]);
    1689  
    1690          // Mark a submission but set the workflowstate to an unreleased state.
    1691          // This should not trigger a notification.
    1692          $this->add_submission($student, $assign);
    1693          $this->submit_for_grading($student, $assign);
    1694          $this->mark_submission($teacher, $assign, $student, 50.0, [
    1695              'sendstudentnotifications' => 1,
    1696              'workflowstate' => ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE,
    1697          ]);
    1698  
    1699          cron_setup_user();
    1700          $sink = $this->redirectMessages();
    1701          assign::cron();
    1702          $messages = $sink->get_messages();
    1703  
    1704          $this->assertEquals(0, count($messages));
    1705  
    1706          // Transition to the released state.
    1707          $this->setUser($teacher);
    1708          $submission = $assign->get_user_submission($student->id, true);
    1709          $submission->workflowstate = ASSIGN_MARKING_WORKFLOW_STATE_RELEASED;
    1710          $assign->testable_apply_grade_to_user($submission, $student->id, 0);
    1711  
    1712          // Now run cron and see that one message was sent.
    1713          cron_setup_user();
    1714          $sink = $this->redirectMessages();
    1715          $this->expectOutputRegex('/Done processing 1 assignment submissions/');
    1716          assign::cron();
    1717          $messages = $sink->get_messages();
    1718  
    1719          $this->assertEquals(1, count($messages));
    1720          $this->assertEquals(1, $messages[0]->notification);
    1721          $this->assertEquals($assign->get_instance()->name, $messages[0]->contexturlname);
    1722      }
    1723  
    1724      public function test_cron_message_includes_courseid() {
    1725          $this->resetAfterTest();
    1726  
    1727          // First run cron so there are no messages waiting to be sent (from other tests).
    1728          cron_setup_user();
    1729          assign::cron();
    1730  
    1731          $course = $this->getDataGenerator()->create_course();
    1732          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1733          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1734  
    1735          // Now create an assignment and add some feedback.
    1736          $this->setUser($teacher);
    1737          $assign = $this->create_instance($course, [
    1738              'sendstudentnotifications' => 1,
    1739          ]);
    1740  
    1741          // Mark a submission but set the workflowstate to an unreleased state.
    1742          // This should not trigger a notification.
    1743          $this->add_submission($student, $assign);
    1744          $this->submit_for_grading($student, $assign);
    1745          $this->mark_submission($teacher, $assign, $student);
    1746          phpunit_util::stop_message_redirection();
    1747  
    1748          // Now run cron and see that one message was sent.
    1749          cron_setup_user();
    1750          $this->preventResetByRollback();
    1751          $sink = $this->redirectEvents();
    1752          $this->expectOutputRegex('/Done processing 1 assignment submissions/');
    1753          assign::cron();
    1754  
    1755          $events = $sink->get_events();
    1756          $event = reset($events);
    1757          $this->assertInstanceOf('\core\event\notification_sent', $event);
    1758          $this->assertEquals($assign->get_course()->id, $event->other['courseid']);
    1759          $sink->close();
    1760      }
    1761  
    1762      public function test_is_graded() {
    1763          $this->resetAfterTest();
    1764  
    1765          $course = $this->getDataGenerator()->create_course();
    1766          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1767          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1768          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1769  
    1770          $assign = $this->create_instance($course);
    1771  
    1772          $this->add_submission($student, $assign);
    1773          $this->submit_for_grading($student, $assign);
    1774          $this->mark_submission($teacher, $assign, $student, 50.0);
    1775  
    1776          $this->setUser($teacher);
    1777          $this->assertEquals(true, $assign->testable_is_graded($student->id));
    1778          $this->assertEquals(false, $assign->testable_is_graded($otherstudent->id));
    1779      }
    1780  
    1781      public function test_can_grade() {
    1782          global $DB;
    1783  
    1784          $this->resetAfterTest();
    1785  
    1786          $course = $this->getDataGenerator()->create_course();
    1787          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1788          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1789  
    1790          $assign = $this->create_instance($course);
    1791  
    1792          $this->setUser($student);
    1793          $this->assertEquals(false, $assign->can_grade());
    1794  
    1795          $this->setUser($teacher);
    1796          $this->assertEquals(true, $assign->can_grade());
    1797  
    1798          // Test the viewgrades capability for other users.
    1799          $this->setUser();
    1800          $this->assertTrue($assign->can_grade($teacher->id));
    1801          $this->assertFalse($assign->can_grade($student->id));
    1802  
    1803          // Test the viewgrades capability - without mod/assign:grade.
    1804          $this->setUser($student);
    1805  
    1806          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
    1807          assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
    1808          $this->assertEquals(false, $assign->can_grade());
    1809      }
    1810  
    1811      public function test_can_view_submission() {
    1812          global $DB;
    1813  
    1814          $this->resetAfterTest();
    1815  
    1816          $course = $this->getDataGenerator()->create_course();
    1817          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1818          $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    1819          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1820          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1821          $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
    1822              $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
    1823  
    1824          $assign = $this->create_instance($course);
    1825  
    1826          $this->setUser($student);
    1827          $this->assertEquals(true, $assign->can_view_submission($student->id));
    1828          $this->assertEquals(false, $assign->can_view_submission($otherstudent->id));
    1829          $this->assertEquals(false, $assign->can_view_submission($teacher->id));
    1830  
    1831          $this->setUser($teacher);
    1832          $this->assertEquals(true, $assign->can_view_submission($student->id));
    1833          $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
    1834          $this->assertEquals(true, $assign->can_view_submission($teacher->id));
    1835          $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
    1836  
    1837          $this->setUser($editingteacher);
    1838          $this->assertEquals(true, $assign->can_view_submission($student->id));
    1839          $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
    1840          $this->assertEquals(true, $assign->can_view_submission($teacher->id));
    1841          $this->assertEquals(true, $assign->can_view_submission($suspendedstudent->id));
    1842  
    1843          // Test the viewgrades capability - without mod/assign:grade.
    1844          $this->setUser($student);
    1845          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
    1846          assign_capability('mod/assign:viewgrades', CAP_ALLOW, $studentrole->id, $assign->get_context()->id);
    1847          $this->assertEquals(true, $assign->can_view_submission($student->id));
    1848          $this->assertEquals(true, $assign->can_view_submission($otherstudent->id));
    1849          $this->assertEquals(true, $assign->can_view_submission($teacher->id));
    1850          $this->assertEquals(false, $assign->can_view_submission($suspendedstudent->id));
    1851      }
    1852  
    1853      public function test_update_submission() {
    1854          $this->resetAfterTest();
    1855  
    1856          $course = $this->getDataGenerator()->create_course();
    1857          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1858          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1859  
    1860          $assign = $this->create_instance($course);
    1861  
    1862          $this->add_submission($student, $assign);
    1863          $submission = $assign->get_user_submission($student->id, 0);
    1864          $assign->testable_update_submission($submission, $student->id, true, true);
    1865  
    1866          $this->setUser($teacher);
    1867  
    1868          // Verify the gradebook update.
    1869          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
    1870  
    1871          $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
    1872          $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
    1873      }
    1874  
    1875      public function test_update_submission_team() {
    1876          $this->resetAfterTest();
    1877  
    1878          $course = $this->getDataGenerator()->create_course();
    1879          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1880          $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1881  
    1882          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1883          groups_add_member($group, $student);
    1884  
    1885          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1886          groups_add_member($group, $otherstudent);
    1887  
    1888          $assign = $this->create_instance($course, [
    1889              'teamsubmission' => 1,
    1890          ]);
    1891  
    1892          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
    1893          $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
    1894          $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
    1895  
    1896          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
    1897          $this->asserttrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
    1898          $this->assertNull($gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
    1899  
    1900          $this->add_submission($student, $assign);
    1901          $submission = $assign->get_group_submission($student->id, 0, true);
    1902          $assign->testable_update_submission($submission, $student->id, true, true);
    1903  
    1904          // Verify the gradebook update for the student.
    1905          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
    1906  
    1907          $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
    1908          $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
    1909  
    1910          // Verify the gradebook update for the other student.
    1911          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $otherstudent->id);
    1912  
    1913          $this->assertTrue(isset($gradinginfo->items[0]->grades[$otherstudent->id]));
    1914          $this->assertEquals($otherstudent->id, $gradinginfo->items[0]->grades[$otherstudent->id]->usermodified);
    1915      }
    1916  
    1917      public function test_update_submission_suspended() {
    1918          $this->resetAfterTest();
    1919  
    1920          $course = $this->getDataGenerator()->create_course();
    1921          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1922          $student = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
    1923  
    1924          $assign = $this->create_instance($course);
    1925  
    1926          $this->add_submission($student, $assign);
    1927          $submission = $assign->get_user_submission($student->id, 0);
    1928          $assign->testable_update_submission($submission, $student->id, true, false);
    1929  
    1930          $this->setUser($teacher);
    1931  
    1932          // Verify the gradebook update.
    1933          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
    1934  
    1935          $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
    1936          $this->assertEquals($student->id, $gradinginfo->items[0]->grades[$student->id]->usermodified);
    1937      }
    1938  
    1939      public function test_update_submission_blind() {
    1940          $this->resetAfterTest();
    1941  
    1942          $course = $this->getDataGenerator()->create_course();
    1943          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1944          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1945  
    1946          $assign = $this->create_instance($course, [
    1947              'blindmarking' => 1,
    1948          ]);
    1949  
    1950          $this->add_submission($student, $assign);
    1951          $submission = $assign->get_user_submission($student->id, 0);
    1952          $assign->testable_update_submission($submission, $student->id, true, false);
    1953  
    1954          // Verify the gradebook update.
    1955          $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $assign->get_instance()->id, $student->id);
    1956  
    1957          // The usermodified is not set because this is blind marked.
    1958          $this->assertTrue(isset($gradinginfo->items[0]->grades[$student->id]));
    1959          $this->assertNull($gradinginfo->items[0]->grades[$student->id]->usermodified);
    1960      }
    1961  
    1962      public function test_group_submissions_submit_for_marking_requireallteammemberssubmit() {
    1963          global $PAGE;
    1964  
    1965          $this->resetAfterTest();
    1966  
    1967          $course = $this->getDataGenerator()->create_course();
    1968          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    1969          $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    1970  
    1971          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1972          groups_add_member($group, $student);
    1973  
    1974          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    1975          groups_add_member($group, $otherstudent);
    1976  
    1977          $assign = $this->create_instance($course, [
    1978              'teamsubmission' => 1,
    1979              'assignsubmission_onlinetext_enabled' => 1,
    1980              'submissiondrafts' => 1,
    1981              'requireallteammemberssubmit' => 1,
    1982          ]);
    1983  
    1984          // Now verify group assignments.
    1985          $this->setUser($teacher);
    1986          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
    1987  
    1988          // Add a submission.
    1989          $this->add_submission($student, $assign);
    1990  
    1991          // Check we can see the submit button.
    1992          $this->setUser($student);
    1993          $output = $assign->view_student_summary($student, true);
    1994          $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
    1995  
    1996          $submission = $assign->get_group_submission($student->id, 0, true);
    1997          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
    1998          $assign->testable_update_submission($submission, $student->id, true, true);
    1999  
    2000          // Check that the student does not see "Submit" button.
    2001          $output = $assign->view_student_summary($student, true);
    2002          $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
    2003  
    2004          // Change to another user in the same group.
    2005          $this->setUser($otherstudent);
    2006          $output = $assign->view_student_summary($otherstudent, true);
    2007          $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
    2008  
    2009          $submission = $assign->get_group_submission($otherstudent->id, 0, true);
    2010          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
    2011          $assign->testable_update_submission($submission, $otherstudent->id, true, true);
    2012          $output = $assign->view_student_summary($otherstudent, true);
    2013          $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
    2014      }
    2015  
    2016      public function test_group_submissions_submit_for_marking() {
    2017          global $PAGE;
    2018  
    2019          $this->resetAfterTest();
    2020  
    2021          $course = $this->getDataGenerator()->create_course();
    2022          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2023          $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    2024  
    2025          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2026          groups_add_member($group, $student);
    2027  
    2028          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2029          groups_add_member($group, $otherstudent);
    2030  
    2031          // Now verify group assignments.
    2032          $this->setUser($teacher);
    2033          $time = time();
    2034          $assign = $this->create_instance($course, [
    2035              'teamsubmission' => 1,
    2036              'assignsubmission_onlinetext_enabled' => 1,
    2037              'submissiondrafts' => 1,
    2038              'requireallteammemberssubmit' => 0,
    2039              'duedate' => $time - (2 * DAYSECS),
    2040          ]);
    2041          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
    2042  
    2043          // Add a submission.
    2044          $this->add_submission($student, $assign);
    2045  
    2046          // Check we can see the submit button.
    2047          $output = $assign->view_student_summary($student, true);
    2048          $this->assertStringContainsString(get_string('submitassignment', 'assign'), $output);
    2049          $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
    2050          $difftime = time() - $time;
    2051          $this->assertStringContainsString(get_string('overdue', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
    2052  
    2053          $submission = $assign->get_group_submission($student->id, 0, true);
    2054          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
    2055          $assign->testable_update_submission($submission, $student->id, true, true);
    2056  
    2057          // Check that the student does not see "Submit" button.
    2058          $output = $assign->view_student_summary($student, true);
    2059          $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
    2060  
    2061          // Change to another user in the same group.
    2062          $this->setUser($otherstudent);
    2063          $output = $assign->view_student_summary($otherstudent, true);
    2064          $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
    2065  
    2066          // Check that time remaining is not overdue.
    2067          $this->assertStringContainsString(get_string('timeremaining', 'assign'), $output);
    2068          $difftime = time() - $time;
    2069          $this->assertStringContainsString(get_string('submittedlate', 'assign', format_time((2 * DAYSECS) + $difftime)), $output);
    2070  
    2071          $submission = $assign->get_group_submission($otherstudent->id, 0, true);
    2072          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
    2073          $assign->testable_update_submission($submission, $otherstudent->id, true, true);
    2074          $output = $assign->view_student_summary($otherstudent, true);
    2075          $this->assertStringNotContainsString(get_string('submitassignment', 'assign'), $output);
    2076      }
    2077  
    2078      public function test_submissions_open() {
    2079          global $DB;
    2080  
    2081          $this->resetAfterTest();
    2082  
    2083          $course = $this->getDataGenerator()->create_course();
    2084          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2085          $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    2086          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2087          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2088          $suspendedstudent = $this->getDataGenerator()->create_and_enrol(
    2089              $course, 'student', null, 'manual', 0, 0, ENROL_USER_SUSPENDED);
    2090  
    2091          $this->setAdminUser();
    2092  
    2093          $now = time();
    2094          $tomorrow = $now + DAYSECS;
    2095          $oneweek = $now + WEEKSECS;
    2096          $yesterday = $now - DAYSECS;
    2097  
    2098          $assign = $this->create_instance($course);
    2099          $this->assertEquals(true, $assign->testable_submissions_open($student->id));
    2100  
    2101          $assign = $this->create_instance($course, ['duedate' => $tomorrow]);
    2102          $this->assertEquals(true, $assign->testable_submissions_open($student->id));
    2103  
    2104          $assign = $this->create_instance($course, ['duedate' => $yesterday]);
    2105          $this->assertEquals(true, $assign->testable_submissions_open($student->id));
    2106  
    2107          $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $tomorrow]);
    2108          $this->assertEquals(true, $assign->testable_submissions_open($student->id));
    2109  
    2110          $assign = $this->create_instance($course, ['duedate' => $yesterday, 'cutoffdate' => $yesterday]);
    2111          $this->assertEquals(false, $assign->testable_submissions_open($student->id));
    2112  
    2113          $assign->testable_save_user_extension($student->id, $tomorrow);
    2114          $this->assertEquals(true, $assign->testable_submissions_open($student->id));
    2115  
    2116          $assign = $this->create_instance($course, ['submissiondrafts' => 1]);
    2117          $this->assertEquals(true, $assign->testable_submissions_open($student->id));
    2118  
    2119          $this->setUser($student);
    2120          $submission = $assign->get_user_submission($student->id, true);
    2121          $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
    2122          $assign->testable_update_submission($submission, $student->id, true, false);
    2123  
    2124          $this->setUser($teacher);
    2125          $this->assertEquals(false, $assign->testable_submissions_open($student->id));
    2126      }
    2127  
    2128      public function test_get_graders() {
    2129          global $DB;
    2130  
    2131          $this->resetAfterTest();
    2132  
    2133          $course = $this->getDataGenerator()->create_course();
    2134          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2135          $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    2136          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2137  
    2138          $this->setAdminUser();
    2139  
    2140          // Create an assignment with no groups.
    2141          $assign = $this->create_instance($course);
    2142          $this->assertCount(2, $assign->testable_get_graders($student->id));
    2143      }
    2144  
    2145      public function test_get_graders_separate_groups() {
    2146          global $DB;
    2147  
    2148          $this->resetAfterTest();
    2149  
    2150          $course = $this->getDataGenerator()->create_course();
    2151          $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2152          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2153          $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    2154          $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    2155          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2156          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2157  
    2158          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    2159          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    2160          groups_add_member($group1, $student);
    2161  
    2162          $this->setAdminUser();
    2163  
    2164          // Force create an assignment with SEPARATEGROUPS.
    2165          $group = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    2166          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    2167  
    2168          $assign = $this->create_instance($course, [
    2169              'groupingid' => $grouping->id,
    2170              'groupmode' => SEPARATEGROUPS,
    2171          ]);
    2172  
    2173          $this->assertCount(4, $assign->testable_get_graders($student->id));
    2174  
    2175          // Note the second student is in a group that is not in the grouping.
    2176          // This means that we get all graders that are not in a group in the grouping.
    2177          $this->assertCount(4, $assign->testable_get_graders($otherstudent->id));
    2178      }
    2179  
    2180      public function test_get_notified_users() {
    2181          global $CFG, $DB;
    2182  
    2183          $this->resetAfterTest();
    2184  
    2185          $course = $this->getDataGenerator()->create_course();
    2186          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    2187          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    2188          $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
    2189  
    2190          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2191          groups_add_member($group1, $teacher);
    2192  
    2193          $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    2194          groups_add_member($group1, $editingteacher);
    2195  
    2196          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2197          groups_add_member($group1, $student);
    2198  
    2199          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2200          $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2201  
    2202          $capability = 'mod/assign:receivegradernotifications';
    2203          $coursecontext = context_course::instance($course->id);
    2204          $role = $DB->get_record('role', array('shortname' => 'teacher'));
    2205  
    2206          $this->setUser($teacher);
    2207  
    2208          // Create an assignment with no groups.
    2209          $assign = $this->create_instance($course);
    2210  
    2211          $this->assertCount(3, $assign->testable_get_notifiable_users($student->id));
    2212  
    2213          // Change nonediting teachers role to not receive grader notifications.
    2214          assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
    2215  
    2216          // Only the editing teachers will be returned.
    2217          $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
    2218  
    2219          // Note the second student is in a group that is not in the grouping.
    2220          // This means that we get all graders that are not in a group in the grouping.
    2221          $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
    2222      }
    2223  
    2224      public function test_get_notified_users_in_grouping() {
    2225          global $CFG, $DB;
    2226  
    2227          $this->resetAfterTest();
    2228  
    2229          $course = $this->getDataGenerator()->create_course();
    2230          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    2231          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    2232          $this->getDataGenerator()->create_grouping_group(array('groupid' => $group1->id, 'groupingid' => $grouping->id));
    2233  
    2234          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2235          groups_add_member($group1, $teacher);
    2236  
    2237          $editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
    2238          groups_add_member($group1, $editingteacher);
    2239  
    2240          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2241          groups_add_member($group1, $student);
    2242  
    2243          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2244          $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2245  
    2246          // Force create an assignment with SEPARATEGROUPS.
    2247          $assign = $this->create_instance($course, [
    2248              'groupingid' => $grouping->id,
    2249              'groupmode' => SEPARATEGROUPS,
    2250          ]);
    2251  
    2252          // Student is in a group - only the tacher and editing teacher in the group shoudl be present.
    2253          $this->setUser($student);
    2254          $this->assertCount(2, $assign->testable_get_notifiable_users($student->id));
    2255  
    2256          // Note the second student is in a group that is not in the grouping.
    2257          // This means that we get all graders that are not in a group in the grouping.
    2258          $this->assertCount(1, $assign->testable_get_notifiable_users($otherstudent->id));
    2259  
    2260          // Change nonediting teachers role to not receive grader notifications.
    2261          $capability = 'mod/assign:receivegradernotifications';
    2262          $coursecontext = context_course::instance($course->id);
    2263          $role = $DB->get_record('role', ['shortname' => 'teacher']);
    2264          assign_capability($capability, CAP_PROHIBIT, $role->id, $coursecontext);
    2265  
    2266          // Only the editing teachers will be returned.
    2267          $this->assertCount(1, $assign->testable_get_notifiable_users($student->id));
    2268  
    2269          // Note the second student is in a group that is not in the grouping.
    2270          // This means that we get all graders that are not in a group in the grouping.
    2271          // Unfortunately there are no editing teachers who are not in a group.
    2272          $this->assertCount(0, $assign->testable_get_notifiable_users($otherstudent->id));
    2273      }
    2274  
    2275      public function test_group_members_only() {
    2276          global $CFG;
    2277  
    2278          $this->resetAfterTest();
    2279  
    2280          $course = $this->getDataGenerator()->create_course();
    2281          $grouping = $this->getDataGenerator()->create_grouping(array('courseid' => $course->id));
    2282          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    2283          $this->getDataGenerator()->create_grouping_group([
    2284              'groupid' => $group1->id,
    2285              'groupingid' => $grouping->id,
    2286          ]);
    2287  
    2288          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    2289          $this->getDataGenerator()->create_grouping_group([
    2290              'groupid' => $group2->id,
    2291              'groupingid' => $grouping->id,
    2292          ]);
    2293  
    2294          $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
    2295  
    2296          // Add users in the following groups
    2297          // - Teacher - Group 1.
    2298          // - Student - Group 1.
    2299          // - Student - Group 2.
    2300          // - Student - Unrelated Group
    2301          // - Student - No group.
    2302          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2303          groups_add_member($group1, $teacher);
    2304  
    2305          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2306          groups_add_member($group1, $student);
    2307  
    2308          $otherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2309          groups_add_member($group2, $otherstudent);
    2310  
    2311          $yetotherstudent = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2312          groups_add_member($group2, $otherstudent);
    2313  
    2314          $this->getDataGenerator()->create_and_enrol($course, 'student');
    2315  
    2316          $this->setAdminUser();
    2317  
    2318          $CFG->enableavailability = true;
    2319          $assign = $this->create_instance($course, [], [
    2320              'availability' => json_encode(
    2321                  \core_availability\tree::get_root_json([\availability_grouping\condition::get_json()])
    2322              ),
    2323              'groupingid' => $grouping->id,
    2324          ]);
    2325  
    2326          // The two students in groups should be returned, but not the teacher in the group, or the student not in the
    2327          // group, or the student in an unrelated group.
    2328          $this->setUser($teacher);
    2329          $participants = $assign->list_participants(0, true);
    2330          $this->assertCount(2, $participants);
    2331          $this->assertTrue(isset($participants[$student->id]));
    2332          $this->assertTrue(isset($participants[$otherstudent->id]));
    2333      }
    2334  
    2335      public function test_get_uniqueid_for_user() {
    2336          $this->resetAfterTest();
    2337  
    2338          $course = $this->getDataGenerator()->create_course();
    2339          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2340          $students = [];
    2341          for ($i = 0; $i < 10; $i++) {
    2342              $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2343              $students[$student->id] = $student;
    2344          }
    2345  
    2346          $this->setUser($teacher);
    2347          $assign = $this->create_instance($course);
    2348  
    2349          foreach ($students as $student) {
    2350              $uniqueid = $assign->get_uniqueid_for_user($student->id);
    2351              $this->assertEquals($student->id, $assign->get_user_id_for_uniqueid($uniqueid));
    2352          }
    2353      }
    2354  
    2355      public function test_show_student_summary() {
    2356          global $CFG, $PAGE;
    2357  
    2358          $this->resetAfterTest();
    2359  
    2360          $course = $this->getDataGenerator()->create_course();
    2361          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2362          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2363          $this->setUser($teacher);
    2364          $assign = $this->create_instance($course);
    2365          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
    2366  
    2367          // No feedback should be available because this student has not been graded.
    2368          $this->setUser($student);
    2369          $output = $assign->view_student_summary($student, true);
    2370          $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if there is no grade');
    2371  
    2372          // Simulate adding a grade.
    2373          $this->add_submission($student, $assign);
    2374          $this->submit_for_grading($student, $assign);
    2375          $this->mark_submission($teacher, $assign, $student);
    2376  
    2377          // Now we should see the feedback.
    2378          $this->setUser($student);
    2379          $output = $assign->view_student_summary($student, true);
    2380          $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
    2381  
    2382          // Now hide the grade in gradebook.
    2383          $this->setUser($teacher);
    2384          require_once($CFG->libdir.'/gradelib.php');
    2385          $gradeitem = new grade_item(array(
    2386              'itemtype'      => 'mod',
    2387              'itemmodule'    => 'assign',
    2388              'iteminstance'  => $assign->get_instance()->id,
    2389              'courseid'      => $course->id));
    2390  
    2391          $gradeitem->set_hidden(1, false);
    2392  
    2393          // No feedback should be available because the grade is hidden.
    2394          $this->setUser($student);
    2395          $output = $assign->view_student_summary($student, true);
    2396          $this->assertDoesNotMatchRegularExpression('/Feedback/', $output,
    2397              'Do not show feedback if the grade is hidden in the gradebook');
    2398  
    2399          // Freeze the context.
    2400          $this->setAdminUser();
    2401          $context = $assign->get_context();
    2402          $CFG->contextlocking = true;
    2403          $context->set_locked(true);
    2404  
    2405          // No feedback should be available because the grade is hidden.
    2406          $this->setUser($student);
    2407          $output = $assign->view_student_summary($student, true);
    2408          $this->assertDoesNotMatchRegularExpression('/Feedback/', $output, 'Do not show feedback if the grade is hidden in the gradebook');
    2409  
    2410          // Show the feedback again - it should still be visible even in a frozen context.
    2411          $this->setUser($teacher);
    2412          $gradeitem->set_hidden(0, false);
    2413  
    2414          $this->setUser($student);
    2415          $output = $assign->view_student_summary($student, true);
    2416          $this->assertMatchesRegularExpression('/Feedback/', $output, 'Show feedback if there is a grade');
    2417      }
    2418  
    2419      public function test_show_student_summary_with_feedback() {
    2420          global $CFG, $PAGE;
    2421  
    2422          $this->resetAfterTest();
    2423  
    2424          $course = $this->getDataGenerator()->create_course();
    2425          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2426          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2427          $this->setUser($teacher);
    2428          $assign = $this->create_instance($course, [
    2429              'assignfeedback_comments_enabled' => 1
    2430          ]);
    2431          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
    2432  
    2433          // No feedback should be available because this student has not been graded.
    2434          $this->setUser($student);
    2435          $output = $assign->view_student_summary($student, true);
    2436          $this->assertDoesNotMatchRegularExpression('/Feedback/', $output);
    2437  
    2438          // Simulate adding a grade.
    2439          $this->add_submission($student, $assign);
    2440          $this->submit_for_grading($student, $assign);
    2441          $this->mark_submission($teacher, $assign, $student, null, [
    2442              'assignfeedbackcomments_editor' => [
    2443                  'text' => 'Tomato sauce',
    2444                  'format' => FORMAT_MOODLE,
    2445              ],
    2446          ]);
    2447  
    2448          // Should have feedback but no grade.
    2449          $this->setUser($student);
    2450          $output = $assign->view_student_summary($student, true);
    2451          $this->assertMatchesRegularExpression('/Feedback/', $output);
    2452          $this->assertMatchesRegularExpression('/Tomato sauce/', $output);
    2453          $this->assertDoesNotMatchRegularExpression('/Grade/', $output, 'Do not show grade when there is no grade.');
    2454          $this->assertDoesNotMatchRegularExpression('/Graded on/', $output, 'Do not show graded date when there is no grade.');
    2455  
    2456          // Add a grade now.
    2457          $this->mark_submission($teacher, $assign, $student, 50.0, [
    2458              'assignfeedbackcomments_editor' => [
    2459                  'text' => 'Bechamel sauce',
    2460                  'format' => FORMAT_MOODLE,
    2461              ],
    2462          ]);
    2463  
    2464          // Should have feedback but no grade.
    2465          $this->setUser($student);
    2466          $output = $assign->view_student_summary($student, true);
    2467          $this->assertDoesNotMatchRegularExpression('/Tomato sauce/', $output);
    2468          $this->assertMatchesRegularExpression('/Bechamel sauce/', $output);
    2469          $this->assertMatchesRegularExpression('/Grade/', $output);
    2470          $this->assertMatchesRegularExpression('/Graded on/', $output);
    2471  
    2472          // Now hide the grade in gradebook.
    2473          $this->setUser($teacher);
    2474          $gradeitem = new grade_item(array(
    2475              'itemtype'      => 'mod',
    2476              'itemmodule'    => 'assign',
    2477              'iteminstance'  => $assign->get_instance()->id,
    2478              'courseid'      => $course->id));
    2479  
    2480          $gradeitem->set_hidden(1, false);
    2481  
    2482          // No feedback should be available because the grade is hidden.
    2483          $this->setUser($student);
    2484          $output = $assign->view_student_summary($student, true);
    2485          $this->assertDoesNotMatchRegularExpression('/Feedback/', $output,
    2486              'Do not show feedback if the grade is hidden in the gradebook');
    2487      }
    2488  
    2489      /**
    2490       * Test reopen behavior when in "Manual" mode.
    2491       */
    2492      public function test_attempt_reopen_method_manual() {
    2493          global $PAGE;
    2494  
    2495          $this->resetAfterTest();
    2496          $course = $this->getDataGenerator()->create_course();
    2497          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
    2498          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
    2499  
    2500          $assign = $this->create_instance($course, [
    2501              'attemptreopenmethod' => ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL,
    2502              'maxattempts' => 3,
    2503              'submissiondrafts' => 1,
    2504              'assignsubmission_onlinetext_enabled' => 1,
    2505          ]);
    2506          $PAGE->set_url(new moodle_url('/mod/assign/view.php', ['id' => $assign->get_course_module()->id]));
    2507  
    2508          // Student should be able to see an add submission button.
    2509          $this->setUser($student);
    2510          $output = $assign->view_student_summary($student, true);
    2511          $this->assertNotEquals(false, strpos($output, get_string('addsubmission', 'assign')));
    2512  
    2513          // Add a submission.
    2514          $this->add_submission($student, $assign);
    2515          $this->submit_for_grading($student, $assign);
    2516  
    2517          // Verify the student cannot make changes to the submission.
    2518          $output = $assign->view_student_summary($student, true);
    2519          $this->assertEquals(false, strpos($output, get_string('addsubmission', 'assign')));
    2520  
    2521          // Mark the submission.
    2522          $this->mark_submission($teacher, $assign, $student);
    2523  
    2524          // Check the student can see the grade.
    2525          $this->setUser($student);
    2526          $output = $assign->view_student_summary(