}
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');
// 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));
}
protected function define_structure() {
+ global $CFG;
+
+ require_once($CFG->libdir . '/grade/constants.php');
// Settings to use.
$userinfo = $this->get_setting_value('userinfo');
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.
}
protected function process_grade_grade($data) {
+ global $CFG;
+
+ require_once($CFG->libdir . '/grade/constants.php');
+
$data = (object)($data);
$olduserid = $data->userid;
$oldid = $data->id;
$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}'");
}
}
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);
$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);
}
}
-
}
/**
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));
}
}
$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'] = ' ';
} 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";
// ========================================================================================================================
} 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) {
\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();
* 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');
*/
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
*
* @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.
*
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();
+ }
}
* @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;
if ($feedback !== false and !$grade->is_overridden()) {
$grade->feedback = $feedback;
$grade->feedbackformat = $feedbackformat;
+ $grade->feedbackfiles = $feedbackfiles;
}
// update final grade if possible
\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;
+ }
}
$DB->update_record($this->table, $data);
+ $historyid = null;
if (empty($CFG->disablegradehistory)) {
unset($data->timecreated);
$data->action = GRADE_HISTORY_UPDATE;
$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;
}
$data = $this->get_record_data();
+ $historyid = null;
if (empty($CFG->disablegradehistory)) {
unset($data->timecreated);
$data->action = GRADE_HISTORY_INSERT;
$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;
}
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
*
$rawgrade = false;
$feedback = false;
$feedbackformat = FORMAT_MOODLE;
+ $feedbackfiles = [];
$usermodified = $USER->id;
$datesubmitted = null;
$dategraded = null;
$feedbackformat = $grade['feedbackformat'];
}
+ if (array_key_exists('feedbackfiles', $grade)) {
+ $feedbackfiles = $grade['feedbackfiles'];
+ }
+
if (array_key_exists('usermodified', $grade)) {
$usermodified = $grade['usermodified'];
}
}
// 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;
}
}
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;
* 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 ===
$subpluginelement->set_source_table('assignfeedback_comments',
array('grade' => backup::VAR_PARENTID));
+ $subpluginelement->annotate_files(
+ 'assignfeedback_comments',
+ 'feedback',
+ 'grade'
+ );
+
return $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
+ );
}
}
'commenttext' => 'privacy:metadata:commentpurpose'
];
$collection->add_database_table('assignfeedback_comments', $data, 'privacy:metadata:tablesummary');
+ $collection->link_subsystem('core_files', 'privacy:metadata:filepurpose');
+
return $collection;
}
// 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);
}
}
*/
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();
}
*/
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]);
}
$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';
--- /dev/null
+<?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);
+}
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.
*
}
}
- 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;
*
* @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) {
if ($format === false) {
$format = FORMAT_HTML;
}
- $data->assignfeedbackcomments_editor['text'] = $text;
- $data->assignfeedbackcomments_editor['format'] = $format;
+ $data->assignfeedbackcomments = $text;
+ $data->assignfeedbackcommentsformat = $format;
return true;
}
}
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;
}
*/
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;
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;
}
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 '';
}
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
*
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
+ ];
+ }
}
* @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;
$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);
// 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());
}
/**
$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);
$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));
+
}
/**
$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);
// 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());
}
}
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.
*
if (isset($grade->feedbacktext)) {
$gradebookgrade['feedback'] = $grade->feedbacktext;
}
+ if (isset($grade->feedbackfiles)) {
+ $gradebookgrade['feedbackfiles'] = $grade->feedbackfiles;
+ }
return $gradebookgrade;
}
// Remove the grade (if it exists) from the gradebook as it is not 'final'.
$grade->grade = -1;
$grade->feedbacktext = '';
+ $grade->feebackfiles = [];
}
if ($submission != null) {
// 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);
}
}
}
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);
}
// 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);
}
}
}
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;
* Testing for comment inline settings
*/
public function test_feedback_comment_commentinline() {
- global $CFG;
+ global $CFG, $USER;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
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);
$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']);
}
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,