diff --git a/mod/assign/feedback/editpdf/ajax.php b/mod/assign/feedback/editpdf/ajax.php
index 1451d5a7b44..8baf3ffb7f8 100644
--- a/mod/assign/feedback/editpdf/ajax.php
+++ b/mod/assign/feedback/editpdf/ajax.php
@@ -37,6 +37,7 @@ $action = optional_param('action', '', PARAM_ALPHANUM);
$assignmentid = required_param('assignmentid', PARAM_INT);
$userid = required_param('userid', PARAM_INT);
$attemptnumber = required_param('attemptnumber', PARAM_INT);
+$readonly = optional_param('readonly', false, PARAM_BOOL);
$cm = \get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
$context = \context_module::instance($cm->id);
@@ -53,12 +54,19 @@ if ($action == 'loadallpages') {
$draft = true;
if (!has_capability('mod/assign:grade', $context)) {
$draft = false;
+ $readonly = true; // A student always sees the readonly version.
require_capability('mod/assign:submit', $context);
}
+ // Whoever is viewing the readonly version should not use the drafts, but the actual annotations.
+ if ($readonly) {
+ $draft = false;
+ }
+
$pages = document_services::get_page_images_for_attempt($assignment,
$userid,
- $attemptnumber);
+ $attemptnumber,
+ $readonly);
$response = new stdClass();
$response->pagecount = count($pages);
@@ -66,13 +74,19 @@ if ($action == 'loadallpages') {
$grade = $assignment->get_user_grade($userid, true);
+ // The readonly files are stored in a different file area.
+ $filearea = document_services::PAGE_IMAGE_FILEAREA;
+ if ($readonly) {
+ $filearea = document_services::PAGE_IMAGE_READONLY_FILEAREA;
+ }
+
foreach ($pages as $id => $pagefile) {
$index = count($response->pages);
$page = new stdClass();
$comments = page_editor::get_comments($grade->id, $index, $draft);
$page->url = moodle_url::make_pluginfile_url($context->id,
'assignfeedback_editpdf',
- document_services::PAGE_IMAGE_FILEAREA,
+ $filearea,
$grade->id,
'/',
$pagefile->get_filename())->out();
diff --git a/mod/assign/feedback/editpdf/ajax_progress.php b/mod/assign/feedback/editpdf/ajax_progress.php
index 184a957550c..815484818cd 100644
--- a/mod/assign/feedback/editpdf/ajax_progress.php
+++ b/mod/assign/feedback/editpdf/ajax_progress.php
@@ -49,6 +49,7 @@ try {
throw new coding_exception('grade not found');
}
+ // No need to handle the readonly files here, the should be already generated.
$component = 'assignfeedback_editpdf';
$filearea = document_services::PAGE_IMAGE_FILEAREA;
$filepath = '/';
diff --git a/mod/assign/feedback/editpdf/backup/moodle2/backup_assignfeedback_editpdf_subplugin.class.php b/mod/assign/feedback/editpdf/backup/moodle2/backup_assignfeedback_editpdf_subplugin.class.php
index 546b8769ecb..4fe8cc66a0a 100644
--- a/mod/assign/feedback/editpdf/backup/moodle2/backup_assignfeedback_editpdf_subplugin.class.php
+++ b/mod/assign/feedback/editpdf/backup/moodle2/backup_assignfeedback_editpdf_subplugin.class.php
@@ -61,8 +61,11 @@ class backup_assignfeedback_editpdf_subplugin extends backup_subplugin {
$subpluginelementfiles->set_source_sql('SELECT id AS gradeid from {assign_grades} where id = :gradeid', array('gradeid' => backup::VAR_PARENTID));
$subpluginelementannotation->set_source_table('assignfeedback_editpdf_annot', array('gradeid' => backup::VAR_PARENTID));
$subpluginelementcomment->set_source_table('assignfeedback_editpdf_cmnt', array('gradeid' => backup::VAR_PARENTID));
- // We only need to backup the files in the final pdf area - all the others can be regenerated.
- $subpluginelementfiles->annotate_files('assignfeedback_editpdf', 'download', 'gradeid');
+ // We only need to backup the files in the final pdf area, and the readonly page images - the others can be regenerated.
+ $subpluginelementfiles->annotate_files('assignfeedback_editpdf',
+ \assignfeedback_editpdf\document_services::FINAL_PDF_FILEAREA, 'gradeid');
+ $subpluginelementfiles->annotate_files('assignfeedback_editpdf',
+ \assignfeedback_editpdf\document_services::PAGE_IMAGE_READONLY_FILEAREA, 'gradeid');
$subpluginelementfiles->annotate_files('assignfeedback_editpdf', 'stamps', 'gradeid');
return $subplugin;
}
diff --git a/mod/assign/feedback/editpdf/backup/moodle2/restore_assignfeedback_editpdf_subplugin.class.php b/mod/assign/feedback/editpdf/backup/moodle2/restore_assignfeedback_editpdf_subplugin.class.php
index cb7923ea237..c520e2b7dc1 100644
--- a/mod/assign/feedback/editpdf/backup/moodle2/restore_assignfeedback_editpdf_subplugin.class.php
+++ b/mod/assign/feedback/editpdf/backup/moodle2/restore_assignfeedback_editpdf_subplugin.class.php
@@ -68,7 +68,10 @@ class restore_assignfeedback_editpdf_subplugin extends restore_subplugin {
$data = (object)$data;
// In this case the id is the old gradeid which will be mapped.
- $this->add_related_files('assignfeedback_editpdf', 'download', 'grade', null, $data->gradeid);
+ $this->add_related_files('assignfeedback_editpdf',
+ \assignfeedback_editpdf\document_services::FINAL_PDF_FILEAREA, 'grade', null, $data->gradeid);
+ $this->add_related_files('assignfeedback_editpdf',
+ \assignfeedback_editpdf\document_services::PAGE_IMAGE_READONLY_FILEAREA, 'grade', null, $data->gradeid);
$this->add_related_files('assignfeedback_editpdf', 'stamps', 'grade', null, $data->gradeid);
}
diff --git a/mod/assign/feedback/editpdf/classes/document_services.php b/mod/assign/feedback/editpdf/classes/document_services.php
index 468b1032921..912bd8addff 100644
--- a/mod/assign/feedback/editpdf/classes/document_services.php
+++ b/mod/assign/feedback/editpdf/classes/document_services.php
@@ -42,6 +42,8 @@ class document_services {
const COMBINED_PDF_FILEAREA = 'combined';
/** File area for page images */
const PAGE_IMAGE_FILEAREA = 'pages';
+ /** File area for readonly page images */
+ const PAGE_IMAGE_READONLY_FILEAREA = 'readonlypages';
/** Filename for combined pdf */
const COMBINED_PDF_FILENAME = 'combined.pdf';
@@ -268,9 +270,10 @@ class document_services {
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
+ * @param bool $readonly When true we get the number of pages for the readonly version.
* @return int number of pages
*/
- public static function page_number_for_attempt($assignment, $userid, $attemptnumber) {
+ public static function page_number_for_attempt($assignment, $userid, $attemptnumber, $readonly = false) {
global $CFG;
require_once($CFG->libdir . '/pdflib.php');
@@ -281,6 +284,19 @@ class document_services {
\print_error('nopermission');
}
+ // When in readonly we can return the number of images in the DB because they should already exist,
+ // if for some reason they do not, then we proceed as for the normal version.
+ if ($readonly) {
+ $grade = $assignment->get_user_grade($userid, true, $attemptnumber);
+ $fs = get_file_storage();
+ $files = $fs->get_directory_files($assignment->get_context()->id, 'assignfeedback_editpdf',
+ self::PAGE_IMAGE_READONLY_FILEAREA, $grade->id, '/');
+ $pagecount = count($files);
+ if ($pagecount > 0) {
+ return $pagecount;
+ }
+ }
+
// Get a combined pdf file from all submitted pdf files.
$file = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
if (!$file) {
@@ -363,12 +379,25 @@ class document_services {
/**
* This function returns a list of the page images from a pdf.
+ *
+ * The readonly version is different than the normal one. The readonly version contains a copy
+ * of the pages in the state they were when the PDF was annotated, by doing so we prevent the
+ * the pages that are displayed to change as soon as the submission changes.
+ *
+ * Though there is an edge case, if the PDF was annotated before MDL-45580, then it is possible
+ * that we do not find any readonly version of the pages. In that case, we will get the normal
+ * pages and copy them to the readonly area. This ensures that the pages will remain in that
+ * state until the submission is updated. When the normal files do not exist, we throw an exception
+ * because the readonly pages should only ever be displayed after a teacher has annotated the PDF,
+ * they would not exist until they do.
+ *
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
+ * @param bool $readonly If true, then we are requesting the readonly version.
* @return array(stored_file)
*/
- public static function get_page_images_for_attempt($assignment, $userid, $attemptnumber) {
+ public static function get_page_images_for_attempt($assignment, $userid, $attemptnumber, $readonly = false) {
$assignment = self::get_assignment_from_param($assignment);
@@ -385,26 +414,39 @@ class document_services {
$contextid = $assignment->get_context()->id;
$component = 'assignfeedback_editpdf';
- $filearea = self::PAGE_IMAGE_FILEAREA;
$itemid = $grade->id;
$filepath = '/';
+ $filearea = self::PAGE_IMAGE_FILEAREA;
$fs = \get_file_storage();
+ // If we are after the readonly pages...
+ $copytoreadonly = false;
+ if ($readonly) {
+ $filearea = self::PAGE_IMAGE_READONLY_FILEAREA;
+ if ($fs->is_area_empty($contextid, $component, $filearea, $itemid)) {
+ // We have a problem here, we were supposed to find the files...
+ // let's fallback on the other area, and copy the files to the readonly area.
+ $copytoreadonly = true;
+ $filearea = self::PAGE_IMAGE_FILEAREA;
+ }
+ }
+
$files = $fs->get_directory_files($contextid, $component, $filearea, $itemid, $filepath);
+ $pages = array();
if (!empty($files)) {
$first = reset($files);
- if ($first->get_timemodified() < $submission->timemodified) {
-
+ if (!$readonly && $first->get_timemodified() < $submission->timemodified) {
+ // Image files are stale, we need to regenerate them, except in readonly mode.
+ // We also need to remove the draft annotations and comments associated with this attempt.
$fs->delete_area_files($contextid, $component, $filearea, $itemid);
- // Image files are stale - regenerate them.
+ page_editor::delete_draft_content($itemid);
$files = array();
} else {
// Need to reorder the files following their name.
// because get_directory_files() return a different order than generate_page_images_for_attempt().
- $orderedfiles = array();
foreach($files as $file) {
// Extract the page number from the file name image_pageXXXX.png.
preg_match('/page([\d]+)\./', $file->get_filename(), $matches);
@@ -415,14 +457,26 @@ class document_services {
$pagenumber = (int)$matches[1];
// Save the page in the ordered array.
- $orderedfiles[$pagenumber] = $file;
+ $pages[$pagenumber] = $file;
}
- ksort($orderedfiles);
+ ksort($pages);
- return $orderedfiles;
+ if ($copytoreadonly) {
+ self::copy_pages_to_readonly_area($assignment, $grade);
+ }
}
}
- return self::generate_page_images_for_attempt($assignment, $userid, $attemptnumber);
+
+ if (empty($pages)) {
+ if ($readonly) {
+ // This should never happen, there should be a version of the pages available
+ // whenever we are requesting the readonly version.
+ throw new \moodle_exception('Could not find readonly pages for grade ' . $grade->id);
+ }
+ $pages = self::generate_page_images_for_attempt($assignment, $userid, $attemptnumber);
+ }
+
+ return $pages;
}
/**
@@ -465,6 +519,11 @@ class document_services {
/**
* This function takes the combined pdf and embeds all the comments and annotations.
+ *
+ * This also moves the annotations and comments from drafts to not drafts. And it will
+ * copy all the images stored to the readonly area, so that they can be viewed online, and
+ * not be overwritten when a new submission is sent.
+ *
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
@@ -567,9 +626,41 @@ class document_services {
@unlink($combined);
@rmdir($tmpdir);
+ self::copy_pages_to_readonly_area($assignment, $grade);
+
return $file;
}
+ /**
+ * Copy the pages image to the readonly area.
+ *
+ * @param int|\assign $assignment The assignment.
+ * @param \stdClass $grade The grade record.
+ * @return void
+ */
+ public static function copy_pages_to_readonly_area($assignment, $grade) {
+ $fs = get_file_storage();
+ $assignment = self::get_assignment_from_param($assignment);
+ $contextid = $assignment->get_context()->id;
+ $component = 'assignfeedback_editpdf';
+ $itemid = $grade->id;
+
+ // Get all the pages.
+ $originalfiles = $fs->get_area_files($contextid, $component, self::PAGE_IMAGE_FILEAREA, $itemid);
+ if (empty($originalfiles)) {
+ // Nothing to do here...
+ return;
+ }
+
+ // Delete the old readonly files.
+ $fs->delete_area_files($contextid, $component, self::PAGE_IMAGE_READONLY_FILEAREA, $itemid);
+
+ // Do the copying.
+ foreach ($originalfiles as $originalfile) {
+ $fs->create_file_from_storedfile(array('filearea' => self::PAGE_IMAGE_READONLY_FILEAREA), $originalfile);
+ }
+ }
+
/**
* This function returns the generated pdf (if it exists).
* @param int|\assign $assignment
diff --git a/mod/assign/feedback/editpdf/classes/page_editor.php b/mod/assign/feedback/editpdf/classes/page_editor.php
index 1b1447ac0df..3dfa0716871 100644
--- a/mod/assign/feedback/editpdf/classes/page_editor.php
+++ b/mod/assign/feedback/editpdf/classes/page_editor.php
@@ -340,4 +340,21 @@ class page_editor {
return true;
}
+
+ /**
+ * Delete the draft annotations and comments.
+ *
+ * This is intended to be used when the version of the PDF has changed and the annotations
+ * might not be relevant any more, therefore we should delete them.
+ *
+ * @param int $gradeid The grade ID.
+ * @return bool
+ */
+ public static function delete_draft_content($gradeid) {
+ global $DB;
+ $conditions = array('gradeid' => $gradeid, 'draft' => 1);
+ $result = $DB->delete_records('assignfeedback_editpdf_annot', $conditions);
+ $result = $result && $DB->delete_records('assignfeedback_editpdf_cmnt', $conditions);
+ return $result;
+ }
}
diff --git a/mod/assign/feedback/editpdf/locallib.php b/mod/assign/feedback/editpdf/locallib.php
index f98bee9cd2e..be4a8a83bd6 100644
--- a/mod/assign/feedback/editpdf/locallib.php
+++ b/mod/assign/feedback/editpdf/locallib.php
@@ -137,7 +137,8 @@ class assign_feedback_editpdf extends assign_feedback_plugin {
// Retrieve total number of pages.
$pagetotal = document_services::page_number_for_attempt($this->assignment->get_instance()->id,
$userid,
- $attempt);
+ $attempt,
+ $readonly);
$widget = new assignfeedback_editpdf_widget($this->assignment->get_instance()->id,
$userid,
diff --git a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
index 05b1dc5f200..3f4859a2a37 100644
--- a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
+++ b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
@@ -3351,7 +3351,8 @@ EDITOR.prototype = {
action : 'loadallpages',
userid : this.get('userid'),
attemptnumber : this.get('attemptnumber'),
- assignmentid : this.get('assignmentid')
+ assignmentid : this.get('assignmentid'),
+ readonly : this.get('readonly') ? 1 : 0
},
on: {
success: function(tid, response) {
diff --git a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
index e03ddc03d0a..5e8c6bf3b2e 100644
--- a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
+++ b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
@@ -3,6 +3,6 @@ YUI.add("moodle-assignfeedback_editpdf-editor",function(e,t){var n=M.cfg.wwwroot
,this.endy)]),i=c[this.colour],i=i.replace("rgb","rgba"),i=i.replace(")",",0.5)"),n=this.editor.graphic.addShape({type:e.Rect,width:r.width,height:r.height,stroke:!1,fill:{color:i},x:r.x,y:r.y}),t.shapes.push(n),this.drawable=t,ANNOTATIONHIGHLIGHT.superclass.draw.apply(this)},draw_current_edit:function(t){var n=new M.assignfeedback_editpdf.drawable(this.editor),r,i,s;return i=new M.assignfeedback_editpdf.rect,i.bound([new M.assignfeedback_editpdf.point(t.start.x,t.start.y),new M.assignfeedback_editpdf.point(t.end.x,t.end.y)]),i.has_min_width()||i.set_min_width(),s=c[t.annotationcolour],s=s.replace("rgb","rgba"),s=s.replace(")",",0.5)"),r=this.editor.graphic.addShape({type:e.Rect,width:i.width,height:16,stroke:!1,fill:{color:s},x:i.x,y:t.start.y}),n.shapes.push(r),n},init_from_edit:function(e){var t=new M.assignfeedback_editpdf.rect;return t.bound([e.start,e.end]),this.gradeid=this.editor.get("gradeid"),this.pageno=this.editor.currentpage,this.x=t.x,this.y=e.start.y,this.endx=t.x+t.width,this.endy=e.start.y+16,this.colour=e.annotationcolour,this.page="",t.has_min_width()}}),M.assignfeedback_editpdf=M.assignfeedback_editpdf||{},M.assignfeedback_editpdf.annotationhighlight=ANNOTATIONHIGHLIGHT,ANNOTATIONSTAMP=function(e){ANNOTATIONSTAMP.superclass.constructor.apply(this,[e])},ANNOTATIONSTAMP.NAME="annotationstamp",ANNOTATIONSTAMP.ATTRS={},e.extend(ANNOTATIONSTAMP,M.assignfeedback_editpdf.annotation,{draw:function(){var t=new M.assignfeedback_editpdf.drawable(this.editor),n=e.one(o.DRAWINGREGION),r,i;return i=this.editor.get_window_coordinates(new M.assignfeedback_editpdf.point(this.x,this.y)),r=e.Node.create("