MDL-31276: Offline marking. Includes download/upload of csv grades.
authorDamyon Wiese <damyon.wiese@netspot.com.au>
Mon, 23 Jul 2012 06:14:25 +0000 (14:14 +0800)
committerDamyon Wiese <damyon.wiese@netspot.com.au>
Mon, 10 Sep 2012 06:18:32 +0000 (14:18 +0800)
Also provides bulk upload/download of feedback files via zips.

27 files changed:
mod/assign/assignmentplugin.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/file/batchuploadfilesform.php [new file with mode: 0644]
mod/assign/feedback/file/importzipform.php [new file with mode: 0644]
mod/assign/feedback/file/importziplib.php [new file with mode: 0644]
mod/assign/feedback/file/lang/en/assignfeedback_file.php
mod/assign/feedback/file/locallib.php
mod/assign/feedback/file/renderable.php [new file with mode: 0644]
mod/assign/feedback/file/renderer.php [new file with mode: 0644]
mod/assign/feedback/file/uploadzipform.php [new file with mode: 0644]
mod/assign/feedback/offline/db/access.php [new file with mode: 0644]
mod/assign/feedback/offline/importgradesform.php [new file with mode: 0644]
mod/assign/feedback/offline/importgradeslib.php [new file with mode: 0644]
mod/assign/feedback/offline/lang/en/assignfeedback_offline.php [new file with mode: 0644]
mod/assign/feedback/offline/locallib.php [new file with mode: 0644]
mod/assign/feedback/offline/settings.php [new file with mode: 0644]
mod/assign/feedback/offline/uploadgradesform.php [new file with mode: 0644]
mod/assign/feedback/offline/version.php [new file with mode: 0644]
mod/assign/feedbackplugin.php
mod/assign/gradingbatchoperationsform.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/module.js
mod/assign/renderer.php
mod/assign/submission/comments/locallib.php
mod/assign/submission/onlinetext/locallib.php

index d71281b..7ba2055 100644 (file)
@@ -348,15 +348,48 @@ abstract class assign_plugin {
         return '';
     }
 
+    /**
+     * Given a field name and value should update this plugins submission or grade
+     *
+     * @param string $name Name of the field.
+     * @param string $value Updated text
+     * @param int $submissionorgradeid The id of the submission or grade
+     * @return bool - true if the value was updated
+     */
+    public function set_editor_text($name, $value, $submissionorgradeid) {
+        return false;
+    }
+
+    /**
+     * Given a field name and value should update this plugins submission or grade
+     *
+     * @param string $name Name of the field.
+     * @param int $format Updated format
+     * @param int $submissionorgradeid The id of the submission or grade
+     * @return bool - true if the value was updated
+     */
+    public function set_editor_format($name, $value, $submissionorgradeid) {
+        return false;
+    }
+
+    /**
+     * Return a list of the fields that can be exported or imported via text.
+     *
+     * @return array - The list of field names (strings) and descriptions. ($name => $description)
+     */
+    public function get_editor_fields() {
+        return array();
+    }
+
     /**
      * Given a field name, should return the text of an editor field that is part of
      * this plugin. This is used when exporting to portfolio.
      *
      * @param string $name Name of the field.
-     * @param int $submissionid The id of the submission
+     * @param int $submissionorgradeid The id of the submission or grade
      * @return string - The text for the editor field
      */
-    public function get_editor_text($name, $submissionid) {
+    public function get_editor_text($name, $submissionorgradeid) {
         return '';
     }
 
@@ -522,4 +555,32 @@ abstract class assign_plugin {
                                     false);
     }
 
+    /**
+     * This allows a plugin to render a page in the context of the assignment
+     *
+     * If the plugin creates a link to the assignment view.php page with
+     * The following required parameters:
+     *      id=coursemoduleid
+     *      plugin=type
+     *      pluginsubtype=assignfeedback|assignsubmission
+     *      pluginaction=customaction
+     *
+     * Then this function will be called to display the page with the pluginaction passed as action
+     * @param string $action The plugin specified action
+     * @return string
+     */
+    public function view_page($action) {
+        return '';
+    }
+
+    /**
+     * If this plugin should not include a column in the grading table or a row on the summary page
+     * return false
+     *
+     * @return bool
+     */
+    public function has_user_summary() {
+        return true;
+    }
+
 }
index 6510353..9eab5f7 100644 (file)
@@ -101,6 +101,61 @@ class assign_feedback_comments extends assign_feedback_plugin {
         return true;
     }
 
