Skip to content

Commit

Permalink
MDL-61537 assignfeedback_editpdf: Rotate PDF page
Browse files Browse the repository at this point in the history
Add page rotation feature.
  • Loading branch information
Nathan Nguyen authored and Nathan Nguyen committed Mar 15, 2019
1 parent ec81914 commit 9432553
Show file tree
Hide file tree
Showing 20 changed files with 885 additions and 26 deletions.
21 changes: 21 additions & 0 deletions mod/assign/feedback/editpdf/ajax.php
Expand Up @@ -225,5 +225,26 @@
$result = $result && page_editor::unrelease_drafts($grade->id);
echo json_encode($result);
die();
} else if ($action == 'rotatepage') {
require_capability('mod/assign:grade', $context);
$response = new stdClass();
$index = required_param('index', PARAM_INT);
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$rotateleft = required_param('rotateleft', PARAM_BOOL);
$filearea = document_services::PAGE_IMAGE_FILEAREA;
$pagefile = document_services::rotate_page($assignment, $userid, $attemptnumber, $index, $rotateleft);
$page = new stdClass();
$page->url = moodle_url::make_pluginfile_url($context->id, document_services::COMPONENT, $filearea,
$grade->id, '/', $pagefile->get_filename())->out();
if ($imageinfo = $pagefile->get_imageinfo()) {
$page->width = $imageinfo['width'];
$page->height = $imageinfo['height'];
} else {
$page->width = 0;
$page->height = 0;
}
$response = (object)['page' => $page];
echo json_encode($response);
die();
}

Expand Up @@ -48,19 +48,25 @@ protected function define_grade_subplugin_structure() {
$subpluginelementannotation = new backup_nested_element('annotation', null, array('gradeid', 'pageno', 'type', 'x', 'y', 'endx', 'endy', 'colour', 'path', 'draft'));
$subpluginelementcomments = new backup_nested_element('feedback_editpdf_comments');
$subpluginelementcomment = new backup_nested_element('comment', null, array('gradeid', 'pageno', 'x', 'y', 'width', 'rawtext', 'colour', 'draft'));
$subpluginelementrotation = new backup_nested_element('feedback_editpdf_rotation');
$subpluginelementpagerotation = new backup_nested_element('pagerotation', null,
array('gradeid', 'pageno', 'pathnamehash', 'isrotated', 'degree'));

// Connect XML elements into the tree.
$subplugin->add_child($subpluginwrapper);
$subpluginelementannotations->add_child($subpluginelementannotation);
$subpluginelementcomments->add_child($subpluginelementcomment);
$subpluginelementrotation->add_child($subpluginelementpagerotation);
$subpluginwrapper->add_child($subpluginelementfiles);
$subpluginwrapper->add_child($subpluginelementannotations);
$subpluginwrapper->add_child($subpluginelementcomments);
$subpluginwrapper->add_child($subpluginelementrotation);

// Set source to populate the data.
$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));
$subpluginelementpagerotation->set_source_table('assignfeedback_editpdf_rot', array('gradeid' => backup::VAR_PARENTID));
// 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');
Expand Down
Expand Up @@ -57,6 +57,11 @@ protected function define_grade_subplugin_structure() {
$elepath = $this->get_pathfor('/feedback_editpdf_annotations/annotation');
$paths[] = new restore_path_element($elename, $elepath);

// Rotation details.
$elename = $this->get_namefor('pagerotation');
$elepath = $this->get_pathfor('/feedback_editpdf_rotation/pagerotation');
$paths[] = new restore_path_element($elename, $elepath);

return $paths;
}

Expand Down Expand Up @@ -109,4 +114,15 @@ public function process_assignfeedback_editpdf_comment($data) {

}

