mirror of
https://github.com/moodle/moodle.git
synced 2025-03-22 08:30:04 +01:00
Merge branch 'MDL-45580-master' of git://github.com/FMCorz/moodle
This commit is contained in:
commit
2dffa24f10
@ -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();
|
||||
|
@ -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 = '/';
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
File diff suppressed because one or more lines are too long
@ -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) {
|
||||
|
@ -346,7 +346,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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user