Merge branch 'MDL-27520_master' of git://github.com/markn86/moodle
authorDavid Monllao <davidm@moodle.com>
Wed, 17 Oct 2018 13:42:55 +0000 (15:42 +0200)
committerDavid Monllao <davidm@moodle.com>
Wed, 17 Oct 2018 13:42:55 +0000 (15:42 +0200)
22 files changed:
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_stepslib.php
grade/report/history/classes/output/tablelog.php
grade/report/user/lib.php
lib/filelib.php
lib/grade/constants.php
lib/grade/grade_grade.php
lib/grade/grade_item.php
lib/grade/grade_object.php
lib/gradelib.php
lib/upgrade.txt
mod/assign/feedback/comments/backup/moodle2/backup_assignfeedback_comments_subplugin.class.php
mod/assign/feedback/comments/backup/moodle2/restore_assignfeedback_comments_subplugin.class.php
mod/assign/feedback/comments/classes/privacy/provider.php
mod/assign/feedback/comments/lang/en/assignfeedback_comments.php
mod/assign/feedback/comments/lib.php [new file with mode: 0644]
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/comments/tests/privacy_test.php
mod/assign/feedbackplugin.php
mod/assign/locallib.php
mod/assign/tests/locallib_test.php
mod/assign/upgrade.txt

index b90490a..ef99fa6 100644 (file)
@@ -2418,6 +2418,9 @@ class backup_activity_grades_structure_step extends backup_structure_step {
     }
 
     protected function define_structure() {
+        global $CFG;
+
+        require_once($CFG->libdir . '/grade/constants.php');
 
         // To know if we are including userinfo
         $userinfo = $this->get_setting_value('userinfo');
@@ -2474,6 +2477,7 @@ class backup_activity_grades_structure_step extends backup_structure_step {
         // This only happens if we are including user info
         if ($userinfo) {
             $grade->set_source_table('grade_grades', array('itemid' => backup::VAR_PARENTID));
+            $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA, 'id');
         }
 
         $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
@@ -2510,6 +2514,9 @@ class backup_activity_grade_history_structure_step extends backup_structure_step
     }
 
     protected function define_structure() {
+        global $CFG;
+
+        require_once($CFG->libdir . '/grade/constants.php');
 
         // Settings to use.
         $userinfo = $this->get_setting_value('userinfo');
@@ -2537,6 +2544,7 @@ class backup_activity_grade_history_structure_step extends backup_structure_step
                                      JOIN {backup_ids_temp} bi ON ggh.itemid = bi.itemid
                                     WHERE bi.backupid = ?
                                       AND bi.itemname = 'grade_item'", array(backup::VAR_BACKUPID));
+            $grade->annotate_files(GRADE_FILE_COMPONENT, GRADE_HISTORY_FEEDBACK_FILEAREA, 'id');
         }
 
         // Annotations.
index 250aeb2..1e78ee3 100644 (file)
@@ -3705,6 +3705,10 @@ class restore_activity_grades_structure_step extends restore_structure_step {
     }
 
     protected function process_grade_grade($data) {
+        global $CFG;
+
+        require_once($CFG->libdir . '/grade/constants.php');
+
         $data = (object)($data);
         $olduserid = $data->userid;
         $oldid = $data->id;
@@ -3719,7 +3723,16 @@ class restore_activity_grades_structure_step extends restore_structure_step {
 
             $grade = new grade_grade($data, false);
             $grade->insert('restore');
-            $this->set_mapping('grade_grades', $oldid, $grade->id);
+
+            $this->set_mapping('grade_grades', $oldid, $grade->id, true);
+
+            $this->add_related_files(
+                GRADE_FILE_COMPONENT,
+                GRADE_FEEDBACK_FILEAREA,
+                'grade_grades',
+                null,
+                $oldid
+            );
         } else {
             debugging("Mapped user id not found for user id '{$olduserid}', grade item id '{$data->itemid}'");
         }
@@ -3794,9 +3807,12 @@ class restore_activity_grade_history_structure_step extends restore_structure_st
     }
 
     protected function process_grade_grade($data) {
-        global $DB;
+        global $CFG, $DB;
+
+        require_once($CFG->libdir . '/grade/constants.php');
 
         $data = (object) $data;
+        $oldhistoryid = $data->id;
         $olduserid = $data->userid;
         unset($data->id);
 
@@ -3807,13 +3823,23 @@ class restore_activity_grade_history_structure_step extends restore_structure_st
             $data->oldid = $this->get_mappingid('grade_grades', $data->oldid);
             $data->usermodified = $this->get_mappingid('user', $data->usermodified, null);
             $data->rawscaleid = $this->get_mappingid('scale', $data->rawscaleid);
-            $DB->insert_record('grade_grades_history', $data);
+
+            $newhistoryid = $DB->insert_record('grade_grades_history', $data);
+
+            $this->set_mapping('grade_grades_history', $oldhistoryid, $newhistoryid, true);
+
+            $this->add_related_files(
+                GRADE_FILE_COMPONENT,
+                GRADE_HISTORY_FEEDBACK_FILEAREA,
+                'grade_grades_history',
+                null,
+                $oldhistoryid
+            );
         } else {
             $message = "Mapped user id not found for user id '{$olduserid}', grade item id '{$data->itemid}'";
             $this->log($message, backup::LOG_DEBUG);
         }
     }
-
 }
 
 /**
index b6e7d1d..4d9c9fc 100644 (file)
@@ -314,7 +314,20 @@ class tablelog extends \table_sql implements \renderable {
         if ($this->is_downloading()) {
             return $history->feedback;
         } else {
-            return format_text($history->feedback, $history->feedbackformat, array('context' => $this->context));
+            // We need the activity context, not the course context.
+            $gradeitem = $this->gradeitems[$history->itemid];
+            $context = $gradeitem->get_context();
+
+            $feedback = file_rewrite_pluginfile_urls(
+                $history->feedback,
+                'pluginfile.php',
+                $context->id,
+                GRADE_FILE_COMPONENT,
+                GRADE_HISTORY_FEEDBACK_FILEAREA,
+                $history->id
+            );
+
+            return format_text($feedback, $history->feedbackformat, array('context' => $context));
         }
     }
 
index ea40100..dab9bb5 100644 (file)
@@ -685,16 +685,30 @@ class grade_report_user extends grade_report {
                     $gradeitemdata['feedback'] = '';
                     $gradeitemdata['feedbackformat'] = $grade_grade->feedbackformat;
 
+                    if ($grade_grade->feedback) {
+                        $grade_grade->feedback = file_rewrite_pluginfile_urls(
+                            $grade_grade->feedback,
+                            'pluginfile.php',
+                            $grade_grade->get_context()->id,
+                            GRADE_FILE_COMPONENT,
+                            GRADE_FEEDBACK_FILEAREA,
+                            $grade_grade->id
+                        );
+                    }
+
                     if ($grade_grade->overridden > 0 AND ($type == 'categoryitem' OR $type == 'courseitem')) {
                     $data['feedback']['class'] = $classfeedback.' feedbacktext';
-                        $data['feedback']['content'] = get_string('overridden', 'grades').': ' . format_text($grade_grade->feedback, $grade_grade->feedbackformat);
+                        $data['feedback']['content'] = get_string('overridden', 'grades').': ' .
+                            format_text($grade_grade->feedback, $grade_grade->feedbackformat,
+                                ['context' => $grade_grade->get_context()]);
                         $gradeitemdata['feedback'] = $grade_grade->feedback;
                     } else if (empty($grade_grade->feedback) or (!$this->canviewhidden and $grade_grade->is_hidden())) {
                         $data['feedback']['class'] = $classfeedback.' feedbacktext';
                         $data['feedback']['content'] = '&nbsp;';
                     } else {
                         $data['feedback']['class'] = $classfeedback.' feedbacktext';
-                        $data['feedback']['content'] = format_text($grade_grade->feedback, $grade_grade->feedbackformat);
+                        $data['feedback']['content'] = format_text($grade_grade->feedback, $grade_grade->feedbackformat,
+                            ['context' => $grade_grade->get_context()]);
                         $gradeitemdata['feedback'] = $grade_grade->feedback;
                     }
                     $data['feedback']['headers'] = "$header_cat $header_row feedback";
index ded335b..b096a5b 100644 (file)
@@ -4198,6 +4198,9 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null, $offlin
 
     // ========================================================================================================================
     } else if ($component === 'grade') {
+
+        require_once($CFG->libdir . '/grade/constants.php');
+
         if (($filearea === 'outcome' or $filearea === 'scale') and $context->contextlevel == CONTEXT_SYSTEM) {
             // Global gradebook files
             if ($CFG->forcelogin) {
@@ -4213,15 +4216,35 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null, $offlin
             \core\session\manager::write_close(); // Unlock session during file serving.
             send_stored_file($file, 60*60, 0, $forcedownload, $sendfileoptions);
 
-        } else if ($filearea === 'feedback' and $context->contextlevel == CONTEXT_COURSE) {
-            //TODO: nobody implemented this yet in grade edit form!!
-            send_file_not_found();
+        } else if ($filearea == GRADE_FEEDBACK_FILEAREA || $filearea == GRADE_HISTORY_FEEDBACK_FILEAREA) {
+            if ($context->contextlevel != CONTEXT_MODULE) {
+                send_file_not_found;
+            }
 
-            if ($CFG->forcelogin || $course->id != SITEID) {
-                require_login($course);
+            require_login($course, false);
+
+            $gradeid = (int) array_shift($args);
+            $filename = array_pop($args);
+            if ($filearea == GRADE_HISTORY_FEEDBACK_FILEAREA) {
+                $grade = $DB->get_record('grade_grades_history', ['id' => $gradeid]);
+            } else {
+                $grade = $DB->get_record('grade_grades', ['id' => $gradeid]);
             }
 
-            $fullpath = "/$context->id/$component/$filearea/".implode('/', $args);
+            if (!$grade) {
+                send_file_not_found();
+            }
+
+            $iscurrentuser = $USER->id == $grade->userid;
+
+            if (!$iscurrentuser) {
+                $coursecontext = context_course::instance($course->id);
+                if (!has_capability('moodle/grade:viewall', $coursecontext)) {
+                    send_file_not_found();
+                }
+            }
+
+            $fullpath = "/$context->id/$component/$filearea/$gradeid/$filename";
 
             if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
                 send_file_not_found();
index 662965e..039ed3b 100644 (file)
@@ -263,3 +263,18 @@ define('GRADE_MIN_MAX_FROM_GRADE_ITEM', 1);
  * GRADE_MIN_MAX_FROM_GRADE_GRADE - Get the grade min/max from the grade grade.
  */
 define('GRADE_MIN_MAX_FROM_GRADE_GRADE', 2);
