MDL-55528 fileconverter_unoconv: Add new unoconv plugin

AMOS BEGIN
 MOV [pathtounoconv,admin],[pathtounoconv,fileconverter_unoconv]
 MOV [pathtounoconv_help,admin],[pathtounoconv_help,fileconv_helperter_unoconv_help]
 MOV [unoconvwarning,admin],[unoconvwarning,fileconverter_unoconv]
 MOV [test_unoconv,assignfeedback_editpdf],[test_unoconv,fileconverter_unoconv]
 MOV [test_unoconvempty,assignfeedback_editpdf],[test_unoconvempty,fileconverter_unoconv]
 MOV [test_unoconvdoesnotexist,assignfeedback_editpdf],[test_unoconvdoesnotexist,fileconverter_unoconv]
 MOV [test_unoconvdownload,assignfeedback_editpdf],[test_unoconvdownload,fileconverter_unoconv]
 MOV [test_unoconvisdir,assignfeedback_editpdf],[test_unoconvisdir,fileconverter_unoconv]
 MOV [test_unoconvnotestfile,assignfeedback_editpdf],[test_unoconvnotestfile,fileconverter_unoconv]
 MOV [test_unoconvnotexecutable,assignfeedback_editpdf],[test_unoconvnotexecutable,fileconverter_unoconv]
 MOV [test_unoconvok,assignfeedback_editpdf],[test_unoconvok,fileconverter_unoconv]
 MOV [test_unoconvversionnotsupported,assignfeedback_editpdf],[test_unoconvversionnotsupported,fileconverter_unoconv]
AMOS END
This commit is contained in:
Andrew Nicols 2017-02-17 09:40:45 +08:00
parent 34df779a95
commit 1568168717
15 changed files with 692 additions and 343 deletions

View File

