See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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 moodle hooks for the assign module. 19 * 20 * It delegates most functions to the assignment class. 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 defined('MOODLE_INTERNAL') || die(); 27 28 /** 29 * Adds an assignment instance 30 * 31 * This is done by calling the add_instance() method of the assignment type class 32 * @param stdClass $data 33 * @param mod_assign_mod_form $form 34 * @return int The instance id of the new assignment 35 */ 36 function assign_add_instance(stdClass $data, mod_assign_mod_form $form = null) { 37 global $CFG; 38 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 39 40 $assignment = new assign(context_module::instance($data->coursemodule), null, null); 41 return $assignment->add_instance($data, true); 42 } 43 44 /** 45 * delete an assignment instance 46 * @param int $id 47 * @return bool 48 */ 49 function assign_delete_instance($id) { 50 global $CFG; 51 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 52 $cm = get_coursemodule_from_instance('assign', $id, 0, false, MUST_EXIST); 53 $context = context_module::instance($cm->id); 54 55 $assignment = new assign($context, null, null); 56 return $assignment->delete_instance(); 57 } 58 59 /** 60 * This function is used by the reset_course_userdata function in moodlelib. 61 * This function will remove all assignment submissions and feedbacks in the database 62 * and clean up any related data. 63 * 64 * @param stdClass $data the data submitted from the reset course. 65 * @return array 66 */ 67 function assign_reset_userdata($data) { 68 global $CFG, $DB; 69 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 70 71 $status = array(); 72 $params = array('courseid'=>$data->courseid); 73 $sql = "SELECT a.id FROM {assign} a WHERE a.course=:courseid"; 74 $course = $DB->get_record('course', array('id'=>$data->courseid), '*', MUST_EXIST); 75 if ($assigns = $DB->get_records_sql($sql, $params)) { 76 foreach ($assigns as $assign) { 77 $cm = get_coursemodule_from_instance('assign', 78 $assign->id, 79 $data->courseid, 80 false, 81 MUST_EXIST); 82 $context = context_module::instance($cm->id); 83 $assignment = new assign($context, $cm, $course); 84 $status = array_merge($status, $assignment->reset_userdata($data)); 85 } 86 } 87 return $status; 88 } 89 90 /** 91 * This standard function will check all instances of this module 92 * and make sure there are up-to-date events created for each of them. 93 * If courseid = 0, then every assignment event in the site is checked, else 94 * only assignment events belonging to the course specified are checked. 95 * 96 * @param int $courseid 97 * @param int|stdClass $instance Assign module instance or ID. 98 * @param int|stdClass $cm Course module object or ID (not used in this module). 99 * @return bool 100 */ 101 function assign_refresh_events($courseid = 0, $instance = null, $cm = null) { 102 global $CFG, $DB; 103 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 104 105 // If we have instance information then we can just update the one event instead of updating all events. 106 if (isset($instance)) { 107 if (!is_object($instance)) { 108 $instance = $DB->get_record('assign', array('id' => $instance), '*', MUST_EXIST); 109 } 110 if (isset($cm)) { 111 if (!is_object($cm)) { 112 assign_prepare_update_events($instance); 113 return true; 114 } else { 115 $course = get_course($instance->course); 116 assign_prepare_update_events($instance, $course, $cm); 117 return true; 118 } 119 } 120 } 121 122 if ($courseid) { 123 // Make sure that the course id is numeric. 124 if (!is_numeric($courseid)) { 125 return false; 126 } 127 if (!$assigns = $DB->get_records('assign', array('course' => $courseid))) { 128 return false; 129 } 130 // Get course from courseid parameter. 131 if (!$course = $DB->get_record('course', array('id' => $courseid), '*')) { 132 return false; 133 } 134 } else { 135 if (!$assigns = $DB->get_records('assign')) { 136 return false; 137 } 138 } 139 foreach ($assigns as $assign) { 140 assign_prepare_update_events($assign); 141 } 142 143 return true; 144 } 145 146 /** 147 * This actually updates the normal and completion calendar events. 148 * 149 * @param stdClass $assign Assignment object (from DB). 150 * @param stdClass $course Course object. 151 * @param stdClass $cm Course module object. 152 */ 153 function assign_prepare_update_events($assign, $course = null, $cm = null) { 154 global $DB; 155 if (!isset($course)) { 156 // Get course and course module for the assignment. 157 list($course, $cm) = get_course_and_cm_from_instance($assign->id, 'assign', $assign->course); 158 } 159 // Refresh the assignment's calendar events. 160 $context = context_module::instance($cm->id); 161 $assignment = new assign($context, $cm, $course); 162 $assignment->update_calendar($cm->id); 163 // Refresh the calendar events also for the assignment overrides. 164 $overrides = $DB->get_records('assign_overrides', ['assignid' => $assign->id], '', 165 'id, groupid, userid, duedate, sortorder'); 166 foreach ($overrides as $override) { 167 if (empty($override->userid)) { 168 unset($override->userid); 169 } 170 if (empty($override->groupid)) { 171 unset($override->groupid); 172 } 173 assign_update_events($assignment, $override); 174 } 175 } 176 177 /** 178 * Removes all grades from gradebook 179 * 180 * @param int $courseid The ID of the course to reset 181 * @param string $type Optional type of assignment to limit the reset to a particular assignment type 182 */ 183 function assign_reset_gradebook($courseid, $type='') { 184 global $CFG, $DB; 185 186 $params = array('moduletype'=>'assign', 'courseid'=>$courseid); 187 $sql = 'SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid 188 FROM {assign} a, {course_modules} cm, {modules} m 189 WHERE m.name=:moduletype AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid'; 190 191 if ($assignments = $DB->get_records_sql($sql, $params)) { 192 foreach ($assignments as $assignment) { 193 assign_grade_item_update($assignment, 'reset'); 194 } 195 } 196 } 197 198 /** 199 * Implementation of the function for printing the form elements that control 200 * whether the course reset functionality affects the assignment. 201 * @param moodleform $mform form passed by reference 202 */ 203 function assign_reset_course_form_definition(&$mform) { 204 $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign')); 205 $name = get_string('deleteallsubmissions', 'assign'); 206 $mform->addElement('advcheckbox', 'reset_assign_submissions', $name); 207 $mform->addElement('advcheckbox', 'reset_assign_user_overrides', 208 get_string('removealluseroverrides', 'assign')); 209 $mform->addElement('advcheckbox', 'reset_assign_group_overrides', 210 get_string('removeallgroupoverrides', 'assign')); 211 } 212 213 /** 214 * Course reset form defaults. 215 * @param object $course 216 * @return array 217 */ 218 function assign_reset_course_form_defaults($course) { 219 return array('reset_assign_submissions' => 1, 220 'reset_assign_group_overrides' => 1, 221 'reset_assign_user_overrides' => 1); 222 } 223 224 /** 225 * Update an assignment instance 226 * 227 * This is done by calling the update_instance() method of the assignment type class 228 * @param stdClass $data 229 * @param stdClass $form - unused 230 * @return object 231 */ 232 function assign_update_instance(stdClass $data, $form) { 233 global $CFG; 234 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 235 $context = context_module::instance($data->coursemodule); 236 $assignment = new assign($context, null, null); 237 return $assignment->update_instance($data); 238 } 239 240 /** 241 * This function updates the events associated to the assign. 242 * If $override is non-zero, then it updates only the events 243 * associated with the specified override. 244 * 245 * @param assign $assign the assign object. 246 * @param object $override (optional) limit to a specific override 247 */ 248 function assign_update_events($assign, $override = null) { 249 global $CFG, $DB; 250 251 require_once($CFG->dirroot . '/calendar/lib.php'); 252 253 $assigninstance = $assign->get_instance(); 254 255 // Load the old events relating to this assign. 256 $conds = array('modulename' => 'assign', 'instance' => $assigninstance->id); 257 if (!empty($override)) { 258 // Only load events for this override. 259 if (isset($override->userid)) { 260 $conds['userid'] = $override->userid; 261 } else if (isset($override->groupid)) { 262 $conds['groupid'] = $override->groupid; 263 } else { 264 // This is not a valid override, it may have been left from a bad import or restore. 265 $conds['groupid'] = $conds['userid'] = 0; 266 } 267 } 268 $oldevents = $DB->get_records('event', $conds, 'id ASC'); 269 270 // Now make a to-do list of all that needs to be updated. 271 if (empty($override)) { 272 // We are updating the primary settings for the assignment, so we need to add all the overrides. 273 $overrides = $DB->get_records('assign_overrides', array('assignid' => $assigninstance->id), 'id ASC'); 274 // It is necessary to add an empty stdClass to the beginning of the array as the $oldevents 275 // list contains the original (non-override) event for the module. If this is not included 276 // the logic below will end up updating the wrong row when we try to reconcile this $overrides 277 // list against the $oldevents list. 278 array_unshift($overrides, new stdClass()); 279 } else { 280 // Just do the one override. 281 $overrides = array($override); 282 } 283 284 if (!empty($assign->get_course_module())) { 285 $cmid = $assign->get_course_module()->id; 286 } else { 287 $cmid = get_coursemodule_from_instance('assign', $assigninstance->id, $assigninstance->course)->id; 288 } 289 290 foreach ($overrides as $current) { 291 $groupid = isset($current->groupid) ? $current->groupid : 0; 292 $userid = isset($current->userid) ? $current->userid : 0; 293 $duedate = isset($current->duedate) ? $current->duedate : $assigninstance->duedate; 294 295 // Only add 'due' events for an override if they differ from the assign default. 296 $addclose = empty($current->id) || !empty($current->duedate); 297 298 $event = new stdClass(); 299 $event->type = CALENDAR_EVENT_TYPE_ACTION; 300 $event->description = format_module_intro('assign', $assigninstance, $cmid, false); 301 $event->format = FORMAT_HTML; 302 // Events module won't show user events when the courseid is nonzero. 303 $event->courseid = ($userid) ? 0 : $assigninstance->course; 304 $event->groupid = $groupid; 305 $event->userid = $userid; 306 $event->modulename = 'assign'; 307 $event->instance = $assigninstance->id; 308 $event->timestart = $duedate; 309 $event->timeduration = 0; 310 $event->timesort = $event->timestart + $event->timeduration; 311 $event->visible = instance_is_visible('assign', $assigninstance); 312 $event->eventtype = ASSIGN_EVENT_TYPE_DUE; 313 $event->priority = null; 314 315 // Determine the event name and priority. 316 if ($groupid) { 317 // Group override event. 318 $params = new stdClass(); 319 $params->assign = $assigninstance->name; 320 $params->group = groups_get_group_name($groupid); 321 if ($params->group === false) { 322 // Group doesn't exist, just skip it. 323 continue; 324 } 325 $eventname = get_string('overridegroupeventname', 'assign', $params); 326 // Set group override priority. 327 if (isset($current->sortorder)) { 328 $event->priority = $current->sortorder; 329 } 330 } else if ($userid) { 331 // User override event. 332 $params = new stdClass(); 333 $params->assign = $assigninstance->name; 334 $eventname = get_string('overrideusereventname', 'assign', $params); 335 // Set user override priority. 336 $event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY; 337 } else { 338 // The parent event. 339 $eventname = $assigninstance->name; 340 } 341 342 if ($duedate && $addclose) { 343 if ($oldevent = array_shift($oldevents)) { 344 $event->id = $oldevent->id; 345 } else { 346 unset($event->id); 347 } 348 $event->name = $eventname.' ('.get_string('duedate', 'assign').')'; 349 calendar_event::create($event, false); 350 } 351 } 352 353 // Delete any leftover events. 354 foreach ($oldevents as $badevent) { 355 $badevent = calendar_event::load($badevent); 356 $badevent->delete(); 357 } 358 } 359 360 /** 361 * Return the list if Moodle features this module supports 362 * 363 * @param string $feature FEATURE_xx constant for requested feature 364 * @return mixed True if module supports feature, null if doesn't know 365 */ 366 function assign_supports($feature) { 367 switch($feature) { 368 case FEATURE_GROUPS: 369 return true; 370 case FEATURE_GROUPINGS: 371 return true; 372 case FEATURE_MOD_INTRO: 373 return true; 374 case FEATURE_COMPLETION_TRACKS_VIEWS: 375 return true; 376 case FEATURE_COMPLETION_HAS_RULES: 377 return true; 378 case FEATURE_GRADE_HAS_GRADE: 379 return true; 380 case FEATURE_GRADE_OUTCOMES: 381 return true; 382 case FEATURE_BACKUP_MOODLE2: 383 return true; 384 case FEATURE_SHOW_DESCRIPTION: 385 return true; 386 case FEATURE_ADVANCED_GRADING: 387 return true; 388 case FEATURE_PLAGIARISM: 389 return true; 390 case FEATURE_COMMENT: 391 return true; 392 393 default: 394 return null; 395 } 396 } 397 398 /** 399 * extend an assigment navigation settings 400 * 401 * @param settings_navigation $settings 402 * @param navigation_node $navref 403 * @return void 404 */ 405 function assign_extend_settings_navigation(settings_navigation $settings, navigation_node $navref) { 406 global $PAGE, $DB; 407 408 // We want to add these new nodes after the Edit settings node, and before the 409 // Locally assigned roles node. Of course, both of those are controlled by capabilities. 410 $keys = $navref->get_children_key_list(); 411 $beforekey = null; 412 $i = array_search('modedit', $keys); 413 if ($i === false and array_key_exists(0, $keys)) { 414 $beforekey = $keys[0]; 415 } else if (array_key_exists($i + 1, $keys)) { 416 $beforekey = $keys[$i + 1]; 417 } 418 419 $cm = $PAGE->cm; 420 if (!$cm) { 421 return; 422 } 423 424 $context = $cm->context; 425 $course = $PAGE->course; 426 427 if (!$course) { 428 return; 429 } 430 431 if (has_capability('mod/assign:manageoverrides', $PAGE->cm->context)) { 432 $url = new moodle_url('/mod/assign/overrides.php', array('cmid' => $PAGE->cm->id)); 433 $node = navigation_node::create(get_string('groupoverrides', 'assign'), 434 new moodle_url($url, array('mode' => 'group')), 435 navigation_node::TYPE_SETTING, null, 'mod_assign_groupoverrides'); 436 $navref->add_node($node, $beforekey); 437 438 $node = navigation_node::create(get_string('useroverrides', 'assign'), 439 new moodle_url($url, array('mode' => 'user')), 440 navigation_node::TYPE_SETTING, null, 'mod_assign_useroverrides'); 441 $navref->add_node($node, $beforekey); 442 } 443 444 // Link to gradebook. 445 if (has_capability('gradereport/grader:view', $cm->context) && 446 has_capability('moodle/grade:viewall', $cm->context)) { 447 $link = new moodle_url('/grade/report/grader/index.php', array('id' => $course->id)); 448 $linkname = get_string('viewgradebook', 'assign'); 449 $node = $navref->add($linkname, $link, navigation_node::TYPE_SETTING); 450 } 451 452 // Link to download all submissions. 453 if (has_any_capability(array('mod/assign:grade', 'mod/assign:viewgrades'), $context)) { 454 $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'grading')); 455 $node = $navref->add(get_string('viewgrading', 'assign'), $link, navigation_node::TYPE_SETTING); 456 457 $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'downloadall')); 458 $node = $navref->add(get_string('downloadall', 'assign'), $link, navigation_node::TYPE_SETTING); 459 } 460 461 if (has_capability('mod/assign:revealidentities', $context)) { 462 $dbparams = array('id'=>$cm->instance); 463 $assignment = $DB->get_record('assign', $dbparams, 'blindmarking, revealidentities'); 464 465 if ($assignment && $assignment->blindmarking && !$assignment->revealidentities) { 466 $urlparams = array('id' => $cm->id, 'action'=>'revealidentities'); 467 $url = new moodle_url('/mod/assign/view.php', $urlparams); 468 $linkname = get_string('revealidentities', 'assign'); 469 $node = $navref->add($linkname, $url, navigation_node::TYPE_SETTING); 470 } 471 } 472 } 473 474 /** 475 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information 476 * for the course (see resource). 477 * 478 * Given a course_module object, this function returns any "extra" information that may be needed 479 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php. 480 * 481 * @param stdClass $coursemodule The coursemodule object (record). 482 * @return cached_cm_info An object on information that the courses 483 * will know about (most noticeably, an icon). 484 */ 485 function assign_get_coursemodule_info($coursemodule) { 486 global $CFG, $DB; 487 488 $dbparams = array('id'=>$coursemodule->instance); 489 $fields = 'id, name, alwaysshowdescription, allowsubmissionsfromdate, intro, introformat, completionsubmit'; 490 if (! $assignment = $DB->get_record('assign', $dbparams, $fields)) { 491 return false; 492 } 493 494 $result = new cached_cm_info(); 495 $result->name = $assignment->name; 496 if ($coursemodule->showdescription) { 497 if ($assignment->alwaysshowdescription || time() > $assignment->allowsubmissionsfromdate) { 498 // Convert intro to html. Do not filter cached version, filters run at display time. 499 $result->content = format_module_intro('assign', $assignment, $coursemodule->id, false); 500 } 501 } 502 503 // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'. 504 if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) { 505 $result->customdata['customcompletionrules']['completionsubmit'] = $assignment->completionsubmit; 506 } 507 508 return $result; 509 } 510 511 /** 512 * Callback which returns human-readable strings describing the active completion custom rules for the module instance. 513 * 514 * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules'] 515 * @return array $descriptions the array of descriptions for the custom rules. 516 */ 517 function mod_assign_get_completion_active_rule_descriptions($cm) { 518 // Values will be present in cm_info, and we assume these are up to date. 519 if (empty($cm->customdata['customcompletionrules']) 520 || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) { 521 return []; 522 } 523 524 $descriptions = []; 525 foreach ($cm->customdata['customcompletionrules'] as $key => $val) { 526 switch ($key) { 527 case 'completionsubmit': 528 if (!empty($val)) { 529 $descriptions[] = get_string('completionsubmit', 'assign'); 530 } 531 break; 532 default: 533 break; 534 } 535 } 536 return $descriptions; 537 } 538 539 /** 540 * Return a list of page types 541 * @param string $pagetype current page type 542 * @param stdClass $parentcontext Block's parent context 543 * @param stdClass $currentcontext Current context of block 544 */ 545 function assign_page_type_list($pagetype, $parentcontext, $currentcontext) { 546 $modulepagetype = array( 547 'mod-assign-*' => get_string('page-mod-assign-x', 'assign'), 548 'mod-assign-view' => get_string('page-mod-assign-view', 'assign'), 549 ); 550 return $modulepagetype; 551 } 552 553 /** 554 * @deprecated since Moodle 3.3, when the block_course_overview block was removed. 555 */ 556 function assign_print_overview() { 557 throw new coding_exception('assign_print_overview() can not be used any more and is obsolete.'); 558 } 559 560 /** 561 * @deprecated since Moodle 3.3, when the block_course_overview block was removed. 562 */ 563 function assign_get_mysubmission_details_for_print_overview() { 564 throw new coding_exception('assign_get_mysubmission_details_for_print_overview() can not be used any more and is obsolete.'); 565 } 566 567 /** 568 * @deprecated since Moodle 3.3, when the block_course_overview block was removed. 569 */ 570 function assign_get_grade_details_for_print_overview() { 571 throw new coding_exception('assign_get_grade_details_for_print_overview() can not be used any more and is obsolete.'); 572 } 573 574 /** 575 * Print recent activity from all assignments in a given course 576 * 577 * This is used by the recent activity block 578 * @param mixed $course the course to print activity for 579 * @param bool $viewfullnames boolean to determine whether to show full names or not 580 * @param int $timestart the time the rendering started 581 * @return bool true if activity was printed, false otherwise. 582 */ 583 function assign_print_recent_activity($course, $viewfullnames, $timestart) { 584 global $CFG, $USER, $DB, $OUTPUT; 585 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 586 587 // Do not use log table if possible, it may be huge. 588 589 $dbparams = array($timestart, $course->id, 'assign', ASSIGN_SUBMISSION_STATUS_SUBMITTED); 590 $namefields = user_picture::fields('u', null, 'userid'); 591 if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, um.id as recordid, 592 $namefields 593 FROM {assign_submission} asb 594 JOIN {assign} a ON a.id = asb.assignment 595 JOIN {course_modules} cm ON cm.instance = a.id 596 JOIN {modules} md ON md.id = cm.module 597 JOIN {user} u ON u.id = asb.userid 598 LEFT JOIN {assign_user_mapping} um ON um.userid = u.id AND um.assignment = a.id 599 WHERE asb.timemodified > ? AND 600 asb.latest = 1 AND 601 a.course = ? AND 602 md.name = ? AND 603 asb.status = ? 604 ORDER BY asb.timemodified ASC", $dbparams)) { 605 return false; 606 } 607 608 $modinfo = get_fast_modinfo($course); 609 $show = array(); 610 $grader = array(); 611 612 $showrecentsubmissions = get_config('assign', 'showrecentsubmissions'); 613 614 foreach ($submissions as $submission) { 615 if (!array_key_exists($submission->cmid, $modinfo->get_cms())) { 616 continue; 617 } 618 $cm = $modinfo->get_cm($submission->cmid); 619 if (!$cm->uservisible) { 620 continue; 621 } 622 if ($submission->userid == $USER->id) { 623 $show[] = $submission; 624 continue; 625 } 626 627 $context = context_module::instance($submission->cmid); 628 // The act of submitting of assignment may be considered private - 629 // only graders will see it if specified. 630 if (empty($showrecentsubmissions)) { 631 if (!array_key_exists($cm->id, $grader)) { 632 $grader[$cm->id] = has_capability('moodle/grade:viewall', $context); 633 } 634 if (!$grader[$cm->id]) { 635 continue; 636 } 637 } 638 639 $groupmode = groups_get_activity_groupmode($cm, $course); 640 641 if ($groupmode == SEPARATEGROUPS && 642 !has_capability('moodle/site:accessallgroups', $context)) { 643 if (isguestuser()) { 644 // Shortcut - guest user does not belong into any group. 645 continue; 646 } 647 648 // This will be slow - show only users that share group with me in this cm. 649 if (!$modinfo->get_groups($cm->groupingid)) { 650 continue; 651 } 652 $usersgroups = groups_get_all_groups($course->id, $submission->userid, $cm->groupingid); 653 if (is_array($usersgroups)) { 654 $usersgroups = array_keys($usersgroups); 655 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid)); 656 if (empty($intersect)) { 657 continue; 658 } 659 } 660 } 661 $show[] = $submission; 662 } 663 664 if (empty($show)) { 665 return false; 666 } 667 668 echo $OUTPUT->heading(get_string('newsubmissions', 'assign') . ':', 6); 669 670 foreach ($show as $submission) { 671 $cm = $modinfo->get_cm($submission->cmid); 672 $context = context_module::instance($submission->cmid); 673 $assign = new assign($context, $cm, $cm->course); 674 $link = $CFG->wwwroot.'/mod/assign/view.php?id='.$cm->id; 675 // Obscure first and last name if blind marking enabled. 676 if ($assign->is_blind_marking()) { 677 $submission->firstname = get_string('participant', 'mod_assign'); 678 if (empty($submission->recordid)) { 679 $submission->recordid = $assign->get_uniqueid_for_user($submission->userid); 680 } 681 $submission->lastname = $submission->recordid; 682 } 683 print_recent_activity_note($submission->timemodified, 684 $submission, 685 $cm->name, 686 $link, 687 false, 688 $viewfullnames); 689 } 690 691 return true; 692 } 693 694 /** 695 * Returns all assignments since a given time. 696 * 697 * @param array $activities The activity information is returned in this array 698 * @param int $index The current index in the activities array 699 * @param int $timestart The earliest activity to show 700 * @param int $courseid Limit the search to this course 701 * @param int $cmid The course module id 702 * @param int $userid Optional user id 703 * @param int $groupid Optional group id 704 * @return void 705 */ 706 function assign_get_recent_mod_activity(&$activities, 707 &$index, 708 $timestart, 709 $courseid, 710 $cmid, 711 $userid=0, 712 $groupid=0) { 713 global $CFG, $COURSE, $USER, $DB; 714 715 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 716 717 if ($COURSE->id == $courseid) { 718 $course = $COURSE; 719 } else { 720 $course = $DB->get_record('course', array('id'=>$courseid)); 721 } 722 723 $modinfo = get_fast_modinfo($course); 724 725 $cm = $modinfo->get_cm($cmid); 726 $params = array(); 727 if ($userid) { 728 $userselect = 'AND u.id = :userid'; 729 $params['userid'] = $userid; 730 } else { 731 $userselect = ''; 732 } 733 734 if ($groupid) { 735 $groupselect = 'AND gm.groupid = :groupid'; 736 $groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id'; 737 $params['groupid'] = $groupid; 738 } else { 739 $groupselect = ''; 740 $groupjoin = ''; 741 } 742 743 $params['cminstance'] = $cm->instance; 744 $params['timestart'] = $timestart; 745 $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 746 747 $userfields = user_picture::fields('u', null, 'userid'); 748 749 if (!$submissions = $DB->get_records_sql('SELECT asb.id, asb.timemodified, ' . 750 $userfields . 751 ' FROM {assign_submission} asb 752 JOIN {assign} a ON a.id = asb.assignment 753 JOIN {user} u ON u.id = asb.userid ' . 754 $groupjoin . 755 ' WHERE asb.timemodified > :timestart AND 756 asb.status = :submitted AND 757 a.id = :cminstance 758 ' . $userselect . ' ' . $groupselect . 759 ' ORDER BY asb.timemodified ASC', $params)) { 760 return; 761 } 762 763 $groupmode = groups_get_activity_groupmode($cm, $course); 764 $cmcontext = context_module::instance($cm->id); 765 $grader = has_capability('moodle/grade:viewall', $cmcontext); 766 $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext); 767 $viewfullnames = has_capability('moodle/site:viewfullnames', $cmcontext); 768 769 770 $showrecentsubmissions = get_config('assign', 'showrecentsubmissions'); 771 $show = array(); 772 foreach ($submissions as $submission) { 773 if ($submission->userid == $USER->id) { 774 $show[] = $submission; 775 continue; 776 } 777 // The act of submitting of assignment may be considered private - 778 // only graders will see it if specified. 779 if (empty($showrecentsubmissions)) { 780 if (!$grader) { 781 continue; 782 } 783 } 784 785 if ($groupmode == SEPARATEGROUPS and !$accessallgroups) { 786 if (isguestuser()) { 787 // Shortcut - guest user does not belong into any group. 788 continue; 789 } 790 791 // This will be slow - show only users that share group with me in this cm. 792 if (!$modinfo->get_groups($cm->groupingid)) { 793 continue; 794 } 795 $usersgroups = groups_get_all_groups($course->id, $submission->userid, $cm->groupingid); 796 if (is_array($usersgroups)) { 797 $usersgroups = array_keys($usersgroups); 798 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid)); 799 if (empty($intersect)) { 800 continue; 801 } 802 } 803 } 804 $show[] = $submission; 805 } 806 807 if (empty($show)) { 808 return; 809 } 810 811 if ($grader) { 812 require_once($CFG->libdir.'/gradelib.php'); 813 $userids = array(); 814 foreach ($show as $id => $submission) { 815 $userids[] = $submission->userid; 816 } 817 $grades = grade_get_grades($courseid, 'mod', 'assign', $cm->instance, $userids); 818 } 819 820 $aname = format_string($cm->name, true); 821 foreach ($show as $submission) { 822 $activity = new stdClass(); 823 824 $activity->type = 'assign'; 825 $activity->cmid = $cm->id; 826 $activity->name = $aname; 827 $activity->sectionnum = $cm->sectionnum; 828 $activity->timestamp = $submission->timemodified; 829 $activity->user = new stdClass(); 830 if ($grader) { 831 $activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade; 832 } 833 834 $userfields = explode(',', user_picture::fields()); 835 foreach ($userfields as $userfield) { 836 if ($userfield == 'id') { 837 // Aliased in SQL above. 838 $activity->user->{$userfield} = $submission->userid; 839 } else { 840 $activity->user->{$userfield} = $submission->{$userfield}; 841 } 842 } 843 $activity->user->fullname = fullname($submission, $viewfullnames); 844 845 $activities[$index++] = $activity; 846 } 847 848 return; 849 } 850 851 /** 852 * Print recent activity from all assignments in a given course 853 * 854 * This is used by course/recent.php 855 * @param stdClass $activity 856 * @param int $courseid 857 * @param bool $detail 858 * @param array $modnames 859 */ 860 function assign_print_recent_mod_activity($activity, $courseid, $detail, $modnames) { 861 global $CFG, $OUTPUT; 862 863 echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">'; 864 865 echo '<tr><td class="userpicture" valign="top">'; 866 echo $OUTPUT->user_picture($activity->user); 867 echo '</td><td>'; 868 869 if ($detail) { 870 $modname = $modnames[$activity->type]; 871 echo '<div class="title">'; 872 echo $OUTPUT->image_icon('icon', $modname, 'assign'); 873 echo '<a href="' . $CFG->wwwroot . '/mod/assign/view.php?id=' . $activity->cmid . '">'; 874 echo $activity->name; 875 echo '</a>'; 876 echo '</div>'; 877 } 878 879 if (isset($activity->grade)) { 880 echo '<div class="grade">'; 881 echo get_string('grade').': '; 882 echo $activity->grade; 883 echo '</div>'; 884 } 885 886 echo '<div class="user">'; 887 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&course=$courseid\">"; 888 echo "{$activity->user->fullname}</a> - " . userdate($activity->timestamp); 889 echo '</div>'; 890 891 echo '</td></tr></table>'; 892 } 893 894 /** 895 * @deprecated since Moodle 3.8 896 */ 897 function assign_scale_used() { 898 throw new coding_exception('assign_scale_used() can not be used anymore. Plugins can implement ' . 899 '<modname>_scale_used_anywhere, all implementations of <modname>_scale_used are now ignored'); 900 } 901 902 /** 903 * Checks if scale is being used by any instance of assignment 904 * 905 * This is used to find out if scale used anywhere 906 * @param int $scaleid 907 * @return boolean True if the scale is used by any assignment 908 */ 909 function assign_scale_used_anywhere($scaleid) { 910 global $DB; 911 912 if ($scaleid and $DB->record_exists('assign', array('grade'=>-$scaleid))) { 913 return true; 914 } else { 915 return false; 916 } 917 } 918 919 /** 920 * List the actions that correspond to a view of this module. 921 * This is used by the participation report. 922 * 923 * Note: This is not used by new logging system. Event with 924 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will 925 * be considered as view action. 926 * 927 * @return array 928 */ 929 function assign_get_view_actions() { 930 return array('view submission', 'view feedback'); 931 } 932 933 /** 934 * List the actions that correspond to a post of this module. 935 * This is used by the participation report. 936 * 937 * Note: This is not used by new logging system. Event with 938 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING 939 * will be considered as post action. 940 * 941 * @return array 942 */ 943 function assign_get_post_actions() { 944 return array('upload', 'submit', 'submit for grading'); 945 } 946 947 /** 948 * Returns all other capabilities used by this module. 949 * @return array Array of capability strings 950 */ 951 function assign_get_extra_capabilities() { 952 return ['gradereport/grader:view', 'moodle/grade:viewall']; 953 } 954 955 /** 956 * Create grade item for given assignment. 957 * 958 * @param stdClass $assign record with extra cmidnumber 959 * @param array $grades optional array/object of grade(s); 'reset' means reset grades in gradebook 960 * @return int 0 if ok, error code otherwise 961 */ 962 function assign_grade_item_update($assign, $grades=null) { 963 global $CFG; 964 require_once($CFG->libdir.'/gradelib.php'); 965 966 if (!isset($assign->courseid)) { 967 $assign->courseid = $assign->course; 968 } 969 970 $params = array('itemname'=>$assign->name, 'idnumber'=>$assign->cmidnumber); 971 972 // Check if feedback plugin for gradebook is enabled, if yes then 973 // gradetype = GRADE_TYPE_TEXT else GRADE_TYPE_NONE. 974 $gradefeedbackenabled = false; 975 976 if (isset($assign->gradefeedbackenabled)) { 977 $gradefeedbackenabled = $assign->gradefeedbackenabled; 978 } else if ($assign->grade == 0) { // Grade feedback is needed only when grade == 0. 979 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 980 $mod = get_coursemodule_from_instance('assign', $assign->id, $assign->courseid); 981 $cm = context_module::instance($mod->id); 982 $assignment = new assign($cm, null, null); 983 $gradefeedbackenabled = $assignment->is_gradebook_feedback_enabled(); 984 } 985 986 if ($assign->grade > 0) { 987 $params['gradetype'] = GRADE_TYPE_VALUE; 988 $params['grademax'] = $assign->grade; 989 $params['grademin'] = 0; 990 991 } else if ($assign->grade < 0) { 992 $params['gradetype'] = GRADE_TYPE_SCALE; 993 $params['scaleid'] = -$assign->grade; 994 995 } else if ($gradefeedbackenabled) { 996 // $assign->grade == 0 and feedback enabled. 997 $params['gradetype'] = GRADE_TYPE_TEXT; 998 } else { 999 // $assign->grade == 0 and no feedback enabled. 1000 $params['gradetype'] = GRADE_TYPE_NONE; 1001 } 1002 1003 if ($grades === 'reset') { 1004 $params['reset'] = true; 1005 $grades = null; 1006 } 1007 1008 return grade_update('mod/assign', 1009 $assign->courseid, 1010 'mod', 1011 'assign', 1012 $assign->id, 1013 0, 1014 $grades, 1015 $params); 1016 } 1017 1018 /** 1019 * Return grade for given user or all users. 1020 * 1021 * @param stdClass $assign record of assign with an additional cmidnumber 1022 * @param int $userid optional user id, 0 means all users 1023 * @return array array of grades, false if none 1024 */ 1025 function assign_get_user_grades($assign, $userid=0) { 1026 global $CFG; 1027 1028 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1029 1030 $cm = get_coursemodule_from_instance('assign', $assign->id, 0, false, MUST_EXIST); 1031 $context = context_module::instance($cm->id); 1032 $assignment = new assign($context, null, null); 1033 $assignment->set_instance($assign); 1034 return $assignment->get_user_grades_for_gradebook($userid); 1035 } 1036 1037 /** 1038 * Update activity grades. 1039 * 1040 * @param stdClass $assign database record 1041 * @param int $userid specific user only, 0 means all 1042 * @param bool $nullifnone - not used 1043 */ 1044 function assign_update_grades($assign, $userid=0, $nullifnone=true) { 1045 global $CFG; 1046 require_once($CFG->libdir.'/gradelib.php'); 1047 1048 if ($assign->grade == 0) { 1049 assign_grade_item_update($assign); 1050 1051 } else if ($grades = assign_get_user_grades($assign, $userid)) { 1052 foreach ($grades as $k => $v) { 1053 if ($v->rawgrade == -1) { 1054 $grades[$k]->rawgrade = null; 1055 } 1056 } 1057 assign_grade_item_update($assign, $grades); 1058 1059 } else { 1060 assign_grade_item_update($assign); 1061 } 1062 } 1063 1064 /** 1065 * List the file areas that can be browsed. 1066 * 1067 * @param stdClass $course 1068 * @param stdClass $cm 1069 * @param stdClass $context 1070 * @return array 1071 */ 1072 function assign_get_file_areas($course, $cm, $context) { 1073 global $CFG; 1074 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1075 1076 $areas = array(ASSIGN_INTROATTACHMENT_FILEAREA => get_string('introattachments', 'mod_assign')); 1077 1078 $assignment = new assign($context, $cm, $course); 1079 foreach ($assignment->get_submission_plugins() as $plugin) { 1080 if ($plugin->is_visible()) { 1081 $pluginareas = $plugin->get_file_areas(); 1082 1083 if ($pluginareas) { 1084 $areas = array_merge($areas, $pluginareas); 1085 } 1086 } 1087 } 1088 foreach ($assignment->get_feedback_plugins() as $plugin) { 1089 if ($plugin->is_visible()) { 1090 $pluginareas = $plugin->get_file_areas(); 1091 1092 if ($pluginareas) { 1093 $areas = array_merge($areas, $pluginareas); 1094 } 1095 } 1096 } 1097 1098 return $areas; 1099 } 1100 1101 /** 1102 * File browsing support for assign module. 1103 * 1104 * @param file_browser $browser 1105 * @param object $areas 1106 * @param object $course 1107 * @param object $cm 1108 * @param object $context 1109 * @param string $filearea 1110 * @param int $itemid 1111 * @param string $filepath 1112 * @param string $filename 1113 * @return object file_info instance or null if not found 1114 */ 1115 function assign_get_file_info($browser, 1116 $areas, 1117 $course, 1118 $cm, 1119 $context, 1120 $filearea, 1121 $itemid, 1122 $filepath, 1123 $filename) { 1124 global $CFG; 1125 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1126 1127 if ($context->contextlevel != CONTEXT_MODULE) { 1128 return null; 1129 } 1130 1131 $urlbase = $CFG->wwwroot.'/pluginfile.php'; 1132 $fs = get_file_storage(); 1133 $filepath = is_null($filepath) ? '/' : $filepath; 1134 $filename = is_null($filename) ? '.' : $filename; 1135 1136 // Need to find where this belongs to. 1137 $assignment = new assign($context, $cm, $course); 1138 if ($filearea === ASSIGN_INTROATTACHMENT_FILEAREA) { 1139 if (!has_capability('moodle/course:managefiles', $context)) { 1140 // Students can not peak here! 1141 return null; 1142 } 1143 if (!($storedfile = $fs->get_file($assignment->get_context()->id, 1144 'mod_assign', $filearea, 0, $filepath, $filename))) { 1145 return null; 1146 } 1147 return new file_info_stored($browser, 1148 $assignment->get_context(), 1149 $storedfile, 1150 $urlbase, 1151 $filearea, 1152 $itemid, 1153 true, 1154 true, 1155 false); 1156 } 1157 1158 $pluginowner = null; 1159 foreach ($assignment->get_submission_plugins() as $plugin) { 1160 if ($plugin->is_visible()) { 1161 $pluginareas = $plugin->get_file_areas(); 1162 1163 if (array_key_exists($filearea, $pluginareas)) { 1164 $pluginowner = $plugin; 1165 break; 1166 } 1167 } 1168 } 1169 if (!$pluginowner) { 1170 foreach ($assignment->get_feedback_plugins() as $plugin) { 1171 if ($plugin->is_visible()) { 1172 $pluginareas = $plugin->get_file_areas(); 1173 1174 if (array_key_exists($filearea, $pluginareas)) { 1175 $pluginowner = $plugin; 1176 break; 1177 } 1178 } 1179 } 1180 } 1181 1182 if (!$pluginowner) { 1183 return null; 1184 } 1185 1186 $result = $pluginowner->get_file_info($browser, $filearea, $itemid, $filepath, $filename); 1187 return $result; 1188 } 1189 1190 /** 1191 * Prints the complete info about a user's interaction with an assignment. 1192 * 1193 * @param stdClass $course 1194 * @param stdClass $user 1195 * @param stdClass $coursemodule 1196 * @param stdClass $assign the database assign record 1197 * 1198 * This prints the submission summary and feedback summary for this student. 1199 */ 1200 function assign_user_complete($course, $user, $coursemodule, $assign) { 1201 global $CFG; 1202 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1203 1204 $context = context_module::instance($coursemodule->id); 1205 1206 $assignment = new assign($context, $coursemodule, $course); 1207 1208 echo $assignment->view_student_summary($user, false); 1209 } 1210 1211 /** 1212 * Rescale all grades for this activity and push the new grades to the gradebook. 1213 * 1214 * @param stdClass $course Course db record 1215 * @param stdClass $cm Course module db record 1216 * @param float $oldmin 1217 * @param float $oldmax 1218 * @param float $newmin 1219 * @param float $newmax 1220 */ 1221 function assign_rescale_activity_grades($course, $cm, $oldmin, $oldmax, $newmin, $newmax) { 1222 global $DB; 1223 1224 if ($oldmax <= $oldmin) { 1225 // Grades cannot be scaled. 1226 return false; 1227 } 1228 $scale = ($newmax - $newmin) / ($oldmax - $oldmin); 1229 if (($newmax - $newmin) <= 1) { 1230 // We would lose too much precision, lets bail. 1231 return false; 1232 } 1233 1234 $params = array( 1235 'p1' => $oldmin, 1236 'p2' => $scale, 1237 'p3' => $newmin, 1238 'a' => $cm->instance 1239 ); 1240 1241 // Only rescale grades that are greater than or equal to 0. Anything else is a special value. 1242 $sql = 'UPDATE {assign_grades} set grade = (((grade - :p1) * :p2) + :p3) where assignment = :a and grade >= 0'; 1243 $dbupdate = $DB->execute($sql, $params); 1244 if (!$dbupdate) { 1245 return false; 1246 } 1247 1248 // Now re-push all grades to the gradebook. 1249 $dbparams = array('id' => $cm->instance); 1250 $assign = $DB->get_record('assign', $dbparams); 1251 $assign->cmidnumber = $cm->idnumber; 1252 1253 assign_update_grades($assign); 1254 1255 return true; 1256 } 1257 1258 /** 1259 * Print the grade information for the assignment for this user. 1260 * 1261 * @param stdClass $course 1262 * @param stdClass $user 1263 * @param stdClass $coursemodule 1264 * @param stdClass $assignment 1265 */ 1266 function assign_user_outline($course, $user, $coursemodule, $assignment) { 1267 global $CFG; 1268 require_once($CFG->libdir.'/gradelib.php'); 1269 require_once($CFG->dirroot.'/grade/grading/lib.php'); 1270 1271 $gradinginfo = grade_get_grades($course->id, 1272 'mod', 1273 'assign', 1274 $assignment->id, 1275 $user->id); 1276 1277 $gradingitem = $gradinginfo->items[0]; 1278 $gradebookgrade = $gradingitem->grades[$user->id]; 1279 1280 if (empty($gradebookgrade->str_long_grade)) { 1281 return null; 1282 } 1283 $result = new stdClass(); 1284 if (!$gradingitem->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) { 1285 $result->info = get_string('outlinegrade', 'assign', $gradebookgrade->str_long_grade); 1286 } else { 1287 $result->info = get_string('grade') . ': ' . get_string('hidden', 'grades'); 1288 } 1289 $result->time = $gradebookgrade->dategraded; 1290 1291 return $result; 1292 } 1293 1294 /** 1295 * Obtains the automatic completion state for this module based on any conditions 1296 * in assign settings. 1297 * 1298 * @param object $course Course 1299 * @param object $cm Course-module 1300 * @param int $userid User ID 1301 * @param bool $type Type of comparison (or/and; can be used as return value if no conditions) 1302 * @return bool True if completed, false if not, $type if conditions not set. 1303 */ 1304 function assign_get_completion_state($course, $cm, $userid, $type) { 1305 global $CFG, $DB; 1306 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1307 1308 $assign = new assign(null, $cm, $course); 1309 1310 // If completion option is enabled, evaluate it and return true/false. 1311 if ($assign->get_instance()->completionsubmit) { 1312 if ($assign->get_instance()->teamsubmission) { 1313 $submission = $assign->get_group_submission($userid, 0, false); 1314 } else { 1315 $submission = $assign->get_user_submission($userid, false); 1316 } 1317 return $submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED; 1318 } else { 1319 // Completion option is not enabled so just return $type. 1320 return $type; 1321 } 1322 } 1323 1324 /** 1325 * Serves intro attachment files. 1326 * 1327 * @param mixed $course course or id of the course 1328 * @param mixed $cm course module or id of the course module 1329 * @param context $context 1330 * @param string $filearea 1331 * @param array $args 1332 * @param bool $forcedownload 1333 * @param array $options additional options affecting the file serving 1334 * @return bool false if file not found, does not return if found - just send the file 1335 */ 1336 function assign_pluginfile($course, 1337 $cm, 1338 context $context, 1339 $filearea, 1340 $args, 1341 $forcedownload, 1342 array $options=array()) { 1343 global $CFG; 1344 1345 if ($context->contextlevel != CONTEXT_MODULE) { 1346 return false; 1347 } 1348 1349 require_login($course, false, $cm); 1350 if (!has_capability('mod/assign:view', $context)) { 1351 return false; 1352 } 1353 1354 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1355 $assign = new assign($context, $cm, $course); 1356 1357 if ($filearea !== ASSIGN_INTROATTACHMENT_FILEAREA) { 1358 return false; 1359 } 1360 if (!$assign->show_intro()) { 1361 return false; 1362 } 1363 1364 $itemid = (int)array_shift($args); 1365 if ($itemid != 0) { 1366 return false; 1367 } 1368 1369 $relativepath = implode('/', $args); 1370 1371 $fullpath = "/{$context->id}/mod_assign/$filearea/$itemid/$relativepath"; 1372 1373 $fs = get_file_storage(); 1374 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 1375 return false; 1376 } 1377 send_stored_file($file, 0, 0, $forcedownload, $options); 1378 } 1379 1380 /** 1381 * Serve the grading panel as a fragment. 1382 * 1383 * @param array $args List of named arguments for the fragment loader. 1384 * @return string 1385 */ 1386 function mod_assign_output_fragment_gradingpanel($args) { 1387 global $CFG; 1388 1389 $context = $args['context']; 1390 1391 if ($context->contextlevel != CONTEXT_MODULE) { 1392 return null; 1393 } 1394 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1395 $assign = new assign($context, null, null); 1396 1397 $userid = clean_param($args['userid'], PARAM_INT); 1398 $attemptnumber = clean_param($args['attemptnumber'], PARAM_INT); 1399 $formdata = array(); 1400 if (!empty($args['jsonformdata'])) { 1401 $serialiseddata = json_decode($args['jsonformdata']); 1402 parse_str($serialiseddata, $formdata); 1403 } 1404 $viewargs = array( 1405 'userid' => $userid, 1406 'attemptnumber' => $attemptnumber, 1407 'formdata' => $formdata 1408 ); 1409 1410 return $assign->view('gradingpanel', $viewargs); 1411 } 1412 1413 /** 1414 * Check if the module has any update that affects the current user since a given time. 1415 * 1416 * @param cm_info $cm course module data 1417 * @param int $from the time to check updates from 1418 * @param array $filter if we need to check only specific updates 1419 * @return stdClass an object with the different type of areas indicating if they were updated or not 1420 * @since Moodle 3.2 1421 */ 1422 function assign_check_updates_since(cm_info $cm, $from, $filter = array()) { 1423 global $DB, $USER, $CFG; 1424 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1425 1426 $updates = new stdClass(); 1427 $updates = course_check_module_updates_since($cm, $from, array(ASSIGN_INTROATTACHMENT_FILEAREA), $filter); 1428 1429 // Check if there is a new submission by the user or new grades. 1430 $select = 'assignment = :id AND userid = :userid AND (timecreated > :since1 OR timemodified > :since2)'; 1431 $params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from); 1432 $updates->submissions = (object) array('updated' => false); 1433 $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id'); 1434 if (!empty($submissions)) { 1435 $updates->submissions->updated = true; 1436 $updates->submissions->itemids = array_keys($submissions); 1437 } 1438 1439 $updates->grades = (object) array('updated' => false); 1440 $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id'); 1441 if (!empty($grades)) { 1442 $updates->grades->updated = true; 1443 $updates->grades->itemids = array_keys($grades); 1444 } 1445 1446 // Now, teachers should see other students updates. 1447 if (has_capability('mod/assign:viewgrades', $cm->context)) { 1448 $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from); 1449 $select = 'assignment = :id AND (timecreated > :since1 OR timemodified > :since2)'; 1450 1451 if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) { 1452 $groupusers = array_keys(groups_get_activity_shared_group_members($cm)); 1453 if (empty($groupusers)) { 1454 return $updates; 1455 } 1456 list($insql, $inparams) = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED); 1457 $select .= ' AND userid ' . $insql; 1458 $params = array_merge($params, $inparams); 1459 } 1460 1461 $updates->usersubmissions = (object) array('updated' => false); 1462 $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id'); 1463 if (!empty($submissions)) { 1464 $updates->usersubmissions->updated = true; 1465 $updates->usersubmissions->itemids = array_keys($submissions); 1466 } 1467 1468 $updates->usergrades = (object) array('updated' => false); 1469 $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id'); 1470 if (!empty($grades)) { 1471 $updates->usergrades->updated = true; 1472 $updates->usergrades->itemids = array_keys($grades); 1473 } 1474 } 1475 1476 return $updates; 1477 } 1478 1479 /** 1480 * Is the event visible? 1481 * 1482 * This is used to determine global visibility of an event in all places throughout Moodle. For example, 1483 * the ASSIGN_EVENT_TYPE_GRADINGDUE event will not be shown to students on their calendar. 1484 * 1485 * @param calendar_event $event 1486 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default). 1487 * @return bool Returns true if the event is visible to the current user, false otherwise. 1488 */ 1489 function mod_assign_core_calendar_is_event_visible(calendar_event $event, $userid = 0) { 1490 global $CFG, $USER; 1491 1492 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1493 1494 if (empty($userid)) { 1495 $userid = $USER->id; 1496 } 1497 1498 $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance]; 1499 $context = context_module::instance($cm->id); 1500 1501 $assign = new assign($context, $cm, null); 1502 1503 if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { 1504 return $assign->can_grade($userid); 1505 } else { 1506 return true; 1507 } 1508 } 1509 1510 /** 1511 * This function receives a calendar event and returns the action associated with it, or null if there is none. 1512 * 1513 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event 1514 * is not displayed on the block. 1515 * 1516 * @param calendar_event $event 1517 * @param \core_calendar\action_factory $factory 1518 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default). 1519 * @return \core_calendar\local\event\entities\action_interface|null 1520 */ 1521 function mod_assign_core_calendar_provide_event_action(calendar_event $event, 1522 \core_calendar\action_factory $factory, 1523 $userid = 0) { 1524 1525 global $CFG, $USER; 1526 1527 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1528 1529 if (empty($userid)) { 1530 $userid = $USER->id; 1531 } 1532 1533 $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance]; 1534 $context = context_module::instance($cm->id); 1535 1536 $completion = new \completion_info($cm->get_course()); 1537 1538 $completiondata = $completion->get_data($cm, false, $userid); 1539 1540 if ($completiondata->completionstate != COMPLETION_INCOMPLETE) { 1541 return null; 1542 } 1543 1544 $assign = new assign($context, $cm, null); 1545 1546 // Apply overrides. 1547 $assign->update_effective_access($userid); 1548 1549 if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { 1550 $name = get_string('grade'); 1551 $url = new \moodle_url('/mod/assign/view.php', [ 1552 'id' => $cm->id, 1553 'action' => 'grader' 1554 ]); 1555 $actionable = $assign->can_grade($userid) && (time() >= $assign->get_instance()->allowsubmissionsfromdate); 1556 $itemcount = $actionable ? $assign->count_submissions_need_grading() : 0; 1557 } else { 1558 $usersubmission = $assign->get_user_submission($userid, false); 1559 if ($usersubmission && $usersubmission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) { 1560 // The user has already submitted. 1561 // We do not want to change the text to edit the submission, we want to remove the event from the Dashboard entirely. 1562 return null; 1563 } 1564 1565 $participant = $assign->get_participant($userid); 1566 1567 if (!$participant) { 1568 // If the user is not a participant in the assignment then they have 1569 // no action to take. This will filter out the events for teachers. 1570 return null; 1571 } 1572 1573 // The user has not yet submitted anything. Show the addsubmission link. 1574 $name = get_string('addsubmission', 'assign'); 1575 $url = new \moodle_url('/mod/assign/view.php', [ 1576 'id' => $cm->id, 1577 'action' => 'editsubmission' 1578 ]); 1579 $itemcount = 1; 1580 $actionable = $assign->is_any_submission_plugin_enabled() && $assign->can_edit_submission($userid, $userid); 1581 } 1582 1583 return $factory->create_instance( 1584 $name, 1585 $url, 1586 $itemcount, 1587 $actionable 1588 ); 1589 } 1590 1591 /** 1592 * Callback function that determines whether an action event should be showing its item count 1593 * based on the event type and the item count. 1594 * 1595 * @param calendar_event $event The calendar event. 1596 * @param int $itemcount The item count associated with the action event. 1597 * @return bool 1598 */ 1599 function mod_assign_core_calendar_event_action_shows_item_count(calendar_event $event, $itemcount = 0) { 1600 // List of event types where the action event's item count should be shown. 1601 $eventtypesshowingitemcount = [ 1602 ASSIGN_EVENT_TYPE_GRADINGDUE 1603 ]; 1604 // For mod_assign, item count should be shown if the event type is 'gradingdue' and there is one or more item count. 1605 return in_array($event->eventtype, $eventtypesshowingitemcount) && $itemcount > 0; 1606 } 1607 1608 /** 1609 * This function calculates the minimum and maximum cutoff values for the timestart of 1610 * the given event. 1611 * 1612 * It will return an array with two values, the first being the minimum cutoff value and 1613 * the second being the maximum cutoff value. Either or both values can be null, which 1614 * indicates there is no minimum or maximum, respectively. 1615 * 1616 * If a cutoff is required then the function must return an array containing the cutoff 1617 * timestamp and error string to display to the user if the cutoff value is violated. 1618 * 1619 * A minimum and maximum cutoff return value will look like: 1620 * [ 1621 * [1505704373, 'The due date must be after the sbumission start date'], 1622 * [1506741172, 'The due date must be before the cutoff date'] 1623 * ] 1624 * 1625 * If the event does not have a valid timestart range then [false, false] will 1626 * be returned. 1627 * 1628 * @param calendar_event $event The calendar event to get the time range for 1629 * @param stdClass $instance The module instance to get the range from 1630 * @return array 1631 */ 1632 function mod_assign_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) { 1633 global $CFG; 1634 1635 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1636 1637 $courseid = $event->courseid; 1638 $modulename = $event->modulename; 1639 $instanceid = $event->instance; 1640 $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid]; 1641 $context = context_module::instance($coursemodule->id); 1642 $assign = new assign($context, null, null); 1643 $assign->set_instance($instance); 1644 1645 return $assign->get_valid_calendar_event_timestart_range($event); 1646 } 1647 1648 /** 1649 * This function will update the assign module according to the 1650 * event that has been modified. 1651 * 1652 * @throws \moodle_exception 1653 * @param \calendar_event $event 1654 * @param stdClass $instance The module instance to get the range from 1655 */ 1656 function mod_assign_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $instance) { 1657 global $CFG, $DB; 1658 1659 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1660 1661 if (empty($event->instance) || $event->modulename != 'assign') { 1662 return; 1663 } 1664 1665 if ($instance->id != $event->instance) { 1666 return; 1667 } 1668 1669 if (!in_array($event->eventtype, [ASSIGN_EVENT_TYPE_DUE, ASSIGN_EVENT_TYPE_GRADINGDUE])) { 1670 return; 1671 } 1672 1673 $courseid = $event->courseid; 1674 $modulename = $event->modulename; 1675 $instanceid = $event->instance; 1676 $modified = false; 1677 $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid]; 1678 $context = context_module::instance($coursemodule->id); 1679 1680 // The user does not have the capability to modify this activity. 1681 if (!has_capability('moodle/course:manageactivities', $context)) { 1682 return; 1683 } 1684 1685 $assign = new assign($context, $coursemodule, null); 1686 $assign->set_instance($instance); 1687 1688 if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) { 1689 // This check is in here because due date events are currently 1690 // the only events that can be overridden, so we can save a DB 1691 // query if we don't bother checking other events. 1692 if ($assign->is_override_calendar_event($event)) { 1693 // This is an override event so we should ignore it. 1694 return; 1695 } 1696 1697 $newduedate = $event->timestart; 1698 1699 if ($newduedate != $instance->duedate) { 1700 $instance->duedate = $newduedate; 1701 $modified = true; 1702 } 1703 } else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { 1704 $newduedate = $event->timestart; 1705 1706 if ($newduedate != $instance->gradingduedate) { 1707 $instance->gradingduedate = $newduedate; 1708 $modified = true; 1709 } 1710 } 1711 1712 if ($modified) { 1713 $instance->timemodified = time(); 1714 // Persist the assign instance changes. 1715 $DB->update_record('assign', $instance); 1716 $assign->update_calendar($coursemodule->id); 1717 $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context); 1718 $event->trigger(); 1719 } 1720 } 1721 1722 /** 1723 * Return a list of all the user preferences used by mod_assign. 1724 * 1725 * @return array 1726 */ 1727 function mod_assign_user_preferences() { 1728 $preferences = array(); 1729 $preferences['assign_filter'] = array( 1730 'type' => PARAM_ALPHA, 1731 'null' => NULL_NOT_ALLOWED, 1732 'default' => '' 1733 ); 1734 $preferences['assign_workflowfilter'] = array( 1735 'type' => PARAM_ALPHA, 1736 'null' => NULL_NOT_ALLOWED, 1737 'default' => '' 1738 ); 1739 $preferences['assign_markerfilter'] = array( 1740 'type' => PARAM_ALPHANUMEXT, 1741 'null' => NULL_NOT_ALLOWED, 1742 'default' => '' 1743 ); 1744 1745 return $preferences; 1746 } 1747 1748 /** 1749 * Given an array with a file path, it returns the itemid and the filepath for the defined filearea. 1750 * 1751 * @param string $filearea The filearea. 1752 * @param array $args The path (the part after the filearea and before the filename). 1753 * @return array The itemid and the filepath inside the $args path, for the defined filearea. 1754 */ 1755 function mod_assign_get_path_from_pluginfile(string $filearea, array $args) : array { 1756 // Assign never has an itemid (the number represents the revision but it's not stored in database). 1757 array_shift($args); 1758 1759 // Get the filepath. 1760 if (empty($args)) { 1761 $filepath = '/'; 1762 } else { 1763 $filepath = '/' . implode('/', $args) . '/'; 1764 } 1765 1766 return [ 1767 'itemid' => 0, 1768 'filepath' => $filepath, 1769 ]; 1770 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body