3 * assignment_base is the base class for assignment types
5 * This class provides all the functionality for an assignment
8 DEFINE ('ASSIGNMENT_COUNT_WORDS', 1);
9 DEFINE ('ASSIGNMENT_COUNT_LETTERS', 2);
11 if (!isset($CFG->assignment_maxbytes
)) {
12 set_config("assignment_maxbytes", 1024000); // Default maximum size for all assignments
14 if (!isset($CFG->assignment_itemstocount
)) {
15 set_config("assignment_itemstocount", ASSIGNMENT_COUNT_WORDS
); // Default item to count
19 * Standard base class for all assignment submodules (assignment types).
21 class assignment_base
{
39 * Constructor for the base assignment class
41 * Constructor for the base assignment class.
42 * If cmid is set create the cm, course, assignment objects.
43 * If the assignment is hidden and the user is not a teacher then
44 * this prints a page header and notice.
46 * @param cmid integer, the current course module id - not set for new assignments
47 * @param assignment object, usually null, but if we have it we pass it to save db access
48 * @param cm object, usually null, but if we have it we pass it to save db access
49 * @param course object, usually null, but if we have it we pass it to save db access
51 function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
52 if ($cmid == 'staticonly') {
53 //use static functions only!
61 } else if (! $this->cm
= get_coursemodule_from_id('assignment', $cmid)) {
62 error('Course Module ID was incorrect');
65 $this->context
= get_context_instance(CONTEXT_MODULE
,$this->cm
->id
);
68 $this->course
= $course;
69 } else if (! $this->course
= get_record('course', 'id', $this->cm
->course
)) {
70 error('Course is misconfigured');
74 $this->assignment
= $assignment;
75 } else if (! $this->assignment
= get_record('assignment', 'id', $this->cm
->instance
)) {
76 error('assignment ID was incorrect');
79 $this->assignment
->cmidnumber
= $this->cm
->id
; // compatibility with modedit assignment obj
80 $this->assignment
->courseid
= $this->course
->id
; // compatibility with modedit assignment obj
82 require_once($CFG->libdir
.'/gradelib.php');
83 $this->lockedgrades
= grade_is_locked($this->course
->id
, 'mod', 'assignment', $this->assignment
->id
, 0);
85 $this->strassignment
= get_string('modulename', 'assignment');
86 $this->strassignments
= get_string('modulenameplural', 'assignment');
87 $this->strsubmissions
= get_string('submissions', 'assignment');
88 $this->strlastmodified
= get_string('lastmodified');
90 $this->navigation
[] = array('name' => $this->strassignments
, 'link' => "index.php?id={$this->course->id}", 'type' => 'activity');
92 $this->pagetitle
= strip_tags($this->course
->shortname
.': '.$this->strassignment
.': '.format_string($this->assignment
->name
,true));
95 $context = get_context_instance(CONTEXT_MODULE
, $cmid);
96 if (!$this->cm
->visible
and !has_capability('moodle/course:viewhiddenactivities', $context)) {
97 $pagetitle = strip_tags($this->course
->shortname
.': '.$this->strassignment
);
98 $this->navigation
[] = array('name' => $this->strassignment
, 'link' => '', 'type' => 'activityinstance');
99 $navigation = build_navigation($this->navigation
);
101 print_header($pagetitle, $this->course
->fullname
, $this->navigation
,
102 "", "", true, '', navmenu($this->course
, $this->cm
));
103 notice(get_string("activityiscurrentlyhidden"), "$CFG->wwwroot/course/view.php?id={$this->course->id}");
105 $this->currentgroup
= groups_get_activity_group($this->cm
);
107 /// Set up things for a HTML editor if it's needed
108 if ($this->usehtmleditor
= can_use_html_editor()) {
109 $this->defaultformat
= FORMAT_HTML
;
111 $this->defaultformat
= FORMAT_MOODLE
;
116 * Display the assignment, used by view.php
118 * This in turn calls the methods producing individual parts of the page
122 $context = get_context_instance(CONTEXT_MODULE
,$this->cm
->id
);
123 require_capability('mod/assignment:view', $context);
125 add_to_log($this->course
->id
, "assignment", "view", "view.php?id={$this->cm->id}",
126 $this->assignment
->id
, $this->cm
->id
);
128 $this->view_header();
134 $this->view_feedback();
136 $this->view_footer();
140 * Display the header and top of a page
142 * (this doesn't change much for assignment types)
143 * This is used by the view() method to print the header of view.php but
144 * it can be used on other pages in which case the string to denote the
145 * page in the navigation trail should be passed as an argument
147 * @param $subpage string Description of subpage to be used in navigation trail
149 function view_header($subpage='') {
155 $this->navigation
[] = array('name' => format_string($this->assignment
->name
,true), 'link' => "view.php?id={$this->cm->id}", 'type' => 'activityinstance');
156 $this->navigation
[] = array('name' => $subpage, 'link' => '', 'type' => 'title');
158 $this->navigation
[] = array('name' => format_string($this->assignment
->name
,true), 'link' => '', 'type' => 'activityinstance');
161 $navigation = build_navigation($this->navigation
);
163 print_header($this->pagetitle
, $this->course
->fullname
, $navigation, '', '',
164 true, update_module_button($this->cm
->id
, $this->course
->id
, $this->strassignment
),
165 navmenu($this->course
, $this->cm
));
167 $groupmode = groups_get_activity_groupmode($this->cm
);
168 $currentgroup = groups_get_activity_group($this->cm
);
169 groups_print_activity_menu($this->cm
, 'view.php?id=' . $this->cm
->id
);
171 echo '<div class="reportlink">'.$this->submittedlink().'</div>';
172 echo '<div class="clearer"></div>';
177 * Display the assignment intro
179 * This will most likely be extended by assignment type plug-ins
180 * The default implementation prints the assignment description in a box
182 function view_intro() {
183 print_simple_box_start('center', '', '', 0, 'generalbox', 'intro');
184 $formatoptions = new stdClass
;
185 $formatoptions->noclean
= true;
186 echo format_text($this->assignment
->description
, $this->assignment
->format
, $formatoptions);
187 print_simple_box_end();
191 * Display the assignment dates
193 * Prints the assignment start and end dates in a box.
194 * This will be suitable for most assignment types
196 function view_dates() {
197 if (!$this->assignment
->timeavailable
&& !$this->assignment
->timedue
) {
201 print_simple_box_start('center', '', '', 0, 'generalbox', 'dates');
203 if ($this->assignment
->timeavailable
) {
204 echo '<tr><td class="c0">'.get_string('availabledate','assignment').':</td>';
205 echo ' <td class="c1">'.userdate($this->assignment
->timeavailable
).'</td></tr>';
207 if ($this->assignment
->timedue
) {
208 echo '<tr><td class="c0">'.get_string('duedate','assignment').':</td>';
209 echo ' <td class="c1">'.userdate($this->assignment
->timedue
).'</td></tr>';
212 print_simple_box_end();
217 * Display the bottom and footer of a page
219 * This default method just prints the footer.
220 * This will be suitable for most assignment types
222 function view_footer() {
223 print_footer($this->course
);
227 * Display the feedback to the student
229 * This default method prints the teacher picture and name, date when marked,
230 * grade and teacher submissioncomment.
232 * @param $submission object The submission object or NULL in which case it will be loaded
234 function view_feedback($submission=NULL) {
237 if (!$submission) { /// Get submission for this assignment
238 $submission = $this->get_submission($USER->id
);
241 if (empty($submission->timemarked
)) { /// Nothing to show, so print nothing
245 /// We need the teacher info
246 if (! $teacher = get_record('user', 'id', $submission->teacher
)) {
247 error('Could not find the teacher');
250 /// Print the feedback
251 print_heading(get_string('feedbackfromteacher', 'assignment', $this->course
->teacher
));
253 echo '<table cellspacing="0" class="feedback">';
256 echo '<td class="left picture">';
257 print_user_picture($teacher->id
, $this->course
->id
, $teacher->picture
);
259 echo '<td class="topic">';
260 echo '<div class="from">';
261 echo '<div class="fullname">'.fullname($teacher).'</div>';
262 echo '<div class="time">'.userdate($submission->timemarked
).'</div>';
268 echo '<td class="left side"> </td>';
269 echo '<td class="content">';
270 if ($this->assignment
->grade
) {
271 echo '<div class="grade">';
272 echo get_string("grade").': '.$this->display_grade($submission->grade
);
274 echo '<div class="clearer"></div>';
277 echo '<div class="comment">';
278 echo format_text($submission->submissioncomment
, $submission->format
);
286 * Returns a link with info about the state of the assignment submissions
288 * This is used by view_header to put this link at the top right of the page.
289 * For teachers it gives the number of submitted assignments with a link
290 * For students it gives the time of their submission.
291 * This will be suitable for most assignment types.
294 function submittedlink() {
299 $context = get_context_instance(CONTEXT_MODULE
,$this->cm
->id
);
300 if (has_capability('mod/assignment:grade', $context)) {
302 // if this user can mark and is put in a group
303 // then he can only see/mark submission in his own groups
304 if (!has_capability('moodle/course:managegroups', $context) and (groups_get_activity_groupmode($this->cm
) == SEPARATEGROUPS
)) {
305 $count = $this->count_real_submissions($this->currentgroup
); // Only their groups
307 $count = $this->count_real_submissions(); // Everyone
309 $submitted = '<a href="submissions.php?id='.$this->cm
->id
.'">'.
310 get_string('viewsubmissions', 'assignment', $count).'</a>';
312 if (!empty($USER->id
)) {
313 if ($submission = $this->get_submission($USER->id
)) {
314 if ($submission->timemodified
) {
315 if ($submission->timemodified
<= $this->assignment
->timedue ||
empty($this->assignment
->timedue
)) {
316 $submitted = '<span class="early">'.userdate($submission->timemodified
).'</span>';
318 $submitted = '<span class="late">'.userdate($submission->timemodified
).'</span>';
329 function setup_elements(&$mform) {
334 * Create a new assignment activity
336 * Given an object containing all the necessary data,
337 * (defined by the form in mod.html) this function
338 * will create a new instance and return the id number
339 * of the new instance.
340 * The due data is added to the calendar
341 * This is common to all assignment types.
343 * @param $assignment object The data from the form on mod.html
344 * @return int The id of the assignment
346 function add_instance($assignment) {
349 $assignment->timemodified
= time();
350 $assignment->courseid
= $assignment->course
;
352 if ($returnid = insert_record("assignment", $assignment)) {
353 $assignment->id
= $returnid;
355 if ($assignment->timedue
) {
356 $event = new object();
357 $event->name
= $assignment->name
;
358 $event->description
= $assignment->description
;
359 $event->courseid
= $assignment->course
;
362 $event->modulename
= 'assignment';
363 $event->instance
= $returnid;
364 $event->eventtype
= 'due';
365 $event->timestart
= $assignment->timedue
;
366 $event->timeduration
= 0;
371 $assignment = stripslashes_recursive($assignment);
372 assignment_grade_item_update($assignment);
381 * Deletes an assignment activity
383 * Deletes all database records, files and calendar events for this assignment.
384 * @param $assignment object The assignment to be deleted
385 * @return boolean False indicates error
387 function delete_instance($assignment) {
390 $assignment->courseid
= $assignment->course
;
394 if (! delete_records('assignment_submissions', 'assignment', $assignment->id
)) {
398 if (! delete_records('assignment', 'id', $assignment->id
)) {
402 if (! delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id
)) {
406 // delete file area with all attachments - ignore errors
407 require_once($CFG->libdir
.'/filelib.php');
408 fulldelete($CFG->dataroot
.'/'.$assignment->course
.'/'.$CFG->moddata
.'/assignment/'.$assignment->id
);
410 assignment_grade_item_delete($assignment);
416 * Updates a new assignment activity
418 * Given an object containing all the necessary data,
419 * (defined by the form in mod.html) this function
420 * will update the assignment instance and return the id number
421 * The due date is updated in the calendar
422 * This is common to all assignment types.
424 * @param $assignment object The data from the form on mod.html
425 * @return int The assignment id
427 function update_instance($assignment) {
430 $assignment->timemodified
= time();
432 $assignment->id
= $assignment->instance
;
433 $assignment->courseid
= $assignment->course
;
435 if (!update_record('assignment', $assignment)) {
439 if ($assignment->timedue
) {
440 $event = new object();
442 if ($event->id
= get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id
)) {
444 $event->name
= $assignment->name
;
445 $event->description
= $assignment->description
;
446 $event->timestart
= $assignment->timedue
;
448 update_event($event);
450 $event = new object();
451 $event->name
= $assignment->name
;
452 $event->description
= $assignment->description
;
453 $event->courseid
= $assignment->course
;
456 $event->modulename
= 'assignment';
457 $event->instance
= $assignment->id
;
458 $event->eventtype
= 'due';
459 $event->timestart
= $assignment->timedue
;
460 $event->timeduration
= 0;
465 delete_records('event', 'modulename', 'assignment', 'instance', $assignment->id
);
468 // get existing grade item
469 $assignment = stripslashes_recursive($assignment);
471 assignment_grade_item_update($assignment);
477 * Update grade item for this submission.
479 function update_grade($submission) {
480 assignment_update_grades($this->assignment
, $submission->userid
);
484 * Top-level function for handling of submissions called by submissions.php
486 * This is for handling the teacher interaction with the grading interface
487 * This should be suitable for most assignment types.
489 * @param $mode string Specifies the kind of teacher interaction taking place
491 function submissions($mode) {
492 ///The main switch is changed to facilitate
493 ///1) Batch fast grading
494 ///2) Skip to the next one on the popup
495 ///3) Save and Skip to the next one on the popup
497 //make user global so we can use the id
500 // no grading when grades are locked
501 if ($this->lockedgrades
) {
506 case 'grade': // We are in a popup window grading
507 if ($submission = $this->process_feedback()) {
508 //IE needs proper header with encoding
509 print_header(get_string('feedback', 'assignment').':'.format_string($this->assignment
->name
));
510 print_heading(get_string('changessaved'));
511 print $this->update_main_listing($submission);
516 case 'single': // We are in a popup window displaying submission
517 $this->display_submission();
520 case 'all': // Main window, display everything
521 $this->display_submissions();
525 ///do the fast grading stuff - this process should work for all 3 subclasses
529 if (isset($_POST['submissioncomment'])) {
530 $col = 'submissioncomment';
533 if (isset($_POST['menu'])) {
538 //both submissioncomment and grade columns collapsed..
539 $this->display_submissions();
542 foreach ($_POST[$col] as $id => $unusedvalue){
544 $id = (int)$id; //clean parameter name
546 $this->process_outcomes($id);
548 if (!$submission = $this->get_submission($id)) {
549 $submission = $this->prepare_new_submission($id);
550 $newsubmission = true;
552 $newsubmission = false;
554 unset($submission->data1
); // Don't need to update this.
555 unset($submission->data2
); // Don't need to update this.
557 //for fast grade, we need to check if any changes take place
561 $grade = $_POST['menu'][$id];
562 $updatedb = $updatedb ||
($submission->grade
!= $grade);
563 $submission->grade
= $grade;
565 if (!$newsubmission) {
566 unset($submission->grade
); // Don't need to update this.
570 $commentvalue = trim($_POST['submissioncomment'][$id]);
571 $updatedb = $updatedb ||
($submission->submissioncomment
!= stripslashes($commentvalue));
572 $submission->submissioncomment
= $commentvalue;
574 unset($submission->submissioncomment
); // Don't need to update this.
577 $submission->teacher
= $USER->id
;
578 $submission->mailed
= $updatedb?
0:$submission->mailed
;//only change if it's an update
579 $submission->timemarked
= time();
581 //if it is not an update, we don't change the last modified time etc.
582 //this will also not write into database if no submissioncomment and grade is entered.
585 if ($newsubmission) {
586 if (!$sid = insert_record('assignment_submissions', $submission)) {
589 $submission->id
= $sid;
591 if (!update_record('assignment_submissions', $submission)) {
596 // triger grade event
597 $this->update_grade($submission);
599 //add to log only if updating
600 add_to_log($this->course
->id
, 'assignment', 'update grades',
601 'submissions.php?id='.$this->assignment
->id
.'&user='.$submission->userid
,
602 $submission->userid
, $this->cm
->id
);
607 $message = notify(get_string('changessaved'), 'notifysuccess', 'center', true);
609 $this->display_submissions($message);
614 /// We are currently in pop up, but we want to skip to next one without saving.
615 /// This turns out to be similar to a single case
616 /// The URL used is for the next submission.
618 $this->display_submission();
622 ///We are in pop up. save the current one and go to the next one.
623 //first we save the current changes
624 if ($submission = $this->process_feedback()) {
625 //print_heading(get_string('changessaved'));
626 $extra_javascript = $this->update_main_listing($submission);
629 //then we display the next submission
630 $this->display_submission($extra_javascript);
634 echo "something seriously is wrong!!";
640 * Helper method updating the listing on the main script from popup using javascript
642 * @param $submission object The submission whose data is to be updated on the main page
644 function update_main_listing($submission) {
645 global $SESSION, $CFG;
649 $perpage = get_user_preferences('assignment_perpage', 10);
651 $quickgrade = get_user_preferences('assignment_quickgrade', 0);
653 /// Run some Javascript to try and update the parent page
654 $output .= '<script type="text/javascript">'."\n<!--\n";
655 if (empty($SESSION->flextable
['mod-assignment-submissions']->collapse
['submissioncomment'])) {
657 $output.= 'opener.document.getElementById("submissioncomment'.$submission->userid
.'").value="'
658 .trim($submission->submissioncomment
).'";'."\n";
660 $output.= 'opener.document.getElementById("com'.$submission->userid
.
661 '").innerHTML="'.shorten_text(trim(strip_tags($submission->submissioncomment
)), 15)."\";\n";
665 if (empty($SESSION->flextable
['mod-assignment-submissions']->collapse
['grade'])) {
666 //echo optional_param('menuindex');
668 $output.= 'opener.document.getElementById("menumenu'.$submission->userid
.
669 '").selectedIndex="'.optional_param('menuindex', 0, PARAM_INT
).'";'."\n";
671 $output.= 'opener.document.getElementById("g'.$submission->userid
.'").innerHTML="'.
672 $this->display_grade($submission->grade
)."\";\n";
675 //need to add student's assignments in there too.
676 if (empty($SESSION->flextable
['mod-assignment-submissions']->collapse
['timemodified']) &&
677 $submission->timemodified
) {
678 $output.= 'opener.document.getElementById("ts'.$submission->userid
.
679 '").innerHTML="'.addslashes_js($this->print_student_answer($submission->userid
)).userdate($submission->timemodified
)."\";\n";
682 if (empty($SESSION->flextable
['mod-assignment-submissions']->collapse
['timemarked']) &&
683 $submission->timemarked
) {
684 $output.= 'opener.document.getElementById("tt'.$submission->userid
.
685 '").innerHTML="'.userdate($submission->timemarked
)."\";\n";
688 if (empty($SESSION->flextable
['mod-assignment-submissions']->collapse
['status'])) {
689 $output.= 'opener.document.getElementById("up'.$submission->userid
.'").className="s1";';
690 $buttontext = get_string('update');
691 $button = link_to_popup_window ('/mod/assignment/submissions.php?id='.$this->cm
->id
.'&userid='.$submission->userid
.'&mode=single'.'&offset='.(optional_param('offset', '', PARAM_INT
)-1),
692 'grade'.$submission->userid
, $buttontext, 450, 700, $buttontext, 'none', true, 'button'.$submission->userid
);
693 $output.= 'opener.document.getElementById("up'.$submission->userid
.'").innerHTML="'.addslashes_js($button).'";';
696 if (!empty($CFG->enableoutcomes
) and empty($SESSION->flextable
['mod-assignment-submissions']->collapse
['outcomes'])) {
697 if ($outcomes_data = grade_get_outcomes($this->course
->id
, 'mod', 'assignment', $this->assignment
->id
, $submission->userid
)) {
698 foreach($outcomes_data as $n=>$data) {
704 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid
.
705 '").selectedIndex="'.$data->grade
.'";'."\n";
707 $options = make_grades_menu(-$data->scaleid
);
708 $options[0] = get_string('nooutcome', 'grades');
709 $output.= 'opener.document.getElementById("outcome_'.$n.'_'.$submission->userid
.'").innerHTML="'.$options[$data->grade
]."\";\n";
717 $output .= "\n-->\n</script>";
722 * Return a grade in user-friendly form, whether it's a scale or not
725 * @return string User-friendly representation of grade
727 function display_grade($grade) {
729 static $scalegrades = array(); // Cache scales for each assignment - they might have different scales!!
731 if ($this->assignment
->grade
>= 0) { // Normal number
735 return $grade.' / '.$this->assignment
->grade
;
739 if (empty($scalegrades[$this->assignment
->id
])) {
740 if ($scale = get_record('scale', 'id', -($this->assignment
->grade
))) {
741 $scalegrades[$this->assignment
->id
] = make_menu_from_list($scale->scale
);
746 if (isset($scalegrades[$this->assignment
->id
][$grade])) {
747 return $scalegrades[$this->assignment
->id
][$grade];
754 * Display a single submission, ready for grading on a popup window
756 * This default method prints the teacher info and submissioncomment box at the top and
757 * the student info and submission at the bottom.
758 * This method also fetches the necessary data in order to be able to
759 * provide a "Next submission" button.
760 * Calls preprocess_submission() to give assignment type plug-ins a chance
761 * to process submissions before they are graded
762 * This method gets its arguments from the page parameters userid and offset
764 function display_submission($extra_javascript = '') {
767 require_once($CFG->libdir
.'/gradelib.php');
769 $userid = required_param('userid', PARAM_INT
);
770 $offset = required_param('offset', PARAM_INT
);//offset for where to start looking for student.
772 if (!$user = get_record('user', 'id', $userid)) {
773 error('No such user!');
776 if (!$submission = $this->get_submission($user->id
)) {
777 $submission = $this->prepare_new_submission($userid);
779 if ($submission->timemodified
> $submission->timemarked
) {
780 $subtype = 'assignmentnew';
782 $subtype = 'assignmentold';
785 /// construct SQL, using current offset to find the data of the next student
786 $course = $this->course
;
787 $assignment = $this->assignment
;
789 $context = get_context_instance(CONTEXT_MODULE
, $cm->id
);
791 /// Get all ppl that can submit assignments
793 $currentgroup = groups_get_activity_group($cm);
795 $users = get_users_by_capability($context, 'mod/assignment:submit', 'u.id, u.id', '', '', '', $currentgroup, '', false);
797 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture,
798 s.id AS submissionid, s.grade, s.submissioncomment,
799 s.timemodified, s.timemarked,
800 COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
801 $sql = 'FROM '.$CFG->prefix
.'user u '.
802 'LEFT JOIN '.$CFG->prefix
.'assignment_submissions s ON u.id = s.userid
803 AND s.assignment = '.$this->assignment
->id
.' '.
804 'WHERE u.id IN ('.implode(',', array_keys($users)).') ';
806 require_once($CFG->libdir
.'/tablelib.php');
808 if ($sort = flexible_table
::get_sql_sort('mod-assignment-submissions')) {
809 $sort = 'ORDER BY '.$sort.' ';
813 if (($auser = get_records_sql($select.$sql.$sort, $offset+
1, 1)) !== false) {
814 $nextuser = array_shift($auser);
815 /// Calculate user status
816 $nextuser->status
= ($nextuser->timemarked
> 0) && ($nextuser->timemarked
>= $nextuser->timemodified
);
817 $nextid = $nextuser->id
;
820 print_header(get_string('feedback', 'assignment').':'.fullname($user, true).':'.format_string($this->assignment
->name
));
822 /// Print any extra javascript needed for saveandnext
823 echo $extra_javascript;
825 ///SOme javascript to help with setting up >.>
827 echo '<script type="text/javascript">'."\n";
828 echo 'function setNext(){'."\n";
829 echo 'document.getElementById(\'submitform\').mode.value=\'next\';'."\n";
830 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
833 echo 'function saveNext(){'."\n";
834 echo 'document.getElementById(\'submitform\').mode.value=\'saveandnext\';'."\n";
835 echo 'document.getElementById(\'submitform\').userid.value="'.$nextid.'";'."\n";
836 echo 'document.getElementById(\'submitform\').saveuserid.value="'.$userid.'";'."\n";
837 echo 'document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex;'."\n";
840 echo '</script>'."\n";
841 echo '<table cellspacing="0" class="feedback '.$subtype.'" >';
843 ///Start of teacher info row
846 echo '<td class="picture teacher">';
847 if ($submission->teacher
) {
848 $teacher = get_record('user', 'id', $submission->teacher
);
853 print_user_picture($teacher->id
, $this->course
->id
, $teacher->picture
);
855 echo '<td class="content">';
856 echo '<form id="submitform" action="submissions.php" method="post">';
857 echo '<div>'; // xhtml compatibility - invisiblefieldset was breaking layout here
858 echo '<input type="hidden" name="offset" value="'.($offset+
1).'" />';
859 echo '<input type="hidden" name="userid" value="'.$userid.'" />';
860 echo '<input type="hidden" name="id" value="'.$this->cm
->id
.'" />';
861 echo '<input type="hidden" name="mode" value="grade" />';
862 echo '<input type="hidden" name="menuindex" value="0" />';//selected menu index
864 //new hidden field, initialized to -1.
865 echo '<input type="hidden" name="saveuserid" value="-1" />';
867 if ($submission->timemarked
) {
868 echo '<div class="from">';
869 echo '<div class="fullname">'.fullname($teacher, true).'</div>';
870 echo '<div class="time">'.userdate($submission->timemarked
).'</div>';
873 echo '<div class="grade"><label for="menugrade">'.get_string('grade').'</label> ';
874 choose_from_menu(make_grades_menu($this->assignment
->grade
), 'grade', $submission->grade
, get_string('nograde'), '', -1);
877 echo '<div class="clearer"></div>';
879 if (!empty($CFG->enableoutcomes
) and $outcomes_data = grade_get_outcomes($this->course
->id
, 'mod', 'assignment', $this->assignment
->id
, $userid)) {
880 foreach($outcomes_data as $n=>$data) {
881 echo '<div class="outcome"><label for="menuoutcome_'.$n.'">'.$data->name
.'</label> ';
882 $options = make_grades_menu(-$data->scaleid
);
884 $options[0] = get_string('nooutcome', 'grades');
885 echo $options[$data->grade
];
887 choose_from_menu($options, 'outcome_'.$n.'['.$userid.']', $data->grade
, get_string('nooutcome', 'grades'), '', 0, false, false, 0, 'menuoutcome_'.$n);
890 echo '<div class="clearer"></div>';
895 $this->preprocess_submission($submission);
897 print_textarea($this->usehtmleditor
, 14, 58, 0, 0, 'submissioncomment', $submission->submissioncomment
, $this->course
->id
);
899 if ($this->usehtmleditor
) {
900 echo '<input type="hidden" name="format" value="'.FORMAT_HTML
.'" />';
902 echo '<div class="format">';
903 choose_from_menu(format_text_menu(), "format", $submission->format
, "");
904 helpbutton("textformat", get_string("helpformatting"));
908 ///Print Buttons in Single View
909 echo '<div class="buttons">';
910 echo '<input type="submit" name="submit" value="'.get_string('savechanges').'" onclick = "document.getElementById(\'submitform\').menuindex.value = document.getElementById(\'submitform\').grade.selectedIndex" />';
911 echo '<input type="submit" name="cancel" value="'.get_string('cancel').'" />';
912 //if there are more to be graded.
914 echo '<input type="submit" name="saveandnext" value="'.get_string('saveandnext').'" onclick="saveNext()" />';
915 echo '<input type="submit" name="next" value="'.get_string('next').'" onclick="setNext();" />';
918 echo '</div></form>';
920 $customfeedback = $this->custom_feedbackform($submission, true);
921 if (!empty($customfeedback)) {
922 echo $customfeedback;
927 ///End of teacher info row, Start of student info row
929 echo '<td class="picture user">';
930 print_user_picture($user->id
, $this->course
->id
, $user->picture
);
932 echo '<td class="topic">';
933 echo '<div class="from">';
934 echo '<div class="fullname">'.fullname($user, true).'</div>';
935 if ($submission->timemodified
) {
936 echo '<div class="time">'.userdate($submission->timemodified
).
937 $this->display_lateness($submission->timemodified
).'</div>';
940 $this->print_user_files($user->id
);
944 ///End of student info row
948 if ($this->usehtmleditor
) {
952 print_footer('none');
956 * Preprocess submission before grading
958 * Called by display_submission()
959 * The default type does nothing here.
960 * @param $submission object The submission object
962 function preprocess_submission(&$submission) {
966 * Display all the submissions ready for grading
968 function display_submissions($message='') {
969 global $CFG, $db, $USER;
970 require_once($CFG->libdir
.'/gradelib.php');
972 /* first we check to see if the form has just been submitted
973 * to request user_preference updates
976 if (isset($_POST['updatepref'])){
977 $perpage = optional_param('perpage', 10, PARAM_INT
);
978 $perpage = ($perpage <= 0) ?
10 : $perpage ;
979 set_user_preference('assignment_perpage', $perpage);
980 set_user_preference('assignment_quickgrade', optional_param('quickgrade',0, PARAM_BOOL
));
983 /* next we get perpage and quickgrade (allow quick grade) params
986 $perpage = get_user_preferences('assignment_perpage', 10);
988 if ($this->lockedgrades
) {
991 $quickgrade = get_user_preferences('assignment_quickgrade', 0);
994 if (!empty($CFG->enableoutcomes
) and grade_get_outcomes($this->course
->id
, 'mod', 'assignment', $this->assignment
->id
)) {
995 $uses_outcomes = true;
997 $uses_outcomes = false;
1000 $teacherattempts = true; /// Temporary measure
1001 $page = optional_param('page', 0, PARAM_INT
);
1002 $strsaveallfeedback = get_string('saveallfeedback', 'assignment');
1004 /// Some shortcuts to make the code read better
1006 $course = $this->course
;
1007 $assignment = $this->assignment
;
1010 $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
1012 add_to_log($course->id
, 'assignment', 'view submission', 'submissions.php?id='.$this->assignment
->id
, $this->assignment
->id
, $this->cm
->id
);
1014 $navlinks = array();
1015 $navlinks[] = array('name' => $this->strassignments
, 'link' => "index.php?id=$course->id", 'type' => 'activity');
1016 $navlinks[] = array('name' => format_string($this->assignment
->name
,true),
1017 'link' => "view.php?a={$this->assignment->id}",
1018 'type' => 'activityinstance');
1019 $navlinks[] = array('name' => $this->strsubmissions
, 'link' => '', 'type' => 'title');
1020 $navigation = build_navigation($navlinks);
1022 print_header_simple(format_string($this->assignment
->name
,true), "", $navigation,
1023 '', '', true, update_module_button($cm->id
, $course->id
, $this->strassignment
), navmenu($course, $cm));
1025 if (!empty($message)) {
1026 echo $message; // display messages here if any
1029 $context = get_context_instance(CONTEXT_MODULE
, $cm->id
);
1031 /// find out current groups mode
1032 $groupmode = groups_get_activity_groupmode($cm);
1033 $currentgroup = groups_get_activity_group($cm, true);
1034 groups_print_activity_menu($cm, 'submissions.php?id=' . $this->cm
->id
);
1036 /// Get all ppl that are allowed to submit assignments
1037 $users = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', $currentgroup, '', false);
1038 $users = array_keys($users);
1040 if (!empty($CFG->enablegroupings
) && !empty($cm->groupingid
)) {
1041 $groupingusers = groups_get_grouping_members($cm->groupingid
, 'u.id', 'u.id');
1042 $users = array_intersect($users, array_keys($groupingusers));
1046 $tablecolumns = array('picture', 'fullname', 'grade', 'submissioncomment', 'timemodified', 'timemarked', 'status');
1047 if ($uses_outcomes) {
1048 $tablecolumns[] = ''; // no sorting based on outcomes column
1051 $tableheaders = array('',
1052 get_string('fullname'),
1053 get_string('grade'),
1054 get_string('comment', 'assignment'),
1055 get_string('lastmodified').' ('.$course->student
.')',
1056 get_string('lastmodified').' ('.$course->teacher
.')',
1057 get_string('status'));
1058 if ($uses_outcomes) {
1059 $tableheaders[] = get_string('outcomes', 'grades');
1062 require_once($CFG->libdir
.'/tablelib.php');
1063 $table = new flexible_table('mod-assignment-submissions');
1065 $table->define_columns($tablecolumns);
1066 $table->define_headers($tableheaders);
1067 $table->define_baseurl($CFG->wwwroot
.'/mod/assignment/submissions.php?id='.$this->cm
->id
.'&currentgroup='.$currentgroup);
1069 $table->sortable(true, 'lastname');//sorted by lastname by default
1070 $table->collapsible(true);
1071 $table->initialbars(true);
1073 $table->column_suppress('picture');
1074 $table->column_suppress('fullname');
1076 $table->column_class('picture', 'picture');
1077 $table->column_class('fullname', 'fullname');
1078 $table->column_class('grade', 'grade');
1079 $table->column_class('submissioncomment', 'comment');
1080 $table->column_class('timemodified', 'timemodified');
1081 $table->column_class('timemarked', 'timemarked');
1082 $table->column_class('status', 'status');
1083 if ($uses_outcomes) {
1084 $table->column_class('outcomes', 'outcomes');
1087 $table->set_attribute('cellspacing', '0');
1088 $table->set_attribute('id', 'attempts');
1089 $table->set_attribute('class', 'submissions');
1090 $table->set_attribute('width', '90%');
1091 //$table->set_attribute('align', 'center');
1093 // Start working -- this is necessary as soon as the niceties are over
1096 /// Check to see if groups are being used in this assignment
1098 if (!$teacherattempts) {
1099 $teachers = get_course_teachers($course->id
);
1100 if (!empty($teachers)) {
1101 $keys = array_keys($teachers);
1103 foreach ($keys as $key) {
1104 unset($users[$key]);
1108 if (empty($users)) {
1109 print_heading(get_string('noattempts','assignment'));
1113 /// Construct the SQL
1115 if ($where = $table->get_sql_where()) {
1119 if ($sort = $table->get_sql_sort()) {
1120 $sort = ' ORDER BY '.$sort;
1123 $select = 'SELECT u.id, u.firstname, u.lastname, u.picture,
1124 s.id AS submissionid, s.grade, s.submissioncomment,
1125 s.timemodified, s.timemarked,
1126 COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ';
1127 $sql = 'FROM '.$CFG->prefix
.'user u '.
1128 'LEFT JOIN '.$CFG->prefix
.'assignment_submissions s ON u.id = s.userid
1129 AND s.assignment = '.$this->assignment
->id
.' '.
1130 'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
1132 $table->pagesize($perpage, count($users));
1134 ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
1135 $offset = $page * $perpage;
1137 $strupdate = get_string('update');
1138 $strgrade = get_string('grade');
1139 $grademenu = make_grades_menu($this->assignment
->grade
);
1141 if (($ausers = get_records_sql($select.$sql.$sort, $table->get_page_start(), $table->get_page_size())) !== false) {
1143 foreach ($ausers as $auser) {
1144 /// Calculate user status
1145 $auser->status
= ($auser->timemarked
> 0) && ($auser->timemarked
>= $auser->timemodified
);
1146 $picture = print_user_picture($auser->id
, $course->id
, $auser->picture
, false, true);
1148 if (empty($auser->submissionid
)) {
1149 $auser->grade
= -1; //no submission yet
1152 if (!empty($auser->submissionid
)) {
1153 ///Prints student answer and student modified date
1154 ///attach file or print link to student answer, depending on the type of the assignment.
1155 ///Refer to print_student_answer in inherited classes.
1156 if ($auser->timemodified
> 0) {
1157 $studentmodified = '<div id="ts'.$auser->id
.'">'.$this->print_student_answer($auser->id
)
1158 . userdate($auser->timemodified
).'</div>';
1160 $studentmodified = '<div id="ts'.$auser->id
.'"> </div>';
1162 ///Print grade, dropdown or text
1163 if ($auser->timemarked
> 0) {
1164 $teachermodified = '<div id="tt'.$auser->id
.'">'.userdate($auser->timemarked
).'</div>';
1167 $menu = choose_from_menu(make_grades_menu($this->assignment
->grade
),
1168 'menu['.$auser->id
.']', $auser->grade
,
1169 get_string('nograde'),'',-1,true,false,$tabindex++
);
1170 $grade = '<div id="g'.$auser->id
.'">'. $menu .'</div>';
1172 $grade = '<div id="g'.$auser->id
.'">'.$this->display_grade($auser->grade
).'</div>';
1176 $teachermodified = '<div id="tt'.$auser->id
.'"> </div>';
1178 $menu = choose_from_menu(make_grades_menu($this->assignment
->grade
),
1179 'menu['.$auser->id
.']', $auser->grade
,
1180 get_string('nograde'),'',-1,true,false,$tabindex++
);
1181 $grade = '<div id="g'.$auser->id
.'">'.$menu.'</div>';
1183 $grade = '<div id="g'.$auser->id
.'">'.$this->display_grade($auser->grade
).'</div>';
1188 $comment = '<div id="com'.$auser->id
.'">'
1189 . '<textarea tabindex="'.$tabindex++
.'" name="submissioncomment['.$auser->id
.']" id="submissioncomment'
1190 . $auser->id
.'" rows="2" cols="20">'.($auser->submissioncomment
).'</textarea></div>';
1192 $comment = '<div id="com'.$auser->id
.'">'.shorten_text(strip_tags($auser->submissioncomment
),15).'</div>';
1195 $studentmodified = '<div id="ts'.$auser->id
.'"> </div>';
1196 $teachermodified = '<div id="tt'.$auser->id
.'"> </div>';
1197 $status = '<div id="st'.$auser->id
.'"> </div>';
1199 if ($quickgrade) { // allow editing
1200 $menu = choose_from_menu(make_grades_menu($this->assignment
->grade
),
1201 'menu['.$auser->id
.']', $auser->grade
,
1202 get_string('nograde'),'',-1,true,false,$tabindex++
);
1203 $grade = '<div id="g'.$auser->id
.'">'.$menu.'</div>';
1205 $grade = '<div id="g'.$auser->id
.'">-</div>';
1209 $comment = '<div id="com'.$auser->id
.'">'
1210 . '<textarea tabindex="'.$tabindex++
.'" name="submissioncomment['.$auser->id
.']" id="submissioncomment'
1211 . $auser->id
.'" rows="2" cols="20">'.($auser->submissioncomment
).'</textarea></div>';
1213 $comment = '<div id="com'.$auser->id
.'"> </div>';
1217 if (empty($auser->status
)) { /// Confirm we have exclusively 0 or 1
1223 $buttontext = ($auser->status
== 1) ?
$strupdate : $strgrade;
1225 if ($this->lockedgrades
) {
1226 $status = get_string('gradeitemlocked', 'grades');
1228 ///No more buttons, we use popups ;-).
1229 $popup_url = '/mod/assignment/submissions.php?id='.$this->cm
->id
1230 . '&userid='.$auser->id
.'&mode=single'.'&offset='.$offset++
;
1231 $button = link_to_popup_window ($popup_url, 'grade'.$auser->id
, $buttontext, 600, 780,
1232 $buttontext, 'none', true, 'button'.$auser->id
);
1234 $status = '<div id="up'.$auser->id
.'" class="s'.$auser->status
.'">'.$button.'</div>';
1239 if ($uses_outcomes and $outcomes_data = grade_get_outcomes($this->course
->id
, 'mod', 'assignment', $this->assignment
->id
, $auser->id
)) {
1241 foreach($outcomes_data as $n=>$data) {
1242 $outcomes .= '<div class="outcome"><label>'.$data->name
.'</label>';
1243 $options = make_grades_menu(-$data->scaleid
);
1245 if ($data->locked
or !$quickgrade) {
1246 $options[0] = get_string('nooutcome', 'grades');
1247 $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id
.'">'.$options[$data->grade
].'</span>';
1250 $outcomes .= choose_from_menu($options, 'outcome_'.$n.'['.$auser->id
.']',
1251 $data->grade
, get_string('nooutcome', 'grades'), '', 0, true, false, 0, 'outcome_'.$n.'_'.$auser->id
);
1253 $outcomes .= '</div>';
1258 $row = array($picture, fullname($auser), $grade, $comment, $studentmodified, $teachermodified, $status);
1259 if ($uses_outcomes) {
1263 $table->add_data($row);
1267 /// Print quickgrade form around the table
1269 echo '<form action="submissions.php" id="fastg" method="post">';
1271 echo '<input type="hidden" name="id" value="'.$this->cm
->id
.'" />';
1272 echo '<input type="hidden" name="mode" value="fastgrade" />';
1273 echo '<input type="hidden" name="page" value="'.$page.'" />';
1275 //echo '<div style="text-align:center"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
1278 $table->print_html(); /// Print the whole table
1281 echo '<div style="text-align:center"><input type="submit" name="fastg" value="'.get_string('saveallfeedback', 'assignment').'" /></div>';
1284 /// End of fast grading form
1286 /// Mini form for setting user preference
1288 echo '<form id="options" action="submissions.php?id='.$this->cm
->id
.'" method="post">';
1289 echo '<fieldset class="invisiblefieldset">';
1290 echo '<input type="hidden" id="updatepref" name="updatepref" value="1" />';
1291 echo '<table id="optiontable">';
1292 echo '<tr align="right"><td>';
1293 echo '<label for="perpage">'.get_string('pagesize','assignment').'</label>';
1295 echo '<td align="left">';
1296 echo '<input type="text" id="perpage" name="perpage" size="1" value="'.$perpage.'" />';
1297 helpbutton('pagesize', get_string('pagesize','assignment'), 'assignment');
1299 if (!$this->lockedgrades
) {
1300 echo '<tr align="right">';
1302 print_string('quickgrade','assignment');
1304 echo '<td align="left">';
1306 echo '<input type="checkbox" name="quickgrade" value="1" checked="checked" />';
1308 echo '<input type="checkbox" name="quickgrade" value="1" />';
1310 helpbutton('quickgrade', get_string('quickgrade', 'assignment'), 'assignment').'</p></div>';
1314 echo '<td colspan="2" align="right">';
1315 echo '<input type="submit" value="'.get_string('savepreferences').'" />';
1316 echo '</td></tr></table>';
1320 print_footer($this->course
);
1324 * Process teacher feedback submission
1326 * This is called by submissions() when a grading even has taken place.
1327 * It gets its data from the submitted form.
1328 * @return object The updated submission object
1330 function process_feedback() {
1334 if (!$feedback = data_submitted()) { // No incoming data?
1338 ///For save and next, we need to know the userid to save, and the userid to go
1339 ///We use a new hidden field in the form, and set it to -1. If it's set, we use this
1340 ///as the userid to store
1341 if ((int)$feedback->saveuserid
!== -1){
1342 $feedback->userid
= $feedback->saveuserid
;
1345 if (!empty($feedback->cancel
)) { // User hit cancel button
1349 // store outcomes if needed
1350 $this->process_outcomes($feedback->userid
);
1352 $submission = $this->get_submission($feedback->userid
, true); // Get or make one
1354 $submission->grade
= $feedback->grade
;
1355 $submission->submissioncomment
= $feedback->submissioncomment
;
1356 $submission->format
= $feedback->format
;
1357 $submission->teacher
= $USER->id
;
1358 $submission->mailed
= 0; // Make sure mail goes out (again, even)
1359 $submission->timemarked
= time();
1361 unset($submission->data1
); // Don't need to update this.
1362 unset($submission->data2
); // Don't need to update this.
1364 if (empty($submission->timemodified
)) { // eg for offline assignments
1365 // $submission->timemodified = time();
1368 if (! update_record('assignment_submissions', $submission)) {
1372 // triger grade event
1373 $this->update_grade($submission);
1375 add_to_log($this->course
->id
, 'assignment', 'update grades',
1376 'submissions.php?id='.$this->assignment
->id
.'&user='.$feedback->userid
, $feedback->userid
, $this->cm
->id
);
1382 function process_outcomes($userid) {
1385 if (empty($CFG->enableoutcomes
)) {
1389 require_once($CFG->libdir
.'/gradelib.php');
1391 if (!$formdata = data_submitted()) {
1396 if ($outcomes = grade_get_outcomes($this->course
->id
, 'mod', 'assignment', $this->assignment
->id
, $userid)) {
1397 foreach($outcomes as $n=>$old) {
1398 $name = 'outcome_'.$n;
1399 if (isset($formdata->{$name}[$userid]) and $old->grade
!= $formdata->{$name}[$userid]) {
1400 $data[$n] = $formdata->{$name}[$userid];
1404 if (count($data) > 0) {
1405 grade_update_outcomes('mod/assignment', $this->course
->id
, 'mod', 'assignment', $this->assignment
->id
, $userid, $data);
1411 * Load the submission object for a particular user
1413 * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
1414 * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
1415 * @return object The submission
1417 function get_submission($userid=0, $createnew=false) {
1420 if (empty($userid)) {
1421 $userid = $USER->id
;
1424 $submission = get_record('assignment_submissions', 'assignment', $this->assignment
->id
, 'userid', $userid);
1426 if ($submission ||
!$createnew) {
1429 $newsubmission = $this->prepare_new_submission($userid);
1430 if (!insert_record("assignment_submissions", $newsubmission)) {
1431 error("Could not insert a new empty submission");
1434 return get_record('assignment_submissions', 'assignment', $this->assignment
->id
, 'userid', $userid);
1438 * Instantiates a new submission object for a given user
1440 * Sets the assignment, userid and times, everything else is set to default values.
1441 * @param $userid int The userid for which we want a submission object
1442 * @return object The submission
1444 function prepare_new_submission($userid) {
1445 $submission = new Object;
1446 $submission->assignment
= $this->assignment
->id
;
1447 $submission->userid
= $userid;
1448 //$submission->timecreated = time();
1449 $submission->timecreated
= '';
1450 // teachers should not be modifying modified date, except offline assignments
1451 $submission->timemodified
= $submission->timecreated
;
1452 $submission->numfiles
= 0;
1453 $submission->data1
= '';
1454 $submission->data2
= '';
1455 $submission->grade
= -1;
1456 $submission->submissioncomment
= '';
1457 $submission->format
= 0;
1458 $submission->teacher
= 0;
1459 $submission->timemarked
= 0;
1460 $submission->mailed
= 0;
1465 * Return all assignment submissions by ENROLLED students (even empty)
1467 * @param $sort string optional field names for the ORDER BY in the sql query
1468 * @param $dir string optional specifying the sort direction, defaults to DESC
1469 * @return array The submission objects indexed by id
1471 function get_submissions($sort='', $dir='DESC') {
1472 return assignment_get_all_submissions($this->assignment
, $sort, $dir);
1476 * Counts all real assignment submissions by ENROLLED students (not empty ones)
1478 * @param $groupid int optional If nonzero then count is restricted to this group
1479 * @return int The number of submissions
1481 function count_real_submissions($groupid=0) {
1482 return assignment_count_real_submissions($this->assignment
, $groupid);
1486 * Alerts teachers by email of new or changed assignments that need grading
1488 * First checks whether the option to email teachers is set for this assignment.
1489 * Sends an email to ALL teachers in the course (or in the group if using separate groups).
1490 * Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
1491 * @param $submission object The submission that has changed
1493 function email_teachers($submission) {
1496 if (empty($this->assignment
->emailteachers
)) { // No need to do anything
1500 $user = get_record('user', 'id', $submission->userid
);
1502 if ($teachers = $this->get_graders($user)) {
1504 $strassignments = get_string('modulenameplural', 'assignment');
1505 $strassignment = get_string('modulename', 'assignment');
1506 $strsubmitted = get_string('submitted', 'assignment');
1508 foreach ($teachers as $teacher) {
1509 $info = new object();
1510 $info->username
= fullname($user, true);
1511 $info->assignment
= format_string($this->assignment
->name
,true);
1512 $info->url
= $CFG->wwwroot
.'/mod/assignment/submissions.php?id='.$this->cm
->id
;
1514 $postsubject = $strsubmitted.': '.$info->username
.' -> '.$this->assignment
->name
;
1515 $posttext = $this->email_teachers_text($info);
1516 $posthtml = ($teacher->mailformat
== 1) ?
$this->email_teachers_html($info) : '';
1518 @email_to_user
($teacher, $user, $postsubject, $posttext, $posthtml); // If it fails, oh well, too bad.
1524 * Returns a list of teachers that should be grading given submission
1526 function get_graders($user) {
1528 $potgraders = get_users_by_capability($this->context
, 'mod/assignment:grade', '', '', '', '', '', '', false, false);
1531 if (groups_get_activity_groupmode($this->cm
) == SEPARATEGROUPS
) { // Separate groups are being used
1532 if ($groups = groups_get_all_groups($this->course
->id
, $user->id
)) { // Try to find all groups
1533 foreach ($groups as $group) {
1534 foreach ($potgraders as $t) {
1535 if ($t->id
== $user->id
) {
1536 continue; // do not send self
1538 if (groups_is_member($group->id
, $t->id
)) {
1539 $graders[$t->id
] = $t;
1544 // user not in group, try to find graders without group
1545 foreach ($potgraders as $t) {
1546 if ($t->id
== $user->id
) {
1547 continue; // do not send self
1549 if (!groups_get_all_groups($this->course
->id
, $t->id
)) { //ugly hack
1550 $graders[$t->id
] = $t;
1555 foreach ($potgraders as $t) {
1556 if ($t->id
== $user->id
) {
1557 continue; // do not send self
1559 $graders[$t->id
] = $t;
1566 * Creates the text content for emails to teachers
1568 * @param $info object The info used by the 'emailteachermail' language string
1571 function email_teachers_text($info) {
1572 $posttext = format_string($this->course
->shortname
).' -> '.$this->strassignments
.' -> '.
1573 format_string($this->assignment
->name
)."\n";
1574 $posttext .= '---------------------------------------------------------------------'."\n";
1575 $posttext .= get_string("emailteachermail", "assignment", $info)."\n";
1576 $posttext .= "\n---------------------------------------------------------------------\n";
1581 * Creates the html content for emails to teachers
1583 * @param $info object The info used by the 'emailteachermailhtml' language string
1586 function email_teachers_html($info) {
1588 $posthtml = '<p><font face="sans-serif">'.
1589 '<a href="'.$CFG->wwwroot
.'/course/view.php?id='.$this->course
->id
.'">'.format_string($this->course
->shortname
).'</a> ->'.
1590 '<a href="'.$CFG->wwwroot
.'/mod/assignment/index.php?id='.$this->course
->id
.'">'.$this->strassignments
.'</a> ->'.
1591 '<a href="'.$CFG->wwwroot
.'/mod/assignment/view.php?id='.$this->cm
->id
.'">'.format_string($this->assignment
->name
).'</a></font></p>';
1592 $posthtml .= '<hr /><font face="sans-serif">';
1593 $posthtml .= '<p>'.get_string('emailteachermailhtml', 'assignment', $info).'</p>';
1594 $posthtml .= '</font><hr />';
1599 * Produces a list of links to the files uploaded by a user
1601 * @param $userid int optional id of the user. If 0 then $USER->id is used.
1602 * @param $return boolean optional defaults to false. If true the list is returned rather than printed
1603 * @return string optional
1605 function print_user_files($userid=0, $return=false) {
1609 if (!isloggedin()) {
1612 $userid = $USER->id
;
1615 $filearea = $this->file_area_name($userid);
1619 if ($basedir = $this->file_area($userid)) {
1620 if ($files = get_directory_list($basedir)) {
1621 require_once($CFG->libdir
.'/filelib.php');
1622 foreach ($files as $key => $file) {
1624 $icon = mimeinfo('icon', $file);
1626 if ($CFG->slasharguments
) {
1627 $ffurl = "$CFG->wwwroot/file.php/$filearea/$file";
1629 $ffurl = "$CFG->wwwroot/file.php?file=/$filearea/$file";
1632 $output .= '<img align="middle" src="'.$CFG->pixpath
.'/f/'.$icon.'" class="icon" alt="'.$icon.'" />'.
1633 '<a href="'.$ffurl.'" >'.$file.'</a><br />';
1638 $output = '<div class="files">'.$output.'</div>';
1647 * Count the files uploaded by a given user
1649 * @param $userid int The user id
1652 function count_user_files($userid) {
1655 $filearea = $this->file_area_name($userid);
1657 if ( is_dir($CFG->dataroot
.'/'.$filearea) && $basedir = $this->file_area($userid)) {
1658 if ($files = get_directory_list($basedir)) {
1659 return count($files);
1666 * Creates a directory file name, suitable for make_upload_directory()
1668 * @param $userid int The user id
1669 * @return string path to file area
1671 function file_area_name($userid) {
1674 return $this->course
->id
.'/'.$CFG->moddata
.'/assignment/'.$this->assignment
->id
.'/'.$userid;
1678 * Makes an upload directory
1680 * @param $userid int The user id
1681 * @return string path to file area.
1683 function file_area($userid) {
1684 return make_upload_directory( $this->file_area_name($userid) );
1688 * Returns true if the student is allowed to submit
1690 * Checks that the assignment has started and, if the option to prevent late
1691 * submissions is set, also checks that the assignment has not yet closed.
1696 if ($this->assignment
->preventlate
&& $this->assignment
->timedue
) {
1697 return ($this->assignment
->timeavailable
<= $time && $time <= $this->assignment
->timedue
);
1699 return ($this->assignment
->timeavailable
<= $time);
1704 * Return an outline of the user's interaction with the assignment
1706 * The default method prints the grade and timemodified
1707 * @param $user object
1708 * @return object with properties ->info and ->time
1710 function user_outline($user) {
1711 if ($submission = $this->get_submission($user->id
)) {
1713 $result = new object();
1714 $result->info
= get_string('grade').': '.$this->display_grade($submission->grade
);
1715 $result->time
= $submission->timemodified
;
1722 * Print complete information about the user's interaction with the assignment
1724 * @param $user object
1726 function user_complete($user) {
1727 if ($submission = $this->get_submission($user->id
)) {
1728 if ($basedir = $this->file_area($user->id
)) {
1729 if ($files = get_directory_list($basedir)) {
1730 $countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
1731 foreach ($files as $file) {
1732 $countfiles .= "; $file";
1737 print_simple_box_start();
1738 echo get_string("lastmodified").": ";
1739 echo userdate($submission->timemodified
);
1740 echo $this->display_lateness($submission->timemodified
);
1742 $this->print_user_files($user->id
);
1746 if (empty($submission->timemarked
)) {
1747 print_string("notgradedyet", "assignment");
1749 $this->view_feedback($submission);
1752 print_simple_box_end();
1755 print_string("notsubmittedyet", "assignment");
1760 * Return a string indicating how late a submission is
1762 * @param $timesubmitted int
1765 function display_lateness($timesubmitted) {
1766 return assignment_display_lateness($timesubmitted, $this->assignment
->timedue
);
1770 * Empty method stub for all delete actions.
1773 //nothing by default
1774 redirect('view.php?id='.$this->cm
->id
);
1778 * Empty custom feedback grading form.
1780 function custom_feedbackform($submission, $return=false) {
1781 //nothing by default
1786 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
1787 * for the course (see resource).
1789 * Given a course_module object, this function returns any "extra" information that may be needed
1790 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
1792 * @param $coursemodule object The coursemodule object (record).
1793 * @return object An object on information that the coures will know about (most noticeably, an icon).
1796 function get_coursemodule_info($coursemodule) {
1801 * Plugin cron method - do not use $this here, create new assignment instances if needed.
1805 //no plugin cron by default - override if needed
1808 } ////// End of the assignment_base class
1812 /// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
1815 * Deletes an assignment instance
1817 * This is done by calling the delete_instance() method of the assignment type class
1819 function assignment_delete_instance($id){
1822 if (! $assignment = get_record('assignment', 'id', $id)) {
1826 // fall back to base class if plugin missing
1827 $classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
1828 if (file_exists($classfile)) {
1829 require_once($classfile);
1830 $assignmentclass = "assignment_$assignment->assignmenttype";
1833 debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
1834 $assignmentclass = "assignment_base";
1837 $ass = new $assignmentclass();
1838 return $ass->delete_instance($assignment);
1843 * Updates an assignment instance
1845 * This is done by calling the update_instance() method of the assignment type class
1847 function assignment_update_instance($assignment){
1850 $assignment->assignmenttype
= clean_param($assignment->assignmenttype
, PARAM_SAFEDIR
);
1852 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1853 $assignmentclass = "assignment_$assignment->assignmenttype";
1854 $ass = new $assignmentclass();
1855 return $ass->update_instance($assignment);
1860 * Adds an assignment instance
1862 * This is done by calling the add_instance() method of the assignment type class
1864 function assignment_add_instance($assignment) {
1867 $assignment->assignmenttype
= clean_param($assignment->assignmenttype
, PARAM_SAFEDIR
);
1869 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1870 $assignmentclass = "assignment_$assignment->assignmenttype";
1871 $ass = new $assignmentclass();
1872 return $ass->add_instance($assignment);
1877 * Returns an outline of a user interaction with an assignment
1879 * This is done by calling the user_outline() method of the assignment type class
1881 function assignment_user_outline($course, $user, $mod, $assignment) {
1884 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1885 $assignmentclass = "assignment_$assignment->assignmenttype";
1886 $ass = new $assignmentclass($mod->id
, $assignment, $mod, $course);
1887 return $ass->user_outline($user);
1891 * Prints the complete info about a user's interaction with an assignment
1893 * This is done by calling the user_complete() method of the assignment type class
1895 function assignment_user_complete($course, $user, $mod, $assignment) {
1898 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
1899 $assignmentclass = "assignment_$assignment->assignmenttype";
1900 $ass = new $assignmentclass($mod->id
, $assignment, $mod, $course);
1901 return $ass->user_complete($user);
1905 * Function to be run periodically according to the moodle cron
1907 * Finds all assignment notifications that have yet to be mailed out, and mails them
1909 function assignment_cron () {
1913 /// first execute all crons in plugins
1914 if ($plugins = get_list_of_plugins('mod/assignment/type')) {
1915 foreach ($plugins as $plugin) {
1916 require_once("$CFG->dirroot/mod/assignment/type/$plugin/assignment.class.php");
1917 $assignmentclass = "assignment_$plugin";
1918 $ass = new $assignmentclass();
1923 /// Notices older than 1 day will not be mailed. This is to avoid the problem where
1924 /// cron has not been running for a long time, and then suddenly people are flooded
1925 /// with mail from the past few weeks or months
1928 $endtime = $timenow - $CFG->maxeditingtime
;
1929 $starttime = $endtime - 24 * 3600; /// One day earlier
1931 if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
1933 $CFG->enablerecordcache
= true; // We want all the caching we can get
1935 $realuser = clone($USER);
1937 foreach ($submissions as $key => $submission) {
1938 if (! set_field("assignment_submissions", "mailed", "1", "id", "$submission->id")) {
1939 echo "Could not update the mailed field for id $submission->id. Not mailed.\n";
1940 unset($submissions[$key]);
1946 foreach ($submissions as $submission) {
1948 echo "Processing assignment submission $submission->id\n";
1950 if (! $user = get_record("user", "id", "$submission->userid")) {
1951 echo "Could not find user $post->userid\n";
1955 if (! $course = get_record("course", "id", "$submission->course")) {
1956 echo "Could not find course $submission->course\n";
1960 /// Override the language and timezone of the "current" user, so that
1961 /// mail is customised for the receiver.
1963 course_setup($course);
1965 if (!has_capability('moodle/course:view', get_context_instance(CONTEXT_COURSE
, $submission->course
), $user->id
)) {
1966 echo fullname($user)." not an active participant in " . format_string($course->shortname
) . "\n";
1970 if (! $teacher = get_record("user", "id", "$submission->teacher")) {
1971 echo "Could not find teacher $submission->teacher\n";
1975 if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment
, $course->id
)) {
1976 echo "Could not find course module for assignment id $submission->assignment\n";
1980 if (! $mod->visible
) { /// Hold mail notification for hidden assignments until later
1984 $strassignments = get_string("modulenameplural", "assignment");
1985 $strassignment = get_string("modulename", "assignment");
1987 $assignmentinfo = new object();
1988 $assignmentinfo->teacher
= fullname($teacher);
1989 $assignmentinfo->assignment
= format_string($submission->name
,true);
1990 $assignmentinfo->url
= "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
1992 $postsubject = "$course->shortname: $strassignments: ".format_string($submission->name
,true);
1993 $posttext = "$course->shortname -> $strassignments -> ".format_string($submission->name
,true)."\n";
1994 $posttext .= "---------------------------------------------------------------------\n";
1995 $posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
1996 $posttext .= "---------------------------------------------------------------------\n";
1998 if ($user->mailformat
== 1) { // HTML
1999 $posthtml = "<p><font face=\"sans-serif\">".
2000 "<a href=\"$CFG->wwwroot/course/view.php?id=$course->id\">$course->shortname</a> ->".
2001 "<a href=\"$CFG->wwwroot/mod/assignment/index.php?id=$course->id\">$strassignments</a> ->".
2002 "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=$mod->id\">".format_string($submission->name
,true)."</a></font></p>";
2003 $posthtml .= "<hr /><font face=\"sans-serif\">";
2004 $posthtml .= "<p>".get_string("assignmentmailhtml", "assignment", $assignmentinfo)."</p>";
2005 $posthtml .= "</font><hr />";
2010 if (! email_to_user($user, $teacher, $postsubject, $posttext, $posthtml)) {
2011 echo "Error: assignment cron: Could not send out mail for id $submission->id to user $user->id ($user->email)\n";
2016 course_setup(SITEID
); // reset cron user language, theme and timezone settings
2024 * Return grade for given user or all users.
2026 * @param int $assignmentid id of assignment
2027 * @param int $userid optional user id, 0 means all users
2028 * @return array array of grades, false if none
2030 function assignment_get_user_grades($assignment, $userid=0) {
2033 $user = $userid ?
"AND u.id = $userid" : "";
2035 $sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat
2036 FROM {$CFG->prefix}user u, {$CFG->prefix}assignment_submissions s
2037 WHERE u.id = s.userid AND s.assignment = $assignment->id
2040 return get_records_sql($sql);
2044 * Update grades by firing grade_updated event
2046 * @param object $assignment null means all assignments
2047 * @param int $userid specific user only, 0 mean all
2049 function assignment_update_grades($assignment=null, $userid=0, $nullifnone=true) {
2051 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
2052 require_once($CFG->libdir
.'/gradelib.php');
2055 if ($assignment != null) {
2056 if ($grades = assignment_get_user_grades($assignment, $userid)) {
2057 foreach($grades as $k=>$v) {
2058 if ($v->rawgrade
== -1) {
2059 $grades[$k]->rawgrade
= null;
2062 assignment_grade_item_update($assignment);
2063 grade_update('mod/assignment', $assignment->courseid
, 'mod', 'assignment', $assignment->id
, 0, $grades);
2067 $sql = "SELECT a.*, cm.idnumber as cmidnumber, a.course as courseid
2068 FROM {$CFG->prefix}assignment a, {$CFG->prefix}course_modules cm, {$CFG->prefix}modules m
2069 WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
2070 if ($rs = get_recordset_sql($sql)) {
2071 if ($rs->RecordCount() > 0) {
2072 while ($assignment = rs_fetch_next_record($rs)) {
2073 assignment_grade_item_update($assignment);
2074 if ($assignment->grade
!= 0) {
2075 assignment_update_grades($assignment);
2085 * Create grade item for given assignment
2087 * @param object $assignment object with extra cmidnumber
2088 * @return int 0 if ok, error code otherwise
2090 function assignment_grade_item_update($assignment) {
2092 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
2093 require_once($CFG->libdir
.'/gradelib.php');
2096 if (!isset($assignment->courseid
)) {
2097 $assignment->courseid
= $assignment->course
;
2100 $params = array('itemname'=>$assignment->name
, 'idnumber'=>$assignment->cmidnumber
);
2102 if ($assignment->grade
> 0) {
2103 $params['gradetype'] = GRADE_TYPE_VALUE
;
2104 $params['grademax'] = $assignment->grade
;
2105 $params['grademin'] = 0;
2107 } else if ($assignment->grade
< 0) {
2108 $params['gradetype'] = GRADE_TYPE_SCALE
;
2109 $params['scaleid'] = -$assignment->grade
;
2112 $params['gradetype'] = GRADE_TYPE_NONE
;
2115 return grade_update('mod/assignment', $assignment->courseid
, 'mod', 'assignment', $assignment->id
, 0, NULL, $params);
2119 * Delete grade item for given assignment
2121 * @param object $assignment object
2122 * @return object assignment
2124 function assignment_grade_item_delete($assignment) {
2126 require_once($CFG->libdir
.'/gradelib.php');
2128 if (!isset($assignment->courseid
)) {
2129 $assignment->courseid
= $assignment->course
;
2132 return grade_update('mod/assignment', $assignment->courseid
, 'mod', 'assignment', $assignment->id
, 0, NULL, array('deleted'=>1));
2136 * Gradebook informs this module about new grade or feedback.
2139 function assignment_grade_updated($instance, $itemnumber, $userid, $gradevalue, $feedback, $feedbackformat, $usermodified) {
2142 if (!$assignment = get_record('assignment', 'id', $instance)) {
2145 if (! $course = get_record('course', 'id', $assignment->course
)) {
2148 if (! $cm = get_coursemodule_from_instance('assignment', $assignment->id
, $course->id
)) {
2152 // Load up the required assignment class
2153 require_once($CFG->dirroot
.'/mod/assignment/type/'.$assignment->assignmenttype
.'/assignment.class.php');
2154 $assignmentclass = 'assignment_'.$assignment->assignmenttype
;
2155 $assignmentinstance = new $assignmentclass($cm->id
, $assignment, $cm, $course);
2157 $old = $assignmentinstance->get_submission($userid, true); // Get or make one
2158 $submission = new object();
2159 $submission->id
= $old->id
;
2160 $submission->userid
= $old->userid
;
2161 $submission->teacher
= $usermodified;
2163 if ($gradevalue === false) {
2164 $submission->grade
= $old->grade
;
2165 } else if (is_null($gradevalue)) {
2166 $submission->grade
= -1;
2168 $submission->grade
= (int)$gradevalue; // round it for now
2169 $submission->timemarked
= time();
2172 if ($feedback === false) {
2173 $submission->submissioncomment
= addslashes($old->submissioncomment
);
2174 $submission->format
= $old->format
;
2176 $submission->submissioncomment
= addslashes($feedback);
2177 $submission->format
= (int)$feedbackformat;
2180 if ($old->submissioncomment
!= $submission->submissioncomment
or $old->grade
!= $submission->grade
) {
2182 $submission->mailed
= 0; // Make sure mail goes out (again, even)
2184 if (!update_record('assignment_submissions', $submission)) {
2189 // TODO: add proper logging
2190 add_to_log($course->id
, 'assignment', 'update grades',
2191 'submissions.php?id='.$assignment->id
.'&user='.$submission->userid
, $submission->userid
, $cm->id
);
2197 * Returns the users with data in one assignment (students and teachers)
2199 * @param $assignmentid int
2200 * @return array of user objects
2202 function assignment_get_participants($assignmentid) {
2207 $students = get_records_sql("SELECT DISTINCT u.id, u.id
2208 FROM {$CFG->prefix}user u,
2209 {$CFG->prefix}assignment_submissions a
2210 WHERE a.assignment = '$assignmentid' and
2213 $teachers = get_records_sql("SELECT DISTINCT u.id, u.id
2214 FROM {$CFG->prefix}user u,
2215 {$CFG->prefix}assignment_submissions a
2216 WHERE a.assignment = '$assignmentid' and
2219 //Add teachers to students
2221 foreach ($teachers as $teacher) {
2222 $students[$teacher->id
] = $teacher;
2225 //Return students array (it contains an array of unique users)
2230 * Checks if a scale is being used by an assignment
2232 * This is used by the backup code to decide whether to back up a scale
2233 * @param $assignmentid int
2234 * @param $scaleid int
2235 * @return boolean True if the scale is used by the assignment
2237 function assignment_scale_used ($assignmentid, $scaleid) {
2241 $rec = get_record('assignment','id',$assignmentid,'grade',-$scaleid);
2243 if (!empty($rec) && !empty($scaleid)) {
2251 * Make sure up-to-date events are created for all assignment instances
2253 * This standard function will check all instances of this module
2254 * and make sure there are up-to-date events created for each of them.
2255 * If courseid = 0, then every assignment event in the site is checked, else
2256 * only assignment events belonging to the course specified are checked.
2257 * This function is used, in its new format, by restore_refresh_events()
2259 * @param $courseid int optional If zero then all assignments for all courses are covered
2260 * @return boolean Always returns true
2262 function assignment_refresh_events($courseid = 0) {
2264 if ($courseid == 0) {
2265 if (! $assignments = get_records("assignment")) {
2269 if (! $assignments = get_records("assignment", "course", $courseid)) {
2273 $moduleid = get_field('modules', 'id', 'name', 'assignment');
2275 foreach ($assignments as $assignment) {
2277 $event->name
= addslashes($assignment->name
);
2278 $event->description
= addslashes($assignment->description
);
2279 $event->timestart
= $assignment->timedue
;
2281 if ($event->id
= get_field('event', 'id', 'modulename', 'assignment', 'instance', $assignment->id
)) {
2282 update_event($event);
2285 $event->courseid
= $assignment->course
;
2286 $event->groupid
= 0;
2288 $event->modulename
= 'assignment';
2289 $event->instance
= $assignment->id
;
2290 $event->eventtype
= 'due';
2291 $event->timeduration
= 0;
2292 $event->visible
= get_field('course_modules', 'visible', 'module', $moduleid, 'instance', $assignment->id
);
2301 * Print recent activity from all assignments in a given course
2303 * This is used by the recent activity block
2305 function assignment_print_recent_activity($course, $isteacher, $timestart) {
2309 $assignments = array();
2311 if (!$logs = get_records_select('log', 'time > \''.$timestart.'\' AND '.
2312 'course = \''.$course->id
.'\' AND '.
2313 'module = \'assignment\' AND '.
2314 'action = \'upload\' ', 'time ASC')) {
2318 foreach ($logs as $log) {
2319 //Create a temp valid module structure (course,id)
2320 $tempmod = new object();
2321 $tempmod->course
= $log->course
;
2322 $tempmod->id
= $log->info
;
2323 //Obtain the visible property from the instance
2324 $modvisible = instance_is_visible($log->module
,$tempmod);
2326 //Only if the mod is visible
2328 if ($info = assignment_log_info($log)) {
2329 $assignments[$log->info
] = $info;
2330 $assignments[$log->info
]->time
= $log->time
;
2331 $assignments[$log->info
]->url
= str_replace('&', '&', $log->url
);
2336 if (!empty($assignments)) {
2337 print_headline(get_string('newsubmissions', 'assignment').':');
2338 foreach ($assignments as $assignment) {
2339 print_recent_activity_note($assignment->time
, $assignment, $assignment->name
,
2340 $CFG->wwwroot
.'/mod/assignment/'.$assignment->url
);
2350 * Returns all assignments since a given time.
2352 * If assignment is specified then this restricts the results
2354 function assignment_get_recent_mod_activity(&$activities, &$index, $sincetime, $courseid, $assignment="0", $user="", $groupid="") {
2359 $assignmentselect = " AND cm.id = '$assignment'";
2361 $assignmentselect = "";
2364 $userselect = " AND u.id = '$user'";
2369 $assignments = get_records_sql("SELECT asub.*, u.firstname, u.lastname, u.picture, u.id as userid,
2370 a.grade as maxgrade, name, cm.instance, cm.section, a.assignmenttype
2371 FROM {$CFG->prefix}assignment_submissions asub,
2372 {$CFG->prefix}user u,
2373 {$CFG->prefix}assignment a,
2374 {$CFG->prefix}course_modules cm
2375 WHERE asub.timemodified > '$sincetime'
2376 AND asub.userid = u.id $userselect
2377 AND a.id = asub.assignment $assignmentselect
2378 AND cm.course = '$courseid'
2379 AND cm.instance = a.id
2380 ORDER BY asub.timemodified ASC");
2382 if (empty($assignments))
2385 foreach ($assignments as $assignment) {
2386 if (empty($groupid) ||
groups_is_member($groupid, $assignment->userid
)) {
2388 $tmpactivity = new Object;
2390 $tmpactivity->type
= "assignment";
2391 $tmpactivity->defaultindex
= $index;
2392 $tmpactivity->instance
= $assignment->instance
;
2393 $tmpactivity->name
= $assignment->name
;
2394 $tmpactivity->section
= $assignment->section
;
2396 $tmpactivity->content
->grade
= $assignment->grade
;
2397 $tmpactivity->content
->maxgrade
= $assignment->maxgrade
;
2398 $tmpactivity->content
->type
= $assignment->assignmenttype
;
2400 $tmpactivity->user
->userid
= $assignment->userid
;
2401 $tmpactivity->user
->fullname
= fullname($assignment);
2402 $tmpactivity->user
->picture
= $assignment->picture
;
2404 $tmpactivity->timestamp
= $assignment->timemodified
;
2406 $activities[] = $tmpactivity;
2416 * Print recent activity from all assignments in a given course
2418 * This is used by course/recent.php
2420 function assignment_print_recent_mod_activity($activity, $course, $detail=false) {
2423 echo '<table border="0" cellpadding="3" cellspacing="0">';
2425 echo "<tr><td class=\"userpicture\" valign=\"top\">";
2426 print_user_picture($activity->user
->userid
, $course, $activity->user
->picture
);
2427 echo "</td><td width=\"100%\"><font size=2>";
2430 echo "<img src=\"$CFG->modpixpath/$activity->type/icon.gif\" ".
2431 "class=\"icon\" alt=\"$activity->type\"> ";
2432 echo "<a href=\"$CFG->wwwroot/mod/assignment/view.php?id=" . $activity->instance
. "\">"
2433 . format_string($activity->name
,true) . "</a> - ";
2437 if (has_capability('moodle/course:viewrecent', get_context_instance(CONTEXT_COURSE
, $course))) {
2438 $grades = "(" . $activity->content
->grade
. " / " . $activity->content
->maxgrade
. ") ";
2440 $assignment->id
= $activity->instance
;
2441 $assignment->course
= $course;
2442 $user->id
= $activity->user
->userid
;
2447 echo "<a href=\"$CFG->wwwroot/user/view.php?id="
2448 . $activity->user
->userid
. "&course=$course\">"
2449 . $activity->user
->fullname
. "</a> ";
2451 echo " - " . userdate($activity->timestamp
);
2453 echo "</font></td></tr>";
2457 /// GENERIC SQL FUNCTIONS
2460 * Fetch info from logs
2462 * @param $log object with properties ->info (the assignment id) and ->userid
2463 * @return array with assignment name and user firstname and lastname
2465 function assignment_log_info($log) {
2467 return get_record_sql("SELECT a.name, u.firstname, u.lastname
2468 FROM {$CFG->prefix}assignment a,
2469 {$CFG->prefix}user u
2470 WHERE a.id = '$log->info'
2471 AND u.id = '$log->userid'");
2475 * Return list of marked submissions that have not been mailed out for currently enrolled students
2479 function assignment_get_unmailed_submissions($starttime, $endtime) {
2483 return get_records_sql("SELECT s.*, a.course, a.name
2484 FROM {$CFG->prefix}assignment_submissions s,
2485 {$CFG->prefix}assignment a
2487 AND s.timemarked <= $endtime
2488 AND s.timemarked >= $starttime
2489 AND s.assignment = a.id");
2491 /* return get_records_sql("SELECT s.*, a.course, a.name
2492 FROM {$CFG->prefix}assignment_submissions s,
2493 {$CFG->prefix}assignment a,
2494 {$CFG->prefix}user_students us
2496 AND s.timemarked <= $endtime
2497 AND s.timemarked >= $starttime
2498 AND s.assignment = a.id
2499 AND s.userid = us.userid
2500 AND a.course = us.course");
2505 * Counts all real assignment submissions by ENROLLED students (not empty ones)
2507 * There are also assignment type methods count_real_submissions() wich in the default
2508 * implementation simply call this function.
2509 * @param $groupid int optional If nonzero then count is restricted to this group
2510 * @return int The number of submissions
2512 function assignment_count_real_submissions($assignment, $groupid=0) {
2515 if ($groupid) { /// How many in a particular group?
2516 return count_records_sql("SELECT COUNT(DISTINCT gm.userid, gm.groupid)
2517 FROM {$CFG->prefix}assignment_submissions a,
2518 {$CFG->prefix}groups_members g
2519 WHERE a.assignment = $assignment->id
2520 AND a.timemodified > 0
2521 AND g.groupid = '$groupid'
2522 AND a.userid = g.userid ");
2524 $cm = get_coursemodule_from_instance('assignment', $assignment->id
);
2525 $context = get_context_instance(CONTEXT_MODULE
, $cm->id
);
2527 // this is all the users with this capability set, in this context or higher
2528 if ($users = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2529 foreach ($users as $user) {
2530 $array[] = $user->id
;
2533 $userlists = '('.implode(',',$array).')';
2535 return count_records_sql("SELECT COUNT(*)
2536 FROM {$CFG->prefix}assignment_submissions
2537 WHERE assignment = '$assignment->id'
2538 AND timemodified > 0
2539 AND userid IN $userlists ");
2541 return 0; // no users enroled in course
2548 * Return all assignment submissions by ENROLLED students (even empty)
2550 * There are also assignment type methods get_submissions() wich in the default
2551 * implementation simply call this function.
2552 * @param $sort string optional field names for the ORDER BY in the sql query
2553 * @param $dir string optional specifying the sort direction, defaults to DESC
2554 * @return array The submission objects indexed by id
2556 function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
2557 /// Return all assignment submissions by ENROLLED students (even empty)
2560 if ($sort == "lastname" or $sort == "firstname") {
2561 $sort = "u.$sort $dir";
2562 } else if (empty($sort)) {
2563 $sort = "a.timemodified DESC";
2565 $sort = "a.$sort $dir";
2568 /* not sure this is needed at all since assignmenet already has a course define, so this join?
2569 $select = "s.course = '$assignment->course' AND";
2570 if ($assignment->course == SITEID) {
2574 return get_records_sql("SELECT a.*
2575 FROM {$CFG->prefix}assignment_submissions a,
2576 {$CFG->prefix}user u
2577 WHERE u.id = a.userid
2578 AND a.assignment = '$assignment->id'
2581 /* return get_records_sql("SELECT a.*
2582 FROM {$CFG->prefix}assignment_submissions a,
2583 {$CFG->prefix}user_students s,
2584 {$CFG->prefix}user u
2585 WHERE a.userid = s.userid
2587 AND $select a.assignment = '$assignment->id'
2593 * Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
2594 * for the course (see resource).
2596 * Given a course_module object, this function returns any "extra" information that may be needed
2597 * when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
2599 * @param $coursemodule object The coursemodule object (record).
2600 * @return object An object on information that the coures will know about (most noticeably, an icon).
2603 function assignment_get_coursemodule_info($coursemodule) {
2606 if (! $assignment = get_record('assignment', 'id', $coursemodule->instance
)) {
2610 require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
2611 $assignmentclass = "assignment_$assignment->assignmenttype";
2612 $ass = new $assignmentclass($coursemodule->id
, $assignment);
2614 return $ass->get_coursemodule_info($coursemodule);
2619 /// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS ///////////////////////////////////////
2622 * Returns an array of installed assignment types indexed and sorted by name
2624 * @return array The index is the name of the assignment type, the value its full name from the language strings
2626 function assignment_types() {
2628 $names = get_list_of_plugins('mod/assignment/type');
2629 foreach ($names as $name) {
2630 $types[$name] = get_string('type'.$name, 'assignment');
2637 * Executes upgrade scripts for assignment types when necessary
2639 function assignment_upgrade_submodules() {
2643 /// Install/upgrade assignment types (it uses, simply, the standard plugin architecture)
2644 upgrade_plugins('assignment_type', 'mod/assignment/type', "$CFG->wwwroot/$CFG->admin/index.php");
2648 function assignment_print_overview($courses, &$htmlarray) {
2652 if (empty($courses) ||
!is_array($courses) ||
count($courses) == 0) {
2656 if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
2660 // Do assignment_base::isopen() here without loading the whole thing for speed
2661 foreach ($assignments as $key => $assignment) {
2663 if ($assignment->timedue
) {
2664 if ($assignment->preventlate
) {
2665 $isopen = ($assignment->timeavailable
<= $time && $time <= $assignment->timedue
);
2667 $isopen = ($assignment->timeavailable
<= $time);
2670 if (empty($isopen) ||
empty($assignment->timedue
)) {
2671 unset($assignments[$key]);
2675 $strduedate = get_string('duedate', 'assignment');
2676 $strduedateno = get_string('duedateno', 'assignment');
2677 $strgraded = get_string('graded', 'assignment');
2678 $strnotgradedyet = get_string('notgradedyet', 'assignment');
2679 $strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
2680 $strsubmitted = get_string('submitted', 'assignment');
2681 $strassignment = get_string('modulename', 'assignment');
2682 $strreviewed = get_string('reviewed','assignment');
2684 foreach ($assignments as $assignment) {
2685 $str = '<div class="assignment overview"><div class="name">'.$strassignment. ': '.
2686 '<a '.($assignment->visible ?
'':' class="dimmed"').
2687 'title="'.$strassignment.'" href="'.$CFG->wwwroot
.
2688 '/mod/assignment/view.php?id='.$assignment->coursemodule
.'">'.
2689 $assignment->name
.'</a></div>';
2690 if ($assignment->timedue
) {
2691 $str .= '<div class="info">'.$strduedate.': '.userdate($assignment->timedue
).'</div>';
2693 $str .= '<div class="info">'.$strduedateno.'</div>';
2695 $context = get_context_instance(CONTEXT_MODULE
, $assignment->coursemodule
);
2696 if (has_capability('mod/assignment:grade', $context)) {
2698 // count how many people can submit
2699 $submissions = 0; // init
2700 if ($students = get_users_by_capability($context, 'mod/assignment:submit', '', '', '', '', 0, '', false)) {
2701 foreach ($students as $student) {
2702 if (get_records_sql("SELECT id,id FROM {$CFG->prefix}assignment_submissions
2703 WHERE assignment = $assignment->id AND
2704 userid = $student->id AND
2713 $str .= get_string('submissionsnotgraded', 'assignment', $submissions);
2717 FROM {$CFG->prefix}assignment_submissions
2718 WHERE userid = '$USER->id'
2719 AND assignment = '{$assignment->id}'";
2720 if ($submission = get_record_sql($sql)) {
2721 if ($submission->teacher
== 0 && $submission->timemarked
== 0) {
2722 $str .= $strsubmitted . ', ' . $strnotgradedyet;
2723 } else if ($submission->grade
<= 0) {
2724 $str .= $strsubmitted . ', ' . $strreviewed;
2726 $str .= $strsubmitted . ', ' . $strgraded;
2729 $str .= $strnotsubmittedyet . ' ' . assignment_display_lateness(time(), $assignment->timedue
);
2733 if (empty($htmlarray[$assignment->course
]['assignment'])) {
2734 $htmlarray[$assignment->course
]['assignment'] = $str;
2736 $htmlarray[$assignment->course
]['assignment'] .= $str;
2741 function assignment_display_lateness($timesubmitted, $timedue) {
2745 $time = $timedue - $timesubmitted;
2747 $timetext = get_string('late', 'assignment', format_time($time));
2748 return ' (<span class="late">'.$timetext.'</span>)';
2750 $timetext = get_string('early', 'assignment', format_time($time));
2751 return ' (<span class="early">'.$timetext.'</span>)';
2755 function assignment_get_view_actions() {
2756 return array('view');
2759 function assignment_get_post_actions() {
2760 return array('upload');
2763 function assignment_get_types() {
2767 $type = new object();
2768 $type->modclass
= MOD_CLASS_ACTIVITY
;
2769 $type->type
= "assignment_group_start";
2770 $type->typestr
= '--'.get_string('modulenameplural', 'assignment');
2773 $standardassignments = array('upload','online','uploadsingle','offline');
2774 foreach ($standardassignments as $assignmenttype) {
2775 $type = new object();
2776 $type->modclass
= MOD_CLASS_ACTIVITY
;
2777 $type->type
= "assignment&type=$assignmenttype";
2778 $type->typestr
= get_string("type$assignmenttype", 'assignment');
2782 /// Drop-in extra assignment types
2783 $assignmenttypes = get_list_of_plugins('mod/assignment/type');
2784 foreach ($assignmenttypes as $assignmenttype) {
2785 if (!empty($CFG->{'assignment_hide_'.$assignmenttype})) { // Not wanted
2788 if (!in_array($assignmenttype, $standardassignments)) {
2789 $type = new object();
2790 $type->modclass
= MOD_CLASS_ACTIVITY
;
2791 $type->type
= "assignment&type=$assignmenttype";
2792 $type->typestr
= get_string("type$assignmenttype", 'assignment');
2797 $type = new object();
2798 $type->modclass
= MOD_CLASS_ACTIVITY
;
2799 $type->type
= "assignment_group_end";
2800 $type->typestr
= '--';