MDL-55956 mod_assign: Remove calendar events with 'open' eventtype
[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  * @return bool
98  */
99 function assign_refresh_events($courseid = 0) {
100     global $CFG, $DB;
101     require_once($CFG->dirroot . '/mod/assign/locallib.php');
103     if ($courseid) {
104         // Make sure that the course id is numeric.
105         if (!is_numeric($courseid)) {
106             return false;
107         }
108         if (!$assigns = $DB->get_records('assign', array('course' => $courseid))) {
109             return false;
110         }
111         // Get course from courseid parameter.
112         if (!$course = $DB->get_record('course', array('id' => $courseid), '*')) {
113             return false;
114         }
115     } else {
116         if (!$assigns = $DB->get_records('assign')) {
117             return false;
118         }
119     }
120     foreach ($assigns as $assign) {
121         // Use assignment's course column if courseid parameter is not given.
122         if (!$courseid) {
123             $courseid = $assign->course;
124             if (!$course = $DB->get_record('course', array('id' => $courseid), '*')) {
125                 continue;
126             }
127         }
128         if (!$cm = get_coursemodule_from_instance('assign', $assign->id, $courseid, false)) {
129             continue;
130         }
131         $context = context_module::instance($cm->id);
132         $assignment = new assign($context, $cm, $course);
133         $assignment->update_calendar($cm->id);
134     }
136     return true;
139 /**
140  * Removes all grades from gradebook
141  *
142  * @param int $courseid The ID of the course to reset
143  * @param string $type Optional type of assignment to limit the reset to a particular assignment type
144  */
145 function assign_reset_gradebook($courseid, $type='') {
146     global $CFG, $DB;
148     $params = array('moduletype'=>'assign', 'courseid'=>$courseid);
149     $sql = 'SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
150             FROM {assign} a, {course_modules} cm, {modules} m
151             WHERE m.name=:moduletype AND m.id=cm.module AND cm.instance=a.id AND a.course=:courseid';
153     if ($assignments = $DB->get_records_sql($sql, $params)) {
154         foreach ($assignments as $assignment) {
155             assign_grade_item_update($assignment, 'reset');
156         }
157     }
160 /**
161  * Implementation of the function for printing the form elements that control
162  * whether the course reset functionality affects the assignment.
163  * @param moodleform $mform form passed by reference
164  */
165 function assign_reset_course_form_definition(&$mform) {
166     $mform->addElement('header', 'assignheader', get_string('modulenameplural', 'assign'));
167     $name = get_string('deleteallsubmissions', 'assign');
168     $mform->addElement('advcheckbox', 'reset_assign_submissions', $name);
169     $mform->addElement('advcheckbox', 'reset_assign_user_overrides',
170         get_string('removealluseroverrides', 'assign'));
171     $mform->addElement('advcheckbox', 'reset_assign_group_overrides',
172         get_string('removeallgroupoverrides', 'assign'));
175 /**
176  * Course reset form defaults.
177  * @param  object $course
178  * @return array
179  */
180 function assign_reset_course_form_defaults($course) {
181     return array('reset_assign_submissions' => 1,
182             'reset_assign_group_overrides' => 1,
183             'reset_assign_user_overrides' => 1);
186 /**
187  * Update an assignment instance
188  *
189  * This is done by calling the update_instance() method of the assignment type class
190  * @param stdClass $data
191  * @param stdClass $form - unused
192  * @return object
193  */
194 function assign_update_instance(stdClass $data, $form) {
195     global $CFG;
196     require_once($CFG->dirroot . '/mod/assign/locallib.php');
197     $context = context_module::instance($data->coursemodule);
198     $assignment = new assign($context, null, null);
199     return $assignment->update_instance($data);
202 /**
203  * This function updates the events associated to the assign.
204  * If $override is non-zero, then it updates only the events
205  * associated with the specified override.
206  *
207  * @uses ASSIGN_MAX_EVENT_LENGTH
208  * @param object $assign the assign object.
209  * @param object $override (optional) limit to a specific override
210  */
211 function assign_update_events($assign, $override = null) {
212     global $CFG, $DB;
214     require_once($CFG->dirroot . '/calendar/lib.php');
216     // Load the old events relating to this assign.
217     $conds = array('modulename' => 'assign',
218         'instance' => $assign->get_context()->id);
219     if (!empty($override)) {
220         // Only load events for this override.
221         if (isset($override->userid)) {
222             $conds['userid'] = $override->userid;
223         } else {
224             $conds['groupid'] = $override->groupid;
225         }
226     }
227     $oldevents = $DB->get_records('event', $conds);
229     // Now make a to-do list of all that needs to be updated.
230     if (empty($override)) {
231         // We are updating the primary settings for the assign, so we need to add all the overrides.
232         $overrides = $DB->get_records('assign_overrides', array('assignid' => $assign->id));
233         // As well as the original assign (empty override).
234         $overrides[] = new stdClass();
235     } else {
236         // Just do the one override.
237         $overrides = array($override);
238     }
240     foreach ($overrides as $current) {
241         $groupid   = isset($current->groupid) ? $current->groupid : 0;
242         $userid    = isset($current->userid) ? $current->userid : 0;
243         $duedate = isset($current->duedate) ? $current->duedate : $assign->get_context()->duedate;
245         // Only add 'due' events for an override if they differ from the assign default.
246         $addclose = empty($current->id) || !empty($current->duedate);
248         if (!empty($assign->coursemodule)) {
249             $cmid = $assign->coursemodule;
250         } else {
251             $cmid = get_coursemodule_from_instance('assign', $assign->get_context()->id, $assign->get_context()->course)->id;
252         }
254         $event = new stdClass();
255         $event->description = format_module_intro('assign', $assign->get_context(), $cmid);
256         // Events module won't show user events when the courseid is nonzero.
257         $event->courseid    = ($userid) ? 0 : $assign->get_context()->course;
258         $event->groupid     = $groupid;
259         $event->userid      = $userid;
260         $event->modulename  = 'assign';
261         $event->instance    = $assign->get_context()->id;
262         $event->timestart   = $duedate;
263         $event->timeduration = 0;
264         $event->visible     = instance_is_visible('assign', $assign);
265         $event->eventtype   = 'due';
267         // Determine the event name and priority.
268         if ($groupid) {
269             // Group override event.
270             $params = new stdClass();
271             $params->assign = $assign->get_context()->name;
272             $params->group = groups_get_group_name($groupid);
273             if ($params->group === false) {
274                 // Group doesn't exist, just skip it.
275                 continue;
276             }
277             $eventname = get_string('overridegroupeventname', 'assign', $params);
278             // Set group override priority.
279             if (isset($current->sortorder)) {
280                 $event->priority = $current->sortorder;
281             }
282         } else if ($userid) {
283             // User override event.
284             $params = new stdClass();
285             $params->assign = $assign->get_context()->name;
286             $eventname = get_string('overrideusereventname', 'assign', $params);
287             // Set user override priority.
288             $event->priority = CALENDAR_EVENT_USER_OVERRIDE_PRIORITY;
289         } else {
290             // The parent event.
291             $eventname = $assign->name;
292         }
294         if ($duedate && $addclose) {
295             if ($oldevent = array_shift($oldevents)) {
296                 $event->id = $oldevent->id;
297             } else {
298                 unset($event->id);
299             }
300             $event->name      = $eventname.' ('.get_string('duedate', 'assign').')';
301             $event->timestart = $duedate;
302             $event->eventtype = 'due';
303             calendar_event::create($event);
304         }
305     }
307     // Delete any leftover events.
308     foreach ($oldevents as $badevent) {
309         $badevent = calendar_event::load($badevent);
310         $badevent->delete();
311     }
314 /**
315  * Return the list if Moodle features this module supports
316  *
317  * @param string $feature FEATURE_xx constant for requested feature
318  * @return mixed True if module supports feature, null if doesn't know
319  */
320 function assign_supports($feature) {
321     switch($feature) {
322         case FEATURE_GROUPS:
323             return true;
324         case FEATURE_GROUPINGS:
325             return true;
326         case FEATURE_MOD_INTRO:
327             return true;
328         case FEATURE_COMPLETION_TRACKS_VIEWS:
329             return true;
330         case FEATURE_COMPLETION_HAS_RULES:
331             return true;
332         case FEATURE_GRADE_HAS_GRADE:
333             return true;
334         case FEATURE_GRADE_OUTCOMES:
335             return true;
336         case FEATURE_BACKUP_MOODLE2:
337             return true;
338         case FEATURE_SHOW_DESCRIPTION:
339             return true;
340         case FEATURE_ADVANCED_GRADING:
341             return true;
342         case FEATURE_PLAGIARISM:
343             return true;
344         case FEATURE_COMMENT:
345             return true;
347         default:
348             return null;
349     }
352 /**
353  * Lists all gradable areas for the advanced grading methods gramework
354  *
355  * @return array('string'=>'string') An array with area names as keys and descriptions as values
356  */
357 function assign_grading_areas_list() {
358     return array('submissions'=>get_string('submissions', 'assign'));
362 /**
363  * extend an assigment navigation settings
364  *
365  * @param settings_navigation $settings
366  * @param navigation_node $navref
367  * @return void
368  */
369 function assign_extend_settings_navigation(settings_navigation $settings, navigation_node $navref) {
370     global $PAGE, $DB;
372     // We want to add these new nodes after the Edit settings node, and before the
373     // Locally assigned roles node. Of course, both of those are controlled by capabilities.
374     $keys = $navref->get_children_key_list();
375     $beforekey = null;
376     $i = array_search('modedit', $keys);
377     if ($i === false and array_key_exists(0, $keys)) {
378         $beforekey = $keys[0];
379     } else if (array_key_exists($i + 1, $keys)) {
380         $beforekey = $keys[$i + 1];
381     }
383     $cm = $PAGE->cm;
384     if (!$cm) {
385         return;
386     }
388     $context = $cm->context;
389     $course = $PAGE->course;
391     if (!$course) {
392         return;
393     }
395     if (has_capability('mod/assign:manageoverrides', $PAGE->cm->context)) {
396         $url = new moodle_url('/mod/assign/overrides.php', array('cmid' => $PAGE->cm->id));
397         $node = navigation_node::create(get_string('groupoverrides', 'assign'),
398             new moodle_url($url, array('mode' => 'group')),
399             navigation_node::TYPE_SETTING, null, 'mod_assign_groupoverrides');
400         $navref->add_node($node, $beforekey);
402         $node = navigation_node::create(get_string('useroverrides', 'assign'),
403             new moodle_url($url, array('mode' => 'user')),
404             navigation_node::TYPE_SETTING, null, 'mod_assign_useroverrides');
405         $navref->add_node($node, $beforekey);
406     }
408     // Link to gradebook.
409     if (has_capability('gradereport/grader:view', $cm->context) &&
410             has_capability('moodle/grade:viewall', $cm->context)) {
411         $link = new moodle_url('/grade/report/grader/index.php', array('id' => $course->id));
412         $linkname = get_string('viewgradebook', 'assign');
413         $node = $navref->add($linkname, $link, navigation_node::TYPE_SETTING);
414     }
416     // Link to download all submissions.
417     if (has_any_capability(array('mod/assign:grade', 'mod/assign:viewgrades'), $context)) {
418         $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'grading'));
419         $node = $navref->add(get_string('viewgrading', 'assign'), $link, navigation_node::TYPE_SETTING);
421         $link = new moodle_url('/mod/assign/view.php', array('id' => $cm->id, 'action'=>'downloadall'));
422         $node = $navref->add(get_string('downloadall', 'assign'), $link, navigation_node::TYPE_SETTING);
423     }
425     if (has_capability('mod/assign:revealidentities', $context)) {
426         $dbparams = array('id'=>$cm->instance);
427         $assignment = $DB->get_record('assign', $dbparams, 'blindmarking, revealidentities');
429         if ($assignment && $assignment->blindmarking && !$assignment->revealidentities) {
430             $urlparams = array('id' => $cm->id, 'action'=>'revealidentities');
431             $url = new moodle_url('/mod/assign/view.php', $urlparams);
432             $linkname = get_string('revealidentities', 'assign');
433             $node = $navref->add($linkname, $url, navigation_node::TYPE_SETTING);
434         }
435     }
438 /**
439  * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
440  * for the course (see resource).
441  *
442  * Given a course_module object, this function returns any "extra" information that may be needed
443  * when printing this activity in a course listing.  See get_array_of_activities() in course/lib.php.
444  *
445  * @param stdClass $coursemodule The coursemodule object (record).
446  * @return cached_cm_info An object on information that the courses
447  *                        will know about (most noticeably, an icon).
448  */
449 function assign_get_coursemodule_info($coursemodule) {
450     global $CFG, $DB;
452     $dbparams = array('id'=>$coursemodule->instance);
453     $fields = 'id, name, alwaysshowdescription, allowsubmissionsfromdate, intro, introformat';
454     if (! $assignment = $DB->get_record('assign', $dbparams, $fields)) {
455         return false;
456     }
458     $result = new cached_cm_info();
459     $result->name = $assignment->name;
460     if ($coursemodule->showdescription) {
461         if ($assignment->alwaysshowdescription || time() > $assignment->allowsubmissionsfromdate) {
462             // Convert intro to html. Do not filter cached version, filters run at display time.
463             $result->content = format_module_intro('assign', $assignment, $coursemodule->id, false);
464         }
465     }
466     return $result;
469 /**
470  * Return a list of page types
471  * @param string $pagetype current page type
472  * @param stdClass $parentcontext Block's parent context
473  * @param stdClass $currentcontext Current context of block
474  */
475 function assign_page_type_list($pagetype, $parentcontext, $currentcontext) {
476     $modulepagetype = array(
477         'mod-assign-*' => get_string('page-mod-assign-x', 'assign'),
478         'mod-assign-view' => get_string('page-mod-assign-view', 'assign'),
479     );
480     return $modulepagetype;
483 /**
484  * Print an overview of all assignments
485  * for the courses.
486  *
487  * @param mixed $courses The list of courses to print the overview for
488  * @param array $htmlarray The array of html to return
489  *
490  * @return true
491  */
492 function assign_print_overview($courses, &$htmlarray) {
493     global $CFG, $DB;
495     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
496         return true;
497     }
499     if (!$assignments = get_all_instances_in_courses('assign', $courses)) {
500         return true;
501     }
503     $assignmentids = array();
505     // Do assignment_base::isopen() here without loading the whole thing for speed.
506     foreach ($assignments as $key => $assignment) {
507         $time = time();
508         $isopen = false;
509         if ($assignment->duedate) {
510             $duedate = false;
511             if ($assignment->cutoffdate) {
512                 $duedate = $assignment->cutoffdate;
513             }
514             if ($duedate) {
515                 $isopen = ($assignment->allowsubmissionsfromdate <= $time && $time <= $duedate);
516             } else {
517                 $isopen = ($assignment->allowsubmissionsfromdate <= $time);
518             }
519         }
520         if ($isopen) {
521             $assignmentids[] = $assignment->id;
522         }
523     }
525     if (empty($assignmentids)) {
526         // No assignments to look at - we're done.
527         return true;
528     }
530     // Definitely something to print, now include the constants we need.
531     require_once($CFG->dirroot . '/mod/assign/locallib.php');
533     $strduedate = get_string('duedate', 'assign');
534     $strcutoffdate = get_string('nosubmissionsacceptedafter', 'assign');
535     $strnolatesubmissions = get_string('nolatesubmissions', 'assign');
536     $strduedateno = get_string('duedateno', 'assign');
537     $strassignment = get_string('modulename', 'assign');
539     // We do all possible database work here *outside* of the loop to ensure this scales.
540     list($sqlassignmentids, $assignmentidparams) = $DB->get_in_or_equal($assignmentids);
542     $mysubmissions = null;
543     $unmarkedsubmissions = null;
545     foreach ($assignments as $assignment) {
547         // Do not show assignments that are not open.
548         if (!in_array($assignment->id, $assignmentids)) {
549             continue;
550         }
552         $context = context_module::instance($assignment->coursemodule);
554         // Does the submission status of the assignment require notification?
555         if (has_capability('mod/assign:submit', $context, null, false)) {
556             // Does the submission status of the assignment require notification?
557             $submitdetails = assign_get_mysubmission_details_for_print_overview($mysubmissions, $sqlassignmentids,
558                     $assignmentidparams, $assignment);
559         } else {
560             $submitdetails = false;
561         }
563         if (has_capability('mod/assign:grade', $context, null, false)) {
564             // Does the grading status of the assignment require notification ?
565             $gradedetails = assign_get_grade_details_for_print_overview($unmarkedsubmissions, $sqlassignmentids,
566                     $assignmentidparams, $assignment, $context);
567         } else {
568             $gradedetails = false;
569         }
571         if (empty($submitdetails) && empty($gradedetails)) {
572             // There is no need to display this assignment as there is nothing to notify.
573             continue;
574         }
576         $dimmedclass = '';
577         if (!$assignment->visible) {
578             $dimmedclass = ' class="dimmed"';
579         }
580         $href = $CFG->wwwroot . '/mod/assign/view.php?id=' . $assignment->coursemodule;
581         $basestr = '<div class="assign overview">' .
582                '<div class="name">' .
583                $strassignment . ': '.
584                '<a ' . $dimmedclass .
585                    'title="' . $strassignment . '" ' .
586                    'href="' . $href . '">' .
587                format_string($assignment->name) .
588                '</a></div>';
589         if ($assignment->duedate) {
590             $userdate = userdate($assignment->duedate);
591             $basestr .= '<div class="info">' . $strduedate . ': ' . $userdate . '</div>';
592         } else {
593             $basestr .= '<div class="info">' . $strduedateno . '</div>';
594         }
595         if ($assignment->cutoffdate) {
596             if ($assignment->cutoffdate == $assignment->duedate) {
597                 $basestr .= '<div class="info">' . $strnolatesubmissions . '</div>';
598             } else {
599                 $userdate = userdate($assignment->cutoffdate);
600                 $basestr .= '<div class="info">' . $strcutoffdate . ': ' . $userdate . '</div>';
601             }
602         }
604         // Show only relevant information.
605         if (!empty($submitdetails)) {
606             $basestr .= $submitdetails;
607         }
609         if (!empty($gradedetails)) {
610             $basestr .= $gradedetails;
611         }
612         $basestr .= '</div>';
614         if (empty($htmlarray[$assignment->course]['assign'])) {
615             $htmlarray[$assignment->course]['assign'] = $basestr;
616         } else {
617             $htmlarray[$assignment->course]['assign'] .= $basestr;
618         }
619     }
620     return true;
623 /**
624  * This api generates html to be displayed to students in print overview section, related to their submission status of the given
625  * assignment.
626  *
627  * @param array $mysubmissions list of submissions of current user indexed by assignment id.
628  * @param string $sqlassignmentids sql clause used to filter open assignments.
629  * @param array $assignmentidparams sql params used to filter open assignments.
630  * @param stdClass $assignment current assignment
631  *
632  * @return bool|string html to display , false if nothing needs to be displayed.
633  * @throws coding_exception
634  */
635 function assign_get_mysubmission_details_for_print_overview(&$mysubmissions, $sqlassignmentids, $assignmentidparams,
636                                                             $assignment) {
637     global $USER, $DB;
639     if ($assignment->nosubmissions) {
640         // Offline assignment. No need to display alerts for offline assignments.
641         return false;
642     }
644     $strnotsubmittedyet = get_string('notsubmittedyet', 'assign');
646     if (!isset($mysubmissions)) {
648         // Get all user submissions, indexed by assignment id.
649         $dbparams = array_merge(array($USER->id), $assignmentidparams, array($USER->id));
650         $mysubmissions = $DB->get_records_sql('SELECT a.id AS assignment,
651                                                       a.nosubmissions AS nosubmissions,
652                                                       g.timemodified AS timemarked,
653                                                       g.grader AS grader,
654                                                       g.grade AS grade,
655                                                       s.status AS status
656                                                  FROM {assign} a, {assign_submission} s
657                                             LEFT JOIN {assign_grades} g ON
658                                                       g.assignment = s.assignment AND
659                                                       g.userid = ? AND
660                                                       g.attemptnumber = s.attemptnumber
661                                                 WHERE a.id ' . $sqlassignmentids . ' AND
662                                                       s.latest = 1 AND
663                                                       s.assignment = a.id AND
664                                                       s.userid = ?', $dbparams);
665     }
667     $submitdetails = '';
668     $submitdetails .= '<div class="details">';
669     $submitdetails .= get_string('mysubmission', 'assign');
670     $submission = false;
672     if (isset($mysubmissions[$assignment->id])) {
673         $submission = $mysubmissions[$assignment->id];
674     }
676     if ($submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
677         // A valid submission already exists, no need to notify students about this.
678         return false;
679     }
681     // We need to show details only if a valid submission doesn't exist.
682     if (!$submission ||
683         !$submission->status ||
684         $submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT ||
685         $submission->status == ASSIGN_SUBMISSION_STATUS_NEW
686     ) {
687         $submitdetails .= $strnotsubmittedyet;
688     } else {
689         $submitdetails .= get_string('submissionstatus_' . $submission->status, 'assign');
690     }
691     if ($assignment->markingworkflow) {
692         $workflowstate = $DB->get_field('assign_user_flags', 'workflowstate', array('assignment' =>
693                 $assignment->id, 'userid' => $USER->id));
694         if ($workflowstate) {
695             $gradingstatus = 'markingworkflowstate' . $workflowstate;
696         } else {
697             $gradingstatus = 'markingworkflowstate' . ASSIGN_MARKING_WORKFLOW_STATE_NOTMARKED;
698         }
699     } else if (!empty($submission->grade) && $submission->grade !== null && $submission->grade >= 0) {
700         $gradingstatus = ASSIGN_GRADING_STATUS_GRADED;
701     } else {
702         $gradingstatus = ASSIGN_GRADING_STATUS_NOT_GRADED;
703     }
704     $submitdetails .= ', ' . get_string($gradingstatus, 'assign');
705     $submitdetails .= '</div>';
706     return $submitdetails;
709 /**
710  * This api generates html to be displayed to teachers in print overview section, related to the grading status of the given
711  * assignment's submissions.
712  *
713  * @param array $unmarkedsubmissions list of submissions of that are currently unmarked indexed by assignment id.
714  * @param string $sqlassignmentids sql clause used to filter open assignments.
715  * @param array $assignmentidparams sql params used to filter open assignments.
716  * @param stdClass $assignment current assignment
717  * @param context $context context of the assignment.
718  *
719  * @return bool|string html to display , false if nothing needs to be displayed.
720  * @throws coding_exception
721  */
722 function assign_get_grade_details_for_print_overview(&$unmarkedsubmissions, $sqlassignmentids, $assignmentidparams,
723                                                      $assignment, $context) {
724     global $DB;
725     if (!isset($unmarkedsubmissions)) {
726         // Build up and array of unmarked submissions indexed by assignment id/ userid
727         // for use where the user has grading rights on assignment.
728         $dbparams = array_merge(array(ASSIGN_SUBMISSION_STATUS_SUBMITTED), $assignmentidparams);
729         $rs = $DB->get_recordset_sql('SELECT s.assignment as assignment,
730                                              s.userid as userid,
731                                              s.id as id,
732                                              s.status as status,
733                                              g.timemodified as timegraded
734                                         FROM {assign_submission} s
735                                    LEFT JOIN {assign_grades} g ON
736                                              s.userid = g.userid AND
737                                              s.assignment = g.assignment AND
738                                              g.attemptnumber = s.attemptnumber
739                                        WHERE
740                                              ( g.timemodified is NULL OR
741                                              s.timemodified >= g.timemodified OR
742                                              g.grade IS NULL ) AND
743                                              s.timemodified IS NOT NULL AND
744                                              s.status = ? AND
745                                              s.latest = 1 AND
746                                              s.assignment ' . $sqlassignmentids, $dbparams);
748         $unmarkedsubmissions = array();
749         foreach ($rs as $rd) {
750             $unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
751         }
752         $rs->close();
753     }
755     // Count how many people can submit.
756     $submissions = 0;
757     if ($students = get_enrolled_users($context, 'mod/assign:view', 0, 'u.id')) {
758         foreach ($students as $student) {
759             if (isset($unmarkedsubmissions[$assignment->id][$student->id])) {
760                 $submissions++;
761             }
762         }
763     }
765     if ($submissions) {
766         $urlparams = array('id' => $assignment->coursemodule, 'action' => 'grading');
767         $url = new moodle_url('/mod/assign/view.php', $urlparams);
768         $gradedetails = '<div class="details">' .
769                 '<a href="' . $url . '">' .
770                 get_string('submissionsnotgraded', 'assign', $submissions) .
771                 '</a></div>';
772         return $gradedetails;
773     } else {
774         return false;
775     }
779 /**
780  * Print recent activity from all assignments in a given course
781  *
782  * This is used by the recent activity block
783  * @param mixed $course the course to print activity for
784  * @param bool $viewfullnames boolean to determine whether to show full names or not
785  * @param int $timestart the time the rendering started
786  * @return bool true if activity was printed, false otherwise.
787  */
788 function assign_print_recent_activity($course, $viewfullnames, $timestart) {
789     global $CFG, $USER, $DB, $OUTPUT;
790     require_once($CFG->dirroot . '/mod/assign/locallib.php');
792     // Do not use log table if possible, it may be huge.
794     $dbparams = array($timestart, $course->id, 'assign', ASSIGN_SUBMISSION_STATUS_SUBMITTED);
795     $namefields = user_picture::fields('u', null, 'userid');
796     if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, um.id as recordid,
797                                                      $namefields
798                                                 FROM {assign_submission} asb
799                                                      JOIN {assign} a      ON a.id = asb.assignment
800                                                      JOIN {course_modules} cm ON cm.instance = a.id
801                                                      JOIN {modules} md        ON md.id = cm.module
802                                                      JOIN {user} u            ON u.id = asb.userid
803                                                 LEFT JOIN {assign_user_mapping} um ON um.userid = u.id AND um.assignment = a.id
804                                                WHERE asb.timemodified > ? AND
805                                                      asb.latest = 1 AND
806                                                      a.course = ? AND
807                                                      md.name = ? AND
808                                                      asb.status = ?
809                                             ORDER BY asb.timemodified ASC", $dbparams)) {
810          return false;
811     }
813     $modinfo = get_fast_modinfo($course);
814     $show    = array();
815     $grader  = array();
817     $showrecentsubmissions = get_config('assign', 'showrecentsubmissions');
819     foreach ($submissions as $submission) {
820         if (!array_key_exists($submission->cmid, $modinfo->get_cms())) {
821             continue;
822         }
823         $cm = $modinfo->get_cm($submission->cmid);
824         if (!$cm->uservisible) {
825             continue;
826         }
827         if ($submission->userid == $USER->id) {
828             $show[] = $submission;
829             continue;
830         }
832         $context = context_module::instance($submission->cmid);
833         // The act of submitting of assignment may be considered private -
834         // only graders will see it if specified.
835         if (empty($showrecentsubmissions)) {
836             if (!array_key_exists($cm->id, $grader)) {
837                 $grader[$cm->id] = has_capability('moodle/grade:viewall', $context);
838             }
839             if (!$grader[$cm->id]) {
840                 continue;
841             }
842         }
844         $groupmode = groups_get_activity_groupmode($cm, $course);
846         if ($groupmode == SEPARATEGROUPS &&
847                 !has_capability('moodle/site:accessallgroups',  $context)) {
848             if (isguestuser()) {
849                 // Shortcut - guest user does not belong into any group.
850                 continue;
851             }
853             // This will be slow - show only users that share group with me in this cm.
854             if (!$modinfo->get_groups($cm->groupingid)) {
855                 continue;
856             }
857             $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
858             if (is_array($usersgroups)) {
859                 $usersgroups = array_keys($usersgroups);
860                 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
861                 if (empty($intersect)) {
862                     continue;
863                 }
864             }
865         }
866         $show[] = $submission;
867     }
869     if (empty($show)) {
870         return false;
871     }
873     echo $OUTPUT->heading(get_string('newsubmissions', 'assign').':', 3);
875     foreach ($show as $submission) {
876         $cm = $modinfo->get_cm($submission->cmid);
877         $context = context_module::instance($submission->cmid);
878         $assign = new assign($context, $cm, $cm->course);
879         $link = $CFG->wwwroot.'/mod/assign/view.php?id='.$cm->id;
880         // Obscure first and last name if blind marking enabled.
881         if ($assign->is_blind_marking()) {
882             $submission->firstname = get_string('participant', 'mod_assign');
883             if (empty($submission->recordid)) {
884                 $submission->recordid = $assign->get_uniqueid_for_user($submission->userid);
885             }
886             $submission->lastname = $submission->recordid;
887         }
888         print_recent_activity_note($submission->timemodified,
889                                    $submission,
890                                    $cm->name,
891                                    $link,
892                                    false,
893                                    $viewfullnames);
894     }
896     return true;
899 /**
900  * Returns all assignments since a given time.
901  *
902  * @param array $activities The activity information is returned in this array
903  * @param int $index The current index in the activities array
904  * @param int $timestart The earliest activity to show
905  * @param int $courseid Limit the search to this course
906  * @param int $cmid The course module id
907  * @param int $userid Optional user id
908  * @param int $groupid Optional group id
909  * @return void
910  */
911 function assign_get_recent_mod_activity(&$activities,
912                                         &$index,
913                                         $timestart,
914                                         $courseid,
915                                         $cmid,
916                                         $userid=0,
917                                         $groupid=0) {
918     global $CFG, $COURSE, $USER, $DB;
920     require_once($CFG->dirroot . '/mod/assign/locallib.php');
922     if ($COURSE->id == $courseid) {
923         $course = $COURSE;
924     } else {
925         $course = $DB->get_record('course', array('id'=>$courseid));
926     }
928     $modinfo = get_fast_modinfo($course);
930     $cm = $modinfo->get_cm($cmid);
931     $params = array();
932     if ($userid) {
933         $userselect = 'AND u.id = :userid';
934         $params['userid'] = $userid;
935     } else {
936         $userselect = '';
937     }
939     if ($groupid) {
940         $groupselect = 'AND gm.groupid = :groupid';
941         $groupjoin   = 'JOIN {groups_members} gm ON  gm.userid=u.id';
942         $params['groupid'] = $groupid;
943     } else {
944         $groupselect = '';
945         $groupjoin   = '';
946     }
948     $params['cminstance'] = $cm->instance;
949     $params['timestart'] = $timestart;
950     $params['submitted'] = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
952     $userfields = user_picture::fields('u', null, 'userid');
954     if (!$submissions = $DB->get_records_sql('SELECT asb.id, asb.timemodified, ' .
955                                                      $userfields .
956                                              '  FROM {assign_submission} asb
957                                                 JOIN {assign} a ON a.id = asb.assignment
958                                                 JOIN {user} u ON u.id = asb.userid ' .
959                                           $groupjoin .
960                                             '  WHERE asb.timemodified > :timestart AND
961                                                      asb.status = :submitted AND
962                                                      a.id = :cminstance
963                                                      ' . $userselect . ' ' . $groupselect .
964                                             ' ORDER BY asb.timemodified ASC', $params)) {
965          return;
966     }
968     $groupmode       = groups_get_activity_groupmode($cm, $course);
969     $cmcontext      = context_module::instance($cm->id);
970     $grader          = has_capability('moodle/grade:viewall', $cmcontext);
971     $accessallgroups = has_capability('moodle/site:accessallgroups', $cmcontext);
972     $viewfullnames   = has_capability('moodle/site:viewfullnames', $cmcontext);
975     $showrecentsubmissions = get_config('assign', 'showrecentsubmissions');
976     $show = array();
977     foreach ($submissions as $submission) {
978         if ($submission->userid == $USER->id) {
979             $show[] = $submission;
980             continue;
981         }
982         // The act of submitting of assignment may be considered private -
983         // only graders will see it if specified.
984         if (empty($showrecentsubmissions)) {
985             if (!$grader) {
986                 continue;
987             }
988         }
990         if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
991             if (isguestuser()) {
992                 // Shortcut - guest user does not belong into any group.
993                 continue;
994             }
996             // This will be slow - show only users that share group with me in this cm.
997             if (!$modinfo->get_groups($cm->groupingid)) {
998                 continue;
999             }
1000             $usersgroups =  groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
1001             if (is_array($usersgroups)) {
1002                 $usersgroups = array_keys($usersgroups);
1003                 $intersect = array_intersect($usersgroups, $modinfo->get_groups($cm->groupingid));
1004                 if (empty($intersect)) {
1005                     continue;
1006                 }
1007             }
1008         }
1009         $show[] = $submission;
1010     }
1012     if (empty($show)) {
1013         return;
1014     }
1016     if ($grader) {
1017         require_once($CFG->libdir.'/gradelib.php');
1018         $userids = array();
1019         foreach ($show as $id => $submission) {
1020             $userids[] = $submission->userid;
1021         }
1022         $grades = grade_get_grades($courseid, 'mod', 'assign', $cm->instance, $userids);
1023     }
1025     $aname = format_string($cm->name, true);
1026     foreach ($show as $submission) {
1027         $activity = new stdClass();
1029         $activity->type         = 'assign';
1030         $activity->cmid         = $cm->id;
1031         $activity->name         = $aname;
1032         $activity->sectionnum   = $cm->sectionnum;
1033         $activity->timestamp    = $submission->timemodified;
1034         $activity->user         = new stdClass();
1035         if ($grader) {
1036             $activity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
1037         }
1039         $userfields = explode(',', user_picture::fields());
1040         foreach ($userfields as $userfield) {
1041             if ($userfield == 'id') {
1042                 // Aliased in SQL above.
1043                 $activity->user->{$userfield} = $submission->userid;
1044             } else {
1045                 $activity->user->{$userfield} = $submission->{$userfield};
1046             }
1047         }
1048         $activity->user->fullname = fullname($submission, $viewfullnames);
1050         $activities[$index++] = $activity;
1051     }
1053     return;
1056 /**
1057  * Print recent activity from all assignments in a given course
1058  *
1059  * This is used by course/recent.php
1060  * @param stdClass $activity
1061  * @param int $courseid
1062  * @param bool $detail
1063  * @param array $modnames
1064  */
1065 function assign_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
1066     global $CFG, $OUTPUT;
1068     echo '<table border="0" cellpadding="3" cellspacing="0" class="assignment-recent">';
1070     echo '<tr><td class="userpicture" valign="top">';
1071     echo $OUTPUT->user_picture($activity->user);
1072     echo '</td><td>';
1074     if ($detail) {
1075         $modname = $modnames[$activity->type];
1076         echo '<div class="title">';
1077         echo '<img src="' . $OUTPUT->pix_url('icon', 'assign') . '" '.
1078              'class="icon" alt="' . $modname . '">';
1079         echo '<a href="' . $CFG->wwwroot . '/mod/assign/view.php?id=' . $activity->cmid . '">';
1080         echo $activity->name;
1081         echo '</a>';
1082         echo '</div>';
1083     }
1085     if (isset($activity->grade)) {
1086         echo '<div class="grade">';
1087         echo get_string('grade').': ';
1088         echo $activity->grade;
1089         echo '</div>';
1090     }
1092     echo '<div class="user">';
1093     echo "<a href=\"$CFG->wwwroot/user/view.php?id={$activity->user->id}&amp;course=$courseid\">";
1094     echo "{$activity->user->fullname}</a>  - " . userdate($activity->timestamp);
1095     echo '</div>';
1097     echo '</td></tr></table>';
1100 /**
1101  * Checks if a scale is being used by an assignment.
1102  *
1103  * This is used by the backup code to decide whether to back up a scale
1104  * @param int $assignmentid
1105  * @param int $scaleid
1106  * @return boolean True if the scale is used by the assignment
1107  */
1108 function assign_scale_used($assignmentid, $scaleid) {
1109     global $DB;
1111     $return = false;
1112     $rec = $DB->get_record('assign', array('id'=>$assignmentid, 'grade'=>-$scaleid));
1114     if (!empty($rec) && !empty($scaleid)) {
1115         $return = true;
1116     }
1118     return $return;
1121 /**
1122  * Checks if scale is being used by any instance of assignment
1123  *
1124  * This is used to find out if scale used anywhere
1125  * @param int $scaleid
1126  * @return boolean True if the scale is used by any assignment
1127  */
1128 function assign_scale_used_anywhere($scaleid) {
1129     global $DB;
1131     if ($scaleid and $DB->record_exists('assign', array('grade'=>-$scaleid))) {
1132         return true;
1133     } else {
1134         return false;
1135     }
1138 /**
1139  * List the actions that correspond to a view of this module.
1140  * This is used by the participation report.
1141  *
1142  * Note: This is not used by new logging system. Event with
1143  *       crud = 'r' and edulevel = LEVEL_PARTICIPATING will
1144  *       be considered as view action.
1145  *
1146  * @return array
1147  */
1148 function assign_get_view_actions() {
1149     return array('view submission', 'view feedback');
1152 /**
1153  * List the actions that correspond to a post of this module.
1154  * This is used by the participation report.
1155  *
1156  * Note: This is not used by new logging system. Event with
1157  *       crud = ('c' || 'u' || 'd') and edulevel = LEVEL_PARTICIPATING
1158  *       will be considered as post action.
1159  *
1160  * @return array
1161  */
1162 function assign_get_post_actions() {
1163     return array('upload', 'submit', 'submit for grading');
1166 /**
1167  * Call cron on the assign module.
1168  */
1169 function assign_cron() {
1170     global $CFG;
1172     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1173     assign::cron();
1175     $plugins = core_component::get_plugin_list('assignsubmission');
1177     foreach ($plugins as $name => $plugin) {
1178         $disabled = get_config('assignsubmission_' . $name, 'disabled');
1179         if (!$disabled) {
1180             $class = 'assign_submission_' . $name;
1181             require_once($CFG->dirroot . '/mod/assign/submission/' . $name . '/locallib.php');
1182             $class::cron();
1183         }
1184     }
1185     $plugins = core_component::get_plugin_list('assignfeedback');
1187     foreach ($plugins as $name => $plugin) {
1188         $disabled = get_config('assignfeedback_' . $name, 'disabled');
1189         if (!$disabled) {
1190             $class = 'assign_feedback_' . $name;
1191             require_once($CFG->dirroot . '/mod/assign/feedback/' . $name . '/locallib.php');
1192             $class::cron();
1193         }
1194     }
1196     return true;
1199 /**
1200  * Returns all other capabilities used by this module.
1201  * @return array Array of capability strings
1202  */
1203 function assign_get_extra_capabilities() {
1204     return array('gradereport/grader:view',
1205                  'moodle/grade:viewall',
1206                  'moodle/site:viewfullnames',
1207                  'moodle/site:config');
1210 /**
1211  * Create grade item for given assignment.
1212  *
1213  * @param stdClass $assign record with extra cmidnumber
1214  * @param array $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
1215  * @return int 0 if ok, error code otherwise
1216  */
1217 function assign_grade_item_update($assign, $grades=null) {
1218     global $CFG;
1219     require_once($CFG->libdir.'/gradelib.php');
1221     if (!isset($assign->courseid)) {
1222         $assign->courseid = $assign->course;
1223     }
1225     $params = array('itemname'=>$assign->name, 'idnumber'=>$assign->cmidnumber);
1227     // Check if feedback plugin for gradebook is enabled, if yes then
1228     // gradetype = GRADE_TYPE_TEXT else GRADE_TYPE_NONE.
1229     $gradefeedbackenabled = false;
1231     if (isset($assign->gradefeedbackenabled)) {
1232         $gradefeedbackenabled = $assign->gradefeedbackenabled;
1233     } else if ($assign->grade == 0) { // Grade feedback is needed only when grade == 0.
1234         require_once($CFG->dirroot . '/mod/assign/locallib.php');
1235         $mod = get_coursemodule_from_instance('assign', $assign->id, $assign->courseid);
1236         $cm = context_module::instance($mod->id);
1237         $assignment = new assign($cm, null, null);
1238         $gradefeedbackenabled = $assignment->is_gradebook_feedback_enabled();
1239     }
1241     if ($assign->grade > 0) {
1242         $params['gradetype'] = GRADE_TYPE_VALUE;
1243         $params['grademax']  = $assign->grade;
1244         $params['grademin']  = 0;
1246     } else if ($assign->grade < 0) {
1247         $params['gradetype'] = GRADE_TYPE_SCALE;
1248         $params['scaleid']   = -$assign->grade;
1250     } else if ($gradefeedbackenabled) {
1251         // $assign->grade == 0 and feedback enabled.
1252         $params['gradetype'] = GRADE_TYPE_TEXT;
1253     } else {
1254         // $assign->grade == 0 and no feedback enabled.
1255         $params['gradetype'] = GRADE_TYPE_NONE;
1256     }
1258     if ($grades  === 'reset') {
1259         $params['reset'] = true;
1260         $grades = null;
1261     }
1263     return grade_update('mod/assign',
1264                         $assign->courseid,
1265                         'mod',
1266                         'assign',
1267                         $assign->id,
1268                         0,
1269                         $grades,
1270                         $params);
1273 /**
1274  * Return grade for given user or all users.
1275  *
1276  * @param stdClass $assign record of assign with an additional cmidnumber
1277  * @param int $userid optional user id, 0 means all users
1278  * @return array array of grades, false if none
1279  */
1280 function assign_get_user_grades($assign, $userid=0) {
1281     global $CFG;
1283     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1285     $cm = get_coursemodule_from_instance('assign', $assign->id, 0, false, MUST_EXIST);
1286     $context = context_module::instance($cm->id);
1287     $assignment = new assign($context, null, null);
1288     $assignment->set_instance($assign);
1289     return $assignment->get_user_grades_for_gradebook($userid);
1292 /**
1293  * Update activity grades.
1294  *
1295  * @param stdClass $assign database record
1296  * @param int $userid specific user only, 0 means all
1297  * @param bool $nullifnone - not used
1298  */
1299 function assign_update_grades($assign, $userid=0, $nullifnone=true) {
1300     global $CFG;
1301     require_once($CFG->libdir.'/gradelib.php');
1303     if ($assign->grade == 0) {
1304         assign_grade_item_update($assign);
1306     } else if ($grades = assign_get_user_grades($assign, $userid)) {
1307         foreach ($grades as $k => $v) {
1308             if ($v->rawgrade == -1) {
1309                 $grades[$k]->rawgrade = null;
1310             }
1311         }
1312         assign_grade_item_update($assign, $grades);
1314     } else {
1315         assign_grade_item_update($assign);
1316     }
1319 /**
1320  * List the file areas that can be browsed.
1321  *
1322  * @param stdClass $course
1323  * @param stdClass $cm
1324  * @param stdClass $context
1325  * @return array
1326  */
1327 function assign_get_file_areas($course, $cm, $context) {
1328     global $CFG;
1329     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1331     $areas = array(ASSIGN_INTROATTACHMENT_FILEAREA => get_string('introattachments', 'mod_assign'));
1333     $assignment = new assign($context, $cm, $course);
1334     foreach ($assignment->get_submission_plugins() as $plugin) {
1335         if ($plugin->is_visible()) {
1336             $pluginareas = $plugin->get_file_areas();
1338             if ($pluginareas) {
1339                 $areas = array_merge($areas, $pluginareas);
1340             }
1341         }
1342     }
1343     foreach ($assignment->get_feedback_plugins() as $plugin) {
1344         if ($plugin->is_visible()) {
1345             $pluginareas = $plugin->get_file_areas();
1347             if ($pluginareas) {
1348                 $areas = array_merge($areas, $pluginareas);
1349             }
1350         }
1351     }
1353     return $areas;
1356 /**
1357  * File browsing support for assign module.
1358  *
1359  * @param file_browser $browser
1360  * @param object $areas
1361  * @param object $course
1362  * @param object $cm
1363  * @param object $context
1364  * @param string $filearea
1365  * @param int $itemid
1366  * @param string $filepath
1367  * @param string $filename
1368  * @return object file_info instance or null if not found
1369  */
1370 function assign_get_file_info($browser,
1371                               $areas,
1372                               $course,
1373                               $cm,
1374                               $context,
1375                               $filearea,
1376                               $itemid,
1377                               $filepath,
1378                               $filename) {
1379     global $CFG;
1380     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1382     if ($context->contextlevel != CONTEXT_MODULE) {
1383         return null;
1384     }
1386     $urlbase = $CFG->wwwroot.'/pluginfile.php';
1387     $fs = get_file_storage();
1388     $filepath = is_null($filepath) ? '/' : $filepath;
1389     $filename = is_null($filename) ? '.' : $filename;
1391     // Need to find where this belongs to.
1392     $assignment = new assign($context, $cm, $course);
1393     if ($filearea === ASSIGN_INTROATTACHMENT_FILEAREA) {
1394         if (!has_capability('moodle/course:managefiles', $context)) {
1395             // Students can not peak here!
1396             return null;
1397         }
1398         if (!($storedfile = $fs->get_file($assignment->get_context()->id,
1399                                           'mod_assign', $filearea, 0, $filepath, $filename))) {
1400             return null;
1401         }
1402         return new file_info_stored($browser,
1403                         $assignment->get_context(),
1404                         $storedfile,
1405                         $urlbase,
1406                         $filearea,
1407                         $itemid,
1408                         true,
1409                         true,
1410                         false);
1411     }
1413     $pluginowner = null;
1414     foreach ($assignment->get_submission_plugins() as $plugin) {
1415         if ($plugin->is_visible()) {
1416             $pluginareas = $plugin->get_file_areas();
1418             if (array_key_exists($filearea, $pluginareas)) {
1419                 $pluginowner = $plugin;
1420                 break;
1421             }
1422         }
1423     }
1424     if (!$pluginowner) {
1425         foreach ($assignment->get_feedback_plugins() as $plugin) {
1426             if ($plugin->is_visible()) {
1427                 $pluginareas = $plugin->get_file_areas();
1429                 if (array_key_exists($filearea, $pluginareas)) {
1430                     $pluginowner = $plugin;
1431                     break;
1432                 }
1433             }
1434         }
1435     }
1437     if (!$pluginowner) {
1438         return null;
1439     }
1441     $result = $pluginowner->get_file_info($browser, $filearea, $itemid, $filepath, $filename);
1442     return $result;
1445 /**
1446  * Prints the complete info about a user's interaction with an assignment.
1447  *
1448  * @param stdClass $course
1449  * @param stdClass $user
1450  * @param stdClass $coursemodule
1451  * @param stdClass $assign the database assign record
1452  *
1453  * This prints the submission summary and feedback summary for this student.
1454  */
1455 function assign_user_complete($course, $user, $coursemodule, $assign) {
1456     global $CFG;
1457     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1459     $context = context_module::instance($coursemodule->id);
1461     $assignment = new assign($context, $coursemodule, $course);
1463     echo $assignment->view_student_summary($user, false);
1466 /**
1467  * Rescale all grades for this activity and push the new grades to the gradebook.
1468  *
1469  * @param stdClass $course Course db record
1470  * @param stdClass $cm Course module db record
1471  * @param float $oldmin
1472  * @param float $oldmax
1473  * @param float $newmin
1474  * @param float $newmax
1475  */
1476 function assign_rescale_activity_grades($course, $cm, $oldmin, $oldmax, $newmin, $newmax) {
1477     global $DB;
1479     if ($oldmax <= $oldmin) {
1480         // Grades cannot be scaled.
1481         return false;
1482     }
1483     $scale = ($newmax - $newmin) / ($oldmax - $oldmin);
1484     if (($newmax - $newmin) <= 1) {
1485         // We would lose too much precision, lets bail.
1486         return false;
1487     }
1489     $params = array(
1490         'p1' => $oldmin,
1491         'p2' => $scale,
1492         'p3' => $newmin,
1493         'a' => $cm->instance
1494     );
1496     $sql = 'UPDATE {assign_grades} set grade = (((grade - :p1) * :p2) + :p3) where assignment = :a';
1497     $dbupdate = $DB->execute($sql, $params);
1498     if (!$dbupdate) {
1499         return false;
1500     }
1502     // Now re-push all grades to the gradebook.
1503     $dbparams = array('id' => $cm->instance);
1504     $assign = $DB->get_record('assign', $dbparams);
1505     $assign->cmidnumber = $cm->idnumber;
1507     assign_update_grades($assign);
1509     return true;
1512 /**
1513  * Print the grade information for the assignment for this user.
1514  *
1515  * @param stdClass $course
1516  * @param stdClass $user
1517  * @param stdClass $coursemodule
1518  * @param stdClass $assignment
1519  */
1520 function assign_user_outline($course, $user, $coursemodule, $assignment) {
1521     global $CFG;
1522     require_once($CFG->libdir.'/gradelib.php');
1523     require_once($CFG->dirroot.'/grade/grading/lib.php');
1525     $gradinginfo = grade_get_grades($course->id,
1526                                         'mod',
1527                                         'assign',
1528                                         $assignment->id,
1529                                         $user->id);
1531     $gradingitem = $gradinginfo->items[0];
1532     $gradebookgrade = $gradingitem->grades[$user->id];
1534     if (empty($gradebookgrade->str_long_grade)) {
1535         return null;
1536     }
1537     $result = new stdClass();
1538     $result->info = get_string('outlinegrade', 'assign', $gradebookgrade->str_long_grade);
1539     $result->time = $gradebookgrade->dategraded;
1541     return $result;
1544 /**
1545  * Obtains the automatic completion state for this module based on any conditions
1546  * in assign settings.
1547  *
1548  * @param object $course Course
1549  * @param object $cm Course-module
1550  * @param int $userid User ID
1551  * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
1552  * @return bool True if completed, false if not, $type if conditions not set.
1553  */
1554 function assign_get_completion_state($course, $cm, $userid, $type) {
1555     global $CFG, $DB;
1556     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1558     $assign = new assign(null, $cm, $course);
1560     // If completion option is enabled, evaluate it and return true/false.
1561     if ($assign->get_instance()->completionsubmit) {
1562         if ($assign->get_instance()->teamsubmission) {
1563             $submission = $assign->get_group_submission($userid, 0, false);
1564         } else {
1565             $submission = $assign->get_user_submission($userid, false);
1566         }
1567         return $submission && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED;
1568     } else {
1569         // Completion option is not enabled so just return $type.
1570         return $type;
1571     }
1574 /**
1575  * Serves intro attachment files.
1576  *
1577  * @param mixed $course course or id of the course
1578  * @param mixed $cm course module or id of the course module
1579  * @param context $context
1580  * @param string $filearea
1581  * @param array $args
1582  * @param bool $forcedownload
1583  * @param array $options additional options affecting the file serving
1584  * @return bool false if file not found, does not return if found - just send the file
1585  */
1586 function assign_pluginfile($course,
1587                 $cm,
1588                 context $context,
1589                 $filearea,
1590                 $args,
1591                 $forcedownload,
1592                 array $options=array()) {
1593     global $CFG;
1595     if ($context->contextlevel != CONTEXT_MODULE) {
1596         return false;
1597     }
1599     require_login($course, false, $cm);
1600     if (!has_capability('mod/assign:view', $context)) {
1601         return false;
1602     }
1604     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1605     $assign = new assign($context, $cm, $course);
1607     if ($filearea !== ASSIGN_INTROATTACHMENT_FILEAREA) {
1608         return false;
1609     }
1610     if (!$assign->show_intro()) {
1611         return false;
1612     }
1614     $itemid = (int)array_shift($args);
1615     if ($itemid != 0) {
1616         return false;
1617     }
1619     $relativepath = implode('/', $args);
1621     $fullpath = "/{$context->id}/mod_assign/$filearea/$itemid/$relativepath";
1623     $fs = get_file_storage();
1624     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
1625         return false;
1626     }
1627     send_stored_file($file, 0, 0, $forcedownload, $options);
1630 /**
1631  * Serve the grading panel as a fragment.
1632  *
1633  * @param array $args List of named arguments for the fragment loader.
1634  * @return string
1635  */
1636 function mod_assign_output_fragment_gradingpanel($args) {
1637     global $CFG;
1639     $context = $args['context'];
1641     if ($context->contextlevel != CONTEXT_MODULE) {
1642         return null;
1643     }
1644     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1645     $assign = new assign($context, null, null);
1647     $userid = clean_param($args['userid'], PARAM_INT);
1648     $attemptnumber = clean_param($args['attemptnumber'], PARAM_INT);
1649     $formdata = array();
1650     if (!empty($args['jsonformdata'])) {
1651         $serialiseddata = json_decode($args['jsonformdata']);
1652         parse_str($serialiseddata, $formdata);
1653     }
1654     $viewargs = array(
1655         'userid' => $userid,
1656         'attemptnumber' => $attemptnumber,
1657         'formdata' => $formdata
1658     );
1660     return $assign->view('gradingpanel', $viewargs);
1663 /**
1664  * Check if the module has any update that affects the current user since a given time.
1665  *
1666  * @param  cm_info $cm course module data
1667  * @param  int $from the time to check updates from
1668  * @param  array $filter  if we need to check only specific updates
1669  * @return stdClass an object with the different type of areas indicating if they were updated or not
1670  * @since Moodle 3.2
1671  */
1672 function assign_check_updates_since(cm_info $cm, $from, $filter = array()) {
1673     global $DB, $USER, $CFG;
1674     require_once($CFG->dirroot . '/mod/assign/locallib.php');
1676     $updates = new stdClass();
1677     $updates = course_check_module_updates_since($cm, $from, array(ASSIGN_INTROATTACHMENT_FILEAREA), $filter);
1679     // Check if there is a new submission by the user or new grades.
1680     $select = 'assignment = :id AND userid = :userid AND (timecreated > :since1 OR timemodified > :since2)';
1681     $params = array('id' => $cm->instance, 'userid' => $USER->id, 'since1' => $from, 'since2' => $from);
1682     $updates->submissions = (object) array('updated' => false);
1683     $submissions = $DB->get_records_select('assign_submission', $select, $params, '', 'id');
1684     if (!empty($submissions)) {
1685         $updates->submissions->updated = true;
1686         $updates->submissions->itemids = array_keys($submissions);
1687     }
1689     $updates->grades = (object) array('updated' => false);
1690     $grades = $DB->get_records_select('assign_grades', $select, $params, '', 'id');
1691     if (!empty($grades)) {
1692         $updates->grades->updated = true;
1693         $updates->grades->itemids = array_keys($grades);
1694     }
1696     return $updates;