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