Commit 9432553b authored by Nathan Nguyen's avatar Nathan Nguyen Committed by Nathan Nguyen
Browse files

MDL-61537 assignfeedback_editpdf: Rotate PDF page

Add page rotation feature.
parent ec819146
......@@ -225,5 +225,26 @@ if ($action === 'pollconversions') {
$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();
}
......@@ -48,19 +48,25 @@ class backup_assignfeedback_editpdf_subplugin extends backup_subplugin {
$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');
......
......@@ -57,6 +57,11 @@ class restore_assignfeedback_editpdf_subplugin extends restore_subplugin {
$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;
}
......@@ -109,4 +114,15 @@ class restore_assignfeedback_editpdf_subplugin extends restore_subplugin {
}
/**
* 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);
}
}
......@@ -38,6 +38,8 @@ use DOMDocument;
*/
class document_services {
/** Compoment name */
const COMPONENT = "assignfeedback_editpdf";
/** File area for generated pdf */
const FINAL_PDF_FILEAREA = 'download';
/** File area for combined pdf */
......@@ -263,7 +265,6 @@ EOD;
$submission = $assignment->get_user_submission($userid, false, $attemptnumber);
}
$contextid = $assignment->get_context()->id;
$component = 'assignfeedback_editpdf';
$filearea = self::COMBINED_PDF_FILEAREA;
......@@ -365,9 +366,10 @@ EOD;
* @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');
......@@ -416,6 +418,16 @@ EOD;
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);
......@@ -423,6 +435,12 @@ EOD;
$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.
......@@ -490,6 +508,7 @@ EOD;
$files = $fs->get_directory_files($contextid, $component, $filearea, $itemid, $filepath);
$pages = array();
$resetrotation = false;
if (!empty($files)) {
$first = reset($files);
$pagemodified = $first->get_timemodified();
......@@ -513,11 +532,12 @@ EOD;
$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])) {
......@@ -539,7 +559,7 @@ EOD;
// 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;
......@@ -648,7 +668,20 @@ EOD;
$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);
......@@ -666,6 +699,8 @@ EOD;
$annotation->path,
$stamptmpdir);
}
$pdf->SetAutoPageBreak($autopagebreak, $pagemargin);
$pdf->setPageMark();
}
if (!empty($allcomments)) {
......@@ -688,7 +723,6 @@ EOD;
$generatedpdf = $tmpdir . '/' . $filename;
$pdf->save_pdf($generatedpdf);
$record = new \stdClass();
$record->contextid = $assignment->get_context()->id;
......@@ -698,7 +732,6 @@ EOD;
$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);
......@@ -809,4 +842,129 @@ EOD;
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;
}
}
......@@ -396,4 +396,46 @@ class page_editor {
$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;
}
}
......@@ -70,7 +70,8 @@ class pdf extends \FPDI {
const MIN_ANNOTATION_HEIGHT = 5;
/** Blank PDF file used during error. */
const BLANK_PDF = '/mod/assign/feedback/editpdf/fixtures/blank.pdf';
/** Page image file name prefix*/
const IMAGE_PAGE = 'image_page';
/**
* Get the name of the font to use in generated PDF files.
* If $CFG->pdfexportfont is set - use it, otherwise use "freesans" as this
......@@ -551,7 +552,7 @@ class pdf extends \FPDI {
throw new \coding_exception('The specified image output folder is not a valid folder');
}
$imagefile = $this->imagefolder.'/image_page' . $pageno . '.png';
$imagefile = $this->imagefolder . '/' . self::IMAGE_PAGE . $pageno . '.png';
$generate = true;
if (file_exists($imagefile)) {
if (filemtime($imagefile) > filemtime($this->filename)) {
......@@ -583,7 +584,7 @@ class pdf extends \FPDI {
}
}
return 'image_page'.$pageno.'.png';
return self::IMAGE_PAGE . $pageno . '.png';
}
/**
......@@ -689,7 +690,7 @@ class pdf extends \FPDI {
$pdf->set_image_folder($tmperrorimagefolder);
$image = $pdf->get_image(0);
$pdf->Close(); // PDF loaded and never saved/outputted needs to be closed.
$newimg = 'image_page' . $pageno . '.png';
$newimg = self::IMAGE_PAGE . $pageno . '.png';
copy($tmperrorimagefolder . '/' . $image, $errorimagefolder . '/' . $newimg);
return $newimg;
......@@ -736,7 +737,7 @@ class pdf extends \FPDI {
}
$testimagefolder = \make_temp_directory('assignfeedback_editpdf_test');
@unlink($testimagefolder.'/image_page0.png'); // Delete any previous test images.
unlink($testimagefolder . '/' . self::IMAGE_PAGE . '0.png'); // Delete any previous test images.
$pdf = new pdf();
$pdf->set_pdf($testfile);
......@@ -761,10 +762,49 @@ class pdf extends \FPDI {
require_once($CFG->libdir.'/filelib.php');
$testimagefolder = \make_temp_directory('assignfeedback_editpdf_test');
$testimage = $testimagefolder.'/image_page0.png';
$testimage = $testimagefolder . '/' . self::IMAGE_PAGE . '0.png';
send_file($testimage, basename($testimage), 0);
die();
}
/**
* This function add an image file to PDF page.
* @param \stored_file $imagestoredfile Image file to be added
*/
public function add_image_page($imagestoredfile) {
$imageinfo = $imagestoredfile->get_imageinfo();
$imagecontent = $imagestoredfile->get_content();
$this->currentpage++;
$template = $this->importPage($this->currentpage);
$size = $this->getTemplateSize($template);
if ($imageinfo["width"] > $imageinfo["height"]) {
if ($size['w'] < $size['h']) {
$temp = $size['w'];
$size['w'] = $size['h'];
$size['h'] = $temp;
}
$orientation = 'L';
} else if ($imageinfo["width"] < $imageinfo["height"]) {
if ($size['w'] > $size['h']) {
$temp = $size['w'];
$size['w'] = $size['h'];
$size['h'] = $temp;
}
$orientation = 'P';
} else {
$orientation = 'P';
}
$this->SetHeaderMargin(0);
$this->SetFooterMargin(0);
$this->SetMargins(0, 0, 0, true);
$this->setPrintFooter(false);
$this->setPrintHeader(false);
$this->AddPage($orientation, $size);
$this->SetAutoPageBreak(false, 0);
$this->Image('@' . $imagecontent, 0, 0, $size['w'], $size['h'],
'', '', '', false, null, '', false, false, 0);
}
}
......@@ -174,6 +174,7 @@ class provider implements
// Remove table entries.
$DB->delete_records_select('assignfeedback_editpdf_annot', "gradeid $sql", $params);
$DB->delete_records_select('assignfeedback_editpdf_cmnt', "gradeid $sql", $params);
$DB->delete_records_select('assignfeedback_editpdf_rot', "gradeid $sql", $params);
// Submission records in assignfeedback_editpdf_queue will be cleaned up in a scheduled task
}
}
......@@ -42,6 +42,8 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
private function get_shortcut($name) {
$shortcuts = array('navigate-previous-button' => 'j',
'rotateleft' => 'q',
'rotateright' => 'w',
'navigate-page-select' => 'k',
'navigate-next-button' => 'l',
'searchcomments' => 'h',
......@@ -161,6 +163,13 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
$navigation3 .= $this->render_toolbar_button('comment_expcol', 'expcolcomments', $this->get_shortcut('expcolcomments'));
$navigation3 = html_writer::div($navigation3, 'navigation-expcol', array('role' => 'navigation'));
$rotationtools = '';
if (!$widget->readonly) {
$rotationtools .= $this->render_toolbar_button('rotate_left', 'rotateleft', $this->get_shortcut('rotateleft'));
$rotationtools .= $this->render_toolbar_button('rotate_right', 'rotateright', $this->get_shortcut('rotateright'));
$rotationtools = html_writer::div($rotationtools, 'toolbar', array('role' => 'toolbar'));
}
$toolbargroup = '';
$clearfix = html_writer::div('', 'clearfix');
if (!$widget->readonly) {
......@@ -193,7 +202,7 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
$toolbar4 = html_writer::div($toolbar4, 'toolbar', array('role'=>'toolbar'));
// Add toolbars to toolbar_group in order of display, and float the toolbar_group right.
$toolbars = $toolbar1 . $toolbar2 . $toolbar3 . $toolbar4;
$toolbars = $rotationtools . $toolbar1 . $toolbar2 . $toolbar3 . $toolbar4;
$toolbargroup = html_writer::div($toolbars, 'toolbar_group', array('role' => 'toolbar_group'));
}
......
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/assign/feedback/editpdf/db" VERSION="20180925" COMMENT="XMLDB file for Moodle mod/assign/feedback/editpdf"
<XMLDB PATH="mod/assign/feedback/editpdf/db" VERSION="20190107" COMMENT="XMLDB file for Moodle mod/assign/feedback/editpdf"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd"
>
......@@ -71,5 +71,22 @@
<KEY NAME="submissionid-submissionattempt" TYPE="unique" FIELDS="submissionid, submissionattempt"/>
</KEYS>
</TABLE>
<TABLE NAME="assignfeedback_editpdf_rot" COMMENT="Stores rotation information of a page.">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="gradeid" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="pageno" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Page number"/>
<FIELD NAME="pathnamehash" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="File path hash of the rotated page"/>
<FIELD NAME="isrotated" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether the page is rotated or not"/>
<FIELD NAME="degree" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Rotation degree"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="gradeid" TYPE="foreign" FIELDS="gradeid" REFTABLE="assign_grades" REFFIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="gradeid_pageno" UNIQUE="true" FIELDS="gradeid, pageno"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>
\ No newline at end of file
</XMLDB>
......@@ -95,5 +95,33 @@ function xmldb_assignfeedback_editpdf_upgrade($oldversion) {
// Automatically generated Moodle v3.6.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2019010800) {
// Define table assignfeedback_editpdf_rot to be created.
$table = new xmldb_table('assignfeedback_editpdf_rot');
// Adding fields to table assignfeedback_editpdf_rot.
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('gradeid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('pageno', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
$table->add_field('pathnamehash', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null);
$table->add_field('isrotated', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0');
$table->add_field('degree', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
// Adding keys to table assignfeedback_editpdf_rot.
$table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']);
$table->add_key('gradeid', XMLDB_KEY_FOREIGN, ['gradeid'], 'assign_grades', ['id']);
// Adding indexes to table assignfeedback_editpdf_rot.
$table->add_index('gradeid_pageno', XMLDB_INDEX_UNIQUE, ['gradeid', 'pageno']);
// Conditionally launch create table for assignfeedback_editpdf_rot.