Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]

   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   * This file contains a renderer for the assignment class
  19   *
  20   * @package   mod_assign
  21   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace mod_assign\output;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  require_once($CFG->dirroot . '/mod/assign/locallib.php');
  30  
  31  use \mod_assign\output\grading_app;
  32  
  33  /**
  34   * A custom renderer class that extends the plugin_renderer_base and is used by the assign module.
  35   *
  36   * @package mod_assign
  37   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  38   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class renderer extends \plugin_renderer_base {
  41  
  42      /**
  43       * Rendering assignment files
  44       *
  45       * @param \context $context
  46       * @param int $userid
  47       * @param string $filearea
  48       * @param string $component
  49       * @param stdClass $course
  50       * @param stdClass $coursemodule
  51       * @return string
  52       */
  53      public function assign_files(\context $context, $userid, $filearea, $component, $course = null, $coursemodule = null) {
  54          return $this->render(new \assign_files($context, $userid, $filearea, $component, $course, $coursemodule));
  55      }
  56  
  57      /**
  58       * Rendering assignment files
  59       *
  60       * @param \assign_files $tree
  61       * @return string
  62       */
  63      public function render_assign_files(\assign_files $tree) {
  64          $this->htmlid = \html_writer::random_id('assign_files_tree');
  65          $this->page->requires->js_init_call('M.mod_assign.init_tree', array(true, $this->htmlid));
  66          $html = '<div id="'.$this->htmlid.'">';
  67          $html .= $this->htmllize_tree($tree, $tree->dir);
  68          $html .= '</div>';
  69  
  70          if ($tree->portfolioform) {
  71              $html .= $tree->portfolioform;
  72          }
  73          return $html;
  74      }
  75  
  76      /**
  77       * Utility function to add a row of data to a table with 2 columns where the first column is the table's header.
  78       * Modified the table param and does not return a value.
  79       *
  80       * @param \html_table $table The table to append the row of data to
  81       * @param string $first The first column text
  82       * @param string $second The second column text
  83       * @param array $firstattributes The first column attributes (optional)
  84       * @param array $secondattributes The second column attributes (optional)
  85       * @return void
  86       */
  87      private function add_table_row_tuple(\html_table $table, $first, $second, $firstattributes = [],
  88              $secondattributes = []) {
  89          $row = new \html_table_row();
  90          $cell1 = new \html_table_cell($first);
  91          $cell1->header = true;
  92          if (!empty($firstattributes)) {
  93              $cell1->attributes = $firstattributes;
  94          }
  95          $cell2 = new \html_table_cell($second);
  96          if (!empty($secondattributes)) {
  97              $cell2->attributes = $secondattributes;
  98          }
  99          $row->cells = array($cell1, $cell2);
 100          $table->data[] = $row;
 101      }
 102  
 103      /**
 104       * Render a grading message notification
 105       * @param \assign_gradingmessage $result The result to render
 106       * @return string
 107       */
 108      public function render_assign_gradingmessage(\assign_gradingmessage $result) {
 109          $urlparams = array('id' => $result->coursemoduleid, 'action'=>'grading');
 110          if (!empty($result->page)) {
 111              $urlparams['page'] = $result->page;
 112          }
 113          $url = new \moodle_url('/mod/assign/view.php', $urlparams);
 114          $classes = $result->gradingerror ? 'notifyproblem' : 'notifysuccess';
 115  
 116          $o = '';
 117          $o .= $this->output->heading($result->heading, 4);
 118          $o .= $this->output->notification($result->message, $classes);
 119          $o .= $this->output->continue_button($url);
 120          return $o;
 121      }
 122  
 123      /**
 124       * Render the generic form
 125       * @param \assign_form $form The form to render
 126       * @return string
 127       */
 128      public function render_assign_form(\assign_form $form) {
 129          $o = '';
 130          if ($form->jsinitfunction) {
 131              $this->page->requires->js_init_call($form->jsinitfunction, array());
 132          }
 133          $o .= $this->output->box_start('boxaligncenter ' . $form->classname);
 134          $o .= $this->moodleform($form->form);
 135          $o .= $this->output->box_end();
 136          return $o;
 137      }
 138  
 139      /**
 140       * Render the user summary
 141       *
 142       * @param \assign_user_summary $summary The user summary to render
 143       * @return string
 144       */
 145      public function render_assign_user_summary(\assign_user_summary $summary) {
 146          $o = '';
 147          $supendedclass = '';
 148          $suspendedicon = '';
 149  
 150          if (!$summary->user) {
 151              return;
 152          }
 153  
 154          if ($summary->suspendeduser) {
 155              $supendedclass = ' usersuspended';
 156              $suspendedstring = get_string('userenrolmentsuspended', 'grades');
 157              $suspendedicon = ' ' . $this->pix_icon('i/enrolmentsuspended', $suspendedstring);
 158          }
 159          $o .= $this->output->container_start('usersummary');
 160          $o .= $this->output->box_start('boxaligncenter usersummarysection'.$supendedclass);
 161          if ($summary->blindmarking) {
 162              $o .= get_string('hiddenuser', 'assign') . $summary->uniqueidforuser.$suspendedicon;
 163          } else {
 164              $o .= $this->output->user_picture($summary->user);
 165              $o .= $this->output->spacer(array('width'=>30));
 166              $urlparams = array('id' => $summary->user->id, 'course'=>$summary->courseid);
 167              $url = new \moodle_url('/user/view.php', $urlparams);
 168              $fullname = fullname($summary->user, $summary->viewfullnames);
 169              $extrainfo = array();
 170              foreach ($summary->extrauserfields as $extrafield) {
 171                  $extrainfo[] = s($summary->user->$extrafield);
 172              }
 173              if (count($extrainfo)) {
 174                  $fullname .= ' (' . implode(', ', $extrainfo) . ')';
 175              }
 176              $fullname .= $suspendedicon;
 177              $o .= $this->output->action_link($url, $fullname);
 178          }
 179          $o .= $this->output->box_end();
 180          $o .= $this->output->container_end();
 181  
 182          return $o;
 183      }
 184  
 185      /**
 186       * Render the submit for grading page
 187       *
 188       * @param \assign_submit_for_grading_page $page
 189       * @return string
 190       */
 191      public function render_assign_submit_for_grading_page($page) {
 192          $o = '';
 193  
 194          $o .= $this->output->container_start('submitforgrading');
 195          $o .= $this->output->heading(get_string('confirmsubmissionheading', 'assign'), 3);
 196  
 197          $cancelurl = new \moodle_url('/mod/assign/view.php', array('id' => $page->coursemoduleid));
 198          if (count($page->notifications)) {
 199              // At least one of the submission plugins is not ready for submission.
 200  
 201              $o .= $this->output->heading(get_string('submissionnotready', 'assign'), 4);
 202  
 203              foreach ($page->notifications as $notification) {
 204                  $o .= $this->output->notification($notification);
 205              }
 206  
 207              $o .= $this->output->continue_button($cancelurl);
 208          } else {
 209              // All submission plugins ready - show the confirmation form.
 210              $o .= $this->moodleform($page->confirmform);
 211          }
 212          $o .= $this->output->container_end();
 213  
 214          return $o;
 215      }
 216  
 217      /**
 218       * Page is done - render the footer.
 219       *
 220       * @return void
 221       */
 222      public function render_footer() {
 223          return $this->output->footer();
 224      }
 225  
 226      /**
 227       * Render the header.
 228       *
 229       * @param assign_header $header
 230       * @return string
 231       */
 232      public function render_assign_header(assign_header $header) {
 233          if ($header->subpage) {
 234              $this->page->navbar->add($header->subpage, $header->subpageurl);
 235              $args = ['contextname' => $header->context->get_context_name(false, true), 'subpage' => $header->subpage];
 236              $title = get_string('subpagetitle', 'assign', $args);
 237          } else {
 238              $title = $header->context->get_context_name(false, true);
 239          }
 240          $courseshortname = $header->context->get_course_context()->get_context_name(false, true);
 241          $title = $courseshortname . ': ' . $title;
 242          $heading = format_string($header->assign->name, false, array('context' => $header->context));
 243  
 244          $this->page->set_title($title);
 245          $this->page->set_heading($this->page->course->fullname);
 246  
 247          $description = $header->preface;
 248          if ($header->showintro || $header->activity) {
 249              $description = $this->output->box_start('generalbox boxaligncenter');
 250              if ($header->showintro) {
 251                  $description .= format_module_intro('assign', $header->assign, $header->coursemoduleid);
 252              }
 253              if ($header->activity) {
 254                  $description .= $this->format_activity_text($header->assign, $header->coursemoduleid);
 255              }
 256              $description .= $header->postfix;
 257              $description .= $this->output->box_end();
 258          }
 259  
 260          $activityheader = $this->page->activityheader;
 261          $activityheader->set_attrs([
 262              'title' => $activityheader->is_title_allowed() ? $heading : '',
 263              'description' => $description
 264          ]);
 265  
 266          return $this->output->header();
 267      }
 268  
 269      /**
 270       * Render the header for an individual plugin.
 271       *
 272       * @param \assign_plugin_header $header
 273       * @return string
 274       */
 275      public function render_assign_plugin_header(\assign_plugin_header $header) {
 276          $o = $header->plugin->view_header();
 277          return $o;
 278      }
 279  
 280      /**
 281       * Render a table containing the current status of the grading process.
 282       *
 283       * @param \assign_grading_summary $summary
 284       * @return string
 285       */
 286      public function render_assign_grading_summary(\assign_grading_summary $summary) {
 287          // Create a table for the data.
 288          $o = '';
 289          $o .= $this->output->container_start('gradingsummary');
 290          $o .= $this->output->heading(get_string('gradingsummary', 'assign'), 3);
 291  
 292          if (isset($summary->cm)) {
 293              $currenturl = new \moodle_url('/mod/assign/view.php', array('id' => $summary->cm->id));
 294              $o .= groups_print_activity_menu($summary->cm, $currenturl->out(), true);
 295          }
 296  
 297          $o .= $this->output->box_start('boxaligncenter gradingsummarytable');
 298          $t = new \html_table();
 299          $t->attributes['class'] = 'generaltable table-bordered';
 300  
 301          // Visibility Status.
 302          $cell1content = get_string('hiddenfromstudents');
 303          $cell2content = (!$summary->isvisible) ? get_string('yes') : get_string('no');
 304          $this->add_table_row_tuple($t, $cell1content, $cell2content);
 305  
 306          // Status.
 307          if ($summary->teamsubmission) {
 308              if ($summary->warnofungroupedusers === \assign_grading_summary::WARN_GROUPS_REQUIRED) {
 309                  $o .= $this->output->notification(get_string('ungroupedusers', 'assign'));
 310              } else if ($summary->warnofungroupedusers === \assign_grading_summary::WARN_GROUPS_OPTIONAL) {
 311                  $o .= $this->output->notification(get_string('ungroupedusersoptional', 'assign'));
 312              }
 313              $cell1content = get_string('numberofteams', 'assign');
 314          } else {
 315              $cell1content = get_string('numberofparticipants', 'assign');
 316          }
 317  
 318          $cell2content = $summary->participantcount;
 319          $this->add_table_row_tuple($t, $cell1content, $cell2content);
 320  
 321          // Drafts count and dont show drafts count when using offline assignment.
 322          if ($summary->submissiondraftsenabled && $summary->submissionsenabled) {
 323              $cell1content = get_string('numberofdraftsubmissions', 'assign');
 324              $cell2content = $summary->submissiondraftscount;
 325              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 326          }
 327  
 328          // Submitted for grading.
 329          if ($summary->submissionsenabled) {
 330              $cell1content = get_string('numberofsubmittedassignments', 'assign');
 331              $cell2content = $summary->submissionssubmittedcount;
 332              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 333  
 334              if (!$summary->teamsubmission) {
 335                  $cell1content = get_string('numberofsubmissionsneedgrading', 'assign');
 336                  $cell2content = $summary->submissionsneedgradingcount;
 337                  $this->add_table_row_tuple($t, $cell1content, $cell2content);
 338              }
 339          }
 340  
 341          $time = time();
 342          if ($summary->duedate) {
 343              // Time remaining.
 344              $duedate = $summary->duedate;
 345              $cell1content = get_string('timeremaining', 'assign');
 346              if ($summary->courserelativedatesmode) {
 347                  $cell2content = get_string('relativedatessubmissiontimeleft', 'mod_assign');
 348              } else {
 349                  if ($duedate - $time <= 0) {
 350                      $cell2content = get_string('assignmentisdue', 'assign');
 351                  } else {
 352                      $cell2content = format_time($duedate - $time);
 353                  }
 354              }
 355  
 356              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 357  
 358              if ($duedate < $time) {
 359                  $cell1content = get_string('latesubmissions', 'assign');
 360                  $cutoffdate = $summary->cutoffdate;
 361                  if ($cutoffdate) {
 362                      if ($cutoffdate > $time) {
 363                          $cell2content = get_string('latesubmissionsaccepted', 'assign', userdate($summary->cutoffdate));
 364                      } else {
 365                          $cell2content = get_string('nomoresubmissionsaccepted', 'assign');
 366                      }
 367  
 368                      $this->add_table_row_tuple($t, $cell1content, $cell2content);
 369                  }
 370              }
 371  
 372          }
 373  
 374          // Add time limit info if there is one.
 375          $timelimitenabled = get_config('assign', 'enabletimelimit');
 376          if ($timelimitenabled && $summary->timelimit > 0) {
 377              $cell1content = get_string('timelimit', 'assign');
 378              $cell2content = format_time($summary->timelimit);
 379              $this->add_table_row_tuple($t, $cell1content, $cell2content, [], []);
 380          }
 381  
 382          // All done - write the table.
 383          $o .= \html_writer::table($t);
 384          $o .= $this->output->box_end();
 385  
 386          // Close the container and insert a spacer.
 387          $o .= $this->output->container_end();
 388          $o .= \html_writer::end_tag('center');
 389  
 390          return $o;
 391      }
 392  
 393      /**
 394       * Render a table containing all the current grades and feedback.
 395       *
 396       * @param \assign_feedback_status $status
 397       * @return string
 398       */
 399      public function render_assign_feedback_status(\assign_feedback_status $status) {
 400          $o = '';
 401  
 402          $o .= $this->output->container_start('feedback');
 403          $o .= $this->output->heading(get_string('feedback', 'assign'), 3);
 404          $o .= $this->output->box_start('boxaligncenter feedbacktable');
 405          $t = new \html_table();
 406  
 407          // Grade.
 408          if (isset($status->gradefordisplay)) {
 409              $cell1content = get_string('gradenoun');
 410              $cell2content = $status->gradefordisplay;
 411              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 412  
 413              // Grade date.
 414              $cell1content = get_string('gradedon', 'assign');
 415              $cell2content = userdate($status->gradeddate);
 416              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 417          }
 418  
 419          if ($status->grader) {
 420              // Grader.
 421              $cell1content = get_string('gradedby', 'assign');
 422              $cell2content = $this->output->user_picture($status->grader) .
 423                              $this->output->spacer(array('width' => 30)) .
 424                              fullname($status->grader, $status->canviewfullnames);
 425              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 426          }
 427  
 428          foreach ($status->feedbackplugins as $plugin) {
 429              if ($plugin->is_enabled() &&
 430                      $plugin->is_visible() &&
 431                      $plugin->has_user_summary() &&
 432                      !empty($status->grade) &&
 433                      !$plugin->is_empty($status->grade)) {
 434  
 435                  $displaymode = \assign_feedback_plugin_feedback::SUMMARY;
 436                  $pluginfeedback = new \assign_feedback_plugin_feedback($plugin,
 437                                                                        $status->grade,
 438                                                                        $displaymode,
 439                                                                        $status->coursemoduleid,
 440                                                                        $status->returnaction,
 441                                                                        $status->returnparams);
 442                  $cell1content = $plugin->get_name();
 443                  $cell2content = $this->render($pluginfeedback);
 444                  $this->add_table_row_tuple($t, $cell1content, $cell2content);
 445              }
 446          }
 447  
 448          $o .= \html_writer::table($t);
 449          $o .= $this->output->box_end();
 450  
 451          if (!empty($status->gradingcontrollergrade)) {
 452              $o .= $this->output->heading(get_string('gradebreakdown', 'assign'), 4);
 453              $o .= $status->gradingcontrollergrade;
 454          }
 455  
 456          $o .= $this->output->container_end();
 457          return $o;
 458      }
 459  
 460      /**
 461       * Render a compact view of the current status of the submission.
 462       *
 463       * @param \assign_submission_status_compact $status
 464       * @return string
 465       */
 466      public function render_assign_submission_status_compact(\assign_submission_status_compact $status) {
 467          $o = '';
 468          $o .= $this->output->container_start('submissionstatustable');
 469          $o .= $this->output->heading(get_string('submission', 'assign'), 3);
 470  
 471          if ($status->teamsubmissionenabled) {
 472              $group = $status->submissiongroup;
 473              if ($group) {
 474                  $team = format_string($group->name, false, $status->context);
 475              } else if ($status->preventsubmissionnotingroup) {
 476                  if (count($status->usergroups) == 0) {
 477                      $team = '<span class="alert alert-error">' . get_string('noteam', 'assign') . '</span>';
 478                  } else if (count($status->usergroups) > 1) {
 479                      $team = '<span class="alert alert-error">' . get_string('multipleteams', 'assign') . '</span>';
 480                  }
 481              } else {
 482                  $team = get_string('defaultteam', 'assign');
 483              }
 484              $o .= $this->output->container(get_string('teamname', 'assign', $team), 'teamname');
 485          }
 486  
 487          if (!$status->teamsubmissionenabled) {
 488              if ($status->submission && $status->submission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
 489                  $statusstr = get_string('submissionstatus_' . $status->submission->status, 'assign');
 490                  $o .= $this->output->container($statusstr, 'submissionstatus' . $status->submission->status);
 491              } else {
 492                  if (!$status->submissionsenabled) {
 493                      $o .= $this->output->container(get_string('noonlinesubmissions', 'assign'), 'submissionstatus');
 494                  } else {
 495                      $o .= $this->output->container(get_string('noattempt', 'assign'), 'submissionstatus');
 496                  }
 497              }
 498          } else {
 499              $group = $status->submissiongroup;
 500              if (!$group && $status->preventsubmissionnotingroup) {
 501                  $o .= $this->output->container(get_string('nosubmission', 'assign'), 'submissionstatus');
 502              } else if ($status->teamsubmission && $status->teamsubmission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
 503                  $teamstatus = $status->teamsubmission->status;
 504                  $submissionsummary = get_string('submissionstatus_' . $teamstatus, 'assign');
 505                  $groupid = 0;
 506                  if ($status->submissiongroup) {
 507                      $groupid = $status->submissiongroup->id;
 508                  }
 509  
 510                  $members = $status->submissiongroupmemberswhoneedtosubmit;
 511                  $userslist = array();
 512                  foreach ($members as $member) {
 513                      $urlparams = array('id' => $member->id, 'course' => $status->courseid);
 514                      $url = new \moodle_url('/user/view.php', $urlparams);
 515                      if ($status->view == assign_submission_status::GRADER_VIEW && $status->blindmarking) {
 516                          $userslist[] = $member->alias;
 517                      } else {
 518                          $fullname = fullname($member, $status->canviewfullnames);
 519                          $userslist[] = $this->output->action_link($url, $fullname);
 520                      }
 521                  }
 522                  if (count($userslist) > 0) {
 523                      $userstr = join(', ', $userslist);
 524                      $formatteduserstr = get_string('userswhoneedtosubmit', 'assign', $userstr);
 525                      $submissionsummary .= $this->output->container($formatteduserstr);
 526                  }
 527                  $o .= $this->output->container($submissionsummary, 'submissionstatus' . $status->teamsubmission->status);
 528              } else {
 529                  if (!$status->submissionsenabled) {
 530                      $o .= $this->output->container(get_string('noonlinesubmissions', 'assign'), 'submissionstatus');
 531                  } else {
 532                      $o .= $this->output->container(get_string('nosubmission', 'assign'), 'submissionstatus');
 533                  }
 534              }
 535          }
 536  
 537          // Is locked?
 538          if ($status->locked) {
 539              $o .= $this->output->container(get_string('submissionslocked', 'assign'), 'submissionlocked');
 540          }
 541  
 542          // Grading status.
 543          $statusstr = '';
 544          $classname = 'gradingstatus';
 545          if ($status->gradingstatus == ASSIGN_GRADING_STATUS_GRADED ||
 546              $status->gradingstatus == ASSIGN_GRADING_STATUS_NOT_GRADED) {
 547              $statusstr = get_string($status->gradingstatus, 'assign');
 548          } else {
 549              $gradingstatus = 'markingworkflowstate' . $status->gradingstatus;
 550              $statusstr = get_string($gradingstatus, 'assign');
 551          }
 552          if ($status->gradingstatus == ASSIGN_GRADING_STATUS_GRADED ||
 553              $status->gradingstatus == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
 554              $classname = 'submissiongraded';
 555          } else {
 556              $classname = 'submissionnotgraded';
 557          }
 558  
 559          $o .= $this->output->container($statusstr, $classname);
 560  
 561          $submission = $status->teamsubmission ? $status->teamsubmission : $status->submission;
 562          $duedate = $status->duedate;
 563          if ($duedate > 0) {
 564  
 565              if ($status->extensionduedate) {
 566                  // Extension date.
 567                  $duedate = $status->extensionduedate;
 568              }
 569          }
 570  
 571          // Time remaining.
 572          // Only add the row if there is a due date, or a countdown.
 573          if ($status->duedate > 0 || !empty($submission->timestarted)) {
 574              [$remaining, $classname] = $this->get_time_remaining($status);
 575  
 576              // If the assignment is not submitted, and there is a submission in progress,
 577              // Add a heading for the time limit.
 578              if (!empty($submission) &&
 579                  $submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED &&
 580                  !empty($submission->timestarted)
 581              ) {
 582                  $o .= $this->output->container(get_string('timeremaining', 'assign'));
 583              }
 584              $o .= $this->output->container($remaining, $classname);
 585          }
 586  
 587          // Show graders whether this submission is editable by students.
 588          if ($status->view == assign_submission_status::GRADER_VIEW) {
 589              if ($status->canedit) {
 590                  $o .= $this->output->container(get_string('submissioneditable', 'assign'), 'submissioneditable');
 591              } else {
 592                  $o .= $this->output->container(get_string('submissionnoteditable', 'assign'), 'submissionnoteditable');
 593              }
 594          }
 595  
 596          // Grading criteria preview.
 597          if (!empty($status->gradingcontrollerpreview)) {
 598              $o .= $this->output->container($status->gradingcontrollerpreview, 'gradingmethodpreview');
 599          }
 600  
 601          if ($submission) {
 602  
 603              if (!$status->teamsubmission || $status->submissiongroup != false || !$status->preventsubmissionnotingroup) {
 604                  foreach ($status->submissionplugins as $plugin) {
 605                      $pluginshowsummary = !$plugin->is_empty($submission) || !$plugin->allow_submissions();
 606                      if ($plugin->is_enabled() &&
 607                          $plugin->is_visible() &&
 608                          $plugin->has_user_summary() &&
 609                          $pluginshowsummary
 610                      ) {
 611  
 612                          $displaymode = \assign_submission_plugin_submission::SUMMARY;
 613                          $pluginsubmission = new \assign_submission_plugin_submission($plugin,
 614                              $submission,
 615                              $displaymode,
 616                              $status->coursemoduleid,
 617                              $status->returnaction,
 618                              $status->returnparams);
 619                          $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
 620                          $o .= $this->output->container($this->render($pluginsubmission), 'assignsubmission ' . $plugincomponent);
 621                      }
 622                  }
 623              }
 624          }
 625  
 626          $o .= $this->output->container_end();
 627          return $o;
 628      }
 629  
 630      /**
 631       * Render a table containing the current status of the submission.
 632       *
 633       * @param assign_submission_status $status
 634       * @return string
 635       */
 636      public function render_assign_submission_status(assign_submission_status $status) {
 637          $o = '';
 638          $o .= $this->output->container_start('submissionstatustable');
 639          $o .= $this->output->heading(get_string('submissionstatusheading', 'assign'), 3);
 640          $time = time();
 641  
 642          $o .= $this->output->box_start('boxaligncenter submissionsummarytable');
 643  
 644          $t = new \html_table();
 645          $t->attributes['class'] = 'generaltable table-bordered';
 646  
 647          $warningmsg = '';
 648          if ($status->teamsubmissionenabled) {
 649              $cell1content = get_string('submissionteam', 'assign');
 650              $group = $status->submissiongroup;
 651              if ($group) {
 652                  $cell2content = format_string($group->name, false, ['context' => $status->context]);
 653              } else if ($status->preventsubmissionnotingroup) {
 654                  if (count($status->usergroups) == 0) {
 655                      $notification = new \core\output\notification(get_string('noteam', 'assign'), 'error');
 656                      $notification->set_show_closebutton(false);
 657                      $warningmsg = $this->output->notification(get_string('noteam_desc', 'assign'), 'error');
 658                  } else if (count($status->usergroups) > 1) {
 659                      $notification = new \core\output\notification(get_string('multipleteams', 'assign'), 'error');
 660                      $notification->set_show_closebutton(false);
 661                      $warningmsg = $this->output->notification(get_string('multipleteams_desc', 'assign'), 'error');
 662                  }
 663                  $cell2content = $this->output->render($notification);
 664              } else {
 665                  $cell2content = get_string('defaultteam', 'assign');
 666              }
 667  
 668              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 669          }
 670  
 671          if ($status->attemptreopenmethod != ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
 672              $currentattempt = 1;
 673              if (!$status->teamsubmissionenabled) {
 674                  if ($status->submission) {
 675                      $currentattempt = $status->submission->attemptnumber + 1;
 676                  }
 677              } else {
 678                  if ($status->teamsubmission) {
 679                      $currentattempt = $status->teamsubmission->attemptnumber + 1;
 680                  }
 681              }
 682  
 683              $cell1content = get_string('attemptnumber', 'assign');
 684              $maxattempts = $status->maxattempts;
 685              if ($maxattempts == ASSIGN_UNLIMITED_ATTEMPTS) {
 686                  $cell2content = get_string('currentattempt', 'assign', $currentattempt);
 687              } else {
 688                  $cell2content = get_string('currentattemptof', 'assign',
 689                      array('attemptnumber' => $currentattempt, 'maxattempts' => $maxattempts));
 690              }
 691  
 692              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 693          }
 694  
 695          $cell1content = get_string('submissionstatus', 'assign');
 696          $cell2attributes = [];
 697          if (!$status->teamsubmissionenabled) {
 698              if ($status->submission && $status->submission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
 699                  $cell2content = get_string('submissionstatus_' . $status->submission->status, 'assign');
 700                  $cell2attributes = array('class' => 'submissionstatus' . $status->submission->status);
 701              } else {
 702                  if (!$status->submissionsenabled) {
 703                      $cell2content = get_string('noonlinesubmissions', 'assign');
 704                  } else {
 705                      $cell2content = get_string('nosubmissionyet', 'assign');
 706                  }
 707              }
 708          } else {
 709              $group = $status->submissiongroup;
 710              if (!$group && $status->preventsubmissionnotingroup) {
 711                  $cell2content = get_string('nosubmission', 'assign');
 712              } else if ($status->teamsubmission && $status->teamsubmission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
 713                  $teamstatus = $status->teamsubmission->status;
 714                  $cell2content = get_string('submissionstatus_' . $teamstatus, 'assign');
 715  
 716                  $members = $status->submissiongroupmemberswhoneedtosubmit;
 717                  $userslist = array();
 718                  foreach ($members as $member) {
 719                      $urlparams = array('id' => $member->id, 'course'=>$status->courseid);
 720                      $url = new \moodle_url('/user/view.php', $urlparams);
 721                      if ($status->view == assign_submission_status::GRADER_VIEW && $status->blindmarking) {
 722                          $userslist[] = $member->alias;
 723                      } else {
 724                          $fullname = fullname($member, $status->canviewfullnames);
 725                          $userslist[] = $this->output->action_link($url, $fullname);
 726                      }
 727                  }
 728                  if (count($userslist) > 0) {
 729                      $userstr = join(', ', $userslist);
 730                      $formatteduserstr = get_string('userswhoneedtosubmit', 'assign', $userstr);
 731                      $cell2content .= $this->output->container($formatteduserstr);
 732                  }
 733  
 734                  $cell2attributes = array('class' => 'submissionstatus' . $status->teamsubmission->status);
 735              } else {
 736                  if (!$status->submissionsenabled) {
 737                      $cell2content = get_string('noonlinesubmissions', 'assign');
 738                  } else {
 739                      $cell2content = get_string('nosubmission', 'assign');
 740                  }
 741              }
 742          }
 743  
 744          $this->add_table_row_tuple($t, $cell1content, $cell2content, [], $cell2attributes);
 745  
 746          // Is locked?
 747          if ($status->locked) {
 748              $cell1content = '';
 749              $cell2content = get_string('submissionslocked', 'assign');
 750              $cell2attributes = array('class' => 'submissionlocked');
 751              $this->add_table_row_tuple($t, $cell1content, $cell2content, [], $cell2attributes);
 752          }
 753  
 754          // Grading status.
 755          $cell1content = get_string('gradingstatus', 'assign');
 756          if ($status->gradingstatus == ASSIGN_GRADING_STATUS_GRADED ||
 757              $status->gradingstatus == ASSIGN_GRADING_STATUS_NOT_GRADED) {
 758              $cell2content = get_string($status->gradingstatus, 'assign');
 759          } else {
 760              $gradingstatus = 'markingworkflowstate' . $status->gradingstatus;
 761              $cell2content = get_string($gradingstatus, 'assign');
 762          }
 763          if ($status->gradingstatus == ASSIGN_GRADING_STATUS_GRADED ||
 764              $status->gradingstatus == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
 765              $cell2attributes = array('class' => 'submissiongraded');
 766          } else {
 767              $cell2attributes = array('class' => 'submissionnotgraded');
 768          }
 769          $this->add_table_row_tuple($t, $cell1content, $cell2content, [], $cell2attributes);
 770  
 771          $submission = $status->teamsubmission ? $status->teamsubmission : $status->submission;
 772          $duedate = $status->duedate;
 773          if ($duedate > 0) {
 774              if ($status->view == assign_submission_status::GRADER_VIEW) {
 775                  if ($status->cutoffdate) {
 776                      // Cut off date.
 777                      $cell1content = get_string('cutoffdate', 'assign');
 778                      $cell2content = userdate($status->cutoffdate);
 779                      $this->add_table_row_tuple($t, $cell1content, $cell2content);
 780                  }
 781              }
 782  
 783              if ($status->extensionduedate) {
 784                  // Extension date.
 785                  $cell1content = get_string('extensionduedate', 'assign');
 786                  $cell2content = userdate($status->extensionduedate);
 787                  $this->add_table_row_tuple($t, $cell1content, $cell2content);
 788                  $duedate = $status->extensionduedate;
 789              }
 790          }
 791  
 792          // Time remaining.
 793          // Only add the row if there is a due date, or a countdown.
 794          if ($status->duedate > 0 || !empty($submission->timestarted)) {
 795              $cell1content = get_string('timeremaining', 'assign');
 796              [$cell2content, $cell2attributes] = $this->get_time_remaining($status);
 797              $this->add_table_row_tuple($t, $cell1content, $cell2content, [], ['class' => $cell2attributes]);
 798          }
 799  
 800          // Add time limit info if there is one.
 801          $timelimitenabled = get_config('assign', 'enabletimelimit') && $status->timelimit > 0;
 802          if ($timelimitenabled && $status->timelimit > 0) {
 803              $cell1content = get_string('timelimit', 'assign');
 804              $cell2content = format_time($status->timelimit);
 805              $this->add_table_row_tuple($t, $cell1content, $cell2content, [], []);
 806          }
 807  
 808          // Show graders whether this submission is editable by students.
 809          if ($status->view == assign_submission_status::GRADER_VIEW) {
 810              $cell1content = get_string('editingstatus', 'assign');
 811              if ($status->canedit) {
 812                  $cell2content = get_string('submissioneditable', 'assign');
 813                  $cell2attributes = array('class' => 'submissioneditable');
 814              } else {
 815                  $cell2content = get_string('submissionnoteditable', 'assign');
 816                  $cell2attributes = array('class' => 'submissionnoteditable');
 817              }
 818              $this->add_table_row_tuple($t, $cell1content, $cell2content, [], $cell2attributes);
 819          }
 820  
 821          // Last modified.
 822          if ($submission) {
 823              $cell1content = get_string('timemodified', 'assign');
 824  
 825              if ($submission->status != ASSIGN_SUBMISSION_STATUS_NEW) {
 826                  $cell2content = userdate($submission->timemodified);
 827              } else {
 828                  $cell2content = "-";
 829              }
 830  
 831              $this->add_table_row_tuple($t, $cell1content, $cell2content);
 832  
 833              if (!$status->teamsubmission || $status->submissiongroup != false || !$status->preventsubmissionnotingroup) {
 834                  foreach ($status->submissionplugins as $plugin) {
 835                      $pluginshowsummary = !$plugin->is_empty($submission) || !$plugin->allow_submissions();
 836                      if ($plugin->is_enabled() &&
 837                          $plugin->is_visible() &&
 838                          $plugin->has_user_summary() &&
 839                          $pluginshowsummary
 840                      ) {
 841  
 842                          $cell1content = $plugin->get_name();
 843                          $displaymode = \assign_submission_plugin_submission::SUMMARY;
 844                          $pluginsubmission = new \assign_submission_plugin_submission($plugin,
 845                              $submission,
 846                              $displaymode,
 847                              $status->coursemoduleid,
 848                              $status->returnaction,
 849                              $status->returnparams);
 850                          $cell2content = $this->render($pluginsubmission);
 851                          $this->add_table_row_tuple($t, $cell1content, $cell2content);
 852                      }
 853                  }
 854              }
 855          }
 856  
 857          $o .= $warningmsg;
 858          $o .= \html_writer::table($t);
 859          $o .= $this->output->box_end();
 860  
 861          // Grading criteria preview.
 862          if (!empty($status->gradingcontrollerpreview)) {
 863              $o .= $this->output->heading(get_string('gradingmethodpreview', 'assign'), 4);
 864              $o .= $status->gradingcontrollerpreview;
 865          }
 866  
 867          $o .= $this->output->container_end();
 868          return $o;
 869      }
 870  
 871      /**
 872       * Output the attempt history chooser for this assignment
 873       *
 874       * @param \assign_attempt_history_chooser $history
 875       * @return string
 876       */
 877      public function render_assign_attempt_history_chooser(\assign_attempt_history_chooser $history) {
 878          $o = '';
 879  
 880          $context = $history->export_for_template($this);
 881          $o .= $this->render_from_template('mod_assign/attempt_history_chooser', $context);
 882  
 883          return $o;
 884      }
 885  
 886      /**
 887       * Output the attempt history for this assignment
 888       *
 889       * @param \assign_attempt_history $history
 890       * @return string
 891       */
 892      public function render_assign_attempt_history(\assign_attempt_history $history) {
 893          $o = '';
 894  
 895          // Don't show the last one because it is the current submission.
 896          array_pop($history->submissions);
 897  
 898          // Show newest to oldest.
 899          $history->submissions = array_reverse($history->submissions);
 900  
 901          if (empty($history->submissions)) {
 902              return '';
 903          }
 904  
 905          $containerid = 'attempthistory' . uniqid();
 906          $o .= $this->output->heading(get_string('attempthistory', 'assign'), 3);
 907          $o .= $this->box_start('attempthistory', $containerid);
 908  
 909          foreach ($history->submissions as $i => $submission) {
 910              $grade = null;
 911              foreach ($history->grades as $onegrade) {
 912                  if ($onegrade->attemptnumber == $submission->attemptnumber) {
 913                      if ($onegrade->grade != ASSIGN_GRADE_NOT_SET) {
 914                          $grade = $onegrade;
 915                      }
 916                      break;
 917                  }
 918              }
 919  
 920              if ($submission) {
 921                  $submissionsummary = userdate($submission->timemodified);
 922              } else {
 923                  $submissionsummary = get_string('nosubmission', 'assign');
 924              }
 925  
 926              $attemptsummaryparams = array('attemptnumber'=>$submission->attemptnumber+1,
 927                                            'submissionsummary'=>$submissionsummary);
 928              $o .= $this->heading(get_string('attemptheading', 'assign', $attemptsummaryparams), 4);
 929  
 930              $t = new \html_table();
 931  
 932              if ($submission) {
 933                  $cell1content = get_string('submissionstatus', 'assign');
 934                  $cell2content = get_string('submissionstatus_' . $submission->status, 'assign');
 935                  $this->add_table_row_tuple($t, $cell1content, $cell2content);
 936  
 937                  foreach ($history->submissionplugins as $plugin) {
 938                      $pluginshowsummary = !$plugin->is_empty($submission) || !$plugin->allow_submissions();
 939                      if ($plugin->is_enabled() &&
 940                              $plugin->is_visible() &&
 941                              $plugin->has_user_summary() &&
 942                              $pluginshowsummary) {
 943  
 944                          $cell1content = $plugin->get_name();
 945                          $pluginsubmission = new \assign_submission_plugin_submission($plugin,
 946                                                                                      $submission,
 947                                                                                      \assign_submission_plugin_submission::SUMMARY,
 948                                                                                      $history->coursemoduleid,
 949                                                                                      $history->returnaction,
 950                                                                                      $history->returnparams);
 951                          $cell2content = $this->render($pluginsubmission);
 952                          $this->add_table_row_tuple($t, $cell1content, $cell2content);
 953                      }
 954                  }
 955              }
 956  
 957              if ($grade) {
 958                  // Heading 'feedback'.
 959                  $title = get_string('feedback', 'assign', $i);
 960                  $title .= $this->output->spacer(array('width'=>10));
 961                  if ($history->cangrade) {
 962                      // Edit previous feedback.
 963                      $returnparams = http_build_query($history->returnparams);
 964                      $urlparams = array('id' => $history->coursemoduleid,
 965                                     'rownum'=>$history->rownum,
 966                                     'useridlistid'=>$history->useridlistid,
 967                                     'attemptnumber'=>$grade->attemptnumber,
 968                                     'action'=>'grade',
 969                                     'returnaction'=>$history->returnaction,
 970                                     'returnparams'=>$returnparams);
 971                      $url = new \moodle_url('/mod/assign/view.php', $urlparams);
 972                      $icon = new \pix_icon('gradefeedback',
 973                                              get_string('editattemptfeedback', 'assign', $grade->attemptnumber+1),
 974                                              'mod_assign');
 975                      $title .= $this->output->action_icon($url, $icon);
 976                  }
 977                  $cell = new \html_table_cell($title);
 978                  $cell->attributes['class'] = 'feedbacktitle';
 979                  $cell->colspan = 2;
 980                  $t->data[] = new \html_table_row(array($cell));
 981  
 982                  // Grade.
 983                  $cell1content = get_string('gradenoun');
 984                  $cell2content = $grade->gradefordisplay;
 985                  $this->add_table_row_tuple($t, $cell1content, $cell2content);
 986  
 987                  // Graded on.
 988                  $cell1content = get_string('gradedon', 'assign');
 989                  $cell2content = userdate($grade->timemodified);
 990                  $this->add_table_row_tuple($t, $cell1content, $cell2content);
 991  
 992                  // Graded by set to a real user. Not set can be empty or -1.
 993                  if (!empty($grade->grader) && is_object($grade->grader)) {
 994                      $cell1content = get_string('gradedby', 'assign');
 995                      $cell2content = $this->output->user_picture($grade->grader) .
 996                                      $this->output->spacer(array('width' => 30)) . fullname($grade->grader);
 997                      $this->add_table_row_tuple($t, $cell1content, $cell2content);
 998                  }
 999  
1000                  // Feedback from plugins.
1001                  foreach ($history->feedbackplugins as $plugin) {
1002                      if ($plugin->is_enabled() &&
1003                          $plugin->is_visible() &&
1004                          $plugin->has_user_summary() &&
1005                          !$plugin->is_empty($grade)) {
1006  
1007                          $pluginfeedback = new \assign_feedback_plugin_feedback(
1008                              $plugin, $grade, \assign_feedback_plugin_feedback::SUMMARY, $history->coursemoduleid,
1009                              $history->returnaction, $history->returnparams
1010                          );
1011  
1012                          $cell1content = $plugin->get_name();
1013                          $cell2content = $this->render($pluginfeedback);
1014                          $this->add_table_row_tuple($t, $cell1content, $cell2content);
1015                      }
1016  
1017                  }
1018  
1019              }
1020  
1021              $o .= \html_writer::table($t);
1022          }
1023          $o .= $this->box_end();
1024  
1025          $this->page->requires->yui_module('moodle-mod_assign-history', 'Y.one("#' . $containerid . '").history');
1026  
1027          return $o;
1028      }
1029  
1030      /**
1031       * Render a submission plugin submission
1032       *
1033       * @param \assign_submission_plugin_submission $submissionplugin
1034       * @return string
1035       */
1036      public function render_assign_submission_plugin_submission(\assign_submission_plugin_submission $submissionplugin) {
1037          $o = '';
1038  
1039          if ($submissionplugin->view == \assign_submission_plugin_submission::SUMMARY) {
1040              $showviewlink = false;
1041              $summary = $submissionplugin->plugin->view_summary($submissionplugin->submission,
1042                                                                 $showviewlink);
1043  
1044              $classsuffix = $submissionplugin->plugin->get_subtype() .
1045                             '_' .
1046                             $submissionplugin->plugin->get_type() .
1047                             '_' .
1048                             $submissionplugin->submission->id;
1049  
1050              $o .= $this->output->box_start('boxaligncenter plugincontentsummary summary_' . $classsuffix);
1051  
1052              $link = '';
1053              if ($showviewlink) {
1054                  $previewstr = get_string('viewsubmission', 'assign');
1055                  $icon = $this->output->pix_icon('t/preview', $previewstr);
1056  
1057                  $expandstr = get_string('viewfull', 'assign');
1058                  $expandicon = $this->output->pix_icon('t/switch_plus', $expandstr);
1059                  $options = array(
1060                      'class' => 'expandsummaryicon expand_' . $classsuffix,
1061                      'aria-label' => $expandstr,
1062                      'role' => 'button',
1063                      'aria-expanded' => 'false'
1064                  );
1065                  $o .= \html_writer::link('', $expandicon, $options);
1066  
1067                  $jsparams = array($submissionplugin->plugin->get_subtype(),
1068                                    $submissionplugin->plugin->get_type(),
1069                                    $submissionplugin->submission->id);
1070  
1071                  $this->page->requires->js_init_call('M.mod_assign.init_plugin_summary', $jsparams);
1072  
1073                  $action = 'viewplugin' . $submissionplugin->plugin->get_subtype();
1074                  $returnparams = http_build_query($submissionplugin->returnparams);
1075                  $link .= '<noscript>';
1076                  $urlparams = array('id' => $submissionplugin->coursemoduleid,
1077                                     'sid'=>$submissionplugin->submission->id,
1078                                     'plugin'=>$submissionplugin->plugin->get_type(),
1079                                     'action'=>$action,
1080                                     'returnaction'=>$submissionplugin->returnaction,
1081                                     'returnparams'=>$returnparams);
1082                  $url = new \moodle_url('/mod/assign/view.php', $urlparams);
1083                  $link .= $this->output->action_link($url, $icon);
1084                  $link .= '</noscript>';
1085  
1086                  $link .= $this->output->spacer(array('width'=>15));
1087              }
1088  
1089              $o .= $link . $summary;
1090              $o .= $this->output->box_end();
1091              if ($showviewlink) {
1092                  $o .= $this->output->box_start('boxaligncenter hidefull full_' . $classsuffix);
1093                  $collapsestr = get_string('viewsummary', 'assign');
1094                  $options = array(
1095                      'class' => 'expandsummaryicon contract_' . $classsuffix,
1096                      'aria-label' => $collapsestr,
1097                      'role' => 'button',
1098                      'aria-expanded' => 'true'
1099                  );
1100                  $collapseicon = $this->output->pix_icon('t/switch_minus', $collapsestr);
1101                  $o .= \html_writer::link('', $collapseicon, $options);
1102  
1103                  $o .= $submissionplugin->plugin->view($submissionplugin->submission);
1104                  $o .= $this->output->box_end();
1105              }
1106          } else if ($submissionplugin->view == \assign_submission_plugin_submission::FULL) {
1107              $o .= $this->output->box_start('boxaligncenter submissionfull');
1108              $o .= $submissionplugin->plugin->view($submissionplugin->submission);
1109              $o .= $this->output->box_end();
1110          }
1111  
1112          return $o;
1113      }
1114  
1115      /**
1116       * Render the grading table.
1117       *
1118       * @param \assign_grading_table $table
1119       * @return string
1120       */
1121      public function render_assign_grading_table(\assign_grading_table $table) {
1122          $o = '';
1123          $o .= $this->output->box_start('boxaligncenter gradingtable position-relative');
1124  
1125          $this->page->requires->js_init_call('M.mod_assign.init_grading_table', array());
1126          $this->page->requires->string_for_js('nousersselected', 'assign');
1127          $this->page->requires->string_for_js('batchoperationconfirmgrantextension', 'assign');
1128          $this->page->requires->string_for_js('batchoperationconfirmlock', 'assign');
1129          $this->page->requires->string_for_js('batchoperationconfirmremovesubmission', 'assign');
1130          $this->page->requires->string_for_js('batchoperationconfirmreverttodraft', 'assign');
1131          $this->page->requires->string_for_js('batchoperationconfirmunlock', 'assign');
1132          $this->page->requires->string_for_js('batchoperationconfirmaddattempt', 'assign');
1133          $this->page->requires->string_for_js('batchoperationconfirmdownloadselected', 'assign');
1134          $this->page->requires->string_for_js('batchoperationconfirmsetmarkingworkflowstate', 'assign');
1135          $this->page->requires->string_for_js('batchoperationconfirmsetmarkingallocation', 'assign');
1136          $this->page->requires->string_for_js('editaction', 'assign');
1137          foreach ($table->plugingradingbatchoperations as $plugin => $operations) {
1138              foreach ($operations as $operation => $description) {
1139                  $this->page->requires->string_for_js('batchoperationconfirm' . $operation,
1140                                                       'assignfeedback_' . $plugin);
1141              }
1142          }
1143          $o .= $this->flexible_table($table, $table->get_rows_per_page(), true);
1144          $o .= $this->output->box_end();
1145  
1146          return $o;
1147      }
1148  
1149      /**
1150       * Render a feedback plugin feedback
1151       *
1152       * @param \assign_feedback_plugin_feedback $feedbackplugin
1153       * @return string
1154       */
1155      public function render_assign_feedback_plugin_feedback(\assign_feedback_plugin_feedback $feedbackplugin) {
1156          $o = '';
1157  
1158          if ($feedbackplugin->view == \assign_feedback_plugin_feedback::SUMMARY) {
1159              $showviewlink = false;
1160              $summary = $feedbackplugin->plugin->view_summary($feedbackplugin->grade, $showviewlink);
1161  
1162              $classsuffix = $feedbackplugin->plugin->get_subtype() .
1163                             '_' .
1164                             $feedbackplugin->plugin->get_type() .
1165                             '_' .
1166                             $feedbackplugin->grade->id;
1167              $o .= $this->output->box_start('boxaligncenter plugincontentsummary summary_' . $classsuffix);
1168  
1169              $link = '';
1170              if ($showviewlink) {
1171                  $previewstr = get_string('viewfeedback', 'assign');
1172                  $icon = $this->output->pix_icon('t/preview', $previewstr);
1173  
1174                  $expandstr = get_string('viewfull', 'assign');
1175                  $expandicon = $this->output->pix_icon('t/switch_plus', $expandstr);
1176                  $options = array(
1177                      'class' => 'expandsummaryicon expand_' . $classsuffix,
1178                      'aria-label' => $expandstr,
1179                      'role' => 'button',
1180                      'aria-expanded' => 'false'
1181                  );
1182                  $o .= \html_writer::link('', $expandicon, $options);
1183  
1184                  $jsparams = array($feedbackplugin->plugin->get_subtype(),
1185                                    $feedbackplugin->plugin->get_type(),
1186                                    $feedbackplugin->grade->id);
1187                  $this->page->requires->js_init_call('M.mod_assign.init_plugin_summary', $jsparams);
1188  
1189                  $urlparams = array('id' => $feedbackplugin->coursemoduleid,
1190                                     'gid'=>$feedbackplugin->grade->id,
1191                                     'plugin'=>$feedbackplugin->plugin->get_type(),
1192                                     'action'=>'viewplugin' . $feedbackplugin->plugin->get_subtype(),
1193                                     'returnaction'=>$feedbackplugin->returnaction,
1194                                     'returnparams'=>http_build_query($feedbackplugin->returnparams));
1195                  $url = new \moodle_url('/mod/assign/view.php', $urlparams);
1196                  $link .= '<noscript>';
1197                  $link .= $this->output->action_link($url, $icon);
1198                  $link .= '</noscript>';
1199  
1200                  $link .= $this->output->spacer(array('width'=>15));
1201              }
1202  
1203              $o .= $link . $summary;
1204              $o .= $this->output->box_end();
1205              if ($showviewlink) {
1206                  $o .= $this->output->box_start('boxaligncenter hidefull full_' . $classsuffix);
1207                  $collapsestr = get_string('viewsummary', 'assign');
1208                  $options = array(
1209                      'class' => 'expandsummaryicon contract_' . $classsuffix,
1210                      'aria-label' => $collapsestr,
1211                      'role' => 'button',
1212                      'aria-expanded' => 'true'
1213                  );
1214                  $collapseicon = $this->output->pix_icon('t/switch_minus', $collapsestr);
1215                  $o .= \html_writer::link('', $collapseicon, $options);
1216  
1217                  $o .= $feedbackplugin->plugin->view($feedbackplugin->grade);
1218                  $o .= $this->output->box_end();
1219              }
1220          } else if ($feedbackplugin->view == \assign_feedback_plugin_feedback::FULL) {
1221              $o .= $this->output->box_start('boxaligncenter feedbackfull');
1222              $o .= $feedbackplugin->plugin->view($feedbackplugin->grade);
1223              $o .= $this->output->box_end();
1224          }
1225  
1226          return $o;
1227      }
1228  
1229      /**
1230       * Render a course index summary
1231       *
1232       * @param \assign_course_index_summary $indexsummary
1233       * @return string
1234       */
1235      public function render_assign_course_index_summary(\assign_course_index_summary $indexsummary) {
1236          $o = '';
1237  
1238          $strplural = get_string('modulenameplural', 'assign');
1239          $strsectionname  = $indexsummary->courseformatname;
1240          $strduedate = get_string('duedate', 'assign');
1241          $strsubmission = get_string('submission', 'assign');
1242          $strgrade = get_string('gradenoun');
1243  
1244          $table = new \html_table();
1245          if ($indexsummary->usesections) {
1246              $table->head  = array ($strsectionname, $strplural, $strduedate, $strsubmission, $strgrade);
1247              $table->align = array ('left', 'left', 'center', 'right', 'right');
1248          } else {
1249              $table->head  = array ($strplural, $strduedate, $strsubmission, $strgrade);
1250              $table->align = array ('left', 'left', 'center', 'right');
1251          }
1252          $table->data = array();
1253  
1254          $currentsection = '';
1255          foreach ($indexsummary->assignments as $info) {
1256              $params = array('id' => $info['cmid']);
1257              $link = \html_writer::link(new \moodle_url('/mod/assign/view.php', $params),
1258                                        $info['cmname']);
1259              $due = $info['timedue'] ? userdate($info['timedue']) : '-';
1260  
1261              if ($info['cangrade']) {
1262                  $params['action'] = 'grading';
1263                  $gradeinfo = \html_writer::link(new \moodle_url('/mod/assign/view.php', $params),
1264                      get_string('numberofsubmissionsneedgradinglabel', 'assign', $info['gradeinfo']));
1265              } else {
1266                  $gradeinfo = $info['gradeinfo'];
1267              }
1268  
1269              $printsection = '';
1270              if ($indexsummary->usesections) {
1271                  if ($info['sectionname'] !== $currentsection) {
1272                      if ($info['sectionname']) {
1273                          $printsection = $info['sectionname'];
1274                      }
1275                      if ($currentsection !== '') {
1276                          $table->data[] = 'hr';
1277                      }
1278                      $currentsection = $info['sectionname'];
1279                  }
1280              }
1281  
1282              if ($indexsummary->usesections) {
1283                  $row = [$printsection, $link, $due, $info['submissioninfo'], $gradeinfo];
1284              } else {
1285                  $row = [$link, $due, $info['submissioninfo'], $gradeinfo];
1286              }
1287              $table->data[] = $row;
1288          }
1289  
1290          $o .= \html_writer::table($table);
1291  
1292          return $o;
1293      }
1294  
1295      /**
1296       * Get the time remaining for a submission.
1297       *
1298       * @param \mod_assign\output\assign_submission_status $status
1299       * @return array The first element is the time remaining as a human readable
1300       *               string and the second is a CSS class.
1301       */
1302      protected function get_time_remaining(\mod_assign\output\assign_submission_status $status): array {
1303          $time = time();
1304          $submission = $status->teamsubmission ? $status->teamsubmission : $status->submission;
1305          $submissionstarted = $submission && property_exists($submission, 'timestarted') && $submission->timestarted;
1306          $timelimitenabled = get_config('assign', 'enabletimelimit') && $status->timelimit > 0 && $submissionstarted;
1307          // Define $duedate as latest between due date and extension - which is a possibility...
1308          $extensionduedate = intval($status->extensionduedate);
1309          $duedate = !empty($extensionduedate) ? max($status->duedate, $extensionduedate) : $status->duedate;
1310          $duedatereached = $duedate > 0 && $duedate - $time <= 0;
1311          $timelimitenabledbeforeduedate = $timelimitenabled && !$duedatereached;
1312  
1313          // There is a submission, display the relevant early/late message.
1314          if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
1315              $latecalculation = $submission->timemodified - ($timelimitenabledbeforeduedate ? $submission->timestarted : 0);
1316              $latethreshold = $timelimitenabledbeforeduedate ? $status->timelimit : $duedate;
1317              $earlystring = $timelimitenabledbeforeduedate ? 'submittedundertime' : 'submittedearly';
1318              $latestring = $timelimitenabledbeforeduedate ? 'submittedovertime' : 'submittedlate';
1319              $ontime = $latecalculation <= $latethreshold;
1320              return [
1321                  get_string(
1322                      $ontime ? $earlystring : $latestring,
1323                      'assign',
1324                      format_time($latecalculation - $latethreshold)
1325                  ),
1326                  $ontime ? 'earlysubmission' : 'latesubmission'
1327              ];
1328          }
1329  
1330          // There is no submission, due date has passed, show assignment is overdue.
1331          if ($duedatereached) {
1332              return [
1333                  get_string(
1334                      $status->submissionsenabled ? 'overdue' : 'duedatereached',
1335                      'assign',
1336                      format_time($time - $duedate)
1337                  ),
1338                  'overdue'
1339              ];
1340          }
1341  
1342          // An attempt has started and there is a time limit, display the time limit.
1343          if ($timelimitenabled && !empty($submission->timestarted)) {
1344              return [
1345                  (new \assign($status->context, null, null))->get_timelimit_panel($submission),
1346                  'timeremaining'
1347              ];
1348          }
1349  
1350          // Assignment is not overdue, and no submission has been made. Just display the due date.
1351          return [get_string('paramtimeremaining', 'assign', format_time($duedate - $time)), 'timeremaining'];
1352      }
1353  
1354      /**
1355       * Internal function - creates htmls structure suitable for YUI tree.
1356       *
1357       * @param \assign_files $tree
1358       * @param array $dir
1359       * @return string
1360       */
1361      protected function htmllize_tree(\assign_files $tree, $dir) {
1362          global $CFG;
1363          $yuiconfig = array();
1364          $yuiconfig['type'] = 'html';
1365  
1366          if (empty($dir['subdirs']) and empty($dir['files'])) {
1367              return '';
1368          }
1369  
1370          $result = '<ul>';
1371          foreach ($dir['subdirs'] as $subdir) {
1372              $image = $this->output->pix_icon(file_folder_icon(),
1373                                               $subdir['dirname'],
1374                                               'moodle',
1375                                               array('class'=>'icon'));
1376              $result .= '<li yuiConfig=\'' . json_encode($yuiconfig) . '\'>' .
1377                         '<div>' . $image . ' ' . s($subdir['dirname']) . '</div> ' .
1378                         $this->htmllize_tree($tree, $subdir) .
1379                         '</li>';
1380          }
1381  
1382          foreach ($dir['files'] as $file) {
1383              $filename = $file->get_filename();
1384              if ($CFG->enableplagiarism) {
1385                  require_once($CFG->libdir.'/plagiarismlib.php');
1386                  $plagiarismlinks = plagiarism_get_links(array('userid'=>$file->get_userid(),
1387                                                               'file'=>$file,
1388                                                               'cmid'=>$tree->cm->id,
1389                                                               'course'=>$tree->course));
1390              } else {
1391                  $plagiarismlinks = '';
1392              }
1393              $image = $this->output->pix_icon(file_file_icon($file),
1394                                               $filename,
1395                                               'moodle',
1396                                               array('class'=>'icon'));
1397              $result .= '<li yuiConfig=\'' . json_encode($yuiconfig) . '\'>' .
1398                  '<div>' .
1399                      '<div class="fileuploadsubmission">' . $image . ' ' .
1400                      $file->fileurl . ' ' .
1401                      $plagiarismlinks . ' ' .
1402                      $file->portfoliobutton . ' ' .
1403                      '</div>' .
1404                      '<div class="fileuploadsubmissiontime">' . $file->timemodified . '</div>' .
1405                  '</div>' .
1406              '</li>';
1407          }
1408  
1409          $result .= '</ul>';
1410  
1411          return $result;
1412      }
1413  
1414      /**
1415       * Helper method dealing with the fact we can not just fetch the output of flexible_table
1416       *
1417       * @param \flexible_table $table The table to render
1418       * @param int $rowsperpage How many assignments to render in a page
1419       * @param bool $displaylinks - Whether to render links in the table
1420       *                             (e.g. downloads would not enable this)
1421       * @return string HTML
1422       */
1423      protected function flexible_table(\flexible_table $table, $rowsperpage, $displaylinks) {
1424  
1425          $o = '';
1426          ob_start();
1427          $table->out($rowsperpage, $displaylinks);
1428          $o = ob_get_contents();
1429          ob_end_clean();
1430  
1431          return $o;
1432      }
1433  
1434      /**
1435       * Helper method dealing with the fact we can not just fetch the output of moodleforms
1436       *
1437       * @param \moodleform $mform
1438       * @return string HTML
1439       */
1440      protected function moodleform(\moodleform $mform) {
1441  
1442          $o = '';
1443          ob_start();
1444          $mform->display();
1445          $o = ob_get_contents();
1446          ob_end_clean();
1447  
1448          return $o;
1449      }
1450  
1451      /**
1452       * Defer to template.
1453       *
1454       * @param grading_app $app - All the data to render the grading app.
1455       */
1456      public function render_grading_app(grading_app $app) {
1457          $context = $app->export_for_template($this);
1458          return $this->render_from_template('mod_assign/grading_app', $context);
1459      }
1460  
1461      /**
1462       * Renders the submission action menu.
1463       *
1464       * @param \mod_assign\output\actionmenu $actionmenu The actionmenu
1465       * @return string Rendered action menu.
1466       */
1467      public function submission_actionmenu(\mod_assign\output\actionmenu $actionmenu): string {
1468          $context = $actionmenu->export_for_template($this);
1469          return $this->render_from_template('mod_assign/submission_actionmenu', $context);
1470      }
1471  
1472      /**
1473       * Renders the user submission action menu.
1474       *
1475       * @param \mod_assign\output\user_submission_actionmenu $actionmenu The actionmenu
1476       * @return string The rendered action menu.
1477       */
1478      public function render_user_submission_actionmenu(\mod_assign\output\user_submission_actionmenu $actionmenu): string {
1479          $context = $actionmenu->export_for_template($this);
1480          return $this->render_from_template('mod_assign/user_submission_actionmenu', $context);
1481      }
1482  
1483      /**
1484       * Renders the override action menu.
1485       *
1486       * @param \mod_assign\output\override_actionmenu $actionmenu The actionmenu
1487       * @return string The rendered override action menu.
1488       */
1489      public function render_override_actionmenu(\mod_assign\output\override_actionmenu $actionmenu): string {
1490          $context = $actionmenu->export_for_template($this);
1491          return $this->render_from_template('mod_assign/override_actionmenu', $context);
1492      }
1493  
1494      /**
1495       * Renders the grading action menu.
1496       *
1497       * @param \mod_assign\output\grading_actionmenu $actionmenu The actionmenu
1498       * @return string The rendered grading action menu.
1499       */
1500      public function render_grading_actionmenu(\mod_assign\output\grading_actionmenu $actionmenu): string {
1501          $context = $actionmenu->export_for_template($this);
1502          return $this->render_from_template('mod_assign/grading_actionmenu', $context);
1503      }
1504  
1505      /**
1506       * Formats activity intro text.
1507       *
1508       * @param object $assign Instance of assign.
1509       * @param int $cmid Course module ID.
1510       * @return string
1511       */
1512      public function format_activity_text($assign, $cmid) {
1513          global $CFG;
1514          require_once("$CFG->libdir/filelib.php");
1515          $context = \context_module::instance($cmid);
1516          $options = array('noclean' => true, 'para' => false, 'filter' => true, 'context' => $context, 'overflowdiv' => true);
1517          $activity = file_rewrite_pluginfile_urls(
1518              $assign->activity, 'pluginfile.php', $context->id, 'mod_assign', ASSIGN_ACTIVITYATTACHMENT_FILEAREA, 0);
1519          return trim(format_text($activity, $assign->activityformat, $options, null));
1520      }
1521  }