MDL-55528 assignfeedback_editpdf: Update to use fileconverter_unoconv
authorAndrew Nicols <andrew@nicols.co.uk>
Fri, 3 Mar 2017 01:57:19 +0000 (09:57 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Fri, 10 Mar 2017 02:45:46 +0000 (10:45 +0800)
15 files changed:
mod/assign/feedback/editpdf/ajax.php
mod/assign/feedback/editpdf/classes/combined_document.php [new file with mode: 0644]
mod/assign/feedback/editpdf/classes/document_services.php
mod/assign/feedback/editpdf/classes/pdf.php
mod/assign/feedback/editpdf/classes/renderer.php
mod/assign/feedback/editpdf/classes/task/convert_submissions.php
mod/assign/feedback/editpdf/classes/widget.php
mod/assign/feedback/editpdf/lang/en/assignfeedback_editpdf.php
mod/assign/feedback/editpdf/locallib.php
mod/assign/feedback/editpdf/settings.php
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js
mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js
mod/assign/feedback/editpdf/yui/src/editor/js/editor.js
mod/assign/feedback/editpdf/yui/src/editor/meta/editor.json

index 4009477..538d920 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 use \assignfeedback_editpdf\document_services;
+use \assignfeedback_editpdf\combined_document;
 use \assignfeedback_editpdf\page_editor;
 use \assignfeedback_editpdf\comments_quick_list;
 
@@ -50,57 +51,80 @@ if (!$assignment->can_view_submission($userid)) {
     print_error('nopermission');
 }
 
-if ($action == 'loadallpages') {
+if ($action === 'pollconversions') {
     $draft = true;
     if (!has_capability('mod/assign:grade', $context)) {
+        // A student always sees the readonly version.
+        $readonly = true;
         $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) {
+        // Whoever is viewing the readonly version should not use the drafts, but the actual annotations.
         $draft = false;
     }
 
-    $pages = document_services::get_page_images_for_attempt($assignment,
-                                                            $userid,
-                                                            $attemptnumber,
-                                                            $readonly);
-
-    $response = new stdClass();
-    $response->pagecount = count($pages);
-    $response->pages = array();
-
-    $grade = $assignment->get_user_grade($userid, true, $attemptnumber);
-
-    // 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;
-    }
+    $response = (object) [
+            'status' => -1,
+            'filecount' => 0,
+            'pagecount' => 0,
+            'pageready' => 0,
+            'pages' => [],
+        ];
+
+    $combineddocument = document_services::get_combined_document_for_attempt($assignment, $userid, $attemptnumber);
+    $response->status = $combineddocument->get_status();
+    $response->filecount = $combineddocument->get_document_count();
+
+    if ($response->status === combined_document::STATUS_READY) {
+        $combineddocument = document_services::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
+        $response->pagecount = $combineddocument->get_page_count();
+    } else if ($response->status === combined_document::STATUS_COMPLETE || $response->status === combined_document::STATUS_FAILED) {
+        $pages = document_services::get_page_images_for_attempt($assignment,
+                                                                $userid,
+                                                                $attemptnumber,
+                                                                $readonly);
+
+        $response->pagecount = $combineddocument->get_page_count();
+
+        $grade = $assignment->get_user_grade($userid, true, $attemptnumber);
+
+        // 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',
-                                                     $filearea,
-                                                     $grade->id,
-                                                     '/',
-                                                     $pagefile->get_filename())->out();
-        $page->comments = $comments;
-        if ($imageinfo = $pagefile->get_imageinfo()) {
-            $page->width = $imageinfo['width'];
-            $page->height = $imageinfo['height'];
-        } else {
-            $page->width = 0;
-            $page->height = 0;
+        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',
+                                                        $filearea,
+                                                        $grade->id,
+                                                        '/',
+                                                        $pagefile->get_filename())->out();
+            $page->comments = $comments;
+            if ($imageinfo = $pagefile->get_imageinfo()) {
+                $page->width = $imageinfo['width'];
+                $page->height = $imageinfo['height'];
+            } else {
+                $page->width = 0;
+                $page->height = 0;
+            }
+            $annotations = page_editor::get_annotations($grade->id, $index, $draft);
+            $page->annotations = $annotations;
+            $response->pages[] = $page;
+
+            $component = 'assignfeedback_editpdf';
+            $filearea = document_services::PAGE_IMAGE_FILEAREA;
+            $filepath = '/';
+            $fs = get_file_storage();
+            $files = $fs->get_directory_files($context->id, $component, $filearea, $grade->id, $filepath);
+            $response->pageready = count($files);
         }
-        $annotations = page_editor::get_annotations($grade->id, $index, $draft);
-        $page->annotations = $annotations;
-        array_push($response->pages, $page);
     }
 
     echo json_encode($response);
