[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/mod/assign/ -> locallib.php (source)

   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 the definition for the class assignment
  19   *
  20   * This class provides all the functionality for the new assign module.
  21   *
  22   * @package   mod_assign
  23   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  24   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  // Assignment submission statuses.
  30  define('ASSIGN_SUBMISSION_STATUS_NEW', 'new');
  31  define('ASSIGN_SUBMISSION_STATUS_REOPENED', 'reopened');
  32  define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft');
  33  define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted');
  34  
  35  // Search filters for grading page.
  36  define('ASSIGN_FILTER_SUBMITTED', 'submitted');
  37  define('ASSIGN_FILTER_NOT_SUBMITTED', 'notsubmitted');
  38  define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
  39  define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
  40  
  41  // Marker filter for grading page.
  42  define('ASSIGN_MARKER_FILTER_NO_MARKER', -1);
  43  
  44  // Reopen attempt methods.
  45  define('ASSIGN_ATTEMPT_REOPEN_METHOD_NONE', 'none');
  46  define('ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL', 'manual');
  47  define('ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS', 'untilpass');
  48  
  49  // Special value means allow unlimited attempts.
  50  define('ASSIGN_UNLIMITED_ATTEMPTS', -1);
  51  
  52  // Grading states.
  53  define('ASSIGN_GRADING_STATUS_GRADED', 'graded');
  54  define('ASSIGN_GRADING_STATUS_NOT_GRADED', 'notgraded');
  55  
  56  // Marking workflow states.
  57  define('ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED', 'notmarked');
  58  define('ASSIGN_MARKING_WORKFLOW_STATE_INMARKING', 'inmarking');
  59  define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW', 'readyforreview');
  60  define('ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW', 'inreview');
  61  define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease');
  62  define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released');
  63  
  64  // Name of file area for intro attachments.
  65  define('ASSIGN_INTROATTACHMENT_FILEAREA', 'introattachment');
  66  
  67  require_once($CFG->libdir . '/accesslib.php');
  68  require_once($CFG->libdir . '/formslib.php');
  69  require_once($CFG->dirroot . '/repository/lib.php');
  70  require_once($CFG->dirroot . '/mod/assign/mod_form.php');
  71  require_once($CFG->libdir . '/gradelib.php');
  72  require_once($CFG->dirroot . '/grade/grading/lib.php');
  73  require_once($CFG->dirroot . '/mod/assign/feedbackplugin.php');
  74  require_once($CFG->dirroot . '/mod/assign/submissionplugin.php');
  75  require_once($CFG->dirroot . '/mod/assign/renderable.php');
  76  require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
  77  require_once($CFG->libdir . '/eventslib.php');
  78  require_once($CFG->libdir . '/portfolio/caller.php');
  79  
  80  use \mod_assign\output\grading_app;
  81  
  82  /**
  83   * Standard base class for mod_assign (assignment types).
  84   *
  85   * @package   mod_assign
  86   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  87   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  88   */
  89  class assign {
  90  
  91      /** @var stdClass the assignment record that contains the global settings for this assign instance */
  92      private $instance;
  93  
  94      /** @var stdClass the grade_item record for this assign instance's primary grade item. */
  95      private $gradeitem;
  96  
  97      /** @var context the context of the course module for this assign instance
  98       *               (or just the course if we are creating a new one)
  99       */
 100      private $context;
 101  
 102      /** @var stdClass the course this assign instance belongs to */
 103      private $course;
 104  
 105      /** @var stdClass the admin config for all assign instances  */
 106      private $adminconfig;
 107  
 108      /** @var assign_renderer the custom renderer for this module */
 109      private $output;
 110  
 111      /** @var cm_info the course module for this assign instance */
 112      private $coursemodule;
 113  
 114      /** @var array cache for things like the coursemodule name or the scale menu -
 115       *             only lives for a single request.
 116       */
 117      private $cache;
 118  
 119      /** @var array list of the installed submission plugins */
 120      private $submissionplugins;
 121  
 122      /** @var array list of the installed feedback plugins */
 123      private $feedbackplugins;
 124  
 125      /** @var string action to be used to return to this page
 126       *              (without repeating any form submissions etc).
 127       */
 128      private $returnaction = 'view';
 129  
 130      /** @var array params to be used to return to this page */
 131      private $returnparams = array();
 132  
 133      /** @var string modulename prevents excessive calls to get_string */
 134      private static $modulename = null;
 135  
 136      /** @var string modulenameplural prevents excessive calls to get_string */
 137      private static $modulenameplural = null;
 138  
 139      /** @var array of marking workflow states for the current user */
 140      private $markingworkflowstates = null;
 141  
 142      /** @var bool whether to exclude users with inactive enrolment */
 143      private $showonlyactiveenrol = null;
 144  
 145      /** @var string A key used to identify userlists created by this object. */
 146      private $useridlistid = null;
 147  
 148      /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
 149      private $participants = array();
 150  
 151      /** @var array cached list of user groups when team submissions are enabled. The cache key will be the user. */
 152      private $usersubmissiongroups = array();
 153  
 154      /** @var array cached list of user groups. The cache key will be the user. */
 155      private $usergroups = array();
 156  
 157      /** @var array cached list of IDs of users who share group membership with the user. The cache key will be the user. */
 158      private $sharedgroupmembers = array();
 159  
 160      /**
 161       * Constructor for the base assign class.
 162       *
 163       * Note: For $coursemodule you can supply a stdclass if you like, but it
 164       * will be more efficient to supply a cm_info object.
 165       *
 166       * @param mixed $coursemodulecontext context|null the course module context
 167       *                                   (or the course context if the coursemodule has not been
 168       *                                   created yet).
 169       * @param mixed $coursemodule the current course module if it was already loaded,
 170       *                            otherwise this class will load one from the context as required.
 171       * @param mixed $course the current course  if it was already loaded,
 172       *                      otherwise this class will load one from the context as required.
 173       */
 174      public function __construct($coursemodulecontext, $coursemodule, $course) {
 175          global $SESSION;
 176  
 177          $this->context = $coursemodulecontext;
 178          $this->course = $course;
 179  
 180          // Ensure that $this->coursemodule is a cm_info object (or null).
 181          $this->coursemodule = cm_info::create($coursemodule);
 182  
 183          // Temporary cache only lives for a single request - used to reduce db lookups.
 184          $this->cache = array();
 185  
 186          $this->submissionplugins = $this->load_plugins('assignsubmission');
 187          $this->feedbackplugins = $this->load_plugins('assignfeedback');
 188  
 189          // Extra entropy is required for uniqid() to work on cygwin.
 190          $this->useridlistid = clean_param(uniqid('', true), PARAM_ALPHANUM);
 191  
 192          if (!isset($SESSION->mod_assign_useridlist)) {
 193              $SESSION->mod_assign_useridlist = [];
 194          }
 195      }
 196  
 197      /**
 198       * Set the action and parameters that can be used to return to the current page.
 199       *
 200       * @param string $action The action for the current page
 201       * @param array $params An array of name value pairs which form the parameters
 202       *                      to return to the current page.
 203       * @return void
 204       */
 205      public function register_return_link($action, $params) {
 206          global $PAGE;
 207          $params['action'] = $action;
 208          $cm = $this->get_course_module();
 209          if ($cm) {
 210              $currenturl = new moodle_url('/mod/assign/view.php', array('id' => $cm->id));
 211          } else {
 212              $currenturl = new moodle_url('/mod/assign/index.php', array('id' => $this->get_course()->id));
 213          }
 214  
 215          $currenturl->params($params);
 216          $PAGE->set_url($currenturl);
 217      }
 218  
 219      /**
 220       * Return an action that can be used to get back to the current page.
 221       *
 222       * @return string action
 223       */
 224      public function get_return_action() {
 225          global $PAGE;
 226  
 227          // Web services don't set a URL, we should avoid debugging when ussing the url object.
 228          if (!WS_SERVER) {
 229              $params = $PAGE->url->params();
 230          }
 231  
 232          if (!empty($params['action'])) {
 233              return $params['action'];
 234          }
 235          return '';
 236      }
 237  
 238      /**
 239       * Based on the current assignment settings should we display the intro.
 240       *
 241       * @return bool showintro
 242       */
 243      public function show_intro() {
 244          if ($this->get_instance()->alwaysshowdescription ||
 245                  time() > $this->get_instance()->allowsubmissionsfromdate) {
 246              return true;
 247          }
 248          return false;
 249      }
 250  
 251      /**
 252       * Return a list of parameters that can be used to get back to the current page.
 253       *
 254       * @return array params
 255       */
 256      public function get_return_params() {
 257          global $PAGE;
 258  
 259          $params = $PAGE->url->params();
 260          unset($params['id']);
 261          unset($params['action']);
 262          return $params;
 263      }
 264  
 265      /**
 266       * Set the submitted form data.
 267       *
 268       * @param stdClass $data The form data (instance)
 269       */
 270      public function set_instance(stdClass $data) {
 271          $this->instance = $data;
 272      }
 273  
 274      /**
 275       * Set the context.
 276       *
 277       * @param context $context The new context
 278       */
 279      public function set_context(context $context) {
 280          $this->context = $context;
 281      }
 282  
 283      /**
 284       * Set the course data.
 285       *
 286       * @param stdClass $course The course data
 287       */
 288      public function set_course(stdClass $course) {
 289          $this->course = $course;
 290      }
 291  
 292      /**
 293       * Get list of feedback plugins installed.
 294       *
 295       * @return array
 296       */
 297      public function get_feedback_plugins() {
 298          return $this->feedbackplugins;
 299      }
 300  
 301      /**
 302       * Get list of submission plugins installed.
 303       *
 304       * @return array
 305       */
 306      public function get_submission_plugins() {
 307          return $this->submissionplugins;
 308      }
 309  
 310      /**
 311       * Is blind marking enabled and reveal identities not set yet?
 312       *
 313       * @return bool
 314       */
 315      public function is_blind_marking() {
 316          return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
 317      }
 318  
 319      /**
 320       * Does an assignment have submission(s) or grade(s) already?
 321       *
 322       * @return bool
 323       */
 324      public function has_submissions_or_grades() {
 325          $allgrades = $this->count_grades();
 326          $allsubmissions = $this->count_submissions();
 327          if (($allgrades == 0) && ($allsubmissions == 0)) {
 328              return false;
 329          }
 330          return true;
 331      }
 332  
 333      /**
 334       * Get a specific submission plugin by its type.
 335       *
 336       * @param string $subtype assignsubmission | assignfeedback
 337       * @param string $type
 338       * @return mixed assign_plugin|null
 339       */
 340      public function get_plugin_by_type($subtype, $type) {
 341          $shortsubtype = substr($subtype, strlen('assign'));
 342          $name = $shortsubtype . 'plugins';
 343          if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
 344              return null;
 345          }
 346          $pluginlist = $this->$name;
 347          foreach ($pluginlist as $plugin) {
 348              if ($plugin->get_type() == $type) {
 349                  return $plugin;
 350              }
 351          }
 352          return null;
 353      }
 354  
 355      /**
 356       * Get a feedback plugin by type.
 357       *
 358       * @param string $type - The type of plugin e.g comments
 359       * @return mixed assign_feedback_plugin|null
 360       */
 361      public function get_feedback_plugin_by_type($type) {
 362          return $this->get_plugin_by_type('assignfeedback', $type);
 363      }
 364  
 365      /**
 366       * Get a submission plugin by type.
 367       *
 368       * @param string $type - The type of plugin e.g comments
 369       * @return mixed assign_submission_plugin|null
 370       */
 371      public function get_submission_plugin_by_type($type) {
 372          return $this->get_plugin_by_type('assignsubmission', $type);
 373      }
 374  
 375      /**
 376       * Load the plugins from the sub folders under subtype.
 377       *
 378       * @param string $subtype - either submission or feedback
 379       * @return array - The sorted list of plugins
 380       */
 381      protected function load_plugins($subtype) {
 382          global $CFG;
 383          $result = array();
 384  
 385          $names = core_component::get_plugin_list($subtype);
 386  
 387          foreach ($names as $name => $path) {
 388              if (file_exists($path . '/locallib.php')) {
 389                  require_once ($path . '/locallib.php');
 390  
 391                  $shortsubtype = substr($subtype, strlen('assign'));
 392                  $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
 393  
 394                  $plugin = new $pluginclass($this, $name);
 395  
 396                  if ($plugin instanceof assign_plugin) {
 397                      $idx = $plugin->get_sort_order();
 398                      while (array_key_exists($idx, $result)) {
 399                          $idx +=1;
 400                      }
 401                      $result[$idx] = $plugin;
 402                  }
 403              }
 404          }
 405          ksort($result);
 406          return $result;
 407      }
 408  
 409      /**
 410       * Display the assignment, used by view.php
 411       *
 412       * The assignment is displayed differently depending on your role,
 413       * the settings for the assignment and the status of the assignment.
 414       *
 415       * @param string $action The current action if any.
 416       * @param array $args Optional arguments to pass to the view (instead of getting them from GET and POST).
 417       * @return string - The page output.
 418       */
 419      public function view($action='', $args = array()) {
 420          global $PAGE;
 421  
 422          $o = '';
 423          $mform = null;
 424          $notices = array();
 425          $nextpageparams = array();
 426  
 427          if (!empty($this->get_course_module()->id)) {
 428              $nextpageparams['id'] = $this->get_course_module()->id;
 429          }
 430  
 431          // Handle form submissions first.
 432          if ($action == 'savesubmission') {
 433              $action = 'editsubmission';
 434              if ($this->process_save_submission($mform, $notices)) {
 435                  $action = 'redirect';
 436                  $nextpageparams['action'] = 'view';
 437              }
 438          } else if ($action == 'editprevioussubmission') {
 439              $action = 'editsubmission';
 440              if ($this->process_copy_previous_attempt($notices)) {
 441                  $action = 'redirect';
 442                  $nextpageparams['action'] = 'editsubmission';
 443              }
 444          } else if ($action == 'lock') {
 445              $this->process_lock_submission();
 446              $action = 'redirect';
 447              $nextpageparams['action'] = 'grading';
 448          } else if ($action == 'addattempt') {
 449              $this->process_add_attempt(required_param('userid', PARAM_INT));
 450              $action = 'redirect';
 451              $nextpageparams['action'] = 'grading';
 452          } else if ($action == 'reverttodraft') {
 453              $this->process_revert_to_draft();
 454              $action = 'redirect';
 455              $nextpageparams['action'] = 'grading';
 456          } else if ($action == 'unlock') {
 457              $this->process_unlock_submission();
 458              $action = 'redirect';
 459              $nextpageparams['action'] = 'grading';
 460          } else if ($action == 'setbatchmarkingworkflowstate') {
 461              $this->process_set_batch_marking_workflow_state();
 462              $action = 'redirect';
 463              $nextpageparams['action'] = 'grading';
 464          } else if ($action == 'setbatchmarkingallocation') {
 465              $this->process_set_batch_marking_allocation();
 466              $action = 'redirect';
 467              $nextpageparams['action'] = 'grading';
 468          } else if ($action == 'confirmsubmit') {
 469              $action = 'submit';
 470              if ($this->process_submit_for_grading($mform, $notices)) {
 471                  $action = 'redirect';
 472                  $nextpageparams['action'] = 'view';
 473              } else if ($notices) {
 474                  $action = 'viewsubmitforgradingerror';
 475              }
 476          } else if ($action == 'submitotherforgrading') {
 477              if ($this->process_submit_other_for_grading($mform, $notices)) {
 478                  $action = 'redirect';
 479                  $nextpageparams['action'] = 'grading';
 480              } else {
 481                  $action = 'viewsubmitforgradingerror';
 482              }
 483          } else if ($action == 'gradingbatchoperation') {
 484              $action = $this->process_grading_batch_operation($mform);
 485              if ($action == 'grading') {
 486                  $action = 'redirect';
 487                  $nextpageparams['action'] = 'grading';
 488              }
 489          } else if ($action == 'submitgrade') {
 490              if (optional_param('saveandshownext', null, PARAM_RAW)) {
 491                  // Save and show next.
 492                  $action = 'grade';
 493                  if ($this->process_save_grade($mform)) {
 494                      $action = 'redirect';
 495                      $nextpageparams['action'] = 'grade';
 496                      $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
 497                      $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
 498                  }
 499              } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
 500                  $action = 'redirect';
 501                  $nextpageparams['action'] = 'grade';
 502                  $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1;
 503                  $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
 504              } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
 505                  $action = 'redirect';
 506                  $nextpageparams['action'] = 'grade';
 507                  $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
 508                  $nextpageparams['useridlistid'] = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
 509              } else if (optional_param('savegrade', null, PARAM_RAW)) {
 510                  // Save changes button.
 511                  $action = 'grade';
 512                  if ($this->process_save_grade($mform)) {
 513                      $action = 'redirect';
 514                      $nextpageparams['action'] = 'savegradingresult';
 515                  }
 516              } else {
 517                  // Cancel button.
 518                  $action = 'redirect';
 519                  $nextpageparams['action'] = 'grading';
 520              }
 521          } else if ($action == 'quickgrade') {
 522              $message = $this->process_save_quick_grades();
 523              $action = 'quickgradingresult';
 524          } else if ($action == 'saveoptions') {
 525              $this->process_save_grading_options();
 526              $action = 'redirect';
 527              $nextpageparams['action'] = 'grading';
 528          } else if ($action == 'saveextension') {
 529              $action = 'grantextension';
 530              if ($this->process_save_extension($mform)) {
 531                  $action = 'redirect';
 532                  $nextpageparams['action'] = 'grading';
 533              }
 534          } else if ($action == 'revealidentitiesconfirm') {
 535              $this->process_reveal_identities();
 536              $action = 'redirect';
 537              $nextpageparams['action'] = 'grading';
 538          }
 539  
 540          $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
 541                                'useridlistid' => optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM));
 542          $this->register_return_link($action, $returnparams);
 543  
 544          // Include any page action as part of the body tag CSS id.
 545          if (!empty($action)) {
 546              $PAGE->set_pagetype('mod-assign-' . $action);
 547          }
 548          // Now show the right view page.
 549          if ($action == 'redirect') {
 550              $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams);
 551              redirect($nextpageurl);
 552              return;
 553          } else if ($action == 'savegradingresult') {
 554              $message = get_string('gradingchangessaved', 'assign');
 555              $o .= $this->view_savegrading_result($message);
 556          } else if ($action == 'quickgradingresult') {
 557              $mform = null;
 558              $o .= $this->view_quickgrading_result($message);
 559          } else if ($action == 'gradingpanel') {
 560              $o .= $this->view_single_grading_panel($args);
 561          } else if ($action == 'grade') {
 562              $o .= $this->view_single_grade_page($mform);
 563          } else if ($action == 'viewpluginassignfeedback') {
 564              $o .= $this->view_plugin_content('assignfeedback');
 565          } else if ($action == 'viewpluginassignsubmission') {
 566              $o .= $this->view_plugin_content('assignsubmission');
 567          } else if ($action == 'editsubmission') {
 568              $o .= $this->view_edit_submission_page($mform, $notices);
 569          } else if ($action == 'grader') {
 570              $o .= $this->view_grader();
 571          } else if ($action == 'grading') {
 572              $o .= $this->view_grading_page();
 573          } else if ($action == 'downloadall') {
 574              $o .= $this->download_submissions();
 575          } else if ($action == 'submit') {
 576              $o .= $this->check_submit_for_grading($mform);
 577          } else if ($action == 'grantextension') {
 578              $o .= $this->view_grant_extension($mform);
 579          } else if ($action == 'revealidentities') {
 580              $o .= $this->view_reveal_identities_confirm($mform);
 581          } else if ($action == 'plugingradingbatchoperation') {
 582              $o .= $this->view_plugin_grading_batch_operation($mform);
 583          } else if ($action == 'viewpluginpage') {
 584               $o .= $this->view_plugin_page();
 585          } else if ($action == 'viewcourseindex') {
 586               $o .= $this->view_course_index();
 587          } else if ($action == 'viewbatchsetmarkingworkflowstate') {
 588               $o .= $this->view_batch_set_workflow_state($mform);
 589          } else if ($action == 'viewbatchmarkingallocation') {
 590              $o .= $this->view_batch_markingallocation($mform);
 591          } else if ($action == 'viewsubmitforgradingerror') {
 592              $o .= $this->view_error_page(get_string('submitforgrading', 'assign'), $notices);
 593          } else {
 594              $o .= $this->view_submission_page();
 595          }
 596  
 597          return $o;
 598      }
 599  
 600      /**
 601       * Add this instance to the database.
 602       *
 603       * @param stdClass $formdata The data submitted from the form
 604       * @param bool $callplugins This is used to skip the plugin code
 605       *             when upgrading an old assignment to a new one (the plugins get called manually)
 606       * @return mixed false if an error occurs or the int id of the new instance
 607       */
 608      public function add_instance(stdClass $formdata, $callplugins) {
 609          global $DB;
 610          $adminconfig = $this->get_admin_config();
 611  
 612          $err = '';
 613  
 614          // Add the database record.
 615          $update = new stdClass();
 616          $update->name = $formdata->name;
 617          $update->timemodified = time();
 618          $update->timecreated = time();
 619          $update->course = $formdata->course;
 620          $update->courseid = $formdata->course;
 621          $update->intro = $formdata->intro;
 622          $update->introformat = $formdata->introformat;
 623          $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
 624          $update->submissiondrafts = $formdata->submissiondrafts;
 625          $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
 626          $update->sendnotifications = $formdata->sendnotifications;
 627          $update->sendlatenotifications = $formdata->sendlatenotifications;
 628          $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
 629          if (isset($formdata->sendstudentnotifications)) {
 630              $update->sendstudentnotifications = $formdata->sendstudentnotifications;
 631          }
 632          $update->duedate = $formdata->duedate;
 633          $update->cutoffdate = $formdata->cutoffdate;
 634          $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
 635          $update->grade = $formdata->grade;
 636          $update->completionsubmit = !empty($formdata->completionsubmit);
 637          $update->teamsubmission = $formdata->teamsubmission;
 638          $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
 639          if (isset($formdata->teamsubmissiongroupingid)) {
 640              $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
 641          }
 642          $update->blindmarking = $formdata->blindmarking;
 643          $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
 644          if (!empty($formdata->attemptreopenmethod)) {
 645              $update->attemptreopenmethod = $formdata->attemptreopenmethod;
 646          }
 647          if (!empty($formdata->maxattempts)) {
 648              $update->maxattempts = $formdata->maxattempts;
 649          }
 650          if (isset($formdata->preventsubmissionnotingroup)) {
 651              $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup;
 652          }
 653          $update->markingworkflow = $formdata->markingworkflow;
 654          $update->markingallocation = $formdata->markingallocation;
 655          if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
 656              $update->markingallocation = 0;
 657          }
 658  
 659          $returnid = $DB->insert_record('assign', $update);
 660          $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
 661          // Cache the course record.
 662          $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
 663  
 664          $this->save_intro_draft_files($formdata);
 665  
 666          if ($callplugins) {
 667              // Call save_settings hook for submission plugins.
 668              foreach ($this->submissionplugins as $plugin) {
 669                  if (!$this->update_plugin_instance($plugin, $formdata)) {
 670                      print_error($plugin->get_error());
 671                      return false;
 672                  }
 673              }
 674              foreach ($this->feedbackplugins as $plugin) {
 675                  if (!$this->update_plugin_instance($plugin, $formdata)) {
 676                      print_error($plugin->get_error());
 677                      return false;
 678                  }
 679              }
 680  
 681              // In the case of upgrades the coursemodule has not been set,
 682              // so we need to wait before calling these two.
 683              $this->update_calendar($formdata->coursemodule);
 684              $this->update_gradebook(false, $formdata->coursemodule);
 685  
 686          }
 687  
 688          $update = new stdClass();
 689          $update->id = $this->get_instance()->id;
 690          $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
 691          $DB->update_record('assign', $update);
 692  
 693          return $returnid;
 694      }
 695  
 696      /**
 697       * Delete all grades from the gradebook for this assignment.
 698       *
 699       * @return bool
 700       */
 701      protected function delete_grades() {
 702          global $CFG;
 703  
 704          $result = grade_update('mod/assign',
 705                                 $this->get_course()->id,
 706                                 'mod',
 707                                 'assign',
 708                                 $this->get_instance()->id,
 709                                 0,
 710                                 null,
 711                                 array('deleted'=>1));
 712          return $result == GRADE_UPDATE_OK;
 713      }
 714  
 715      /**
 716       * Delete this instance from the database.
 717       *
 718       * @return bool false if an error occurs
 719       */
 720      public function delete_instance() {
 721          global $DB;
 722          $result = true;
 723  
 724          foreach ($this->submissionplugins as $plugin) {
 725              if (!$plugin->delete_instance()) {
 726                  print_error($plugin->get_error());
 727                  $result = false;
 728              }
 729          }
 730          foreach ($this->feedbackplugins as $plugin) {
 731              if (!$plugin->delete_instance()) {
 732                  print_error($plugin->get_error());
 733                  $result = false;
 734              }
 735          }
 736  
 737          // Delete files associated with this assignment.
 738          $fs = get_file_storage();
 739          if (! $fs->delete_area_files($this->context->id) ) {
 740              $result = false;
 741          }
 742  
 743          // Delete_records will throw an exception if it fails - so no need for error checking here.
 744          $DB->delete_records('assign_submission', array('assignment' => $this->get_instance()->id));
 745          $DB->delete_records('assign_grades', array('assignment' => $this->get_instance()->id));
 746          $DB->delete_records('assign_plugin_config', array('assignment' => $this->get_instance()->id));
 747          $DB->delete_records('assign_user_flags', array('assignment' => $this->get_instance()->id));
 748          $DB->delete_records('assign_user_mapping', array('assignment' => $this->get_instance()->id));
 749  
 750          // Delete items from the gradebook.
 751          if (! $this->delete_grades()) {
 752              $result = false;
 753          }
 754  
 755          // Delete the instance.
 756          $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
 757  
 758          return $result;
 759      }
 760  
 761      /**
 762       * Actual implementation of the reset course functionality, delete all the
 763       * assignment submissions for course $data->courseid.
 764       *
 765       * @param stdClass $data the data submitted from the reset course.
 766       * @return array status array
 767       */
 768      public function reset_userdata($data) {
 769          global $CFG, $DB;
 770  
 771          $componentstr = get_string('modulenameplural', 'assign');
 772          $status = array();
 773  
 774          $fs = get_file_storage();
 775          if (!empty($data->reset_assign_submissions)) {
 776              // Delete files associated with this assignment.
 777              foreach ($this->submissionplugins as $plugin) {
 778                  $fileareas = array();
 779                  $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
 780                  $fileareas = $plugin->get_file_areas();
 781                  foreach ($fileareas as $filearea => $notused) {
 782                      $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
 783                  }
 784  
 785                  if (!$plugin->delete_instance()) {
 786                      $status[] = array('component'=>$componentstr,
 787                                        'item'=>get_string('deleteallsubmissions', 'assign'),
 788                                        'error'=>$plugin->get_error());
 789                  }
 790              }
 791  
 792              foreach ($this->feedbackplugins as $plugin) {
 793                  $fileareas = array();
 794                  $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
 795                  $fileareas = $plugin->get_file_areas();
 796                  foreach ($fileareas as $filearea => $notused) {
 797                      $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
 798                  }
 799  
 800                  if (!$plugin->delete_instance()) {
 801                      $status[] = array('component'=>$componentstr,
 802                                        'item'=>get_string('deleteallsubmissions', 'assign'),
 803                                        'error'=>$plugin->get_error());
 804                  }
 805              }
 806  
 807              $assignids = $DB->get_records('assign', array('course' => $data->courseid), '', 'id');
 808              list($sql, $params) = $DB->get_in_or_equal(array_keys($assignids));
 809  
 810              $DB->delete_records_select('assign_submission', "assignment $sql", $params);
 811              $DB->delete_records_select('assign_user_flags', "assignment $sql", $params);
 812  
 813              $status[] = array('component'=>$componentstr,
 814                                'item'=>get_string('deleteallsubmissions', 'assign'),
 815                                'error'=>false);
 816  
 817              if (!empty($data->reset_gradebook_grades)) {
 818                  $DB->delete_records_select('assign_grades', "assignment $sql", $params);
 819                  // Remove all grades from gradebook.
 820                  require_once($CFG->dirroot.'/mod/assign/lib.php');
 821                  assign_reset_gradebook($data->courseid);
 822  
 823                  // Reset revealidentities if both submissions and grades have been reset.
 824                  if ($this->get_instance()->blindmarking && $this->get_instance()->revealidentities) {
 825                      $DB->set_field('assign', 'revealidentities', 0, array('id' => $this->get_instance()->id));
 826                  }
 827              }
 828          }
 829          // Updating dates - shift may be negative too.
 830          if ($data->timeshift) {
 831              shift_course_mod_dates('assign',
 832                                      array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'),
 833                                      $data->timeshift,
 834                                      $data->courseid, $this->get_instance()->id);
 835              $status[] = array('component'=>$componentstr,
 836                                'item'=>get_string('datechanged'),
 837                                'error'=>false);
 838          }
 839  
 840          return $status;
 841      }
 842  
 843      /**
 844       * Update the settings for a single plugin.
 845       *
 846       * @param assign_plugin $plugin The plugin to update
 847       * @param stdClass $formdata The form data
 848       * @return bool false if an error occurs
 849       */
 850      protected function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
 851          if ($plugin->is_visible()) {
 852              $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
 853              if (!empty($formdata->$enabledname)) {
 854                  $plugin->enable();
 855                  if (!$plugin->save_settings($formdata)) {
 856                      print_error($plugin->get_error());
 857                      return false;
 858                  }
 859              } else {
 860                  $plugin->disable();
 861              }
 862          }
 863          return true;
 864      }
 865  
 866      /**
 867       * Update the gradebook information for this assignment.
 868       *
 869       * @param bool $reset If true, will reset all grades in the gradbook for this assignment
 870       * @param int $coursemoduleid This is required because it might not exist in the database yet
 871       * @return bool
 872       */
 873      public function update_gradebook($reset, $coursemoduleid) {
 874          global $CFG;
 875  
 876          require_once($CFG->dirroot.'/mod/assign/lib.php');
 877          $assign = clone $this->get_instance();
 878          $assign->cmidnumber = $coursemoduleid;
 879  
 880          // Set assign gradebook feedback plugin status (enabled and visible).
 881          $assign->gradefeedbackenabled = $this->is_gradebook_feedback_enabled();
 882  
 883          $param = null;
 884          if ($reset) {
 885              $param = 'reset';
 886          }
 887  
 888          return assign_grade_item_update($assign, $param);
 889      }
 890  
 891      /**
 892       * Get the marking table page size
 893       *
 894       * @return integer
 895       */
 896      public function get_assign_perpage() {
 897          $perpage = (int) get_user_preferences('assign_perpage', 10);
 898          $adminconfig = $this->get_admin_config();
 899          $maxperpage = -1;
 900          if (isset($adminconfig->maxperpage)) {
 901              $maxperpage = $adminconfig->maxperpage;
 902          }
 903          if (isset($maxperpage) &&
 904              $maxperpage != -1 &&
 905              ($perpage == -1 || $perpage > $maxperpage)) {
 906              $perpage = $maxperpage;
 907          }
 908          return $perpage;
 909      }
 910  
 911      /**
 912       * Load and cache the admin config for this module.
 913       *
 914       * @return stdClass the plugin config
 915       */
 916      public function get_admin_config() {
 917          if ($this->adminconfig) {
 918              return $this->adminconfig;
 919          }
 920          $this->adminconfig = get_config('assign');
 921          return $this->adminconfig;
 922      }
 923  
 924      /**
 925       * Update the calendar entries for this assignment.
 926       *
 927       * @param int $coursemoduleid - Required to pass this in because it might
 928       *                              not exist in the database yet.
 929       * @return bool
 930       */
 931      public function update_calendar($coursemoduleid) {
 932          global $DB, $CFG;
 933          require_once($CFG->dirroot.'/calendar/lib.php');
 934  
 935          // Special case for add_instance as the coursemodule has not been set yet.
 936          $instance = $this->get_instance();
 937  
 938          $eventtype = 'due';
 939  
 940          if ($instance->duedate) {
 941              $event = new stdClass();
 942  
 943              $params = array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype);
 944              $event->id = $DB->get_field('event', 'id', $params);
 945              $event->name = $instance->name;
 946              $event->timestart = $instance->duedate;
 947  
 948              // Convert the links to pluginfile. It is a bit hacky but at this stage the files
 949              // might not have been saved in the module area yet.
 950              $intro = $instance->intro;
 951              if ($draftid = file_get_submitted_draft_itemid('introeditor')) {
 952                  $intro = file_rewrite_urls_to_pluginfile($intro, $draftid);
 953              }
 954  
 955              // We need to remove the links to files as the calendar is not ready
 956              // to support module events with file areas.
 957              $intro = strip_pluginfile_content($intro);
 958              if ($this->show_intro()) {
 959                  $event->description = array(
 960                      'text' => $intro,
 961                      'format' => $instance->introformat
 962                  );
 963              } else {
 964                  $event->description = array(
 965                      'text' => '',
 966                      'format' => $instance->introformat
 967                  );
 968              }
 969  
 970              if ($event->id) {
 971                  $calendarevent = calendar_event::load($event->id);
 972                  $calendarevent->update($event);
 973              } else {
 974                  unset($event->id);
 975                  $event->courseid    = $instance->course;
 976                  $event->groupid     = 0;
 977                  $event->userid      = 0;
 978                  $event->modulename  = 'assign';
 979                  $event->instance    = $instance->id;
 980                  $event->eventtype   = $eventtype;
 981                  $event->timeduration = 0;
 982                  calendar_event::create($event);
 983              }
 984          } else {
 985              $DB->delete_records('event', array('modulename' => 'assign', 'instance' => $instance->id, 'eventtype' => $eventtype));
 986          }
 987      }
 988  
 989  
 990      /**
 991       * Update this instance in the database.
 992       *
 993       * @param stdClass $formdata - the data submitted from the form
 994       * @return bool false if an error occurs
 995       */
 996      public function update_instance($formdata) {
 997          global $DB;
 998          $adminconfig = $this->get_admin_config();
 999  
1000          $update = new stdClass();
1001          $update->id = $formdata->instance;
1002          $update->name = $formdata->name;
1003          $update->timemodified = time();
1004          $update->course = $formdata->course;
1005          $update->intro = $formdata->intro;
1006          $update->introformat = $formdata->introformat;
1007          $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
1008          $update->submissiondrafts = $formdata->submissiondrafts;
1009          $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
1010          $update->sendnotifications = $formdata->sendnotifications;
1011          $update->sendlatenotifications = $formdata->sendlatenotifications;
1012          $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
1013          if (isset($formdata->sendstudentnotifications)) {
1014              $update->sendstudentnotifications = $formdata->sendstudentnotifications;
1015          }
1016          $update->duedate = $formdata->duedate;
1017          $update->cutoffdate = $formdata->cutoffdate;
1018          $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
1019          $update->grade = $formdata->grade;
1020          if (!empty($formdata->completionunlocked)) {
1021              $update->completionsubmit = !empty($formdata->completionsubmit);
1022          }
1023          $update->teamsubmission = $formdata->teamsubmission;
1024          $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
1025          if (isset($formdata->teamsubmissiongroupingid)) {
1026              $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
1027          }
1028          $update->blindmarking = $formdata->blindmarking;
1029          $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
1030          if (!empty($formdata->attemptreopenmethod)) {
1031              $update->attemptreopenmethod = $formdata->attemptreopenmethod;
1032          }
1033          if (!empty($formdata->maxattempts)) {
1034              $update->maxattempts = $formdata->maxattempts;
1035          }
1036          if (isset($formdata->preventsubmissionnotingroup)) {
1037              $update->preventsubmissionnotingroup = $formdata->preventsubmissionnotingroup;
1038          }
1039          $update->markingworkflow = $formdata->markingworkflow;
1040          $update->markingallocation = $formdata->markingallocation;
1041          if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
1042              $update->markingallocation = 0;
1043          }
1044  
1045          $result = $DB->update_record('assign', $update);
1046          $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
1047  
1048          $this->save_intro_draft_files($formdata);
1049  
1050          // Load the assignment so the plugins have access to it.
1051  
1052          // Call save_settings hook for submission plugins.
1053          foreach ($this->submissionplugins as $plugin) {
1054              if (!$this->update_plugin_instance($plugin, $formdata)) {
1055                  print_error($plugin->get_error());
1056                  return false;
1057              }
1058          }
1059          foreach ($this->feedbackplugins as $plugin) {
1060              if (!$this->update_plugin_instance($plugin, $formdata)) {
1061                  print_error($plugin->get_error());
1062                  return false;
1063              }
1064          }
1065  
1066          $this->update_calendar($this->get_course_module()->id);
1067          $this->update_gradebook(false, $this->get_course_module()->id);
1068  
1069          $update = new stdClass();
1070          $update->id = $this->get_instance()->id;
1071          $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
1072          $DB->update_record('assign', $update);
1073  
1074          return $result;
1075      }
1076  
1077      /**
1078       * Save the attachments in the draft areas.
1079       *
1080       * @param stdClass $formdata
1081       */
1082      protected function save_intro_draft_files($formdata) {
1083          if (isset($formdata->introattachments)) {
1084              file_save_draft_area_files($formdata->introattachments, $this->get_context()->id,
1085                                         'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0);
1086          }
1087      }
1088  
1089      /**
1090       * Add elements in grading plugin form.
1091       *
1092       * @param mixed $grade stdClass|null
1093       * @param MoodleQuickForm $mform
1094       * @param stdClass $data
1095       * @param int $userid - The userid we are grading
1096       * @return void
1097       */
1098      protected function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
1099          foreach ($this->feedbackplugins as $plugin) {
1100              if ($plugin->is_enabled() && $plugin->is_visible()) {
1101                  $plugin->get_form_elements_for_user($grade, $mform, $data, $userid);
1102              }
1103          }
1104      }
1105  
1106  
1107  
1108      /**
1109       * Add one plugins settings to edit plugin form.
1110       *
1111       * @param assign_plugin $plugin The plugin to add the settings from
1112       * @param MoodleQuickForm $mform The form to add the configuration settings to.
1113       *                               This form is modified directly (not returned).
1114       * @param array $pluginsenabled A list of form elements to be added to a group.
1115       *                              The new element is added to this array by this function.
1116       * @return void
1117       */
1118      protected function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform, & $pluginsenabled) {
1119          global $CFG;
1120          if ($plugin->is_visible() && !$plugin->is_configurable() && $plugin->is_enabled()) {
1121              $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
1122              $pluginsenabled[] = $mform->createElement('hidden', $name, 1);
1123              $mform->setType($name, PARAM_BOOL);
1124              $plugin->get_settings($mform);
1125          } else if ($plugin->is_visible() && $plugin->is_configurable()) {
1126              $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
1127              $label = $plugin->get_name();
1128              $label .= ' ' . $this->get_renderer()->help_icon('enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
1129              $pluginsenabled[] = $mform->createElement('checkbox', $name, '', $label);
1130  
1131              $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
1132              if ($plugin->get_config('enabled') !== false) {
1133                  $default = $plugin->is_enabled();
1134              }
1135              $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
1136  
1137              $plugin->get_settings($mform);
1138  
1139          }
1140      }
1141  
1142      /**
1143       * Add settings to edit plugin form.
1144       *
1145       * @param MoodleQuickForm $mform The form to add the configuration settings to.
1146       *                               This form is modified directly (not returned).
1147       * @return void
1148       */
1149      public function add_all_plugin_settings(MoodleQuickForm $mform) {
1150          $mform->addElement('header', 'submissiontypes', get_string('submissiontypes', 'assign'));
1151  
1152          $submissionpluginsenabled = array();
1153          $group = $mform->addGroup(array(), 'submissionplugins', get_string('submissiontypes', 'assign'), array(' '), false);
1154          foreach ($this->submissionplugins as $plugin) {
1155              $this->add_plugin_settings($plugin, $mform, $submissionpluginsenabled);
1156          }
1157          $group->setElements($submissionpluginsenabled);
1158  
1159          $mform->addElement('header', 'feedbacktypes', get_string('feedbacktypes', 'assign'));
1160          $feedbackpluginsenabled = array();
1161          $group = $mform->addGroup(array(), 'feedbackplugins', get_string('feedbacktypes', 'assign'), array(' '), false);
1162          foreach ($this->feedbackplugins as $plugin) {
1163              $this->add_plugin_settings($plugin, $mform, $feedbackpluginsenabled);
1164          }
1165          $group->setElements($feedbackpluginsenabled);
1166          $mform->setExpanded('submissiontypes');
1167      }
1168  
1169      /**
1170       * Allow each plugin an opportunity to update the defaultvalues
1171       * passed in to the settings form (needed to set up draft areas for
1172       * editor and filemanager elements)
1173       *
1174       * @param array $defaultvalues
1175       */
1176      public function plugin_data_preprocessing(&$defaultvalues) {
1177          foreach ($this->submissionplugins as $plugin) {
1178              if ($plugin->is_visible()) {
1179                  $plugin->data_preprocessing($defaultvalues);
1180              }
1181          }
1182          foreach ($this->feedbackplugins as $plugin) {
1183              if ($plugin->is_visible()) {
1184                  $plugin->data_preprocessing($defaultvalues);
1185              }
1186          }
1187      }
1188  
1189      /**
1190       * Get the name of the current module.
1191       *
1192       * @return string the module name (Assignment)
1193       */
1194      protected function get_module_name() {
1195          if (isset(self::$modulename)) {
1196              return self::$modulename;
1197          }
1198          self::$modulename = get_string('modulename', 'assign');
1199          return self::$modulename;
1200      }
1201  
1202      /**
1203       * Get the plural name of the current module.
1204       *
1205       * @return string the module name plural (Assignments)
1206       */
1207      protected function get_module_name_plural() {
1208          if (isset(self::$modulenameplural)) {
1209              return self::$modulenameplural;
1210          }
1211          self::$modulenameplural = get_string('modulenameplural', 'assign');
1212          return self::$modulenameplural;
1213      }
1214  
1215      /**
1216       * Has this assignment been constructed from an instance?
1217       *
1218       * @return bool
1219       */
1220      public function has_instance() {
1221          return $this->instance || $this->get_course_module();
1222      }
1223  
1224      /**
1225       * Get the settings for the current instance of this assignment
1226       *
1227       * @return stdClass The settings
1228       */
1229      public function get_instance() {
1230          global $DB;
1231          if ($this->instance) {
1232              return $this->instance;
1233          }
1234          if ($this->get_course_module()) {
1235              $params = array('id' => $this->get_course_module()->instance);
1236              $this->instance = $DB->get_record('assign', $params, '*', MUST_EXIST);
1237          }
1238          if (!$this->instance) {
1239              throw new coding_exception('Improper use of the assignment class. ' .
1240                                         'Cannot load the assignment record.');
1241          }
1242          return $this->instance;
1243      }
1244  
1245      /**
1246       * Get the primary grade item for this assign instance.
1247       *
1248       * @return stdClass The grade_item record
1249       */
1250      public function get_grade_item() {
1251          if ($this->gradeitem) {
1252              return $this->gradeitem;
1253          }
1254          $instance = $this->get_instance();
1255          $params = array('itemtype' => 'mod',
1256                          'itemmodule' => 'assign',
1257                          'iteminstance' => $instance->id,
1258                          'courseid' => $instance->course,
1259                          'itemnumber' => 0);
1260          $this->gradeitem = grade_item::fetch($params);
1261          if (!$this->gradeitem) {
1262              throw new coding_exception('Improper use of the assignment class. ' .
1263                                         'Cannot load the grade item.');
1264          }
1265          return $this->gradeitem;
1266      }
1267  
1268      /**
1269       * Get the context of the current course.
1270       *
1271       * @return mixed context|null The course context
1272       */
1273      public function get_course_context() {
1274          if (!$this->context && !$this->course) {
1275              throw new coding_exception('Improper use of the assignment class. ' .
1276                                         'Cannot load the course context.');
1277          }
1278          if ($this->context) {
1279              return $this->context->get_course_context();
1280          } else {
1281              return context_course::instance($this->course->id);
1282          }
1283      }
1284  
1285  
1286      /**
1287       * Get the current course module.
1288       *
1289       * @return cm_info|null The course module or null if not known
1290       */
1291      public function get_course_module() {
1292          if ($this->coursemodule) {
1293              return $this->coursemodule;
1294          }
1295          if (!$this->context) {
1296              return null;
1297          }
1298  
1299          if ($this->context->contextlevel == CONTEXT_MODULE) {
1300              $modinfo = get_fast_modinfo($this->get_course());
1301              $this->coursemodule = $modinfo->get_cm($this->context->instanceid);
1302              return $this->coursemodule;
1303          }
1304          return null;
1305      }
1306  
1307      /**
1308       * Get context module.
1309       *
1310       * @return context
1311       */
1312      public function get_context() {
1313          return $this->context;
1314      }
1315  
1316      /**
1317       * Get the current course.
1318       *
1319       * @return mixed stdClass|null The course
1320       */
1321      public function get_course() {
1322          global $DB;
1323  
1324          if ($this->course) {
1325              return $this->course;
1326          }
1327  
1328          if (!$this->context) {
1329              return null;
1330          }
1331          $params = array('id' => $this->get_course_context()->instanceid);
1332          $this->course = $DB->get_record('course', $params, '*', MUST_EXIST);
1333  
1334          return $this->course;
1335      }
1336  
1337      /**
1338       * Count the number of intro attachments.
1339       *
1340       * @return int
1341       */
1342      protected function count_attachments() {
1343  
1344          $fs = get_file_storage();
1345          $files = $fs->get_area_files($this->get_context()->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA,
1346                          0, 'id', false);
1347  
1348          return count($files);
1349      }
1350  
1351      /**
1352       * Are there any intro attachments to display?
1353       *
1354       * @return boolean
1355       */
1356      protected function has_visible_attachments() {
1357          return ($this->count_attachments() > 0);
1358      }
1359  
1360      /**
1361       * Return a grade in user-friendly form, whether it's a scale or not.
1362       *
1363       * @param mixed $grade int|null
1364       * @param boolean $editing Are we allowing changes to this grade?
1365       * @param int $userid The user id the grade belongs to
1366       * @param int $modified Timestamp from when the grade was last modified
1367       * @return string User-friendly representation of grade
1368       */
1369      public function display_grade($grade, $editing, $userid=0, $modified=0) {
1370          global $DB;
1371  
1372          static $scalegrades = array();
1373  
1374          $o = '';
1375  
1376          if ($this->get_instance()->grade >= 0) {
1377              // Normal number.
1378              if ($editing && $this->get_instance()->grade > 0) {
1379                  if ($grade < 0) {
1380                      $displaygrade = '';
1381                  } else {
1382                      $displaygrade = format_float($grade, 2);
1383                  }
1384                  $o .= '<label class="accesshide" for="quickgrade_' . $userid . '">' .
1385                         get_string('usergrade', 'assign') .
1386                         '</label>';
1387                  $o .= '<input type="text"
1388                                id="quickgrade_' . $userid . '"
1389                                name="quickgrade_' . $userid . '"
1390                                value="' .  $displaygrade . '"
1391                                size="6"
1392                                maxlength="10"
1393                                class="quickgrade"/>';
1394                  $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
1395                  return $o;
1396              } else {
1397                  if ($grade == -1 || $grade === null) {
1398                      $o .= '-';
1399                  } else {
1400                      $item = $this->get_grade_item();
1401                      $o .= grade_format_gradevalue($grade, $item);
1402                      if ($item->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) {
1403                          // If displaying the raw grade, also display the total value.
1404                          $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
1405                      }
1406                  }
1407                  return $o;
1408              }
1409  
1410          } else {
1411              // Scale.
1412              if (empty($this->cache['scale'])) {
1413                  if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
1414                      $this->cache['scale'] = make_menu_from_list($scale->scale);
1415                  } else {
1416                      $o .= '-';
1417                      return $o;
1418                  }
1419              }
1420              if ($editing) {
1421                  $o .= '<label class="accesshide"
1422                                for="quickgrade_' . $userid . '">' .
1423                        get_string('usergrade', 'assign') .
1424                        '</label>';
1425                  $o .= '<select name="quickgrade_' . $userid . '" class="quickgrade">';
1426                  $o .= '<option value="-1">' . get_string('nograde') . '</option>';
1427                  foreach ($this->cache['scale'] as $optionid => $option) {
1428                      $selected = '';
1429                      if ($grade == $optionid) {
1430                          $selected = 'selected="selected"';
1431                      }
1432                      $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
1433                  }
1434                  $o .= '</select>';
1435                  return $o;
1436              } else {
1437                  $scaleid = (int)$grade;
1438                  if (isset($this->cache['scale'][$scaleid])) {
1439                      $o .= $this->cache['scale'][$scaleid];
1440                      return $o;
1441                  }
1442                  $o .= '-';
1443                  return $o;
1444              }
1445          }
1446      }
1447  
1448      /**
1449       * Get the submission status/grading status for all submissions in this assignment for the
1450       * given paticipants.
1451       *
1452       * These statuses match the available filters (requiregrading, submitted, notsubmitted).
1453       * If this is a group assignment, group info is also returned.
1454       *
1455       * @param array $participants an associative array where the key is the participant id and
1456       *                            the value is the participant record.
1457       * @return array an associative array where the key is the participant id and the value is
1458       *               the participant record.
1459       */
1460      private function get_submission_info_for_participants($participants) {
1461          global $DB;
1462  
1463          if (empty($participants)) {
1464              return $participants;
1465          }
1466  
1467          list($insql, $params) = $DB->get_in_or_equal(array_keys($participants), SQL_PARAMS_NAMED);
1468  
1469          $assignid = $this->get_instance()->id;
1470          $params['assignmentid1'] = $assignid;
1471          $params['assignmentid2'] = $assignid;
1472  
1473          $sql = 'SELECT u.id, s.status, s.timemodified AS stime, g.timemodified AS gtime, g.grade FROM {user} u
1474                           LEFT JOIN {assign_submission} s
1475                                  ON u.id = s.userid
1476                                 AND s.assignment = :assignmentid1
1477                                 AND s.latest = 1
1478                           LEFT JOIN {assign_grades} g
1479                                  ON u.id = g.userid
1480                                 AND g.assignment = :assignmentid2
1481                                 AND g.attemptnumber = s.attemptnumber
1482                           WHERE u.id ' . $insql;
1483  
1484          $records = $DB->get_records_sql($sql, $params);
1485  
1486          if ($this->get_instance()->teamsubmission) {
1487              // Get all groups.
1488              $allgroups = groups_get_all_groups($this->get_course()->id,
1489                                                 array_keys($participants),
1490                                                 $this->get_instance()->teamsubmissiongroupingid,
1491                                                 'DISTINCT g.id, g.name');
1492  
1493          }
1494          foreach ($participants as $userid => $participant) {
1495              $participants[$userid]->fullname = $this->fullname($participant);
1496              $participants[$userid]->submitted = false;
1497              $participants[$userid]->requiregrading = false;
1498          }
1499  
1500          foreach ($records as $userid => $submissioninfo) {
1501              // These filters are 100% the same as the ones in the grading table SQL.
1502              $submitted = false;
1503              $requiregrading = false;
1504  
1505              if (!empty($submissioninfo->stime) && $submissioninfo->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
1506                  $submitted = true;
1507              }
1508  
1509              if ($submitted && ($submissioninfo->stime >= $submissioninfo->gtime ||
1510                      empty($submissioninfo->gtime) ||
1511                      $submissioninfo->grade === null)) {
1512                  $requiregrading = true;
1513              }
1514  
1515              $participants[$userid]->submitted = $submitted;
1516              $participants[$userid]->requiregrading = $requiregrading;
1517              if ($this->get_instance()->teamsubmission) {
1518                  $group = $this->get_submission_group($userid);
1519                  if ($group) {
1520                      $participants[$userid]->groupid = $group->id;
1521                      $participants[$userid]->groupname = $group->name;
1522                  }
1523              }
1524          }
1525          return $participants;
1526      }
1527  
1528      /**
1529       * Get the submission status/grading status for all submissions in this assignment.
1530       * These statuses match the available filters (requiregrading, submitted, notsubmitted).
1531       * If this is a group assignment, group info is also returned.
1532       *
1533       * @param int $currentgroup
1534       * @return array List of user records with extra fields 'submitted', 'notsubmitted', 'requiregrading', 'groupid', 'groupname'
1535       */
1536      public function list_participants_with_filter_status_and_group($currentgroup) {
1537          $participants = $this->list_participants($currentgroup, false);
1538  
1539          if (empty($participants)) {
1540              return $participants;
1541          } else {
1542              return $this->get_submission_info_for_participants($participants);
1543          }
1544      }
1545  
1546      /**
1547       * Load a list of users enrolled in the current course with the specified permission and group.
1548       * 0 for no group.
1549       *
1550       * @param int $currentgroup
1551       * @param bool $idsonly
1552       * @return array List of user records
1553       */
1554      public function list_participants($currentgroup, $idsonly) {
1555  
1556          if (empty($currentgroup)) {
1557              $currentgroup = 0;
1558          }
1559  
1560          $key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users();
1561          if (!isset($this->participants[$key])) {
1562              $order = 'u.lastname, u.firstname, u.id';
1563              if ($this->is_blind_marking()) {
1564                  $order = 'u.id';
1565              }
1566              $users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', $order, null, null,
1567                      $this->show_only_active_users());
1568  
1569              $cm = $this->get_course_module();
1570              $info = new \core_availability\info_module($cm);
1571              $users = $info->filter_user_list($users);
1572  
1573              $this->participants[$key] = $users;
1574          }
1575  
1576          if ($idsonly) {
1577              $idslist = array();
1578              foreach ($this->participants[$key] as $id => $user) {
1579                  $idslist[$id] = new stdClass();
1580                  $idslist[$id]->id = $id;
1581              }
1582              return $idslist;
1583          }
1584          return $this->participants[$key];
1585      }
1586  
1587      /**
1588       * Load a user if they are enrolled in the current course. Populated with submission
1589       * status for this assignment.
1590       *
1591       * @param int $userid
1592       * @return null|stdClass user record
1593       */
1594      public function get_participant($userid) {
1595          global $DB;
1596  
1597          $participant = $DB->get_record('user', array('id' => $userid));
1598          if (!$participant) {
1599              return null;
1600          }
1601  
1602          if (!is_enrolled($this->context, $participant, 'mod/assign:submit', $this->show_only_active_users())) {
1603              return null;
1604          }
1605  
1606          $result = $this->get_submission_info_for_participants(array($participant->id => $participant));
1607          return $result[$participant->id];
1608      }
1609  
1610      /**
1611       * Load a count of valid teams for this assignment.
1612       *
1613       * @param int $activitygroup Activity active group
1614       * @return int number of valid teams
1615       */
1616      public function count_teams($activitygroup = 0) {
1617  
1618          $count = 0;
1619  
1620          $participants = $this->list_participants($activitygroup, true);
1621  
1622          // If a team submission grouping id is provided all good as all returned groups
1623          // are the submission teams, but if no team submission grouping was specified
1624          // $groups will contain all participants groups.
1625          if ($this->get_instance()->teamsubmissiongroupingid) {
1626  
1627              // We restrict the users to the selected group ones.
1628              $groups = groups_get_all_groups($this->get_course()->id,
1629                                              array_keys($participants),
1630                                              $this->get_instance()->teamsubmissiongroupingid,
1631                                              'DISTINCT g.id, g.name');
1632  
1633              $count = count($groups);
1634  
1635              // When a specific group is selected we don't count the default group users.
1636              if ($activitygroup == 0) {
1637                  if (empty($this->get_instance()->preventsubmissionnotingroup)) {
1638                      // See if there are any users in the default group.
1639                      $defaultusers = $this->get_submission_group_members(0, true);
1640                      if (count($defaultusers) > 0) {
1641                          $count += 1;
1642                      }
1643                  }
1644              }
1645          } else {
1646              // It is faster to loop around participants if no grouping was specified.
1647              $groups = array();
1648              foreach ($participants as $participant) {
1649                  if ($group = $this->get_submission_group($participant->id)) {
1650                      $groups[$group->id] = true;
1651                  } else if (empty($this->get_instance()->preventsubmissionnotingroup)) {
1652                      $groups[0] = true;
1653                  }
1654              }
1655  
1656              $count = count($groups);
1657          }
1658  
1659          return $count;
1660      }
1661  
1662      /**
1663       * Load a count of active users enrolled in the current course with the specified permission and group.
1664       * 0 for no group.
1665       *
1666       * @param int $currentgroup
1667       * @return int number of matching users
1668       */
1669      public function count_participants($currentgroup) {
1670          return count($this->list_participants($currentgroup, true));
1671      }
1672  
1673      /**
1674       * Load a count of active users submissions in the current module that require grading
1675       * This means the submission modification time is more recent than the
1676       * grading modification time and the status is SUBMITTED.
1677       *
1678       * @return int number of matching submissions
1679       */
1680      public function count_submissions_need_grading() {
1681          global $DB;
1682  
1683          if ($this->get_instance()->teamsubmission) {
1684              // This does not make sense for group assignment because the submission is shared.
1685              return 0;
1686          }
1687  
1688          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1689          list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1690  
1691          $params['assignid'] = $this->get_instance()->id;
1692          $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1693  
1694          $sql = 'SELECT COUNT(s.userid)
1695                     FROM {assign_submission} s
1696                     LEFT JOIN {assign_grades} g ON
1697                          s.assignment = g.assignment AND
1698                          s.userid = g.userid AND
1699                          g.attemptnumber = s.attemptnumber
1700                     JOIN(' . $esql . ') e ON e.id = s.userid
1701                     WHERE
1702                          s.latest = 1 AND
1703                          s.assignment = :assignid AND
1704                          s.timemodified IS NOT NULL AND
1705                          s.status = :submitted AND
1706                          (s.timemodified >= g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL)';
1707  
1708          return $DB->count_records_sql($sql, $params);
1709      }
1710  
1711      /**
1712       * Load a count of grades.
1713       *
1714       * @return int number of grades
1715       */
1716      public function count_grades() {
1717          global $DB;
1718  
1719          if (!$this->has_instance()) {
1720              return 0;
1721          }
1722  
1723          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1724          list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1725  
1726          $params['assignid'] = $this->get_instance()->id;
1727  
1728          $sql = 'SELECT COUNT(g.userid)
1729                     FROM {assign_grades} g
1730                     JOIN(' . $esql . ') e ON e.id = g.userid
1731                     WHERE g.assignment = :assignid';
1732  
1733          return $DB->count_records_sql($sql, $params);
1734      }
1735  
1736      /**
1737       * Load a count of submissions.
1738       *
1739       * @param bool $includenew When true, also counts the submissions with status 'new'.
1740       * @return int number of submissions
1741       */
1742      public function count_submissions($includenew = false) {
1743          global $DB;
1744  
1745          if (!$this->has_instance()) {
1746              return 0;
1747          }
1748  
1749          $params = array();
1750          $sqlnew = '';
1751  
1752          if (!$includenew) {
1753              $sqlnew = ' AND s.status <> :status ';
1754              $params['status'] = ASSIGN_SUBMISSION_STATUS_NEW;
1755          }
1756  
1757          if ($this->get_instance()->teamsubmission) {
1758              // We cannot join on the enrolment tables for group submissions (no userid).
1759              $sql = 'SELECT COUNT(DISTINCT s.groupid)
1760                          FROM {assign_submission} s
1761                          WHERE
1762                              s.assignment = :assignid AND
1763                              s.timemodified IS NOT NULL AND
1764                              s.userid = :groupuserid' .
1765                              $sqlnew;
1766  
1767              $params['assignid'] = $this->get_instance()->id;
1768              $params['groupuserid'] = 0;
1769          } else {
1770              $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1771              list($esql, $enrolparams) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1772  
1773              $params = array_merge($params, $enrolparams);
1774              $params['assignid'] = $this->get_instance()->id;
1775  
1776              $sql = 'SELECT COUNT(DISTINCT s.userid)
1777                         FROM {assign_submission} s
1778                         JOIN(' . $esql . ') e ON e.id = s.userid
1779                         WHERE
1780                              s.assignment = :assignid AND
1781                              s.timemodified IS NOT NULL ' .
1782                              $sqlnew;
1783  
1784          }
1785  
1786          return $DB->count_records_sql($sql, $params);
1787      }
1788  
1789      /**
1790       * Load a count of submissions with a specified status.
1791       *
1792       * @param string $status The submission status - should match one of the constants
1793       * @return int number of matching submissions
1794       */
1795      public function count_submissions_with_status($status) {
1796          global $DB;
1797  
1798          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1799          list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1800  
1801          $params['assignid'] = $this->get_instance()->id;
1802          $params['assignid2'] = $this->get_instance()->id;
1803          $params['submissionstatus'] = $status;
1804  
1805          if ($this->get_instance()->teamsubmission) {
1806  
1807              $groupsstr = '';
1808              if ($currentgroup != 0) {
1809                  // If there is an active group we should only display the current group users groups.
1810                  $participants = $this->list_participants($currentgroup, true);
1811                  $groups = groups_get_all_groups($this->get_course()->id,
1812                                                  array_keys($participants),
1813                                                  $this->get_instance()->teamsubmissiongroupingid,
1814                                                  'DISTINCT g.id, g.name');
1815                  list($groupssql, $groupsparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED);
1816                  $groupsstr = 's.groupid ' . $groupssql . ' AND';
1817                  $params = $params + $groupsparams;
1818              }
1819              $sql = 'SELECT COUNT(s.groupid)
1820                          FROM {assign_submission} s
1821                          WHERE
1822                              s.latest = 1 AND
1823                              s.assignment = :assignid AND
1824                              s.timemodified IS NOT NULL AND
1825                              s.userid = :groupuserid AND '
1826                              . $groupsstr . '
1827                              s.status = :submissionstatus';
1828              $params['groupuserid'] = 0;
1829          } else {
1830              $sql = 'SELECT COUNT(s.userid)
1831                          FROM {assign_submission} s
1832                          JOIN(' . $esql . ') e ON e.id = s.userid
1833                          WHERE
1834                              s.latest = 1 AND
1835                              s.assignment = :assignid AND
1836                              s.timemodified IS NOT NULL AND
1837                              s.status = :submissionstatus';
1838  
1839          }
1840  
1841          return $DB->count_records_sql($sql, $params);
1842      }
1843  
1844      /**
1845       * Utility function to get the userid for every row in the grading table
1846       * so the order can be frozen while we iterate it.
1847       *
1848       * @return array An array of userids
1849       */
1850      protected function get_grading_userid_list() {
1851          $filter = get_user_preferences('assign_filter', '');
1852          $table = new assign_grading_table($this, 0, $filter, 0, false);
1853  
1854          $useridlist = $table->get_column_data('userid');
1855  
1856          return $useridlist;
1857      }
1858  
1859      /**
1860       * Generate zip file from array of given files.
1861       *
1862       * @param array $filesforzipping - array of files to pass into archive_to_pathname.
1863       *                                 This array is indexed by the final file name and each
1864       *                                 element in the array is an instance of a stored_file object.
1865       * @return path of temp file - note this returned file does
1866       *         not have a .zip extension - it is a temp file.
1867       */
1868      protected function pack_files($filesforzipping) {
1869          global $CFG;
1870          // Create path for new zip file.
1871          $tempzip = tempnam($CFG->tempdir . '/', 'assignment_');
1872          // Zip files.
1873          $zipper = new zip_packer();
1874          if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1875              return $tempzip;
1876          }
1877          return false;
1878      }
1879  
1880      /**
1881       * Finds all assignment notifications that have yet to be mailed out, and mails them.
1882       *
1883       * Cron function to be run periodically according to the moodle cron.
1884       *
1885       * @return bool
1886       */
1887      public static function cron() {
1888          global $DB;
1889  
1890          // Only ever send a max of one days worth of updates.
1891          $yesterday = time() - (24 * 3600);
1892          $timenow   = time();
1893          $lastcron = $DB->get_field('modules', 'lastcron', array('name' => 'assign'));
1894  
1895          // Collect all submissions that require mailing.
1896          // Submissions are included if all are true:
1897          //   - The assignment is visible in the gradebook.
1898          //   - No previous notification has been sent.
1899          //   - If marking workflow is not enabled, the grade was updated in the past 24 hours, or
1900          //     if marking workflow is enabled, the workflow state is at 'released'.
1901          $sql = "SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities,
1902                         g.*, g.timemodified as lastmodified, cm.id as cmid
1903                   FROM {assign} a
1904                   JOIN {assign_grades} g ON g.assignment = a.id
1905              LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid
1906                   JOIN {course_modules} cm ON cm.course = a.course AND cm.instance = a.id
1907                   JOIN {modules} md ON md.id = cm.module AND md.name = 'assign'
1908                   JOIN {grade_items} gri ON gri.iteminstance = a.id AND gri.courseid = a.course AND gri.itemmodule = md.name
1909                   WHERE ((a.markingworkflow = 0 AND g.timemodified >= :yesterday AND g.timemodified <= :today) OR
1910                          (a.markingworkflow = 1 AND uf.workflowstate = :wfreleased)) AND
1911                         uf.mailed = 0 AND gri.hidden = 0
1912                ORDER BY a.course, cm.id";
1913  
1914          $params = array(
1915              'yesterday' => $yesterday,
1916              'today' => $timenow,
1917              'wfreleased' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
1918          );
1919          $submissions = $DB->get_records_sql($sql, $params);
1920  
1921          if (!empty($submissions)) {
1922  
1923              mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
1924  
1925              // Preload courses we are going to need those.
1926              $courseids = array();
1927              foreach ($submissions as $submission) {
1928                  $courseids[] = $submission->course;
1929              }
1930  
1931              // Filter out duplicates.
1932              $courseids = array_unique($courseids);
1933              $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
1934              list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
1935              $sql = 'SELECT c.*, ' . $ctxselect .
1936                        ' FROM {course} c
1937                   LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
1938                       WHERE c.id ' . $courseidsql;
1939  
1940              $params['contextlevel'] = CONTEXT_COURSE;
1941              $courses = $DB->get_records_sql($sql, $params);
1942  
1943              // Clean up... this could go on for a while.
1944              unset($courseids);
1945              unset($ctxselect);
1946              unset($courseidsql);
1947              unset($params);
1948  
1949              // Message students about new feedback.
1950              foreach ($submissions as $submission) {
1951  
1952                  mtrace("Processing assignment submission $submission->id ...");
1953  
1954                  // Do not cache user lookups - could be too many.
1955                  if (!$user = $DB->get_record('user', array('id'=>$submission->userid))) {
1956                      mtrace('Could not find user ' . $submission->userid);
1957                      continue;
1958                  }
1959  
1960                  // Use a cache to prevent the same DB queries happening over and over.
1961                  if (!array_key_exists($submission->course, $courses)) {
1962                      mtrace('Could not find course ' . $submission->course);
1963                      continue;
1964                  }
1965                  $course = $courses[$submission->course];
1966                  if (isset($course->ctxid)) {
1967                      // Context has not yet been preloaded. Do so now.
1968                      context_helper::preload_from_record($course);
1969                  }
1970  
1971                  // Override the language and timezone of the "current" user, so that
1972                  // mail is customised for the receiver.
1973                  cron_setup_user($user, $course);
1974  
1975                  // Context lookups are already cached.
1976                  $coursecontext = context_course::instance($course->id);
1977                  if (!is_enrolled($coursecontext, $user->id)) {
1978                      $courseshortname = format_string($course->shortname,
1979                                                       true,
1980                                                       array('context' => $coursecontext));
1981                      mtrace(fullname($user) . ' not an active participant in ' . $courseshortname);
1982                      continue;
1983                  }
1984  
1985                  if (!$grader = $DB->get_record('user', array('id'=>$submission->grader))) {
1986                      mtrace('Could not find grader ' . $submission->grader);
1987                      continue;
1988                  }
1989  
1990                  $modinfo = get_fast_modinfo($course, $user->id);
1991                  $cm = $modinfo->get_cm($submission->cmid);
1992                  // Context lookups are already cached.
1993                  $contextmodule = context_module::instance($cm->id);
1994  
1995                  if (!$cm->uservisible) {
1996                      // Hold mail notification for assignments the user cannot access until later.
1997                      continue;
1998                  }
1999  
2000                  // Need to send this to the student.
2001                  $messagetype = 'feedbackavailable';
2002                  $eventtype = 'assign_notification';
2003                  $updatetime = $submission->lastmodified;
2004                  $modulename = get_string('modulename', 'assign');
2005  
2006                  $uniqueid = 0;
2007                  if ($submission->blindmarking && !$submission->revealidentities) {
2008                      $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
2009                  }
2010                  $showusers = $submission->blindmarking && !$submission->revealidentities;
2011                  self::send_assignment_notification($grader,
2012                                                     $user,
2013                                                     $messagetype,
2014                                                     $eventtype,
2015                                                     $updatetime,
2016                                                     $cm,
2017                                                     $contextmodule,
2018                                                     $course,
2019                                                     $modulename,
2020                                                     $submission->name,
2021                                                     $showusers,
2022                                                     $uniqueid);
2023  
2024                  $flags = $DB->get_record('assign_user_flags', array('userid'=>$user->id, 'assignment'=>$submission->assignment));
2025                  if ($flags) {
2026                      $flags->mailed = 1;
2027                      $DB->update_record('assign_user_flags', $flags);
2028                  } else {
2029                      $flags = new stdClass();
2030                      $flags->userid = $user->id;
2031                      $flags->assignment = $submission->assignment;
2032                      $flags->mailed = 1;
2033                      $DB->insert_record('assign_user_flags', $flags);
2034                  }
2035  
2036                  mtrace('Done');
2037              }
2038              mtrace('Done processing ' . count($submissions) . ' assignment submissions');
2039  
2040              cron_setup_user();
2041  
2042              // Free up memory just to be sure.
2043              unset($courses);
2044          }
2045  
2046          // Update calendar events to provide a description.
2047          $sql = 'SELECT id
2048                      FROM {assign}
2049                      WHERE
2050                          allowsubmissionsfromdate >= :lastcron AND
2051                          allowsubmissionsfromdate <= :timenow AND
2052                          alwaysshowdescription = 0';
2053          $params = array('lastcron' => $lastcron, 'timenow' => $timenow);
2054          $newlyavailable = $DB->get_records_sql($sql, $params);
2055          foreach ($newlyavailable as $record) {
2056              $cm = get_coursemodule_from_instance('assign', $record->id, 0, false, MUST_EXIST);
2057              $context = context_module::instance($cm->id);
2058  
2059              $assignment = new assign($context, null, null);
2060              $assignment->update_calendar($cm->id);
2061          }
2062  
2063          return true;
2064      }
2065  
2066      /**
2067       * Mark in the database that this grade record should have an update notification sent by cron.
2068       *
2069       * @param stdClass $grade a grade record keyed on id
2070       * @param bool $mailedoverride when true, flag notification to be sent again.
2071       * @return bool true for success
2072       */
2073      public function notify_grade_modified($grade, $mailedoverride = false) {
2074          global $DB;
2075  
2076          $flags = $this->get_user_flags($grade->userid, true);
2077          if ($flags->mailed != 1 || $mailedoverride) {
2078              $flags->mailed = 0;
2079          }
2080  
2081          return $this->update_user_flags($flags);
2082      }
2083  
2084      /**
2085       * Update user flags for this user in this assignment.
2086       *
2087       * @param stdClass $flags a flags record keyed on id
2088       * @return bool true for success
2089       */
2090      public function update_user_flags($flags) {
2091          global $DB;
2092          if ($flags->userid <= 0 || $flags->assignment <= 0 || $flags->id <= 0) {
2093              return false;
2094          }
2095  
2096          $result = $DB->update_record('assign_user_flags', $flags);
2097          return $result;
2098      }
2099  
2100      /**
2101       * Update a grade in the grade table for the assignment and in the gradebook.
2102       *
2103       * @param stdClass $grade a grade record keyed on id
2104       * @param bool $reopenattempt If the attempt reopen method is manual, allow another attempt at this assignment.
2105       * @return bool true for success
2106       */
2107      public function update_grade($grade, $reopenattempt = false) {
2108          global $DB;
2109  
2110          $grade->timemodified = time();
2111  
2112          if (!empty($grade->workflowstate)) {
2113              $validstates = $this->get_marking_workflow_states_for_current_user();
2114              if (!array_key_exists($grade->workflowstate, $validstates)) {
2115                  return false;
2116              }
2117          }
2118  
2119          if ($grade->grade && $grade->grade != -1) {
2120              if ($this->get_instance()->grade > 0) {
2121                  if (!is_numeric($grade->grade)) {
2122                      return false;
2123                  } else if ($grade->grade > $this->get_instance()->grade) {
2124                      return false;
2125                  } else if ($grade->grade < 0) {
2126                      return false;
2127                  }
2128              } else {
2129                  // This is a scale.
2130                  if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
2131                      $scaleoptions = make_menu_from_list($scale->scale);
2132                      if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
2133                          return false;
2134                      }
2135                  }
2136              }
2137          }
2138  
2139          if (empty($grade->attemptnumber)) {
2140              // Set it to the default.
2141              $grade->attemptnumber = 0;
2142          }
2143          $DB->update_record('assign_grades', $grade);
2144  
2145          $submission = null;
2146          if ($this->get_instance()->teamsubmission) {
2147              $submission = $this->get_group_submission($grade->userid, 0, false);
2148          } else {
2149              $submission = $this->get_user_submission($grade->userid, false);
2150          }
2151  
2152          // Only push to gradebook if the update is for the latest attempt.
2153          // Not the latest attempt.
2154          if ($submission && $submission->attemptnumber != $grade->attemptnumber) {
2155              return true;
2156          }
2157  
2158          if ($this->gradebook_item_update(null, $grade)) {
2159              \mod_assign\event\submission_graded::create_from_grade($this, $grade)->trigger();
2160          }
2161  
2162          // If the conditions are met, allow another attempt.
2163          if ($submission) {
2164              $this->reopen_submission_if_required($grade->userid,
2165                      $submission,
2166                      $reopenattempt);
2167          }
2168  
2169          return true;
2170      }
2171  
2172      /**
2173       * View the grant extension date page.
2174       *
2175       * Uses url parameters 'userid'
2176       * or from parameter 'selectedusers'
2177       *
2178       * @param moodleform $mform - Used for validation of the submitted data
2179       * @return string
2180       */
2181      protected function view_grant_extension($mform) {
2182          global $DB, $CFG;
2183          require_once($CFG->dirroot . '/mod/assign/extensionform.php');
2184  
2185          $o = '';
2186  
2187          $data = new stdClass();
2188          $data->id = $this->get_course_module()->id;
2189  
2190          $formparams = array(
2191              'instance' => $this->get_instance()
2192          );
2193  
2194          $extrauserfields = get_extra_user_fields($this->get_context());
2195  
2196          if ($mform) {
2197              $submitteddata = $mform->get_data();
2198              $users = $submitteddata->selectedusers;
2199              $userlist = explode(',', $users);
2200  
2201              $data->selectedusers = $users;
2202              $data->userid = 0;
2203  
2204              $usershtml = '';
2205              $usercount = 0;
2206              foreach ($userlist as $userid) {
2207                  if ($usercount >= 5) {
2208                      $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5);
2209                      break;
2210                  }
2211                  $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
2212  
2213                  $usershtml .= $this->get_renderer()->render(new assign_user_summary($user,
2214                                                                      $this->get_course()->id,
2215                                                                      has_capability('moodle/site:viewfullnames',
2216                                                                      $this->get_course_context()),
2217                                                                      $this->is_blind_marking(),
2218                                                                      $this->get_uniqueid_for_user($user->id),
2219                                                                      $extrauserfields,
2220                                                                      !$this->is_active_user($userid)));
2221                  $usercount += 1;
2222              }
2223  
2224              $formparams['userscount'] = count($userlist);
2225              $formparams['usershtml'] = $usershtml;
2226  
2227          } else {
2228              $userid = required_param('userid', PARAM_INT);
2229              $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
2230              $flags = $this->get_user_flags($userid, false);
2231  
2232              $data->userid = $user->id;
2233              if ($flags) {
2234                  $data->extensionduedate = $flags->extensionduedate;
2235              }
2236  
2237              $usershtml = $this->get_renderer()->render(new assign_user_summary($user,
2238                                                                  $this->get_course()->id,
2239                                                                  has_capability('moodle/site:viewfullnames',
2240                                                                  $this->get_course_context()),
2241                                                                  $this->is_blind_marking(),
2242                                                                  $this->get_uniqueid_for_user($user->id),
2243                                                                  $extrauserfields,
2244                                                                  !$this->is_active_user($userid)));
2245              $formparams['usershtml'] = $usershtml;
2246          }
2247  
2248          $mform = new mod_assign_extension_form(null, $formparams);
2249          $mform->set_data($data);
2250          $header = new assign_header($this->get_instance(),
2251                                      $this->get_context(),
2252                                      $this->show_intro(),
2253                                      $this->get_course_module()->id,
2254                                      get_string('grantextension', 'assign'));
2255          $o .= $this->get_renderer()->render($header);
2256          $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
2257          $o .= $this->view_footer();
2258          return $o;
2259      }
2260  
2261      /**
2262       * Get a list of the users in the same group as this user.
2263       *
2264       * @param int $groupid The id of the group whose members we want or 0 for the default group
2265       * @param bool $onlyids Whether to retrieve only the user id's
2266       * @param bool $excludesuspended Whether to exclude suspended users
2267       * @return array The users (possibly id's only)
2268       */
2269      public function get_submission_group_members($groupid, $onlyids, $excludesuspended = false) {
2270          $members = array();
2271          if ($groupid != 0) {
2272              $allusers = $this->list_participants($groupid, $onlyids);
2273              foreach ($allusers as $user) {
2274                  if ($this->get_submission_group($user->id)) {
2275                      $members[] = $user;
2276                  }
2277              }
2278          } else {
2279              $allusers = $this->list_participants(null, $onlyids);
2280              foreach ($allusers as $user) {
2281                  if ($this->get_submission_group($user->id) == null) {
2282                      $members[] = $user;
2283                  }
2284              }
2285          }
2286          // Exclude suspended users, if user can't see them.
2287          if ($excludesuspended || !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
2288              foreach ($members as $key => $member) {
2289                  if (!$this->is_active_user($member->id)) {
2290                      unset($members[$key]);
2291                  }
2292              }
2293          }
2294  
2295          return $members;
2296      }
2297  
2298      /**
2299       * Get a list of the users in the same group as this user that have not submitted the assignment.
2300       *
2301       * @param int $groupid The id of the group whose members we want or 0 for the default group
2302       * @param bool $onlyids Whether to retrieve only the user id's
2303       * @return array The users (possibly id's only)
2304       */
2305      public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
2306          $instance = $this->get_instance();
2307          if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) {
2308              return array();
2309          }
2310          $members = $this->get_submission_group_members($groupid, $onlyids);
2311  
2312          foreach ($members as $id => $member) {
2313              $submission = $this->get_user_submission($member->id, false);
2314              if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
2315                  unset($members[$id]);
2316              } else {
2317                  if ($this->is_blind_marking()) {
2318                      $members[$id]->alias = get_string('hiddenuser', 'assign') .
2319                                             $this->get_uniqueid_for_user($id);
2320                  }
2321              }
2322          }
2323          return $members;
2324      }
2325  
2326      /**
2327       * Load the group submission object for a particular user, optionally creating it if required.
2328       *
2329       * @param int $userid The id of the user whose submission we want
2330       * @param int $groupid The id of the group for this user - may be 0 in which
2331       *                     case it is determined from the userid.
2332       * @param bool $create If set to true a new submission object will be created in the database
2333       *                     with the status set to "new".
2334       * @param int $attemptnumber - -1 means the latest attempt
2335       * @return stdClass The submission
2336       */
2337      public function get_group_submission($userid, $groupid, $create, $attemptnumber=-1) {
2338          global $DB;
2339  
2340          if ($groupid == 0) {
2341              $group = $this->get_submission_group($userid);
2342              if ($group) {
2343                  $groupid = $group->id;
2344              }
2345          }
2346  
2347          // Now get the group submission.
2348          $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2349          if ($attemptnumber >= 0) {
2350              $params['attemptnumber'] = $attemptnumber;
2351          }
2352  
2353          // Only return the row with the highest attemptnumber.
2354          $submission = null;
2355          $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2356          if ($submissions) {
2357              $submission = reset($submissions);
2358          }
2359  
2360          if ($submission) {
2361              return $submission;
2362          }
2363          if ($create) {
2364              $submission = new stdClass();
2365              $submission->assignment = $this->get_instance()->id;
2366              $submission->userid = 0;
2367              $submission->groupid = $groupid;
2368              $submission->timecreated = time();
2369              $submission->timemodified = $submission->timecreated;
2370              if ($attemptnumber >= 0) {
2371                  $submission->attemptnumber = $attemptnumber;
2372              } else {
2373                  $submission->attemptnumber = 0;
2374              }
2375              // Work out if this is the latest submission.
2376              $submission->latest = 0;
2377              $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2378              if ($attemptnumber == -1) {
2379                  // This is a new submission so it must be the latest.
2380                  $submission->latest = 1;
2381              } else {
2382                  // We need to work this out.
2383                  $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
2384                  if ($result) {
2385                      $latestsubmission = reset($result);
2386                  }
2387                  if (!$latestsubmission || ($attemptnumber == $latestsubmission->attemptnumber)) {
2388                      $submission->latest = 1;
2389                  }
2390              }
2391              if ($submission->latest) {
2392                  // This is the case when we need to set latest to 0 for all the other attempts.
2393                  $DB->set_field('assign_submission', 'latest', 0, $params);
2394              }
2395              $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
2396              $sid = $DB->insert_record('assign_submission', $submission);
2397              return $DB->get_record('assign_submission', array('id' => $sid));
2398          }
2399          return false;
2400      }
2401  
2402      /**
2403       * View a summary listing of all assignments in the current course.
2404       *
2405       * @return string
2406       */
2407      private function view_course_index() {
2408          global $USER;
2409  
2410          $o = '';
2411  
2412          $course = $this->get_course();
2413          $strplural = get_string('modulenameplural', 'assign');
2414  
2415          if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
2416              $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
2417              $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
2418              return $o;
2419          }
2420  
2421          $strsectionname = '';
2422          $usesections = course_format_uses_sections($course->format);
2423          $modinfo = get_fast_modinfo($course);
2424  
2425          if ($usesections) {
2426              $strsectionname = get_string('sectionname', 'format_'.$course->format);
2427              $sections = $modinfo->get_section_info_all();
2428          }
2429          $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
2430  
2431          $timenow = time();
2432  
2433          $currentsection = '';
2434          foreach ($modinfo->instances['assign'] as $cm) {
2435              if (!$cm->uservisible) {
2436                  continue;
2437              }
2438  
2439              $timedue = $cms[$cm->id]->duedate;
2440  
2441              $sectionname = '';
2442              if ($usesections && $cm->sectionnum) {
2443                  $sectionname = get_section_name($course, $sections[$cm->sectionnum]);
2444              }
2445  
2446              $submitted = '';
2447              $context = context_module::instance($cm->id);
2448  
2449              $assignment = new assign($context, $cm, $course);
2450  
2451              if (has_capability('mod/assign:grade', $context)) {
2452                  $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
2453  
2454              } else if (has_capability('mod/assign:submit', $context)) {
2455                  $usersubmission = $assignment->get_user_submission($USER->id, false);
2456  
2457                  if (!empty($usersubmission->status)) {
2458                      $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
2459                  } else {
2460                      $submitted = get_string('submissionstatus_', 'assign');
2461                  }
2462              }
2463              $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
2464              if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
2465                      !$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
2466                  $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
2467              } else {
2468                  $grade = '-';
2469              }
2470  
2471              $courseindexsummary->add_assign_info($cm->id, $cm->name, $sectionname, $timedue, $submitted, $grade);
2472  
2473          }
2474  
2475          $o .= $this->get_renderer()->render($courseindexsummary);
2476          $o .= $this->view_footer();
2477  
2478          return $o;
2479      }
2480  
2481      /**
2482       * View a page rendered by a plugin.
2483       *
2484       * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'.
2485       *
2486       * @return string
2487       */
2488      protected function view_plugin_page() {
2489          global $USER;
2490  
2491          $o = '';
2492  
2493          $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
2494          $plugintype = required_param('plugin', PARAM_TEXT);
2495          $pluginaction = required_param('pluginaction', PARAM_ALPHA);
2496  
2497          $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
2498          if (!$plugin) {
2499              print_error('invalidformdata', '');
2500              return;
2501          }
2502  
2503          $o .= $plugin->view_page($pluginaction);
2504  
2505          return $o;
2506      }
2507  
2508  
2509      /**
2510       * This is used for team assignments to get the group for the specified user.
2511       * If the user is a member of multiple or no groups this will return false
2512       *
2513       * @param int $userid The id of the user whose submission we want
2514       * @return mixed The group or false
2515       */
2516      public function get_submission_group($userid) {
2517  
2518          if (isset($this->usersubmissiongroups[$userid])) {
2519              return $this->usersubmissiongroups[$userid];
2520          }
2521  
2522          $groups = $this->get_all_groups($userid);
2523          if (count($groups) != 1) {
2524              $return = false;
2525          } else {
2526              $return = array_pop($groups);
2527          }
2528  
2529          // Cache the user submission group.
2530          $this->usersubmissiongroups[$userid] = $return;
2531  
2532          return $return;
2533      }
2534  
2535      /**
2536       * Gets all groups the user is a member of.
2537       *
2538       * @param int $userid Teh id of the user who's groups we are checking
2539       * @return array The group objects
2540       */
2541      public function get_all_groups($userid) {
2542          if (isset($this->usergroups[$userid])) {
2543              return $this->usergroups[$userid];
2544          }
2545  
2546          $grouping = $this->get_instance()->teamsubmissiongroupingid;
2547          $return = groups_get_all_groups($this->get_course()->id, $userid, $grouping);
2548  
2549          $this->usergroups[$userid] = $return;
2550  
2551          return $return;
2552      }
2553  
2554  
2555      /**
2556       * Display the submission that is used by a plugin.
2557       *
2558       * Uses url parameters 'sid', 'gid' and 'plugin'.
2559       *
2560       * @param string $pluginsubtype
2561       * @return string
2562       */
2563      protected function view_plugin_content($pluginsubtype) {
2564          $o = '';
2565  
2566          $submissionid = optional_param('sid', 0, PARAM_INT);
2567          $gradeid = optional_param('gid', 0, PARAM_INT);
2568          $plugintype = required_param('plugin', PARAM_TEXT);
2569          $item = null;
2570          if ($pluginsubtype == 'assignsubmission') {
2571              $plugin = $this->get_submission_plugin_by_type($plugintype);
2572              if ($submissionid <= 0) {
2573                  throw new coding_exception('Submission id should not be 0');
2574              }
2575              $item = $this->get_submission($submissionid);
2576  
2577              // Check permissions.
2578              $this->require_view_submission($item->userid);
2579              $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2580                                                                $this->get_context(),
2581                                                                $this->show_intro(),
2582                                                                $this->get_course_module()->id,
2583                                                                $plugin->get_name()));
2584              $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
2585                                                                $item,
2586                                                                assign_submission_plugin_submission::FULL,
2587                                                                $this->get_course_module()->id,
2588                                                                $this->get_return_action(),
2589                                                                $this->get_return_params()));
2590  
2591              // Trigger event for viewing a submission.
2592              \mod_assign\event\submission_viewed::create_from_submission($this, $item)->trigger();
2593  
2594          } else {
2595              $plugin = $this->get_feedback_plugin_by_type($plugintype);
2596              if ($gradeid <= 0) {
2597                  throw new coding_exception('Grade id should not be 0');
2598              }
2599              $item = $this->get_grade($gradeid);
2600              // Check permissions.
2601              $this->require_view_submission($item->userid);
2602              $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2603                                                                $this->get_context(),
2604                                                                $this->show_intro(),
2605                                                                $this->get_course_module()->id,
2606                                                                $plugin->get_name()));
2607              $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
2608                                                                $item,
2609                                                                assign_feedback_plugin_feedback::FULL,
2610                                                                $this->get_course_module()->id,
2611                                                                $this->get_return_action(),
2612                                                                $this->get_return_params()));
2613  
2614              // Trigger event for viewing feedback.
2615              \mod_assign\event\feedback_viewed::create_from_grade($this, $item)->trigger();
2616          }
2617  
2618          $o .= $this->view_return_links();
2619  
2620          $o .= $this->view_footer();
2621  
2622          return $o;
2623      }
2624  
2625      /**
2626       * Rewrite plugin file urls so they resolve correctly in an exported zip.
2627       *
2628       * @param string $text - The replacement text
2629       * @param stdClass $user - The user record
2630       * @param assign_plugin $plugin - The assignment plugin
2631       */
2632      public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
2633          $groupmode = groups_get_activity_groupmode($this->get_course_module());
2634          $groupname = '';
2635          if ($groupmode) {
2636              $groupid = groups_get_activity_group($this->get_course_module(), true);
2637              $groupname = groups_get_group_name($groupid).'-';
2638          }
2639  
2640          if ($this->is_blind_marking()) {
2641              $prefix = $groupname . get_string('participant', 'assign');
2642              $prefix = str_replace('_', ' ', $prefix);
2643              $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2644          } else {
2645              $prefix = $groupname . fullname($user);
2646              $prefix = str_replace('_', ' ', $prefix);
2647              $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2648          }
2649  
2650          $subtype = $plugin->get_subtype();
2651          $type = $plugin->get_type();
2652          $prefix = $prefix . $subtype . '_' . $type . '_';
2653  
2654          $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
2655  
2656          return $result;
2657      }
2658  
2659      /**
2660       * Render the content in editor that is often used by plugin.
2661       *
2662       * @param string $filearea
2663       * @param int  $submissionid
2664       * @param string $plugintype
2665       * @param string $editor
2666       * @param string $component
2667       * @return string
2668       */
2669      public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
2670          global $CFG;
2671  
2672          $result = '';
2673  
2674          $plugin = $this->get_submission_plugin_by_type($plugintype);
2675  
2676          $text = $plugin->get_editor_text($editor, $submissionid);
2677          $format = $plugin->get_editor_format($editor, $submissionid);
2678  
2679          $finaltext = file_rewrite_pluginfile_urls($text,
2680                                                    'pluginfile.php',
2681                                                    $this->get_context()->id,
2682                                                    $component,
2683                                                    $filearea,
2684                                                    $submissionid);
2685          $params = array('overflowdiv' => true, 'context' => $this->get_context());
2686          $result .= format_text($finaltext, $format, $params);
2687  
2688          if ($CFG->enableportfolios && has_capability('mod/assign:exportownsubmission', $this->context)) {
2689              require_once($CFG->libdir . '/portfoliolib.php');
2690  
2691              $button = new portfolio_add_button();
2692              $portfolioparams = array('cmid' => $this->get_course_module()->id,
2693                                       'sid' => $submissionid,
2694                                       'plugin' => $plugintype,
2695                                       'editor' => $editor,
2696                                       'area'=>$filearea);
2697              $button->set_callback_options('assign_portfolio_caller', $portfolioparams, 'mod_assign');
2698              $fs = get_file_storage();
2699  
2700              if ($files = $fs->get_area_files($this->context->id,
2701                                               $component,
2702                                               $filearea,
2703                                               $submissionid,
2704                                               'timemodified',
2705                                               false)) {
2706                  $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
2707              } else {
2708                  $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
2709              }
2710              $result .= $button->to_html();
2711          }
2712          return $result;
2713      }
2714  
2715      /**
2716       * Display a continue page after grading.
2717       *
2718       * @param string $message - The message to display.
2719       * @return string
2720       */
2721      protected function view_savegrading_result($message) {
2722          $o = '';
2723          $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2724                                                        $this->get_context(),
2725                                                        $this->show_intro(),
2726                                                        $this->get_course_module()->id,
2727                                                        get_string('savegradingresult', 'assign')));
2728          $gradingresult = new assign_gradingmessage(get_string('savegradingresult', 'assign'),
2729                                                     $message,
2730                                                     $this->get_course_module()->id);
2731          $o .= $this->get_renderer()->render($gradingresult);
2732          $o .= $this->view_footer();
2733          return $o;
2734      }
2735      /**
2736       * Display a continue page after quickgrading.
2737       *
2738       * @param string $message - The message to display.
2739       * @return string
2740       */
2741      protected function view_quickgrading_result($message) {
2742          $o = '';
2743          $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2744                                                        $this->get_context(),
2745                                                        $this->show_intro(),
2746                                                        $this->get_course_module()->id,
2747                                                        get_string('quickgradingresult', 'assign')));
2748          $lastpage = optional_param('lastpage', null, PARAM_INT);
2749          $gradingresult = new assign_gradingmessage(get_string('quickgradingresult', 'assign'),
2750                                                     $message,
2751                                                     $this->get_course_module()->id,
2752                                                     false,
2753                                                     $lastpage);
2754          $o .= $this->get_renderer()->render($gradingresult);
2755          $o .= $this->view_footer();
2756          return $o;
2757      }
2758  
2759      /**
2760       * Display the page footer.
2761       *
2762       * @return string
2763       */
2764      protected function view_footer() {
2765          // When viewing the footer during PHPUNIT tests a set_state error is thrown.
2766          if (!PHPUNIT_TEST) {
2767              return $this->get_renderer()->render_footer();
2768          }
2769  
2770          return '';
2771      }
2772  
2773      /**
2774       * Throw an error if the permissions to view this users submission are missing.
2775       *
2776       * @throws required_capability_exception
2777       * @return none
2778       */
2779      public function require_view_submission($userid) {
2780          if (!$this->can_view_submission($userid)) {
2781              throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2782          }
2783      }
2784  
2785      /**
2786       * Throw an error if the permissions to view grades in this assignment are missing.
2787       *
2788       * @throws required_capability_exception
2789       * @return none
2790       */
2791      public function require_view_grades() {
2792          if (!$this->can_view_grades()) {
2793              throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2794          }
2795      }
2796  
2797      /**
2798       * Does this user have view grade or grade permission for this assignment?
2799       *
2800       * @return bool
2801       */
2802      public function can_view_grades() {
2803          // Permissions check.
2804          if (!has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
2805              return false;
2806          }
2807  
2808          return true;
2809      }
2810  
2811      /**
2812       * Does this user have grade permission for this assignment?
2813       *
2814       * @return bool
2815       */
2816      public function can_grade() {
2817          // Permissions check.
2818          if (!has_capability('mod/assign:grade', $this->context)) {
2819              return false;
2820          }
2821  
2822          return true;
2823      }
2824  
2825      /**
2826       * Download a zip file of all assignment submissions.
2827       *
2828       * @param array $userids Array of user ids to download assignment submissions in a zip file
2829       * @return string - If an error occurs, this will contain the error page.
2830       */
2831      protected function download_submissions($userids = false) {
2832          global $CFG, $DB;
2833  
2834          // More efficient to load this here.
2835          require_once($CFG->libdir.'/filelib.php');
2836  
2837          // Increase the server timeout to handle the creation and sending of large zip files.
2838          core_php_time_limit::raise();
2839  
2840          $this->require_view_grades();
2841  
2842          // Load all users with submit.
2843          $students = get_enrolled_users($this->context, "mod/assign:submit", null, 'u.*', null, null, null,
2844                          $this->show_only_active_users());
2845  
2846          // Build a list of files to zip.
2847          $filesforzipping = array();
2848          $fs = get_file_storage();
2849  
2850          $groupmode = groups_get_activity_groupmode($this->get_course_module());
2851          // All users.
2852          $groupid = 0;
2853          $groupname = '';
2854          if ($groupmode) {
2855              $groupid = groups_get_activity_group($this->get_course_module(), true);
2856              $groupname = groups_get_group_name($groupid).'-';
2857          }
2858  
2859          // Construct the zip file name.
2860          $filename = clean_filename($this->get_course()->shortname . '-' .
2861                                     $this->get_instance()->name . '-' .
2862                                     $groupname.$this->get_course_module()->id . '.zip');
2863  
2864          // Get all the files for each student.
2865          foreach ($students as $student) {
2866              $userid = $student->id;
2867              // Download all assigments submission or only selected users.
2868              if ($userids and !in_array($userid, $userids)) {
2869                  continue;
2870              }
2871  
2872              if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
2873                  // Get the plugins to add their own files to the zip.
2874  
2875                  $submissiongroup = false;
2876                  $groupname = '';
2877                  if ($this->get_instance()->teamsubmission) {
2878                      $submission = $this->get_group_submission($userid, 0, false);
2879                      $submissiongroup = $this->get_submission_group($userid);
2880                      if ($submissiongroup) {
2881                          $groupname = $submissiongroup->name . '-';
2882                      } else {
2883                          $groupname = get_string('defaultteam', 'assign') . '-';
2884                      }
2885                  } else {
2886                      $submission = $this->get_user_submission($userid, false);
2887                  }
2888  
2889                  if ($this->is_blind_marking()) {
2890                      $prefix = str_replace('_', ' ', $groupname . get_string('participant', 'assign'));
2891                      $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid));
2892                  } else {
2893                      $prefix = str_replace('_', ' ', $groupname . fullname($student));
2894                      $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid));
2895                  }
2896  
2897                  if ($submission) {
2898                      foreach ($this->submissionplugins as $plugin) {
2899                          if ($plugin->is_enabled() && $plugin->is_visible()) {
2900                              $pluginfiles = $plugin->get_files($submission, $student);
2901                              foreach ($pluginfiles as $zipfilepath => $file) {
2902                                  $subtype = $plugin->get_subtype();
2903                                  $type = $plugin->get_type();
2904                                  $zipfilename = basename($zipfilepath);
2905                                  $prefixedfilename = clean_filename($prefix .
2906                                                                     '_' .
2907                                                                     $subtype .
2908                                                                     '_' .
2909                                                                     $type .
2910                                                                     '_');
2911                                  if ($type == 'file') {
2912                                      $pathfilename = $prefixedfilename . $file->get_filepath() . $zipfilename;
2913                                  } else if ($type == 'onlinetext') {
2914                                      $pathfilename = $prefixedfilename . '/' . $zipfilename;
2915                                  } else {
2916                                      $pathfilename = $prefixedfilename . '/' . $zipfilename;
2917                                  }
2918                                  $pathfilename = clean_param($pathfilename, PARAM_PATH);
2919                                  $filesforzipping[$pathfilename] = $file;
2920                              }
2921                          }
2922                      }
2923                  }
2924              }
2925          }
2926          $result = '';
2927          if (count($filesforzipping) == 0) {
2928              $header = new assign_header($this->get_instance(),
2929                                          $this->get_context(),
2930                                          '',
2931                                          $this->get_course_module()->id,
2932                                          get_string('downloadall', 'assign'));
2933              $result .= $this->get_renderer()->render($header);
2934              $result .= $this->get_renderer()->notification(get_string('nosubmission', 'assign'));
2935              $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2936                                                                      'action'=>'grading'));
2937              $result .= $this->get_renderer()->continue_button($url);
2938              $result .= $this->view_footer();
2939          } else if ($zipfile = $this->pack_files($filesforzipping)) {
2940              \mod_assign\event\all_submissions_downloaded::create_from_assign($this)->trigger();
2941              // Send file and delete after sending.
2942              send_temp_file($zipfile, $filename);
2943              // We will not get here - send_temp_file calls exit.
2944          }
2945          return $result;
2946      }
2947  
2948      /**
2949       * Util function to add a message to the log.
2950       *
2951       * @deprecated since 2.7 - Use new events system instead.
2952       *             (see http://docs.moodle.org/dev/Migrating_logging_calls_in_plugins).
2953       *
2954       * @param string $action The current action
2955       * @param string $info A detailed description of the change. But no more than 255 characters.
2956       * @param string $url The url to the assign module instance.
2957       * @param bool $return If true, returns the arguments, else adds to log. The purpose of this is to
2958       *                     retrieve the arguments to use them with the new event system (Event 2).
2959       * @return void|array
2960       */
2961      public function add_to_log($action = '', $info = '', $url='', $return = false) {
2962          global $USER;
2963  
2964          $fullurl = 'view.php?id=' . $this->get_course_module()->id;
2965          if ($url != '') {
2966              $fullurl .= '&' . $url;
2967          }
2968  
2969          $args = array(
2970              $this->get_course()->id,
2971              'assign',
2972              $action,
2973              $fullurl,
2974              $info,
2975              $this->get_course_module()->id
2976          );
2977  
2978          if ($return) {
2979              // We only need to call debugging when returning a value. This is because the call to
2980              // call_user_func_array('add_to_log', $args) will trigger a debugging message of it's own.
2981              debugging('The mod_assign add_to_log() function is now deprecated.', DEBUG_DEVELOPER);
2982              return $args;
2983          }
2984          call_user_func_array('add_to_log', $args);
2985      }
2986  
2987      /**
2988       * Lazy load the page renderer and expose the renderer to plugins.
2989       *
2990       * @return assign_renderer
2991       */
2992      public function get_renderer() {
2993          global $PAGE;
2994          if ($this->output) {
2995              return $this->output;
2996          }
2997          $this->output = $PAGE->get_renderer('mod_assign', null, RENDERER_TARGET_GENERAL);
2998          return $this->output;
2999      }
3000  
3001      /**
3002       * Load the submission object for a particular user, optionally creating it if required.
3003       *
3004       * For team assignments there are 2 submissions - the student submission and the team submission
3005       * All files are associated with the team submission but the status of the students contribution is
3006       * recorded separately.
3007       *
3008       * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
3009       * @param bool $create If set to true a new submission object will be created in the database with the status set to "new".
3010       * @param int $attemptnumber - -1 means the latest attempt
3011       * @return stdClass The submission
3012       */
3013      public function get_user_submission($userid, $create, $attemptnumber=-1) {
3014          global $DB, $USER;
3015  
3016          if (!$userid) {
3017              $userid = $USER->id;
3018          }
3019          // If the userid is not null then use userid.
3020          $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
3021          if ($attemptnumber >= 0) {
3022              $params['attemptnumber'] = $attemptnumber;
3023          }
3024  
3025          // Only return the row with the highest attemptnumber.
3026          $submission = null;
3027          $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
3028          if ($submissions) {
3029              $submission = reset($submissions);
3030          }
3031  
3032          if ($submission) {
3033              return $submission;
3034          }
3035          if ($create) {
3036              $submission = new stdClass();
3037              $submission->assignment   = $this->get_instance()->id;
3038              $submission->userid       = $userid;
3039              $submission->timecreated = time();
3040              $submission->timemodified = $submission->timecreated;
3041              $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
3042              if ($attemptnumber >= 0) {
3043                  $submission->attemptnumber = $attemptnumber;
3044              } else {
3045                  $submission->attemptnumber = 0;
3046              }
3047              // Work out if this is the latest submission.
3048              $submission->latest = 0;
3049              $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
3050              if ($attemptnumber == -1) {
3051                  // This is a new submission so it must be the latest.
3052                  $submission->latest = 1;
3053              } else {
3054                  // We need to work this out.
3055                  $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
3056                  $latestsubmission = null;
3057                  if ($result) {
3058                      $latestsubmission = reset($result);
3059                  }
3060                  if (empty($latestsubmission) || ($attemptnumber > $latestsubmission->attemptnumber)) {
3061                      $submission->latest = 1;
3062                  }
3063              }
3064              if ($submission->latest) {
3065                  // This is the case when we need to set latest to 0 for all the other attempts.
3066                  $DB->set_field('assign_submission', 'latest', 0, $params);
3067              }
3068              $sid = $DB->insert_record('assign_submission', $submission);
3069              return $DB->get_record('assign_submission', array('id' => $sid));
3070          }
3071          return false;
3072      }
3073  
3074      /**
3075       * Load the submission object from it's id.
3076       *
3077       * @param int $submissionid The id of the submission we want
3078       * @return stdClass The submission
3079       */
3080      protected function get_submission($submissionid) {
3081          global $DB;
3082  
3083          $params = array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid);
3084          return $DB->get_record('assign_submission', $params, '*', MUST_EXIST);
3085      }
3086  
3087      /**
3088       * This will retrieve a user flags object from the db optionally creating it if required.
3089       * The user flags was split from the user_grades table in 2.5.
3090       *
3091       * @param int $userid The user we are getting the flags for.
3092       * @param bool $create If true the flags record will be created if it does not exist
3093       * @return stdClass The flags record
3094       */
3095      public function get_user_flags($userid, $create) {
3096          global $DB, $USER;
3097  
3098          // If the userid is not null then use userid.
3099          if (!$userid) {
3100              $userid = $USER->id;
3101          }
3102  
3103          $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
3104  
3105          $flags = $DB->get_record('assign_user_flags', $params);
3106  
3107          if ($flags) {
3108              return $flags;
3109          }
3110          if ($create) {
3111              $flags = new stdClass();
3112              $flags->assignment = $this->get_instance()->id;
3113              $flags->userid = $userid;
3114              $flags->locked = 0;
3115              $flags->extensionduedate = 0;
3116              $flags->workflowstate = '';
3117              $flags->allocatedmarker = 0;
3118  
3119              // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
3120              // This is because students only want to be notified about certain types of update (grades and feedback).
3121              $flags->mailed = 2;
3122  
3123              $fid = $DB->insert_record('assign_user_flags', $flags);
3124              $flags->id = $fid;
3125              return $flags;
3126          }
3127          return false;
3128      }
3129  
3130      /**
3131       * This will retrieve a grade object from the db, optionally creating it if required.
3132       *
3133       * @param int $userid The user we are grading
3134       * @param bool $create If true the grade will be created if it does not exist
3135       * @param int $attemptnumber The attempt number to retrieve the grade for. -1 means the latest submission.
3136       * @return stdClass The grade record
3137       */
3138      public function get_user_grade($userid, $create, $attemptnumber=-1) {
3139          global $DB, $USER;
3140  
3141          // If the userid is not null then use userid.
3142          if (!$userid) {
3143              $userid = $USER->id;
3144          }
3145          $submission = null;
3146  
3147          $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
3148          if ($attemptnumber < 0 || $create) {
3149              // Make sure this grade matches the latest submission attempt.
3150              if ($this->get_instance()->teamsubmission) {
3151                  $submission = $this->get_group_submission($userid, 0, true);
3152              } else {
3153                  $submission = $this->get_user_submission($userid, true);
3154              }
3155              if ($submission) {
3156                  $attemptnumber = $submission->attemptnumber;
3157              }
3158          }
3159  
3160          if ($attemptnumber >= 0) {
3161              $params['attemptnumber'] = $attemptnumber;
3162          }
3163  
3164          $grades = $DB->get_records('assign_grades', $params, 'attemptnumber DESC', '*', 0, 1);
3165  
3166          if ($grades) {
3167              return reset($grades);
3168          }
3169          if ($create) {
3170              $grade = new stdClass();
3171              $grade->assignment   = $this->get_instance()->id;
3172              $grade->userid       = $userid;
3173              $grade->timecreated = time();
3174              // If we are "auto-creating" a grade - and there is a submission
3175              // the new grade should not have a more recent timemodified value
3176              // than the submission.
3177              if ($submission) {
3178                  $grade->timemodified = $submission->timemodified;
3179              } else {
3180                  $grade->timemodified = $grade->timecreated;
3181              }
3182              $grade->grade = -1;
3183              $grade->grader = $USER->id;
3184              if ($attemptnumber >= 0) {
3185                  $grade->attemptnumber = $attemptnumber;
3186              }
3187  
3188              $gid = $DB->insert_record('assign_grades', $grade);
3189              $grade->id = $gid;
3190              return $grade;
3191          }
3192          return false;
3193      }
3194  
3195      /**
3196       * This will retrieve a grade object from the db.
3197       *
3198       * @param int $gradeid The id of the grade
3199       * @return stdClass The grade record
3200       */
3201      protected function get_grade($gradeid) {
3202          global $DB;
3203  
3204          $params = array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid);
3205          return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
3206      }
3207  
3208      /**
3209       * Print the grading page for a single user submission.
3210       *
3211       * @param array $args Optional args array (better than pulling args from _GET and _POST)
3212       * @return string
3213       */
3214      protected function view_single_grading_panel($args) {
3215          global $DB, $CFG, $SESSION, $PAGE;
3216  
3217          $o = '';
3218          $instance = $this->get_instance();
3219  
3220          require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3221  
3222          // Need submit permission to submit an assignment.
3223          require_capability('mod/assign:grade', $this->context);
3224  
3225          // If userid is passed - we are only grading a single student.
3226          $userid = $args['userid'];
3227          $attemptnumber = $args['attemptnumber'];
3228  
3229          $rownum = 0;
3230          $useridlist = array($userid);
3231  
3232          $last = true;
3233          // This variation on the url will link direct to this student, with no next/previous links.
3234          // The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
3235          $returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
3236          $this->register_return_link('grade', $returnparams);
3237  
3238          $user = $DB->get_record('user', array('id' => $userid));
3239          $submission = $this->get_user_submission($userid, false, $attemptnumber);
3240          $submissiongroup = null;
3241          $teamsubmission = null;
3242          $notsubmitted = array();
3243          if ($instance->teamsubmission) {
3244              $teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
3245              $submissiongroup = $this->get_submission_group($userid);
3246              $groupid = 0;
3247              if ($submissiongroup) {
3248                  $groupid = $submissiongroup->id;
3249              }
3250              $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
3251  
3252          }
3253  
3254          // Get the requested grade.
3255          $grade = $this->get_user_grade($userid, false, $attemptnumber);
3256          $flags = $this->get_user_flags($userid, false);
3257          if ($this->can_view_submission($userid)) {
3258              $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
3259              $extensionduedate = null;
3260              if ($flags) {
3261                  $extensionduedate = $flags->extensionduedate;
3262              }
3263              $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
3264              $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3265              $usergroups = $this->get_all_groups($user->id);
3266  
3267              $submissionstatus = new assign_submission_status_compact($instance->allowsubmissionsfromdate,
3268                                                                       $instance->alwaysshowdescription,
3269                                                                       $submission,
3270                                                                       $instance->teamsubmission,
3271                                                                       $teamsubmission,
3272                                                                       $submissiongroup,
3273                                                                       $notsubmitted,
3274                                                                       $this->is_any_submission_plugin_enabled(),
3275                                                                       $gradelocked,
3276                                                                       $this->is_graded($userid),
3277                                                                       $instance->duedate,
3278                                                                       $instance->cutoffdate,
3279                                                                       $this->get_submission_plugins(),
3280                                                                       $this->get_return_action(),
3281                                                                       $this->get_return_params(),
3282                                                                       $this->get_course_module()->id,
3283                                                                       $this->get_course()->id,
3284                                                                       assign_submission_status::GRADER_VIEW,
3285                                                                       $showedit,
3286                                                                       false,
3287                                                                       $viewfullnames,
3288                                                                       $extensionduedate,
3289                                                                       $this->get_context(),
3290                                                                       $this->is_blind_marking(),
3291                                                                       '',
3292                                                                       $instance->attemptreopenmethod,
3293                                                                       $instance->maxattempts,
3294                                                                       $this->get_grading_status($userid),
3295                                                                       $instance->preventsubmissionnotingroup,
3296                                                                       $usergroups);
3297              $o .= $this->get_renderer()->render($submissionstatus);
3298          }
3299  
3300          if ($grade) {
3301              $data = new stdClass();
3302              if ($grade->grade !== null && $grade->grade >= 0) {
3303                  $data->grade = format_float($grade->grade, 2);
3304              }
3305          } else {
3306              $data = new stdClass();
3307              $data->grade = '';
3308          }
3309  
3310          if (!empty($flags->workflowstate)) {
3311              $data->workflowstate = $flags->workflowstate;
3312          }
3313          if (!empty($flags->allocatedmarker)) {
3314              $data->allocatedmarker = $flags->allocatedmarker;
3315          }
3316  
3317          // Warning if required.
3318          $allsubmissions = $this->get_all_submissions($userid);
3319  
3320          if ($attemptnumber != -1 && ($attemptnumber + 1) != count($allsubmissions)) {
3321              $params = array('attemptnumber' => $attemptnumber + 1,
3322                              'totalattempts' => count($allsubmissions));
3323              $message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
3324              $o .= $this->get_renderer()->notification($message);
3325          }
3326  
3327          $pagination = array('rownum' => $rownum,
3328                              'useridlistid' => 0,
3329                              'last' => $last,
3330                              'userid' => $userid,
3331                              'attemptnumber' => $attemptnumber,
3332                              'gradingpanel' => true);
3333  
3334          if (!empty($args['formdata'])) {
3335              $data = (array) $data;
3336              $data = (object) array_merge($data, $args['formdata']);
3337          }
3338          $formparams = array($this, $data, $pagination);
3339          $mform = new mod_assign_grade_form(null,
3340                                             $formparams,
3341                                             'post',
3342                                             '',
3343                                             array('class' => 'gradeform'));
3344  
3345          if (!empty($args['formdata'])) {
3346              // If we were passed form data - we want the form to check the data
3347              // and show errors.
3348              $mform->is_validated();
3349          }
3350          $o .= $this->get_renderer()->heading(get_string('grade'), 3);
3351          $o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
3352  
3353          if (count($allsubmissions) > 1) {
3354              $allgrades = $this->get_all_grades($userid);
3355              $history = new assign_attempt_history_chooser($allsubmissions,
3356                                                            $allgrades,
3357                                                            $this->get_course_module()->id,
3358                                                            $userid);
3359  
3360              $o .= $this->get_renderer()->render($history);
3361          }
3362  
3363          \mod_assign\event\grading_form_viewed::create_from_user($this, $user)->trigger();
3364  
3365          return $o;
3366      }
3367  
3368      /**
3369       * Print the grading page for a single user submission.
3370       *
3371       * @param moodleform $mform
3372       * @return string
3373       */
3374      protected function view_single_grade_page($mform) {
3375          global $DB, $CFG, $SESSION;
3376  
3377          $o = '';
3378          $instance = $this->get_instance();
3379  
3380          require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3381  
3382          // Need submit permission to submit an assignment.
3383          require_capability('mod/assign:grade', $this->context);
3384  
3385          $header = new assign_header($instance,
3386                                      $this->get_context(),
3387                                      false,
3388                                      $this->get_course_module()->id,
3389                                      get_string('grading', 'assign'));
3390          $o .= $this->get_renderer()->render($header);
3391  
3392          // If userid is passed - we are only grading a single student.
3393          $rownum = optional_param('rownum', 0, PARAM_INT);
3394          $useridlistid = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
3395          $userid = optional_param('userid', 0, PARAM_INT);
3396          $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
3397  
3398          if (!$userid) {
3399              $useridlistkey = $this->get_useridlist_key($useridlistid);
3400              if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) {
3401                  $SESSION->mod_assign_useridlist[$useridlistkey] = $this->get_grading_userid_list();
3402              }
3403              $useridlist = $SESSION->mod_assign_useridlist[$useridlistkey];
3404          } else {
3405              $rownum = 0;
3406              $useridlistid = 0;
3407              $useridlist = array($userid);
3408          }
3409  
3410          if ($rownum < 0 || $rownum > count($useridlist)) {
3411              throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
3412          }
3413  
3414          $last = false;
3415          $userid = $useridlist[$rownum];
3416          if ($rownum == count($useridlist) - 1) {
3417              $last = true;
3418          }
3419          // This variation on the url will link direct to this student, with no next/previous links.
3420          // The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
3421          $returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
3422          $this->register_return_link('grade', $returnparams);
3423  
3424          $user = $DB->get_record('user', array('id' => $userid));
3425          if ($user) {
3426              $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3427              $usersummary = new assign_user_summary($user,
3428                                                     $this->get_course()->id,
3429                                                     $viewfullnames,
3430                                                     $this->is_blind_marking(),
3431                                                     $this->get_uniqueid_for_user($user->id),
3432                                                     get_extra_user_fields($this->get_context()),
3433                                                     !$this->is_active_user($userid));
3434              $o .= $this->get_renderer()->render($usersummary);
3435          }
3436          $submission = $this->get_user_submission($userid, false, $attemptnumber);
3437          $submissiongroup = null;
3438          $teamsubmission = null;
3439          $notsubmitted = array();
3440          if ($instance->teamsubmission) {
3441              $teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
3442              $submissiongroup = $this->get_submission_group($userid);
3443              $groupid = 0;
3444              if ($submissiongroup) {
3445                  $groupid = $submissiongroup->id;
3446              }
3447              $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
3448  
3449          }
3450  
3451          // Get the requested grade.
3452          $grade = $this->get_user_grade($userid, false, $attemptnumber);
3453          $flags = $this->get_user_flags($userid, false);
3454          if ($this->can_view_submission($userid)) {
3455              $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
3456              $extensionduedate = null;
3457              if ($flags) {
3458                  $extensionduedate = $flags->extensionduedate;
3459              }
3460              $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
3461              $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3462              $usergroups = $this->get_all_groups($user->id);
3463  
3464              $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
3465                                                               $instance->alwaysshowdescription,
3466                                                               $submission,
3467                                                               $instance->teamsubmission,
3468                                                               $teamsubmission,
3469                                                               $submissiongroup,
3470                                                               $notsubmitted,
3471                                                               $this->is_any_submission_plugin_enabled(),
3472                                                               $gradelocked,
3473                                                               $this->is_graded($userid),
3474                                                               $instance->duedate,
3475                                                               $instance->cutoffdate,
3476                                                               $this->get_submission_plugins(),
3477                                                               $this->get_return_action(),
3478                                                               $this->get_return_params(),
3479                                                               $this->get_course_module()->id,
3480                                                               $this->get_course()->id,
3481                                                               assign_submission_status::GRADER_VIEW,
3482                                                               $showedit,
3483                                                               false,
3484                                                               $viewfullnames,
3485                                                               $extensionduedate,
3486                                                               $this->get_context(),
3487                                                               $this->is_blind_marking(),
3488                                                               '',
3489                                                               $instance->attemptreopenmethod,
3490                                                               $instance->maxattempts,
3491                                                               $this->get_grading_status($userid),
3492                                                               $instance->preventsubmissionnotingroup,
3493                                                               $usergroups);
3494              $o .= $this->get_renderer()->render($submissionstatus);
3495          }
3496  
3497          if ($grade) {
3498              $data = new stdClass();
3499              if ($grade->grade !== null && $grade->grade >= 0) {
3500                  $data->grade = format_float($grade->grade, 2);
3501              }
3502          } else {
3503              $data = new stdClass();
3504              $data->grade = '';
3505          }
3506  
3507          if (!empty($flags->workflowstate)) {
3508              $data->workflowstate = $flags->workflowstate;
3509          }
3510          if (!empty($flags->allocatedmarker)) {
3511              $data->allocatedmarker = $flags->allocatedmarker;
3512          }
3513  
3514          // Warning if required.
3515          $allsubmissions = $this->get_all_submissions($userid);
3516  
3517          if ($attemptnumber != -1 && ($attemptnumber + 1) != count($allsubmissions)) {
3518              $params = array('attemptnumber'=>$attemptnumber + 1,
3519                              'totalattempts'=>count($allsubmissions));
3520              $message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
3521              $o .= $this->get_renderer()->notification($message);
3522          }
3523  
3524          // Now show the grading form.
3525          if (!$mform) {
3526              $pagination = array('rownum' => $rownum,
3527                                  'useridlistid' => $useridlistid,
3528                                  'last' => $last,
3529                                  'userid' => $userid,
3530                                  'attemptnumber' => $attemptnumber);
3531              $formparams = array($this, $data, $pagination);
3532              $mform = new mod_assign_grade_form(null,
3533                                                 $formparams,
3534                                                 'post',
3535                                                 '',
3536                                                 array('class'=>'gradeform'));
3537          }
3538          $o .= $this->get_renderer()->heading(get_string('grade'), 3);
3539          $o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
3540  
3541          if (count($allsubmissions) > 1 && $attemptnumber == -1) {
3542              $allgrades = $this->get_all_grades($userid);
3543              $history = new assign_attempt_history($allsubmissions,
3544                                                    $allgrades,
3545                                                    $this->get_submission_plugins(),
3546                                                    $this->get_feedback_plugins(),
3547                                                    $this->get_course_module()->id,
3548                                                    $this->get_return_action(),
3549                                                    $this->get_return_params(),
3550                                                    true,
3551                                                    $useridlistid,
3552                                                    $rownum);
3553  
3554              $o .= $this->get_renderer()->render($history);
3555          }
3556  
3557          \mod_assign\event\grading_form_viewed::create_from_user($this, $user)->trigger();
3558  
3559          $o .= $this->view_footer();
3560          return $o;
3561      }
3562  
3563      /**
3564       * Show a confirmation page to make sure they want to release student identities.
3565       *
3566       * @return string
3567       */
3568      protected function view_reveal_identities_confirm() {
3569          require_capability('mod/assign:revealidentities', $this->get_context());
3570  
3571          $o = '';
3572          $header = new assign_header($this->get_instance(),
3573                                      $this->get_context(),
3574                                      false,
3575                                      $this->get_course_module()->id);
3576          $o .= $this->get_renderer()->render($header);
3577  
3578          $urlparams = array('id'=>$this->get_course_module()->id,
3579                             'action'=>'revealidentitiesconfirm',
3580                             'sesskey'=>sesskey());
3581          $confirmurl = new moodle_url('/mod/assign/view.php', $urlparams);
3582  
3583          $urlparams = array('id'=>$this->get_course_module()->id,
3584                             'action'=>'grading');
3585          $cancelurl = new moodle_url('/mod/assign/view.php', $urlparams);
3586  
3587          $o .= $this->get_renderer()->confirm(get_string('revealidentitiesconfirm', 'assign'),
3588                                               $confirmurl,
3589                                               $cancelurl);
3590          $o .= $this->view_footer();
3591  
3592          \mod_assign\event\reveal_identities_confirmation_page_viewed::create_from_assign($this)->trigger();
3593  
3594          return $o;
3595      }
3596  
3597      /**
3598       * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
3599       *
3600       * @return string
3601       */
3602      protected function view_return_links() {
3603          $returnaction = optional_param('returnaction', '', PARAM_ALPHA);
3604          $returnparams = optional_param('returnparams', '', PARAM_TEXT);
3605  
3606          $params = array();
3607          $returnparams = str_replace('&amp;', '&', $returnparams);
3608          parse_str($returnparams, $params);
3609          $newparams = array('id' => $this->get_course_module()->id, 'action' => $returnaction);
3610          $params = array_merge($newparams, $params);
3611  
3612          $url = new moodle_url('/mod/assign/view.php', $params);
3613          return $this->get_renderer()->single_button($url, get_string('back'), 'get');
3614      }
3615  
3616      /**
3617       * View the grading table of all submissions for this assignment.
3618       *
3619       * @return string
3620       */
3621      protected function view_grading_table() {
3622          global $USER, $CFG, $SESSION;
3623  
3624          // Include grading options form.
3625          require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
3626          require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
3627          require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
3628          $o = '';
3629          $cmid = $this->get_course_module()->id;
3630  
3631          $links = array();
3632          if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
3633                  has_capability('moodle/grade:viewall', $this->get_course_context())) {
3634              $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
3635              $links[$gradebookurl] = get_string('viewgradebook', 'assign');
3636          }
3637          if ($this->is_any_submission_plugin_enabled() && $this->count_submissions()) {
3638              $downloadurl = '/mod/assign/view.php?id=' . $cmid . '&action=downloadall';
3639              $links[$downloadurl] = get_string('downloadall', 'assign');
3640          }
3641          if ($this->is_blind_marking() &&
3642                  has_capability('mod/assign:revealidentities', $this->get_context())) {
3643              $revealidentitiesurl = '/mod/assign/view.php?id=' . $cmid . '&action=revealidentities';
3644              $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
3645          }
3646          foreach ($this->get_feedback_plugins() as $plugin) {
3647              if ($plugin->is_enabled() && $plugin->is_visible()) {
3648                  foreach ($plugin->get_grading_actions() as $action => $description) {
3649                      $url = '/mod/assign/view.php' .
3650                             '?id=' .  $cmid .
3651                             '&plugin=' . $plugin->get_type() .
3652                             '&pluginsubtype=assignfeedback' .
3653                             '&action=viewpluginpage&pluginaction=' . $action;
3654                      $links[$url] = $description;
3655                  }
3656              }
3657          }
3658  
3659          // Sort links alphabetically based on the link description.
3660          core_collator::asort($links);
3661  
3662          $gradingactions = new url_select($links);
3663          $gradingactions->set_label(get_string('choosegradingaction', 'assign'));
3664  
3665          $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
3666  
3667          $perpage = $this->get_assign_perpage();
3668          $filter = get_user_preferences('assign_filter', '');
3669          $markerfilter = get_user_preferences('assign_markerfilter', '');
3670          $workflowfilter = get_user_preferences('assign_workflowfilter', '');
3671          $controller = $gradingmanager->get_active_controller();
3672          $showquickgrading = empty($controller) && $this->can_grade();
3673          $quickgrading = get_user_preferences('assign_quickgrading', false);
3674          $showonlyactiveenrolopt = has_capability('moodle/course:viewsuspendedusers', $this->context);
3675  
3676          $markingallocation = $this->get_instance()->markingworkflow &&
3677              $this->get_instance()->markingallocation &&
3678              has_capability('mod/assign:manageallocations', $this->context);
3679          // Get markers to use in drop lists.
3680          $markingallocationoptions = array();
3681          if ($markingallocation) {
3682              list($sort, $params) = users_order_by_sql();
3683              $markers = get_users_by_capability($this->context, 'mod/assign:grade', '', $sort);
3684              $markingallocationoptions[''] = get_string('filternone', 'assign');
3685              $markingallocationoptions[ASSIGN_MARKER_FILTER_NO_MARKER] = get_string('markerfilternomarker', 'assign');
3686              foreach ($markers as $marker) {
3687                  $markingallocationoptions[$marker->id] = fullname($marker);
3688              }
3689          }
3690  
3691          $markingworkflow = $this->get_instance()->markingworkflow;
3692          // Get marking states to show in form.
3693          $markingworkflowoptions = array();
3694          if ($markingworkflow) {
3695              $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
3696              $markingworkflowoptions[''] = get_string('filternone', 'assign');
3697              $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = $notmarked;
3698              $markingworkflowoptions = array_merge($markingworkflowoptions, $this->get_marking_workflow_states_for_current_user());
3699          }
3700  
3701          // Print options for changing the filter and changing the number of results per page.
3702          $gradingoptionsformparams = array('cm'=>$cmid,
3703                                            'contextid'=>$this->context->id,
3704                                            'userid'=>$USER->id,
3705                                            'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
3706                                            'showquickgrading'=>$showquickgrading,
3707                                            'quickgrading'=>$quickgrading,
3708                                            'markingworkflowopt'=>$markingworkflowoptions,
3709                                            'markingallocationopt'=>$markingallocationoptions,
3710                                            'showonlyactiveenrolopt'=>$showonlyactiveenrolopt,
3711                                            'showonlyactiveenrol'=>$this->show_only_active_users());
3712  
3713          $classoptions = array('class'=>'gradingoptionsform');
3714          $gradingoptionsform = new mod_assign_grading_options_form(null,
3715                                                                    $gradingoptionsformparams,
3716                                                                    'post',
3717                                                                    '',
3718                                                                    $classoptions);
3719  
3720          $batchformparams = array('cm'=>$cmid,
3721                                   'submissiondrafts'=>$this->get_instance()->submissiondrafts,
3722                                   'duedate'=>$this->get_instance()->duedate,
3723                                   'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
3724                                   'feedbackplugins'=>$this->get_feedback_plugins(),
3725                                   'context'=>$this->get_context(),
3726                                   'markingworkflow'=>$markingworkflow,
3727                                   'markingallocation'=>$markingallocation);
3728          $classoptions = array('class'=>'gradingbatchoperationsform');
3729  
3730          $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
3731                                                                                     $batchformparams,
3732                                                                                     'post',
3733                                                                                     '',
3734                                                                                     $classoptions);
3735  
3736          $gradingoptionsdata = new stdClass();
3737          $gradingoptionsdata->perpage = $perpage;
3738          $gradingoptionsdata->filter = $filter;
3739          $gradingoptionsdata->markerfilter = $markerfilter;
3740          $gradingoptionsdata->workflowfilter = $workflowfilter;
3741          $gradingoptionsform->set_data($gradingoptionsdata);
3742  
3743          $actionformtext = $this->get_renderer()->render($gradingactions);
3744          $header = new assign_header($this->get_instance(),
3745                                      $this->get_context(),
3746                                      false,
3747                                      $this->get_course_module()->id,
3748                                      get_string('grading', 'assign'),
3749                                      $actionformtext);
3750          $o .= $this->get_renderer()->render($header);
3751  
3752          $currenturl = $CFG->wwwroot .
3753                        '/mod/assign/view.php?id=' .
3754                        $this->get_course_module()->id .
3755                        '&action=grading';
3756  
3757          $o .= groups_print_activity_menu($this->get_course_module(), $currenturl, true);
3758  
3759          // Plagiarism update status apearring in the grading book.
3760          if (!empty($CFG->enableplagiarism)) {
3761              require_once($CFG->libdir . '/plagiarismlib.php');
3762              $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
3763          }
3764  
3765          if ($this->is_blind_marking() && has_capability('mod/assign:viewblinddetails', $this->get_context())) {
3766              $o .= $this->get_renderer()->notification(get_string('blindmarkingenabledwarning', 'assign'), 'notifymessage');
3767          }
3768  
3769          // Load and print the table of submissions.
3770          if ($showquickgrading && $quickgrading) {
3771              $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, true);
3772              $table = $this->get_renderer()->render($gradingtable);
3773              $page = optional_param('page', null, PARAM_INT);
3774              $quickformparams = array('cm'=>$this->get_course_module()->id,
3775                                       'gradingtable'=>$table,
3776                                       'sendstudentnotifications' => $this->get_instance()->sendstudentnotifications,
3777                                       'page' => $page);
3778              $quickgradingform = new mod_assign_quick_grading_form(null, $quickformparams);
3779  
3780              $o .= $this->get_renderer()->render(new assign_form('quickgradingform', $quickgradingform));
3781          } else {
3782              $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, false);
3783              $o .= $this->get_renderer()->render($gradingtable);
3784          }
3785  
3786          if ($this->can_grade()) {
3787              // We need to store the order of uses in the table as the person may wish to grade them.
3788              // This is done based on the row number of the user.
3789              $useridlist = $gradingtable->get_column_data('userid');
3790              $SESSION->mod_assign_useridlist[$this->get_useridlist_key()] = $useridlist;
3791          }
3792  
3793          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
3794          $users = array_keys($this->list_participants($currentgroup, true));
3795          if (count($users) != 0 && $this->can_grade()) {
3796              // If no enrolled user in a course then don't display the batch operations feature.
3797              $assignform = new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform);
3798              $o .= $this->get_renderer()->render($assignform);
3799          }
3800          $assignform = new assign_form('gradingoptionsform',
3801                                        $gradingoptionsform,
3802                                        'M.mod_assign.init_grading_options');
3803          $o .= $this->get_renderer()->render($assignform);
3804          return $o;
3805      }
3806  
3807      /**
3808       * View entire grader app.
3809       *
3810       * @return string
3811       */
3812      protected function view_grader() {
3813          global $USER, $PAGE;
3814  
3815          $o = '';
3816          // Need submit permission to submit an assignment.
3817          $this->require_view_grades();
3818  
3819          $PAGE->set_pagelayout('embedded');
3820  
3821          $PAGE->set_title($this->get_context()->get_context_name());
3822  
3823          $o .= $this->get_renderer()->header();
3824  
3825          $userid = optional_param('userid', 0, PARAM_INT);
3826          $blindid = optional_param('blindid', 0, PARAM_INT);
3827  
3828          if (!$userid && $blindid) {
3829              $userid = $this->get_user_id_for_uniqueid($blindid);
3830          }
3831  
3832          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
3833          $framegrader = new grading_app($userid, $currentgroup, $this);
3834  
3835          $o .= $this->get_renderer()->render($framegrader);
3836  
3837          $o .= $this->view_footer();
3838  
3839          \mod_assign\event\grading_table_viewed::create_from_assign($this)->trigger();
3840  
3841          return $o;
3842      }
3843      /**
3844       * View entire grading page.
3845       *
3846       * @return string
3847       */
3848      protected function view_grading_page() {
3849          global $CFG;
3850  
3851          $o = '';
3852          // Need submit permission to submit an assignment.
3853          $this->require_view_grades();
3854          require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3855  
3856          // Only load this if it is.
3857          $o .= $this->view_grading_table();
3858  
3859          $o .= $this->view_footer();
3860  
3861          \mod_assign\event\grading_table_viewed::create_from_assign($this)->trigger();
3862  
3863          return $o;
3864      }
3865  
3866      /**
3867       * Capture the output of the plagiarism plugins disclosures and return it as a string.
3868       *
3869       * @return string
3870       */
3871      protected function plagiarism_print_disclosure() {
3872          global $CFG;
3873          $o = '';
3874  
3875          if (!empty($CFG->enableplagiarism)) {
3876              require_once($CFG->libdir . '/plagiarismlib.php');
3877  
3878              $o .= plagiarism_print_disclosure($this->get_course_module()->id);
3879          }
3880  
3881          return $o;
3882      }
3883  
3884      /**
3885       * Message for students when assignment submissions have been closed.
3886       *
3887       * @param string $title The page title
3888       * @param array $notices The array of notices to show.
3889       * @return string
3890       */
3891      protected function view_notices($title, $notices) {
3892          global $CFG;
3893  
3894          $o = '';
3895  
3896          $header = new assign_header($this->get_instance(),
3897                                      $this->get_context(),
3898                                      $this->show_intro(),
3899                                      $this->get_course_module()->id,
3900                                      $title);
3901          $o .= $this->get_renderer()->render($header);
3902  
3903          foreach ($notices as $notice) {
3904              $o .= $this->get_renderer()->notification($notice);
3905          }
3906  
3907          $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id, 'action'=>'view'));
3908          $o .= $this->get_renderer()->continue_button($url);
3909  
3910          $o .= $this->view_footer();
3911  
3912          return $o;
3913      }
3914  
3915      /**
3916       * Get the name for a user - hiding their real name if blind marking is on.
3917       *
3918       * @param stdClass $user The user record as required by fullname()
3919       * @return string The name.
3920       */
3921      public function fullname($user) {
3922          if ($this->is_blind_marking()) {
3923              $hasviewblind = has_capability('mod/assign:viewblinddetails', $this->get_context());
3924              $uniqueid = $this->get_uniqueid_for_user($user->id);
3925              if ($hasviewblind) {
3926                  return get_string('participant', 'assign') . ' ' . $uniqueid . ' (' . fullname($user) . ')';
3927              } else {
3928                  return get_string('participant', 'assign') . ' ' . $uniqueid;
3929              }
3930          } else {
3931              return fullname($user);
3932          }
3933      }
3934  
3935      /**
3936       * View edit submissions page.
3937       *
3938       * @param moodleform $mform
3939       * @param array $notices A list of notices to display at the top of the
3940       *                       edit submission form (e.g. from plugins).
3941       * @return string The page output.
3942       */
3943      protected function view_edit_submission_page($mform, $notices) {
3944          global $CFG, $USER, $DB;
3945  
3946          $o = '';
3947          require_once($CFG->dirroot . '/mod/assign/submission_form.php');
3948          // Need submit permission to submit an assignment.
3949          $userid = optional_param('userid', $USER->id, PARAM_INT);
3950          $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
3951  
3952          // This variation on the url will link direct to this student.
3953          // The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
3954          $returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
3955          $this->register_return_link('editsubmission', $returnparams);
3956  
3957          if ($userid == $USER->id) {
3958              if (!$this->can_edit_submission($userid, $USER->id)) {
3959                  print_error('nopermission');
3960              }
3961              // User is editing their own submission.
3962              require_capability('mod/assign:submit', $this->context);
3963              $title = get_string('editsubmission', 'assign');
3964          } else {
3965              // User is editing another user's submission.
3966              if (!$this->can_edit_submission($userid, $USER->id)) {
3967                  print_error('nopermission');
3968              }
3969  
3970              $name = $this->fullname($user);
3971              $title = get_string('editsubmissionother', 'assign', $name);
3972          }
3973  
3974          if (!$this->submissions_open($userid)) {
3975              $message = array(get_string('submissionsclosed', 'assign'));
3976              return $this->view_notices($title, $message);
3977          }
3978  
3979          $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
3980                                                        $this->get_context(),
3981                                                        $this->show_intro(),
3982                                                        $this->get_course_module()->id,
3983                                                        $title));
3984          if ($userid == $USER->id) {
3985              // We only show this if it their submission.
3986              $o .= $this->plagiarism_print_disclosure();
3987          }
3988          $data = new stdClass();
3989          $data->userid = $userid;
3990          if (!$mform) {
3991              $mform = new mod_assign_submission_form(null, array($this, $data));
3992          }
3993  
3994          foreach ($notices as $notice) {
3995              $o .= $this->get_renderer()->notification($notice);
3996          }
3997  
3998          $o .= $this->get_renderer()->render(new assign_form('editsubmissionform', $mform));
3999  
4000          $o .= $this->view_footer();
4001  
4002          \mod_assign\event\submission_form_viewed::create_from_user($this, $user)->trigger();
4003  
4004          return $o;
4005      }
4006  
4007      /**
4008       * See if this assignment has a grade yet.
4009       *
4010       * @param int $userid
4011       * @return bool
4012       */
4013      protected function is_graded($userid) {
4014          $grade = $this->get_user_grade($userid, false);
4015          if ($grade) {
4016              return ($grade->grade !== null && $grade->grade >= 0);
4017          }
4018          return false;
4019      }
4020  
4021      /**
4022       * Perform an access check to see if the current $USER can view this group submission.
4023       *
4024       * @param int $groupid
4025       * @return bool
4026       */
4027      public function can_view_group_submission($groupid) {
4028          global $USER;
4029  
4030          $members = $this->get_submission_group_members($groupid, true);
4031          foreach ($members as $member) {
4032              // If we can view any members submission, we can view the submission for the group.
4033              if ($this->can_view_submission($member->id)) {
4034                  return true;
4035              }
4036          }
4037          return false;
4038      }
4039  
4040      /**
4041       * Perform an access check to see if the current $USER can view this users submission.
4042       *
4043       * @param int $userid
4044       * @return bool
4045       */
4046      public function can_view_submission($userid) {
4047          global $USER;
4048  
4049          if (!$this->is_active_user($userid) && !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
4050              return false;
4051          }
4052          if (!is_enrolled($this->get_course_context(), $userid)) {
4053              return false;
4054          }
4055          if (has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
4056              return true;
4057          }
4058          if ($userid == $USER->id && has_capability('mod/assign:submit', $this->context)) {
4059              return true;
4060          }
4061          return false;
4062      }
4063  
4064      /**
4065       * Allows the plugin to show a batch grading operation page.
4066       *
4067       * @param moodleform $mform
4068       * @return none
4069       */
4070      protected function view_plugin_grading_batch_operation($mform) {
4071          require_capability('mod/assign:grade', $this->context);
4072          $prefix = 'plugingradingbatchoperation_';
4073  
4074          if ($data = $mform->get_data()) {
4075              $tail = substr($data->operation, strlen($prefix));
4076              list($plugintype, $action) = explode('_', $tail, 2);
4077  
4078              $plugin = $this->get_feedback_plugin_by_type($plugintype);
4079              if ($plugin) {
4080                  $users = $data->selectedusers;
4081                  $userlist = explode(',', $users);
4082                  echo $plugin->grading_batch_operation($action, $userlist);
4083                  return;
4084              }
4085          }
4086          print_error('invalidformdata', '');
4087      }
4088  
4089      /**
4090       * Ask the user to confirm they want to perform this batch operation
4091       *
4092       * @param moodleform $mform Set to a grading batch operations form
4093       * @return string - the page to view after processing these actions
4094       */
4095      protected function process_grading_batch_operation(& $mform) {
4096          global $CFG;
4097          require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
4098          require_sesskey();
4099  
4100          $markingallocation = $this->get_instance()->markingworkflow &&
4101              $this->get_instance()->markingallocation &&
4102              has_capability('mod/assign:manageallocations', $this->context);
4103  
4104          $batchformparams = array('cm'=>$this->get_course_module()->id,
4105                                   'submissiondrafts'=>$this->get_instance()->submissiondrafts,
4106                                   'duedate'=>$this->get_instance()->duedate,
4107                                   'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
4108                                   'feedbackplugins'=>$this->get_feedback_plugins(),
4109                                   'context'=>$this->get_context(),
4110                                   'markingworkflow'=>$this->get_instance()->markingworkflow,
4111                                   'markingallocation'=>$markingallocation);
4112          $formclasses = array('class'=>'gradingbatchoperationsform');
4113          $mform = new mod_assign_grading_batch_operations_form(null,
4114                                                                $batchformparams,
4115                                                                'post',
4116                                                                '',
4117                                                                $formclasses);
4118  
4119          if ($data = $mform->get_data()) {
4120              // Get the list of users.
4121              $users = $data->selectedusers;
4122              $userlist = explode(',', $users);
4123  
4124              $prefix = 'plugingradingbatchoperation_';
4125  
4126              if ($data->operation == 'grantextension') {
4127                  // Reset the form so the grant extension page will create the extension form.
4128                  return 'grantextension';
4129              } else if ($data->operation == 'setmarkingworkflowstate') {
4130                  return 'viewbatchsetmarkingworkflowstate';
4131              } else if ($data->operation == 'setmarkingallocation') {
4132                  return 'viewbatchmarkingallocation';
4133              } else if (strpos($data->operation, $prefix) === 0) {
4134                  $tail = substr($data->operation, strlen($prefix));
4135                  list($plugintype, $action) = explode('_', $tail, 2);
4136  
4137                  $plugin = $this->get_feedback_plugin_by_type($plugintype);
4138                  if ($plugin) {
4139                      return 'plugingradingbatchoperation';
4140                  }
4141              }
4142  
4143              if ($data->operation == 'downloadselected') {
4144                  $this->download_submissions($userlist);
4145              } else {
4146                  foreach ($userlist as $userid) {
4147                      if ($data->operation == 'lock') {
4148                          $this->process_lock_submission($userid);
4149                      } else if ($data->operation == 'unlock') {
4150                          $this->process_unlock_submission($userid);
4151                      } else if ($data->operation == 'reverttodraft') {
4152                          $this->process_revert_to_draft($userid);
4153                      } else if ($data->operation == 'addattempt') {
4154                          if (!$this->get_instance()->teamsubmission) {
4155                              $this->process_add_attempt($userid);
4156                          }
4157                      }
4158                  }
4159              }
4160              if ($this->get_instance()->teamsubmission && $data->operation == 'addattempt') {
4161                  // This needs to be handled separately so that each team submission is only re-opened one time.
4162                  $this->process_add_attempt_group($userlist);
4163              }
4164          }
4165  
4166          return 'grading';
4167      }
4168  
4169      /**
4170       * Shows a form that allows the workflow state for selected submissions to be changed.
4171       *
4172       * @param moodleform $mform Set to a grading batch operations form
4173       * @return string - the page to view after processing these actions
4174       */
4175      protected function view_batch_set_workflow_state($mform) {
4176          global $CFG, $DB;
4177  
4178          require_once($CFG->dirroot . '/mod/assign/batchsetmarkingworkflowstateform.php');
4179  
4180          $o = '';
4181  
4182          $submitteddata = $mform->get_data();
4183          $users = $submitteddata->selectedusers;
4184          $userlist = explode(',', $users);
4185  
4186          $formdata = array('id' => $this->get_course_module()->id,
4187                            'selectedusers' => $users);
4188  
4189          $usershtml = '';
4190  
4191          $usercount = 0;
4192          $extrauserfields = get_extra_user_fields($this->get_context());
4193          foreach ($userlist as $userid) {
4194              if ($usercount >= 5) {
4195                  $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5);
4196                  break;
4197              }
4198              $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
4199  
4200              $usershtml .= $this->get_renderer()->render(new assign_user_summary($user,
4201                                                                  $this->get_course()->id,
4202                                                                  has_capability('moodle/site:viewfullnames',
4203                                                                  $this->get_course_context()),
4204                                                                  $this->is_blind_marking(),
4205                                                                  $this->get_uniqueid_for_user($user->id),
4206                                                                  $extrauserfields,
4207                                                                  !$this->is_active_user($userid)));
4208              $usercount += 1;
4209          }
4210  
4211          $formparams = array(
4212              'userscount' => count($userlist),
4213              'usershtml' => $usershtml,
4214              'markingworkflowstates' => $this->get_marking_workflow_states_for_current_user()
4215          );
4216  
4217          $mform = new mod_assign_batch_set_marking_workflow_state_form(null, $formparams);
4218          $mform->set_data($formdata);    // Initialises the hidden elements.
4219          $header = new assign_header($this->get_instance(),
4220              $this->get_context(),
4221              $this->show_intro(),
4222              $this->get_course_module()->id,
4223              get_string('setmarkingworkflowstate', 'assign'));
4224          $o .= $this->get_renderer()->render($header);
4225          $o .= $this->get_renderer()->render(new assign_form('setworkflowstate', $mform));
4226          $o .= $this->view_footer();
4227  
4228          \mod_assign\event\batch_set_workflow_state_viewed::create_from_assign($this)->trigger();
4229  
4230          return $o;
4231      }
4232  
4233      /**
4234       * Shows a form that allows the allocated marker for selected submissions to be changed.
4235       *
4236       * @param moodleform $mform Set to a grading batch operations form
4237       * @return string - the page to view after processing these actions
4238       */
4239      public function view_batch_markingallocation($mform) {
4240          global $CFG, $DB;
4241  
4242          require_once($CFG->dirroot . '/mod/assign/batchsetallocatedmarkerform.php');
4243  
4244          $o = '';
4245  
4246          $submitteddata = $mform->get_data();
4247          $users = $submitteddata->selectedusers;
4248          $userlist = explode(',', $users);
4249  
4250          $formdata = array('id' => $this->get_course_module()->id,
4251                            'selectedusers' => $users);
4252  
4253          $usershtml = '';
4254  
4255          $usercount = 0;
4256          $extrauserfields = get_extra_user_fields($this->get_context());
4257          foreach ($userlist as $userid) {
4258              if ($usercount >= 5) {
4259                  $usershtml .= get_string('moreusers', 'assign', count($userlist) - 5);
4260                  break;
4261              }
4262              $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
4263  
4264              $usershtml .= $this->get_renderer()->render(new assign_user_summary($user,
4265                  $this->get_course()->id,
4266                  has_capability('moodle/site:viewfullnames',
4267                  $this->get_course_context()),
4268                  $this->is_blind_marking(),
4269                  $this->get_uniqueid_for_user($user->id),
4270                  $extrauserfields,
4271                  !$this->is_active_user($userid)));
4272              $usercount += 1;
4273          }
4274  
4275          $formparams = array(
4276              'userscount' => count($userlist),
4277              'usershtml' => $usershtml,
4278          );
4279  
4280          list($sort, $params) = users_order_by_sql();
4281          $markers = get_users_by_capability($this->get_context(), 'mod/assign:grade', '', $sort);
4282          $markerlist = array();
4283          foreach ($markers as $marker) {
4284              $markerlist[$marker->id] = fullname($marker);
4285          }
4286  
4287          $formparams['markers'] = $markerlist;
4288  
4289          $mform = new mod_assign_batch_set_allocatedmarker_form(null, $formparams);
4290          $mform->set_data($formdata);    // Initialises the hidden elements.
4291          $header = new assign_header($this->get_instance(),
4292              $this->get_context(),
4293              $this->show_intro(),
4294              $this->get_course_module()->id,
4295              get_string('setmarkingallocation', 'assign'));
4296          $o .= $this->get_renderer()->render($header);
4297          $o .= $this->get_renderer()->render(new assign_form('setworkflowstate', $mform));
4298          $o .= $this->view_footer();
4299  
4300          \mod_assign\event\batch_set_marker_allocation_viewed::create_from_assign($this)->trigger();
4301  
4302          return $o;
4303      }
4304  
4305      /**
4306       * Ask the user to confirm they want to submit their work for grading.
4307       *
4308       * @param moodleform $mform - null unless form validation has failed
4309       * @return string
4310       */
4311      protected function check_submit_for_grading($mform) {
4312          global $USER, $CFG;
4313  
4314          require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
4315  
4316          // Check that all of the submission plugins are ready for this submission.
4317          $notifications = array();
4318          $submission = $this->get_user_submission($USER->id, false);
4319          $plugins = $this->get_submission_plugins();
4320          foreach ($plugins as $plugin) {
4321              if ($plugin->is_enabled() && $plugin->is_visible()) {
4322                  $check = $plugin->precheck_submission($submission);
4323                  if ($check !== true) {
4324                      $notifications[] = $check;
4325                  }
4326              }
4327          }
4328  
4329          $data = new stdClass();
4330          $adminconfig = $this->get_admin_config();
4331          $requiresubmissionstatement = $this->get_instance()->requiresubmissionstatement &&
4332                                         !empty($adminconfig->submissionstatement);
4333  
4334          $submissionstatement = '';
4335          if (!empty($adminconfig->submissionstatement)) {
4336              // Format the submission statement before its sent. We turn off para because this is going within
4337              // a form element.
4338              $options = array(
4339                  'context' => $this->get_context(),
4340                  'para' => false
4341              );
4342              $submissionstatement = format_text($adminconfig->submissionstatement, FORMAT_MOODLE, $options);
4343          }
4344  
4345          if ($mform == null) {
4346              $mform = new mod_assign_confirm_submission_form(null, array($requiresubmissionstatement,
4347                                                                          $submissionstatement,
4348                                                                          $this->get_course_module()->id,
4349                                                                          $data));
4350          }
4351          $o = '';
4352          $o .= $this->get_renderer()->header();
4353          $submitforgradingpage = new assign_submit_for_grading_page($notifications,
4354                                                                     $this->get_course_module()->id,
4355                                                                     $mform);
4356          $o .= $this->get_renderer()->render($submitforgradingpage);
4357          $o .= $this->view_footer();
4358  
4359          \mod_assign\event\submission_confirmation_form_viewed::create_from_assign($this)->trigger();
4360  
4361          return $o;
4362      }
4363  
4364      /**
4365       * Creates an assign_submission_status renderable.
4366       *
4367       * @param stdClass $user the user to get the report for
4368       * @param bool $showlinks return plain text or links to the profile
4369       * @return assign_submission_status renderable object
4370       */
4371      public function get_assign_submission_status_renderable($user, $showlinks) {
4372          global $PAGE;
4373  
4374          $instance = $this->get_instance();
4375          $flags = $this->get_user_flags($user->id, false);
4376          $submission = $this->get_user_submission($user->id, false);
4377  
4378          $teamsubmission = null;
4379          $submissiongroup = null;
4380          $notsubmitted = array();
4381          if ($instance->teamsubmission) {
4382              $teamsubmission = $this->get_group_submission($user->id, 0, false);
4383              $submissiongroup = $this->get_submission_group($user->id);
4384              $groupid = 0;
4385              if ($submissiongroup) {
4386                  $groupid = $submissiongroup->id;
4387              }
4388              $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
4389          }
4390  
4391          $showedit = $showlinks &&
4392                      ($this->is_any_submission_plugin_enabled()) &&
4393                      $this->can_edit_submission($user->id);
4394  
4395          $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($user->id, false);
4396  
4397          // Grading criteria preview.
4398          $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions');
4399          $gradingcontrollerpreview = '';
4400          if ($gradingmethod = $gradingmanager->get_active_method()) {
4401              $controller = $gradingmanager->get_controller($gradingmethod);
4402              if ($controller->is_form_defined()) {
4403                  $gradingcontrollerpreview = $controller->render_preview($PAGE);
4404              }
4405          }
4406  
4407          $showsubmit = ($showlinks && $this->submissions_open($user->id));
4408          $showsubmit = ($showsubmit && $this->show_submit_button($submission, $teamsubmission, $user->id));
4409  
4410          $extensionduedate = null;
4411          if ($flags) {
4412              $extensionduedate = $flags->extensionduedate;
4413          }
4414          $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
4415  
4416          $gradingstatus = $this->get_grading_status($user->id);
4417          $usergroups = $this->get_all_groups($user->id);
4418          $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
4419                                                            $instance->alwaysshowdescription,
4420                                                            $submission,
4421                                                            $instance->teamsubmission,
4422                                                            $teamsubmission,
4423                                                            $submissiongroup,
4424                                                            $notsubmitted,
4425                                                            $this->is_any_submission_plugin_enabled(),
4426                                                            $gradelocked,
4427                                                            $this->is_graded($user->id),
4428                                                            $instance->duedate,
4429                                                            $instance->cutoffdate,
4430                                                            $this->get_submission_plugins(),
4431                                                            $this->get_return_action(),
4432                                                            $this->get_return_params(),
4433                                                            $this->get_course_module()->id,
4434                                                            $this->get_course()->id,
4435                                                            assign_submission_status::STUDENT_VIEW,
4436                                                            $showedit,
4437                                                            $showsubmit,
4438                                                            $viewfullnames,
4439                                                            $extensionduedate,
4440                                                            $this->get_context(),
4441                                                            $this->is_blind_marking(),
4442                                                            $gradingcontrollerpreview,
4443                                                            $instance->attemptreopenmethod,
4444                                                            $instance->maxattempts,
4445                                                            $gradingstatus,
4446                                                            $instance->preventsubmissionnotingroup,
4447                                                            $usergroups);
4448          return $submissionstatus;
4449      }
4450  
4451  
4452      /**
4453       * Creates an assign_feedback_status renderable.
4454       *
4455       * @param stdClass $user the user to get the report for
4456       * @return assign_feedback_status renderable object
4457       */
4458      public function get_assign_feedback_status_renderable($user) {
4459          global $CFG, $DB, $PAGE;
4460  
4461          require_once($CFG->libdir.'/gradelib.php');
4462          require_once($CFG->dirroot.'/grade/grading/lib.php');
4463  
4464          $instance = $this->get_instance();
4465          $grade = $this->get_user_grade($user->id, false);
4466          $gradingstatus = $this->get_grading_status($user->id);
4467  
4468          $gradinginfo = grade_get_grades($this->get_course()->id,
4469                                      'mod',
4470                                      'assign',
4471                                      $instance->id,
4472                                      $user->id);
4473  
4474          $gradingitem = null;
4475          $gradebookgrade = null;
4476          if (isset($gradinginfo->items[0])) {
4477              $gradingitem = $gradinginfo->items[0];
4478              $gradebookgrade = $gradingitem->grades[$user->id];
4479          }
4480  
4481          // Check to see if all feedback plugins are empty.
4482          $emptyplugins = true;
4483          if ($grade) {
4484              foreach ($this->get_feedback_plugins() as $plugin) {
4485                  if ($plugin->is_visible() && $plugin->is_enabled()) {
4486                      if (!$plugin->is_empty($grade)) {
4487                          $emptyplugins = false;
4488                      }
4489                  }
4490              }
4491          }
4492  
4493          if ($this->get_instance()->markingworkflow && $gradingstatus != ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
4494              $emptyplugins = true; // Don't show feedback plugins until released either.
4495          }
4496  
4497          $cangrade = has_capability('mod/assign:grade', $this->get_context());
4498          // If there is a visible grade, show the summary.
4499          if ((!is_null($gradebookgrade->grade) || !$emptyplugins)
4500                  && ($cangrade || !$gradebookgrade->hidden)) {
4501  
4502              $gradefordisplay = null;
4503              $gradeddate = null;
4504              $grader = null;
4505              $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
4506  
4507              // Only show the grade if it is not hidden in gradebook.
4508              if (!is_null($gradebookgrade->grade) && ($cangrade || !$gradebookgrade->hidden)) {
4509                  if ($controller = $gradingmanager->get_active_controller()) {
4510                      $menu = make_grades_menu($this->get_instance()->grade);
4511                      $controller->set_grade_range($menu, $this->get_instance()->grade > 0);
4512                      $gradefordisplay = $controller->render_grade($PAGE,
4513                                                                   $grade->id,
4514                                                                   $gradingitem,
4515                                                                   $gradebookgrade->str_long_grade,
4516                                                                   $cangrade);
4517                  } else {
4518                      $gradefordisplay = $this->display_grade($gradebookgrade->grade, false);
4519                  }
4520                  $gradeddate = $gradebookgrade->dategraded;
4521                  if (isset($grade->grader)) {
4522                      $grader = $DB->get_record('user', array('id' => $grade->grader));
4523                  }
4524              }
4525  
4526              $feedbackstatus = new assign_feedback_status($gradefordisplay,
4527                                                    $gradeddate,
4528                                                    $grader,
4529                                                    $this->get_feedback_plugins(),
4530                                                    $grade,
4531                                                    $this->get_course_module()->id,
4532                                                    $this->get_return_action(),
4533                                                    $this->get_return_params());
4534              return $feedbackstatus;
4535          }
4536          return;
4537      }
4538  
4539      /**
4540       * Creates an assign_attempt_history renderable.
4541       *
4542       * @param stdClass $user the user to get the report for
4543       * @return assign_attempt_history renderable object
4544       */
4545      public function get_assign_attempt_history_renderable($user) {
4546  
4547          $allsubmissions = $this->get_all_submissions($user->id);
4548          $allgrades = $this->get_all_grades($user->id);
4549  
4550          $history = new assign_attempt_history($allsubmissions,
4551                                                $allgrades,
4552                                                $this->get_submission_plugins(),
4553                                                $this->get_feedback_plugins(),
4554                                                $this->get_course_module()->id,
4555                                                $this->get_return_action(),
4556                                                $this->get_return_params(),
4557                                                false,
4558                                                0,
4559                                                0);
4560          return $history;
4561      }
4562  
4563      /**
4564       * Print 2 tables of information with no action links -
4565       * the submission summary and the grading summary.
4566       *
4567       * @param stdClass $user the user to print the report for
4568       * @param bool $showlinks - Return plain text or links to the profile
4569       * @return string - the html summary
4570       */
4571      public function view_student_summary($user, $showlinks) {
4572  
4573          $o = '';
4574  
4575          if ($this->can_view_submission($user->id)) {
4576  
4577              if (has_capability('mod/assign:submit', $this->get_context(), $user)) {
4578                  $submissionstatus = $this->get_assign_submission_status_renderable($user, $showlinks);
4579                  $o .= $this->get_renderer()->render($submissionstatus);
4580              }
4581  
4582              // If there is a visible grade, show the feedback.
4583              $feedbackstatus = $this->get_assign_feedback_status_renderable($user);
4584              if ($feedbackstatus) {
4585                  $o .= $this->get_renderer()->render($feedbackstatus);
4586              }
4587  
4588              // If there is more than one submission, show the history.
4589              $history = $this->get_assign_attempt_history_renderable($user);
4590              if (count($history->submissions) > 1) {
4591                  $o .= $this->get_renderer()->render($history);
4592              }
4593          }
4594          return $o;
4595      }
4596  
4597      /**
4598       * Returns true if the submit subsission button should be shown to the user.
4599       *
4600       * @param stdClass $submission The users own submission record.
4601       * @param stdClass $teamsubmission The users team submission record if there is one
4602       * @param int $userid The user
4603       * @return bool
4604       */
4605      protected function show_submit_button($submission = null, $teamsubmission = null, $userid = null) {
4606          if ($teamsubmission) {
4607              if ($teamsubmission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
4608                  // The assignment submission has been completed.
4609                  return false;
4610              } else if ($this->submission_empty($teamsubmission)) {
4611                  // There is nothing to submit yet.
4612                  return false;
4613              } else if ($submission && $submission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
4614                  // The user has already clicked the submit button on the team submission.
4615                  return false;
4616              } else if (
4617                  !empty($this->get_instance()->preventsubmissionnotingroup)
4618                  && $this->get_submission_group($userid) == false
4619              ) {
4620                  return false;
4621              }
4622          } else if ($submission) {
4623              if ($submission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
4624                  // The assignment submission has been completed.
4625                  return false;
4626              } else if ($this->submission_empty($submission)) {
4627                  // There is nothing to submit.
4628                  return false;
4629              }
4630          } else {
4631              // We've not got a valid submission or team submission.
4632              return false;
4633          }
4634          // Last check is that this instance allows drafts.
4635          return $this->get_instance()->submissiondrafts;
4636      }
4637  
4638      /**
4639       * Get the grades for all previous attempts.
4640       * For each grade - the grader is a full user record,
4641       * and gradefordisplay is added (rendered from grading manager).
4642       *
4643       * @param int $userid If not set, $USER->id will be used.
4644       * @return array $grades All grade records for this user.
4645       */
4646      protected function get_all_grades($userid) {
4647          global $DB, $USER, $PAGE;
4648  
4649          // If the userid is not null then use userid.
4650          if (!$userid) {
4651              $userid = $USER->id;
4652          }
4653  
4654          $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
4655  
4656          $grades = $DB->get_records('assign_grades', $params, 'attemptnumber ASC');
4657  
4658          $gradercache = array();
4659          $cangrade = has_capability('mod/assign:grade', $this->get_context());
4660  
4661          // Need gradingitem and gradingmanager.
4662          $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
4663          $controller = $gradingmanager->get_active_controller();
4664  
4665          $gradinginfo = grade_get_grades($this->get_course()->id,
4666                                          'mod',
4667                                          'assign',
4668                                          $this->get_instance()->id,
4669                                          $userid);
4670  
4671          $gradingitem = null;
4672          if (isset($gradinginfo->items[0])) {
4673              $gradingitem = $gradinginfo->items[0];
4674          }
4675  
4676          foreach ($grades as $grade) {
4677              // First lookup the grader info.
4678              if (isset($gradercache[$grade->grader])) {
4679                  $grade->grader = $gradercache[$grade->grader];
4680              } else {
4681                  // Not in cache - need to load the grader record.
4682                  $grade->grader = $DB->get_record('user', array('id'=>$grade->grader));
4683                  $gradercache[$grade->grader->id] = $grade->grader;
4684              }
4685  
4686              // Now get the gradefordisplay.
4687              if ($controller) {
4688                  $controller->set_grade_range(make_grades_menu($this->get_instance()->grade), $this->get_instance()->grade > 0);
4689                  $grade->gradefordisplay = $controller->render_grade($PAGE,
4690                                                                       $grade->id,
4691                                                                       $gradingitem,
4692                                                                       $grade->grade,
4693                                                                       $cangrade);
4694              } else {
4695                  $grade->gradefordisplay = $this->display_grade($grade->grade, false);
4696              }
4697  
4698          }
4699  
4700          return $grades;
4701      }
4702  
4703      /**
4704       * Get the submissions for all previous attempts.
4705       *
4706       * @param int $userid If not set, $USER->id will be used.
4707       * @return array $submissions All submission records for this user (or group).
4708       */
4709      protected function get_all_submissions($userid) {
4710          global $DB, $USER;
4711  
4712          // If the userid is not null then use userid.
4713          if (!$userid) {
4714              $userid = $USER->id;
4715          }
4716  
4717          $params = array();
4718  
4719          if ($this->get_instance()->teamsubmission) {
4720              $groupid = 0;
4721              $group = $this->get_submission_group($userid);
4722              if ($group) {
4723                  $groupid = $group->id;
4724              }
4725  
4726              // Params to get the group submissions.
4727              $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
4728          } else {
4729              // Params to get the user submissions.
4730              $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
4731          }
4732  
4733          // Return the submissions ordered by attempt.
4734          $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber ASC');
4735  
4736          return $submissions;
4737      }
4738  
4739      /**
4740       * Creates an assign_grading_summary renderable.
4741       *
4742       * @return assign_grading_summary renderable object
4743       */
4744      public function get_assign_grading_summary_renderable() {
4745  
4746          $instance = $this->get_instance();
4747  
4748          $draft = ASSIGN_SUBMISSION_STATUS_DRAFT;
4749          $submitted = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
4750  
4751          $activitygroup = groups_get_activity_group($this->get_course_module());
4752  
4753          if ($instance->teamsubmission) {
4754              $defaultteammembers = $this->get_submission_group_members(0, true);
4755              $warnofungroupedusers = (count($defaultteammembers) > 0 && $instance->preventsubmissionnotingroup);
4756  
4757              $summary = new assign_grading_summary($this->count_teams($activitygroup),
4758                                                    $instance->submissiondrafts,
4759                                                    $this->count_submissions_with_status($draft),
4760                                                    $this->is_any_submission_plugin_enabled(),
4761                                                    $this->count_submissions_with_status($submitted),
4762                                                    $instance->cutoffdate,
4763                                                    $instance->duedate,
4764                                                    $this->get_course_module()->id,
4765                                                    $this->count_submissions_need_grading(),
4766                                                    $instance->teamsubmission,
4767                                                    $warnofungroupedusers);
4768          } else {
4769              // The active group has already been updated in groups_print_activity_menu().
4770              $countparticipants = $this->count_participants($activitygroup);
4771              $summary = new assign_grading_summary($countparticipants,
4772                                                    $instance->submissiondrafts,
4773                                                    $this->count_submissions_with_status($draft),
4774                                                    $this->is_any_submission_plugin_enabled(),
4775                                                    $this->count_submissions_with_status($submitted),
4776                                                    $instance->cutoffdate,
4777                                                    $instance->duedate,
4778                                                    $this->get_course_module()->id,
4779                                                    $this->count_submissions_need_grading(),
4780                                                    $instance->teamsubmission,
4781                                                    false);
4782  
4783          }
4784  
4785          return $summary;
4786      }
4787  
4788      /**
4789       * View submissions page (contains details of current submission).
4790       *
4791       * @return string
4792       */
4793      protected function view_submission_page() {
4794          global $CFG, $DB, $USER, $PAGE;
4795  
4796          $instance = $this->get_instance();
4797  
4798          $o = '';
4799  
4800          $postfix = '';
4801          if ($this->has_visible_attachments()) {
4802              $postfix = $this->render_area_files('mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0);
4803          }
4804          $o .= $this->get_renderer()->render(new assign_header($instance,
4805                                                        $this->get_context(),
4806                                                        $this->show_intro(),
4807                                                        $this->get_course_module()->id,
4808                                                        '', '', $postfix));
4809  
4810          // Display plugin specific headers.
4811          $plugins = array_merge($this->get_submission_plugins(), $this->get_feedback_plugins());
4812          foreach ($plugins as $plugin) {
4813              if ($plugin->is_enabled() && $plugin->is_visible()) {
4814                  $o .= $this->get_renderer()->render(new assign_plugin_header($plugin));
4815              }
4816          }
4817  
4818          if ($this->can_view_grades()) {
4819              // Group selector will only be displayed if necessary.
4820              $currenturl = new moodle_url('/mod/assign/view.php', array('id' => $this->get_course_module()->id));
4821              $o .= groups_print_activity_menu($this->get_course_module(), $currenturl->out(), true);
4822  
4823              $summary = $this->get_assign_grading_summary_renderable();
4824              $o .= $this->get_renderer()->render($summary);
4825          }
4826          $grade = $this->get_user_grade($USER->id, false);
4827          $submission = $this->get_user_submission($USER->id, false);
4828  
4829          if ($this->can_view_submission($USER->id)) {
4830              $o .= $this->view_student_summary($USER, true);
4831          }
4832  
4833          $o .= $this->view_footer();
4834  
4835          \mod_assign\event\submission_status_viewed::create_from_assign($this)->trigger();
4836  
4837          return $o;
4838      }
4839  
4840      /**
4841       * Convert the final raw grade(s) in the grading table for the gradebook.
4842       *
4843       * @param stdClass $grade
4844       * @return array
4845       */
4846      protected function convert_grade_for_gradebook(stdClass $grade) {
4847          $gradebookgrade = array();
4848          if ($grade->grade >= 0) {
4849              $gradebookgrade['rawgrade'] = $grade->grade;
4850          }
4851          // Allow "no grade" to be chosen.
4852          if ($grade->grade == -1) {
4853              $gradebookgrade['rawgrade'] = NULL;
4854          }
4855          $gradebookgrade['userid'] = $grade->userid;
4856          $gradebookgrade['usermodified'] = $grade->grader;
4857          $gradebookgrade['datesubmitted'] = null;
4858          $gradebookgrade['dategraded'] = $grade->timemodified;
4859          if (isset($grade->feedbackformat)) {
4860              $gradebookgrade['feedbackformat'] = $grade->feedbackformat;
4861          }
4862          if (isset($grade->feedbacktext)) {
4863              $gradebookgrade['feedback'] = $grade->feedbacktext;
4864          }
4865  
4866          return $gradebookgrade;
4867      }
4868  
4869      /**
4870       * Convert submission details for the gradebook.
4871       *
4872       * @param stdClass $submission
4873       * @return array
4874       */
4875      protected function convert_submission_for_gradebook(stdClass $submission) {
4876          $gradebookgrade = array();
4877  
4878          $gradebookgrade['userid'] = $submission->userid;
4879          $gradebookgrade['usermodified'] = $submission->userid;
4880          $gradebookgrade['datesubmitted'] = $submission->timemodified;
4881  
4882          return $gradebookgrade;
4883      }
4884  
4885      /**
4886       * Update grades in the gradebook.
4887       *
4888       * @param mixed $submission stdClass|null
4889       * @param mixed $grade stdClass|null
4890       * @return bool
4891       */
4892      protected function gradebook_item_update($submission=null, $grade=null) {
4893          global $CFG;
4894  
4895          require_once($CFG->dirroot.'/mod/assign/lib.php');
4896          // Do not push grade to gradebook if blind marking is active as
4897          // the gradebook would reveal the students.
4898          if ($this->is_blind_marking()) {
4899              return false;
4900          }
4901  
4902          // If marking workflow is enabled and grade is not released then remove any grade that may exist in the gradebook.
4903          if ($this->get_instance()->markingworkflow && !empty($grade) &&
4904                  $this->get_grading_status($grade->userid) != ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
4905              // Remove the grade (if it exists) from the gradebook as it is not 'final'.
4906              $grade->grade = -1;
4907              $grade->feedbacktext = '';
4908          }
4909  
4910          if ($submission != null) {
4911              if ($submission->userid == 0) {
4912                  // This is a group submission update.
4913                  $team = groups_get_members($submission->groupid, 'u.id');
4914  
4915                  foreach ($team as $member) {
4916                      $membersubmission = clone $submission;
4917                      $membersubmission->groupid = 0;
4918                      $membersubmission->userid = $member->id;
4919                      $this->gradebook_item_update($membersubmission, null);
4920                  }
4921                  return;
4922              }
4923  
4924              $gradebookgrade = $this->convert_submission_for_gradebook($submission);
4925  
4926          } else {
4927              $gradebookgrade = $this->convert_grade_for_gradebook($grade);
4928          }
4929          // Grading is disabled, return.
4930          if ($this->grading_disabled($gradebookgrade['userid'])) {
4931              return false;
4932          }
4933          $assign = clone $this->get_instance();
4934          $assign->cmidnumber = $this->get_course_module()->idnumber;
4935          // Set assign gradebook feedback plugin status (enabled and visible).
4936          $assign->gradefeedbackenabled = $this->is_gradebook_feedback_enabled();
4937          return assign_grade_item_update($assign, $gradebookgrade) == GRADE_UPDATE_OK;
4938      }
4939  
4940      /**
4941       * Update team submission.
4942       *
4943       * @param stdClass $submission
4944       * @param int $userid
4945       * @param bool $updatetime
4946       * @return bool
4947       */
4948      protected function update_team_submission(stdClass $submission, $userid, $updatetime) {
4949          global $DB;
4950  
4951          if ($updatetime) {
4952              $submission->timemodified = time();
4953          }
4954  
4955          // First update the submission for the current user.
4956          $mysubmission = $this->get_user_submission($userid, true, $submission->attemptnumber);
4957          $mysubmission->status = $submission->status;
4958  
4959          $this->update_submission($mysubmission, 0, $updatetime, false);
4960  
4961          // Now check the team settings to see if this assignment qualifies as submitted or draft.
4962          $team = $this->get_submission_group_members($submission->groupid, true);
4963  
4964          $allsubmitted = true;
4965          $anysubmitted = false;
4966          $result = true;
4967          if ($submission->status != ASSIGN_SUBMISSION_STATUS_REOPENED) {
4968              foreach ($team as $member) {
4969                  $membersubmission = $this->get_user_submission($member->id, false, $submission->attemptnumber);
4970  
4971                  // If no submission found for team member and member is active then everyone has not submitted.
4972                  if (!$membersubmission || $membersubmission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED
4973                          && ($this->is_active_user($member->id))) {
4974                      $allsubmitted = false;
4975                      if ($anysubmitted) {
4976                          break;
4977                      }
4978                  } else {
4979                      $anysubmitted = true;
4980                  }
4981              }
4982              if ($this->get_instance()->requireallteammemberssubmit) {
4983                  if ($allsubmitted) {
4984                      $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
4985                  } else {
4986                      $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
4987                  }
4988                  $result = $DB->update_record('assign_submission', $submission);
4989              } else {
4990                  if ($anysubmitted) {
4991                      $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
4992                  } else {
4993                      $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
4994                  }
4995                  $result = $DB->update_record('assign_submission', $submission);
4996              }
4997          } else {
4998              // Set the group submission to reopened.
4999              foreach ($team as $member) {
5000                  $membersubmission = $this->get_user_submission($member->id, true, $submission->attemptnumber);
5001                  $membersubmission->status = ASSIGN_SUBMISSION_STATUS_REOPENED;
5002                  $result = $DB->update_record('assign_submission', $membersubmission) && $result;
5003              }
5004              $result = $DB->update_record('assign_submission', $submission) && $result;
5005          }
5006  
5007          $this->gradebook_item_update($submission);
5008          return $result;
5009      }
5010  
5011      /**
5012       * Update grades in the gradebook based on submission time.
5013       *
5014       * @param stdClass $submission
5015       * @param int $userid
5016       * @param bool $updatetime
5017       * @param bool $teamsubmission
5018       * @return bool
5019       */
5020      protected function update_submission(stdClass $submission, $userid, $updatetime, $teamsubmission) {
5021          global $DB;
5022  
5023          if ($teamsubmission) {
5024              return $this->update_team_submission($submission, $userid, $updatetime);
5025          }
5026  
5027          if ($updatetime) {
5028              $submission->timemodified = time();
5029          }
5030          $result= $DB->update_record('assign_submission', $submission);
5031          if ($result) {
5032              $this->gradebook_item_update($submission);
5033          }
5034          return $result;
5035      }
5036  
5037      /**
5038       * Is this assignment open for submissions?
5039       *
5040       * Check the due date,
5041       * prevent late submissions,
5042       * has this person already submitted,
5043       * is the assignment locked?
5044       *
5045       * @param int $userid - Optional userid so we can see if a different user can submit
5046       * @param bool $skipenrolled - Skip enrollment checks (because they have been done already)
5047       * @param stdClass $submission - Pre-fetched submission record (or false to fetch it)
5048       * @param stdClass $flags - Pre-fetched user flags record (or false to fetch it)
5049       * @param stdClass $gradinginfo - Pre-fetched user gradinginfo record (or false to fetch it)
5050       * @return bool
5051       */
5052      public function submissions_open($userid = 0,
5053                                       $skipenrolled = false,
5054                                       $submission = false,
5055                                       $flags = false,
5056                                       $gradinginfo = false) {
5057          global $USER;
5058  
5059          if (!$userid) {
5060              $userid = $USER->id;
5061          }
5062  
5063          $time = time();
5064          $dateopen = true;
5065          $finaldate = false;
5066          if ($this->get_instance()->cutoffdate) {
5067              $finaldate = $this->get_instance()->cutoffdate;
5068          }
5069  
5070          if ($flags === false) {
5071              $flags = $this->get_user_flags($userid, false);
5072          }
5073          if ($flags && $flags->locked) {
5074              return false;
5075          }
5076  
5077          // User extensions.
5078          if ($finaldate) {
5079              if ($flags && $flags->extensionduedate) {
5080                  // Extension can be before cut off date.
5081                  if ($flags->extensionduedate > $finaldate) {
5082                      $finaldate = $flags->extensionduedate;
5083                  }
5084              }
5085          }
5086  
5087          if ($finaldate) {
5088              $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time && $time <= $finaldate);
5089          } else {
5090              $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time);
5091          }
5092  
5093          if (!$dateopen) {
5094              return false;
5095          }
5096  
5097          // Now check if this user has already submitted etc.
5098          if (!$skipenrolled && !is_enrolled($this->get_course_context(), $userid)) {
5099              return false;
5100          }
5101          // Note you can pass null for submission and it will not be fetched.
5102          if ($submission === false) {
5103              if ($this->get_instance()->teamsubmission) {
5104                  $submission = $this->get_group_submission($userid, 0, false);
5105              } else {
5106                  $submission = $this->get_user_submission($userid, false);
5107              }
5108          }
5109          if ($submission) {
5110  
5111              if ($this->get_instance()->submissiondrafts && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
5112                  // Drafts are tracked and the student has submitted the assignment.
5113                  return false;
5114              }
5115          }
5116  
5117          // See if this user grade is locked in the gradebook.
5118          if ($gradinginfo === false) {
5119              $gradinginfo = grade_get_grades($this->get_course()->id,
5120                                              'mod',
5121                                              'assign',
5122                                              $this->get_instance()->id,
5123                                              array($userid));
5124          }
5125          if ($gradinginfo &&
5126                  isset($gradinginfo->items[0]->grades[$userid]) &&
5127                  $gradinginfo->items[0]->grades[$userid]->locked) {
5128              return false;
5129          }
5130  
5131          return true;
5132      }
5133  
5134      /**
5135       * Render the files in file area.
5136       *
5137       * @param string $component
5138       * @param string $area
5139       * @param int $submissionid
5140       * @return string
5141       */
5142      public function render_area_files($component, $area, $submissionid) {
5143          global $USER;
5144  
5145          return $this->get_renderer()->assign_files($this->context, $submissionid, $area, $component);
5146  
5147      }
5148  
5149      /**
5150       * Capability check to make sure this grader can edit this submission.
5151       *
5152       * @param int $userid - The user whose submission is to be edited
5153       * @param int $graderid (optional) - The user who will do the editing (default to $USER->id).
5154       * @return bool
5155       */
5156      public function can_edit_submission($userid, $graderid = 0) {
5157          global $USER;
5158  
5159          if (empty($graderid)) {
5160              $graderid = $USER->id;
5161          }
5162  
5163          $instance = $this->get_instance();
5164          if ($userid == $graderid &&
5165              $instance->teamsubmission &&
5166              $instance->preventsubmissionnotingroup &&
5167              $this->get_submission_group($userid) == false) {
5168              return false;
5169          }
5170  
5171          if ($userid == $graderid &&
5172                  $this->submissions_open($userid) &&
5173                  has_capability('mod/assign:submit', $this->context, $graderid)) {
5174              // User can edit their own submission.
5175              return true;
5176          }
5177  
5178          if (!has_capability('mod/assign:editothersubmission', $this->context, $graderid)) {
5179              return false;
5180          }
5181  
5182          $cm = $this->get_course_module();
5183          if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
5184              $sharedgroupmembers = $this->get_shared_group_members($cm, $graderid);
5185              return in_array($userid, $sharedgroupmembers);
5186          }
5187          return true;
5188      }
5189  
5190      /**
5191       * Returns IDs of the users who share group membership with the specified user.
5192       *
5193       * @param stdClass|cm_info $cm Course-module
5194       * @param int $userid User ID
5195       * @return array An array of ID of users.
5196       */
5197      public function get_shared_group_members($cm, $userid) {
5198          if (!isset($this->sharedgroupmembers[$userid])) {
5199              $this->sharedgroupmembers[$userid] = array();
5200              $groupsids = array_keys(groups_get_activity_allowed_groups($cm, $userid));
5201              foreach ($groupsids as $groupid) {
5202                  $members = array_keys(groups_get_members($groupid, 'u.id'));
5203                  $this->sharedgroupmembers[$userid] = array_merge($this->sharedgroupmembers[$userid], $members);
5204              }
5205          }
5206  
5207          return $this->sharedgroupmembers[$userid];
5208      }
5209  
5210      /**
5211       * Returns a list of teachers that should be grading given submission.
5212       *
5213       * @param int $userid The submission to grade
5214       * @return array
5215       */
5216      protected function get_graders($userid) {
5217          // Potential graders should be active users only.
5218          $potentialgraders = get_enrolled_users($this->context, "mod/assign:grade", null, 'u.*', null, null, null, true);
5219  
5220          $graders = array();
5221          if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) {
5222              if ($groups = groups_get_all_groups($this->get_course()->id, $userid, $this->get_course_module()->groupingid)) {
5223                  foreach ($groups as $group) {
5224                      foreach ($potentialgraders as $grader) {
5225                          if ($grader->id == $userid) {
5226                              // Do not send self.
5227                              continue;
5228                          }
5229                          if (groups_is_member($group->id, $grader->id)) {
5230                              $graders[$grader->id] = $grader;
5231                          }
5232                      }
5233                  }
5234              } else {
5235                  // User not in group, try to find graders without group.
5236                  foreach ($potentialgraders as $grader) {
5237                      if ($grader->id == $userid) {
5238                          // Do not send self.
5239                          continue;
5240                      }
5241                      if (!groups_has_membership($this->get_course_module(), $grader->id)) {
5242                          $graders[$grader->id] = $grader;
5243                      }
5244                  }
5245              }
5246          } else {
5247              foreach ($potentialgraders as $grader) {
5248                  if ($grader->id == $userid) {
5249                      // Do not send self.
5250                      continue;
5251                  }
5252                  // Must be enrolled.
5253                  if (is_enrolled($this->get_course_context(), $grader->id)) {
5254                      $graders[$grader->id] = $grader;
5255                  }
5256              }
5257          }
5258          return $graders;
5259      }
5260  
5261      /**
5262       * Returns a list of users that should receive notification about given submission.
5263       *
5264       * @param int $userid The submission to grade
5265       * @return array
5266       */
5267      protected function get_notifiable_users($userid) {
5268          // Potential users should be active users only.
5269          $potentialusers = get_enrolled_users($this->context, "mod/assign:receivegradernotifications",
5270                                               null, 'u.*', null, null, null, true);
5271  
5272          $notifiableusers = array();
5273          if (groups_get_activity_groupmode($this->get_course_module()) == SEPARATEGROUPS) {
5274              if ($groups = groups_get_all_groups($this->get_course()->id, $userid, $this->get_course_module()->groupingid)) {
5275                  foreach ($groups as $group) {
5276                      foreach ($potentialusers as $potentialuser) {
5277                          if ($potentialuser->id == $userid) {
5278                              // Do not send self.
5279                              continue;
5280                          }
5281                          if (groups_is_member($group->id, $potentialuser->id)) {
5282                              $notifiableusers[$potentialuser->id] = $potentialuser;
5283                          }
5284                      }
5285                  }
5286              } else {
5287                  // User not in group, try to find graders without group.
5288                  foreach ($potentialusers as $potentialuser) {
5289                      if ($potentialuser->id == $userid) {
5290                          // Do not send self.
5291                          continue;
5292                      }
5293                      if (!groups_has_membership($this->get_course_module(), $potentialuser->id)) {
5294                          $notifiableusers[$potentialuser->id] = $potentialuser;
5295                      }
5296                  }
5297              }
5298          } else {
5299              foreach ($potentialusers as $potentialuser) {
5300                  if ($potentialuser->id == $userid) {
5301                      // Do not send self.
5302                      continue;
5303                  }
5304                  // Must be enrolled.
5305                  if (is_enrolled($this->get_course_context(), $potentialuser->id)) {
5306                      $notifiableusers[$potentialuser->id] = $potentialuser;
5307                  }
5308              }
5309          }
5310          return $notifiableusers;
5311      }
5312  
5313      /**
5314       * Format a notification for plain text.
5315       *
5316       * @param string $messagetype
5317       * @param stdClass $info
5318       * @param stdClass $course
5319       * @param stdClass $context
5320       * @param string $modulename
5321       * @param string $assignmentname
5322       */
5323      protected static function format_notification_message_text($messagetype,
5324                                                               $info,
5325                                                               $course,
5326                                                               $context,
5327                                                               $modulename,
5328                                                               $assignmentname) {
5329          $formatparams = array('context' => $context->get_course_context());
5330          $posttext  = format_string($course->shortname, true, $formatparams) .
5331                       ' -> ' .
5332                       $modulename .
5333                       ' -> ' .
5334                       format_string($assignmentname, true, $formatparams) . "\n";
5335          $posttext .= '---------------------------------------------------------------------' . "\n";
5336          $posttext .= get_string($messagetype . 'text', 'assign', $info)."\n";
5337          $posttext .= "\n---------------------------------------------------------------------\n";
5338          return $posttext;
5339      }
5340  
5341      /**
5342       * Format a notification for HTML.
5343       *
5344       * @param string $messagetype
5345       * @param stdClass $info
5346       * @param stdClass $course
5347       * @param stdClass $context
5348       * @param string $modulename
5349       * @param stdClass $coursemodule
5350       * @param string $assignmentname
5351       */
5352      protected static function format_notification_message_html($messagetype,
5353                                                               $info,
5354                                                               $course,
5355                                                               $context,
5356                                                               $modulename,
5357                                                               $coursemodule,
5358                                                               $assignmentname) {
5359          global $CFG;
5360          $formatparams = array('context' => $context->get_course_context());
5361          $posthtml  = '<p><font face="sans-serif">' .
5362                       '<a href="' . $CFG->wwwroot . '/course/view.php?id=' . $course->id . '">' .
5363                       format_string($course->shortname, true, $formatparams) .
5364                       '</a> ->' .
5365                       '<a href="' . $CFG->wwwroot . '/mod/assign/index.php?id=' . $course->id . '">' .
5366                       $modulename .
5367                       '</a> ->' .
5368                       '<a href="' . $CFG->wwwroot . '/mod/assign/view.php?id=' . $coursemodule->id . '">' .
5369                       format_string($assignmentname, true, $formatparams) .
5370                       '</a></font></p>';
5371          $posthtml .= '<hr /><font face="sans-serif">';
5372          $posthtml .= '<p>' . get_string($messagetype . 'html', 'assign', $info) . '</p>';
5373          $posthtml .= '</font><hr />';
5374          return $posthtml;
5375      }
5376  
5377      /**
5378       * Message someone about something (static so it can be called from cron).
5379       *
5380       * @param stdClass $userfrom
5381       * @param stdClass $userto
5382       * @param string $messagetype
5383       * @param string $eventtype
5384       * @param int $updatetime
5385       * @param stdClass $coursemodule
5386       * @param stdClass $context
5387       * @param stdClass $course
5388       * @param string $modulename
5389       * @param string $assignmentname
5390       * @param bool $blindmarking
5391       * @param int $uniqueidforuser
5392       * @return void
5393       */
5394      public static function send_assignment_notification($userfrom,
5395                                                          $userto,
5396                                                          $messagetype,
5397                                                          $eventtype,
5398                                                          $updatetime,
5399                                                          $coursemodule,
5400                                                          $context,
5401                                                          $course,
5402                                                          $modulename,
5403                                                          $assignmentname,
5404                                                          $blindmarking,
5405                                                          $uniqueidforuser) {
5406          global $CFG;
5407  
5408          $info = new stdClass();
5409          if ($blindmarking) {
5410              $userfrom = clone($userfrom);
5411              $info->username = get_string('participant', 'assign') . ' ' . $uniqueidforuser;
5412              $userfrom->firstname = get_string('participant', 'assign');
5413              $userfrom->lastname = $uniqueidforuser;
5414              $userfrom->email = $CFG->noreplyaddress;
5415          } else {
5416              $info->username = fullname($userfrom, true);
5417          }
5418          $info->assignment = format_string($assignmentname, true, array('context'=>$context));
5419          $info->url = $CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id;
5420          $info->timeupdated = userdate($updatetime, get_string('strftimerecentfull'));
5421  
5422          $postsubject = get_string($messagetype . 'small', 'assign', $info);
5423          $posttext = self::format_notification_message_text($messagetype,
5424                                                             $info,
5425                                                             $course,
5426                                                             $context,
5427                                                             $modulename,
5428                                                             $assignmentname);
5429          $posthtml = '';
5430          if ($userto->mailformat == 1) {
5431              $posthtml = self::format_notification_message_html($messagetype,
5432                                                                 $info,
5433                                                                 $course,
5434                                                                 $context,
5435                                                                 $modulename,
5436                                                                 $coursemodule,
5437                                                                 $assignmentname);
5438          }
5439  
5440          $eventdata = new stdClass();
5441          $eventdata->modulename       = 'assign';
5442          $eventdata->userfrom         = $userfrom;
5443          $eventdata->userto           = $userto;
5444          $eventdata->subject          = $postsubject;
5445          $eventdata->fullmessage      = $posttext;
5446          $eventdata->fullmessageformat = FORMAT_PLAIN;
5447          $eventdata->fullmessagehtml  = $posthtml;
5448          $eventdata->smallmessage     = $postsubject;
5449  
5450          $eventdata->name            = $eventtype;
5451          $eventdata->component       = 'mod_assign';
5452          $eventdata->notification    = 1;
5453          $eventdata->contexturl      = $info->url;
5454          $eventdata->contexturlname  = $info->assignment;
5455  
5456          message_send($eventdata);
5457      }
5458  
5459      /**
5460       * Message someone about something.
5461       *
5462       * @param stdClass $userfrom
5463       * @param stdClass $userto
5464       * @param string $messagetype
5465       * @param string $eventtype
5466       * @param int $updatetime
5467       * @return void
5468       */
5469      public function send_notification($userfrom, $userto, $messagetype, $eventtype, $updatetime) {
5470          global $USER;
5471          $userid = core_user::is_real_user($userfrom->id) ? $userfrom->id : $USER->id;
5472          $uniqueid = $this->get_uniqueid_for_user($userid);
5473          self::send_assignment_notification($userfrom,
5474                                             $userto,
5475                                             $messagetype,
5476                                             $eventtype,
5477                                             $updatetime,
5478                                             $this->get_course_module(),
5479                                             $this->get_context(),
5480                                             $this->get_course(),
5481                                             $this->get_module_name(),
5482                                             $this->get_instance()->name,
5483                                             $this->is_blind_marking(),
5484                                             $uniqueid);
5485      }
5486  
5487      /**
5488       * Notify student upon successful submission copy.
5489       *
5490       * @param stdClass $submission
5491       * @return void
5492       */
5493      protected function notify_student_submission_copied(stdClass $submission) {
5494          global $DB, $USER;
5495  
5496          $adminconfig = $this->get_admin_config();
5497          // Use the same setting for this - no need for another one.
5498          if (empty($adminconfig->submissionreceipts)) {
5499              // No need to do anything.
5500              return;
5501          }
5502          if ($submission->userid) {
5503              $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
5504          } else {
5505              $user = $USER;
5506          }
5507          $this->send_notification($user,
5508                                   $user,
5509                                   'submissioncopied',
5510                                   'assign_notification',
5511                                   $submission->timemodified);
5512      }
5513      /**
5514       * Notify student upon successful submission.
5515       *
5516       * @param stdClass $submission
5517       * @return void
5518       */
5519      protected function notify_student_submission_receipt(stdClass $submission) {
5520          global $DB, $USER;
5521  
5522          $adminconfig = $this->get_admin_config();
5523          if (empty($adminconfig->submissionreceipts)) {
5524              // No need to do anything.
5525              return;
5526          }
5527          if ($submission->userid) {
5528              $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
5529          } else {
5530              $user = $USER;
5531          }
5532          if ($submission->userid == $USER->id) {
5533              $this->send_notification(core_user::get_noreply_user(),
5534                                       $user,
5535                                       'submissionreceipt',
5536                                       'assign_notification',
5537                                       $submission->timemodified);
5538          } else {
5539              $this->send_notification($USER,
5540                                       $user,
5541                                       'submissionreceiptother',
5542                                       'assign_notification',
5543                                       $submission->timemodified);
5544          }
5545      }
5546  
5547      /**
5548       * Send notifications to graders upon student submissions.
5549       *
5550       * @param stdClass $submission
5551       * @return void
5552       */
5553      protected function notify_graders(stdClass $submission) {
5554          global $DB, $USER;
5555  
5556          $instance = $this->get_instance();
5557  
5558          $late = $instance->duedate && ($instance->duedate < time());
5559  
5560          if (!$instance->sendnotifications && !($late && $instance->sendlatenotifications)) {
5561              // No need to do anything.
5562              return;
5563          }
5564  
5565          if ($submission->userid) {
5566              $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
5567          } else {
5568              $user = $USER;
5569          }
5570  
5571          if ($notifyusers = $this->get_notifiable_users($user->id)) {
5572              foreach ($notifyusers as $notifyuser) {
5573                  $this->send_notification($user,
5574                                           $notifyuser,
5575                                           'gradersubmissionupdated',
5576                                           'assign_notification',
5577                                           $submission->timemodified);
5578              }
5579          }
5580      }
5581  
5582      /**
5583       * Submit a submission for grading.
5584       *
5585       * @param stdClass $data - The form data
5586       * @param array $notices - List of error messages to display on an error condition.
5587       * @return bool Return false if the submission was not submitted.
5588       */
5589      public function submit_for_grading($data, $notices) {
5590          global $USER;
5591  
5592          $userid = $USER->id;
5593          if (!empty($data->userid)) {
5594              $userid = $data->userid;
5595          }
5596          // Need submit permission to submit an assignment.
5597          if ($userid == $USER->id) {
5598              require_capability('mod/assign:submit', $this->context);
5599          } else {
5600              if (!$this->can_edit_submission($userid, $USER->id)) {
5601                  print_error('nopermission');
5602              }
5603          }
5604  
5605          $instance = $this->get_instance();
5606  
5607          if ($instance->teamsubmission) {
5608              $submission = $this->get_group_submission($userid, 0, true);
5609          } else {
5610              $submission = $this->get_user_submission($userid, true);
5611          }
5612  
5613          if (!$this->submissions_open($userid)) {
5614              $notices[] = get_string('submissionsclosed', 'assign');
5615              return false;
5616          }
5617  
5618          if ($instance->requiresubmissionstatement && empty($data->submissionstatement) && $USER->id == $userid) {
5619              return false;
5620          }
5621  
5622          if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
5623              // Give each submission plugin a chance to process the submission.
5624              $plugins = $this->get_submission_plugins();
5625              foreach ($plugins as $plugin) {
5626                  if ($plugin->is_enabled() && $plugin->is_visible()) {
5627                      $plugin->submit_for_grading($submission);
5628                  }
5629              }
5630  
5631              $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
5632              $this->update_submission($submission, $userid, true, $instance->teamsubmission);
5633              $completion = new completion_info($this->get_course());
5634              if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
5635                  $this->update_activity_completion_records($instance->teamsubmission,
5636                                                            $instance->requireallteammemberssubmit,
5637                                                            $submission,
5638                                                            $userid,
5639                                                            COMPLETION_COMPLETE,
5640                                                            $completion);
5641              }
5642  
5643              if (!empty($data->submissionstatement) && $USER->id == $userid) {
5644                  \mod_assign\event\statement_accepted::create_from_submission($this, $submission)->trigger();
5645              }
5646              $this->notify_graders($submission);
5647              $this->notify_student_submission_receipt($submission);
5648  
5649              \mod_assign\event\assessable_submitted::create_from_submission($this, $submission, false)->trigger();
5650  
5651              return true;
5652          }
5653          $notices[] = get_string('submissionsclosed', 'assign');
5654          return false;
5655      }
5656  
5657      /**
5658       * A students submission is submitted for grading by a teacher.
5659       *
5660       * @return bool
5661       */
5662      protected function process_submit_other_for_grading($mform, $notices) {
5663          global $USER, $CFG;
5664  
5665          require_sesskey();
5666  
5667          $userid = optional_param('userid', $USER->id, PARAM_INT);
5668  
5669          if (!$this->submissions_open($userid)) {
5670              $notices[] = get_string('submissionsclosed', 'assign');
5671              return false;
5672          }
5673          $data = new stdClass();
5674          $data->userid = $userid;
5675          return $this->submit_for_grading($data, $notices);
5676      }
5677  
5678      /**
5679       * Assignment submission is processed before grading.
5680       *
5681       * @param moodleform|null $mform If validation failed when submitting this form - this is the moodleform.
5682       *               It can be null.
5683       * @return bool Return false if the validation fails. This affects which page is displayed next.
5684       */
5685      protected function process_submit_for_grading($mform, $notices) {
5686          global $CFG;
5687  
5688          require_once($CFG->dirroot . '/mod/assign/submissionconfirmform.php');
5689          require_sesskey();
5690  
5691          if (!$this->submissions_open()) {
5692              $notices[] = get_string('submissionsclosed', 'assign');
5693              return false;
5694          }
5695          $instance = $this->get_instance();
5696          $data = new stdClass();
5697          $adminconfig = $this->get_admin_config();
5698          $requiresubmissionstatement = $instance->requiresubmissionstatement &&
5699                                         !empty($adminconfig->submissionstatement);
5700  
5701          $submissionstatement = '';
5702          if (!empty($adminconfig->submissionstatement)) {
5703              // Format the submission statement before its sent. We turn off para because this is going within
5704              // a form element.
5705              $options = array(
5706                  'context' => $this->get_context(),
5707                  'para' => false
5708              );
5709              $submissionstatement = format_text($adminconfig->submissionstatement, FORMAT_MOODLE, $options);
5710          }
5711  
5712          if ($mform == null) {
5713              $mform = new mod_assign_confirm_submission_form(null, array($requiresubmissionstatement,
5714                                                                      $submissionstatement,
5715                                                                      $this->get_course_module()->id,
5716                                                                      $data));
5717          }
5718  
5719          $data = $mform->get_data();
5720          if (!$mform->is_cancelled()) {
5721              if ($mform->get_data() == false) {
5722                  return false;
5723              }
5724              return $this->submit_for_grading($data, $notices);
5725          }
5726          return true;
5727      }
5728  
5729      /**
5730       * Save the extension date for a single user.
5731       *
5732       * @param int $userid The user id
5733       * @param mixed $extensionduedate Either an integer date or null
5734       * @return boolean
5735       */
5736      public function save_user_extension($userid, $extensionduedate) {
5737          global $DB;
5738  
5739          // Need submit permission to submit an assignment.
5740          require_capability('mod/assign:grantextension', $this->context);
5741  
5742          if (!is_enrolled($this->get_course_context(), $userid)) {
5743              return false;
5744          }
5745          if (!has_capability('mod/assign:submit', $this->context, $userid)) {
5746              return false;
5747          }
5748  
5749          if ($this->get_instance()->duedate && $extensionduedate) {
5750              if ($this->get_instance()->duedate > $extensionduedate) {
5751                  return false;
5752              }
5753          }
5754          if ($this->get_instance()->allowsubmissionsfromdate && $extensionduedate) {
5755              if ($this->get_instance()->allowsubmissionsfromdate > $extensionduedate) {
5756                  return false;
5757              }
5758          }
5759  
5760          $flags = $this->get_user_flags($userid, true);
5761          $flags->extensionduedate = $extensionduedate;
5762  
5763          $result = $this->update_user_flags($flags);
5764  
5765          if ($result) {
5766              \mod_assign\event\extension_granted::create_from_assign($this, $userid)->trigger();
5767          }
5768          return $result;
5769      }
5770  
5771      /**
5772       * Save extension date.
5773       *
5774       * @param moodleform $mform The submitted form
5775       * @return boolean
5776       */
5777      protected function process_save_extension(& $mform) {
5778          global $DB, $CFG;
5779  
5780          // Include extension form.
5781          require_once($CFG->dirroot . '/mod/assign/extensionform.php');
5782          require_sesskey();
5783  
5784          $formparams = array(
5785              'instance' => $this->get_instance(),
5786              'userscount' => 0,
5787              'usershtml' => '',
5788          );
5789  
5790          $mform = new mod_assign_extension_form(null, $formparams);
5791  
5792          if ($mform->is_cancelled()) {
5793              return true;
5794          }
5795  
5796          if ($formdata = $mform->get_data()) {
5797              if (!empty($formdata->selectedusers)) {
5798                  $users = explode(',', $formdata->selectedusers);
5799                  $result = true;
5800                  foreach ($users as $userid) {
5801                      $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
5802                      $result = $this->save_user_extension($user->id, $formdata->extensionduedate) && $result;
5803                  }
5804                  return $result;
5805              }
5806              if (!empty($formdata->userid)) {
5807                  $user = $DB->get_record('user', array('id' => $formdata->userid), '*', MUST_EXIST);
5808                  return $this->save_user_extension($user->id, $formdata->extensionduedate);
5809              }
5810          }
5811  
5812          return false;
5813      }
5814  
5815  
5816      /**
5817       * Save quick grades.
5818       *
5819       * @return string The result of the save operation
5820       */
5821      protected function process_save_quick_grades() {
5822          global $USER, $DB, $CFG;
5823  
5824          // Need grade permission.
5825          require_capability('mod/assign:grade', $this->context);
5826          require_sesskey();
5827  
5828          // Make sure advanced grading is disabled.
5829          $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
5830          $controller = $gradingmanager->get_active_controller();
5831          if (!empty($controller)) {
5832              return get_string('errorquickgradingvsadvancedgrading', 'assign');
5833          }
5834  
5835          $users = array();
5836          // First check all the last modified values.
5837          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
5838          $participants = $this->list_participants($currentgroup, true);
5839  
5840          // Gets a list of possible users and look for values based upon that.
5841          foreach ($participants as $userid => $unused) {
5842              $modified = optional_param('grademodified_' . $userid, -1, PARAM_INT);
5843              $attemptnumber = optional_param('gradeattempt_' . $userid, -1, PARAM_INT);
5844              // Gather the userid, updated grade and last modified value.
5845              $record = new stdClass();
5846              $record->userid = $userid;
5847              if ($modified >= 0) {
5848                  $record->grade = unformat_float(optional_param('quickgrade_' . $record->userid, -1, PARAM_TEXT));
5849                  $record->workflowstate = optional_param('quickgrade_' . $record->userid.'_workflowstate', false, PARAM_TEXT);
5850                  $record->allocatedmarker = optional_param('quickgrade_' . $record->userid.'_allocatedmarker', false, PARAM_INT);
5851              } else {
5852                  // This user was not in the grading table.
5853                  continue;
5854              }
5855              $record->attemptnumber = $attemptnumber;
5856              $record->lastmodified = $modified;
5857              $record->gradinginfo = grade_get_grades($this->get_course()->id,
5858                                                      'mod',
5859                                                      'assign',
5860                                                      $this->get_instance()->id,
5861                                                      array($userid));
5862              $users[$userid] = $record;
5863          }
5864  
5865          if (empty($users)) {
5866              return get_string('nousersselected', 'assign');
5867          }
5868  
5869          list($userids, $params) = $DB->get_in_or_equal(array_keys($users), SQL_PARAMS_NAMED);
5870          $params['assignid1'] = $this->get_instance()->id;
5871          $params['assignid2'] = $this->get_instance()->id;
5872  
5873          // Check them all for currency.
5874          $grademaxattempt = 'SELECT s.userid, s.attemptnumber AS maxattempt
5875                                FROM {assign_submission} s
5876                               WHERE s.assignment = :assignid1 AND s.latest = 1';
5877  
5878          $sql = 'SELECT u.id AS userid, g.grade AS grade, g.timemodified AS lastmodified,
5879                         uf.workflowstate, uf.allocatedmarker, gmx.maxattempt AS attemptnumber
5880                    FROM {user} u
5881               LEFT JOIN ( ' . $grademaxattempt . ' ) gmx ON u.id = gmx.userid
5882               LEFT JOIN {assign_grades} g ON
5883                         u.id = g.userid AND
5884                         g.assignment = :assignid2 AND
5885                         g.attemptnumber = gmx.maxattempt
5886               LEFT JOIN {assign_user_flags} uf ON uf.assignment = g.assignment AND uf.userid = g.userid
5887                   WHERE u.id ' . $userids;
5888          $currentgrades = $DB->get_recordset_sql($sql, $params);
5889  
5890          $modifiedusers = array();
5891          foreach ($currentgrades as $current) {
5892              $modified = $users[(int)$current->userid];
5893              $grade = $this->get_user_grade($modified->userid, false);
5894              // Check to see if the grade column was even visible.
5895              $gradecolpresent = optional_param('quickgrade_' . $modified->userid, false, PARAM_INT) !== false;
5896  
5897              // Check to see if the outcomes were modified.
5898              if ($CFG->enableoutcomes) {
5899                  foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
5900                      $oldoutcome = $outcome->grades[$modified->userid]->grade;
5901                      $paramname = 'outcome_' . $outcomeid . '_' . $modified->userid;
5902                      $newoutcome = optional_param($paramname, -1, PARAM_FLOAT);
5903                      // Check to see if the outcome column was even visible.
5904                      $outcomecolpresent = optional_param($paramname, false, PARAM_FLOAT) !== false;
5905                      if ($outcomecolpresent && ($oldoutcome != $newoutcome)) {
5906                          // Can't check modified time for outcomes because it is not reported.
5907                          $modifiedusers[$modified->userid] = $modified;
5908                          continue;
5909                      }
5910                  }
5911              }
5912  
5913              // Let plugins participate.
5914              foreach ($this->feedbackplugins as $plugin) {
5915                  if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
5916                      // The plugins must handle is_quickgrading_modified correctly - ie
5917                      // handle hidden columns.
5918                      if ($plugin->is_quickgrading_modified($modified->userid, $grade)) {
5919                          if ((int)$current->lastmodified > (int)$modified->lastmodified) {
5920                              return get_string('errorrecordmodified', 'assign');
5921                          } else {
5922                              $modifiedusers[$modified->userid] = $modified;
5923                              continue;
5924                          }
5925                      }
5926                  }
5927              }
5928  
5929              if (($current->grade < 0 || $current->grade === null) &&
5930                  ($modified->grade < 0 || $modified->grade === null)) {
5931                  // Different ways to indicate no grade.
5932                  $modified->grade = $current->grade; // Keep existing grade.
5933              }
5934              // Treat 0 and null as different values.
5935              if ($current->grade !== null) {
5936                  $current->grade = floatval($current->grade);
5937              }
5938              $gradechanged = $gradecolpresent && $current->grade !== $modified->grade;
5939              $markingallocationchanged = $this->get_instance()->markingworkflow &&
5940                                          $this->get_instance()->markingallocation &&
5941                                              ($modified->allocatedmarker !== false) &&
5942                                              ($current->allocatedmarker != $modified->allocatedmarker);
5943              $workflowstatechanged = $this->get_instance()->markingworkflow &&
5944                                              ($modified->workflowstate !== false) &&
5945                                              ($current->workflowstate != $modified->workflowstate);
5946              if ($gradechanged || $markingallocationchanged || $workflowstatechanged) {
5947                  // Grade changed.
5948                  if ($this->grading_disabled($modified->userid)) {
5949                      continue;
5950                  }
5951                  $badmodified = (int)$current->lastmodified > (int)$modified->lastmodified;
5952                  $badattempt = (int)$current->attemptnumber != (int)$modified->attemptnumber;
5953                  if ($badmodified || $badattempt) {
5954                      // Error - record has been modified since viewing the page.
5955                      return get_string('errorrecordmodified', 'assign');
5956                  } else {
5957                      $modifiedusers[$modified->userid] = $modified;
5958                  }
5959              }
5960  
5961          }
5962          $currentgrades->close();
5963  
5964          $adminconfig = $this->get_admin_config();
5965          $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
5966  
5967          // Ok - ready to process the updates.
5968          foreach ($modifiedusers as $userid => $modified) {
5969              $grade = $this->get_user_grade($userid, true);
5970              $flags = $this->get_user_flags($userid, true);
5971              $grade->grade= grade_floatval(unformat_float($modified->grade));
5972              $grade->grader= $USER->id;
5973              $gradecolpresent = optional_param('quickgrade_' . $userid, false, PARAM_INT) !== false;
5974  
5975              // Save plugins data.
5976              foreach ($this->feedbackplugins as $plugin) {
5977                  if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->supports_quickgrading()) {
5978                      $plugin->save_quickgrading_changes($userid, $grade);
5979                      if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
5980                          // This is the feedback plugin chose to push comments to the gradebook.
5981                          $grade->feedbacktext = $plugin->text_for_gradebook($grade);
5982                          $grade->feedbackformat = $plugin->format_for_gradebook($grade);
5983                      }
5984                  }
5985              }
5986  
5987              // These will be set to false if they are not present in the quickgrading
5988              // form (e.g. column hidden).
5989              $workflowstatemodified = ($modified->workflowstate !== false) &&
5990                                          ($flags->workflowstate != $modified->workflowstate);
5991  
5992              $allocatedmarkermodified = ($modified->allocatedmarker !== false) &&
5993                                          ($flags->allocatedmarker != $modified->allocatedmarker);
5994  
5995              if ($workflowstatemodified) {
5996                  $flags->workflowstate = $modified->workflowstate;
5997              }
5998              if ($allocatedmarkermodified) {
5999                  $flags->allocatedmarker = $modified->allocatedmarker;
6000              }
6001              if ($workflowstatemodified || $allocatedmarkermodified) {
6002                  if ($this->update_user_flags($flags) && $workflowstatemodified) {
6003                      $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
6004                      \mod_assign\event\workflow_state_updated::create_from_user($this, $user, $flags->workflowstate)->trigger();
6005                  }
6006              }
6007              $this->update_grade($grade);
6008  
6009              // Allow teachers to skip sending notifications.
6010              if (optional_param('sendstudentnotifications', true, PARAM_BOOL)) {
6011                  $this->notify_grade_modified($grade, true);
6012              }
6013  
6014              // Save outcomes.
6015              if ($CFG->enableoutcomes) {
6016                  $data = array();
6017                  foreach ($modified->gradinginfo->outcomes as $outcomeid => $outcome) {
6018                      $oldoutcome = $outcome->grades[$modified->userid]->grade;
6019                      $paramname = 'outcome_' . $outcomeid . '_' . $modified->userid;
6020                      // This will be false if the input was not in the quickgrading
6021                      // form (e.g. column hidden).
6022                      $newoutcome = optional_param($paramname, false, PARAM_INT);
6023                      if ($newoutcome !== false && ($oldoutcome != $newoutcome)) {
6024                          $data[$outcomeid] = $newoutcome;
6025                      }
6026                  }
6027                  if (count($data) > 0) {
6028                      grade_update_outcomes('mod/assign',
6029                                            $this->course->id,
6030                                            'mod',
6031                                            'assign',
6032                                            $this->get_instance()->id,
6033                                            $userid,
6034                                            $data);
6035                  }
6036              }
6037          }
6038  
6039          return get_string('quickgradingchangessaved', 'assign');
6040      }
6041  
6042      /**
6043       * Reveal student identities to markers (and the gradebook).
6044       *
6045       * @return void
6046       */
6047      public function reveal_identities() {
6048          global $DB;
6049  
6050          require_capability('mod/assign:revealidentities', $this->context);
6051  
6052          if ($this->get_instance()->revealidentities || empty($this->get_instance()->blindmarking)) {
6053              return false;
6054          }
6055  
6056          // Update the assignment record.
6057          $update = new stdClass();
6058          $update->id = $this->get_instance()->id;
6059          $update->revealidentities = 1;
6060          $DB->update_record('assign', $update);
6061  
6062          // Refresh the instance data.
6063          $this->instance = null;
6064  
6065          // Release the grades to the gradebook.
6066          // First create the column in the gradebook.
6067          $this->update_gradebook(false, $this->get_course_module()->id);
6068  
6069          // Now release all grades.
6070  
6071          $adminconfig = $this->get_admin_config();
6072          $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
6073          $gradebookplugin = str_replace('assignfeedback_', '', $gradebookplugin);
6074          $grades = $DB->get_records('assign_grades', array('assignment'=>$this->get_instance()->id));
6075  
6076          $plugin = $this->get_feedback_plugin_by_type($gradebookplugin);
6077  
6078          foreach ($grades as $grade) {
6079              // Fetch any comments for this student.
6080              if ($plugin && $plugin->is_enabled() && $plugin->is_visible()) {
6081                  $grade->feedbacktext = $plugin->text_for_gradebook($grade);
6082                  $grade->feedbackformat = $plugin->format_for_gradebook($grade);
6083              }
6084              $this->gradebook_item_update(null, $grade);
6085          }
6086  
6087          \mod_assign\event\identities_revealed::create_from_assign($this)->trigger();
6088      }
6089  
6090      /**
6091       * Reveal student identities to markers (and the gradebook).
6092       *
6093       * @return void
6094       */
6095      protected function process_reveal_identities() {
6096  
6097          if (!confirm_sesskey()) {
6098              return false;
6099          }
6100  
6101          return $this->reveal_identities();
6102      }
6103  
6104  
6105      /**
6106       * Save grading options.
6107       *
6108       * @return void
6109       */
6110      protected function process_save_grading_options() {
6111          global $USER, $CFG;
6112  
6113          // Include grading options form.
6114          require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
6115  
6116          // Need submit permission to submit an assignment.
6117          $this->require_view_grades();
6118          require_sesskey();
6119  
6120          // Is advanced grading enabled?
6121          $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
6122          $controller = $gradingmanager->get_active_controller();
6123          $showquickgrading = empty($controller);
6124          if (!is_null($this->context)) {
6125              $showonlyactiveenrolopt = has_capability('moodle/course:viewsuspendedusers', $this->context);
6126          } else {
6127              $showonlyactiveenrolopt = false;
6128          }
6129  
6130          $markingallocation = $this->get_instance()->markingworkflow &&
6131              $this->get_instance()->markingallocation &&
6132              has_capability('mod/assign:manageallocations', $this->context);
6133          // Get markers to use in drop lists.
6134          $markingallocationoptions = array();
6135          if ($markingallocation) {
6136              $markingallocationoptions[''] = get_string('filternone', 'assign');
6137              $markingallocationoptions[ASSIGN_MARKER_FILTER_NO_MARKER] = get_string('markerfilternomarker', 'assign');
6138              list($sort, $params) = users_order_by_sql();
6139              $markers = get_users_by_capability($this->context, 'mod/assign:grade', '', $sort);
6140              foreach ($markers as $marker) {
6141                  $markingallocationoptions[$marker->id] = fullname($marker);
6142              }
6143          }
6144  
6145          // Get marking states to show in form.
6146          $markingworkflowoptions = array();
6147          if ($this->get_instance()->markingworkflow) {
6148              $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
6149              $markingworkflowoptions[''] = get_string('filternone', 'assign');
6150              $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = $notmarked;
6151              $markingworkflowoptions = array_merge($markingworkflowoptions, $this->get_marking_workflow_states_for_current_user());
6152          }
6153  
6154          $gradingoptionsparams = array('cm'=>$this->get_course_module()->id,
6155                                        'contextid'=>$this->context->id,
6156                                        'userid'=>$USER->id,
6157                                        'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
6158                                        'showquickgrading'=>$showquickgrading,
6159                                        'quickgrading'=>false,
6160                                        'markingworkflowopt' => $markingworkflowoptions,
6161                                        'markingallocationopt' => $markingallocationoptions,
6162                                        'showonlyactiveenrolopt'=>$showonlyactiveenrolopt,
6163                                        'showonlyactiveenrol'=>$this->show_only_active_users());
6164  
6165          $mform = new mod_assign_grading_options_form(null, $gradingoptionsparams);
6166          if ($formdata = $mform->get_data()) {
6167              set_user_preference('assign_perpage', $formdata->perpage);
6168              if (isset($formdata->filter)) {
6169                  set_user_preference('assign_filter', $formdata->filter);
6170              }
6171              if (isset($formdata->markerfilter)) {
6172                  set_user_preference('assign_markerfilter', $formdata->markerfilter);
6173              }
6174              if (isset($formdata->workflowfilter)) {
6175                  set_user_preference('assign_workflowfilter', $formdata->workflowfilter);
6176              }
6177              if ($showquickgrading) {
6178                  set_user_preference('assign_quickgrading', isset($formdata->quickgrading));
6179              }
6180              if (!empty($showonlyactiveenrolopt)) {
6181                  $showonlyactiveenrol = isset($formdata->showonlyactiveenrol);
6182                  set_user_preference('grade_report_showonlyactiveenrol', $showonlyactiveenrol);
6183                  $this->showonlyactiveenrol = $showonlyactiveenrol;
6184              }
6185          }
6186      }
6187  
6188      /**
6189       * Take a grade object and print a short summary for the log file.
6190       * The size limit for the log file is 255 characters, so be careful not
6191       * to include too much information.
6192       *
6193       * @deprecated since 2.7
6194       *
6195       * @param stdClass $grade
6196       * @return string
6197       */
6198      public function format_grade_for_log(stdClass $grade) {
6199          global $DB;
6200  
6201          $user = $DB->get_record('user', array('id' => $grade->userid), '*', MUST_EXIST);
6202  
6203          $info = get_string('gradestudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
6204          if ($grade->grade != '') {
6205              $info .= get_string('grade') . ': ' . $this->display_grade($grade->grade, false) . '. ';
6206          } else {
6207              $info .= get_string('nograde', 'assign');
6208          }
6209          return $info;
6210      }
6211  
6212      /**
6213       * Take a submission object and print a short summary for the log file.
6214       * The size limit for the log file is 255 characters, so be careful not
6215       * to include too much information.
6216       *
6217       * @deprecated since 2.7
6218       *
6219       * @param stdClass $submission
6220       * @return string
6221       */
6222      public function format_submission_for_log(stdClass $submission) {
6223          global $DB;
6224  
6225          $info = '';
6226          if ($submission->userid) {
6227              $user = $DB->get_record('user', array('id' => $submission->userid), '*', MUST_EXIST);
6228              $name = fullname($user);
6229          } else {
6230              $group = $this->get_submission_group($submission->userid);
6231              if ($group) {
6232                  $name = $group->name;
6233              } else {
6234                  $name = get_string('defaultteam', 'assign');
6235              }
6236          }
6237          $status = get_string('submissionstatus_' . $submission->status, 'assign');
6238          $params = array('id'=>$submission->userid, 'fullname'=>$name, 'status'=>$status);
6239          $info .= get_string('submissionlog', 'assign', $params) . ' <br>';
6240  
6241          foreach ($this->submissionplugins as $plugin) {
6242              if ($plugin->is_enabled() && $plugin->is_visible()) {
6243                  $info .= '<br>' . $plugin->format_for_log($submission);
6244              }
6245          }
6246  
6247          return $info;
6248      }
6249  
6250      /**
6251       * Require a valid sess key and then call copy_previous_attempt.
6252       *
6253       * @param  array $notices Any error messages that should be shown
6254       *                        to the user at the top of the edit submission form.
6255       * @return bool
6256       */
6257      protected function process_copy_previous_attempt(&$notices) {
6258          require_sesskey();
6259  
6260          return $this->copy_previous_attempt($notices);
6261      }
6262  
6263      /**
6264       * Copy the current assignment submission from the last submitted attempt.
6265       *
6266       * @param  array $notices Any error messages that should be shown
6267       *                        to the user at the top of the edit submission form.
6268       * @return bool
6269       */
6270      public function copy_previous_attempt(&$notices) {
6271          global $USER, $CFG;
6272  
6273          require_capability('mod/assign:submit', $this->context);
6274  
6275          $instance = $this->get_instance();
6276          if ($instance->teamsubmission) {
6277              $submission = $this->get_group_submission($USER->id, 0, true);
6278          } else {
6279              $submission = $this->get_user_submission($USER->id, true);
6280          }
6281          if (!$submission || $submission->status != ASSIGN_SUBMISSION_STATUS_REOPENED) {
6282              $notices[] = get_string('submissionnotcopiedinvalidstatus', 'assign');
6283              return false;
6284          }
6285          $flags = $this->get_user_flags($USER->id, false);
6286  
6287          // Get the flags to check if it is locked.
6288          if ($flags && $flags->locked) {
6289              $notices[] = get_string('submissionslocked', 'assign');
6290              return false;
6291          }
6292          if ($instance->submissiondrafts) {
6293              $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
6294          } else {
6295              $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
6296          }
6297          $this->update_submission($submission, $USER->id, true, $instance->teamsubmission);
6298  
6299          // Find the previous submission.
6300          if ($instance->teamsubmission) {
6301              $previoussubmission = $this->get_group_submission($USER->id, 0, true, $submission->attemptnumber - 1);
6302          } else {
6303              $previoussubmission = $this->get_user_submission($USER->id, true, $submission->attemptnumber - 1);
6304          }
6305  
6306          if (!$previoussubmission) {
6307              // There was no previous submission so there is nothing else to do.
6308              return true;
6309          }
6310  
6311          $pluginerror = false;
6312          foreach ($this->get_submission_plugins() as $plugin) {
6313              if ($plugin->is_visible() && $plugin->is_enabled()) {
6314                  if (!$plugin->copy_submission($previoussubmission, $submission)) {
6315                      $notices[] = $plugin->get_error();
6316                      $pluginerror = true;
6317                  }
6318              }
6319          }
6320          if ($pluginerror) {
6321              return false;
6322          }
6323  
6324          \mod_assign\event\submission_duplicated::create_from_submission($this, $submission)->trigger();
6325  
6326          $complete = COMPLETION_INCOMPLETE;
6327          if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
6328              $complete = COMPLETION_COMPLETE;
6329          }
6330          $completion = new completion_info($this->get_course());
6331          if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
6332              $this->update_activity_completion_records($instance->teamsubmission,
6333                                                        $instance->requireallteammemberssubmit,
6334                                                        $submission,
6335                                                        $USER->id,
6336                                                        $complete,
6337                                                        $completion);
6338          }
6339  
6340          if (!$instance->submissiondrafts) {
6341              // There is a case for not notifying the student about the submission copy,
6342              // but it provides a record of the event and if they then cancel editing it
6343              // is clear that the submission was copied.
6344              $this->notify_student_submission_copied($submission);
6345              $this->notify_graders($submission);
6346  
6347              // The same logic applies here - we could not notify teachers,
6348              // but then they would wonder why there are submitted assignments
6349              // and they haven't been notified.
6350              \mod_assign\event\assessable_submitted::create_from_submission($this, $submission, true)->trigger();
6351          }
6352          return true;
6353      }
6354  
6355      /**
6356       * Determine if the current submission is empty or not.
6357       *
6358       * @param submission $submission the students submission record to check.
6359       * @return bool
6360       */
6361      public function submission_empty($submission) {
6362          $allempty = true;
6363  
6364          foreach ($this->submissionplugins as $plugin) {
6365              if ($plugin->is_enabled() && $plugin->is_visible()) {
6366                  if (!$allempty || !$plugin->is_empty($submission)) {
6367                      $allempty = false;
6368                  }
6369              }
6370          }
6371          return $allempty;
6372      }
6373  
6374      /**
6375       * Determine if a new submission is empty or not
6376       *
6377       * @param stdClass $data Submission data
6378       * @return bool
6379       */
6380      public function new_submission_empty($data) {
6381          foreach ($this->submissionplugins as $plugin) {
6382              if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions() &&
6383                      !$plugin->submission_is_empty($data)) {
6384                  return false;
6385              }
6386          }
6387          return true;
6388      }
6389  
6390      /**
6391       * Save assignment submission for the current user.
6392       *
6393       * @param  stdClass $data
6394       * @param  array $notices Any error messages that should be shown
6395       *                        to the user.
6396       * @return bool
6397       */
6398      public function save_submission(stdClass $data, & $notices) {
6399          global $CFG, $USER, $DB;
6400  
6401          $userid = $USER->id;
6402          if (!empty($data->userid)) {
6403              $userid = $data->userid;
6404          }
6405  
6406          $user = clone($USER);
6407          if ($userid == $USER->id) {
6408              require_capability('mod/assign:submit', $this->context);
6409          } else {
6410              $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
6411              if (!$this->can_edit_submission($userid, $USER->id)) {
6412                  print_error('nopermission');
6413              }
6414          }
6415          $instance = $this->get_instance();
6416  
6417          if ($instance->teamsubmission) {
6418              $submission = $this->get_group_submission($userid, 0, true);
6419          } else {
6420              $submission = $this->get_user_submission($userid, true);
6421          }
6422  
6423          if ($this->new_submission_empty($data)) {
6424              $notices[] = get_string('submissionempty', 'mod_assign');
6425              return false;
6426          }
6427  
6428          // Check that no one has modified the submission since we started looking at it.
6429          if (isset($data->lastmodified) && ($submission->timemodified > $data->lastmodified)) {
6430              // Another user has submitted something. Notify the current user.
6431              if ($submission->status !== ASSIGN_SUBMISSION_STATUS_NEW) {
6432                  $notices[] = $instance->teamsubmission ? get_string('submissionmodifiedgroup', 'mod_assign')
6433                                                         : get_string('submissionmodified', 'mod_assign');
6434                  return false;
6435              }
6436          }
6437  
6438          if ($instance->submissiondrafts) {
6439              $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
6440          } else {
6441              $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
6442          }
6443  
6444          $flags = $this->get_user_flags($userid, false);
6445  
6446          // Get the flags to check if it is locked.
6447          if ($flags && $flags->locked) {
6448              print_error('submissionslocked', 'assign');
6449              return true;
6450          }
6451  
6452          $pluginerror = false;
6453          foreach ($this->submissionplugins as $plugin) {
6454              if ($plugin->is_enabled() && $plugin->is_visible()) {
6455                  if (!$plugin->save($submission, $data)) {
6456                      $notices[] = $plugin->get_error();
6457                      $pluginerror = true;
6458                  }
6459              }
6460          }
6461  
6462          $allempty = $this->submission_empty($submission);
6463          if ($pluginerror || $allempty) {
6464              if ($allempty) {
6465                  $notices[] = get_string('submissionempty', 'mod_assign');
6466              }
6467              return false;
6468          }
6469  
6470          $this->update_submission($submission, $userid, true, $instance->teamsubmission);
6471  
6472          if ($instance->teamsubmission && !$instance->requireallteammemberssubmit) {
6473              $team = $this->get_submission_group_members($submission->groupid, true);
6474  
6475              foreach ($team as $member) {
6476                  if ($member->id != $userid) {
6477                      $membersubmission = clone($submission);
6478                      $this->update_submission($membersubmission, $member->id, true, $instance->teamsubmission);
6479                  }
6480              }
6481          }
6482  
6483          // Logging.
6484          if (isset($data->submissionstatement) && ($userid == $USER->id)) {
6485              \mod_assign\event\statement_accepted::create_from_submission($this, $submission)->trigger();
6486          }
6487  
6488          $complete = COMPLETION_INCOMPLETE;
6489          if ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
6490              $complete = COMPLETION_COMPLETE;
6491          }
6492          $completion = new completion_info($this->get_course());
6493          if ($completion->is_enabled($this->get_course_module()) && $instance->completionsubmit) {
6494              $completion->update_state($this->get_course_module(), $complete, $userid);
6495          }
6496  
6497          if (!$instance->submissiondrafts) {
6498              $this->notify_student_submission_receipt($submission);
6499              $this->notify_graders($submission);
6500              \mod_assign\event\assessable_submitted::create_from_submission($this, $submission, true)->trigger();
6501          }
6502          return true;
6503      }
6504  
6505      /**
6506       * Save assignment submission.
6507       *
6508       * @param  moodleform $mform
6509       * @param  array $notices Any error messages that should be shown
6510       *                        to the user at the top of the edit submission form.
6511       * @return bool
6512       */
6513      protected function process_save_submission(&$mform, &$notices) {
6514          global $CFG, $USER;
6515  
6516          // Include submission form.
6517          require_once($CFG->dirroot . '/mod/assign/submission_form.php');
6518  
6519          $userid = optional_param('userid', $USER->id, PARAM_INT);
6520          // Need submit permission to submit an assignment.
6521          require_sesskey();
6522          if (!$this->submissions_open($userid)) {
6523              $notices[] = get_string('duedatereached', 'assign');
6524              return false;
6525          }
6526          $instance = $this->get_instance();
6527  
6528          $data = new stdClass();
6529          $data->userid = $userid;
6530          $mform = new mod_assign_submission_form(null, array($this, $data));
6531          if ($mform->is_cancelled()) {
6532              return true;
6533          }
6534          if ($data = $mform->get_data()) {
6535              return $this->save_submission($data, $notices);
6536          }
6537          return false;
6538      }
6539  
6540  
6541      /**
6542       * Determine if this users grade can be edited.
6543       *
6544       * @param int $userid - The student userid
6545       * @param bool $checkworkflow - whether to include a check for the workflow state.
6546       * @return bool $gradingdisabled
6547       */
6548      public function grading_disabled($userid, $checkworkflow=true) {
6549          global $CFG;
6550          if ($checkworkflow && $this->get_instance()->markingworkflow) {
6551              $grade = $this->get_user_grade($userid, false);
6552              $validstates = $this->get_marking_workflow_states_for_current_user();
6553              if (!empty($grade) && !empty($grade->workflowstate) && !array_key_exists($grade->workflowstate, $validstates)) {
6554                  return true;
6555              }
6556          }
6557          $gradinginfo = grade_get_grades($this->get_course()->id,
6558                                          'mod',
6559                                          'assign',
6560                                          $this->get_instance()->id,
6561                                          array($userid));
6562          if (!$gradinginfo) {
6563              return false;
6564          }
6565  
6566          if (!isset($gradinginfo->items[0]->grades[$userid])) {
6567              return false;
6568          }
6569          $gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked ||
6570                             $gradinginfo->items[0]->grades[$userid]->overridden;
6571          return $gradingdisabled;
6572      }
6573  
6574  
6575      /**
6576       * Get an instance of a grading form if advanced grading is enabled.
6577       * This is specific to the assignment, marker and student.
6578       *
6579       * @param int $userid - The student userid
6580       * @param stdClass|false $grade - The grade record
6581       * @param bool $gradingdisabled
6582       * @return mixed gradingform_instance|null $gradinginstance
6583       */
6584      protected function get_grading_instance($userid, $grade, $gradingdisabled) {
6585          global $CFG, $USER;
6586  
6587          $grademenu = make_grades_menu($this->get_instance()->grade);
6588          $allowgradedecimals = $this->get_instance()->grade > 0;
6589  
6590          $advancedgradingwarning = false;
6591          $gradingmanager = get_grading_manager($this->context, 'mod_assign', 'submissions');
6592          $gradinginstance = null;
6593          if ($gradingmethod = $gradingmanager->get_active_method()) {
6594              $controller = $gradingmanager->get_controller($gradingmethod);
6595              if ($controller->is_form_available()) {
6596                  $itemid = null;
6597                  if ($grade) {
6598                      $itemid = $grade->id;
6599                  }
6600                  if ($gradingdisabled && $itemid) {
6601                      $gradinginstance = $controller->get_current_instance($USER->id, $itemid);
6602                  } else if (!$gradingdisabled) {
6603                      $instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
6604                      $gradinginstance = $controller->get_or_create_instance($instanceid,
6605                                                                             $USER->id,
6606                                                                             $itemid);
6607                  }
6608              } else {
6609                  $advancedgradingwarning = $controller->form_unavailable_notification();
6610              }
6611          }
6612          if ($gradinginstance) {
6613              $gradinginstance->get_controller()->set_grade_range($grademenu, $allowgradedecimals);
6614          }
6615          return $gradinginstance;
6616      }
6617  
6618      /**
6619       * Add elements to grade form.
6620       *
6621       * @param MoodleQuickForm $mform
6622       * @param stdClass $data
6623       * @param array $params
6624       * @return void
6625       */
6626      public function add_grade_form_elements(MoodleQuickForm $mform, stdClass $data, $params) {
6627          global $USER, $CFG, $SESSION;
6628          $settings = $this->get_instance();
6629  
6630          $rownum = isset($params['rownum']) ? $params['rownum'] : 0;
6631          $last = isset($params['last']) ? $params['last'] : true;
6632          $useridlistid = isset($params['useridlistid']) ? $params['useridlistid'] : 0;
6633          $userid = isset($params['userid']) ? $params['userid'] : 0;
6634          $attemptnumber = isset($params['attemptnumber']) ? $params['attemptnumber'] : 0;
6635          $gradingpanel = !empty($params['gradingpanel']);
6636          $bothids = ($userid && $useridlistid);
6637  
6638          if (!$userid || $bothids) {
6639              $useridlistkey = $this->get_useridlist_key($useridlistid);
6640              if (empty($SESSION->mod_assign_useridlist[$useridlistkey])) {
6641                  $SESSION->mod_assign_useridlist[$useridlistkey] = $this->get_grading_userid_list();
6642              }
6643              $useridlist = $SESSION->mod_assign_useridlist[$useridlistkey];
6644          } else {
6645              $useridlist = array($userid);
6646              $rownum = 0;
6647              $useridlistid = '';
6648          }
6649  
6650          $userid = $useridlist[$rownum];
6651          $grade = $this->get_user_grade($userid, false, $attemptnumber);
6652  
6653          $submission = null;
6654          if ($this->get_instance()->teamsubmission) {
6655              $submission = $this->get_group_submission($userid, 0, false, $attemptnumber);
6656          } else {
6657              $submission = $this->get_user_submission($userid, false, $attemptnumber);
6658          }
6659  
6660          // Add advanced grading.
6661          $gradingdisabled = $this->grading_disabled($userid);
6662          $gradinginstance = $this->get_grading_instance($userid, $grade, $gradingdisabled);
6663  
6664          $mform->addElement('header', 'gradeheader', get_string('grade'));
6665          if ($gradinginstance) {
6666              $gradingelement = $mform->addElement('grading',
6667                                                   'advancedgrading',
6668                                                   get_string('grade').':',
6669                                                   array('gradinginstance' => $gradinginstance));
6670              if ($gradingdisabled) {
6671                  $gradingelement->freeze();
6672              } else {
6673                  $mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
6674                  $mform->setType('advancedgradinginstanceid', PARAM_INT);
6675              }
6676          } else {
6677              // Use simple direct grading.
6678              if ($this->get_instance()->grade > 0) {
6679                  $name = get_string('gradeoutof', 'assign', $this->get_instance()->grade);
6680                  if (!$gradingdisabled) {
6681                      $gradingelement = $mform->addElement('text', 'grade', $name);
6682                      $mform->addHelpButton('grade', 'gradeoutofhelp', 'assign');
6683                      $mform->setType('grade', PARAM_RAW);
6684                  } else {
6685                      $mform->addElement('hidden', 'grade', $name);
6686                      $mform->hardFreeze('grade');
6687                      $mform->setType('grade', PARAM_RAW);
6688                      $strgradelocked = get_string('gradelocked', 'assign');
6689                      $mform->addElement('static', 'gradedisabled', $name, $strgradelocked);
6690                      $mform->addHelpButton('gradedisabled', 'gradeoutofhelp', 'assign');
6691                  }
6692              } else {
6693                  $grademenu = array(-1 => get_string("nograde")) + make_grades_menu($this->get_instance()->grade);
6694                  if (count($grademenu) > 1) {
6695                      $gradingelement = $mform->addElement('select', 'grade', get_string('grade') . ':', $grademenu);
6696  
6697                      // The grade is already formatted with format_float so it needs to be converted back to an integer.
6698                      if (!empty($data->grade)) {
6699                          $data->grade = (int)unformat_float($data->grade);
6700                      }
6701                      $mform->setType('grade', PARAM_INT);
6702                      if ($gradingdisabled) {
6703                          $gradingelement->freeze();
6704                      }
6705                  }
6706              }
6707          }
6708  
6709          $gradinginfo = grade_get_grades($this->get_course()->id,
6710                                          'mod',
6711                                          'assign',
6712                                          $this->get_instance()->id,
6713                                          $userid);
6714          if (!empty($CFG->enableoutcomes)) {
6715              foreach ($gradinginfo->outcomes as $index => $outcome) {
6716                  $options = make_grades_menu(-$outcome->scaleid);
6717                  if ($outcome->grades[$userid]->locked) {
6718                      $options[0] = get_string('nooutcome', 'grades');
6719                      $mform->addElement('static',
6720                                         'outcome_' . $index . '[' . $userid . ']',
6721                                         $outcome->name . ':',
6722                                         $options[$outcome->grades[$userid]->grade]);
6723                  } else {
6724                      $options[''] = get_string('nooutcome', 'grades');
6725                      $attributes = array('id' => 'menuoutcome_' . $index );
6726                      $mform->addElement('select',
6727                                         'outcome_' . $index . '[' . $userid . ']',
6728                                         $outcome->name.':',
6729                                         $options,
6730                                         $attributes);
6731                      $mform->setType('outcome_' . $index . '[' . $userid . ']', PARAM_INT);
6732                      $mform->setDefault('outcome_' . $index . '[' . $userid . ']',
6733                                         $outcome->grades[$userid]->grade);
6734                  }
6735              }
6736          }
6737  
6738          $capabilitylist = array('gradereport/grader:view', 'moodle/grade:viewall');
6739          if (has_all_capabilities($capabilitylist, $this->get_course_context())) {
6740              $urlparams = array('id'=>$this->get_course()->id);
6741              $url = new moodle_url('/grade/report/grader/index.php', $urlparams);
6742              $usergrade = '-';
6743              if (isset($gradinginfo->items[0]->grades[$userid]->str_grade)) {
6744                  $usergrade = $gradinginfo->items[0]->grades[$userid]->str_grade;
6745              }
6746              $gradestring = $this->get_renderer()->action_link($url, $usergrade);
6747          } else {
6748              $usergrade = '-';
6749              if (isset($gradinginfo->items[0]->grades[$userid]) &&
6750                      !$gradinginfo->items[0]->grades[$userid]->hidden) {
6751                  $usergrade = $gradinginfo->items[0]->grades[$userid]->str_grade;
6752              }
6753              $gradestring = $usergrade;
6754          }
6755  
6756          if ($this->get_instance()->markingworkflow) {
6757              $states = $this->get_marking_workflow_states_for_current_user();
6758              $options = array('' => get_string('markingworkflowstatenotmarked', 'assign')) + $states;
6759              $mform->addElement('select', 'workflowstate', get_string('markingworkflowstate', 'assign'), $options);
6760              $mform->addHelpButton('workflowstate', 'markingworkflowstate', 'assign');
6761          }
6762  
6763          if ($this->get_instance()->markingworkflow &&
6764              $this->get_instance()->markingallocation &&
6765              has_capability('mod/assign:manageallocations', $this->context)) {
6766  
6767              list($sort, $params) = users_order_by_sql();
6768              $markers = get_users_by_capability($this->context, 'mod/assign:grade', '', $sort);
6769              $markerlist = array('' =>  get_string('choosemarker', 'assign'));
6770              foreach ($markers as $marker) {
6771                  $markerlist[$marker->id] = fullname($marker);
6772              }
6773              $mform->addElement('select', 'allocatedmarker', get_string('allocatedmarker', 'assign'), $markerlist);
6774              $mform->addHelpButton('allocatedmarker', 'allocatedmarker', 'assign');
6775              $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW);
6776              $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW);
6777              $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE);
6778              $mform->disabledIf('allocatedmarker', 'workflowstate', 'eq', ASSIGN_MARKING_WORKFLOW_STATE_RELEASED);
6779          }
6780          $gradestring = '<span class="currentgrade">' . $gradestring . '</span>';
6781          $mform->addElement('static', 'currentgrade', get_string('currentgrade', 'assign'), $gradestring);
6782  
6783          if (count($useridlist) > 1) {
6784              $strparams = array('current'=>$rownum+1, 'total'=>count($useridlist));
6785              $name = get_string('outof', 'assign', $strparams);
6786              $mform->addElement('static', 'gradingstudent', get_string('gradingstudent', 'assign'), $name);
6787          }
6788  
6789          // Let feedback plugins add elements to the grading form.
6790          $this->add_plugin_grade_elements($grade, $mform, $data, $userid);
6791  
6792          // Hidden params.
6793          $mform->addElement('hidden', 'id', $this->get_course_module()->id);
6794          $mform->setType('id', PARAM_INT);
6795          $mform->addElement('hidden', 'rownum', $rownum);
6796          $mform->setType('rownum', PARAM_INT);
6797          $mform->setConstant('rownum', $rownum);
6798          $mform->addElement('hidden', 'useridlistid', $useridlistid);
6799          $mform->setType('useridlistid', PARAM_ALPHANUM);
6800          $mform->addElement('hidden', 'attemptnumber', $attemptnumber);
6801          $mform->setType('attemptnumber', PARAM_INT);
6802          $mform->addElement('hidden', 'ajax', optional_param('ajax', 0, PARAM_INT));
6803          $mform->setType('ajax', PARAM_INT);
6804          $mform->addElement('hidden', 'userid', optional_param('userid', 0, PARAM_INT));
6805          $mform->setType('userid', PARAM_INT);
6806  
6807          if ($this->get_instance()->teamsubmission) {
6808              $mform->addElement('header', 'groupsubmissionsettings', get_string('groupsubmissionsettings', 'assign'));
6809              $mform->addElement('selectyesno', 'applytoall', get_string('applytoteam', 'assign'));
6810              $mform->setDefault('applytoall', 1);
6811          }
6812  
6813          // Do not show if we are editing a previous attempt.
6814          if ($attemptnumber == -1 && $this->get_instance()->attemptreopenmethod != ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
6815              $mform->addElement('header', 'attemptsettings', get_string('attemptsettings', 'assign'));
6816              $attemptreopenmethod = get_string('attemptreopenmethod_' . $this->get_instance()->attemptreopenmethod, 'assign');
6817              $mform->addElement('static', 'attemptreopenmethod', get_string('attemptreopenmethod', 'assign'), $attemptreopenmethod);
6818  
6819              $attemptnumber = 0;
6820              if ($submission) {
6821                  $attemptnumber = $submission->attemptnumber;
6822              }
6823              $maxattempts = $this->get_instance()->maxattempts;
6824              if ($maxattempts == ASSIGN_UNLIMITED_ATTEMPTS) {
6825                  $maxattempts = get_string('unlimitedattempts', 'assign');
6826              }
6827              $mform->addelement('static', 'maxattemptslabel', get_string('maxattempts', 'assign'), $maxattempts);
6828              $mform->addelement('static', 'attemptnumberlabel', get_string('attemptnumber', 'assign'), $attemptnumber + 1);
6829  
6830              $ismanual = $this->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL;
6831              $issubmission = !empty($submission);
6832              $isunlimited = $this->get_instance()->maxattempts == ASSIGN_UNLIMITED_ATTEMPTS;
6833              $islessthanmaxattempts = $issubmission && ($submission->attemptnumber < ($this->get_instance()->maxattempts-1));
6834  
6835              if ($ismanual && (!$issubmission || $isunlimited || $islessthanmaxattempts)) {
6836                  $mform->addElement('selectyesno', 'addattempt', get_string('addattempt', 'assign'));
6837                  $mform->setDefault('addattempt', 0);
6838              }
6839          }
6840          if (!$gradingpanel) {
6841              $mform->addElement('selectyesno', 'sendstudentnotifications', get_string('sendstudentnotifications', 'assign'));
6842          } else {
6843              $mform->addElement('hidden', 'sendstudentnotifications', get_string('sendstudentnotifications', 'assign'));
6844              $mform->setType('sendstudentnotifications', PARAM_BOOL);
6845          }
6846          // Get assignment visibility information for student.
6847          $modinfo = get_fast_modinfo($settings->course, $userid);
6848          $cm = $modinfo->get_cm($this->get_course_module()->id);
6849  
6850          // Don't allow notification to be sent if the student can't access the assignment,
6851          // or until in "Released" state if using marking workflow.
6852          if (!$cm->uservisible) {
6853              $mform->setDefault('sendstudentnotifications', 0);
6854              $mform->freeze('sendstudentnotifications');
6855          } else if ($this->get_instance()->markingworkflow) {
6856              $mform->setDefault('sendstudentnotifications', 0);
6857              $mform->disabledIf('sendstudentnotifications', 'workflowstate', 'neq', ASSIGN_MARKING_WORKFLOW_STATE_RELEASED);
6858          } else {
6859              $mform->setDefault('sendstudentnotifications', $this->get_instance()->sendstudentnotifications);
6860          }
6861  
6862          $mform->addElement('hidden', 'action', 'submitgrade');
6863          $mform->setType('action', PARAM_ALPHA);
6864  
6865          if (!$gradingpanel) {
6866  
6867              $buttonarray = array();
6868              $name = get_string('savechanges', 'assign');
6869              $buttonarray[] = $mform->createElement('submit', 'savegrade', $name);
6870              if (!$last) {
6871                  $name = get_string('savenext', 'assign');
6872                  $buttonarray[] = $mform->createElement('submit', 'saveandshownext', $name);
6873              }
6874              $buttonarray[] = $mform->createElement('cancel', 'cancelbutton', get_string('cancel'));
6875              $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
6876              $mform->closeHeaderBefore('buttonar');
6877              $buttonarray = array();
6878  
6879              if ($rownum > 0) {
6880                  $name = get_string('previous', 'assign');
6881                  $buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', $name);
6882              }
6883  
6884              if (!$last) {
6885                  $name = get_string('nosavebutnext', 'assign');
6886                  $buttonarray[] = $mform->createElement('submit', 'nosaveandnext', $name);
6887              }
6888              if (!empty($buttonarray)) {
6889                  $mform->addGroup($buttonarray, 'navar', '', array(' '), false);
6890              }
6891          }
6892          // The grading form does not work well with shortforms.
6893          $mform->setDisableShortforms();
6894      }
6895  
6896      /**
6897       * Add elements in submission plugin form.
6898       *
6899       * @param mixed $submission stdClass|null
6900       * @param MoodleQuickForm $mform
6901       * @param stdClass $data
6902       * @param int $userid The current userid (same as $USER->id)
6903       * @return void
6904       */
6905      protected function add_plugin_submission_elements($submission,
6906                                                      MoodleQuickForm $mform,
6907                                                      stdClass $data,
6908                                                      $userid) {
6909          foreach ($this->submissionplugins as $plugin) {
6910              if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions()) {
6911                  $plugin->get_form_elements_for_user($submission, $mform, $data, $userid);
6912              }
6913          }
6914      }
6915  
6916      /**
6917       * Check if feedback plugins installed are enabled.
6918       *
6919       * @return bool
6920       */
6921      public function is_any_feedback_plugin_enabled() {
6922          if (!isset($this->cache['any_feedback_plugin_enabled'])) {
6923              $this->cache['any_feedback_plugin_enabled'] = false;
6924              foreach ($this->feedbackplugins as $plugin) {
6925                  if ($plugin->is_enabled() && $plugin->is_visible()) {
6926                      $this->cache['any_feedback_plugin_enabled'] = true;
6927                      break;
6928                  }
6929              }
6930          }
6931  
6932          return $this->cache['any_feedback_plugin_enabled'];
6933  
6934      }
6935  
6936      /**
6937       * Check if submission plugins installed are enabled.
6938       *
6939       * @return bool
6940       */
6941      public function is_any_submission_plugin_enabled() {
6942          if (!isset($this->cache['any_submission_plugin_enabled'])) {
6943              $this->cache['any_submission_plugin_enabled'] = false;
6944              foreach ($this->submissionplugins as $plugin) {
6945                  if ($plugin->is_enabled() && $plugin->is_visible() && $plugin->allow_submissions()) {
6946                      $this->cache['any_submission_plugin_enabled'] = true;
6947                      break;
6948                  }
6949              }
6950          }
6951  
6952          return $this->cache['any_submission_plugin_enabled'];
6953  
6954      }
6955  
6956      /**
6957       * Add elements to submission form.
6958       * @param MoodleQuickForm $mform
6959       * @param stdClass $data
6960       * @return void
6961       */
6962      public function add_submission_form_elements(MoodleQuickForm $mform, stdClass $data) {
6963          global $USER;
6964  
6965          $userid = $data->userid;
6966          // Team submissions.
6967          if ($this->get_instance()->teamsubmission) {
6968              $submission = $this->get_group_submission($userid, 0, false);
6969          } else {
6970              $submission = $this->get_user_submission($userid, false);
6971          }
6972  
6973          // Submission statement.
6974          $adminconfig = $this->get_admin_config();
6975  
6976          $requiresubmissionstatement = $this->get_instance()->requiresubmissionstatement &&
6977                                         !empty($adminconfig->submissionstatement);
6978  
6979          $draftsenabled = $this->get_instance()->submissiondrafts;
6980  
6981          // Only show submission statement if we are editing our own submission.
6982          if ($requiresubmissionstatement && !$draftsenabled && $userid == $USER->id) {
6983  
6984              $submissionstatement = '';
6985              if (!empty($adminconfig->submissionstatement)) {
6986                  // Format the submission statement before its sent. We turn off para because this is going within
6987                  // a form element.
6988                  $options = array(
6989                      'context' => $this->get_context(),
6990                      'para' => false
6991                  );
6992                  $submissionstatement = format_text($adminconfig->submissionstatement, FORMAT_MOODLE, $options);
6993              }
6994              $mform->addElement('checkbox', 'submissionstatement', '', $submissionstatement);
6995              $mform->addRule('submissionstatement', get_string('required'), 'required', null, 'client');
6996          }
6997  
6998          $this->add_plugin_submission_elements($submission, $mform, $data, $userid);
6999  
7000          // Hidden params.
7001          $mform->addElement('hidden', 'id', $this->get_course_module()->id);
7002          $mform->setType('id', PARAM_INT);
7003  
7004          $mform->addElement('hidden', 'userid', $userid);
7005          $mform->setType('userid', PARAM_INT);
7006  
7007          $mform->addElement('hidden', 'action', 'savesubmission');
7008          $mform->setType('action', PARAM_TEXT);
7009      }
7010  
7011      /**
7012       * Revert to draft.
7013       *
7014       * @param int $userid
7015       * @return boolean
7016       */
7017      public function revert_to_draft($userid) {
7018          global $DB, $USER;
7019  
7020          // Need grade permission.
7021          require_capability('mod/assign:grade', $this->context);
7022  
7023          if ($this->get_instance()->teamsubmission) {
7024              $submission = $this->get_group_submission($userid, 0, false);
7025          } else {
7026              $submission = $this->get_user_submission($userid, false);
7027          }
7028  
7029          if (!$submission) {
7030              return false;
7031          }
7032          $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
7033          $this->update_submission($submission, $userid, true, $this->get_instance()->teamsubmission);
7034  
7035          // Give each submission plugin a chance to process the reverting to draft.
7036          $plugins = $this->get_submission_plugins();
7037          foreach ($plugins as $plugin) {
7038              if ($plugin->is_enabled() && $plugin->is_visible()) {
7039                  $plugin->revert_to_draft($submission);
7040              }
7041          }
7042          // Update the modified time on the grade (grader modified).
7043          $grade = $this->get_user_grade($userid, true);
7044          $grade->grader = $USER->id;
7045          $this->update_grade($grade);
7046  
7047          $completion = new completion_info($this->get_course());
7048          if ($completion->is_enabled($this->get_course_module()) &&
7049                  $this->get_instance()->completionsubmit) {
7050              $completion->update_state($this->get_course_module(), COMPLETION_INCOMPLETE, $userid);
7051          }
7052          \mod_assign\event\submission_status_updated::create_from_submission($this, $submission)->trigger();
7053          return true;
7054      }
7055  
7056      /**
7057       * Revert to draft.
7058       * Uses url parameter userid if userid not supplied as a parameter.
7059       *
7060       * @param int $userid
7061       * @return boolean
7062       */
7063      protected function process_revert_to_draft($userid = 0) {
7064          require_sesskey();
7065  
7066          if (!$userid) {
7067              $userid = required_param('userid', PARAM_INT);
7068          }
7069  
7070          return $this->revert_to_draft($userid);
7071      }
7072  
7073      /**
7074       * Prevent student updates to this submission
7075       *
7076       * @param int $userid
7077       * @return bool
7078       */
7079      public function lock_submission($userid) {
7080          global $USER, $DB;
7081          // Need grade permission.
7082          require_capability('mod/assign:grade', $this->context);
7083  
7084          // Give each submission plugin a chance to process the locking.
7085          $plugins = $this->get_submission_plugins();
7086          $submission = $this->get_user_submission($userid, false);
7087  
7088          $flags = $this->get_user_flags($userid, true);
7089          $flags->locked = 1;
7090          $this->update_user_flags($flags);
7091  
7092          foreach ($plugins as $plugin) {
7093              if ($plugin->is_enabled() && $plugin->is_visible()) {
7094                  $plugin->lock($submission, $flags);
7095              }
7096          }
7097  
7098          $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
7099          \mod_assign\event\submission_locked::create_from_user($this, $user)->trigger();
7100          return true;
7101      }
7102  
7103  
7104      /**
7105       * Set the workflow state for multiple users
7106       *
7107       * @return void
7108       */
7109      protected function process_set_batch_marking_workflow_state() {
7110          global $CFG, $DB;
7111  
7112          // Include batch marking workflow form.
7113          require_once($CFG->dirroot . '/mod/assign/batchsetmarkingworkflowstateform.php');
7114  
7115          $formparams = array(
7116              'userscount' => 0,  // This form is never re-displayed, so we don't need to
7117              'usershtml' => '',  // initialise these parameters with real information.
7118              'markingworkflowstates' => $this->get_marking_workflow_states_for_current_user()
7119          );
7120  
7121          $mform = new mod_assign_batch_set_marking_workflow_state_form(null, $formparams);
7122  
7123          if ($mform->is_cancelled()) {
7124              return true;
7125          }
7126  
7127          if ($formdata = $mform->get_data()) {
7128              $useridlist = explode(',', $formdata->selectedusers);
7129              $state = $formdata->markingworkflowstate;
7130  
7131              foreach ($useridlist as $userid) {
7132                  $flags = $this->get_user_flags($userid, true);
7133  
7134                  $flags->workflowstate = $state;
7135  
7136                  // Clear the mailed flag if notification is requested, the student hasn't been
7137                  // notified previously, the student can access the assignment, and the state
7138                  // is "Released".
7139                  $modinfo = get_fast_modinfo($this->course, $userid);
7140                  $cm = $modinfo->get_cm($this->get_course_module()->id);
7141                  if ($formdata->sendstudentnotifications && $cm->uservisible &&
7142                          $state == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
7143                      $flags->mailed = 0;
7144                  }
7145  
7146                  $gradingdisabled = $this->grading_disabled($userid);
7147  
7148                  // Will not apply update if user does not have permission to assign this workflow state.
7149                  if (!$gradingdisabled && $this->update_user_flags($flags)) {
7150                      if ($state == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
7151                          // Update Gradebook.
7152                          $assign = clone $this->get_instance();
7153                          $assign->cmidnumber = $this->get_course_module()->idnumber;
7154                          // Set assign gradebook feedback plugin status.
7155                          $assign->gradefeedbackenabled = $this->is_gradebook_feedback_enabled();
7156                          assign_update_grades($assign, $userid);
7157                      }
7158  
7159                      $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
7160                      \mod_assign\event\workflow_state_updated::create_from_user($this, $user, $state)->trigger();
7161                  }
7162              }
7163          }
7164      }
7165  
7166      /**
7167       * Set the marking allocation for multiple users
7168       *
7169       * @return void
7170       */
7171      protected function process_set_batch_marking_allocation() {
7172          global $CFG, $DB;
7173  
7174          // Include batch marking allocation form.
7175          require_once($CFG->dirroot . '/mod/assign/batchsetallocatedmarkerform.php');
7176  
7177          $formparams = array(
7178              'userscount' => 0,  // This form is never re-displayed, so we don't need to
7179              'usershtml' => ''   // initialise these parameters with real information.
7180          );
7181  
7182          list($sort, $params) = users_order_by_sql();
7183          $markers = get_users_by_capability($this->get_context(), 'mod/assign:grade', '', $sort);
7184          $markerlist = array();
7185          foreach ($markers as $marker) {
7186              $markerlist[$marker->id] = fullname($marker);
7187          }
7188  
7189          $formparams['markers'] = $markerlist;
7190  
7191          $mform = new mod_assign_batch_set_allocatedmarker_form(null, $formparams);
7192  
7193          if ($mform->is_cancelled()) {
7194              return true;
7195          }
7196  
7197          if ($formdata = $mform->get_data()) {
7198              $useridlist = explode(',', $formdata->selectedusers);
7199              $marker = $DB->get_record('user', array('id' => $formdata->allocatedmarker), '*', MUST_EXIST);
7200  
7201              foreach ($useridlist as $userid) {
7202                  $flags = $this->get_user_flags($userid, true);
7203                  if ($flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW ||
7204                      $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW ||
7205                      $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE ||
7206                      $flags->workflowstate == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
7207  
7208                      continue; // Allocated marker can only be changed in certain workflow states.
7209                  }
7210  
7211                  $flags->allocatedmarker = $marker->id;
7212  
7213                  if ($this->update_user_flags($flags)) {
7214                      $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
7215                      \mod_assign\event\marker_updated::create_from_marker($this, $user, $marker)->trigger();
7216                  }
7217              }
7218          }
7219      }
7220  
7221  
7222      /**
7223       * Prevent student updates to this submission.
7224       * Uses url parameter userid.
7225       *
7226       * @param int $userid
7227       * @return void
7228       */
7229      protected function process_lock_submission($userid = 0) {
7230  
7231          require_sesskey();
7232  
7233          if (!$userid) {
7234              $userid = required_param('userid', PARAM_INT);
7235          }
7236  
7237          return $this->lock_submission($userid);
7238      }
7239  
7240      /**
7241       * Unlock the student submission.
7242       *
7243       * @param int $userid
7244       * @return bool
7245       */
7246      public function unlock_submission($userid) {
7247          global $USER, $DB;
7248  
7249          // Need grade permission.
7250          require_capability('mod/assign:grade', $this->context);
7251  
7252          // Give each submission plugin a chance to process the unlocking.
7253          $plugins = $this->get_submission_plugins();
7254          $submission = $this->get_user_submission($userid, false);
7255  
7256          $flags = $this->get_user_flags($userid, true);
7257          $flags->locked = 0;
7258          $this->update_user_flags($flags);
7259  
7260          foreach ($plugins as $plugin) {
7261              if ($plugin->is_enabled() && $plugin->is_visible()) {
7262                  $plugin->unlock($submission, $flags);
7263              }
7264          }
7265  
7266          $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
7267          \mod_assign\event\submission_unlocked::create_from_user($this, $user)->trigger();
7268          return true;
7269      }
7270  
7271      /**
7272       * Unlock the student submission.
7273       * Uses url parameter userid.
7274       *
7275       * @param int $userid
7276       * @return bool
7277       */
7278      protected function process_unlock_submission($userid = 0) {
7279  
7280          require_sesskey();
7281  
7282          if (!$userid) {
7283              $userid = required_param('userid', PARAM_INT);
7284          }
7285  
7286          return $this->unlock_submission($userid);
7287      }
7288  
7289      /**
7290       * Apply a grade from a grading form to a user (may be called multiple times for a group submission).
7291       *
7292       * @param stdClass $formdata - the data from the form
7293       * @param int $userid - the user to apply the grade to
7294       * @param int $attemptnumber - The attempt number to apply the grade to.
7295       * @return void
7296       */
7297      protected function apply_grade_to_user($formdata, $userid, $attemptnumber) {
7298          global $USER, $CFG, $DB;
7299  
7300          $grade = $this->get_user_grade($userid, true, $attemptnumber);
7301          $originalgrade = $grade->grade;
7302          $gradingdisabled = $this->grading_disabled($userid);
7303          $gradinginstance = $this->get_grading_instance($userid, $grade, $gradingdisabled);
7304          if (!$gradingdisabled) {
7305              if ($gradinginstance) {
7306                  $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading,
7307                                                                         $grade->id);
7308              } else {
7309                  // Handle the case when grade is set to No Grade.
7310                  if (isset($formdata->grade)) {
7311                      $grade->grade = grade_floatval(unformat_float($formdata->grade));
7312                  }
7313              }
7314              if (isset($formdata->workflowstate) || isset($formdata->allocatedmarker)) {
7315                  $flags = $this->get_user_flags($userid, true);
7316                  $oldworkflowstate = $flags->workflowstate;
7317                  $flags->workflowstate = isset($formdata->workflowstate) ? $formdata->workflowstate : $flags->workflowstate;
7318                  $flags->allocatedmarker = isset($formdata->allocatedmarker) ? $formdata->allocatedmarker : $flags->allocatedmarker;
7319                  if ($this->update_user_flags($flags) &&
7320                          isset($formdata->workflowstate) &&
7321                          $formdata->workflowstate !== $oldworkflowstate) {
7322                      $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
7323                      \mod_assign\event\workflow_state_updated::create_from_user($this, $user, $formdata->workflowstate)->trigger();
7324                  }
7325              }
7326          }
7327          $grade->grader= $USER->id;
7328  
7329          $adminconfig = $this->get_admin_config();
7330          $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
7331  
7332          $feedbackmodified = false;
7333  
7334          // Call save in plugins.
7335          foreach ($this->feedbackplugins as $plugin) {
7336              if ($plugin->is_enabled() && $plugin->is_visible()) {
7337                  $gradingmodified = $plugin->is_feedback_modified($grade, $formdata);
7338                  if ($gradingmodified) {
7339                      if (!$plugin->save($grade, $formdata)) {
7340                          $result = false;
7341                          print_error($plugin->get_error());
7342                      }
7343                      // If $feedbackmodified is true, keep it true.
7344                      $feedbackmodified = $feedbackmodified || $gradingmodified;
7345                  }
7346                  if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
7347                      // This is the feedback plugin chose to push comments to the gradebook.
7348                      $grade->feedbacktext = $plugin->text_for_gradebook($grade);
7349                      $grade->feedbackformat = $plugin->format_for_gradebook($grade);
7350                  }
7351              }
7352          }
7353  
7354          // We do not want to update the timemodified if no grade was added.
7355          if (!empty($formdata->addattempt) ||
7356                  ($originalgrade !== null && $originalgrade != -1) ||
7357                  ($grade->grade !== null && $grade->grade != -1) ||
7358                  $feedbackmodified) {
7359              $this->update_grade($grade, !empty($formdata->addattempt));
7360          }
7361          // Note the default if not provided for this option is true (e.g. webservices).
7362          // This is for backwards compatibility.
7363          if (!isset($formdata->sendstudentnotifications) || $formdata->sendstudentnotifications) {
7364              $this->notify_grade_modified($grade, true);
7365          }
7366      }
7367  
7368  
7369      /**
7370       * Save outcomes submitted from grading form.
7371       *
7372       * @param int $userid
7373       * @param stdClass $formdata
7374       * @param int $sourceuserid The user ID under which the outcome data is accessible. This is relevant
7375       *                          for an outcome set to a user but applied to an entire group.
7376       */
7377      protected function process_outcomes($userid, $formdata, $sourceuserid = null) {
7378          global $CFG, $USER;
7379  
7380          if (empty($CFG->enableoutcomes)) {
7381              return;
7382          }
7383          if ($this->grading_disabled($userid)) {
7384              return;
7385          }
7386  
7387          require_once($CFG->libdir.'/gradelib.php');
7388  
7389          $data = array();
7390          $gradinginfo = grade_get_grades($this->get_course()->id,
7391                                          'mod',
7392                                          'assign',
7393                                          $this->get_instance()->id,
7394                                          $userid);
7395  
7396          if (!empty($gradinginfo->outcomes)) {
7397              foreach ($gradinginfo->outcomes as $index => $oldoutcome) {
7398                  $name = 'outcome_'.$index;
7399                  $sourceuserid = $sourceuserid !== null ? $sourceuserid : $userid;
7400                  if (isset($formdata->{$name}[$sourceuserid]) &&
7401                          $oldoutcome->grades[$userid]->grade != $formdata->{$name}[$sourceuserid]) {
7402                      $data[$index] = $formdata->{$name}[$sourceuserid];
7403                  }
7404              }
7405          }
7406          if (count($data) > 0) {
7407              grade_update_outcomes('mod/assign',
7408                                    $this->course->id,
7409                                    'mod',
7410                                    'assign',
7411                                    $this->get_instance()->id,
7412                                    $userid,
7413                                    $data);
7414          }
7415      }
7416  
7417      /**
7418       * If the requirements are met - reopen the submission for another attempt.
7419       * Only call this function when grading the latest attempt.
7420       *
7421       * @param int $userid The userid.
7422       * @param stdClass $submission The submission (may be a group submission).
7423       * @param bool $addattempt - True if the "allow another attempt" checkbox was checked.
7424       * @return bool - true if another attempt was added.
7425       */
7426      protected function reopen_submission_if_required($userid, $submission, $addattempt) {
7427          $instance = $this->get_instance();
7428          $maxattemptsreached = !empty($submission) &&
7429                                $submission->attemptnumber >= ($instance->maxattempts - 1) &&
7430                                $instance->maxattempts != ASSIGN_UNLIMITED_ATTEMPTS;
7431          $shouldreopen = false;
7432          if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS) {
7433              // Check the gradetopass from the gradebook.
7434              $gradeitem = $this->get_grade_item();
7435              if ($gradeitem) {
7436                  $gradegrade = grade_grade::fetch(array('userid' => $userid, 'itemid' => $gradeitem->id));
7437  
7438                  // Do not reopen if is_passed returns null, e.g. if there is no pass criterion set.
7439                  if ($gradegrade && ($gradegrade->is_passed() === false)) {
7440                      $shouldreopen = true;
7441                  }
7442              }
7443          }
7444          if ($instance->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL &&
7445                  !empty($addattempt)) {
7446              $shouldreopen = true;
7447          }
7448          if ($shouldreopen && !$maxattemptsreached) {
7449              $this->add_attempt($userid);
7450              return true;
7451          }
7452          return false;
7453      }
7454  
7455      /**
7456       * Save grade update.
7457       *
7458       * @param int $userid
7459       * @param  stdClass $data
7460       * @return bool - was the grade saved
7461       */
7462      public function save_grade($userid, $data) {
7463  
7464          // Need grade permission.
7465          require_capability('mod/assign:grade', $this->context);
7466  
7467          $instance = $this->get_instance();
7468          $submission = null;
7469          if ($instance->teamsubmission) {
7470              $submission = $this->get_group_submission($userid, 0, false, $data->attemptnumber);
7471          } else {
7472              $submission = $this->get_user_submission($userid, false, $data->attemptnumber);
7473          }
7474          if ($instance->teamsubmission && !empty($data->applytoall)) {
7475              $groupid = 0;
7476              if ($this->get_submission_group($userid)) {
7477                  $group = $this->get_submission_group($userid);
7478                  if ($group) {
7479                      $groupid = $group->id;
7480                  }
7481              }
7482              $members = $this->get_submission_group_members($groupid, true, $this->show_only_active_users());
7483              foreach ($members as $member) {
7484                  // User may exist in multple groups (which should put them in the default group).
7485                  $this->apply_grade_to_user($data, $member->id, $data->attemptnumber);
7486                  $this->process_outcomes($member->id, $data, $userid);
7487              }
7488          } else {
7489              $this->apply_grade_to_user($data, $userid, $data->attemptnumber);
7490  
7491              $this->process_outcomes($userid, $data);
7492          }
7493  
7494          return true;
7495      }
7496  
7497      /**
7498       * Save grade.
7499       *
7500       * @param  moodleform $mform
7501       * @return bool - was the grade saved
7502       */
7503      protected function process_save_grade(&$mform) {
7504          global $CFG, $SESSION;
7505          // Include grade form.
7506          require_once($CFG->dirroot . '/mod/assign/gradeform.php');
7507  
7508          require_sesskey();
7509  
7510          $instance = $this->get_instance();
7511          $rownum = required_param('rownum', PARAM_INT);
7512          $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
7513          $useridlistid = optional_param('useridlistid', $this->get_useridlist_key_id(), PARAM_ALPHANUM);
7514          $userid = optional_param('userid', 0, PARAM_INT);
7515          if (!$userid) {
7516              if (empty($SESSION->mod_assign_useridlist[$this->get_useridlist_key($useridlistid)])) {
7517                  // If the userid list is not stored we must not save, as it is possible that the user in a
7518                  // given row position may not be the same now as when the grading page was generated.
7519                  $url = new moodle_url('/mod/assign/view.php', array('id' => $this->get_course_module()->id));
7520                  throw new moodle_exception('useridlistnotcached', 'mod_assign', $url);
7521              }
7522              $useridlist = $SESSION->mod_assign_useridlist[$this->get_useridlist_key($useridlistid)];
7523          } else {
7524              $useridlist = array($userid);
7525              $rownum = 0;
7526          }
7527  
7528          $last = false;
7529          $userid = $useridlist[$rownum];
7530          if ($rownum == count($useridlist) - 1) {
7531              $last = true;
7532          }
7533  
7534          $data = new stdClass();
7535  
7536          $gradeformparams = array('rownum' => $rownum,
7537                                   'useridlistid' => $useridlistid,
7538                                   'last' => $last,
7539                                   'attemptnumber' => $attemptnumber,
7540                                   'userid' => $userid);
7541          $mform = new mod_assign_grade_form(null,
7542                                             array($this, $data, $gradeformparams),
7543                                             'post',
7544                                             '',
7545                                             array('class'=>'gradeform'));
7546  
7547          if ($formdata = $mform->get_data()) {
7548              return $this->save_grade($userid, $formdata);
7549          } else {
7550              return false;
7551          }
7552      }
7553  
7554      /**
7555       * This function is a static wrapper around can_upgrade.
7556       *
7557       * @param string $type The plugin type
7558       * @param int $version The plugin version
7559       * @return bool
7560       */
7561      public static function can_upgrade_assignment($type, $version) {
7562          $assignment = new assign(null, null, null);
7563          return $assignment->can_upgrade($type, $version);
7564      }
7565  
7566      /**
7567       * This function returns true if it can upgrade an assignment from the 2.2 module.
7568       *
7569       * @param string $type The plugin type
7570       * @param int $version The plugin version
7571       * @return bool
7572       */
7573      public function can_upgrade($type, $version) {
7574          if ($type == 'offline' && $version >= 2011112900) {
7575              return true;
7576          }
7577          foreach ($this->submissionplugins as $plugin) {
7578              if ($plugin->can_upgrade($type, $version)) {
7579                  return true;
7580              }
7581          }
7582          foreach ($this->feedbackplugins as $plugin) {
7583              if ($plugin->can_upgrade($type, $version)) {
7584                  return true;
7585              }
7586          }
7587          return false;
7588      }
7589  
7590      /**
7591       * Copy all the files from the old assignment files area to the new one.
7592       * This is used by the plugin upgrade code.
7593       *
7594       * @param int $oldcontextid The old assignment context id
7595       * @param int $oldcomponent The old assignment component ('assignment')
7596       * @param int $oldfilearea The old assignment filearea ('submissions')
7597       * @param int $olditemid The old submissionid (can be null e.g. intro)
7598       * @param int $newcontextid The new assignment context id
7599       * @param int $newcomponent The new assignment component ('assignment')
7600       * @param int $newfilearea The new assignment filearea ('submissions')
7601       * @param int $newitemid The new submissionid (can be null e.g. intro)
7602       * @return int The number of files copied
7603       */
7604      public function copy_area_files_for_upgrade($oldcontextid,
7605                                                  $oldcomponent,
7606                                                  $oldfilearea,
7607                                                  $olditemid,
7608                                                  $newcontextid,
7609                                                  $newcomponent,
7610                                                  $newfilearea,
7611                                                  $newitemid) {
7612          // Note, this code is based on some code in filestorage - but that code
7613          // deleted the old files (which we don't want).
7614          $count = 0;
7615  
7616          $fs = get_file_storage();
7617  
7618          $oldfiles = $fs->get_area_files($oldcontextid,
7619                                          $oldcomponent,
7620                                          $oldfilearea,
7621                                          $olditemid,
7622                                          'id',
7623                                          false);
7624          foreach ($oldfiles as $oldfile) {
7625              $filerecord = new stdClass();
7626              $filerecord->contextid = $newcontextid;
7627              $filerecord->component = $newcomponent;
7628              $filerecord->filearea = $newfilearea;
7629              $filerecord->itemid = $newitemid;
7630              $fs->create_file_from_storedfile($filerecord, $oldfile);
7631              $count += 1;
7632          }
7633  
7634          return $count;
7635      }
7636  
7637      /**
7638       * Add a new attempt for each user in the list - but reopen each group assignment
7639       * at most 1 time.
7640       *
7641       * @param array $useridlist Array of userids to reopen.
7642       * @return bool
7643       */
7644      protected function process_add_attempt_group($useridlist) {
7645          $groupsprocessed = array();
7646          $result = true;
7647  
7648          foreach ($useridlist as $userid) {
7649              $groupid = 0;
7650              $group = $this->get_submission_group($userid);
7651              if ($group) {
7652                  $groupid = $group->id;
7653              }
7654  
7655              if (empty($groupsprocessed[$groupid])) {
7656                  $result = $this->process_add_attempt($userid) && $result;
7657                  $groupsprocessed[$groupid] = true;
7658              }
7659          }
7660          return $result;
7661      }
7662  
7663      /**
7664       * Check for a sess key and then call add_attempt.
7665       *
7666       * @param int $userid int The user to add the attempt for
7667       * @return bool - true if successful.
7668       */
7669      protected function process_add_attempt($userid) {
7670          require_sesskey();
7671  
7672          return $this->add_attempt($userid);
7673      }
7674  
7675      /**
7676       * Add a new attempt for a user.
7677       *
7678       * @param int $userid int The user to add the attempt for
7679       * @return bool - true if successful.
7680       */
7681      protected function add_attempt($userid) {
7682          require_capability('mod/assign:grade', $this->context);
7683  
7684          if ($this->get_instance()->attemptreopenmethod == ASSIGN_ATTEMPT_REOPEN_METHOD_NONE) {
7685              return false;
7686          }
7687  
7688          if ($this->get_instance()->teamsubmission) {
7689              $oldsubmission = $this->get_group_submission($userid, 0, false);
7690          } else {
7691              $oldsubmission = $this->get_user_submission($userid, false);
7692          }
7693  
7694          if (!$oldsubmission) {
7695              return false;
7696          }
7697  
7698          // No more than max attempts allowed.
7699          if ($this->get_instance()->maxattempts != ASSIGN_UNLIMITED_ATTEMPTS &&
7700              $oldsubmission->attemptnumber >= ($this->get_instance()->maxattempts - 1)) {
7701              return false;
7702          }
7703  
7704          // Create the new submission record for the group/user.
7705          if ($this->get_instance()->teamsubmission) {
7706              $newsubmission = $this->get_group_submission($userid, 0, true, $oldsubmission->attemptnumber + 1);
7707          } else {
7708              $newsubmission = $this->get_user_submission($userid, true, $oldsubmission->attemptnumber + 1);
7709          }
7710  
7711          // Set the status of the new attempt to reopened.
7712          $newsubmission->status = ASSIGN_SUBMISSION_STATUS_REOPENED;
7713  
7714          // Give each submission plugin a chance to process the add_attempt.
7715          $plugins = $this->get_submission_plugins();
7716          foreach ($plugins as $plugin) {
7717              if ($plugin->is_enabled() && $plugin->is_visible()) {
7718                  $plugin->add_attempt($oldsubmission, $newsubmission);
7719              }
7720          }
7721  
7722          $this->update_submission($newsubmission, $userid, false, $this->get_instance()->teamsubmission);
7723          $flags = $this->get_user_flags($userid, false);
7724          if (isset($flags->locked) && $flags->locked) { // May not exist.
7725              $this->process_unlock_submission($userid);
7726          }
7727          return true;
7728      }
7729  
7730      /**
7731       * Get an upto date list of user grades and feedback for the gradebook.
7732       *
7733       * @param int $userid int or 0 for all users
7734       * @return array of grade data formated for the gradebook api
7735       *         The data required by the gradebook api is userid,
7736       *                                                   rawgrade,
7737       *                                                   feedback,
7738       *                                                   feedbackformat,
7739       *                                                   usermodified,
7740       *                                                   dategraded,
7741       *                                                   datesubmitted
7742       */
7743      public function get_user_grades_for_gradebook($userid) {
7744          global $DB, $CFG;
7745          $grades = array();
7746          $assignmentid = $this->get_instance()->id;
7747  
7748          $adminconfig = $this->get_admin_config();
7749          $gradebookpluginname = $adminconfig->feedback_plugin_for_gradebook;
7750          $gradebookplugin = null;
7751  
7752          // Find the gradebook plugin.
7753          foreach ($this->feedbackplugins as $plugin) {
7754              if ($plugin->is_enabled() && $plugin->is_visible()) {
7755                  if (('assignfeedback_' . $plugin->get_type()) == $gradebookpluginname) {
7756                      $gradebookplugin = $plugin;
7757                  }
7758              }
7759          }
7760          if ($userid) {
7761              $where = ' WHERE u.id = :userid ';
7762          } else {
7763              $where = ' WHERE u.id != :userid ';
7764          }
7765  
7766          // When the gradebook asks us for grades - only return the last attempt for each user.
7767          $params = array('assignid1'=>$assignmentid,
7768                          'assignid2'=>$assignmentid,
7769                          'userid'=>$userid);
7770          $graderesults = $DB->get_recordset_sql('SELECT
7771                                                      u.id as userid,
7772                                                      s.timemodified as datesubmitted,
7773                                                      g.grade as rawgrade,
7774                                                      g.timemodified as dategraded,
7775                                                      g.grader as usermodified
7776                                                  FROM {user} u
7777                                                  LEFT JOIN {assign_submission} s
7778                                                      ON u.id = s.userid and s.assignment = :assignid1 AND
7779                                                      s.latest = 1
7780                                                  JOIN {assign_grades} g
7781                                                      ON u.id = g.userid and g.assignment = :assignid2 AND
7782                                                      g.attemptnumber = s.attemptnumber' .
7783                                                  $where, $params);
7784  
7785          foreach ($graderesults as $result) {
7786              $gradingstatus = $this->get_grading_status($result->userid);
7787              if (!$this->get_instance()->markingworkflow || $gradingstatus == ASSIGN_MARKING_WORKFLOW_STATE_RELEASED) {
7788                  $gradebookgrade = clone $result;
7789                  // Now get the feedback.
7790                  if ($gradebookplugin) {
7791                      $grade = $this->get_user_grade($result->userid, false);
7792                      if ($grade) {
7793                          $gradebookgrade->feedback = $gradebookplugin->text_for_gradebook($grade);
7794                          $gradebookgrade->feedbackformat = $gradebookplugin->format_for_gradebook($grade);
7795                      }
7796                  }
7797                  $grades[$gradebookgrade->userid] = $gradebookgrade;
7798              }
7799          }
7800  
7801          $graderesults->close();
7802          return $grades;
7803      }
7804  
7805      /**
7806       * Call the static version of this function
7807       *
7808       * @param int $userid The userid to lookup
7809       * @return int The unique id
7810       */
7811      public function get_uniqueid_for_user($userid) {
7812          return self::get_uniqueid_for_user_static($this->get_instance()->id, $userid);
7813      }
7814  
7815      /**
7816       * Foreach participant in the course - assign them a random id.
7817       *
7818       * @param int $assignid The assignid to lookup
7819       */
7820      public static function allocate_unique_ids($assignid) {
7821          global $DB;
7822  
7823          $cm = get_coursemodule_from_instance('assign', $assignid, 0, false, MUST_EXIST);
7824          $context = context_module::instance($cm->id);
7825  
7826          $currentgroup = groups_get_activity_group($cm, true);
7827          $users = get_enrolled_users($context, "mod/assign:submit", $currentgroup, 'u.id');
7828  
7829          // Shuffle the users.
7830          shuffle($users);
7831  
7832          foreach ($users as $user) {
7833              $record = $DB->get_record('assign_user_mapping',
7834                                        array('assignment'=>$assignid, 'userid'=>$user->id),
7835                                       'id');
7836              if (!$record) {
7837                  $record = new stdClass();
7838                  $record->assignment = $assignid;
7839                  $record->userid = $user->id;
7840                  $DB->insert_record('assign_user_mapping', $record);
7841              }
7842          }
7843      }
7844  
7845      /**
7846       * Lookup this user id and return the unique id for this assignment.
7847       *
7848       * @param int $assignid The assignment id
7849       * @param int $userid The userid to lookup
7850       * @return int The unique id
7851       */
7852      public static function get_uniqueid_for_user_static($assignid, $userid) {
7853          global $DB;
7854  
7855          // Search for a record.
7856          $params = array('assignment'=>$assignid, 'userid'=>$userid);
7857          if ($record = $DB->get_record('assign_user_mapping', $params, 'id')) {
7858              return $record->id;
7859          }
7860  
7861          // Be a little smart about this - there is no record for the current user.
7862          // We should ensure any unallocated ids for the current participant
7863          // list are distrubited randomly.
7864          self::allocate_unique_ids($assignid);
7865  
7866          // Retry the search for a record.
7867          if ($record = $DB->get_record('assign_user_mapping', $params, 'id')) {
7868              return $record->id;
7869          }
7870  
7871          // The requested user must not be a participant. Add a record anyway.
7872          $record = new stdClass();
7873          $record->assignment = $assignid;
7874          $record->userid = $userid;
7875  
7876          return $DB->insert_record('assign_user_mapping', $record);
7877      }
7878  
7879      /**
7880       * Call the static version of this function.
7881       *
7882       * @param int $uniqueid The uniqueid to lookup
7883       * @return int The user id or false if they don't exist
7884       */
7885      public function get_user_id_for_uniqueid($uniqueid) {
7886          return self::get_user_id_for_uniqueid_static($this->get_instance()->id, $uniqueid);
7887      }
7888  
7889      /**
7890       * Lookup this unique id and return the user id for this assignment.
7891       *
7892       * @param int $assignid The id of the assignment this user mapping is in
7893       * @param int $uniqueid The uniqueid to lookup
7894       * @return int The user id or false if they don't exist
7895       */
7896      public static function get_user_id_for_uniqueid_static($assignid, $uniqueid) {
7897          global $DB;
7898  
7899          // Search for a record.
7900          if ($record = $DB->get_record('assign_user_mapping',
7901                                        array('assignment'=>$assignid, 'id'=>$uniqueid),
7902                                        'userid',
7903                                        IGNORE_MISSING)) {
7904              return $record->userid;
7905          }
7906  
7907          return false;
7908      }
7909  
7910      /**
7911       * Get the list of marking_workflow states the current user has permission to transition a grade to.
7912       *
7913       * @return array of state => description
7914       */
7915      public function get_marking_workflow_states_for_current_user() {
7916          if (!empty($this->markingworkflowstates)) {
7917              return $this->markingworkflowstates;
7918          }
7919          $states = array();
7920          if (has_capability('mod/assign:grade', $this->context)) {
7921              $states[ASSIGN_MARKING_WORKFLOW_STATE_INMARKING] = get_string('markingworkflowstateinmarking', 'assign');
7922              $states[ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW] = get_string('markingworkflowstatereadyforreview', 'assign');
7923          }
7924          if (has_any_capability(array('mod/assign:reviewgrades',
7925                                       'mod/assign:managegrades'), $this->context)) {
7926              $states[ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW] = get_string('markingworkflowstateinreview', 'assign');
7927              $states[ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE] = get_string('markingworkflowstatereadyforrelease', 'assign');
7928          }
7929          if (has_any_capability(array('mod/assign:releasegrades',
7930                                       'mod/assign:managegrades'), $this->context)) {
7931              $states[ASSIGN_MARKING_WORKFLOW_STATE_RELEASED] = get_string('markingworkflowstatereleased', 'assign');
7932          }
7933          $this->markingworkflowstates = $states;
7934          return $this->markingworkflowstates;
7935      }
7936  
7937      /**
7938       * Check is only active users in course should be shown.
7939       *
7940       * @return bool true if only active users should be shown.
7941       */
7942      public function show_only_active_users() {
7943          global $CFG;
7944  
7945          if (is_null($this->showonlyactiveenrol)) {
7946              $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
7947              $this->showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
7948  
7949              if (!is_null($this->context)) {
7950                  $this->showonlyactiveenrol = $this->showonlyactiveenrol ||
7951                              !has_capability('moodle/course:viewsuspendedusers', $this->context);
7952              }
7953          }
7954          return $this->showonlyactiveenrol;
7955      }
7956  
7957      /**
7958       * Return true is user is active user in course else false
7959       *
7960       * @param int $userid
7961       * @return bool true is user is active in course.
7962       */
7963      public function is_active_user($userid) {
7964          return !in_array($userid, get_suspended_userids($this->context, true));
7965      }
7966  
7967      /**
7968       * Returns true if gradebook feedback plugin is enabled
7969       *
7970       * @return bool true if gradebook feedback plugin is enabled and visible else false.
7971       */
7972      public function is_gradebook_feedback_enabled() {
7973          // Get default grade book feedback plugin.
7974          $adminconfig = $this->get_admin_config();
7975          $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
7976          $gradebookplugin = str_replace('assignfeedback_', '', $gradebookplugin);
7977  
7978          // Check if default gradebook feedback is visible and enabled.
7979          $gradebookfeedbackplugin = $this->get_feedback_plugin_by_type($gradebookplugin);
7980  
7981          if (empty($gradebookfeedbackplugin)) {
7982              return false;
7983          }
7984  
7985          if ($gradebookfeedbackplugin->is_visible() && $gradebookfeedbackplugin->is_enabled()) {
7986              return true;
7987          }
7988  
7989          // Gradebook feedback plugin is either not visible/enabled.
7990          return false;
7991      }
7992  
7993      /**
7994       * Returns the grading status.
7995       *
7996       * @param int $userid the user id
7997       * @return string returns the grading status
7998       */
7999      public function get_grading_status($userid) {
8000          if ($this->get_instance()->markingworkflow) {
8001              $flags = $this->get_user_flags($userid, false);
8002              if (!empty($flags->workflowstate)) {
8003                  return $flags->workflowstate;
8004              }
8005              return ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
8006          } else {
8007              $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
8008              $grade = $this->get_user_grade($userid, false, $attemptnumber);
8009  
8010              if (!empty($grade) && $grade->grade !== null && $grade->grade >= 0) {
8011                  return ASSIGN_GRADING_STATUS_GRADED;
8012              } else {
8013                  return ASSIGN_GRADING_STATUS_NOT_GRADED;
8014              }
8015          }
8016      }
8017  
8018      /**
8019       * The id used to uniquily identify the cache for this instance of the assign object.
8020       *
8021       * @return string
8022       */
8023      public function get_useridlist_key_id() {
8024          return $this->useridlistid;
8025      }
8026  
8027      /**
8028       * Generates the key that should be used for an entry in the useridlist cache.
8029       *
8030       * @param string $id Generate a key for this instance (optional)
8031       * @return string The key for the id, or new entry if no $id is passed.
8032       */
8033      public function get_useridlist_key($id = null) {
8034          if ($id === null) {
8035              $id = $this->get_useridlist_key_id();
8036          }
8037          return $this->get_course_module()->id . '_' . $id;
8038      }
8039  
8040      /**
8041       * Updates and creates the completion records in mdl_course_modules_completion.
8042       *
8043       * @param int $teamsubmission value of 0 or 1 to indicate whether this is a group activity
8044       * @param int $requireallteammemberssubmit value of 0 or 1 to indicate whether all group members must click Submit
8045       * @param obj $submission the submission
8046       * @param int $userid the user id
8047       * @param int $complete
8048       * @param obj $completion
8049       *
8050       * @return null
8051       */
8052      protected function update_activity_completion_records($teamsubmission,
8053                                                            $requireallteammemberssubmit,
8054                                                            $submission,
8055                                                            $userid,
8056                                                            $complete,
8057                                                            $completion) {
8058  
8059          if (($teamsubmission && $submission->groupid > 0 && !$requireallteammemberssubmit) ||
8060              ($teamsubmission && $submission->groupid > 0 && $requireallteammemberssubmit &&
8061               $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
8062  
8063              $members = groups_get_members($submission->groupid);
8064  
8065              foreach ($members as $member) {
8066                  $completion->update_state($this->get_course_module(), $complete, $member->id);
8067              }
8068          } else {
8069              $completion->update_state($this->get_course_module(), $complete, $userid);
8070          }
8071  
8072          return;
8073      }
8074  
8075      /**
8076       * Update the module completion status (set it viewed).
8077       *
8078       * @since Moodle 3.2
8079       */
8080      public function set_module_viewed() {
8081          $completion = new completion_info($this->get_course());
8082          $completion->set_module_viewed($this->get_course_module());
8083      }
8084  }
8085  
8086  /**
8087   * Portfolio caller class for mod_assign.
8088   *
8089   * @package   mod_assign
8090   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
8091   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
8092   */
8093  class assign_portfolio_caller extends portfolio_module_caller_base {
8094  
8095      /** @var int callback arg - the id of submission we export */
8096      protected $sid;
8097  
8098      /** @var string component of the submission files we export*/
8099      protected $component;
8100  
8101      /** @var string callback arg - the area of submission files we export */
8102      protected $area;
8103  
8104      /** @var int callback arg - the id of file we export */
8105      protected $fileid;
8106  
8107      /** @var int callback arg - the cmid of the assignment we export */
8108      protected $cmid;
8109  
8110      /** @var string callback arg - the plugintype of the editor we export */
8111      protected $plugin;
8112  
8113      /** @var string callback arg - the name of the editor field we export */
8114      protected $editor;
8115  
8116      /**
8117       * Callback arg for a single file export.
8118       */
8119      public static function expected_callbackargs() {
8120          return array(
8121              'cmid' => true,
8122              'sid' => false,
8123              'area' => false,
8124              'component' => false,
8125              'fileid' => false,
8126              'plugin' => false,
8127              'editor' => false,
8128          );
8129      }
8130  
8131      /**
8132       * The constructor.
8133       *
8134       * @param array $callbackargs
8135       */
8136      public function __construct($callbackargs) {
8137          parent::__construct($callbackargs);
8138          $this->cm = get_coursemodule_from_id('assign', $this->cmid, 0, false, MUST_EXIST);
8139      }
8140  
8141      /**
8142       * Load data needed for the portfolio export.
8143       *
8144       * If the assignment type implements portfolio_load_data(), the processing is delegated
8145       * to it. Otherwise, the caller must provide either fileid (to export single file) or
8146       * submissionid and filearea (to export all data attached to the given submission file area)
8147       * via callback arguments.
8148       *
8149       * @throws     portfolio_caller_exception
8150       */
8151      public function load_data() {
8152  
8153          $context = context_module::instance($this->cmid);
8154  
8155          if (empty($this->fileid)) {
8156              if (empty($this->sid) || empty($this->area)) {
8157                  throw new portfolio_caller_exception('invalidfileandsubmissionid', 'mod_assign');
8158              }
8159  
8160          }
8161  
8162          // Export either an area of files or a single file (see function for more detail).
8163          // The first arg is an id or null. If it is an id, the rest of the args are ignored.
8164          // If it is null, the rest of the args are used to load a list of files from get_areafiles.
8165          $this->set_file_and_format_data($this->fileid,
8166                                          $context->id,
8167                                          $this->component,
8168                                          $this->area,
8169                                          $this->sid,
8170                                          'timemodified',
8171                                          false);
8172  
8173      }
8174  
8175      /**
8176       * Prepares the package up before control is passed to the portfolio plugin.
8177       *
8178       * @throws portfolio_caller_exception
8179       * @return mixed
8180       */
8181      public function prepare_package() {
8182  
8183          if ($this->plugin && $this->editor) {
8184              $options = portfolio_format_text_options();
8185              $context = context_module::instance($this->cmid);
8186              $options->context = $context;
8187  
8188              $plugin = $this->get_submission_plugin();
8189  
8190              $text = $plugin->get_editor_text($this->editor, $this->sid);
8191              $format = $plugin->get_editor_format($this->editor, $this->sid);
8192  
8193              $html = format_text($text, $format, $options);
8194              $html = portfolio_rewrite_pluginfile_urls($html,
8195                                                        $context->id,
8196                                                        'mod_assign',
8197                                                        $this->area,
8198                                                        $this->sid,
8199                                                        $this->exporter->get('format'));
8200  
8201              $exporterclass = $this->exporter->get('formatclass');
8202              if (in_array($exporterclass, array(PORTFOLIO_FORMAT_PLAINHTML, PORTFOLIO_FORMAT_RICHHTML))) {
8203                  if ($files = $this->exporter->get('caller')->get('multifiles')) {
8204                      foreach ($files as $file) {
8205                          $this->exporter->copy_existing_file($file);
8206                      }
8207                  }
8208                  return $this->exporter->write_new_file($html, 'assignment.html', !empty($files));
8209              } else if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
8210                  $leapwriter = $this->exporter->get('format')->leap2a_writer();
8211                  $entry = new portfolio_format_leap2a_entry($this->area . $this->cmid,
8212                                                             $context->get_context_name(),
8213                                                             'resource',
8214                                                             $html);
8215  
8216                  $entry->add_category('web', 'resource_type');
8217                  $entry->author = $this->user;
8218                  $leapwriter->add_entry($entry);
8219                  if ($files = $this->exporter->get('caller')->get('multifiles')) {
8220                      $leapwriter->link_files($entry, $files, $this->area . $this->cmid . 'file');
8221                      foreach ($files as $file) {
8222                          $this->exporter->copy_existing_file($file);
8223                      }
8224                  }
8225                  return $this->exporter->write_new_file($leapwriter->to_xml(),
8226                                                         $this->exporter->get('format')->manifest_name(),
8227                                                         true);
8228              } else {
8229                  debugging('invalid format class: ' . $this->exporter->get('formatclass'));
8230              }
8231  
8232          }
8233  
8234          if ($this->exporter->get('formatclass') == PORTFOLIO_FORMAT_LEAP2A) {
8235              $leapwriter = $this->exporter->get('format')->leap2a_writer();
8236              $files = array();
8237              if ($this->singlefile) {
8238                  $files[] = $this->singlefile;
8239              } else if ($this->multifiles) {
8240                  $files = $this->multifiles;
8241              } else {
8242                  throw new portfolio_caller_exception('invalidpreparepackagefile',
8243                                                       'portfolio',
8244                                                       $this->get_return_url());
8245              }
8246  
8247              $entryids = array();
8248              foreach ($files as $file) {
8249                  $entry = new portfolio_format_leap2a_file($file->get_filename(), $file);
8250                  $entry->author = $this->user;
8251                  $leapwriter->add_entry($entry);
8252                  $this->exporter->copy_existing_file($file);
8253                  $entryids[] = $entry->id;
8254              }
8255              if (count($files) > 1) {
8256                  $baseid = 'assign' . $this->cmid . $this->area;
8257                  $context = context_module::instance($this->cmid);
8258  
8259                  // If we have multiple files, they should be grouped together into a folder.
8260                  $entry = new portfolio_format_leap2a_entry($baseid . 'group',
8261                                                             $context->get_context_name(),
8262                                                             'selection');
8263                  $leapwriter->add_entry($entry);
8264                  $leapwriter->make_selection($entry, $entryids, 'Folder');
8265              }
8266              return $this->exporter->write_new_file($leapwriter->to_xml(),
8267                                                     $this->exporter->get('format')->manifest_name(),
8268                                                     true);
8269          }
8270          return $this->prepare_package_file();
8271      }
8272  
8273      /**
8274       * Fetch the plugin by its type.
8275       *
8276       * @return assign_submission_plugin
8277       */
8278      protected function get_submission_plugin() {
8279          global $CFG;
8280          if (!$this->plugin || !$this->cmid) {
8281              return null;
8282          }
8283  
8284          require_once($CFG->dirroot . '/mod/assign/locallib.php');
8285  
8286          $context = context_module::instance($this->cmid);
8287  
8288          $assignment = new assign($context, null, null);
8289          return $assignment->get_submission_plugin_by_type($this->plugin);
8290      }
8291  
8292      /**
8293       * Calculate a sha1 has of either a single file or a list
8294       * of files based on the data set by load_data.
8295       *
8296       * @return string
8297       */
8298      public function get_sha1() {
8299  
8300          if ($this->plugin && $this->editor) {
8301              $plugin = $this->get_submission_plugin();
8302              $options = portfolio_format_text_options();
8303              $options->context = context_module::instance($this->cmid);
8304  
8305              $text = format_text($plugin->get_editor_text($this->editor, $this->sid),
8306                                  $plugin->get_editor_format($this->editor, $this->sid),
8307                                  $options);
8308              $textsha1 = sha1($text);
8309              $filesha1 = '';
8310              try {
8311                  $filesha1 = $this->get_sha1_file();
8312              } catch (portfolio_caller_exception $e) {
8313                  // No files.
8314              }
8315              return sha1($textsha1 . $filesha1);
8316          }
8317          return $this->get_sha1_file();
8318      }
8319  
8320      /**
8321       * Calculate the time to transfer either a single file or a list
8322       * of files based on the data set by load_data.
8323       *
8324       * @return int
8325       */
8326      public function expected_time() {
8327          return $this->expected_time_file();
8328      }
8329  
8330      /**
8331       * Checking the permissions.
8332       *
8333       * @return bool
8334       */
8335      public function check_permissions() {
8336          $context = context_module::instance($this->cmid);
8337          return has_capability('mod/assign:exportownsubmission', $context);
8338      }
8339  
8340      /**
8341       * Display a module name.
8342       *
8343       * @return string
8344       */
8345      public static function display_name() {
8346          return get_string('modulename', 'assign');
8347      }
8348  
8349      /**
8350       * Return array of formats supported by this portfolio call back.
8351       *
8352       * @return array
8353       */
8354      public static function base_supported_formats() {
8355          return array(PORTFOLIO_FORMAT_FILE, PORTFOLIO_FORMAT_LEAP2A);
8356      }
8357  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1