MDL-64879 mod_assign: deprecate legacy assign_cron() function
[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 if (isset($override->groupid)) {
262             $conds['groupid'] = $override->groupid;
263         } else {
264             // This is not a valid override, it may have been left from a bad import or restore.
265             $conds['groupid'] = $conds['userid'] = 0;
266         }
267     }
268     $oldevents = $DB->get_records('event', $conds, 'id ASC');
270     // Now make a to-do list of all that needs to be updated.
271     if (empty($override)) {
272         // We are updating the primary settings for the assignment, so we need to add all the overrides.
273         $overrides = $DB->get_records('assign_overrides', array('assignid' => $assigninstance->id), 'id ASC');
274         // It is necessary to add an empty stdClass to the beginning of the array as the $oldevents
275         // list contains the original (non-override) event for the module. If this is not included
276         // the logic below will end up updating the wrong row when we try to reconcile this $overrides
277         // list against the $oldevents list.
278         array_unshift($overrides, new stdClass());
279     } else {
280         // Just do the one override.
281         $overrides = array($override);
282     }
284     if (!empty($assign->get_course_module())) {
285         $cmid = $assign->get_course_module()->id;
286     } else {
287         $cmid = get_coursemodule_from_instance('assign', $assigninstance->id, $assigninstance->course)->id;
288     }
290     foreach ($overrides as $current) {
291         $groupid   = isset($current->groupid) ? $current->groupid : 0;
292         $userid    = isset($current->userid) ? $current->userid : 0;
293         $duedate = isset($current->duedate) ? $current->duedate : $assigninstance->duedate;
295         // Only add 'due' events for an override if they differ from the assign default.
296         $addclose = empty($current->id) || !empty($current->duedate);
298         $event = new stdClass();
299         $event->type = CALENDAR_EVENT_TYPE_ACTION;
300         $event->description = format_module_intro('assign', $assigninstance, $cmid);
301         // Events module won't show user events when the courseid is nonzero.
302         $event->courseid    = ($userid) ? 0 : $assigninstance->course;
303         $event->groupid     = $groupid;
304         $event->userid      = $userid;
305         $event->modulename  = 'assign';
306         $event->instance    = $assigninstance->id;
307         $event->timestart   = $duedate;
308         $event->timeduration = 0;
309         $event->timesort    = $event->timestart + $event->timeduration;
310         $event->visible     = instance_is_visible('assign', $assigninstance);
311         $event->eventtype   = ASSIGN_EVENT_TYPE_DUE;
312         $event->priority    = null;
314         // Determine the event name and priority.
315         if ($groupid) {
316             // Group override event.
317             $params = new stdClass();
318             $params->assign = $assigninstance->name;
319             $params->group = groups_get_group_name($groupid);
320             if ($params->group === false) {
321                 // Group doesn't exist, just skip it.
322                 continue;
323             }
324             $eventname = get_string('overridegroupeventname', 'assign', $params);
325             // Set group override priority.
326             if (isset($current->sortorder)) {
327                 $event->priority = $current->sortorder;
328             }
329         } else if ($userid) {
330             // User override event.
331             $params = new stdClass();
332             $params->assign = $assigninstance->name;
333             $eventname = get_string('overrideusereventname', 'assign', $params);
334             // Set user override priority.
335             $event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
336         } else {
337             // The parent event.
338             $eventname = $assigninstance->name;
339         }
341         if ($duedate && $addclose) {
342             if ($oldevent = array_shift($oldevents)) {
343                 $event->id = $oldevent->id;
344             } else {
345                 unset($event->id);
346             }
347             $event->name      = $eventname.' ('.get_string('duedate', 'assign').')';
348             calendar_event::create($event, false);
349         }
350     }
352     // Delete any leftover events.
353     foreach ($oldevents as $badevent) {
354         $badevent = calendar_event::load($badevent);
355         $badevent->delete();
356     }
359 /**
360  * Return the list if Moodle features this module supports
361  *
362  * @param string $feature FEATURE_xx constant for requested feature
363  * @return mixed True if module supports feature, null if doesn't know
364  */
365 function assign_supports($feature) {
366     switch($feature) {
367         case FEATURE_GROUPS:
368             return true;
369         case FEATURE_GROUPINGS:
370             return true;
371         case FEATURE_MOD_INTRO:
372             return true;
373         case FEATURE_COMPLETION_TRACKS_VIEWS:
374             return true;
375         case FEATURE_COMPLETION_HAS_RULES:
376             return true;
377         case FEATURE_GRADE_HAS_GRADE:
378             return true;
379         case FEATURE_GRADE_OUTCOMES:
380             return true;
381         case FEATURE_BACKUP_MOODLE2:
382             return true;
383         case FEATURE_SHOW_DESCRIPTION:
384             return true;
385         case FEATURE_ADVANCED_GRADING:
386             return true;
387         case FEATURE_PLAGIARISM:
388             return true;
389         case FEATURE_COMMENT:
390             return true;
392         default:
393             return null;
394     }
397 /**
398  * Lists all gradable areas for the advanced grading methods gramework
399  *
400  * @return array('string'=>'string') An array with area names as keys and descriptions as values
401  */
402 function assign_grading_areas_list() {
403     return array('submissions'=>get_string('submissions', 'assign'));
407 /**
408  * extend an assigment navigation settings
409  *
410  * @param settings_navigation $settings
411  * @param navigation_node $navref
412  * @return void
413  */
414 function assign_extend_settings_navigation(settings_navigation $settings, navigation_node $navref) {
415     global $PAGE, $DB;
417     // We want to add these new nodes after the Edit settings node, and before the
418     // Locally assigned roles node. Of course, both of those are controlled by capabilities.
419     $keys = $navref->get_children_key_list();
420     $beforekey = null;
421     $i = array_search('modedit', $keys);
422     if ($i === false and array_key_exists(0, $keys)) {
423         $beforekey = $keys[0];
424     } else if (array_key_exists($i + 1, $keys)) {
425         $beforekey = $keys[$i + 1];
426     }
428     $cm = $PAGE->cm;
429     if (!$cm) {
430         return;
431     }
433     $context = $cm->context;
434     $course = $PAGE->course;
436     if (!$course) {
437         return;
438     }
440     if (has_capability('mod/assign:manageoverrides', $PAGE->cm->context)) {
441         $url = new moodle_url('/mod/assign/overrides.php', array('cmid' => $PAGE->cm->id));
442         $node = navigation_node::create(get_string('groupoverrides', 'assign'),
443             new moodle_url($url, array('mode' => 'group')),
444             navigation_node::TYPE_SETTING, null, 'mod_assign_groupoverrides');
445         $navref->add_node($node, $beforekey);
447         $node = navigation_node::create(get_string('useroverrides', 'assign'),
448             new moodle_url($url, array('mode' => 'user')),
449             navigation_node::TYPE_SETTING, null, 'mod_assign_useroverrides');
450         $navref->add_node($node, $beforekey);
451     }
453     // Link to gradebook.
454     if (has_capability('gradereport/grader:view', $cm->context) &&
455             has_capability('moodle/grade:viewall', $cm->context)) {
456         $link = new moodle_url('/grade/report/grader/index.php', array('id' => $course->id));
457         $linkname = get_string('viewgradebook', 'assign');
458         $node = $navref->add($linkname, $link, navigation_node::TYPE_SETTING);
459     }
461     // Link to download all submissions.
462     if (has_any_capability(array('mod/assign:grade', 'mod/assign:viewgrades'), $context)) {
463         $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'grading'));
464         $node = $navref->add(get_string('viewgrading', 'assign'), $link, navigation_node::TYPE_SETTING);
466         $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'downloadall'));
467         $node = $navref->add(get_string('downloadall', 'assign'), $link, navigation_node::TYPE_SETTING);
468     }
470     if (has_capability('mod/assign:revealidentities', $context)) {
471         $dbparams = array('id'=>$cm->instance);
472         $assignment = $DB->get_record('assign', $dbparams, 'blindmarking, revealidentities');
474         if ($assignment && $assignment->blindmarking && !$assignment->revealidentities) {
475             $urlparams = array('id' => $cm->id, 'action'=>'revealidentities');
476             $url = new moodle_url('/mod/assign/view.php', $urlparams);
477             $linkname = get_string('revealidentities', 'assign');
478             $node = $navref->add($linkname, $url, navigation_node::TYPE_SETTING);
479         }
480     }
483 /**
484  * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
485  * for the course (see resource).
486  *
487  * Given a course_module object, this function returns any "extra" information that may be needed
488  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
489  *
490  * @param stdClass $coursemodule The coursemodule object (record).
491  * @return cached_cm_info An object on information that the courses
492  *                        will know about (most noticeably, an icon).
493  */
494 function assign_get_coursemodule_info($coursemodule) {
495     global $CFG, $DB;
497     $dbparams = array('id'=>$coursemodule->instance);
498     $fields = 'id, name, alwaysshowdescription, allowsubmissionsfromdate, intro, introformat, completionsubmit';
499     if (! $assignment = $DB->get_record('assign', $dbparams, $fields)) {
500         return false;
501     }
503     $result = new cached_cm_info();
504     $result->name = $assignment->name;
505     if ($coursemodule->showdescription) {
506         if ($assignment->alwaysshowdescription || time() > $assignment->allowsubmissionsfromdate) {
507             // Convert intro to html. Do not filter cached version, filters run at display time.
508             $result->content = format_module_intro('assign', $assignment, $coursemodule->id, false);
509         }
510     }
512     // Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
513     if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
514         $result->customdata['customcompletionrules']['completionsubmit'] = $assignment->completionsubmit;
515     }
517     return $result;
520 /**
521  * Callback which returns human-readable strings describing the active completion custom rules for the module instance.
522  *
523  * @param cm_info|stdClass $cm object with fields ->completion and ->customdata['customcompletionrules']
524  * @return array $descriptions the array of descriptions for the custom rules.
525  */
526 function mod_assign_get_completion_active_rule_descriptions($cm) {
527     // Values will be present in cm_info, and we assume these are up to date.
528     if (empty($cm->customdata['customcompletionrules'])
529         || $cm->completion != COMPLETION_TRACKING_AUTOMATIC) {
530         return [];
531     }
533     $descriptions = [];
534     foreach ($cm->customdata['customcompletionrules'] as $key => $val) {
535         switch ($key) {
536             case 'completionsubmit':
537                 if (!empty($val)) {
538                     $descriptions[] = get_string('completionsubmit', 'assign');
539                 }
540                 break;
541             default:
542                 break;
543         }
544     }
545     return $descriptions;
548 /**
549  * Return a list of page types
550  * @param string $pagetype current page type
551  * @param stdClass $parentcontext Block's parent context
552  * @param stdClass $currentcontext Current context of block
553  */
554 function assign_page_type_list($pagetype, $parentcontext, $currentcontext) {
555     $modulepagetype = array(
556         'mod-assign-*' => get_string('page-mod-assign-x', 'assign'),
557         'mod-assign-view' => get_string('page-mod-assign-view', 'assign'),
558     );
559     return $modulepagetype;
562 /**
563  * Print an overview of all assignments
564  * for the courses.
565  *
566  * @deprecated since 3.3
567  * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
568  * @param mixed $courses The list of courses to print the overview for
569  * @param array $htmlarray The array of html to return
570  * @return true
571  */
572 function assign_print_overview($courses, &$htmlarray) {
573     global $CFG, $DB;
575     debugging('The function assign_print_overview() is now deprecated.', DEBUG_DEVELOPER);
577     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
578         return true;
579     }
581     if (!$assignments = get_all_instances_in_courses('assign', $courses)) {
582         return true;
583     }
585     $assignmentids = array();
587     // Do assignment_base::isopen() here without loading the whole thing for speed.
588     foreach ($assignments as $key => $assignment) {
589         $time = time();
590         $isopen = false;
591         if ($assignment->duedate) {
592             $duedate = false;
593             if ($assignment->cutoffdate) {
594                 $duedate = $assignment->cutoffdate;
595             }
596             if ($duedate) {
597                 $isopen = ($assignment->allowsubmissionsfromdate <= $time && $time <= $duedate);
598             } else {
599                 $isopen = ($assignment->allowsubmissionsfromdate <= $time);
600             }
601         }
602         if ($isopen) {
603             $assignmentids[] = $assignment->id;
604         }
605     }
607     if (empty($assignmentids)) {
608         // No assignments to look at - we're done.
609         return true;
610     }
612     // Definitely something to print, now include the constants we need.
613     require_once($CFG->dirroot . '/mod/assign/locallib.php');
615     $strduedate = get_string('duedate', 'assign');
616     $strcutoffdate = get_string('nosubmissionsacceptedafter', 'assign');
617     $strnolatesubmissions = get_string('nolatesubmissions', 'assign');
618     $strduedateno = get_string('duedateno', 'assign');
619     $strassignment = get_string('modulename', 'assign');
621     // We do all possible database work here *outside* of the loop to ensure this scales.
622     list($sqlassignmentids, $assignmentidparams) = $DB->get_in_or_equal($assignmentids);
624     $mysubmissions = null;
625     $unmarkedsubmissions = null;
627     foreach ($assignments as $assignment) {
629         // Do not show assignments that are not open.
630         if (!in_array($assignment->id, $assignmentids)) {
631             continue;
632         }
634         $context = context_module::instance($assignment->coursemodule);
636         // Does the submission status of the assignment require notification?
637         if (has_capability('mod/assign:submit', $context, null, false)) {
638             // Does the submission status of the assignment require notification?
639             $submitdetails = assign_get_mysubmission_details_for_print_overview($mysubmissions, $sqlassignmentids,
640                     $assignmentidparams, $assignment);
641         } else {
642             $submitdetails = false;
643         }
645         if (has_capability('mod/assign:grade', $context, null, false)) {
646             // Does the grading status of the assignment require notification ?
647             $gradedetails = assign_get_grade_details_for_print_overview($unmarkedsubmissions, $sqlassignmentids,
648                     $assignmentidparams, $assignment, $context);
649         } else {
650             $gradedetails = false;
651         }
653         if (empty($submitdetails) && empty($gradedetails)) {
654             // There is no need to display this assignment as there is nothing to notify.
655             continue;
656         }
658         $dimmedclass = '';
659         if (!$assignment->visible) {
660             $dimmedclass = ' class="dimmed"';
661         }
662         $href = $CFG->wwwroot . '/mod/assign/view.php?id=' . $assignment->coursemodule;
663         $basestr = '<div class="assign overview">' .
664                '<div class="name">' .
665                $strassignment . ': '.
666                '<a ' . $dimmedclass .
667                    'title="' . $strassignment . '" ' .
668                    'href="' . $href . '">' .
669                format_string($assignment->name) .
670                '</a></div>';
671         if ($assignment->duedate) {
672             $userdate = userdate($assignment->duedate);
673             $basestr .= '<div class="info">' . $strduedate . ': ' . $userdate . '</div>';
674         } else {
675             $basestr .= '<div class="info">' . $strduedateno . '</div>';
676         }
677         if ($assignment->cutoffdate) {
678             if ($assignment->cutoffdate == $assignment->duedate) {
679                 $basestr .= '<div class="info">' . $strnolatesubmissions . '</div>';
680             } else {
681                 $userdate = userdate($assignment->cutoffdate);
682                 $basestr .= '<div class="info">' . $strcutoffdate . ': ' . $userdate . '</div>';
683             }
684         }
686         // Show only relevant information.
687         if (!empty($submitdetails)) {
688             $basestr .= $submitdetails;
689         }
691         if (!empty($gradedetails)) {
692             $basestr .= $gradedetails;
693         }
694         $basestr .= '</div>';
696         if (empty($htmlarray[$assignment->course]['assign'])) {
697             $htmlarray[$assignment->course]['assign'] = $basestr;
698         } else {
699             $htmlarray[$assignment->course]['assign'] .= $basestr;
700         }
701     }
702     return true;
705 /**
706  * This api generates html to be displayed to students in print overview section, related to their submission status of the given
707  * assignment.
708  *
709  * @deprecated since 3.3
710  * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
711  * @param array $mysubmissions list of submissions of current user indexed by assignment id.
712  * @param string $sqlassignmentids sql clause used to filter open assignments.
713  * @param array $assignmentidparams sql params used to filter open assignments.
714  * @param stdClass $assignment current assignment
715  *
716  * @return bool|string html to display , false if nothing needs to be displayed.
717  * @throws coding_exception
718  */
719 function assign_get_mysubmission_details_for_print_overview(&$mysubmissions, $sqlassignmentids, $assignmentidparams,
720                                                             $assignment) {
721     global $USER, $DB;
723     debugging('The function assign_get_mysubmission_details_for_print_overview() is now deprecated.', DEBUG_DEVELOPER);
725     if ($assignment->nosubmissions) {
726         // Offline assignment. No need to display alerts for offline assignments.
727         return false;
728     }
730     $strnotsubmittedyet = get_string('notsubmittedyet', 'assign');
732     if (!isset($mysubmissions)) {
734         // Get all user submissions, indexed by assignment id.
735         $dbparams = array_merge(array($USER->id), $assignmentidparams, array($USER->id));
736         $mysubmissions = $DB->get_records_sql('SELECT a.id AS assignment,
737                                                       a.nosubmissions AS nosubmissions,
738                                                       g.timemodified AS timemarked,
739                                                       g.grader AS grader,
740                                                       g.grade AS grade,
741                                                       s.status AS status
742                                                  FROM {assign} a, {assign_submission} s
743                                             LEFT JOIN {assign_grades} g ON
744                                                       g.assignment = s.assignment AND
745                                                       g.userid = ? AND
746                                                       g.attemptnumber = s.attemptnumber
747                                                 WHERE a.id ' . $sqlassignmentids . ' AND
748                                                       s.latest = 1 AND
749                                                       s.assignment = a.id AND
750                                                       s.userid = ?', $dbparams);
751     }
753     $submitdetails = '';
754     $submitdetails .= '<div class="details">';
755     $submitdetails .= get_string('mysubmission', 'assign');
756     $submission = false;
758     if (isset($mysubmissions[$assignment->id])) {
759         $submission = $mysubmissions[$assignment->id];
760     }
762     if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
763         // A valid submission already exists, no need to notify students about this.
764         return false;
765     }
767     // We need to show details only if a valid submission doesn't exist.
768     if (!$submission ||
769         !$submission->status ||
770         $submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT ||
771         $submission->status == ASSIGN_SUBMISSION_STATUS_NEW
772     ) {
773         $submitdetails .= $strnotsubmittedyet;
774     } else {
775         $submitdetails .= get_string('submissionstatus_' . $submission->status, 'assign');
776     }
777     if ($assignment->markingworkflow) {
778         $workflowstate = $DB->get_field('assign_user_flags', 'workflowstate', array('assignment' =>
779                 $assignment->id, 'userid' => $USER->id));
780         if ($workflowstate) {
781             $gradingstatus = 'markingworkflowstate' . $workflowstate;
782         } else {
783             $gradingstatus = 'markingworkflowstate' . ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
784         }
785     } else if (!empty($submission->grade) && $submission->grade !== null && $submission->grade >= 0) {
786         $gradingstatus = ASSIGN_GRADING_STATUS_GRADED;
787     } else {
788         $gradingstatus = ASSIGN_GRADING_STATUS_NOT_GRADED;
789     }
790     $submitdetails .= ', ' . get_string($gradingstatus, 'assign');
791     $submitdetails .= '</div>';
792     return $submitdetails;
795 /**
796  * This api generates html to be displayed to teachers in print overview section, related to the grading status of the given
797  * assignment's submissions.
798  *
799  * @deprecated since 3.3
800  * @todo The final deprecation of this function will take place in Moodle 3.7 - see MDL-57487.
801  * @param array $unmarkedsubmissions list of submissions of that are currently unmarked indexed by assignment id.
802  * @param string $sqlassignmentids sql clause used to filter open assignments.
803  * @param array $assignmentidparams sql params used to filter open assignments.
804  * @param stdClass $assignment current assignment
805  * @param context $context context of the assignment.
806  *
807  * @return bool|string html to display , false if nothing needs to be displayed.
808  * @throws coding_exception
809  */
810 function assign_get_grade_details_for_print_overview(&$unmarkedsubmissions, $sqlassignmentids, $assignmentidparams,
811                                                      $assignment, $context) {
812     global $DB;
814     debugging('The function assign_get_grade_details_for_print_overview() is now deprecated.', DEBUG_DEVELOPER);
816     if (!isset($unmarkedsubmissions)) {
817         // Build up and array of unmarked submissions indexed by assignment id/ userid
818         // for use where the user has grading rights on assignment.
819         $dbparams = array_merge(array(ASSIGN_SUBMISSION_STATUS_SUBMITTED), $assignmentidparams);
820         $rs = $DB->get_recordset_sql('SELECT s.assignment as assignment,
821                                              s.userid as userid,
822                                              s.id as id,
823                                              s.status as status,
824                                              g.timemodified as timegraded
825                                         FROM {assign_submission} s
826                                    LEFT JOIN {assign_grades} g ON
827                                              s.userid = g.userid AND
828                                              s.assignment = g.assignment AND
829                                              g.attemptnumber = s.attemptnumber
830                                    LEFT JOIN {assign} a ON
831                                              a.id = s.assignment
832                                        WHERE
833                                              ( g.timemodified is NULL OR
834                                              s.timemodified >= g.timemodified OR
835                                              g.grade IS NULL OR
836                                              (g.grade = -1 AND
837                                              a.grade < 0)) AND
838                                              s.timemodified IS NOT NULL AND
839                                              s.status = ? AND
840                                              s.latest = 1 AND
841                                              s.assignment ' . $sqlassignmentids, $dbparams);
843         $unmarkedsubmissions = array();
844         foreach ($rs as $rd) {
845             $unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
846         }
847         $rs->close();
848     }
850     // Count how many people can submit.
851     $submissions = 0;
852     if ($students = get_enrolled_users($context, 'mod/assign:view', 0, 'u.id')) {
853         foreach ($students as $student) {
854             if (isset($unmarkedsubmissions[$assignment->id][$student->id])) {
855                 $submissions++;
856             }
857         }
858     }
860     if ($submissions) {
861         $urlparams = array('id' => $assignment->coursemodule, 'action' => 'grading');
862         $url = new moodle_url('/mod/assign/view.php', $urlparams);
863         $gradedetails = '<div class="details">' .
864                 '<a href="' . $url . '">' .
865                 get_string('submissionsnotgraded', 'assign', $submissions) .
866                 '</a></div>';
867         return $gradedetails;
868     } else {
869         return false;
870     }
874 /**
875  * Print recent activity from all assignments in a given course
876  *
877  * This is used by the recent activity block
878  * @param mixed $course the course to print activity for
879  * @param bool $viewfullnames boolean to determine whether to show full names or not
880  * @param int $timestart the time the rendering started
881  * @return bool true if activity was printed, false otherwise.
882  */
883 function assign_print_recent_activity($course, $viewfullnames, $timestart) {
884     global $CFG, $USER, $DB, $OUTPUT;
885     require_once($CFG->dirroot . '/mod/assign/locallib.php');
887     // Do not use log table if possible, it may be huge.
889     $dbparams = array($timestart, $course->id, 'assign', ASSIGN_SUBMISSION_STATUS_SUBMITTED);
890     $namefields = user_picture::fields('u', null, 'userid');
891     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, um.id as recordid,
892                                                      $namefields
893                                                 FROM {assign_submission} asb
894                                                      JOIN {assign} a      ON a.id = asb.assignment
895                                                      JOIN {course_modules} cm ON cm.instance = a.id
896                                                      JOIN {modules} md        ON md.id = cm.module
897                                                      JOIN {user} u            ON u.id = asb.userid
898                                                 LEFT JOIN {assign_user_mapping} um ON um.userid = u.id AND um.assignment = a.id
899                                                WHERE asb.timemodified > ? AND
900                                                      asb.latest = 1 AND
901                                                      a.course = ? AND
902                                                      md.name = ? AND
903                                                      asb.status = ?
904                                             ORDER BY asb.timemodified ASC", $dbparams)) {
905          return false;
906     }
908     $modinfo = get_fast_modinfo($course);
909     $show    = array();
910     $grader  = array();
912     $showrecentsubmissions = get_config('assign', 'showrecentsubmissions');
914     foreach ($submissions as $submission) {
915         if (!array_key_exists($submission->cmid, $modinfo->get_cms())) {
916             continue;
917         }
918         $cm = $modinfo->get_cm($submission->cmid);
919         if (!$cm->uservisible) {
920             continue;
921         }
922         if ($submission->userid == $USER->id) {
923             $show[] = $submission;
924             continue;
925         }
927         $context = context_module::instance($submission->cmid);
928         // The act of submitting of assignment may be considered private -
929         // only graders will see it if specified.
930         if (empty($showrecentsubmissions)) {
931             if (!array_key_exists($cm->id, $grader)) {
932                 $grader[$cm->id] = has_capability('moodle/grade:viewall', $context);
933             }
934             if (!$grader[$cm->id]) {
935                 continue;
936             }
937         }
939         $groupmode = groups_get_activity_groupmode($cm, $course);
941         if ($groupmode == SEPARATEGROUPS &&
942                 !has_capability('moodle/site:accessallgroups',  $context)) {
943             if (isguestuser()) {
944                 // Shortcut - guest user does not belong into any group.
945                 continue;
946             }
948             // This will be slow - show only users that share group with me in this cm.
949             if (!$modinfo->get_groups($cm->groupingid)) {
950                 continue;
951             }
952             $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
953             if (is_array($usersgroups)) {
954                 $usersgroups = array_keys($usersgroups);
955                 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
956                 if (empty($intersect)) {
957                     continue;
958                 }
959             }
960         }
961         $show[] = $submission;
962     }
964     if (empty($show)) {
965         return false;
966     }
968     echo $OUTPUT->heading(get_string('newsubmissions', 'assign').':', 3);
970     foreach ($show as $submission) {
971         $cm = $modinfo->get_cm($submission->cmid);
972         $context = context_module::instance($submission->cmid);
973         $assign = new assign($context, $cm, $cm->course);
974         $link = $CFG->wwwroot.'/mod/assign/view.php?id='.$cm->id;
975         // Obscure first and last name if blind marking enabled.
976         if ($assign->is_blind_marking()) {
977             $submission->firstname = get_string('participant', 'mod_assign');
978             if (empty($submission->recordid)) {
979                 $submission->recordid = $assign->get_uniqueid_for_user($submission->userid);
980             }
981             $submission->lastname = $submission->recordid;
982         }
983         print_recent_activity_note($submission->timemodified,
984                                    $submission,
985                                    $cm->name,
986                                    $link,
987                                    false,
988                                    $viewfullnames);
989     }
991     return true;
994 /**
995  * Returns all assignments since a given time.
996  *
997  * @param array $activities The activity information is returned in this array
998  * @param int $index The current index in the activities array
999  * @param int $timestart The earliest activity to show
1000  * @param int $courseid Limit the search to this course
1001  * @param int $cmid The course module id
1002  * @param int $userid Optional user id
1003  * @param int $groupid Optional group id
1004  * @return void
1005  */
1006 function assign_get_recent_mod_activity(&$activities,
1007                                         &$index,
1008                                         $timestart,
1009                                         $courseid,
1010                                         $cmid,
1011                                         $userid=0,
1012                                         $groupid=0) {
1013     global $CFG, $COURSE, $USER, $DB;
1015     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1017     if ($COURSE->id == $courseid) {
1018         $course = $COURSE;
1019     } else {
1020         $course = $DB->get_record('course', array('id'=>$courseid));
1021     }
1023     $modinfo = get_fast_modinfo($course);
1025     $cm = $modinfo->get_cm($cmid);
1026     $params = array();
1027     if ($userid) {
1028         $userselect = 'AND u.id = :userid';
1029         $params['userid'] = $userid;
1030     } else {
1031         $userselect = '';
1032     }
1034     if ($groupid) {
1035         $groupselect = 'AND gm.groupid = :groupid';
1036         $groupjoin   = 'JOIN {groups_members} gm ON  gm.userid=u.id';
1037         $params['groupid'] = $groupid;
1038     } else {
1039         $groupselect = '';
1040         $groupjoin   = '';
1041     }
1043     $params['cminstance'] = $cm->instance;
1044     $params['timestart'] = $timestart;
1045     $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1047     $userfields = user_picture::fields('u', null, 'userid');
1049     if (!$submissions = $DB->get_records_sql('SELECT asb.id, asb.timemodified, ' .
1050                                                      $userfields .
1051                                              '  FROM {assign_submission} asb
1052                                                 JOIN {assign} a ON a.id = asb.assignment
1053                                                 JOIN {user} u ON u.id = asb.userid ' .
1054                                           $groupjoin .
1055                                             '  WHERE asb.timemodified > :timestart AND
1056                                                      asb.status = :submitted AND
1057                                                      a.id = :cminstance
1058                                                      ' . $userselect . ' ' . $groupselect .
1059                                             ' ORDER BY asb.timemodified ASC', $params)) {
1060          return;
1061     }
1063     $groupmode       = groups_get_activity_groupmode($cm, $course);
1064     $cmcontext      = context_module::instance($cm->id);
1065     $grader          = has_capability('moodle/grade:viewall', $cmcontext);
1066     $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext);
1067     $viewfullnames   = has_capability('moodle/site:viewfullnames', $cmcontext);
1070     $showrecentsubmissions = get_config('assign', 'showrecentsubmissions');
1071     $show = array();
1072     foreach ($submissions as $submission) {
1073         if ($submission->userid == $USER->id) {
1074             $show[] = $submission;
1075             continue;
1076         }
1077         // The act of submitting of assignment may be considered private -
1078         // only graders will see it if specified.
1079         if (empty($showrecentsubmissions)) {
1080             if (!$grader) {
1081                 continue;
1082             }
1083         }
1085         if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
1086             if (isguestuser()) {
1087                 // Shortcut - guest user does not belong into any group.
1088                 continue;
1089             }
1091             // This will be slow - show only users that share group with me in this cm.
1092             if (!$modinfo->get_groups($cm->groupingid)) {
1093                 continue;
1094             }
1095             $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
1096             if (is_array($usersgroups)) {
1097                 $usersgroups = array_keys($usersgroups);
1098                 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
1099                 if (empty($intersect)) {
1100                     continue;
1101                 }
1102             }
1103         }
1104         $show[] = $submission;
1105     }
1107     if (empty($show)) {
1108         return;
1109     }
1111     if ($grader) {
1112         require_once($CFG->libdir.'/gradelib.php');
1113         $userids = array();
1114         foreach ($show as $id => $submission) {
1115             $userids[] = $submission->userid;
1116         }
1117         $grades = grade_get_grades($courseid, 'mod', 'assign', $cm->instance, $userids);
1118     }
1120     $aname = format_string($cm->name, true);
1121     foreach ($show as $submission) {
1122         $activity = new stdClass();
1124         $activity->type         = 'assign';
1125         $activity->cmid         = $cm->id;
1126         $activity->name         = $aname;
1127         $activity->sectionnum   = $cm->sectionnum;
1128         $activity->timestamp    = $submission->timemodified;
1129         $activity->user         = new stdClass();
1130         if ($grader) {
1131             $activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
1132         }
1134         $userfields = explode(',', user_picture::fields());
1135         foreach ($userfields as $userfield) {
1136             if ($userfield == 'id') {
1137                 // Aliased in SQL above.
1138                 $activity->user->{$userfield} = $submission->userid;
1139             } else {
1140                 $activity->user->{$userfield} = $submission->{$userfield};
1141             }
1142         }
1143         $activity->user->fullname = fullname($submission, $viewfullnames);
1145         $activities[$index++] = $activity;
1146     }
1148     return;
1151 /**
1152  * Print recent activity from all assignments in a given course
1153  *
1154  * This is used by course/recent.php
1155  * @param stdClass $activity
1156  * @param int $courseid
1157  * @param bool $detail
1158  * @param array $modnames
1159  */
1160 function assign_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
1161     global $CFG, $OUTPUT;
1163     echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
1165     echo '<tr><td class="userpicture" valign="top">';
1166     echo $OUTPUT->user_picture($activity->user);
1167     echo '</td><td>';
1169     if ($detail) {
1170         $modname = $modnames[$activity->type];
1171         echo '<div class="title">';
1172         echo $OUTPUT->image_icon('icon', $modname, 'assign');
1173         echo '<a href="' . $CFG->wwwroot . '/mod/assign/view.php?id=' . $activity->cmid . '">';
1174         echo $activity->name;
1175         echo '</a>';
1176         echo '</div>';
1177     }
1179     if (isset($activity->grade)) {
1180         echo '<div class="grade">';
1181         echo get_string('grade').': ';
1182         echo $activity->grade;
1183         echo '</div>';
1184     }
1186     echo '<div class="user">';
1187     echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">";
1188     echo "{$activity->user->fullname}</a>  - " . userdate($activity->timestamp);
1189     echo '</div>';
1191     echo '</td></tr></table>';
1194 /**
1195  * Checks if a scale is being used by an assignment.
1196  *
1197  * This is used by the backup code to decide whether to back up a scale
1198  * @param int $assignmentid
1199  * @param int $scaleid
1200  * @return boolean True if the scale is used by the assignment
1201  */
1202 function assign_scale_used($assignmentid, $scaleid) {
1203     global $DB;
1205     $return = false;
1206     $rec = $DB->get_record('assign', array('id'=>$assignmentid, 'grade'=>-$scaleid));
1208     if (!empty($rec) && !empty($scaleid)) {
1209         $return = true;
1210     }
1212     return $return;
1215 /**
1216  * Checks if scale is being used by any instance of assignment
1217  *
1218  * This is used to find out if scale used anywhere
1219  * @param int $scaleid
1220  * @return boolean True if the scale is used by any assignment
1221  */
1222 function assign_scale_used_anywhere($scaleid) {
1223     global $DB;
1225     if ($scaleid and $DB->record_exists('assign', array('grade'=>-$scaleid))) {
1226         return true;
1227     } else {
1228         return false;
1229     }
1232 /**
1233  * List the actions that correspond to a view of this module.
1234  * This is used by the participation report.
1235  *
1236  * Note: This is not used by new logging system. Event with
1237  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
1238  *       be considered as view action.
1239  *
1240  * @return array
1241  */
1242 function assign_get_view_actions() {
1243     return array('view submission', 'view feedback');
1246 /**
1247  * List the actions that correspond to a post of this module.
1248  * This is used by the participation report.
1249  *
1250  * Note: This is not used by new logging system. Event with
1251  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
1252  *       will be considered as post action.
1253  *
1254  * @return array
1255  */
1256 function assign_get_post_actions() {
1257     return array('upload', 'submit', 'submit for grading');
1260 /**
1261  * Returns all other capabilities used by this module.
1262  * @return array Array of capability strings
1263  */
1264 function assign_get_extra_capabilities() {
1265     return ['gradereport/grader:view', 'moodle/grade:viewall'];
1268 /**
1269  * Create grade item for given assignment.
1270  *
1271  * @param stdClass $assign record with extra cmidnumber
1272  * @param array $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1273  * @return int 0 if ok, error code otherwise
1274  */
1275 function assign_grade_item_update($assign, $grades=null) {
1276     global $CFG;
1277     require_once($CFG->libdir.'/gradelib.php');
1279     if (!isset($assign->courseid)) {
1280         $assign->courseid = $assign->course;
1281     }
1283     $params = array('itemname'=>$assign->name, 'idnumber'=>$assign->cmidnumber);
1285     // Check if feedback plugin for gradebook is enabled, if yes then
1286     // gradetype = GRADE_TYPE_TEXT else GRADE_TYPE_NONE.
1287     $gradefeedbackenabled = false;
1289     if (isset($assign->gradefeedbackenabled)) {
1290         $gradefeedbackenabled = $assign->gradefeedbackenabled;
1291     } else if ($assign->grade == 0) { // Grade feedback is needed only when grade == 0.
1292         require_once($CFG->dirroot . '/mod/assign/locallib.php');
1293         $mod = get_coursemodule_from_instance('assign', $assign->id, $assign->courseid);
1294         $cm = context_module::instance($mod->id);
1295         $assignment = new assign($cm, null, null);
1296         $gradefeedbackenabled = $assignment->is_gradebook_feedback_enabled();
1297     }
1299     if ($assign->grade > 0) {
1300         $params['gradetype'] = GRADE_TYPE_VALUE;
1301         $params['grademax']  = $assign->grade;
1302         $params['grademin']  = 0;
1304     } else if ($assign->grade < 0) {
1305         $params['gradetype'] = GRADE_TYPE_SCALE;
1306         $params['scaleid']   = -$assign->grade;
1308     } else if ($gradefeedbackenabled) {
1309         // $assign->grade == 0 and feedback enabled.
1310         $params['gradetype'] = GRADE_TYPE_TEXT;
1311     } else {
1312         // $assign->grade == 0 and no feedback enabled.
1313         $params['gradetype'] = GRADE_TYPE_NONE;
1314     }
1316     if ($grades  === 'reset') {
1317         $params['reset'] = true;
1318         $grades = null;
1319     }
1321     return grade_update('mod/assign',
1322                         $assign->courseid,
1323                         'mod',
1324                         'assign',
1325                         $assign->id,
1326                         0,
1327                         $grades,
1328                         $params);
1331 /**
1332  * Return grade for given user or all users.
1333  *
1334  * @param stdClass $assign record of assign with an additional cmidnumber
1335  * @param int $userid optional user id, 0 means all users
1336  * @return array array of grades, false if none
1337  */
1338 function assign_get_user_grades($assign, $userid=0) {
1339     global $CFG;
1341     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1343     $cm = get_coursemodule_from_instance('assign', $assign->id, 0, false, MUST_EXIST);
1344     $context = context_module::instance($cm->id);
1345     $assignment = new assign($context, null, null);
1346     $assignment->set_instance($assign);
1347     return $assignment->get_user_grades_for_gradebook($userid);
1350 /**
1351  * Update activity grades.
1352  *
1353  * @param stdClass $assign database record
1354  * @param int $userid specific user only, 0 means all
1355  * @param bool $nullifnone - not used
1356  */
1357 function assign_update_grades($assign, $userid=0, $nullifnone=true) {
1358     global $CFG;
1359     require_once($CFG->libdir.'/gradelib.php');
1361     if ($assign->grade == 0) {
1362         assign_grade_item_update($assign);
1364     } else if ($grades = assign_get_user_grades($assign, $userid)) {
1365         foreach ($grades as $k => $v) {
1366             if ($v->rawgrade == -1) {
1367                 $grades[$k]->rawgrade = null;
1368             }
1369         }
1370         assign_grade_item_update($assign, $grades);
1372     } else {
1373         assign_grade_item_update($assign);
1374     }
1377 /**
1378  * List the file areas that can be browsed.
1379  *
1380  * @param stdClass $course
1381  * @param stdClass $cm
1382  * @param stdClass $context
1383  * @return array
1384  */
1385 function assign_get_file_areas($course, $cm, $context) {
1386     global $CFG;
1387     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1389     $areas = array(ASSIGN_INTROATTACHMENT_FILEAREA => get_string('introattachments', 'mod_assign'));
1391     $assignment = new assign($context, $cm, $course);
1392     foreach ($assignment->get_submission_plugins() as $plugin) {
1393         if ($plugin->is_visible()) {
1394             $pluginareas = $plugin->get_file_areas();
1396             if ($pluginareas) {
1397                 $areas = array_merge($areas, $pluginareas);
1398             }
1399         }
1400     }
1401     foreach ($assignment->get_feedback_plugins() as $plugin) {
1402         if ($plugin->is_visible()) {
1403             $pluginareas = $plugin->get_file_areas();
1405             if ($pluginareas) {
1406                 $areas = array_merge($areas, $pluginareas);
1407             }
1408         }
1409     }
1411     return $areas;
1414 /**
1415  * File browsing support for assign module.
1416  *
1417  * @param file_browser $browser
1418  * @param object $areas
1419  * @param object $course
1420  * @param object $cm
1421  * @param object $context
1422  * @param string $filearea
1423  * @param int $itemid
1424  * @param string $filepath
1425  * @param string $filename
1426  * @return object file_info instance or null if not found
1427  */
1428 function assign_get_file_info($browser,
1429                               $areas,
1430                               $course,
1431                               $cm,
1432                               $context,
1433                               $filearea,
1434                               $itemid,
1435                               $filepath,
1436                               $filename) {
1437     global $CFG;
1438     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1440     if ($context->contextlevel != CONTEXT_MODULE) {
1441         return null;
1442     }
1444     $urlbase = $CFG->wwwroot.'/pluginfile.php';
1445     $fs = get_file_storage();
1446     $filepath = is_null($filepath) ? '/' : $filepath;
1447     $filename = is_null($filename) ? '.' : $filename;
1449     // Need to find where this belongs to.
1450     $assignment = new assign($context, $cm, $course);
1451     if ($filearea === ASSIGN_INTROATTACHMENT_FILEAREA) {
1452         if (!has_capability('moodle/course:managefiles', $context)) {
1453             // Students can not peak here!
1454             return null;
1455         }
1456         if (!($storedfile = $fs->get_file($assignment->get_context()->id,
1457                                           'mod_assign', $filearea, 0, $filepath, $filename))) {
1458             return null;
1459         }
1460         return new file_info_stored($browser,
1461                         $assignment->get_context(),
1462                         $storedfile,
1463                         $urlbase,
1464                         $filearea,
1465                         $itemid,
1466                         true,
1467                         true,
1468                         false);
1469     }
1471     $pluginowner = null;
1472     foreach ($assignment->get_submission_plugins() as $plugin) {
1473         if ($plugin->is_visible()) {
1474             $pluginareas = $plugin->get_file_areas();
1476             if (array_key_exists($filearea, $pluginareas)) {
1477                 $pluginowner = $plugin;
1478                 break;
1479             }
1480         }
1481     }
1482     if (!$pluginowner) {
1483         foreach ($assignment->get_feedback_plugins() as $plugin) {
1484             if ($plugin->is_visible()) {
1485                 $pluginareas = $plugin->get_file_areas();
1487                 if (array_key_exists($filearea, $pluginareas)) {
1488                     $pluginowner = $plugin;
1489                     break;
1490                 }
1491             }
1492         }
1493     }
1495     if (!$pluginowner) {
1496         return null;
1497     }
1499     $result = $pluginowner->get_file_info($browser, $filearea, $itemid, $filepath, $filename);
1500     return $result;
1503 /**
1504  * Prints the complete info about a user's interaction with an assignment.
1505  *
1506  * @param stdClass $course
1507  * @param stdClass $user
1508  * @param stdClass $coursemodule
1509  * @param stdClass $assign the database assign record
1510  *
1511  * This prints the submission summary and feedback summary for this student.
1512  */
1513 function assign_user_complete($course, $user, $coursemodule, $assign) {
1514     global $CFG;
1515     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1517     $context = context_module::instance($coursemodule->id);
1519     $assignment = new assign($context, $coursemodule, $course);
1521     echo $assignment->view_student_summary($user, false);
1524 /**
1525  * Rescale all grades for this activity and push the new grades to the gradebook.
1526  *
1527  * @param stdClass $course Course db record
1528  * @param stdClass $cm Course module db record
1529  * @param float $oldmin
1530  * @param float $oldmax
1531  * @param float $newmin
1532  * @param float $newmax
1533  */
1534 function assign_rescale_activity_grades($course, $cm, $oldmin, $oldmax, $newmin, $newmax) {
1535     global $DB;
1537     if ($oldmax <= $oldmin) {
1538         // Grades cannot be scaled.
1539         return false;
1540     }
1541     $scale = ($newmax - $newmin) / ($oldmax - $oldmin);
1542     if (($newmax - $newmin) <= 1) {
1543         // We would lose too much precision, lets bail.
1544         return false;
1545     }
1547     $params = array(
1548         'p1' => $oldmin,
1549         'p2' => $scale,
1550         'p3' => $newmin,
1551         'a' => $cm->instance
1552     );
1554     // Only rescale grades that are greater than or equal to 0. Anything else is a special value.
1555     $sql = 'UPDATE {assign_grades} set grade = (((grade - :p1) * :p2) + :p3) where assignment = :a and grade >= 0';
1556     $dbupdate = $DB->execute($sql, $params);
1557     if (!$dbupdate) {
1558         return false;
1559     }
1561     // Now re-push all grades to the gradebook.
1562     $dbparams = array('id' => $cm->instance);
1563     $assign = $DB->get_record('assign', $dbparams);
1564     $assign->cmidnumber = $cm->idnumber;
1566     assign_update_grades($assign);
1568     return true;
1571 /**
1572  * Print the grade information for the assignment for this user.
1573  *
1574  * @param stdClass $course
1575  * @param stdClass $user
1576  * @param stdClass $coursemodule
1577  * @param stdClass $assignment
1578  */
1579 function assign_user_outline($course, $user, $coursemodule, $assignment) {
1580     global $CFG;
1581     require_once($CFG->libdir.'/gradelib.php');
1582     require_once($CFG->dirroot.'/grade/grading/lib.php');
1584     $gradinginfo = grade_get_grades($course->id,
1585                                         'mod',
1586                                         'assign',
1587                                         $assignment->id,
1588                                         $user->id);
1590     $gradingitem = $gradinginfo->items[0];
1591     $gradebookgrade = $gradingitem->grades[$user->id];
1593     if (empty($gradebookgrade->str_long_grade)) {
1594         return null;
1595     }
1596     $result = new stdClass();
1597     if (!$gradingitem->hidden || has_capability('moodle/grade:viewhidden', context_course::instance($course->id))) {
1598         $result->info = get_string('outlinegrade', 'assign', $gradebookgrade->str_long_grade);
1599     } else {
1600         $result->info = get_string('grade') . ': ' . get_string('hidden', 'grades');
1601     }
1602     $result->time = $gradebookgrade->dategraded;
1604     return $result;
1607 /**
1608  * Obtains the automatic completion state for this module based on any conditions
1609  * in assign settings.
1610  *
1611  * @param object $course Course
1612  * @param object $cm Course-module
1613  * @param int $userid User ID
1614  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
1615  * @return bool True if completed, false if not, $type if conditions not set.
1616  */
1617 function assign_get_completion_state($course, $cm, $userid, $type) {
1618     global $CFG, $DB;
1619     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1621     $assign = new assign(null, $cm, $course);
1623     // If completion option is enabled, evaluate it and return true/false.
1624     if ($assign->get_instance()->completionsubmit) {
1625         if ($assign->get_instance()->teamsubmission) {
1626             $submission = $assign->get_group_submission($userid, 0, false);
1627         } else {
1628             $submission = $assign->get_user_submission($userid, false);
1629         }
1630         return $submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1631     } else {
1632         // Completion option is not enabled so just return $type.
1633         return $type;
1634     }
1637 /**
1638  * Serves intro attachment files.
1639  *
1640  * @param mixed $course course or id of the course
1641  * @param mixed $cm course module or id of the course module
1642  * @param context $context
1643  * @param string $filearea
1644  * @param array $args
1645  * @param bool $forcedownload
1646  * @param array $options additional options affecting the file serving
1647  * @return bool false if file not found, does not return if found - just send the file
1648  */
1649 function assign_pluginfile($course,
1650                 $cm,
1651                 context $context,
1652                 $filearea,
1653                 $args,
1654                 $forcedownload,
1655                 array $options=array()) {
1656     global $CFG;
1658     if ($context->contextlevel != CONTEXT_MODULE) {
1659         return false;
1660     }
1662     require_login($course, false, $cm);
1663     if (!has_capability('mod/assign:view', $context)) {
1664         return false;
1665     }
1667     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1668     $assign = new assign($context, $cm, $course);
1670     if ($filearea !== ASSIGN_INTROATTACHMENT_FILEAREA) {
1671         return false;
1672     }
1673     if (!$assign->show_intro()) {
1674         return false;
1675     }
1677     $itemid = (int)array_shift($args);
1678     if ($itemid != 0) {
1679         return false;
1680     }
1682     $relativepath = implode('/', $args);
1684     $fullpath = "/{$context->id}/mod_assign/$filearea/$itemid/$relativepath";
1686     $fs = get_file_storage();
1687     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1688         return false;
1689     }
1690     send_stored_file($file, 0, 0, $forcedownload, $options);
1693 /**
1694  * Serve the grading panel as a fragment.
1695  *
1696  * @param array $args List of named arguments for the fragment loader.
1697  * @return string
1698  */
1699 function mod_assign_output_fragment_gradingpanel($args) {
1700     global $CFG;
1702     $context = $args['context'];
1704     if ($context->contextlevel != CONTEXT_MODULE) {
1705         return null;
1706     }
1707     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1708     $assign = new assign($context, null, null);
1710     $userid = clean_param($args['userid'], PARAM_INT);
1711     $attemptnumber = clean_param($args['attemptnumber'], PARAM_INT);
1712     $formdata = array();
1713     if (!empty($args['jsonformdata'])) {
1714         $serialiseddata = json_decode($args['jsonformdata']);
1715         parse_str($serialiseddata, $formdata);
1716     }
1717     $viewargs = array(
1718         'userid' => $userid,
1719         'attemptnumber' => $attemptnumber,
1720         'formdata' => $formdata
1721     );
1723     return $assign->view('gradingpanel', $viewargs);
1726 /**
1727  * Check if the module has any update that affects the current user since a given time.
1728  *
1729  * @param  cm_info $cm course module data
1730  * @param  int $from the time to check updates from
1731  * @param  array $filter  if we need to check only specific updates
1732  * @return stdClass an object with the different type of areas indicating if they were updated or not
1733  * @since Moodle 3.2
1734  */
1735 function assign_check_updates_since(cm_info $cm, $from, $filter = array()) {
1736     global $DB, $USER, $CFG;
1737     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1739     $updates = new stdClass();
1740     $updates = course_check_module_updates_since($cm, $from, array(ASSIGN_INTROATTACHMENT_FILEAREA), $filter);
1742     // Check if there is a new submission by the user or new grades.
1743     $select = 'assignment = :id AND userid = :userid AND (timecreated > :since1 OR timemodified > :since2)';
1744     $params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from);
1745     $updates->submissions = (object) array('updated' => false);
1746     $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id');
1747     if (!empty($submissions)) {
1748         $updates->submissions->updated = true;
1749         $updates->submissions->itemids = array_keys($submissions);
1750     }
1752     $updates->grades = (object) array('updated' => false);
1753     $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id');
1754     if (!empty($grades)) {
1755         $updates->grades->updated = true;
1756         $updates->grades->itemids = array_keys($grades);
1757     }
1759     // Now, teachers should see other students updates.
1760     if (has_capability('mod/assign:viewgrades', $cm->context)) {
1761         $params = array('id' => $cm->instance, 'since1' => $from, 'since2' => $from);
1762         $select = 'assignment = :id AND (timecreated > :since1 OR timemodified > :since2)';
1764         if (groups_get_activity_groupmode($cm) == SEPARATEGROUPS) {
1765             $groupusers = array_keys(groups_get_activity_shared_group_members($cm));
1766             if (empty($groupusers)) {
1767                 return $updates;
1768             }
1769             list($insql, $inparams) = $DB->get_in_or_equal($groupusers, SQL_PARAMS_NAMED);
1770             $select .= ' AND userid ' . $insql;
1771             $params = array_merge($params, $inparams);
1772         }
1774         $updates->usersubmissions = (object) array('updated' => false);
1775         $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id');
1776         if (!empty($submissions)) {
1777             $updates->usersubmissions->updated = true;
1778             $updates->usersubmissions->itemids = array_keys($submissions);
1779         }
1781         $updates->usergrades = (object) array('updated' => false);
1782         $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id');
1783         if (!empty($grades)) {
1784             $updates->usergrades->updated = true;
1785             $updates->usergrades->itemids = array_keys($grades);
1786         }
1787     }
1789     return $updates;
1792 /**
1793  * Is the event visible?
1794  *
1795  * This is used to determine global visibility of an event in all places throughout Moodle. For example,
1796  * the ASSIGN_EVENT_TYPE_GRADINGDUE event will not be shown to students on their calendar.
1797  *
1798  * @param calendar_event $event
1799  * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1800  * @return bool Returns true if the event is visible to the current user, false otherwise.
1801  */
1802 function mod_assign_core_calendar_is_event_visible(calendar_event $event, $userid = 0) {
1803     global $CFG, $USER;
1805     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1807     if (empty($userid)) {
1808         $userid = $USER->id;
1809     }
1811     $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance];
1812     $context = context_module::instance($cm->id);
1814     $assign = new assign($context, $cm, null);
1816     if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
1817         return $assign->can_grade($userid);
1818     } else {
1819         return true;
1820     }
1823 /**
1824  * This function receives a calendar event and returns the action associated with it, or null if there is none.
1825  *
1826  * This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
1827  * is not displayed on the block.
1828  *
1829  * @param calendar_event $event
1830  * @param \core_calendar\action_factory $factory
1831  * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
1832  * @return \core_calendar\local\event\entities\action_interface|null
1833  */
1834 function mod_assign_core_calendar_provide_event_action(calendar_event $event,
1835                                                        \core_calendar\action_factory $factory,
1836                                                        $userid = 0) {
1838     global $CFG, $USER;
1840     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1842     if (empty($userid)) {
1843         $userid = $USER->id;
1844     }
1846     $cm = get_fast_modinfo($event->courseid, $userid)->instances['assign'][$event->instance];
1847     $context = context_module::instance($cm->id);
1849     $assign = new assign($context, $cm, null);
1851     // Apply overrides.
1852     $assign->update_effective_access($userid);
1854     if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
1855         $name = get_string('grade');
1856         $url = new \moodle_url('/mod/assign/view.php', [
1857             'id' => $cm->id,
1858             'action' => 'grader'
1859         ]);
1860         $itemcount = $assign->count_submissions_need_grading();
1861         $actionable = $assign->can_grade($userid) && (time() >= $assign->get_instance()->allowsubmissionsfromdate);
1862     } else {
1863         $usersubmission = $assign->get_user_submission($userid, false);
1864         if ($usersubmission && $usersubmission->status === ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
1865             // The user has already submitted.
1866             // We do not want to change the text to edit the submission, we want to remove the event from the Dashboard entirely.
1867             return null;
1868         }
1870         $participant = $assign->get_participant($userid);
1872         if (!$participant) {
1873             // If the user is not a participant in the assignment then they have
1874             // no action to take. This will filter out the events for teachers.
1875             return null;
1876         }
1878         // The user has not yet submitted anything. Show the addsubmission link.
1879         $name = get_string('addsubmission', 'assign');
1880         $url = new \moodle_url('/mod/assign/view.php', [
1881             'id' => $cm->id,
1882             'action' => 'editsubmission'
1883         ]);
1884         $itemcount = 1;
1885         $actionable = $assign->is_any_submission_plugin_enabled() && $assign->can_edit_submission($userid, $userid);
1886     }
1888     return $factory->create_instance(
1889         $name,
1890         $url,
1891         $itemcount,
1892         $actionable
1893     );
1896 /**
1897  * Callback function that determines whether an action event should be showing its item count
1898  * based on the event type and the item count.
1899  *
1900  * @param calendar_event $event The calendar event.
1901  * @param int $itemcount The item count associated with the action event.
1902  * @return bool
1903  */
1904 function mod_assign_core_calendar_event_action_shows_item_count(calendar_event $event, $itemcount = 0) {
1905     // List of event types where the action event's item count should be shown.
1906     $eventtypesshowingitemcount = [
1907         ASSIGN_EVENT_TYPE_GRADINGDUE
1908     ];
1909     // For mod_assign, item count should be shown if the event type is 'gradingdue' and there is one or more item count.
1910     return in_array($event->eventtype, $eventtypesshowingitemcount) && $itemcount > 0;
1913 /**
1914  * This function calculates the minimum and maximum cutoff values for the timestart of
1915  * the given event.
1916  *
1917  * It will return an array with two values, the first being the minimum cutoff value and
1918  * the second being the maximum cutoff value. Either or both values can be null, which
1919  * indicates there is no minimum or maximum, respectively.
1920  *
1921  * If a cutoff is required then the function must return an array containing the cutoff
1922  * timestamp and error string to display to the user if the cutoff value is violated.
1923  *
1924  * A minimum and maximum cutoff return value will look like:
1925  * [
1926  *     [1505704373, 'The due date must be after the sbumission start date'],
1927  *     [1506741172, 'The due date must be before the cutoff date']
1928  * ]
1929  *
1930  * If the event does not have a valid timestart range then [false, false] will
1931  * be returned.
1932  *
1933  * @param calendar_event $event The calendar event to get the time range for
1934  * @param stdClass $instance The module instance to get the range from
1935  * @return array
1936  */
1937 function mod_assign_core_calendar_get_valid_event_timestart_range(\calendar_event $event, \stdClass $instance) {
1938     global $CFG;
1940     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1942     $courseid = $event->courseid;
1943     $modulename = $event->modulename;
1944     $instanceid = $event->instance;
1945     $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1946     $context = context_module::instance($coursemodule->id);
1947     $assign = new assign($context, null, null);
1948     $assign->set_instance($instance);
1950     return $assign->get_valid_calendar_event_timestart_range($event);
1953 /**
1954  * This function will update the assign module according to the
1955  * event that has been modified.
1956  *
1957  * @throws \moodle_exception
1958  * @param \calendar_event $event
1959  * @param stdClass $instance The module instance to get the range from
1960  */
1961 function mod_assign_core_calendar_event_timestart_updated(\calendar_event $event, \stdClass $instance) {
1962     global $CFG, $DB;
1964     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1966     if (empty($event->instance) || $event->modulename != 'assign') {
1967         return;
1968     }
1970     if ($instance->id != $event->instance) {
1971         return;
1972     }
1974     if (!in_array($event->eventtype, [ASSIGN_EVENT_TYPE_DUE, ASSIGN_EVENT_TYPE_GRADINGDUE])) {
1975         return;
1976     }
1978     $courseid = $event->courseid;
1979     $modulename = $event->modulename;
1980     $instanceid = $event->instance;
1981     $modified = false;
1982     $coursemodule = get_fast_modinfo($courseid)->instances[$modulename][$instanceid];
1983     $context = context_module::instance($coursemodule->id);
1985     // The user does not have the capability to modify this activity.
1986     if (!has_capability('moodle/course:manageactivities', $context)) {
1987         return;
1988     }
1990     $assign = new assign($context, $coursemodule, null);
1991     $assign->set_instance($instance);
1993     if ($event->eventtype == ASSIGN_EVENT_TYPE_DUE) {
1994         // This check is in here because due date events are currently
1995         // the only events that can be overridden, so we can save a DB
1996         // query if we don't bother checking other events.
1997         if ($assign->is_override_calendar_event($event)) {
1998             // This is an override event so we should ignore it.
1999             return;
2000         }
2002         $newduedate = $event->timestart;
2004         if ($newduedate != $instance->duedate) {
2005             $instance->duedate = $newduedate;
2006             $modified = true;
2007         }
2008     } else if ($event->eventtype == ASSIGN_EVENT_TYPE_GRADINGDUE) {
2009         $newduedate = $event->timestart;
2011         if ($newduedate != $instance->gradingduedate) {
2012             $instance->gradingduedate = $newduedate;
2013             $modified = true;
2014         }
2015     }
2017     if ($modified) {
2018         $instance->timemodified = time();
2019         // Persist the assign instance changes.
2020         $DB->update_record('assign', $instance);
2021         $assign->update_calendar($coursemodule->id);
2022         $event = \core\event\course_module_updated::create_from_cm($coursemodule, $context);
2023         $event->trigger();
2024     }
2027 /**
2028  * Return a list of all the user preferences used by mod_assign.
2029  *
2030  * @return array
2031  */
2032 function mod_assign_user_preferences() {
2033     $preferences = array();
2034     $preferences['assign_filter'] = array(
2035         'type' => PARAM_ALPHA,
2036         'null' => NULL_NOT_ALLOWED,
2037         'default' => ''
2038     );
2039     $preferences['assign_workflowfilter'] = array(
2040         'type' => PARAM_ALPHA,
2041         'null' => NULL_NOT_ALLOWED,
2042         'default' => ''
2043     );
2044     $preferences['assign_markerfilter'] = array(
2045         'type' => PARAM_ALPHANUMEXT,
2046         'null' => NULL_NOT_ALLOWED,
2047         'default' => ''
2048     );
2050     return $preferences;