Merge branch 'MDL-58272-master-2' of https://github.com/snake/moodle
[moodle.git] / files / classes / converter.php
CommitLineData
34df779a
AN
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.
13//
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/>.
16
17/**
18 * Class for converting files between different file formats using unoconv.
19 *
20 * @package core_files
21 * @copyright 2017 Damyon Wiese
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24namespace core_files;
25
26defined('MOODLE_INTERNAL') || die();
27
28use stored_file;
29
30/**
31 * Class for converting files between different formats using unoconv.
32 *
33 * @package core_files
34 * @copyright 2017 Damyon Wiese
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 */
37class converter {
38
39 /**
40 * Get a list of enabled plugins and classes.
41 *
92a951c5 42 * @return array List of enabled plugins
34df779a
AN
43 */
44 protected function get_enabled_plugins() {
45 $plugins = \core\plugininfo\fileconverter::get_enabled_plugins();
46
47 $pluginclasses = [];
48 foreach ($plugins as $plugin) {
49 $pluginclasses[$plugin] = \core\plugininfo\fileconverter::get_classname($plugin);
50 }
51
52 return $pluginclasses;
53 }
54
55 /**
56 * Return the file_storage API.
57 *
58 * This allows for mocking of the file_storage API.
59 *
92a951c5 60 * @return \file_storage
34df779a
AN
61 */
62 protected function get_file_storage() {
63 return get_file_storage();
64 }
65
66 /**
67 * Start the conversion for a stored_file into a new format.
68 *
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).
92a951c5 72 * @return conversion conversion object
34df779a
AN
73 */
74 public function start_conversion(stored_file $file, $format, $forcerefresh = false) {
75 $conversions = conversion::get_conversions_for_file($file, $format);
76
77 if ($forcerefresh || count($conversions) > 1) {
78 while ($conversion = array_shift($conversions)) {
79 if ($conversion->get('id')) {
80 $conversion->delete();
81 }
82 }
83 }
84
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);
93 }
94
95 if ($conversion->get('status') !== conversion::STATUS_COMPLETE) {
96 $this->poll_conversion($conversion);
97 }
98
99 return $conversion;
100 }
101
102 /**
103 * Poll for updates to the supplied conversion.
104 *
105 * @param conversion $conversion The conversion in progress
106 * @return $this
107 */
108 public function poll_conversion(conversion $conversion) {
109 $format = $conversion->get('targetformat');
110 $file = $conversion->get_sourcefile();
111
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);
34df779a 121 }
9d669806 122 $conversion->update();
34df779a
AN
123 }
124
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.
46878ed8 130 $from = pathinfo($file->get_filename(), PATHINFO_EXTENSION);
34df779a
AN
131 $converters = $this->get_document_converter_classes($from, $format);
132 $currentconverter = $this->get_next_converter($converters, $conversion->get('converter'));
133
134 if (!$currentconverter) {
135 // No more converters available.
136 $conversion->set('status', conversion::STATUS_FAILED);
9d669806 137 $conversion->update();
34df779a
AN
138 return $this;
139 }
140
141 do {
142 $conversion
143 ->set('converter', $currentconverter)
144 ->set('status', conversion::STATUS_IN_PROGRESS)
145 ->update();
146
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);
152
153 $conversion->update();
154 }
155
156 return $this;
157 }
158
159 /**
160 * Fetch the next converter to try.
161 *
162 * @param array $converters The list of converters to try
163 * @param string|null $currentconverter The converter currently in use
92a951c5 164 * @return string|false Name of next converter if present
34df779a
AN
165 */
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;
174 }
175 } else if (!empty($converters)) {
176 return $converters[0];
177 } else {
178 return false;
179 }
180 }
181
182 /**
183 * Fetch the class for the preferred document converter.
184 *
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
188 */
189 protected function get_document_converter_classes($from, $to) {
190 $classes = [];
191
192 $converters = $this->get_enabled_plugins();
193 foreach ($converters as $plugin => $classname) {
194 if (!class_exists($classname)) {
195 continue;
196 }
197
198 if (!$classname::are_requirements_met()) {
199 continue;
200 }
201
202 if ($classname::supports($from, $to)) {
203 $classes[] = $classname;
204 }
205 }
206
207 return $classes;
208 }
209
210 /**
211 * Check whether document conversion is supported for this file and target format.
212 *
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
216 */
217 public function can_convert_storedfile_to(stored_file $file, $to) {
218 if ($file->is_directory()) {
219 // Directories cannot be converted.
220 return false;
221 }
222
223 if (!$file->get_filesize()) {
224 // Empty files cannot be converted.
225 return false;
226 }
227
46878ed8 228 $from = pathinfo($file->get_filename(), PATHINFO_EXTENSION);
34df779a 229 if (!$from) {
46878ed8 230 // No file extension could be found. Unable to determine converter.
34df779a
AN
231 return false;
232 }
233
234 return $this->can_convert_format_to($from, $to);
235 }
236
237 /**
238 * Check whether document conversion is supported for this file and target format.
239 *
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
243 */
244 public function can_convert_format_to($from, $to) {
245 return !empty($this->get_document_converter_classes($from, $to));
246 }
247
248}