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 */ 24 namespace core_files; 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 use 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 */ 37 class converter { 38 39 /** 40 * Get a list of enabled plugins and classes. 41 * 42 * @return array List of enabled plugins 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 * 60 * @return \file_storage 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). 72 * @return conversion conversion object 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); 121 } 122 $conversion->update(); 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. 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')); 133 134 if (!$currentconverter) { 135 // No more converters available. 136 $conversion->set('status', conversion::STATUS_FAILED); 137 $conversion->update(); 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 164 * @return string|false Name of next converter if present 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 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; 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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body