+
+/**
+ * The component to store grade files.
+ */
+define('GRADE_FILE_COMPONENT', 'grade');
+
+/**
+ * The file area to store the associated grade_grades feedback files.
+ */
+define('GRADE_FEEDBACK_FILEAREA', 'feedback');
+
+/**
+ * The file area to store the associated grade_grades_history feedback files.
+ */
+define('GRADE_HISTORY_FEEDBACK_FILEAREA', 'historyfeedback');
index f2b30af..aef9f51 100644 (file)
@@ -172,6 +172,22 @@ class grade_grade extends grade_object {
      */
     public $aggregationweight = null;
 
+    /**
+     * Feedback files to copy.
+     *
+     * Example -
+     *
+     * [
+     *     'contextid' => 1,
+     *     'component' => 'mod_xyz',
+     *     'filearea' => 'mod_xyz_feedback',
+     *     'itemid' => 2
+     * ];
+     *
+     * @var array
+     */
+    public $feedbackfiles = [];
+
     /**
      * Returns array of grades for given grade_item+users
      *
@@ -1017,13 +1033,60 @@ class grade_grade extends grade_object {
      * @return bool success
      */
     public function update($source=null) {
-        $this->rawgrade    = grade_floatval($this->rawgrade);
-        $this->finalgrade  = grade_floatval($this->finalgrade);
+        $this->rawgrade = grade_floatval($this->rawgrade);
+        $this->finalgrade = grade_floatval($this->finalgrade);
         $this->rawgrademin = grade_floatval($this->rawgrademin);
         $this->rawgrademax = grade_floatval($this->rawgrademax);
         return parent::update($source);
     }
 
+
+    /**
+     * Handles adding feedback files in the gradebook.
+     *
+     * @param int|null $historyid
+     */
+    protected function add_feedback_files(int $historyid = null) {
+        global $CFG;
+
+        // We only support feedback files for modules atm.
+        if ($this->grade_item && $this->grade_item->is_external_item()) {
+            $context = $this->get_context();
+            $this->copy_feedback_files($context, GRADE_FEEDBACK_FILEAREA, $this->id);
+
+            if (empty($CFG->disablegradehistory) && $historyid) {
+                $this->copy_feedback_files($context, GRADE_HISTORY_FEEDBACK_FILEAREA, $historyid);
+            }
+        }
+
+        return $this->id;
+    }
+
+    /**
+     * Handles updating feedback files in the gradebook.
+     *
+     * @param int|null $historyid
+     */
+    protected function update_feedback_files(int $historyid = null){
+        global $CFG;
+
+        // We only support feedback files for modules atm.
+        if ($this->grade_item && $this->grade_item->is_external_item()) {
+            $context = $this->get_context();
+
+            $fs = new file_storage();
+            $fs->delete_area_files($context->id, GRADE_FILE_COMPONENT, GRADE_FEEDBACK_FILEAREA, $this->id);
+
+            $this->copy_feedback_files($context, GRADE_FEEDBACK_FILEAREA, $this->id);
+
+            if (empty($CFG->disablegradehistory) && $historyid) {
+                $this->copy_feedback_files($context, GRADE_HISTORY_FEEDBACK_FILEAREA, $historyid);
+            }
+        }
+
+        return true;
+    }
+
     /**
      * Deletes the grade_grade instance from the database.
      *
@@ -1121,4 +1184,44 @@ class grade_grade extends grade_object {
         return array('status' => $this->get_aggregationstatus(),
                      'weight' => $this->get_aggregationweight());
     }
+
+    /**
+     * Handles copying feedback files to a specified gradebook file area.
+     *
+     * @param context $context
+     * @param string $filearea
+     * @param int $itemid
+     */
+    private function copy_feedback_files(context $context, string $filearea, int $itemid) {
+        if ($this->feedbackfiles) {
+            $filestocopycontextid = $this->feedbackfiles['contextid'];
+            $filestocopycomponent = $this->feedbackfiles['component'];
+            $filestocopyfilearea = $this->feedbackfiles['filearea'];
+            $filestocopyitemid = $this->feedbackfiles['itemid'];
+
+            $fs = new file_storage();
+            if ($filestocopy = $fs->get_area_files($filestocopycontextid, $filestocopycomponent, $filestocopyfilearea,
+                    $filestocopyitemid)) {
+                foreach ($filestocopy as $filetocopy) {
+                    $destination = [
+                        'contextid' => $context->id,
+                        'component' => GRADE_FILE_COMPONENT,
+                        'filearea' => $filearea,
+                        'itemid' => $itemid
+                    ];
+                    $fs->create_file_from_storedfile($destination, $filetocopy);
+                }
+            }
+        }
+    }
+
+    /**
+     * Determine the correct context for this grade_grade.
+     *
+     * @return context
+     */
+    public function get_context() {
+        $this->load_grade_item();
+        return $this->grade_item->get_context();
+    }
 }
