MDL-58768 mod_assign: Added userid param to calendar callbacks
[moodle.git] / mod / assign / lib.php
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/>.
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();
28 /**
29  * Adds an assignment instance
30  *
31  * This is done by calling the add_instance() method of the assignment type class
32  * @param stdClass $data
33  * @param mod_assign_mod_form $form
34  * @return int The instance id of the new assignment
35  */
36 function assign_add_instance(stdClass $data, mod_assign_mod_form $form = null) {
37     global $CFG;
38     require_once($CFG->dirroot . '/mod/assign/locallib.php');
40     $assignment = new assign(context_module::instance($data->coursemodule), null, null);
41     return $assignment->add_instance($data, true);
42 }
44 /**
45  * delete an assignment instance
46  * @param int $id
47  * @return bool
48  */
49 function assign_delete_instance($id) {
50     global $CFG;
51     require_once($CFG->dirroot . '/mod/assign/locallib.php');
52     $cm = get_coursemodule_from_instance('assign', $id, 0, false, MUST_EXIST);
53     $context = context_module::instance($cm->id);
55     $assignment = new assign($context, null, null);
56     return $assignment->delete_instance();
57 }
59 /**
60  * This function is used by the reset_course_userdata function in moodlelib.
61  * This function will remove all assignment submissions and feedbacks in the database
62  * and clean up any related data.
63  *
64  * @param stdClass $data the data submitted from the reset course.
65  * @return array
66  */
67 function assign_reset_userdata($data) {
68     global $CFG, $DB;
69     require_once($CFG->dirroot . '/mod/assign/locallib.php');
71     $status = array();
72     $params = array('courseid'=>$data->courseid);
73     $sql = "SELECT a.id FROM {assign} a WHERE a.course=:courseid";
74     $course = $DB->get_record('course', array('id'=>$data->courseid), '*', MUST_EXIST);
75     if ($assigns = $DB->get_records_sql($sql, $params)) {
76         foreach ($assigns as $assign) {
77             $cm = get_coursemodule_from_instance('assign',
78                                                  $assign->id,
79                                                  $data->courseid,
80                                                  false,
81                                                  MUST_EXIST);
82             $context = context_module::instance($cm->id);
83             $assignment = new assign($context, $cm, $course);
84             $status = array_merge($status, $assignment->reset_userdata($data));
85         }
86     }
87     return $status;
88 }
90 /**
91  * This standard function will check all instances of this module
92  * and make sure there are up-to-date events created for each of them.
93  * If courseid = 0, then every assignment event in the site is checked, else
94  * only assignment events belonging to the course specified are checked.
95  *
96  * @param int $courseid
97  * @param int|stdClass $instance Assign module instance or ID.
98  * @param int|stdClass $cm Course module object or ID (not used in this module).
99  * @return bool
100  */
101 function assign_refresh_events($courseid = 0, $instance = null, $cm = null) {
102     global $CFG, $DB;
103     require_once($CFG->dirroot . '/mod/assign/locallib.php');
105     // If we have instance information then we can just update the one event instead of updating all events.
106     if (isset($instance)) {
107         if (!is_object($instance)) {
108             $instance = $DB->get_record('assign', array('id' => $instance), '*', MUST_EXIST);
109         }
110         if (isset($cm)) {
111             if (!is_object($cm)) {
112                 assign_prepare_update_events($instance);
113                 return true;
114             } else {
115                 $course = get_course($instance->course);
116                 assign_prepare_update_events($instance, $course, $cm);
117                 return true;
118             }
119         }
120     }
122     if ($courseid) {
123         // Make sure that the course id is numeric.
124         if (!is_numeric($courseid)) {
125             return false;
126         }
127         if (!$assigns = $DB->get_records('assign', array('course' => $courseid))) {
128             return false;
129         }
130         // Get course from courseid parameter.
131         if (!$course = $DB->get_record('course', array('id' => $courseid), '*')) {
132             return false;
133         }
134     } else {
135         if (!$assigns = $DB->get_records('assign')) {
136             return false;
137         }
138     }
139     foreach ($assigns as $assign) {
140         assign_prepare_update_events($assign);
141     }
143     return true;
146 /**
147  * This actually updates the normal and completion calendar events.
148  *
149  * @param  stdClass $assign Assignment object (from DB).
150  * @param  stdClass $course Course object.
151  * @param  stdClass $cm Course module object.
152  */
153 function assign_prepare_update_events($assign, $course = null, $cm = null) {
154     global $DB;
155     if (!isset($course)) {
156         // Get course and course module for the assignment.
157         list($course, $cm) = get_course_and_cm_from_instance($assign->id, 'assign', $assign->course);
158     }
159     // Refresh the assignment's calendar events.
160     $context = context_module::instance($cm->id);
161     $assignment = new assign($context, $cm, $course);
162     $assignment->update_calendar($cm->id);
163     // Refresh the calendar events also for the assignment overrides.
164     $overrides = $DB->get_records('assign_overrides', ['assignid' => $assign->id], '',
165                                   'id, groupid, userid, duedate, sortorder');
166     foreach ($overrides as $override) {
167         if (empty($override->userid)) {
168             unset($override->userid);
169         }
170         if (empty($override->groupid)) {
171             unset($override->groupid);
172         }
173         assign_update_events($assignment, $override);
174     }
177 /**
178  * Removes all grades from gradebook
179  *
180  * @param int $courseid The ID of the course to reset
181  * @param string $type Optional type of assignment to limit the reset to a particular assignment type
182  */
183 function assign_reset_gradebook($courseid, $type='') {
184     global $CFG, $DB;
186     $params = array('moduletype'=>'assign', 'courseid'=>$courseid);
187     $sql = 'SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
188             FROM {assign} a, {course_modules} cm, {modules} m
189             WHERE m.name=:moduletype AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid';
191     if ($assignments = $DB->get_records_sql($sql, $params)) {
192         foreach ($assignments as $assignment) {
193             assign_grade_item_update($assignment, 'reset');
194         }
195     }
198 /**
199  * Implementation of the function for printing the form elements that control
200  * whether the course reset functionality affects the assignment.
201  * @param moodleform $mform form passed by reference
202  */
203 function assign_reset_course_form_definition(&$mform) {
204     $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign'));
205     $name = get_string('deleteallsubmissions', 'assign');
206     $mform->addElement('advcheckbox', 'reset_assign_submissions', $name);
207     $mform->addElement('advcheckbox', 'reset_assign_user_overrides',
208         get_string('removealluseroverrides', 'assign'));
209     $mform->addElement('advcheckbox', 'reset_assign_group_overrides',
210         get_string('removeallgroupoverrides', 'assign'));
213 /**
214  * Course reset form defaults.
215  * @param  object $course
216  * @return array
217  */
218 function assign_reset_course_form_defaults($course) {
219     return array('reset_assign_submissions' => 1,
220             'reset_assign_group_overrides' => 1,
221             'reset_assign_user_overrides' => 1);
224 /**
225  * Update an assignment instance
226  *
227  * This is done by calling the update_instance() method of the assignment type class
228  * @param stdClass $data
229  * @param stdClass $form - unused
230  * @return object
231  */
232 function assign_update_instance(stdClass $data, $form) {
233     global $CFG;
234     require_once($CFG->dirroot . '/mod/assign/locallib.php');
235     $context = context_module::instance($data->coursemodule);
236     $assignment = new assign($context, null, null);
237     return $assignment->update_instance($data);
240 /**
241  * This function updates the events associated to the assign.
242  * If $override is non-zero, then it updates only the events
243  * associated with the specified override.
244  *
245  * @param assign $assign the assign object.
246  * @param object $override (optional) limit to a specific override
247  */
248 function assign_update_events($assign, $override = null) {
249     global $CFG, $DB;
251     require_once($CFG->dirroot . '/calendar/lib.php');
253     $assigninstance = $assign->get_instance();
255     // Load the old events relating to this assign.
256     $conds = array('modulename' => 'assign', 'instance' => $assigninstance->id);
257     if (!empty($override)) {
258         // Only load events for this override.
259         if (isset($override->userid)) {
260             $conds['userid'] = $override->userid;
261         } else {
262             $conds['groupid'] = $override->groupid;
263         }
264     }
265     $oldevents = $DB->get_records('event', $conds, 'id ASC');
267     // Now make a to-do list of all that needs to be updated.
268     if (empty($override)) {
269         // We are updating the primary settings for the assignment, so we need to add all the overrides.
270         $overrides = $DB->get_records('assign_overrides', array('assignid' => $assigninstance->id), 'id ASC');
271         // It is necessary to add an empty stdClass to the beginning of the array as the $oldevents
272         // list contains the original (non-override) event for the module. If this is not included
273         // the logic below will end up updating the wrong row when we try to reconcile this $overrides
274         // list against the $oldevents list.
275         array_unshift($overrides, new stdClass());
276     } else {
277         // Just do the one override.
278         $overrides = array($override);
279     }
281     if (!empty($assign->get_course_module())) {
282         $cmid = $assign->get_course_module()->id;
283     } else {
284         $cmid = get_coursemodule_from_instance('assign', $assigninstance->id, $assigninstance->course)->id;
285     }
287     foreach ($overrides as $current) {
288         $groupid   = isset($current->groupid) ? $current->groupid : 0;
289         $userid    = isset($current->userid) ? $current->userid : 0;
290         $duedate = isset($current->duedate) ? $current->duedate : $assigninstance->duedate;
292         // Only add 'due' events for an override if they differ from the assign default.
293         $addclose = empty($current->id) || !empty($current->duedate);
295         $event = new stdClass();
296         $event->type = CALENDAR_EVENT_TYPE_ACTION;
297         $event->description = format_module_intro('assign', $assigninstance, $cmid);
298         // Events module won't show user events when the courseid is nonzero.
299         $event->courseid    = ($userid) ? 0 : $assigninstance->course;
300         $event->groupid     = $groupid;
301         $event->userid      = $userid;
302         $event->modulename  = 'assign';
303         $event->instance    = $assigninstance->id;
304         $event->timestart   = $duedate;
305         $event->timeduration = 0;
306         $event->timesort    = $event->timestart + $event->timeduration;
307         $event->visible     = instance_is_visible('assign', $assigninstance);
308         $event->eventtype   = ASSIGN_EVENT_TYPE_DUE;
309         $event->priority    = null;
311         // Determine the event name and priority.
312         if ($groupid) {
313             // Group override event.
314             $params = new stdClass();
315             $params->assign = $assigninstance->name;
316             $params->group = groups_get_group_name($groupid);
317             if ($params->group === false) {
318                 // Group doesn't exist, just skip it.
319                 continue;
320             }
321             $eventname = get_string('overridegroupeventname', 'assign', $params);
322             // Set group override priority.
323             if (isset($current->sortorder)) {
324                 $event->priority = $current->sortorder;
325             }
326         } else if ($userid) {
327             // User override event.
328             $params = new stdClass();
329             $params->assign = $assigninstance->name;
330             $eventname = get_string('overrideusereventname', 'assign', $params);
331             // Set user override priority.
332             $event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
333         } else {
334             // The parent event.
335             $eventname = $assigninstance->name;
336         }
338         if ($duedate && $addclose) {
339             if ($oldevent = array_shift($oldevents)) {
340                 $event->id = $oldevent->id;
341             } else {
342                 unset($event->id);
343             }
344             $event->name      = $eventname.' ('.get_string('duedate', 'assign').')';
345             calendar_event::create($event);
346         }
347     }
349     // Delete any leftover events.
350     foreach ($oldevents as $badevent) {
351         $badevent = calendar_event::load($badevent);
352         $badevent->delete();
353     }
356 /**
357  * Return the list if Moodle features this module supports
358  *
359  * @param string $feature FEATURE_xx constant for requested feature
360  * @return mixed True if module supports feature, null if doesn't know
361  */
362 function assign_supports($feature) {
363     switch($feature) {
364         case FEATURE_GROUPS:
365             return true;
366         case FEATURE_GROUPINGS:
367             return true;
368         case FEATURE_MOD_INTRO:
369             return true;
370         case FEATURE_COMPLETION_TRACKS_VIEWS:
371             return true;
372         case FEATURE_COMPLETION_HAS_RULES:
373             return true;
374         case FEATURE_GRADE_HAS_GRADE:
375             return true;
376         case FEATURE_GRADE_OUTCOMES:
377             return true;
378         case FEATURE_BACKUP_MOODLE2:
379             return true;
380         case FEATURE_SHOW_DESCRIPTION:
381             return true;
382         case FEATURE_ADVANCED_GRADING:
383             return true;
384         case FEATURE_PLAGIARISM:
385             return true;
386         case FEATURE_COMMENT:
387             return true;
389         default:
390             return null;
391     }
394 /**
395  * Lists all gradable areas for the advanced grading methods gramework
396  *
397  * @return array('string'=>'string') An array with area names as keys and descriptions as values
398  */
399 function assign_grading_areas_list() {
400     return array('submissions'=>get_string('submissions', 'assign'));
404 /**
405  * extend an assigment navigation settings
406  *
407  * @param settings_navigation $settings
408  * @param navigation_node $navref
409  * @return void
410  */
411 function assign_extend_settings_navigation(settings_navigation $settings, navigation_node $navref) {
412     global $PAGE, $DB;
414     // We want to add these new nodes after the Edit settings node, and before the
415     // Locally assigned roles node. Of course, both of those are controlled by capabilities.
416     $keys = $navref->get_children_key_list();
417     $beforekey = null;
418     $i = array_search('modedit', $keys);
419     if ($i === false and array_key_exists(0, $keys)) {
420         $beforekey = $keys[0];
421     } else if (array_key_exists($i + 1, $keys)) {
422         $beforekey = $keys[$i + 1];
423     }
425     $cm = $PAGE->cm;
426     if (!$cm) {
427         return;
428     }
430     $context = $cm->context;
431     $course = $PAGE->course;
433     if (!$course) {
434         return;
435     }
437     if (has_capability('mod/assign:manageoverrides', $PAGE->cm->context)) {
438         $url = new moodle_url('/mod/assign/overrides.php', array('cmid' => $PAGE->cm->id));
439         $node = navigation_node::create(get_string('groupoverrides', 'assign'),
440             new moodle_url($url, array('mode' => 'group')),
441             navigation_node::TYPE_SETTING, null, 'mod_assign_groupoverrides');
442         $navref->add_node($node, $beforekey);
444         $node = navigation_node::create(get_string('useroverrides', 'assign'),
445             new moodle_url($url, array('mode' => 'user')),
446             navigation_node::TYPE_SETTING, null, 'mod_assign_useroverrides');
447         $navref->add_node($node, $beforekey);
448     }
450     // Link to gradebook.
451     if (has_capability('gradereport/grader:view', $cm->context) &&
452             has_capability('moodle/grade:viewall', $cm->context)) {
453         $link = new moodle_url('/grade/report/grader/index.php', array('id' => $course->id));
454         $linkname = get_string('viewgradebook', 'assign');
455         $node = $navref->add($linkname, $link, navigation_node::TYPE_SETTING);
456     }
458     // Link to download all submissions.
459     if (has_any_capability(array('mod/assign:grade', 'mod/assign:viewgrades'), $context)) {
460         $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'grading'));
461         $node = $navref->add(get_string('viewgrading', 'assign'), $link, navigation_node::TYPE_SETTING);
463         $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'downloadall'));
464         $node = $navref->add(get_string('downloadall', 'assign'), $link, navigation_node::TYPE_SETTING);
465     }
467     if (has_capability('mod/assign:revealidentities', $context)) {
468         $dbparams = array('id'=>$cm->instance);
469         $assignment = $DB->get_record('assign', $dbparams, 'blindmarking, revealidentities');
471         if ($assignment && $assignment->blindmarking && !$assignment->revealidentities) {
472             $urlparams = array('id' => $cm->id, 'action'=>'revealidentities');
473             $url = new moodle_url('/mod/assign/view.php', $urlparams);
474             $linkname = get_string('revealidentities', 'assign');
475             $node = $navref->add($linkname, $url, navigation_node::TYPE_SETTING);
476         }
477     }
480 /**
481  * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
482  * for the course (see resource).
483  *
484  * Given a course_module object, this function returns any "extra" information that may be needed
485  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
486  *
487  * @param stdClass $coursemodule The coursemodule object (record).
488  * @return cached_cm_info An object on information that the courses
489  *                        will know about (most noticeably, an icon).
490  */
491 function assign_get_coursemodule_info($coursemodule) {
492     global $CFG, $DB;
494     $dbparams = array('id'=>$coursemodule->instance);
495     $fields = 'id, name, alwaysshowdescription, allowsubmissionsfromdate, intro, introformat, completionsubmit';
496     if (! $assignment = $DB->get_record('assign', $dbparams, $fields)) {
497         return false;
498     }
500     $result = new cached_cm_info();
501     $result->name = $assignment->name;
502     if ($coursemodule->showdescription) {
503         if ($assignment->alwaysshowdescription || time() > $assignment->allowsubmissionsfromdate) {
504             // Convert intro to html. Do not filter cached version, filters run at display time.
505             $result->content = format_module_intro('assign', $assignment, $coursemodule->id, false);
506         }
507     }
509     // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
510     if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
511         $result->customdata['customcompletionrules']['completionsubmit'] = $assignment->completionsubmit;
512     }
514     return $result;
517 /**
518  * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
519  *
520  * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
521  * @return array $descriptions the array of descriptions for the custom rules.
522  */
523 function mod_assign_get_completion_active_rule_descriptions($cm) {
524     // Values will be present in cm_info, and we assume these are up to date.
525     if (empty($cm->customdata['customcompletionrules'])
526         || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
527         return [];
528     }
530     $descriptions = [];
531     foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
532         switch ($key) {
533             case 'completionsubmit':
534                 if (empty($val)) {
535                     continue;
536                 }
537                 $descriptions[] = get_string('completionsubmit', 'assign');
538                 break;
539             default:
540                 break;
541         }
542     }
543     return $descriptions;
546 /**
547  * Return a list of page types
548  * @param string $pagetype current page type
549  * @param stdClass $parentcontext Block's parent context
550  * @param stdClass $currentcontext Current context of block
551  */
552 function assign_page_type_list($pagetype, $parentcontext, $currentcontext) {
553     $modulepagetype = array(
554         'mod-assign-*' => get_string('page-mod-assign-x', 'assign'),
555         'mod-assign-view' => get_string('page-mod-assign-view', 'assign'),
556     );
557     return $modulepagetype;
560 /**
561  * Print an overview of all assignments
562  * for the courses.
563  *
564  * @deprecated since 3.3
565  * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
566  * @param mixed $courses The list of courses to print the overview for
567  * @param array $htmlarray The array of html to return
568  * @return true
569  */
570 function assign_print_overview($courses, &$htmlarray) {
571     global $CFG, $DB;
573     debugging('The function assign_print_overview() is now deprecated.', DEBUG_DEVELOPER);
575     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
576         return true;
577     }
579     if (!$assignments = get_all_instances_in_courses('assign', $courses)) {
580         return true;
581     }
583     $assignmentids = array();
585     // Do assignment_base::isopen() here without loading the whole thing for speed.
586     foreach ($assignments as $key => $assignment) {
587         $time = time();
588         $isopen = false;
589         if ($assignment->duedate) {
590             $duedate = false;
591             if ($assignment->cutoffdate) {
592                 $duedate = $assignment->cutoffdate;
593             }
594             if ($duedate) {
595                 $isopen = ($assignment->allowsubmissionsfromdate <= $time && $time <= $duedate);
596             } else {
597                 $isopen = ($assignment->allowsubmissionsfromdate <= $time);
598             }
599         }
600         if ($isopen) {
601             $assignmentids[] = $assignment->id;
602         }
603     }
605     if (empty($assignmentids)) {
606         // No assignments to look at - we're done.
607         return true;
608     }
610     // Definitely something to print, now include the constants we need.
611     require_once($CFG->dirroot . '/mod/assign/locallib.php');
613     $strduedate = get_string('duedate', 'assign');
614     $strcutoffdate = get_string('nosubmissionsacceptedafter', 'assign');
615     $strnolatesubmissions = get_string('nolatesubmissions', 'assign');
616     $strduedateno = get_string('duedateno', 'assign');
617     $strassignment = get_string('modulename', 'assign');
619     // We do all possible database work here *outside* of the loop to ensure this scales.
620     list($sqlassignmentids, $assignmentidparams) = $DB->get_in_or_equal($assignmentids);
622     $mysubmissions = null;
623     $unmarkedsubmissions = null;
625     foreach ($assignments as $assignment) {
627         // Do not show assignments that are not open.
628         if (!in_array($assignment->id, $assignmentids)) {
629             continue;
630         }
632         $context = context_module::instance($assignment->coursemodule);
634         // Does the submission status of the assignment require notification?
635         if (has_capability('mod/assign:submit', $context, null, false)) {
636             // Does the submission status of the assignment require notification?
637             $submitdetails = assign_get_mysubmission_details_for_print_overview($mysubmissions, $sqlassignmentids,
638                     $assignmentidparams, $assignment);
639         } else {
640             $submitdetails = false;
641         }
643         if (has_capability('mod/assign:grade', $context, null, false)) {
644             // Does the grading status of the assignment require notification ?
645             $gradedetails = assign_get_grade_details_for_print_overview($unmarkedsubmissions, $sqlassignmentids,
646                     $assignmentidparams, $assignment, $context);
647         } else {
648             $gradedetails = false;
649         }
651         if (empty($submitdetails) && empty($gradedetails)) {
652             // There is no need to display this assignment as there is nothing to notify.
653             continue;
654         }
656         $dimmedclass = '';
657         if (!$assignment->visible) {
658             $dimmedclass = ' class="dimmed"';
659         }
660         $href = $CFG->wwwroot . '/mod/assign/view.php?id=' . $assignment->coursemodule;
661         $basestr = '<div class="assign overview">' .
662                '<div class="name">' .
663                $strassignment . ': '.
664                '<a ' . $dimmedclass .
665                    'title="' . $strassignment . '" ' .
666                    'href="' . $href . '">' .
667                format_string($assignment->name) .
668                '</a></div>';
669         if ($assignment->duedate) {
670             $userdate = userdate($assignment->duedate);
671             $basestr .= '<div class="info">' . $strduedate . ': ' . $userdate . '</div>';
672         } else {
673             $basestr .= '<div class="info">' . $strduedateno . '</div>';
674         }
675         if ($assignment->cutoffdate) {
676             if ($assignment->cutoffdate == $assignment->duedate) {
677                 $basestr .= '<div class="info">' . $strnolatesubmissions . '</div>';
678             } else {
679                 $userdate = userdate($assignment->cutoffdate);
680                 $basestr .= '<div class="info">' . $strcutoffdate . ': ' . $userdate . '</div>';
681             }
682         }
684         // Show only relevant information.
685         if (!empty($submitdetails)) {
686             $basestr .= $submitdetails;
687         }
689         if (!empty($gradedetails)) {
690             $basestr .= $gradedetails;
691         }
692         $basestr .= '</div>';
694         if (empty($htmlarray[$assignment->course]['assign'])) {
695             $htmlarray[$assignment->course]['assign'] = $basestr;
696         } else {
697             $htmlarray[$assignment->course]['assign'] .= $basestr;
698         }
699     }
700     return true;
703 /**
704  * This api generates html to be displayed to students in print overview section, related to their submission status of the given
705  * assignment.
706  *
707  * @deprecated since 3.3
708  * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
709  * @param array $mysubmissions list of submissions of current user indexed by assignment id.
710  * @param string $sqlassignmentids sql clause used to filter open assignments.
711  * @param array $assignmentidparams sql params used to filter open assignments.
712  * @param stdClass $assignment current assignment
713  *
714  * @return bool|string html to display , false if nothing needs to be displayed.
715  * @throws coding_exception
716  */
717 function assign_get_mysubmission_details_for_print_overview(&$mysubmissions, $sqlassignmentids, $assignmentidparams,
718                                                             $assignment) {
719     global $USER, $DB;
721     debugging('The function assign_get_mysubmission_details_for_print_overview() is now deprecated.', DEBUG_DEVELOPER);
723     if ($assignment->nosubmissions) {
724         // Offline assignment. No need to display alerts for offline assignments.
725         return false;
726     }
728     $strnotsubmittedyet = get_string('notsubmittedyet', 'assign');
730     if (!isset($mysubmissions)) {
732         // Get all user submissions, indexed by assignment id.
733         $dbparams = array_merge(array($USER->id), $assignmentidparams, array($USER->id));
734         $mysubmissions = $DB->get_records_sql('SELECT a.id AS assignment,
735                                                       a.nosubmissions AS nosubmissions,
736                                                       g.timemodified AS timemarked,
737                                                       g.grader AS grader,
738                                                       g.grade AS grade,
739                                                       s.status AS status
740                                                  FROM {assign} a, {assign_submission} s
741                                             LEFT JOIN {assign_grades} g ON
742                                                       g.assignment = s.assignment AND
743                                                       g.userid = ? AND
744                                                       g.attemptnumber = s.attemptnumber
745                                                 WHERE a.id ' . $sqlassignmentids . ' AND
746                                                       s.latest = 1 AND
747                                                       s.assignment = a.id AND
748                                                       s.userid = ?', $dbparams);
749     }
751     $submitdetails = '';
752     $submitdetails .= '<div class="details">';
753     $submitdetails .= get_string('mysubmission', 'assign');
754     $submission = false;
756     if (isset($mysubmissions[$assignment->id])) {
757         $submission = $mysubmissions[$assignment->id];
758     }
760     if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
761         // A valid submission already exists, no need to notify students about this.
762         return false;
763     }
765     // We need to show details only if a valid submission doesn't exist.
766     if (!$submission ||
767         !$submission->status ||
768         $submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT ||
769         $submission->status == ASSIGN_SUBMISSION_STATUS_NEW
770     ) {
771         $submitdetails .= $strnotsubmittedyet;
772     } else {
773         $submitdetails .= get_string('submissionstatus_' . $submission->status, 'assign');
774     }
775     if ($assignment->markingworkflow) {
776         $workflowstate = $DB->get_field('assign_user_flags', 'workflowstate', array('assignment' =>
777                 $assignment->id, 'userid' => $USER->id));
778         if ($workflowstate) {
779             $gradingstatus = 'markingworkflowstate' . $workflowstate;
780         } else {
781             $gradingstatus = 'markingworkflowstate' . ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
782         }
783     } else if (!empty($submission->grade) && $submission->grade !== null && $submission->grade >= 0) {
784         $gradingstatus = ASSIGN_GRADING_STATUS_GRADED;
785     } else {
786         $gradingstatus = ASSIGN_GRADING_STATUS_NOT_GRADED;
787     }
788     $submitdetails .= ', ' . get_string($gradingstatus, 'assign');
789     $submitdetails .= '</div>';
790     return $submitdetails;
793 /**
794  * This api generates html to be displayed to teachers in print overview section, related to the grading status of the given
795  * assignment's submissions.
796  *
797  * @deprecated since 3.3
798  * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
799  * @param array $unmarkedsubmissions list of submissions of that are currently unmarked indexed by assignment id.
800  * @param string $sqlassignmentids sql clause used to filter open assignments.
801  * @param array $assignmentidparams sql params used to filter open assignments.
802  * @param stdClass $assignment current assignment
803  * @param context $context context of the assignment.
804  *
805  * @return bool|string html to display , false if nothing needs to be displayed.
806  * @throws coding_exception
807  */
808 function assign_get_grade_details_for_print_overview(&$unmarkedsubmissions, $sqlassignmentids, $assignmentidparams,
809                                                      $assignment, $context) {
810     global $DB;
812     debugging('The function assign_get_grade_details_for_print_overview() is now deprecated.', DEBUG_DEVELOPER);
814     if (!isset($unmarkedsubmissions)) {
815         // Build up and array of unmarked submissions indexed by assignment id/ userid
816         // for use where the user has grading rights on assignment.
817         $dbparams = array_merge(array(ASSIGN_SUBMISSION_STATUS_SUBMITTED), $assignmentidparams);
818         $rs = $DB->get_recordset_sql('SELECT s.assignment as assignment,
819                                              s.userid as userid,
820                                              s.id as id,
821                                              s.status as status,
822                                              g.timemodified as timegraded
823                                         FROM {assign_submission} s
824                                    LEFT JOIN {assign_grades} g ON
825                                              s.userid = g.userid AND
826                                              s.assignment = g.assignment AND
827                                              g.attemptnumber = s.attemptnumber
828                                    LEFT JOIN {assign} a ON
829                                              a.id = s.assignment
830                                        WHERE
831                                              ( g.timemodified is NULL OR
832                                              s.timemodified >= g.timemodified OR
833                                              g.grade IS NULL OR
834                                              (g.grade = -1 AND
835                                              a.grade < 0)) AND
836                                              s.timemodified IS NOT NULL AND
837                                              s.status = ? AND
838                                              s.latest = 1 AND
839                                              s.assignment ' . $sqlassignmentids, $dbparams);
841         $unmarkedsubmissions = array();
842         foreach ($rs as $rd) {
843             $unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
844         }
845         $rs->close();
846     }
848     // Count how many people can submit.
849     $submissions = 0;
850     if ($students = get_enrolled_users($context, 'mod/assign:view', 0, 'u.id')) {
851         foreach ($students as $student) {
852             if (isset($unmarkedsubmissions[$assignment->id][$student->id])) {
853                 $submissions++;
854             }
855         }
856     }
858     if ($submissions) {
859         $urlparams = array('id' => $assignment->coursemodule, 'action' => 'grading');
860         $url = new moodle_url('/mod/assign/view.php', $urlparams);
861         $gradedetails = '<div class="details">' .
862                 '<a href="' . $url . '">' .
863                 get_string('submissionsnotgraded', 'assign', $submissions) .
864                 '</a></div>';
865         return $gradedetails;
866     } else {
867         return false;
868     }
872 /**
873  * Print recent activity from all assignments in a given course
874  *
875  * This is used by the recent activity block
876  * @param mixed $course the course to print activity for
877  * @param bool $viewfullnames boolean to determine whether to show full names or not
878  * @param int $timestart the time the rendering started
879  * @return bool true if activity was printed, false otherwise.
880  */
881 function assign_print_recent_activity($course, $viewfullnames, $timestart) {
882     global $CFG, $USER, $DB, $OUTPUT;
883     require_once($CFG->dirroot . '/mod/assign/locallib.php');
885     // Do not use log table if possible, it may be huge.
887     $dbparams = array($timestart, $course->id, 'assign', ASSIGN_SUBMISSION_STATUS_SUBMITTED);
888     $namefields = user_picture::fields('u', null, 'userid');
889     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, um.id as recordid,
890                                                      $namefields
891                                                 FROM {assign_submission} asb
892                                                      JOIN {assign} a      ON a.id = asb.assignment
893                                                      JOIN {course_modules} cm ON cm.instance = a.id
894                                                      JOIN {modules} md        ON md.id = cm.module
895                                                      JOIN {user} u            ON u.id = asb.userid
896                                                 LEFT JOIN {assign_user_mapping} um ON um.userid = u.id AND um.assignment = a.id
897                                                WHERE asb.timemodified > ? AND
898                                                      asb.latest = 1 AND
899                                                      a.course = ? AND
900                                                      md.name = ? AND
901                                                      asb.status = ?
902                                             ORDER BY asb.timemodified ASC", $dbparams)) {
903          return false;
904     }
906     $modinfo = get_fast_modinfo($course);
907     $show    = array();
908     $grader  = array();
910     $showrecentsubmissions = get_config('assign', 'showrecentsubmissions');
912     foreach ($submissions as $submission) {
913         if (!array_key_exists($submission->cmid, $modinfo->get_cms())) {
914             continue;
915         }
916         $cm = $modinfo->get_cm($submission->cmid);
917         if (!$cm->uservisible) {
918             continue;
919         }
920         if ($submission->userid == $USER->id) {
921             $show[] = $submission;
922             continue;
923         }
925         $context = context_module::instance($submission->cmid);
926         // The act of submitting of assignment may be considered private -
927         // only graders will see it if specified.
928         if (empty($showrecentsubmissions)) {
929             if (!array_key_exists($cm->id, $grader)) {
930                 $grader[$cm->id] = has_capability('moodle/grade:viewall', $context);
931             }
932             if (!$grader[$cm->id]) {
933                 continue;
934             }
935         }
937         $groupmode = groups_get_activity_groupmode($cm, $course);
939         if ($groupmode == SEPARATEGROUPS &&
940                 !has_capability('moodle/site:accessallgroups',  $context)) {
941             if (isguestuser()) {
942                 // Shortcut - guest user does not belong into any group.
943                 continue;
944             }
946             // This will be slow - show only users that share group with me in this cm.
947             if (!$modinfo->get_groups($cm->groupingid)) {
948                 continue;
949             }
950             $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
951             if (is_array($usersgroups)) {
952                 $usersgroups = array_keys($usersgroups);
953                 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
954                 if (empty($intersect)) {
955                     continue;
956                 }
957             }
958         }
959         $show[] = $submission;
960     }
962     if (empty($show)) {
963         return false;
964     }
966     echo $OUTPUT->heading(get_string('newsubmissions', 'assign').':', 3);
968     foreach ($show as $submission) {
969         $cm = $modinfo->get_cm($submission->cmid);
970         $context = context_module::instance($submission->cmid);
971         $assign = new assign($context, $cm, $cm->course);
972         $link = $CFG->wwwroot.'/mod/assign/view.php?id='.$cm->id;
973         // Obscure first and last name if blind marking enabled.
974         if ($assign->is_blind_marking()) {
975             $submission->firstname = get_string('participant', 'mod_assign');
976             if (empty($submission->recordid)) {
977                 $submission->recordid = $assign->get_uniqueid_for_user($submission->userid);
978             }
979             $submission->lastname = $submission->recordid;
980         }
981         print_recent_activity_note($submission->timemodified,
982                                    $submission,
983                                    $cm->name,
984                                    $link,
985                                    false,
986                                    $viewfullnames);
987     }
989     return true;
992 /**
993  * Returns all assignments since a given time.
994  *
995  * @param array $activities The activity information is returned in this array
996  * @param int $index The current index in the activities array
997  * @param int $timestart The earliest activity to show
998  * @param int $courseid Limit the search to this course
999  * @param int $cmid The course module id
1000  * @param int $userid Optional user id
1001  * @param int $groupid Optional group id
1002  * @return void
1003  */
1004 function assign_get_recent_mod_activity(&$activities,
1005                                         &$index,
1006                                         $timestart,
1007                                         $courseid,
1008                                         $cmid,
1009                                         $userid=0,
1010                                         $groupid=0) {
1011     global $CFG, $COURSE, $USER, $DB;
1013     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1015     if ($COURSE->id == $courseid) {
1016         $course = $COURSE;
1017     } else {
1018         $course = $DB->get_record('course', array('id'=>$courseid));
1019     }
1021     $modinfo = get_fast_modinfo($course);
1023     $cm = $modinfo->get_cm($cmid);
1024     $params = array();
1025     if ($userid) {
1026         $userselect = 'AND u.id = :userid';
1027         $params['userid'] = $userid;
1028     } else {
1029         $userselect = '';
1030     }
1032     if ($groupid) {
1033         $groupselect = 'AND gm.groupid = :groupid';
1034         $groupjoin   = 'JOIN {groups_members} gm ON  gm.userid=u.id';
1035         $params['groupid'] = $groupid;
1036     } else {
1037         $groupselect = '';
1038         $groupjoin   = '';
1039     }
1041     $params['cminstance'] = $cm->instance;
1042     $params['timestart'] = $timestart;
1043     $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1045     $userfields = user_picture::fields('u', null, 'userid');
1047     if (!$submissions = $DB->get_records_sql('SELECT asb.id, asb.timemodified, ' .
1048                                                      $userfields .
1049                                              '  FROM {assign_submission} asb
1050                                                 JOIN {assign} a ON a.id = asb.assignment
1051                                                 JOIN {user} u ON u.id = asb.userid ' .
1052                                           $groupjoin .
1053                                             '  WHERE asb.timemodified > :timestart AND
1054                                                      asb.status = :submitted AND
1055                                                      a.id = :cminstance
1056                                                      ' . $userselect . ' ' . $groupselect .
1057                                             ' ORDER BY asb.timemodified ASC', $params)) {
1058          return;
1059     }
1061     $groupmode       = groups_get_activity_groupmode($cm, $course);
1062     $cmcontext      = context_module::instance($cm->id);
1063     $grader          = has_capability('moodle/grade:viewall', $cmcontext);
1064     $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext);
1065     $viewfullnames   = has_capability('moodle/site:viewfullnames', $cmcontext);
1068     $showrecentsubmissions = get_config('assign', 'showrecentsubmissions');
1069     $show = array();
1070     foreach ($submissions as $submission) {
1071         if ($submission->userid == $USER->id) {
1072             $show[] = $submission;
1073             continue;
1074         }
1075         // The act of submitting of assignment may be considered private -
1076         // only graders will see it if specified.
1077         if (empty($showrecentsubmissions)) {
1078             if (!$grader) {
1079                 continue;
1080             }
1081         }
1083         if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
1084             if (isguestuser()) {
1085                 // Shortcut - guest user does not belong into any group.
1086                 continue;
1087             }
1089             // This will be slow - show only users that share group with me in this cm.
1090             if (!$modinfo->get_groups($cm->groupingid)) {
1091                 continue;
1092             }
1093             $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
1094             if (is_array($usersgroups)) {
1095                 $usersgroups = array_keys($usersgroups);
1096                 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
1097                 if (empty($intersect)) {
1098                     continue;
1099                 }
1100             }
1101         }
1102         $show[] = $submission;
1103     }
1105     if (empty($show)) {
1106         return;
1107     }
1109     if ($grader) {
1110         require_once($CFG->libdir.'/gradelib.php');
1111         $userids = array();
1112         foreach ($show as $id => $submission) {
1113             $userids[] = $submission->userid;
1114         }
1115         $grades = grade_get_grades($courseid, 'mod', 'assign', $cm->instance, $userids);
1116     }
1118     $aname = format_string($cm->name, true);
1119     foreach ($show as $submission) {
1120         $activity = new stdClass();
1122         $activity->type         = 'assign';
1123         $activity->cmid         = $cm->id;
1124         $activity->name         = $aname;
1125         $activity->sectionnum   = $cm->sectionnum;
1126         $activity->timestamp    = $submission->timemodified;
1127         $activity->user         = new stdClass();
1128         if ($grader) {
1129             $activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
1130         }
1132         $userfields = explode(',', user_picture::fields());
1133         foreach ($userfields as $userfield) {
1134             if ($userfield == 'id') {
1135                 // Aliased in SQL above.
1136                 $activity->user->{$userfield} = $submission->userid;
1137             } else {
1138                 $activity->user->{$userfield} = $submission->{$userfield};
1139             }
1140         }
1141         $activity->user->fullname = fullname($submission, $viewfullnames);
1143         $activities[$index++] = $activity;
1144     }
1146     return;
1149 /**
1150  * Print recent activity from all assignments in a given course
1151  *
1152  * This is used by course/recent.php
1153  * @param stdClass $activity
1154  * @param int $courseid
1155  * @param bool $detail
1156  * @param array $modnames
1157  */
1158 function assign_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
1159     global $CFG, $OUTPUT;
1161     echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
1163     echo '<tr><td class="userpicture" valign="top">';
1164     echo $OUTPUT->user_picture($activity->user);
1165     echo '</td><td>';
1167     if ($detail) {
1168         $modname = $modnames[$activity->type];
1169         echo '<div class="title">';
1170         echo $OUTPUT->image_icon('icon', $modname, 'assign');
1171         echo '<a href="' . $CFG->wwwroot . '/mod/assign/view.php?id=' . $activity->cmid . '">';
1172         echo $activity->name;
1173         echo '</a>';
1174         echo '</div>';
1175     }
1177     if (isset($activity->grade)) {
1178         echo '<div class="grade">';
1179         echo get_string('grade').': ';
1180         echo $activity->grade;
1181         echo '</div>';
1182     }
1184     echo '<div class="user">';
1185     echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">";
1186     echo "{$activity->user->fullname}</a>  - " . userdate($activity->timestamp);
1187     echo '</div>';
1189     echo '</td></tr></table>';
1192 /**
1193  * Checks if a scale is being used by an assignment.
1194  *
1195  * This is used by the backup code to decide whether to back up a scale
1196  * @param int $assignmentid
1197  * @param int $scaleid
1198  * @return boolean True if the scale is used by the assignment
1199  */
1200 function assign_scale_used($assignmentid, $scaleid) {
1201     global $DB;
1203     $return = false;
1204     $rec = $DB->get_record('assign', array('id'=>$assignmentid, 'grade'=>-$scaleid));
1206     if (!empty($rec) && !empty($scaleid)) {
1207         $return = true;
1208     }
1210     return $return;
1213 /**
1214  * Checks if scale is being used by any instance of assignment
1215  *
1216  * This is used to find out if scale used anywhere
1217  * @param int $scaleid
1218  * @return boolean True if the scale is used by any assignment
1219  */
1220 function assign_scale_used_anywhere($scaleid) {
1221     global $DB;
1223     if ($scaleid and $DB->record_exists('assign', array('grade'=>-$scaleid))) {
1224         return true;
1225     } else {
1226         return false;
1227     }
1230 /**
1231  * List the actions that correspond to a view of this module.
1232  * This is used by the participation report.
1233  *
1234  * Note: This is not used by new logging system. Event with
1235  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
1236  *       be considered as view action.
1237  *
1238  * @return array
1239  */
1240 function assign_get_view_actions() {
1241     return array('view submission', 'view feedback');
1244 /**
1245  * List the actions that correspond to a post of this module.
1246  * This is used by the participation report.
1247  *
1248  * Note: This is not used by new logging system. Event with
1249  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
1250  *       will be considered as post action.
1251  *
1252  * @return array
1253  */
1254 function assign_get_post_actions() {
1255     return array('upload', 'submit', 'submit for grading');
1258 /**
1259  * Call cron on the assign module.
1260  */
1261 function assign_cron() {
1262     global $CFG;
1264     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1265     assign::cron();
1267     $plugins = core_component::get_plugin_list('assignsubmission');
1269     foreach ($plugins as $name => $plugin) {
1270         $disabled = get_config('assignsubmission_' . $name, 'disabled');
1271         if (!$disabled) {
1272             $class = 'assign_submission_' . $name;
1273             require_once($CFG->dirroot . '/mod/assign/submission/' . $name . '/locallib.php');
1274             $class::cron();
1275         }
1276     }
1277     $plugins = core_component::get_plugin_list('assignfeedback');
1279     foreach ($plugins as $name => $plugin) {
1280         $disabled = get_config('assignfeedback_' . $name, 'disabled');
1281         if (!$disabled) {
1282             $class = 'assign_feedback_' . $name;
1283             require_once($CFG->dirroot . '/mod/assign/feedback/' . $name . '/locallib.php');
1284             $class::cron();
1285         }
1286     }
1288     return true;
1291 /**
1292  * Returns all other capabilities used by this module.
1293  * @return array Array of capability strings
1294  */
1295 function assign_get_extra_capabilities() {
1296     return array('gradereport/grader:view',
1297                  'moodle/grade:viewall',
1298                  'moodle/site:viewfullnames',
1299                  'moodle/site:config');
1302 /**
1303  * Create grade item for given assignment.
1304  *
1305  * @param stdClass $assign record with extra cmidnumber
1306  * @param array $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1307  * @return int 0 if ok, error code otherwise
1308  */
1309 function assign_grade_item_update($assign, $grades=null) {
1310     global $CFG;
1311     require_once($CFG->libdir.'/gradelib.php');
1313     if (!isset($assign->courseid)) {
1314         $assign->courseid = $assign->course;
1315     }
1317     $params = array('itemname'=>$assign->name, 'idnumber'=>$assign->cmidnumber);
1319     // Check if feedback plugin for gradebook is enabled, if yes then
1320     // gradetype = GRADE_TYPE_TEXT else GRADE_TYPE_NONE.
1321     $gradefeedbackenabled = false;
1323     if (isset($assign->gradefeedbackenabled)) {
1324         $gradefeedbackenabled = $assign->gradefeedbackenabled;
1325     } else if ($assign->grade == 0) { // Grade feedback is needed only when grade == 0.
1326         require_once($CFG->dirroot . '/mod/assign/locallib.php');
1327         $mod = get_coursemodule_from_instance('assign', $assign->id, $assign->courseid);
1328         $cm = context_module::instance($mod->id);
1329         $assignment = new assign($cm, null, null);
1330         $gradefeedbackenabled = $assignment->is_gradebook_feedback_enabled();
1331     }
1333     if ($assign->grade > 0) {
1334         $params['gradetype'] = GRADE_TYPE_VALUE;
1335         $params['grademax']  = $assign->grade;
1336         $params['grademin']  = 0;
1338     } else if ($assign->grade < 0) {
1339         $params['gradetype'] = GRADE_TYPE_SCALE;
1340         $params['scaleid']   = -$assign->grade;
1342     } else if ($gradefeedbackenabled) {
1343         // $assign->grade == 0 and feedback enabled.
1344         $params['gradetype'] = GRADE_TYPE_TEXT;
1345     } else {
1346         // $assign->grade == 0 and no feedback enabled.
1347         $params['gradetype'] = GRADE_TYPE_NONE;
1348     }
1350     if ($grades  === 'reset') {
1351         $params['reset'] = true;
1352         $grades = null;
1353     }
1355     return grade_update('mod/assign',
1356                         $assign->courseid,
1357                         'mod',
1358                         'assign',
1359                         $assign->id,
1360                         0,
1361                         $grades,
1362                         $params);
1365 /**
1366  * Return grade for given user or all users.
1367  *
1368  * @param stdClass $assign record of assign with an additional cmidnumber
1369  * @param int $userid optional user id, 0 means all users
1370  * @return array array of grades, false if none
1371  */
1372 function assign_get_user_grades($assign, $userid=0) {
1373     global $CFG;
1375     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1377     $cm = get_coursemodule_from_instance('assign', $assign->id, 0, false, MUST_EXIST);
1378     $context = context_module::instance($cm->id);
1379     $assignment = new assign($context, null, null);
1380     $assignment->set_instance($assign);
1381     return $assignment->get_user_grades_for_gradebook($userid);
1384 /**
1385  * Update activity grades.
1386  *
1387  * @param stdClass $assign database record
1388  * @param int $userid specific user only, 0 means all
1389  * @param bool $nullifnone - not used
1390  */
1391 function assign_update_grades($assign, $userid=0, $nullifnone=true) {
1392     global $CFG;
1393     require_once($CFG->libdir.'/gradelib.php');
1395     if ($assign->grade == 0) {
1396         assign_grade_item_update($assign);
1398     } else if ($grades = assign_get_user_grades($assign, $userid)) {
1399         foreach ($grades as $k => $v) {
1400             if ($v->rawgrade == -1) {
1401                 $grades[$k]->rawgrade = null;
1402             }
1403         }
1404         assign_grade_item_update($assign, $grades);
1406     } else {
1407         assign_grade_item_update($assign);
1408     }
1411 /**
1412  * List the file areas that can be browsed.
1413  *
1414  * @param stdClass $course
1415  * @param stdClass $cm
1416  * @param stdClass $context
1417  * @return array
1418  */
1419 function assign_get_file_areas($course, $cm, $context) {
1420     global $CFG;
1421     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1423     $areas = array(ASSIGN_INTROATTACHMENT_FILEAREA => get_string('introattachments', 'mod_assign'));
1425     $assignment = new assign($context, $cm, $course);
1426     foreach ($assignment->get_submission_plugins() as $plugin) {
1427         if ($plugin->is_visible()) {
1428             $pluginareas = $plugin->get_file_areas();
1430             if ($pluginareas) {
1431                 $areas = array_merge($areas, $pluginareas);
1432             }
1433         }
1434     }
1435     foreach ($assignment->get_feedback_plugins() as $plugin) {
1436         if ($plugin->is_visible()) {
1437             $pluginareas = $plugin->get_file_areas();
1439             if ($pluginareas) {
1440                 $areas = array_merge($areas, $pluginareas);
1441             }
1442         }
1443     }
1445     return $areas;
1448 /**
1449  * File browsing support for assign module.
1450  *
1451  * @param file_browser $browser
1452  * @param object $areas
1453  * @param object $course
1454  * @param object $cm
1455  * @param object $context
1456  * @param string $filearea
1457  * @param int $itemid
1458  * @param string $filepath
1459  * @param string $filename
1460  * @return object file_info instance or null if not found
1461  */
1462 function assign_get_file_info($browser,
1463                               $areas,
1464                               $course,
1465                               $cm,
1466                               $context,
1467                               $filearea,
1468                               $itemid,
1469                               $filepath,
1470                               $filename) {
1471     global $CFG;
1472     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1474     if ($context->contextlevel != CONTEXT_MODULE) {
1475         return null;
1476     }
1478     $urlbase = $CFG->wwwroot.'/pluginfile.php';
1479     $fs = get_file_storage();
1480     $filepath = is_null($filepath) ? '/' : $filepath;
1481     $filename = is_null($filename) ? '.' : $filename;
1483     // Need to find where this belongs to.
1484     $assignment = new assign($context, $cm, $course);
1485     if ($filearea === ASSIGN_INTROATTACHMENT_FILEAREA) {
1486         if (!has_capability('moodle/course:managefiles', $context)) {
1487             // Students can not peak here!
1488             return null;
1489         }
1490         if (!($storedfile = $fs->get_file($assignment->get_context()->id,
1491                                           'mod_assign', $filearea, 0, $filepath, $filename))) {
1492             return null;
1493         }
1494         return new file_info_stored($browser,
1495                         $assignment->get_context(),
1496                         $storedfile,
1497                         $urlbase,
1498                         $filearea,
1499                         $itemid,
1500                         true,
1501                         true,
1502                         false);
1503     }
1505     $pluginowner = null;
1506     foreach ($assignment->get_submission_plugins() as $plugin) {
1507         if ($plugin->is_visible()) {
1508             $pluginareas = $plugin->get_file_areas();
1510             if (array_key_exists($filearea, $pluginareas)) {
1511                 $pluginowner = $plugin;
1512                 break;
1513             }
1514         }
1515     }
1516     if (!$pluginowner) {
1517         foreach ($assignment->get_feedback_plugins() as $plugin) {
1518             if ($plugin->is_visible()) {
1519                 $pluginareas = $plugin->get_file_areas();
1521                 if (array_key_exists($filearea, $pluginareas)) {
1522                     $pluginowner = $plugin;
1523                     break;
1524                 }
1525             }
1526         }
1527     }
1529     if (!$pluginowner) {
1530         return null;
1531     }
1533     $result = $pluginowner->get_file_info($browser, $filearea, $itemid, $filepath, $filename);
1534     return $result;
1537 /**
1538  * Prints the complete info about a user's interaction with an assignment.
1539  *
1540  * @param stdClass $course
1541  * @param stdClass $user
1542  * @param stdClass $coursemodule
1543  * @param stdClass $assign the database assign record
1544  *
1545  * This prints the submission summary and feedback summary for this student.
1546  */
1547 function assign_user_complete($course, $user, $coursemodule, $assign) {
1548     global $CFG;
1549     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1551     $context = context_module::instance($coursemodule->id);
1553     $assignment = new assign($context, $coursemodule, $course);
1555     echo $assignment->view_student_summary($user, false);
1558 /**
1559  * Rescale all grades for this activity and push the new grades to the gradebook.
1560  *
1561  * @param stdClass $course Course db record
1562  * @param stdClass $cm Course module db record
1563  * @param float $oldmin
1564  * @param float $oldmax
1565  * @param float $newmin
1566  * @param float $newmax
1567  */
1568 function assign_rescale_activity_grades($course, $cm, $oldmin, $oldmax, $newmin, $newmax) {
1569     global $DB;
1571     if ($oldmax <= $oldmin) {
1572         // Grades cannot be scaled.
1573         return false;
1574     }
1575     $scale = ($newmax - $newmin) / ($oldmax - $oldmin);
1576     if (($newmax - $newmin) <= 1) {
1577         // We would lose too much precision, lets bail.
1578         return false;
1579     }
1581     $params = array(
1582         'p1' => $oldmin,
1583         'p2' => $scale,
1584         'p3' => $newmin,
1585         'a' => $cm->instance
1586     );
1588     // Only rescale grades that are greater than or equal to 0. Anything else is a special value.
1589     $sql = 'UPDATE {assign_grades} set grade = (((grade - :p1) * :p2) + :p3) where assignment = :a and grade >= 0';
1590     $dbupdate = $DB->execute($sql, $params);
1591     if (!$dbupdate) {
1592         return false;
1593     }
1595     // Now re-push all grades to the gradebook.
1596     $dbparams = array('id' => $cm->instance);
1597     $assign = $DB->get_record('assign', $dbparams);
1598     $assign->cmidnumber = $cm->idnumber;
1600     assign_update_grades($assign);
1602     return true;
1605 /**
1606  * Print the grade information for the assignment for this user.
1607  *
1608  * @param stdClass $course
1609  * @param stdClass $user
1610  * @param stdClass $coursemodule
1611  * @param stdClass $assignment
1612  */
1613 function assign_user_outline($course, $user, $coursemodule, $assignment) {
1614     global $CFG;
1615     require_once($CFG->libdir.'/gradelib.php');
1616     require_once($CFG->dirroot.'/grade/grading/lib.php');
1618     $gradinginfo = grade_get_grades($course->id,
1619                                         'mod',
1620                                         'assign',
1621                                         $assignment->id,
1622                                         $user->id);
1624     $gradingitem = $gradinginfo->items[0];
1625     $gradebookgrade = $gradingitem->grades[$user->id];
1627     if (empty($gradebookgrade->str_long_grade)) {
1628         return null;
1629     }
1630     $result = new stdClass();
1631     $result->info = get_string('outlinegrade', 'assign', $gradebookgrade->str_long_grade);
1632     $result->time = $gradebookgrade->dategraded;
1634     return $result;
1637 /**
1638  * Obtains the automatic completion state for this module based on any conditions
1639  * in assign settings.
1640  *
1641  * @param object $course Course
1642  * @param object $cm Course-module
1643  * @param int $userid User ID
1644  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
1645  * @return bool True if completed, false if not, $type if conditions not set.
1646  */
1647 function assign_get_completion_state($course, $cm, $userid, $type) {
1648     global $CFG, $DB;
1649     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1651     $assign = new assign(null, $cm, $course);
1653     // If completion option is enabled, evaluate it and return true/false.
1654     if ($assign->get_instance()->completionsubmit) {
1655         if ($assign->get_instance()->teamsubmission) {
1656             $submission = $assign->get_group_submission($userid, 0, false);
1657         } else {
1658             $submission = $assign->get_user_submission($userid, false);
1659         }
1660         return $submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1661     } else {
1662         // Completion option is not enabled so just return $type.
1663         return $type;
1664     }
1667 /**
1668  * Serves intro attachment files.
1669  *
1670  * @param mixed $course course or id of the course
1671  * @param mixed $cm course module or id of the course module
1672  * @param context $context
1673  * @param string $filearea
1674  * @param array $args
1675  * @param bool $forcedownload
1676  * @param array $options additional options affecting the file serving
1677  * @return bool false if file not found, does not return if found - just send the file
1678  */
1679 function assign_pluginfile($course,
1680                 $cm,
1681                 context $context,
1682                 $filearea,
1683                 $args,
1684                 $forcedownload,
1685                 array $options=array()) {
1686     global $CFG;
1688     if ($context->contextlevel != CONTEXT_MODULE) {
1689         return false;
1690     }
1692     require_login($course, false, $cm);
1693     if (!has_capability('mod/assign:view', $context)) {
1694         return false;
1695     }
1697     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1698     $assign = new assign($context, $cm, $course);
1700     if ($filearea !== ASSIGN_INTROATTACHMENT_FILEAREA) {
1701         return false;
1702     }
1703     if (!$assign->show_intro()) {
1704         return false;
1705     }
1707     $itemid = (int)array_shift($args);
1708     if ($itemid != 0) {
1709         return false;
1710     }
1712     $relativepath = implode('/', $args);
1714     $fullpath = "/{$context->id}/mod_assign/$filearea/$itemid/$relativepath";
1716     $fs = get_file_storage();
1717     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1718         return false;
1719     }
1720     send_stored_file($file, 0, 0, $forcedownload, $options);
1723 /**
1724  * Serve the grading panel as a fragment.
1725  *
1726  * @param array $args List of named arguments for the fragment loader.
1727  * @return string
1728  */
1729 function mod_assign_output_fragment_gradingpanel($args) {
1730     global $CFG;
1732     $context = $args['context'];
1734     if ($context->contextlevel != CONTEXT_MODULE) {
1735         return null;
1736     }
1737     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1738     $assign = new assign($context, null, null);
1740     $userid = clean_param($args['userid'], PARAM_INT);
1741     $attemptnumber = clean_param($args['attemptnumber'], PARAM_INT);
1742     $formdata = array();
1743     if (!empty($args['jsonformdata'])) {
1744         $serialiseddata = json_decode($args['jsonformdata']);
1745         parse_str($serialiseddata, $formdata);
1746     }
1747     $viewargs = array(
1748         'userid' => $userid,
1749         'attemptnumber' => $attemptnumber,
1750         'formdata' => $formdata
1751     );
1753     return $assign->view('gradingpanel', $viewargs);
1756 /**
1757  * Check if the module has any update that affects the current user since a given time.
1758  *
1759  * @param  cm_info $cm course module data
1760  * @param  int $from the time to check updates from
1761  * @param  array $filter  if we need to check only specific updates
1762  * @return stdClass an object with the different type of areas indicating if they were updated or not
1763  * @since Moodle 3.2
1764  */
1765 function assign_check_updates_since(cm_info $cm, $from, $filter = array()) {
1766     global $DB, $USER, $CFG;
1767     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1769     $updates = new stdClass();
1770     $updates = course_check_module_updates_since($cm, $from, array(ASSIGN_INTROATTACHMENT_FILEAREA), $filter);
1772     // Check if there is a new submission by the user or new grades.
1773     $select = 'assignment = :id AND userid = :userid AND (timecreated > :since1 OR timemodified > :since2)';
1774     $params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from);
1775     $updates->submissions = (object) array('updated' => false);
1776     $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id');
1777     if (!empty($submissions)) {
1778         $updates->submissions->updated = true;
1779         $updates->submissions->itemids = array_keys($submissions);
1780     }
1782     $updates->grades = (object) array('updated' => false);
1783     $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id');
1784     if (!empty($grades)) {
1785         $updates->grades->updated = true;
1786         $updates->grades->itemids = array_keys($grades);
1787     }
1789     // Now, teachers should see other students updates.
1790     if (has_capability('mod/assign:viewgrades', $cm->context)) {
1791         $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
1792         $select = 'assignment = :id AND (timecreated > :since1 OR timemodified > :since2)';
1794         if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
1795             $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
1796             if (empty($groupusers)) {
1797                 return $updates;
1798             }
1799             list($insql, $inparams) = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED);
1800             $select .= ' AND userid ' . $insql;
1801             $params = array_merge($params, $inparams);
1802         }
1804         $updates->usersubmissions = (object) array('updated' => false);
1805         $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id');
1806         if (!empty($submissions)) {
1807             $updates->usersubmissions->updated = true;
1808             $updates->usersubmissions->itemids = array_keys($submissions);
1809         }
1811         $updates->usergrades = (object) array('updated' => false);
1812         $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id');
1813         if (!empty($grades)) {
1814             $updates->usergrades->updated = true;
1815             $updates->usergrades->itemids = array_keys($grades);
1816         }
1817     }
1819     return $updates;
1822 /**
1823  * Is the event visible?
1824  *
1825  * This is used to determine global visibility of an event in all places throughout Moodle. For example,
1826  * the ASSIGN_EVENT_TYPE_GRADINGDUE event will not be shown to students on their calendar.
1827  *
1828  * @param calendar_event $event
1829  * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1830  * @return bool Returns true if the event is visible to the current user, false otherwise.
1831  */
1832 function mod_assign_core_calendar_is_event_visible(calendar_event $event, $userid = 0) {
1833     global $CFG, $USER;
1835     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1837     if (empty($userid)) {
1838         $userid = $USER->id;
1839     }
1841     $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance];
1842     $context = context_module::instance($cm->id);
1844     $assign = new assign($context, $cm, null);
1846     if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
1847         return $assign->can_grade($userid);
1848     } else {
1849         return true;
1850     }
1853 /**
1854  * This function receives a calendar event and returns the action associated with it, or null if there is none.
1855  *
1856  * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1857  * is not displayed on the block.
1858  *
1859  * @param calendar_event $event
1860  * @param \core_calendar\action_factory $factory
1861  * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1862  * @return \core_calendar\local\event\entities\action_interface|null
1863  */
1864 function mod_assign_core_calendar_provide_event_action(calendar_event $event,
1865                                                        \core_calendar\action_factory $factory,
1866                                                        $userid = 0) {
1868     global $CFG, $USER;
1870     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1872     if (empty($userid)) {
1873         $userid = $USER->id;
1874     }
1876     $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance];
1877     $context = context_module::instance($cm->id);
1879     $assign = new assign($context, $cm, null);
1881     // Apply overrides.
1882     $assign->update_effective_access($userid);
1884     if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
1885         $name = get_string('grade');
1886         $url = new \moodle_url('/mod/assign/view.php', [
1887             'id' => $cm->id,
1888             'action' => 'grader'
1889         ]);
1890         $itemcount = $assign->count_submissions_need_grading();
1891         $actionable = $assign->can_grade($userid) && (time() >= $assign->get_instance()->allowsubmissionsfromdate);
1892     } else {
1893         $usersubmission = $assign->get_user_submission($userid, false);
1894         if ($usersubmission && $usersubmission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
1895             // The user has already submitted.
1896             // We do not want to change the text to edit the submission, we want to remove the event from the Dashboard entirely.
1897             return null;
1898         }
1900         $participant = $assign->get_participant($userid);
1902         if (!$participant) {
1903             // If the user is not a participant in the assignment then they have
1904             // no action to take. This will filter out the events for teachers.
1905             return null;
1906         }
1908         // The user has not yet submitted anything. Show the addsubmission link.
1909         $name = get_string('addsubmission', 'assign');
1910         $url = new \moodle_url('/mod/assign/view.php', [
1911             'id' => $cm->id,
1912             'action' => 'editsubmission'
1913         ]);
1914         $itemcount = 1;
1915         $actionable = $assign->is_any_submission_plugin_enabled() && $assign->can_edit_submission($userid, $userid);
1916     }
1918     return $factory->create_instance(
1919         $name,
1920         $url,
1921         $itemcount,
1922         $actionable
1923     );
1926 /**
1927  * Callback function that determines whether an action event should be showing its item count
1928  * based on the event type and the item count.
1929  *
1930  * @param calendar_event $event The calendar event.
1931  * @param int $itemcount The item count associated with the action event.
1932  * @return bool
1933  */
1934 function mod_assign_core_calendar_event_action_shows_item_count(calendar_event $event, $itemcount = 0) {
1935     // List of event types where the action event's item count should be shown.
1936     $eventtypesshowingitemcount = [
1937         ASSIGN_EVENT_TYPE_GRADINGDUE
1938     ];
1939     // For mod_assign, item count should be shown if the event type is 'gradingdue' and there is one or more item count.
1940     return in_array($event->eventtype, $eventtypesshowingitemcount) && $itemcount > 0;
1943 /**
1944  * This function calculates the minimum and maximum cutoff values for the timestart of
1945  * the given event.
1946  *
1947  * It will return an array with two values, the first being the minimum cutoff value and
1948  * the second being the maximum cutoff value. Either or both values can be null, which
1949  * indicates there is no minimum or maximum, respectively.
1950  *
1951  * If a cutoff is required then the function must return an array containing the cutoff
1952  * timestamp and error string to display to the user if the cutoff value is violated.
1953  *
1954  * A minimum and maximum cutoff return value will look like:
1955  * [
1956  *     [1505704373, 'The due date must be after the sbumission start date'],
1957  *     [1506741172, 'The due date must be before the cutoff date']
1958  * ]
1959  *
1960  * If the event does not have a valid timestart range then [false, false] will
1961  * be returned.
1962  *
1963  * @param calendar_event $event The calendar event to get the time range for
1964  * @param stdClass $instance The module instance to get the range from
1965  * @return array
1966  */
1967 function mod_assign_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
1968     global $CFG;
1970     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1972     $courseid = $event->courseid;
1973     $modulename = $event->modulename;
1974     $instanceid = $event->instance;
1975     $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1976     $context = context_module::instance($coursemodule->id);
1977     $assign = new assign($context, null, null);
1978     $assign->set_instance($instance);
1980     return $assign->get_valid_calendar_event_timestart_range($event);
1983 /**
1984  * This function will update the assign module according to the
1985  * event that has been modified.
1986  *
1987  * @throws \moodle_exception
1988  * @param \calendar_event $event
1989  * @param stdClass $instance The module instance to get the range from
1990  */
1991 function mod_assign_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $instance) {
1992     global $CFG, $DB;
1994     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1996     if (empty($event->instance) || $event->modulename != 'assign') {
1997         return;
1998     }
2000     if ($instance->id != $event->instance) {
2001         return;
2002     }
2004     if (!in_array($event->eventtype, [ASSIGN_EVENT_TYPE_DUE, ASSIGN_EVENT_TYPE_GRADINGDUE])) {
2005         return;
2006     }
2008     $courseid = $event->courseid;
2009     $modulename = $event->modulename;
2010     $instanceid = $event->instance;
2011     $modified = false;
2012     $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
2013     $context = context_module::instance($coursemodule->id);
2015     // The user does not have the capability to modify this activity.
2016     if (!has_capability('moodle/course:manageactivities', $context)) {
2017         return;
2018     }
2020     $assign = new assign($context, $coursemodule, null);
2021     $assign->set_instance($instance);
2023     if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
2024         // This check is in here because due date events are currently
2025         // the only events that can be overridden, so we can save a DB
2026         // query if we don't bother checking other events.
2027         if ($assign->is_override_calendar_event($event)) {
2028             // This is an override event so we should ignore it.
2029             return;
2030         }
2032         $newduedate = $event->timestart;
2034         if ($newduedate != $instance->duedate) {
2035             $instance->duedate = $newduedate;
2036             $modified = true;
2037         }
2038     } else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
2039         $newduedate = $event->timestart;
2041         if ($newduedate != $instance->gradingduedate) {
2042             $instance->gradingduedate = $newduedate;
2043             $modified = true;
2044         }
2045     }
2047     if ($modified) {
2048         $instance->timemodified = time();
2049         // Persist the assign instance changes.
2050         $DB->update_record('assign', $instance);
2051         $assign->update_calendar($coursemodule->id);
2052         $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
2053         $event->trigger();
2054     }