MDL-53923 mod_assign: test unoconv path
[moodle.git] / mod / assign / feedback / editpdf / classes / pdf.php
CommitLineData
5c386472
DW
1<?php
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
17/**
18 * Library code for manipulating PDFs
19 *
20 * @package assignfeedback_editpdf
21 * @copyright 2012 Davo Smith
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25namespace assignfeedback_editpdf;
26
27defined('MOODLE_INTERNAL') || die();
28
29global $CFG;
30require_once($CFG->libdir.'/pdflib.php');
31require_once($CFG->dirroot.'/mod/assign/feedback/editpdf/fpdi/fpdi.php');
32
33/**
34 * Library code for manipulating PDFs
35 *
36 * @package assignfeedback_editpdf
37 * @copyright 2012 Davo Smith
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 */
40class pdf extends \FPDI {
41
42 /** @var int the number of the current page in the PDF being processed */
43 protected $currentpage = 0;
44 /** @var int the total number of pages in the PDF being processed */
45 protected $pagecount = 0;
46 /** @var float used to scale the pixel position of annotations (in the database) to the position in the final PDF */
47 protected $scale = 0.0;
48 /** @var string the path in which to store generated page images */
49 protected $imagefolder = null;
50 /** @var string the path to the PDF currently being processed */
51 protected $filename = null;
52
53 /** No errors */
54 const GSPATH_OK = 'ok';
55 /** Not set */
56 const GSPATH_EMPTY = 'empty';
57 /** Does not exist */
58 const GSPATH_DOESNOTEXIST = 'doesnotexist';
59 /** Is a dir */
60 const GSPATH_ISDIR = 'isdir';
61 /** Not executable */
62 const GSPATH_NOTEXECUTABLE = 'notexecutable';
63 /** Test file missing */
64 const GSPATH_NOTESTFILE = 'notestfile';
65 /** Any other error */
66 const GSPATH_ERROR = 'error';
c4d69228
SL
67 /** No errors */
68 const UNOCONVPATH_OK = 'ok';
69 /** Not set */
70 const UNOCONVPATH_EMPTY = 'empty';
71 /** Does not exist */
72 const UNOCONVPATH_DOESNOTEXIST = 'doesnotexist';
73 /** Is a dir */
74 const UNOCONVPATH_ISDIR = 'isdir';
75 /** Not executable */
76 const UNOCONVPATH_NOTEXECUTABLE = 'notexecutable';
77 /** Test file missing */
78 const UNOCONVPATH_NOTESTFILE = 'notestfile';
79 /** Version not supported */
80 const UNOCONVPATH_VERSIONNOTSUPPORTED = 'versionnotsupported';
81 /** Any other error */
82 const UNOCONVPATH_ERROR = 'error';
83
baf881b8
RT
84 /** Min. width an annotation should have */
85 const MIN_ANNOTATION_WIDTH = 5;
86 /** Min. height an annotation should have */
87 const MIN_ANNOTATION_HEIGHT = 5;
5c386472
DW
88
89 /**
90 * Combine the given PDF files into a single PDF. Optionally add a coversheet and coversheet fields.
91 * @param string[] $pdflist the filenames of the files to combine
92 * @param string $outfilename the filename to write to
93 * @return int the number of pages in the combined PDF
94 */
95 public function combine_pdfs($pdflist, $outfilename) {
96
4fe9950b 97 raise_memory_limit(MEMORY_EXTRA);
ac2b4ffc 98 $olddebug = error_reporting(0);
4fe9950b 99
5c386472
DW
100 $this->setPageUnit('pt');
101 $this->setPrintHeader(false);
102 $this->setPrintFooter(false);
103 $this->scale = 72.0 / 100.0;
104 $this->SetFont('helvetica', '', 16.0 * $this->scale);
105 $this->SetTextColor(0, 0, 0);
106
107 $totalpagecount = 0;
108
109 foreach ($pdflist as $file) {
110 $pagecount = $this->setSourceFile($file);
111 $totalpagecount += $pagecount;
112 for ($i = 1; $i<=$pagecount; $i++) {
113 $this->create_page_from_source($i);
114 }
115 }
116
117 $this->save_pdf($outfilename);
ac2b4ffc 118 error_reporting($olddebug);
5c386472
DW
119
120 return $totalpagecount;
121 }
122
123 /**
124 * The number of the current page in the PDF being processed
125 * @return int
126 */
127 public function current_page() {
128 return $this->currentpage;
129 }
130
131 /**
132 * The total number of pages in the PDF being processed
133 * @return int
134 */
135 public function page_count() {
136 return $this->pagecount;
137 }
138
139 /**
140 * Load the specified PDF and set the initial output configuration
141 * Used when processing comments and outputting a new PDF
142 * @param string $filename the path to the PDF to load
143 * @return int the number of pages in the PDF
144 */
145 public function load_pdf($filename) {
4fe9950b 146 raise_memory_limit(MEMORY_EXTRA);
ac2b4ffc 147 $olddebug = error_reporting(0);
4fe9950b 148
5c386472
DW
149 $this->setPageUnit('pt');
150 $this->scale = 72.0 / 100.0;
151 $this->SetFont('helvetica', '', 16.0 * $this->scale);
152 $this->SetFillColor(255, 255, 176);
153 $this->SetDrawColor(0, 0, 0);
154 $this->SetLineWidth(1.0 * $this->scale);
155 $this->SetTextColor(0, 0, 0);
156 $this->setPrintHeader(false);
157 $this->setPrintFooter(false);
158 $this->pagecount = $this->setSourceFile($filename);
159 $this->filename = $filename;
4fe9950b 160
ac2b4ffc 161 error_reporting($olddebug);
5c386472
DW
162 return $this->pagecount;
163 }
164
165 /**
166 * Sets the name of the PDF to process, but only loads the file if the
167 * pagecount is zero (in order to count the number of pages)
168 * Used when generating page images (but not a new PDF)
169 * @param string $filename the path to the PDF to process
170 * @param int $pagecount optional the number of pages in the PDF, if known
171 * @return int the number of pages in the PDF
172 */
173 public function set_pdf($filename, $pagecount = 0) {
174 if ($pagecount == 0) {
175 return $this->load_pdf($filename);
176 } else {
177 $this->filename = $filename;
178 $this->pagecount = $pagecount;
179 return $pagecount;
180 }
181 }
182
183 /**
184 * Copy the next page from the source file and set it as the current page
185 * @return bool true if successful
186 */
187 public function copy_page() {
188 if (!$this->filename) {
189 return false;
190 }
191 if ($this->currentpage>=$this->pagecount) {
192 return false;
193 }
194 $this->currentpage++;
195 $this->create_page_from_source($this->currentpage);
196 return true;
197 }
198
199 /**
200 * Create a page from a source PDF.
201 *
202 * @param int $pageno
203 */
204 protected function create_page_from_source($pageno) {
205 // Get the size (and deduce the orientation) of the next page.
206 $template = $this->importPage($pageno);
207 $size = $this->getTemplateSize($template);
208 $orientation = 'P';
209 if ($size['w'] > $size['h']) {
210 $orientation = 'L';
211 }
212 // Create a page of the required size / orientation.
213 $this->AddPage($orientation, array($size['w'], $size['h']));
214 // Prevent new page creation when comments are at the bottom of a page.
215 $this->setPageOrientation($orientation, false, 0);
216 // Fill in the page with the original contents from the student.
217 $this->useTemplate($template);
218 }
219
220 /**
221 * Copy all the remaining pages in the file
222 */
223 public function copy_remaining_pages() {
224 $morepages = true;
225 while ($morepages) {
226 $morepages = $this->copy_page();
227 }
228 }
229
230 /**
231 * Add a comment to the current page
232 * @param string $text the text of the comment
233 * @param int $x the x-coordinate of the comment (in pixels)
234 * @param int $y the y-coordinate of the comment (in pixels)
235 * @param int $width the width of the comment (in pixels)
236 * @param string $colour optional the background colour of the comment (red, yellow, green, blue, white, clear)
237 * @return bool true if successful (always)
238 */
239 public function add_comment($text, $x, $y, $width, $colour = 'yellow') {
240 if (!$this->filename) {
241 return false;
242 }
1d38083c 243 $this->SetDrawColor(51, 51, 51);
5c386472
DW
244 switch ($colour) {
245 case 'red':
1d38083c 246 $this->SetFillColor(249, 181, 179);
5c386472
DW
247 break;
248 case 'green':
1d38083c 249 $this->SetFillColor(214, 234, 178);
5c386472
DW
250 break;
251 case 'blue':
1d38083c 252 $this->SetFillColor(203, 217, 237);
5c386472
DW
253 break;
254 case 'white':
255 $this->SetFillColor(255, 255, 255);
256 break;
257 default: /* Yellow */
1d38083c 258 $this->SetFillColor(255, 236, 174);
5c386472
DW
259 break;
260 }
261
262 $x *= $this->scale;
263 $y *= $this->scale;
264 $width *= $this->scale;
265 $text = str_replace('&lt;', '<', $text);
266 $text = str_replace('&gt;', '>', $text);
267 // Draw the text with a border, but no background colour (using a background colour would cause the fill to
268 // appear behind any existing content on the page, hence the extra filled rectangle drawn below).
269 $this->MultiCell($width, 1.0, $text, 0, 'L', 0, 4, $x, $y); /* width, height, text, border, justify, fill, ln, x, y */
270 if ($colour != 'clear') {
271 $newy = $this->GetY();
272 // Now we know the final size of the comment, draw a rectangle with the background colour.
273 $this->Rect($x, $y, $width, $newy - $y, 'DF');
274 // Re-draw the text over the top of the background rectangle.
275 $this->MultiCell($width, 1.0, $text, 0, 'L', 0, 4, $x, $y); /* width, height, text, border, justify, fill, ln, x, y */
276 }
277 return true;
278 }
279
280 /**
281 * Add an annotation to the current page
282 * @param int $sx starting x-coordinate (in pixels)
283 * @param int $sy starting y-coordinate (in pixels)
284 * @param int $ex ending x-coordinate (in pixels)
285 * @param int $ey ending y-coordinate (in pixels)
286 * @param string $colour optional the colour of the annotation (red, yellow, green, blue, white, black)
287 * @param string $type optional the type of annotation (line, oval, rectangle, highlight, pen, stamp)
288 * @param int[]|string $path optional for 'pen' annotations this is an array of x and y coordinates for
289 * the line, for 'stamp' annotations it is the name of the stamp file (without the path)
290 * @param string $imagefolder - Folder containing stamp images.
291 * @return bool true if successful (always)
292 */
293 public function add_annotation($sx, $sy, $ex, $ey, $colour = 'yellow', $type = 'line', $path, $imagefolder) {
294 global $CFG;
295 if (!$this->filename) {
296 return false;
297 }
298 switch ($colour) {
299 case 'yellow':
1d38083c 300 $colourarray = array(255, 207, 53);
5c386472
DW
301 break;
302 case 'green':
1d38083c 303 $colourarray = array(153, 202, 62);
5c386472
DW
304 break;
305 case 'blue':
1d38083c 306 $colourarray = array(125, 159, 211);
5c386472
DW
307 break;
308 case 'white':
309 $colourarray = array(255, 255, 255);
310 break;
311 case 'black':
1d38083c 312 $colourarray = array(51, 51, 51);
5c386472
DW
313 break;
314 default: /* Red */
315 $colour = 'red';
1d38083c 316 $colourarray = array(239, 69, 64);
5c386472
DW
317 break;
318 }
319 $this->SetDrawColorArray($colourarray);
320
321 $sx *= $this->scale;
322 $sy *= $this->scale;
323 $ex *= $this->scale;
324 $ey *= $this->scale;
325
326 $this->SetLineWidth(3.0 * $this->scale);
327 switch ($type) {
328 case 'oval':
329 $rx = abs($sx - $ex) / 2;
330 $ry = abs($sy - $ey) / 2;
331 $sx = min($sx, $ex) + $rx;
332 $sy = min($sy, $ey) + $ry;
baf881b8
RT
333
334 // $rx and $ry should be >= min width and height
335 if ($rx < self::MIN_ANNOTATION_WIDTH) {
336 $rx = self::MIN_ANNOTATION_WIDTH;
337 }
338 if ($ry < self::MIN_ANNOTATION_HEIGHT) {
339 $ry = self::MIN_ANNOTATION_HEIGHT;
340 }
341
5c386472
DW
342 $this->Ellipse($sx, $sy, $rx, $ry);
343 break;
344 case 'rectangle':
345 $w = abs($sx - $ex);
346 $h = abs($sy - $ey);
347 $sx = min($sx, $ex);
348 $sy = min($sy, $ey);
baf881b8
RT
349
350 // Width or height should be >= min width and height
351 if ($w < self::MIN_ANNOTATION_WIDTH) {
352 $w = self::MIN_ANNOTATION_WIDTH;
353 }
354 if ($h < self::MIN_ANNOTATION_HEIGHT) {
355 $h = self::MIN_ANNOTATION_HEIGHT;
356 }
5c386472
DW
357 $this->Rect($sx, $sy, $w, $h);
358 break;
359 case 'highlight':
360 $w = abs($sx - $ex);
361 $h = 8.0 * $this->scale;
362 $sx = min($sx, $ex);
363 $sy = min($sy, $ey) + ($h * 0.5);
364 $this->SetAlpha(0.5, 'Normal', 0.5, 'Normal');
365 $this->SetLineWidth(8.0 * $this->scale);
baf881b8
RT
366
367 // width should be >= min width
368 if ($w < self::MIN_ANNOTATION_WIDTH) {
369 $w = self::MIN_ANNOTATION_WIDTH;
370 }
371
5c386472
DW
372 $this->Rect($sx, $sy, $w, $h);
373 $this->SetAlpha(1.0, 'Normal', 1.0, 'Normal');
374 break;
375 case 'pen':
376 if ($path) {
377 $scalepath = array();
378 $points = preg_split('/[,:]/', $path);
379 foreach ($points as $point) {
380 $scalepath[] = intval($point) * $this->scale;
381 }
baf881b8
RT
382
383 if (!empty($scalepath)) {
384 $this->PolyLine($scalepath, 'S');
385 }
5c386472
DW
386 }
387 break;
388 case 'stamp':
389 $imgfile = $imagefolder . '/' . clean_filename($path);
390 $w = abs($sx - $ex);
391 $h = abs($sy - $ey);
392 $sx = min($sx, $ex);
393 $sy = min($sy, $ey);
baf881b8
RT
394
395 // Stamp is always more than 40px, so no need to check width/height.
5c386472
DW
396 $this->Image($imgfile, $sx, $sy, $w, $h);
397 break;
398 default: // Line.
399 $this->Line($sx, $sy, $ex, $ey);
400 break;
401 }
402 $this->SetDrawColor(0, 0, 0);
403 $this->SetLineWidth(1.0 * $this->scale);
404
405 return true;
406 }
407
408 /**
409 * Save the completed PDF to the given file
410 * @param string $filename the filename for the PDF (including the full path)
411 */
412 public function save_pdf($filename) {
ac2b4ffc 413 $olddebug = error_reporting(0);
5c386472 414 $this->Output($filename, 'F');
ac2b4ffc 415 error_reporting($olddebug);
5c386472
DW
416 }
417
418 /**
419 * Set the path to the folder in which to generate page image files
420 * @param string $folder
421 */
422 public function set_image_folder($folder) {
423 $this->imagefolder = $folder;
424 }
425
426 /**
427 * Generate an image of the specified page in the PDF
428 * @param int $pageno the page to generate the image of
429 * @throws moodle_exception
430 * @throws coding_exception
431 * @return string the filename of the generated image
432 */
433 public function get_image($pageno) {
1bce3a70
RT
434 global $CFG;
435
5c386472
DW
436 if (!$this->filename) {
437 throw new \coding_exception('Attempting to generate a page image without first setting the PDF filename');
438 }
439
440 if (!$this->imagefolder) {
441 throw new \coding_exception('Attempting to generate a page image without first specifying the image output folder');
442 }
443
444 if (!is_dir($this->imagefolder)) {
445 throw new \coding_exception('The specified image output folder is not a valid folder');
446 }
447
448 $imagefile = $this->imagefolder.'/image_page' . $pageno . '.png';
449 $generate = true;
450 if (file_exists($imagefile)) {
451 if (filemtime($imagefile)>filemtime($this->filename)) {
452 // Make sure the image is newer than the PDF file.
453 $generate = false;
454 }
455 }
456
457 if ($generate) {
458 // Use ghostscript to generate an image of the specified page.
1bce3a70 459 $gsexec = \escapeshellarg($CFG->pathtogs);
1f738c8c
DW
460 $imageres = \escapeshellarg(100);
461 $imagefilearg = \escapeshellarg($imagefile);
462 $filename = \escapeshellarg($this->filename);
463 $pagenoinc = \escapeshellarg($pageno + 1);
5c386472 464 $command = "$gsexec -q -sDEVICE=png16m -dSAFER -dBATCH -dNOPAUSE -r$imageres -dFirstPage=$pagenoinc -dLastPage=$pagenoinc ".
626d8335 465 "-dDOINTERPOLATE -dGraphicsAlphaBits=4 -dTextAlphaBits=4 -sOutputFile=$imagefilearg $filename";
5c386472 466
9092378d
DP
467 $output = null;
468 $result = exec($command, $output);
5c386472 469 if (!file_exists($imagefile)) {
9092378d
DP
470 $fullerror = '<pre>'.get_string('command', 'assignfeedback_editpdf')."\n";
471 $fullerror .= $command . "\n\n";
472 $fullerror .= get_string('result', 'assignfeedback_editpdf')."\n";
473 $fullerror .= htmlspecialchars($result) . "\n\n";
474 $fullerror .= get_string('output', 'assignfeedback_editpdf')."\n";
475 $fullerror .= htmlspecialchars(implode("\n",$output)) . '</pre>';
476 throw new \moodle_exception('errorgenerateimage', 'assignfeedback_editpdf', '', $fullerror);
5c386472
DW
477 }
478 }
479
480 return 'image_page'.$pageno.'.png';
481 }
482
483 /**
484 * Check to see if PDF is version 1.4 (or below); if not: use ghostscript to convert it
485 * @param stored_file $file
486 * @return string path to copy or converted pdf (false == fail)
487 */
488 public static function ensure_pdf_compatible(\stored_file $file) {
1bce3a70
RT
489 global $CFG;
490
5c386472
DW
491 $temparea = \make_temp_directory('assignfeedback_editpdf');
492 $hash = $file->get_contenthash(); // Use the contenthash to make sure the temp files have unique names.
493 $tempsrc = $temparea . "/src-$hash.pdf";
494 $tempdst = $temparea . "/dst-$hash.pdf";
ac2b4ffc 495 $file->copy_content_to($tempsrc); // Copy the file.
5c386472 496
ac2b4ffc
DW
497 $pdf = new pdf();
498 $pagecount = 0;
499 try {
500 $pagecount = $pdf->load_pdf($tempsrc);
501 } catch (\Exception $e) {
502 // PDF was not valid - try running it through ghostscript to clean it up.
503 $pagecount = 0;
504 }
a916d557 505 $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed.
ac2b4ffc
DW
506
507 if ($pagecount > 0) {
508 // Page is valid and can be read by tcpdf.
509 return $tempsrc;
5c386472
DW
510 }
511
1bce3a70 512 $gsexec = \escapeshellarg($CFG->pathtogs);
1f738c8c
DW
513 $tempdstarg = \escapeshellarg($tempdst);
514 $tempsrcarg = \escapeshellarg($tempsrc);
515 $command = "$gsexec -q -sDEVICE=pdfwrite -dBATCH -dNOPAUSE -sOutputFile=$tempdstarg $tempsrcarg";
5c386472
DW
516 exec($command);
517 @unlink($tempsrc);
518 if (!file_exists($tempdst)) {
519 // Something has gone wrong in the conversion.
520 return false;
521 }
522
ac2b4ffc
DW
523 $pdf = new pdf();
524 $pagecount = 0;
525 try {
526 $pagecount = $pdf->load_pdf($tempdst);
527 } catch (\Exception $e) {
528 // PDF was not valid - try running it through ghostscript to clean it up.
529 $pagecount = 0;
530 }
a916d557
EL
531 $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed.
532
ac2b4ffc
DW
533 if ($pagecount <= 0) {
534 @unlink($tempdst);
535 // Could not parse the converted pdf.
536 return false;
537 }
538
5c386472
DW
539 return $tempdst;
540 }
541
542 /**
543 * Test that the configured path to ghostscript is correct and working.
544 * @param bool $generateimage - If true - a test image will be generated to verify the install.
545 * @return bool
546 */
547 public static function test_gs_path($generateimage = true) {
548 global $CFG;
549
550 $ret = (object)array(
551 'status' => self::GSPATH_OK,
552 'message' => null,
553 );
1bce3a70 554 $gspath = $CFG->pathtogs;
5c386472
DW
555 if (empty($gspath)) {
556 $ret->status = self::GSPATH_EMPTY;
557 return $ret;
558 }
559 if (!file_exists($gspath)) {
560 $ret->status = self::GSPATH_DOESNOTEXIST;
561 return $ret;
562 }
563 if (is_dir($gspath)) {
564 $ret->status = self::GSPATH_ISDIR;
565 return $ret;
566 }
567 if (!is_executable($gspath)) {
568 $ret->status = self::GSPATH_NOTEXECUTABLE;
569 return $ret;
570 }
571
9f7674bd 572 if (!$generateimage) {
5c386472
DW
573 return $ret;
574 }
575
9f7674bd
AO
576 $testfile = $CFG->dirroot.'/mod/assign/feedback/editpdf/tests/fixtures/testgs.pdf';
577 if (!file_exists($testfile)) {
578 $ret->status = self::GSPATH_NOTESTFILE;
5c386472
DW
579 return $ret;
580 }
581
582 $testimagefolder = \make_temp_directory('assignfeedback_editpdf_test');
583 @unlink($testimagefolder.'/image_page0.png'); // Delete any previous test images.
584
585 $pdf = new pdf();
586 $pdf->set_pdf($testfile);
587 $pdf->set_image_folder($testimagefolder);
588 try {
589 $pdf->get_image(0);
590 } catch (\moodle_exception $e) {
591 $ret->status = self::GSPATH_ERROR;
592 $ret->message = $e->getMessage();
593 }
a916d557 594 $pdf->Close(); // PDF loaded and never saved/outputted needs to be closed.
5c386472
DW
595
596 return $ret;
597 }
598
599 /**
600 * If the test image has been generated correctly - send it direct to the browser.
601 */
602 public static function send_test_image() {
603 global $CFG;
604 header('Content-type: image/png');
605 require_once($CFG->libdir.'/filelib.php');
606
607 $testimagefolder = \make_temp_directory('assignfeedback_editpdf_test');
608 $testimage = $testimagefolder.'/image_page0.png';
0c431257 609 send_file($testimage, basename($testimage), 0);
5c386472
DW
610 die();
611 }
612
c4d69228
SL
613 /**
614 * If the test pdf has been generated correctly and send it direct to the browser.
615 */
616 public static function send_test_pdf() {
617 global $CFG;
618 require_once($CFG->libdir . '/filelib.php');
619
620 $filerecord = array(
621 'contextid' => \context_system::instance()->id,
622 'component' => 'test',
623 'filearea' => 'assignfeedback_editpdf',
624 'itemid' => 0,
625 'filepath' => '/',
626 'filename' => 'unoconv_test.docx'
627 );
628
629 // Get the fixture doc file content and generate and stored_file object.
630 $fs = get_file_storage();
631 $fixturefile = $CFG->libdir . '/tests/fixtures/unoconv-source.docx';
632 $fixturedata = file_get_contents($fixturefile);
633 $testdocx = $fs->get_file($filerecord['contextid'], $filerecord['component'], $filerecord['filearea'],
634 $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename']);
635 if (!$testdocx) {
636 $testdocx = $fs->create_file_from_string($filerecord, $fixturedata);
637
638 }
639
640 // Convert the doc file to pdf and send it direct to the browser.
641 $result = $fs->get_converted_document($testdocx, 'pdf');
642 readfile_accel($result, 'application/pdf', true);
643 }
644
645 /**
646 * Check if unoconv configured path is correct and working.
647 *
648 * @return \stdClass an object with the test status and the UNOCONVPATH_ constant message.
649 */
650 public static function test_unoconv_path() {
651 global $CFG;
652 $unoconvpath = $CFG->pathtounoconv;
653
654 $ret = new \stdClass();
655 $ret->status = self::UNOCONVPATH_OK;
656 $ret->message = null;
657
658 if (empty($unoconvpath)) {
659 $ret->status = self::UNOCONVPATH_EMPTY;
660 return $ret;
661 }
662 if (!file_exists($unoconvpath)) {
663 $ret->status = self::UNOCONVPATH_DOESNOTEXIST;
664 return $ret;
665 }
666 if (is_dir($unoconvpath)) {
667 $ret->status = self::UNOCONVPATH_ISDIR;
668 return $ret;
669 }
670 if (!is_executable($unoconvpath)) {
671 $ret->status = self::UNOCONVPATH_NOTEXECUTABLE;
672 return $ret;
673 }
674 if (self::check_unoconv_version_support() === false) {
675 $ret->status = self::UNOCONVPATH_VERSIONNOTSUPPORTED;
676 return $ret;
677 }
678
679 return $ret;
680 }
681
682 /**
683 * Check if the installed version of unoconv is supported.
684 *
685 * @return bool true if the present version is supported, false otherwise.
686 */
687 public static function check_unoconv_version_support() {
688 global $CFG;
689 $unoconvbin = \escapeshellarg($CFG->pathtounoconv);
690 $command = "$unoconvbin --version";
691 exec($command, $output);
692 preg_match('/([0-9]+\.[0-9]+)/', $output[0], $matches);
693 $currentversion = (float)$matches[0];
694 $supportedversion = 0.7;
695 if ($currentversion < $supportedversion) {
696 return false;
697 }
698
699 return true;
700 }
5c386472
DW
701}
702