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