MDL-55528 core_files: Create new fileconverter plugintype
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -20,28 +20,33 @@
|
||||
{{info}}
|
||||
</td>
|
||||
{{/infocolumnname}}
|
||||
<td>
|
||||
<td class="text-nowrap">
|
||||
{{#moveuplink}}
|
||||
<a href="{{{moveuplink}}}">
|
||||
{{#moveupicon}}{{>core/pix_icon}}{{/moveupicon}}
|
||||
{{#pix}}t/up, moodle, {{#str}} up, moodle {{/str}}{{/pix}}
|
||||
</a>
|
||||
{{/moveuplink}}
|
||||
{{^moveuplink}}
|
||||
{{#spacericon}}{{>core/pix_icon}}{{/spacericon}}
|
||||
{{#pix}}spacer, moodle{{/pix}}
|
||||
{{/moveuplink}}
|
||||
|
||||
{{#movedownlink}}
|
||||
<a href="{{{movedownlink}}}">
|
||||
{{#movedownicon}}{{>core/pix_icon}}{{/movedownicon}}
|
||||
{{#pix}}t/down, moodle, {{#str}} down, moodle {{/str}}{{/pix}}
|
||||
</a>
|
||||
{{/movedownlink}}
|
||||
{{^movedownlink}}
|
||||
{{#spacericon}}{{>core/pix_icon}}{{/spacericon}}
|
||||
{{#pix}}spacer, moodle{{/pix}}
|
||||
{{/movedownlink}}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{{togglelink}}}">
|
||||
{{#toggleicon}}{{>core/pix_icon}}{{/toggleicon}}
|
||||
{{#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>
|
||||
|
||||
@@ -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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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();
|
||||
}
|
||||
@@ -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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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));
|
||||
}
|
||||
}
|
||||
@@ -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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -105,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';
|
||||
@@ -128,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';
|
||||
|
||||
+32
-11
@@ -7399,6 +7399,7 @@ 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() {
|
||||
@@ -7407,18 +7408,21 @@ abstract class admin_setting_manage_plugins extends admin_setting {
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
@@ -7514,19 +7518,12 @@ abstract class admin_setting_manage_plugins extends admin_setting {
|
||||
public function output_html($data, $query = '') {
|
||||
global $CFG, $OUTPUT, $DB, $PAGE;
|
||||
|
||||
$spacer = new pix_icon('spacer', '', 'moodle');
|
||||
$moveup = new pix_icon('t/up', get_string('up'), 'moodle');
|
||||
$movedown = new pix_icon('t/down', get_string('down'), 'moodle');
|
||||
|
||||
$context = (object) [
|
||||
'manageurl' => new moodle_url($this->get_manage_url(), [
|
||||
'type' => $this->get_plugin_type(),
|
||||
'sesskey' => sesskey(),
|
||||
]),
|
||||
'infocolumnname' => $this->get_info_column_name(),
|
||||
'spacericon' => $spacer->export_for_template($OUTPUT),
|
||||
'moveupicon' => $moveup->export_for_template($OUTPUT),
|
||||
'movedownicon' => $movedown->export_for_template($OUTPUT),
|
||||
'plugins' => [],
|
||||
];
|
||||
|
||||
@@ -7541,7 +7538,6 @@ abstract class admin_setting_manage_plugins extends admin_setting {
|
||||
'plugin' => $plugin->displayname,
|
||||
'enabled' => $plugin->is_enabled(),
|
||||
'togglelink' => '',
|
||||
'toggleicon' => '',
|
||||
'moveuplink' => '',
|
||||
'movedownlink' => '',
|
||||
'settingslink' => $plugin->get_settings_url(),
|
||||
@@ -7552,7 +7548,7 @@ abstract class admin_setting_manage_plugins extends admin_setting {
|
||||
// Enable/Disable link.
|
||||
$togglelink = new moodle_url($pluginlink);
|
||||
if ($plugin->is_enabled()) {
|
||||
$toggleicon = new pix_icon('i/hide', get_string('disable', 'moodle'), 'moodle');
|
||||
$toggletarget = false;
|
||||
$togglelink->param('action', 'disable');
|
||||
|
||||
if (count($context->plugins)) {
|
||||
@@ -7567,11 +7563,11 @@ abstract class admin_setting_manage_plugins extends admin_setting {
|
||||
|
||||
$pluginkey->info = $this->get_info_column($plugin);
|
||||
} else {
|
||||
$toggleicon = new pix_icon('i/show', get_string('enable', 'moodle'), 'moodle');
|
||||
$toggletarget = true;
|
||||
$togglelink->param('action', 'enable');
|
||||
}
|
||||
|
||||
$pluginkey->toggleicon = $toggleicon->export_for_template($OUTPUT);
|
||||
$pluginkey->toggletarget = $toggletarget;
|
||||
$pluginkey->togglelink = $togglelink;
|
||||
|
||||
$frankenstyle = $plugin->type . '_' . $plugin->name;
|
||||
@@ -7593,6 +7589,31 @@ abstract class admin_setting_manage_plugins extends admin_setting {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -1768,6 +1768,9 @@ class core_plugin_manager {
|
||||
'imagegallery'
|
||||
),
|
||||
|
||||
'fileconverter' => array(
|
||||
),
|
||||
|
||||
'editor' => array(
|
||||
'atto', 'textarea', 'tinymce'
|
||||
),
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
+21
-1
@@ -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"
|
||||
>
|
||||
@@ -2388,6 +2388,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' => '*'
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2555,5 +2555,37 @@ function xmldb_main_upgrade($oldversion) {
|
||||
upgrade_main_savepoint(true, 2017021400.00);
|
||||
}
|
||||
|
||||
if ($oldversion < 2017030700.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, 2017030700.00);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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
-1
@@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2017030300.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2017030700.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user