/**
* Processes one /feedback_editpdf_rotation/pagerotation element
* @param mixed $data
*/
public function process_assignfeedback_editpdf_pagerotation($data) {
global $DB;
$data = (object)$data;
$oldgradeid = $data->gradeid;
$data->gradeid = $this->get_mappingid('grade', $oldgradeid);
$DB->insert_record('assignfeedback_editpdf_rot', $data);
}
}
172 changes: 165 additions & 7 deletions mod/assign/feedback/editpdf/classes/document_services.php
Expand Up @@ -38,6 +38,8 @@
*/
class document_services {

/** Compoment name */
const COMPONENT = "assignfeedback_editpdf";
/** File area for generated pdf */
const FINAL_PDF_FILEAREA = 'download';
/** File area for combined pdf */
Expand Down Expand Up @@ -263,7 +265,6 @@ public static function get_combined_document_for_attempt($assignment, $userid, $
$submission = $assignment->get_user_submission($userid, false, $attemptnumber);
}


$contextid = $assignment->get_context()->id;
$component = 'assignfeedback_editpdf';
$filearea = self::COMBINED_PDF_FILEAREA;
Expand Down Expand Up @@ -365,9 +366,10 @@ public static function page_number_for_attempt($assignment, $userid, $attemptnum
* @param int|\assign $assignment
* @param int $userid
* @param int $attemptnumber (-1 means latest attempt)
* @param bool $resetrotation check if need to reset page rotation information
* @return array(stored_file)
*/
protected static function generate_page_images_for_attempt($assignment, $userid, $attemptnumber) {
protected static function generate_page_images_for_attempt($assignment, $userid, $attemptnumber, $resetrotation = true) {
global $CFG;

require_once($CFG->libdir . '/pdflib.php');
Expand Down Expand Up @@ -416,13 +418,29 @@ protected static function generate_page_images_for_attempt($assignment, $userid,
for ($i = 0; $i < $pagecount; $i++) {
try {
$image = $pdf->get_image($i);
if (!$resetrotation) {
$pagerotation = page_editor::get_page_rotation($grade->id, $i);
$degree = !empty($pagerotation) ? $pagerotation->degree : 0;
if ($degree != 0) {
$filepath = $tmpdir . '/' . $image;
$imageresource = imagecreatefrompng($filepath);
$content = imagerotate($imageresource, $degree, 0);
imagepng($content, $filepath);
}
}
} catch (\moodle_exception $e) {
// We catch only moodle_exception here as other exceptions indicate issue with setup not the pdf.
$image = pdf::get_error_image($tmpdir, $i);
}
$record->filename = basename($image);
$files[$i] = $fs->create_file_from_pathname($record, $tmpdir . '/' . $image);
@unlink($tmpdir . '/' . $image);
// Set page rotation default value.
if (!empty($files[$i])) {
if ($resetrotation) {
page_editor::set_page_rotation($grade->id, $i, false, $files[$i]->get_pathnamehash());
}
}
}
$pdf->Close(); // PDF loaded and never saved/outputted needs to be closed.

Expand Down Expand Up @@ -490,6 +508,7 @@ public static function get_page_images_for_attempt($assignment, $userid, $attemp
$files = $fs->get_directory_files($contextid, $component, $filearea, $itemid, $filepath);

$pages = array();
$resetrotation = false;
if (!empty($files)) {
$first = reset($files);
$pagemodified = $first->get_timemodified();
Expand All @@ -513,11 +532,12 @@ public static function get_page_images_for_attempt($assignment, $userid, $attemp
$fs->delete_area_files($contextid, $component, $filearea, $itemid);
page_editor::delete_draft_content($itemid);
$files = array();
$resetrotation = true;
} else {

// Need to reorder the files following their name.
// because get_directory_files() return a different order than generate_page_images_for_attempt().
foreach($files as $file) {
foreach ($files as $file) {
// Extract the page number from the file name image_pageXXXX.png.
preg_match('/page([\d]+)\./', $file->get_filename(), $matches);
if (empty($matches) or !is_numeric($matches[1])) {
Expand All @@ -539,7 +559,7 @@ public static function get_page_images_for_attempt($assignment, $userid, $attemp
// 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);
$pages = self::generate_page_images_for_attempt($assignment, $userid, $attemptnumber, $resetrotation);
}

return $pages;
Expand Down Expand Up @@ -648,7 +668,20 @@ public static function generate_feedback_document($assignment, $userid, $attempt
$allcomments = array();

for ($i = 0; $i < $pagecount; $i++) {
$pdf->copy_page();
$pagerotation = page_editor::get_page_rotation($grade->id, $i);
$pagemargin = $pdf->getBreakMargin();
$autopagebreak = $pdf->getAutoPageBreak();
if (empty($pagerotation) || !$pagerotation->isrotated) {
$pdf->copy_page();
} else {
$rotatedimagefile = $fs->get_file_by_hash($pagerotation->pathnamehash);
if (empty($rotatedimagefile)) {
$pdf->copy_page();
} else {
$pdf->add_image_page($rotatedimagefile);
}
}

$comments = page_editor::get_comments($grade->id, $i, false);
$annotations = page_editor::get_annotations($grade->id, $i, false);

Expand All @@ -666,6 +699,8 @@ public static function generate_feedback_document($assignment, $userid, $attempt
$annotation->path,
$stamptmpdir);
}
$pdf->SetAutoPageBreak($autopagebreak, $pagemargin);
$pdf->setPageMark();
}

if (!empty($allcomments)) {
Expand All @@ -688,7 +723,6 @@ public static function generate_feedback_document($assignment, $userid, $attempt
$generatedpdf = $tmpdir . '/' . $filename;
$pdf->save_pdf($generatedpdf);


$record = new \stdClass();

$record->contextid = $assignment->get_context()->id;
Expand All @@ -698,7 +732,6 @@ public static function generate_feedback_document($assignment, $userid, $attempt
$record->filepath = '/';
$record->filename = $filename;


// Only keep one current version of the generated pdf.
$fs->delete_area_files($record->contextid, $record->component, $record->filearea, $record->itemid);

Expand Down Expand Up @@ -809,4 +842,129 @@ public static function delete_feedback_document($assignment, $userid, $attemptnu
return $fs->delete_area_files($contextid, $component, $filearea, $itemid);
}

/**
* Get All files in a File area
* @param int|\assign $assignment Assignment
* @param int $userid User ID
* @param int $attemptnumber Attempt Number
* @param string $filearea File Area
* @param string $filepath File Path
* @return array
*/
private static function get_files($assignment, $userid, $attemptnumber, $filearea, $filepath = '/') {
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$itemid = $grade->id;
$contextid = $assignment->get_context()->id;
$component = self::COMPONENT;
$fs = get_file_storage();
$files = $fs->get_directory_files($contextid, $component, $filearea, $itemid, $filepath);
return $files;
}

/**
* Save file.
* @param int|\assign $assignment Assignment
* @param int $userid User ID
* @param int $attemptnumber Attempt Number
* @param string $filearea File Area
* @param string $newfilepath File Path
* @param string $storedfilepath stored file path
* @return \stored_file
* @throws \file_exception
* @throws \stored_file_creation_exception
*/
private static function save_file($assignment, $userid, $attemptnumber, $filearea, $newfilepath, $storedfilepath = '/') {
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
$itemid = $grade->id;
$contextid = $assignment->get_context()->id;

$record = new \stdClass();
$record->contextid = $contextid;
$record->component = self::COMPONENT;
$record->filearea = $filearea;
$record->itemid = $itemid;
$record->filepath = $storedfilepath;
$record->filename = basename($newfilepath);

$fs = get_file_storage();

$oldfile = $fs->get_file($record->contextid, $record->component, $record->filearea,
$record->itemid, $record->filepath, $record->filename);

$newhash = sha1($newfilepath);

// Delete old file if exists.
if ($oldfile && $newhash !== $oldfile->get_contenthash()) {
$oldfile->delete();
}

return $fs->create_file_from_pathname($record, $newfilepath);
}

/**
* This function rotate a page, and mark the page as rotated.
* @param int|\assign $assignment Assignment
* @param int $userid User ID
* @param int $attemptnumber Attempt Number
* @param int $index Index of Current Page
* @param bool $rotateleft To determine whether the page is rotated left or right.
* @return null|\stored_file return rotated File
* @throws \coding_exception
* @throws \file_exception
* @throws \moodle_exception
* @throws \stored_file_creation_exception
*/
public static function rotate_page($assignment, $userid, $attemptnumber, $index, $rotateleft) {
$assignment = self::get_assignment_from_param($assignment);
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
// Check permission.
if (!$assignment->can_view_submission($userid)) {
print_error('nopermission');
}

$filearea = self::PAGE_IMAGE_FILEAREA;
$files = self::get_files($assignment, $userid, $attemptnumber, $filearea);
if (!empty($files)) {
foreach ($files as $file) {
preg_match('/' . pdf::IMAGE_PAGE . '([\d]+)\./', $file->get_filename(), $matches);
if (empty($matches) or !is_numeric($matches[1])) {
throw new \coding_exception("'" . $file->get_filename()
. "' file hasn't the expected format filename: image_pageXXXX.png.");
}
$pagenumber = (int)$matches[1];

if ($pagenumber == $index) {
$source = imagecreatefromstring($file->get_content());
$pagerotation = page_editor::get_page_rotation($grade->id, $index);
$degree = empty($pagerotation) ? 0 : $pagerotation->degree;
if ($rotateleft) {
$content = imagerotate($source, 90, 0);
$degree = ($degree + 90) % 360;
} else {
$content = imagerotate($source, -90, 0);
$degree = ($degree - 90) % 360;
}
$filename = $matches[0].'png';
$tmpdir = make_temp_directory(self::COMPONENT . '/' . self::PAGE_IMAGE_FILEAREA . '/'
. self::hash($assignment, $userid, $attemptnumber));
$tempfile = $tmpdir . '/' . time() . '_' . $filename;
imagepng($content, $tempfile);

$filearea = self::PAGE_IMAGE_FILEAREA;
$newfile = self::save_file($assignment, $userid, $attemptnumber, $filearea, $tempfile);

unlink($tempfile);
rmdir($tmpdir);
imagedestroy($source);
imagedestroy($content);
$file->delete();
if (!empty($newfile)) {
page_editor::set_page_rotation($grade->id, $pagenumber, true, $newfile->get_pathnamehash(), $degree);
}
return $newfile;
}
}
}
return null;
}
}
42 changes: 42 additions & 0 deletions mod/assign/feedback/editpdf/classes/page_editor.php
Expand Up @@ -396,4 +396,46 @@ public static function delete_draft_content($gradeid) {
$result = $result && $DB->delete_records('assignfeedback_editpdf_cmnt', $conditions);
return $result;
}

/**
* Set page rotation value.
* @param int $gradeid grade id.
* @param int $pageno page number.
* @param bool $isrotated whether the page is rotated or not.
* @param string $pathnamehash path name hash
* @param int $degree rotation degree.
* @throws \dml_exception
*/
public static function set_page_rotation($gradeid, $pageno, $isrotated, $pathnamehash, $degree = 0) {
global $DB;
$oldrecord = self::get_page_rotation($gradeid, $pageno);
if ($oldrecord == null) {
$record = new \stdClass();
$record->gradeid = $gradeid;
$record->pageno = $pageno;
$record->isrotated = $isrotated;
$record->pathnamehash = $pathnamehash;
$record->degree = $degree;
$DB->insert_record('assignfeedback_editpdf_rot', $record, false);
} else {
$oldrecord->isrotated = $isrotated;
$oldrecord->pathnamehash = $pathnamehash;
$oldrecord->degree = $degree;
$DB->update_record('assignfeedback_editpdf_rot', $oldrecord, false);
}
}

/**
* Get Page Rotation Value.
* @param int $gradeid grade id.
* @param int $pageno page number.
* @return mixed
* @throws \dml_exception
*/
public static function get_page_rotation($gradeid, $pageno) {
global $DB;
$result = $DB->get_record('assignfeedback_editpdf_rot', array('gradeid' => $gradeid, 'pageno' => $pageno));
return $result;
}

}

0 comments on commit 9432553

Please sign in to comment.