Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
moodle
moodle
Commits
34df779a
Commit
34df779a
authored
Feb 17, 2017
by
Andrew Nicols
Browse files
MDL-55528 core_files: Create new fileconverter plugintype
parent
29ce0058
Changes
19
Expand all
Hide whitespace changes
Inline
Side-by-side
admin/settings/plugins.php
View file @
34df779a
...
...
@@ -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
)
{
...
...
admin/templates/setting_manage_plugins.mustache
View file @
34df779a
...
...
@@ -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
}}
{{#
spacer
icon
}}{{>
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
}}
{{#
spacer
icon
}}{{>
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>
...
...
files/classes/conversion.php
0 → 100644
View file @
34df779a
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Classes for converting files between different file formats.
*
* @package core_files
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace
core_files
;
defined
(
'MOODLE_INTERNAL'
)
||
die
();
use
stored_file
;
/**
* Class representing a conversion currently in progress.
*
* @package core_files
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class
conversion
extends
\
core\persistent
{
/**
* Status value representing a conversion waiting to start.
*/
const
STATUS_PENDING
=
0
;
/**
* Status value representing a conversion in progress.
*/
const
STATUS_IN_PROGRESS
=
1
;
/**
* Status value representing a successful conversion.
*/
const
STATUS_COMPLETE
=
2
;
/**
* Status value representing a failed conversion.
*/
const
STATUS_FAILED
=
-
1
;
/**
* Table name for this persistent.
*/
const
TABLE
=
'file_conversion'
;
protected
static
function
define_properties
()
{
return
array
(
'sourcefileid'
=>
[
'type'
=>
PARAM_INT
,
],
'targetformat'
=>
[
'type'
=>
PARAM_ALPHANUMEXT
,
],
'status'
=>
[
'type'
=>
PARAM_INT
,
'choices'
=>
[
self
::
STATUS_PENDING
,
self
::
STATUS_IN_PROGRESS
,
self
::
STATUS_COMPLETE
,
self
::
STATUS_FAILED
,
],
'default'
=>
self
::
STATUS_PENDING
,
],
'statusmessage'
=>
[
'type'
=>
PARAM_RAW
,
'null'
=>
NULL_ALLOWED
,
'default'
=>
null
,
],
'converter'
=>
[
'type'
=>
PARAM_RAW
,
'null'
=>
NULL_ALLOWED
,
'default'
=>
null
,
],
'destfileid'
=>
[
'type'
=>
PARAM_INT
,
'null'
=>
NULL_ALLOWED
,
'default'
=>
null
,
],
'data'
=>
[
'type'
=>
PARAM_RAW
,
'null'
=>
NULL_ALLOWED
,
'default'
=>
null
,
],
);
}
/**
* Fetch all conversions relating to the specified file.
*
* Only conversions which have a valid file are returned.
*
* @param stored_file $file The source file being converted
* @param string $format The targetforamt to filter to
* @return conversion[]
*/
public
static
function
get_conversions_for_file
(
stored_file
$file
,
$format
)
{
global
$DB
;
$instances
=
[];
// Conversion records are intended for tracking a conversion in progress or recently completed.
// The record is removed periodically, but the destination file is not.
// We need to fetch all conversion records which match the source file and target, and also all source and
// destination files which do not have a conversion record.
$sqlfields
=
self
::
get_sql_fields
(
'c'
,
'conversion'
);
// Fetch actual conversions which relate to the specified source file, and have a matching conversion record,
// and either have a valid destination file which still exists, or do not have a destination file at all.
$sql
=
"SELECT
{
$sqlfields
}
FROM {"
.
self
::
TABLE
.
"} c
INNER JOIN
{
files
}
conversionsourcefile ON conversionsourcefile.id = c.sourcefileid
LEFT JOIN
{
files
}
conversiondestfile ON conversiondestfile.id = c.destfileid
WHERE
conversionsourcefile.contenthash = :ccontenthash
AND c.targetformat = :cformat
AND (
c.destfileid IS NULL OR conversiondestfile.id IS NOT NULL
)"
;
// Fetch a empty conversion record for each source/destination combination that we find to match where the
// destination file is in the correct filearea/filepath/filename combination to meet the requirements.
// This ensures that existing conversions are used where possible, even if there is no 'conversion' record for
// them.
$sql
.
=
"
UNION ALL
SELECT
NULL AS conversionid,
orphanedsourcefile.id AS conversionsourcefileid,
:oformat AS conversiontargetformat,
2 AS conversionstatus,
NULL AS conversionstatusmessage,
NULL AS conversionconverter,
orphaneddestfile.id AS conversiondestfileid,
NULL AS conversiondata,
0 AS conversiontimecreated,
0 AS conversiontimemodified,
0 AS conversionusermodified
FROM
{
files
}
orphanedsourcefile
INNER JOIN
{
files
}
orphaneddestfile ON (
orphaneddestfile.filename = orphanedsourcefile.contenthash
AND orphaneddestfile.component = 'core'
AND orphaneddestfile.filearea = 'documentconversion'
AND orphaneddestfile.filepath = :ofilepath
)
LEFT JOIN {"
.
self
::
TABLE
.
"} orphanedconversion ON orphanedconversion.destfileid = orphaneddestfile.id
WHERE
orphanedconversion.id IS NULL
AND
orphanedsourcefile.id = :osourcefileid
"
;
$records
=
$DB
->
get_records_sql
(
$sql
,
[
'ccontenthash'
=>
$file
->
get_contenthash
(),
'osourcefileid'
=>
$file
->
get_id
(),
'cfilepath'
=>
"/
{
$format
}
/"
,
'ofilepath'
=>
"/
{
$format
}
/"
,
'cformat'
=>
$format
,
'oformat'
=>
$format
,
]);
foreach
(
$records
as
$record
)
{
$data
=
self
::
extract_record
(
$record
,
'conversion'
);
$newrecord
=
new
static
(
0
,
$data
);
$instances
[]
=
$newrecord
;
}
return
$instances
;
}
/**
* Remove all old conversion records.
*/
public
static
function
remove_old_conversion_records
()
{
global
$DB
;
$DB
->
delete_records_select
(
self
::
TABLE
,
'timemodified <= :weekagosecs'
,
[
'weekagosecs'
=>
time
()
-
WEEKSECS
,
]);
}
/**
* Set the source file id for the conversion.
*
* @param stored_file $file The file to convert
* @return $this
*/
public
function
set_sourcefile
(
stored_file
$file
)
{
$this
->
raw_set
(
'sourcefileid'
,
$file
->
get_id
());
return
$this
;
}
/**
* Fetch the source file.
*
* @return stored_file|false
*/
public
function
get_sourcefile
()
{
$fs
=
get_file_storage
();
return
$fs
->
get_file_by_id
(
$this
->
get
(
'sourcefileid'
));
}
/**
* Set the destination file for this conversion.
*
* @param string $filepath The path to the converted file
* @return $this
*/
public
function
store_destfile_from_path
(
$filepath
)
{
if
(
$record
=
$this
->
get_file_record
())
{
$fs
=
get_file_storage
();
$existing
=
$fs
->
get_file
(
$record
[
'contextid'
],
$record
[
'component'
],
$record
[
'filearea'
],
$record
[
'itemid'
],
$record
[
'filepath'
],
$record
[
'filename'
]
);
if
(
$existing
)
{
$existing
->
delete
();
}
$file
=
$fs
->
create_file_from_pathname
(
$record
,
$filepath
);
$this
->
raw_set
(
'destfileid'
,
$file
->
get_id
());
}
return
$this
;
}
/**
* Set the destination file for this conversion.
*
* @param string $content The content of the converted file
* @return $this
*/
public
function
store_destfile_from_string
(
$content
)
{
if
(
$record
=
$this
->
get_file_record
())
{
$fs
=
get_file_storage
();
$existing
=
$fs
->
get_file
(
$record
[
'contextid'
],
$record
[
'component'
],
$record
[
'filearea'
],
$record
[
'itemid'
],
$record
[
'filepath'
],
$record
[
'filename'
]
);
if
(
$existing
)
{
$existing
->
delete
();
}
$file
=
$fs
->
create_file_from_string
(
$record
,
$content
);
$this
->
raw_set
(
'destfileid'
,
$file
->
get_id
());
}
return
$this
;
}
/**
* Get the destination file.
*
* @return stored_file|this
*/
public
function
get_destfile
()
{
$fs
=
get_file_storage
();
return
$fs
->
get_file_by_id
(
$this
->
get
(
'destfileid'
));
}
/**
* Helper to ensure that the returned status is always an int.
*
* @return int
*/
protected
function
get_status
()
{
return
(
int
)
$this
->
raw_get
(
'status'
);
}
/**
* Get an instance of the current converter.
*
* @return converter_interface|false
*/
public
function
get_converter_instance
()
{
$currentconverter
=
$this
->
get
(
'converter'
);
if
(
$currentconverter
&&
class_exists
(
$currentconverter
))
{
return
new
$currentconverter
();
}
else
{
return
false
;
}
}
/**
* Transform data into a storable format.
*
* @param stdClass $data The data to be stored
* @return $this
*/
protected
function
set_data
(
$data
)
{
$this
->
raw_set
(
'data'
,
json_encode
(
$data
));
return
$this
;
}
/**
* Transform data into a storable format.
*
* @return stdClass The stored data
*/
protected
function
get_data
()
{
$data
=
$this
->
raw_get
(
'data'
);
if
(
!
empty
(
$data
))
{
return
json_decode
(
$data
);
}
return
(
object
)
[];
}
/**
* Return the file record base for use in the files table.
*
* @return array
*/
protected
function
get_file_record
()
{
$file
=
$this
->
get_sourcefile
();
if
(
!
$file
)
{
// If the source file was removed before we completed, we must return early.
return
false
;
}
return
[
'contextid'
=>
\
context_system
::
instance
()
->
id
,
'component'
=>
'core'
,
'filearea'
=>
'documentconversion'
,
'itemid'
=>
0
,
'filepath'
=>
"/"
.
$this
->
get
(
'targetformat'
)
.
"/"
,
'filename'
=>
$file
->
get_contenthash
(),
];
}
}
files/classes/converter.php
0 → 100644
View file @
34df779a
<?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
;