/mod/assign/feedback/editpdf/classes/combined_document.php
PHP | 446 lines | 200 code | 65 blank | 181 comment | 28 complexity | 2e223728651f56145ae13e3354aa8161 MD5 | raw file
- <?php
- // This file is part of Moodle - http://moodle.org/
- //
- // Moodle is free software: you can redistribute it and/or modify
- // it under the terms of the GNU General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // Moodle is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License
- // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
- /**
- * This file contains the combined document class for the assignfeedback_editpdf plugin.
- *
- * @package assignfeedback_editpdf
- * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- namespace assignfeedback_editpdf;
- defined('MOODLE_INTERNAL') || die();
- /**
- * The combined_document class for the assignfeedback_editpdf plugin.
- *
- * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
- class combined_document {
- /**
- * Status value representing a conversion waiting to start.
- */
- const STATUS_PENDING_INPUT = 0;
- /**
- * Status value representing all documents ready to be combined.
- */
- const STATUS_READY = 1;
- /**
- * Status value representing all documents are ready to be combined as are supported.
- */
- const STATUS_READY_PARTIAL = 3;
- /**
- * Status value representing a successful conversion.
- */
- const STATUS_COMPLETE = 2;
- /**
- * Status value representing a permanent error.
- */
- const STATUS_FAILED = -1;
- /**
- * The list of files which make this document.
- */
- protected $sourcefiles = [];
- /**
- * The resultant combined file.
- */
- protected $combinedfile;
- /**
- * The combination status.
- */
- protected $combinationstatus = null;
- /**
- * The number of pages in the combined PDF.
- */
- protected $pagecount = 0;
- /**
- * Check the current status of the document combination.
- * Note that the combined document may not contain all the source files if some of the
- * source files were not able to be converted. An example is an audio file with a pdf cover sheet. Only
- * the cover sheet will be included in the combined document.
- *
- * @return int
- */
- public function get_status() {
- if ($this->combinedfile) {
- // The combined file exists. Report success.
- return self::STATUS_COMPLETE;
- }
- if (empty($this->sourcefiles)) {
- // There are no source files to combine.
- return self::STATUS_FAILED;
- }
- if (!empty($this->combinationstatus)) {
- // The combination is in progress and has set a status.
- // Return it instead.
- return $this->combinationstatus;
- }
- $pending = false;
- $partial = false;
- foreach ($this->sourcefiles as $file) {
- // The combined file has not yet been generated.
- // Check the status of each source file.
- if (is_a($file, \core_files\conversion::class)) {
- $status = $file->get('status');
- switch ($status) {
- case \core_files\conversion::STATUS_IN_PROGRESS:
- case \core_files\conversion::STATUS_PENDING:
- $pending = true;
- break;
- // There are 4 status flags, so the only remaining one is complete which is fine.
- case \core_files\conversion::STATUS_FAILED:
- $partial = true;
- break;
- }
- }
- }
- if ($pending) {
- return self::STATUS_PENDING_INPUT;
- } else {
- if ($partial) {
- return self::STATUS_READY_PARTIAL;
- }
- return self::STATUS_READY;
- }
- }
- /**
- * Set the completed combined file.
- *
- * @param stored_file $file The completed document for all files to be combined.
- * @return $this
- */
- public function set_combined_file($file) {
- $this->combinedfile = $file;
- return $this;
- }
- /**
- * Return true of the combined file contained only some of the submission files.
- *
- * @return boolean
- */
- public function is_partial_conversion() {
- $combinedfile = $this->get_combined_file();
- if (empty($combinedfile)) {
- return false;
- }
- $filearea = $combinedfile->get_filearea();
- return $filearea == document_services::PARTIAL_PDF_FILEAREA;
- }
- /**
- * Retrieve the completed combined file.
- *
- * @return stored_file
- */
- public function get_combined_file() {
- return $this->combinedfile;
- }
- /**
- * Set all source files which are to be combined.
- *
- * @param stored_file|conversion[] $files The complete list of all source files to be combined.
- * @return $this
- */
- public function set_source_files($files) {
- $this->sourcefiles = $files;
- return $this;
- }
- /**
- * Add an additional source file to the end of the existing list.
- *
- * @param stored_file|conversion $file The file to add to the end of the list.
- * @return $this
- */
- public function add_source_file($file) {
- $this->sourcefiles[] = $file;
- return $this;
- }
- /**
- * Retrieve the complete list of source files.
- *
- * @return stored_file|conversion[]
- */
- public function get_source_files() {
- return $this->sourcefiles;
- }
- /**
- * Refresh the files.
- *
- * This includes polling any pending conversions to see if they are complete.
- *
- * @return $this
- */
- public function refresh_files() {
- $converter = new \core_files\converter();
- foreach ($this->sourcefiles as $file) {
- if (is_a($file, \core_files\conversion::class)) {
- $status = $file->get('status');
- switch ($status) {
- case \core_files\conversion::STATUS_COMPLETE:
- continue 2;
- break;
- default:
- $converter->poll_conversion($conversion);
- }
- }
- }
- return $this;
- }
- /**
- * Combine all source files into a single PDF and store it in the
- * file_storage API using the supplied contextid and itemid.
- *
- * @param int $contextid The contextid for the file to be stored under
- * @param int $itemid The itemid for the file to be stored under
- * @return $this
- */
- public function combine_files($contextid, $itemid) {
- global $CFG;
- $currentstatus = $this->get_status();
- $readystatuslist = [self::STATUS_READY, self::STATUS_READY_PARTIAL];
- if ($currentstatus === self::STATUS_FAILED) {
- $this->store_empty_document($contextid, $itemid);
- return $this;
- } else if (!in_array($currentstatus, $readystatuslist)) {
- // The document is either:
- // * already combined; or
- // * pending input being fully converted; or
- // * unable to continue due to an issue with the input documents.
- //
- // Exit early as we cannot continue.
- return $this;
- }
- require_once($CFG->libdir . '/pdflib.php');
- $pdf = new pdf();
- $files = $this->get_source_files();
- $compatiblepdfs = [];
- foreach ($files as $file) {
- // Check that each file is compatible and add it to the list.
- // Note: We drop non-compatible files.
- $compatiblepdf = false;
- if (is_a($file, \core_files\conversion::class)) {
- $status = $file->get('status');
- if ($status == \core_files\conversion::STATUS_COMPLETE) {
- $compatiblepdf = pdf::ensure_pdf_compatible($file->get_destfile());
- }
- } else {
- $compatiblepdf = pdf::ensure_pdf_compatible($file);
- }
- if ($compatiblepdf) {
- $compatiblepdfs[] = $compatiblepdf;
- }
- }
- $tmpdir = make_request_directory();
- $tmpfile = $tmpdir . '/' . document_services::COMBINED_PDF_FILENAME;
- try {
- $pagecount = $pdf->combine_pdfs($compatiblepdfs, $tmpfile);
- $pdf->Close();
- } catch (\Exception $e) {
- // Unable to combine the PDF.
- debugging('TCPDF could not process the pdf files:' . $e->getMessage(), DEBUG_DEVELOPER);
- $pdf->Close();
- return $this->mark_combination_failed();
- }
- // Verify the PDF.
- $verifypdf = new pdf();
- $verifypagecount = $verifypdf->load_pdf($tmpfile);
- $verifypdf->Close();
- if ($verifypagecount <= 0) {
- // No pages were found in the combined PDF.
- return $this->mark_combination_failed();
- }
- // Store the newly created file as a stored_file.
- $this->store_combined_file($tmpfile, $contextid, $itemid, ($currentstatus == self::STATUS_READY_PARTIAL));
- // Note the verified page count.
- $this->pagecount = $verifypagecount;
- return $this;
- }
- /**
- * Mark the combination attempt as having encountered a permanent failure.
- *
- * @return $this
- */
- protected function mark_combination_failed() {
- $this->combinationstatus = self::STATUS_FAILED;
- return $this;
- }
- /**
- * Store the combined file in the file_storage API.
- *
- * @param string $tmpfile The path to the file on disk to be stored.
- * @param int $contextid The contextid for the file to be stored under
- * @param int $itemid The itemid for the file to be stored under
- * @param boolean $partial The combined pdf contains only some of the source files.
- * @return $this
- */
- protected function store_combined_file($tmpfile, $contextid, $itemid, $partial = false) {
- // Store the file.
- $record = $this->get_stored_file_record($contextid, $itemid, $partial);
- $fs = get_file_storage();
- // Delete existing files first.
- $fs->delete_area_files($record->contextid, $record->component, $record->filearea, $record->itemid);
- // This was a combined pdf.
- $file = $fs->create_file_from_pathname($record, $tmpfile);
- $this->set_combined_file($file);
- return $this;
- }
- /**
- * Store the empty document file in the file_storage API.
- *
- * @param int $contextid The contextid for the file to be stored under
- * @param int $itemid The itemid for the file to be stored under
- * @return $this
- */
- protected function store_empty_document($contextid, $itemid) {
- // Store the file.
- $record = $this->get_stored_file_record($contextid, $itemid);
- $fs = get_file_storage();
- // Delete existing files first.
- $fs->delete_area_files($record->contextid, $record->component, $record->filearea, $record->itemid);
- $file = $fs->create_file_from_string($record, base64_decode(document_services::BLANK_PDF_BASE64));
- $this->pagecount = 1;
- $this->set_combined_file($file);
- return $this;
- }
- /**
- * Get the total number of pages in the combined document.
- *
- * If there are no pages, or it is not yet possible to count them a
- * value of 0 is returned.
- *
- * @return int
- */
- public function get_page_count() {
- if ($this->pagecount) {
- return $this->pagecount;
- }
- $status = $this->get_status();
- if ($status === self::STATUS_FAILED) {
- // The empty document will be returned.
- return 1;
- }
- if ($status !== self::STATUS_COMPLETE) {
- // No pages yet.
- return 0;
- }
- // Load the PDF to determine the page count.
- $temparea = make_request_directory();
- $tempsrc = $temparea . "/source.pdf";
- $this->get_combined_file()->copy_content_to($tempsrc);
- $pdf = new pdf();
- $pagecount = $pdf->load_pdf($tempsrc);
- $pdf->Close();
- if ($pagecount <= 0) {
- // Something went wrong. Return an empty page count again.
- return 0;
- }
- $this->pagecount = $pagecount;
- return $this->pagecount;
- }
- /**
- * Get the total number of documents to be combined.
- *
- * @return int
- */
- public function get_document_count() {
- return count($this->sourcefiles);
- }
- /**
- * Helper to fetch the stored_file record.
- *
- * @param int $contextid The contextid for the file to be stored under
- * @param int $itemid The itemid for the file to be stored under
- * @param boolean $partial The combined file contains only some of the source files.
- * @return stdClass
- */
- protected function get_stored_file_record($contextid, $itemid, $partial = false) {
- $filearea = document_services::COMBINED_PDF_FILEAREA;
- if ($partial) {
- $filearea = document_services::PARTIAL_PDF_FILEAREA;
- }
- return (object) [
- 'contextid' => $contextid,
- 'component' => 'assignfeedback_editpdf',
- 'filearea' => $filearea,
- 'itemid' => $itemid,
- 'filepath' => '/',
- 'filename' => document_services::COMBINED_PDF_FILENAME,
- ];
- }
- }