+    /**
+     * Return a list of the text fields that can be imported/exported by this plugin
+     *
+     * @return array An array of field names and descriptions. (name=>description, ...)
+     */
+    public function get_editor_fields() {
+        return array('comments' => get_string('pluginname', 'assignfeedback_comments'));
+    }
+
+    /**
+     * Get the saved text content from the editor
+     *
+     * @param string $name
+     * @param int $submissionid
+     * @return string
+     */
+    public function get_editor_text($name, $gradeid) {
+        if ($name == 'comments') {
+            $feedbackcomments = $this->get_feedback_comments($gradeid);
+            if ($feedbackcomments) {
+                return $feedbackcomments->commenttext;
+            }
+        }
+
+        return '';
+    }
+
+    /**
+     * Get the saved text content from the editor
+     *
+     * @param string $name
+     * @param int $submissionid
+     * @return string
+     */
+    public function set_editor_text($name, $value, $gradeid) {
+        global $DB;
+
+        if ($name == 'comments') {
+            $feedbackcomment = $this->get_feedback_comments($gradeid);
+            if ($feedbackcomment) {
+                $feedbackcomment->commenttext = $value;
+                return $DB->update_record('assignfeedback_comments', $feedbackcomment);
+            } else {
+                $feedbackcomment = new stdClass();
+                $feedbackcomment->commenttext = $value;
+                $feedbackcomment->commentformat = FORMAT_HTML;
+                $feedbackcomment->grade = $gradeid;
+                $feedbackcomment->assignment = $this->assignment->get_instance()->id;
+                return $DB->insert_record('assignfeedback_comments', $feedbackcomment) > 0;
+            }
+        }
+
+        return false;
+    }
+
     /**
      * Save quickgrading changes
      *
@@ -317,4 +372,5 @@ class assign_feedback_comments extends assign_feedback_plugin {
     public function is_empty(stdClass $grade) {
         return $this->view($grade) == '';
     }
+
 }
diff --git a/mod/assign/feedback/file/batchuploadfilesform.php b/mod/assign/feedback/file/batchuploadfilesform.php
new file mode 100644 (file)
index 0000000..350f80f
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the forms to create and edit an instance of this module
+ *
+ * @package   mod_assign
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+require_once($CFG->libdir.'/formslib.php');
+require_once($CFG->dirroot . '/mod/assign/feedback/file/locallib.php');
+
+/**
+ * Assignment grading options form
+ *
+ * @package   mod_assign
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_file_batch_upload_files_form extends moodleform {
+    /**
+     * Define this form - called by the parent constructor
+     */
+    function definition() {
+        global $COURSE, $USER;
+
+        $mform = $this->_form;
+        $params = $this->_customdata;
+
+        $mform->addElement('header', '', get_string('batchuploadfilesforusers', 'assignfeedback_file', count($params['users'])));
+        $mform->addElement('static', 'userslist', get_string('selectedusers', 'assignfeedback_file'), $params['usershtml']);
+
+        $data = new stdClass();
+        $fileoptions = array('subdirs'=>1,
+                                'maxbytes'=>$COURSE->maxbytes,
+                                'accepted_types'=>'*',
+                                'return_types'=>FILE_INTERNAL);
+
+        $data = file_prepare_standard_filemanager($data, 'files', $fileoptions, $params['context'], 'assignfeedback_file', ASSIGNFEEDBACK_BATCHFILE_FILEAREA, $USER->id);
+
+        $mform->addElement('filemanager', 'files_filemanager', '', null, $fileoptions);
+
+        $this->set_data($data);
+
+        $mform->addElement('hidden', 'id', $params['cm']);
+        $mform->addElement('hidden', 'operation', 'plugingradingbatchoperation_file_uploadfiles');
+        $mform->addElement('hidden', 'action', 'viewpluginpage');
+        $mform->addElement('hidden', 'pluginaction', 'uploadfiles');
+        $mform->addElement('hidden', 'plugin', 'file');
+        $mform->addElement('hidden', 'pluginsubtype', 'assignfeedback');
+        $mform->addElement('hidden', 'selectedusers', implode(',', $params['users']));
+        $this->add_action_buttons(true, get_string('uploadfiles', 'assignfeedback_file'));
+
+    }
+
+}
+
diff --git a/mod/assign/feedback/file/importzipform.php b/mod/assign/feedback/file/importzipform.php
new file mode 100644 (file)
index 0000000..ffab544
--- /dev/null
@@ -0,0 +1,129 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the forms to create and edit an instance of this module
+ *
+ * @package   assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+require_once($CFG->libdir.'/formslib.php');
+require_once($CFG->dirroot.'/mod/assign/feedback/file/importziplib.php');
+
+/*
+ * Import zip form
+ *
+ * @package   assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_file_import_zip_form extends moodleform implements renderable {
+
+    /**
+     * Create this grade import form
+     */
+    function definition() {
+        global $CFG, $PAGE;
+
+        $mform = $this->_form;
+        $params = $this->_customdata;
+
+        $renderer = $PAGE->get_renderer('assign');
+
+        // Visible elements.
+        $assignment = $params['assignment'];
+        $contextid = $assignment->get_context()->id;
+        $importer = $params['importer'];
+        $update = false;
+
+        if (!$importer) {
+            print_error('invalidarguments');
+            return;
+        }
+
+
+        $files = $importer->get_import_files($contextid);
+
+        $mform->addElement('header', 'uploadzip', get_string('confirmuploadzip', 'assignfeedback_file'));
+
+        $currentgroup = groups_get_activity_group($assignment->get_course_module(), true);
+        $allusers = $assignment->list_participants($currentgroup, false);
+        $participants = array();
+        foreach ($allusers as $user) {
+            $participants[$assignment->get_uniqueid_for_user($user->id)] = $user;
+        }
+
+        $fs = get_file_storage();
+
+        $updates = array();
+        foreach ($files as $unzippedfile) {
+            $user = null;
+            $plugin = null;
+            $filename = '';
+
+            if ($importer->is_valid_filename_for_import($assignment, $unzippedfile, $participants, $user, $plugin, $filename)) {
+                if ($importer->is_file_modified($assignment, $user, $plugin, $filename, $unzippedfile)) {
+                    // Get a string we can show to identify this user.
+                    $userdesc = fullname($user);
+                    if ($assignment->is_blind_marking()) {
+                        $userdesc = get_string('hiddenuser', 'assign') .
+                                    $assignment->get_unique_id_for_user($user->id);
+                    }
+                    $grade = $assignment->get_user_grade($user->id, false);
+                    $exists = $fs->file_exists($contextid,
+                                               'assignfeedback_file',
+                                               ASSIGNFEEDBACK_FILE_FILEAREA,
+                                               $grade->id,
+                                               '/',
+                                               $filename);
+                    if (!$grade || !$exists) {
+                        $updates[] = get_string('feedbackfileadded', 'assignfeedback_file',
+                                            array('filename'=>$filename, 'student'=>$userdesc));
+                    } else {
+                        $updates[] = get_string('feedbackfileupdated', 'assignfeedback_file',
+                                            array('filename'=>$filename, 'student'=>$userdesc));
+                    }
+                }
+            }
+        }
+
+
+        if (count($updates)) {
+            $mform->addElement('html', $renderer->list_block_contents(array(), $updates));
+        } else {
+            $mform->addElement('html', get_string('nochanges', 'assignfeedback_file'));
+        }
+
+        $mform->addElement('hidden', 'id', $assignment->get_course_module()->id);
+        $mform->addElement('hidden', 'action', 'viewpluginpage');
+        $mform->addElement('hidden', 'confirm', 'true');
+        $mform->addElement('hidden', 'plugin', 'file');
+        $mform->addElement('hidden', 'pluginsubtype', 'assignfeedback');
+        $mform->addElement('hidden', 'pluginaction', 'uploadzip');
+        if (count($updates)) {
+            $this->add_action_buttons(true, get_string('confirm'));
+        } else {
+            $mform->addElement('cancel');
+            $mform->closeHeaderBefore('cancel');
+        }
+    }
+}
+
diff --git a/mod/assign/feedback/file/importziplib.php b/mod/assign/feedback/file/importziplib.php
new file mode 100644 (file)
index 0000000..b006d2e
--- /dev/null
@@ -0,0 +1,297 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the definition for the library class for file feedback plugin
+ *
+ *
+ * @package   assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * library class for importing feedback files from a zip
+ *
+ * @package   asignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_file_zip_importer {
+
+    /**
+     * Is this filename valid (contains a unique participant ID) for import?
+     *
+     * @param assign $assignment - The assignment instance
+     * @param stored_file $fileinfo - The fileinfo
+     * @param array $participants - A list of valid participants for this module indexed by unique_id
+     * @param stdClass $user - Set to the user that matches by participant id
+     * @param string $filename - Set to truncated filename (prefix stripped)
+     * @return true If the participant Id can be extracted and this is a valid user
+     */
+    public function is_valid_filename_for_import($assignment, $fileinfo, $participants, & $user, & $plugin, & $filename) {
+        if ($fileinfo->is_directory()) {
+            return false;
+        }
+
+        // Ignore hidden files.
+        if (strpos($fileinfo->get_filename(), '.') === 0) {
+            return false;
+        }
+        // Ignore hidden files.
+        if (strpos($fileinfo->get_filename(), '~') === 0) {
+            return false;
+        }
+
+        $info = explode('_', $fileinfo->get_filename(), 5);
+
+        if (count($info) < 5) {
+            return false;
+        }
+
+        $participantid = $info[1];
+        $filename = $info[4];
+        $plugin = $assignment->get_plugin_by_type($info[2], $info[3]);
+
+        if (!is_numeric($participantid)) {
+            return false;
+        }
+
+        if (!$plugin) {
+            return false;
+        }
+
+        // Convert to int.
+        $participantid += 0;
+
+        if (empty($participants[$participantid])) {
+            return false;
+        }
+
+        $user = $participants[$participantid];
+        return true;
+    }
+
+    /**
+     * Does this file exist in any of the current files supported by this plugin for this user?
+     *
+     * @param assign $assignment - The assignment instance
+     * @param stdClass $user The user matching this uploaded file
+     * @param assign_plugin $plugin The matching plugin from the filename
+     * @param string $filename The parsed filename from the zip
+     * @param stored_file $fileinfo The info about the extracted file from the zip
+     * @return bool - True if the file has been modified or is new
+     */
+    public function is_file_modified($assignment, $user, $plugin, $filename, $fileinfo) {
+        $sg = null;
+
+        if ($plugin->get_subtype() == 'assignsubmission') {
+            $sg = $assignment->get_user_submission($user->id, false);
+        } else if ($plugin->get_subtype() == 'assignfeedback') {
+            $sg = $assignment->get_user_grade($user->id, false);
+        } else {
+            return false;
+        }
+
+        if (!$sg) {
+            return true;
+        }
+        foreach ($plugin->get_files($sg) as $pluginfilename => $file) {
+            if ($pluginfilename == $filename) {
+                // Extract the file and compare hashes.
+                $contenthash = '';
+                if (is_array($file)) {
+                    $content = reset($file);
+                    $contenthash = sha1($content);
+                } else {
+                    $contenthash = $file->get_contenthash();
+                }
+                if ($contenthash != $fileinfo->get_contenthash()) {
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Delete all temp files used when importing a zip
+     *
+     * @param int $contextid - The context id of this assignment instance
+     * @return bool true if all files were deleted
+     */
+    public function delete_import_files($contextid) {
+        global $USER;
+
+        $fs = get_file_storage();
+
+        return $fs->delete_area_files($contextid,
+                                      'assignfeedback_files',
+                                      ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
+                                      $USER->id);
+    }
+
+    /**
+     * Extract the uploaded zip to a temporary import area for this user
+     *
+     * @param stored_file $zipfile The uploaded file
+     * @param int $contextid The context for this assignment
+     * @return bool - True if the files were unpacked
+     */
+    public function extract_files_from_zip($zipfile, $contextid) {
+        global $USER;
+
+        $feedbackfilesupdated = 0;
+        $feedbackfilesadded = 0;
+        $userswithnewfeedback = array();
+
+        // Unzipping a large zip file is memory intensive.
+        raise_memory_limit(MEMORY_EXTRA);
+
+        $packer = get_file_packer('application/zip');
+        @set_time_limit(ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME);
+
+        return $packer->extract_to_storage($zipfile,
+                                    $contextid,
+                                    'assignfeedback_file',
+                                    ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
+                                    $USER->id,
+                                    'import');
+
+    }
+
+    /**
+     * Get the list of files extracted from the uploaded zip
+     *
+     * @param int $contextid
+     * @return array of stored_files
+     */
+    public function get_import_files($contextid) {
+        global $USER;
+
+        $fs = get_file_storage();
+        $files = $fs->get_directory_files($contextid,
+                                          'assignfeedback_file',
+                                          ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
+                                          $USER->id,
+                                          '/import/');
+
+        return $files;
+    }
+
+    /**
+     * Process an uploaded zip file
+     *
+     * @param assign $assignment - The assignment instance
+     * @param assign_feedback_file $fileplugin - The file feedback plugin
+     * @return string - The html response
+     */
+    public function import_zip_files($assignment, $fileplugin) {
+        global $USER, $CFG, $PAGE, $DB;
+
+        @set_time_limit(ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME);
+        $packer = get_file_packer('application/zip');
+
+        $feedbackfilesupdated = 0;
+        $feedbackfilesadded = 0;
+        $userswithnewfeedback = array();
+        $contextid = $assignment->get_context()->id;
+
+        $fs = get_file_storage();
+        $files = $fs->get_directory_files($contextid,
+                                          'assignfeedback_file',
+                                          ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
+                                          $USER->id,
+                                          '/import/');
+
+        $currentgroup = groups_get_activity_group($assignment->get_course_module(), true);
+        $allusers = $assignment->list_participants($currentgroup, false);
+        $participants = array();
+        foreach ($allusers as $user) {
+            $participants[$assignment->get_uniqueid_for_user($user->id)] = $user;
+        }
+
+        foreach ($files as $unzippedfile) {
+            // Set the timeout for unzipping each file.
+            $user = null;
+            $plugin = null;
+            $filename = '';
+
+            if ($this->is_valid_filename_for_import($assignment, $unzippedfile, $participants, $user, $plugin, $filename)) {
+                if ($this->is_file_modified($assignment, $user, $plugin, $filename, $unzippedfile)) {
+                    $grade = $assignment->get_user_grade($user->id, true);
+
+                    if ($oldfile = $fs->get_file($contextid,
+                                                 'assignfeedback_file',
+                                                 ASSIGNFEEDBACK_FILE_FILEAREA,
+                                                 $grade->id,
+                                                 '/',
+                                                 $filename)) {
+                        // Update existing feedback file.
+                        $oldfile->replace_content_with($unzippedfile);
+                        $feedbackfilesupdated++;
+                    } else {
+                        // Create a new feedback file.
+                        $newfilerecord = new stdClass();
+                        $newfilerecord->contextid = $contextid;
+                        $newfilerecord->component = 'assignfeedback_file';
+                        $newfilerecord->filearea = ASSIGNFEEDBACK_FILE_FILEAREA;
+                        $newfilerecord->filename = $filename;
+                        $newfilerecord->filepath = '/';
+                        $newfilerecord->itemid = $grade->id;
+                        $fs->create_file_from_storedfile($newfilerecord, $unzippedfile);
+                        $feedbackfilesadded++;
+                    }
+                    $userswithnewfeedback[$user->id] = 1;
+
+                    // Update the number of feedback files for this user.
+                    $fileplugin->update_file_count($grade);
+
+                    // Update the last modified time on the grade which will trigger student notifications.
+                    $assignment->update_grade($grade);
+                }
+            }
+        }
+
+
+        require_once($CFG->dirroot . '/mod/assign/feedback/file/renderable.php');
+        $importsummary = new assignfeedback_file_import_summary($assignment->get_course_module()->id,
+                                                            count($userswithnewfeedback),
+                                                            $feedbackfilesadded,
+                                                            $feedbackfilesupdated);
+
+        $assignrenderer = $assignment->get_renderer();
+        $renderer = $PAGE->get_renderer('assignfeedback_file');
+
+        $o = '';
+
+        $o .= $assignrenderer->render(new assign_header($assignment->get_instance(),
+                                                        $assignment->get_context(),
+                                                        false,
+                                                        $assignment->get_course_module()->id,
+                                                        get_string('uploadzipsummary', 'assignfeedback_file')));
+
+        $o .= $renderer->render($importsummary);
+
+        $o .= $assignrenderer->render_footer();
+        return $o;
+    }
+
+}
index ca017e9..20d4069 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['batchoperationconfirmuploadfiles'] = 'Upload one or more feedback files for all selected users?';
+$string['batchuploadfiles'] = 'Upload feedback files for multiple users';
+$string['batchuploadfilesforusers'] = 'Send feedback files to {$a} selected user(s).';
 $string['configmaxbytes'] = 'Maximum file size';
+$string['confirmuploadzip'] = 'Confirm zip upload';
 $string['countfiles'] = '{$a} files';
 $string['default'] = 'Enabled by default';
 $string['default_help'] = 'If set, this feedback method will be enabled by default for all new assignments.';
 $string['enabled'] = 'File feedback';
 $string['enabled_help'] = 'If enabled, the teacher will be able to upload files with feedback when marking the assignments. These files may be, but are not limited to marked up student submissions, documents with comments or spoken audio feedback. ';
+$string['feedbackzip'] = 'Zip file with feedback files';
+$string['feedbackfileadded'] = 'New feedback file "{$a->filename}" for student "{$a->student}"';
+$string['feedbackfileupdated'] = 'Modified feedback file "{$a->filename}" for student "{$a->student}"';
+$string['feedbackzip_help'] = 'A zip file containing a list of feedback files for one or more students. Feedback files will be assigned to students based on the participant id which should be the second part of each filename immediately after the users full name. This naming convention is used when downloading submissions so you can download all submissions, add comments to a few files and then rezip and upload all of the files. Files with no changes will be ignored.';
 $string['file'] = 'Feedback files';
+$string['filesupdated'] = 'Feedback files updated: {$a}';
+$string['filesadded'] = 'Feedback files added: {$a}';
+$string['importfeedbackfiles'] = 'Import feedback file(s)';
 $string['maxbytes'] = 'Maximum file size';
 $string['maxfiles'] = 'Maximum number of uploaded files';
 $string['maximumsize'] = 'Maximum file size';
+$string['moreusers'] = '{$a} more...';
+$string['nochanges'] = 'No changes';
 $string['pluginname'] = 'File feedback';
+$string['uploadfiles'] = 'Send feedback files';
+$string['uploadzip'] = 'Upload multiple feedback files in a zip';
+$string['uploadzipsummary'] = 'Feedback files imported from a zip';
+$string['userswithnewfeedback'] = 'Users with updated feedback: {$a}';
+$string['selectedusers'] = 'Selected users';
index 9989cf3..e06f969 100644 (file)
@@ -28,7 +28,11 @@ defined('MOODLE_INTERNAL') || die();
  * File areas for file feedback assignment
  */
 define('ASSIGNFEEDBACK_FILE_FILEAREA', 'feedback_files');
+define('ASSIGNFEEDBACK_FILE_BATCH_FILEAREA', 'feedback_files_batch');
+define('ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA', 'feedback_files_import');
 define('ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES', 5);
+define('ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS', 5);
+define('ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME', 120);
 
 /**
  * library class for file feedback plugin extending feedback plugin base class
@@ -72,6 +76,48 @@ class assign_feedback_file extends assign_feedback_plugin {
         return $fileoptions;
     }
 
+    /**
+     * Copy all the files from one file area to another
+     *
+     * @param int fromcontextid - The source context id
+     * @param string fromcomponent - The source component
+     * @param string fromfilearea - The source filearea
+     * @param int fromitemid - The source item id
+     * @param int tocontextid - The destination context id
+     * @param string tocomponent - The destination component
+     * @param string tofilearea - The destination filearea
+     * @param int toitemid - The destination item id
+     * @return boolean
+     */
+    private function copy_area_files(file_storage $fs,
+                                     $fromcontextid,
+                                     $fromcomponent,
+                                     $fromfilearea,
+                                     $fromitemid,
+                                     $tocontextid,
+                                     $tocomponent,
+                                     $tofilearea,
+                                     $toitemid) {
+
+        $newfilerecord = new stdClass();
+        $newfilerecord->contextid = $tocontextid;
+        $newfilerecord->component = $tocomponent;
+        $newfilerecord->filearea = $tofilearea;
+        $newfilerecord->itemid = $toitemid;
+
+        if ($files = $fs->get_area_files($fromcontextid, $fromcomponent, $fromfilearea, $fromitemid)) {
+            foreach ($files as $file) {
+                if ($file->is_directory() and $file->get_filepath() === '/') {
+                    // We need a way to mark the age of each draft area.
+                    // By not copying the root dir we force it to be created automatically with current timestamp.
+                    continue;
+                }
+                $newfile = $fs->create_file_from_storedfile($newfilerecord, $file);
+            }
+        }
+        return true;
+    }
+
     /**
      * Get form elements for grading form
      *
@@ -101,7 +147,6 @@ class assign_feedback_file extends assign_feedback_plugin {
      * @return int
      */
     private function count_files($gradeid, $area) {
-        global $USER;
 
         $fs = get_file_storage();
         $files = $fs->get_area_files($this->assignment->get_context()->id, 'assignfeedback_file', $area, $gradeid, "id", false);
@@ -110,22 +155,14 @@ class assign_feedback_file extends assign_feedback_plugin {
     }
 
     /**
-     * Save the feedback files
+     * Update the number of files in the file area
      *
-     * @param stdClass $grade
-     * @param stdClass $data
-     * @return bool
+     * @param stdClass $grade The grade record
+     * @return bool - true if the value was saved
      */
-    public function save(stdClass $grade, stdClass $data) {
-
+    public function update_file_count($grade) {
         global $DB;
 
-        $fileoptions = $this->get_file_options();
-
-
-        $data = file_postupdate_standard_filemanager($data, 'files', $fileoptions, $this->assignment->get_context(), 'assignfeedback_file', ASSIGNFEEDBACK_FILE_FILEAREA, $grade->id);
-
-
         $filefeedback = $this->get_file_feedback($grade->id);
         if ($filefeedback) {
             $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
@@ -139,6 +176,21 @@ class assign_feedback_file extends assign_feedback_plugin {
         }
     }
 
+    /**
+     * Save the feedback files
+     *
+     * @param stdClass $grade
+     * @param stdClass $data
+     * @return bool
+     */
+    public function save(stdClass $grade, stdClass $data) {
+        $fileoptions = $this->get_file_options();
+
+        $data = file_postupdate_standard_filemanager($data, 'files', $fileoptions, $this->assignment->get_context(), 'assignfeedback_file', ASSIGNFEEDBACK_FILE_FILEAREA, $grade->id);
+
+        return $this->update_file_count($grade);
+    }
+
     /**
      * Display the list of files  in the feedback status table
      *
@@ -260,4 +312,249 @@ class assign_feedback_file extends assign_feedback_plugin {
         }
         return true;
     }
+
+    /**
+     * Return a list of the batch grading operations performed by this plugin
+     * This plugin supports batch upload files and upload zip
+     *
+     * @return array The list of batch grading operations
+     */
+    public function get_grading_batch_operations() {
+        return array('uploadfiles'=>get_string('uploadfiles', 'assignfeedback_file'));
+    }
+
+    /**
+     * Upload files and send them to multiple users
+     *
+     * @param array $users - An array of user ids
+     * @return string - The response html
+     */
+    public function view_batch_upload_files($users) {
+        global $CFG, $DB, $USER;
+
+        require_capability('mod/assign:grade', $this->assignment->get_context());
+        require_once($CFG->dirroot . '/mod/assign/feedback/file/batchuploadfilesform.php');
+        require_once($CFG->dirroot . '/mod/assign/renderable.php');
+
+        $formparams = array('cm'=>$this->assignment->get_course_module()->id,
+                            'users'=>$users,
+                            'context'=>$this->assignment->get_context());
+
+        $usershtml = '';
+
+        $usercount = 0;
+        foreach ($users as $userid) {
+            if ($usercount >= ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS) {
+                $usershtml .= get_string('moreusers', 'assignfeedback_file', count($users) - ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS);
+                break;
+            }
+            $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
+
+            $usershtml .= $this->assignment->get_renderer()->render(new assign_user_summary($user,
+                                                                $this->assignment->get_course()->id,
+                                                                has_capability('moodle/site:viewfullnames',
+                                                                $this->assignment->get_course_context()),
+                                                                $this->assignment->is_blind_marking(),
+                                                                $this->assignment->get_uniqueid_for_user($user->id)));
+            $usercount += 1;
+        }
+
+        $formparams['usershtml'] = $usershtml;
+
+        $mform = new assignfeedback_file_batch_upload_files_form(null, $formparams);
+
+        if ($mform->is_cancelled()) {
+            redirect(new moodle_url('view.php',
+                                    array('id'=>$this->assignment->get_course_module()->id,
+                                          'action'=>'grading')));
+            return;
+        } else if ($data = $mform->get_data()) {
+            // Copy the files from the draft area to a temporary import area.
+            $data = file_postupdate_standard_filemanager($data,
+                                                         'files',
+                                                         $this->get_file_options(),
+                                                         $this->assignment->get_context(),
+                                                         'assignfeedback_file',
+                                                         ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
+                                                         $USER->id);
+            $fs = get_file_storage();
+
+            // Now copy each of these files to the users feedback file area.
+            foreach ($users as $userid) {
+                $grade = $this->assignment->get_user_grade($userid, true);
+
+                $this->copy_area_files($fs,
+                                       $this->assignment->get_context()->id,
+                                       'assignfeedback_file',
+                                       ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
+                                       $USER->id,
+                                       $this->assignment->get_context()->id,
+                                       'assignfeedback_file',
+                                       ASSIGNFEEDBACK_FILE_FILEAREA,
+                                       $grade->id);
+
+
+                $filefeedback = $this->get_file_feedback($grade->id);
+                if ($filefeedback) {
+                    $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
+                    $DB->update_record('assignfeedback_file', $filefeedback);
+                } else {
+                    $filefeedback = new stdClass();
+                    $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
+                    $filefeedback->grade = $grade->id;
+                    $filefeedback->assignment = $this->assignment->get_instance()->id;
+                    $DB->insert_record('assignfeedback_file', $filefeedback);
+                }
+            }
+
+            // Now delete the temporary import area.
+            $fs->delete_area_files($this->assignment->get_context()->id,
+                                   'assignfeedback_file',
+                                   ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
+                                   $USER->id);
+
+            redirect(new moodle_url('view.php',
+                                    array('id'=>$this->assignment->get_course_module()->id,
+                                          'action'=>'grading')));
+            return;
+        } else {
+
+            $o = '';
+            $o .= $this->assignment->get_renderer()->render(new assign_header($this->assignment->get_instance(),
+                                                          $this->assignment->get_context(),
+                                                          false,
+                                                          $this->assignment->get_course_module()->id,
+                                                          get_string('batchuploadfiles', 'assignfeedback_file')));
+            $o .= $this->assignment->get_renderer()->render(new assign_form('batchuploadfiles', $mform));
+            $o .= $this->assignment->get_renderer()->render_footer();
+        }
+
+        return $o;
+    }
+
+    /**
+     * User has chosen a custom grading batch operation and selected some users
+     *
+     * @param string $action - The chosen action
+     * @param array $users - An array of user ids
+     * @return string - The response html
+     */
+    public function grading_batch_operation($action, $users) {
+
+        if ($action == 'uploadfiles') {
+            return $this->view_batch_upload_files($users);
+        }
+        return '';
+    }
+
+    /**
+     * View the upload zip form
+     *
+     * @return string - The html response
+     */
+    function view_upload_zip() {
+        global $CFG, $USER;
+
+        require_capability('mod/assign:grade', $this->assignment->get_context());
+        require_once($CFG->dirroot . '/mod/assign/feedback/file/uploadzipform.php');
+        require_once($CFG->dirroot . '/mod/assign/feedback/file/importziplib.php');
+        require_once($CFG->dirroot . '/mod/assign/feedback/file/importzipform.php');
+
+        $mform = new assignfeedback_file_upload_zip_form(null,
+                                                          array('context'=>$this->assignment->get_context(),
+                                                                'cm'=>$this->assignment->get_course_module()->id));
+
+        $o = '';
+
+        $confirm = optional_param('confirm', 0, PARAM_BOOL);
+        $renderer = $this->assignment->get_renderer();
+        // Delete any existing files.
+        $importer = new assignfeedback_file_zip_importer();
+        $contextid = $this->assignment->get_context()->id;
+
+        if ($mform->is_cancelled()) {
+            $importer->delete_import_files($contextid);
+            redirect(new moodle_url('view.php',
+                                    array('id'=>$this->assignment->get_course_module()->id,
+                                          'action'=>'grading')));
+            return;
+        } else if ($confirm) {
+            $params = array('assignment'=>$this->assignment, 'importer'=>$importer);
+
+            $mform = new assignfeedback_file_import_zip_form(null, $params);
+            if ($mform->is_cancelled()) {
+                $importer->delete_import_files($contextid);
+                redirect(new moodle_url('view.php',
+                                        array('id'=>$this->assignment->get_course_module()->id,
+                                          'action'=>'grading')));
+                return;
+            }
+
+            $o .= $importer->import_zip_files($this->assignment, $this);
+            $importer->delete_import_files($contextid);
+        } else if (($data = $mform->get_data()) &&
+                   ($zipfile = $mform->save_stored_file('feedbackzip',
+                                                        $contextid,
+                                                        'assignfeedback_file',
+                                                        ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
+                                                        $USER->id,
+                                                        '/',
+                                                        'import.zip',
+                                                        true))) {
+
+            $importer->extract_files_from_zip($zipfile, $contextid);
+
+            $params = array('assignment'=>$this->assignment, 'importer'=>$importer);
+
+            $mform = new assignfeedback_file_import_zip_form(null, $params);
+
+            $o .= $renderer->render(new assign_header($this->assignment->get_instance(),
+                                                            $this->assignment->get_context(),
+                                                            false,
+                                                            $this->assignment->get_course_module()->id,
+                                                            get_string('confirmuploadzip', 'assignfeedback_file')));
+            $o .= $renderer->render(new assign_form('confirmimportzip', $mform));
+            $o .= $renderer->render_footer();
+
+        } else {
+
+            $o .= $renderer->render(new assign_header($this->assignment->get_instance(),
+                                                            $this->assignment->get_context(),
+                                                            false,
+                                                            $this->assignment->get_course_module()->id,
+                                                            get_string('uploadzip', 'assignfeedback_file')));
+            $o .= $renderer->render(new assign_form('uploadfeedbackzip', $mform));
+            $o .= $renderer->render_footer();
+        }
+
+        return $o;
+    }
+
+    /**
+     * Called by the assignment module when someone chooses something from the grading navigation or batch operations list
+     *
+     * @param string $action - The page to view
+     * @return string - The html response
+     */
+    public function view_page($action) {
+        if ($action == 'uploadfiles') {
+            $users = required_param('selectedusers', PARAM_TEXT);
+            return $this->view_batch_upload_files(explode(',', $users));
+        }
+        if ($action == 'uploadzip') {
+            return $this->view_upload_zip();
+        }
+
+        return '';
+    }
+
+    /**
+     * Return a list of the grading actions performed by this plugin
+     * This plugin supports upload zip
+     *
+     * @return array The list of grading actions
+     */
+    public function get_grading_actions() {
+        return array('uploadzip'=>get_string('uploadzip', 'assignfeedback_file'));
+    }
 }
diff --git a/mod/assign/feedback/file/renderable.php b/mod/assign/feedback/file/renderable.php
new file mode 100644 (file)
index 0000000..938918d
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains a renderer for the assignment class
+ *
+ * @package   assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * A renderable summary of the zip import
+ *
+ * @package assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_file_import_summary implements renderable {
+    /** @var int $cmid Course module id for constructing navigation links */
+    public $cmid = 0;
+    /** @var int $userswithnewfeedback The number of users who have received new feedback */
+    public $userswithnewfeedback = 0;
+    /** @var int $feedbackfilesadded The number of new feedback files */
+    public $feedbackfilesadded = 0;
+    /** @var int $feedbackfilesupdated The number of updated feedback files */
+    public $feedbackfilesupdated = 0;
+
+    /**
+     * Constructor for this renderable class
+     *
+     * @param int $cmid - The course module id for navigation
+     * @param int $userswithnewfeedbac - The number of users with new feedback
+     * @param int $feedbackfilesadded - The number of feedback files added
+     * @param int $feedbackfilesupdated - The number of feedback files updated
+     */
+    public function __construct($cmid, $userswithnewfeedback, $feedbackfilesadded, $feedbackfilesupdated) {
+        $this->cmid = $cmid;
+        $this->userswithnewfeedback = $userswithnewfeedback;
+        $this->feedbackfilesadded = $feedbackfilesadded;
+        $this->feedbackfilesupdated = $feedbackfilesupdated;
+    }
+}
diff --git a/mod/assign/feedback/file/renderer.php b/mod/assign/feedback/file/renderer.php
new file mode 100644 (file)
index 0000000..00a8838
--- /dev/null
@@ -0,0 +1,55 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains a renderer for the assignment class
+ *
+ * @package   assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * A custom renderer class that extends the plugin_renderer_base and is used by the assign module.
+ *
+ * @package assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_file_renderer extends plugin_renderer_base {
+
+    /**
+     * Render a summary of the zip file import
+     *
+     * @param assignfeedback_file_import_summary $summary - Stats about the zip import
+     * @return string The html response
+     */
+    public function render_assignfeedback_file_import_summary($summary) {
+        $o = '';
+        $o .= $this->container(get_string('userswithnewfeedback', 'assignfeedback_file', $summary->userswithnewfeedback));
+        $o .= $this->container(get_string('filesupdated', 'assignfeedback_file', $summary->feedbackfilesupdated));
+        $o .= $this->container(get_string('filesadded', 'assignfeedback_file', $summary->feedbackfilesadded));
+
+        $url = new moodle_url('view.php',
+                              array('id'=>$summary->cmid,
+                                    'action'=>'grading'));
+        $o .= $this->continue_button($url);
+        return $o;
+    }
+}
+
diff --git a/mod/assign/feedback/file/uploadzipform.php b/mod/assign/feedback/file/uploadzipform.php
new file mode 100644 (file)
index 0000000..218aee9
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the forms to create and edit an instance of this module
+ *
+ * @package   assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+require_once ($CFG->libdir.'/formslib.php');
+
+/**
+ * Upload feedback zip
+ *
+ * @package   assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_file_upload_zip_form extends moodleform {
+    /**
+     * Define this form - called by the parent constructor
+     */
+    function definition() {
+        global $COURSE, $USER;
+
+        $mform = $this->_form;
+        $params = $this->_customdata;
+
+        $mform->addElement('header', '', get_string('uploadzip', 'assignfeedback_file'));
+
+        $fileoptions = array('subdirs'=>0,
+                                'maxbytes'=>$COURSE->maxbytes,
+                                'accepted_types'=>'zip',
+                                'maxfiles'=>1,
+                                'return_types'=>FILE_INTERNAL);
+
+        $mform->addElement('filepicker', 'feedbackzip', get_string('uploadafile'), null, $fileoptions);
+        $mform->addRule('feedbackzip', get_string('uploadnofilefound'), 'required', null, 'client');
+        $mform->addHelpButton('feedbackzip', 'feedbackzip', 'assignfeedback_file');
+
+        $mform->addElement('hidden', 'id', $params['cm']);
+        $mform->addElement('hidden', 'action', 'viewpluginpage');
+        $mform->addElement('hidden', 'pluginaction', 'uploadzip');
+        $mform->addElement('hidden', 'plugin', 'file');
+        $mform->addElement('hidden', 'pluginsubtype', 'assignfeedback');
+        $this->add_action_buttons(true, get_string('importfeedbackfiles', 'assignfeedback_file'));
+
+    }
+
+}
+
diff --git a/mod/assign/feedback/offline/db/access.php b/mod/assign/feedback/offline/db/access.php
new file mode 100644 (file)
index 0000000..03e660c
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Capability definitions for this module.
+ *
+ * @package   assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$capabilities = array();
+
diff --git a/mod/assign/feedback/offline/importgradesform.php b/mod/assign/feedback/offline/importgradesform.php
new file mode 100644 (file)
index 0000000..e4ffec7
--- /dev/null
@@ -0,0 +1,180 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the forms to create and edit an instance of this module
+ *
+ * @package   assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+require_once($CFG->libdir.'/formslib.php');
+require_once($CFG->dirroot.'/mod/assign/feedback/offline/importgradeslib.php');
+
+/*
+ * Import grades form
+ *
+ * @package   assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_offline_import_grades_form extends moodleform implements renderable {
+
+    /**
+     * Create this grade import form
+     */
+    function definition() {
+        global $CFG, $PAGE, $DB;
+
+        $mform = $this->_form;
+        $params = $this->_customdata;
+
+        $renderer = $PAGE->get_renderer('assign');
+
+        // Visible elements.
+        $assignment = $params['assignment'];
+        $csvdata = $params['csvdata'];
+        $gradeimporter = $params['gradeimporter'];
+        $update = false;
+
+        $ignoremodified = $params['ignoremodified'];
+        $draftid = $params['draftid'];
+
+        if (!$gradeimporter) {
+            print_error('invalidarguments');
+            return;
+        }
+
+        if ($csvdata) {
+            $gradeimporter->parsecsv($csvdata);
+        }
+
+        $scaleoptions = null;
+        if ($assignment->get_instance()->grade < 0) {
+            $scale = $DB->get_record('scale', array('id'=>-($assignment->get_instance()->grade)));
+            if ($scale) {
+                $scaleoptions = explode(',', $scale->scale);
+            }
+        }
+        if (!$gradeimporter->init()) {
+            $thisurl = new moodle_url('/mod/assign/view.php', array('action'=>'viewpluginpage',
+                                                                     'pluginsubtype'=>'assignfeedback',
+                                                                     'plugin'=>'offline',
+                                                                     'pluginaction'=>'uploadgrades',
+                                                                     'id'=>$assignment->get_course_module()->id));
+            print_error('invalidgradeimport', 'assignfeedback_offline', $thisurl);
+            return;
+        }
+
+        $mform->addElement('header', 'importgrades', get_string('importgrades', 'assignfeedback_offline'));
+
+        $updates = array();
+        while ($record = $gradeimporter->next()) {
+            $user = $record->user;
+            $grade = $record->grade;
+            $gradedesc = $grade;
+            $modified = $record->modified;
+            $userdesc = fullname($user);
+            if ($assignment->is_blind_marking()) {
+                $userdesc = get_string('hiddenuser', 'assign') . $assignment->get_unique_id_for_user($user->id);
+            }
+
+            $usergrade = $assignment->get_user_grade($user->id, false);
+            // Note: we lose the seconds when converting to user date format - so must not count seconds in comparision.
+            $skip = false;
+
+            $stalemodificationdate = ($usergrade && $usergrade->timemodified > ($modified + 60));
+
+            if (!empty($scaleoptions)) {
+                // This is a scale - we need to convert any grades to indexes in the scale.
+                $scaleindex = array_search($grade, $scaleoptions);
+                if ($scaleindex !== false) {
+                    $grade = $scaleindex + 1;
+                } else {
+                    $grade = '';
+                }
+            }
+
+            if ($usergrade && $usergrade->grade == $grade) {
+                // Skip - grade not modified.
+                $skip = true;
+            } else if (!isset($grade) || $grade === '' || $grade < 0) {
+                // Skip - grade has no value.
+                $skip = true;
+            } else if (!$ignoremodified && $stalemodificationdate) {
+                // Skip - grade has been modified.
+                $skip = true;
+            } else if ($assignment->grading_disabled($user->id)) {
+                // Skip grade is locked.
+                $skip = true;
+            }
+
+            if (!$skip) {
+                $update = true;
+                $updates[] = get_string('gradeupdate', 'assignfeedback_offline',
+                                            array('grade'=>$gradedesc, 'student'=>$userdesc));
+            }
+
+            if ($ignoremodified || !$stalemodificationdate) {
+                foreach ($record->feedback as $feedback) {
+                    $plugin = $feedback['plugin'];
+                    $field = $feedback['field'];
+                    $newvalue = $feedback['value'];
+                    $description = $feedback['description'];
+                    $oldvalue = '';
+                    if ($usergrade) {
+                        $oldvalue = $plugin->get_editor_text($field, $usergrade->id);
+                    }
+                    if ($newvalue != $oldvalue) {
+                        $update = true;
+                        $updates[] = get_string('feedbackupdate', 'assignfeedback_offline',
+                                                    array('text'=>$newvalue, 'field'=>$description, 'student'=>$userdesc));
+                    }
+                }
+            }
+
+        }
+        $gradeimporter->close(false);
+
+        if ($update) {
+            $mform->addElement('html', $renderer->list_block_contents(array(), $updates));
+        } else {
+            $mform->addElement('html', get_string('nochanges', 'assignfeedback_offline'));
+        }
+
+        $mform->addElement('hidden', 'id', $assignment->get_course_module()->id);
+        $mform->addElement('hidden', 'action', 'viewpluginpage');
+        $mform->addElement('hidden', 'confirm', 'true');
+        $mform->addElement('hidden', 'plugin', 'offline');
+        $mform->addElement('hidden', 'pluginsubtype', 'assignfeedback');
+        $mform->addElement('hidden', 'pluginaction', 'uploadgrades');
+        $mform->addElement('hidden', 'importid', $gradeimporter->importid);
+        $mform->addElement('hidden', 'ignoremodified', $ignoremodified);
+        $mform->addElement('hidden', 'draftid', $draftid);
+        if ($update) {
+            $this->add_action_buttons(true, get_string('confirm'));
+        } else {
+            $mform->addElement('cancel');
+            $mform->closeHeaderBefore('cancel');
+        }
+
+    }
+}
+
diff --git a/mod/assign/feedback/offline/importgradeslib.php b/mod/assign/feedback/offline/importgradeslib.php
new file mode 100644 (file)
index 0000000..fba5ef9
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the forms to create and edit an instance of this module
+ *
+ * @package assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+/*
+ * CSV Grade importer
+ *
+ * @package   assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_offline_grade_importer {
+
+    /** var string $importid - unique id for this import operation - must be passed between requests */
+    public $importid;
+
+    /** @var csv_import_reader $csvreader - the csv importer class */
+    private $csvreader;
+
+    /** @var assignment $assignment - the assignment class */
+    private $assignment;
+
+    /** @var int $gradeindex the column index containing the grades */
+    private $gradeindex = -1;
+
+    /** @var int $idindex the column index containing the unique id  */
+    private $idindex = -1;
+
+    /** @var int $modifiedindex the column index containing the last modified time */
+    private $modifiedindex = -1;
+
+    /** @var array $validusers only the enrolled users with the correct capability in this course */
+    private $validusers;
+
+    /** @var array $feedbackcolumnindexes A lookup of column indexes for feedback plugin text import columns */
+    private $feedbackcolumnindexes = array();
+
+    /**
+     * Constructor
+     *
+     * @param string importid
+     */
+    function __construct($importid, assign $assignment) {
+        $this->importid = $importid;
+        $this->assignment = $assignment;
+    }
+
+    /**
+     * Parse a csv file and save the content to a temp file
+     * Should be called before init()
+     *
+     * @return bool false is a failed import
+     */
+    function parsecsv($csvdata) {
+        $this->csvreader = new csv_import_reader($this->importid, 'assignfeedback_offline');
+        $this->csvreader->load_csv_content($csvdata, 'utf-8', 'comma');
+    }
+
+    /**
+     * Initialise the import reader and locate the column indexes.
+     *
+     * @return bool false is a failed import
+     */
+    function init() {
+        if ($this->csvreader == null) {
+            $this->csvreader = new csv_import_reader($this->importid, 'assignfeedback_offline');
+        }
+        $this->csvreader->init();
+
+        $columns = $this->csvreader->get_columns();
+
+        $strgrade = get_string('grade');
+        $strid = get_string('recordid', 'assign');
+        $strmodified = get_string('lastmodifiedgrade', 'assign');
+
+        foreach ($this->assignment->get_feedback_plugins() as $plugin) {
+            if ($plugin->is_enabled() && $plugin->is_visible()) {
+                foreach ($plugin->get_editor_fields() as $field => $description) {
+                    $this->feedbackcolumnindexes[$description] = array('plugin'=>$plugin,
+                                                                       'field'=>$field,
+                                                                       'description'=>$description);
+                }
+            }
+        }
+
+        if ($columns) {
+            foreach ($columns as $index => $column) {
+                if (isset($this->feedbackcolumnindexes[$column])) {
+                    $this->feedbackcolumnindexes[$column]['index'] = $index;
+                }
+                if ($column == $strgrade) {
+                    $this->gradeindex = $index;
+                }
+                if ($column == $strid) {
+                    $this->idindex = $index;
+                }
+                if ($column == $strmodified) {
+                    $this->modifiedindex = $index;
+                }
+            }
+        }
+
+        if ($this->idindex < 0 || $this->gradeindex < 0 || $this->modifiedindex < 0) {
+            return false;
+        }
+
+        $groupmode = groups_get_activity_groupmode($this->assignment->get_course_module());
+        // All users.
+        $groupid = 0;
+        $groupname = '';
+        if ($groupmode) {
+            $groupid = groups_get_activity_group($this->assignment->get_course_module(), true);
+            $groupname = groups_get_group_name($groupid).'-';
+        }
+        $this->validusers = $this->assignment->list_participants($groupid, false);
+        return true;
+    }
+
+    /**
+     * Get the next row of data from the csv file (only the columns we care about)
+     *
+     * @global moodle_database $DB
+     * @return stdClass or false The stdClass is an object containing user, grade and lastmodified
+     */
+    function next() {
+        global $DB;
+        $result = new stdClass();
+
+        while ($record = $this->csvreader->next()) {
+            $idstr = $record[$this->idindex];
+            // Strip the integer from the end of the participant string.
+            $id = substr($idstr, strlen(get_string('hiddenuser', 'assign')));
+            if ($userid = $this->assignment->get_user_id_for_uniqueid($id)) {
+                if (array_key_exists($userid, $this->validusers)) {
+                    $result->grade = $record[$this->gradeindex];
+                    $result->modified = strtotime($record[$this->modifiedindex]);
+                    $result->user = $this->validusers[$userid];
+                    $result->feedback = array();
+                    foreach ($this->feedbackcolumnindexes as $description => $details) {
+                        $details['value'] = $record[$details['index']];
+                        $result->feedback[] = $details;
+                    }
+
+                    return $result;
+                }
+            }
+        }
+
+        // If we got here the csvreader had no more rows.
+        return false;
+    }
+
+    /**
+     * Close the grade importer file and optionally delete any temp files
+     *
+     * @param bool $delete
+     */
+    function close($delete) {
+        $this->csvreader->close();
+        if ($delete) {
+            $this->csvreader->cleanup();
+        }
+    }
+}
+
diff --git a/mod/assign/feedback/offline/lang/en/assignfeedback_offline.php b/mod/assign/feedback/offline/lang/en/assignfeedback_offline.php
new file mode 100644 (file)
index 0000000..5fd0d64
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Strings for component 'feedback_file', language 'en'
+ *
+ * @package   assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['confirmimport'] = 'Confirm grades import';
+$string['default'] = 'Enabled by default';
+$string['default_help'] = 'If set, offline grading with worksheets will be enabled by default for all new assignments.';
+$string['downloadgrades'] = 'Download grading worksheet';
+$string['enabled'] = 'Offline grading worksheet';
+$string['enabled_help'] = 'If enabled, the teacher will be able to download and upload a worksheet with student grades when marking the assignments.';
+$string['feedbackupdate'] = 'Set field "{$a->field}" for "{$a->student}" to "{$a->text}"';
+$string['graderecentlymodified'] = 'The grade has been modified in Moodle more recently than in the grading worksheet for {$a}';
+$string['gradelockedingradebook'] = 'The grade has been locked in the gradebook for {$a}';
+$string['gradeupdate'] = 'Set grade for {$a->student} to {$a->grade}';
+$string['ignoremodified'] = 'Allow updating records that have been modified more recently in Moodle than in the spreadsheet.';
+$string['ignoremodified_help'] = 'When the grading worksheet is downloaded from Moodle it contains the last modified date for each of the grades. If any of the grades are updated in Moodle after this worksheet is downloaded, by default Moodle will refuse to overwrite this updated information when importing the grades. By selecting this option Moodle will disable this safety check and it may be possible for multiple markers to overwrite each others grades.';
+$string['importgrades'] = 'Confirm changes in grading worksheet';
+$string['invalidgradeimport'] = 'Moodle could not read the uploaded worksheet. Make sure it is saved in comma separated value format (.csv) and try again.';
+$string['gradesfile'] = 'Grading worksheet (csv format)';
+$string['gradesfile_help'] = 'Grading worksheet with modified grades. This file must be a csv file that has been downloaded form this assignment and must contain columns for the student grade, and identifier.';
+$string['nochanges'] = 'No modified grades found in uploaded worksheet';
+$string['offlinegradingworksheet'] = 'Grades';
+$string['pluginname'] = 'Offline grading worksheet';
+$string['processgrades'] = 'Import grades';
+$string['skiprecord'] = 'Skip record';
+$string['updaterecord'] = 'Update record';
+$string['uploadgrades'] = 'Upload grading worksheet';
+$string['updatedgrades'] = 'Updated {$a} grades and feedback';
diff --git a/mod/assign/feedback/offline/locallib.php b/mod/assign/feedback/offline/locallib.php
new file mode 100644 (file)
index 0000000..784a04e
--- /dev/null
@@ -0,0 +1,387 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the definition for the library class for file feedback plugin
+ *
+ *
+ * @package   assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->dirroot.'/grade/grading/lib.php');
+
+/**
+ * library class for file feedback plugin extending feedback plugin base class
+ *
+ * @package   asignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assign_feedback_offline extends assign_feedback_plugin {
+
+    /**
+     * Get the name of the file feedback plugin
+     * @return string
+     */
+    public function get_name() {
+        return get_string('pluginname', 'assignfeedback_offline');
+    }
+
+    /**
+     * Get form elements for grading form
+     *
+     * @param stdClass $grade
+     * @param MoodleQuickForm $mform
+     * @param stdClass $data
+     * @return bool true if elements were added to the form
+     */
+    public function get_form_elements($grade, MoodleQuickForm $mform, stdClass $data) {
+        return false;
+    }
+
+    /**
+     * Return true if there are no feedback files
+     * @param stdClass $grade
+     */
+    public function is_empty(stdClass $grade) {
+        return true;
+    }
+
+    /**
+     * Loop through uploaded grades and update the grades for this assignment
+     *
+     * @param int $draftid - The unique draft item id for this import
+     * @param int $importid - The unique import ID for this csv import operation
+     * @return string - The html response
+     */
+    public function process_import_grades($draftid, $importid, $ignoremodified) {
+        global $USER, $DB;
+
+        require_sesskey();
+        require_capability('mod/assign:grade', $this->assignment->get_context());
+
+        $gradeimporter = new assignfeedback_offline_grade_importer($importid, $this->assignment);
+
+        $context = get_context_instance(CONTEXT_USER, $USER->id);
+        $fs = get_file_storage();
+        if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
+            redirect(new moodle_url('view.php',
+                                array('id'=>$this->assignment->get_course_module()->id,
+                                      'action'=>'grading')));
+            return;
+        }
+        $file = reset($files);
+
+        $csvdata = $file->get_content();
+
+        if ($csvdata) {
+            $gradeimporter->parsecsv($csvdata);
+        }
+        if (!$gradeimporter->init()) {
+            $thisurl = new moodle_url('/mod/assign/view.php', array('action'=>'viewpluginpage',
+                                                                     'pluginsubtype'=>'assignfeedback',
+                                                                     'plugin'=>'offline',
+                                                                     'pluginaction'=>'uploadgrades',
+                                                                     'id'=>$assignment->get_course_module()->id));
+            print_error('invalidgradeimport', 'assignfeedback_offline', $thisurl);
+            return;
+        }
+        // Does this assignment use a scale?
+        $scaleoptions = null;
+        if ($this->assignment->get_instance()->grade < 0) {
+            $scale = $DB->get_record('scale', array('id'=>-($this->assignment->get_instance()->grade)));
+            if ($scale) {
+                $scaleoptions = explode(',', $scale->scale);
+            }
+        }
+        // We may need to upgrade the gradebook comments after this update.
+        $adminconfig = $this->assignment->get_admin_config();
+        $gradebookplugin = $adminconfig->feedback_plugin_for_gradebook;
+
+        $updatecount = 0;
+        while ($record = $gradeimporter->next()) {
+            $user = $record->user;
+            $modified = $record->modified;
+            $userdesc = fullname($user);
+            $usergrade = $this->assignment->get_user_grade($user->id, false);
+
+            if (!empty($scaleoptions)) {
+                // This is a scale - we need to convert any grades to indexes in the scale.
+                $scaleindex = array_search($record->grade, $scaleoptions);
+                if ($scaleindex !== false) {
+                    $record->grade = $scaleindex + 1;
+                } else {
+                    $record->grade = '';
+                }
+            }
+
+            // Note: Do not count the seconds when comparing modified dates.
+            $skip = false;
+            $stalemodificationdate = ($usergrade && $usergrade->timemodified > ($modified + 60));
+
+            if ($usergrade && $usergrade->grade == $record->grade) {
+                // Skip - grade not modified.
+                $skip = true;
+            } else if (!isset($record->grade) || $record->grade === '' || $record->grade < 0) {
+                // Skip - grade has no value.
+                $skip = true;
+            } else if (!$ignoremodified && $stalemodificationdate) {
+                // Skip - grade has been modified.
+                $skip = true;
+            } else if ($this->assignment->grading_disabled($record->user->id)) {
+                // Skip grade is locked.
+                $skip = true;
+            }
+
+            if (!$skip) {
+                $grade = $this->assignment->get_user_grade($record->user->id, true);
+
+                $grade->grade = $record->grade;
+                $grade->grader = $USER->id;
+                if ($this->assignment->update_grade($grade)) {
+                    $this->assignment->add_to_log('grade submission', $this->assignment->format_grade_for_log($grade));
+                    $updatecount += 1;
+                }
+            }
+
+            if ($ignoremodified || !$stalemodificationdate) {
+                foreach ($record->feedback as $feedback) {
+                    $plugin = $feedback['plugin'];
+                    $field = $feedback['field'];
+                    $newvalue = $feedback['value'];
+                    $description = $feedback['description'];
+                    $oldvalue = '';
+                    if ($usergrade) {
+                        $oldvalue = $plugin->get_editor_text($field, $usergrade->id);
+                        if (empty($oldvalue)) {
+                            $oldvalue = '';
+                        }
+                    }
+                    if ($newvalue != $oldvalue) {
+                        $updatecount += 1;
+                        $grade = $this->assignment->get_user_grade($record->user->id, true);
+                        if ($plugin->set_editor_text($field, $newvalue, $grade->id)) {
+                            $logdesc = get_string('feedbackupdate', 'assignfeedback_offline',
+                                                  array('field'=>$description,
+                                                        'student'=>$userdesc,
+                                                        'text'=>$newvalue));
+
+                            $this->assignment->add_to_log('save grading feedback', $logdesc);
+                        }
+
+                        // If this is the gradebook comments plugin - post an update to the gradebook.
+                        if (($plugin->get_subtype() . '_' . $plugin->get_type()) == $gradebookplugin) {
+                            $grade->feedbacktext = $plugin->text_for_gradebook($grade);
+                            $grade->feedbackformat = $plugin->format_for_gradebook($grade);
+                            $this->assignment->update_grade($grade);
+                        }
+                    }
+                }
+            }
+        }
+        $gradeimporter->close(true);
+
+        $renderer = $this->assignment->get_renderer();
+        $o = '';
+
+        $o .= $renderer->render(new assign_header($this->assignment->get_instance(),
+                                                  $this->assignment->get_context(),
+                                                  false,
+                                                  $this->assignment->get_course_module()->id,
+                                                  get_string('importgrades', 'assignfeedback_offline')));
+        $o .= $renderer->box(get_string('updatedgrades', 'assignfeedback_offline', $updatecount));
+        $url = new moodle_url('view.php',
+                              array('id'=>$this->assignment->get_course_module()->id,
+                                    'action'=>'grading'));
+        $o .= $renderer->continue_button($url);
+        $o .= $renderer->render_footer();
+        return $o;
+    }
+
+    /**
+     * Display upload grades form
+     *
+     * @return string The response html
+     */
+    public function upload_grades() {
+        global $CFG, $USER;
+
+        require_capability('mod/assign:grade', $this->assignment->get_context());
+        require_once($CFG->dirroot . '/mod/assign/feedback/offline/uploadgradesform.php');
+        require_once($CFG->dirroot . '/mod/assign/feedback/offline/importgradesform.php');
+        require_once($CFG->dirroot . '/mod/assign/feedback/offline/importgradeslib.php');
+        require_once($CFG->libdir . '/csvlib.class.php');
+
+        $mform = new assignfeedback_offline_upload_grades_form(null,
+                                                              array('context'=>$this->assignment->get_context(),
+                                                                    'cm'=>$this->assignment->get_course_module()->id));
+
+        $o = '';
+
+        $confirm = optional_param('confirm', 0, PARAM_BOOL);
+        $renderer = $this->assignment->get_renderer();
+
+        if ($mform->is_cancelled()) {
+            redirect(new moodle_url('view.php',
+                                    array('id'=>$this->assignment->get_course_module()->id,
+                                          'action'=>'grading')));
+            return;
+        } else if (($data = $mform->get_data()) &&
+                   ($csvdata = $mform->get_file_content('gradesfile'))) {
+
+            $importid = csv_import_reader::get_new_iid('assignfeedback_offline');
+            $gradeimporter = new assignfeedback_offline_grade_importer($importid, $this->assignment);
+            // File exists and was valid.
+            $ignoremodified = !empty($data->ignoremodified);
+
+            $draftid = $data->gradesfile;
+
+            // Preview import.
+
+            $mform = new assignfeedback_offline_import_grades_form(null, array('assignment'=>$this->assignment,
+                                                                       'csvdata'=>$csvdata,
+                                                                       'ignoremodified'=>$ignoremodified,
+                                                                       'gradeimporter'=>$gradeimporter,
+                                                                       'draftid'=>$draftid));
+
+            $o .= $renderer->render(new assign_header($this->assignment->get_instance(),
+                                                            $this->assignment->get_context(),
+                                                            false,
+                                                            $this->assignment->get_course_module()->id,
+                                                            get_string('confirmimport', 'assignfeedback_offline')));
+            $o .= $renderer->render(new assign_form('confirmimport', $mform));
+            $o .= $renderer->render_footer();
+        } else if ($confirm) {
+
+            $importid = optional_param('importid', 0, PARAM_INT);
+            $draftid = optional_param('draftid', 0, PARAM_INT);
+            $ignoremodified = optional_param('ignoremodified', 0, PARAM_BOOL);
+            $gradeimporter = new assignfeedback_offline_grade_importer($importid, $this->assignment);
+            $mform = new assignfeedback_offline_import_grades_form(null, array('assignment'=>$this->assignment,
+                                                                       'csvdata'=>'',
+                                                                       'ignoremodified'=>$ignoremodified,
+                                                                       'gradeimporter'=>$gradeimporter,
+                                                                       'draftid'=>$draftid));
+            if ($mform->is_cancelled()) {
+                redirect(new moodle_url('view.php',
+                                        array('id'=>$this->assignment->get_course_module()->id,
+                                              'action'=>'grading')));
+                return;
+            }
+
+            $o .= $this->process_import_grades($draftid, $importid, $ignoremodified);
+        } else {
+
+            $o .= $renderer->render(new assign_header($this->assignment->get_instance(),
+                                                            $this->assignment->get_context(),
+                                                            false,
+                                                            $this->assignment->get_course_module()->id,
+                                                            get_string('uploadgrades', 'assignfeedback_offline')));
+            $o .= $renderer->render(new assign_form('batchuploadfiles', $mform));
+            $o .= $renderer->render_footer();
+        }
+
+        return $o;
+    }
+
+    /**
+     * Download a marking worksheet
+     *
+     * @return string The response html
+     */
+    public function download_grades() {
+        global $CFG;
+
+        require_capability('mod/assign:grade', $this->assignment->get_context());
+        require_once($CFG->dirroot . '/mod/assign/gradingtable.php');
+
+        $groupmode = groups_get_activity_groupmode($this->assignment->get_course_module());
+        // All users.
+        $groupid = 0;
+        $groupname = '';
+        if ($groupmode) {
+            $groupid = groups_get_activity_group($this->assignment->get_course_module(), true);
+            $groupname = groups_get_group_name($groupid) . '-';
+        }
+        $filename = clean_filename(get_string('offlinegradingworksheet', 'assignfeedback_offline') . '-' .
+                                   $this->assignment->get_course()->shortname . '-' .
+                                   $this->assignment->get_instance()->name . '-' .
+                                   $groupname .
+                                   $this->assignment->get_course_module()->id); //name of new zip file.
+
+        $table = new assign_grading_table($this->assignment, 0, '', 0, false, $filename);
+
+        $table->out(0, false);
+        return;
+    }
+
+    /**
+     * Print a sub page in this plugin
+     *
+     * @param string $action - The plugin action
+     * @return string The response html
+     */
+    public function view_page($action) {
+        if ($action == 'downloadgrades') {
+            return $this->download_grades();
+        } else if ($action == 'uploadgrades') {
+            return $this->upload_grades();
+        }
+
+        return '';
+    }
+
+    /**
+     * Return a list of the grading actions performed by this plugin
+     * This plugin supports upload zip
+     *
+     * @return array The list of grading actions
+     */
+    public function get_grading_actions() {
+        return array('uploadgrades'=>get_string('uploadgrades', 'assignfeedback_offline'),
+                    'downloadgrades'=>get_string('downloadgrades', 'assignfeedback_offline'));
+    }
+
+    /**
+     * Override the default is_enabled to disable this plugin if advanced grading is active
+     *
+     * @return bool
+     */
+    public function is_enabled() {
+        $gradingmanager = get_grading_manager($this->assignment->get_context(), 'mod_assign', 'submissions');
+        $controller = $gradingmanager->get_active_controller();
+        $active = !empty($controller);
+
+        if ($active) {
+            return false;
+        }
+        return parent::is_enabled();
+    }
+
+    /**
+     * Do not show this plugin in the grading table or on the front page
+     *
+     * @return bool
+     */
+    public function has_user_summary() {
+        return false;
+    }
+
+}
diff --git a/mod/assign/feedback/offline/settings.php b/mod/assign/feedback/offline/settings.php
new file mode 100644 (file)
index 0000000..d408865
--- /dev/null
@@ -0,0 +1,28 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file defines the admin settings for this plugin
+ *
+ * @package   assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$settings->add(new admin_setting_configcheckbox('assignfeedback_offline/default',
+                   new lang_string('default', 'assignfeedback_offline'),
+                   new lang_string('default_help', 'assignfeedback_offline'), 0));
+
diff --git a/mod/assign/feedback/offline/uploadgradesform.php b/mod/assign/feedback/offline/uploadgradesform.php
new file mode 100644 (file)
index 0000000..e73e23d
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the forms to create and edit an instance of this module
+ *
+ * @package   assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
+
+require_once ($CFG->libdir.'/formslib.php');
+
+/**
+ * Upload modified grading worksheet
+ *
+ * @package   assignfeedback_offline
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class assignfeedback_offline_upload_grades_form extends moodleform {
+    /**
+     * Define this form - called by the parent constructor
+     */
+    function definition() {
+        global $COURSE, $USER;
+
+        $mform = $this->_form;
+        $params = $this->_customdata;
+
+        $mform->addElement('header', '', get_string('uploadgrades', 'assignfeedback_offline'));
+
+        $fileoptions = array('subdirs'=>0,
+                                'maxbytes'=>$COURSE->maxbytes,
+                                'accepted_types'=>'csv',
+                                'maxfiles'=>1,
+                                'return_types'=>FILE_INTERNAL);
+
+        $mform->addElement('filepicker', 'gradesfile', get_string('uploadafile'), null, $fileoptions);
+        $mform->addRule('gradesfile', get_string('uploadnofilefound'), 'required', null, 'client');
+        $mform->addHelpButton('gradesfile', 'gradesfile', 'assignfeedback_offline');
+
+        $mform->addElement('checkbox', 'ignoremodified', '', get_string('ignoremodified', 'assignfeedback_offline'));
+        $mform->addHelpButton('ignoremodified', 'ignoremodified', 'assignfeedback_offline');
+
+        $mform->addElement('hidden', 'id', $params['cm']);
+        $mform->addElement('hidden', 'action', 'viewpluginpage');
+        $mform->addElement('hidden', 'pluginaction', 'uploadgrades');
+        $mform->addElement('hidden', 'plugin', 'offline');
+        $mform->addElement('hidden', 'pluginsubtype', 'assignfeedback');
+        $this->add_action_buttons(true, get_string('uploadgrades', 'assignfeedback_offline'));
+
+    }
+
+}
+
diff --git a/mod/assign/feedback/offline/version.php b/mod/assign/feedback/offline/version.php
new file mode 100644 (file)
index 0000000..694daa2
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * This file contains the version information for the comments feedback plugin
+ *
+ * @package assignfeedback_file
+ * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$plugin->version   = 2012082300;
+$plugin->requires  = 2012082300;
+$plugin->component = 'assignfeedback_offline';
+
index 6677e6a..79eb41c 100644 (file)
@@ -123,4 +123,40 @@ abstract class assign_feedback_plugin extends assign_plugin {
         return false;
     }
 
