MDL-63349 assignfeedback_editpdf: Rotate submitted image automatically

This commit is contained in:
Nathan Nguyen 2019-03-27 10:21:05 +11:00
parent a31719f91a
commit 4a6edc5767
6 changed files with 166 additions and 3 deletions

View File

@ -1130,4 +1130,47 @@ class stored_file {
public function compare_to_string($content) {
return $this->get_contenthash() === file_storage::hash_from_string($content);
}
/**
* Generate a rotated image for this stored_file based on exif information.
*
* @return array|false False when a problem occurs, else the image data and image size.
* @since Moodle 3.8
*/
public function rotate_image() {
$content = $this->get_content();
$mimetype = $this->get_mimetype();
if ($mimetype === "image/jpeg" && function_exists("exif_read_data")) {
$exif = @exif_read_data("data://image/jpeg;base64," . base64_encode($content));
if (isset($exif['ExifImageWidth']) && isset($exif['ExifImageLength']) && isset($exif['Orientation'])) {
$rotation = [
3 => -180,
6 => -90,
8 => -270,
];
$orientation = $exif['Orientation'];
if ($orientation !== 1) {
$source = @imagecreatefromstring($content);
$data = @imagerotate($source, $rotation[$orientation], 0);
if (!empty($data)) {
if ($orientation == 1 || $orientation == 3) {
$size = [
'width' => $exif["ExifImageWidth"],
'height' => $exif["ExifImageLength"],
];
} else {
$size = [
'height' => $exif["ExifImageWidth"],
'width' => $exif["ExifImageLength"],
];
}
imagedestroy($source);
return [$data, $size];
}
}
}
}
return [false, false];
}
}

View File

@ -2,7 +2,7 @@ This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 3.8 ===
* The rotate_image function has been added to the stored_file class (MDL-63349)
* The yui checknet module is removed. Call \core\session\manager::keepalive instead.
* The generate_uuid() function has been deprecated. Please use \core\uuid::generate() instead.
* Remove lib/pear/auth/RADIUS.php (MDL-65746)

View File

