Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

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

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