Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

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

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