Merge branch 'MDL-79705-402' of https://github.com/paulholden/moodle into MOODLE_402_...
[moodle.git] / media / classes / manager.php
blob7038d25d6cffb2e9c864eff0b3b12a00f652336a
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 * Manager for media files
20 * @package core_media
21 * @copyright 2016 Marina Glancy
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 defined('MOODLE_INTERNAL') || die();
27 /**
28 * Manager for media files.
30 * Used in file resources, media filter, and any other places that need to
31 * output embedded media.
33 * Usage:
34 * $manager = core_media_manager::instance();
37 * @package core_media
38 * @copyright 2016 Marina Glancy
39 * @author 2011 The Open University
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 final class core_media_manager {
43 /**
44 * Option: Disable text link fallback.
46 * Use this option if you are going to print a visible link anyway so it is
47 * pointless to have one as fallback.
49 * To enable, set value to true.
51 const OPTION_NO_LINK = 'nolink';
53 /**
54 * Option: When embedding, if there is no matching embed, do not use the
55 * default link fallback player; instead return blank.
57 * This is different from OPTION_NO_LINK because this option still uses the
58 * fallback link if there is some kind of embedding. Use this option if you
59 * are going to check if the return value is blank and handle it specially.
61 * To enable, set value to true.
63 const OPTION_FALLBACK_TO_BLANK = 'embedorblank';
65 /**
66 * Option: Enable players which are only suitable for use when we trust the
67 * user who embedded the content.
69 * In the past, this option enabled the SWF player (which was removed).
70 * However, this setting will remain because it might be used by third-party plugins.
72 * To enable, set value to true.
74 const OPTION_TRUSTED = 'trusted';
76 /**
77 * Option: Put a div around the output (if not blank) so that it displays
78 * as a block using the 'resourcecontent' CSS class.
80 * To enable, set value to true.
82 const OPTION_BLOCK = 'block';
84 /**
85 * Option: When the request for media players came from a text filter this option will contain the
86 * original HTML snippet, usually one of the tags: <a> or <video> or <audio>
88 * Players that support other HTML5 features such as tracks may find them in this option.
90 const OPTION_ORIGINAL_TEXT = 'originaltext';
92 /** @var array Array of available 'player' objects */
93 private $players;
95 /** @var string Regex pattern for links which may contain embeddable content */
96 private $embeddablemarkers;
98 /** @var core_media_manager caches a singleton instance */
99 static private $instance;
101 /** @var moodle_page page this instance was initialised for */
102 private $page;
105 * Returns a singleton instance of a manager
107 * Note as of Moodle 3.3, this will call setup for you.
109 * @return core_media_manager
111 public static function instance($page = null) {
112 // Use the passed $page if given, otherwise the $PAGE global.
113 if (!$page) {
114 global $PAGE;
115 $page = $PAGE;
117 if (self::$instance === null || ($page && self::$instance->page !== $page)) {
118 self::$instance = new self($page);
120 return self::$instance;
124 * Construct a new core_media_manager instance
126 * @param moodle_page $page The page we are going to add requirements to.
127 * @see core_media_manager::instance()
129 private function __construct($page) {
130 if ($page) {
131 $this->page = $page;
132 $players = $this->get_players();
133 foreach ($players as $player) {
134 $player->setup($page);
136 } else {
137 debugging('Could not determine the $PAGE. Media plugins will not be set up', DEBUG_DEVELOPER);
142 * @deprecated since Moodle 3.3. The setup is now done in ::instance() so there is no need to call this.
144 public function setup() {
145 throw new coding_exception('core_media_manager::setup() can not be used any more because it is done in ::instance()');
149 * Resets cached singleton instance. To be used after $CFG->media_plugins_sortorder is modified
151 public static function reset_caches() {
152 self::$instance = null;
156 * Obtains the list of core_media_player objects currently in use to render
157 * items.
159 * The list is in rank order (highest first) and does not include players
160 * which are disabled.
162 * @return core_media_player[] Array of core_media_player objects in rank order
164 private function get_players() {
165 // Save time by only building the list once.
166 if (!$this->players) {
167 $sortorder = \core\plugininfo\media::get_enabled_plugins();
169 $this->players = [];
170 foreach ($sortorder as $name) {
171 $classname = "media_" . $name . "_plugin";
172 if (class_exists($classname)) {
173 $this->players[] = new $classname();
177 return $this->players;
181 * Renders a media file (audio or video) using suitable embedded player.
183 * See embed_alternatives function for full description of parameters.
184 * This function calls through to that one.
186 * When using this function you can also specify width and height in the
187 * URL by including ?d=100x100 at the end. If specified in the URL, this
188 * will override the $width and $height parameters.
190 * @param moodle_url $url Full URL of media file
191 * @param string $name Optional user-readable name to display in download link
192 * @param int $width Width in pixels (optional)
193 * @param int $height Height in pixels (optional)
194 * @param array $options Array of key/value pairs
195 * @return string HTML content of embed
197 public function embed_url(moodle_url $url, $name = '', $width = 0, $height = 0,
198 $options = array()) {
200 // Get width and height from URL if specified (overrides parameters in
201 // function call).
202 $rawurl = $url->out(false);
203 if (preg_match('/[?#]d=([\d]{1,4}%?)x([\d]{1,4}%?)/', $rawurl, $matches)) {
204 $width = $matches[1];
205 $height = $matches[2];
206 $url = new moodle_url(str_replace($matches[0], '', $rawurl));
209 // Defer to array version of function.
210 return $this->embed_alternatives(array($url), $name, $width, $height, $options);
214 * Renders media files (audio or video) using suitable embedded player.
215 * The list of URLs should be alternative versions of the same content in
216 * multiple formats. If there is only one format it should have a single
217 * entry.
219 * If the media files are not in a supported format, this will give students
220 * a download link to each format. The download link uses the filename
221 * unless you supply the optional name parameter.
223 * Width and height are optional. If specified, these are suggested sizes
224 * and should be the exact values supplied by the user, if they come from
225 * user input. These will be treated as relating to the size of the video
226 * content, not including any player control bar.
228 * For audio files, height will be ignored. For video files, a few formats
229 * work if you specify only width, but in general if you specify width
230 * you must specify height as well.
232 * The $options array is passed through to the core_media_player classes
233 * that render the object tag. The keys can contain values from
234 * core_media::OPTION_xx.
236 * @param array $alternatives Array of moodle_url to media files
237 * @param string $name Optional user-readable name to display in download link
238 * @param int $width Width in pixels (optional)
239 * @param int $height Height in pixels (optional)
240 * @param array $options Array of key/value pairs
241 * @return string HTML content of embed
243 public function embed_alternatives($alternatives, $name = '', $width = 0, $height = 0,
244 $options = array()) {
246 // Get list of player plugins.
247 $players = $this->get_players();
249 // Set up initial text which will be replaced by first player that
250 // supports any of the formats.
251 $out = core_media_player::PLACEHOLDER;
253 // Loop through all players that support any of these URLs.
254 foreach ($players as $player) {
255 $supported = $player->list_supported_urls($alternatives, $options);
256 if ($supported) {
257 // Embed.
258 $text = $player->embed($supported, $name, $width, $height, $options);
260 // Put this in place of the 'fallback' slot in the previous text.
261 $out = str_replace(core_media_player::PLACEHOLDER, $text, $out);
263 // Check if we need to continue looking for players.
264 if (strpos($out, core_media_player::PLACEHOLDER) === false) {
265 break;
270 if (!empty($options[self::OPTION_FALLBACK_TO_BLANK]) && $out === core_media_player::PLACEHOLDER) {
271 // In case of OPTION_FALLBACK_TO_BLANK and no player matched do not fallback to link, just return empty string.
272 return '';
275 // Remove 'fallback' slot from final version and return it.
276 $fallback = $this->fallback_to_link($alternatives, $name, $options);
277 $out = str_replace(core_media_player::PLACEHOLDER, $fallback, $out);
278 $out = str_replace(core_media_player::LINKPLACEHOLDER, $fallback, $out);
279 if (!empty($options[self::OPTION_BLOCK]) && $out !== '') {
280 $out = html_writer::tag('div', $out, array('class' => 'resourcecontent'));
282 return $out;
286 * Returns links to the specified URLs unless OPTION_NO_LINK is passed.
288 * @param array $urls URLs of media files
289 * @param string $name Display name; '' to use default
290 * @param array $options Options array
291 * @return string HTML code for embed
293 private function fallback_to_link($urls, $name, $options) {
294 // If link is turned off, return empty.
295 if (!empty($options[self::OPTION_NO_LINK])) {
296 return '';
299 // Build up link content.
300 $output = '';
301 foreach ($urls as $url) {
302 if (strval($name) !== '' && $output === '') {
303 $title = $name;
304 } else {
305 $title = $this->get_filename($url);
307 $printlink = html_writer::link($url, $title, array('class' => 'mediafallbacklink'));
308 if ($output) {
309 // Where there are multiple available formats, there are fallback links
310 // for all formats, separated by /.
311 $output .= ' / ';
313 $output .= $printlink;
315 return $output;
319 * Checks whether a file can be embedded. If this returns true you will get
320 * an embedded player; if this returns false, you will just get a download
321 * link.
323 * This is a wrapper for can_embed_urls.
325 * @param moodle_url $url URL of media file
326 * @param array $options Options (same as when embedding)
327 * @return bool True if file can be embedded
329 public function can_embed_url(moodle_url $url, $options = array()) {
330 return $this->can_embed_urls(array($url), $options);
334 * Checks whether a file can be embedded. If this returns true you will get
335 * an embedded player; if this returns false, you will just get a download
336 * link.
338 * @param array $urls URL of media file and any alternatives (moodle_url)
339 * @param array $options Options (same as when embedding)
340 * @return bool True if file can be embedded
342 public function can_embed_urls(array $urls, $options = array()) {
343 // Check all players to see if any of them support it.
344 foreach ($this->get_players() as $player) {
345 // First player that supports it, return true.
346 if ($player->list_supported_urls($urls, $options)) {
347 return true;
350 return false;
354 * Obtains a list of markers that can be used in a regular expression when
355 * searching for URLs that can be embedded by any player type.
357 * This string is used to improve peformance of regex matching by ensuring
358 * that the (presumably C) regex code can do a quick keyword check on the
359 * URL part of a link to see if it matches one of these, rather than having
360 * to go into PHP code for every single link to see if it can be embedded.
362 * @return string String suitable for use in regex such as '(\.mp4|\.flv)'
364 public function get_embeddable_markers() {
365 if (empty($this->embeddablemarkers)) {
366 $markers = '';
367 foreach ($this->get_players() as $player) {
368 foreach ($player->get_embeddable_markers() as $marker) {
369 if ($markers !== '') {
370 $markers .= '|';
372 $markers .= preg_quote($marker);
375 $this->embeddablemarkers = $markers;
377 return $this->embeddablemarkers;
381 * Given a string containing multiple URLs separated by #, this will split
382 * it into an array of moodle_url objects suitable for using when calling
383 * embed_alternatives.
385 * Note that the input string should NOT be html-escaped (i.e. if it comes
386 * from html, call html_entity_decode first).
388 * @param string $combinedurl String of 1 or more alternatives separated by #
389 * @param int $width Output variable: width (will be set to 0 if not specified)
390 * @param int $height Output variable: height (0 if not specified)
391 * @return array Array of 1 or more moodle_url objects
393 public function split_alternatives($combinedurl, &$width, &$height) {
394 global $CFG;
395 $urls = explode('#', $combinedurl);
396 $width = 0;
397 $height = 0;
398 $returnurls = array();
400 foreach ($urls as $url) {
401 $matches = null;
403 // You can specify the size as a separate part of the array like
404 // #d=640x480 without actually including a url in it.
405 if (preg_match('/^d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
406 $width = $matches[1];
407 $height = $matches[2];
408 continue;
411 // Can also include the ?d= as part of one of the URLs (if you use
412 // more than one they will be ignored except the last).
413 if (preg_match('/\?d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
414 $width = $matches[1];
415 $height = $matches[2];
417 // Trim from URL.
418 $url = str_replace($matches[0], '', $url);
421 // Clean up url.
422 $url = fix_utf8($url);
423 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
424 if (!validateUrlSyntax($url, 's?H?S?F?R?E?u-P-a?I?p?f?q?r?')) {
425 continue;
428 // Turn it into moodle_url object.
429 $returnurls[] = new moodle_url($url);
432 return $returnurls;
436 * Returns the file extension for a URL.
437 * @param moodle_url $url URL
439 public function get_extension(moodle_url $url) {
440 // Note: Does not use core_text (. is UTF8-safe).
441 $filename = self::get_filename($url);
442 $dot = strrpos($filename, '.');
443 if ($dot === false) {
444 return '';
445 } else {
446 return strtolower(substr($filename, $dot + 1));
451 * Obtains the filename from the moodle_url.
452 * @param moodle_url $url URL
453 * @return string Filename only (not escaped)
455 public function get_filename(moodle_url $url) {
456 // Use the 'file' parameter if provided (for links created when
457 // slasharguments was off). If not present, just use URL path.
458 $path = $url->get_param('file');
459 if (!$path) {
460 $path = $url->get_path();
463 // Remove everything before last / if present. Does not use textlib as / is UTF8-safe.
464 $slash = strrpos($path, '/');
465 if ($slash !== false) {
466 $path = substr($path, $slash + 1);
468 return $path;
472 * Guesses MIME type for a moodle_url based on file extension.
473 * @param moodle_url $url URL
474 * @return string MIME type
476 public function get_mimetype(moodle_url $url) {
477 return mimeinfo('type', $this->get_filename($url));