@ -12,7 +12,6 @@ $temp->add(new admin_setting_configexecutable('pathtodu', new lang_string('patht
$temp->add(new admin_setting_configexecutable('aspellpath', new lang_string('aspellpath', 'admin'), new lang_string('edhelpaspellpath'), ''));
$temp->add(new admin_setting_configexecutable('pathtodot', new lang_string('pathtodot', 'admin'), new lang_string('pathtodot_help', 'admin'), ''));
$temp->add(new admin_setting_configexecutable('pathtogs', new lang_string('pathtogs', 'admin'), new lang_string('pathtogs_help', 'admin'), '/usr/bin/gs'));
$temp->add(new admin_setting_configexecutable('pathtounoconv', new lang_string('pathtounoconv', 'admin'), new lang_string('pathtounoconv_help', 'admin'), '/usr/bin/unoconv'));
$ADMIN->add('server', $temp);

View File

@ -0,0 +1,362 @@
<?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/>.
/**
* Class for converting files between different file formats using unoconv.
*
* @package fileconverter_unoconv
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace fileconverter_unoconv;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/filelib.php');
use stored_file;
use \core_files\conversion;
/**
* Class for converting files between different formats using unoconv.
*
* @package fileconverter_unoconv
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class converter implements \core_files\converter_interface {
/** No errors */
const UNOCONVPATH_OK = 'ok';
/** Not set */
const UNOCONVPATH_EMPTY = 'empty';
/** Does not exist */
const UNOCONVPATH_DOESNOTEXIST = 'doesnotexist';
/** Is a dir */
const UNOCONVPATH_ISDIR = 'isdir';
/** Not executable */
const UNOCONVPATH_NOTEXECUTABLE = 'notexecutable';
/** Test file missing */
const UNOCONVPATH_NOTESTFILE = 'notestfile';
/** Version not supported */
const UNOCONVPATH_VERSIONNOTSUPPORTED = 'versionnotsupported';
/** Any other error */
const UNOCONVPATH_ERROR = 'error';
/**
* @var bool $requirementsmet Whether requirements have been met.
*/
protected static $requirementsmet = null;
/**
* @var array $formats The list of formats supported by unoconv.
*/
protected static $formats;
/**
* Convert a document to a new format and return a conversion object relating to the conversion in progress.
*
* @param conversion $conversion The file to be converted
* @return $this
*/
public function start_document_conversion(\core_files\conversion $conversion) {
global $CFG;
if (!self::are_requirements_met()) {
$conversion->set('status', conversion::STATUS_FAILED);
return $this;
}
$file = $conversion->get_sourcefile();
// Sanity check that the conversion is supported.
$fromformat = pathinfo($file->get_filename(), PATHINFO_EXTENSION);
if (!self::is_format_supported($fromformat)) {
$conversion->set('status', conversion::STATUS_FAILED);
return $this;
}
$format = $conversion->get('targetformat');
if (!self::is_format_supported($format)) {
$conversion->set('status', conversion::STATUS_FAILED);
return $this;
}
// Update the status to IN_PROGRESS.
$conversion->set('status', \core_files\conversion::STATUS_IN_PROGRESS);
$conversion->update();
// Copy the file to the tmp dir.
$uniqdir = make_unique_writable_directory(make_temp_directory('core_file/conversions'));
\core_shutdown_manager::register_function('remove_dir', array($uniqdir));
$localfilename = $file->get_id() . '.' . $fromformat;
$filename = $uniqdir . '/' . $localfilename;
try {
// This function can either return false, or throw an exception so we need to handle both.
if ($file->copy_content_to($filename) === false) {
throw new file_exception('storedfileproblem', 'Could not copy file contents to temp file.');
}
} catch (file_exception $fe) {
throw $fe;
}
// The temporary file to copy into.
$newtmpfile = pathinfo($filename, PATHINFO_FILENAME) . '.' . $format;
$newtmpfile = $uniqdir . '/' . clean_param($newtmpfile, PARAM_FILE);
$cmd = escapeshellcmd(trim($CFG->pathtounoconv)) . ' ' .
escapeshellarg('-f') . ' ' .
escapeshellarg($format) . ' ' .
escapeshellarg('-o') . ' ' .
escapeshellarg($newtmpfile) . ' ' .
escapeshellarg($filename);
$output = null;
$currentdir = getcwd();
chdir($uniqdir);
$result = exec($cmd, $output);
chdir($currentdir);
touch($newtmpfile);
if (filesize($newtmpfile) === 0) {
$conversion->set('status', conversion::STATUS_FAILED);
return $this;
}
$conversion
->store_destfile_from_path($newtmpfile)
->set('status', conversion::STATUS_COMPLETE)
->update();
return $this;
}
/**
* Poll an existing conversion for status update.
*
* @param conversion $conversion The file to be converted
* @return $this
*/
public function poll_conversion_status(conversion $conversion) {
// Unoconv does not support asynchronous conversion.
return $this;
}
/**
* Generate and serve the test document.
*
* @return stored_file
*/
public function serve_test_document() {
global $CFG;
require_once($CFG->libdir . '/filelib.php');
$format = 'pdf';
$filerecord = [
'contextid' => \context_system::instance()->id,
'component' => 'test',
'filearea' => 'fileconverter_unoconv',
'itemid' => 0,
'filepath' => '/',
'filename' => 'unoconv_test.docx'
];
// Get the fixture doc file content and generate and stored_file object.
$fs = get_file_storage();
$testdocx = $fs->get_file($filerecord['contextid'], $filerecord['component'], $filerecord['filearea'],
$filerecord['itemid'], $filerecord['filepath'], $filerecord['filename']);
if (!$testdocx) {
$fixturefile = dirname(__DIR__) . '/tests/fixtures/unoconv-source.docx';
$testdocx = $fs->create_file_from_pathname($filerecord, $fixturefile);
}
$conversions = conversion::get_conversions_for_file($testdocx, $format);
$conversion = new conversion(0, (object) [
'sourcefileid' => $testdocx->get_id(),
'targetformat' => $format,
]);
$conversion->create();
// Convert the doc file to the target format and send it direct to the browser.
$conversion = $this->start_document_conversion($conversion);
do {
sleep(1);
$this->poll_conversion_status($conversion);
$status = $conversion->get('status');
} while ($status !== conversion::STATUS_COMPLETE && $status !== conversion::STATUS_FAILED);
readfile_accel($conversion->get_destfile(), 'application/pdf', true);
}
/**
* Whether the plugin is configured and requirements are met.
*
* @return bool
*/
public static function are_requirements_met() {
if (self::$requirementsmet === null) {
$requirementsmet = self::test_unoconv_path()->status === self::UNOCONVPATH_OK;
$requirementsmet = $requirementsmet && self::is_minimum_version_met();
self::$requirementsmet = $requirementsmet;
}
return self::$requirementsmet;
}
/**
* Whether the minimum version of unoconv has been met.
*
* @return bool
*/
protected static function is_minimum_version_met() {
global $CFG;
$currentversion = 0;
$supportedversion = 0.7;
$unoconvbin = \escapeshellarg($CFG->pathtounoconv);
$command = "$unoconvbin --version";
exec($command, $output);
// If the command execution returned some output, then get the unoconv version.
if ($output) {
foreach ($output as $response) {
if (preg_match('/unoconv (\\d+\\.\\d+)/', $response, $matches)) {
$currentversion = (float) $matches[1];
}
}
if ($currentversion < $supportedversion) {
return false;
} else {
return true;
}
}
return false;
}
/**
* Whether the plugin is fully configured.
*
* @return bool
*/
public static function test_unoconv_path() {
global $CFG;
$unoconvpath = $CFG->pathtounoconv;
$ret = new \stdClass();
$ret->status = self::UNOCONVPATH_OK;
$ret->message = null;
if (empty($unoconvpath)) {
$ret->status = self::UNOCONVPATH_EMPTY;
return $ret;
}
if (!file_exists($unoconvpath)) {
$ret->status = self::UNOCONVPATH_DOESNOTEXIST;
return $ret;
}
if (is_dir($unoconvpath)) {
$ret->status = self::UNOCONVPATH_ISDIR;
return $ret;
}
if (!\file_is_executable($unoconvpath)) {
$ret->status = self::UNOCONVPATH_NOTEXECUTABLE;
return $ret;
}
if (!self::is_minimum_version_met()) {
$ret->status = self::UNOCONVPATH_VERSIONNOTSUPPORTED;
return $ret;
}
return $ret;
}
/**
* Whether a file conversion can be completed using this converter.
*
* @param string $from The source type
* @param string $to The destination type
* @return bool
*/
public static function supports($from, $to) {
return self::is_format_supported($from) && self::is_format_supported($to);
}
/**
* Whether the specified file format is supported.
*
* @param string $format Whether conversions between this format and another are supported
* @return bool
*/
protected static function is_format_supported($format) {
$formats = self::fetch_supported_formats();
$format = trim(\core_text::strtolower($format));
return in_array($format, $formats);
}
/**
* Fetch the list of supported file formats.
*
* @return array
*/
protected static function fetch_supported_formats() {
global $CFG;
if (!isset(self::$formats)) {
// Ask unoconv for it's list of supported document formats.
$cmd = escapeshellcmd(trim($CFG->pathtounoconv)) . ' --show';
$pipes = array();
$pipesspec = array(2 => array('pipe', 'w'));
$proc = proc_open($cmd, $pipesspec, $pipes);
$programoutput = stream_get_contents($pipes[2]);
fclose($pipes[2]);
proc_close($proc);
$matches = array();
preg_match_all('/\[\.(.*)\]/', $programoutput, $matches);
$formats = $matches[1];
self::$formats = array_unique($formats);
}
return self::$formats;
}
/**
* A list of the supported conversions.
*
* @return string
*/
public function get_supported_conversions() {
return implode(', ', self::fetch_supported_formats());
}
}

View File

@ -0,0 +1,40 @@
<?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/>.
defined('MOODLE_INTERNAL') || die();
/**
* Installation for unoconv.
*
* @package fileconverter_unoconv
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
function xmldb_fileconverter_unoconv_install() {
global $CFG;
$unoconvpresent = !empty($CFG->pathtounoconv);
$unoconvpresent = $unoconvpresent && file_exists($CFG->pathtounoconv);
$unoconvpresent = $unoconvpresent && !is_dir($CFG->pathtounoconv);
$unoconvpresent = $unoconvpresent && file_is_executable($CFG->pathtounoconv);
if ($unoconvpresent) {
// Unoconv is currently configured correctly.
// Enable it.
$plugins = \core_plugin_manager::instance()->get_plugins_of_type('fileconverter');
$plugins['unoconv']->set_enabled(true);
}
}

View File

@ -0,0 +1,39 @@
<?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/>.
/**
* Strings for plugin 'fileconverter_unoconv'
*
* @package fileconverter_unoconv
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['pathtounoconv'] = 'Path to unoconv document converter';
$string['pathtounoconv_help'] = 'Path to unoconv document converter. This is an executable that is capable of converting between document formats supported by LibreOffice. This is optional, but if specified, Moodle will use it to automatically convert between document formats. This is used to support a wider range of input files for the assignment annotate PDF feature.';
$string['pluginname'] = 'Unoconv';
$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_unoconvempty'] = 'The unoconv path is not set. Please review your path settings.';
$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.';
$string['unoconvwarning'] = 'The version of unoconv you have installed is not supported.';

View File

@ -0,0 +1,36 @@
<?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/>.
/**
* Settings for unoconv.
*
* @package fileconverter_unoconv
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
// Unoconv setting.
$settings->add(new admin_setting_configexecutable('pathtounoconv',
new lang_string('pathtounoconv', 'fileconverter_unoconv'),
new lang_string('pathtounoconv_help', 'fileconverter_unoconv'),
'/usr/bin/unoconv')
);
$url = new moodle_url('/files/converter/unoconv/testunoconv.php');
$link = html_writer::link($url, get_string('test_unoconv', 'fileconverter_unoconv'));
$settings->add(new admin_setting_heading('test_unoconv', '', $link));

View File

@ -0,0 +1,145 @@
<?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/>.
/**
* Test unoconv functionality.
*
* @package core
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* A set of tests for some of the unoconv functionality within Moodle.
*
* @package core
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class fileconverter_unoconv_converter_testcase extends advanced_testcase {
/**
* Helper to skip tests which _require_ unoconv.
*/
protected function require_unoconv() {
global $CFG;
if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
$this->markTestSkipped();
}
}
/**
* Get a testable mock of the fileconverter_unoconv class.
*
* @param array $mockedmethods A list of methods you intend to override
* If no methods are specified, only abstract functions are mocked.
* @return \fileconverter_unoconv\converter
*/
protected function get_testable_mock($mockedmethods = null) {
$converter = $this->getMockBuilder(\fileconverter_unoconv\converter::class)
->setMethods($mockedmethods)
->getMock();
return $converter;
}
/**
* Tests for the start_document_conversion function.
*/
public function test_start_document_conversion() {
$this->resetAfterTest();
$this->require_unoconv();
// Mock the file to be converted.
$filerecord = [
'contextid' => context_system::instance()->id,
'component' => 'test',
'filearea' => 'unittest',
'itemid' => 0,
'filepath' => '/',
'filename' => 'test.docx',
];
$fs = get_file_storage();
$source = __DIR__ . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR . 'unoconv-source.docx';
$testfile = $fs->create_file_from_pathname($filerecord, $source);
$converter = $this->get_testable_mock();
// Convert the document.
$result = $converter->start_document_conversion($testfile, 'pdf');
$this->assertNotFalse($result);
$this->assertSame('application/pdf', $result->get_mimetype());
$this->assertGreaterThan(0, $result->get_filesize());
// Repeat immediately with the file forcing re-generation.
$new = $converter->start_document_conversion($testfile, 'pdf', true);
$this->assertNotFalse($new);
$this->assertSame('application/pdf', $new->get_mimetype());
$this->assertGreaterThan(0, $new->get_filesize());
$this->assertNotEquals($result->get_id(), $new->get_id());
// Note: We cannot compare contenthash for PDF because the PDF has a unique ID, and a creation timestamp
// imprinted in the file.
}
/**
* Tests for the test_unoconv_path function.
*
* @dataProvider provider_test_unoconv_path
* @param string $path The path to test
* @param int $status The expected status
*/
public function test_test_unoconv_path($path, $status) {
global $CFG;
$this->resetAfterTest();
// Set the current path.
$CFG->pathtounoconv = $path;
// Run the tests.
$result = \fileconverter_unoconv\converter::test_unoconv_path();
$this->assertEquals($status, $result->status);
}
/**
* Provider for test_unoconv_path.
*
* @return array
*/
public function provider_test_unoconv_path() {
return [
'Empty path' => [
'path' => null,
'status' => \fileconverter_unoconv\converter::UNOCONVPATH_EMPTY,
],
'Invalid file' => [
'path' => '/path/to/nonexistent/file',
'status' => \fileconverter_unoconv\converter::UNOCONVPATH_DOESNOTEXIST,
],
'Directory' => [
'path' => __DIR__,
'status' => \fileconverter_unoconv\converter::UNOCONVPATH_ISDIR,
],
];
}
}

View File

@ -13,59 +13,59 @@
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Test that unoconv is configured correctly
*
* @package assignfeedback_editpdf
* @copyright 2016 Simey Lameze
* @package fileconverter_unoconv
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require(__DIR__ . '/../../../../config.php');
require(__DIR__ . '/../../../config.php');
require_once($CFG->libdir . '/filelib.php');
$sendpdf = optional_param('sendpdf', 0, PARAM_BOOL);
$PAGE->set_url(new moodle_url('/mod/assign/feedback/editpdf/testunoconv.php'));
$PAGE->set_url(new moodle_url('/files/converter/unoconv/testunoconv.php'));
$PAGE->set_context(context_system::instance());
require_login();
require_capability('moodle/site:config', context_system::instance());
$strheading = get_string('test_unoconv', 'assignfeedback_editpdf');
$strheading = get_string('test_unoconv', 'fileconverter_unoconv');
$PAGE->navbar->add(get_string('administrationsite'));
$PAGE->navbar->add(get_string('plugins', 'admin'));
$PAGE->navbar->add(get_string('assignmentplugins', 'mod_assign'));
$PAGE->navbar->add(get_string('feedbackplugins', 'mod_assign'));
$PAGE->navbar->add(get_string('pluginname', 'assignfeedback_editpdf'),
new moodle_url('/admin/settings.php', array('section' => 'assignfeedback_editpdf')));
$PAGE->navbar->add(get_string('pluginname', 'fileconverter_unoconv'),
new moodle_url('/admin/settings.php', array('section' => 'fileconverterunoconv')));
$PAGE->navbar->add($strheading);
$PAGE->set_heading($strheading);
$PAGE->set_title($strheading);
$converter = new \fileconverter_unoconv\converter();
if ($sendpdf) {
require_sesskey();
// Serve the generated test pdf.
file_storage::send_test_pdf();
$converter->serve_test_document();
die();
}
$result = file_storage::test_unoconv_path();
$result = \fileconverter_unoconv\converter::test_unoconv_path();
switch ($result->status) {
case file_storage::UNOCONVPATH_OK:
$msg = $OUTPUT->notification(get_string('test_unoconvok', 'assignfeedback_editpdf'), 'success');
case \fileconverter_unoconv\converter::UNOCONVPATH_OK:
$msg = $OUTPUT->notification(get_string('test_unoconvok', 'fileconverter_unoconv'), 'success');
$pdflink = new moodle_url($PAGE->url, array('sendpdf' => 1, 'sesskey' => sesskey()));
$msg .= html_writer::link($pdflink, get_string('test_unoconvdownload', 'assignfeedback_editpdf'));
$msg .= html_writer::link($pdflink, get_string('test_unoconvdownload', 'fileconverter_unoconv'));
$msg .= html_writer::empty_tag('br');
break;
case file_storage::UNOCONVPATH_ERROR:
$msg = $OUTPUT->notification($result->message, 'warning');
break;
default:
$msg = $OUTPUT->notification(get_string("test_unoconv{$result->status}", 'assignfeedback_editpdf'), 'warning');
$msg = $OUTPUT->notification(get_string("test_unoconv{$result->status}", 'fileconverter_unoconv'), 'warning');
break;
}
$returl = new moodle_url('/admin/settings.php', array('section' => 'assignfeedback_editpdf'));
$returl = new moodle_url('/admin/settings.php', array('section' => 'fileconverter_unoconv'));
$msg .= $OUTPUT->continue_button($returl);
echo $OUTPUT->header();