+    /**
+     * Return a list of the batch grading operations supported by this plugin
+     *
+     * @return array - An array of action and description strings. The action will be passed to grading_batch_operation.
+     */
+    public function get_grading_batch_operations() {
+        return array();
+    }
+
+    /**
+     * Return a list of the grading actions supported by this plugin
+     *
+     * A grading action is a page that is not specific to a user but to the whole assignment.
+     * @return array - An array of action and description strings. The action will be passed to grading_action.
+     */
+    public function get_grading_actions() {
+        return array();
+    }
+
+    /**
+     * Show a grading action form
+     *
+     * @return string The page containing the form
+     */
+    public function grading_action($action) {
+        return '';
+    }
+
+    /**
+     * Show a batch operations form
+     *
+     * @return string The page containing the form
+     */
+    public function grading_batch_operation($action, $users) {
+        return '';
+    }
 }
index fa6a18e..38b73c8 100644 (file)
@@ -55,7 +55,16 @@ class mod_assign_grading_batch_operations_form extends moodleform {
         if ($instance['duedate']) {
             $options['grantextension'] = get_string('grantextension', 'assign');
         }
-        $mform->addElement('hidden', 'action', 'batchgradingoperation');
+
+        foreach ($instance['feedbackplugins'] as $plugin) {
+            if ($plugin->is_visible() && $plugin->is_enabled()) {
+                foreach ($plugin->get_grading_batch_operations() as $action => $description) {
+                    $options['plugingradingbatchoperation_' . $plugin->get_type() . '_' . $action] = $description;
+                }
+            }
+        }
+
+        $mform->addElement('hidden', 'action', 'gradingbatchoperation');
         $mform->addElement('hidden', 'id', $instance['cm']);
         $mform->addElement('hidden', 'selectedusers', '', array('class'=>'selectedusers'));
         $mform->addElement('hidden', 'returnaction', 'grading');
index 51bc69a..45785b1 100644 (file)
@@ -56,7 +56,12 @@ class assign_grading_table extends table_sql implements renderable {
     private $groupsubmissions = array();
     /** @var array $submissiongroups - A static cache of submission groups */
     private $submissiongroups = array();
-
+    /** @var string $plugingradingbatchoperations - List of plugin supported batch operations */
+    public $plugingradingbatchoperations = array();
+    /** @var array $plugincache - A cache of plugin lookups to match a column name to a plugin efficiently */
+    private $plugincache = array();
+    /** @var array $scale - A list of the keys and descriptions for the custom scale */
+    private $scale = null;
 
     /**
      * overridden constructor keeps a reference to the assignment class that is displaying this table
@@ -67,10 +72,21 @@ class assign_grading_table extends table_sql implements renderable {
      * @param int $rowoffset For showing a subsequent page of results
      * @param bool $quickgrading Is this table wrapped in a quickgrading form?
      */
-    function __construct(assign $assignment, $perpage, $filter, $rowoffset, $quickgrading) {
+    function __construct(assign $assignment, $perpage, $filter, $rowoffset, $quickgrading, $downloadfilename = null) {
         global $CFG, $PAGE, $DB;
         parent::__construct('mod_assign_grading');
         $this->assignment = $assignment;
+
+        foreach ($assignment->get_feedback_plugins() as $plugin) {
+            if ($plugin->is_visible() && $plugin->is_enabled()) {
+                foreach ($plugin->get_grading_batch_operations() as $action => $description) {
+                    if (empty($this->plugingradingbatchoperations)) {
+                        $this->plugingradingbatchoperations[$plugin->get_type()] = array();
+                    }
+                    $this->plugingradingbatchoperations[$plugin->get_type()][$action] = $description;
+                }
+            }
+        }
         $this->perpage = $perpage;
         $this->quickgrading = $quickgrading;
         $this->output = $PAGE->get_renderer('mod_assign');
@@ -131,23 +147,35 @@ class assign_grading_table extends table_sql implements renderable {
         }
         $this->set_sql($fields, $from, $where, $params);
 
+        if ($downloadfilename) {
+            $this->is_downloading('csv', $downloadfilename);
+        }
+
         $columns = array();
         $headers = array();
 
-        // Select
-        $columns[] = 'select';
-        $headers[] = get_string('select') . '<div class="selectall"><input type="checkbox" name="selectall" title="' . get_string('selectall') . '"/></div>';
-
-        // Edit links
+        // Select.
         if (!$this->is_downloading()) {
+            $columns[] = 'select';
+            $headers[] = get_string('select') .
+                        '<div class="selectall"><input type="checkbox" name="selectall" title="' .
+                        get_string('selectall') .
+                        '"/></div>';
+
+            // Edit links.
             $columns[] = 'edit';
             $headers[] = get_string('edit');
         }
 
         // User picture.
         if (!$this->assignment->is_blind_marking()) {
-            $columns[] = 'picture';
-            $headers[] = get_string('pictureofuser');
+            if (!$this->is_downloading()) {
+                $columns[] = 'picture';
+                $headers[] = get_string('pictureofuser');
+            } else {
+                $columns[] = 'recordid';
+                $headers[] = get_string('recordid', 'assign');
+            }
 
             // Fullname.
             $columns[] = 'fullname';
@@ -176,6 +204,16 @@ class assign_grading_table extends table_sql implements renderable {
         // Grade
         $columns[] = 'grade';
         $headers[] = get_string('grade');
+        if ($this->is_downloading()) {
+            if ($this->assignment->get_instance()->grade >= 0) {
+                $columns[] = 'grademax';
+                $headers[] = get_string('maxgrade', 'assign');
+            } else {
+                // This is a custom scale.
+                $columns[] = 'scale';
+                $headers[] = get_string('scale', 'assign');
+            }
+        }
 
         // Submission plugins
         if ($assignment->is_any_submission_plugin_enabled()) {
@@ -183,9 +221,22 @@ class assign_grading_table extends table_sql implements renderable {
             $headers[] = get_string('lastmodifiedsubmission', 'assign');
 
             foreach ($this->assignment->get_submission_plugins() as $plugin) {
-                if ($plugin->is_visible() && $plugin->is_enabled()) {
-                    $columns[] = 'assignsubmission_' . $plugin->get_type();
-                    $headers[] = $plugin->get_name();
+                if ($this->is_downloading()) {
+                    if ($plugin->is_visible() && $plugin->is_enabled()) {
+                        foreach ($plugin->get_editor_fields() as $field => $description) {
+                            $index = 'plugin' . count($this->plugincache);
+                            $this->plugincache[$index] = array($plugin, $field);
+                            $columns[] = $index;
+                            $headers[] = $plugin->get_name();
+                        }
+                    }
+                } else {
+                    if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
+                        $index = 'plugin' . count($this->plugincache);
+                        $this->plugincache[$index] = array($plugin);
+                        $columns[] = $index;
+                        $headers[] = $plugin->get_name();
+                    }
                 }
             }
         }
@@ -196,8 +247,19 @@ class assign_grading_table extends table_sql implements renderable {
 
         // Feedback plugins
         foreach ($this->assignment->get_feedback_plugins() as $plugin) {
-            if ($plugin->is_visible() && $plugin->is_enabled()) {
-                $columns[] = 'assignfeedback_' . $plugin->get_type();
+            if ($this->is_downloading()) {
+                if ($plugin->is_visible() && $plugin->is_enabled()) {
+                    foreach ($plugin->get_editor_fields() as $field => $description) {
+                        $index = 'plugin' . count($this->plugincache);
+                        $this->plugincache[$index] = array($plugin, $field);
+                        $columns[] = $index;
+                        $headers[] = $description;
+                    }
+                }
+            } else if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
+                $index = 'plugin' . count($this->plugincache);
+                $this->plugincache[$index] = array($plugin);
+                $columns[] = $index;
                 $headers[] = $plugin->get_name();
             }
         }
@@ -230,12 +292,12 @@ class assign_grading_table extends table_sql implements renderable {
         }
 
         foreach ($this->assignment->get_submission_plugins() as $plugin) {
-            if ($plugin->is_visible() && $plugin->is_enabled()) {
+            if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
                 $this->no_sorting('assignsubmission_' . $plugin->get_type());
             }
         }
         foreach ($this->assignment->get_feedback_plugins() as $plugin) {
-            if ($plugin->is_visible() && $plugin->is_enabled()) {
+            if ($plugin->is_visible() && $plugin->is_enabled() && $plugin->has_user_summary()) {
                 $this->no_sorting('assignfeedback_' . $plugin->get_type());
             }
         }
@@ -248,7 +310,7 @@ class assign_grading_table extends table_sql implements renderable {
      * @return string
      */
      function col_recordid(stdClass $row) {
-         return get_string('hiddenuser', 'assign', $this->assignment->get_uniqueid_for_user($row->userid));
+         return get_string('hiddenuser', 'assign') . $this->assignment->get_uniqueid_for_user($row->userid);
      }
 
 
@@ -271,6 +333,25 @@ class assign_grading_table extends table_sql implements renderable {
         return $this->perpage;
     }
 
+    /**
+     * For download only - list all the valid options for this custom scale.
+     *
+     * @param stdClass $row - The row of data
+     * @return string A list of valid options for the current scale
+     */
+    function col_scale($row) {
+        global $DB;
+
+        if (empty($this->scale)) {
+            $this->scale = $DB->get_record('scale', array('id'=>-($this->assignment->get_instance()->grade)));
+        }
+
+        if (!empty($this->scale->scale)) {
+            return implode("\n", explode(',', $this->scale->scale));
+        }
+        return '';
+    }
+
     /**
      * Display a grade with scales etc.
      *
@@ -282,10 +363,21 @@ class assign_grading_table extends table_sql implements renderable {
      */
     function display_grade($grade, $editable, $userid, $modified) {
         if ($this->is_downloading()) {
-            return $grade;
+            if ($this->assignment->get_instance()->grade >= 0) {
+                if ($grade == -1 || $grade === null) {
+                    return '';
+                }
+                return format_float($grade);
+            } else {
+                // This is a custom scale.
+                $scale = $this->assignment->display_grade($grade, false);
+                if ($scale == '-') {
+                    $scale = '';
+                }
+                return $scale;
+            }
         }
-        $o = $this->assignment->display_grade($grade, $editable, $userid, $modified);
-        return $o;
+        return $this->assignment->display_grade($grade, $editable, $userid, $modified);
     }
 
     /**
@@ -405,9 +497,13 @@ class assign_grading_table extends table_sql implements renderable {
      * @return string
      */
     function col_fullname($row) {
-        $courseid = $this->assignment->get_course()->id;
-        $link= new moodle_url('/user/view.php', array('id' =>$row->id, 'course'=>$courseid));
-        return $this->output->action_link($link, fullname($row));
+        if (!$this->is_downloading()) {
+            $courseid = $this->assignment->get_course()->id;
+            $link= new moodle_url('/user/view.php', array('id' =>$row->id, 'course'=>$courseid));
+            return $this->output->action_link($link, fullname($row));
+        } else {
+            return fullname($row);
+        }
     }
 
     /**
@@ -433,6 +529,16 @@ class assign_grading_table extends table_sql implements renderable {
         return false;
     }
 
+    /**
+     * Format a column of data for display
+     *
+     * @param stdClass $row
+     * @return string
+     */
+    function col_grademax(stdClass $row) {
+        return format_float($this->assignment->get_instance()->grade,2);
+    }
+
     /**
      * Format a column of data for display
      *
@@ -445,6 +551,7 @@ class assign_grading_table extends table_sql implements renderable {
         $link = '';
         $separator = '';
         $grade = '';
+        $gradingdisabled = $this->assignment->grading_disabled($row->id);
 
         if (!$this->is_downloading()) {
             $icon = $this->output->pix_icon('gradefeedback', get_string('grade'), 'mod_assign');
@@ -454,10 +561,8 @@ class assign_grading_table extends table_sql implements renderable {
             $link = $this->output->action_link($url, $icon);
             $separator = $this->output->spacer(array(), true);
         }
-        $gradingdisabled = $this->assignment->grading_disabled($row->id);
         $grade = $this->display_grade($row->grade, $this->quickgrading && !$gradingdisabled, $row->userid, $row->timemarked);
 
-        //return $grade . $separator . $link;
         return $link . $separator . $grade;
     }
 
@@ -551,6 +656,10 @@ class assign_grading_table extends table_sql implements renderable {
             }
         }
 
+        if ($this->is_downloading()) {
+            $o = strip_tags(str_replace('</div>', "\n", $o));
+        }
+
         return $o;
     }
 
@@ -700,19 +809,31 @@ class assign_grading_table extends table_sql implements renderable {
      * @return mixed string or NULL
      */
     function other_cols($colname, $row){
-        if (($pos = strpos($colname, 'assignsubmission_')) !== false) {
-            $plugin = $this->assignment->get_submission_plugin_by_type(substr($colname, strlen('assignsubmission_')));
+        $plugincache = $this->plugincache[$colname];
 
-            if ($plugin->is_visible() && $plugin->is_enabled()) {
+        $plugin = $plugincache[0];
+
+        $field = null;
+        if (isset($plugincache[1])) {
+            $field = $plugincache[1];
+        }
+
+        if ($plugin->is_visible() && $plugin->is_enabled()) {
+            if ($plugin->get_subtype() == 'assignsubmission') {
                 if ($this->assignment->get_instance()->teamsubmission) {
                     $group = false;
                     $submission = false;
                     $this->get_group_and_submission($row->id, $group, $submission);
                     if ($submission) {
+                        if (isset($field)) {
+                            return $plugin->get_editor_text($field, $submission->id);
+                        }
                         return $this->format_plugin_summary_with_link($plugin, $submission, 'grading', array());
                     }
                 } else if ($row->submissionid) {
-
+                    if (isset($field)) {
+                        return $plugin->get_editor_text($field, $row->submissionid);
+                    }
                     $submission = new stdClass();
                     $submission->id = $row->submissionid;
                     $submission->timecreated = $row->firstsubmission;
@@ -721,13 +842,12 @@ class assign_grading_table extends table_sql implements renderable {
                     $submission->userid = $row->userid;
                     return $this->format_plugin_summary_with_link($plugin, $submission, 'grading', array());
                 }
-            }
-            return '';
-        }
-        if (($pos = strpos($colname, 'feedback_')) !== false) {
-            $plugin = $this->assignment->get_feedback_plugin_by_type(substr($colname, strlen('assignfeedback_')));
-            if ($plugin->is_visible() && $plugin->is_enabled()) {
+            } else {
                 $grade = null;
+                if (isset($field)) {
+                    return $plugin->get_editor_text($field, $row->gradeid);
+                }
+
                 if ($row->gradeid) {
                     $grade = new stdClass();
                     $grade->id = $row->gradeid;
@@ -744,9 +864,8 @@ class assign_grading_table extends table_sql implements renderable {
                     return $this->format_plugin_summary_with_link($plugin, $grade, 'grading', array());
                 }
             }
-            return '';
         }
-        return NULL;
+        return '';
     }
 
     /**
index d4b4d70..53f3e17 100644 (file)
@@ -159,7 +159,7 @@ $string['gradingstatus'] = 'Grading status';
 $string['gradingstudentprogress'] = 'Grading student {$a->index} of {$a->count}';
 $string['gradingsummary'] = 'Grading summary';
 $string['hideshow'] = 'Hide/Show';
-$string['hiddenuser'] = 'Participant {$a}';
+$string['hiddenuser'] = 'Participant ';
 $string['instructionfiles'] = 'Instruction files';
 $string['invalidgradeforscale'] = 'The grade supplied was not valid for the current scale';
 $string['invalidfloatforgrade'] = 'The grade provided could not be understood: {$a}';
@@ -171,6 +171,7 @@ $string['locksubmissionforstudent'] = 'Prevent any more submissions for student:
 $string['locksubmissions'] = 'Lock submissions';
 $string['manageassignfeedbackplugins'] = 'Manage assignment feedback plugins';
 $string['manageassignsubmissionplugins'] = 'Manage assignment submission plugins';
+$string['maxgrade'] = 'Maximum Grade';
 $string['messageprovider:assign_notification'] = 'Assignment notifications';
 $string['modulename'] = 'Assignment';
 $string['modulename_help'] = 'The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback.
@@ -230,6 +231,7 @@ $string['reviewed'] = 'Reviewed';
 $string['savechanges'] = 'Save changes';
 $string['saveallquickgradingchanges'] = 'Save all quick grading changes';
 $string['savenext'] = 'Save and show next';
+$string['scale'] = 'Scale';
 $string['sendnotifications'] = 'Notify graders about submissions';
 $string['sendnotifications_help'] = 'If enabled, graders (usually teachers) receive a message whenever a student submits an assignment, early, on time and late. Message methods are configurable.';
 $string['selectlink'] = 'Select...';
index 567252f..74bff7d 100644 (file)
@@ -244,7 +244,7 @@ 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';
         $pluginlist = $this->$name;
@@ -305,6 +305,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
@@ -339,9 +347,8 @@ class assign {
             if ($this->process_submit_for_grading($mform)) {
                 $action = 'view';
             }
-            // save and show next button
-        } else if ($action == 'batchgradingoperation') {
-            $action = $this->process_batch_grading_operation();
+        } 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
@@ -411,6 +418,10 @@ class assign {
             $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();
         }
@@ -1354,7 +1365,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();
@@ -1560,6 +1571,34 @@ class assign {
         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
@@ -1783,7 +1822,8 @@ class assign {
 
 
                         foreach ($pluginfiles as $zipfilename => $file) {
-                            $filesforzipping[$prefix . $zipfilename] = $file;
+                            $prefixedfilename = $prefix . $plugin->get_subtype() . '_' . $plugin->get_type() . '_' . $zipfilename;
+                            $filesforzipping[$prefixedfilename] = $file;
                         }
                     }
                 }
@@ -1826,7 +1866,7 @@ class assign {
      * @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) {
@@ -1877,7 +1917,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) {
@@ -2129,6 +2169,18 @@ class assign {
             $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);
 
@@ -2158,7 +2210,8 @@ class assign {
         $gradingbatchoperationsform = 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),
+                                                                        'duedate'=>$this->get_instance()->duedate,
+                                                                        'feedbackplugins'=>$this->get_feedback_plugins()),
                                                                   'post', '',
                                                                   array('class'=>'gradingbatchoperationsform'));
 
@@ -2342,27 +2395,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 - 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,
-                                                                        'duedate'=>$this->get_instance()->duedate),
-                                                                  '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($prefix, $data->operation) == 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);
@@ -2370,8 +2464,6 @@ class assign {
                     $this->process_unlock($userid);
                 } else if ($data->operation == 'reverttodraft') {
                     $this->process_revert_to_draft($userid);
-                } else if ($data->operation == 'grantextension') {
-                    return 'grantextension';
                 }
             }
         }
@@ -3447,7 +3539,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);
@@ -3461,7 +3553,7 @@ class assign {
         if ($grade->locked) {
             $info .= get_string('submissionslocked', 'assign') . '. ';
         }
-        if ($grade->extensionduedate) {
+        if (!empty($grade->extensionduedate)) {
             $info .= get_string('userextensiondate', 'assign', userdate($grade->extensionduedate));
         }
         return $info;
@@ -4297,5 +4389,36 @@ class assign {
 
         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 $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;
+    }
+
 }
 
index a89f417..a1986b5 100644 (file)
@@ -76,7 +76,18 @@ M.mod_assign.init_grading_table = function(Y) {
                 alert(M.str.assign.nousersselected);
                 e.preventDefault();
             } else {
-                if (!confirm(eval('M.str.assign.batchoperationconfirm' + operation.get('value')))) {
+                action = operation.get('value');
+                prefix = 'plugingradingbatchoperation_';
+                if (action.indexOf(prefix) == 0) {
+                    pluginaction = action.substr(prefix.length);
+                    plugin = pluginaction.split('_')[0];
+                    action = pluginaction.substr(plugin.length + 1);
+                    confirmmessage = eval('M.str.assignfeedback_' + plugin + '.batchoperationconfirm' + action);
+                } else {
+                    confirmmessage = eval('M.str.assign.batchoperationconfirm' + operation.get('value'));
+                }
+                console.log(confirmmessage);
+                if (!confirm(confirmmessage)) {
                     e.preventDefault();
                 }
             }
@@ -139,4 +150,4 @@ M.mod_assign.init_grade_change = function(Y) {
             }
         });
     }
-};
\ No newline at end of file
+};
index 0a0d4fe..65d9bc0 100644 (file)
@@ -132,7 +132,7 @@ class mod_assign_renderer extends plugin_renderer_base {
         $o .= $this->output->container_start('usersummary');
         $o .= $this->output->box_start('boxaligncenter usersummarysection');
         if ($summary->blindmarking) {
-            $o .= get_string('hiddenuser', 'assign', $summary->uniqueidforuser);
+            $o .= get_string('hiddenuser', 'assign') . $summary->uniqueidforuser;
         } else {
             $o .= $this->output->user_picture($summary->user);
             $o .= $this->output->spacer(array('width'=>30));
@@ -343,7 +343,12 @@ class mod_assign_renderer extends plugin_renderer_base {
         }
 
         foreach ($status->feedbackplugins as $plugin) {
-            if ($plugin->is_enabled() && $plugin->is_visible() && !empty($status->grade) && !$plugin->is_empty($status->grade)) {
+            if ($plugin->is_enabled() &&
+                    $plugin->is_visible() &&
+                    $plugin->has_user_summary() &&
+                    !empty($status->grade) &&
+                    !$plugin->is_empty($status->grade)) {
+
                 $row = new html_table_row();
                 $cell1 = new html_table_cell($plugin->get_name());
                 $pluginfeedback = new assign_feedback_plugin_feedback($plugin, $status->grade, assign_feedback_plugin_feedback::SUMMARY, $status->coursemoduleid, $status->returnaction, $status->returnparams);
@@ -558,7 +563,11 @@ class mod_assign_renderer extends plugin_renderer_base {
             $t->data[] = $row;
 
             foreach ($status->submissionplugins as $plugin) {
-                if ($plugin->is_enabled() && $plugin->is_visible() && !$plugin->is_empty($submission)) {
+                if ($plugin->is_enabled() &&
+                    $plugin->is_visible() &&
+                    $plugin->has_user_summary() &&
+                    !$plugin->is_empty($submission)) {
+
                     $row = new html_table_row();
                     $cell1 = new html_table_cell($plugin->get_name());
                     $pluginsubmission = new assign_submission_plugin_submission($plugin,
@@ -653,6 +662,7 @@ class mod_assign_renderer extends plugin_renderer_base {
     public function render_assign_grading_table(assign_grading_table $table) {
         $o = '';
         $o .= $this->output->box_start('boxaligncenter gradingtable');
+
         $this->page->requires->js_init_call('M.mod_assign.init_grading_table', array());
         $this->page->requires->string_for_js('nousersselected', 'assign');
         $this->page->requires->string_for_js('batchoperationconfirmgrantextension', 'assign');
@@ -660,6 +670,11 @@ class mod_assign_renderer extends plugin_renderer_base {
         $this->page->requires->string_for_js('batchoperationconfirmreverttodraft', 'assign');
         $this->page->requires->string_for_js('batchoperationconfirmunlock', 'assign');
         $this->page->requires->string_for_js('editaction', 'assign');
+        foreach ($table->plugingradingbatchoperations as $plugin => $operations) {
+            foreach ($operations as $operation => $description) {
+                $this->page->requires->string_for_js('batchoperationconfirm' . $operation, 'assignfeedback_' . $plugin);
+            }
+        }
         // need to get from prefs
         $o .= $this->flexible_table($table, $table->get_rows_per_page(), true);
         $o .= $this->output->box_end();
index 8cdfebb..29a1d5e 100644 (file)
@@ -176,5 +176,4 @@ class assign_submission_comments extends assign_submission_plugin {
         return parent::is_enabled();
     }
 
-
 }
index 1c5b757..a94cc85 100644 (file)
@@ -164,6 +164,15 @@ class assign_submission_onlinetext extends assign_submission_plugin {
 
     }
 
+    /**
+     * Return a list of the text fields that can be imported/exported by this plugin
+     *
+     * @return array An array of field names and descriptions. (name=>description, ...)
+     */
+    public function get_editor_fields() {
+        return array('onlinetext' => get_string('pluginname', 'assignsubmission_comments'));
+    }
+
     /**
      * Get the saved text content from the editor
      *
@@ -252,12 +261,12 @@ class assign_submission_onlinetext extends assign_submission_plugin {
 
             if (!$this->assignment->is_blind_marking()) {
                 $filename = str_replace('_', '', fullname($user)) . '_' .
-                            $this->assignment->get_uniqueid_for_user($userid) . '_' .
+                            $this->assignment->get_uniqueid_for_user($submission->userid) . '_' .
                             $this->get_name() . '_';
                 $prefix = clean_filename($filename);
             } else {
                 $filename = get_string('participant', 'assign') . '_' .
-                            $this->assignment->get_uniqueid_for_user($userid) . '_' .
+                            $this->assignment->get_uniqueid_for_user($submission->userid) . '_' .
                             $this->get_name() . '_';
                 $prefix = clean_filename($filename);
             }