Merge branch 'install_master' of https://git.in.moodle.com/amosbot/moodle-install
[moodle.git] / media / classes / manager.php
blob39bca4cf1183a98e0d7d64d8fc52ae98932a0cd2
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 * At present, this option enables the SWF player.
71 * To enable, set value to true.
73 const OPTION_TRUSTED = 'trusted';
75 /**
76 * Option: Put a div around the output (if not blank) so that it displays
77 * as a block using the 'resourcecontent' CSS class.
79 * To enable, set value to true.
81 const OPTION_BLOCK = 'block';
83 /**
84 * Option: When the request for media players came from a text filter this option will contain the
85 * original HTML snippet, usually one of the tags: <a> or <video> or <audio>
87 * Players that support other HTML5 features such as tracks may find them in this option.
89 const OPTION_ORIGINAL_TEXT = 'originaltext';
91 /** @var array Array of available 'player' objects */
92 private $players;
94 /** @var string Regex pattern for links which may contain embeddable content */
95 private $embeddablemarkers;
97 /** @var core_media_manager caches a singleton instance */
98 static private $instance;
100 /** @var moodle_page page this instance was initialised for */
101 private $page;
104 * Returns a singleton instance of a manager
106 * Note as of Moodle 3.3, this will call setup for you.
108 * @return core_media_manager
110 public static function instance($page = null) {
111 // Use the passed $page if given, otherwise the $PAGE global.
112 if (!$page) {
113 global $PAGE;
114 $page = $PAGE;
116 if (self::$instance === null || ($page && self::$instance->page !== $page)) {
117 self::$instance = new self($page);
119 return self::$instance;
123 * Construct a new core_media_manager instance
125 * @param moodle_page $page The page we are going to add requirements to.
126 * @see core_media_manager::instance()
128 private function __construct($page) {
129 if ($page) {
130 $this->page = $page;
131 $players = $this->get_players();
132 foreach ($players as $player) {
133 $player->setup($page);
135 } else {
136 debugging('Could not determine the $PAGE. Media plugins will not be set up', DEBUG_DEVELOPER);
141 * Setup page requirements.
143 * This should must only be called once per page request.
145 * @deprecated Moodle 3.3, The setup is now done in ::instance() so there is no need to call this
146 * @param moodle_page $page The page we are going to add requirements to.
147 * @see core_media_manager::instance()
148 * @todo MDL-57632 final deprecation
150 public function setup($page) {
151 debugging('core_media_manager::setup() is deprecated.' .
152 'You only need to call core_media_manager::instance() now', DEBUG_DEVELOPER);
153 // No need to call ::instance from here, because the instance has already be set up.
157 * Resets cached singleton instance. To be used after $CFG->media_plugins_sortorder is modified
159 public static function reset_caches() {
160 self::$instance = null;
164 * Obtains the list of core_media_player objects currently in use to render
165 * items.
167 * The list is in rank order (highest first) and does not include players
168 * which are disabled.
170 * @return core_media_player[] Array of core_media_player objects in rank order
172 private function get_players() {
173 // Save time by only building the list once.
174 if (!$this->players) {
175 $sortorder = \core\plugininfo\media::get_enabled_plugins();
177 $this->players = [];
178 foreach ($sortorder as $name) {
179 $classname = "media_" . $name . "_plugin";
180 if (class_exists($classname)) {
181 $this->players[] = new $classname();
185 return $this->players;
189 * Renders a media file (audio or video) using suitable embedded player.
191 * See embed_alternatives function for full description of parameters.
192 * This function calls through to that one.
194 * When using this function you can also specify width and height in the
195 * URL by including ?d=100x100 at the end. If specified in the URL, this
196 * will override the $width and $height parameters.
198 * @param moodle_url $url Full URL of media file
199 * @param string $name Optional user-readable name to display in download link
200 * @param int $width Width in pixels (optional)
201 * @param int $height Height in pixels (optional)
202 * @param array $options Array of key/value pairs
203 * @return string HTML content of embed
205 public function embed_url(moodle_url $url, $name = '', $width = 0, $height = 0,
206 $options = array()) {
208 // Get width and height from URL if specified (overrides parameters in
209 // function call).
210 $rawurl = $url->out(false);
211 if (preg_match('/[?#]d=([\d]{1,4}%?)x([\d]{1,4}%?)/', $rawurl, $matches)) {
212 $width = $matches[1];
213 $height = $matches[2];
214 $url = new moodle_url(str_replace($matches[0], '', $rawurl));
217 // Defer to array version of function.
218 return $this->embed_alternatives(array($url), $name, $width, $height, $options);
222 * Renders media files (audio or video) using suitable embedded player.
223 * The list of URLs should be alternative versions of the same content in
224 * multiple formats. If there is only one format it should have a single
225 * entry.
227 * If the media files are not in a supported format, this will give students
228 * a download link to each format. The download link uses the filename
229 * unless you supply the optional name parameter.
231 * Width and height are optional. If specified, these are suggested sizes
232 * and should be the exact values supplied by the user, if they come from
233 * user input. These will be treated as relating to the size of the video
234 * content, not including any player control bar.
236 * For audio files, height will be ignored. For video files, a few formats
237 * work if you specify only width, but in general if you specify width
238 * you must specify height as well.
240 * The $options array is passed through to the core_media_player classes
241 * that render the object tag. The keys can contain values from
242 * core_media::OPTION_xx.
244 * @param array $alternatives Array of moodle_url to media files
245 * @param string $name Optional user-readable name to display in download link
246 * @param int $width Width in pixels (optional)
247 * @param int $height Height in pixels (optional)
248 * @param array $options Array of key/value pairs
249 * @return string HTML content of embed
251 public function embed_alternatives($alternatives, $name = '', $width = 0, $height = 0,
252 $options = array()) {
254 // Get list of player plugins.
255 $players = $this->get_players();
257 // Set up initial text which will be replaced by first player that
258 // supports any of the formats.
259 $out = core_media_player::PLACEHOLDER;
261 // Loop through all players that support any of these URLs.
262 foreach ($players as $player) {
263 $supported = $player->list_supported_urls($alternatives, $options);
264 if ($supported) {
265 // Embed.
266 $text = $player->embed($supported, $name, $width, $height, $options);
268 // Put this in place of the 'fallback' slot in the previous text.
269 $out = str_replace(core_media_player::PLACEHOLDER, $text, $out);
271 // Check if we need to continue looking for players.
272 if (strpos($out, core_media_player::PLACEHOLDER) === false) {
273 break;
278 if (!empty($options[self::OPTION_FALLBACK_TO_BLANK]) && $out === core_media_player::PLACEHOLDER) {
279 // In case of OPTION_FALLBACK_TO_BLANK and no player matched do not fallback to link, just return empty string.
280 return '';
283 // Remove 'fallback' slot from final version and return it.
284 $fallback = $this->fallback_to_link($alternatives, $name, $options);
285 $out = str_replace(core_media_player::PLACEHOLDER, $fallback, $out);
286 $out = str_replace(core_media_player::LINKPLACEHOLDER, $fallback, $out);
287 if (!empty($options[self::OPTION_BLOCK]) && $out !== '') {
288 $out = html_writer::tag('div', $out, array('class' => 'resourcecontent'));
290 return $out;
294 * Returns links to the specified URLs unless OPTION_NO_LINK is passed.
296 * @param array $urls URLs of media files
297 * @param string $name Display name; '' to use default
298 * @param array $options Options array
299 * @return string HTML code for embed
301 private function fallback_to_link($urls, $name, $options) {
302 // If link is turned off, return empty.
303 if (!empty($options[self::OPTION_NO_LINK])) {
304 return '';
307 // Build up link content.
308 $output = '';
309 foreach ($urls as $url) {
310 if (strval($name) !== '' && $output === '') {
311 $title = $name;
312 } else {
313 $title = $this->get_filename($url);
315 $printlink = html_writer::link($url, $title, array('class' => 'mediafallbacklink'));
316 if ($output) {
317 // Where there are multiple available formats, there are fallback links
318 // for all formats, separated by /.
319 $output .= ' / ';
321 $output .= $printlink;
323 return $output;
327 * Checks whether a file can be embedded. If this returns true you will get
328 * an embedded player; if this returns false, you will just get a download
329 * link.
331 * This is a wrapper for can_embed_urls.
333 * @param moodle_url $url URL of media file
334 * @param array $options Options (same as when embedding)
335 * @return bool True if file can be embedded
337 public function can_embed_url(moodle_url $url, $options = array()) {
338 return $this->can_embed_urls(array($url), $options);
342 * Checks whether a file can be embedded. If this returns true you will get
343 * an embedded player; if this returns false, you will just get a download
344 * link.
346 * @param array $urls URL of media file and any alternatives (moodle_url)
347 * @param array $options Options (same as when embedding)
348 * @return bool True if file can be embedded
350 public function can_embed_urls(array $urls, $options = array()) {
351 // Check all players to see if any of them support it.
352 foreach ($this->get_players() as $player) {
353 // First player that supports it, return true.
354 if ($player->list_supported_urls($urls, $options)) {
355 return true;
358 return false;
362 * Obtains a list of markers that can be used in a regular expression when
363 * searching for URLs that can be embedded by any player type.
365 * This string is used to improve peformance of regex matching by ensuring
366 * that the (presumably C) regex code can do a quick keyword check on the
367 * URL part of a link to see if it matches one of these, rather than having
368 * to go into PHP code for every single link to see if it can be embedded.
370 * @return string String suitable for use in regex such as '(\.mp4|\.flv)'
372 public function get_embeddable_markers() {
373 if (empty($this->embeddablemarkers)) {
374 $markers = '';
375 foreach ($this->get_players() as $player) {
376 foreach ($player->get_embeddable_markers() as $marker) {
377 if ($markers !== '') {
378 $markers .= '|';
380 $markers .= preg_quote($marker);
383 $this->embeddablemarkers = $markers;
385 return $this->embeddablemarkers;
389 * Given a string containing multiple URLs separated by #, this will split
390 * it into an array of moodle_url objects suitable for using when calling
391 * embed_alternatives.
393 * Note that the input string should NOT be html-escaped (i.e. if it comes
394 * from html, call html_entity_decode first).
396 * @param string $combinedurl String of 1 or more alternatives separated by #
397 * @param int $width Output variable: width (will be set to 0 if not specified)
398 * @param int $height Output variable: height (0 if not specified)
399 * @return array Array of 1 or more moodle_url objects
401 public function split_alternatives($combinedurl, &$width, &$height) {
402 global $CFG;
403 $urls = explode('#', $combinedurl);
404 $width = 0;
405 $height = 0;
406 $returnurls = array();
408 foreach ($urls as $url) {
409 $matches = null;
411 // You can specify the size as a separate part of the array like
412 // #d=640x480 without actually including a url in it.
413 if (preg_match('/^d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
414 $width = $matches[1];
415 $height = $matches[2];
416 continue;
419 // Can also include the ?d= as part of one of the URLs (if you use
420 // more than one they will be ignored except the last).
421 if (preg_match('/\?d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
422 $width = $matches[1];
423 $height = $matches[2];
425 // Trim from URL.
426 $url = str_replace($matches[0], '', $url);
429 // Clean up url.
430 $url = fix_utf8($url);
431 include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
432 if (!validateUrlSyntax($url, 's?H?S?F?R?E?u-P-a?I?p?f?q?r?')) {
433 continue;
436 // Turn it into moodle_url object.
437 $returnurls[] = new moodle_url($url);
440 return $returnurls;
444 * Returns the file extension for a URL.
445 * @param moodle_url $url URL
447 public function get_extension(moodle_url $url) {
448 // Note: Does not use core_text (. is UTF8-safe).
449 $filename = self::get_filename($url);
450 $dot = strrpos($filename, '.');
451 if ($dot === false) {
452 return '';
453 } else {
454 return strtolower(substr($filename, $dot + 1));
459 * Obtains the filename from the moodle_url.
460 * @param moodle_url $url URL
461 * @return string Filename only (not escaped)
463 public function get_filename(moodle_url $url) {
464 // Use the 'file' parameter if provided (for links created when
465 // slasharguments was off). If not present, just use URL path.
466 $path = $url->get_param('file');
467 if (!$path) {
468 $path = $url->get_path();
471 // Remove everything before last / if present. Does not use textlib as / is UTF8-safe.
472 $slash = strrpos($path, '/');
473 if ($slash !== false) {
474 $path = substr($path, $slash + 1);
476 return $path;
480 * Guesses MIME type for a moodle_url based on file extension.
481 * @param moodle_url $url URL
482 * @return string MIME type
484 public function get_mimetype(moodle_url $url) {
485 return mimeinfo('type', $this->get_filename($url));