View File

@ -0,0 +1,29 @@
<?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/>.
/**
* Version details
*
* @package fileconverter_unoconv
* @copyright 2017 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017020600; // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires = 2016112900; // Requires this Moodle version
$plugin->component = 'fileconverter_unoconv'; // Full name of the plugin (used for diagnostics).

View File

@ -824,8 +824,6 @@ $string['pathtopgdumpinvalid'] = 'Invalid path to pg_dump - either wrong path or
$string['pathtopsql'] = 'Path to psql';
$string['pathtopsqldesc'] = 'This is only necessary to enter if you have more than one psql on your system (for example if you have more than one version of postgresql installed)';
$string['pathtopsqlinvalid'] = 'Invalid path to psql - either wrong path or not executable';
$string['pathtounoconv'] = 'Path to unoconv document converter';
$string['pathtounoconv_help'] = 'Path to unoconv document converter. This is an executable that is capable of converting between document formats supported by LibreOffice. This is optional, but if specified, Moodle will use it to automatically convert between document formats. This is used to support a wider range of input files for the assignment annotate PDF feature.';
$string['pcreunicodewarning'] = 'It is strongly recommended to use PCRE PHP extension that is compatible with Unicode characters.';
$string['perfdebug'] = 'Performance info';
$string['performance'] = 'Performance';
@ -1132,7 +1130,6 @@ $string['unbookmarkthispage'] = 'Unbookmark this page';
$string['unicoderequired'] = 'It is required that you store all your data in Unicode format (UTF-8). New installations must be performed into databases that have their default character set as Unicode. If you are upgrading, you should perform the UTF-8 migration process (see the Admin page).';
$string['uninstallplugin'] = 'Uninstall';
$string['unlockaccount'] = 'Unlock account';
$string['unoconvwarning'] = 'The version of unoconv you have installed is not supported. Moodle\'s assignment grading feature requires version 0.7 or higher.';
$string['unsettheme'] = 'Unset theme';
$string['unsupported'] = 'Unsupported';
$string['unsupporteddbfileformat'] = 'Your database has tables using Antelope as the file format. Full UTF-8 support in MySQL and MariaDB requires the Barracuda file format. Please convert the tables to the Barracuda file format. See the documentation <a href="https://docs.moodle.org/en/cli">Administration via command line</a> for details of a tool for converting InnoDB tables to Barracuda.';

View File

@ -1769,6 +1769,7 @@ class core_plugin_manager {
),
'fileconverter' => array(
'unoconv'
),
'editor' => array(

View File

@ -50,27 +50,6 @@ class file_storage {
/** @var file_system filesystem */
private $filesystem;
/** @var array List of formats supported by unoconv */
private $unoconvformats;
// Unoconv constants.
/** No errors */
const UNOCONVPATH_OK = 'ok';
/** Not set */
const UNOCONVPATH_EMPTY = 'empty';
/** Does not exist */
const UNOCONVPATH_DOESNOTEXIST = 'doesnotexist';
/** Is a dir */
const UNOCONVPATH_ISDIR = 'isdir';
/** Not executable */
const UNOCONVPATH_NOTEXECUTABLE = 'notexecutable';
/** Test file missing */
const UNOCONVPATH_NOTESTFILE = 'notestfile';
/** Version not supported */
const UNOCONVPATH_VERSIONNOTSUPPORTED = 'versionnotsupported';
/** Any other error */
const UNOCONVPATH_ERROR = 'error';
/**
* Constructor - do not use directly use {@link get_file_storage()} call instead.
*/
@ -188,19 +167,11 @@ class file_storage {
* @return stored_file|bool false if unable to create the conversion, stored file otherwise
*/
public function get_converted_document(stored_file $file, $format, $forcerefresh = false) {
debugging('The get_converted_document function has been deprecated and the unoconv functions been removed. '
. 'The file has not been converted. '
. 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
$context = context_system::instance();
$path = '/' . $format . '/';
$conversion = $this->get_file($context->id, 'core', 'documentconversion', 0, $path, $file->get_contenthash());
if (!$conversion || $forcerefresh) {
$conversion = $this->create_converted_document($file, $format, $forcerefresh);
if (!$conversion) {
return false;
}
}
return $conversion;
return false;
}
/**
@ -210,26 +181,10 @@ class file_storage {
* @return bool - True if the format is supported for input.
*/
protected function is_format_supported_by_unoconv($format) {
global $CFG;
debugging('The is_format_supported_by_unoconv function has been deprecated and the unoconv functions been removed. '
. 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
if (!isset($this->unoconvformats)) {
// Ask unoconv for it's list of supported document formats.
$cmd = escapeshellcmd(trim($CFG->pathtounoconv)) . ' --show';
$pipes = array();
$pipesspec = array(2 => array('pipe', 'w'));
$proc = proc_open($cmd, $pipesspec, $pipes);
$programoutput = stream_get_contents($pipes[2]);
fclose($pipes[2]);
proc_close($proc);
$matches = array();
preg_match_all('/\[\.(.*)\]/', $programoutput, $matches);
$this->unoconvformats = $matches[1];
$this->unoconvformats = array_unique($this->unoconvformats);
}
$sanitized = trim(core_text::strtolower($format));
return in_array($sanitized, $this->unoconvformats);
return false;
}
/**
@ -238,24 +193,9 @@ class file_storage {
* @return bool true if the present version is supported, false otherwise.
*/
public static function can_convert_documents() {
global $CFG;
$currentversion = 0;
$supportedversion = 0.7;
$unoconvbin = \escapeshellarg($CFG->pathtounoconv);
$command = "$unoconvbin --version";
exec($command, $output);
// If the command execution returned some output, then get the unoconv version.
if ($output) {
foreach ($output as $response) {
if (preg_match('/unoconv (\\d+\\.\\d+)/', $response, $matches)) {
$currentversion = (float)$matches[1];
}
}
if ($currentversion < $supportedversion) {
return false;
}
return true;
}
debugging('The can_convert_documents function has been deprecated and the unoconv functions been removed. '
. 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
return false;
}
@ -263,32 +203,10 @@ class file_storage {
* Regenerate the test pdf and send it direct to the browser.
*/
public static function send_test_pdf() {
global $CFG;
require_once($CFG->libdir . '/filelib.php');
debugging('The send_test_pdf function has been deprecated and the unoconv functions been removed. '
. 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
$filerecord = array(
'contextid' => \context_system::instance()->id,
'component' => 'test',
'filearea' => 'assignfeedback_editpdf',
'itemid' => 0,
'filepath' => '/',
'filename' => 'unoconv_test.docx'
);
// Get the fixture doc file content and generate and stored_file object.
$fs = get_file_storage();
$fixturefile = $CFG->libdir . '/tests/fixtures/unoconv-source.docx';
$fixturedata = file_get_contents($fixturefile);
$testdocx = $fs->get_file($filerecord['contextid'], $filerecord['component'], $filerecord['filearea'],
$filerecord['itemid'], $filerecord['filepath'], $filerecord['filename']);
if (!$testdocx) {
$testdocx = $fs->create_file_from_string($filerecord, $fixturedata);
}
// Convert the doc file to pdf and send it direct to the browser.
$result = $fs->get_converted_document($testdocx, 'pdf', true);
readfile_accel($result, 'application/pdf', true);
return false;
}
/**
@ -297,125 +215,10 @@ class file_storage {
* @return \stdClass an object with the test status and the UNOCONVPATH_ constant message.
*/
public static function test_unoconv_path() {
global $CFG;
$unoconvpath = $CFG->pathtounoconv;
debugging('The test_unoconv_path function has been deprecated and the unoconv functions been removed. '
. 'Please update your code to use the file conversion API instead.', DEBUG_DEVELOPER);
$ret = new \stdClass();
$ret->status = self::UNOCONVPATH_OK;
$ret->message = null;
if (empty($unoconvpath)) {
$ret->status = self::UNOCONVPATH_EMPTY;
return $ret;
}
if (!file_exists($unoconvpath)) {
$ret->status = self::UNOCONVPATH_DOESNOTEXIST;
return $ret;
}
if (is_dir($unoconvpath)) {
$ret->status = self::UNOCONVPATH_ISDIR;
return $ret;
}
if (!file_is_executable($unoconvpath)) {
$ret->status = self::UNOCONVPATH_NOTEXECUTABLE;
return $ret;
}
if (!\file_storage::can_convert_documents()) {
$ret->status = self::UNOCONVPATH_VERSIONNOTSUPPORTED;
return $ret;
}
return $ret;
}
/**
* Perform a file format conversion on the specified document.
*
* @param stored_file $file the file we want to preview
* @param string $format The desired format - e.g. 'pdf'. Formats are specified by file extension.
* @return stored_file|bool false if unable to create the conversion, stored file otherwise
*/
protected function create_converted_document(stored_file $file, $format, $forcerefresh = false) {
global $CFG;
if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
return false;
}
$fileextension = core_text::strtolower(pathinfo($file->get_filename(), PATHINFO_EXTENSION));
if (!self::is_format_supported_by_unoconv($fileextension)) {
return false;
}
if (!self::is_format_supported_by_unoconv($format)) {
return false;
}
// Copy the file to the tmp dir.
$uniqdir = "core_file/conversions/" . uniqid($file->get_id() . "-", true);
$tmp = make_temp_directory($uniqdir);
$ext = pathinfo($file->get_filename(), PATHINFO_EXTENSION);
// Safety.
$localfilename = $file->get_id() . '.' . $ext;
$filename = $tmp . '/' . $localfilename;
try {
// This function can either return false, or throw an exception so we need to handle both.
if ($file->copy_content_to($filename) === false) {
throw new file_exception('storedfileproblem', 'Could not copy file contents to temp file.');
}
} catch (file_exception $fe) {
remove_dir($tmp);
throw $fe;
}
$newtmpfile = pathinfo($filename, PATHINFO_FILENAME) . '.' . $format;
// Safety.
$newtmpfile = $tmp . '/' . clean_param($newtmpfile, PARAM_FILE);
$cmd = escapeshellcmd(trim($CFG->pathtounoconv)) . ' ' .
escapeshellarg('-f') . ' ' .
escapeshellarg($format) . ' ' .
escapeshellarg('-o') . ' ' .
escapeshellarg($newtmpfile) . ' ' .
escapeshellarg($filename);
$output = null;
$currentdir = getcwd();
chdir($tmp);
$result = exec($cmd, $output);
chdir($currentdir);
touch($newtmpfile);
if (filesize($newtmpfile) === 0) {
remove_dir($tmp);
// Cleanup.
return false;
}
$context = context_system::instance();
$path = '/' . $format . '/';
$record = array(
'contextid' => $context->id,
'component' => 'core',
'filearea' => 'documentconversion',
'itemid' => 0,
'filepath' => $path,
'filename' => $file->get_contenthash(),
);
if ($forcerefresh) {
$existing = $this->get_file($context->id, 'core', 'documentconversion', 0, $path, $file->get_contenthash());
if ($existing) {
$existing->delete();
}
}
$convertedfile = $this->create_file_from_pathname($record, $newtmpfile);
// Cleanup.
remove_dir($tmp);
return $convertedfile;
return false;
}
/**

View File

@ -1,107 +0,0 @@
<?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/>.
/**
* Test unoconv functionality.
*
* @package core
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* A set of tests for some of the unoconv functionality within Moodle.
*
* @package core
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_unoconv_testcase extends advanced_testcase {
public function get_converted_document_provider() {
$fixturepath = __DIR__ . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR;
return [
'HTML => PDF' => [
'source' => $fixturepath . 'unoconv-source.html',
'sourcefilename' => 'test.html',
'format' => 'pdf',
'mimetype' => 'application/pdf',
],
'docx => PDF' => [
'source' => $fixturepath . 'unoconv-source.docx',
'sourcefilename' => 'test.docx',
'format' => 'pdf',
'mimetype' => 'application/pdf',
],
'HTML => TXT' => [
'source' => $fixturepath . 'unoconv-source.html',
'sourcefilename' => 'test.html',
'format' => 'txt',
'mimetype' => 'text/plain',
],
'docx => TXT' => [
'source' => $fixturepath . 'unoconv-source.docx',
'sourcefilename' => 'test.docx',
'format' => 'txt',
'mimetype' => 'text/plain',
],
];
}
/**
* @dataProvider get_converted_document_provider
*/
public function test_get_converted_document($source, $sourcefilename, $format, $mimetype) {
global $CFG;
if (empty($CFG->pathtounoconv) || !file_is_executable(trim($CFG->pathtounoconv))) {
// No conversions are possible, sorry.
return $this->markTestSkipped();
}
$this->resetAfterTest();
$filerecord = array(
'contextid' => context_system::instance()->id,
'component' => 'test',
'filearea' => 'unittest',
'itemid' => 0,
'filepath' => '/',
'filename' => $sourcefilename,
);
$fs = get_file_storage();
//$testfile = $fs->create_file_from_string($filerecord, file_get_contents($source));
$testfile = $fs->create_file_from_pathname($filerecord, $source);
$result = $fs->get_converted_document($testfile, $format);
$this->assertNotFalse($result);
$this->assertSame($mimetype, $result->get_mimetype());
$this->assertGreaterThan(0, $result->get_filesize());
// Repeat immediately with the file forcing re-generation.
$new = $fs->get_converted_document($testfile, $format, true);
$this->assertNotFalse($new);
$this->assertSame($mimetype, $new->get_mimetype());
$this->assertGreaterThan(0, $new->get_filesize());
$this->assertNotEquals($result->get_id(), $new->get_id());
// Note: We cannot compare contenthash for PDF because the PDF has a unique ID, and a creation timestamp
// imprinted in the file.
}
}

View File

@ -25,6 +25,11 @@ information provided here is intended especially for developers.
- file_storage::try_content_recovery - See MDL-46375 for more information
- file_storage::content_exists - See MDL-46375 for more information
- file_storage::deleted_file_cleanup - See MDL-46375 for more information
- file_storage::get_converted_document
- file_storage::is_format_supported_by_unoconv
- file_storage::can_convert_documents
- file_storage::send_test_pdf
- file_storage::test_unoconv_path
* Following behat steps have been removed from core:
- I click on "<element_string>" "<selector_string>" in the "<row_text_string>" table row
- I go to notifications page