Search moodle.org's
Developer Documentation

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * This file contains the definition for the class assignment
  19   *
  20   * This class provides all the functionality for the new assign module.
  21   *
  22   * @package   mod_assign
  23   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  24   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  // Assignment submission statuses.
  30  define('ASSIGN_SUBMISSION_STATUS_NEW', 'new');
  31  define('ASSIGN_SUBMISSION_STATUS_REOPENED', 'reopened');
  32  define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft');
  33  define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted');
  34  
  35  // Search filters for grading page.
  36  define('ASSIGN_FILTER_SUBMITTED', 'submitted');
  37  define('ASSIGN_FILTER_NOT_SUBMITTED', 'notsubmitted');
  38  define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
  39  define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
  40  
  41  // Marker filter for grading page.
  42  define('ASSIGN_MARKER_FILTER_NO_MARKER', -1);
  43  
  44  // Reopen attempt methods.
  45  define('ASSIGN_ATTEMPT_REOPEN_METHOD_NONE', 'none');
  46  define('ASSIGN_ATTEMPT_REOPEN_METHOD_MANUAL', 'manual');
  47  define('ASSIGN_ATTEMPT_REOPEN_METHOD_UNTILPASS', 'untilpass');
  48  
  49  // Special value means allow unlimited attempts.
  50  define('ASSIGN_UNLIMITED_ATTEMPTS', -1);
  51  
  52  // Grading states.
  53  define('ASSIGN_GRADING_STATUS_GRADED', 'graded');
  54  define('ASSIGN_GRADING_STATUS_NOT_GRADED', 'notgraded');
  55  
  56  // Marking workflow states.
  57  define('ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED', 'notmarked');
  58  define('ASSIGN_MARKING_WORKFLOW_STATE_INMARKING', 'inmarking');
  59  define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORREVIEW', 'readyforreview');
  60  define('ASSIGN_MARKING_WORKFLOW_STATE_INREVIEW', 'inreview');
  61  define('ASSIGN_MARKING_WORKFLOW_STATE_READYFORRELEASE', 'readyforrelease');
  62  define('ASSIGN_MARKING_WORKFLOW_STATE_RELEASED', 'released');
  63  
  64  // Name of file area for intro attachments.
  65  define('ASSIGN_INTROATTACHMENT_FILEAREA', 'introattachment');
  66  
  67  require_once($CFG->libdir . '/accesslib.php');
  68  require_once($CFG->libdir . '/formslib.php');
  69  require_once($CFG->dirroot . '/repository/lib.php');
  70  require_once($CFG->dirroot . '/mod/assign/mod_form.php');
  71  require_once($CFG->libdir . '/gradelib.php');
  72  require_once($CFG->dirroot . '/grade/grading/lib.php');
  73  require_once($CFG->dirroot . '/mod/assign/feedbackplugin.php');
  74  require_once($CFG->dirroot . '/mod/assign/submissionplugin.php');
  75  require_once($CFG->dirroot . '/mod/assign/renderable.php');
  76  require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
  77  require_once($CFG->libdir . '/eventslib.php');
  78  require_once($CFG->libdir . '/portfolio/caller.php');
  79  
  80  /**
  81   * Standard base class for mod_assign (assignment types).
  82   *
  83   * @package   mod_assign
  84   * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  85   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  86   */
  87  class assign {
  88  
  89      /** @var stdClass the assignment record that contains the global settings for this assign instance */
  90      private $instance;
  91  
  92      /** @var stdClass the grade_item record for this assign instance's primary grade item. */
  93      private $gradeitem;
  94  
  95      /** @var context the context of the course module for this assign instance
  96       *               (or just the course if we are creating a new one)
  97       */
  98      private $context;
  99  
 100      /** @var stdClass the course this assign instance belongs to */
 101      private $course;
 102  
 103      /** @var stdClass the admin config for all assign instances  */
 104      private $adminconfig;
 105  
 106      /** @var assign_renderer the custom renderer for this module */
 107      private $output;
 108  
 109      /** @var cm_info the course module for this assign instance */
 110      private $coursemodule;
 111  
 112      /** @var array cache for things like the coursemodule name or the scale menu -
 113       *             only lives for a single request.
 114       */
 115      private $cache;
 116  
 117      /** @var array list of the installed submission plugins */
 118      private $submissionplugins;
 119  
 120      /** @var array list of the installed feedback plugins */
 121      private $feedbackplugins;
 122  
 123      /** @var string action to be used to return to this page
 124       *              (without repeating any form submissions etc).
 125       */
 126      private $returnaction = 'view';
 127  
 128      /** @var array params to be used to return to this page */
 129      private $returnparams = array();
 130  
 131      /** @var string modulename prevents excessive calls to get_string */
 132      private static $modulename = null;
 133  
 134      /** @var string modulenameplural prevents excessive calls to get_string */
 135      private static $modulenameplural = null;
 136  
 137      /** @var array of marking workflow states for the current user */
 138      private $markingworkflowstates = null;
 139  
 140      /** @var bool whether to exclude users with inactive enrolment */
 141      private $showonlyactiveenrol = null;
 142  
 143      /** @var array cached list of participants for this assignment. The cache key will be group, showactive and the context id */
 144      private $participants = array();
 145  
 146      /** @var array cached list of user groups when team submissions are enabled. The cache key will be the user. */
 147      private $usersubmissiongroups = array();
 148  
 149      /**
 150       * Constructor for the base assign class.
 151       *
 152       * Note: For $coursemodule you can supply a stdclass if you like, but it
 153       * will be more efficient to supply a cm_info object.
 154       *
 155       * @param mixed $coursemodulecontext context|null the course module context
 156       *                                   (or the course context if the coursemodule has not been
 157       *                                   created yet).
 158       * @param mixed $coursemodule the current course module if it was already loaded,
 159       *                            otherwise this class will load one from the context as required.
 160       * @param mixed $course the current course  if it was already loaded,
 161       *                      otherwise this class will load one from the context as required.
 162       */
 163      public function __construct($coursemodulecontext, $coursemodule, $course) {
 164          $this->context = $coursemodulecontext;
 165          $this->course = $course;
 166  
 167          // Ensure that $this->coursemodule is a cm_info object (or null).
 168          $this->coursemodule = cm_info::create($coursemodule);
 169  
 170          // Temporary cache only lives for a single request - used to reduce db lookups.
 171          $this->cache = array();
 172  
 173          $this->submissionplugins = $this->load_plugins('assignsubmission');
 174          $this->feedbackplugins = $this->load_plugins('assignfeedback');
 175      }
 176  
 177      /**
 178       * Set the action and parameters that can be used to return to the current page.
 179       *
 180       * @param string $action The action for the current page
 181       * @param array $params An array of name value pairs which form the parameters
 182       *                      to return to the current page.
 183       * @return void
 184       */
 185      public function register_return_link($action, $params) {
 186          global $PAGE;
 187          $params['action'] = $action;
 188          $currenturl = $PAGE->url;
 189  
 190          $currenturl->params($params);
 191          $PAGE->set_url($currenturl);
 192      }
 193  
 194      /**
 195       * Return an action that can be used to get back to the current page.
 196       *
 197       * @return string action
 198       */
 199      public function get_return_action() {
 200          global $PAGE;
 201  
 202          $params = $PAGE->url->params();
 203  
 204          if (!empty($params['action'])) {
 205              return $params['action'];
 206          }
 207          return '';
 208      }
 209  
 210      /**
 211       * Based on the current assignment settings should we display the intro.
 212       *
 213       * @return bool showintro
 214       */
 215      public function show_intro() {
 216          if ($this->get_instance()->alwaysshowdescription ||
 217                  time() > $this->get_instance()->allowsubmissionsfromdate) {
 218              return true;
 219          }
 220          return false;
 221      }
 222  
 223      /**
 224       * Return a list of parameters that can be used to get back to the current page.
 225       *
 226       * @return array params
 227       */
 228      public function get_return_params() {
 229          global $PAGE;
 230  
 231          $params = $PAGE->url->params();
 232          unset($params['id']);
 233          unset($params['action']);
 234          return $params;
 235      }
 236  
 237      /**
 238       * Set the submitted form data.
 239       *
 240       * @param stdClass $data The form data (instance)
 241       */
 242      public function set_instance(stdClass $data) {
 243          $this->instance = $data;
 244      }
 245  
 246      /**
 247       * Set the context.
 248       *
 249       * @param context $context The new context
 250       */
 251      public function set_context(context $context) {
 252          $this->context = $context;
 253      }
 254  
 255      /**
 256       * Set the course data.
 257       *
 258       * @param stdClass $course The course data
 259       */
 260      public function set_course(stdClass $course) {
 261          $this->course = $course;
 262      }
 263  
 264      /**
 265       * Get list of feedback plugins installed.
 266       *
 267       * @return array
 268       */
 269      public function get_feedback_plugins() {
 270          return $this->feedbackplugins;
 271      }
 272  
 273      /**
 274       * Get list of submission plugins installed.
 275       *
 276       * @return array
 277       */
 278      public function get_submission_plugins() {
 279          return $this->submissionplugins;
 280      }
 281  
 282      /**
 283       * Is blind marking enabled and reveal identities not set yet?
 284       *
 285       * @return bool
 286       */
 287      public function is_blind_marking() {
 288          return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
 289      }
 290  
 291      /**
 292       * Does an assignment have submission(s) or grade(s) already?
 293       *
 294       * @return bool
 295       */
 296      public function has_submissions_or_grades() {
 297          $allgrades = $this->count_grades();
 298          $allsubmissions = $this->count_submissions();
 299          if (($allgrades == 0) && ($allsubmissions == 0)) {
 300              return false;
 301          }
 302          return true;
 303      }
 304  
 305      /**
 306       * Get a specific submission plugin by its type.
 307       *
 308       * @param string $subtype assignsubmission | assignfeedback
 309       * @param string $type
 310       * @return mixed assign_plugin|null
 311       */
 312      public function get_plugin_by_type($subtype, $type) {
 313          $shortsubtype = substr($subtype, strlen('assign'));
 314          $name = $shortsubtype . 'plugins';
 315          if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
 316              return null;
 317          }
 318          $pluginlist = $this->$name;
 319          foreach ($pluginlist as $plugin) {
 320              if ($plugin->get_type() == $type) {
 321                  return $plugin;
 322              }
 323          }
 324          return null;
 325      }
 326  
 327      /**
 328       * Get a feedback plugin by type.
 329       *
 330       * @param string $type - The type of plugin e.g comments
 331       * @return mixed assign_feedback_plugin|null
 332       */
 333      public function get_feedback_plugin_by_type($type) {
 334          return $this->get_plugin_by_type('assignfeedback', $type);
 335      }
 336  
 337      /**
 338       * Get a submission plugin by type.
 339       *
 340       * @param string $type - The type of plugin e.g comments
 341       * @return mixed assign_submission_plugin|null
 342       */
 343      public function get_submission_plugin_by_type($type) {
 344          return $this->get_plugin_by_type('assignsubmission', $type);
 345      }
 346  
 347      /**
 348       * Load the plugins from the sub folders under subtype.
 349       *
 350       * @param string $subtype - either submission or feedback
 351       * @return array - The sorted list of plugins
 352       */
 353      protected function load_plugins($subtype) {
 354          global $CFG;
 355          $result = array();
 356  
 357          $names = core_component::get_plugin_list($subtype);
 358  
 359          foreach ($names as $name => $path) {
 360              if (file_exists($path . '/locallib.php')) {
 361                  require_once ($path . '/locallib.php');
 362  
 363                  $shortsubtype = substr($subtype, strlen('assign'));
 364                  $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
 365  
 366                  $plugin = new $pluginclass($this, $name);
 367  
 368                  if ($plugin instanceof assign_plugin) {
 369                      $idx = $plugin->get_sort_order();
 370                      while (array_key_exists($idx, $result)) {
 371                          $idx +=1;
 372                      }
 373                      $result[$idx] = $plugin;
 374                  }
 375              }
 376          }
 377          ksort($result);
 378          return $result;
 379      }
 380  
 381      /**
 382       * Display the assignment, used by view.php
 383       *
 384       * The assignment is displayed differently depending on your role,
 385       * the settings for the assignment and the status of the assignment.
 386       *
 387       * @param string $action The current action if any.
 388       * @return string - The page output.
 389       */
 390      public function view($action='') {
 391  
 392          $o = '';
 393          $mform = null;
 394          $notices = array();
 395          $nextpageparams = array();
 396  
 397          if (!empty($this->get_course_module()->id)) {
 398              $nextpageparams['id'] = $this->get_course_module()->id;
 399          }
 400  
 401          // Handle form submissions first.
 402          if ($action == 'savesubmission') {
 403              $action = 'editsubmission';
 404              if ($this->process_save_submission($mform, $notices)) {
 405                  $action = 'redirect';
 406                  $nextpageparams['action'] = 'view';
 407              }
 408          } else if ($action == 'editprevioussubmission') {
 409              $action = 'editsubmission';
 410              if ($this->process_copy_previous_attempt($notices)) {
 411                  $action = 'redirect';
 412                  $nextpageparams['action'] = 'editsubmission';
 413              }
 414          } else if ($action == 'lock') {
 415              $this->process_lock_submission();
 416              $action = 'redirect';
 417              $nextpageparams['action'] = 'grading';
 418          } else if ($action == 'addattempt') {
 419              $this->process_add_attempt(required_param('userid', PARAM_INT));
 420              $action = 'redirect';
 421              $nextpageparams['action'] = 'grading';
 422          } else if ($action == 'reverttodraft') {
 423              $this->process_revert_to_draft();
 424              $action = 'redirect';
 425              $nextpageparams['action'] = 'grading';
 426          } else if ($action == 'unlock') {
 427              $this->process_unlock_submission();
 428              $action = 'redirect';
 429              $nextpageparams['action'] = 'grading';
 430          } else if ($action == 'setbatchmarkingworkflowstate') {
 431              $this->process_set_batch_marking_workflow_state();
 432              $action = 'redirect';
 433              $nextpageparams['action'] = 'grading';
 434          } else if ($action == 'setbatchmarkingallocation') {
 435              $this->process_set_batch_marking_allocation();
 436              $action = 'redirect';
 437              $nextpageparams['action'] = 'grading';
 438          } else if ($action == 'confirmsubmit') {
 439              $action = 'submit';
 440              if ($this->process_submit_for_grading($mform, $notices)) {
 441                  $action = 'redirect';
 442                  $nextpageparams['action'] = 'view';
 443              } else if ($notices) {
 444                  $action = 'viewsubmitforgradingerror';
 445              }
 446          } else if ($action == 'submitotherforgrading') {
 447              if ($this->process_submit_other_for_grading($mform, $notices)) {
 448                  $action = 'redirect';
 449                  $nextpageparams['action'] = 'grading';
 450              } else {
 451                  $action = 'viewsubmitforgradingerror';
 452              }
 453          } else if ($action == 'gradingbatchoperation') {
 454              $action = $this->process_grading_batch_operation($mform);
 455              if ($action == 'grading') {
 456                  $action = 'redirect';
 457                  $nextpageparams['action'] = 'grading';
 458              }
 459          } else if ($action == 'submitgrade') {
 460              if (optional_param('saveandshownext', null, PARAM_RAW)) {
 461                  // Save and show next.
 462                  $action = 'grade';
 463                  if ($this->process_save_grade($mform)) {
 464                      $action = 'redirect';
 465                      $nextpageparams['action'] = 'grade';
 466                      $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
 467                      $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
 468                  }
 469              } else if (optional_param('nosaveandprevious', null, PARAM_RAW)) {
 470                  $action = 'redirect';
 471                  $nextpageparams['action'] = 'grade';
 472                  $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) - 1;
 473                  $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
 474              } else if (optional_param('nosaveandnext', null, PARAM_RAW)) {
 475                  $action = 'redirect';
 476                  $nextpageparams['action'] = 'grade';
 477                  $nextpageparams['rownum'] = optional_param('rownum', 0, PARAM_INT) + 1;
 478                  $nextpageparams['useridlistid'] = optional_param('useridlistid', time(), PARAM_INT);
 479              } else if (optional_param('savegrade', null, PARAM_RAW)) {
 480                  // Save changes button.
 481                  $action = 'grade';
 482                  if ($this->process_save_grade($mform)) {
 483                      $action = 'redirect';
 484                      $nextpageparams['action'] = 'savegradingresult';
 485                  }
 486              } else {
 487                  // Cancel button.
 488                  $action = 'redirect';
 489                  $nextpageparams['action'] = 'grading';
 490              }
 491          } else if ($action == 'quickgrade') {
 492              $message = $this->process_save_quick_grades();
 493              $action = 'quickgradingresult';
 494          } else if ($action == 'saveoptions') {
 495              $this->process_save_grading_options();
 496              $action = 'redirect';
 497              $nextpageparams['action'] = 'grading';
 498          } else if ($action == 'saveextension') {
 499              $action = 'grantextension';
 500              if ($this->process_save_extension($mform)) {
 501                  $action = 'redirect';
 502                  $nextpageparams['action'] = 'grading';
 503              }
 504          } else if ($action == 'revealidentitiesconfirm') {
 505              $this->process_reveal_identities();
 506              $action = 'redirect';
 507              $nextpageparams['action'] = 'grading';
 508          }
 509  
 510          $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT),
 511                                'useridlistid'=>optional_param('useridlistid', 0, PARAM_INT));
 512          $this->register_return_link($action, $returnparams);
 513  
 514          // Now show the right view page.
 515          if ($action == 'redirect') {
 516              $nextpageurl = new moodle_url('/mod/assign/view.php', $nextpageparams);
 517              redirect($nextpageurl);
 518              return;
 519          } else if ($action == 'savegradingresult') {
 520              $message = get_string('gradingchangessaved', 'assign');
 521              $o .= $this->view_savegrading_result($message);
 522          } else if ($action == 'quickgradingresult') {
 523              $mform = null;
 524              $o .= $this->view_quickgrading_result($message);
 525          } else if ($action == 'grade') {
 526              $o .= $this->view_single_grade_page($mform);
 527          } else if ($action == 'viewpluginassignfeedback') {
 528              $o .= $this->view_plugin_content('assignfeedback');
 529          } else if ($action == 'viewpluginassignsubmission') {
 530              $o .= $this->view_plugin_content('assignsubmission');
 531          } else if ($action == 'editsubmission') {
 532              $o .= $this->view_edit_submission_page($mform, $notices);
 533          } else if ($action == 'grading') {
 534              $o .= $this->view_grading_page();
 535          } else if ($action == 'downloadall') {
 536              $o .= $this->download_submissions();
 537          } else if ($action == 'submit') {
 538              $o .= $this->check_submit_for_grading($mform);
 539          } else if ($action == 'grantextension') {
 540              $o .= $this->view_grant_extension($mform);
 541          } else if ($action == 'revealidentities') {
 542              $o .= $this->view_reveal_identities_confirm($mform);
 543          } else if ($action == 'plugingradingbatchoperation') {
 544              $o .= $this->view_plugin_grading_batch_operation($mform);
 545          } else if ($action == 'viewpluginpage') {
 546               $o .= $this->view_plugin_page();
 547          } else if ($action == 'viewcourseindex') {
 548               $o .= $this->view_course_index();
 549          } else if ($action == 'viewbatchsetmarkingworkflowstate') {
 550               $o .= $this->view_batch_set_workflow_state($mform);
 551          } else if ($action == 'viewbatchmarkingallocation') {
 552              $o .= $this->view_batch_markingallocation($mform);
 553          } else if ($action == 'viewsubmitforgradingerror') {
 554              $o .= $this->view_error_page(get_string('submitforgrading', 'assign'), $notices);
 555          } else {
 556              $o .= $this->view_submission_page();
 557          }
 558  
 559          return $o;
 560      }
 561  
 562      /**
 563       * Add this instance to the database.
 564       *
 565       * @param stdClass $formdata The data submitted from the form
 566       * @param bool $callplugins This is used to skip the plugin code
 567       *             when upgrading an old assignment to a new one (the plugins get called manually)
 568       * @return mixed false if an error occurs or the int id of the new instance
 569       */
 570      public function add_instance(stdClass $formdata, $callplugins) {
 571          global $DB;
 572          $adminconfig = $this->get_admin_config();
 573  
 574          $err = '';
 575  
 576          // Add the database record.
 577          $update = new stdClass();
 578          $update->name = $formdata->name;
 579          $update->timemodified = time();
 580          $update->timecreated = time();
 581          $update->course = $formdata->course;
 582          $update->courseid = $formdata->course;
 583          $update->intro = $formdata->intro;
 584          $update->introformat = $formdata->introformat;
 585          $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
 586          $update->submissiondrafts = $formdata->submissiondrafts;
 587          $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
 588          $update->sendnotifications = $formdata->sendnotifications;
 589          $update->sendlatenotifications = $formdata->sendlatenotifications;
 590          $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
 591          if (isset($formdata->sendstudentnotifications)) {
 592              $update->sendstudentnotifications = $formdata->sendstudentnotifications;
 593          }
 594          $update->duedate = $formdata->duedate;
 595          $update->cutoffdate = $formdata->cutoffdate;
 596          $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
 597          $update->grade = $formdata->grade;
 598          $update->completionsubmit = !empty($formdata->completionsubmit);
 599          $update->teamsubmission = $formdata->teamsubmission;
 600          $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
 601          if (isset($formdata->teamsubmissiongroupingid)) {
 602              $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
 603          }
 604          $update->blindmarking = $formdata->blindmarking;
 605          $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
 606          if (!empty($formdata->attemptreopenmethod)) {
 607              $update->attemptreopenmethod = $formdata->attemptreopenmethod;
 608          }
 609          if (!empty($formdata->maxattempts)) {
 610              $update->maxattempts = $formdata->maxattempts;
 611          }
 612          $update->markingworkflow = $formdata->markingworkflow;
 613          $update->markingallocation = $formdata->markingallocation;
 614          if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
 615              $update->markingallocation = 0;
 616          }
 617  
 618          $returnid = $DB->insert_record('assign', $update);
 619          $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
 620          // Cache the course record.
 621          $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
 622  
 623          $this->save_intro_draft_files($formdata);
 624  
 625          if ($callplugins) {
 626              // Call save_settings hook for submission plugins.
 627              foreach ($this->submissionplugins as $plugin) {
 628                  if (!$this->update_plugin_instance($plugin, $formdata)) {
 629                      print_error($plugin->get_error());
 630                      return false;
 631                  }
 632              }
 633              foreach ($this->feedbackplugins as $plugin) {
 634                  if (!$this->update_plugin_instance($plugin, $formdata)) {
 635                      print_error($plugin->get_error());
 636                      return false;
 637                  }
 638              }
 639  
 640              // In the case of upgrades the coursemodule has not been set,
 641              // so we need to wait before calling these two.
 642              $this->update_calendar($formdata->coursemodule);
 643              $this->update_gradebook(false, $formdata->coursemodule);
 644  
 645          }
 646  
 647          $update = new stdClass();
 648          $update->id = $this->get_instance()->id;
 649          $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
 650          $DB->update_record('assign', $update);
 651  
 652          return $returnid;
 653      }
 654  
 655      /**
 656       * Delete all grades from the gradebook for this assignment.
 657       *
 658       * @return bool
 659       */
 660      protected function delete_grades() {
 661          global $CFG;
 662  
 663          $result = grade_update('mod/assign',
 664                                 $this->get_course()->id,
 665                                 'mod',
 666                                 'assign',
 667                                 $this->get_instance()->id,
 668                                 0,
 669                                 null,
 670                                 array('deleted'=>1));
 671          return $result == GRADE_UPDATE_OK;
 672      }
 673  
 674      /**
 675       * Delete this instance from the database.
 676       *
 677       * @return bool false if an error occurs
 678       */
 679      public function delete_instance() {
 680          global $DB;
 681          $result = true;
 682  
 683          foreach ($this->submissionplugins as $plugin) {
 684              if (!$plugin->delete_instance()) {
 685                  print_error($plugin->get_error());
 686                  $result = false;
 687              }
 688          }
 689          foreach ($this->feedbackplugins as $plugin) {
 690              if (!$plugin->delete_instance()) {
 691                  print_error($plugin->get_error());
 692                  $result = false;
 693              }
 694          }
 695  
 696          // Delete files associated with this assignment.
 697          $fs = get_file_storage();
 698          if (! $fs->delete_area_files($this->context->id) ) {
 699              $result = false;
 700          }
 701  
 702          // Delete_records will throw an exception if it fails - so no need for error checking here.
 703          $DB->delete_records('assign_submission', array('assignment' => $this->get_instance()->id));
 704          $DB->delete_records('assign_grades', array('assignment' => $this->get_instance()->id));
 705          $DB->delete_records('assign_plugin_config', array('assignment' => $this->get_instance()->id));
 706          $DB->delete_records('assign_user_flags', array('assignment' => $this->get_instance()->id));
 707          $DB->delete_records('assign_user_mapping', array('assignment' => $this->get_instance()->id));
 708  
 709          // Delete items from the gradebook.
 710          if (! $this->delete_grades()) {
 711              $result = false;
 712          }
 713  
 714          // Delete the instance.
 715          $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
 716  
 717          return $result;
 718      }
 719  
 720      /**
 721       * Actual implementation of the reset course functionality, delete all the
 722       * assignment submissions for course $data->courseid.
 723       *
 724       * @param stdClass $data the data submitted from the reset course.
 725       * @return array status array
 726       */
 727      public function reset_userdata($data) {
 728          global $CFG, $DB;
 729  
 730          $componentstr = get_string('modulenameplural', 'assign');
 731          $status = array();
 732  
 733          $fs = get_file_storage();
 734          if (!empty($data->reset_assign_submissions)) {
 735              // Delete files associated with this assignment.
 736              foreach ($this->submissionplugins as $plugin) {
 737                  $fileareas = array();
 738                  $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
 739                  $fileareas = $plugin->get_file_areas();
 740                  foreach ($fileareas as $filearea => $notused) {
 741                      $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
 742                  }
 743  
 744                  if (!$plugin->delete_instance()) {
 745                      $status[] = array('component'=>$componentstr,
 746                                        'item'=>get_string('deleteallsubmissions', 'assign'),
 747                                        'error'=>$plugin->get_error());
 748                  }
 749              }
 750  
 751              foreach ($this->feedbackplugins as $plugin) {
 752                  $fileareas = array();
 753                  $plugincomponent = $plugin->get_subtype() . '_' . $plugin->get_type();
 754                  $fileareas = $plugin->get_file_areas();
 755                  foreach ($fileareas as $filearea => $notused) {
 756                      $fs->delete_area_files($this->context->id, $plugincomponent, $filearea);
 757                  }
 758  
 759                  if (!$plugin->delete_instance()) {
 760                      $status[] = array('component'=>$componentstr,
 761                                        'item'=>get_string('deleteallsubmissions', 'assign'),
 762                                        'error'=>$plugin->get_error());
 763                  }
 764              }
 765  
 766              $assignids = $DB->get_records('assign', array('course' => $data->courseid), '', 'id');
 767              list($sql, $params) = $DB->get_in_or_equal(array_keys($assignids));
 768  
 769              $DB->delete_records_select('assign_submission', "assignment $sql", $params);
 770              $DB->delete_records_select('assign_user_flags', "assignment $sql", $params);
 771  
 772              $status[] = array('component'=>$componentstr,
 773                                'item'=>get_string('deleteallsubmissions', 'assign'),
 774                                'error'=>false);
 775  
 776              if (!empty($data->reset_gradebook_grades)) {
 777                  $DB->delete_records_select('assign_grades', "assignment $sql", $params);
 778                  // Remove all grades from gradebook.
 779                  require_once($CFG->dirroot.'/mod/assign/lib.php');
 780                  assign_reset_gradebook($data->courseid);
 781  
 782                  // Reset revealidentities if both submissions and grades have been reset.
 783                  if ($this->get_instance()->blindmarking && $this->get_instance()->revealidentities) {
 784                      $DB->set_field('assign', 'revealidentities', 0, array('id' => $this->get_instance()->id));
 785                  }
 786              }
 787          }
 788          // Updating dates - shift may be negative too.
 789          if ($data->timeshift) {
 790              shift_course_mod_dates('assign',
 791                                      array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'),
 792                                      $data->timeshift,
 793                                      $data->courseid, $this->get_instance()->id);
 794              $status[] = array('component'=>$componentstr,
 795                                'item'=>get_string('datechanged'),
 796                                'error'=>false);
 797          }
 798  
 799          return $status;
 800      }
 801  
 802      /**
 803       * Update the settings for a single plugin.
 804       *
 805       * @param assign_plugin $plugin The plugin to update
 806       * @param stdClass $formdata The form data
 807       * @return bool false if an error occurs
 808       */
 809      protected function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
 810          if ($plugin->is_visible()) {
 811              $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
 812              if (!empty($formdata->$enabledname)) {
 813                  $plugin->enable();
 814                  if (!$plugin->save_settings($formdata)) {
 815                      print_error($plugin->get_error());
 816                      return false;
 817                  }
 818              } else {
 819                  $plugin->disable();
 820              }
 821          }
 822          return true;
 823      }
 824  
 825      /**
 826       * Update the gradebook information for this assignment.
 827       *
 828       * @param bool $reset If true, will reset all grades in the gradbook for this assignment
 829       * @param int $coursemoduleid This is required because it might not exist in the database yet
 830       * @return bool
 831       */
 832      public function update_gradebook($reset, $coursemoduleid) {
 833          global $CFG;
 834  
 835          require_once($CFG->dirroot.'/mod/assign/lib.php');
 836          $assign = clone $this->get_instance();
 837          $assign->cmidnumber = $coursemoduleid;
 838  
 839          // Set assign gradebook feedback plugin status (enabled and visible).
 840          $assign->gradefeedbackenabled = $this->is_gradebook_feedback_enabled();
 841  
 842          $param = null;
 843          if ($reset) {
 844              $param = 'reset';
 845          }
 846  
 847          return assign_grade_item_update($assign, $param);
 848      }
 849  
 850      /**
 851       * Load and cache the admin config for this module.
 852       *
 853       * @return stdClass the plugin config
 854       */
 855      public function get_admin_config() {
 856          if ($this->adminconfig) {
 857              return $this->adminconfig;
 858          }
 859          $this->adminconfig = get_config('assign');
 860          return $this->adminconfig;
 861      }
 862  
 863      /**
 864       * Update the calendar entries for this assignment.
 865       *
 866       * @param int $coursemoduleid - Required to pass this in because it might
 867       *                              not exist in the database yet.
 868       * @return bool
 869       */
 870      public function update_calendar($coursemoduleid) {
 871          global $DB, $CFG;
 872          require_once($CFG->dirroot.'/calendar/lib.php');
 873  
 874          // Special case for add_instance as the coursemodule has not been set yet.
 875          $instance = $this->get_instance();
 876  
 877          if ($instance->duedate) {
 878              $event = new stdClass();
 879  
 880              $params = array('modulename'=>'assign', 'instance'=>$instance->id);
 881              $event->id = $DB->get_field('event', 'id', $params);
 882              $event->name = $instance->name;
 883              $event->timestart = $instance->duedate;
 884  
 885              // Convert the links to pluginfile. It is a bit hacky but at this stage the files
 886              // might not have been saved in the module area yet.
 887              $intro = $instance->intro;
 888              if ($draftid = file_get_submitted_draft_itemid('introeditor')) {
 889                  $intro = file_rewrite_urls_to_pluginfile($intro, $draftid);
 890              }
 891  
 892              // We need to remove the links to files as the calendar is not ready
 893              // to support module events with file areas.
 894              $intro = strip_pluginfile_content($intro);
 895              if ($this->show_intro()) {
 896                  $event->description = array(
 897                      'text' => $intro,
 898                      'format' => $instance->introformat
 899                  );
 900              } else {
 901                  $event->description = array(
 902                      'text' => '',
 903                      'format' => $instance->introformat
 904                  );
 905              }
 906  
 907              if ($event->id) {
 908                  $calendarevent = calendar_event::load($event->id);
 909                  $calendarevent->update($event);
 910              } else {
 911                  unset($event->id);
 912                  $event->courseid    = $instance->course;
 913                  $event->groupid     = 0;
 914                  $event->userid      = 0;
 915                  $event->modulename  = 'assign';
 916                  $event->instance    = $instance->id;
 917                  $event->eventtype   = 'due';
 918                  $event->timeduration = 0;
 919                  calendar_event::create($event);
 920              }
 921          } else {
 922              $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$instance->id));
 923          }
 924      }
 925  
 926  
 927      /**
 928       * Update this instance in the database.
 929       *
 930       * @param stdClass $formdata - the data submitted from the form
 931       * @return bool false if an error occurs
 932       */
 933      public function update_instance($formdata) {
 934          global $DB;
 935          $adminconfig = $this->get_admin_config();
 936  
 937          $update = new stdClass();
 938          $update->id = $formdata->instance;
 939          $update->name = $formdata->name;
 940          $update->timemodified = time();
 941          $update->course = $formdata->course;
 942          $update->intro = $formdata->intro;
 943          $update->introformat = $formdata->introformat;
 944          $update->alwaysshowdescription = !empty($formdata->alwaysshowdescription);
 945          $update->submissiondrafts = $formdata->submissiondrafts;
 946          $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
 947          $update->sendnotifications = $formdata->sendnotifications;
 948          $update->sendlatenotifications = $formdata->sendlatenotifications;
 949          $update->sendstudentnotifications = $adminconfig->sendstudentnotifications;
 950          if (isset($formdata->sendstudentnotifications)) {
 951              $update->sendstudentnotifications = $formdata->sendstudentnotifications;
 952          }
 953          $update->duedate = $formdata->duedate;
 954          $update->cutoffdate = $formdata->cutoffdate;
 955          $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
 956          $update->grade = $formdata->grade;
 957          if (!empty($formdata->completionunlocked)) {
 958              $update->completionsubmit = !empty($formdata->completionsubmit);
 959          }
 960          $update->teamsubmission = $formdata->teamsubmission;
 961          $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
 962          if (isset($formdata->teamsubmissiongroupingid)) {
 963              $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
 964          }
 965          $update->blindmarking = $formdata->blindmarking;
 966          $update->attemptreopenmethod = ASSIGN_ATTEMPT_REOPEN_METHOD_NONE;
 967          if (!empty($formdata->attemptreopenmethod)) {
 968              $update->attemptreopenmethod = $formdata->attemptreopenmethod;
 969          }
 970          if (!empty($formdata->maxattempts)) {
 971              $update->maxattempts = $formdata->maxattempts;
 972          }
 973          $update->markingworkflow = $formdata->markingworkflow;
 974          $update->markingallocation = $formdata->markingallocation;
 975          if (empty($update->markingworkflow)) { // If marking workflow is disabled, make sure allocation is disabled.
 976              $update->markingallocation = 0;
 977          }
 978  
 979          $result = $DB->update_record('assign', $update);
 980          $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
 981  
 982          $this->save_intro_draft_files($formdata);
 983  
 984          // Load the assignment so the plugins have access to it.
 985  
 986          // Call save_settings hook for submission plugins.
 987          foreach ($this->submissionplugins as $plugin) {
 988              if (!$this->update_plugin_instance($plugin, $formdata)) {
 989                  print_error($plugin->get_error());
 990                  return false;
 991              }
 992          }
 993          foreach ($this->feedbackplugins as $plugin) {
 994              if (!$this->update_plugin_instance($plugin, $formdata)) {
 995                  print_error($plugin->get_error());
 996                  return false;
 997              }
 998          }
 999  
1000          $this->update_calendar($this->get_course_module()->id);
1001          $this->update_gradebook(false, $this->get_course_module()->id);
1002  
1003          $update = new stdClass();
1004          $update->id = $this->get_instance()->id;
1005          $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
1006          $DB->update_record('assign', $update);
1007  
1008          return $result;
1009      }
1010  
1011      /**
1012       * Save the attachments in the draft areas.
1013       *
1014       * @param stdClass $formdata
1015       */
1016      protected function save_intro_draft_files($formdata) {
1017          if (isset($formdata->introattachments)) {
1018              file_save_draft_area_files($formdata->introattachments, $this->get_context()->id,
1019                                         'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA, 0);
1020          }
1021      }
1022  
1023      /**
1024       * Add elements in grading plugin form.
1025       *
1026       * @param mixed $grade stdClass|null
1027       * @param MoodleQuickForm $mform
1028       * @param stdClass $data
1029       * @param int $userid - The userid we are grading
1030       * @return void
1031       */
1032      protected function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
1033          foreach ($this->feedbackplugins as $plugin) {
1034              if ($plugin->is_enabled() && $plugin->is_visible()) {
1035                  $plugin->get_form_elements_for_user($grade, $mform, $data, $userid);
1036              }
1037          }
1038      }
1039  
1040  
1041  
1042      /**
1043       * Add one plugins settings to edit plugin form.
1044       *
1045       * @param assign_plugin $plugin The plugin to add the settings from
1046       * @param MoodleQuickForm $mform The form to add the configuration settings to.
1047       *                               This form is modified directly (not returned).
1048       * @param array $pluginsenabled A list of form elements to be added to a group.
1049       *                              The new element is added to this array by this function.
1050       * @return void
1051       */
1052      protected function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform, & $pluginsenabled) {
1053          global $CFG;
1054          if ($plugin->is_visible() && !$plugin->is_configurable() && $plugin->is_enabled()) {
1055              $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
1056              $pluginsenabled[] = $mform->createElement('hidden', $name, 1);
1057              $mform->setType($name, PARAM_BOOL);
1058              $plugin->get_settings($mform);
1059          } else if ($plugin->is_visible() && $plugin->is_configurable()) {
1060              $name = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
1061              $label = $plugin->get_name();
1062              $label .= ' ' . $this->get_renderer()->help_icon('enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
1063              $pluginsenabled[] = $mform->createElement('checkbox', $name, '', $label);
1064  
1065              $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
1066              if ($plugin->get_config('enabled') !== false) {
1067                  $default = $plugin->is_enabled();
1068              }
1069              $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
1070  
1071              $plugin->get_settings($mform);
1072  
1073          }
1074      }
1075  
1076      /**
1077       * Add settings to edit plugin form.
1078       *
1079       * @param MoodleQuickForm $mform The form to add the configuration settings to.
1080       *                               This form is modified directly (not returned).
1081       * @return void
1082       */
1083      public function add_all_plugin_settings(MoodleQuickForm $mform) {
1084          $mform->addElement('header', 'submissiontypes', get_string('submissiontypes', 'assign'));
1085  
1086          $submissionpluginsenabled = array();
1087          $group = $mform->addGroup(array(), 'submissionplugins', get_string('submissiontypes', 'assign'), array(' '), false);
1088          foreach ($this->submissionplugins as $plugin) {
1089              $this->add_plugin_settings($plugin, $mform, $submissionpluginsenabled);
1090          }
1091          $group->setElements($submissionpluginsenabled);
1092  
1093          $mform->addElement('header', 'feedbacktypes', get_string('feedbacktypes', 'assign'));
1094          $feedbackpluginsenabled = array();
1095          $group = $mform->addGroup(array(), 'feedbackplugins', get_string('feedbacktypes', 'assign'), array(' '), false);
1096          foreach ($this->feedbackplugins as $plugin) {
1097              $this->add_plugin_settings($plugin, $mform, $feedbackpluginsenabled);
1098          }
1099          $group->setElements($feedbackpluginsenabled);
1100          $mform->setExpanded('submissiontypes');
1101      }
1102  
1103      /**
1104       * Allow each plugin an opportunity to update the defaultvalues
1105       * passed in to the settings form (needed to set up draft areas for
1106       * editor and filemanager elements)
1107       *
1108       * @param array $defaultvalues
1109       */
1110      public function plugin_data_preprocessing(&$defaultvalues) {
1111          foreach ($this->submissionplugins as $plugin) {
1112              if ($plugin->is_visible()) {
1113                  $plugin->data_preprocessing($defaultvalues);
1114              }
1115          }
1116          foreach ($this->feedbackplugins as $plugin) {
1117              if ($plugin->is_visible()) {
1118                  $plugin->data_preprocessing($defaultvalues);
1119              }
1120          }
1121      }
1122  
1123      /**
1124       * Get the name of the current module.
1125       *
1126       * @return string the module name (Assignment)
1127       */
1128      protected function get_module_name() {
1129          if (isset(self::$modulename)) {
1130              return self::$modulename;
1131          }
1132          self::$modulename = get_string('modulename', 'assign');
1133          return self::$modulename;
1134      }
1135  
1136      /**
1137       * Get the plural name of the current module.
1138       *
1139       * @return string the module name plural (Assignments)
1140       */
1141      protected function get_module_name_plural() {
1142          if (isset(self::$modulenameplural)) {
1143              return self::$modulenameplural;
1144          }
1145          self::$modulenameplural = get_string('modulenameplural', 'assign');
1146          return self::$modulenameplural;
1147      }
1148  
1149      /**
1150       * Has this assignment been constructed from an instance?
1151       *
1152       * @return bool
1153       */
1154      public function has_instance() {
1155          return $this->instance || $this->get_course_module();
1156      }
1157  
1158      /**
1159       * Get the settings for the current instance of this assignment
1160       *
1161       * @return stdClass The settings
1162       */
1163      public function get_instance() {
1164          global $DB;
1165          if ($this->instance) {
1166              return $this->instance;
1167          }
1168          if ($this->get_course_module()) {
1169              $params = array('id' => $this->get_course_module()->instance);
1170              $this->instance = $DB->get_record('assign', $params, '*', MUST_EXIST);
1171          }
1172          if (!$this->instance) {
1173              throw new coding_exception('Improper use of the assignment class. ' .
1174                                         'Cannot load the assignment record.');
1175          }
1176          return $this->instance;
1177      }
1178  
1179      /**
1180       * Get the primary grade item for this assign instance.
1181       *
1182       * @return stdClass The grade_item record
1183       */
1184      public function get_grade_item() {
1185          if ($this->gradeitem) {
1186              return $this->gradeitem;
1187          }
1188          $instance = $this->get_instance();
1189          $params = array('itemtype' => 'mod',
1190                          'itemmodule' => 'assign',
1191                          'iteminstance' => $instance->id,
1192                          'courseid' => $instance->course,
1193                          'itemnumber' => 0);
1194          $this->gradeitem = grade_item::fetch($params);
1195          if (!$this->gradeitem) {
1196              throw new coding_exception('Improper use of the assignment class. ' .
1197                                         'Cannot load the grade item.');
1198          }
1199          return $this->gradeitem;
1200      }
1201  
1202      /**
1203       * Get the context of the current course.
1204       *
1205       * @return mixed context|null The course context
1206       */
1207      public function get_course_context() {
1208          if (!$this->context && !$this->course) {
1209              throw new coding_exception('Improper use of the assignment class. ' .
1210                                         'Cannot load the course context.');
1211          }
1212          if ($this->context) {
1213              return $this->context->get_course_context();
1214          } else {
1215              return context_course::instance($this->course->id);
1216          }
1217      }
1218  
1219  
1220      /**
1221       * Get the current course module.
1222       *
1223       * @return cm_info|null The course module or null if not known
1224       */
1225      public function get_course_module() {
1226          if ($this->coursemodule) {
1227              return $this->coursemodule;
1228          }
1229          if (!$this->context) {
1230              return null;
1231          }
1232  
1233          if ($this->context->contextlevel == CONTEXT_MODULE) {
1234              $modinfo = get_fast_modinfo($this->get_course());
1235              $this->coursemodule = $modinfo->get_cm($this->context->instanceid);
1236              return $this->coursemodule;
1237          }
1238          return null;
1239      }
1240  
1241      /**
1242       * Get context module.
1243       *
1244       * @return context
1245       */
1246      public function get_context() {
1247          return $this->context;
1248      }
1249  
1250      /**
1251       * Get the current course.
1252       *
1253       * @return mixed stdClass|null The course
1254       */
1255      public function get_course() {
1256          global $DB;
1257  
1258          if ($this->course) {
1259              return $this->course;
1260          }
1261  
1262          if (!$this->context) {
1263              return null;
1264          }
1265          $params = array('id' => $this->get_course_context()->instanceid);
1266          $this->course = $DB->get_record('course', $params, '*', MUST_EXIST);
1267  
1268          return $this->course;
1269      }
1270  
1271      /**
1272       * Count the number of intro attachments.
1273       *
1274       * @return int
1275       */
1276      protected function count_attachments() {
1277  
1278          $fs = get_file_storage();
1279          $files = $fs->get_area_files($this->get_context()->id, 'mod_assign', ASSIGN_INTROATTACHMENT_FILEAREA,
1280                          0, 'id', false);
1281  
1282          return count($files);
1283      }
1284  
1285      /**
1286       * Are there any intro attachments to display?
1287       *
1288       * @return boolean
1289       */
1290      protected function has_visible_attachments() {
1291          return ($this->count_attachments() > 0);
1292      }
1293  
1294      /**
1295       * Return a grade in user-friendly form, whether it's a scale or not.
1296       *
1297       * @param mixed $grade int|null
1298       * @param boolean $editing Are we allowing changes to this grade?
1299       * @param int $userid The user id the grade belongs to
1300       * @param int $modified Timestamp from when the grade was last modified
1301       * @return string User-friendly representation of grade
1302       */
1303      public function display_grade($grade, $editing, $userid=0, $modified=0) {
1304          global $DB;
1305  
1306          static $scalegrades = array();
1307  
1308          $o = '';
1309  
1310          if ($this->get_instance()->grade >= 0) {
1311              // Normal number.
1312              if ($editing && $this->get_instance()->grade > 0) {
1313                  if ($grade < 0) {
1314                      $displaygrade = '';
1315                  } else {
1316                      $displaygrade = format_float($grade, 2);
1317                  }
1318                  $o .= '<label class="accesshide" for="quickgrade_' . $userid . '">' .
1319                         get_string('usergrade', 'assign') .
1320                         '</label>';
1321                  $o .= '<input type="text"
1322                                id="quickgrade_' . $userid . '"
1323                                name="quickgrade_' . $userid . '"
1324                                value="' .  $displaygrade . '"
1325                                size="6"
1326                                maxlength="10"
1327                                class="quickgrade"/>';
1328                  $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
1329                  return $o;
1330              } else {
1331                  if ($grade == -1 || $grade === null) {
1332                      $o .= '-';
1333                  } else {
1334                      $item = $this->get_grade_item();
1335                      $o .= grade_format_gradevalue($grade, $item);
1336                      if ($item->get_displaytype() == GRADE_DISPLAY_TYPE_REAL) {
1337                          // If displaying the raw grade, also display the total value.
1338                          $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade, 2);
1339                      }
1340                  }
1341                  return $o;
1342              }
1343  
1344          } else {
1345              // Scale.
1346              if (empty($this->cache['scale'])) {
1347                  if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
1348                      $this->cache['scale'] = make_menu_from_list($scale->scale);
1349                  } else {
1350                      $o .= '-';
1351                      return $o;
1352                  }
1353              }
1354              if ($editing) {
1355                  $o .= '<label class="accesshide"
1356                                for="quickgrade_' . $userid . '">' .
1357                        get_string('usergrade', 'assign') .
1358                        '</label>';
1359                  $o .= '<select name="quickgrade_' . $userid . '" class="quickgrade">';
1360                  $o .= '<option value="-1">' . get_string('nograde') . '</option>';
1361                  foreach ($this->cache['scale'] as $optionid => $option) {
1362                      $selected = '';
1363                      if ($grade == $optionid) {
1364                          $selected = 'selected="selected"';
1365                      }
1366                      $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
1367                  }
1368                  $o .= '</select>';
1369                  return $o;
1370              } else {
1371                  $scaleid = (int)$grade;
1372                  if (isset($this->cache['scale'][$scaleid])) {
1373                      $o .= $this->cache['scale'][$scaleid];
1374                      return $o;
1375                  }
1376                  $o .= '-';
1377                  return $o;
1378              }
1379          }
1380      }
1381  
1382      /**
1383       * Load a list of users enrolled in the current course with the specified permission and group.
1384       * 0 for no group.
1385       *
1386       * @param int $currentgroup
1387       * @param bool $idsonly
1388       * @return array List of user records
1389       */
1390      public function list_participants($currentgroup, $idsonly) {
1391  
1392          if (empty($currentgroup)) {
1393              $currentgroup = 0;
1394          }
1395  
1396          $key = $this->context->id . '-' . $currentgroup . '-' . $this->show_only_active_users();
1397          if (!isset($this->participants[$key])) {
1398              $users = get_enrolled_users($this->context, 'mod/assign:submit', $currentgroup, 'u.*', null, null, null,
1399                      $this->show_only_active_users());
1400  
1401              $cm = $this->get_course_module();
1402              $info = new \core_availability\info_module($cm);
1403              $users = $info->filter_user_list($users);
1404  
1405              $this->participants[$key] = $users;
1406          }
1407  
1408          if ($idsonly) {
1409              $idslist = array();
1410              foreach ($this->participants[$key] as $id => $user) {
1411                  $idslist[$id] = new stdClass();
1412                  $idslist[$id]->id = $id;
1413              }
1414              return $idslist;
1415          }
1416          return $this->participants[$key];
1417      }
1418  
1419      /**
1420       * Load a count of valid teams for this assignment.
1421       *
1422       * @param int $activitygroup Activity active group
1423       * @return int number of valid teams
1424       */
1425      public function count_teams($activitygroup = 0) {
1426  
1427          $count = 0;
1428  
1429          $participants = $this->list_participants($activitygroup, true);
1430  
1431          // If a team submission grouping id is provided all good as all returned groups
1432          // are the submission teams, but if no team submission grouping was specified
1433          // $groups will contain all participants groups.
1434          if ($this->get_instance()->teamsubmissiongroupingid) {
1435  
1436              // We restrict the users to the selected group ones.
1437              $groups = groups_get_all_groups($this->get_course()->id,
1438                                              array_keys($participants),
1439                                              $this->get_instance()->teamsubmissiongroupingid,
1440                                              'DISTINCT g.id, g.name');
1441  
1442              $count = count($groups);
1443  
1444              // When a specific group is selected we don't count the default group users.
1445              if ($activitygroup == 0) {
1446  
1447                  // See if there are any users in the default group.
1448                  $defaultusers = $this->get_submission_group_members(0, true);
1449                  if (count($defaultusers) > 0) {
1450                      $count += 1;
1451                  }
1452              }
1453          } else {
1454              // It is faster to loop around participants if no grouping was specified.
1455              $groups = array();
1456              foreach ($participants as $participant) {
1457                  if ($group = $this->get_submission_group($participant->id)) {
1458                      $groups[$group->id] = true;
1459                  } else {
1460                      $groups[0] = true;
1461                  }
1462              }
1463  
1464              $count = count($groups);
1465          }
1466  
1467          return $count;
1468      }
1469  
1470      /**
1471       * Load a count of active users enrolled in the current course with the specified permission and group.
1472       * 0 for no group.
1473       *
1474       * @param int $currentgroup
1475       * @return int number of matching users
1476       */
1477      public function count_participants($currentgroup) {
1478          return count($this->list_participants($currentgroup, true));
1479      }
1480  
1481      /**
1482       * Load a count of active users submissions in the current module that require grading
1483       * This means the submission modification time is more recent than the
1484       * grading modification time and the status is SUBMITTED.
1485       *
1486       * @return int number of matching submissions
1487       */
1488      public function count_submissions_need_grading() {
1489          global $DB;
1490  
1491          if ($this->get_instance()->teamsubmission) {
1492              // This does not make sense for group assignment because the submission is shared.
1493              return 0;
1494          }
1495  
1496          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1497          list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1498  
1499          $params['assignid'] = $this->get_instance()->id;
1500          $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1501  
1502          $sql = 'SELECT COUNT(s.userid)
1503                     FROM {assign_submission} s
1504                     LEFT JOIN {assign_grades} g ON
1505                          s.assignment = g.assignment AND
1506                          s.userid = g.userid AND
1507                          g.attemptnumber = s.attemptnumber
1508                     JOIN(' . $esql . ') e ON e.id = s.userid
1509                     WHERE
1510                          s.latest = 1 AND
1511                          s.assignment = :assignid AND
1512                          s.timemodified IS NOT NULL AND
1513                          s.status = :submitted AND
1514                          (s.timemodified >= g.timemodified OR g.timemodified IS NULL OR g.grade IS NULL)';
1515  
1516          return $DB->count_records_sql($sql, $params);
1517      }
1518  
1519      /**
1520       * Load a count of grades.
1521       *
1522       * @return int number of grades
1523       */
1524      public function count_grades() {
1525          global $DB;
1526  
1527          if (!$this->has_instance()) {
1528              return 0;
1529          }
1530  
1531          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1532          list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1533  
1534          $params['assignid'] = $this->get_instance()->id;
1535  
1536          $sql = 'SELECT COUNT(g.userid)
1537                     FROM {assign_grades} g
1538                     JOIN(' . $esql . ') e ON e.id = g.userid
1539                     WHERE g.assignment = :assignid';
1540  
1541          return $DB->count_records_sql($sql, $params);
1542      }
1543  
1544      /**
1545       * Load a count of submissions.
1546       *
1547       * @param bool $includenew When true, also counts the submissions with status 'new'.
1548       * @return int number of submissions
1549       */
1550      public function count_submissions($includenew = false) {
1551          global $DB;
1552  
1553          if (!$this->has_instance()) {
1554              return 0;
1555          }
1556  
1557          $params = array();
1558          $sqlnew = '';
1559  
1560          if (!$includenew) {
1561              $sqlnew = ' AND s.status <> :status ';
1562              $params['status'] = ASSIGN_SUBMISSION_STATUS_NEW;
1563          }
1564  
1565          if ($this->get_instance()->teamsubmission) {
1566              // We cannot join on the enrolment tables for group submissions (no userid).
1567              $sql = 'SELECT COUNT(DISTINCT s.groupid)
1568                          FROM {assign_submission} s
1569                          WHERE
1570                              s.assignment = :assignid AND
1571                              s.timemodified IS NOT NULL AND
1572                              s.userid = :groupuserid' .
1573                              $sqlnew;
1574  
1575              $params['assignid'] = $this->get_instance()->id;
1576              $params['groupuserid'] = 0;
1577          } else {
1578              $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1579              list($esql, $enrolparams) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1580  
1581              $params = array_merge($params, $enrolparams);
1582              $params['assignid'] = $this->get_instance()->id;
1583  
1584              $sql = 'SELECT COUNT(DISTINCT s.userid)
1585                         FROM {assign_submission} s
1586                         JOIN(' . $esql . ') e ON e.id = s.userid
1587                         WHERE
1588                              s.assignment = :assignid AND
1589                              s.timemodified IS NOT NULL ' .
1590                              $sqlnew;
1591  
1592          }
1593  
1594          return $DB->count_records_sql($sql, $params);
1595      }
1596  
1597      /**
1598       * Load a count of submissions with a specified status.
1599       *
1600       * @param string $status The submission status - should match one of the constants
1601       * @return int number of matching submissions
1602       */
1603      public function count_submissions_with_status($status) {
1604          global $DB;
1605  
1606          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
1607          list($esql, $params) = get_enrolled_sql($this->get_context(), 'mod/assign:submit', $currentgroup, true);
1608  
1609          $params['assignid'] = $this->get_instance()->id;
1610          $params['assignid2'] = $this->get_instance()->id;
1611          $params['submissionstatus'] = $status;
1612  
1613          if ($this->get_instance()->teamsubmission) {
1614  
1615              $groupsstr = '';
1616              if ($currentgroup != 0) {
1617                  // If there is an active group we should only display the current group users groups.
1618                  $participants = $this->list_participants($currentgroup, true);
1619                  $groups = groups_get_all_groups($this->get_course()->id,
1620                                                  array_keys($participants),
1621                                                  $this->get_instance()->teamsubmissiongroupingid,
1622                                                  'DISTINCT g.id, g.name');
1623                  list($groupssql, $groupsparams) = $DB->get_in_or_equal(array_keys($groups), SQL_PARAMS_NAMED);
1624                  $groupsstr = 's.groupid ' . $groupssql . ' AND';
1625                  $params = $params + $groupsparams;
1626              }
1627              $sql = 'SELECT COUNT(s.groupid)
1628                          FROM {assign_submission} s
1629                          WHERE
1630                              s.latest = 1 AND
1631                              s.assignment = :assignid AND
1632                              s.timemodified IS NOT NULL AND
1633                              s.userid = :groupuserid AND '
1634                              . $groupsstr . '
1635                              s.status = :submissionstatus';
1636              $params['groupuserid'] = 0;
1637          } else {
1638              $sql = 'SELECT COUNT(s.userid)
1639                          FROM {assign_submission} s
1640                          JOIN(' . $esql . ') e ON e.id = s.userid
1641                          WHERE
1642                              s.latest = 1 AND
1643                              s.assignment = :assignid AND
1644                              s.timemodified IS NOT NULL AND
1645                              s.status = :submissionstatus';
1646  
1647          }
1648  
1649          return $DB->count_records_sql($sql, $params);
1650      }
1651  
1652      /**
1653       * Utility function to get the userid for every row in the grading table
1654       * so the order can be frozen while we iterate it.
1655       *
1656       * @return array An array of userids
1657       */
1658      protected function get_grading_userid_list() {
1659          $filter = get_user_preferences('assign_filter', '');
1660          $table = new assign_grading_table($this, 0, $filter, 0, false);
1661  
1662          $useridlist = $table->get_column_data('userid');
1663  
1664          return $useridlist;
1665      }
1666  
1667      /**
1668       * Generate zip file from array of given files.
1669       *
1670       * @param array $filesforzipping - array of files to pass into archive_to_pathname.
1671       *                                 This array is indexed by the final file name and each
1672       *                                 element in the array is an instance of a stored_file object.
1673       * @return path of temp file - note this returned file does
1674       *         not have a .zip extension - it is a temp file.
1675       */
1676      protected function pack_files($filesforzipping) {
1677          global $CFG;
1678          // Create path for new zip file.
1679          $tempzip = tempnam($CFG->tempdir . '/', 'assignment_');
1680          // Zip files.
1681          $zipper = new zip_packer();
1682          if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
1683              return $tempzip;
1684          }
1685          return false;
1686      }
1687  
1688      /**
1689       * Finds all assignment notifications that have yet to be mailed out, and mails them.
1690       *
1691       * Cron function to be run periodically according to the moodle cron.
1692       *
1693       * @return bool
1694       */
1695      public static function cron() {
1696          global $DB;
1697  
1698          // Only ever send a max of one days worth of updates.
1699          $yesterday = time() - (24 * 3600);
1700          $timenow   = time();
1701          $lastcron = $DB->get_field('modules', 'lastcron', array('name' => 'assign'));
1702  
1703          // Collect all submissions that require mailing.
1704          // Submissions are included if all are true:
1705          //   - The assignment is visible in the gradebook.
1706          //   - No previous notification has been sent.
1707          //   - If marking workflow is not enabled, the grade was updated in the past 24 hours, or
1708          //     if marking workflow is enabled, the workflow state is at 'released'.
1709          $sql = "SELECT g.id as gradeid, a.course, a.name, a.blindmarking, a.revealidentities,
1710                         g.*, g.timemodified as lastmodified, cm.id as cmid
1711                   FROM {assign} a
1712                   JOIN {assign_grades} g ON g.assignment = a.id
1713              LEFT JOIN {assign_user_flags} uf ON uf.assignment = a.id AND uf.userid = g.userid
1714                   JOIN {course_modules} cm ON cm.course = a.course AND cm.instance = a.id
1715                   JOIN {modules} md ON md.id = cm.module AND md.name = 'assign'
1716                   JOIN {grade_items} gri ON gri.iteminstance = a.id AND gri.courseid = a.course AND gri.itemmodule = md.name
1717                   WHERE ((a.markingworkflow = 0 AND g.timemodified >= :yesterday AND g.timemodified <= :today) OR
1718                          (a.markingworkflow = 1 AND uf.workflowstate = :wfreleased)) AND
1719                         uf.mailed = 0 AND gri.hidden = 0
1720                ORDER BY a.course, cm.id";
1721  
1722          $params = array(
1723              'yesterday' => $yesterday,
1724              'today' => $timenow,
1725              'wfreleased' => ASSIGN_MARKING_WORKFLOW_STATE_RELEASED,
1726          );
1727          $submissions = $DB->get_records_sql($sql, $params);
1728  
1729          if (!empty($submissions)) {
1730  
1731              mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
1732  
1733              // Preload courses we are going to need those.
1734              $courseids = array();
1735              foreach ($submissions as $submission) {
1736                  $courseids[] = $submission->course;
1737              }
1738  
1739              // Filter out duplicates.
1740              $courseids = array_unique($courseids);
1741              $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
1742              list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
1743              $sql = 'SELECT c.*, ' . $ctxselect .
1744                        ' FROM {course} c
1745                   LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
1746                       WHERE c.id ' . $courseidsql;
1747  
1748              $params['contextlevel'] = CONTEXT_COURSE;
1749              $courses = $DB->get_records_sql($sql, $params);
1750  
1751              // Clean up... this could go on for a while.
1752              unset($courseids);
1753              unset($ctxselect);
1754              unset($courseidsql);
1755              unset($params);
1756  
1757              // Message students about new feedback.
1758              foreach ($submissions as $submission) {
1759  
1760                  mtrace("Processing assignment submission $submission->id ...");
1761  
1762                  // Do not cache user lookups - could be too many.
1763                  if (!$user = $DB->get_record('user', array('id'=>$submission->userid))) {
1764                      mtrace('Could not find user ' . $submission->userid);
1765                      continue;
1766                  }
1767  
1768                  // Use a cache to prevent the same DB queries happening over and over.
1769                  if (!array_key_exists($submission->course, $courses)) {
1770                      mtrace('Could not find course ' . $submission->course);
1771                      continue;
1772                  }
1773                  $course = $courses[$submission->course];
1774                  if (isset($course->ctxid)) {
1775                      // Context has not yet been preloaded. Do so now.
1776                      context_helper::preload_from_record($course);
1777                  }
1778  
1779                  // Override the language and timezone of the "current" user, so that
1780                  // mail is customised for the receiver.
1781                  cron_setup_user($user, $course);
1782  
1783                  // Context lookups are already cached.
1784                  $coursecontext = context_course::instance($course->id);
1785                  if (!is_enrolled($coursecontext, $user->id)) {
1786                      $courseshortname = format_string($course->shortname,
1787                                                       true,
1788                                                       array('context' => $coursecontext));
1789                      mtrace(fullname($user) . ' not an active participant in ' . $courseshortname);
1790                      continue;
1791                  }
1792  
1793                  if (!$grader = $DB->get_record('user', array('id'=>$submission->grader))) {
1794                      mtrace('Could not find grader ' . $submission->grader);
1795                      continue;
1796                  }
1797  
1798                  $modinfo = get_fast_modinfo($course, $user->id);
1799                  $cm = $modinfo->get_cm($submission->cmid);
1800                  // Context lookups are already cached.
1801                  $contextmodule = context_module::instance($cm->id);
1802  
1803                  if (!$cm->uservisible) {
1804                      // Hold mail notification for assignments the user cannot access until later.
1805                      continue;
1806                  }
1807  
1808                  // Need to send this to the student.
1809                  $messagetype = 'feedbackavailable';
1810                  $eventtype = 'assign_notification';
1811                  $updatetime = $submission->lastmodified;
1812                  $modulename = get_string('modulename', 'assign');
1813  
1814                  $uniqueid = 0;
1815                  if ($submission->blindmarking && !$submission->revealidentities) {
1816                      $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
1817                  }
1818                  $showusers = $submission->blindmarking && !$submission->revealidentities;
1819                  self::send_assignment_notification($grader,
1820                                                     $user,
1821                                                     $messagetype,
1822                                                     $eventtype,
1823                                                     $updatetime,
1824                                                     $cm,
1825                                                     $contextmodule,
1826                                                     $course,
1827                                                     $modulename,
1828                                                     $submission->name,
1829                                                     $showusers,
1830                                                     $uniqueid);
1831  
1832                  $flags = $DB->get_record('assign_user_flags', array('userid'=>$user->id, 'assignment'=>$submission->assignment));
1833                  if ($flags) {
1834                      $flags->mailed = 1;
1835                      $DB->update_record('assign_user_flags', $flags);
1836                  } else {
1837                      $flags = new stdClass();
1838                      $flags->userid = $user->id;
1839                      $flags->assignment = $submission->assignment;
1840                      $flags->mailed = 1;
1841                      $DB->insert_record('assign_user_flags', $flags);
1842                  }
1843  
1844                  mtrace('Done');
1845              }
1846              mtrace('Done processing ' . count($submissions) . ' assignment submissions');
1847  
1848              cron_setup_user();
1849  
1850              // Free up memory just to be sure.
1851              unset($courses);
1852          }
1853  
1854          // Update calendar events to provide a description.
1855          $sql = 'SELECT id
1856                      FROM {assign}
1857                      WHERE
1858                          allowsubmissionsfromdate >= :lastcron AND
1859                          allowsubmissionsfromdate <= :timenow AND
1860                          alwaysshowdescription = 0';
1861          $params = array('lastcron' => $lastcron, 'timenow' => $timenow);
1862          $newlyavailable = $DB->get_records_sql($sql, $params);
1863          foreach ($newlyavailable as $record) {
1864              $cm = get_coursemodule_from_instance('assign', $record->id, 0, false, MUST_EXIST);
1865              $context = context_module::instance($cm->id);
1866  
1867              $assignment = new assign($context, null, null);
1868              $assignment->update_calendar($cm->id);
1869          }
1870  
1871          return true;
1872      }
1873  
1874      /**
1875       * Mark in the database that this grade record should have an update notification sent by cron.
1876       *
1877       * @param stdClass $grade a grade record keyed on id
1878       * @param bool $mailedoverride when true, flag notification to be sent again.
1879       * @return bool true for success
1880       */
1881      public function notify_grade_modified($grade, $mailedoverride = false) {
1882          global $DB;
1883  
1884          $flags = $this->get_user_flags($grade->userid, true);
1885          if ($flags->mailed != 1 || $mailedoverride) {
1886              $flags->mailed = 0;
1887          }
1888  
1889          return $this->update_user_flags($flags);
1890      }
1891  
1892      /**
1893       * Update user flags for this user in this assignment.
1894       *
1895       * @param stdClass $flags a flags record keyed on id
1896       * @return bool true for success
1897       */
1898      public function update_user_flags($flags) {
1899          global $DB;
1900          if ($flags->userid <= 0 || $flags->assignment <= 0 || $flags->id <= 0) {
1901              return false;
1902          }
1903  
1904          $result = $DB->update_record('assign_user_flags', $flags);
1905          return $result;
1906      }
1907  
1908      /**
1909       * Update a grade in the grade table for the assignment and in the gradebook.
1910       *
1911       * @param stdClass $grade a grade record keyed on id
1912       * @param bool $reopenattempt If the attempt reopen method is manual, allow another attempt at this assignment.
1913       * @return bool true for success
1914       */
1915      public function update_grade($grade, $reopenattempt = false) {
1916          global $DB;
1917  
1918          $grade->timemodified = time();
1919  
1920          if (!empty($grade->workflowstate)) {
1921              $validstates = $this->get_marking_workflow_states_for_current_user();
1922              if (!array_key_exists($grade->workflowstate, $validstates)) {
1923                  return false;
1924              }
1925          }
1926  
1927          if ($grade->grade && $grade->grade != -1) {
1928              if ($this->get_instance()->grade > 0) {
1929                  if (!is_numeric($grade->grade)) {
1930                      return false;
1931                  } else if ($grade->grade > $this->get_instance()->grade) {
1932                      return false;
1933                  } else if ($grade->grade < 0) {
1934                      return false;
1935                  }
1936              } else {
1937                  // This is a scale.
1938                  if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
1939                      $scaleoptions = make_menu_from_list($scale->scale);
1940                      if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
1941                          return false;
1942                      }
1943                  }
1944              }
1945          }
1946  
1947          if (empty($grade->attemptnumber)) {
1948              // Set it to the default.
1949              $grade->attemptnumber = 0;
1950          }
1951          $DB->update_record('assign_grades', $grade);
1952  
1953          $submission = null;
1954          if ($this->get_instance()->teamsubmission) {
1955              $submission = $this->get_group_submission($grade->userid, 0, false);
1956          } else {
1957              $submission = $this->get_user_submission($grade->userid, false);
1958          }
1959  
1960          // Only push to gradebook if the update is for the latest attempt.
1961          // Not the latest attempt.
1962          if ($submission && $submission->attemptnumber != $grade->attemptnumber) {
1963              return true;
1964          }
1965  
1966          if ($this->gradebook_item_update(null, $grade)) {
1967              \mod_assign\event\submission_graded::create_from_grade($this, $grade)->trigger();
1968          }
1969  
1970          // If the conditions are met, allow another attempt.
1971          if ($submission) {
1972              $this->reopen_submission_if_required($grade->userid,
1973                      $submission,
1974                      $reopenattempt);
1975          }
1976  
1977          return true;
1978      }
1979  
1980      /**
1981       * View the grant extension date page.
1982       *
1983       * Uses url parameters 'userid'
1984       * or from parameter 'selectedusers'
1985       *
1986       * @param moodleform $mform - Used for validation of the submitted data
1987       * @return string
1988       */
1989      protected function view_grant_extension($mform) {
1990          global $DB, $CFG;
1991          require_once($CFG->dirroot . '/mod/assign/extensionform.php');
1992  
1993          $o = '';
1994          $batchusers = optional_param('selectedusers', '', PARAM_SEQUENCE);
1995          $data = new stdClass();
1996          $data->extensionduedate = null;
1997          $userid = 0;
1998          if (!$batchusers) {
1999              $userid = required_param('userid', PARAM_INT);
2000  
2001              $flags = $this->get_user_flags($userid, false);
2002  
2003              $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
2004  
2005              if ($flags) {
2006                  $data->extensionduedate = $flags->extensionduedate;
2007              }
2008              $data->userid = $userid;
2009          } else {
2010              $data->batchusers = $batchusers;
2011          }
2012          $header = new assign_header($this->get_instance(),
2013                                      $this->get_context(),
2014                                      $this->show_intro(),
2015                                      $this->get_course_module()->id,
2016                                      get_string('grantextension', 'assign'));
2017          $o .= $this->get_renderer()->render($header);
2018  
2019          if (!$mform) {
2020              $formparams = array($this->get_course_module()->id,
2021                                  $userid,
2022                                  $batchusers,
2023                                  $this->get_instance(),
2024                                  $data);
2025              $mform = new mod_assign_extension_form(null, $formparams);
2026          }
2027          $o .= $this->get_renderer()->render(new assign_form('extensionform', $mform));
2028          $o .= $this->view_footer();
2029          return $o;
2030      }
2031  
2032      /**
2033       * Get a list of the users in the same group as this user.
2034       *
2035       * @param int $groupid The id of the group whose members we want or 0 for the default group
2036       * @param bool $onlyids Whether to retrieve only the user id's
2037       * @param bool $excludesuspended Whether to exclude suspended users
2038       * @return array The users (possibly id's only)
2039       */
2040      public function get_submission_group_members($groupid, $onlyids, $excludesuspended = false) {
2041          $members = array();
2042          if ($groupid != 0) {
2043              if ($onlyids) {
2044                  $allusers = groups_get_members($groupid, 'u.id');
2045              } else {
2046                  $allusers = groups_get_members($groupid);
2047              }
2048              foreach ($allusers as $user) {
2049                  if ($this->get_submission_group($user->id)) {
2050                      $members[] = $user;
2051                  }
2052              }
2053          } else {
2054              $allusers = $this->list_participants(null, $onlyids);
2055              foreach ($allusers as $user) {
2056                  if ($this->get_submission_group($user->id) == null) {
2057                      $members[] = $user;
2058                  }
2059              }
2060          }
2061          // Exclude suspended users, if user can't see them.
2062          if ($excludesuspended || !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
2063              foreach ($members as $key => $member) {
2064                  if (!$this->is_active_user($member->id)) {
2065                      unset($members[$key]);
2066                  }
2067              }
2068          }
2069  
2070          return $members;
2071      }
2072  
2073      /**
2074       * Get a list of the users in the same group as this user that have not submitted the assignment.
2075       *
2076       * @param int $groupid The id of the group whose members we want or 0 for the default group
2077       * @param bool $onlyids Whether to retrieve only the user id's
2078       * @return array The users (possibly id's only)
2079       */
2080      public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
2081          $instance = $this->get_instance();
2082          if (!$instance->teamsubmission || !$instance->requireallteammemberssubmit) {
2083              return array();
2084          }
2085          $members = $this->get_submission_group_members($groupid, $onlyids);
2086  
2087          foreach ($members as $id => $member) {
2088              $submission = $this->get_user_submission($member->id, false);
2089              if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
2090                  unset($members[$id]);
2091              } else {
2092                  if ($this->is_blind_marking()) {
2093                      $members[$id]->alias = get_string('hiddenuser', 'assign') .
2094                                             $this->get_uniqueid_for_user($id);
2095                  }
2096              }
2097          }
2098          return $members;
2099      }
2100  
2101      /**
2102       * Load the group submission object for a particular user, optionally creating it if required.
2103       *
2104       * @param int $userid The id of the user whose submission we want
2105       * @param int $groupid The id of the group for this user - may be 0 in which
2106       *                     case it is determined from the userid.
2107       * @param bool $create If set to true a new submission object will be created in the database
2108       *                     with the status set to "new".
2109       * @param int $attemptnumber - -1 means the latest attempt
2110       * @return stdClass The submission
2111       */
2112      public function get_group_submission($userid, $groupid, $create, $attemptnumber=-1) {
2113          global $DB;
2114  
2115          if ($groupid == 0) {
2116              $group = $this->get_submission_group($userid);
2117              if ($group) {
2118                  $groupid = $group->id;
2119              }
2120          }
2121  
2122          // Now get the group submission.
2123          $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2124          if ($attemptnumber >= 0) {
2125              $params['attemptnumber'] = $attemptnumber;
2126          }
2127  
2128          // Only return the row with the highest attemptnumber.
2129          $submission = null;
2130          $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2131          if ($submissions) {
2132              $submission = reset($submissions);
2133          }
2134  
2135          if ($submission) {
2136              return $submission;
2137          }
2138          if ($create) {
2139              $submission = new stdClass();
2140              $submission->assignment = $this->get_instance()->id;
2141              $submission->userid = 0;
2142              $submission->groupid = $groupid;
2143              $submission->timecreated = time();
2144              $submission->timemodified = $submission->timecreated;
2145              if ($attemptnumber >= 0) {
2146                  $submission->attemptnumber = $attemptnumber;
2147              } else {
2148                  $submission->attemptnumber = 0;
2149              }
2150              // Work out if this is the latest submission.
2151              $submission->latest = 0;
2152              $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
2153              if ($attemptnumber == -1) {
2154                  // This is a new submission so it must be the latest.
2155                  $submission->latest = 1;
2156              } else {
2157                  // We need to work this out.
2158                  $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
2159                  if ($result) {
2160                      $latestsubmission = reset($result);
2161                  }
2162                  if (!$latestsubmission || ($attemptnumber == $latestsubmission->attemptnumber)) {
2163                      $submission->latest = 1;
2164                  }
2165              }
2166              if ($submission->latest) {
2167                  // This is the case when we need to set latest to 0 for all the other attempts.
2168                  $DB->set_field('assign_submission', 'latest', 0, $params);
2169              }
2170              $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
2171              $sid = $DB->insert_record('assign_submission', $submission);
2172              return $DB->get_record('assign_submission', array('id' => $sid));
2173          }
2174          return false;
2175      }
2176  
2177      /**
2178       * View a summary listing of all assignments in the current course.
2179       *
2180       * @return string
2181       */
2182      private function view_course_index() {
2183          global $USER;
2184  
2185          $o = '';
2186  
2187          $course = $this->get_course();
2188          $strplural = get_string('modulenameplural', 'assign');
2189  
2190          if (!$cms = get_coursemodules_in_course('assign', $course->id, 'm.duedate')) {
2191              $o .= $this->get_renderer()->notification(get_string('thereareno', 'moodle', $strplural));
2192              $o .= $this->get_renderer()->continue_button(new moodle_url('/course/view.php', array('id' => $course->id)));
2193              return $o;
2194          }
2195  
2196          $strsectionname = '';
2197          $usesections = course_format_uses_sections($course->format);
2198          $modinfo = get_fast_modinfo($course);
2199  
2200          if ($usesections) {
2201              $strsectionname = get_string('sectionname', 'format_'.$course->format);
2202              $sections = $modinfo->get_section_info_all();
2203          }
2204          $courseindexsummary = new assign_course_index_summary($usesections, $strsectionname);
2205  
2206          $timenow = time();
2207  
2208          $currentsection = '';
2209          foreach ($modinfo->instances['assign'] as $cm) {
2210              if (!$cm->uservisible) {
2211                  continue;
2212              }
2213  
2214              $timedue = $cms[$cm->id]->duedate;
2215  
2216              $sectionname = '';
2217              if ($usesections && $cm->sectionnum) {
2218                  $sectionname = get_section_name($course, $sections[$cm->sectionnum]);
2219              }
2220  
2221              $submitted = '';
2222              $context = context_module::instance($cm->id);
2223  
2224              $assignment = new assign($context, $cm, $course);
2225  
2226              if (has_capability('mod/assign:grade', $context)) {
2227                  $submitted = $assignment->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED);
2228  
2229              } else if (has_capability('mod/assign:submit', $context)) {
2230                  $usersubmission = $assignment->get_user_submission($USER->id, false);
2231  
2232                  if (!empty($usersubmission->status)) {
2233                      $submitted = get_string('submissionstatus_' . $usersubmission->status, 'assign');
2234                  } else {
2235                      $submitted = get_string('submissionstatus_', 'assign');
2236                  }
2237              }
2238              $gradinginfo = grade_get_grades($course->id, 'mod', 'assign', $cm->instance, $USER->id);
2239              if (isset($gradinginfo->items[0]->grades[$USER->id]) &&
2240                      !$gradinginfo->items[0]->grades[$USER->id]->hidden ) {
2241                  $grade = $gradinginfo->items[0]->grades[$USER->id]->str_grade;
2242              } else {
2243                  $grade = '-';
2244              }
2245  
2246              $courseindexsummary->add_assign_info($cm->id, $cm->name, $sectionname, $timedue, $submitted, $grade);
2247  
2248          }
2249  
2250          $o .= $this->get_renderer()->render($courseindexsummary);
2251          $o .= $this->view_footer();
2252  
2253          return $o;
2254      }
2255  
2256      /**
2257       * View a page rendered by a plugin.
2258       *
2259       * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'.
2260       *
2261       * @return string
2262       */
2263      protected function view_plugin_page() {
2264          global $USER;
2265  
2266          $o = '';
2267  
2268          $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
2269          $plugintype = required_param('plugin', PARAM_TEXT);
2270          $pluginaction = required_param('pluginaction', PARAM_ALPHA);
2271  
2272          $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
2273          if (!$plugin) {
2274              print_error('invalidformdata', '');
2275              return;
2276          }
2277  
2278          $o .= $plugin->view_page($pluginaction);
2279  
2280          return $o;
2281      }
2282  
2283  
2284      /**
2285       * This is used for team assignments to get the group for the specified user.
2286       * If the user is a member of multiple or no groups this will return false
2287       *
2288       * @param int $userid The id of the user whose submission we want
2289       * @return mixed The group or false
2290       */
2291      public function get_submission_group($userid) {
2292  
2293          if (isset($this->usersubmissiongroups[$userid])) {
2294              return $this->usersubmissiongroups[$userid];
2295          }
2296  
2297          $grouping = $this->get_instance()->teamsubmissiongroupingid;
2298          $groups = groups_get_all_groups($this->get_course()->id, $userid, $grouping);
2299          if (count($groups) != 1) {
2300              $return = false;
2301          } else {
2302              $return = array_pop($groups);
2303          }
2304  
2305          // Cache the user submission group.
2306          $this->usersubmissiongroups[$userid] = $return;
2307  
2308          return $return;
2309      }
2310  
2311  
2312      /**
2313       * Display the submission that is used by a plugin.
2314       *
2315       * Uses url parameters 'sid', 'gid' and 'plugin'.
2316       *
2317       * @param string $pluginsubtype
2318       * @return string
2319       */
2320      protected function view_plugin_content($pluginsubtype) {
2321          $o = '';
2322  
2323          $submissionid = optional_param('sid', 0, PARAM_INT);
2324          $gradeid = optional_param('gid', 0, PARAM_INT);
2325          $plugintype = required_param('plugin', PARAM_TEXT);
2326          $item = null;
2327          if ($pluginsubtype == 'assignsubmission') {
2328              $plugin = $this->get_submission_plugin_by_type($plugintype);
2329              if ($submissionid <= 0) {
2330                  throw new coding_exception('Submission id should not be 0');
2331              }
2332              $item = $this->get_submission($submissionid);
2333  
2334              // Check permissions.
2335              $this->require_view_submission($item->userid);
2336              $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2337                                                                $this->get_context(),
2338                                                                $this->show_intro(),
2339                                                                $this->get_course_module()->id,
2340                                                                $plugin->get_name()));
2341              $o .= $this->get_renderer()->render(new assign_submission_plugin_submission($plugin,
2342                                                                $item,
2343                                                                assign_submission_plugin_submission::FULL,
2344                                                                $this->get_course_module()->id,
2345                                                                $this->get_return_action(),
2346                                                                $this->get_return_params()));
2347  
2348              // Trigger event for viewing a submission.
2349              \mod_assign\event\submission_viewed::create_from_submission($this, $item)->trigger();
2350  
2351          } else {
2352              $plugin = $this->get_feedback_plugin_by_type($plugintype);
2353              if ($gradeid <= 0) {
2354                  throw new coding_exception('Grade id should not be 0');
2355              }
2356              $item = $this->get_grade($gradeid);
2357              // Check permissions.
2358              $this->require_view_submission($item->userid);
2359              $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2360                                                                $this->get_context(),
2361                                                                $this->show_intro(),
2362                                                                $this->get_course_module()->id,
2363                                                                $plugin->get_name()));
2364              $o .= $this->get_renderer()->render(new assign_feedback_plugin_feedback($plugin,
2365                                                                $item,
2366                                                                assign_feedback_plugin_feedback::FULL,
2367                                                                $this->get_course_module()->id,
2368                                                                $this->get_return_action(),
2369                                                                $this->get_return_params()));
2370  
2371              // Trigger event for viewing feedback.
2372              \mod_assign\event\feedback_viewed::create_from_grade($this, $item)->trigger();
2373          }
2374  
2375          $o .= $this->view_return_links();
2376  
2377          $o .= $this->view_footer();
2378  
2379          return $o;
2380      }
2381  
2382      /**
2383       * Rewrite plugin file urls so they resolve correctly in an exported zip.
2384       *
2385       * @param string $text - The replacement text
2386       * @param stdClass $user - The user record
2387       * @param assign_plugin $plugin - The assignment plugin
2388       */
2389      public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
2390          $groupmode = groups_get_activity_groupmode($this->get_course_module());
2391          $groupname = '';
2392          if ($groupmode) {
2393              $groupid = groups_get_activity_group($this->get_course_module(), true);
2394              $groupname = groups_get_group_name($groupid).'-';
2395          }
2396  
2397          if ($this->is_blind_marking()) {
2398              $prefix = $groupname . get_string('participant', 'assign');
2399              $prefix = str_replace('_', ' ', $prefix);
2400              $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2401          } else {
2402              $prefix = $groupname . fullname($user);
2403              $prefix = str_replace('_', ' ', $prefix);
2404              $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
2405          }
2406  
2407          $subtype = $plugin->get_subtype();
2408          $type = $plugin->get_type();
2409          $prefix = $prefix . $subtype . '_' . $type . '_';
2410  
2411          $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
2412  
2413          return $result;
2414      }
2415  
2416      /**
2417       * Render the content in editor that is often used by plugin.
2418       *
2419       * @param string $filearea
2420       * @param int  $submissionid
2421       * @param string $plugintype
2422       * @param string $editor
2423       * @param string $component
2424       * @return string
2425       */
2426      public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
2427          global $CFG;
2428  
2429          $result = '';
2430  
2431          $plugin = $this->get_submission_plugin_by_type($plugintype);
2432  
2433          $text = $plugin->get_editor_text($editor, $submissionid);
2434          $format = $plugin->get_editor_format($editor, $submissionid);
2435  
2436          $finaltext = file_rewrite_pluginfile_urls($text,
2437                                                    'pluginfile.php',
2438                                                    $this->get_context()->id,
2439                                                    $component,
2440                                                    $filearea,
2441                                                    $submissionid);
2442          $params = array('overflowdiv' => true, 'context' => $this->get_context());
2443          $result .= format_text($finaltext, $format, $params);
2444  
2445          if ($CFG->enableportfolios && has_capability('mod/assign:exportownsubmission', $this->context)) {
2446              require_once($CFG->libdir . '/portfoliolib.php');
2447  
2448              $button = new portfolio_add_button();
2449              $portfolioparams = array('cmid' => $this->get_course_module()->id,
2450                                       'sid' => $submissionid,
2451                                       'plugin' => $plugintype,
2452                                       'editor' => $editor,
2453                                       'area'=>$filearea);
2454              $button->set_callback_options('assign_portfolio_caller', $portfolioparams, 'mod_assign');
2455              $fs = get_file_storage();
2456  
2457              if ($files = $fs->get_area_files($this->context->id,
2458                                               $component,
2459                                               $filearea,
2460                                               $submissionid,
2461                                               'timemodified',
2462                                               false)) {
2463                  $button->set_formats(PORTFOLIO_FORMAT_RICHHTML);
2464              } else {
2465                  $button->set_formats(PORTFOLIO_FORMAT_PLAINHTML);
2466              }
2467              $result .= $button->to_html();
2468          }
2469          return $result;
2470      }
2471  
2472      /**
2473       * Display a continue page after grading.
2474       *
2475       * @param string $message - The message to display.
2476       * @return string
2477       */
2478      protected function view_savegrading_result($message) {
2479          $o = '';
2480          $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2481                                                        $this->get_context(),
2482                                                        $this->show_intro(),
2483                                                        $this->get_course_module()->id,
2484                                                        get_string('savegradingresult', 'assign')));
2485          $gradingresult = new assign_gradingmessage(get_string('savegradingresult', 'assign'),
2486                                                     $message,
2487                                                     $this->get_course_module()->id);
2488          $o .= $this->get_renderer()->render($gradingresult);
2489          $o .= $this->view_footer();
2490          return $o;
2491      }
2492      /**
2493       * Display a continue page after quickgrading.
2494       *
2495       * @param string $message - The message to display.
2496       * @return string
2497       */
2498      protected function view_quickgrading_result($message) {
2499          $o = '';
2500          $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
2501                                                        $this->get_context(),
2502                                                        $this->show_intro(),
2503                                                        $this->get_course_module()->id,
2504                                                        get_string('quickgradingresult', 'assign')));
2505          $lastpage = optional_param('lastpage', null, PARAM_INT);
2506          $gradingresult = new assign_gradingmessage(get_string('quickgradingresult', 'assign'),
2507                                                     $message,
2508                                                     $this->get_course_module()->id,
2509                                                     false,
2510                                                     $lastpage);
2511          $o .= $this->get_renderer()->render($gradingresult);
2512          $o .= $this->view_footer();
2513          return $o;
2514      }
2515  
2516      /**
2517       * Display the page footer.
2518       *
2519       * @return string
2520       */
2521      protected function view_footer() {
2522          // When viewing the footer during PHPUNIT tests a set_state error is thrown.
2523          if (!PHPUNIT_TEST) {
2524              return $this->get_renderer()->render_footer();
2525          }
2526  
2527          return '';
2528      }
2529  
2530      /**
2531       * Throw an error if the permissions to view this users submission are missing.
2532       *
2533       * @throws required_capability_exception
2534       * @return none
2535       */
2536      public function require_view_submission($userid) {
2537          if (!$this->can_view_submission($userid)) {
2538              throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2539          }
2540      }
2541  
2542      /**
2543       * Throw an error if the permissions to view grades in this assignment are missing.
2544       *
2545       * @throws required_capability_exception
2546       * @return none
2547       */
2548      public function require_view_grades() {
2549          if (!$this->can_view_grades()) {
2550              throw new required_capability_exception($this->context, 'mod/assign:viewgrades', 'nopermission', '');
2551          }
2552      }
2553  
2554      /**
2555       * Does this user have view grade or grade permission for this assignment?
2556       *
2557       * @return bool
2558       */
2559      public function can_view_grades() {
2560          // Permissions check.
2561          if (!has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
2562              return false;
2563          }
2564  
2565          return true;
2566      }
2567  
2568      /**
2569       * Does this user have grade permission for this assignment?
2570       *
2571       * @return bool
2572       */
2573      public function can_grade() {
2574          // Permissions check.
2575          if (!has_capability('mod/assign:grade', $this->context)) {
2576              return false;
2577          }
2578  
2579          return true;
2580      }
2581  
2582      /**
2583       * Download a zip file of all assignment submissions.
2584       *
2585       * @return string - If an error occurs, this will contain the error page.
2586       */
2587      protected function download_submissions() {
2588          global $CFG, $DB;
2589  
2590          // More efficient to load this here.
2591          require_once($CFG->libdir.'/filelib.php');
2592  
2593          // Increase the server timeout to handle the creation and sending of large zip files.
2594          core_php_time_limit::raise();
2595  
2596          $this->require_view_grades();
2597  
2598          // Load all users with submit.
2599          $students = get_enrolled_users($this->context, "mod/assign:submit", null, 'u.*', null, null, null,
2600                          $this->show_only_active_users());
2601  
2602          // Build a list of files to zip.
2603          $filesforzipping = array();
2604          $fs = get_file_storage();
2605  
2606          $groupmode = groups_get_activity_groupmode($this->get_course_module());
2607          // All users.
2608          $groupid = 0;
2609          $groupname = '';
2610          if ($groupmode) {
2611              $groupid = groups_get_activity_group($this->get_course_module(), true);
2612              $groupname = groups_get_group_name($groupid).'-';
2613          }
2614  
2615          // Construct the zip file name.
2616          $filename = clean_filename($this->get_course()->shortname . '-' .
2617                                     $this->get_instance()->name . '-' .
2618                                     $groupname.$this->get_course_module()->id . '.zip');
2619  
2620          // Get all the files for each student.
2621          foreach ($students as $student) {
2622              $userid = $student->id;
2623  
2624              if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
2625                  // Get the plugins to add their own files to the zip.
2626  
2627                  $submissiongroup = false;
2628                  $groupname = '';
2629                  if ($this->get_instance()->teamsubmission) {
2630                      $submission = $this->get_group_submission($userid, 0, false);
2631                      $submissiongroup = $this->get_submission_group($userid);
2632                      if ($submissiongroup) {
2633                          $groupname = $submissiongroup->name . '-';
2634                      } else {
2635                          $groupname = get_string('defaultteam', 'assign') . '-';
2636                      }
2637                  } else {
2638                      $submission = $this->get_user_submission($userid, false);
2639                  }
2640  
2641                  if ($this->is_blind_marking()) {
2642                      $prefix = str_replace('_', ' ', $groupname . get_string('participant', 'assign'));
2643                      $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
2644                  } else {
2645                      $prefix = str_replace('_', ' ', $groupname . fullname($student));
2646                      $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($userid) . '_');
2647                  }
2648  
2649                  if ($submission) {
2650                      foreach ($this->submissionplugins as $plugin) {
2651                          if ($plugin->is_enabled() && $plugin->is_visible()) {
2652                              $pluginfiles = $plugin->get_files($submission, $student);
2653                              foreach ($pluginfiles as $zipfilename => $file) {
2654                                  $subtype = $plugin->get_subtype();
2655                                  $type = $plugin->get_type();
2656                                  $prefixedfilename = clean_filename($prefix .
2657                                                                     $subtype .
2658                                                                     '_' .
2659                                                                     $type .
2660                                                                     '_' .
2661                                                                     $zipfilename);
2662                                  $filesforzipping[$prefixedfilename] = $file;
2663                              }
2664                          }
2665                      }
2666                  }
2667              }
2668          }
2669          $result = '';
2670          if (count($filesforzipping) == 0) {
2671              $header = new assign_header($this->get_instance(),
2672                                          $this->get_context(),
2673                                          '',
2674                                          $this->get_course_module()->id,
2675                                          get_string('downloadall', 'assign'));
2676              $result .= $this->get_renderer()->render($header);
2677              $result .= $this->get_renderer()->notification(get_string('nosubmission', 'assign'));
2678              $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
2679                                                                      'action'=>'grading'));
2680              $result .= $this->get_renderer()->continue_button($url);
2681              $result .= $this->view_footer();
2682          } else if ($zipfile = $this->pack_files($filesforzipping)) {
2683              \mod_assign\event\all_submissions_downloaded::create_from_assign($this)->trigger();
2684              // Send file and delete after sending.
2685              send_temp_file($zipfile, $filename);
2686              // We will not get here - send_temp_file calls exit.
2687          }
2688          return $result;
2689      }
2690  
2691      /**
2692       * Util function to add a message to the log.
2693       *
2694       * @deprecated since 2.7 - Use new events system instead.
2695       *             (see http://docs.moodle.org/dev/Migrating_logging_calls_in_plugins).
2696       *
2697       * @param string $action The current action
2698       * @param string $info A detailed description of the change. But no more than 255 characters.
2699       * @param string $url The url to the assign module instance.
2700       * @param bool $return If true, returns the arguments, else adds to log. The purpose of this is to
2701       *                     retrieve the arguments to use them with the new event system (Event 2).
2702       * @return void|array
2703       */
2704      public function add_to_log($action = '', $info = '', $url='', $return = false) {
2705          global $USER;
2706  
2707          $fullurl = 'view.php?id=' . $this->get_course_module()->id;
2708          if ($url != '') {
2709              $fullurl .= '&' . $url;
2710          }
2711  
2712          $args = array(
2713              $this->get_course()->id,
2714              'assign',
2715              $action,
2716              $fullurl,
2717              $info,
2718              $this->get_course_module()->id
2719          );
2720  
2721          if ($return) {
2722              // We only need to call debugging when returning a value. This is because the call to
2723              // call_user_func_array('add_to_log', $args) will trigger a debugging message of it's own.
2724              debugging('The mod_assign add_to_log() function is now deprecated.', DEBUG_DEVELOPER);
2725              return $args;
2726          }
2727          call_user_func_array('add_to_log', $args);
2728      }
2729  
2730      /**
2731       * Lazy load the page renderer and expose the renderer to plugins.
2732       *
2733       * @return assign_renderer
2734       */
2735      public function get_renderer() {
2736          global $PAGE;
2737          if ($this->output) {
2738              return $this->output;
2739          }
2740          $this->output = $PAGE->get_renderer('mod_assign');
2741          return $this->output;
2742      }
2743  
2744      /**
2745       * Load the submission object for a particular user, optionally creating it if required.
2746       *
2747       * For team assignments there are 2 submissions - the student submission and the team submission
2748       * All files are associated with the team submission but the status of the students contribution is
2749       * recorded separately.
2750       *
2751       * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
2752       * @param bool $create optional - defaults to false. If set to true a new submission object
2753       *                     will be created in the database with the status set to "new".
2754       * @param int $attemptnumber - -1 means the latest attempt
2755       * @return stdClass The submission
2756       */
2757      public function get_user_submission($userid, $create, $attemptnumber=-1) {
2758          global $DB, $USER;
2759  
2760          if (!$userid) {
2761              $userid = $USER->id;
2762          }
2763          // If the userid is not null then use userid.
2764          $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
2765          if ($attemptnumber >= 0) {
2766              $params['attemptnumber'] = $attemptnumber;
2767          }
2768  
2769          // Only return the row with the highest attemptnumber.
2770          $submission = null;
2771          $submissions = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', '*', 0, 1);
2772          if ($submissions) {
2773              $submission = reset($submissions);
2774          }
2775  
2776          if ($submission) {
2777              return $submission;
2778          }
2779          if ($create) {
2780              $submission = new stdClass();
2781              $submission->assignment   = $this->get_instance()->id;
2782              $submission->userid       = $userid;
2783              $submission->timecreated = time();
2784              $submission->timemodified = $submission->timecreated;
2785              $submission->status = ASSIGN_SUBMISSION_STATUS_NEW;
2786              if ($attemptnumber >= 0) {
2787                  $submission->attemptnumber = $attemptnumber;
2788              } else {
2789                  $submission->attemptnumber = 0;
2790              }
2791              // Work out if this is the latest submission.
2792              $submission->latest = 0;
2793              $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
2794              if ($attemptnumber == -1) {
2795                  // This is a new submission so it must be the latest.
2796                  $submission->latest = 1;
2797              } else {
2798                  // We need to work this out.
2799                  $result = $DB->get_records('assign_submission', $params, 'attemptnumber DESC', 'attemptnumber', 0, 1);
2800                  $latestsubmission = null;
2801                  if ($result) {
2802                      $latestsubmission = reset($result);
2803                  }
2804                  if (empty($latestsubmission) || ($attemptnumber > $latestsubmission->attemptnumber)) {
2805                      $submission->latest = 1;
2806                  }
2807              }
2808              if ($submission->latest) {
2809                  // This is the case when we need to set latest to 0 for all the other attempts.
2810                  $DB->set_field('assign_submission', 'latest', 0, $params);
2811              }
2812              $sid = $DB->insert_record('assign_submission', $submission);
2813              return $DB->get_record('assign_submission', array('id' => $sid));
2814          }
2815          return false;
2816      }
2817  
2818      /**
2819       * Load the submission object from it's id.
2820       *
2821       * @param int $submissionid The id of the submission we want
2822       * @return stdClass The submission
2823       */
2824      protected function get_submission($submissionid) {
2825          global $DB;
2826  
2827          $params = array('assignment'=>$this->get_instance()->id, 'id'=>$submissionid);
2828          return $DB->get_record('assign_submission', $params, '*', MUST_EXIST);
2829      }
2830  
2831      /**
2832       * This will retrieve a user flags object from the db optionally creating it if required.
2833       * The user flags was split from the user_grades table in 2.5.
2834       *
2835       * @param int $userid The user we are getting the flags for.
2836       * @param bool $create If true the flags record will be created if it does not exist
2837       * @return stdClass The flags record
2838       */
2839      public function get_user_flags($userid, $create) {
2840          global $DB, $USER;
2841  
2842          // If the userid is not null then use userid.
2843          if (!$userid) {
2844              $userid = $USER->id;
2845          }
2846  
2847          $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
2848  
2849          $flags = $DB->get_record('assign_user_flags', $params);
2850  
2851          if ($flags) {
2852              return $flags;
2853          }
2854          if ($create) {
2855              $flags = new stdClass();
2856              $flags->assignment = $this->get_instance()->id;
2857              $flags->userid = $userid;
2858              $flags->locked = 0;
2859              $flags->extensionduedate = 0;
2860              $flags->workflowstate = '';
2861              $flags->allocatedmarker = 0;
2862  
2863              // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
2864              // This is because students only want to be notified about certain types of update (grades and feedback).
2865              $flags->mailed = 2;
2866  
2867              $fid = $DB->insert_record('assign_user_flags', $flags);
2868              $flags->id = $fid;
2869              return $flags;
2870          }
2871          return false;
2872      }
2873  
2874      /**
2875       * This will retrieve a grade object from the db, optionally creating it if required.
2876       *
2877       * @param int $userid The user we are grading
2878       * @param bool $create If true the grade will be created if it does not exist
2879       * @param int $attemptnumber The attempt number to retrieve the grade for. -1 means the latest submission.
2880       * @return stdClass The grade record
2881       */
2882      public function get_user_grade($userid, $create, $attemptnumber=-1) {
2883          global $DB, $USER;
2884  
2885          // If the userid is not null then use userid.
2886          if (!$userid) {
2887              $userid = $USER->id;
2888          }
2889          $submission = null;
2890  
2891          $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid);
2892          if ($attemptnumber < 0 || $create) {
2893              // Make sure this grade matches the latest submission attempt.
2894              if ($this->get_instance()->teamsubmission) {
2895                  $submission = $this->get_group_submission($userid, 0, true);
2896              } else {
2897                  $submission = $this->get_user_submission($userid, true);
2898              }
2899              if ($submission) {
2900                  $attemptnumber = $submission->attemptnumber;
2901              }
2902          }
2903  
2904          if ($attemptnumber >= 0) {
2905              $params['attemptnumber'] = $attemptnumber;
2906          }
2907  
2908          $grades = $DB->get_records('assign_grades', $params, 'attemptnumber DESC', '*', 0, 1);
2909  
2910          if ($grades) {
2911              return reset($grades);
2912          }
2913          if ($create) {
2914              $grade = new stdClass();
2915              $grade->assignment   = $this->get_instance()->id;
2916              $grade->userid       = $userid;
2917              $grade->timecreated = time();
2918              // If we are "auto-creating" a grade - and there is a submission
2919              // the new grade should not have a more recent timemodified value
2920              // than the submission.
2921              if ($submission) {
2922                  $grade->timemodified = $submission->timemodified;
2923              } else {
2924                  $grade->timemodified = $grade->timecreated;
2925              }
2926              $grade->grade = -1;
2927              $grade->grader = $USER->id;
2928              if ($attemptnumber >= 0) {
2929                  $grade->attemptnumber = $attemptnumber;
2930              }
2931  
2932              $gid = $DB->insert_record('assign_grades', $grade);
2933              $grade->id = $gid;
2934              return $grade;
2935          }
2936          return false;
2937      }
2938  
2939      /**
2940       * This will retrieve a grade object from the db.
2941       *
2942       * @param int $gradeid The id of the grade
2943       * @return stdClass The grade record
2944       */
2945      protected function get_grade($gradeid) {
2946          global $DB;
2947  
2948          $params = array('assignment'=>$this->get_instance()->id, 'id'=>$gradeid);
2949          return $DB->get_record('assign_grades', $params, '*', MUST_EXIST);
2950      }
2951  
2952      /**
2953       * Print the grading page for a single user submission.
2954       *
2955       * @param moodleform $mform
2956       * @return string
2957       */
2958      protected function view_single_grade_page($mform) {
2959          global $DB, $CFG;
2960  
2961          $o = '';
2962          $instance = $this->get_instance();
2963  
2964          require_once($CFG->dirroot . '/mod/assign/gradeform.php');
2965  
2966          // Need submit permission to submit an assignment.
2967          require_capability('mod/assign:grade', $this->context);
2968  
2969          $header = new assign_header($instance,
2970                                      $this->get_context(),
2971                                      false,
2972                                      $this->get_course_module()->id,
2973                                      get_string('grading', 'assign'));
2974          $o .= $this->get_renderer()->render($header);
2975  
2976          // If userid is passed - we are only grading a single student.
2977          $rownum = required_param('rownum', PARAM_INT);
2978          $useridlistid = optional_param('useridlistid', time(), PARAM_INT);
2979          $userid = optional_param('userid', 0, PARAM_INT);
2980          $attemptnumber = optional_param('attemptnumber', -1, PARAM_INT);
2981  
2982          $cache = cache::make_from_params(cache_store::MODE_SESSION, 'mod_assign', 'useridlist');
2983          if (!$userid) {
2984              if (!$useridlist = $cache->get($this->get_course_module()->id . '_' . $useridlistid)) {
2985                  $useridlist = $this->get_grading_userid_list();
2986              }
2987              $cache->set($this->get_course_module()->id . '_' . $useridlistid, $useridlist);
2988          } else {
2989              $rownum = 0;
2990              $useridlist = array($userid);
2991          }
2992  
2993          if ($rownum < 0 || $rownum > count($useridlist)) {
2994              throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
2995          }
2996  
2997          $last = false;
2998          $userid = $useridlist[$rownum];
2999          if ($rownum == count($useridlist) - 1) {
3000              $last = true;
3001          }
3002          // This variation on the url will link direct to this student, with no next/previous links.
3003          // The benefit is the url will be the same every time for this student, so Atto autosave drafts can match up.
3004          $returnparams = array('userid' => $userid, 'rownum' => 0, 'useridlistid' => 0);
3005          $this->register_return_link('grade', $returnparams);
3006  
3007          $user = $DB->get_record('user', array('id' => $userid));
3008          if ($user) {
3009              $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3010              $usersummary = new assign_user_summary($user,
3011                                                     $this->get_course()->id,
3012                                                     $viewfullnames,
3013                                                     $this->is_blind_marking(),
3014                                                     $this->get_uniqueid_for_user($user->id),
3015                                                     get_extra_user_fields($this->get_context()),
3016                                                     !$this->is_active_user($userid));
3017              $o .= $this->get_renderer()->render($usersummary);
3018          }
3019          $submission = $this->get_user_submission($userid, false, $attemptnumber);
3020          $submissiongroup = null;
3021          $teamsubmission = null;
3022          $notsubmitted = array();
3023          if ($instance->teamsubmission) {
3024              $teamsubmission = $this->get_group_submission($userid, 0, false, $attemptnumber);
3025              $submissiongroup = $this->get_submission_group($userid);
3026              $groupid = 0;
3027              if ($submissiongroup) {
3028                  $groupid = $submissiongroup->id;
3029              }
3030              $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
3031  
3032          }
3033  
3034          // Get the requested grade.
3035          $grade = $this->get_user_grade($userid, false, $attemptnumber);
3036          $flags = $this->get_user_flags($userid, false);
3037          if ($this->can_view_submission($userid)) {
3038              $gradelocked = ($flags && $flags->locked) || $this->grading_disabled($userid);
3039              $extensionduedate = null;
3040              if ($flags) {
3041                  $extensionduedate = $flags->extensionduedate;
3042              }
3043              $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
3044              $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
3045  
3046              $submissionstatus = new assign_submission_status($instance->allowsubmissionsfromdate,
3047                                                               $instance->alwaysshowdescription,
3048                                                               $submission,
3049                                                               $instance->teamsubmission,
3050                                                               $teamsubmission,
3051                                                               $submissiongroup,
3052                                                               $notsubmitted,
3053                                                               $this->is_any_submission_plugin_enabled(),
3054                                                               $gradelocked,
3055                                                               $this->is_graded($userid),
3056                                                               $instance->duedate,
3057                                                               $instance->cutoffdate,
3058                                                               $this->get_submission_plugins(),
3059                                                               $this->get_return_action(),
3060                                                               $this->get_return_params(),
3061                                                               $this->get_course_module()->id,
3062                                                               $this->get_course()->id,
3063                                                               assign_submission_status::GRADER_VIEW,
3064                                                               $showedit,
3065                                                               false,
3066                                                               $viewfullnames,
3067                                                               $extensionduedate,
3068                                                               $this->get_context(),
3069                                                               $this->is_blind_marking(),
3070                                                               '',
3071                                                               $instance->attemptreopenmethod,
3072                                                               $instance->maxattempts,
3073                                                               $this->get_grading_status($userid));
3074              $o .= $this->get_renderer()->render($submissionstatus);
3075          }
3076  
3077          if ($grade) {
3078              $data = new stdClass();
3079              if ($grade->grade !== null && $grade->grade >= 0) {
3080                  $data->grade = format_float($grade->grade, 2);
3081              }
3082          } else {
3083              $data = new stdClass();
3084              $data->grade = '';
3085          }
3086  
3087          if (!empty($flags->workflowstate)) {
3088              $data->workflowstate = $flags->workflowstate;
3089          }
3090          if (!empty($flags->allocatedmarker)) {
3091              $data->allocatedmarker = $flags->allocatedmarker;
3092          }
3093  
3094          // Warning if required.
3095          $allsubmissions = $this->get_all_submissions($userid);
3096  
3097          if ($attemptnumber != -1) {
3098              $params = array('attemptnumber'=>$attemptnumber + 1,
3099                              'totalattempts'=>count($allsubmissions));
3100              $message = get_string('editingpreviousfeedbackwarning', 'assign', $params);
3101              $o .= $this->get_renderer()->notification($message);
3102          }
3103  
3104          // Now show the grading form.
3105          if (!$mform) {
3106              $pagination = array('rownum'=>$rownum,
3107                                  'useridlistid'=>$useridlistid,
3108                                  'last'=>$last,
3109                                  'userid'=>optional_param('userid', 0, PARAM_INT),
3110                                  'attemptnumber'=>$attemptnumber);
3111              $formparams = array($this, $data, $pagination);
3112              $mform = new mod_assign_grade_form(null,
3113                                                 $formparams,
3114                                                 'post',
3115                                                 '',
3116                                                 array('class'=>'gradeform'));
3117          }
3118          $o .= $this->get_renderer()->heading(get_string('grade'), 3);
3119          $o .= $this->get_renderer()->render(new assign_form('gradingform', $mform));
3120  
3121          if (count($allsubmissions) > 1 && $attemptnumber == -1) {
3122              $allgrades = $this->get_all_grades($userid);
3123              $history = new assign_attempt_history($allsubmissions,
3124                                                    $allgrades,
3125                                                    $this->get_submission_plugins(),
3126                                                    $this->get_feedback_plugins(),
3127                                                    $this->get_course_module()->id,
3128                                                    $this->get_return_action(),
3129                                                    $this->get_return_params(),
3130                                                    true,
3131                                                    $useridlistid,
3132                                                    $rownum);
3133  
3134              $o .= $this->get_renderer()->render($history);
3135          }
3136  
3137          \mod_assign\event\grading_form_viewed::create_from_user($this, $user)->trigger();
3138  
3139          $o .= $this->view_footer();
3140          return $o;
3141      }
3142  
3143      /**
3144       * Show a confirmation page to make sure they want to release student identities.
3145       *
3146       * @return string
3147       */
3148      protected function view_reveal_identities_confirm() {
3149          require_capability('mod/assign:revealidentities', $this->get_context());
3150  
3151          $o = '';
3152          $header = new assign_header($this->get_instance(),
3153                                      $this->get_context(),
3154                                      false,
3155                                      $this->get_course_module()->id);
3156          $o .= $this->get_renderer()->render($header);
3157  
3158          $urlparams = array('id'=>$this->get_course_module()->id,
3159                             'action'=>'revealidentitiesconfirm',
3160                             'sesskey'=>sesskey());
3161          $confirmurl = new moodle_url('/mod/assign/view.php', $urlparams);
3162  
3163          $urlparams = array('id'=>$this->get_course_module()->id,
3164                             'action'=>'grading');
3165          $cancelurl = new moodle_url('/mod/assign/view.php', $urlparams);
3166  
3167          $o .= $this->get_renderer()->confirm(get_string('revealidentitiesconfirm', 'assign'),
3168                                               $confirmurl,
3169                                               $cancelurl);
3170          $o .= $this->view_footer();
3171  
3172          \mod_assign\event\reveal_identities_confirmation_page_viewed::create_from_assign($this)->trigger();
3173  
3174          return $o;
3175      }
3176  
3177      /**
3178       * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
3179       *
3180       * @return string
3181       */
3182      protected function view_return_links() {
3183          $returnaction = optional_param('returnaction', '', PARAM_ALPHA);
3184          $returnparams = optional_param('returnparams', '', PARAM_TEXT);
3185  
3186          $params = array();
3187          $returnparams = str_replace('&amp;', '&', $returnparams);
3188          parse_str($returnparams, $params);
3189          $newparams = array('id' => $this->get_course_module()->id, 'action' => $returnaction);
3190          $params = array_merge($newparams, $params);
3191  
3192          $url = new moodle_url('/mod/assign/view.php', $params);
3193          return $this->get_renderer()->single_button($url, get_string('back'), 'get');
3194      }
3195  
3196      /**
3197       * View the grading table of all submissions for this assignment.
3198       *
3199       * @return string
3200       */
3201      protected function view_grading_table() {
3202          global $USER, $CFG;
3203  
3204          // Include grading options form.
3205          require_once($CFG->dirroot . '/mod/assign/gradingoptionsform.php');
3206          require_once($CFG->dirroot . '/mod/assign/quickgradingform.php');
3207          require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
3208          $o = '';
3209          $cmid = $this->get_course_module()->id;
3210  
3211          $links = array();
3212          if (has_capability('gradereport/grader:view', $this->get_course_context()) &&
3213                  has_capability('moodle/grade:viewall', $this->get_course_context())) {
3214              $gradebookurl = '/grade/report/grader/index.php?id=' . $this->get_course()->id;
3215              $links[$gradebookurl] = get_string('viewgradebook', 'assign');
3216          }
3217          if ($this->is_any_submission_plugin_enabled() && $this->count_submissions()) {
3218              $downloadurl = '/mod/assign/view.php?id=' . $cmid . '&action=downloadall';
3219              $links[$downloadurl] = get_string('downloadall', 'assign');
3220          }
3221          if ($this->is_blind_marking() &&
3222                  has_capability('mod/assign:revealidentities', $this->get_context())) {
3223              $revealidentitiesurl = '/mod/assign/view.php?id=' . $cmid . '&action=revealidentities';
3224              $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
3225          }
3226          foreach ($this->get_feedback_plugins() as $plugin) {
3227              if ($plugin->is_enabled() && $plugin->is_visible()) {
3228                  foreach ($plugin->get_grading_actions() as $action => $description) {
3229                      $url = '/mod/assign/view.php' .
3230                             '?id=' .  $cmid .
3231                             '&plugin=' . $plugin->get_type() .
3232                             '&pluginsubtype=assignfeedback' .
3233                             '&action=viewpluginpage&pluginaction=' . $action;
3234                      $links[$url] = $description;
3235                  }
3236              }
3237          }
3238  
3239          // Sort links alphabetically based on the link description.
3240          core_collator::asort($links);
3241  
3242          $gradingactions = new url_select($links);
3243          $gradingactions->set_label(get_string('choosegradingaction', 'assign'));
3244  
3245          $gradingmanager = get_grading_manager($this->get_context(), 'mod_assign', 'submissions');
3246  
3247          $perpage = (int) get_user_preferences('assign_perpage', 10);
3248          $filter = get_user_preferences('assign_filter', '');
3249          $markerfilter = get_user_preferences('assign_markerfilter', '');
3250          $workflowfilter = get_user_preferences('assign_workflowfilter', '');
3251          $controller = $gradingmanager->get_active_controller();
3252          $showquickgrading = empty($controller) && $this->can_grade();
3253          $quickgrading = get_user_preferences('assign_quickgrading', false);
3254          $showonlyactiveenrolopt = has_capability('moodle/course:viewsuspendedusers', $this->context);
3255  
3256          $markingallocation = $this->get_instance()->markingworkflow &&
3257              $this->get_instance()->markingallocation &&
3258              has_capability('mod/assign:manageallocations', $this->context);
3259          // Get markers to use in drop lists.
3260          $markingallocationoptions = array();
3261          if ($markingallocation) {
3262              list($sort, $params) = users_order_by_sql();
3263              $markers = get_users_by_capability($this->context, 'mod/assign:grade', '', $sort);
3264              $markingallocationoptions[''] = get_string('filternone', 'assign');
3265              $markingallocationoptions[ASSIGN_MARKER_FILTER_NO_MARKER] = get_string('markerfilternomarker', 'assign');
3266              foreach ($markers as $marker) {
3267                  $markingallocationoptions[$marker->id] = fullname($marker);
3268              }
3269          }
3270  
3271          $markingworkflow = $this->get_instance()->markingworkflow;
3272          // Get marking states to show in form.
3273          $markingworkflowoptions = array();
3274          if ($markingworkflow) {
3275              $notmarked = get_string('markingworkflowstatenotmarked', 'assign');
3276              $markingworkflowoptions[''] = get_string('filternone', 'assign');
3277              $markingworkflowoptions[ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED] = $notmarked;
3278              $markingworkflowoptions = array_merge($markingworkflowoptions, $this->get_marking_workflow_states_for_current_user());
3279          }
3280  
3281          // Print options for changing the filter and changing the number of results per page.
3282          $gradingoptionsformparams = array('cm'=>$cmid,
3283                                            'contextid'=>$this->context->id,
3284                                            'userid'=>$USER->id,
3285                                            'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
3286                                            'showquickgrading'=>$showquickgrading,
3287                                            'quickgrading'=>$quickgrading,
3288                                            'markingworkflowopt'=>$markingworkflowoptions,
3289                                            'markingallocationopt'=>$markingallocationoptions,
3290                                            'showonlyactiveenrolopt'=>$showonlyactiveenrolopt,
3291                                            'showonlyactiveenrol'=>$this->show_only_active_users());
3292  
3293          $classoptions = array('class'=>'gradingoptionsform');
3294          $gradingoptionsform = new mod_assign_grading_options_form(null,
3295                                                                    $gradingoptionsformparams,
3296                                                                    'post',
3297                                                                    '',
3298                                                                    $classoptions);
3299  
3300          $batchformparams = array('cm'=>$cmid,
3301                                   'submissiondrafts'=>$this->get_instance()->submissiondrafts,
3302                                   'duedate'=>$this->get_instance()->duedate,
3303                                   'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
3304                                   'feedbackplugins'=>$this->get_feedback_plugins(),
3305                                   'context'=>$this->get_context(),
3306                                   'markingworkflow'=>$markingworkflow,
3307                                   'markingallocation'=>$markingallocation);
3308          $classoptions = array('class'=>'gradingbatchoperationsform');
3309  
3310          $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
3311                                                                                     $batchformparams,
3312                                                                                     'post',
3313                                                                                     '',
3314                                                                                     $classoptions);
3315  
3316          $gradingoptionsdata = new stdClass();
3317          $gradingoptionsdata->perpage = $perpage;
3318          $gradingoptionsdata->filter = $filter;
3319          $gradingoptionsdata->markerfilter = $markerfilter;
3320          $gradingoptionsdata->workflowfilter = $workflowfilter;
3321          $gradingoptionsform->set_data($gradingoptionsdata);
3322  
3323          $actionformtext = $this->get_renderer()->render($gradingactions);
3324          $header = new assign_header($this->get_instance(),
3325                                      $this->get_context(),
3326                                      false,
3327                                      $this->get_course_module()->id,
3328                                      get_string('grading', 'assign'),
3329                                      $actionformtext);
3330          $o .= $this->get_renderer()->render($header);
3331  
3332          $currenturl = $CFG->wwwroot .
3333                        '/mod/assign/view.php?id=' .
3334                        $this->get_course_module()->id .
3335                        '&action=grading';
3336  
3337          $o .= groups_print_activity_menu($this->get_course_module(), $currenturl, true);
3338  
3339          // Plagiarism update status apearring in the grading book.
3340          if (!empty($CFG->enableplagiarism)) {
3341              require_once($CFG->libdir . '/plagiarismlib.php');
3342              $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
3343          }
3344  
3345          // Load and print the table of submissions.
3346          if ($showquickgrading && $quickgrading) {
3347              $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, true);
3348              $table = $this->get_renderer()->render($gradingtable);
3349              $page = optional_param('page', null, PARAM_INT);
3350              $quickformparams = array('cm'=>$this->get_course_module()->id,
3351                                       'gradingtable'=>$table,
3352                                       'sendstudentnotifications' => $this->get_instance()->sendstudentnotifications,
3353                                       'page' => $page);
3354              $quickgradingform = new mod_assign_quick_grading_form(null, $quickformparams);
3355  
3356              $o .= $this->get_renderer()->render(new assign_form('quickgradingform', $quickgradingform));
3357          } else {
3358              $gradingtable = new assign_grading_table($this, $perpage, $filter, 0, false);
3359              $o .= $this->get_renderer()->render($gradingtable);
3360          }
3361  
3362          $currentgroup = groups_get_activity_group($this->get_course_module(), true);
3363          $users = array_keys($this->list_participants($currentgroup, true));
3364          if (count($users) != 0 && $this->can_grade()) {
3365              // If no enrolled user in a course then don't display the batch operations feature.
3366              $assignform = new assign_form('gradingbatchoperationsform', $gradingbatchoperationsform);
3367              $o .= $this->get_renderer()->render($assignform);
3368          }
3369          $assignform = new assign_form('gradingoptionsform',
3370                                        $gradingoptionsform,
3371                                        'M.mod_assign.init_grading_options');
3372          $o .= $this->get_renderer()->render($assignform);
3373          return $o;
3374      }
3375  
3376      /**
3377       * View entire grading page.
3378       *
3379       * @return string
3380       */
3381      protected function view_grading_page() {
3382          global $CFG;
3383  
3384          $o = '';
3385          // Need submit permission to submit an assignment.
3386          $this->require_view_grades();
3387          require_once($CFG->dirroot . '/mod/assign/gradeform.php');
3388  
3389          // Only load this if it is.
3390          $o .= $this->view_grading_table();
3391  
3392          $o .= $this->view_footer();
3393  
3394          \mod_assign\event\grading_table_viewed::create_from_assign($this)->trigger();
3395  
3396          return $o;
3397      }
3398  
3399      /**
3400       * Capture the output of the plagiarism plugins disclosures and return it as a string.
3401       *
3402       * @return string
3403       */
3404      protected function plagiarism_print_disclosure() {
3405          global $CFG;
3406          $o = '';
3407  
3408          if (!empty($CFG->enableplagiarism)) {
3409              require_once($CFG->libdir . '/plagiarismlib.php');
3410  
3411              $o .= plagiarism_print_disclosure($this->get_course_module()->id);
3412          }
3413  
3414          return $o;
3415      }
3416  
3417      /**
3418       * Message for students when assignment submissions have been closed.
3419       *
3420       * @param string $title The page title
3421       * @param array $notices The array of notices to show.
3422       * @return string
3423       */
3424      protected function view_notices($title, $notices) {
3425          global $CFG;
3426  
3427          $o = '';
3428  
3429          $header = new assign_header($this->get_instance(),
3430                                      $this->get_context(),
3431                                      $this->show_intro(),
3432                                      $this->get_course_module()->id,
3433                                      $title);
3434          $o .= $this->get_renderer()->render($header);
3435  
3436          foreach ($notices as $notice) {
3437              $o .= $this->get_renderer()->notification($notice);
3438          }
3439  
3440          $url = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id, 'action'=>'view'));
3441          $o .= $this->get_renderer()->continue_button($url);
3442  
3443          $o .= $this->view_footer();
3444  
3445          return $o;
3446      }
3447  
3448      /**
3449       * Get the name for a user - hiding their real name if blind marking is on.
3450       *
3451       * @param stdClass $user The user record as required by fullname()
3452       * @return string The name.
3453       */
3454      public function fullname($user) {
3455          if ($this->is_blind_marking()) {
3456              $hasviewblind = has_capability('mod/assign:viewblinddetails', $this->get_context());
3457              if ($hasviewblind) {
3458                  return fullname($user);
3459              } else {
3460                  $uniqueid = $this->get_uniqueid_for_user($user->id);
3461                  return get_string('participant', 'assign') . ' ' . $uniqueid;
3462              }
3463          } else {
3464              return fullname($user);
3465          }
3466      }
3467  
3468      /**
3469       * View edit submissions page.
3470       *
3471       * @param moodleform $mform
3472       * @param array $notices A list of notices to display at the top of the
3473       *                       edit submission form (e.g. from plugins).
3474       * @return string The page output.
3475       */
3476      protected function view_edit_submission_page($mform, $notices) {
3477          global $CFG, $USER, $DB;
3478  
3479          $o = '';
3480          require_once($CFG->dirroot . '/mod/assign/submission_form.php');
3481          // Need submit permission to submit an assignment.
3482          $userid = optional_param('userid', $USER->id, PARAM_INT);
3483          $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
3484          if ($userid == $USER->id) {
3485              // User is editing their own submission.
3486              require_capability('mod/assign:submit', $this->context);
3487              $title = get_string('editsubmission', 'assign');
3488          } else {
3489              // User is editing another user's submission.
3490              if (!$this->can_edit_submission($userid, $USER->id)) {
3491                  print_error('nopermission');
3492              }
3493  
3494              $name = $this->fullname($user);
3495              $title = get_string('editsubmissionother', 'assign', $name);
3496          }
3497  
3498          if (!$this->submissions_open($userid)) {
3499              $message = array(get_string('submissionsclosed', 'assign'));
3500              return $this->view_notices($title, $message);
3501          }
3502  
3503          $o .= $this->get_renderer()->render(new assign_header($this->get_instance(),
3504                                                        $this->get_context(),
3505                                                        $this->show_intro(),
3506                                                        $this->get_course_module()->id,
3507                                                        $title));
3508          if ($userid == $USER->id) {
3509              // We only show this if it their submission.
3510              $o .= $this->plagiarism_print_disclosure();
3511          }
3512          $data = new stdClass();
3513          $data->userid = $userid;
3514          if (!$mform) {
3515              $mform = new mod_assign_submission_form(null, array($this, $data));
3516          }
3517  
3518          foreach ($notices as $notice) {
3519              $o .= $this->get_renderer()->notification($notice);
3520          }
3521  
3522          $o .= $this->get_renderer()->render(new assign_form('editsubmissionform', $mform));
3523  
3524          $o .= $this->view_footer();
3525  
3526          \mod_assign\event\submission_form_viewed::create_from_user($this, $user)->trigger();
3527  
3528          return $o;
3529      }
3530  
3531      /**
3532       * See if this assignment has a grade yet.
3533       *
3534       * @param int $userid
3535       * @return bool
3536       */
3537      protected function is_graded($userid) {
3538          $grade = $this->get_user_grade($userid, false);
3539          if ($grade) {
3540              return ($grade->grade !== null && $grade->grade >= 0);
3541          }
3542          return false;
3543      }
3544  
3545      /**
3546       * Perform an access check to see if the current $USER can view this group submission.
3547       *
3548       * @param int $groupid
3549       * @return bool
3550       */
3551      public function can_view_group_submission($groupid) {
3552          global $USER;
3553  
3554          if (has_capability('mod/assign:grade', $this->context)) {
3555              return true;
3556          }
3557          if (!is_enrolled($this->get_course_context(), $USER->id)) {
3558              return false;
3559          }
3560          $members = $this->get_submission_group_members($groupid, true);
3561          foreach ($members as $member) {
3562              if ($member->id == $USER->id) {
3563                  return true;
3564              }
3565          }
3566          return false;
3567      }
3568  
3569      /**
3570       * Perform an access check to see if the current $USER can view this users submission.
3571       *
3572       * @param int $userid
3573       * @return bool
3574       */
3575      public function can_view_submission($userid) {
3576          global $USER;
3577  
3578          if (!$this->is_active_user($userid) && !has_capability('moodle/course:viewsuspendedusers', $this->context)) {
3579              return false;
3580          }
3581          if (has_any_capability(array('mod/assign:viewgrades', 'mod/assign:grade'), $this->context)) {
3582              return true;
3583          }
3584          if (!is_enrolled($this->get_course_context(), $userid)) {
3585              return false;
3586          }
3587          if ($userid == $USER->id && has_capability('mod/assign:submit', $this->context)) {
3588              return true;
3589          }
3590          return false;
3591      }
3592  
3593      /**
3594       * Allows the plugin to show a batch grading operation page.
3595       *
3596       * @param moodleform $mform
3597       * @return none
3598       */
3599      protected function view_plugin_grading_batch_operation($mform) {
3600          require_capability('mod/assign:grade', $this->context);
3601          $prefix = 'plugingradingbatchoperation_';
3602  
3603          if ($data = $mform->get_data()) {
3604              $tail = substr($data->operation, strlen($prefix));
3605              list($plugintype, $action) = explode('_', $tail, 2);
3606  
3607              $plugin = $this->get_feedback_plugin_by_type($plugintype);
3608              if ($plugin) {
3609                  $users = $data->selectedusers;
3610                  $userlist = explode(',', $users);
3611                  echo $plugin->grading_batch_operation($action, $userlist);
3612                  return;
3613              }
3614          }
3615          print_error('invalidformdata', '');
3616      }
3617  
3618      /**
3619       * Ask the user to confirm they want to perform this batch operation
3620       *
3621       * @param moodleform $mform Set to a grading batch operations form
3622       * @return string - the page to view after processing these actions
3623       */
3624      protected function process_grading_batch_operation(& $mform) {
3625          global $CFG;
3626          require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
3627          require_sesskey();
3628  
3629          $markingallocation = $this->get_instance()->markingworkflow &&
3630              $this->get_instance()->markingallocation &&
3631              has_capability('mod/assign:manageallocations', $this->context);
3632  
3633          $batchformparams = array('cm'=>$this->get_course_module()->id,
3634                                   'submissiondrafts'=>$this->get_instance()->submissiondrafts,
3635                                   'duedate'=>$this->get_instance()->duedate,
3636                                   'attemptreopenmethod'=>$this->get_instance()->attemptreopenmethod,
3637                                   'feedbackplugins'=>$this->get_feedback_plugins(),
3638                                   'context'=>$this->get_context(),
3639                                   'markingworkflow'=>$this->get_instance()->markingworkflow,
3640                                   'markingallocation'=>$markingallocation);
3641