.
/**
* assignment_base is the base class for assignment types
*
* This class provides all the functionality for an assignment
*
* @package mod-assignment
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/** Include eventslib.php */
require_once($CFG->libdir.'/eventslib.php');
/** Include formslib.php */
require_once($CFG->libdir.'/formslib.php');
/** Include calendar/lib.php */
require_once($CFG->dirroot.'/calendar/lib.php');
/** ASSIGNMENT_COUNT_WORDS = 1 */
define('ASSIGNMENT_COUNT_WORDS', 1);
/** ASSIGNMENT_COUNT_LETTERS = 2 */
define('ASSIGNMENT_COUNT_LETTERS', 2);
/**
* Standard base class for all assignment submodules (assignment types).
*
* @package mod-assignment
* @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class assignment_base {
const FILTER_ALL = 0;
const FILTER_SUBMITTED = 1;
const FILTER_REQUIRE_GRADING = 2;
/** @var object */
var $cm;
/** @var object */
var $course;
/** @var stdClass */
var $coursecontext;
/** @var object */
var $assignment;
/** @var string */
var $strassignment;
/** @var string */
var $strassignments;
/** @var string */
var $strsubmissions;
/** @var string */
var $strlastmodified;
/** @var string */
var $pagetitle;
/** @var bool */
var $usehtmleditor;
/**
* @todo document this var
*/
var $defaultformat;
/**
* @todo document this var
*/
var $context;
/** @var string */
var $type;
/**
* Constructor for the base assignment class
*
* Constructor for the base assignment class.
* If cmid is set create the cm, course, assignment objects.
* If the assignment is hidden and the user is not a teacher then
* this prints a page header and notice.
*
* @global object
* @global object
* @param int $cmid the current course module id - not set for new assignments
* @param object $assignment usually null, but if we have it we pass it to save db access
* @param object $cm usually null, but if we have it we pass it to save db access
* @param object $course usually null, but if we have it we pass it to save db access
*/
function assignment_base($cmid='staticonly', $assignment=NULL, $cm=NULL, $course=NULL) {
global $COURSE, $DB;
if ($cmid == 'staticonly') {
//use static functions only!
return;
}
global $CFG;
if ($cm) {
$this->cm = $cm;
} else if (! $this->cm = get_coursemodule_from_id('assignment', $cmid)) {
print_error('invalidcoursemodule');
}
$this->context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
if ($course) {
$this->course = $course;
} else if ($this->cm->course == $COURSE->id) {
$this->course = $COURSE;
} else if (! $this->course = $DB->get_record('course', array('id'=>$this->cm->course))) {
print_error('invalidid', 'assignment');
}
$this->coursecontext = get_context_instance(CONTEXT_COURSE, $this->course->id);
$courseshortname = format_text($this->course->shortname, true, array('context' => $this->coursecontext));
if ($assignment) {
$this->assignment = $assignment;
} else if (! $this->assignment = $DB->get_record('assignment', array('id'=>$this->cm->instance))) {
print_error('invalidid', 'assignment');
}
$this->assignment->cmidnumber = $this->cm->idnumber; // compatibility with modedit assignment obj
$this->assignment->courseid = $this->course->id; // compatibility with modedit assignment obj
$this->strassignment = get_string('modulename', 'assignment');
$this->strassignments = get_string('modulenameplural', 'assignment');
$this->strsubmissions = get_string('submissions', 'assignment');
$this->strlastmodified = get_string('lastmodified');
$this->pagetitle = strip_tags($courseshortname.': '.$this->strassignment.': '.format_string($this->assignment->name, true, array('context' => $this->context)));
// visibility handled by require_login() with $cm parameter
// get current group only when really needed
/// Set up things for a HTML editor if it's needed
$this->defaultformat = editors_get_preferred_format();
}
/**
* Display the assignment, used by view.php
*
* This in turn calls the methods producing individual parts of the page
*/
function view() {
$context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
require_capability('mod/assignment:view', $context);
add_to_log($this->course->id, "assignment", "view", "view.php?id={$this->cm->id}",
$this->assignment->id, $this->cm->id);
$this->view_header();
$this->view_intro();
$this->view_dates();
$this->view_feedback();
$this->view_footer();
}
/**
* Display the header and top of a page
*
* (this doesn't change much for assignment types)
* This is used by the view() method to print the header of view.php but
* it can be used on other pages in which case the string to denote the
* page in the navigation trail should be passed as an argument
*
* @global object
* @param string $subpage Description of subpage to be used in navigation trail
*/
function view_header($subpage='') {
global $CFG, $PAGE, $OUTPUT;
if ($subpage) {
$PAGE->navbar->add($subpage);
}
$PAGE->set_title($this->pagetitle);
$PAGE->set_heading($this->course->fullname);
echo $OUTPUT->header();
groups_print_activity_menu($this->cm, $CFG->wwwroot . '/mod/assignment/view.php?id=' . $this->cm->id);
echo '
'.$this->submittedlink().'
';
echo '';
}
/**
* Display the assignment intro
*
* This will most likely be extended by assignment type plug-ins
* The default implementation prints the assignment description in a box
*/
function view_intro() {
global $OUTPUT;
echo $OUTPUT->box_start('generalbox boxaligncenter', 'intro');
echo format_module_intro('assignment', $this->assignment, $this->cm->id);
echo $OUTPUT->box_end();
echo plagiarism_print_disclosure($this->cm->id);
}
/**
* Display the assignment dates
*
* Prints the assignment start and end dates in a box.
* This will be suitable for most assignment types
*/
function view_dates() {
global $OUTPUT;
if (!$this->assignment->timeavailable && !$this->assignment->timedue) {
return;
}
echo $OUTPUT->box_start('generalbox boxaligncenter', 'dates');
echo '
';
if ($this->assignment->timeavailable) {
echo '
'.get_string('availabledate','assignment').':
';
echo '
'.userdate($this->assignment->timeavailable).'
';
}
if ($this->assignment->timedue) {
echo '
'.get_string('duedate','assignment').':
';
echo '
'.userdate($this->assignment->timedue).'
';
}
echo '
';
echo $OUTPUT->box_end();
}
/**
* Display the bottom and footer of a page
*
* This default method just prints the footer.
* This will be suitable for most assignment types
*/
function view_footer() {
global $OUTPUT;
echo $OUTPUT->footer();
}
/**
* Display the feedback to the student
*
* This default method prints the teacher picture and name, date when marked,
* grade and teacher submissioncomment.
* If advanced grading is used the method render_grade from the
* advanced grading controller is called to display the grade.
*
* @global object
* @global object
* @global object
* @param object $submission The submission object or NULL in which case it will be loaded
*/
function view_feedback($submission=NULL) {
global $USER, $CFG, $DB, $OUTPUT, $PAGE;
require_once($CFG->libdir.'/gradelib.php');
require_once("$CFG->dirroot/grade/grading/lib.php");
if (!$submission) { /// Get submission for this assignment
$userid = $USER->id;
$submission = $this->get_submission($userid);
} else {
$userid = $submission->userid;
}
// Check the user can submit
$canviewfeedback = ($userid == $USER->id && has_capability('mod/assignment:submit', $this->context, $USER->id, false));
// If not then check if the user still has the view cap and has a previous submission
$canviewfeedback = $canviewfeedback || (!empty($submission) && $submission->userid == $USER->id && has_capability('mod/assignment:view', $this->context));
// Or if user can grade (is a teacher or admin)
$canviewfeedback = $canviewfeedback || has_capability('mod/assignment:grade', $this->context);
if (!$canviewfeedback) {
// can not view or submit assignments -> no feedback
return;
}
$grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
$item = $grading_info->items[0];
$grade = $item->grades[$userid];
if ($grade->hidden or $grade->grade === false) { // hidden or error
return;
}
if ($grade->grade === null and empty($grade->str_feedback)) { /// Nothing to show yet
return;
}
$graded_date = $grade->dategraded;
$graded_by = $grade->usermodified;
/// We need the teacher info
if (!$teacher = $DB->get_record('user', array('id'=>$graded_by))) {
print_error('cannotfindteacher');
}
/// Print the feedback
echo $OUTPUT->heading(get_string('feedbackfromteacher', 'assignment', fullname($teacher)));
echo '
';
echo '
';
echo '
';
if ($teacher) {
echo $OUTPUT->user_picture($teacher);
}
echo '
';
if ($this->type == 'uploadsingle') { //@TODO: move to overload view_feedback method in the class or is uploadsingle merging into upload?
$responsefiles = $this->print_responsefiles($submission->userid, true);
if (!empty($responsefiles)) {
echo '
';
echo '
';
echo '
';
echo $responsefiles;
echo '
';
}
}
echo '
';
}
/**
* Returns a link with info about the state of the assignment submissions
*
* This is used by view_header to put this link at the top right of the page.
* For teachers it gives the number of submitted assignments with a link
* For students it gives the time of their submission.
* This will be suitable for most assignment types.
*
* @global object
* @global object
* @param bool $allgroup print all groups info if user can access all groups, suitable for index.php
* @return string
*/
function submittedlink($allgroups=false) {
global $USER;
global $CFG;
$submitted = '';
$urlbase = "{$CFG->wwwroot}/mod/assignment/";
$context = get_context_instance(CONTEXT_MODULE,$this->cm->id);
if (has_capability('mod/assignment:grade', $context)) {
if ($allgroups and has_capability('moodle/site:accessallgroups', $context)) {
$group = 0;
} else {
$group = groups_get_activity_group($this->cm);
}
if ($this->type == 'offline') {
$submitted = ''.
get_string('viewfeedback', 'assignment').'';
} else if ($count = $this->count_real_submissions($group)) {
$submitted = ''.
get_string('viewsubmissions', 'assignment', $count).'';
} else {
$submitted = ''.
get_string('noattempts', 'assignment').'';
}
} else {
if (isloggedin()) {
if ($submission = $this->get_submission($USER->id)) {
// If the submission has been completed
if ($this->is_submitted_with_required_data($submission)) {
if ($submission->timemodified <= $this->assignment->timedue || empty($this->assignment->timedue)) {
$submitted = ''.userdate($submission->timemodified).'';
} else {
$submitted = ''.userdate($submission->timemodified).'';
}
}
}
}
}
return $submitted;
}
/**
* Returns whether the assigment supports lateness information
*
* @return bool This assignment type supports lateness (true, default) or no (false)
*/
function supports_lateness() {
return true;
}
/**
* @todo Document this function
*/
function setup_elements(&$mform) {
}
/**
* Any preprocessing needed for the settings form for
* this assignment type
*
* @param array $default_values - array to fill in with the default values
* in the form 'formelement' => 'value'
* @param object $form - the form that is to be displayed
* @return none
*/
function form_data_preprocessing(&$default_values, $form) {
}
/**
* Any extra validation checks needed for the settings
* form for this assignment type
*
* See lib/formslib.php, 'validation' function for details
*/
function form_validation($data, $files) {
return array();
}
/**
* Create a new assignment activity
*
* Given an object containing all the necessary data,
* (defined by the form in mod_form.php) this function
* will create a new instance and return the id number
* of the new instance.
* The due data is added to the calendar
* This is common to all assignment types.
*
* @global object
* @global object
* @param object $assignment The data from the form on mod_form.php
* @return int The id of the assignment
*/
function add_instance($assignment) {
global $COURSE, $DB;
$assignment->timemodified = time();
$assignment->courseid = $assignment->course;
$returnid = $DB->insert_record("assignment", $assignment);
$assignment->id = $returnid;
if ($assignment->timedue) {
$event = new stdClass();
$event->name = $assignment->name;
$event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
$event->courseid = $assignment->course;
$event->groupid = 0;
$event->userid = 0;
$event->modulename = 'assignment';
$event->instance = $returnid;
$event->eventtype = 'due';
$event->timestart = $assignment->timedue;
$event->timeduration = 0;
calendar_event::create($event);
}
assignment_grade_item_update($assignment);
return $returnid;
}
/**
* Deletes an assignment activity
*
* Deletes all database records, files and calendar events for this assignment.
*
* @global object
* @global object
* @param object $assignment The assignment to be deleted
* @return boolean False indicates error
*/
function delete_instance($assignment) {
global $CFG, $DB;
$assignment->courseid = $assignment->course;
$result = true;
// now get rid of all files
$fs = get_file_storage();
if ($cm = get_coursemodule_from_instance('assignment', $assignment->id)) {
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
$fs->delete_area_files($context->id);
}
if (! $DB->delete_records('assignment_submissions', array('assignment'=>$assignment->id))) {
$result = false;
}
if (! $DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
$result = false;
}
if (! $DB->delete_records('assignment', array('id'=>$assignment->id))) {
$result = false;
}
$mod = $DB->get_field('modules','id',array('name'=>'assignment'));
assignment_grade_item_delete($assignment);
return $result;
}
/**
* Updates a new assignment activity
*
* Given an object containing all the necessary data,
* (defined by the form in mod_form.php) this function
* will update the assignment instance and return the id number
* The due date is updated in the calendar
* This is common to all assignment types.
*
* @global object
* @global object
* @param object $assignment The data from the form on mod_form.php
* @return bool success
*/
function update_instance($assignment) {
global $COURSE, $DB;
$assignment->timemodified = time();
$assignment->id = $assignment->instance;
$assignment->courseid = $assignment->course;
$DB->update_record('assignment', $assignment);
if ($assignment->timedue) {
$event = new stdClass();
if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
$event->name = $assignment->name;
$event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
$event->timestart = $assignment->timedue;
$calendarevent = calendar_event::load($event->id);
$calendarevent->update($event);
} else {
$event = new stdClass();
$event->name = $assignment->name;
$event->description = format_module_intro('assignment', $assignment, $assignment->coursemodule);
$event->courseid = $assignment->course;
$event->groupid = 0;
$event->userid = 0;
$event->modulename = 'assignment';
$event->instance = $assignment->id;
$event->eventtype = 'due';
$event->timestart = $assignment->timedue;
$event->timeduration = 0;
calendar_event::create($event);
}
} else {
$DB->delete_records('event', array('modulename'=>'assignment', 'instance'=>$assignment->id));
}
// get existing grade item
assignment_grade_item_update($assignment);
return true;
}
/**
* Update grade item for this submission.
*/
function update_grade($submission) {
assignment_update_grades($this->assignment, $submission->userid);
}
/**
* Top-level function for handling of submissions called by submissions.php
*
* This is for handling the teacher interaction with the grading interface
* This should be suitable for most assignment types.
*
* @global object
* @param string $mode Specifies the kind of teacher interaction taking place
*/
function submissions($mode) {
///The main switch is changed to facilitate
///1) Batch fast grading
///2) Skip to the next one on the popup
///3) Save and Skip to the next one on the popup
//make user global so we can use the id
global $USER, $OUTPUT, $DB, $PAGE;
$mailinfo = optional_param('mailinfo', null, PARAM_BOOL);
if (optional_param('next', null, PARAM_BOOL)) {
$mode='next';
}
if (optional_param('saveandnext', null, PARAM_BOOL)) {
$mode='saveandnext';
}
if (is_null($mailinfo)) {
if (optional_param('sesskey', null, PARAM_BOOL)) {
set_user_preference('assignment_mailinfo', 0);
} else {
$mailinfo = get_user_preferences('assignment_mailinfo', 0);
}
} else {
set_user_preference('assignment_mailinfo', $mailinfo);
}
if (!($this->validate_and_preprocess_feedback())) {
// form was submitted ('Save' or 'Save and next' was pressed, but validation failed)
$this->display_submission();
return;
}
switch ($mode) {
case 'grade': // We are in a main window grading
if ($submission = $this->process_feedback()) {
$this->display_submissions(get_string('changessaved'));
} else {
$this->display_submissions();
}
break;
case 'single': // We are in a main window displaying one submission
if ($submission = $this->process_feedback()) {
$this->display_submissions(get_string('changessaved'));
} else {
$this->display_submission();
}
break;
case 'all': // Main window, display everything
$this->display_submissions();
break;
case 'fastgrade':
///do the fast grading stuff - this process should work for all 3 subclasses
$grading = false;
$commenting = false;
$col = false;
if (isset($_POST['submissioncomment'])) {
$col = 'submissioncomment';
$commenting = true;
}
if (isset($_POST['menu'])) {
$col = 'menu';
$grading = true;
}
if (!$col) {
//both submissioncomment and grade columns collapsed..
$this->display_submissions();
break;
}
foreach ($_POST[$col] as $id => $unusedvalue){
$id = (int)$id; //clean parameter name
$this->process_outcomes($id);
if (!$submission = $this->get_submission($id)) {
$submission = $this->prepare_new_submission($id);
$newsubmission = true;
} else {
$newsubmission = false;
}
unset($submission->data1); // Don't need to update this.
unset($submission->data2); // Don't need to update this.
//for fast grade, we need to check if any changes take place
$updatedb = false;
if ($grading) {
$grade = $_POST['menu'][$id];
$updatedb = $updatedb || ($submission->grade != $grade);
$submission->grade = $grade;
} else {
if (!$newsubmission) {
unset($submission->grade); // Don't need to update this.
}
}
if ($commenting) {
$commentvalue = trim($_POST['submissioncomment'][$id]);
$updatedb = $updatedb || ($submission->submissioncomment != $commentvalue);
$submission->submissioncomment = $commentvalue;
} else {
unset($submission->submissioncomment); // Don't need to update this.
}
$submission->teacher = $USER->id;
if ($updatedb) {
$submission->mailed = (int)(!$mailinfo);
}
$submission->timemarked = time();
//if it is not an update, we don't change the last modified time etc.
//this will also not write into database if no submissioncomment and grade is entered.
if ($updatedb){
if ($newsubmission) {
if (!isset($submission->submissioncomment)) {
$submission->submissioncomment = '';
}
$sid = $DB->insert_record('assignment_submissions', $submission);
$submission->id = $sid;
} else {
$DB->update_record('assignment_submissions', $submission);
}
// trigger grade event
$this->update_grade($submission);
//add to log only if updating
add_to_log($this->course->id, 'assignment', 'update grades',
'submissions.php?id='.$this->cm->id.'&user='.$submission->userid,
$submission->userid, $this->cm->id);
}
}
$message = $OUTPUT->notification(get_string('changessaved'), 'notifysuccess');
$this->display_submissions($message);
break;
case 'saveandnext':
///We are in pop up. save the current one and go to the next one.
//first we save the current changes
if ($submission = $this->process_feedback()) {
//print_heading(get_string('changessaved'));
//$extra_javascript = $this->update_main_listing($submission);
}
case 'next':
/// We are currently in pop up, but we want to skip to next one without saving.
/// This turns out to be similar to a single case
/// The URL used is for the next submission.
$offset = required_param('offset', PARAM_INT);
$nextid = required_param('nextid', PARAM_INT);
$id = required_param('id', PARAM_INT);
$filter = optional_param('filter', self::FILTER_ALL, PARAM_INT);
if ($mode == 'next' || $filter !== self::FILTER_REQUIRE_GRADING) {
$offset = (int)$offset+1;
}
$redirect = new moodle_url('submissions.php',
array('id' => $id, 'offset' => $offset, 'userid' => $nextid,
'mode' => 'single', 'filter' => $filter));
redirect($redirect);
break;
case 'singlenosave':
$this->display_submission();
break;
default:
echo "something seriously is wrong!!";
break;
}
}
/**
* Checks if grading method allows quickgrade mode. At the moment it is hardcoded
* that advanced grading methods do not allow quickgrade.
*
* Assignment type plugins are not allowed to override this method
*
* @return boolean
*/
public final function quickgrade_mode_allowed() {
global $CFG;
require_once("$CFG->dirroot/grade/grading/lib.php");
if ($controller = get_grading_manager($this->context, 'mod_assignment', 'submission')->get_active_controller()) {
return false;
}
return true;
}
/**
* Helper method updating the listing on the main script from popup using javascript
*
* @global object
* @global object
* @param $submission object The submission whose data is to be updated on the main page
*/
function update_main_listing($submission) {
global $SESSION, $CFG, $OUTPUT;
$output = '';
$perpage = get_user_preferences('assignment_perpage', 10);
$quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
/// Run some Javascript to try and update the parent page
$output .= '";
return $output;
}
/**
* Return a grade in user-friendly form, whether it's a scale or not
*
* @global object
* @param mixed $grade
* @return string User-friendly representation of grade
*/
function display_grade($grade) {
global $DB;
static $scalegrades = array(); // Cache scales for each assignment - they might have different scales!!
if ($this->assignment->grade >= 0) { // Normal number
if ($grade == -1) {
return '-';
} else {
return $grade.' / '.$this->assignment->grade;
}
} else { // Scale
if (empty($scalegrades[$this->assignment->id])) {
if ($scale = $DB->get_record('scale', array('id'=>-($this->assignment->grade)))) {
$scalegrades[$this->assignment->id] = make_menu_from_list($scale->scale);
} else {
return '-';
}
}
if (isset($scalegrades[$this->assignment->id][$grade])) {
return $scalegrades[$this->assignment->id][$grade];
}
return '-';
}
}
/**
* Display a single submission, ready for grading on a popup window
*
* This default method prints the teacher info and submissioncomment box at the top and
* the student info and submission at the bottom.
* This method also fetches the necessary data in order to be able to
* provide a "Next submission" button.
* Calls preprocess_submission() to give assignment type plug-ins a chance
* to process submissions before they are graded
* This method gets its arguments from the page parameters userid and offset
*
* @global object
* @global object
* @param string $extra_javascript
*/
function display_submission($offset=-1,$userid =-1, $display=true) {
global $CFG, $DB, $PAGE, $OUTPUT, $USER;
require_once($CFG->libdir.'/gradelib.php');
require_once($CFG->libdir.'/tablelib.php');
require_once("$CFG->dirroot/repository/lib.php");
require_once("$CFG->dirroot/grade/grading/lib.php");
if ($userid==-1) {
$userid = required_param('userid', PARAM_INT);
}
if ($offset==-1) {
$offset = required_param('offset', PARAM_INT);//offset for where to start looking for student.
}
$filter = optional_param('filter', 0, PARAM_INT);
if (!$user = $DB->get_record('user', array('id'=>$userid))) {
print_error('nousers');
}
if (!$submission = $this->get_submission($user->id)) {
$submission = $this->prepare_new_submission($userid);
}
if ($submission->timemodified > $submission->timemarked) {
$subtype = 'assignmentnew';
} else {
$subtype = 'assignmentold';
}
$grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($user->id));
$gradingdisabled = $grading_info->items[0]->grades[$userid]->locked || $grading_info->items[0]->grades[$userid]->overridden;
/// construct SQL, using current offset to find the data of the next student
$course = $this->course;
$assignment = $this->assignment;
$cm = $this->cm;
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
//reset filter to all for offline assignment
if ($assignment->assignmenttype == 'offline' && $filter == self::FILTER_SUBMITTED) {
$filter = self::FILTER_ALL;
}
/// Get all ppl that can submit assignments
$currentgroup = groups_get_activity_group($cm);
$users = get_enrolled_users($context, 'mod/assignment:submit', $currentgroup, 'u.id');
if ($users) {
$users = array_keys($users);
// if groupmembersonly used, remove users who are not in any group
if (!empty($CFG->enablegroupmembersonly) and $cm->groupmembersonly) {
if ($groupingusers = groups_get_grouping_members($cm->groupingid, 'u.id', 'u.id')) {
$users = array_intersect($users, array_keys($groupingusers));
}
}
}
$nextid = 0;
$where = '';
if($filter == self::FILTER_SUBMITTED) {
$where .= 's.timemodified > 0 AND ';
} else if($filter == self::FILTER_REQUIRE_GRADING) {
$where .= 's.timemarked < s.timemodified AND ';
}
if ($users) {
$userfields = user_picture::fields('u', array('lastaccess'));
$select = "SELECT $userfields,
s.id AS submissionid, s.grade, s.submissioncomment,
s.timemodified, s.timemarked,
CASE WHEN s.timemarked > 0 AND s.timemarked >= s.timemodified THEN 1
ELSE 0 END AS status ";
$sql = 'FROM {user} u '.
'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
AND s.assignment = '.$this->assignment->id.' '.
'WHERE '.$where.'u.id IN ('.implode(',', $users).') ';
if ($sort = flexible_table::get_sort_for_table('mod-assignment-submissions')) {
$sort = 'ORDER BY '.$sort.' ';
}
$auser = $DB->get_records_sql($select.$sql.$sort, null, $offset, 2);
if (is_array($auser) && count($auser)>1) {
$nextuser = next($auser);
$nextid = $nextuser->id;
}
}
if ($submission->teacher) {
$teacher = $DB->get_record('user', array('id'=>$submission->teacher));
} else {
global $USER;
$teacher = $USER;
}
$this->preprocess_submission($submission);
$mformdata = new stdClass();
$mformdata->context = $this->context;
$mformdata->maxbytes = $this->course->maxbytes;
$mformdata->courseid = $this->course->id;
$mformdata->teacher = $teacher;
$mformdata->assignment = $assignment;
$mformdata->submission = $submission;
$mformdata->lateness = $this->display_lateness($submission->timemodified);
$mformdata->auser = $auser;
$mformdata->user = $user;
$mformdata->offset = $offset;
$mformdata->userid = $userid;
$mformdata->cm = $this->cm;
$mformdata->grading_info = $grading_info;
$mformdata->enableoutcomes = $CFG->enableoutcomes;
$mformdata->grade = $this->assignment->grade;
$mformdata->gradingdisabled = $gradingdisabled;
$mformdata->nextid = $nextid;
$mformdata->submissioncomment= $submission->submissioncomment;
$mformdata->submissioncommentformat= FORMAT_HTML;
$mformdata->submission_content= $this->print_user_files($user->id,true);
$mformdata->filter = $filter;
$mformdata->mailinfo = get_user_preferences('assignment_mailinfo', 0);
if ($assignment->assignmenttype == 'upload') {
$mformdata->fileui_options = array('subdirs'=>1, 'maxbytes'=>$assignment->maxbytes, 'maxfiles'=>$assignment->var1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
} elseif ($assignment->assignmenttype == 'uploadsingle') {
$mformdata->fileui_options = array('subdirs'=>0, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
}
$advancedgradingwarning = false;
$gradingmanager = get_grading_manager($this->context, 'mod_assignment', 'submission');
if ($gradingmethod = $gradingmanager->get_active_method()) {
$controller = $gradingmanager->get_controller($gradingmethod);
if ($controller->is_form_available()) {
$itemid = null;
if (!empty($submission->id)) {
$itemid = $submission->id;
}
if ($gradingdisabled && $itemid) {
$mformdata->advancedgradinginstance = $controller->get_current_instance($USER->id, $itemid);
} else if (!$gradingdisabled) {
$instanceid = optional_param('advancedgradinginstanceid', 0, PARAM_INT);
$mformdata->advancedgradinginstance = $controller->get_or_create_instance($instanceid, $USER->id, $itemid);
}
} else {
$advancedgradingwarning = $controller->form_unavailable_notification();
}
}
$submitform = new mod_assignment_grading_form( null, $mformdata );
if (!$display) {
$ret_data = new stdClass();
$ret_data->mform = $submitform;
if (isset($mformdata->fileui_options)) {
$ret_data->fileui_options = $mformdata->fileui_options;
}
return $ret_data;
}
if ($submitform->is_cancelled()) {
redirect('submissions.php?id='.$this->cm->id);
}
$submitform->set_data($mformdata);
$PAGE->set_title($this->course->fullname . ': ' .get_string('feedback', 'assignment').' - '.fullname($user, true));
$PAGE->set_heading($this->course->fullname);
$PAGE->navbar->add(get_string('submissions', 'assignment'), new moodle_url('/mod/assignment/submissions.php', array('id'=>$cm->id)));
$PAGE->navbar->add(fullname($user, true));
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('feedback', 'assignment').': '.fullname($user, true));
// display mform here...
if ($advancedgradingwarning) {
echo $OUTPUT->notification($advancedgradingwarning, 'error');
}
$submitform->display();
$customfeedback = $this->custom_feedbackform($submission, true);
if (!empty($customfeedback)) {
echo $customfeedback;
}
echo $OUTPUT->footer();
}
/**
* Preprocess submission before grading
*
* Called by display_submission()
* The default type does nothing here.
*
* @param object $submission The submission object
*/
function preprocess_submission(&$submission) {
}
/**
* Display all the submissions ready for grading
*
* @global object
* @global object
* @global object
* @global object
* @param string $message
* @return bool|void
*/
function display_submissions($message='') {
global $CFG, $DB, $USER, $DB, $OUTPUT, $PAGE;
require_once($CFG->libdir.'/gradelib.php');
/* first we check to see if the form has just been submitted
* to request user_preference updates
*/
$filters = array(self::FILTER_ALL => get_string('all'),
self::FILTER_REQUIRE_GRADING => get_string('requiregrading', 'assignment'));
$updatepref = optional_param('updatepref', 0, PARAM_BOOL);
if ($updatepref) {
$perpage = optional_param('perpage', 10, PARAM_INT);
$perpage = ($perpage <= 0) ? 10 : $perpage ;
$filter = optional_param('filter', 0, PARAM_INT);
set_user_preference('assignment_perpage', $perpage);
set_user_preference('assignment_quickgrade', optional_param('quickgrade', 0, PARAM_BOOL));
set_user_preference('assignment_filter', $filter);
}
/* next we get perpage and quickgrade (allow quick grade) params
* from database
*/
$perpage = get_user_preferences('assignment_perpage', 10);
$quickgrade = get_user_preferences('assignment_quickgrade', 0) && $this->quickgrade_mode_allowed();
$filter = get_user_preferences('assignment_filter', 0);
$grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id);
if (!empty($CFG->enableoutcomes) and !empty($grading_info->outcomes)) {
$uses_outcomes = true;
} else {
$uses_outcomes = false;
}
$page = optional_param('page', 0, PARAM_INT);
$strsaveallfeedback = get_string('saveallfeedback', 'assignment');
/// Some shortcuts to make the code read better
$course = $this->course;
$assignment = $this->assignment;
$cm = $this->cm;
$hassubmission = false;
// reset filter to all for offline assignment only.
if ($assignment->assignmenttype == 'offline') {
if ($filter == self::FILTER_SUBMITTED) {
$filter = self::FILTER_ALL;
}
} else {
$filters[self::FILTER_SUBMITTED] = get_string('submitted', 'assignment');
}
$tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->cm->id, $this->assignment->id, $this->cm->id);
$PAGE->set_title(format_string($this->assignment->name,true));
$PAGE->set_heading($this->course->fullname);
echo $OUTPUT->header();
echo '
';
//hook to allow plagiarism plugins to update status/print links.
echo plagiarism_update_status($this->course, $this->cm);
$course_context = get_context_instance(CONTEXT_COURSE, $course->id);
if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
echo '
';
/// End of fast grading form
/// Mini form for setting user preference
$formaction = new moodle_url('/mod/assignment/submissions.php', array('id'=>$this->cm->id));
$mform = new MoodleQuickForm('optionspref', 'post', $formaction, '', array('class'=>'optionspref'));
$mform->addElement('hidden', 'updatepref');
$mform->setDefault('updatepref', 1);
$mform->addElement('header', 'qgprefs', get_string('optionalsettings', 'assignment'));
$mform->addElement('select', 'filter', get_string('show'), $filters);
$mform->setDefault('filter', $filter);
$mform->addElement('text', 'perpage', get_string('pagesize', 'assignment'), array('size'=>1));
$mform->setDefault('perpage', $perpage);
if ($this->quickgrade_mode_allowed()) {
$mform->addElement('checkbox', 'quickgrade', get_string('quickgrade','assignment'));
$mform->setDefault('quickgrade', $quickgrade);
$mform->addHelpButton('quickgrade', 'quickgrade', 'assignment');
}
$mform->addElement('submit', 'savepreferences', get_string('savepreferences'));
$mform->display();
echo $OUTPUT->footer();
}
/**
* If the form was cancelled ('Cancel' or 'Next' was pressed), call cancel method
* from advanced grading (if applicable) and returns true
* If the form was submitted, validates it and returns false if validation did not pass.
* If validation passes, preprocess advanced grading (if applicable) and returns true.
*
* Note to the developers: This is NOT the correct way to implement advanced grading
* in grading form. The assignment grading was written long time ago and unfortunately
* does not fully use the mforms. Usually function is_validated() is called to
* validate the form and get_data() is called to get the data from the form.
*
* Here we have to push the calculated grade to $_POST['xgrade'] because further processing
* of the form gets the data not from form->get_data(), but from $_POST (using statement
* like $feedback = data_submitted() )
*/
protected function validate_and_preprocess_feedback() {
global $USER, $CFG;
require_once($CFG->libdir.'/gradelib.php');
if (!($feedback = data_submitted()) || !isset($feedback->userid) || !isset($feedback->offset)) {
return true; // No incoming data, nothing to validate
}
$userid = required_param('userid', PARAM_INT);
$offset = required_param('offset', PARAM_INT);
$gradinginfo = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array($userid));
$gradingdisabled = $gradinginfo->items[0]->grades[$userid]->locked || $gradinginfo->items[0]->grades[$userid]->overridden;
if ($gradingdisabled) {
return true;
}
$submissiondata = $this->display_submission($offset, $userid, false);
$mform = $submissiondata->mform;
$gradinginstance = $mform->use_advanced_grading();
if (optional_param('cancel', false, PARAM_BOOL) || optional_param('next', false, PARAM_BOOL)) {
// form was cancelled
if ($gradinginstance) {
$gradinginstance->cancel();
}
} else if ($mform->is_submitted()) {
// form was submitted (= a submit button other than 'cancel' or 'next' has been clicked)
if (!$mform->is_validated()) {
return false;
}
// preprocess advanced grading here
if ($gradinginstance) {
$data = $mform->get_data();
// create submission if it did not exist yet because we need submission->id for storing the grading instance
$submission = $this->get_submission($userid, true);
$_POST['xgrade'] = $gradinginstance->submit_and_get_grade($data->advancedgrading, $submission->id);
}
}
return true;
}
/**
* Process teacher feedback submission
*
* This is called by submissions() when a grading even has taken place.
* It gets its data from the submitted form.
*
* @global object
* @global object
* @global object
* @return object|bool The updated submission object or false
*/
function process_feedback($formdata=null) {
global $CFG, $USER, $DB;
require_once($CFG->libdir.'/gradelib.php');
if (!$feedback = data_submitted() or !confirm_sesskey()) { // No incoming data?
return false;
}
///For save and next, we need to know the userid to save, and the userid to go
///We use a new hidden field in the form, and set it to -1. If it's set, we use this
///as the userid to store
if ((int)$feedback->saveuserid !== -1){
$feedback->userid = $feedback->saveuserid;
}
if (!empty($feedback->cancel)) { // User hit cancel button
return false;
}
$grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $feedback->userid);
// store outcomes if needed
$this->process_outcomes($feedback->userid);
$submission = $this->get_submission($feedback->userid, true); // Get or make one
if (!($grading_info->items[0]->grades[$feedback->userid]->locked ||
$grading_info->items[0]->grades[$feedback->userid]->overridden) ) {
$submission->grade = $feedback->xgrade;
$submission->submissioncomment = $feedback->submissioncomment_editor['text'];
$submission->teacher = $USER->id;
$mailinfo = get_user_preferences('assignment_mailinfo', 0);
if (!$mailinfo) {
$submission->mailed = 1; // treat as already mailed
} else {
$submission->mailed = 0; // Make sure mail goes out (again, even)
}
$submission->timemarked = time();
unset($submission->data1); // Don't need to update this.
unset($submission->data2); // Don't need to update this.
if (empty($submission->timemodified)) { // eg for offline assignments
// $submission->timemodified = time();
}
$DB->update_record('assignment_submissions', $submission);
// triger grade event
$this->update_grade($submission);
add_to_log($this->course->id, 'assignment', 'update grades',
'submissions.php?id='.$this->cm->id.'&user='.$feedback->userid, $feedback->userid, $this->cm->id);
if (!is_null($formdata)) {
if ($this->type == 'upload' || $this->type == 'uploadsingle') {
$mformdata = $formdata->mform->get_data();
$mformdata = file_postupdate_standard_filemanager($mformdata, 'files', $formdata->fileui_options, $this->context, 'mod_assignment', 'response', $submission->id);
}
}
}
return $submission;
}
function process_outcomes($userid) {
global $CFG, $USER;
if (empty($CFG->enableoutcomes)) {
return;
}
require_once($CFG->libdir.'/gradelib.php');
if (!$formdata = data_submitted() or !confirm_sesskey()) {
return;
}
$data = array();
$grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, $userid);
if (!empty($grading_info->outcomes)) {
foreach($grading_info->outcomes as $n=>$old) {
$name = 'outcome_'.$n;
if (isset($formdata->{$name}[$userid]) and $old->grades[$userid]->grade != $formdata->{$name}[$userid]) {
$data[$n] = $formdata->{$name}[$userid];
}
}
}
if (count($data) > 0) {
grade_update_outcomes('mod/assignment', $this->course->id, 'mod', 'assignment', $this->assignment->id, $userid, $data);
}
}
/**
* Load the submission object for a particular user
*
* @global object
* @global object
* @param int $userid int The id of the user whose submission we want or 0 in which case USER->id is used
* @param bool $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
* @param bool $teachermodified student submission set if false
* @return object|bool The submission or false (if $createnew is false and there is no existing submission).
*/
function get_submission($userid=0, $createnew=false, $teachermodified=false) {
global $USER, $DB;
if (empty($userid)) {
$userid = $USER->id;
}
$submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
if ($submission) {
return $submission;
} else if (!$createnew) {
return false;
}
$newsubmission = $this->prepare_new_submission($userid, $teachermodified);
$DB->insert_record("assignment_submissions", $newsubmission);
return $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
}
/**
* Check the given submission is complete. Preliminary rows are often created in the assignment_submissions
* table before a submission actually takes place. This function checks to see if the given submission has actually
* been submitted.
*
* @param stdClass $submission The submission we want to check for completion
* @return bool Indicates if the submission was found to be complete
*/
public function is_submitted_with_required_data($submission) {
return $submission->timemodified;
}
/**
* Instantiates a new submission object for a given user
*
* Sets the assignment, userid and times, everything else is set to default values.
*
* @param int $userid The userid for which we want a submission object
* @param bool $teachermodified student submission set if false
* @return object The submission
*/
function prepare_new_submission($userid, $teachermodified=false) {
$submission = new stdClass();
$submission->assignment = $this->assignment->id;
$submission->userid = $userid;
$submission->timecreated = time();
// teachers should not be modifying modified date, except offline assignments
if ($teachermodified) {
$submission->timemodified = 0;
} else {
$submission->timemodified = $submission->timecreated;
}
$submission->numfiles = 0;
$submission->data1 = '';
$submission->data2 = '';
$submission->grade = -1;
$submission->submissioncomment = '';
$submission->format = 0;
$submission->teacher = 0;
$submission->timemarked = 0;
$submission->mailed = 0;
return $submission;
}
/**
* Return all assignment submissions by ENROLLED students (even empty)
*
* @param string $sort optional field names for the ORDER BY in the sql query
* @param string $dir optional specifying the sort direction, defaults to DESC
* @return array The submission objects indexed by id
*/
function get_submissions($sort='', $dir='DESC') {
return assignment_get_all_submissions($this->assignment, $sort, $dir);
}
/**
* Counts all complete (real) assignment submissions by enrolled students
*
* @param int $groupid (optional) If nonzero then count is restricted to this group
* @return int The number of submissions
*/
function count_real_submissions($groupid=0) {
global $CFG;
global $DB;
// Grab the context assocated with our course module
$context = get_context_instance(CONTEXT_MODULE, $this->cm->id);
// Get ids of users enrolled in the given course.
list($enroledsql, $params) = get_enrolled_sql($context, 'mod/assignment:view', $groupid);
$params['assignmentid'] = $this->cm->instance;
// Get ids of users enrolled in the given course.
return $DB->count_records_sql("SELECT COUNT('x')
FROM {assignment_submissions} s
LEFT JOIN {assignment} a ON a.id = s.assignment
INNER JOIN ($enroledsql) u ON u.id = s.userid
WHERE s.assignment = :assignmentid AND
s.timemodified > 0", $params);
}
/**
* Alerts teachers by email of new or changed assignments that need grading
*
* First checks whether the option to email teachers is set for this assignment.
* Sends an email to ALL teachers in the course (or in the group if using separate groups).
* Uses the methods email_teachers_text() and email_teachers_html() to construct the content.
*
* @global object
* @global object
* @param $submission object The submission that has changed
* @return void
*/
function email_teachers($submission) {
global $CFG, $DB;
if (empty($this->assignment->emailteachers)) { // No need to do anything
return;
}
$user = $DB->get_record('user', array('id'=>$submission->userid));
if ($teachers = $this->get_graders($user)) {
$strassignments = get_string('modulenameplural', 'assignment');
$strassignment = get_string('modulename', 'assignment');
$strsubmitted = get_string('submitted', 'assignment');
foreach ($teachers as $teacher) {
$info = new stdClass();
$info->username = fullname($user, true);
$info->assignment = format_string($this->assignment->name,true);
$info->url = $CFG->wwwroot.'/mod/assignment/submissions.php?id='.$this->cm->id;
$info->timeupdated = userdate($submission->timemodified, '%c', $teacher->timezone);
$postsubject = $strsubmitted.': '.$info->username.' -> '.$this->assignment->name;
$posttext = $this->email_teachers_text($info);
$posthtml = ($teacher->mailformat == 1) ? $this->email_teachers_html($info) : '';
$eventdata = new stdClass();
$eventdata->modulename = 'assignment';
$eventdata->userfrom = $user;
$eventdata->userto = $teacher;
$eventdata->subject = $postsubject;
$eventdata->fullmessage = $posttext;
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = $posthtml;
$eventdata->smallmessage = $postsubject;
$eventdata->name = 'assignment_updates';
$eventdata->component = 'mod_assignment';
$eventdata->notification = 1;
$eventdata->contexturl = $info->url;
$eventdata->contexturlname = $info->assignment;
message_send($eventdata);
}
}
}
/**
* @param string $filearea
* @param array $args
* @return bool
*/
function send_file($filearea, $args) {
debugging('plugin does not implement file sending', DEBUG_DEVELOPER);
return false;
}
/**
* Returns a list of teachers that should be grading given submission
*
* @param object $user
* @return array
*/
function get_graders($user) {
global $DB;
//potential graders
list($enrolledsql, $params) = get_enrolled_sql($this->context, 'mod/assignment:grade', 0, true);
$sql = "SELECT u.*
FROM {user} u
JOIN ($enrolledsql) je ON je.id = u.id";
$potgraders = $DB->get_records_sql($sql, $params);
$graders = array();
if (groups_get_activity_groupmode($this->cm) == SEPARATEGROUPS) { // Separate groups are being used
if ($groups = groups_get_all_groups($this->course->id, $user->id)) { // Try to find all groups
foreach ($groups as $group) {
foreach ($potgraders as $t) {
if ($t->id == $user->id) {
continue; // do not send self
}
if (groups_is_member($group->id, $t->id)) {
$graders[$t->id] = $t;
}
}
}
} else {
// user not in group, try to find graders without group
foreach ($potgraders as $t) {
if ($t->id == $user->id) {
continue; // do not send self
}
if (!groups_get_all_groups($this->course->id, $t->id)) { //ugly hack
$graders[$t->id] = $t;
}
}
}
} else {
foreach ($potgraders as $t) {
if ($t->id == $user->id) {
continue; // do not send self
}
$graders[$t->id] = $t;
}
}
return $graders;
}
/**
* Creates the text content for emails to teachers
*
* @param $info object The info used by the 'emailteachermail' language string
* @return string
*/
function email_teachers_text($info) {
$posttext = format_string($this->course->shortname, true, array('context' => $this->coursecontext)).' -> '.
$this->strassignments.' -> '.
format_string($this->assignment->name, true, array('context' => $this->context))."\n";
$posttext .= '---------------------------------------------------------------------'."\n";
$posttext .= get_string("emailteachermail", "assignment", $info)."\n";
$posttext .= "\n---------------------------------------------------------------------\n";
return $posttext;
}
/**
* Creates the html content for emails to teachers
*
* @param $info object The info used by the 'emailteachermailhtml' language string
* @return string
*/
function email_teachers_html($info) {
global $CFG;
$posthtml = '
';
$posthtml .= '';
return $posthtml;
}
/**
* Produces a list of links to the files uploaded by a user
*
* @param $userid int optional id of the user. If 0 then $USER->id is used.
* @param $return boolean optional defaults to false. If true the list is returned rather than printed
* @return string optional
*/
function print_user_files($userid=0, $return=false) {
global $CFG, $USER, $OUTPUT;
if (!$userid) {
if (!isloggedin()) {
return '';
}
$userid = $USER->id;
}
$output = '';
$submission = $this->get_submission($userid);
if (!$submission) {
return $output;
}
$fs = get_file_storage();
$files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false);
if (!empty($files)) {
require_once($CFG->dirroot . '/mod/assignment/locallib.php');
if ($CFG->enableportfolios) {
require_once($CFG->libdir.'/portfoliolib.php');
$button = new portfolio_add_button();
}
foreach ($files as $file) {
$filename = $file->get_filename();
$mimetype = $file->get_mimetype();
$path = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_assignment/submission/'.$submission->id.'/'.$filename);
$output .= ''.s($filename).'';
if ($CFG->enableportfolios && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
$button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id, 'fileid' => $file->get_id()), '/mod/assignment/locallib.php');
$button->set_format_by_file($file);
$output .= $button->to_html(PORTFOLIO_ADD_ICON_LINK);
}
if ($CFG->enableplagiarism) {
require_once($CFG->libdir.'/plagiarismlib.php');
$output .= plagiarism_get_links(array('userid'=>$userid, 'file'=>$file, 'cmid'=>$this->cm->id, 'course'=>$this->course, 'assignment'=>$this->assignment));
$output .= ' ';
}
}
if ($CFG->enableportfolios && count($files) > 1 && $this->portfolio_exportable() && has_capability('mod/assignment:exportownsubmission', $this->context)) {
$button->set_callback_options('assignment_portfolio_caller', array('id' => $this->cm->id, 'submissionid' => $submission->id), '/mod/assignment/locallib.php');
$output .= ' ' . $button->to_html(PORTFOLIO_ADD_TEXT_LINK);
}
}
$output = '
'.$output.'
';
if ($return) {
return $output;
}
echo $output;
}
/**
* Count the files uploaded by a given user
*
* @param $itemid int The submission's id as the file's itemid.
* @return int
*/
function count_user_files($itemid) {
$fs = get_file_storage();
$files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $itemid, "id", false);
return count($files);
}
/**
* Returns true if the student is allowed to submit
*
* Checks that the assignment has started and, if the option to prevent late
* submissions is set, also checks that the assignment has not yet closed.
* @return boolean
*/
function isopen() {
$time = time();
if ($this->assignment->preventlate && $this->assignment->timedue) {
return ($this->assignment->timeavailable <= $time && $time <= $this->assignment->timedue);
} else {
return ($this->assignment->timeavailable <= $time);
}
}
/**
* Return true if is set description is hidden till available date
*
* This is needed by calendar so that hidden descriptions do not
* come up in upcoming events.
*
* Check that description is hidden till available date
* By default return false
* Assignments types should implement this method if needed
* @return boolen
*/
function description_is_hidden() {
return false;
}
/**
* Return an outline of the user's interaction with the assignment
*
* The default method prints the grade and timemodified
* @param $grade object
* @return object with properties ->info and ->time
*/
function user_outline($grade) {
$result = new stdClass();
$result->info = get_string('grade').': '.$grade->str_long_grade;
$result->time = $grade->dategraded;
return $result;
}
/**
* Print complete information about the user's interaction with the assignment
*
* @param $user object
*/
function user_complete($user, $grade=null) {
global $OUTPUT;
if ($submission = $this->get_submission($user->id)) {
$fs = get_file_storage();
if ($files = $fs->get_area_files($this->context->id, 'mod_assignment', 'submission', $submission->id, "timemodified", false)) {
$countfiles = count($files)." ".get_string("uploadedfiles", "assignment");
foreach ($files as $file) {
$countfiles .= "; ".$file->get_filename();
}
}
echo $OUTPUT->box_start();
echo get_string("lastmodified").": ";
echo userdate($submission->timemodified);
echo $this->display_lateness($submission->timemodified);
$this->print_user_files($user->id);
echo ' ';
$this->view_feedback($submission);
echo $OUTPUT->box_end();
} else {
if ($grade) {
echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
if ($grade->str_feedback) {
echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
}
}
print_string("notsubmittedyet", "assignment");
}
}
/**
* Return a string indicating how late a submission is
*
* @param $timesubmitted int
* @return string
*/
function display_lateness($timesubmitted) {
return assignment_display_lateness($timesubmitted, $this->assignment->timedue);
}
/**
* Empty method stub for all delete actions.
*/
function delete() {
//nothing by default
redirect('view.php?id='.$this->cm->id);
}
/**
* Empty custom feedback grading form.
*/
function custom_feedbackform($submission, $return=false) {
//nothing by default
return '';
}
/**
* Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
* for the course (see resource).
*
* Given a course_module object, this function returns any "extra" information that may be needed
* when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
*
* @param $coursemodule object The coursemodule object (record).
* @return cached_cm_info Object used to customise appearance on course page
*/
function get_coursemodule_info($coursemodule) {
return null;
}
/**
* Plugin cron method - do not use $this here, create new assignment instances if needed.
* @return void
*/
function cron() {
//no plugin cron by default - override if needed
}
/**
* Reset all submissions
*/
function reset_userdata($data) {
global $CFG, $DB;
if (!$DB->count_records('assignment', array('course'=>$data->courseid, 'assignmenttype'=>$this->type))) {
return array(); // no assignments of this type present
}
$componentstr = get_string('modulenameplural', 'assignment');
$status = array();
$typestr = get_string('type'.$this->type, 'assignment');
// ugly hack to support pluggable assignment type titles...
if($typestr === '[[type'.$this->type.']]'){
$typestr = get_string('type'.$this->type, 'assignment_'.$this->type);
}
if (!empty($data->reset_assignment_submissions)) {
$assignmentssql = "SELECT a.id
FROM {assignment} a
WHERE a.course=? AND a.assignmenttype=?";
$params = array($data->courseid, $this->type);
// now get rid of all submissions and responses
$fs = get_file_storage();
if ($assignments = $DB->get_records_sql($assignmentssql, $params)) {
foreach ($assignments as $assignmentid=>$unused) {
if (!$cm = get_coursemodule_from_instance('assignment', $assignmentid)) {
continue;
}
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
$fs->delete_area_files($context->id, 'mod_assignment', 'submission');
$fs->delete_area_files($context->id, 'mod_assignment', 'response');
}
}
$DB->delete_records_select('assignment_submissions', "assignment IN ($assignmentssql)", $params);
$status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallsubmissions','assignment').': '.$typestr, 'error'=>false);
if (empty($data->reset_gradebook_grades)) {
// remove all grades from gradebook
assignment_reset_gradebook($data->courseid, $this->type);
}
}
/// updating dates - shift may be negative too
if ($data->timeshift) {
shift_course_mod_dates('assignment', array('timedue', 'timeavailable'), $data->timeshift, $data->courseid);
$status[] = array('component'=>$componentstr, 'item'=>get_string('datechanged').': '.$typestr, 'error'=>false);
}
return $status;
}
function portfolio_exportable() {
return false;
}
/**
* base implementation for backing up subtype specific information
* for one single module
*
* @param filehandle $bf file handle for xml file to write to
* @param mixed $preferences the complete backup preference object
*
* @return boolean
*
* @static
*/
static function backup_one_mod($bf, $preferences, $assignment) {
return true;
}
/**
* base implementation for backing up subtype specific information
* for one single submission
*
* @param filehandle $bf file handle for xml file to write to
* @param mixed $preferences the complete backup preference object
* @param object $submission the assignment submission db record
*
* @return boolean
*
* @static
*/
static function backup_one_submission($bf, $preferences, $assignment, $submission) {
return true;
}
/**
* base implementation for restoring subtype specific information
* for one single module
*
* @param array $info the array representing the xml
* @param object $restore the restore preferences
*
* @return boolean
*
* @static
*/
static function restore_one_mod($info, $restore, $assignment) {
return true;
}
/**
* base implementation for restoring subtype specific information
* for one single submission
*
* @param object $submission the newly created submission
* @param array $info the array representing the xml
* @param object $restore the restore preferences
*
* @return boolean
*
* @static
*/
static function restore_one_submission($info, $restore, $assignment, $submission) {
return true;
}
} ////// End of the assignment_base class
class mod_assignment_grading_form extends moodleform {
/** @var stores the advaned grading instance (if used in grading) */
private $advancegradinginstance;
function definition() {
global $OUTPUT;
$mform =& $this->_form;
if (isset($this->_customdata->advancedgradinginstance)) {
$this->use_advanced_grading($this->_customdata->advancedgradinginstance);
}
$formattr = $mform->getAttributes();
$formattr['id'] = 'submitform';
$mform->setAttributes($formattr);
// hidden params
$mform->addElement('hidden', 'offset', ($this->_customdata->offset+1));
$mform->setType('offset', PARAM_INT);
$mform->addElement('hidden', 'userid', $this->_customdata->userid);
$mform->setType('userid', PARAM_INT);
$mform->addElement('hidden', 'nextid', $this->_customdata->nextid);
$mform->setType('nextid', PARAM_INT);
$mform->addElement('hidden', 'id', $this->_customdata->cm->id);
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'sesskey', sesskey());
$mform->setType('sesskey', PARAM_ALPHANUM);
$mform->addElement('hidden', 'mode', 'grade');
$mform->setType('mode', PARAM_TEXT);
$mform->addElement('hidden', 'menuindex', "0");
$mform->setType('menuindex', PARAM_INT);
$mform->addElement('hidden', 'saveuserid', "-1");
$mform->setType('saveuserid', PARAM_INT);
$mform->addElement('hidden', 'filter', "0");
$mform->setType('filter', PARAM_INT);
$mform->addElement('static', 'picture', $OUTPUT->user_picture($this->_customdata->user),
fullname($this->_customdata->user, true) . ' ' .
userdate($this->_customdata->submission->timemodified) .
$this->_customdata->lateness );
$this->add_submission_content();
$this->add_grades_section();
$this->add_feedback_section();
if ($this->_customdata->submission->timemarked) {
$datestring = userdate($this->_customdata->submission->timemarked)." (".format_time(time() - $this->_customdata->submission->timemarked).")";
$mform->addElement('header', 'Last Grade', get_string('lastgrade', 'assignment'));
$mform->addElement('static', 'picture', $OUTPUT->user_picture($this->_customdata->teacher) ,
fullname($this->_customdata->teacher,true).
' '.$datestring);
}
// buttons
$this->add_action_buttons();
}
/**
* Gets or sets the instance for advanced grading
*
* @param gradingform_instance $gradinginstance
*/
public function use_advanced_grading($gradinginstance = false) {
if ($gradinginstance !== false) {
$this->advancegradinginstance = $gradinginstance;
}
return $this->advancegradinginstance;
}
function add_grades_section() {
global $CFG;
$mform =& $this->_form;
$attributes = array();
if ($this->_customdata->gradingdisabled) {
$attributes['disabled'] ='disabled';
}
$mform->addElement('header', 'Grades', get_string('grades', 'grades'));
$grademenu = make_grades_menu($this->_customdata->assignment->grade);
if ($gradinginstance = $this->use_advanced_grading()) {
$gradinginstance->get_controller()->set_grade_range($grademenu);
$gradingelement = $mform->addElement('grading', 'advancedgrading', get_string('grade').':', array('gradinginstance' => $gradinginstance));
if ($this->_customdata->gradingdisabled) {
$gradingelement->freeze();
} else {
$mform->addElement('hidden', 'advancedgradinginstanceid', $gradinginstance->get_id());
}
} else {
// use simple direct grading
$grademenu['-1'] = get_string('nograde');
$mform->addElement('select', 'xgrade', get_string('grade').':', $grademenu, $attributes);
$mform->setDefault('xgrade', $this->_customdata->submission->grade ); //@fixme some bug when element called 'grade' makes it break
$mform->setType('xgrade', PARAM_INT);
}
if (!empty($this->_customdata->enableoutcomes)) {
foreach($this->_customdata->grading_info->outcomes as $n=>$outcome) {
$options = make_grades_menu(-$outcome->scaleid);
if ($outcome->grades[$this->_customdata->submission->userid]->locked) {
$options[0] = get_string('nooutcome', 'grades');
$mform->addElement('static', 'outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->name.':',
$options[$outcome->grades[$this->_customdata->submission->userid]->grade]);
} else {
$options[''] = get_string('nooutcome', 'grades');
$attributes = array('id' => 'menuoutcome_'.$n );
$mform->addElement('select', 'outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->name.':', $options, $attributes );
$mform->setType('outcome_'.$n.'['.$this->_customdata->userid.']', PARAM_INT);
$mform->setDefault('outcome_'.$n.'['.$this->_customdata->userid.']', $outcome->grades[$this->_customdata->submission->userid]->grade );
}
}
}
$course_context = get_context_instance(CONTEXT_MODULE , $this->_customdata->cm->id);
if (has_capability('gradereport/grader:view', $course_context) && has_capability('moodle/grade:viewall', $course_context)) {
$grade = ''.
$this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_grade . '';
}else{
$grade = $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_grade;
}
$mform->addElement('static', 'finalgrade', get_string('currentgrade', 'assignment').':' ,$grade);
$mform->setType('finalgrade', PARAM_INT);
}
/**
*
* @global core_renderer $OUTPUT
*/
function add_feedback_section() {
global $OUTPUT;
$mform =& $this->_form;
$mform->addElement('header', 'Feed Back', get_string('feedback', 'grades'));
if ($this->_customdata->gradingdisabled) {
$mform->addElement('static', 'disabledfeedback', $this->_customdata->grading_info->items[0]->grades[$this->_customdata->userid]->str_feedback );
} else {
// visible elements
$mform->addElement('editor', 'submissioncomment_editor', get_string('feedback', 'assignment').':', null, $this->get_editor_options() );
$mform->setType('submissioncomment_editor', PARAM_RAW); // to be cleaned before display
$mform->setDefault('submissioncomment_editor', $this->_customdata->submission->submissioncomment);
//$mform->addRule('submissioncomment', get_string('required'), 'required', null, 'client');
switch ($this->_customdata->assignment->assignmenttype) {
case 'upload' :
case 'uploadsingle' :
$mform->addElement('filemanager', 'files_filemanager', get_string('responsefiles', 'assignment'). ':', null, $this->_customdata->fileui_options);
break;
default :
break;
}
$mform->addElement('hidden', 'mailinfo_h', "0");
$mform->setType('mailinfo_h', PARAM_INT);
$mform->addElement('checkbox', 'mailinfo',get_string('enablenotification','assignment').
$OUTPUT->help_icon('enablenotification', 'assignment') .':' );
$mform->setType('mailinfo', PARAM_INT);
}
}
function add_action_buttons() {
$mform =& $this->_form;
//if there are more to be graded.
if ($this->_customdata->nextid>0) {
$buttonarray=array();
$buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
//@todo: fix accessibility: javascript dependency not necessary
$buttonarray[] = &$mform->createElement('submit', 'saveandnext', get_string('saveandnext'));
$buttonarray[] = &$mform->createElement('submit', 'next', get_string('next'));
$buttonarray[] = &$mform->createElement('cancel');
} else {
$buttonarray=array();
$buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('savechanges'));
$buttonarray[] = &$mform->createElement('cancel');
}
$mform->addGroup($buttonarray, 'grading_buttonar', '', array(' '), false);
$mform->closeHeaderBefore('grading_buttonar');
$mform->setType('grading_buttonar', PARAM_RAW);
}
function add_submission_content() {
$mform =& $this->_form;
$mform->addElement('header', 'Submission', get_string('submission', 'assignment'));
$mform->addElement('static', '', '' , $this->_customdata->submission_content );
}
protected function get_editor_options() {
$editoroptions = array();
$editoroptions['component'] = 'mod_assignment';
$editoroptions['filearea'] = 'feedback';
$editoroptions['noclean'] = false;
$editoroptions['maxfiles'] = 0; //TODO: no files for now, we need to first implement assignment_feedback area, integration with gradebook, files support in quickgrading, etc. (skodak)
$editoroptions['maxbytes'] = $this->_customdata->maxbytes;
$editoroptions['context'] = $this->_customdata->context;
return $editoroptions;
}
public function set_data($data) {
$editoroptions = $this->get_editor_options();
if (!isset($data->text)) {
$data->text = '';
}
if (!isset($data->format)) {
$data->textformat = FORMAT_HTML;
} else {
$data->textformat = $data->format;
}
if (!empty($this->_customdata->submission->id)) {
$itemid = $this->_customdata->submission->id;
} else {
$itemid = null;
}
switch ($this->_customdata->assignment->assignmenttype) {
case 'upload' :
case 'uploadsingle' :
$data = file_prepare_standard_filemanager($data, 'files', $editoroptions, $this->_customdata->context, 'mod_assignment', 'response', $itemid);
break;
default :
break;
}
$data = file_prepare_standard_editor($data, 'submissioncomment', $editoroptions, $this->_customdata->context, $editoroptions['component'], $editoroptions['filearea'], $itemid);
return parent::set_data($data);
}
public function get_data() {
$data = parent::get_data();
if (!empty($this->_customdata->submission->id)) {
$itemid = $this->_customdata->submission->id;
} else {
$itemid = null; //TODO: this is wrong, itemid MUST be known when saving files!! (skodak)
}
if ($data) {
$editoroptions = $this->get_editor_options();
switch ($this->_customdata->assignment->assignmenttype) {
case 'upload' :
case 'uploadsingle' :
$data = file_postupdate_standard_filemanager($data, 'files', $editoroptions, $this->_customdata->context, 'mod_assignment', 'response', $itemid);
break;
default :
break;
}
$data = file_postupdate_standard_editor($data, 'submissioncomment', $editoroptions, $this->_customdata->context, $editoroptions['component'], $editoroptions['filearea'], $itemid);
}
if ($this->use_advanced_grading() && !isset($data->advancedgrading)) {
$data->advancedgrading = null;
}
return $data;
}
}
/// OTHER STANDARD FUNCTIONS ////////////////////////////////////////////////////////
/**
* Deletes an assignment instance
*
* This is done by calling the delete_instance() method of the assignment type class
*/
function assignment_delete_instance($id){
global $CFG, $DB;
if (! $assignment = $DB->get_record('assignment', array('id'=>$id))) {
return false;
}
// fall back to base class if plugin missing
$classfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
if (file_exists($classfile)) {
require_once($classfile);
$assignmentclass = "assignment_$assignment->assignmenttype";
} else {
debugging("Missing assignment plug-in: {$assignment->assignmenttype}. Using base class for deleting instead.");
$assignmentclass = "assignment_base";
}
$ass = new $assignmentclass();
return $ass->delete_instance($assignment);
}
/**
* Updates an assignment instance
*
* This is done by calling the update_instance() method of the assignment type class
*/
function assignment_update_instance($assignment){
global $CFG;
$assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_PLUGIN);
require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
$assignmentclass = "assignment_$assignment->assignmenttype";
$ass = new $assignmentclass();
return $ass->update_instance($assignment);
}
/**
* Adds an assignment instance
*
* This is done by calling the add_instance() method of the assignment type class
*/
function assignment_add_instance($assignment) {
global $CFG;
$assignment->assignmenttype = clean_param($assignment->assignmenttype, PARAM_PLUGIN);
require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
$assignmentclass = "assignment_$assignment->assignmenttype";
$ass = new $assignmentclass();
return $ass->add_instance($assignment);
}
/**
* Returns an outline of a user interaction with an assignment
*
* This is done by calling the user_outline() method of the assignment type class
*/
function assignment_user_outline($course, $user, $mod, $assignment) {
global $CFG;
require_once("$CFG->libdir/gradelib.php");
require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
$assignmentclass = "assignment_$assignment->assignmenttype";
$ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
$grades = grade_get_grades($course->id, 'mod', 'assignment', $assignment->id, $user->id);
if (!empty($grades->items[0]->grades)) {
return $ass->user_outline(reset($grades->items[0]->grades));
} else {
return null;
}
}
/**
* Prints the complete info about a user's interaction with an assignment
*
* This is done by calling the user_complete() method of the assignment type class
*/
function assignment_user_complete($course, $user, $mod, $assignment) {
global $CFG;
require_once("$CFG->libdir/gradelib.php");
require_once("$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php");
$assignmentclass = "assignment_$assignment->assignmenttype";
$ass = new $assignmentclass($mod->id, $assignment, $mod, $course);
$grades = grade_get_grades($course->id, 'mod', 'assignment', $assignment->id, $user->id);
if (empty($grades->items[0]->grades)) {
$grade = false;
} else {
$grade = reset($grades->items[0]->grades);
}
return $ass->user_complete($user, $grade);
}
/**
* Function to be run periodically according to the moodle cron
*
* Finds all assignment notifications that have yet to be mailed out, and mails them
*/
function assignment_cron () {
global $CFG, $USER, $DB;
/// first execute all crons in plugins
if ($plugins = get_plugin_list('assignment')) {
foreach ($plugins as $plugin=>$dir) {
require_once("$dir/assignment.class.php");
$assignmentclass = "assignment_$plugin";
$ass = new $assignmentclass();
$ass->cron();
}
}
/// Notices older than 1 day will not be mailed. This is to avoid the problem where
/// cron has not been running for a long time, and then suddenly people are flooded
/// with mail from the past few weeks or months
$timenow = time();
$endtime = $timenow - $CFG->maxeditingtime;
$starttime = $endtime - 24 * 3600; /// One day earlier
if ($submissions = assignment_get_unmailed_submissions($starttime, $endtime)) {
$realuser = clone($USER);
foreach ($submissions as $key => $submission) {
$DB->set_field("assignment_submissions", "mailed", "1", array("id"=>$submission->id));
}
$timenow = time();
foreach ($submissions as $submission) {
echo "Processing assignment submission $submission->id\n";
if (! $user = $DB->get_record("user", array("id"=>$submission->userid))) {
echo "Could not find user $user->id\n";
continue;
}
if (! $course = $DB->get_record("course", array("id"=>$submission->course))) {
echo "Could not find course $submission->course\n";
continue;
}
/// Override the language and timezone of the "current" user, so that
/// mail is customised for the receiver.
cron_setup_user($user, $course);
$coursecontext = get_context_instance(CONTEXT_COURSE, $submission->course);
$courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
if (!is_enrolled($coursecontext, $user->id)) {
echo fullname($user)." not an active participant in " . $courseshortname . "\n";
continue;
}
if (! $teacher = $DB->get_record("user", array("id"=>$submission->teacher))) {
echo "Could not find teacher $submission->teacher\n";
continue;
}
if (! $mod = get_coursemodule_from_instance("assignment", $submission->assignment, $course->id)) {
echo "Could not find course module for assignment id $submission->assignment\n";
continue;
}
if (! $mod->visible) { /// Hold mail notification for hidden assignments until later
continue;
}
$strassignments = get_string("modulenameplural", "assignment");
$strassignment = get_string("modulename", "assignment");
$assignmentinfo = new stdClass();
$assignmentinfo->teacher = fullname($teacher);
$assignmentinfo->assignment = format_string($submission->name,true);
$assignmentinfo->url = "$CFG->wwwroot/mod/assignment/view.php?id=$mod->id";
$postsubject = "$courseshortname: $strassignments: ".format_string($submission->name,true);
$posttext = "$courseshortname -> $strassignments -> ".format_string($submission->name,true)."\n";
$posttext .= "---------------------------------------------------------------------\n";
$posttext .= get_string("assignmentmail", "assignment", $assignmentinfo)."\n";
$posttext .= "---------------------------------------------------------------------\n";
if ($user->mailformat == 1) { // HTML
$posthtml = "
";
$posthtml .= "";
} else {
$posthtml = "";
}
$eventdata = new stdClass();
$eventdata->modulename = 'assignment';
$eventdata->userfrom = $teacher;
$eventdata->userto = $user;
$eventdata->subject = $postsubject;
$eventdata->fullmessage = $posttext;
$eventdata->fullmessageformat = FORMAT_PLAIN;
$eventdata->fullmessagehtml = $posthtml;
$eventdata->smallmessage = get_string('assignmentmailsmall', 'assignment', $assignmentinfo);
$eventdata->name = 'assignment_updates';
$eventdata->component = 'mod_assignment';
$eventdata->notification = 1;
$eventdata->contexturl = $assignmentinfo->url;
$eventdata->contexturlname = $assignmentinfo->assignment;
message_send($eventdata);
}
cron_setup_user();
}
return true;
}
/**
* Return grade for given user or all users.
*
* @param int $assignmentid id of assignment
* @param int $userid optional user id, 0 means all users
* @return array array of grades, false if none
*/
function assignment_get_user_grades($assignment, $userid=0) {
global $CFG, $DB;
if ($userid) {
$user = "AND u.id = :userid";
$params = array('userid'=>$userid);
} else {
$user = "";
}
$params['aid'] = $assignment->id;
$sql = "SELECT u.id, u.id AS userid, s.grade AS rawgrade, s.submissioncomment AS feedback, s.format AS feedbackformat,
s.teacher AS usermodified, s.timemarked AS dategraded, s.timemodified AS datesubmitted
FROM {user} u, {assignment_submissions} s
WHERE u.id = s.userid AND s.assignment = :aid
$user";
return $DB->get_records_sql($sql, $params);
}
/**
* Update activity grades
*
* @param object $assignment
* @param int $userid specific user only, 0 means all
*/
function assignment_update_grades($assignment, $userid=0, $nullifnone=true) {
global $CFG, $DB;
require_once($CFG->libdir.'/gradelib.php');
if ($assignment->grade == 0) {
assignment_grade_item_update($assignment);
} else if ($grades = assignment_get_user_grades($assignment, $userid)) {
foreach($grades as $k=>$v) {
if ($v->rawgrade == -1) {
$grades[$k]->rawgrade = null;
}
}
assignment_grade_item_update($assignment, $grades);
} else {
assignment_grade_item_update($assignment);
}
}
/**
* Update all grades in gradebook.
*/
function assignment_upgrade_grades() {
global $DB;
$sql = "SELECT COUNT('x')
FROM {assignment} a, {course_modules} cm, {modules} m
WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
$count = $DB->count_records_sql($sql);
$sql = "SELECT a.*, cm.idnumber AS cmidnumber, a.course AS courseid
FROM {assignment} a, {course_modules} cm, {modules} m
WHERE m.name='assignment' AND m.id=cm.module AND cm.instance=a.id";
$rs = $DB->get_recordset_sql($sql);
if ($rs->valid()) {
// too much debug output
$pbar = new progress_bar('assignmentupgradegrades', 500, true);
$i=0;
foreach ($rs as $assignment) {
$i++;
upgrade_set_timeout(60*5); // set up timeout, may also abort execution
assignment_update_grades($assignment);
$pbar->update($i, $count, "Updating Assignment grades ($i/$count).");
}
upgrade_set_timeout(); // reset to default timeout
}
$rs->close();
}
/**
* Create grade item for given assignment
*
* @param object $assignment object with extra cmidnumber
* @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
* @return int 0 if ok, error code otherwise
*/
function assignment_grade_item_update($assignment, $grades=NULL) {
global $CFG;
require_once($CFG->libdir.'/gradelib.php');
if (!isset($assignment->courseid)) {
$assignment->courseid = $assignment->course;
}
$params = array('itemname'=>$assignment->name, 'idnumber'=>$assignment->cmidnumber);
if ($assignment->grade > 0) {
$params['gradetype'] = GRADE_TYPE_VALUE;
$params['grademax'] = $assignment->grade;
$params['grademin'] = 0;
} else if ($assignment->grade < 0) {
$params['gradetype'] = GRADE_TYPE_SCALE;
$params['scaleid'] = -$assignment->grade;
} else {
$params['gradetype'] = GRADE_TYPE_TEXT; // allow text comments only
}
if ($grades === 'reset') {
$params['reset'] = true;
$grades = NULL;
}
return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, $grades, $params);
}
/**
* Delete grade item for given assignment
*
* @param object $assignment object
* @return object assignment
*/
function assignment_grade_item_delete($assignment) {
global $CFG;
require_once($CFG->libdir.'/gradelib.php');
if (!isset($assignment->courseid)) {
$assignment->courseid = $assignment->course;
}
return grade_update('mod/assignment', $assignment->courseid, 'mod', 'assignment', $assignment->id, 0, NULL, array('deleted'=>1));
}
/**
* Returns the users with data in one assignment (students and teachers)
*
* @todo: deprecated - to be deleted in 2.2
*
* @param $assignmentid int
* @return array of user objects
*/
function assignment_get_participants($assignmentid) {
global $CFG, $DB;
//Get students
$students = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
FROM {user} u,
{assignment_submissions} a
WHERE a.assignment = ? and
u.id = a.userid", array($assignmentid));
//Get teachers
$teachers = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
FROM {user} u,
{assignment_submissions} a
WHERE a.assignment = ? and
u.id = a.teacher", array($assignmentid));
//Add teachers to students
if ($teachers) {
foreach ($teachers as $teacher) {
$students[$teacher->id] = $teacher;
}
}
//Return students array (it contains an array of unique users)
return ($students);
}
/**
* Serves assignment submissions and other files.
*
* @param object $course
* @param object $cm
* @param object $context
* @param string $filearea
* @param array $args
* @param bool $forcedownload
* @return bool false if file not found, does not return if found - just send the file
*/
function assignment_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
global $CFG, $DB;
if ($context->contextlevel != CONTEXT_MODULE) {
return false;
}
require_login($course, false, $cm);
if (!$assignment = $DB->get_record('assignment', array('id'=>$cm->instance))) {
return false;
}
require_once($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
$assignmentclass = 'assignment_'.$assignment->assignmenttype;
$assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm, $course);
return $assignmentinstance->send_file($filearea, $args);
}
/**
* Checks if a scale is being used by an assignment
*
* This is used by the backup code to decide whether to back up a scale
* @param $assignmentid int
* @param $scaleid int
* @return boolean True if the scale is used by the assignment
*/
function assignment_scale_used($assignmentid, $scaleid) {
global $DB;
$return = false;
$rec = $DB->get_record('assignment', array('id'=>$assignmentid,'grade'=>-$scaleid));
if (!empty($rec) && !empty($scaleid)) {
$return = true;
}
return $return;
}
/**
* Checks if scale is being used by any instance of assignment
*
* This is used to find out if scale used anywhere
* @param $scaleid int
* @return boolean True if the scale is used by any assignment
*/
function assignment_scale_used_anywhere($scaleid) {
global $DB;
if ($scaleid and $DB->record_exists('assignment', array('grade'=>-$scaleid))) {
return true;
} else {
return false;
}
}
/**
* Make sure up-to-date events are created for all assignment instances
*
* This standard function will check all instances of this module
* and make sure there are up-to-date events created for each of them.
* If courseid = 0, then every assignment event in the site is checked, else
* only assignment events belonging to the course specified are checked.
* This function is used, in its new format, by restore_refresh_events()
*
* @param $courseid int optional If zero then all assignments for all courses are covered
* @return boolean Always returns true
*/
function assignment_refresh_events($courseid = 0) {
global $DB;
if ($courseid == 0) {
if (! $assignments = $DB->get_records("assignment")) {
return true;
}
} else {
if (! $assignments = $DB->get_records("assignment", array("course"=>$courseid))) {
return true;
}
}
$moduleid = $DB->get_field('modules', 'id', array('name'=>'assignment'));
foreach ($assignments as $assignment) {
$cm = get_coursemodule_from_id('assignment', $assignment->id);
$event = new stdClass();
$event->name = $assignment->name;
$event->description = format_module_intro('assignment', $assignment, $cm->id);
$event->timestart = $assignment->timedue;
if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assignment', 'instance'=>$assignment->id))) {
update_event($event);
} else {
$event->courseid = $assignment->course;
$event->groupid = 0;
$event->userid = 0;
$event->modulename = 'assignment';
$event->instance = $assignment->id;
$event->eventtype = 'due';
$event->timeduration = 0;
$event->visible = $DB->get_field('course_modules', 'visible', array('module'=>$moduleid, 'instance'=>$assignment->id));
add_event($event);
}
}
return true;
}
/**
* Print recent activity from all assignments in a given course
*
* This is used by the recent activity block
*/
function assignment_print_recent_activity($course, $viewfullnames, $timestart) {
global $CFG, $USER, $DB, $OUTPUT;
// do not use log table if possible, it may be huge
if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified, cm.id AS cmid, asb.userid,
u.firstname, u.lastname, u.email, u.picture
FROM {assignment_submissions} asb
JOIN {assignment} a ON a.id = asb.assignment
JOIN {course_modules} cm ON cm.instance = a.id
JOIN {modules} md ON md.id = cm.module
JOIN {user} u ON u.id = asb.userid
WHERE asb.timemodified > ? AND
a.course = ? AND
md.name = 'assignment'
ORDER BY asb.timemodified ASC", array($timestart, $course->id))) {
return false;
}
$modinfo =& get_fast_modinfo($course); // reference needed because we might load the groups
$show = array();
$grader = array();
foreach($submissions as $submission) {
if (!array_key_exists($submission->cmid, $modinfo->cms)) {
continue;
}
$cm = $modinfo->cms[$submission->cmid];
if (!$cm->uservisible) {
continue;
}
if ($submission->userid == $USER->id) {
$show[] = $submission;
continue;
}
// the act of sumbitting of assignment may be considered private - only graders will see it if specified
if (empty($CFG->assignment_showrecentsubmissions)) {
if (!array_key_exists($cm->id, $grader)) {
$grader[$cm->id] = has_capability('moodle/grade:viewall', get_context_instance(CONTEXT_MODULE, $cm->id));
}
if (!$grader[$cm->id]) {
continue;
}
}
$groupmode = groups_get_activity_groupmode($cm, $course);
if ($groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', get_context_instance(CONTEXT_MODULE, $cm->id))) {
if (isguestuser()) {
// shortcut - guest user does not belong into any group
continue;
}
if (is_null($modinfo->groups)) {
$modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
}
// this will be slow - show only users that share group with me in this cm
if (empty($modinfo->groups[$cm->id])) {
continue;
}
$usersgroups = groups_get_all_groups($course->id, $submission->userid, $cm->groupingid);
if (is_array($usersgroups)) {
$usersgroups = array_keys($usersgroups);
$intersect = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
if (empty($intersect)) {
continue;
}
}
}
$show[] = $submission;
}
if (empty($show)) {
return false;
}
echo $OUTPUT->heading(get_string('newsubmissions', 'assignment').':', 3);
foreach ($show as $submission) {
$cm = $modinfo->cms[$submission->cmid];
$link = $CFG->wwwroot.'/mod/assignment/view.php?id='.$cm->id;
print_recent_activity_note($submission->timemodified, $submission, $cm->name, $link, false, $viewfullnames);
}
return true;
}
/**
* Returns all assignments since a given time in specified forum.
*/
function assignment_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid=0, $groupid=0) {
global $CFG, $COURSE, $USER, $DB;
if ($COURSE->id == $courseid) {
$course = $COURSE;
} else {
$course = $DB->get_record('course', array('id'=>$courseid));
}
$modinfo =& get_fast_modinfo($course);
$cm = $modinfo->cms[$cmid];
$params = array();
if ($userid) {
$userselect = "AND u.id = :userid";
$params['userid'] = $userid;
} else {
$userselect = "";
}
if ($groupid) {
$groupselect = "AND gm.groupid = :groupid";
$groupjoin = "JOIN {groups_members} gm ON gm.userid=u.id";
$params['groupid'] = $groupid;
} else {
$groupselect = "";
$groupjoin = "";
}
$params['cminstance'] = $cm->instance;
$params['timestart'] = $timestart;
$userfields = user_picture::fields('u', null, 'userid');
if (!$submissions = $DB->get_records_sql("SELECT asb.id, asb.timemodified,
$userfields
FROM {assignment_submissions} asb
JOIN {assignment} a ON a.id = asb.assignment
JOIN {user} u ON u.id = asb.userid
$groupjoin
WHERE asb.timemodified > :timestart AND a.id = :cminstance
$userselect $groupselect
ORDER BY asb.timemodified ASC", $params)) {
return;
}
$groupmode = groups_get_activity_groupmode($cm, $course);
$cm_context = get_context_instance(CONTEXT_MODULE, $cm->id);
$grader = has_capability('moodle/grade:viewall', $cm_context);
$accessallgroups = has_capability('moodle/site:accessallgroups', $cm_context);
$viewfullnames = has_capability('moodle/site:viewfullnames', $cm_context);
if (is_null($modinfo->groups)) {
$modinfo->groups = groups_get_user_groups($course->id); // load all my groups and cache it in modinfo
}
$show = array();
foreach($submissions as $submission) {
if ($submission->userid == $USER->id) {
$show[] = $submission;
continue;
}
// the act of submitting of assignment may be considered private - only graders will see it if specified
if (empty($CFG->assignment_showrecentsubmissions)) {
if (!$grader) {
continue;
}
}
if ($groupmode == SEPARATEGROUPS and !$accessallgroups) {
if (isguestuser()) {
// shortcut - guest user does not belong into any group
continue;
}
// this will be slow - show only users that share group with me in this cm
if (empty($modinfo->groups[$cm->id])) {
continue;
}
$usersgroups = groups_get_all_groups($course->id, $cm->userid, $cm->groupingid);
if (is_array($usersgroups)) {
$usersgroups = array_keys($usersgroups);
$intersect = array_intersect($usersgroups, $modinfo->groups[$cm->id]);
if (empty($intersect)) {
continue;
}
}
}
$show[] = $submission;
}
if (empty($show)) {
return;
}
if ($grader) {
require_once($CFG->libdir.'/gradelib.php');
$userids = array();
foreach ($show as $id=>$submission) {
$userids[] = $submission->userid;
}
$grades = grade_get_grades($courseid, 'mod', 'assignment', $cm->instance, $userids);
}
$aname = format_string($cm->name,true);
foreach ($show as $submission) {
$tmpactivity = new stdClass();
$tmpactivity->type = 'assignment';
$tmpactivity->cmid = $cm->id;
$tmpactivity->name = $aname;
$tmpactivity->sectionnum = $cm->sectionnum;
$tmpactivity->timestamp = $submission->timemodified;
if ($grader) {
$tmpactivity->grade = $grades->items[0]->grades[$submission->userid]->str_long_grade;
}
$userfields = explode(',', user_picture::fields());
foreach ($userfields as $userfield) {
if ($userfield == 'id') {
$tmpactivity->user->{$userfield} = $submission->userid; // aliased in SQL above
} else {
$tmpactivity->user->{$userfield} = $submission->{$userfield};
}
}
$tmpactivity->user->fullname = fullname($submission, $viewfullnames);
$activities[$index++] = $tmpactivity;
}
return;
}
/**
* Print recent activity from all assignments in a given course
*
* This is used by course/recent.php
*/
function assignment_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
global $CFG, $OUTPUT;
echo '
";
}
/// GENERIC SQL FUNCTIONS
/**
* Fetch info from logs
*
* @param $log object with properties ->info (the assignment id) and ->userid
* @return array with assignment name and user firstname and lastname
*/
function assignment_log_info($log) {
global $CFG, $DB;
return $DB->get_record_sql("SELECT a.name, u.firstname, u.lastname
FROM {assignment} a, {user} u
WHERE a.id = ? AND u.id = ?", array($log->info, $log->userid));
}
/**
* Return list of marked submissions that have not been mailed out for currently enrolled students
*
* @return array
*/
function assignment_get_unmailed_submissions($starttime, $endtime) {
global $CFG, $DB;
return $DB->get_records_sql("SELECT s.*, a.course, a.name
FROM {assignment_submissions} s,
{assignment} a
WHERE s.mailed = 0
AND s.timemarked <= ?
AND s.timemarked >= ?
AND s.assignment = a.id", array($endtime, $starttime));
}
/**
* Counts all complete (real) assignment submissions by enrolled students for the given course modeule.
*
* @deprecated Since Moodle 2.2 MDL-abc - Please do not use this function any more.
* @param cm_info $cm The course module that we wish to perform the count on.
* @param int $groupid (optional) If nonzero then count is restricted to this group
* @return int The number of submissions
*/
function assignment_count_real_submissions($cm, $groupid=0) {
global $CFG, $DB;
// Grab the assignment type for the given course module
$assignmenttype = $DB->get_field($cm->modname, 'assignmenttype', array('id' => $cm->instance), MUST_EXIST);
// Create the expected class file path and class name for the returned assignemnt type
$filename = "{$CFG->dirroot}/mod/assignment/type/{$assignmenttype}/assignment.class.php";
$classname = "assignment_{$assignmenttype}";
// If the file exists and the class is not already loaded we require the class file
if (file_exists($filename) && !class_exists($classname)) {
require_once($filename);
}
// If the required class is still not loaded then we revert to assignment base
if (!class_exists($classname)) {
$classname = 'assignment_base';
}
$instance = new $classname;
// Attach the course module to the assignment type instance and then call the method for counting submissions
$instance->cm = $cm;
return $instance->count_real_submissions($groupid);
}
/**
* Return all assignment submissions by ENROLLED students (even empty)
*
* There are also assignment type methods get_submissions() wich in the default
* implementation simply call this function.
* @param $sort string optional field names for the ORDER BY in the sql query
* @param $dir string optional specifying the sort direction, defaults to DESC
* @return array The submission objects indexed by id
*/
function assignment_get_all_submissions($assignment, $sort="", $dir="DESC") {
/// Return all assignment submissions by ENROLLED students (even empty)
global $CFG, $DB;
if ($sort == "lastname" or $sort == "firstname") {
$sort = "u.$sort $dir";
} else if (empty($sort)) {
$sort = "a.timemodified DESC";
} else {
$sort = "a.$sort $dir";
}
/* not sure this is needed at all since assignment already has a course define, so this join?
$select = "s.course = '$assignment->course' AND";
if ($assignment->course == SITEID) {
$select = '';
}*/
return $DB->get_records_sql("SELECT a.*
FROM {assignment_submissions} a, {user} u
WHERE u.id = a.userid
AND a.assignment = ?
ORDER BY $sort", array($assignment->id));
}
/**
* Add a get_coursemodule_info function in case any assignment type wants to add 'extra' information
* for the course (see resource).
*
* Given a course_module object, this function returns any "extra" information that may be needed
* when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
*
* @param $coursemodule object The coursemodule object (record).
* @return cached_cm_info An object on information that the courses will know about (most noticeably, an icon).
*/
function assignment_get_coursemodule_info($coursemodule) {
global $CFG, $DB;
if (! $assignment = $DB->get_record('assignment', array('id'=>$coursemodule->instance),
'id, assignmenttype, name, intro, introformat')) {
return false;
}
$libfile = "$CFG->dirroot/mod/assignment/type/$assignment->assignmenttype/assignment.class.php";
if (file_exists($libfile)) {
require_once($libfile);
$assignmentclass = "assignment_$assignment->assignmenttype";
$ass = new $assignmentclass('staticonly');
if (!($result = $ass->get_coursemodule_info($coursemodule))) {
$result = new cached_cm_info();
$result->name = $assignment->name;
}
if ($coursemodule->showdescription) {
// Convert intro to html. Do not filter cached version, filters run at display time.
$result->content = format_module_intro('assignment', $assignment, $coursemodule->id, false);
}
return $result;
} else {
debugging('Incorrect assignment type: '.$assignment->assignmenttype);
return false;
}
}
/// OTHER GENERAL FUNCTIONS FOR ASSIGNMENTS ///////////////////////////////////////
/**
* Returns an array of installed assignment types indexed and sorted by name
*
* @return array The index is the name of the assignment type, the value its full name from the language strings
*/
function assignment_types() {
$types = array();
$names = get_plugin_list('assignment');
foreach ($names as $name=>$dir) {
$types[$name] = get_string('type'.$name, 'assignment');
// ugly hack to support pluggable assignment type titles..
if ($types[$name] == '[[type'.$name.']]') {
$types[$name] = get_string('type'.$name, 'assignment_'.$name);
}
}
asort($types);
return $types;
}
function assignment_print_overview($courses, &$htmlarray) {
global $USER, $CFG, $DB;
require_once($CFG->libdir.'/gradelib.php');
if (empty($courses) || !is_array($courses) || count($courses) == 0) {
return array();
}
if (!$assignments = get_all_instances_in_courses('assignment',$courses)) {
return;
}
$assignmentids = array();
// Do assignment_base::isopen() here without loading the whole thing for speed
foreach ($assignments as $key => $assignment) {
$time = time();
if ($assignment->timedue) {
if ($assignment->preventlate) {
$isopen = ($assignment->timeavailable <= $time && $time <= $assignment->timedue);
} else {
$isopen = ($assignment->timeavailable <= $time);
}
}
if (empty($isopen) || empty($assignment->timedue)) {
unset($assignments[$key]);
} else {
$assignmentids[] = $assignment->id;
}
}
if (empty($assignmentids)){
// no assignments to look at - we're done
return true;
}
$strduedate = get_string('duedate', 'assignment');
$strduedateno = get_string('duedateno', 'assignment');
$strgraded = get_string('graded', 'assignment');
$strnotgradedyet = get_string('notgradedyet', 'assignment');
$strnotsubmittedyet = get_string('notsubmittedyet', 'assignment');
$strsubmitted = get_string('submitted', 'assignment');
$strassignment = get_string('modulename', 'assignment');
$strreviewed = get_string('reviewed','assignment');
// NOTE: we do all possible database work here *outside* of the loop to ensure this scales
//
list($sqlassignmentids, $assignmentidparams) = $DB->get_in_or_equal($assignmentids);
// build up and array of unmarked submissions indexed by assignment id/ userid
// for use where the user has grading rights on assignment
$rs = $DB->get_recordset_sql("SELECT id, assignment, userid
FROM {assignment_submissions}
WHERE teacher = 0 AND timemarked = 0
AND assignment $sqlassignmentids", $assignmentidparams);
$unmarkedsubmissions = array();
foreach ($rs as $rd) {
$unmarkedsubmissions[$rd->assignment][$rd->userid] = $rd->id;
}
$rs->close();
// get all user submissions, indexed by assignment id
$mysubmissions = $DB->get_records_sql("SELECT assignment, timemarked, teacher, grade
FROM {assignment_submissions}
WHERE userid = ? AND
assignment $sqlassignmentids", array_merge(array($USER->id), $assignmentidparams));
foreach ($assignments as $assignment) {
$grading_info = grade_get_grades($assignment->course, 'mod', 'assignment', $assignment->id, $USER->id);
$final_grade = $grading_info->items[0]->grades[$USER->id];
$str = '