Search moodle.org's
Developer Documentation

See Release Notes

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

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

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