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