Merge branch 'wip-MDL-61424-master' of git://github.com/marinaglancy/moodle
[moodle.git] / files / classes / converter.php
blobda8c5ba0cbd426472cad923638bb4f4272e48b3a
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Class for converting files between different file formats using unoconv.
20 * @package core_files
21 * @copyright 2017 Damyon Wiese
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 namespace core_files;
26 defined('MOODLE_INTERNAL') || die();
28 use stored_file;
30 /**
31 * Class for converting files between different formats using unoconv.
33 * @package core_files
34 * @copyright 2017 Damyon Wiese
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class converter {
39 /**
40 * Get a list of enabled plugins and classes.
42 * @return array List of enabled plugins
44 protected function get_enabled_plugins() {
45 $plugins = \core\plugininfo\fileconverter::get_enabled_plugins();
47 $pluginclasses = [];
48 foreach ($plugins as $plugin) {
49 $pluginclasses[$plugin] = \core\plugininfo\fileconverter::get_classname($plugin);
52 return $pluginclasses;
55 /**
56 * Return the file_storage API.
58 * This allows for mocking of the file_storage API.
60 * @return \file_storage
62 protected function get_file_storage() {
63 return get_file_storage();
66 /**
67 * Start the conversion for a stored_file into a new format.
69 * @param stored_file $file The file to convert
70 * @param string $format The desired target file format (file extension)
71 * @param boolean $forcerefresh If true, the file will be converted every time (not cached).
72 * @return conversion conversion object
74 public function start_conversion(stored_file $file, $format, $forcerefresh = false) {
75 $conversions = conversion::get_conversions_for_file($file, $format);
77 if ($forcerefresh || count($conversions) > 1) {
78 while ($conversion = array_shift($conversions)) {
79 if ($conversion->get('id')) {
80 $conversion->delete();
85 if (empty($conversions)) {
86 $conversion = new conversion(0, (object) [
87 'sourcefileid' => $file->get_id(),
88 'targetformat' => $format,
89 ]);
90 $conversion->create();
91 } else {
92 $conversion = array_shift($conversions);
95 if ($conversion->get('status') !== conversion::STATUS_COMPLETE) {
96 $this->poll_conversion($conversion);
99 return $conversion;
103 * Poll for updates to the supplied conversion.
105 * @param conversion $conversion The conversion in progress
106 * @return $this
108 public function poll_conversion(conversion $conversion) {
109 $format = $conversion->get('targetformat');
110 $file = $conversion->get_sourcefile();
112 if ($conversion->get('status') == conversion::STATUS_IN_PROGRESS) {
113 // The current conversion is in progress.
114 // Check for updates.
115 if ($instance = $conversion->get_converter_instance()) {
116 $instance->poll_conversion_status($conversion);
117 } else {
118 // Unable to fetch the converter instance.
119 // Reset the status back to PENDING so that it may be picked up again.
120 $conversion->set('status', conversion::STATUS_PENDING);
122 $conversion->update();
125 // Refresh the status.
126 $status = $conversion->get('status');
127 if ($status === conversion::STATUS_PENDING || $status === conversion::STATUS_FAILED) {
128 // The current status is either pending or failed.
129 // Attempt to pick up a new converter and convert the document.
130 $from = pathinfo($file->get_filename(), PATHINFO_EXTENSION);
131 $converters = $this->get_document_converter_classes($from, $format);
132 $currentconverter = $this->get_next_converter($converters, $conversion->get('converter'));
134 if (!$currentconverter) {
135 // No more converters available.
136 $conversion->set('status', conversion::STATUS_FAILED);
137 $conversion->update();
138 return $this;
141 do {
142 $conversion
143 ->set('converter', $currentconverter)
144 ->set('status', conversion::STATUS_IN_PROGRESS)
145 ->update();
147 $instance = $conversion->get_converter_instance();
148 $instance->start_document_conversion($conversion);
149 $failed = $conversion->get('status') === conversion::STATUS_FAILED;
150 $currentconverter = $this->get_next_converter($converters, $currentconverter);
151 } while ($failed && $currentconverter);
153 $conversion->update();
156 return $this;
160 * Fetch the next converter to try.
162 * @param array $converters The list of converters to try
163 * @param string|null $currentconverter The converter currently in use
164 * @return string|false Name of next converter if present
166 protected function get_next_converter($converters, $currentconverter = null) {
167 if ($currentconverter) {
168 $keys = array_keys($converters, $currentconverter);
169 $key = $keys[0];
170 if (isset($converters[$key + 1])) {
171 return $converters[$key + 1];
172 } else {
173 return false;
175 } else if (!empty($converters)) {
176 return $converters[0];
177 } else {
178 return false;
183 * Fetch the class for the preferred document converter.
185 * @param string $from The source target file (file extension)
186 * @param string $to The desired target file format (file extension)
187 * @return string The class for document conversion
189 protected function get_document_converter_classes($from, $to) {
190 $classes = [];
192 $converters = $this->get_enabled_plugins();
193 foreach ($converters as $plugin => $classname) {
194 if (!class_exists($classname)) {
195 continue;
198 if (!$classname::are_requirements_met()) {
199 continue;
202 if ($classname::supports($from, $to)) {
203 $classes[] = $classname;
207 return $classes;
211 * Check whether document conversion is supported for this file and target format.
213 * @param stored_file $file The file to convert
214 * @param string $to The desired target file format (file extension)
215 * @return bool Whether the target type can be converted
217 public function can_convert_storedfile_to(stored_file $file, $to) {
218 if ($file->is_directory()) {
219 // Directories cannot be converted.
220 return false;
223 if (!$file->get_filesize()) {
224 // Empty files cannot be converted.
225 return false;
228 $from = pathinfo($file->get_filename(), PATHINFO_EXTENSION);
229 if (!$from) {
230 // No file extension could be found. Unable to determine converter.
231 return false;
234 return $this->can_convert_format_to($from, $to);
238 * Check whether document conversion is supported for this file and target format.
240 * @param string $from The source target file (file extension)
241 * @param string $to The desired target file format (file extension)
242 * @return bool Whether the target type can be converted
244 public function can_convert_format_to($from, $to) {
245 return !empty($this->get_document_converter_classes($from, $to));