diff --git a/mod/assign/feedback/editpdf/classes/combined_document.php b/mod/assign/feedback/editpdf/classes/combined_document.php
new file mode 100644 (file)
index 0000000..bf19d14
--- /dev/null
@@ -0,0 +1,405 @@
+<?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 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.
+     *
+     * @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;
+        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;
+
+                    case \core_files\conversion::STATUS_FAILED:
+                        return self::STATUS_FAILED;
+                }
+            }
+        }
+        if ($pending) {
+            return self::STATUS_PENDING_INPUT;
+        } else {
+            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;
+    }
+
+    /**
+     * 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;
+                        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();
+        if ($currentstatus === self::STATUS_FAILED) {
+            $this->store_empty_document($contextid, $itemid);
+
+            return $this;
+        } else if ($currentstatus !== self::STATUS_READY) {
+            // 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)) {
+                $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);
+
+        // 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
+     * @return  $this
+     */
+    protected function store_combined_file($tmpfile, $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);
+
+        // 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;
+        }
+
+        if ($this->get_status() === self::STATUS_FAILED) {
+            // The empty document will be returned.
+            return 1;
+        }
+
+        if ($this->get_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
+     * @return  stdClass
+     */
+    protected function get_stored_file_record($contextid, $itemid) {
+        return (object) [
+            'contextid' => $contextid,
+            'component' => 'assignfeedback_editpdf',
+            'filearea' => document_services::COMBINED_PDF_FILEAREA,
+            'itemid' => $itemid,
+            'filepath' => '/',
+            'filename' => document_services::COMBINED_PDF_FILENAME,
+        ];
+    }
+}
index 02f0ec5..11b8263 100644 (file)
@@ -145,12 +145,13 @@ EOD;
      * This function will search for all files that can be converted
      * and concatinated into a PDF (1.4) - for any submission plugin
      * for this students attempt.
+     *
      * @param int|\assign $assignment
      * @param int $userid
      * @param int $attemptnumber (-1 means latest attempt)
-     * @return array(stored_file)
+     * @return combined_document
      */
