Search moodle.org's
Developer Documentation

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