[ Index ]

PHP Cross Reference of moodle-2.8

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


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1