@ -56,6 +56,10 @@ class document_services {
const STAMPS_FILEAREA = 'stamps';
/** Filename for combined pdf */
const COMBINED_PDF_FILENAME = 'combined.pdf';
/** Temporary place to save JPG Image to PDF file */
const TMP_JPG_TO_PDF_FILEAREA = 'tmp_jpg_to_pdf';
/** Temporary place to save (Automatically) Rotated JPG FILE */
const TMP_ROTATED_JPG_FILEAREA = 'tmp_rotated_jpg';
/** Hash of blank pdf */
const BLANK_PDF_HASH = '4c803c92c71f21b423d13de570c8a09e0a31c718';
@ -187,9 +191,28 @@ EOD;
$pluginfiles = $plugin->get_files($submission, $user);
foreach ($pluginfiles as $filename => $file) {
if ($file instanceof \stored_file) {
if ($file->get_mimetype() === 'application/pdf') {
$mimetype = $file->get_mimetype();
// PDF File, no conversion required.
if ($mimetype === 'application/pdf') {
$files[$filename] = $file;
} else if ($convertedfile = $converter->start_conversion($file, 'pdf')) {
} else if ($plugin->allow_image_conversion() && $mimetype === "image/jpeg") {
// Rotates image based on the EXIF value.
list ($rotateddata, $size) = $file->rotate_image();
if ($rotateddata) {
$file = self::save_rotated_image_file($assignment, $userid, $attemptnumber,
$rotateddata, $filename);
}
// Save as PDF file if there is no available converter.
if (!$converter->can_convert_format_to('jpg', 'pdf')) {
$pdffile = self::save_jpg_to_pdf($assignment, $userid, $attemptnumber, $file, $size);
if ($pdffile) {
$files[$filename] = $pdffile;
}
}
}
// The file has not been converted to PDF, try to convert it to PDF.
if (!isset($files[$filename])
&& $convertedfile = $converter->start_conversion($file, 'pdf')) {
$files[$filename] = $convertedfile;
}
} else if ($converter->can_convert_format_to('html', 'pdf')) {
@ -967,4 +990,83 @@ EOD;
}
return null;
}
/**
* Convert jpg file to pdf file
* @param int|\assign $assignment Assignment
* @param int $userid User ID
* @param int $attemptnumber Attempt Number
* @param \stored_file $file file to save
* @param null|array $size size of image
* @return \stored_file
* @throws \file_exception
* @throws \stored_file_creation_exception
*/
private static function save_jpg_to_pdf($assignment, $userid, $attemptnumber, $file, $size=null) {
// Temporary file.
$filename = $file->get_filename();
$tmpdir = make_temp_directory('assignfeedback_editpdf' . DIRECTORY_SEPARATOR
. self::TMP_JPG_TO_PDF_FILEAREA . DIRECTORY_SEPARATOR
. self::hash($assignment, $userid, $attemptnumber));
$tempfile = $tmpdir . DIRECTORY_SEPARATOR . $filename . ".pdf";
// Determine orientation.
$orientation = 'P';
if (!empty($size['width']) && !empty($size['height'])) {
if ($size['width'] > $size['height']) {
$orientation = 'L';
}
}
// Save JPG image to PDF file.
$pdf = new pdf();
$pdf->SetHeaderMargin(0);
$pdf->SetFooterMargin(0);
$pdf->SetMargins(0, 0, 0, true);
$pdf->setPrintFooter(false);
$pdf->setPrintHeader(false);
$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
$pdf->AddPage($orientation);
$pdf->SetAutoPageBreak(false);
// Width has to be define here to fit into A4 page. Otherwise the image will be inserted with original size.
if ($orientation == 'P') {
$pdf->Image('@' . $file->get_content(), 0, 0, 210);
} else {
$pdf->Image('@' . $file->get_content(), 0, 0, 297);
}
$pdf->setPageMark();
$pdf->save_pdf($tempfile);
$filearea = self::TMP_JPG_TO_PDF_FILEAREA;
$pdffile = self::save_file($assignment, $userid, $attemptnumber, $filearea, $tempfile);
if (file_exists($tempfile)) {
unlink($tempfile);
rmdir($tmpdir);
}
return $pdffile;
}
/**
* Save rotated image data to file.
* @param int|\assign $assignment Assignment
* @param int $userid User ID
* @param int $attemptnumber Attempt Number
* @param resource $rotateddata image data to save
* @param string $filename name of the image file
* @return \stored_file
* @throws \file_exception
* @throws \stored_file_creation_exception
*/
private static function save_rotated_image_file($assignment, $userid, $attemptnumber, $rotateddata, $filename) {
$filearea = self::TMP_ROTATED_JPG_FILEAREA;
$tmpdir = make_temp_directory('assignfeedback_editpdf' . DIRECTORY_SEPARATOR
. $filearea . DIRECTORY_SEPARATOR
. self::hash($assignment, $userid, $attemptnumber));
$tempfile = $tmpdir . DIRECTORY_SEPARATOR . basename($filename);
imagejpeg($rotateddata, $tempfile);
$newfile = self::save_file($assignment, $userid, $attemptnumber, $filearea, $tempfile);
if (file_exists($tempfile)) {
unlink($tempfile);
rmdir($tmpdir);
}
return $newfile;
}
}

View File

@ -637,4 +637,12 @@ class assign_submission_file extends assign_submission_plugin {
return $sets;
}
/**
* Determine if the plugin allows image file conversion
* @return bool
*/
public function allow_image_conversion() {
return true;
}
}

View File

@ -146,4 +146,12 @@ abstract class assign_submission_plugin extends assign_plugin {
public function submission_is_empty(stdClass $data) {
return false;
}
/**
* Determine if the plugin allows image file conversion
* @return bool
*/
public function allow_image_conversion() {
return false;
}
}

View File

@ -1,5 +1,7 @@
This files describes API changes in the assign code.
=== 3.8 ===
* The allow_image_conversion method has been added to the submissionplugins. It determines whether the submission plugin
allows image conversion or not. By default conversion is not allowed (except when overwritten in the submission plugin)
* Webservice function mod_assign_get_submission_status, return value 'warnofungroupedusers', changed from PARAM_BOOL to PARAM_ALPHA. See the description for possible values.
* The following functions have been finally deprecated and can not be used anymore:
* assign_scale_used()