Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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 MoodleQuickForm $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 * Print recent activity from all assignments in a given course 623 * 624 * This is used by the recent activity block 625 * @param mixed $course the course to print activity for 626 * @param bool $viewfullnames boolean to determine whether to show full names or not 627 * @param int $timestart the time the rendering started 628 * @return bool true if activity was printed, false otherwise. 629 */ 630 function assign_print_recent_activity($course, $viewfullnames, $timestart) { 631 global $CFG, $USER, $DB, $OUTPUT; 632 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 633 634 // Do not use log table if possible, it may be huge. 635 636 $dbparams = array($timestart, $course->id, 'assign', ASSIGN_SUBMISSION_STATUS_SUBMITTED); 637 $userfieldsapi = \core_user\fields::for_userpic(); 638 $namefields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;; 639 if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, um.id as recordid, 640 $namefields 641 FROM {assign_submission} asb 642 JOIN {assign} a ON a.id = asb.assignment 643 JOIN {course_modules} cm ON cm.instance = a.id 644 JOIN {modules} md ON md.id = cm.module 645 JOIN {user} u ON u.id = asb.userid 646 LEFT JOIN {assign_user_mapping} um ON um.userid = u.id AND um.assignment = a.id 647 WHERE asb.timemodified > ? AND 648 asb.latest = 1 AND 649 a.course = ? AND 650 md.name = ? AND 651 asb.status = ? 652 ORDER BY asb.timemodified ASC", $dbparams)) { 653 return false; 654 } 655 656 $modinfo = get_fast_modinfo($course); 657 $show = array(); 658 $grader = array(); 659 660 $showrecentsubmissions = get_config('assign', 'showrecentsubmissions'); 661 662 foreach ($submissions as $submission) { 663 if (!array_key_exists($submission->cmid, $modinfo->get_cms())) { 664 continue; 665 } 666 $cm = $modinfo->get_cm($submission->cmid); 667 if (!$cm->uservisible) { 668 continue; 669 } 670 if ($submission->userid == $USER->id) { 671 $show[] = $submission; 672 continue; 673 } 674 675 $context = context_module::instance($submission->cmid); 676 // The act of submitting of assignment may be considered private - 677 // only graders will see it if specified. 678 if (empty($showrecentsubmissions)) { 679 if (!array_key_exists($cm->id, $grader)) { 680 $grader[$cm->id] = has_capability('moodle/grade:viewall', $context); 681 } 682 if (!$grader[$cm->id]) { 683 continue; 684 } 685 } 686 687 $groupmode = groups_get_activity_groupmode($cm, $course); 688 689 if ($groupmode == SEPARATEGROUPS && 690 !has_capability('moodle/site:accessallgroups', $context)) { 691 if (isguestuser()) { 692 // Shortcut - guest user does not belong into any group. 693 continue; 694 } 695 696 // This will be slow - show only users that share group with me in this cm. 697 if (!$modinfo->get_groups($cm->groupingid)) { 698 continue; 699 } 700 $usersgroups = groups_get_all_groups($course->id, $submission->userid, $cm->groupingid); 701 if (is_array($usersgroups)) { 702 $usersgroups = array_keys($usersgroups); 703 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid)); 704 if (empty($intersect)) { 705 continue; 706 } 707 } 708 } 709 $show[] = $submission; 710 } 711 712 if (empty($show)) { 713 return false; 714 } 715 716 echo $OUTPUT->heading(get_string('newsubmissions', 'assign') . ':', 6); 717 718 foreach ($show as $submission) { 719 $cm = $modinfo->get_cm($submission->cmid); 720 $context = context_module::instance($submission->cmid); 721 $assign = new assign($context, $cm, $cm->course); 722 $link = $CFG->wwwroot.'/mod/assign/view.php?id='.$cm->id; 723 // Obscure first and last name if blind marking enabled. 724 if ($assign->is_blind_marking()) { 725 $submission->firstname = get_string('participant', 'mod_assign'); 726 if (empty($submission->recordid)) { 727 $submission->recordid = $assign->get_uniqueid_for_user($submission->userid); 728 } 729 $submission->lastname = $submission->recordid; 730 } 731 print_recent_activity_note($submission->timemodified, 732 $submission, 733 $cm->name, 734 $link, 735 false, 736 $viewfullnames); 737 } 738 739 return true; 740 } 741 742 /** 743 * Returns all assignments since a given time. 744 * 745 * @param array $activities The activity information is returned in this array 746 * @param int $index The current index in the activities array 747 * @param int $timestart The earliest activity to show 748 * @param int $courseid Limit the search to this course 749 * @param int $cmid The course module id 750 * @param int $userid Optional user id 751 * @param int $groupid Optional group id 752 * @return void 753 */ 754 function assign_get_recent_mod_activity(&$activities, 755 &$index, 756 $timestart, 757 $courseid, 758 $cmid, 759 $userid=0, 760 $groupid=0) { 761 global $CFG, $COURSE, $USER, $DB; 762 763 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 764 765 if ($COURSE->id == $courseid) { 766 $course = $COURSE; 767 } else { 768 $course = $DB->get_record('course', array('id'=>$courseid)); 769 } 770 771 $modinfo = get_fast_modinfo($course); 772 773 $cm = $modinfo->get_cm($cmid); 774 $params = array(); 775 if ($userid) { 776 $userselect = 'AND u.id = :userid'; 777 $params['userid'] = $userid; 778 } else { 779 $userselect = ''; 780 } 781 782 if ($groupid) { 783 $groupselect = 'AND gm.groupid = :groupid'; 784 $groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id'; 785 $params['groupid'] = $groupid; 786 } else { 787 $groupselect = ''; 788 $groupjoin = ''; 789 } 790 791 $params['cminstance'] = $cm->instance; 792 $params['timestart'] = $timestart; 793 $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED; 794 795 $userfieldsapi = \core_user\fields::for_userpic(); 796 $userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects; 797 798 if (!$submissions = $DB->get_records_sql('SELECT asb.id, asb.timemodified, ' . 799 $userfields . 800 ' FROM {assign_submission} asb 801 JOIN {assign} a ON a.id = asb.assignment 802 JOIN {user} u ON u.id = asb.userid ' . 803 $groupjoin . 804 ' WHERE asb.timemodified > :timestart AND 805 asb.status = :submitted AND 806 a.id = :cminstance 807 ' . $userselect . ' ' . $groupselect . 808 ' ORDER BY asb.timemodified ASC', $params)) { 809 return; 810 } 811 812 $groupmode = groups_get_activity_groupmode($cm, $course); 813 $cmcontext = context_module::instance($cm->id); 814 $grader = has_capability('moodle/grade:viewall', $cmcontext); 815 $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext); 816 $viewfullnames = has_capability('moodle/site:viewfullnames', $cmcontext); 817 818 819 $showrecentsubmissions = get_config('assign', 'showrecentsubmissions'); 820 $show = array(); 821 foreach ($submissions as $submission) { 822 if ($submission->userid == $USER->id) { 823 $show[] = $submission; 824 continue; 825 } 826 // The act of submitting of assignment may be considered private - 827 // only graders will see it if specified. 828 if (empty($showrecentsubmissions)) { 829 if (!$grader) { 830 continue; 831 } 832 } 833 834 if ($groupmode == SEPARATEGROUPS and !$accessallgroups) { 835 if (isguestuser()) { 836 // Shortcut - guest user does not belong into any group. 837 continue; 838 } 839 840 // This will be slow - show only users that share group with me in this cm. 841 if (!$modinfo->get_groups($cm->groupingid)) { 842 continue; 843 } 844 $usersgroups = groups_get_all_groups($course->id, $submission->userid, $cm->groupingid); 845 if (is_array($usersgroups)) { 846 $usersgroups = array_keys($usersgroups); 847 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid)); 848 if (empty($intersect)) { 849 continue; 850 } 851 } 852 } 853 $show[] = $submission; 854 } 855 856 if (empty($show)) { 857 return; 858 } 859 860 if ($grader) { 861 require_once($CFG->libdir.'/gradelib.php'); 862 $userids = array(); 863 foreach ($show as $id => $submission) { 864 $userids[] = $submission->userid; 865 } 866 $grades = grade_get_grades($courseid, 'mod', 'assign', $cm->instance, $userids); 867 } 868 869 $aname = format_string($cm->name, true); 870 foreach ($show as $submission) { 871 $activity = new stdClass(); 872 873 $activity->type = 'assign'; 874 $activity->cmid = $cm->id; 875 $activity->name = $aname; 876 $activity->sectionnum = $cm->sectionnum; 877 $activity->timestamp = $submission->timemodified; 878 $activity->user = new stdClass(); 879 if ($grader) { 880 $activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade; 881 } 882 883 $userfields = explode(',', implode(',', \core_user\fields::get_picture_fields())); 884 foreach ($userfields as $userfield) { 885 if ($userfield == 'id') { 886 // Aliased in SQL above. 887 $activity->user->{$userfield} = $submission->userid; 888 } else { 889 $activity->user->{$userfield} = $submission->{$userfield}; 890 } 891 } 892 $activity->user->fullname = fullname($submission, $viewfullnames); 893 894 $activities[$index++] = $activity; 895 } 896 897 return; 898 } 899 900 /** 901 * Print recent activity from all assignments in a given course 902 * 903 * This is used by course/recent.php 904 * @param stdClass $activity 905 * @param int $courseid 906 * @param bool $detail 907 * @param array $modnames 908 */ 909 function assign_print_recent_mod_activity($activity, $courseid, $detail, $modnames) { 910 global $CFG, $OUTPUT; 911 912 echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">'; 913 914 echo '<tr><td class="userpicture" valign="top">'; 915 echo $OUTPUT->user_picture($activity->user); 916 echo '</td><td>'; 917 918 if ($detail) { 919 $modname = $modnames[$activity->type]; 920 echo '<div class="title">'; 921 echo $OUTPUT->image_icon('monologo', $modname, 'assign'); 922 echo '<a href="' . $CFG->wwwroot . '/mod/assign/view.php?id=' . $activity->cmid . '">'; 923 echo $activity->name; 924 echo '</a>'; 925 echo '</div>'; 926 } 927 928 if (isset($activity->grade)) { 929 echo '<div class="grade">'; 930 echo get_string('gradenoun') . ': '; 931 echo $activity->grade; 932 echo '</div>'; 933 } 934 935 echo '<div class="user">'; 936 echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&course=$courseid\">"; 937 echo "{$activity->user->fullname}</a> - " . userdate($activity->timestamp); 938 echo '</div>'; 939 940 echo '</td></tr></table>'; 941 } 942 943 /** 944 * Checks if scale is being used by any instance of assignment 945 * 946 * This is used to find out if scale used anywhere 947 * @param int $scaleid 948 * @return boolean True if the scale is used by any assignment 949 */ 950 function assign_scale_used_anywhere($scaleid) { 951 global $DB; 952 953 if ($scaleid and $DB->record_exists('assign', array('grade'=>-$scaleid))) { 954 return true; 955 } else { 956 return false; 957 } 958 } 959 960 /** 961 * List the actions that correspond to a view of this module. 962 * This is used by the participation report. 963 * 964 * Note: This is not used by new logging system. Event with 965 * crud = 'r' and edulevel = LEVEL_PARTICIPATING will 966 * be considered as view action. 967 * 968 * @return array 969 */ 970 function assign_get_view_actions() { 971 return array('view submission', 'view feedback'); 972 } 973 974 /** 975 * List the actions that correspond to a post of this module. 976 * This is used by the participation report. 977 * 978 * Note: This is not used by new logging system. Event with 979 * crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING 980 * will be considered as post action. 981 * 982 * @return array 983 */ 984 function assign_get_post_actions() { 985 return array('upload', 'submit', 'submit for grading'); 986 } 987 988 /** 989 * Returns all other capabilities used by this module. 990 * @return array Array of capability strings 991 */ 992 function assign_get_extra_capabilities() { 993 return ['gradereport/grader:view', 'moodle/grade:viewall']; 994 } 995 996 /** 997 * Create grade item for given assignment. 998 * 999 * @param stdClass $assign record with extra cmidnumber 1000 * @param array $grades optional array/object of grade(s); 'reset' means reset grades in gradebook 1001 * @return int 0 if ok, error code otherwise 1002 */ 1003 function assign_grade_item_update($assign, $grades=null) { 1004 global $CFG; 1005 require_once($CFG->libdir.'/gradelib.php'); 1006 1007 if (!isset($assign->courseid)) { 1008 $assign->courseid = $assign->course; 1009 } 1010 1011 $params = array('itemname'=>$assign->name, 'idnumber'=>$assign->cmidnumber); 1012 1013 // Check if feedback plugin for gradebook is enabled, if yes then 1014 // gradetype = GRADE_TYPE_TEXT else GRADE_TYPE_NONE. 1015 $gradefeedbackenabled = false; 1016 1017 if (isset($assign->gradefeedbackenabled)) { 1018 $gradefeedbackenabled = $assign->gradefeedbackenabled; 1019 } else if ($assign->grade == 0) { // Grade feedback is needed only when grade == 0. 1020 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1021 $mod = get_coursemodule_from_instance('assign', $assign->id, $assign->courseid); 1022 $cm = context_module::instance($mod->id); 1023 $assignment = new assign($cm, null, null); 1024 $gradefeedbackenabled = $assignment->is_gradebook_feedback_enabled(); 1025 } 1026 1027 if ($assign->grade > 0) { 1028 $params['gradetype'] = GRADE_TYPE_VALUE; 1029 $params['grademax'] = $assign->grade; 1030 $params['grademin'] = 0; 1031 1032 } else if ($assign->grade < 0) { 1033 $params['gradetype'] = GRADE_TYPE_SCALE; 1034 $params['scaleid'] = -$assign->grade; 1035 1036 } else if ($gradefeedbackenabled) { 1037 // $assign->grade == 0 and feedback enabled. 1038 $params['gradetype'] = GRADE_TYPE_TEXT; 1039 } else { 1040 // $assign->grade == 0 and no feedback enabled. 1041 $params['gradetype'] = GRADE_TYPE_NONE; 1042 } 1043 1044 if ($grades === 'reset') { 1045 $params['reset'] = true; 1046 $grades = null; 1047 } 1048 1049 return grade_update('mod/assign', 1050 $assign->courseid, 1051 'mod', 1052 'assign', 1053 $assign->id, 1054 0, 1055 $grades, 1056 $params); 1057 } 1058 1059 /** 1060 * Return grade for given user or all users. 1061 * 1062 * @param stdClass $assign record of assign with an additional cmidnumber 1063 * @param int $userid optional user id, 0 means all users 1064 * @return array array of grades, false if none 1065 */ 1066 function assign_get_user_grades($assign, $userid=0) { 1067 global $CFG; 1068 1069 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1070 1071 $cm = get_coursemodule_from_instance('assign', $assign->id, 0, false, MUST_EXIST); 1072 $context = context_module::instance($cm->id); 1073 $assignment = new assign($context, null, null); 1074 $assignment->set_instance($assign); 1075 return $assignment->get_user_grades_for_gradebook($userid); 1076 } 1077 1078 /** 1079 * Update activity grades. 1080 * 1081 * @param stdClass $assign database record 1082 * @param int $userid specific user only, 0 means all 1083 * @param bool $nullifnone - not used 1084 */ 1085 function assign_update_grades($assign, $userid=0, $nullifnone=true) { 1086 global $CFG; 1087 require_once($CFG->libdir.'/gradelib.php'); 1088 1089 if ($assign->grade == 0) { 1090 assign_grade_item_update($assign); 1091 1092 } else if ($grades = assign_get_user_grades($assign, $userid)) { 1093 foreach ($grades as $k => $v) { 1094 if ($v->rawgrade == -1) { 1095 $grades[$k]->rawgrade = null; 1096 } 1097 } 1098 assign_grade_item_update($assign, $grades); 1099 1100 } else { 1101 assign_grade_item_update($assign); 1102 } 1103 } 1104 1105 /** 1106 * List the file areas that can be browsed. 1107 * 1108 * @param stdClass $course 1109 * @param stdClass $cm 1110 * @param stdClass $context 1111 * @return array 1112 */ 1113 function assign_get_file_areas($course, $cm, $context) { 1114 global $CFG; 1115 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1116 1117 $areas = array( 1118 ASSIGN_INTROATTACHMENT_FILEAREA => get_string('introattachments', 'mod_assign'), 1119 ASSIGN_ACTIVITYATTACHMENT_FILEAREA => get_string('activityattachments', 'mod_assign'), 1120 ); 1121 1122 $assignment = new assign($context, $cm, $course); 1123 foreach ($assignment->get_submission_plugins() as $plugin) { 1124 if ($plugin->is_visible()) { 1125 $pluginareas = $plugin->get_file_areas(); 1126 1127 if ($pluginareas) { 1128 $areas = array_merge($areas, $pluginareas); 1129 } 1130 } 1131 } 1132 foreach ($assignment->get_feedback_plugins() as $plugin) { 1133 if ($plugin->is_visible()) { 1134 $pluginareas = $plugin->get_file_areas(); 1135 1136 if ($pluginareas) { 1137 $areas = array_merge($areas, $pluginareas); 1138 } 1139 } 1140 } 1141 1142 return $areas; 1143 } 1144 1145 /** 1146 * File browsing support for assign module. 1147 * 1148 * @param file_browser $browser 1149 * @param object $areas 1150 * @param object $course 1151 * @param object $cm 1152 * @param object $context 1153 * @param string $filearea 1154 * @param int $itemid 1155 * @param string $filepath 1156 * @param string $filename 1157 * @return object file_info instance or null if not found 1158 */ 1159 function assign_get_file_info($browser, 1160 $areas, 1161 $course, 1162 $cm, 1163 $context, 1164 $filearea, 1165 $itemid, 1166 $filepath, 1167 $filename) { 1168 global $CFG; 1169 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1170 1171 if ($context->contextlevel != CONTEXT_MODULE) { 1172 return null; 1173 } 1174 1175 $urlbase = $CFG->wwwroot.'/pluginfile.php'; 1176 $fs = get_file_storage(); 1177 $filepath = is_null($filepath) ? '/' : $filepath; 1178 $filename = is_null($filename) ? '.' : $filename; 1179 1180 // Need to find where this belongs to. 1181 $assignment = new assign($context, $cm, $course); 1182 if ($filearea === ASSIGN_INTROATTACHMENT_FILEAREA || $filearea === ASSIGN_ACTIVITYATTACHMENT_FILEAREA) { 1183 if (!has_capability('moodle/course:managefiles', $context)) { 1184 // Students can not peak here! 1185 return null; 1186 } 1187 if (!($storedfile = $fs->get_file($assignment->get_context()->id, 1188 'mod_assign', $filearea, 0, $filepath, $filename))) { 1189 return null; 1190 } 1191 return new file_info_stored($browser, 1192 $assignment->get_context(), 1193 $storedfile, 1194 $urlbase, 1195 $filearea, 1196 $itemid, 1197 true, 1198 true, 1199 false); 1200 } 1201 1202 $pluginowner = null; 1203 foreach ($assignment->get_submission_plugins() as $plugin) { 1204 if ($plugin->is_visible()) { 1205 $pluginareas = $plugin->get_file_areas(); 1206 1207 if (array_key_exists($filearea, $pluginareas)) { 1208 $pluginowner = $plugin; 1209 break; 1210 } 1211 } 1212 } 1213 if (!$pluginowner) { 1214 foreach ($assignment->get_feedback_plugins() as $plugin) { 1215 if ($plugin->is_visible()) { 1216 $pluginareas = $plugin->get_file_areas(); 1217 1218 if (array_key_exists($filearea, $pluginareas)) { 1219 $pluginowner = $plugin; 1220 break; 1221 } 1222 } 1223 } 1224 } 1225 1226 if (!$pluginowner) { 1227 return null; 1228 } 1229 1230 $result = $pluginowner->get_file_info($browser, $filearea, $itemid, $filepath, $filename); 1231 return $result; 1232 } 1233 1234 /** 1235 * Prints the complete info about a user's interaction with an assignment. 1236 * 1237 * @param stdClass $course 1238 * @param stdClass $user 1239 * @param stdClass $coursemodule 1240 * @param stdClass $assign the database assign record 1241 * 1242 * This prints the submission summary and feedback summary for this student. 1243 */ 1244 function assign_user_complete($course, $user, $coursemodule, $assign) { 1245 global $CFG; 1246 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1247 1248 $context = context_module::instance($coursemodule->id); 1249 1250 $assignment = new assign($context, $coursemodule, $course); 1251 1252 echo $assignment->view_student_summary($user, false); 1253 } 1254 1255 /** 1256 * Rescale all grades for this activity and push the new grades to the gradebook. 1257 * 1258 * @param stdClass $course Course db record 1259 * @param stdClass $cm Course module db record 1260 * @param float $oldmin 1261 * @param float $oldmax 1262 * @param float $newmin 1263 * @param float $newmax 1264 */ 1265 function assign_rescale_activity_grades($course, $cm, $oldmin, $oldmax, $newmin, $newmax) { 1266 global $DB; 1267 1268 if ($oldmax <= $oldmin) { 1269 // Grades cannot be scaled. 1270 return false; 1271 } 1272 $scale = ($newmax - $newmin) / ($oldmax - $oldmin); 1273 if (($newmax - $newmin) <= 1) { 1274 // We would lose too much precision, lets bail. 1275 return false; 1276 } 1277 1278 $params = array( 1279 'p1' => $oldmin, 1280 'p2' => $scale, 1281 'p3' => $newmin, 1282 'a' => $cm->instance 1283 ); 1284 1285 // Only rescale grades that are greater than or equal to 0. Anything else is a special value. 1286 $sql = 'UPDATE {assign_grades} set grade = (((grade - :p1) * :p2) + :p3) where assignment = :a and grade >= 0'; 1287 $dbupdate = $DB->execute($sql, $params); 1288 if (!$dbupdate) { 1289 return false; 1290 } 1291 1292 // Now re-push all grades to the gradebook. 1293 $dbparams = array('id' => $cm->instance); 1294 $assign = $DB->get_record('assign', $dbparams); 1295 $assign->cmidnumber = $cm->idnumber; 1296 1297 assign_update_grades($assign); 1298 1299 return true; 1300 } 1301 1302 /** 1303 * Print the grade information for the assignment for this user. 1304 * 1305 * @param stdClass $course 1306 * @param stdClass $user 1307 * @param stdClass $coursemodule 1308 * @param stdClass $assignment 1309 */ 1310 function assign_user_outline($course, $user, $coursemodule, $assignment) { 1311 global $CFG; 1312 require_once($CFG->libdir.'/gradelib.php'); 1313 require_once($CFG->dirroot.'/grade/grading/lib.php'); 1314 1315 $gradinginfo = grade_get_grades($course->id, 1316 'mod', 1317 'assign', 1318 $assignment->id, 1319 $user->id); 1320 1321 $gradingitem = $gradinginfo->items[0]; 1322 $gradebookgrade = $gradingitem->grades[$user->id]; 1323 1324 if (empty($gradebookgrade->str_long_grade)) { 1325 return null; 1326 } 1327 $result = new stdClass(); 1328 if (!$gradingitem->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) { 1329 $result->info = get_string('outlinegrade', 'assign', $gradebookgrade->str_long_grade); 1330 } else { 1331 $result->info = get_string('gradenoun') . ': ' . get_string('hidden', 'grades'); 1332 } 1333 $result->time = $gradebookgrade->dategraded; 1334 1335 return $result; 1336 } 1337 1338 /** 1339 * Serves intro attachment files. 1340 * 1341 * @param mixed $course course or id of the course 1342 * @param mixed $cm course module or id of the course module 1343 * @param context $context 1344 * @param string $filearea 1345 * @param array $args 1346 * @param bool $forcedownload 1347 * @param array $options additional options affecting the file serving 1348 * @return bool false if file not found, does not return if found - just send the file 1349 */ 1350 function assign_pluginfile($course, 1351 $cm, 1352 context $context, 1353 $filearea, 1354 $args, 1355 $forcedownload, 1356 array $options=array()) { 1357 global $CFG; 1358 1359 if ($context->contextlevel != CONTEXT_MODULE) { 1360 return false; 1361 } 1362 1363 require_login($course, false, $cm); 1364 if (!has_capability('mod/assign:view', $context)) { 1365 return false; 1366 } 1367 1368 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1369 $assign = new assign($context, $cm, $course); 1370 1371 if ($filearea !== ASSIGN_INTROATTACHMENT_FILEAREA && $filearea !== ASSIGN_ACTIVITYATTACHMENT_FILEAREA) { 1372 return false; 1373 } 1374 if (!$assign->show_intro()) { 1375 return false; 1376 } 1377 1378 $itemid = (int)array_shift($args); 1379 if ($itemid != 0) { 1380 return false; 1381 } 1382 1383 $relativepath = implode('/', $args); 1384 1385 $fullpath = "/{$context->id}/mod_assign/$filearea/$itemid/$relativepath"; 1386 1387 $fs = get_file_storage(); 1388 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 1389 return false; 1390 } 1391 send_stored_file($file, 0, 0, $forcedownload, $options); 1392 } 1393 1394 /** 1395 * Serve the grading panel as a fragment. 1396 * 1397 * @param array $args List of named arguments for the fragment loader. 1398 * @return string 1399 */ 1400 function mod_assign_output_fragment_gradingpanel($args) { 1401 global $CFG; 1402 1403 $context = $args['context']; 1404 1405 if ($context->contextlevel != CONTEXT_MODULE) { 1406 return null; 1407 } 1408 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1409 $assign = new assign($context, null, null); 1410 1411 $userid = clean_param($args['userid'], PARAM_INT); 1412 1413 $participant = $assign->get_participant($userid); 1414 $isfiltered = $assign->is_userid_filtered($userid); 1415 if (!$participant || !$isfiltered) { 1416 // User is not enrolled or filtered out by filters and table preferences. 1417 return ''; 1418 } 1419 1420 $attemptnumber = clean_param($args['attemptnumber'], PARAM_INT); 1421 $formdata = array(); 1422 if (!empty($args['jsonformdata'])) { 1423 $serialiseddata = json_decode($args['jsonformdata']); 1424 parse_str($serialiseddata, $formdata); 1425 } 1426 $viewargs = array( 1427 'userid' => $userid, 1428 'attemptnumber' => $attemptnumber, 1429 'formdata' => $formdata 1430 ); 1431 1432 return $assign->view('gradingpanel', $viewargs); 1433 } 1434 1435 /** 1436 * Check if the module has any update that affects the current user since a given time. 1437 * 1438 * @param cm_info $cm course module data 1439 * @param int $from the time to check updates from 1440 * @param array $filter if we need to check only specific updates 1441 * @return stdClass an object with the different type of areas indicating if they were updated or not 1442 * @since Moodle 3.2 1443 */ 1444 function assign_check_updates_since(cm_info $cm, $from, $filter = array()) { 1445 global $DB, $USER, $CFG; 1446 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1447 1448 $updates = new stdClass(); 1449 $updates = course_check_module_updates_since($cm, $from, array(ASSIGN_INTROATTACHMENT_FILEAREA), $filter); 1450 1451 // Check if there is a new submission by the user or new grades. 1452 $select = 'assignment = :id AND userid = :userid AND (timecreated > :since1 OR timemodified > :since2)'; 1453 $params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from); 1454 $updates->submissions = (object) array('updated' => false); 1455 $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id'); 1456 if (!empty($submissions)) { 1457 $updates->submissions->updated = true; 1458 $updates->submissions->itemids = array_keys($submissions); 1459 } 1460 1461 $updates->grades = (object) array('updated' => false); 1462 $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id'); 1463 if (!empty($grades)) { 1464 $updates->grades->updated = true; 1465 $updates->grades->itemids = array_keys($grades); 1466 } 1467 1468 // Now, teachers should see other students updates. 1469 if (has_capability('mod/assign:viewgrades', $cm->context)) { 1470 $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from); 1471 $select = 'assignment = :id AND (timecreated > :since1 OR timemodified > :since2)'; 1472 1473 if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) { 1474 $groupusers = array_keys(groups_get_activity_shared_group_members($cm)); 1475 if (empty($groupusers)) { 1476 return $updates; 1477 } 1478 list($insql, $inparams) = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED); 1479 $select .= ' AND userid ' . $insql; 1480 $params = array_merge($params, $inparams); 1481 } 1482 1483 $updates->usersubmissions = (object) array('updated' => false); 1484 $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id'); 1485 if (!empty($submissions)) { 1486 $updates->usersubmissions->updated = true; 1487 $updates->usersubmissions->itemids = array_keys($submissions); 1488 } 1489 1490 $updates->usergrades = (object) array('updated' => false); 1491 $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id'); 1492 if (!empty($grades)) { 1493 $updates->usergrades->updated = true; 1494 $updates->usergrades->itemids = array_keys($grades); 1495 } 1496 } 1497 1498 return $updates; 1499 } 1500 1501 /** 1502 * Is the event visible? 1503 * 1504 * This is used to determine global visibility of an event in all places throughout Moodle. For example, 1505 * the ASSIGN_EVENT_TYPE_GRADINGDUE event will not be shown to students on their calendar. 1506 * 1507 * @param calendar_event $event 1508 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default). 1509 * @return bool Returns true if the event is visible to the current user, false otherwise. 1510 */ 1511 function mod_assign_core_calendar_is_event_visible(calendar_event $event, $userid = 0) { 1512 global $CFG, $USER; 1513 1514 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1515 1516 if (empty($userid)) { 1517 $userid = $USER->id; 1518 } 1519 1520 $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance]; 1521 $context = context_module::instance($cm->id); 1522 1523 $assign = new assign($context, $cm, null); 1524 1525 if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { 1526 return $assign->can_grade($userid); 1527 } else { 1528 return true; 1529 } 1530 } 1531 1532 /** 1533 * This function receives a calendar event and returns the action associated with it, or null if there is none. 1534 * 1535 * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event 1536 * is not displayed on the block. 1537 * 1538 * @param calendar_event $event 1539 * @param \core_calendar\action_factory $factory 1540 * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default). 1541 * @return \core_calendar\local\event\entities\action_interface|null 1542 */ 1543 function mod_assign_core_calendar_provide_event_action(calendar_event $event, 1544 \core_calendar\action_factory $factory, 1545 $userid = 0) { 1546 1547 global $CFG, $USER; 1548 1549 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1550 1551 if (empty($userid)) { 1552 $userid = $USER->id; 1553 } 1554 1555 $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance]; 1556 $context = context_module::instance($cm->id); 1557 1558 $completion = new \completion_info($cm->get_course()); 1559 1560 $completiondata = $completion->get_data($cm, false, $userid); 1561 1562 if ($completiondata->completionstate != COMPLETION_INCOMPLETE) { 1563 return null; 1564 } 1565 1566 $assign = new assign($context, $cm, null); 1567 1568 // Apply overrides. 1569 $assign->update_effective_access($userid); 1570 1571 if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { 1572 $name = get_string('gradeverb'); 1573 $url = new \moodle_url('/mod/assign/view.php', [ 1574 'id' => $cm->id, 1575 'action' => 'grader' 1576 ]); 1577 $actionable = $assign->can_grade($userid) && (time() >= $assign->get_instance()->allowsubmissionsfromdate); 1578 $itemcount = $actionable ? $assign->count_submissions_need_grading() : 0; 1579 } else { 1580 $usersubmission = $assign->get_user_submission($userid, false); 1581 if ($usersubmission && $usersubmission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) { 1582 // The user has already submitted. 1583 // We do not want to change the text to edit the submission, we want to remove the event from the Dashboard entirely. 1584 return null; 1585 } 1586 1587 $instance = $assign->get_instance(); 1588 if ($instance->teamsubmission && !$instance->requireallteammemberssubmit) { 1589 $groupsubmission = $assign->get_group_submission($userid, 0, false); 1590 if ($groupsubmission && $groupsubmission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) { 1591 return null; 1592 } 1593 } 1594 1595 $participant = $assign->get_participant($userid); 1596 1597 if (!$participant) { 1598 // If the user is not a participant in the assignment then they have 1599 // no action to take. This will filter out the events for teachers. 1600 return null; 1601 } 1602 1603 // The user has not yet submitted anything. Show the addsubmission link. 1604 $name = get_string('addsubmission', 'assign'); 1605 $url = new \moodle_url('/mod/assign/view.php', [ 1606 'id' => $cm->id, 1607 'action' => 'editsubmission' 1608 ]); 1609 $itemcount = 1; 1610 $actionable = $assign->is_any_submission_plugin_enabled() && $assign->can_edit_submission($userid, $userid); 1611 } 1612 1613 return $factory->create_instance( 1614 $name, 1615 $url, 1616 $itemcount, 1617 $actionable 1618 ); 1619 } 1620 1621 /** 1622 * Callback function that determines whether an action event should be showing its item count 1623 * based on the event type and the item count. 1624 * 1625 * @param calendar_event $event The calendar event. 1626 * @param int $itemcount The item count associated with the action event. 1627 * @return bool 1628 */ 1629 function mod_assign_core_calendar_event_action_shows_item_count(calendar_event $event, $itemcount = 0) { 1630 // List of event types where the action event's item count should be shown. 1631 $eventtypesshowingitemcount = [ 1632 ASSIGN_EVENT_TYPE_GRADINGDUE 1633 ]; 1634 // For mod_assign, item count should be shown if the event type is 'gradingdue' and there is one or more item count. 1635 return in_array($event->eventtype, $eventtypesshowingitemcount) && $itemcount > 0; 1636 } 1637 1638 /** 1639 * This function calculates the minimum and maximum cutoff values for the timestart of 1640 * the given event. 1641 * 1642 * It will return an array with two values, the first being the minimum cutoff value and 1643 * the second being the maximum cutoff value. Either or both values can be null, which 1644 * indicates there is no minimum or maximum, respectively. 1645 * 1646 * If a cutoff is required then the function must return an array containing the cutoff 1647 * timestamp and error string to display to the user if the cutoff value is violated. 1648 * 1649 * A minimum and maximum cutoff return value will look like: 1650 * [ 1651 * [1505704373, 'The due date must be after the sbumission start date'], 1652 * [1506741172, 'The due date must be before the cutoff date'] 1653 * ] 1654 * 1655 * If the event does not have a valid timestart range then [false, false] will 1656 * be returned. 1657 * 1658 * @param calendar_event $event The calendar event to get the time range for 1659 * @param stdClass $instance The module instance to get the range from 1660 * @return array 1661 */ 1662 function mod_assign_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) { 1663 global $CFG; 1664 1665 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1666 1667 $courseid = $event->courseid; 1668 $modulename = $event->modulename; 1669 $instanceid = $event->instance; 1670 $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid]; 1671 $context = context_module::instance($coursemodule->id); 1672 $assign = new assign($context, null, null); 1673 $assign->set_instance($instance); 1674 1675 return $assign->get_valid_calendar_event_timestart_range($event); 1676 } 1677 1678 /** 1679 * This function will update the assign module according to the 1680 * event that has been modified. 1681 * 1682 * @throws \moodle_exception 1683 * @param \calendar_event $event 1684 * @param stdClass $instance The module instance to get the range from 1685 */ 1686 function mod_assign_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $instance) { 1687 global $CFG, $DB; 1688 1689 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 1690 1691 if (empty($event->instance) || $event->modulename != 'assign') { 1692 return; 1693 } 1694 1695 if ($instance->id != $event->instance) { 1696 return; 1697 } 1698 1699 if (!in_array($event->eventtype, [ASSIGN_EVENT_TYPE_DUE, ASSIGN_EVENT_TYPE_GRADINGDUE])) { 1700 return; 1701 } 1702 1703 $courseid = $event->courseid; 1704 $modulename = $event->modulename; 1705 $instanceid = $event->instance; 1706 $modified = false; 1707 $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid]; 1708 $context = context_module::instance($coursemodule->id); 1709 1710 // The user does not have the capability to modify this activity. 1711 if (!has_capability('moodle/course:manageactivities', $context)) { 1712 return; 1713 } 1714 1715 $assign = new assign($context, $coursemodule, null); 1716 $assign->set_instance($instance); 1717 1718 if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) { 1719 // This check is in here because due date events are currently 1720 // the only events that can be overridden, so we can save a DB 1721 // query if we don't bother checking other events. 1722 if ($assign->is_override_calendar_event($event)) { 1723 // This is an override event so we should ignore it. 1724 return; 1725 } 1726 1727 $newduedate = $event->timestart; 1728 1729 if ($newduedate != $instance->duedate) { 1730 $instance->duedate = $newduedate; 1731 $modified = true; 1732 } 1733 } else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) { 1734 $newduedate = $event->timestart; 1735 1736 if ($newduedate != $instance->gradingduedate) { 1737 $instance->gradingduedate = $newduedate; 1738 $modified = true; 1739 } 1740 } 1741 1742 if ($modified) { 1743 $instance->timemodified = time(); 1744 // Persist the assign instance changes. 1745 $DB->update_record('assign', $instance); 1746 $assign->update_calendar($coursemodule->id); 1747 $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context); 1748 $event->trigger(); 1749 } 1750 } 1751 1752 /** 1753 * Return a list of all the user preferences used by mod_assign. 1754 * 1755 * @return array 1756 */ 1757 function mod_assign_user_preferences() { 1758 $preferences = array(); 1759 $preferences['assign_filter'] = array( 1760 'type' => PARAM_ALPHA, 1761 'null' => NULL_NOT_ALLOWED, 1762 'default' => '' 1763 ); 1764 $preferences['assign_workflowfilter'] = array( 1765 'type' => PARAM_ALPHA, 1766 'null' => NULL_NOT_ALLOWED, 1767 'default' => '' 1768 ); 1769 $preferences['assign_markerfilter'] = array( 1770 'type' => PARAM_ALPHANUMEXT, 1771 'null' => NULL_NOT_ALLOWED, 1772 'default' => '' 1773 ); 1774 1775 return $preferences; 1776 } 1777 1778 /** 1779 * Given an array with a file path, it returns the itemid and the filepath for the defined filearea. 1780 * 1781 * @param string $filearea The filearea. 1782 * @param array $args The path (the part after the filearea and before the filename). 1783 * @return array The itemid and the filepath inside the $args path, for the defined filearea. 1784 */ 1785 function mod_assign_get_path_from_pluginfile(string $filearea, array $args) : array { 1786 // Assign never has an itemid (the number represents the revision but it's not stored in database). 1787 array_shift($args); 1788 1789 // Get the filepath. 1790 if (empty($args)) { 1791 $filepath = '/'; 1792 } else { 1793 $filepath = '/' . implode('/', $args) . '/'; 1794 } 1795 1796 return [ 1797 'itemid' => 0, 1798 'filepath' => $filepath, 1799 ]; 1800 } 1801 1802 /** 1803 * Callback to fetch the activity event type lang string. 1804 * 1805 * @param string $eventtype The event type. 1806 * @return lang_string The event type lang string. 1807 */ 1808 function mod_assign_core_calendar_get_event_action_string(string $eventtype): string { 1809 $modulename = get_string('modulename', 'assign'); 1810 1811 switch ($eventtype) { 1812 case ASSIGN_EVENT_TYPE_DUE: 1813 $identifier = 'calendardue'; 1814 break; 1815 case ASSIGN_EVENT_TYPE_GRADINGDUE: 1816 $identifier = 'calendargradingdue'; 1817 break; 1818 default: 1819 return get_string('requiresaction', 'calendar', $modulename); 1820 } 1821 1822 return get_string($identifier, 'assign', $modulename); 1823 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body