mirror of
https://github.com/moodle/moodle.git
synced 2025-01-19 06:18:28 +01:00
Merge branch 'MDL-55528-master' of git://github.com/andrewnicols/moodle
This commit is contained in:
commit
732bd13171
@ -230,6 +230,19 @@ if ($hassiteconfig) {
|
||||
300, PARAM_INT, 10));
|
||||
$ADMIN->add('mediaplayers', $temp);
|
||||
|
||||
// Convert plugins.
|
||||
$ADMIN->add('modules', new admin_category('fileconverterplugins', new lang_string('type_fileconverter_plural', 'plugin')));
|
||||
$temp = new admin_settingpage('managefileconverterplugins', new lang_string('type_fileconverter', 'plugin'));
|
||||
$temp->add(new admin_setting_manage_fileconverter_plugins());
|
||||
$ADMIN->add('fileconverterplugins', $temp);
|
||||
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('fileconverter');
|
||||
core_collator::asort_objects_by_property($plugins, 'displayname');
|
||||
foreach ($plugins as $plugin) {
|
||||
/** @var \core\plugininfo\media $plugin */
|
||||
$plugin->load_settings($ADMIN, 'fileconverterplugins', $hassiteconfig);
|
||||
}
|
||||
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type('media');
|
||||
core_collator::asort_objects_by_property($plugins, 'displayname');
|
||||
foreach ($plugins as $plugin) {
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
65
admin/templates/setting_manage_plugins.mustache
Normal file
65
admin/templates/setting_manage_plugins.mustache
Normal file
@ -0,0 +1,65 @@
|
||||
<table class="admintable generaltable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="header">{{#str}}name, moodle{{/str}}</th>
|
||||
{{#infocolumnname}}
|
||||
<th class="header">{{infocolumnname}}</th>
|
||||
{{/infocolumnname}}
|
||||
<th class="header">{{#str}}order, moodle{{/str}}</th>
|
||||
<th class="header">{{#str}}isenabled, plugin{{/str}}</th>
|
||||
<th class="header">{{#str}}settings, moodle{{/str}}</th>
|
||||
<th class="header">{{#str}}uninstall, plugin{{/str}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#plugins}}
|
||||
<tr>
|
||||
<td>{{plugin}}</td>
|
||||
{{#infocolumnname}}
|
||||
<td>
|
||||
{{info}}
|
||||
</td>
|
||||
{{/infocolumnname}}
|
||||
<td class="text-nowrap">
|
||||
{{#moveuplink}}
|
||||
<a href="{{{moveuplink}}}">
|
||||
{{#pix}}t/up, moodle, {{#str}} up, moodle {{/str}}{{/pix}}
|
||||
</a>
|
||||
{{/moveuplink}}
|
||||
{{^moveuplink}}
|
||||
{{#pix}}spacer, moodle{{/pix}}
|
||||
{{/moveuplink}}
|
||||
|
||||
{{#movedownlink}}
|
||||
<a href="{{{movedownlink}}}">
|
||||
{{#pix}}t/down, moodle, {{#str}} down, moodle {{/str}}{{/pix}}
|
||||
</a>
|
||||
{{/movedownlink}}
|
||||
{{^movedownlink}}
|
||||
{{#pix}}spacer, moodle{{/pix}}
|
||||
{{/movedownlink}}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{{togglelink}}}">
|
||||
{{#toggletarget}}
|
||||
{{#pix}}i/show, moodle, {{#str}} enable, moodle {{/str}}{{/pix}}
|
||||
{{/toggletarget}}
|
||||
{{^toggletarget}}
|
||||
{{#pix}}i/hide, moodle, {{#str}} disable, moodle {{/str}}{{/pix}}
|
||||
{{/toggletarget}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{#settingslink}}
|
||||
<a href="{{{settingslink}}}">{{#str}}settings,plugin{{/str}}</a>
|
||||
{{/settingslink}}
|
||||
</td>
|
||||
<td>
|
||||
{{#uninstalllink}}
|
||||
<a href="{{{uninstalllink}}}">{{#str}}uninstall,plugin{{/str}}</a>
|
||||
{{/uninstalllink}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/plugins}}
|
||||
</tbody>
|
||||
</table>
|
80
admin/updatesetting.php
Normal file
80
admin/updatesetting.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Generic plugin config manipulation script.
|
||||
*
|
||||
* @package admin
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define('NO_OUTPUT_BUFFERING', true);
|
||||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
|
||||
$action = required_param('action', PARAM_ALPHANUMEXT);
|
||||
$plugin = required_param('plugin', PARAM_PLUGIN);
|
||||
$type = required_param('type', PARAM_PLUGIN);
|
||||
|
||||
$PAGE->set_url('/admin/updatesetting.php');
|
||||
$PAGE->set_context(context_system::instance());
|
||||
|
||||
require_login();
|
||||
require_capability('moodle/site:config', context_system::instance());
|
||||
require_sesskey();
|
||||
|
||||
$plugintypeclass = "\\core\\plugininfo\\{$type}";
|
||||
|
||||
$plugins = \core_plugin_manager::instance()->get_plugins_of_type($type);
|
||||
$sortorder = array_values($plugintypeclass::get_enabled_plugins());
|
||||
|
||||
$return = $plugintypeclass::get_manage_url();
|
||||
|
||||
if (!array_key_exists($plugin, $plugins)) {
|
||||
redirect($return);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'disable':
|
||||
$plugins[$plugin]->set_enabled(false);
|
||||
break;
|
||||
|
||||
case 'enable':
|
||||
$plugins[$plugin]->set_enabled(true);
|
||||
break;
|
||||
|
||||
case 'up':
|
||||
if (($pos = array_search($plugin, $sortorder)) > 0) {
|
||||
$tmp = $sortorder[$pos - 1];
|
||||
$sortorder[$pos - 1] = $sortorder[$pos];
|
||||
$sortorder[$pos] = $tmp;
|
||||
$plugintypeclass::set_enabled_plugins($sortorder);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'down':
|
||||
if ((($pos = array_search($plugin, $sortorder)) !== false) && ($pos < count($sortorder) - 1)) {
|
||||
$tmp = $sortorder[$pos + 1];
|
||||
$sortorder[$pos + 1] = $sortorder[$pos];
|
||||
$sortorder[$pos] = $tmp;
|
||||
$plugintypeclass::set_enabled_plugins($sortorder);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
redirect($return);
|
360
files/classes/conversion.php
Normal file
360
files/classes/conversion.php
Normal file
@ -0,0 +1,360 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Classes for converting files between different file formats.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_files;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use stored_file;
|
||||
|
||||
/**
|
||||
* Class representing a conversion currently in progress.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class conversion extends \core\persistent {
|
||||
|
||||
/**
|
||||
* Status value representing a conversion waiting to start.
|
||||
*/
|
||||
const STATUS_PENDING = 0;
|
||||
|
||||
/**
|
||||
* Status value representing a conversion in progress.
|
||||
*/
|
||||
const STATUS_IN_PROGRESS = 1;
|
||||
|
||||
/**
|
||||
* Status value representing a successful conversion.
|
||||
*/
|
||||
const STATUS_COMPLETE = 2;
|
||||
|
||||
/**
|
||||
* Status value representing a failed conversion.
|
||||
*/
|
||||
const STATUS_FAILED = -1;
|
||||
|
||||
/**
|
||||
* Table name for this persistent.
|
||||
*/
|
||||
const TABLE = 'file_conversion';
|
||||
|
||||
protected static function define_properties() {
|
||||
return array(
|
||||
'sourcefileid' => [
|
||||
'type' => PARAM_INT,
|
||||
],
|
||||
'targetformat' => [
|
||||
'type' => PARAM_ALPHANUMEXT,
|
||||
],
|
||||
'status' => [
|
||||
'type' => PARAM_INT,
|
||||
'choices' => [
|
||||
self::STATUS_PENDING,
|
||||
self::STATUS_IN_PROGRESS,
|
||||
self::STATUS_COMPLETE,
|
||||
self::STATUS_FAILED,
|
||||
],
|
||||
'default' => self::STATUS_PENDING,
|
||||
],
|
||||
'statusmessage' => [
|
||||
'type' => PARAM_RAW,
|
||||
'null' => NULL_ALLOWED,
|
||||
'default' => null,
|
||||
],
|
||||
'converter' => [
|
||||
'type' => PARAM_RAW,
|
||||
'null' => NULL_ALLOWED,
|
||||
'default' => null,
|
||||
],
|
||||
'destfileid' => [
|
||||
'type' => PARAM_INT,
|
||||
'null' => NULL_ALLOWED,
|
||||
'default' => null,
|
||||
],
|
||||
'data' => [
|
||||
'type' => PARAM_RAW,
|
||||
'null' => NULL_ALLOWED,
|
||||
'default' => null,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all conversions relating to the specified file.
|
||||
*
|
||||
* Only conversions which have a valid file are returned.
|
||||
*
|
||||
* @param stored_file $file The source file being converted
|
||||
* @param string $format The targetforamt to filter to
|
||||
* @return conversion[]
|
||||
*/
|
||||
public static function get_conversions_for_file(stored_file $file, $format) {
|
||||
global $DB;
|
||||
$instances = [];
|
||||
|
||||
// Conversion records are intended for tracking a conversion in progress or recently completed.
|
||||
// The record is removed periodically, but the destination file is not.
|
||||
// We need to fetch all conversion records which match the source file and target, and also all source and
|
||||
// destination files which do not have a conversion record.
|
||||
$sqlfields = self::get_sql_fields('c', 'conversion');
|
||||
|
||||
// Fetch actual conversions which relate to the specified source file, and have a matching conversion record,
|
||||
// and either have a valid destination file which still exists, or do not have a destination file at all.
|
||||
$sql = "SELECT {$sqlfields}
|
||||
FROM {" . self::TABLE . "} c
|
||||
INNER JOIN {files} conversionsourcefile ON conversionsourcefile.id = c.sourcefileid
|
||||
LEFT JOIN {files} conversiondestfile ON conversiondestfile.id = c.destfileid
|
||||
WHERE
|
||||
conversionsourcefile.contenthash = :ccontenthash
|
||||
AND c.targetformat = :cformat
|
||||
AND (
|
||||
c.destfileid IS NULL OR conversiondestfile.id IS NOT NULL
|
||||
)";
|
||||
|
||||
// Fetch a empty conversion record for each source/destination combination that we find to match where the
|
||||
// destination file is in the correct filearea/filepath/filename combination to meet the requirements.
|
||||
// This ensures that existing conversions are used where possible, even if there is no 'conversion' record for
|
||||
// them.
|
||||
$sql .= "
|
||||
UNION ALL
|
||||
SELECT
|
||||
NULL AS conversionid,
|
||||
orphanedsourcefile.id AS conversionsourcefileid,
|
||||
:oformat AS conversiontargetformat,
|
||||
2 AS conversionstatus,
|
||||
NULL AS conversionstatusmessage,
|
||||
NULL AS conversionconverter,
|
||||
orphaneddestfile.id AS conversiondestfileid,
|
||||
NULL AS conversiondata,
|
||||
0 AS conversiontimecreated,
|
||||
0 AS conversiontimemodified,
|
||||
0 AS conversionusermodified
|
||||
FROM {files} orphanedsourcefile
|
||||
INNER JOIN {files} orphaneddestfile ON (
|
||||
orphaneddestfile.filename = orphanedsourcefile.contenthash
|
||||
AND orphaneddestfile.component = 'core'
|
||||
AND orphaneddestfile.filearea = 'documentconversion'
|
||||
AND orphaneddestfile.filepath = :ofilepath
|
||||
)
|
||||
LEFT JOIN {" . self::TABLE . "} orphanedconversion ON orphanedconversion.destfileid = orphaneddestfile.id
|
||||
WHERE
|
||||
orphanedconversion.id IS NULL
|
||||
AND
|
||||
orphanedsourcefile.id = :osourcefileid
|
||||
";
|
||||
$records = $DB->get_records_sql($sql, [
|
||||
'ccontenthash' => $file->get_contenthash(),
|
||||
'osourcefileid' => $file->get_id(),
|
||||
'cfilepath' => "/{$format}/",
|
||||
'ofilepath' => "/{$format}/",
|
||||
'cformat' => $format,
|
||||
'oformat' => $format,
|
||||
]);
|
||||
|
||||
foreach ($records as $record) {
|
||||
$data = self::extract_record($record, 'conversion');
|
||||
$newrecord = new static(0, $data);
|
||||
$instances[] = $newrecord;
|
||||
}
|
||||
|
||||
return $instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all old conversion records.
|
||||
*/
|
||||
public static function remove_old_conversion_records() {
|
||||
global $DB;
|
||||
|
||||
$DB->delete_records_select(self::TABLE, 'timemodified <= :weekagosecs', [
|
||||
'weekagosecs' => time() - WEEKSECS,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source file id for the conversion.
|
||||
*
|
||||
* @param stored_file $file The file to convert
|
||||
* @return $this
|
||||
*/
|
||||
public function set_sourcefile(stored_file $file) {
|
||||
$this->raw_set('sourcefileid', $file->get_id());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the source file.
|
||||
*
|
||||
* @return stored_file|false
|
||||
*/
|
||||
public function get_sourcefile() {
|
||||
$fs = get_file_storage();
|
||||
|
||||
return $fs->get_file_by_id($this->get('sourcefileid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination file for this conversion.
|
||||
*
|
||||
* @param string $filepath The path to the converted file
|
||||
* @return $this
|
||||
*/
|
||||
public function store_destfile_from_path($filepath) {
|
||||
if ($record = $this->get_file_record()) {
|
||||
$fs = get_file_storage();
|
||||
$existing = $fs->get_file(
|
||||
$record['contextid'],
|
||||
$record['component'],
|
||||
$record['filearea'],
|
||||
$record['itemid'],
|
||||
$record['filepath'],
|
||||
$record['filename']
|
||||
);
|
||||
if ($existing) {
|
||||
$existing->delete();
|
||||
}
|
||||
$file = $fs->create_file_from_pathname($record, $filepath);
|
||||
|
||||
$this->raw_set('destfileid', $file->get_id());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination file for this conversion.
|
||||
*
|
||||
* @param string $content The content of the converted file
|
||||
* @return $this
|
||||
*/
|
||||
public function store_destfile_from_string($content) {
|
||||
if ($record = $this->get_file_record()) {
|
||||
$fs = get_file_storage();
|
||||
$existing = $fs->get_file(
|
||||
$record['contextid'],
|
||||
$record['component'],
|
||||
$record['filearea'],
|
||||
$record['itemid'],
|
||||
$record['filepath'],
|
||||
$record['filename']
|
||||
);
|
||||
if ($existing) {
|
||||
$existing->delete();
|
||||
}
|
||||
$file = $fs->create_file_from_string($record, $content);
|
||||
|
||||
$this->raw_set('destfileid', $file->get_id());
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the destination file.
|
||||
*
|
||||
* @return stored_file|this
|
||||
*/
|
||||
public function get_destfile() {
|
||||
$fs = get_file_storage();
|
||||
|
||||
return $fs->get_file_by_id($this->get('destfileid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to ensure that the returned status is always an int.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_status() {
|
||||
return (int) $this->raw_get('status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance of the current converter.
|
||||
*
|
||||
* @return converter_interface|false
|
||||
*/
|
||||
public function get_converter_instance() {
|
||||
$currentconverter = $this->get('converter');
|
||||
|
||||
if ($currentconverter && class_exists($currentconverter)) {
|
||||
return new $currentconverter();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform data into a storable format.
|
||||
*
|
||||
* @param stdClass $data The data to be stored
|
||||
* @return $this
|
||||
*/
|
||||
protected function set_data($data) {
|
||||
$this->raw_set('data', json_encode($data));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform data into a storable format.
|
||||
*
|
||||
* @return stdClass The stored data
|
||||
*/
|
||||
protected function get_data() {
|
||||
$data = $this->raw_get('data');
|
||||
|
||||
if (!empty($data)) {
|
||||
return json_decode($data);
|
||||
}
|
||||
|
||||
return (object) [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file record base for use in the files table.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_file_record() {
|
||||
$file = $this->get_sourcefile();
|
||||
|
||||
if (!$file) {
|
||||
// If the source file was removed before we completed, we must return early.
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'contextid' => \context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'documentconversion',
|
||||
'itemid' => 0,
|
||||
'filepath' => "/" . $this->get('targetformat') . "/",
|
||||
'filename' => $file->get_contenthash(),
|
||||
];
|
||||
}
|
||||
}
|
247
files/classes/converter.php
Normal file
247
files/classes/converter.php
Normal file
@ -0,0 +1,247 @@
|
||||
<?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 core_files
|
||||
* @copyright 2017 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_files;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
use stored_file;
|
||||
|
||||
/**
|
||||
* Class for converting files between different formats using unoconv.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class converter {
|
||||
|
||||
/**
|
||||
* Get a list of enabled plugins and classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_enabled_plugins() {
|
||||
$plugins = \core\plugininfo\fileconverter::get_enabled_plugins();
|
||||
|
||||
$pluginclasses = [];
|
||||
foreach ($plugins as $plugin) {
|
||||
$pluginclasses[$plugin] = \core\plugininfo\fileconverter::get_classname($plugin);
|
||||
}
|
||||
|
||||
return $pluginclasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file_storage API.
|
||||
*
|
||||
* This allows for mocking of the file_storage API.
|
||||
*
|
||||
* @return file_storage
|
||||
*/
|
||||
protected function get_file_storage() {
|
||||
return get_file_storage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the conversion for a stored_file into a new format.
|
||||
*
|
||||
* @param stored_file $file The file to convert
|
||||
* @param string $format The desired target file format (file extension)
|
||||
* @param boolean $forcerefresh If true, the file will be converted every time (not cached).
|
||||
* @return conversion
|
||||
*/
|
||||
public function start_conversion(stored_file $file, $format, $forcerefresh = false) {
|
||||
$conversions = conversion::get_conversions_for_file($file, $format);
|
||||
|
||||
if ($forcerefresh || count($conversions) > 1) {
|
||||
while ($conversion = array_shift($conversions)) {
|
||||
if ($conversion->get('id')) {
|
||||
$conversion->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($conversions)) {
|
||||
$conversion = new conversion(0, (object) [
|
||||
'sourcefileid' => $file->get_id(),
|
||||
'targetformat' => $format,
|
||||
]);
|
||||
$conversion->create();
|
||||
} else {
|
||||
$conversion = array_shift($conversions);
|
||||
}
|
||||
|
||||
if ($conversion->get('status') !== conversion::STATUS_COMPLETE) {
|
||||
$this->poll_conversion($conversion);
|
||||
}
|
||||
|
||||
return $conversion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll for updates to the supplied conversion.
|
||||
*
|
||||
* @param conversion $conversion The conversion in progress
|
||||
* @return $this
|
||||
*/
|
||||
public function poll_conversion(conversion $conversion) {
|
||||
$format = $conversion->get('targetformat');
|
||||
$file = $conversion->get_sourcefile();
|
||||
|
||||
if ($conversion->get('status') == conversion::STATUS_IN_PROGRESS) {
|
||||
// The current conversion is in progress.
|
||||
// Check for updates.
|
||||
if ($instance = $conversion->get_converter_instance()) {
|
||||
$instance->poll_conversion_status($conversion);
|
||||
} else {
|
||||
// Unable to fetch the converter instance.
|
||||
// Reset the status back to PENDING so that it may be picked up again.
|
||||
$conversion->set('status', conversion::STATUS_PENDING);
|
||||
$conversion->update();
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the status.
|
||||
$status = $conversion->get('status');
|
||||
if ($status === conversion::STATUS_PENDING || $status === conversion::STATUS_FAILED) {
|
||||
// The current status is either pending or failed.
|
||||
// Attempt to pick up a new converter and convert the document.
|
||||
$from = \core_filetypes::get_file_extension($file->get_mimetype());
|
||||
$converters = $this->get_document_converter_classes($from, $format);
|
||||
$currentconverter = $this->get_next_converter($converters, $conversion->get('converter'));
|
||||
|
||||
if (!$currentconverter) {
|
||||
// No more converters available.
|
||||
$conversion->set('status', conversion::STATUS_FAILED);
|
||||
return $this;
|
||||
}
|
||||
|
||||
do {
|
||||
$conversion
|
||||
->set('converter', $currentconverter)
|
||||
->set('status', conversion::STATUS_IN_PROGRESS)
|
||||
->update();
|
||||
|
||||
$instance = $conversion->get_converter_instance();
|
||||
$instance->start_document_conversion($conversion);
|
||||
$failed = $conversion->get('status') === conversion::STATUS_FAILED;
|
||||
$currentconverter = $this->get_next_converter($converters, $currentconverter);
|
||||
} while ($failed && $currentconverter);
|
||||
|
||||
$conversion->update();
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the next converter to try.
|
||||
*
|
||||
* @param array $converters The list of converters to try
|
||||
* @param string|null $currentconverter The converter currently in use
|
||||
* @return string|false
|
||||
*/
|
||||
protected function get_next_converter($converters, $currentconverter = null) {
|
||||
if ($currentconverter) {
|
||||
$keys = array_keys($converters, $currentconverter);
|
||||
$key = $keys[0];
|
||||
if (isset($converters[$key + 1])) {
|
||||
return $converters[$key + 1];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (!empty($converters)) {
|
||||
return $converters[0];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the class for the preferred document converter.
|
||||
*
|
||||
* @param string $from The source target file (file extension)
|
||||
* @param string $to The desired target file format (file extension)
|
||||
* @return string The class for document conversion
|
||||
*/
|
||||
protected function get_document_converter_classes($from, $to) {
|
||||
$classes = [];
|
||||
|
||||
$converters = $this->get_enabled_plugins();
|
||||
foreach ($converters as $plugin => $classname) {
|
||||
if (!class_exists($classname)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$classname::are_requirements_met()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($classname::supports($from, $to)) {
|
||||
$classes[] = $classname;
|
||||
}
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether document conversion is supported for this file and target format.
|
||||
*
|
||||
* @param stored_file $file The file to convert
|
||||
* @param string $to The desired target file format (file extension)
|
||||
* @return bool Whether the target type can be converted
|
||||
*/
|
||||
public function can_convert_storedfile_to(stored_file $file, $to) {
|
||||
if ($file->is_directory()) {
|
||||
// Directories cannot be converted.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$file->get_filesize()) {
|
||||
// Empty files cannot be converted.
|
||||
return false;
|
||||
}
|
||||
|
||||
$from = \core_filetypes::get_file_extension($file->get_mimetype());
|
||||
if (!$from) {
|
||||
// No mimetype could be found. Unable to determine converter.
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->can_convert_format_to($from, $to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether document conversion is supported for this file and target format.
|
||||
*
|
||||
* @param string $from The source target file (file extension)
|
||||
* @param string $to The desired target file format (file extension)
|
||||
* @return bool Whether the target type can be converted
|
||||
*/
|
||||
public function can_convert_format_to($from, $to) {
|
||||
return !empty($this->get_document_converter_classes($from, $to));
|
||||
}
|
||||
|
||||
}
|
81
files/classes/converter_interface.php
Normal file
81
files/classes/converter_interface.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?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.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_files;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class for converting files between different file formats.
|
||||
*
|
||||
* @package docconvert_unoconv
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
interface converter_interface {
|
||||
|
||||
/**
|
||||
* Whether the plugin is configured and requirements are met.
|
||||
*
|
||||
* Note: This function may be called frequently and caching is advisable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function are_requirements_met();
|
||||
|
||||
/**
|
||||
* 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(conversion $conversion);
|
||||
|
||||
/**
|
||||
* Poll an existing conversion for status update.
|
||||
*
|
||||
* @param conversion $conversion The file to be converted
|
||||
* @return $this
|
||||
*/
|
||||
public function poll_conversion_status(conversion $conversion);
|
||||
|
||||
/**
|
||||
* Determine whether a conversion between the two supplied formats is achievable.
|
||||
*
|
||||
* Note: This function may be called frequently and caching is advisable.
|
||||
*
|
||||
* @param string $from The source type
|
||||
* @param string $to The destination type
|
||||
* @return bool
|
||||
*/
|
||||
public static function supports($from, $to);
|
||||
|
||||
/**
|
||||
* A list of the supported conversions.
|
||||
*
|
||||
* Note: This information is only displayed to administrators.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_supported_conversions();
|
||||
}
|
54
files/classes/task/conversion_cleanup_task.php
Normal file
54
files/classes/task/conversion_cleanup_task.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* A scheduled task to clear up old conversion records.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core_files\task;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* A scheduled task to clear up old conversion records.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class conversion_cleanup_task extends \core\task\scheduled_task {
|
||||
|
||||
/**
|
||||
* Get a descriptive name for this task (shown to admins).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return get_string('fileconversioncleanuptask', 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run task.
|
||||
*/
|
||||
public function execute() {
|
||||
\core_files\conversion::remove_old_conversion_records();
|
||||
}
|
||||
|
||||
}
|
362
files/converter/unoconv/classes/converter.php
Normal file
362
files/converter/unoconv/classes/converter.php
Normal 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());
|
||||
}
|
||||
}
|
40
files/converter/unoconv/db/install.php
Normal file
40
files/converter/unoconv/db/install.php
Normal 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);
|
||||
}
|
||||
}
|
39
files/converter/unoconv/lang/en/fileconverter_unoconv.php
Normal file
39
files/converter/unoconv/lang/en/fileconverter_unoconv.php
Normal 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.';
|
36
files/converter/unoconv/settings.php
Normal file
36
files/converter/unoconv/settings.php
Normal 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));
|
145
files/converter/unoconv/tests/converter_test.php
Normal file
145
files/converter/unoconv/tests/converter_test.php
Normal 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,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
@ -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();
|
29
files/converter/unoconv/version.php
Normal file
29
files/converter/unoconv/version.php
Normal 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).
|
415
files/tests/conversion_test.php
Normal file
415
files/tests/conversion_test.php
Normal file
@ -0,0 +1,415 @@
|
||||
<?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/>.
|
||||
|
||||
|
||||
/**
|
||||
* PHPUnit tests for conversion API.
|
||||
*
|
||||
* @package core_files
|
||||
* @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();
|
||||
|
||||
global $CFG;
|
||||
|
||||
use core_files\conversion;
|
||||
|
||||
/**
|
||||
* PHPUnit tests for conversion persistent.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_files_conversion_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Helper to create a stored file object with the given supplied content.
|
||||
*
|
||||
* @param string $filecontent The content of the mocked file
|
||||
* @param string $filename The file name to use in the stored_file
|
||||
* @param string $filerecord Any overrides to the filerecord
|
||||
* @return stored_file
|
||||
*/
|
||||
protected function create_stored_file($filecontent = 'content', $filename = 'testfile.txt', $filerecord = []) {
|
||||
$filerecord = array_merge([
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => $filename,
|
||||
], $filerecord);
|
||||
|
||||
$fs = get_file_storage();
|
||||
$file = $fs->create_file_from_string($filerecord, $filecontent);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_conversions_for_file returns an existing conversion
|
||||
* record with matching sourcefileid and targetformat.
|
||||
*/
|
||||
public function test_get_conversions_for_file_existing_conversion_incomplete() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
|
||||
$existing = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
$existing->create();
|
||||
|
||||
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
|
||||
|
||||
$this->assertCount(1, $conversions);
|
||||
|
||||
$conversion = array_shift($conversions);
|
||||
$conversionfile = $conversion->get_sourcefile();
|
||||
|
||||
$this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
|
||||
$this->assertFalse($conversion->get_destfile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_conversions_for_file returns an existing conversion
|
||||
* record with matching sourcefileid and targetformat when a second
|
||||
* conversion to a different format exists.
|
||||
*/
|
||||
public function test_get_conversions_for_file_existing_conversion_multiple_formats_incomplete() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
|
||||
$existing = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
$existing->create();
|
||||
|
||||
$second = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'doc',
|
||||
]);
|
||||
$second->create();
|
||||
|
||||
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
|
||||
|
||||
$this->assertCount(1, $conversions);
|
||||
|
||||
$conversion = array_shift($conversions);
|
||||
$conversionfile = $conversion->get_sourcefile();
|
||||
|
||||
$this->assertEquals($sourcefile->get_id(), $conversionfile->get_id());
|
||||
$this->assertFalse($conversion->get_destfile());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_conversions_for_file returns an existing conversion
|
||||
* record with matching sourcefileid and targetformat.
|
||||
*/
|
||||
public function test_get_conversions_for_file_existing_conversion_complete() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$destfile = $this->create_stored_file(
|
||||
'example content',
|
||||
$sourcefile->get_contenthash(),
|
||||
[
|
||||
'component' => 'core',
|
||||
'filearea' => 'documentconversion',
|
||||
'filepath' => '/pdf/',
|
||||
]);
|
||||
|
||||
$existing = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
'destfileid' => $destfile->get_id(),
|
||||
]);
|
||||
$existing->create();
|
||||
|
||||
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
|
||||
|
||||
// Only one file should be returned.
|
||||
$this->assertCount(1, $conversions);
|
||||
|
||||
$conversion = array_shift($conversions);
|
||||
|
||||
$this->assertEquals($sourcefile->get_id(), $conversion->get_sourcefile()->get_id());
|
||||
$this->assertEquals($destfile->get_id(), $conversion->get_destfile()->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_conversions_for_file returns an existing conversion
|
||||
* record with matching sourcefileid and targetformat.
|
||||
*/
|
||||
public function test_get_conversions_for_file_existing_conversion_multiple_formats_complete() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$destfile = $this->create_stored_file(
|
||||
'example content',
|
||||
$sourcefile->get_contenthash(),
|
||||
[
|
||||
'component' => 'core',
|
||||
'filearea' => 'documentconversion',
|
||||
'filepath' => '/pdf/',
|
||||
]);
|
||||
|
||||
$existing = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
'destfileid' => $destfile->get_id(),
|
||||
]);
|
||||
$existing->create();
|
||||
|
||||
$second = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'doc',
|
||||
]);
|
||||
$second->create();
|
||||
|
||||
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
|
||||
|
||||
// Only one file should be returned.
|
||||
$this->assertCount(1, $conversions);
|
||||
|
||||
$conversion = array_shift($conversions);
|
||||
|
||||
$this->assertEquals($sourcefile->get_id(), $conversion->get_sourcefile()->get_id());
|
||||
$this->assertEquals($destfile->get_id(), $conversion->get_destfile()->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_conversions_for_file returns an existing conversion
|
||||
* record does not exist, but the file has previously been converted.
|
||||
*/
|
||||
public function test_get_conversions_for_file_existing_target() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$destfile = $this->create_stored_file(
|
||||
'example content',
|
||||
$sourcefile->get_contenthash(),
|
||||
[
|
||||
'component' => 'core',
|
||||
'filearea' => 'documentconversion',
|
||||
'filepath' => '/pdf/',
|
||||
]);
|
||||
|
||||
$conversions = conversion::get_conversions_for_file($sourcefile, 'pdf');
|
||||
|
||||
$this->assertCount(1, $conversions);
|
||||
|
||||
$conversion = array_shift($conversions);
|
||||
$conversionsource = $conversion->get_sourcefile();
|
||||
$this->assertEquals($sourcefile->get_id(), $conversionsource->get_id());
|
||||
$conversiondest = $conversion->get_destfile();
|
||||
$this->assertEquals($destfile->get_id(), $conversiondest->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that set_sourcefile sets the correct fileid.
|
||||
*/
|
||||
public function test_set_sourcefile() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$conversion = new conversion(0, (object) []);
|
||||
|
||||
$conversion->set_sourcefile($sourcefile);
|
||||
|
||||
$this->assertEquals($sourcefile->get_id(), $conversion->get('sourcefileid'));
|
||||
$this->assertNull($conversion->get('destfileid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that store_destfile_from_path stores the file as expected.
|
||||
*/
|
||||
public function test_store_destfile_from_path() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$conversion = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
|
||||
$fixture = __FILE__;
|
||||
$conversion->store_destfile_from_path($fixture);
|
||||
|
||||
$destfile = $conversion->get_destfile();
|
||||
$this->assertEquals(file_get_contents($fixture), $destfile->get_content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that store_destfile_from_path stores the file as expected.
|
||||
*/
|
||||
public function test_store_destfile_from_path_delete_existing() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$conversion = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
|
||||
$record = [
|
||||
'contextid' => \context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'documentconversion',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/pdf/',
|
||||
];
|
||||
$existingfile = $this->create_stored_file('foo', $sourcefile->get_contenthash(), $record);
|
||||
|
||||
$fixture = __FILE__;
|
||||
$conversion->store_destfile_from_path($fixture);
|
||||
|
||||
$destfile = $conversion->get_destfile();
|
||||
$this->assertEquals(file_get_contents($fixture), $destfile->get_content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that store_destfile_from_path stores the file as expected.
|
||||
*/
|
||||
public function test_store_destfile_from_string() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$conversion = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
|
||||
$fixture = 'Example content';
|
||||
$conversion->store_destfile_from_string($fixture);
|
||||
|
||||
$destfile = $conversion->get_destfile();
|
||||
$this->assertEquals($fixture, $destfile->get_content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that store_destfile_from_string stores the file as expected when
|
||||
* an existing destfile is found.
|
||||
*/
|
||||
public function test_store_destfile_from_string_delete_existing() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$conversion = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
|
||||
$record = [
|
||||
'contextid' => \context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'documentconversion',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/pdf/',
|
||||
];
|
||||
$existingfile = $this->create_stored_file('foo', $sourcefile->get_contenthash(), $record);
|
||||
|
||||
$fixture = 'Example content';
|
||||
$conversion->store_destfile_from_string($fixture);
|
||||
|
||||
$destfile = $conversion->get_destfile();
|
||||
$this->assertEquals($fixture, $destfile->get_content());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the get_status functions cast the status to integer correctly.
|
||||
*/
|
||||
public function test_get_status() {
|
||||
$conversion = new conversion(0, (object) [
|
||||
'status' => (string) 1,
|
||||
]);
|
||||
|
||||
$this->assertInternalType('integer', $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_converter_instance returns false when no converter is set.
|
||||
*/
|
||||
public function test_get_converter_instance_none_set() {
|
||||
$conversion = new conversion(0, (object) []);
|
||||
$this->assertFalse($conversion->get_converter_instance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_converter_instance returns false when no valid converter is set.
|
||||
*/
|
||||
public function test_get_converter_instance_invalid_set() {
|
||||
$conversion = new conversion(0, (object) [
|
||||
'converter' => '\\fileconverter_not_a_valid_converter\\converter',
|
||||
]);
|
||||
$this->assertFalse($conversion->get_converter_instance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_converter_instance returns an instance when a valid converter is set.
|
||||
*/
|
||||
public function test_get_converter_instance_valid_set() {
|
||||
$conversion = new conversion(0, (object) [
|
||||
'converter' => \fileconverter_unoconv\converter::class,
|
||||
]);
|
||||
$this->assertInstanceOf(\fileconverter_unoconv\converter::class, $conversion->get_converter_instance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all old conversion records are removed periodically.
|
||||
*/
|
||||
public function test_remove_old_conversion_records_old() {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$conversion = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
$conversion->create();
|
||||
$DB->set_field(conversion::TABLE, 'timemodified', time() - YEARSECS);
|
||||
|
||||
conversion::remove_old_conversion_records();
|
||||
|
||||
$this->assertEquals(0, $DB->count_records(conversion::TABLE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that all old conversion records are removed periodically.
|
||||
*/
|
||||
public function test_remove_old_conversion_records_young() {
|
||||
$this->resetAfterTest();
|
||||
global $DB;
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
$conversion = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
$conversion->create();
|
||||
$DB->set_field(conversion::TABLE, 'timemodified', time() - DAYSECS);
|
||||
|
||||
conversion::remove_old_conversion_records();
|
||||
|
||||
$this->assertEquals(1, $DB->count_records(conversion::TABLE));
|
||||
}
|
||||
}
|
926
files/tests/converter_test.php
Normal file
926
files/tests/converter_test.php
Normal file
@ -0,0 +1,926 @@
|
||||
<?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/>.
|
||||
|
||||
|
||||
/**
|
||||
* PHPUnit tests for fileconverter API.
|
||||
*
|
||||
* @package core_files
|
||||
* @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();
|
||||
|
||||
global $CFG;
|
||||
|
||||
use core_files\conversion;
|
||||
use core_files\converter;
|
||||
|
||||
/**
|
||||
* PHPUnit tests for fileconverter API.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_files_converter_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Get a testable mock of the abstract files_converter class.
|
||||
*
|
||||
* @param array $mockedmethods A list of methods you intend to override
|
||||
* If no methods are specified, only abstract functions are mocked.
|
||||
* @return \core_files\converter
|
||||
*/
|
||||
protected function get_testable_mock($mockedmethods = []) {
|
||||
$converter = $this->getMockBuilder(\core_files\converter::class)
|
||||
->setMethods($mockedmethods)
|
||||
->getMockForAbstractClass();
|
||||
|
||||
return $converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a testable mock of the conversion.
|
||||
*
|
||||
* @param array $mockedmethods A list of methods you intend to override
|
||||
* @return \core_files\conversion
|
||||
*/
|
||||
protected function get_testable_conversion($mockedmethods = []) {
|
||||
$conversion = $this->getMockBuilder(\core_files\conversion::class)
|
||||
->setMethods($mockedmethods)
|
||||
->setConstructorArgs([0, (object) []])
|
||||
->getMock();
|
||||
|
||||
return $conversion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a testable mock of the abstract files_converter class.
|
||||
*
|
||||
* @param array $mockedmethods A list of methods you intend to override
|
||||
* If no methods are specified, only abstract functions are mocked.
|
||||
* @return \core_files\converter_interface
|
||||
*/
|
||||
protected function get_mocked_converter($mockedmethods = []) {
|
||||
$converter = $this->getMockBuilder(\core_files\converter_interface::class)
|
||||
->setMethods($mockedmethods)
|
||||
->getMockForAbstractClass();
|
||||
|
||||
return $converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a stored file objectw with the given supplied content.
|
||||
*
|
||||
* @param string $filecontent The content of the mocked file
|
||||
* @param string $filename The file name to use in the stored_file
|
||||
* @param array $mockedmethods A list of methods you intend to override
|
||||
* If no methods are specified, only abstract functions are mocked.
|
||||
* @return stored_file
|
||||
*/
|
||||
protected function get_stored_file($filecontent = 'content', $filename = null, $filerecord = [], $mockedmethods = null) {
|
||||
global $CFG;
|
||||
|
||||
$contenthash = sha1($filecontent);
|
||||
if (empty($filename)) {
|
||||
$filename = $contenthash;
|
||||
}
|
||||
|
||||
$filerecord['contenthash'] = $contenthash;
|
||||
$filerecord['filesize'] = strlen($filecontent);
|
||||
$filerecord['filename'] = $filename;
|
||||
$filerecord['id'] = 42;
|
||||
|
||||
$file = $this->getMockBuilder(stored_file::class)
|
||||
->setMethods($mockedmethods)
|
||||
->setConstructorArgs([get_file_storage(), (object) $filerecord])
|
||||
->getMock();
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to create a stored file object with the given supplied content.
|
||||
*
|
||||
* @param string $filecontent The content of the mocked file
|
||||
* @param string $filename The file name to use in the stored_file
|
||||
* @param string $filerecord Any overrides to the filerecord
|
||||
* @return stored_file
|
||||
*/
|
||||
protected function create_stored_file($filecontent = 'content', $filename = 'testfile.txt', $filerecord = []) {
|
||||
$filerecord = array_merge([
|
||||
'contextid' => context_system::instance()->id,
|
||||
'component' => 'core',
|
||||
'filearea' => 'unittest',
|
||||
'itemid' => 0,
|
||||
'filepath' => '/',
|
||||
'filename' => $filename,
|
||||
], $filerecord);
|
||||
|
||||
$fs = get_file_storage();
|
||||
$file = $fs->create_file_from_string($filerecord, $filecontent);
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mock of the file_storage API.
|
||||
*
|
||||
* @param array $mockedmethods A list of methods you intend to override
|
||||
* @return file_storage
|
||||
*/
|
||||
protected function get_file_storage_mock($mockedmethods = []) {
|
||||
$fs = $this->getMockBuilder(\file_storage::class)
|
||||
->setMethods($mockedmethods)
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
return $fs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the start_conversion function.
|
||||
*/
|
||||
public function test_start_conversion_existing_single() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
|
||||
$first = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
$first->create();
|
||||
|
||||
$converter = $this->get_testable_mock(['poll_conversion']);
|
||||
$conversion = $converter->start_conversion($sourcefile, 'pdf', false);
|
||||
|
||||
// The old conversions should still be present and match the one returned.
|
||||
$this->assertEquals($first->get('id'), $conversion->get('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the start_conversion function.
|
||||
*/
|
||||
public function test_start_conversion_existing_multiple() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
|
||||
$first = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
$first->create();
|
||||
|
||||
$second = new conversion(0, (object) [
|
||||
'sourcefileid' => $sourcefile->get_id(),
|
||||
'targetformat' => 'pdf',
|
||||
]);
|
||||
$second->create();
|
||||
|
||||
$converter = $this->get_testable_mock(['poll_conversion']);
|
||||
$conversion = $converter->start_conversion($sourcefile, 'pdf', false);
|
||||
|
||||
// The old conversions should have been removed.
|
||||
$this->assertFalse(conversion::get_record(['id' => $first->get('id')]));
|
||||
$this->assertFalse(conversion::get_record(['id' => $second->get('id')]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the start_conversion function.
|
||||
*/
|
||||
public function test_start_conversion_no_existing() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$sourcefile = $this->create_stored_file();
|
||||
|
||||
$converter = $this->get_testable_mock(['poll_conversion']);
|
||||
$conversion = $converter->start_conversion($sourcefile, 'pdf', false);
|
||||
|
||||
$this->assertInstanceOf(\core_files\conversion::class, $conversion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get_document_converter_classes function with no enabled plugins.
|
||||
*/
|
||||
public function test_get_document_converter_classes_no_plugins() {
|
||||
$converter = $this->get_testable_mock(['get_enabled_plugins']);
|
||||
$converter->method('get_enabled_plugins')->willReturn([]);
|
||||
|
||||
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
|
||||
$method->setAccessible(true);
|
||||
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
|
||||
$this->assertEmpty($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get_document_converter_classes function when no class was found.
|
||||
*/
|
||||
public function test_get_document_converter_classes_plugin_class_not_found() {
|
||||
$converter = $this->get_testable_mock(['get_enabled_plugins']);
|
||||
$converter->method('get_enabled_plugins')->willReturn([
|
||||
'noplugin' => '\not\a\real\plugin',
|
||||
]);
|
||||
|
||||
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
|
||||
$method->setAccessible(true);
|
||||
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
|
||||
$this->assertEmpty($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get_document_converter_classes function when the returned classes do not meet requirements.
|
||||
*/
|
||||
public function test_get_document_converter_classes_plugin_class_requirements_not_met() {
|
||||
$plugin = $this->getMockBuilder(\core_file_converter_requirements_not_met_test::class)
|
||||
->setMethods()
|
||||
->getMock();
|
||||
|
||||
$converter = $this->get_testable_mock(['get_enabled_plugins']);
|
||||
$converter->method('get_enabled_plugins')->willReturn([
|
||||
'test_plugin' => get_class($plugin),
|
||||
]);
|
||||
|
||||
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
|
||||
$method->setAccessible(true);
|
||||
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
|
||||
$this->assertEmpty($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get_document_converter_classes function when the returned classes do not meet requirements.
|
||||
*/
|
||||
public function test_get_document_converter_classes_plugin_class_met_not_supported() {
|
||||
$plugin = $this->getMockBuilder(\core_file_converter_type_not_supported_test::class)
|
||||
->setMethods()
|
||||
->getMock();
|
||||
|
||||
$converter = $this->get_testable_mock(['get_enabled_plugins']);
|
||||
$converter->method('get_enabled_plugins')->willReturn([
|
||||
'test_plugin' => get_class($plugin),
|
||||
]);
|
||||
|
||||
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
|
||||
$method->setAccessible(true);
|
||||
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
|
||||
$this->assertEmpty($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the get_document_converter_classes function when the returned classes do not meet requirements.
|
||||
*/
|
||||
public function test_get_document_converter_classes_plugin_class_met_and_supported() {
|
||||
$plugin = $this->getMockBuilder(\core_file_converter_type_supported_test::class)
|
||||
->setMethods()
|
||||
->getMock();
|
||||
$classname = get_class($plugin);
|
||||
|
||||
$converter = $this->get_testable_mock(['get_enabled_plugins']);
|
||||
$converter->method('get_enabled_plugins')->willReturn([
|
||||
'test_plugin' => $classname,
|
||||
]);
|
||||
|
||||
$method = new ReflectionMethod(\core_files\converter::class, 'get_document_converter_classes');
|
||||
$method->setAccessible(true);
|
||||
$result = $method->invokeArgs($converter, ['docx', 'pdf']);
|
||||
$this->assertCount(1, $result);
|
||||
$this->assertNotFalse(array_search($classname, $result));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_storedfile_to function with a directory.
|
||||
*/
|
||||
public function test_can_convert_storedfile_to_directory() {
|
||||
$converter = $this->get_testable_mock();
|
||||
|
||||
// A file with filename '.' is a directory.
|
||||
$file = $this->get_stored_file('', '.');
|
||||
|
||||
$this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_storedfile_to function with an empty file.
|
||||
*/
|
||||
public function test_can_convert_storedfile_to_emptyfile() {
|
||||
$converter = $this->get_testable_mock();
|
||||
|
||||
// A file with filename '.' is a directory.
|
||||
$file = $this->get_stored_file('');
|
||||
|
||||
$this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_storedfile_to function with a file with indistinguished mimetype.
|
||||
*/
|
||||
public function test_can_convert_storedfile_to_no_mimetype() {
|
||||
$converter = $this->get_testable_mock();
|
||||
|
||||
// A file with filename '.' is a directory.
|
||||
$file = $this->get_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$this->assertFalse($converter->can_convert_storedfile_to($file, 'target'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_storedfile_to function with a file with indistinguished mimetype.
|
||||
*/
|
||||
public function test_can_convert_storedfile_to_docx() {
|
||||
$returnvalue = (object) [];
|
||||
|
||||
$converter = $this->get_testable_mock([
|
||||
'can_convert_format_to'
|
||||
]);
|
||||
|
||||
$types = \core_filetypes::get_types();
|
||||
|
||||
// A file with filename '.' is a directory.
|
||||
$file = $this->get_stored_file('example content', 'example', [
|
||||
'mimetype' => $types['docx']['type'],
|
||||
]);
|
||||
|
||||
$converter->expects($this->once())
|
||||
->method('can_convert_format_to')
|
||||
->willReturn($returnvalue);
|
||||
|
||||
$result = $converter->can_convert_storedfile_to($file, 'target');
|
||||
$this->assertEquals($returnvalue, $result);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test the can_convert_format_to function.
|
||||
*/
|
||||
public function test_can_convert_format_to_found() {
|
||||
$converter = $this->get_testable_mock(['get_document_converter_classes']);
|
||||
|
||||
$mock = $this->get_mocked_converter();
|
||||
|
||||
$converter->method('get_document_converter_classes')
|
||||
->willReturn([$mock]);
|
||||
|
||||
$result = $converter->can_convert_format_to('from', 'to');
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_format_to function.
|
||||
*/
|
||||
public function test_can_convert_format_to_not_found() {
|
||||
$converter = $this->get_testable_mock(['get_document_converter_classes']);
|
||||
|
||||
$converter->method('get_document_converter_classes')
|
||||
->willReturn([]);
|
||||
|
||||
$result = $converter->can_convert_format_to('from', 'to');
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_storedfile_to function with an empty file.
|
||||
*/
|
||||
public function test_poll_conversion_in_progress() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$converter = $this->get_testable_mock([
|
||||
'get_document_converter_classes',
|
||||
'get_next_converter',
|
||||
]);
|
||||
|
||||
$converter->method('get_document_converter_classes')->willReturn([]);
|
||||
$converter->method('get_next_converter')->willReturn(false);
|
||||
$file = $this->create_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$conversion = $this->get_testable_conversion([
|
||||
'get_converter_instance',
|
||||
]);
|
||||
$conversion->set_sourcefile($file);
|
||||
$conversion->set('targetformat', 'target');
|
||||
$conversion->set('status', conversion::STATUS_IN_PROGRESS);
|
||||
|
||||
$converterinstance = $this->get_mocked_converter([
|
||||
'poll_conversion_status',
|
||||
]);
|
||||
$converterinstance->expects($this->once())
|
||||
->method('poll_conversion_status');
|
||||
$conversion->method('get_converter_instance')->willReturn($converterinstance);
|
||||
|
||||
$converter->poll_conversion($conversion);
|
||||
|
||||
$this->assertEquals(conversion::STATUS_IN_PROGRESS, $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test poll_conversion with an in-progress conversion where we are
|
||||
* unable to instantiate the converter instance.
|
||||
*/
|
||||
public function test_poll_conversion_in_progress_fail() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$converter = $this->get_testable_mock([
|
||||
'get_document_converter_classes',
|
||||
'get_next_converter',
|
||||
]);
|
||||
|
||||
$converter->method('get_document_converter_classes')->willReturn([]);
|
||||
$converter->method('get_next_converter')->willReturn(false);
|
||||
$file = $this->create_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$conversion = $this->get_testable_conversion([
|
||||
'get_converter_instance',
|
||||
]);
|
||||
$conversion->set_sourcefile($file);
|
||||
$conversion->set('targetformat', 'target');
|
||||
$conversion->set('status', conversion::STATUS_IN_PROGRESS);
|
||||
$conversion->create();
|
||||
|
||||
$conversion->method('get_converter_instance')->willReturn(false);
|
||||
|
||||
$converter->poll_conversion($conversion);
|
||||
|
||||
$this->assertEquals(conversion::STATUS_FAILED, $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_storedfile_to function with an empty file.
|
||||
*/
|
||||
public function test_poll_conversion_none_supported() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$converter = $this->get_testable_mock([
|
||||
'get_document_converter_classes',
|
||||
'get_next_converter',
|
||||
]);
|
||||
|
||||
$converter->method('get_document_converter_classes')->willReturn([]);
|
||||
$converter->method('get_next_converter')->willReturn(false);
|
||||
$file = $this->create_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$conversion = new conversion(0, (object) [
|
||||
'sourcefileid' => $file->get_id(),
|
||||
'targetformat' => 'target',
|
||||
]);
|
||||
|
||||
$converter->poll_conversion($conversion);
|
||||
|
||||
$this->assertEquals(conversion::STATUS_FAILED, $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_storedfile_to function with an empty file.
|
||||
*/
|
||||
public function test_poll_conversion_pick_first() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$converterinstance = $this->get_mocked_converter([
|
||||
'start_document_conversion',
|
||||
'poll_conversion_status',
|
||||
]);
|
||||
$converter = $this->get_testable_mock([
|
||||
'get_document_converter_classes',
|
||||
'get_next_converter',
|
||||
]);
|
||||
|
||||
$converter->method('get_document_converter_classes')->willReturn([]);
|
||||
$converter->method('get_next_converter')->willReturn(get_class($converterinstance));
|
||||
$file = $this->create_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$conversion = $this->get_testable_conversion([
|
||||
'get_converter_instance',
|
||||
]);
|
||||
$conversion->set_sourcefile($file);
|
||||
$conversion->set('targetformat', 'target');
|
||||
$conversion->set('status', conversion::STATUS_PENDING);
|
||||
$conversion->create();
|
||||
|
||||
$conversion->method('get_converter_instance')->willReturn($converterinstance);
|
||||
|
||||
$converterinstance->expects($this->once())
|
||||
->method('start_document_conversion');
|
||||
$converterinstance->expects($this->never())
|
||||
->method('poll_conversion_status');
|
||||
|
||||
$converter->poll_conversion($conversion);
|
||||
|
||||
$this->assertEquals(conversion::STATUS_IN_PROGRESS, $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the can_convert_storedfile_to function with an empty file.
|
||||
*/
|
||||
public function test_poll_conversion_pick_subsequent() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$converterinstance = $this->get_mocked_converter([
|
||||
'start_document_conversion',
|
||||
'poll_conversion_status',
|
||||
]);
|
||||
$converterinstance2 = $this->get_mocked_converter([
|
||||
'start_document_conversion',
|
||||
'poll_conversion_status',
|
||||
]);
|
||||
$converter = $this->get_testable_mock([
|
||||
'get_document_converter_classes',
|
||||
'get_next_converter',
|
||||
]);
|
||||
|
||||
$converter->method('get_document_converter_classes')->willReturn([]);
|
||||
$converter->method('get_next_converter')
|
||||
->will($this->onConsecutiveCalls(
|
||||
get_class($converterinstance),
|
||||
get_class($converterinstance2)
|
||||
));
|
||||
|
||||
$file = $this->create_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$conversion = $this->get_testable_conversion([
|
||||
'get_converter_instance',
|
||||
'get_status',
|
||||
]);
|
||||
$conversion->set_sourcefile($file);
|
||||
$conversion->set('targetformat', 'target');
|
||||
$conversion->set('status', conversion::STATUS_PENDING);
|
||||
$conversion->create();
|
||||
|
||||
$conversion->method('get_status')
|
||||
->will($this->onConsecutiveCalls(
|
||||
// Initial status check.
|
||||
conversion::STATUS_PENDING,
|
||||
// Second check to make sure it's still pending after polling.
|
||||
conversion::STATUS_PENDING,
|
||||
// First one fails.
|
||||
conversion::STATUS_FAILED,
|
||||
// Second one succeeds.
|
||||
conversion::STATUS_COMPLETE,
|
||||
// And the final result checked in this unit test.
|
||||
conversion::STATUS_COMPLETE
|
||||
));
|
||||
|
||||
$conversion->method('get_converter_instance')
|
||||
->will($this->onConsecutiveCalls(
|
||||
$converterinstance,
|
||||
$converterinstance2
|
||||
));
|
||||
|
||||
$converterinstance->expects($this->once())
|
||||
->method('start_document_conversion');
|
||||
$converterinstance->expects($this->never())
|
||||
->method('poll_conversion_status');
|
||||
$converterinstance2->expects($this->once())
|
||||
->method('start_document_conversion');
|
||||
$converterinstance2->expects($this->never())
|
||||
->method('poll_conversion_status');
|
||||
|
||||
$converter->poll_conversion($conversion);
|
||||
|
||||
$this->assertEquals(conversion::STATUS_COMPLETE, $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the start_conversion with a single converter which succeeds.
|
||||
*/
|
||||
public function test_start_conversion_one_supported_success() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$converter = $this->get_testable_mock([
|
||||
'get_document_converter_classes',
|
||||
]);
|
||||
|
||||
$converter->method('get_document_converter_classes')
|
||||
->willReturn([\core_file_converter_type_successful::class]);
|
||||
|
||||
$file = $this->create_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$conversion = $converter->start_conversion($file, 'target');
|
||||
|
||||
$this->assertEquals(conversion::STATUS_COMPLETE, $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the start_conversion with a single converter which failes.
|
||||
*/
|
||||
public function test_start_conversion_one_supported_failure() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$converter = $this->get_testable_mock([
|
||||
'get_document_converter_classes',
|
||||
]);
|
||||
|
||||
$mock = $this->get_mocked_converter(['start_document_conversion']);
|
||||
$converter->method('get_document_converter_classes')
|
||||
->willReturn([\core_file_converter_type_failed::class]);
|
||||
|
||||
$file = $this->create_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$conversion = $converter->start_conversion($file, 'target');
|
||||
|
||||
$this->assertEquals(conversion::STATUS_FAILED, $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the start_conversion with two converters - fail, then succeed.
|
||||
*/
|
||||
public function test_start_conversion_two_supported() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$converter = $this->get_testable_mock([
|
||||
'get_document_converter_classes',
|
||||
]);
|
||||
|
||||
$mock = $this->get_mocked_converter(['start_document_conversion']);
|
||||
$converter->method('get_document_converter_classes')
|
||||
->willReturn([
|
||||
\core_file_converter_type_failed::class,
|
||||
\core_file_converter_type_successful::class,
|
||||
]);
|
||||
|
||||
$file = $this->create_stored_file('example content', 'example', [
|
||||
'mimetype' => null,
|
||||
]);
|
||||
|
||||
$conversion = $converter->start_conversion($file, 'target');
|
||||
|
||||
$this->assertEquals(conversion::STATUS_COMPLETE, $conversion->get('status'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_next_converter returns false when no converters are available.
|
||||
*/
|
||||
public function test_get_next_converter_no_converters() {
|
||||
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
|
||||
$rcm->setAccessible(true);
|
||||
|
||||
$converter = new \core_files\converter();
|
||||
$result = $rcm->invoke($converter, [], null);
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_next_converter returns false when already on the
|
||||
* only converter.
|
||||
*/
|
||||
public function test_get_next_converter_only_converters() {
|
||||
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
|
||||
$rcm->setAccessible(true);
|
||||
|
||||
$converter = new converter();
|
||||
$result = $rcm->invoke($converter, ['example'], 'example');
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_next_converter returns false when already on the
|
||||
* last converter.
|
||||
*/
|
||||
public function test_get_next_converter_last_converters() {
|
||||
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
|
||||
$rcm->setAccessible(true);
|
||||
|
||||
$converter = new converter();
|
||||
$result = $rcm->invoke($converter, ['foo', 'example'], 'example');
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that get_next_converter returns the next vlaue when in a
|
||||
* current converter.
|
||||
*/
|
||||
public function test_get_next_converter_middle_converters() {
|
||||
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
|
||||
$rcm->setAccessible(true);
|
||||
|
||||
$converter = new converter();
|
||||
$result = $rcm->invoke($converter, ['foo', 'bar', 'baz', 'example'], 'bar');
|
||||
$this->assertEquals('baz', $result);
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Ensure that get_next_converter returns the next vlaue when in a
|
||||
* current converter.
|
||||
*/
|
||||
public function test_get_next_converter_first() {
|
||||
$rcm = new \ReflectionMethod(converter::class, 'get_next_converter');
|
||||
$rcm->setAccessible(true);
|
||||
|
||||
$converter = new converter();
|
||||
$result = $rcm->invoke($converter, ['foo', 'bar', 'baz', 'example']);
|
||||
$this->assertEquals('foo', $result);
|
||||
}
|
||||
}
|
||||
|
||||
class core_file_converter_requirements_test_base implements \core_files\converter_interface {
|
||||
|
||||
/**
|
||||
* Whether the plugin is configured and requirements are met.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function are_requirements_met() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 conversion
|
||||
*/
|
||||
public function start_document_conversion(conversion $conversion) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll an existing conversion for status update.
|
||||
*
|
||||
* @param conversion $conversion The file to be converted
|
||||
* @return conversion
|
||||
*/
|
||||
public function poll_conversion_status(conversion $conversion) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 false;
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of the supported conversions.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_supported_conversions() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class for converter support with requirements are not met.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_file_converter_requirements_not_met_test extends core_file_converter_requirements_test_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class for converter support with requirements met and conversion not supported.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_file_converter_type_not_supported_test extends core_file_converter_requirements_test_base {
|
||||
|
||||
/**
|
||||
* Whether the plugin is configured and requirements are met.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function are_requirements_met() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class for converter support with requirements met and conversion supported.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_file_converter_type_supported_test extends core_file_converter_requirements_test_base {
|
||||
|
||||
/**
|
||||
* Whether the plugin is configured and requirements are met.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function are_requirements_met() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class for converter support with requirements met and successful conversion.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_file_converter_type_successful extends core_file_converter_requirements_test_base {
|
||||
|
||||
/**
|
||||
* 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 conversion
|
||||
*/
|
||||
public function start_document_conversion(conversion $conversion) {
|
||||
$conversion->set('status', conversion::STATUS_COMPLETE);
|
||||
|
||||
return $conversion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test class for converter support with requirements met and failed conversion.
|
||||
*
|
||||
* @package core_files
|
||||
* @copyright 2017 Andrew nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_file_converter_type_failed extends core_file_converter_requirements_test_base {
|
||||
|
||||
/**
|
||||
* Whether the plugin is configured and requirements are met.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function are_requirements_met() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 conversion
|
||||
*/
|
||||
public function start_document_conversion(conversion $conversion) {
|
||||
$conversion->set('status', conversion::STATUS_FAILED);
|
||||
|
||||
return $conversion;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 true;
|
||||
}
|
||||
}
|
@ -529,6 +529,7 @@ $string['experimentalsettings'] = 'Experimental settings';
|
||||
$string['extendedusernamechars'] = 'Allow extended characters in usernames';
|
||||
$string['extramemorylimit'] = 'Extra PHP memory limit';
|
||||
$string['fatalsessionautostart'] = '<p>Serious configuration error detected, please notify server administrator.</p><p> To operate properly, Moodle requires that administrator changes PHP settings.</p><p><code>session.auto_start</code> must be set to <code>off</code>.</p><p>This setting is controlled by editing <code>php.ini</code>, Apache/IIS <br />configuration or <code>.htaccess</code> file on the server.</p>';
|
||||
$string['fileconversioncleanuptask'] = 'Cleanup of temporary records for file conversions.';
|
||||
$string['filecreated'] = 'New file created';
|
||||
$string['filestoredin'] = 'Save file into folder :';
|
||||
$string['filestoredinhelp'] = 'Where the file will be stored';
|
||||
@ -823,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';
|
||||
@ -1131,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.';
|
||||
@ -1245,4 +1243,4 @@ $string['cacheapplicationhelp'] = 'Cached items are shared among all users and e
|
||||
$string['mobile'] = 'Mobile';
|
||||
// Deprecated since Moodle 3.3.
|
||||
$string['loginpasswordautocomplete'] = 'Prevent password autocompletion on login form';
|
||||
$string['loginpasswordautocomplete_help'] = 'If enabled, users are not allowed to save their account password in their browser.';
|
||||
$string['loginpasswordautocomplete_help'] = 'If enabled, users are not allowed to save their account password in their browser.';
|
||||
|
@ -54,6 +54,7 @@ $string['err_response_http_code'] = 'Unable to fetch available updates data - un
|
||||
$string['filterall'] = 'Show all';
|
||||
$string['filtercontribonly'] = 'Show additional plugins only';
|
||||
$string['filterupdatesonly'] = 'Show updateable only';
|
||||
$string['isenabled'] = 'Enabled?';
|
||||
$string['misdepinfoplugin'] = 'Plugin info';
|
||||
$string['misdepinfoversion'] = 'Version info';
|
||||
$string['misdepsavail'] = 'Available missing dependencies';
|
||||
@ -104,6 +105,7 @@ $string['status_nodb'] = 'No database';
|
||||
$string['status_upgrade'] = 'To be upgraded';
|
||||
$string['status_uptodate'] = 'Installed';
|
||||
$string['supportedmoodleversions'] = 'Supported Moodle versions';
|
||||
$string['supportedconversions'] = 'Supported document conversions';
|
||||
$string['systemname'] = 'Identifier';
|
||||
$string['type_antivirus'] = 'Antivirus plugin';
|
||||
$string['type_antivirus_plural'] = 'Antivirus plugins';
|
||||
@ -127,6 +129,8 @@ $string['type_editor'] = 'Editor';
|
||||
$string['type_editor_plural'] = 'Editors';
|
||||
$string['type_enrol'] = 'Enrolment method';
|
||||
$string['type_enrol_plural'] = 'Enrolment methods';
|
||||
$string['type_fileconverter'] = 'Document converter';
|
||||
$string['type_fileconverter_plural'] = 'Document converters';
|
||||
$string['type_filter'] = 'Text filter';
|
||||
$string['type_filter_plural'] = 'Text filters';
|
||||
$string['type_format'] = 'Course format';
|
||||
|
226
lib/adminlib.php
226
lib/adminlib.php
@ -7388,6 +7388,232 @@ class admin_page_managefilters extends admin_externalpage {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
|
||||
* Requires a get_rank method on the plugininfo class for sorting.
|
||||
*
|
||||
* @copyright 2017 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
abstract class admin_setting_manage_plugins extends admin_setting {
|
||||
|
||||
/**
|
||||
* Get the admin settings section name (just a unique string)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_section_name() {
|
||||
return 'manage' . $this->get_plugin_type() . 'plugins';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the admin settings section title (use get_string).
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_section_title();
|
||||
|
||||
/**
|
||||
* Get the type of plugin to manage.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_plugin_type();
|
||||
|
||||
/**
|
||||
* Get the name of the second column.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_info_column_name() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of plugin to manage.
|
||||
*
|
||||
* @param plugininfo The plugin info class.
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_info_column($plugininfo);
|
||||
|
||||
/**
|
||||
* Calls parent::__construct with specific arguments
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->nosave = true;
|
||||
parent::__construct($this->get_section_name(), $this->get_section_title(), '', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true, does nothing
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function get_setting() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns true, does nothing
|
||||
*
|
||||
* @return true
|
||||
*/
|
||||
public function get_defaultsetting() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Always returns '', does not write anything
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return string Always returns ''
|
||||
*/
|
||||
public function write_setting($data) {
|
||||
// Do not write any setting.
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $query is one of the available plugins of this type
|
||||
*
|
||||
* @param string $query The string to search for
|
||||
* @return bool Returns true if found, false if not
|
||||
*/
|
||||
public function is_related($query) {
|
||||
if (parent::is_related($query)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$query = core_text::strtolower($query);
|
||||
$plugins = core_plugin_manager::instance()->get_plugins_of_type($this->get_plugin_type());
|
||||
foreach ($plugins as $name => $plugin) {
|
||||
$localised = $plugin->displayname;
|
||||
if (strpos(core_text::strtolower($name), $query) !== false) {
|
||||
return true;
|
||||
}
|
||||
if (strpos(core_text::strtolower($localised), $query) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL for the management page for this plugintype.
|
||||
*
|
||||
* @return moodle_url
|
||||
*/
|
||||
protected function get_manage_url() {
|
||||
return new moodle_url('/admin/updatesetting.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the HTML to display the control.
|
||||
*
|
||||
* @param string $data Unused
|
||||
* @param string $query
|
||||
* @return string
|
||||
*/
|
||||
public function output_html($data, $query = '') {
|
||||
global $CFG, $OUTPUT, $DB, $PAGE;
|
||||
|
||||
$context = (object) [
|
||||
'manageurl' => new moodle_url($this->get_manage_url(), [
|
||||
'type' => $this->get_plugin_type(),
|
||||
'sesskey' => sesskey(),
|
||||
]),
|
||||
'infocolumnname' => $this->get_info_column_name(),
|
||||
'plugins' => [],
|
||||
];
|
||||
|
||||
$pluginmanager = core_plugin_manager::instance();
|
||||
$allplugins = $pluginmanager->get_plugins_of_type($this->get_plugin_type());
|
||||
$enabled = $pluginmanager->get_enabled_plugins($this->get_plugin_type());
|
||||
$plugins = array_merge($enabled, $allplugins);
|
||||
foreach ($plugins as $key => $plugin) {
|
||||
$pluginlink = new moodle_url($context->manageurl, ['plugin' => $key]);
|
||||
|
||||
$pluginkey = (object) [
|
||||
'plugin' => $plugin->displayname,
|
||||
'enabled' => $plugin->is_enabled(),
|
||||
'togglelink' => '',
|
||||
'moveuplink' => '',
|
||||
'movedownlink' => '',
|
||||
'settingslink' => $plugin->get_settings_url(),
|
||||
'uninstalllink' => '',
|
||||
'info' => '',
|
||||
];
|
||||
|
||||
// Enable/Disable link.
|
||||
$togglelink = new moodle_url($pluginlink);
|
||||
if ($plugin->is_enabled()) {
|
||||
$toggletarget = false;
|
||||
$togglelink->param('action', 'disable');
|
||||
|
||||
if (count($context->plugins)) {
|
||||
// This is not the first plugin.
|
||||
$pluginkey->moveuplink = new moodle_url($pluginlink, ['action' => 'up']);
|
||||
}
|
||||
|
||||
if (count($enabled) > count($context->plugins) + 1) {
|
||||
// This is not the last plugin.
|
||||
$pluginkey->movedownlink = new moodle_url($pluginlink, ['action' => 'down']);
|
||||
}
|
||||
|
||||
$pluginkey->info = $this->get_info_column($plugin);
|
||||
} else {
|
||||
$toggletarget = true;
|
||||
$togglelink->param('action', 'enable');
|
||||
}
|
||||
|
||||
$pluginkey->toggletarget = $toggletarget;
|
||||
$pluginkey->togglelink = $togglelink;
|
||||
|
||||
$frankenstyle = $plugin->type . '_' . $plugin->name;
|
||||
if ($uninstalllink = core_plugin_manager::instance()->get_uninstall_url($frankenstyle, 'manage')) {
|
||||
// This plugin supports uninstallation.
|
||||
$pluginkey->uninstalllink = $uninstalllink;
|
||||
}
|
||||
|
||||
if (!empty($this->get_info_column_name())) {
|
||||
// This plugintype has an info column.
|
||||
$pluginkey->info = $this->get_info_column($plugin);
|
||||
}
|
||||
|
||||
$context->plugins[] = $pluginkey;
|
||||
}
|
||||
|
||||
$str = $OUTPUT->render_from_template('core_admin/setting_manage_plugins', $context);
|
||||
return highlight($query, $str);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic class for managing plugins in a table that allows re-ordering and enable/disable of each plugin.
|
||||
* Requires a get_rank method on the plugininfo class for sorting.
|
||||
*
|
||||
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class admin_setting_manage_fileconverter_plugins extends admin_setting_manage_plugins {
|
||||
public function get_section_title() {
|
||||
return get_string('type_fileconverter_plural', 'plugin');
|
||||
}
|
||||
|
||||
public function get_plugin_type() {
|
||||
return 'fileconverter';
|
||||
}
|
||||
|
||||
public function get_info_column_name() {
|
||||
return get_string('supportedconversions', 'plugin');
|
||||
}
|
||||
|
||||
public function get_info_column($plugininfo) {
|
||||
return $plugininfo->get_supported_conversions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special class for media player plugins management.
|
||||
*
|
||||
|
@ -439,6 +439,7 @@ $cache = '.var_export($cache, true).';
|
||||
'enrol' => $CFG->dirroot.'/enrol',
|
||||
'error' => null,
|
||||
'filepicker' => null,
|
||||
'fileconverter' => $CFG->dirroot.'/files/converter',
|
||||
'files' => $CFG->dirroot.'/files',
|
||||
'filters' => null,
|
||||
//'fonts' => null, // Bogus.
|
||||
@ -525,6 +526,7 @@ $cache = '.var_export($cache, true).';
|
||||
'tool' => $CFG->dirroot.'/'.$CFG->admin.'/tool',
|
||||
'cachestore' => $CFG->dirroot.'/cache/stores',
|
||||
'cachelock' => $CFG->dirroot.'/cache/locks',
|
||||
'fileconverter' => $CFG->dirroot.'/files/converter',
|
||||
);
|
||||
$parents = array();
|
||||
$subplugins = array();
|
||||
|
@ -291,6 +291,23 @@ abstract class core_filetypes {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a mimetype - return a valid file extension for it.
|
||||
*
|
||||
* @param $mimetype string
|
||||
* @return string|bool False if the mimetype was not known, a string indicating a valid file extension otherwise. It may not
|
||||
* be the only valid file extension - just the first one found.
|
||||
*/
|
||||
public static function get_file_extension($mimetype) {
|
||||
$types = self::get_types();
|
||||
foreach ($types as $extension => $info) {
|
||||
if ($info['type'] == $mimetype) {
|
||||
return $extension;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the current types.
|
||||
*
|
||||
|
@ -1755,6 +1755,10 @@ class core_plugin_manager {
|
||||
'imagegallery'
|
||||
),
|
||||
|
||||
'fileconverter' => array(
|
||||
'unoconv'
|
||||
),
|
||||
|
||||
'editor' => array(
|
||||
'atto', 'textarea', 'tinymce'
|
||||
),
|
||||
|
183
lib/classes/plugininfo/fileconverter.php
Normal file
183
lib/classes/plugininfo/fileconverter.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Defines classes used for plugin info.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2017 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
namespace core\plugininfo;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Class for document converter plugins
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2017 Damyon Wiese
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class fileconverter extends base {
|
||||
|
||||
/**
|
||||
* Should there be a way to uninstall the plugin via the administration UI.
|
||||
*
|
||||
* Uninstallation is allowed for fileconverter plugins.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_uninstall_allowed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name for the settings section.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_settings_section_name() {
|
||||
return 'fileconverter' . $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the global settings for a particular availability plugin (if there are any)
|
||||
*
|
||||
* @param \part_of_admin_tree $adminroot
|
||||
* @param string $parentnodename
|
||||
* @param bool $hassiteconfig
|
||||
*/
|
||||
public function load_settings(\part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) {
|
||||
global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them.
|
||||
$ADMIN = $adminroot; // May be used in settings.php.
|
||||
$plugininfo = $this; // Also can be used inside settings.php.
|
||||
|
||||
if (!$this->is_installed_and_upgraded()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$hassiteconfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
$section = $this->get_settings_section_name();
|
||||
|
||||
$settings = null;
|
||||
if (file_exists($this->full_path('settings.php'))) {
|
||||
$settings = new \admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false);
|
||||
include($this->full_path('settings.php')); // This may also set $settings to null.
|
||||
}
|
||||
if ($settings) {
|
||||
$ADMIN->add($parentnodename, $settings);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return URL used for management of plugins of this type.
|
||||
* @return \moodle_url
|
||||
*/
|
||||
public static function get_manage_url() {
|
||||
return new \moodle_url('/admin/settings.php', array('section' => 'managefileconverterplugins'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds all enabled plugins, the result may include missing plugins.
|
||||
*
|
||||
* @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown
|
||||
*/
|
||||
public static function get_enabled_plugins() {
|
||||
global $CFG;
|
||||
|
||||
$order = (!empty($CFG->converter_plugins_sortorder)) ? explode(',', $CFG->converter_plugins_sortorder) : [];
|
||||
if ($order) {
|
||||
$plugins = \core_plugin_manager::instance()->get_installed_plugins('fileconverter');
|
||||
$order = array_intersect($order, array_keys($plugins));
|
||||
}
|
||||
|
||||
return array_combine($order, $order);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current plugin as enabled or disabled
|
||||
* When enabling tries to guess the sortorder based on default rank returned by the plugin.
|
||||
* @param bool $newstate
|
||||
*/
|
||||
public function set_enabled($newstate = true) {
|
||||
$enabled = self::get_enabled_plugins();
|
||||
if (array_key_exists($this->name, $enabled) == $newstate) {
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
if ($newstate) {
|
||||
// Enable converter plugin.
|
||||
$plugins = \core_plugin_manager::instance()->get_plugins_of_type('fileconverter');
|
||||
if (!array_key_exists($this->name, $plugins)) {
|
||||
// Can not be enabled.
|
||||
return;
|
||||
}
|
||||
$enabled[$this->name] = $this->name;
|
||||
self::set_enabled_plugins($enabled);
|
||||
} else {
|
||||
// Disable converter plugin.
|
||||
unset($enabled[$this->name]);
|
||||
self::set_enabled_plugins($enabled);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of enabled converter players in the specified sort order
|
||||
* To be used when changing settings or in unit tests
|
||||
* @param string|array $list list of plugin names without frankenstyle prefix - comma-separated string or an array
|
||||
*/
|
||||
public static function set_enabled_plugins($list) {
|
||||
if (empty($list)) {
|
||||
$list = [];
|
||||
} else if (!is_array($list)) {
|
||||
$list = explode(',', $list);
|
||||
}
|
||||
if ($list) {
|
||||
$plugins = \core_plugin_manager::instance()->get_installed_plugins('fileconverter');
|
||||
$list = array_intersect($list, array_keys($plugins));
|
||||
}
|
||||
set_config('converter_plugins_sortorder', join(',', $list));
|
||||
\core_plugin_manager::reset_caches();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string describing the formats this engine can converter from / to.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_supported_conversions() {
|
||||
$classname = self::get_classname($this->name);
|
||||
if (class_exists($classname)) {
|
||||
$object = new $classname();
|
||||
return $object->get_supported_conversions();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class name for the plugin.
|
||||
*
|
||||
* @param string $plugin
|
||||
* @return string
|
||||
*/
|
||||
public static function get_classname($plugin) {
|
||||
return "\\fileconverter_{$plugin}\\converter";
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<XMLDB PATH="lib/db" VERSION="20170207" COMMENT="XMLDB file for core Moodle tables"
|
||||
<XMLDB PATH="lib/db" VERSION="20170220" COMMENT="XMLDB file for core Moodle tables"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
|
||||
>
|
||||
@ -2389,6 +2389,26 @@
|
||||
<INDEX NAME="uq_external_file" UNIQUE="true" FIELDS="referencehash, repositoryid" COMMENT="The combination of repositoryid and reference field is supposed to be a unique identification of an external file. Because the reference is a TEXT field, we can't use to compose the index. So we use the referencehash instead and the file API is responsible to keep it up-to-date"/>
|
||||
</INDEXES>
|
||||
</TABLE>
|
||||
<TABLE NAME="file_conversion" COMMENT="Table to track file conversions.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
<FIELD NAME="usermodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timecreated" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="sourcefileid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="targetformat" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false"/>
|
||||
<FIELD NAME="status" TYPE="int" LENGTH="10" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
|
||||
<FIELD NAME="statusmessage" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="converter" TYPE="char" LENGTH="255" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="destfileid" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
|
||||
<FIELD NAME="data" TYPE="text" NOTNULL="false" SEQUENCE="false"/>
|
||||
</FIELDS>
|
||||
<KEYS>
|
||||
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
|
||||
<KEY NAME="sourcefileid" TYPE="foreign" FIELDS="sourcefileid" REFTABLE="files" REFFIELDS="id"/>
|
||||
<KEY NAME="destfileid" TYPE="foreign" FIELDS="destfileid" REFTABLE="files" REFFIELDS="id"/>
|
||||
</KEYS>
|
||||
</TABLE>
|
||||
<TABLE NAME="repository" COMMENT="This table contains one entry for every configured external repository instance.">
|
||||
<FIELDS>
|
||||
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
|
||||
|
@ -338,4 +338,13 @@ $tasks = array(
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
),
|
||||
array(
|
||||
'classname' => 'core_files\task\conversion_cleanup_task',
|
||||
'blocking' => 0,
|
||||
'minute' => 'R',
|
||||
'hour' => '2',
|
||||
'day' => '*',
|
||||
'dayofweek' => '*',
|
||||
'month' => '*'
|
||||
),
|
||||
);
|
||||
|
@ -2579,5 +2579,37 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2017030700.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2017031400.00) {
|
||||
|
||||
// Define table file_conversion to be created.
|
||||
$table = new xmldb_table('file_conversion');
|
||||
|
||||
// Adding fields to table file_conversion.
|
||||
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
|
||||
$table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('sourcefileid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('targetformat', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
|
||||
$table->add_field('status', XMLDB_TYPE_INTEGER, '10', null, null, null, '0');
|
||||
$table->add_field('statusmessage', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
$table->add_field('converter', XMLDB_TYPE_CHAR, '255', null, null, null, null);
|
||||
$table->add_field('destfileid', XMLDB_TYPE_INTEGER, '10', null, null, null, null);
|
||||
$table->add_field('data', XMLDB_TYPE_TEXT, null, null, null, null, null);
|
||||
|
||||
// Adding keys to table file_conversion.
|
||||
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
|
||||
$table->add_key('sourcefileid', XMLDB_KEY_FOREIGN, array('sourcefileid'), 'files', array('id'));
|
||||
$table->add_key('destfileid', XMLDB_KEY_FOREIGN, array('destfileid'), 'files', array('id'));
|
||||
|
||||
// Conditionally launch create table for file_conversion.
|
||||
if (!$dbman->table_exists($table)) {
|
||||
$dbman->create_table($table);
|
||||
}
|
||||
|
||||
// Main savepoint reached.
|
||||
upgrade_main_savepoint(true, 2017031400.00);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,7 +34,7 @@ class core_component_testcase extends advanced_testcase {
|
||||
// To be changed if number of subsystems increases/decreases,
|
||||
// this is defined here to annoy devs that try to add more without any thinking,
|
||||
// always verify that it does not collide with any existing add-on modules and subplugins!!!
|
||||
const SUBSYSTEMCOUNT = 65;
|
||||
const SUBSYSTEMCOUNT = 66;
|
||||
|
||||
public function setUp() {
|
||||
$psr0namespaces = new ReflectionProperty('core_component', 'psr0namespaces');
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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 = (object) [
|
||||
'status' => -1,
|
||||
'filecount' => 0,
|
||||
'pagecount' => 0,
|
||||
'pageready' => 0,
|
||||
'pages' => [],
|
||||
];
|
||||
|
||||
$response = new stdClass();
|
||||
$response->pagecount = count($pages);
|
||||
$response->pages = array();
|
||||
$combineddocument = document_services::get_combined_document_for_attempt($assignment, $userid, $attemptnumber);
|
||||
$response->status = $combineddocument->get_status();
|
||||
$response->filecount = $combineddocument->get_document_count();
|
||||
|
||||
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
|
||||
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);
|
||||
|
||||
// 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->pagecount = $combineddocument->get_page_count();
|
||||
|
||||
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;
|
||||
$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;
|
||||
}
|
||||
$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);
|
||||
|
405
mod/assign/feedback/editpdf/classes/combined_document.php
Normal file
405
mod/assign/feedback/editpdf/classes/combined_document.php
Normal file
@ -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,
|
||||
];
|
||||
}
|
||||
}
|
@ -91,7 +91,7 @@ EOD;
|
||||
require_once($CFG->dirroot . '/mod/assign/locallib.php');
|
||||
|
||||
if (!is_object($assignment)) {
|
||||
$cm = \get_coursemodule_from_instance('assign', $assignment, 0, false, MUST_EXIST);
|
||||
$cm = get_coursemodule_from_instance('assign', $assignment, 0, false, MUST_EXIST);
|
||||
$context = \context_module::instance($cm->id);
|
||||
|
||||
$assignment = new \assign($context, null, null);
|
||||
@ -145,19 +145,20 @@ 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);
|
||||
|
||||
// Capability checks.
|
||||
if (!$assignment->can_view_submission($userid)) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
|
||||
$files = array();
|
||||
@ -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,15 +201,28 @@ EOD;
|
||||
$record->filepath = '/';
|
||||
$record->filename = $plugin->get_type() . '-' . $filename;
|
||||
|
||||
$htmlfile = $fs->create_file_from_string($record, $file);
|
||||
try {
|
||||
$convertedfile = $fs->get_converted_document($htmlfile, 'pdf');
|
||||
} catch (\Exception $e) {
|
||||
// Let us delete the file and re-throw the exception.
|
||||
$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();
|
||||
throw $e;
|
||||
$htmlfile = false;
|
||||
}
|
||||
$htmlfile->delete();
|
||||
|
||||
// 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;
|
||||
}
|
||||
@ -215,25 +230,28 @@ 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);
|
||||
|
||||
// Capability checks.
|
||||
if (!$assignment->can_view_submission($userid)) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
|
||||
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
|
||||
@ -249,109 +267,54 @@ EOD;
|
||||
$itemid = $grade->id;
|
||||
$filepath = '/';
|
||||
$filename = self::COMBINED_PDF_FILENAME;
|
||||
$fs = \get_file_storage();
|
||||
$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;
|
||||
public static function get_combined_pdf_for_attempt($assignment, $userid, $attemptnumber) {
|
||||
$document = self::get_combined_document_for_attempt($assignment, $userid, $attemptnumber);
|
||||
|
||||
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.
|
||||
}
|
||||
|
||||
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)
|
||||
@ -366,7 +329,7 @@ EOD;
|
||||
$assignment = self::get_assignment_from_param($assignment);
|
||||
|
||||
if (!$assignment->can_view_submission($userid)) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
|
||||
// When in readonly we can return the number of images in the DB because they should already exist,
|
||||
@ -383,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();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -413,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');
|
||||
@ -421,18 +365,23 @@ EOD;
|
||||
$assignment = self::get_assignment_from_param($assignment);
|
||||
|
||||
if (!$assignment->can_view_submission($userid)) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
@ -447,7 +396,7 @@ EOD;
|
||||
$record->filearea = self::PAGE_IMAGE_FILEAREA;
|
||||
$record->itemid = $grade->id;
|
||||
$record->filepath = '/';
|
||||
$fs = \get_file_storage();
|
||||
$fs = get_file_storage();
|
||||
|
||||
// Remove the existing content of the filearea.
|
||||
$fs->delete_area_files($record->contextid, $record->component, $record->filearea, $record->itemid);
|
||||
@ -498,7 +447,7 @@ EOD;
|
||||
$assignment = self::get_assignment_from_param($assignment);
|
||||
|
||||
if (!$assignment->can_view_submission($userid)) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
|
||||
if ($assignment->get_instance()->teamsubmission) {
|
||||
@ -514,7 +463,7 @@ EOD;
|
||||
$filepath = '/';
|
||||
$filearea = self::PAGE_IMAGE_FILEAREA;
|
||||
|
||||
$fs = \get_file_storage();
|
||||
$fs = get_file_storage();
|
||||
|
||||
// If we are after the readonly pages...
|
||||
if ($readonly) {
|
||||
@ -640,26 +589,33 @@ EOD;
|
||||
$assignment = self::get_assignment_from_param($assignment);
|
||||
|
||||
if (!$assignment->can_view_submission($userid)) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
if (!$assignment->can_grade()) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
$tmpdir = \make_temp_directory('assignfeedback_editpdf/final/' . self::hash($assignment, $userid, $attemptnumber));
|
||||
$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.
|
||||
|
||||
$pdf = new pdf();
|
||||
|
||||
$fs = \get_file_storage();
|
||||
$stamptmpdir = \make_temp_directory('assignfeedback_editpdf/stamps/' . self::hash($assignment, $userid, $attemptnumber));
|
||||
$fs = get_file_storage();
|
||||
$stamptmpdir = make_temp_directory('assignfeedback_editpdf/stamps/' . self::hash($assignment, $userid, $attemptnumber));
|
||||
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
|
||||
// Copy any new stamps to this instance.
|
||||
if ($files = $fs->get_area_files($assignment->get_context()->id,
|
||||
@ -779,7 +735,7 @@ EOD;
|
||||
$assignment = self::get_assignment_from_param($assignment);
|
||||
|
||||
if (!$assignment->can_view_submission($userid)) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
|
||||
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
|
||||
@ -790,7 +746,7 @@ EOD;
|
||||
$itemid = $grade->id;
|
||||
$filepath = '/';
|
||||
|
||||
$fs = \get_file_storage();
|
||||
$fs = get_file_storage();
|
||||
$files = $fs->get_area_files($contextid,
|
||||
$component,
|
||||
$filearea,
|
||||
@ -815,10 +771,10 @@ EOD;
|
||||
$assignment = self::get_assignment_from_param($assignment);
|
||||
|
||||
if (!$assignment->can_view_submission($userid)) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
if (!$assignment->can_grade()) {
|
||||
\print_error('nopermission');
|
||||
print_error('nopermission');
|
||||
}
|
||||
|
||||
$grade = $assignment->get_user_grade($userid, true, $attemptnumber);
|
||||
@ -828,7 +784,7 @@ EOD;
|
||||
$filearea = self::FINAL_PDF_FILEAREA;
|
||||
$itemid = $grade->id;
|
||||
|
||||
$fs = \get_file_storage();
|
||||
$fs = get_file_storage();
|
||||
return $fs->delete_area_files($contextid, $component, $filearea, $itemid);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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...';
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
@ -130,7 +130,7 @@ Feature: In an assignment, teacher can annotate PDF files during grading
|
||||
And I navigate to "View all submissions" in current page administration
|
||||
And I click on "Edit" "link" in the "Student 2" "table_row"
|
||||
And I click on "Grade" "link" in the "Student 2" "table_row"
|
||||
And I wait until the page is ready
|
||||
And I wait for the complete PDF to load
|
||||
And I click on ".linebutton" "css_element"
|
||||
And I draw on the pdf
|
||||
And I press "Save changes"
|
||||
|
@ -78,4 +78,28 @@ class behat_assignfeedback_editpdf extends behat_base {
|
||||
$this->getSession()->executeScript($js);
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* I wait for all pages in the PDF document to be converted to images and loaded.
|
||||
*
|
||||
* @Given /^I wait for the complete PDF to load$/
|
||||
*/
|
||||
public function i_wait_for_all_editpdf_pages_to_load() {
|
||||
// No need to wait if not running JS.
|
||||
if (!$this->running_javascript()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure that the document is ready, and all pages are loaded.
|
||||
$conditions = [
|
||||
'typeof M !== "undefined"',
|
||||
'typeof M.assignfeedback_editpdf !== "undefined"',
|
||||
'typeof M.assignfeedback_editpdf.instance !== "undefined"',
|
||||
'M.assignfeedback_editpdf.instance.documentstatus === 2',
|
||||
'M.assignfeedback_editpdf.instance.pagecount === M.assignfeedback_editpdf.instance.pages.length',
|
||||
];
|
||||
$js = implode(' && ', $conditions);
|
||||
|
||||
$this->getSession()->wait(self::TIMEOUT * 1000, "({$js})");
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ Feature: In a group assignment, teacher can annotate PDF files for all users
|
||||
And I follow "Test assignment name"
|
||||
And I navigate to "View all submissions" in current page administration
|
||||
And I click on "Grade" "link" in the "Submitted for grading" "table_row"
|
||||
And I wait until the page is ready
|
||||
And I wait for the complete PDF to load
|
||||
And I click on ".navigate-next-button" "css_element"
|
||||
And I wait until the page is ready
|
||||
And I click on ".stampbutton" "css_element"
|
||||
|
@ -3171,6 +3171,15 @@ EDITOR.prototype = {
|
||||
*/
|
||||
pages: [],
|
||||
|
||||
/**
|
||||
* The reported status of the document.
|
||||
*
|
||||
* @property documentstatus
|
||||
* @type int
|
||||
* @protected
|
||||
*/
|
||||
documentstatus: 0,
|
||||
|
||||
/**
|
||||
* The yui node for the loading icon.
|
||||
*
|
||||
@ -3430,7 +3439,7 @@ EDITOR.prototype = {
|
||||
this.refresh_button_state();
|
||||
}
|
||||
|
||||
this.load_all_pages();
|
||||
this.start_generation();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3472,7 +3481,7 @@ EDITOR.prototype = {
|
||||
this.refresh_button_state();
|
||||
}
|
||||
|
||||
this.load_all_pages();
|
||||
this.start_generation();
|
||||
drawingcanvas.on('windowresize', this.resize, this);
|
||||
|
||||
resize = false;
|
||||
@ -3489,21 +3498,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'),
|
||||
@ -3511,109 +3530,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);
|
||||
});
|
||||
}
|
||||
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);
|
||||
});
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
},
|
||||
failure: function(tid, response) {
|
||||
return new M.core.exception(response.responseText);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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++) {
|
||||
@ -3643,6 +3659,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.
|
||||
*
|
||||
@ -4083,6 +4221,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;
|
||||
@ -4131,7 +4272,6 @@ EDITOR.prototype = {
|
||||
};
|
||||
|
||||
Y.io(ajaxurl, config);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@ -4348,10 +4488,6 @@ Y.extend(EDITOR, Y.Base, EDITOR.prototype, {
|
||||
stampfiles: {
|
||||
validator: Y.Lang.isArray,
|
||||
value: ''
|
||||
},
|
||||
pagetotal: {
|
||||
validator: Y.Lang.isInteger,
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -4366,6 +4502,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;
|
||||
};
|
||||
@ -4384,6 +4524,7 @@ M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || f
|
||||
"transition",
|
||||
"querystring-stringify-simple",
|
||||
"moodle-core-notification-dialog",
|
||||
"moodle-core-notification-alert",
|
||||
"moodle-core-notification-exception",
|
||||
"moodle-core-notification-ajaxexception"
|
||||
]
|
||||
|
File diff suppressed because one or more lines are too long
@ -3171,6 +3171,15 @@ EDITOR.prototype = {
|
||||
*/
|
||||
pages: [],
|
||||
|
||||
/**
|
||||
* The reported status of the document.
|
||||
*
|
||||
* @property documentstatus
|
||||
* @type int
|
||||
* @protected
|
||||
*/
|
||||
documentstatus: 0,
|
||||
|
||||
/**
|
||||
* The yui node for the loading icon.
|
||||
*
|
||||
@ -3430,7 +3439,7 @@ EDITOR.prototype = {
|
||||
this.refresh_button_state();
|
||||
}
|
||||
|
||||
this.load_all_pages();
|
||||
this.start_generation();
|
||||
},
|
||||
|
||||
/**
|
||||
@ -3472,7 +3481,7 @@ EDITOR.prototype = {
|
||||
this.refresh_button_state();
|
||||
}
|
||||
|
||||
this.load_all_pages();
|
||||
this.start_generation();
|
||||
drawingcanvas.on('windowresize', this.resize, this);
|
||||
|
||||
resize = false;
|
||||
@ -3489,21 +3498,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'),
|
||||
@ -3511,109 +3530,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);
|
||||
});
|
||||
}
|
||||
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);
|
||||
});
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
},
|
||||
failure: function(tid, response) {
|
||||
return new M.core.exception(response.responseText);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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++) {
|
||||
@ -3643,6 +3659,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.
|
||||
*
|
||||
@ -4083,6 +4221,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;
|
||||
@ -4131,7 +4272,6 @@ EDITOR.prototype = {
|
||||
};
|
||||
|
||||
Y.io(ajaxurl, config);
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@ -4348,10 +4488,6 @@ Y.extend(EDITOR, Y.Base, EDITOR.prototype, {
|
||||
stampfiles: {
|
||||
validator: Y.Lang.isArray,
|
||||
value: ''
|
||||
},
|
||||
pagetotal: {
|
||||
validator: Y.Lang.isInteger,
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -4366,6 +4502,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;
|
||||
};
|
||||
@ -4384,6 +4524,7 @@ M.assignfeedback_editpdf.editor.init = M.assignfeedback_editpdf.editor.init || f
|
||||
"transition",
|
||||
"querystring-stringify-simple",
|
||||
"moodle-core-notification-dialog",
|
||||
"moodle-core-notification-alert",
|
||||
"moodle-core-notification-exception",
|
||||
"moodle-core-notification-ajaxexception"
|
||||
]
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
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);
|
||||
});
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
},
|
||||
failure: function(tid, response) {
|
||||
return new M.core.exception(response.responseText);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
|
@ -12,6 +12,7 @@
|
||||
"transition",
|
||||
"querystring-stringify-simple",
|
||||
"moodle-core-notification-dialog",
|
||||
"moodle-core-notification-alert",
|
||||
"moodle-core-notification-exception",
|
||||
"moodle-core-notification-ajaxexception"
|
||||
]
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2017031100.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2017031400.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user