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