Merge branch 'master_MDL-34544' of git://github.com/danmarsden/moodle
[moodle.git] / mod / assign / locallib.php
index bf8c064..2d59aa1 100644 (file)
@@ -215,6 +215,28 @@ class assign {
         return $this->submissionplugins;
     }
 
+    /**
+     * Is blind marking enabled and reveal identities not set yet?
+     *
+     * @return bool
+     */
+    public function is_blind_marking() {
+        return $this->get_instance()->blindmarking && !$this->get_instance()->revealidentities;
+    }
+
+    /**
+     * Does an assignment have submission(s) or grade(s) already?
+     *
+     * @return bool
+     */
+    public function has_submissions_or_grades() {
+        $allgrades = $this->count_grades();
+        $allsubmissions = $this->count_submissions();
+        if (($allgrades == 0) && ($allsubmissions == 0)) {
+            return false;
+        }
+        return true;
+    }
 
     /**
      * get a specific submission plugin by its type
@@ -222,9 +244,12 @@ class assign {
      * @param string $type
      * @return mixed assign_plugin|null
      */
-    private function get_plugin_by_type($subtype, $type) {
+    public function get_plugin_by_type($subtype, $type) {
         $shortsubtype = substr($subtype, strlen('assign'));
         $name = $shortsubtype . 'plugins';
+        if ($name != 'feedbackplugins' && $name != 'submissionplugins') {
+            return null;
+        }
         $pluginlist = $this->$name;
         foreach ($pluginlist as $plugin) {
             if ($plugin->get_type() == $type) {
@@ -283,6 +308,14 @@ class assign {
         return $result;
     }
 
+    /**
+     * Expose the renderer to plugins
+     *
+     * @return assign_renderer
+     */
+    public function get_renderer() {
+        return $this->output;
+    }
 
     /**
      * Display the assignment, used by view.php
@@ -303,25 +336,23 @@ class assign {
             if ($this->process_save_submission($mform)) {
                 $action = 'view';
             }
-         } else if ($action == 'lock') {
+        } else if ($action == 'lock') {
             $this->process_lock();
             $action = 'grading';
-         } else if ($action == 'reverttodraft') {
+        } else if ($action == 'reverttodraft') {
             $this->process_revert_to_draft();
             $action = 'grading';
-         } else if ($action == 'unlock') {
+        } else if ($action == 'unlock') {
             $this->process_unlock();
             $action = 'grading';
-         } else if ($action == 'confirmsubmit') {
+        } else if ($action == 'confirmsubmit') {
             $action = 'submit';
             if ($this->process_submit_for_grading($mform)) {
                 $action = 'view';
             }
-            // save and show next button
-         } else if ($action == 'batchgradingoperation') {
-            $this->process_batch_grading_operation();
-            $action = 'grading';
-         } else if ($action == 'submitgrade') {
+        } else if ($action == 'gradingbatchoperation') {
+            $action = $this->process_grading_batch_operation($mform);
+        } else if ($action == 'submitgrade') {
             if (optional_param('saveandshownext', null, PARAM_ALPHA)) {
                 //save and show next
                 $action = 'grade';
@@ -343,12 +374,20 @@ class assign {
                 //cancel button
                 $action = 'grading';
             }
-        }else if ($action == 'quickgrade') {
+        } else if ($action == 'quickgrade') {
             $message = $this->process_save_quick_grades();
             $action = 'quickgradingresult';
-        }else if ($action == 'saveoptions') {
+        } else if ($action == 'saveoptions') {
             $this->process_save_grading_options();
             $action = 'grading';
+        } else if ($action == 'saveextension') {
+            $action = 'grantextension';
+            if ($this->process_save_extension($mform)) {
+                $action = 'grading';
+            }
+        } else if ($action == 'revealidentitiesconfirm') {
+            $this->process_reveal_identities();
+            $action = 'grading';
         }
 
         $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT));
@@ -378,6 +417,14 @@ class assign {
             $o .= $this->download_submissions();
         } else if ($action == 'submit') {
             $o .= $this->check_submit_for_grading($mform);
+        } else if ($action == 'grantextension') {
+            $o .= $this->view_grant_extension($mform);
+        } else if ($action == 'revealidentities') {
+            $o .= $this->view_reveal_identities_confirm($mform);
+        } else if ($action == 'plugingradingbatchoperation') {
+            $o .= $this->view_plugin_grading_batch_operation($mform);
+        } else if ($action == 'viewpluginpage') {
+             $o .= $this->view_plugin_page();
         } else {
             $o .= $this->view_submission_page();
         }
@@ -409,15 +456,20 @@ class assign {
         $update->intro = $formdata->intro;
         $update->introformat = $formdata->introformat;
         $update->alwaysshowdescription = $formdata->alwaysshowdescription;
-        $update->preventlatesubmissions = $formdata->preventlatesubmissions;
         $update->submissiondrafts = $formdata->submissiondrafts;
         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
         $update->sendnotifications = $formdata->sendnotifications;
         $update->sendlatenotifications = $formdata->sendlatenotifications;
         $update->duedate = $formdata->duedate;
+        $update->cutoffdate = $formdata->cutoffdate;
         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
         $update->grade = $formdata->grade;
         $update->completionsubmit = !empty($formdata->completionsubmit);
+        $update->teamsubmission = $formdata->teamsubmission;
+        $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
+        $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
+        $update->blindmarking = $formdata->blindmarking;
+
         $returnid = $DB->insert_record('assign', $update);
         $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
         // cache the course record
@@ -510,6 +562,75 @@ class assign {
         return $result;
     }
 
+    /**
+     * Actual implementation of the reset course functionality, delete all the
+     * assignment submissions for course $data->courseid.
+     *
+     * @param stdClass $data the data submitted from the reset course.
+     * @return array status array
+     */
+    public function reset_userdata($data) {
+        global $CFG,$DB;
+
+        $componentstr = get_string('modulenameplural', 'assign');
+        $status = array();
+
+        $fs = get_file_storage();
+        if (!empty($data->reset_assign_submissions)) {
+            // Delete files associated with this assignment.
+            foreach ($this->submissionplugins as $plugin) {
+                $fileareas = $plugin->get_file_areas();
+                foreach ($fileareas as $filearea) {
+                    $fs->delete_area_files($this->context->id, 'mod_assign', $filearea);
+                }
+
+                if (!$plugin->delete_instance()) {
+                    $status[] = array('component' => $componentstr,
+                                      'item' => get_string('deleteallsubmissions', 'assign'),
+                                      'error' => $plugin->get_error());
+                }
+            }
+
+            foreach ($this->feedbackplugins as $plugin) {
+                $fileareas = $plugin->get_file_areas();
+                foreach ($fileareas as $filearea) {
+                    $fs->delete_area_files($this->context->id, 'mod_assign', $filearea);
+                }
+
+                if (!$plugin->delete_instance()) {
+                    $status[] = array('component' => $componentstr,
+                                      'item' => get_string('deleteallsubmissions', 'assign'),
+                                      'error' => $plugin->get_error());
+                }
+            }
+
+            $assignssql = "SELECT a.id
+                             FROM {assign} a
+                           WHERE a.course = :course";
+            $params = array ("course" => $data->courseid);
+
+            $DB->delete_records_select('assign_submission', "assignment IN ($assignssql)", $params);
+            $status[] = array('component' => $componentstr,
+                              'item' => get_string('deleteallsubmissions', 'assign'),
+                              'error' => false);
+
+            if (empty($data->reset_gradebook_grades)) {
+                // Remove all grades from gradebook.
+                require_once($CFG->dirroot.'/mod/assign/lib.php');
+                assign_reset_gradebook($data->courseid);
+            }
+        }
+        // Updating dates - shift may be negative too.
+        if ($data->timeshift) {
+            shift_course_mod_dates('assign', array('duedate', 'allowsubmissionsfromdate', 'cutoffdate'), $data->timeshift, $data->courseid);
+            $status[] = array('component' => $componentstr,
+                              'item' => get_string('datechanged'),
+                              'error' => false);
+        }
+
+        return $status;
+    }
+
     /**
      * Update the settings for a single plugin
      *
@@ -629,15 +750,20 @@ class assign {
         $update->intro = $formdata->intro;
         $update->introformat = $formdata->introformat;
         $update->alwaysshowdescription = $formdata->alwaysshowdescription;
-        $update->preventlatesubmissions = $formdata->preventlatesubmissions;
         $update->submissiondrafts = $formdata->submissiondrafts;
         $update->requiresubmissionstatement = $formdata->requiresubmissionstatement;
         $update->sendnotifications = $formdata->sendnotifications;
         $update->sendlatenotifications = $formdata->sendlatenotifications;
         $update->duedate = $formdata->duedate;
+        $update->cutoffdate = $formdata->cutoffdate;
         $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
         $update->grade = $formdata->grade;
         $update->completionsubmit = !empty($formdata->completionsubmit);
+        $update->teamsubmission = $formdata->teamsubmission;
+        $update->requireallteammemberssubmit = $formdata->requireallteammemberssubmit;
+        $update->teamsubmissiongroupingid = $formdata->teamsubmissiongroupingid;
+        $update->blindmarking = $formdata->blindmarking;
+
 
         $result = $DB->update_record('assign', $update);
         $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
@@ -964,6 +1090,24 @@ class assign {
         }
     }
 
+    /**
+     * Load a count of valid teams for this assignment
+     *
+     * @return int number of valid teams
+     */
+    public function count_teams() {
+
+        $groups = groups_get_all_groups($this->get_course()->id, 0, $this->get_instance()->teamsubmissiongroupingid, 'g.id');
+        $count = count($groups);
+
+        // See if there are any users in the default group.
+        $defaultusers = $this->get_submission_group_members(0, true);
+        if (count($defaultusers) > 0) {
+            $count += 1;
+        }
+        return $count;
+    }
+
     /**
      * Load a count of users enrolled in the current course with the specified permission and group (0 for no group)
      *
@@ -996,17 +1140,63 @@ class assign {
     }
 
     /**
-     * Load a count of users enrolled in the current course with the specified permission and group (optional)
+     * Load a count of grades
+     *
+     * @return int number of grades
+     */
+    public function count_grades() {
+        global $DB;
+
+        if (!$this->has_instance()) {
+            return 0;
+        }
+
+        $sql = 'SELECT COUNT(id) FROM {assign_grades} WHERE assignment = ?';
+        $params = array($this->get_course_module()->instance);
+
+        return $DB->count_records_sql($sql, $params);
+    }
+
+    /**
+     * Load a count of submissions
+     *
+     * @return int number of submissions
+     */
+    public function count_submissions() {
+        global $DB;
+
+        if (!$this->has_instance()) {
+            return 0;
+        }
+
+        $sql = 'SELECT COUNT(id) FROM {assign_submission} WHERE assignment = ?';
+        $params = array($this->get_course_module()->instance);
+
+        if ($this->get_instance()->teamsubmission) {
+            // only look at team submissions
+            $sql .= ' AND userid = ?';
+            $params[] = 0;
+        }
+        return $DB->count_records_sql($sql, $params);
+    }
+
+    /**
+     * Load a count of submissions with a specified status
      *
      * @param string $status The submission status - should match one of the constants
      * @return int number of matching submissions
      */
     public function count_submissions_with_status($status) {
         global $DB;
-        return $DB->count_records_sql("SELECT COUNT('x')
-                                     FROM {assign_submission}
-                                    WHERE assignment = ? AND
-                                          status = ?", array($this->get_course_module()->instance, $status));
+        $sql = 'SELECT COUNT(id) FROM {assign_submission} WHERE assignment = ? AND status = ?';
+        $params = array($this->get_course_module()->instance, $status);
+
+        if ($this->get_instance()->teamsubmission) {
+            // only look at team submissions
+            $sql .= ' AND userid = ?';
+            $params[] = 0;
+        }
+        return $DB->count_records_sql($sql, $params);
     }
 
     /**
@@ -1015,7 +1205,7 @@ class assign {
      *
      * @return array An array of userids
      */
-    private function get_grading_userid_list(){
+    private function get_grading_userid_list() {
         $filter = get_user_preferences('assign_filter', '');
         $table = new assign_grading_table($this, 0, $filter, 0, false);
 
@@ -1033,7 +1223,7 @@ class assign {
      * @param bool $last This is set to true if this is the last user in the table
      * @return mixed The user id of the matching user or false if there was an error
      */
-    private function get_userid_for_row($num, $last){
+    private function get_userid_for_row($num, $last) {
         if (!array_key_exists('userid_for_row', $this->cache)) {
             $this->cache['userid_for_row'] = array();
         }
@@ -1110,7 +1300,8 @@ class assign {
         $timenow   = time();
 
         // Collect all submissions from the past 24 hours that require mailing.
-        $sql = "SELECT s.*, a.course, a.name, g.*, g.id as gradeid, g.timemodified as lastmodified
+        $sql = "SELECT s.*, a.course, a.name, a.blindmarking, a.revealidentities,
+                       g.*, g.id as gradeid, g.timemodified as lastmodified
                  FROM {assign} a
                  JOIN {assign_grades} g ON g.assignment = a.id
             LEFT JOIN {assign_submission} s ON s.assignment = a.id AND s.userid = g.userid
@@ -1212,7 +1403,15 @@ class assign {
             $eventtype = 'assign_notification';
             $updatetime = $submission->lastmodified;
             $modulename = get_string('modulename', 'assign');
-            self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime, $mod, $contextmodule, $course, $modulename, $submission->name);
+
+            $uniqueid = 0;
+            if ($submission->blindmarking && !$submission->revealidentities) {
+                $uniqueid = self::get_uniqueid_for_user_static($submission->assignment, $user->id);
+            }
+            self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime,
+                                               $mod, $contextmodule, $course, $modulename, $submission->name,
+                                               $submission->blindmarking && !$submission->revealidentities,
+                                               $uniqueid);
 
             $grade = new stdClass();
             $grade->id = $submission->gradeid;
@@ -1238,7 +1437,7 @@ class assign {
      * @param stdClass $grade a grade record keyed on id
      * @return bool true for success
      */
-    private function update_grade($grade) {
+    public function update_grade($grade) {
         global $DB;
 
         $grade->timemodified = time();
@@ -1270,6 +1469,224 @@ class assign {
         return $result;
     }
 
+    /**
+     * View the grant extension date page
+     *
+     * Uses url parameters 'userid'
+     * or from parameter 'selectedusers'
+     * @param moodleform $mform - Used for validation of the submitted data
+     * @return string
+     */
+    private function view_grant_extension($mform) {
+        global $DB, $CFG;
+        require_once($CFG->dirroot . '/mod/assign/extensionform.php');
+
+        $o = '';
+        $batchusers = optional_param('selectedusers', '', PARAM_TEXT);
+        $data = new stdClass();
+        $data->extensionduedate = null;
+        $userid = 0;
+        if (!$batchusers) {
+            $userid = required_param('userid', PARAM_INT);
+
+            $grade = $this->get_user_grade($userid, false);
+
+            $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
+
+            if ($grade) {
+                $data->extensionduedate = $grade->extensionduedate;
+            }
+            $data->userid = $userid;
+        } else {
+            $data->batchusers = $batchusers;
+        }
+        $o .= $this->output->render(new assign_header($this->get_instance(),
+                                                      $this->get_context(),
+                                                      $this->show_intro(),
+                                                      $this->get_course_module()->id,
+                                                      get_string('grantextension', 'assign')));
+
+        if (!$mform) {
+            $mform = new mod_assign_extension_form(null, array($this->get_course_module()->id,
+                                                               $userid,
+                                                               $batchusers,
+                                                               $this->get_instance(),
+                                                               $data));
+        }
+        $o .= $this->output->render(new assign_form('extensionform', $mform));
+        $o .= $this->view_footer();
+        return $o;
+    }
+
+    /**
+     * Get a list of the users in the same group as this user
+     *
+     * @param int $groupid The id of the group whose members we want or 0 for the default group
+     * @param bool $onlyids Whether to retrieve only the user id's
+     * @return array The users (possibly id's only)
+     */
+    public function get_submission_group_members($groupid, $onlyids) {
+        $members = array();
+        if ($groupid != 0) {
+            if ($onlyids) {
+                $allusers = groups_get_members($groupid, 'u.id');
+            } else {
+                $allusers = groups_get_members($groupid);
+            }
+            foreach ($allusers as $user) {
+                if ($this->get_submission_group($user->id)) {
+                    $members[] = $user;
+                }
+            }
+        } else {
+            $allusers = $this->list_participants(null, $onlyids);
+            foreach ($allusers as $user) {
+                if ($this->get_submission_group($user->id) == null) {
+                    $members[] = $user;
+                }
+            }
+        }
+        return $members;
+    }
+
+    /**
+     * Get a list of the users in the same group as this user that have not submitted the assignment
+     *
+     * @param int $groupid The id of the group whose members we want or 0 for the default group
+     * @param bool $onlyids Whether to retrieve only the user id's
+     * @return array The users (possibly id's only)
+     */
+    public function get_submission_group_members_who_have_not_submitted($groupid, $onlyids) {
+        if (!$this->get_instance()->teamsubmission || !$this->get_instance()->requireallteammemberssubmit) {
+            return array();
+        }
+        $members = $this->get_submission_group_members($groupid, $onlyids);
+
+        foreach ($members as $id => $member) {
+            $submission = $this->get_user_submission($member->id, false);
+            if ($submission && $submission->status != ASSIGN_SUBMISSION_STATUS_DRAFT) {
+                unset($members[$id]);
+            } else {
+                if ($this->is_blind_marking()) {
+                    $members[$id]->alias = get_string('hiddenuser', 'assign') . $this->get_uniqueid_for_user($id);
+                }
+            }
+        }
+        return $members;
+    }
+
+    /**
+     * Load the group submission object for a particular user, optionally creating it if required
+     *
+     * This will create the user submission and the group submission if required
+     *
+     * @param int $userid The id of the user whose submission we want
+     * @param int $groupid The id of the group for this user - may be 0 in which case it is determined from the userid
+     * @param bool $create If set to true a new submission object will be created in the database
+     * @return stdClass The submission
+     */
+    public function get_group_submission($userid, $groupid, $create) {
+        global $DB;
+
+        if ($groupid == 0) {
+            $group = $this->get_submission_group($userid);
+            if ($group) {
+                $groupid = $group->id;
+            }
+        }
+
+        if ($create) {
+            // Make sure there is a submission for this user.
+            $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>0, 'userid'=>$userid);
+            $submission = $DB->get_record('assign_submission', $params);
+
+            if (!$submission) {
+                $submission = new stdClass();
+                $submission->assignment   = $this->get_instance()->id;
+                $submission->userid       = $userid;
+                $submission->groupid      = 0;
+                $submission->timecreated  = time();
+                $submission->timemodified = $submission->timecreated;
+
+                if ($this->get_instance()->submissiondrafts) {
+                    $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
+                } else {
+                    $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+                }
+                $DB->insert_record('assign_submission', $submission);
+            }
+        }
+        // Now get the group submission.
+        $params = array('assignment'=>$this->get_instance()->id, 'groupid'=>$groupid, 'userid'=>0);
+        $submission = $DB->get_record('assign_submission', $params);
+
+        if ($submission) {
+            return $submission;
+        }
+        if ($create) {
+            $submission = new stdClass();
+            $submission->assignment   = $this->get_instance()->id;
+            $submission->userid       = 0;
+            $submission->groupid       = $groupid;
+            $submission->timecreated = time();
+            $submission->timemodified = $submission->timecreated;
+
+            if ($this->get_instance()->submissiondrafts) {
+                $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
+            } else {
+                $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+            }
+            $sid = $DB->insert_record('assign_submission', $submission);
+            $submission->id = $sid;
+            return $submission;
+        }
+        return false;
+    }
+
+    /**
+     * View a page rendered by a plugin
+     *
+     * Uses url parameters 'pluginaction', 'pluginsubtype', 'plugin', and 'id'
+     *
+     * @return string
+     */
+    private function view_plugin_page() {
+        global $USER;
+
+        $o = '';
+
+        $pluginsubtype = required_param('pluginsubtype', PARAM_ALPHA);
+        $plugintype = required_param('plugin', PARAM_TEXT);
+        $pluginaction = required_param('pluginaction', PARAM_ALPHA);
+
+        $plugin = $this->get_plugin_by_type($pluginsubtype, $plugintype);
+        if (!$plugin) {
+            print_error('invalidformdata', '');
+            return;
+        }
+
+        $o .= $plugin->view_page($pluginaction);
+
+        return $o;
+    }
+
+
+    /**
+     * This is used for team assignments to get the group for the specified user.
+     * If the user is a member of multiple or no groups this will return false
+     *
+     * @param int $userid The id of the user whose submission we want
+     * @return mixed The group or false
+     */
+    public function get_submission_group($userid) {
+        $groups = groups_get_all_groups($this->get_course()->id, $userid, $this->get_instance()->teamsubmissiongroupingid);
+        if (count($groups) != 1) {
+            return false;
+        }
+        return array_pop($groups);
+    }
+
+
     /**
      * display the submission that is used by a plugin
      * Uses url parameters 'sid', 'gid' and 'plugin'
@@ -1431,58 +1848,74 @@ class assign {
     private function download_submissions() {
         global $CFG,$DB;
 
-        // more efficient to load this here
+        // More efficient to load this here.
         require_once($CFG->libdir.'/filelib.php');
 
-        // load all submissions
-        $submissions = $this->get_all_submissions('','');
-
-        if (empty($submissions)) {
-            print_error('errornosubmissions', 'assign');
-            return;
-        }
+        // Load all users with submit.
+        $students = get_enrolled_users($this->context, "mod/assign:submit");
 
-        // build a list of files to zip
+        // Build a list of files to zip.
         $filesforzipping = array();
         $fs = get_file_storage();
 
         $groupmode = groups_get_activity_groupmode($this->get_course_module());
-        $groupid = 0;   // All users
+        // All users.
+        $groupid = 0;
         $groupname = '';
         if ($groupmode) {
             $groupid = groups_get_activity_group($this->get_course_module(), true);
             $groupname = groups_get_group_name($groupid).'-';
         }
 
-        // construct the zip file name
-        $filename = str_replace(' ', '_', clean_filename($this->get_course()->shortname.'-'.$this->get_instance()->name.'-'.$groupname.$this->get_course_module()->id.".zip")); //name of new zip file.
-
-        // get all the files for each submission
-        foreach ($submissions as $submission) {
-            $userid = $submission->userid; //get userid
-            if ((groups_is_member($groupid,$userid) or !$groupmode or !$groupid)) {
-                // get the plugins to add their own files to the zip
+        // Construct the zip file name.
+        $filename = clean_filename($this->get_course()->shortname.'-'.
+                                   $this->get_instance()->name.'-'.
+                                   $groupname.$this->get_course_module()->id.".zip");
 
-                $user = $DB->get_record("user", array("id"=>$userid),'id,username,firstname,lastname', MUST_EXIST);
+        // Get all the files for each student.
+        foreach ($students as $student) {
+            $userid = $student->id;
 
-                $prefix = clean_filename(fullname($user) . "_" .$userid . "_");
+            if ((groups_is_member($groupid, $userid) or !$groupmode or !$groupid)) {
+                // Get the plugins to add their own files to the zip.
 
-                foreach ($this->submissionplugins as $plugin) {
-                    if ($plugin->is_enabled() && $plugin->is_visible()) {
-                        $pluginfiles = $plugin->get_files($submission);
+                $submissiongroup = false;
+                $groupname = '';
+                if ($this->get_instance()->teamsubmission) {
+                    $submission = $this->get_group_submission($userid, 0, false);
+                    $submissiongroup = $this->get_submission_group($userid);
+                    $groupname = '-' . $submissiongroup->name;
+                } else {
+                    $submission = $this->get_user_submission($userid, false);
+                }
 
+                if ($this->is_blind_marking()) {
+                    $prefix = clean_filename(str_replace('_', ' ', get_string('participant', 'assign') . $groupname) .
+                                             "_" . $this->get_uniqueid_for_user($userid) . "_");
+                } else {
+                    $prefix = clean_filename(str_replace('_', ' ', fullname($student) . $groupname) .
+                                             "_" . $this->get_uniqueid_for_user($userid) . "_");
+                }
 
-                        foreach ($pluginfiles as $zipfilename => $file) {
-                            $filesforzipping[$prefix . $zipfilename] = $file;
+                if ($submission) {
+                    foreach ($this->submissionplugins as $plugin) {
+                        if ($plugin->is_enabled() && $plugin->is_visible()) {
+                            $pluginfiles = $plugin->get_files($submission);
+                            foreach ($pluginfiles as $zipfilename => $file) {
+                                $subtype = $plugin->get_subtype();
+                                $type = $plugin->get_type();
+                                $prefixedfilename = $prefix . $subtype . '_' . $type . '_' . $zipfilename;
+                                $filesforzipping[$prefixedfilename] = $file;
+                            }
                         }
                     }
                 }
-
             }
-        } // end of foreach loop
+        }
         if ($zipfile = $this->pack_files($filesforzipping)) {
             $this->add_to_log('download all submissions', get_string('downloadall', 'assign'));
-            send_temp_file($zipfile, $filename); //send file and delete after sending.
+            // Send file and delete after sending.
+            send_temp_file($zipfile, $filename);
         }
     }
 
@@ -1508,18 +1941,23 @@ class assign {
     /**
      * Load the submission object for a particular user, optionally creating it if required
      *
+     * For team assignments there are 2 submissions - the student submission and the team submission
+     * All files are associated with the team submission but the status of the students contribution is
+     * recorded separately.
+     *
      * @param int $userid The id of the user whose submission we want or 0 in which case USER->id is used
      * @param bool $create optional Defaults to false. If set to true a new submission object will be created in the database
      * @return stdClass The submission
      */
-    private function get_user_submission($userid, $create) {
+    public function get_user_submission($userid, $create) {
         global $DB, $USER;
 
         if (!$userid) {
             $userid = $USER->id;
         }
-        // if the userid is not null then use userid
-        $submission = $DB->get_record('assign_submission', array('assignment'=>$this->get_instance()->id, 'userid'=>$userid));
+        // If the userid is not null then use userid.
+        $params = array('assignment'=>$this->get_instance()->id, 'userid'=>$userid, 'groupid'=>0);
+        $submission = $DB->get_record('assign_submission', $params);
 
         if ($submission) {
             return $submission;
@@ -1562,7 +2000,7 @@ class assign {
      * @param bool $create If true the grade will be created if it does not exist
      * @return stdClass The grade record
      */
-    private function get_user_grade($userid, $create) {
+    public function get_user_grade($userid, $create) {
         global $DB, $USER;
 
         if (!$userid) {
@@ -1640,32 +2078,75 @@ class assign {
         if ($offset) {
             $_POST = array();
         }
-        if(!$userid){
+        if (!$userid) {
             throw new coding_exception('Row is out of bounds for the current grading table: ' . $rownum);
         }
         $user = $DB->get_record('user', array('id' => $userid));
         if ($user) {
-            $o .= $this->output->render(new assign_user_summary($user, $this->get_course()->id, has_capability('moodle/site:viewfullnames', $this->get_course_context())));
+            $o .= $this->output->render(new assign_user_summary($user,
+                                                                $this->get_course()->id,
+                                                                has_capability('moodle/site:viewfullnames',
+                                                                $this->get_course_context()),
+                                                                $this->is_blind_marking(),
+                                                                $this->get_uniqueid_for_user($user->id)));
         }
         $submission = $this->get_user_submission($userid, false);
+        $submissiongroup = null;
+        $submissiongroupmemberswhohavenotsubmitted = array();
+        $teamsubmission = null;
+        $notsubmitted = array();
+        if ($this->get_instance()->teamsubmission) {
+            $teamsubmission = $this->get_group_submission($userid, 0, false);
+            $submissiongroup = $this->get_submission_group($userid);
+            $groupid = 0;
+            if ($submissiongroup) {
+                $groupid = $submissiongroup->id;
+            }
+            $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
+
+        }
+
         // get the current grade
         $grade = $this->get_user_grade($userid, false);
         if ($this->can_view_submission($userid)) {
             $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($userid);
+            $extensionduedate = null;
+            if ($grade) {
+                $extensionduedate = $grade->extensionduedate;
+            }
+            $showedit = $this->submissions_open($userid) && ($this->is_any_submission_plugin_enabled());
+
+            if ($teamsubmission) {
+                $showsubmit = $showedit && $teamsubmission && ($teamsubmission->status == ASSIGN_SUBMISSION_STATUS_DRAFT);
+            } else {
+                $showsubmit = $showedit && $submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT);
+            }
+            $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
+
             $o .= $this->output->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
                                                               $this->get_instance()->alwaysshowdescription,
                                                               $submission,
+                                                              $this->get_instance()->teamsubmission,
+                                                              $teamsubmission,
+                                                              $submissiongroup,
+                                                              $notsubmitted,
                                                               $this->is_any_submission_plugin_enabled(),
                                                               $gradelocked,
                                                               $this->is_graded($userid),
                                                               $this->get_instance()->duedate,
+                                                              $this->get_instance()->cutoffdate,
                                                               $this->get_submission_plugins(),
                                                               $this->get_return_action(),
                                                               $this->get_return_params(),
                                                               $this->get_course_module()->id,
+                                                              $this->get_course()->id,
                                                               assign_submission_status::GRADER_VIEW,
-                                                              false,
-                                                              false));
+                                                              $showedit,
+                                                              $showsubmit,
+                                                              $viewfullnames,
+                                                              $extensionduedate,
+                                                              $this->get_context(),
+                                                              $this->is_blind_marking()));
         }
         if ($grade) {
             $data = new stdClass();
@@ -1679,18 +2160,53 @@ class assign {
 
         // now show the grading form
         if (!$mform) {
-            $mform = new mod_assign_grade_form(null, array($this, $data, array('rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>$last)), 'post', '', array('class'=>'gradeform'));
+            $pagination = array( 'rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>$last);
+            $formparams = array($this, $data, $pagination);
+            $mform = new mod_assign_grade_form(null,
+                                               $formparams,
+                                               'post',
+                                               '',
+                                               array('class'=>'gradeform'));
         }
         $o .= $this->output->render(new assign_form('gradingform',$mform));
 
-        $this->add_to_log('view grading form', get_string('viewgradingformforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
+        $msg = get_string('viewgradingformforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user)));
+        $this->add_to_log('view grading form', $msg);
+
+        $o .= $this->view_footer();
+        return $o;
+    }
+
+    /**
+     * Show a confirmation page to make sure they want to release student identities
+     *
+     * @return string
+     */
+    private function view_reveal_identities_confirm() {
+        global $CFG, $USER;
+
+        require_capability('mod/assign:revealidentities', $this->get_context());
+
+        $o = '';
+        $o .= $this->output->render(new assign_header($this->get_instance(),
+                                                      $this->get_context(), false, $this->get_course_module()->id));
+
+        $confirmurl = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
+                                                                    'action'=>'revealidentitiesconfirm',
+                                                                    'sesskey'=>sesskey()));
+
+        $cancelurl = new moodle_url('/mod/assign/view.php', array('id'=>$this->get_course_module()->id,
+                                                                    'action'=>'grading'));
 
+        $o .= $this->output->confirm(get_string('revealidentitiesconfirm', 'assign'), $confirmurl, $cancelurl);
         $o .= $this->view_footer();
+        $this->add_to_log('view', get_string('viewrevealidentitiesconfirm', 'assign'));
         return $o;
     }
 
 
 
+
     /**
      * View a link to go back to the previous page. Uses url parameters returnaction and returnparams.
      *
@@ -1732,6 +2248,22 @@ class assign {
             $downloadurl = '/mod/assign/view.php?id=' . $this->get_course_module()->id . '&action=downloadall';
             $links[$downloadurl] = get_string('downloadall', 'assign');
         }
+        if ($this->is_blind_marking() && has_capability('mod/assign:revealidentities', $this->get_context())) {
+            $revealidentitiesurl = '/mod/assign/view.php?id=' . $this->get_course_module()->id . '&action=revealidentities';
+            $links[$revealidentitiesurl] = get_string('revealidentities', 'assign');
+        }
+        foreach ($this->get_feedback_plugins() as $plugin) {
+            if ($plugin->is_enabled() && $plugin->is_visible()) {
+                foreach ($plugin->get_grading_actions() as $action => $description) {
+                    $url = '/mod/assign/view.php' .
+                           '?id=' .  $this->get_course_module()->id .
+                           '&plugin=' . $plugin->get_type() .
+                           '&pluginsubtype=assignfeedback' .
+                           '&action=viewpluginpage&pluginaction=' . $action;
+                    $links[$url] = $description;
+                }
+            }
+        }
 
         $gradingactions = new url_select($links);
 
@@ -1760,7 +2292,9 @@ class assign {
 
         $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
                                                                   array('cm'=>$this->get_course_module()->id,
-                                                                        'submissiondrafts'=>$this->get_instance()->submissiondrafts),
+                                                                        'submissiondrafts'=>$this->get_instance()->submissiondrafts,
+                                                                        'duedate'=>$this->get_instance()->duedate,
+                                                                        'feedbackplugins'=>$this->get_feedback_plugins()),
                                                                   'post', '',
                                                                   array('class'=>'gradingbatchoperationsform'));
 
@@ -1769,6 +2303,11 @@ class assign {
         $gradingoptionsdata->filter = $filter;
         $gradingoptionsform->set_data($gradingoptionsdata);
 
+        $actionformtext = $this->output->render($gradingactions);
+        $o .= $this->output->render(new assign_header($this->get_instance(),
+                                                      $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign'), $actionformtext));
+        $o .= groups_print_activity_menu($this->get_course_module(), $CFG->wwwroot . '/mod/assign/view.php?id=' . $this->get_course_module()->id.'&action=grading', true);
+
         // plagiarism update status apearring in the grading book
         if (!empty($CFG->enableplagiarism)) {
             /** Include plagiarismlib.php */
@@ -1776,12 +2315,6 @@ class assign {
             $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
         }
 
-        $actionformtext = $this->output->render($gradingactions);
-        $o .= $this->output->render(new assign_header($this->get_instance(),
-                                                      $this->get_context(), false, $this->get_course_module()->id, get_string('grading', 'assign'), $actionformtext));
-        $o .= groups_print_activity_menu($this->get_course_module(), $CFG->wwwroot . '/mod/assign/view.php?id=' . $this->get_course_module()->id.'&action=grading', true);
-
-
         // load and print the table of submissions
         if ($showquickgrading && $quickgrading) {
             $table = $this->output->render(new assign_grading_table($this, $perpage, $filter, 0, true));
@@ -1944,26 +2477,68 @@ class assign {
         return true;
     }
 
+    /**
+     * Allows the plugin to show a batch grading operation page.
+     *
+     * @return none
+     */
+    private function view_plugin_grading_batch_operation($mform) {
+        require_capability('mod/assign:grade', $this->context);
+        $prefix = 'plugingradingbatchoperation_';
+
+        if ($data = $mform->get_data()) {
+            $tail = substr($data->operation, strlen($prefix));
+            list($plugintype, $action) = explode('_', $tail, 2);
+
+            $plugin = $this->get_feedback_plugin_by_type($plugintype);
+            if ($plugin) {
+                $users = $data->selectedusers;
+                $userlist = explode(',', $users);
+                echo $plugin->grading_batch_operation($action, $userlist);
+                return;
+            }
+        }
+        print_error('invalidformdata', '');
+    }
+
     /**
      * Ask the user to confirm they want to perform this batch operation
-     * @return string
+     * @param moodleform $mform Set to a grading batch operations form
+     * @return string - the page to view after processing these actions
      */
-    private function process_batch_grading_operation() {
+    private function process_grading_batch_operation(& $mform) {
         global $CFG;
         require_once($CFG->dirroot . '/mod/assign/gradingbatchoperationsform.php');
         require_sesskey();
 
-        $gradingbatchoperationsform = new mod_assign_grading_batch_operations_form(null,
-                                                                  array('cm'=>$this->get_course_module()->id,
-                                                                        'submissiondrafts'=>$this->get_instance()->submissiondrafts),
-                                                                  'post', '',
-                                                                  array('class'=>'gradingbatchoperationsform'));
+        $mform = new mod_assign_grading_batch_operations_form(null,
+                                                              array('cm'=>$this->get_course_module()->id,
+                                                                    'submissiondrafts'=>$this->get_instance()->submissiondrafts,
+                                                                    'duedate'=>$this->get_instance()->duedate,
+                                                                    'feedbackplugins'=>$this->get_feedback_plugins()),
+                                                              'post',
+                                                              '',
+                                                              array('class'=>'gradingbatchoperationsform'));
 
-        if ($data = $gradingbatchoperationsform->get_data()) {
+        if ($data = $mform->get_data()) {
             // get the list of users
             $users = $data->selectedusers;
             $userlist = explode(',', $users);
 
+            $prefix = 'plugingradingbatchoperation_';
+
+            if ($data->operation == 'grantextension') {
+                return 'grantextension';
+            } else if (strpos($data->operation, $prefix) === 0) {
+                $tail = substr($data->operation, strlen($prefix));
+                list($plugintype, $action) = explode('_', $tail, 2);
+
+                $plugin = $this->get_feedback_plugin_by_type($plugintype);
+                if ($plugin) {
+                    return 'plugingradingbatchoperation';
+                }
+            }
+
             foreach ($userlist as $userid) {
                 if ($data->operation == 'lock') {
                     $this->process_lock($userid);
@@ -1975,7 +2550,7 @@ class assign {
             }
         }
 
-        return true;
+        return 'grading';
     }
 
     /**
@@ -2042,26 +2617,60 @@ class assign {
         $submission = $this->get_user_submission($user->id, false);
         $o = '';
 
+        $teamsubmission = null;
+        $submissiongroup = null;
+        $notsubmitted = array();
+        if ($this->get_instance()->teamsubmission) {
+            $teamsubmission = $this->get_group_submission($user->id, 0, false);
+            $submissiongroup = $this->get_submission_group($user->id);
+            $groupid = 0;
+            if ($submissiongroup) {
+                $groupid = $submissiongroup->id;
+            }
+            $notsubmitted = $this->get_submission_group_members_who_have_not_submitted($groupid, false);
+        }
+
         if ($this->can_view_submission($user->id)) {
             $showedit = has_capability('mod/assign:submit', $this->context) &&
-                         $this->submissions_open() && ($this->is_any_submission_plugin_enabled()) && $showlinks;
-            $showsubmit = $submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_DRAFT) && $showlinks;
+                         $this->submissions_open($user->id) && ($this->is_any_submission_plugin_enabled()) && $showlinks;
             $gradelocked = ($grade && $grade->locked) || $this->grading_disabled($user->id);
 
+            $showsubmit = ($submission || $teamsubmission) && $showlinks;
+            if ($teamsubmission && ($teamsubmission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
+                $showsubmit = false;
+            }
+            if ($submission && ($submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED)) {
+                $showsubmit = false;
+            }
+            $extensionduedate = null;
+            if ($grade) {
+                $extensionduedate = $grade->extensionduedate;
+            }
+            $viewfullnames = has_capability('moodle/site:viewfullnames', $this->get_course_context());
             $o .= $this->output->render(new assign_submission_status($this->get_instance()->allowsubmissionsfromdate,
                                                               $this->get_instance()->alwaysshowdescription,
                                                               $submission,
+                                                              $this->get_instance()->teamsubmission,
+                                                              $teamsubmission,
+                                                              $submissiongroup,
+                                                              $notsubmitted,
                                                               $this->is_any_submission_plugin_enabled(),
                                                               $gradelocked,
                                                               $this->is_graded($user->id),
                                                               $this->get_instance()->duedate,
+                                                              $this->get_instance()->cutoffdate,
                                                               $this->get_submission_plugins(),
                                                               $this->get_return_action(),
                                                               $this->get_return_params(),
                                                               $this->get_course_module()->id,
+                                                              $this->get_course()->id,
                                                               assign_submission_status::STUDENT_VIEW,
                                                               $showedit,
-                                                              $showsubmit));
+                                                              $showsubmit,
+                                                              $viewfullnames,
+                                                              $extensionduedate,
+                                                              $this->get_context(),
+                                                              $this->is_blind_marking()));
             require_once($CFG->libdir.'/gradelib.php');
             require_once($CFG->dirroot.'/grade/grading/lib.php');
 
@@ -2137,15 +2746,31 @@ class assign {
                                                       $this->get_course_module()->id));
 
         if ($this->can_grade()) {
-            $o .= $this->output->render(new assign_grading_summary($this->count_participants(0),
-                                                            $this->get_instance()->submissiondrafts,
-                                                            $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
-                                                            $this->is_any_submission_plugin_enabled(),
-                                                            $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
-                                                            $this->get_instance()->duedate,
-                                                            $this->get_course_module()->id,
-                                                            $this->count_submissions_need_grading()
-                                                            ));
+            if ($this->get_instance()->teamsubmission) {
+                $summary = new assign_grading_summary($this->count_teams(),
+                                                      $this->get_instance()->submissiondrafts,
+                                                      $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
+                                                      $this->is_any_submission_plugin_enabled(),
+                                                      $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
+                                                      $this->get_instance()->cutoffdate,
+                                                      $this->get_instance()->duedate,
+                                                      $this->get_course_module()->id,
+                                                      $this->count_submissions_need_grading(),
+                                                      $this->get_instance()->teamsubmission);
+                $o .= $this->output->render($summary);
+            } else {
+                $summary = new assign_grading_summary($this->count_participants(0),
+                                                      $this->get_instance()->submissiondrafts,
+                                                      $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_DRAFT),
+                                                      $this->is_any_submission_plugin_enabled(),
+                                                      $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
+                                                      $this->get_instance()->cutoffdate,
+                                                      $this->get_instance()->duedate,
+                                                      $this->get_course_module()->id,
+                                                      $this->count_submissions_need_grading(),
+                                                      $this->get_instance()->teamsubmission);
+                $o .= $this->output->render($summary);
+            }
         }
         $grade = $this->get_user_grade($USER->id, false);
         $submission = $this->get_user_submission($USER->id, false);
@@ -2214,9 +2839,26 @@ class assign {
      */
     private function gradebook_item_update($submission=NULL, $grade=NULL) {
 
-        if($submission != NULL){
+        // Do not push grade to gradebook if blind marking is active as the gradebook would reveal the students.
+        if ($this->is_blind_marking()) {
+            return false;
+        }
+        if ($submission != NULL) {
+            if ($submission->userid == 0) {
+                // This is a group submission update.
+                $team = groups_get_members($submission->groupid, 'u.id');
+
+                foreach ($team as $member) {
+                    $submission->groupid = 0;
+                    $submission->userid = $member->id;
+                    $this->gradebook_item_update($submission, null);
+                }
+                return;
+            }
+
             $gradebookgrade = $this->convert_submission_for_gradebook($submission);
-        }else{
+
+        } else {
             $gradebookgrade = $this->convert_grade_for_gradebook($grade);
         }
         // Grading is disabled, return.
@@ -2229,16 +2871,81 @@ class assign {
         return assign_grade_item_update($assign, $gradebookgrade);
     }
 
+    /**
+     * update team submission
+     *
+     * @param stdClass $submission
+     * @param int $userid
+     * @param bool $updatetime
+     * @return bool
+     */
+    private function update_team_submission(stdClass $submission, $userid, $updatetime) {
+        global $DB;
+
+        if ($updatetime) {
+            $submission->timemodified = time();
+        }
+
+        // First update the submission for the current user.
+        $mysubmission = $this->get_user_submission($userid, true);
+        $mysubmission->status = $submission->status;
+
+        $this->update_submission($mysubmission, 0, $updatetime, false);
+
+        // Now check the team settings to see if this assignment qualifies as submitted or draft.
+        $team = $this->get_submission_group_members($submission->groupid, true);
+
+        $allsubmitted = true;
+        $anysubmitted = false;
+        foreach ($team as $member) {
+            $membersubmission = $this->get_user_submission($member->id, false);
+
+            if (!$membersubmission || $membersubmission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
+                $allsubmitted = false;
+                if ($anysubmitted) {
+                    break;
+                }
+            } else {
+                $anysubmitted = true;
+            }
+        }
+        if ($this->get_instance()->requireallteammemberssubmit) {
+            if ($allsubmitted) {
+                $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+            } else {
+                $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
+            }
+            $result= $DB->update_record('assign_submission', $submission);
+        } else {
+            if ($anysubmitted) {
+                $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+            } else {
+                $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
+            }
+            $result= $DB->update_record('assign_submission', $submission);
+        }
+
+        $this->gradebook_item_update($submission);
+        return $result;
+    }
+
+
     /**
      * update grades in the gradebook based on submission time
      *
      * @param stdClass $submission
+     * @param int $userid
      * @param bool $updatetime
+     * @param bool $teamsubmission
      * @return bool
      */
-    private function update_submission(stdClass $submission, $updatetime=true) {
+    private function update_submission(stdClass $submission, $userid, $updatetime, $teamsubmission) {
         global $DB;
 
+        if ($teamsubmission) {
+            return $this->update_team_submission($submission, $userid, $updatetime);
+        }
+
         if ($updatetime) {
             $submission->timemodified = time();
         }
@@ -2257,15 +2964,35 @@ class assign {
      * has this person already submitted,
      * is the assignment locked?
      *
+     * @param int $userid - Optional userid so we can see if a different user can submit
      * @return bool
      */
-    private function submissions_open() {
+    private function submissions_open($userid = 0) {
         global $USER;
 
+        if (!$userid) {
+            $userid = $USER->id;
+        }
+
         $time = time();
         $dateopen = true;
-        if ($this->get_instance()->preventlatesubmissions && $this->get_instance()->duedate) {
-            $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time && $time <= $this->get_instance()->duedate);
+        $finaldate = false;
+        if ($this->get_instance()->cutoffdate) {
+            $finaldate = $this->get_instance()->cutoffdate;
+        }
+        // User extensions.
+        if ($finaldate) {
+            $grade = $this->get_user_grade($userid, false);
+            if ($grade && $grade->extensionduedate) {
+                // Extension can be before cut off date.
+                if ($grade->extensionduedate > $finaldate) {
+                    $finaldate = $grade->extensionduedate;
+                }
+            }
+        }
+
+        if ($finaldate) {
+            $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time && $time <= $finaldate);
         } else {
             $dateopen = ($this->get_instance()->allowsubmissionsfromdate <= $time);
         }
@@ -2274,23 +3001,30 @@ class assign {
             return false;
         }
 
-        // now check if this user has already submitted etc.
-        if (!is_enrolled($this->get_course_context(), $USER)) {
+        // Now check if this user has already submitted etc.
+        if (!is_enrolled($this->get_course_context(), $userid)) {
             return false;
         }
-        if ($submission = $this->get_user_submission($USER->id, false)) {
+        $submission = false;
+        if ($this->get_instance()->teamsubmission) {
+            $submission = $this->get_group_submission($USER->id, 0, false);
+        } else {
+            $submission = $this->get_user_submission($USER->id, false);
+        }
+        if ($submission) {
+
             if ($this->get_instance()->submissiondrafts && $submission->status == ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
                 // drafts are tracked and the student has submitted the assignment
                 return false;
             }
         }
-        if ($grade = $this->get_user_grade($USER->id, false)) {
+        if ($grade = $this->get_user_grade($userid, false)) {
             if ($grade->locked) {
                 return false;
             }
         }
 
-        if ($this->grading_disabled($USER->id)) {
+        if ($this->grading_disabled($userid)) {
             return false;
         }
 
@@ -2307,11 +3041,6 @@ class assign {
     public function render_area_files($component, $area, $submissionid) {
         global $USER;
 
-        if (!$submissionid) {
-            $submission = $this->get_user_submission($USER->id,false);
-            $submissionid = $submission->id;
-        }
-
         $fs = get_file_storage();
         $browser = get_file_browser();
         $files = $fs->get_area_files($this->get_context()->id, $component, $area , $submissionid , "timemodified", false);
@@ -2428,11 +3157,16 @@ class assign {
      */
     public static function send_assignment_notification($userfrom, $userto, $messagetype, $eventtype,
                                                         $updatetime, $coursemodule, $context, $course,
-                                                        $modulename, $assignmentname) {
+                                                        $modulename, $assignmentname, $blindmarking,
+                                                        $uniqueidforuser) {
         global $CFG;
 
         $info = new stdClass();
-        $info->username = fullname($userfrom, true);
+        if ($blindmarking) {
+            $info->username = get_string('participant', 'assign') . ' ' . $uniqueidforuser;
+        } else {
+            $info->username = fullname($userfrom, true);
+        }
         $info->assignment = format_string($assignmentname,true, array('context'=>$context));
         $info->url = $CFG->wwwroot.'/mod/assign/view.php?id='.$coursemodule->id;
         $info->timeupdated = strftime('%c',$updatetime);
@@ -2471,37 +3205,43 @@ class assign {
      * @return void
      */
     public function send_notification($userfrom, $userto, $messagetype, $eventtype, $updatetime) {
-        self::send_assignment_notification($userfrom, $userto, $messagetype, $eventtype, $updatetime, $this->get_course_module(), $this->get_context(), $this->get_course(), $this->get_module_name(), $this->get_instance()->name);
+        self::send_assignment_notification($userfrom, $userto, $messagetype, $eventtype,
+                                           $updatetime, $this->get_course_module(), $this->get_context(),
+                                           $this->get_course(), $this->get_module_name(),
+                                           $this->get_instance()->name, $this->is_blind_marking(),
+                                           $this->get_uniqueid_for_user($userfrom->id));
     }
 
     /**
      * Notify student upon successful submission
      *
-     * @global moodle_database $DB
      * @param stdClass $submission
      * @return void
      */
     private function notify_student_submission_receipt(stdClass $submission) {
-        global $DB;
+        global $DB, $USER;
 
         $adminconfig = $this->get_admin_config();
         if (empty($adminconfig->submissionreceipts)) {
             // No need to do anything
             return;
         }
-        $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
+        if ($submission->userid) {
+            $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
+        } else {
+            $user = $USER;
+        }
         $this->send_notification($user, $user, 'submissionreceipt', 'assign_notification', $submission->timemodified);
     }
 
     /**
      * Send notifications to graders upon student submissions
      *
-     * @global moodle_database $DB
      * @param stdClass $submission
      * @return void
      */
     private function notify_graders(stdClass $submission) {
-        global $DB;
+        global $DB, $USER;
 
         $late = $this->get_instance()->duedate && ($this->get_instance()->duedate < time());
 
@@ -2509,7 +3249,11 @@ class assign {
             return;
         }
 
-        $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
+        if ($submission->userid) {
+            $user = $DB->get_record('user', array('id'=>$submission->userid), '*', MUST_EXIST);
+        } else {
+            $user = $USER;
+        }
         if ($teachers = $this->get_graders($user->id)) {
             foreach ($teachers as $teacher) {
                 $this->send_notification($user, $teacher, 'gradersubmissionupdated', 'assign_notification', $submission->timemodified);
@@ -2553,7 +3297,12 @@ class assign {
             if ($mform->get_data() == false) {
                 return false;
             }
-            $submission = $this->get_user_submission($USER->id,true);
+            if ($this->get_instance()->teamsubmission) {
+                $submission = $this->get_group_submission($USER->id, 0, true);
+            } else {
+                $submission = $this->get_user_submission($USER->id, true);
+            }
+
             if ($submission->status != ASSIGN_SUBMISSION_STATUS_SUBMITTED) {
                 // Give each submission plugin a chance to process the submission
                 $plugins = $this->get_submission_plugins();
@@ -2562,7 +3311,7 @@ class assign {
                 }
 
                 $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
-                $this->update_submission($submission);
+                $this->update_submission($submission, $USER->id, true, $this->get_instance()->teamsubmission);
                 $completion = new completion_info($this->get_course());
                 if ($completion->is_enabled($this->get_course_module()) && $this->get_instance()->completionsubmit) {
                     $completion->update_state($this->get_course_module(), COMPLETION_COMPLETE, $USER->id);
@@ -2587,10 +3336,78 @@ class assign {
         return true;
     }
 
+    /**
+     * save the extension date for a single user
+     *
+     * @param int $userid The user id
+     * @param mixed $extensionduedate Either an integer date or null
+     * @return boolean
+     */
+    private function save_user_extension($userid, $extensionduedate) {
+        global $DB;
+
+        $grade = $this->get_user_grade($userid, true);
+        $grade->extensionduedate = $extensionduedate;
+        $grade->timemodified = time();
+
+        $result = $DB->update_record('assign_grades', $grade);
+
+        if ($result) {
+            $this->add_to_log('grant extension', $this->format_grade_for_log($grade));
+        }
+        return $result;
+    }
+
+    /**
+     * save extension date
+     *
+     * @param moodleform $mform The submitted form
+     * @return boolean
+     */
+    private function process_save_extension(& $mform) {
+        global $DB, $CFG;
+
+        // Include extension form.
+        require_once($CFG->dirroot . '/mod/assign/extensionform.php');
+
+        // Need submit permission to submit an assignment.
+        require_capability('mod/assign:grantextension', $this->context);
+
+        $batchusers = optional_param('selectedusers', '', PARAM_TEXT);
+        $userid = 0;
+        if (!$batchusers) {
+            $userid = required_param('userid', PARAM_INT);
+            $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
+        }
+        $mform = new mod_assign_extension_form(null, array($this->get_course_module()->id,
+                                                           $userid,
+                                                           $batchusers,
+                                                           $this->get_instance(),
+                                                           null));
+
+        if ($mform->is_cancelled()) {
+            return true;
+        }
+
+        if ($formdata = $mform->get_data()) {
+            if ($batchusers) {
+                $users = explode(',', $batchusers);
+                $result = true;
+                foreach ($users as $userid) {
+                    $result = $this->save_user_extension($userid, $formdata->extensionduedate) && $result;
+                }
+                return $result;
+            } else {
+                return $this->save_user_extension($userid, $formdata->extensionduedate);
+            }
+        }
+        return false;
+    }
+
+
     /**
      * save quick grades
      *
-     * @global moodle_database $DB
      * @return string The result of the save operation
      */
     private function process_save_quick_grades() {
@@ -2732,6 +3549,53 @@ class assign {
         return get_string('quickgradingchangessaved', 'assign');
     }
 
+    /**
+     * Reveal student identities to markers (and the gradebook)
+     *
+     * @return void
+     */
+    private function process_reveal_identities() {
+        global $DB, $CFG;
+
+        require_capability('mod/assign:revealidentities', $this->context);
+        if (!confirm_sesskey()) {
+            return false;
+        }
+
+        // Update the assignment record.
+        $update = new stdClass();
+        $update->id = $this->get_instance()->id;
+        $update->revealidentities = 1;
+        $DB->update_record('assign', $update);
+
+        // Refresh the instance data.
+        $this->instance = null;
+
+        // Release the grades to the gradebook.
+        // First create the column in the gradebook.
+        $this->update_gradebook(false, $this->get_course_module()->id);
+
+        // Now release all grades.
+
+        $adminconfig = $this->get_admin_config();
+        $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
+        $grades = $DB->get_records('assign_grades', array('assignment'=>$this->get_instance()->id));
+
+        $plugin = $this->get_feedback_plugin_by_type($gradebookplugin);
+
+        foreach ($grades as $grade) {
+            // Fetch any comments for this student.
+            if ($plugin && $plugin->is_enabled() && $plugin->is_visible()) {
+                $grade->feedbacktext = $plugin->text_for_gradebook($grade);
+                $grade->feedbackformat = $plugin->format_for_gradebook($grade);
+            }
+            $this->gradebook_item_update(NULL, $grade);
+        }
+
+        $this->add_to_log('reveal identities', get_string('revealidentities', 'assign'));
+    }
+
+
     /**
      * save grading options
      *
@@ -2765,7 +3629,7 @@ class assign {
     * @param stdClass $grade
     * @return string
     */
-    private function format_grade_for_log(stdClass $grade) {
+    public function format_grade_for_log(stdClass $grade) {
         global $DB;
 
         $user = $DB->get_record('user', array('id' => $grade->userid), '*', MUST_EXIST);
@@ -2779,6 +3643,9 @@ class assign {
         if ($grade->locked) {
             $info .= get_string('submissionslocked', 'assign') . '. ';
         }
+        if (!empty($grade->extensionduedate)) {
+            $info .= get_string('userextensiondate', 'assign', userdate($grade->extensionduedate));
+        }
         return $info;
     }
 
@@ -2829,7 +3696,17 @@ class assign {
             return true;
         }
         if ($data = $mform->get_data()) {
-            $submission = $this->get_user_submission($USER->id, true); //create the submission if needed & its id
+            if ($this->get_instance()->teamsubmission) {
+                $submission = $this->get_group_submission($USER->id, 0, true);
+            } else {
+                $submission = $this->get_user_submission($USER->id, true);
+            }
+            if ($this->get_instance()->submissiondrafts) {
+                $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
+            } else {
+                $submission->status = ASSIGN_SUBMISSION_STATUS_SUBMITTED;
+            }
+
             $grade = $this->get_user_grade($USER->id, false); // get the grade to check if it is locked
             if ($grade && $grade->locked) {
                 print_error('submissionslocked', 'assign');
@@ -2845,7 +3722,7 @@ class assign {
                 }
             }
 
-            $this->update_submission($submission);
+            $this->update_submission($submission, $USER->id, true, $this->get_instance()->teamsubmission);
 
             // Logging
             if (isset($data->submissionstatement)) {
@@ -3031,13 +3908,18 @@ class assign {
         $mform->addElement('hidden', 'ajax', optional_param('ajax', 0, PARAM_INT));
         $mform->setType('ajax', PARAM_INT);
 
+        if ($this->get_instance()->teamsubmission) {
+            $mform->addElement('selectyesno', 'applytoall', get_string('applytoteam', 'assign'));
+            $mform->setDefault('applytoall', 1);
+        }
+
         $mform->addElement('hidden', 'action', 'submitgrade');
         $mform->setType('action', PARAM_ALPHA);
 
 
         $buttonarray=array();
         $buttonarray[] = $mform->createElement('submit', 'savegrade', get_string('savechanges', 'assign'));
-        if (!$last){
+        if (!$last) {
             $buttonarray[] = $mform->createElement('submit', 'saveandshownext', get_string('savenext','assign'));
         }
         $buttonarray[] = $mform->createElement('cancel', 'cancelbutton', get_string('cancel'));
@@ -3049,7 +3931,7 @@ class assign {
             $buttonarray[] = $mform->createElement('submit', 'nosaveandprevious', get_string('previous','assign'));
         }
 
-        if (!$last){
+        if (!$last) {
             $buttonarray[] = $mform->createElement('submit', 'nosaveandnext', get_string('nosavebutnext', 'assign'));
         }
         $mform->addGroup($buttonarray, 'navar', '', array(' '), false);
@@ -3124,9 +4006,14 @@ class assign {
     public function add_submission_form_elements(MoodleQuickForm $mform, stdClass $data) {
         global $USER;
 
-        $submission = $this->get_user_submission($USER->id, false);
+        // Team submissions.
+        if ($this->get_instance()->teamsubmission) {
+            $submission = $this->get_group_submission($USER->id, 0, false);
+        } else {
+            $submission = $this->get_user_submission($USER->id, false);
+        }
 
-        // submission statement
+        // Submission statement.
         $adminconfig = $this->get_admin_config();
 
         $requiresubmissionstatement = !empty($adminconfig->requiresubmissionstatement) ||
@@ -3164,7 +4051,7 @@ class assign {
      * @return void
      */
     private function process_revert_to_draft($userid = 0) {
-        global $DB;
+        global $DB, $USER;
 
         // Need grade permission
         require_capability('mod/assign:grade', $this->context);
@@ -3175,14 +4062,16 @@ class assign {
         }
 
         $submission = $this->get_user_submission($userid, false);
+
         if (!$submission) {
             return;
         }
         $submission->status = ASSIGN_SUBMISSION_STATUS_DRAFT;
-        $this->update_submission($submission, false);
+        $this->update_submission($submission, $USER->id, true, $this->get_instance()->teamsubmission);
 
-        // update the modified time on the grade (grader modified)
+        // Update the modified time on the grade (grader modified).
         $grade = $this->get_user_grade($userid, true);
+        $grade->grader = $USER->id;
         $this->update_grade($grade);
 
         $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
@@ -3249,6 +4138,55 @@ class assign {
         $this->add_to_log('unlock submission', get_string('unlocksubmissionforstudent', 'assign', array('id'=>$user->id, 'fullname'=>fullname($user))));
     }
 
+   /**
+     * Apply a grade from a grading form to a user (may be called multiple times for a group submission)
+     *
+     * @param stdClass $formdata - the data from the form
+     * @param int $userid - the user to apply the grade to
+     * @return void
+     */
+    private function apply_grade_to_user($formdata, $userid) {
+        global $USER, $CFG, $DB;
+
+        $grade = $this->get_user_grade($userid, true);
+        $gradingdisabled = $this->grading_disabled($userid);
+        $gradinginstance = $this->get_grading_instance($userid, $gradingdisabled);
+        if (!$gradingdisabled) {
+            if ($gradinginstance) {
+                $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id);
+            } else {
+                // Handle the case when grade is set to No Grade.
+                if (isset($formdata->grade)) {
+                    $grade->grade= grade_floatval(unformat_float($formdata->grade));
+                }
+            }
+        }
+        $grade->grader= $USER->id;
+
+        $adminconfig = $this->get_admin_config();
+        $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
+
+        // Call save in plugins.
+        foreach ($this->feedbackplugins as $plugin) {
+            if ($plugin->is_enabled() && $plugin->is_visible()) {
+                if (!$plugin->save($grade, $formdata)) {
+                    $result = false;
+                    print_error($plugin->get_error());
+                }
+                if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
+                    // This is the feedback plugin chose to push comments to the gradebook.
+                    $grade->feedbacktext = $plugin->text_for_gradebook($grade);
+                    $grade->feedbackformat = $plugin->format_for_gradebook($grade);
+                }
+            }
+        }
+        $this->update_grade($grade);
+        $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
+
+        $this->add_to_log('grade submission', $this->format_grade_for_log($grade));
+    }
+
+
     /**
      * save outcomes submitted from grading form
      *
@@ -3296,7 +4234,7 @@ class assign {
      * @return bool - was the grade saved
      */
     private function process_save_grade(&$mform) {
-        global $USER, $DB, $CFG;
+        global $CFG;
         // Include grade form
         require_once($CFG->dirroot . '/mod/assign/gradeform.php');
 
@@ -3321,49 +4259,22 @@ class assign {
         $mform = new mod_assign_grade_form(null, array($this, $data, array('rownum'=>$rownum, 'useridlist'=>$useridlist, 'last'=>false)), 'post', '', array('class'=>'gradeform'));
 
         if ($formdata = $mform->get_data()) {
-            $grade = $this->get_user_grade($userid, true);
-            $gradingdisabled = $this->grading_disabled($userid);
-            $gradinginstance = $this->get_grading_instance($userid, $gradingdisabled);
-            if (!$gradingdisabled) {
-                if ($gradinginstance) {
-                    $grade->grade = $gradinginstance->submit_and_get_grade($formdata->advancedgrading, $grade->id);
-                } else {
-                    // handle the case when grade is set to No Grade
-                    if (isset($formdata->grade)) {
-                        $grade->grade = grade_floatval(unformat_float($formdata->grade));
+            if ($this->get_instance()->teamsubmission && $formdata->applytoall) {
+                $groupid = 0;
+                if ($this->get_submission_group($userid)) {
+                    $group = $this->get_submission_group($userid);
+                    if ($group) {
+                        $groupid = $group->id;
                     }
                 }
-            }
-            $grade->grader= $USER->id;
-
-            $adminconfig = $this->get_admin_config();
-            $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
-
-            // call save in plugins
-            foreach ($this->feedbackplugins as $plugin) {
-                if ($plugin->is_enabled() && $plugin->is_visible()) {
-                    if (!$plugin->save($grade, $formdata)) {
-                        $result = false;
-                        print_error($plugin->get_error());
-                    }
-                    if (('assignfeedback_' . $plugin->get_type()) == $gradebookplugin) {
-                        // this is the feedback plugin chose to push comments to the gradebook
-                        $grade->feedbacktext = $plugin->text_for_gradebook($grade);
-                        $grade->feedbackformat = $plugin->format_for_gradebook($grade);
-                    }
+                $members = $this->get_submission_group_members($groupid, true);
+                foreach ($members as $member) {
+                    // User may exist in multple groups (which should put them in the default group).
+                    $this->apply_grade_to_user($formdata, $member->id);
                 }
+            } else {
+                $this->apply_grade_to_user($formdata, $userid);
             }
-            $this->process_outcomes($userid, $formdata);
-
-            $grade->mailed = 0;
-
-            $this->update_grade($grade);
-
-            $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
-
-            $this->add_to_log('grade submission', $this->format_grade_for_log($grade));
-
-
         } else {
             return false;
         }
@@ -3501,5 +4412,104 @@ class assign {
         return $grades;
     }
 
+    /**
+     * Call the static version of this function
+     *
+     * @param int $userid The userid to lookup
+     * @return int The unique id
+     */
+    public function get_uniqueid_for_user($userid) {
+        return self::get_uniqueid_for_user_static($this->get_instance()->id, $userid);
+    }
+
+    /**
+     * Foreach participant in the course - assign them a random id
+     *
+     * @param int $assignid The assignid to lookup
+     */
+    public static function allocate_unique_ids($assignid) {
+        global $DB;
+
+        $cm = get_coursemodule_from_instance('assign', $assignid, 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+
+        $currentgroup = groups_get_activity_group($cm, true);
+        $users = get_enrolled_users($context, "mod/assign:submit", $currentgroup, 'u.id');
+
+        // shuffle the users
+        shuffle($users);
+
+        $record = new stdClass();
+        $record->assignment = $assignid;
+        foreach ($users as $user) {
+            if (!$DB->get_record('assign_user_mapping', array('assignment'=>$assignid, 'userid'=>$user->id), 'id')) {
+                $record->userid = $user->id;
+                $DB->insert_record('assign_user_mapping', $record);
+            }
+        }
+    }
+
+    /**
+     * Lookup this user id and return the unique id for this assignment
+     *
+     * @param int $userid The userid to lookup
+     * @return int The unique id
+     */
+    public static function get_uniqueid_for_user_static($assignid, $userid) {
+        global $DB;
+
+        // Search for a record.
+        if ($record = $DB->get_record('assign_user_mapping', array('assignment'=>$assignid, 'userid'=>$userid), 'id')) {
+            return $record->id;
+        }
+
+        // Be a little smart about this - there is no record for the current user.
+        // We should ensure any unallocated ids for the current participant list are distrubited randomly
+        self::allocate_unique_ids($assignid);
+
+        // Retry the search for a record.
+        if ($record = $DB->get_record('assign_user_mapping', array('assignment'=>$assignid, 'userid'=>$userid), 'id')) {
+            return $record->id;
+        }
+
+        // The requested user must not be a participant. Add a record anyway.
+        $record = new stdClass();
+        $record->assignment = $assignid;
+        $record->userid = $userid;
+
+        return $DB->insert_record('assign_user_mapping', $record);
+    }
+
+    /**
+     * Call the static version of this function
+     *
+     * @param int $uniqueid The uniqueid to lookup
+     * @return int The user id or false if they don't exist
+     */
+    public function get_user_id_for_uniqueid($uniqueid) {
+        return self::get_user_id_for_uniqueid_static($this->get_instance()->id, $uniqueid);
+    }
+
+    /**
+     * Lookup this unique id and return the user id for this assignment
+     *
+     * @param int $assignid The id of the assignment this user mapping is in
+     * @param int $uniqueid The uniqueid to lookup
+     * @return int The user id or false if they don't exist
+     */
+    public static function get_user_id_for_uniqueid_static($assignid, $uniqueid) {
+        global $DB;
+
+        // Search for a record.
+        if ($record = $DB->get_record('assign_user_mapping',
+                                      array('assignment'=>$assignid, 'id'=>$uniqueid),
+                                      'userid',
+                                      IGNORE_MISSING)) {
+            return $record->userid;
+        }
+
+        return false;
+    }
+
 }