index 81d8ef8..d3b12f6 100644 (file)
@@ -1864,9 +1864,19 @@ class grade_item extends grade_object {
      * @param int $dategraded A timestamp of when the student's work was graded
      * @param int $datesubmitted A timestamp of when the student's work was submitted
      * @param grade_grade $grade A grade object, useful for bulk upgrades
+     * @param array $feedbackfiles An array identifying the location of files we want to copy to the gradebook feedback area.
+     *        Example -
+     *        [
+     *            'contextid' => 1,
+     *            'component' => 'mod_xyz',
+     *            'filearea' => 'mod_xyz_feedback',
+     *            'itemid' => 2
+     *        ];
      * @return bool success
      */
-    public function update_raw_grade($userid, $rawgrade=false, $source=NULL, $feedback=false, $feedbackformat=FORMAT_MOODLE, $usermodified=null, $dategraded=null, $datesubmitted=null, $grade=null) {
+    public function update_raw_grade($userid, $rawgrade = false, $source = null, $feedback = false,
+            $feedbackformat = FORMAT_MOODLE, $usermodified = null, $dategraded = null, $datesubmitted=null,
+            $grade = null, array $feedbackfiles = []) {
         global $USER;
 
         $result = true;
@@ -1929,6 +1939,7 @@ class grade_item extends grade_object {
         if ($feedback !== false and !$grade->is_overridden()) {
             $grade->feedback       = $feedback;
             $grade->feedbackformat = $feedbackformat;
+            $grade->feedbackfiles  = $feedbackfiles;
         }
 
         // update final grade if possible
@@ -2469,4 +2480,19 @@ class grade_item extends grade_object {
             \availability_grade\callbacks::grade_item_changed($this->courseid);
         }
     }
+
+    /**
+     * Helper function to get the accurate context for this grade column.
+     *
+     * @return context
+     */
+    public function get_context() {
+        if ($this->itemtype == 'mod') {
+            $cm = get_fast_modinfo($this->courseid)->instances[$this->itemmodule][$this->iteminstance];
+            $context = \context_module::instance($cm->id);
+        } else {
+            $context = \context_course::instance($this->courseid);
+        }
+        return $context;
+    }
 }
index 654228e..33e907e 100644 (file)
@@ -252,6 +252,7 @@ abstract class grade_object {
 
         $DB->update_record($this->table, $data);
 
+        $historyid = null;
         if (empty($CFG->disablegradehistory)) {
             unset($data->timecreated);
             $data->action       = GRADE_HISTORY_UPDATE;
@@ -259,10 +260,13 @@ abstract class grade_object {
             $data->source       = $source;
             $data->timemodified = time();
             $data->loggeduser   = $USER->id;
-            $DB->insert_record($this->table.'_history', $data);
+            $historyid = $DB->insert_record($this->table.'_history', $data);
         }
 
         $this->notify_changed(false);
+
+        $this->update_feedback_files($historyid);
+
         return true;
     }
 
@@ -346,6 +350,7 @@ abstract class grade_object {
 
         $data = $this->get_record_data();
 
+        $historyid = null;
         if (empty($CFG->disablegradehistory)) {
             unset($data->timecreated);
             $data->action       = GRADE_HISTORY_INSERT;
@@ -353,10 +358,13 @@ abstract class grade_object {
             $data->source       = $source;
             $data->timemodified = time();
             $data->loggeduser   = $USER->id;
-            $DB->insert_record($this->table.'_history', $data);
+            $historyid = $DB->insert_record($this->table.'_history', $data);
         }
 
         $this->notify_changed(false);
+
+        $this->add_feedback_files($historyid);
+
         return $this->id;
     }
 
@@ -411,6 +419,22 @@ abstract class grade_object {
     protected function notify_changed($deleted) {
     }
 
+    /**
+     * Handles adding feedback files in the gradebook.
+     *
+     * @param int|null $historyid
+     */
+    protected function add_feedback_files(int $historyid = null) {
+    }
+
+    /**
+     * Handles updating feedback files in the gradebook.
+     *
+     * @param int|null $historyid
+     */
+    protected function update_feedback_files(int $historyid = null) {
+    }
+
     /**
      * Returns the current hidden state of this grade_item
      *
index 9e37c42..a9a6829 100644 (file)
@@ -252,6 +252,7 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
         $rawgrade       = false;
         $feedback       = false;
         $feedbackformat = FORMAT_MOODLE;
+        $feedbackfiles = [];
         $usermodified   = $USER->id;
         $datesubmitted  = null;
         $dategraded     = null;
@@ -268,6 +269,10 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
             $feedbackformat = $grade['feedbackformat'];
         }
 
+        if (array_key_exists('feedbackfiles', $grade)) {
+            $feedbackfiles = $grade['feedbackfiles'];
+        }
+
         if (array_key_exists('usermodified', $grade)) {
             $usermodified = $grade['usermodified'];
         }
@@ -281,7 +286,8 @@ function grade_update($source, $courseid, $itemtype, $itemmodule, $iteminstance,
         }
 
         // update or insert the grade
-        if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified, $dategraded, $datesubmitted, $grade_grade)) {
+        if (!$grade_item->update_raw_grade($userid, $rawgrade, $source, $feedback, $feedbackformat, $usermodified,
+                $dategraded, $datesubmitted, $grade_grade, $feedbackfiles)) {
             $failed = true;
         }
     }
@@ -537,7 +543,17 @@ function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $use
                         if (is_null($grade->feedback)) {
                             $grade->str_feedback = '';
                         } else {
-                            $grade->str_feedback = format_text($grade->feedback, $grade->feedbackformat);
+                            $feedback = file_rewrite_pluginfile_urls(
+                                $grade->feedback,
+                                'pluginfile.php',
+                                $grade_grades[$userid]->get_context()->id,
+                                GRADE_FILE_COMPONENT,
+                                GRADE_FEEDBACK_FILEAREA,
+                                $grade_grades[$userid]->id
+                            );
+
+                            $grade->str_feedback = format_text($feedback, $grade->feedbackformat,
+                                ['context' => $grade_grades[$userid]->get_context()]);
                         }
 
                         $item->grades[$userid] = $grade;
index 6561f65..9d26a5d 100644 (file)
@@ -138,6 +138,16 @@ the groupid field.
 * The event message_deleted has been changed, it no longer records the value of the 'useridto' due to
   the introduction of group messaging. Please, if you have any observers or are triggering this event
   in your code you will have to make some changes!
+* The gradebook now supports the ability to accept files as feedback. This can be achieved by adding
+  'feedbackfiles' to the $grades parameter passed to grade_update().
+    For example -
+        $grades['feedbackfiles'] = [
+            'contextid' => 1,
+            'component' => 'mod_xyz',
+            'filearea' => 'mod_xyz_feedback',
+            'itemid' => 2
+        ];
+  These files will be then copied to the gradebook file area.
 
 === 3.5 ===
 
index d885b34..5d314bd 100644 (file)
@@ -56,6 +56,12 @@ class backup_assignfeedback_comments_subplugin extends backup_subplugin {
         $subpluginelement->set_source_table('assignfeedback_comments',
                                             array('grade' => backup::VAR_PARENTID));
 
+        $subpluginelement->annotate_files(
+            'assignfeedback_comments',
+            'feedback',
+            'grade'
+        );
+
         return $subplugin;
     }
 }
index 9b95aed..1df4099 100644 (file)
@@ -71,5 +71,13 @@ class restore_assignfeedback_comments_subplugin extends restore_subplugin {
         $data->grade = $this->get_mappingid('grade', $data->grade);
 
         $DB->insert_record('assignfeedback_comments', $data);
+
+        $this->add_related_files(
+            'assignfeedback_comments',
+            'feedback',
+            'grade',
+            null,
+            $oldgradeid
+        );
     }
 }
index ac9aa7f..9302526 100644 (file)
@@ -58,6 +58,8 @@ class provider implements metadataprovider, assignfeedback_provider {
             'commenttext' => 'privacy:metadata:commentpurpose'
         ];
         $collection->add_database_table('assignfeedback_comments', $data, 'privacy:metadata:tablesummary');
+        $collection->link_subsystem('core_files', 'privacy:metadata:filepurpose');
+
         return $collection;
     }
 
@@ -91,13 +93,29 @@ class provider implements metadataprovider, assignfeedback_provider {
         // Get that comment information and jam it into that exporter.
         $assign = $exportdata->get_assign();
         $plugin = $assign->get_plugin_by_type('assignfeedback', 'comments');
-        $comments = $plugin->get_feedback_comments($exportdata->get_pluginobject()->id);
+        $gradeid = $exportdata->get_pluginobject()->id;
+        $comments = $plugin->get_feedback_comments($gradeid);
         if ($comments && !empty($comments->commenttext)) {
-            $data = (object)['commenttext' => format_text($comments->commenttext, $comments->commentformat,
-                    ['context' => $exportdata->get_context()])];
-            writer::with_context($exportdata->get_context())
-                    ->export_data(array_merge($exportdata->get_subcontext(),
-                            [get_string('privacy:commentpath', 'assignfeedback_comments')]), $data);
+            $comments->commenttext = writer::with_context($assign->get_context())->rewrite_pluginfile_urls(
+                [],
+                ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+                ASSIGNFEEDBACK_COMMENTS_FILEAREA,
+                $gradeid,
+                $comments->commenttext
+            );
+
+            $currentpath = array_merge(
+                $exportdata->get_subcontext(),
+                [get_string('privacy:commentpath', 'assignfeedback_comments')]
+            );
+            $data = (object)
+            [
+                'commenttext' => format_text($comments->commenttext, $comments->commentformat,
+                    ['context' => $exportdata->get_context()])
+            ];
+            writer::with_context($exportdata->get_context())->export_data($currentpath, $data);
+            writer::with_context($exportdata->get_context())->export_area_files($currentpath,
+                ASSIGNFEEDBACK_COMMENTS_COMPONENT, ASSIGNFEEDBACK_COMMENTS_FILEAREA, $gradeid);
         }
     }
 
@@ -108,6 +126,10 @@ class provider implements metadataprovider, assignfeedback_provider {
      */
     public static function delete_feedback_for_context(assign_plugin_request_data $requestdata) {
         $assign = $requestdata->get_assign();
+        $fs = get_file_storage();
+        $fs->delete_area_files($requestdata->get_context()->id, ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA);
+
         $plugin = $assign->get_plugin_by_type('assignfeedback', 'comments');
         $plugin->delete_instance();
     }
@@ -119,6 +141,11 @@ class provider implements metadataprovider, assignfeedback_provider {
      */
     public static function delete_feedback_for_grade(assign_plugin_request_data $requestdata) {
         global $DB;
+
+        $fs = new \file_storage();
+        $fs->delete_area_files($requestdata->get_context()->id, ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA, $requestdata->get_pluginobject()->id);
+
         $DB->delete_records('assignfeedback_comments', ['assignment' => $requestdata->get_assign()->get_instance()->id,
                 'grade' => $requestdata->get_pluginobject()->id]);
     }
index b8c7551..ee4c540 100644 (file)
@@ -30,6 +30,7 @@ $string['pluginname'] = 'Feedback comments';
 $string['privacy:commentpath'] = 'Feedback comments';
 $string['privacy:metadata:assignmentid'] = 'Assignment ID';
 $string['privacy:metadata:commentpurpose'] = 'The comment text.';
+$string['privacy:metadata:filepurpose'] = 'Feedback files from the teacher for the student.';
 $string['privacy:metadata:gradepurpose'] = 'The grade ID associated with the comment.';
 $string['privacy:metadata:tablesummary'] = 'This stores comments made by the graders as feedback for the student on their submission.';
 $string['commentinline'] = 'Comment inline';
diff --git a/mod/assign/feedback/comments/lib.php b/mod/assign/feedback/comments/lib.php
new file mode 100644 (file)
index 0000000..1939422
--- /dev/null
@@ -0,0 +1,82 @@
+<?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 moodle hooks for the comments feedback plugin
+ *
+ * @package   assignfeedback_comments
+ * @copyright 2018 Mark Nelson <markn@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Serves assignment comment feedback files.
+ *
+ * @param mixed $course course or id of the course
+ * @param mixed $cm course module or id of the course module
+ * @param context $context
+ * @param string $filearea
+ * @param array $args
+ * @param bool $forcedownload
+ * @param array $options - List of options affecting file serving.
+ * @return bool false if file not found, does not return if found - just send the file
+ */
+function assignfeedback_comments_pluginfile(
+        $course,
+        $cm,
+        context $context,
+        $filearea,
+        $args,
+        $forcedownload,
+        array $options = []) {
+    global $CFG, $DB;
+
+    require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+    if ($context->contextlevel != CONTEXT_MODULE) {
+        return false;
+    }
+
+    require_login($course, false, $cm);
+    $itemid = (int)array_shift($args);
+    $record = $DB->get_record('assign_grades', array('id' => $itemid), 'userid,assignment', MUST_EXIST);
+    $userid = $record->userid;
+
+    $assign = new assign($context, $cm, $course);
+    $instance = $assign->get_instance();
+
+    if ($instance->id != $record->assignment) {
+        return false;
+    }
+
+    if (!$assign->can_view_submission($userid)) {
+        return false;
+    }
+
+    $relativepath = implode('/', $args);
+
+    $fullpath = "/{$context->id}/assignfeedback_comments/$filearea/$itemid/$relativepath";
+
+    $fs = get_file_storage();
+
+    if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
+        return false;
+    }
+
+    // Download MUST be forced - security!
+    send_stored_file($file, 0, 0, true, $options);
+}
index e6c0f4a..4940063 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+// File component for feedback comments.
+define('ASSIGNFEEDBACK_COMMENTS_COMPONENT', 'assignfeedback_comments');
+
+// File area for feedback comments.
+define('ASSIGNFEEDBACK_COMMENTS_FILEAREA', 'feedback');
+
 /**
  * Library class for comment feedback plugin extending feedback plugin base class.
  *
@@ -116,7 +122,15 @@ class assign_feedback_comments extends assign_feedback_plugin {
             }
         }
 
-        if ($commenttext == $data->assignfeedbackcomments_editor['text']) {
+        $formtext = $data->assignfeedbackcomments_editor['text'];
+
+        // Need to convert the form text to use @@PLUGINFILE@@ and format it so we can compare it with what is stored in the DB.
+        if (isset($data->assignfeedbackcomments_editor['itemid'])) {
+            $formtext = file_rewrite_urls_to_pluginfile($formtext, $data->assignfeedbackcomments_editor['itemid']);
+            $formtext = format_text($formtext, FORMAT_HTML);
+        }
+
+        if ($commenttext == $formtext) {
             return false;
         } else {
             return true;
@@ -254,18 +268,47 @@ class assign_feedback_comments extends assign_feedback_plugin {
      *
      * @param stdClass $submission
      * @param stdClass $data - Form data to be filled with the converted submission text and format.
+     * @param stdClass|null $grade
      * @return boolean - True if feedback text was set.
      */
-    protected function convert_submission_text_to_feedback($submission, $data) {
+    protected function convert_submission_text_to_feedback($submission, $data, $grade) {
+        global $DB;
+
         $format = false;
         $text = '';
 
         foreach ($this->assignment->get_submission_plugins() as $plugin) {
             $fields = $plugin->get_editor_fields();
             if ($plugin->is_enabled() && $plugin->is_visible() && !$plugin->is_empty($submission) && !empty($fields)) {
+                $user = $DB->get_record('user', ['id' => $submission->userid]);
+                // Copy the files to the feedback area.
+                if ($files = $plugin->get_files($submission, $user)) {
+                    $fs = get_file_storage();
+                    $component = 'assignfeedback_comments';
+                    $filearea = ASSIGNFEEDBACK_COMMENTS_FILEAREA;
+                    $itemid = $grade->id;
+                    $fieldupdates = [
+                        'component' => $component,
+                        'filearea' => $filearea,
+                        'itemid' => $itemid
+                    ];
+                    foreach ($files as $file) {
+                        if ($file instanceof stored_file) {
+                            // Before we create it, check that it doesn't already exist.
+                            if (!$fs->file_exists(
+                                    $file->get_contextid(),
+                                    $component,
+                                    $filearea,
+                                    $itemid,
+                                    $file->get_filepath(),
+                                    $file->get_filename())) {
+                                $fs->create_file_from_storedfile($fieldupdates, $file);
+                            }
+                        }
+                    }
+                }
                 foreach ($fields as $key => $description) {
-                    $rawtext = strip_pluginfile_content($plugin->get_editor_text($key, $submission->id));
-
+                    $rawtext = clean_text($plugin->get_editor_text($key, $submission->id));
                     $newformat = $plugin->get_editor_format($key, $submission->id);
 
                     if ($format !== false && $newformat != $format) {
@@ -282,8 +325,8 @@ class assign_feedback_comments extends assign_feedback_plugin {
         if ($format === false) {
             $format = FORMAT_HTML;
         }
-        $data->assignfeedbackcomments_editor['text'] = $text;
-        $data->assignfeedbackcomments_editor['format'] = $format;
+        $data->assignfeedbackcomments = $text;
+        $data->assignfeedbackcommentsformat = $format;
 
         return true;
     }
@@ -306,16 +349,25 @@ class assign_feedback_comments extends assign_feedback_plugin {
         }
 
         if ($feedbackcomments && !empty($feedbackcomments->commenttext)) {
-            $data->assignfeedbackcomments_editor['text'] = $feedbackcomments->commenttext;
-            $data->assignfeedbackcomments_editor['format'] = $feedbackcomments->commentformat;
+            $data->assignfeedbackcomments = $feedbackcomments->commenttext;
+            $data->assignfeedbackcommentsformat = $feedbackcomments->commentformat;
         } else {
             // No feedback given yet - maybe we need to copy the text from the submission?
             if (!empty($commentinlinenabled) && $submission) {
-                $this->convert_submission_text_to_feedback($submission, $data);
+                $this->convert_submission_text_to_feedback($submission, $data, $grade);
             }
         }
 
-        $mform->addElement('editor', 'assignfeedbackcomments_editor', $this->get_name(), null, null);
+        file_prepare_standard_editor(
+            $data,
+            'assignfeedbackcomments',
+            $this->get_editor_options(),
+            $this->assignment->get_context(),
+            ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA,
+            $grade->id
+        );
+        $mform->addElement('editor', 'assignfeedbackcomments_editor', $this->get_name(), null, $this->get_editor_options());
 
         return true;
     }
@@ -329,15 +381,27 @@ class assign_feedback_comments extends assign_feedback_plugin {
      */
     public function save(stdClass $grade, stdClass $data) {
         global $DB;
+
+        // Save the files.
+        $data = file_postupdate_standard_editor(
+            $data,
+            'assignfeedbackcomments',
+            $this->get_editor_options(),
+            $this->assignment->get_context(),
+            ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA,
+            $grade->id
+        );
+
         $feedbackcomment = $this->get_feedback_comments($grade->id);
         if ($feedbackcomment) {
-            $feedbackcomment->commenttext = $data->assignfeedbackcomments_editor['text'];
-            $feedbackcomment->commentformat = $data->assignfeedbackcomments_editor['format'];
+            $feedbackcomment->commenttext = $data->assignfeedbackcomments;
+            $feedbackcomment->commentformat = $data->assignfeedbackcommentsformat;
             return $DB->update_record('assignfeedback_comments', $feedbackcomment);
         } else {
             $feedbackcomment = new stdClass();
-            $feedbackcomment->commenttext = $data->assignfeedbackcomments_editor['text'];
-            $feedbackcomment->commentformat = $data->assignfeedbackcomments_editor['format'];
+            $feedbackcomment->commenttext = $data->assignfeedbackcomments;
+            $feedbackcomment->commentformat = $data->assignfeedbackcommentsformat;
             $feedbackcomment->grade = $grade->id;
             $feedbackcomment->assignment = $this->assignment->get_instance()->id;
             return $DB->insert_record('assignfeedback_comments', $feedbackcomment) > 0;
@@ -354,12 +418,17 @@ class assign_feedback_comments extends assign_feedback_plugin {
     public function view_summary(stdClass $grade, & $showviewlink) {
         $feedbackcomments = $this->get_feedback_comments($grade->id);
         if ($feedbackcomments) {
-            $text = format_text($feedbackcomments->commenttext,
-                                $feedbackcomments->commentformat,
-                                array('context' => $this->assignment->get_context()));
-            $short = shorten_text($text, 140);
+            $text = $this->rewrite_feedback_comments_urls($feedbackcomments->commenttext, $grade->id);
+            $text = format_text(
+                $text,
+                $feedbackcomments->commentformat,
+                [
+                    'context' => $this->assignment->get_context()
+                ]
+            );
 
             // Show the view all link if the text has been shortened.
+            $short = shorten_text($text, 140);
             $showviewlink = $short != $text;
             return $short;
         }
@@ -375,9 +444,16 @@ class assign_feedback_comments extends assign_feedback_plugin {
     public function view(stdClass $grade) {
         $feedbackcomments = $this->get_feedback_comments($grade->id);
         if ($feedbackcomments) {
-            return format_text($feedbackcomments->commenttext,
-                               $feedbackcomments->commentformat,
-                               array('context' => $this->assignment->get_context()));
+            $text = $this->rewrite_feedback_comments_urls($feedbackcomments->commenttext, $grade->id);
+            $text = format_text(
+                $text,
+                $feedbackcomments->commentformat,
+                [
+                    'context' => $this->assignment->get_context()
+                ]
+            );
+
+            return $text;
         }
         return '';
     }
@@ -482,6 +558,21 @@ class assign_feedback_comments extends assign_feedback_plugin {
         return '';
     }
 
+    /**
+     * Return any files this plugin wishes to save to the gradebook.
+     *
+     * @param stdClass $grade The assign_grades object from the db
+     * @return array
+     */
+    public function files_for_gradebook(stdClass $grade) : array {
+        return [
+            'contextid' => $this->assignment->get_context()->id,
+            'component' => ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            'filearea' => ASSIGNFEEDBACK_COMMENTS_FILEAREA,
+            'itemid' => $grade->id
+        ];
+    }
+
     /**
      * The assignment has been deleted - cleanup
      *
@@ -526,4 +617,38 @@ class assign_feedback_comments extends assign_feedback_plugin {
     public function get_config_for_external() {
         return (array) $this->get_config();
     }
+
+    /**
+     * Convert encoded URLs in $text from the @@PLUGINFILE@@/... form to an actual URL.
+     *
+     * @param string $text the Text to check
+     * @param int $gradeid The grade ID which refers to the id in the gradebook
+     */
+    private function rewrite_feedback_comments_urls(string $text, int $gradeid) {
+        return file_rewrite_pluginfile_urls(
+            $text,
+            'pluginfile.php',
+            $this->assignment->get_context()->id,
+            ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA,
+            $gradeid
+        );
+    }
+
+    /**
+     * File format options.
+     *
+     * @return array
+     */
+    private function get_editor_options() {
+        global $COURSE;
+
+        return [
+            'subdirs' => 1,
+            'maxbytes' => $COURSE->maxbytes,
+            'accepted_types' => '*',
+            'context' => $this->assignment->get_context(),
+            'maxfiles' => EDITOR_UNLIMITED_FILES
+        ];
+    }
 }
index 3649452..276b9cf 100644 (file)
@@ -47,6 +47,8 @@ class assignfeedback_comments_privacy_testcase extends \mod_assign\tests\mod_ass
      * @return array   Feedback plugin object and the grade object.
      */
     protected function create_feedback($assign, $student, $teacher, $submissiontext, $feedbacktext) {
+        global $CFG;
+
         $submission = new \stdClass();
         $submission->assignment = $assign->get_instance()->id;
         $submission->userid = $student->id;
@@ -62,11 +64,33 @@ class assignfeedback_comments_privacy_testcase extends \mod_assign\tests\mod_ass
 
         $this->setUser($teacher);
 
+        $context = context_user::instance($teacher->id);
+
+        $draftitemid = file_get_unused_draft_itemid();
+        file_prepare_draft_area($draftitemid, $context->id, ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA, $grade->id);
+
+        $dummy = array(
+            'contextid' => $context->id,
+            'component' => 'user',
+            'filearea' => 'draft',
+            'itemid' => $draftitemid,
+            'filepath' => '/',
+            'filename' => 'feedback1.txt'
+        );
+
+        $fs = get_file_storage();
+        $fs->create_file_from_string($dummy, $feedbacktext);
+
+        $feedbacktext = $feedbacktext .
+            " <img src='{$CFG->wwwroot}/draftfile.php/{$context->id}/user/draft/{$draftitemid}/feedback1.txt.png>";
+
         $plugin = $assign->get_feedback_plugin_by_type('comments');
         $feedbackdata = new \stdClass();
         $feedbackdata->assignfeedbackcomments_editor = [
             'text' => $feedbacktext,
-            'format' => 1
+            'format' => FORMAT_HTML,
+            'itemid' => $draftitemid
         ];
 
         $plugin->save($grade, $feedbackdata);
@@ -109,12 +133,24 @@ class assignfeedback_comments_privacy_testcase extends \mod_assign\tests\mod_ass
         // The student should be able to see the teachers feedback.
         $exportdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $grade, [], $user1);
         \assignfeedback_comments\privacy\provider::export_feedback_user_data($exportdata);
-        $this->assertEquals($feedbacktext, $writer->get_data(['Feedback comments'])->commenttext);
+        $this->assertContains($feedbacktext, $writer->get_data(['Feedback comments'])->commenttext);
+
+        $filespath = [];
+        $filespath[] = 'Feedback comments';
+        $feedbackfile = $writer->get_files($filespath)['feedback1.txt'];
+
+        $this->assertInstanceOf('stored_file', $feedbackfile);
+        $this->assertEquals('feedback1.txt', $feedbackfile->get_filename());
 
         // The teacher should also be able to see the feedback that they provided.
         $exportdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $grade, [], $user2);
         \assignfeedback_comments\privacy\provider::export_feedback_user_data($exportdata);
-        $this->assertEquals($feedbacktext, $writer->get_data(['Feedback comments'])->commenttext);
+        $this->assertContains($feedbacktext, $writer->get_data(['Feedback comments'])->commenttext);
+
+        $feedbackfile = $writer->get_files($filespath)['feedback1.txt'];
+
+        $this->assertInstanceOf('stored_file', $feedbackfile);
+        $this->assertEquals('feedback1.txt', $feedbackfile->get_filename());
     }
 
     /**
@@ -147,6 +183,12 @@ class assignfeedback_comments_privacy_testcase extends \mod_assign\tests\mod_ass
         $feedbackcomments = $plugin1->get_feedback_comments($grade2->id);
         $this->assertNotEmpty($feedbackcomments);
 
+        $fs = new file_storage();
+        $files = $fs->get_area_files($assign->get_context()->id, ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA);
+        // 4 including directories.
+        $this->assertEquals(4, count($files));
+
         // Delete all comments for this context.
         $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign);
         assignfeedback_comments\privacy\provider::delete_feedback_for_context($requestdata);
@@ -156,6 +198,12 @@ class assignfeedback_comments_privacy_testcase extends \mod_assign\tests\mod_ass
         $this->assertEmpty($feedbackcomments);
         $feedbackcomments = $plugin1->get_feedback_comments($grade2->id);
         $this->assertEmpty($feedbackcomments);
+
+        $fs = new file_storage();
+        $files = $fs->get_area_files($assign->get_context()->id, ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA);
+        $this->assertEquals(0, count($files));
+
     }
 
     /**
@@ -188,6 +236,12 @@ class assignfeedback_comments_privacy_testcase extends \mod_assign\tests\mod_ass
         $feedbackcomments = $plugin1->get_feedback_comments($grade2->id);
         $this->assertNotEmpty($feedbackcomments);
 
+        $fs = new file_storage();
+        $files = $fs->get_area_files($assign->get_context()->id, ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA);
+        // 4 including directories.
+        $this->assertEquals(4, count($files));
+
         // Delete all comments for this grade object.
         $requestdata = new \mod_assign\privacy\assign_plugin_request_data($context, $assign, $grade1, [], $user1);
         assignfeedback_comments\privacy\provider::delete_feedback_for_grade($requestdata);
@@ -199,5 +253,18 @@ class assignfeedback_comments_privacy_testcase extends \mod_assign\tests\mod_ass
         // These comments should not.
         $feedbackcomments = $plugin1->get_feedback_comments($grade2->id);
         $this->assertNotEmpty($feedbackcomments);
+
+        $fs = new file_storage();
+        $files = $fs->get_area_files($assign->get_context()->id, ASSIGNFEEDBACK_COMMENTS_COMPONENT,
+            ASSIGNFEEDBACK_COMMENTS_FILEAREA);
+        // 2 files that were not deleted.
+        $this->assertEquals(2, count($files));
+
+        array_shift($files);
+        $file = array_shift($files);
+
+        $this->assertInstanceOf('stored_file', $file);
+        $this->assertEquals('feedback1.txt', $file->get_filename());
+        $this->assertEquals($grade2->id, $file->get_itemid());
     }
 }
index 340b435..9b959a3 100644 (file)
@@ -79,6 +79,28 @@ abstract class assign_feedback_plugin extends assign_plugin {
         return '';
     }
 
+    /**
+     * Return any files this plugin wishes to save to the gradebook.
+     *
+     * The array being returned should contain the necessary information to
+     * identify and copy the files.
+     *
+     * eg.
+     *
+     * [
+     *      'contextid' => $modulecontext->id,
+     *      'component' => ASSIGNFEEDBACK_XYZ_COMPONENT,
+     *      'filearea' => ASSIGNFEEDBACK_XYZ_FILEAREA,
+     *      'itemid' => $grade->id
+     * ]
+     *
+     * @param stdClass $grade The assign_grades object from the db
+     * @return array
+     */
+    public function files_for_gradebook(stdClass $grade) : array {
+        return [];
+    }
+
     /**
      * Override to indicate a plugin supports quickgrading.
      *
index bf1b717..d6ebc69 100644 (file)
@@ -5463,6 +5463,9 @@ class assign {
         if (isset($grade->feedbacktext)) {
             $gradebookgrade['feedback'] = $grade->feedbacktext;
         }
+        if (isset($grade->feedbackfiles)) {
+            $gradebookgrade['feedbackfiles'] = $grade->feedbackfiles;
+        }
 
         return $gradebookgrade;
     }
@@ -5506,6 +5509,7 @@ class assign {
             // Remove the grade (if it exists) from the gradebook as it is not 'final'.
             $grade->grade = -1;
             $grade->feedbacktext = '';
+            $grade->feebackfiles = [];
         }
 
         if ($submission != null) {
@@ -6610,6 +6614,7 @@ class assign {
                         // This is the feedback plugin chose to push comments to the gradebook.
                         $grade->feedbacktext = $plugin->text_for_gradebook($grade);
                         $grade->feedbackformat = $plugin->format_for_gradebook($grade);
+                        $grade->feedbackfiles = $plugin->files_for_gradebook($grade);
                     }
                 }
             }
@@ -6710,6 +6715,7 @@ class assign {
             if ($plugin && $plugin->is_enabled() && $plugin->is_visible()) {
                 $grade->feedbacktext = $plugin->text_for_gradebook($grade);
                 $grade->feedbackformat = $plugin->format_for_gradebook($grade);
+                $grade->feedbackfiles = $plugin->files_for_gradebook($grade);
             }
             $this->gradebook_item_update(null, $grade);
         }
@@ -7988,6 +7994,7 @@ class assign {
                     // This is the feedback plugin chose to push comments to the gradebook.
                     $grade->feedbacktext = $plugin->text_for_gradebook($grade);
                     $grade->feedbackformat = $plugin->format_for_gradebook($grade);
+                    $grade->feedbackfiles = $plugin->files_for_gradebook($grade);
                 }
             }
         }
@@ -8465,6 +8472,7 @@ class assign {
                     if ($grade) {
                         $gradebookgrade->feedback = $gradebookplugin->text_for_gradebook($grade);
                         $gradebookgrade->feedbackformat = $gradebookplugin->format_for_gradebook($grade);
+                        $gradebookgrade->feedbackfiles = $gradebookplugin->files_for_gradebook($grade);
                     }
                 }
                 $grades[$gradebookgrade->userid] = $gradebookgrade;
index 13e0bd6..b62649b 100644 (file)
@@ -3004,7 +3004,7 @@ class mod_assign_locallib_testcase extends advanced_testcase {
      * Testing for comment inline settings
      */
     public function test_feedback_comment_commentinline() {
-        global $CFG;
+        global $CFG, $USER;
 
         $this->resetAfterTest();
         $course = $this->getDataGenerator()->create_course();
@@ -3024,22 +3024,6 @@ Internal link 1:<img src='@@PLUGINFILE@@/logo-240x60.gif' alt='Moodle'/>
 Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\"/>
 Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\" alt=\"bananas\">Link text</a>
 Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
-";
-
-        // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
-        $filteredtext = "Hello!
-
-I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
-
-URL outside a tag: https://moodle.org/logo/logo-240x60.gif
-Plugin url outside a tag: @@PLUGINFILE@@/logo-240x60.gif
-
-External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
-External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
-Internal link 1:
-Internal link 2:
-Anchor link 1:Link text
-Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
 ";
 
         $this->setUser($teacher);
@@ -3068,6 +3052,27 @@ Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
         $formparams = array($assign, $data, $pagination);
         $mform = new mod_assign_grade_form(null, [$assign, $data, $pagination]);
 
+        // We need to get the URL these will be transformed to.
+        $context = context_user::instance($USER->id);
+        $itemid = $data->assignfeedbackcomments_editor['itemid'];
+        $url = $CFG->wwwroot . '/draftfile.php/' . $context->id . '/user/draft/' . $itemid;
+
+        // Note the internal images have been stripped and the html is purified (quotes fixed in this case).
+        $filteredtext = "Hello!
+
+I'm writing to you from the Moodle Majlis in Muscat, Oman, where we just had several days of Moodle community goodness.
+
+URL outside a tag: https://moodle.org/logo/logo-240x60.gif
+Plugin url outside a tag: $url/logo-240x60.gif
+
+External link 1:<img src=\"https://moodle.org/logo/logo-240x60.gif\" alt=\"Moodle\" />
+External link 2:<img alt=\"Moodle\" src=\"https://moodle.org/logo/logo-240x60.gif\" />
+Internal link 1:<img src=\"$url/logo-240x60.gif\" alt=\"Moodle\" />
+Internal link 2:<img alt=\"Moodle\" src=\"@@PLUGINFILE@@logo-240x60.gif\" />
+Anchor link 1:<a href=\"@@PLUGINFILE@@logo-240x60.gif\">Link text</a>
+Anchor link 2:<a title=\"bananas\" href=\"../logo-240x60.gif\">Link text</a>
+";
+
         $this->assertEquals($filteredtext, $data->assignfeedbackcomments_editor['text']);
     }
 
index dbcd9fd..1b1eebf 100644 (file)
@@ -5,6 +5,15 @@ This files describes API changes in the assign code.
   It encouraged poor unit test design and led to significant performance issues with unit tests. See MDL-55609 for
   further information.
 * The function can_grade() now has optional $user parameter.
+* Feedback plugins can now specify whether or not they want to attach files to the
+  feedback that is stored in the gradebook via the new method files_for_gradebook().
+  An example of what this method would return is -
+  [
+      'contextid' => $modulecontext->id,
+      'component' => ASSIGNFEEDBACK_XYZ_COMPONENT,
+      'filearea' => ASSIGNFEEDBACK_XYZ_FILEAREA,
+      'itemid' => $grade->id
+  ]
 
 === 3.5 ===
 * Functions assign:get_assign_grading_summary_renderable, assign:can_view_submission, assign:count_submissions_with_status,