-    public static function list_compatible_submission_files_for_attempt($assignment, $userid, $attemptnumber) {
+    protected static function list_compatible_submission_files_for_attempt($assignment, $userid, $attemptnumber) {
         global $USER, $DB;
 
         $assignment = self::get_assignment_from_param($assignment);
@@ -171,10 +172,11 @@ EOD;
 
         // User has not submitted anything yet.
         if (!$submission) {
-            return $files;
+            return new combined_document();
         }
 
         $fs = get_file_storage();
+        $converter = new \core_files\converter();
         // Ask each plugin for it's list of files.
         foreach ($assignment->get_submission_plugins() as $plugin) {
             if ($plugin->is_enabled() && $plugin->is_visible()) {
@@ -183,7 +185,7 @@ EOD;
                     if ($file instanceof \stored_file) {
                         if ($file->get_mimetype() === 'application/pdf') {
                             $files[$filename] = $file;
-                        } else if ($convertedfile = $fs->get_converted_document($file, 'pdf')) {
+                        } else if ($convertedfile = $converter->start_conversion($file, 'pdf')) {
                             $files[$filename] = $convertedfile;
                         }
                     } else {
@@ -199,9 +201,28 @@ EOD;
                         $record->filepath = '/';
                         $record->filename = $plugin->get_type() . '-' . $filename;
 
-                        $htmlfile = $fs->create_file_from_string($record, $file);
-                        $convertedfile = $fs->get_converted_document($htmlfile, 'pdf');
-                        $htmlfile->delete();
+                        $htmlfile = $fs->get_file($record->contextid,
+                                $record->component,
+                                $record->filearea,
+                                $record->itemid,
+                                $record->filepath,
+                                $record->filename);
+
+                        $newhash = sha1($file);
+
+                        // If the file exists, and the content hash doesn't match, remove it.
+                        if ($htmlfile && $newhash !== $htmlfile->get_contenthash()) {
+                            $htmlfile->delete();
+                            $htmlfile = false;
+                        }
+
+                        // If the file doesn't exist, or if it was removed above, create a new one.
+                        if (!$htmlfile) {
+                            $htmlfile = $fs->create_file_from_string($record, $file);
+                        }
+
+                        $convertedfile = $converter->start_conversion($htmlfile, 'pdf');
+
                         if ($convertedfile) {
                             $files[$filename] = $convertedfile;
                         }
@@ -209,18 +230,21 @@ EOD;
                 }
             }
         }
-        return $files;
+        $combineddocument = new combined_document();
+        $combineddocument->set_source_files($files);
+
+        return $combineddocument;
     }
 
     /**
-     * This function return the combined pdf for all valid submission files.
+     * Fetch the current combined document ready for state checking.
+     *
      * @param int|\assign $assignment
      * @param int $userid
      * @param int $attemptnumber (-1 means latest attempt)
-     * @return stored_file
+     * @return combined_document
      */
-    public static function get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber) {
-
+    public static function get_combined_document_for_attempt($assignment, $userid, $attemptnumber) {
         global $USER, $DB;
 
         $assignment = self::get_assignment_from_param($assignment);
@@ -245,107 +269,52 @@ EOD;
         $filename = self::COMBINED_PDF_FILENAME;
         $fs = \get_file_storage();
 
-        if (!$combinedpdf = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
-            return self::generate_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
+        $combinedpdf = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename);
+        if ($combinedpdf && $submission) {
+            if ($combinedpdf->get_timemodified() < $submission->timemodified) {
+                // The submission has been updated since the PDF was generated.
+                $combinedpdf = false;
+            } else if ($combinedpdf->get_contenthash() == self::BLANK_PDF_HASH) {
+                // The PDF is for a blank page.
+                $combinedpdf = false;
+            }
         }
-        if ($submission && ($combinedpdf->get_timemodified() < $submission->timemodified ||
-                $combinedpdf->get_contenthash() == self::BLANK_PDF_HASH)) {
-            return self::generate_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
+
+        if (empty($combinedpdf)) {
+            // The combined PDF does not exist yet. Return the list of files to be combined.
+            return self::list_compatible_submission_files_for_attempt($assignment, $userid, $attemptnumber);
+        } else {
+            // The combined PDF aleady exists. Return it in a new combined_document object.
+            $combineddocument = new combined_document();
+            return $combineddocument->set_combined_file($combinedpdf);
         }
-        return $combinedpdf;
     }
 
     /**
-     * This function will take all of the compatible files for a submission
-     * and combine them into one PDF.
+     * This function return the combined pdf for all valid submission files.
+     *
      * @param int|\assign $assignment
      * @param int $userid
      * @param int $attemptnumber (-1 means latest attempt)
-     * @return stored_file
+     * @return combined_document
      */
-    public static function generate_combined_pdf_for_attempt($assignment, $userid, $attemptnumber) {
-        global $CFG;
-
-        require_once($CFG->libdir . '/pdflib.php');
-
-        $assignment = self::get_assignment_from_param($assignment);
-
-        if (!$assignment->can_view_submission($userid)) {
-            \print_error('nopermission');
-        }
-
-        $files = self::list_compatible_submission_files_for_attempt($assignment, $userid, $attemptnumber);
-
-        $pdf = new pdf();
-        if ($files) {
-            // Create a mega joined PDF.
-            $compatiblepdfs = array();
-            foreach ($files as $file) {
-                $compatiblepdf = pdf::ensure_pdf_compatible($file);
-                if ($compatiblepdf) {
-                    array_push($compatiblepdfs, $compatiblepdf);
-                }
-            }
-
-            $tmpdir = \make_temp_directory('assignfeedback_editpdf/combined/' . self::hash($assignment, $userid, $attemptnumber));
-            $tmpfile = $tmpdir . '/' . self::COMBINED_PDF_FILENAME;
-
-            @unlink($tmpfile);
-            try {
-                $pagecount = $pdf->combine_pdfs($compatiblepdfs, $tmpfile);
-            } catch (\Exception $e) {
-                debugging('TCPDF could not process the pdf files:' . $e->getMessage(), DEBUG_DEVELOPER);
-                // TCPDF does not recover from errors so we need to re-initialise the class.
-                $pagecount = 0;
-            }
-            if ($pagecount == 0) {
-                // We at least want a single blank page.
-                debugging('TCPDF did not produce a valid pdf:' . $tmpfile . '. Replacing with a blank pdf.', DEBUG_DEVELOPER);
-                @unlink($tmpfile);
-                $files = false;
-            }
-        }
-        $pdf->Close(); // No real need to close this pdf, because it has been saved by combine_pdfs(), but for clarity.
-
-        $grade = $assignment->get_user_grade($userid, true, $attemptnumber);
-        $record = new \stdClass();
-
-        $record->contextid = $assignment->get_context()->id;
-        $record->component = 'assignfeedback_editpdf';
-        $record->filearea = self::COMBINED_PDF_FILEAREA;
-        $record->itemid = $grade->id;
-        $record->filepath = '/';
-        $record->filename = self::COMBINED_PDF_FILENAME;
-        $fs = \get_file_storage();
-
-        $fs->delete_area_files($record->contextid, $record->component, $record->filearea, $record->itemid);
-
-        // Detect corrupt generated pdfs and replace with a blank one.
-        if ($files) {
-            $verifypdf = new pdf();
-            $pagecount = $verifypdf->load_pdf($tmpfile);
-            if ($pagecount <= 0) {
-                $files = false;
-            }
-            $verifypdf->Close(); // PDF loaded and never saved/outputted needs to be closed.
-        }
+    public static function get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber) {
+        $document = self::get_combined_document_for_attempt($assignment, $userid, $attemptnumber);
 
-        if (!$files) {
-            $file = $fs->create_file_from_string($record, base64_decode(self::BLANK_PDF_BASE64));
+        if ($document->get_status() === combined_document::STATUS_COMPLETE) {
+            // The combined document is already ready.
+            return $document;
         } else {
-            // This was a combined pdf.
-            $file = $fs->create_file_from_pathname($record, $tmpfile);
-            @unlink($tmpfile);
-
-            // Test the generated file for correctness.
-            $compatiblepdf = pdf::ensure_pdf_compatible($file);
+            // Attempt to combined the files in the document.
+            $grade = $assignment->get_user_grade($userid, true, $attemptnumber);
+            $document->combine_files($assignment->get_context()->id, $grade->id);
+            return $document;
         }
-
-        return $file;
     }
 
     /**
      * This function will return the number of pages of a pdf.
+     *
      * @param int|\assign $assignment
      * @param int $userid
      * @param int $attemptnumber (-1 means latest attempt)
@@ -377,27 +346,8 @@ EOD;
         }
 
         // Get a combined pdf file from all submitted pdf files.
-        $file = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
-        if (!$file) {
-            \print_error('Could not generate combined pdf.');
-        }
-
-        // Store the combined pdf file somewhere to be opened by tcpdf.
-        $tmpdir = \make_temp_directory('assignfeedback_editpdf/pagetotal/'
-            . self::hash($assignment, $userid, $attemptnumber));
-        $combined = $tmpdir . '/' . self::COMBINED_PDF_FILENAME;
-        $file->copy_content_to($combined); // Copy the file.
-
-        // Get the total number of pages.
-        $pdf = new pdf();
-        $pagecount = $pdf->set_pdf($combined);
-        $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed.
-
-        // Delete temporary folders and files.
-        @unlink($combined);
-        @rmdir($tmpdir);
-
-        return $pagecount;
+        $document = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
+        return $document->get_page_count();
     }
 
     /**
@@ -407,7 +357,7 @@ EOD;
      * @param int $attemptnumber (-1 means latest attempt)
      * @return array(stored_file)
      */
-    public static function generate_page_images_for_attempt($assignment, $userid, $attemptnumber) {
+    protected static function generate_page_images_for_attempt($assignment, $userid, $attemptnumber) {
         global $CFG;
 
         require_once($CFG->libdir . '/pdflib.php');
@@ -419,14 +369,19 @@ EOD;
         }
 
         // Need to generate the page images - first get a combined pdf.
-        $file = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
-        if (!$file) {
-            throw new \moodle_exception('Could not generate combined pdf.');
+        $document = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
+
+        $status = $document->get_status();
+        if ($status === combined_document::STATUS_FAILED) {
+            print_error('Could not generate combined pdf.');
+        } else if ($status === combined_document::STATUS_PENDING_INPUT) {
+            // The conversion is still in progress.
+            return [];
         }
 
         $tmpdir = \make_temp_directory('assignfeedback_editpdf/pageimages/' . self::hash($assignment, $userid, $attemptnumber));
         $combined = $tmpdir . '/' . self::COMBINED_PDF_FILENAME;
-        $file->copy_content_to($combined); // Copy the file.
+        $document->get_combined_file()->copy_content_to($combined); // Copy the file.
 
         $pdf = new pdf();
 
@@ -641,11 +596,18 @@ EOD;
         }
 
         // Need to generate the page images - first get a combined pdf.
-        $file = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
-        if (!$file) {
-            throw new \moodle_exception('Could not generate combined pdf.');
+        $document = self::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
+
+        $status = $document->get_status();
+        if ($status === combined_document::STATUS_FAILED) {
+            print_error('Could not generate combined pdf.');
+        } else if ($status === combined_document::STATUS_PENDING_INPUT) {
+            // The conversion is still in progress.
+            return false;
         }
 
+        $file = $document->get_combined_file();
+
         $tmpdir = \make_temp_directory('assignfeedback_editpdf/final/' . self::hash($assignment, $userid, $attemptnumber));
         $combined = $tmpdir . '/' . self::COMBINED_PDF_FILENAME;
         $file->copy_content_to($combined); // Copy the file.
index 700ec98..223ac50 100644 (file)
@@ -467,17 +467,29 @@ class pdf extends \FPDI {
 
     /**
      * Check to see if PDF is version 1.4 (or below); if not: use ghostscript to convert it
-     * @param \stored_file $file
+     *
+     * @param stored_file $file
      * @return string path to copy or converted pdf (false == fail)
      */
     public static function ensure_pdf_compatible(\stored_file $file) {
         global $CFG;
 
-        $temparea = \make_temp_directory('assignfeedback_editpdf');
-        $hash = $file->get_contenthash(); // Use the contenthash to make sure the temp files have unique names.
-        $tempsrc = $temparea . "/src-$hash.pdf";
-        $tempdst = $temparea . "/dst-$hash.pdf";
-        $file->copy_content_to($tempsrc); // Copy the file.
+        // Copy the stored_file to local disk for checking.
+        $temparea = make_request_directory();
+        $tempsrc = $temparea . "/source.pdf";
+        $file->copy_content_to($tempsrc);
+
+        return self::ensure_pdf_file_compatible($tempsrc);
+    }
+
+    /**
+     * Check to see if PDF is version 1.4 (or below); if not: use ghostscript to convert it
+     *
+     * @param   string $tempsrc The path to the file on disk.
+     * @return  string path to copy or converted pdf (false == fail)
+     */
+    public static function ensure_pdf_file_compatible($tempsrc) {
+        global $CFG;
 
         $pdf = new pdf();
         $pagecount = 0;
@@ -490,16 +502,18 @@ class pdf extends \FPDI {
         $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed.
 
         if ($pagecount > 0) {
-            // Page is valid and can be read by tcpdf.
+            // PDF is already valid and can be read by tcpdf.
             return $tempsrc;
         }
 
+        $temparea = make_request_directory();
+        $tempdst = $temparea . "/target.pdf";
+
         $gsexec = \escapeshellarg($CFG->pathtogs);
         $tempdstarg = \escapeshellarg($tempdst);
         $tempsrcarg = \escapeshellarg($tempsrc);
         $command = "$gsexec -q -sDEVICE=pdfwrite -dBATCH -dNOPAUSE -sOutputFile=$tempdstarg $tempsrcarg";
         exec($command);
-        @unlink($tempsrc);
         if (!file_exists($tempdst)) {
             // Something has gone wrong in the conversion.
             return false;
@@ -516,7 +530,6 @@ class pdf extends \FPDI {
         $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed.
 
         if ($pagecount <= 0) {
-            @unlink($tempdst);
             // Could not parse the converted pdf.
             return false;
         }
index 909476b..9fdaf1e 100644 (file)
@@ -224,16 +224,19 @@ class assignfeedback_editpdf_renderer extends plugin_renderer_base {
 
         $footer = '';
 
-        $editorparams = array(array('header'=>$header,
-                                    'body'=>$body,
-                                    'footer'=>$footer,
-                                    'linkid'=>$linkid,
-                                    'assignmentid'=>$widget->assignment,
-                                    'userid'=>$widget->userid,
-                                    'attemptnumber'=>$widget->attemptnumber,
-                                    'stampfiles'=>$widget->stampfiles,
-                                    'readonly'=>$widget->readonly,
-                                    'pagetotal'=>$widget->pagetotal));
+        $editorparams = array(
+            array(
+                'header' => $header,
+                'body' => $body,
+                'footer' => $footer,
+                'linkid' => $linkid,
+                'assignmentid' => $widget->assignment,
+                'userid' => $widget->userid,
+                'attemptnumber' => $widget->attemptnumber,
+                'stampfiles' => $widget->stampfiles,
+                'readonly' => $widget->readonly,
+            )
+        );
 
         $this->page->requires->yui_module('moodle-assignfeedback_editpdf-editor',
                                           'M.assignfeedback_editpdf.editor.init',
index 6662e51..9d89738 100644 (file)
@@ -25,6 +25,7 @@ namespace assignfeedback_editpdf\task;
 
 use core\task\scheduled_task;
 use assignfeedback_editpdf\document_services;
+use assignfeedback_editpdf\combined_document;
 use context_module;
 use assign;
 
@@ -91,22 +92,42 @@ class convert_submissions extends scheduled_task {
             }
 
             mtrace('Convert ' . count($users) . ' submission attempt(s) for assignment ' . $assignmentid);
+            $keepinqueue = false;
             foreach ($users as $userid) {
+                $combineddocument = document_services::get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber);
+                $status = $combineddocument->get_status();
+
+                switch ($combineddocument->get_status()) {
+                    case combined_document::STATUS_READY:
+                    case combined_document::STATUS_PENDING_INPUT:
+                        // The document has not been converted yet or is somehow still ready.
+                        $keepinqueue = true;
+                        continue;
+                }
+
                 try {
-                    document_services::get_page_images_for_attempt($assignment,
-                                                                   $userid,
-                                                                   $attemptnumber,
-                                                                   true);
-                    document_services::get_page_images_for_attempt($assignment,
-                                                                   $userid,
-                                                                   $attemptnumber,
-                                                                   false);
+                    document_services::get_page_images_for_attempt(
+                            $assignment,
+                            $userid,
+                            $attemptnumber,
+                            false
+                        );
+                    document_services::get_page_images_for_attempt(
+                            $assignment,
+                            $userid,
+                            $attemptnumber,
+                            true
+                        );
                 } catch (\moodle_exception $e) {
                     mtrace('Conversion failed with error:' . $e->errorcode);
+                    $keepinqueue = true;
                 }
             }
 
-            $DB->delete_records('assignfeedback_editpdf_queue', array('id' => $record->id));
+            if (!$keepinqueue) {
+                // Remove from queue unless requested not to.
+                $DB->delete_records('assignfeedback_editpdf_queue', array('id' => $record->id));
+            }
         }
     }
 
index 83b17c7..550b77e 100644 (file)
@@ -47,8 +47,6 @@ class assignfeedback_editpdf_widget implements renderable {
     public $stampfiles = array();
     /** @var bool $readonly */
     public $readonly = true;
-    /** @var integer $pagetotal */
-    public $pagetotal = 0;
 
     /**
      * Constructor
@@ -59,10 +57,9 @@ class assignfeedback_editpdf_widget implements renderable {
      * @param string $downloadfilename - Name of the generated pdf.
      * @param string[] $stampfiles - The file names of the stamps.
      * @param bool $readonly - Show the readonly interface (no tools).
-     * @param integer $pagetotal - The total number of pages.
      */
     public function __construct($assignment, $userid, $attemptnumber, $downloadurl,
-                                $downloadfilename, $stampfiles, $readonly, $pagetotal) {
+                                $downloadfilename, $stampfiles, $readonly) {
         $this->assignment = $assignment;
         $this->userid = $userid;
         $this->attemptnumber = $attemptnumber;
@@ -70,6 +67,5 @@ class assignfeedback_editpdf_widget implements renderable {
         $this->downloadfilename = $downloadfilename;
         $this->stampfiles = $stampfiles;
         $this->readonly = $readonly;
-        $this->pagetotal = $pagetotal;
     }
 }
index f5341c4..ec9ee62 100644 (file)
@@ -55,7 +55,6 @@ $string['gotopage'] = 'Go to page';
 $string['green'] = 'Green';
 $string['gsimage'] = 'Ghostscript test image';
 $string['pathtogspathdesc'] = 'Please note that annotate PDF requires the path to ghostscript to be set in {$a}.';
-$string['pathtounoconvpathdesc'] = 'Please note that annotate PDF requires the path to unoconv to be set in {$a}.';
 $string['highlight'] = 'Highlight';
 $string['jsrequired'] = 'JavaScript is required to annotate a PDF. Please enable JavaScript in your browser to use this feature.';
 $string['launcheditor'] = 'Launch PDF editor...';
@@ -88,14 +87,6 @@ $string['test_notexecutable'] = 'The ghostscript points to a file that is not ex
 $string['test_ok'] = 'The ghostscript path appears to be OK - please check you can see the message in the image below';
 $string['test_doesnotexist'] = 'The ghostscript path points to a non-existent file';
 $string['test_empty'] = 'The ghostscript path is empty - please enter the correct path';
-$string['test_unoconv'] = 'Test unoconv path';
-$string['test_unoconvdoesnotexist'] = 'The unoconv path does not point to the unoconv program. Please review your path settings.';
-$string['test_unoconvdownload'] = 'Download the converted pdf test file.';
-$string['test_unoconvisdir'] = 'The unoconv path points to a folder, please include the unoconv program in the path you specify';
-$string['test_unoconvnotestfile'] = 'The test document to be coverted into a PDF is missing';
-$string['test_unoconvnotexecutable'] = 'The unoconv path points to a file that is not executable';
-$string['test_unoconvok'] = 'The unoconv path appears to be properly configured.';
-$string['test_unoconvversionnotsupported'] = 'The version of unoconv you have installed is not supported. Moodle\'s assignment grading feature requires version 0.7 or higher.';
 $string['toolbarbutton'] = '{$a->tool} {$a->shortcut}';
 $string['tool'] = 'Tool';
 $string['viewfeedbackonline'] = 'View annotated PDF...';
index f69c8f5..a3c77c9 100644 (file)
@@ -134,20 +134,14 @@ class assign_feedback_editpdf extends assign_feedback_plugin {
            $filename = $feedbackfile->get_filename();
         }
 
-        // Retrieve total number of pages.
-        $pagetotal = document_services::page_number_for_attempt($this->assignment->get_instance()->id,
-                $userid,
-                $attempt,
-                $readonly);
-
         $widget = new assignfeedback_editpdf_widget($this->assignment->get_instance()->id,
                                                     $userid,
                                                     $attempt,
                                                     $url,
                                                     $filename,
                                                     $stampfiles,
-                                                    $readonly,
-                                                    $pagetotal);
+                                                    $readonly
+                                                );
         return $widget;
     }
 
index 5454543..674a21f 100644 (file)
@@ -42,13 +42,3 @@ $settings->add(new admin_setting_heading('pathtogs', get_string('pathtogs', 'adm
 $url = new moodle_url('/mod/assign/feedback/editpdf/testgs.php');
 $link = html_writer::link($url, get_string('testgs', 'assignfeedback_editpdf'));
 $settings->add(new admin_setting_heading('testgs', '', $link));
-
-// Unoconv setting.
-$systempathslink = new moodle_url('/admin/settings.php', array('section' => 'systempaths'));
-$systempathlink = html_writer::link($systempathslink, get_string('systempaths', 'admin'));
-$settings->add(new admin_setting_heading('pathtounoconv', get_string('pathtounoconv', 'admin'),
-    get_string('pathtounoconvpathdesc', 'assignfeedback_editpdf', $systempathlink)));
-
-$url = new moodle_url('/mod/assign/feedback/editpdf/testunoconv.php');
-$link = html_writer::link($url, get_string('test_unoconv', 'assignfeedback_editpdf'));
-$settings->add(new admin_setting_heading('test_unoconv', '', $link));
\ No newline at end of file
index 4a3ade1..cb1ac7a 100644 (file)
Binary files a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js and b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-debug.js differ
index fd41cf8..d70a645 100644 (file)
Binary files a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js and b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor-min.js differ
index 4a3ade1..cb1ac7a 100644 (file)
Binary files a/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js and b/mod/assign/feedback/editpdf/yui/build/moodle-assignfeedback_editpdf-editor/moodle-assignfeedback_editpdf-editor.js differ
index 0134bca..be14735 100644 (file)
@@ -80,6 +80,15 @@ EDITOR.prototype = {
      */
     pages: [],
 
+    /**
+     * The reported status of the document.
+     *
+     * @property documentstatus
+     * @type int
+     * @protected
+     */
+    documentstatus: 0,
+
     /**
      * The yui node for the loading icon.
      *
@@ -339,7 +348,7 @@ EDITOR.prototype = {
             this.refresh_button_state();
         }
 
-        this.load_all_pages();
+        this.start_generation();
     },
 
     /**
@@ -381,7 +390,7 @@ EDITOR.prototype = {
                 this.refresh_button_state();
             }
 
-            this.load_all_pages();
+            this.start_generation();
             drawingcanvas.on('windowresize', this.resize, this);
 
             resize = false;
@@ -398,21 +407,31 @@ EDITOR.prototype = {
 
     /**
      * Called to load the information and annotations for all pages.
-     * @method load_all_pages
+     *
+     * @method start_generation
      */
-    load_all_pages: function() {
-        var ajaxurl = AJAXBASE,
-            config,
-            checkconversionstatus,
-            ajax_error_total;
+    start_generation: function() {
+        this.poll_document_conversion_status();
+    },
 
-        config = {
+    /**
+     * Poll the current document conversion status and start the next step
+     * in the process.
+     *
+     * @method poll_document_conversion_status
+     */
+    poll_document_conversion_status: function() {
+        if (this.get('destroyed')) {
+            return;
+        }
+
+        Y.io(AJAXBASE, {
             method: 'get',
             context: this,
             sync: false,
             data: {
                 sesskey: M.cfg.sesskey,
-                action: 'loadallpages',
+                action: 'pollconversions',
                 userid: this.get('userid'),
                 attemptnumber: this.get('attemptnumber'),
                 assignmentid: this.get('assignmentid'),
@@ -420,109 +439,106 @@ EDITOR.prototype = {
             },
             on: {
                 success: function(tid, response) {
-                    this.all_pages_loaded(response.responseText);
+                    var data = this.handle_response_data(response),
+                        poll = false;
+                    if (data) {
+                        this.documentstatus = data.status;
+                        if (data.status === 0) {
+                            // The combined document is still waiting for input to be ready.
+                            poll = true;
+
+                        } else if (data.status === 1) {
+                            // The combine document is ready for conversion into a single PDF.
+                            poll = true;
+
+                        } else if (data.status === 2 || data.status === -1) {
+                            // The combined PDF is ready.
+                            // We now know the page count and can convert it to a set of images.
+                            this.pagecount = data.pagecount;
+
+                            if (data.pageready == data.pagecount) {
+                                this.prepare_pages_for_display(data);
+                            } else {
+                                // Some pages are not ready yet.
+                                // Note: We use a different polling process here which does not block.
+                                this.update_page_load_progress();
+
+                                // Fetch the images for the combined document.
+                                this.start_document_to_image_conversion();
+                            }
+                        }
+
+                        if (poll) {
+                            // Check again in 1 second.
+                            Y.later(1000, this, this.poll_document_conversion_status);
+                        }
+                    }
                 },
                 failure: function(tid, response) {
                     return new M.core.exception(response.responseText);
                 }
             }
-        };
-
-        Y.io(ajaxurl, config);
-
-        // If pages are not loaded, check PDF conversion status for the progress bar.
-        if (this.pagecount <= 0) {
-            checkconversionstatus = {
-                method: 'get',
-                context: this,
-                sync: false,
-                data: {
-                    sesskey: M.cfg.sesskey,
-                    action: 'conversionstatus',
-                    userid: this.get('userid'),
-                    attemptnumber: this.get('attemptnumber'),
-                    assignmentid: this.get('assignmentid')
-                },
-                on: {
-                    success: function(tid, response) {
-                        ajax_error_total = 0;
-                        if (this.pagecount === 0) {
-                            var pagetotal = this.get('pagetotal');
-
-                            // Update the progress bar.
-                            var progressbarcontainer = this.get_dialogue_element(SELECTOR.PROGRESSBARCONTAINER);
-                            var progressbar = progressbarcontainer.one('.bar');
-                            if (progressbar) {
-                                // Calculate progress.
-                                var progress = (response.response / pagetotal) * 100;
-                                progressbar.setStyle('width', progress + '%');
-                                progressbarcontainer.setAttribute('aria-valuenow', progress);
-                            }
+        });
+    },
 
-                            // New ajax request delayed of a second.
-                            M.util.js_pending('checkconversionstatus');
-                            Y.later(1000, this, function() {
-                                M.util.js_complete('checkconversionstatus');
-                                Y.io(AJAXBASEPROGRESS, checkconversionstatus);
-                            });
-                        }
-                    },
-                    failure: function(tid, response) {
-                        ajax_error_total = ajax_error_total + 1;
-                        // We only continue on error if the all pages were not generated,
-                        // and if the ajax call did not produce 5 errors in the row.
-                        if (this.pagecount === 0 && ajax_error_total < 5) {
-                            M.util.js_pending('checkconversionstatus');
-                            Y.later(1000, this, function() {
-                                M.util.js_complete('checkconversionstatus');
-                                Y.io(AJAXBASEPROGRESS, checkconversionstatus);
-                            });
+    /**
+     * Spwan the PDF to Image conversion on the server.
+     *
+     * @method get_images_for_documents
+     */
+    start_document_to_image_conversion: function() {
+        if (this.get('destroyed')) {
+            return;
+        }
+        Y.io(AJAXBASE, {
+            method: 'get',
+            context: this,
+            sync: false,
+            data: {
+                sesskey: M.cfg.sesskey,
+                action: 'pollconversions',
+                userid: this.get('userid'),
+                attemptnumber: this.get('attemptnumber'),
+                assignmentid: this.get('assignmentid'),
+                readonly: this.get('readonly') ? 1 : 0
+            },
+            on: {
+                success: function(tid, response) {
+                    var data = this.handle_response_data(response);
+                    if (data) {
+                        this.documentstatus = data.status;
+                        if (data.status === 2) {
+                            // The pages are ready. Add all of the annotations to them.
+                            this.prepare_pages_for_display(data);
                         }
-                        return new M.core.exception(response.responseText);
                     }
+                },
+                failure: function(tid, response) {
+                    return new M.core.exception(response.responseText);
                 }
-            };
-            // We start the AJAX "generated page total number" call a second later to give a chance to
-            // the AJAX "combined pdf generation" call to clean the previous submission images.
-            M.util.js_pending('checkconversionstatus');
-            Y.later(1000, this, function() {
-                ajax_error_total = 0;
-                M.util.js_complete('checkconversionstatus');
-                Y.io(AJAXBASEPROGRESS, checkconversionstatus);
-            });
-        }
+            }
+        });
     },
 
     /**
      * The info about all pages in the pdf has been returned.
+     *
      * @param string The ajax response as text.
      * @protected
-     * @method all_pages_loaded
+     * @method prepare_pages_for_display
      */
-    all_pages_loaded: function(responsetext) {
-        var data, i, j, comment, error;
-        try {
-            data = Y.JSON.parse(responsetext);
-            if (data.error || !data.pagecount) {
-                if (this.dialogue) {
-                    this.dialogue.hide();
-                }
-                // Display alert dialogue.
-                error = new M.core.alert({message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
-                error.show();
-                return;
-            }
-        } catch (e) {
+    prepare_pages_for_display: function(data) {
+        var i, j, comment, error;
+        if (!data.pagecount) {
             if (this.dialogue) {
                 this.dialogue.hide();
             }
             // Display alert dialogue.
-            error = new M.core.alert({title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
+            error = new M.core.alert({message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf')});
             error.show();
             return;
         }
 
-        this.pagecount = data.pagecount;
         this.pages = data.pages;
 
         for (i = 0; i < this.pages.length; i++) {
@@ -552,6 +568,128 @@ EDITOR.prototype = {
         this.change_page();
     },
 
+    /**
+     * Fetch the page images.
+     *
+     * @method update_page_load_progress
+     */
+    update_page_load_progress: function() {
+        if (this.get('destroyed')) {
+            return;
+        }
+        var checkconversionstatus,
+            ajax_error_total = 0,
+            progressbar = this.get_dialogue_element(SELECTOR.PROGRESSBARCONTAINER + ' .bar');
+
+        if (!progressbar) {
+            return;
+        }
+
+        // If pages are not loaded, check PDF conversion status for the progress bar.
+        checkconversionstatus = {
+            method: 'get',
+            context: this,
+            sync: false,
+            data: {
+                sesskey: M.cfg.sesskey,
+                action: 'conversionstatus',
+                userid: this.get('userid'),
+                attemptnumber: this.get('attemptnumber'),
+                assignmentid: this.get('assignmentid')
+            },
+            on: {
+                success: function(tid, response) {
+                    if (this.get('destroyed')) {
+                        return;
+                    }
+                    ajax_error_total = 0;
+
+                    var progress = 0;
+                    var progressbar = this.get_dialogue_element(SELECTOR.PROGRESSBARCONTAINER + ' .bar');
+                    if (progressbar) {
+                        // Calculate progress.
+                        progress = (response.response / this.pagecount) * 100;
+                        progressbar.setStyle('width', progress + '%');
+                        progressbar.ancestor(SELECTOR.PROGRESSBARCONTAINER).setAttribute('aria-valuenow', progress);
+
+                        if (progress < 100) {
+                            // Keep polling until all pages are generated.
+                            M.util.js_pending('checkconversionstatus');
+                            Y.later(1000, this, function() {
+                                M.util.js_complete('checkconversionstatus');
+                                Y.io(AJAXBASEPROGRESS, checkconversionstatus);
+                            });
+                        }
+                    }
+                },
+                failure: function(tid, response) {
+                    if (this.get('destroyed')) {
+                        return;
+                    }
+                    ajax_error_total = ajax_error_total + 1;
+                    // We only continue on error if the all pages were not generated,
+                    // and if the ajax call did not produce 5 errors in the row.
+                    if (this.pagecount === 0 && ajax_error_total < 5) {
+                        M.util.js_pending('checkconversionstatus');
+                        Y.later(1000, this, function() {
+                            M.util.js_complete('checkconversionstatus');
+                            Y.io(AJAXBASEPROGRESS, checkconversionstatus);
+                        });
+                    }
+                    return new M.core.exception(response.responseText);
+                }
+            }
+        };
+        // We start the AJAX "generated page total number" call a second later to give a chance to
+        // the AJAX "combined pdf generation" call to clean the previous submission images.
+        M.util.js_pending('checkconversionstatus');
+        Y.later(1000, this, function() {
+            ajax_error_total = 0;
+            M.util.js_complete('checkconversionstatus');
+            Y.io(AJAXBASEPROGRESS, checkconversionstatus);
+        });
+    },
+
+    /**
+     * Handle response data.
+     *
+     * @method  handle_response_data
+     * @param   {object} response
+     * @return  {object}
+     */
+    handle_response_data: function(response) {
+        if (this.get('destroyed')) {
+            return;
+        }
+        var data;
+        try {
+            data = Y.JSON.parse(response.responseText);
+            if (data.error) {
+                if (this.dialogue) {
+                    this.dialogue.hide();
+                }
+
+                new M.core.alert({
+                    message: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf'),
+                    visible: true
+                });
+            } else {
+                return data;
+            }
+        } catch (e) {
+            if (this.dialogue) {
+                this.dialogue.hide();
+            }
+
+            new M.core.alert({
+                title: M.util.get_string('cannotopenpdf', 'assignfeedback_editpdf'),
+                visible: true
+            });
+        }
+
+        return;
+    },
+
     /**
      * Get the full pluginfile url for an image file - just given the filename.
      *
@@ -992,6 +1130,9 @@ EDITOR.prototype = {
      * @method save_current_page
      */
     save_current_page: function() {
+        if (this.get('destroyed')) {
+            return;
+        }
         var ajaxurl = AJAXBASE,
             pageselect = this.get_dialogue_element(SELECTOR.PAGESELECT),
             config;
@@ -1040,7 +1181,6 @@ EDITOR.prototype = {
         };
 
         Y.io(ajaxurl, config);
-
     },
 
     /**
@@ -1257,10 +1397,6 @@ Y.extend(EDITOR, Y.Base, EDITOR.prototype, {
         stampfiles: {
             validator: Y.Lang.isArray,
             value: ''
-        },
-        pagetotal: {
-            validator: Y.Lang.isInteger,
-            value: 0
         }
     }
 });
@@ -1275,6 +1411,10 @@ M.assignfeedback_editpdf.editor = M.assignfeedback_editpdf.editor || {};
  * @param {Object} params
  */
 M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || function(params) {
+    if (typeof M.assignfeedback_editpdf.instance !== 'undefined') {
+        M.assignfeedback_editpdf.instance.destroy();
+    }
+
     M.assignfeedback_editpdf.instance = new EDITOR(params);
     return M.assignfeedback_editpdf.instance;
 };
index b2256b2..2f2887d 100644 (file)
@@ -12,6 +12,7 @@
         "transition",
         "querystring-stringify-simple",
         "moodle-core-notification-dialog",
+        "moodle-core-notification-alert",
         "moodle-core-notification-exception",
         "moodle-core-notification-ajaxexception"
     ]