mirror of
https://github.com/moodle/moodle.git
synced 2025-08-08 02:16:41 +02:00
Merge branch 'MDL-45580-26' of git://github.com/FMCorz/moodle into MOODLE_26_STABLE
This commit is contained in:
commit
1b25b037dc
11 changed files with 156 additions and 23 deletions
|
@ -37,6 +37,7 @@ $action = optional_param('action', '', PARAM_ALPHANUM);
|
||||||
$assignmentid = required_param('assignmentid', PARAM_INT);
|
$assignmentid = required_param('assignmentid', PARAM_INT);
|
||||||
$userid = required_param('userid', PARAM_INT);
|
$userid = required_param('userid', PARAM_INT);
|
||||||
$attemptnumber = required_param('attemptnumber', 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);
|
$cm = \get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
|
||||||
$context = \context_module::instance($cm->id);
|
$context = \context_module::instance($cm->id);
|
||||||
|
@ -53,12 +54,19 @@ if ($action == 'loadallpages') {
|
||||||
$draft = true;
|
$draft = true;
|
||||||
if (!has_capability('mod/assign:grade', $context)) {
|
if (!has_capability('mod/assign:grade', $context)) {
|
||||||
$draft = false;
|
$draft = false;
|
||||||
|
$readonly = true; // A student always sees the readonly version.
|
||||||
require_capability('mod/assign:submit', $context);
|
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,
|
$pages = document_services::get_page_images_for_attempt($assignment,
|
||||||
$userid,
|
$userid,
|
||||||
$attemptnumber);
|
$attemptnumber,
|
||||||
|
$readonly);
|
||||||
|
|
||||||
$response = new stdClass();
|
$response = new stdClass();
|
||||||
$response->pagecount = count($pages);
|
$response->pagecount = count($pages);
|
||||||
|
@ -66,13 +74,19 @@ if ($action == 'loadallpages') {
|
||||||
|
|
||||||
$grade = $assignment->get_user_grade($userid, true);
|
$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) {
|
foreach ($pages as $id => $pagefile) {
|
||||||
$index = count($response->pages);
|
$index = count($response->pages);
|
||||||
$page = new stdClass();
|
$page = new stdClass();
|
||||||
$comments = page_editor::get_comments($grade->id, $index, $draft);
|
$comments = page_editor::get_comments($grade->id, $index, $draft);
|
||||||
$page->url = moodle_url::make_pluginfile_url($context->id,
|
$page->url = moodle_url::make_pluginfile_url($context->id,
|
||||||
'assignfeedback_editpdf',
|
'assignfeedback_editpdf',
|
||||||
document_services::PAGE_IMAGE_FILEAREA,
|
$filearea,
|
||||||
$grade->id,
|
$grade->id,
|
||||||
'/',
|
'/',
|
||||||
$pagefile->get_filename())->out();
|
$pagefile->get_filename())->out();
|
||||||
|
|
|
@ -49,6 +49,7 @@ try {
|
||||||
throw new coding_exception('grade not found');
|
throw new coding_exception('grade not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No need to handle the readonly files here, the should be already generated.
|
||||||
$component = 'assignfeedback_editpdf';
|
$component = 'assignfeedback_editpdf';
|
||||||
$filearea = document_services::PAGE_IMAGE_FILEAREA;
|
$filearea = document_services::PAGE_IMAGE_FILEAREA;
|
||||||
$filepath = '/';
|
$filepath = '/';
|
||||||
|
|
|
@ -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));
|
$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));
|
$subpluginelementannotation->set_source_table('assignfeedback_editpdf_annot', array('gradeid' => backup::VAR_PARENTID));
|
||||||
$subpluginelementcomment->set_source_table('assignfeedback_editpdf_cmnt', 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.
|
// 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', 'download', 'gradeid');
|
$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');
|
$subpluginelementfiles->annotate_files('assignfeedback_editpdf', 'stamps', 'gradeid');
|
||||||
return $subplugin;
|
return $subplugin;
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,10 @@ class restore_assignfeedback_editpdf_subplugin extends restore_subplugin {
|
||||||
$data = (object)$data;
|
$data = (object)$data;
|
||||||
|
|
||||||
// In this case the id is the old gradeid which will be mapped.
|
// 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);
|
$this->add_related_files('assignfeedback_editpdf', 'stamps', 'grade', null, $data->gradeid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,8 @@ class document_services {
|
||||||
const COMBINED_PDF_FILEAREA = 'combined';
|
const COMBINED_PDF_FILEAREA = 'combined';
|
||||||
/** File area for page images */
|
/** File area for page images */
|
||||||
const PAGE_IMAGE_FILEAREA = 'pages';
|
const PAGE_IMAGE_FILEAREA = 'pages';
|
||||||
|
/** File area for readonly page images */
|
||||||
|
const PAGE_IMAGE_READONLY_FILEAREA = 'readonlypages';
|
||||||
/** Filename for combined pdf */
|
/** Filename for combined pdf */
|
||||||
const COMBINED_PDF_FILENAME = 'combined.pdf';
|
const COMBINED_PDF_FILENAME = 'combined.pdf';
|
||||||
|
|
||||||
|
@ -268,9 +270,10 @@ class document_services {
|
||||||
* @param int|\assign $assignment
|
* @param int|\assign $assignment
|
||||||
* @param int $userid
|
* @param int $userid
|
||||||
* @param int $attemptnumber (-1 means latest attempt)
|
* @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
|
* @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;
|
global $CFG;
|
||||||
|
|
||||||
require_once($CFG->libdir . '/pdflib.php');
|
require_once($CFG->libdir . '/pdflib.php');
|
||||||
|
@ -281,6 +284,19 @@ class document_services {
|
||||||
\print_error('nopermission');
|
\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.
|
// Get a combined pdf file from all submitted pdf files.
|
||||||
$file = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
|
$file = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
|
||||||
if (!$file) {
|
if (!$file) {
|
||||||
|
@ -363,12 +379,25 @@ class document_services {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function returns a list of the page images from a pdf.
|
* 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|\assign $assignment
|
||||||
* @param int $userid
|
* @param int $userid
|
||||||
* @param int $attemptnumber (-1 means latest attempt)
|
* @param int $attemptnumber (-1 means latest attempt)
|
||||||
|
* @param bool $readonly If true, then we are requesting the readonly version.
|
||||||
* @return array(stored_file)
|
* @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);
|
$assignment = self::get_assignment_from_param($assignment);
|
||||||
|
|
||||||
|
@ -385,26 +414,39 @@ class document_services {
|
||||||
|
|
||||||
$contextid = $assignment->get_context()->id;
|
$contextid = $assignment->get_context()->id;
|
||||||
$component = 'assignfeedback_editpdf';
|
$component = 'assignfeedback_editpdf';
|
||||||
$filearea = self::PAGE_IMAGE_FILEAREA;
|
|
||||||
$itemid = $grade->id;
|
$itemid = $grade->id;
|
||||||
$filepath = '/';
|
$filepath = '/';
|
||||||
|
$filearea = self::PAGE_IMAGE_FILEAREA;
|
||||||
|
|
||||||
$fs = \get_file_storage();
|
$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);
|
$files = $fs->get_directory_files($contextid, $component, $filearea, $itemid, $filepath);
|
||||||
|
|
||||||
|
$pages = array();
|
||||||
if (!empty($files)) {
|
if (!empty($files)) {
|
||||||
$first = reset($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);
|
$fs->delete_area_files($contextid, $component, $filearea, $itemid);
|
||||||
// Image files are stale - regenerate them.
|
page_editor::delete_draft_content($itemid);
|
||||||
$files = array();
|
$files = array();
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Need to reorder the files following their name.
|
// Need to reorder the files following their name.
|
||||||
// because get_directory_files() return a different order than generate_page_images_for_attempt().
|
// because get_directory_files() return a different order than generate_page_images_for_attempt().
|
||||||
$orderedfiles = array();
|
|
||||||
foreach($files as $file) {
|
foreach($files as $file) {
|
||||||
// Extract the page number from the file name image_pageXXXX.png.
|
// Extract the page number from the file name image_pageXXXX.png.
|
||||||
preg_match('/page([\d]+)\./', $file->get_filename(), $matches);
|
preg_match('/page([\d]+)\./', $file->get_filename(), $matches);
|
||||||
|
@ -415,14 +457,26 @@ class document_services {
|
||||||
$pagenumber = (int)$matches[1];
|
$pagenumber = (int)$matches[1];
|
||||||
|
|
||||||
// Save the page in the ordered array.
|
// 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 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|\assign $assignment
|
||||||
* @param int $userid
|
* @param int $userid
|
||||||
* @param int $attemptnumber (-1 means latest attempt)
|
* @param int $attemptnumber (-1 means latest attempt)
|
||||||
|
@ -567,9 +626,41 @@ class document_services {
|
||||||
@unlink($combined);
|
@unlink($combined);
|
||||||
@rmdir($tmpdir);
|
@rmdir($tmpdir);
|
||||||
|
|
||||||
|
self::copy_pages_to_readonly_area($assignment, $grade);
|
||||||
|
|
||||||
return $file;
|
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).
|
* This function returns the generated pdf (if it exists).
|
||||||
* @param int|\assign $assignment
|
* @param int|\assign $assignment
|
||||||
|
|
|
@ -340,4 +340,21 @@ class page_editor {
|
||||||
|
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,8 @@ class assign_feedback_editpdf extends assign_feedback_plugin {
|
||||||
// Retrieve total number of pages.
|
// Retrieve total number of pages.
|
||||||
$pagetotal = document_services::page_number_for_attempt($this->assignment->get_instance()->id,
|
$pagetotal = document_services::page_number_for_attempt($this->assignment->get_instance()->id,
|
||||||
$userid,
|
$userid,
|
||||||
$attempt);
|
$attempt,
|
||||||
|
$readonly);
|
||||||
|
|
||||||
$widget = new assignfeedback_editpdf_widget($this->assignment->get_instance()->id,
|
$widget = new assignfeedback_editpdf_widget($this->assignment->get_instance()->id,
|
||||||
$userid,
|
$userid,
|
||||||
|
|
|
@ -3245,7 +3245,8 @@ EDITOR.prototype = {
|
||||||
action : 'loadallpages',
|
action : 'loadallpages',
|
||||||
userid : this.get('userid'),
|
userid : this.get('userid'),
|
||||||
attemptnumber : this.get('attemptnumber'),
|
attemptnumber : this.get('attemptnumber'),
|
||||||
assignmentid : this.get('assignmentid')
|
assignmentid : this.get('assignmentid'),
|
||||||
|
readonly : this.get('readonly') ? 1 : 0
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
success: function(tid, response) {
|
success: function(tid, response) {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -3245,7 +3245,8 @@ EDITOR.prototype = {
|
||||||
action : 'loadallpages',
|
action : 'loadallpages',
|
||||||
userid : this.get('userid'),
|
userid : this.get('userid'),
|
||||||
attemptnumber : this.get('attemptnumber'),
|
attemptnumber : this.get('attemptnumber'),
|
||||||
assignmentid : this.get('assignmentid')
|
assignmentid : this.get('assignmentid'),
|
||||||
|
readonly : this.get('readonly') ? 1 : 0
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
success: function(tid, response) {
|
success: function(tid, response) {
|
||||||
|
|
|
@ -337,7 +337,8 @@ EDITOR.prototype = {
|
||||||
action : 'loadallpages',
|
action : 'loadallpages',
|
||||||
userid : this.get('userid'),
|
userid : this.get('userid'),
|
||||||
attemptnumber : this.get('attemptnumber'),
|
attemptnumber : this.get('attemptnumber'),
|
||||||
assignmentid : this.get('assignmentid')
|
assignmentid : this.get('assignmentid'),
|
||||||
|
readonly : this.get('readonly') ? 1 : 0
|
||||||
},
|
},
|
||||||
on: {
|
on: {
|
||||||
success: function(tid, response) {
|
success: function(tid, response) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue