Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 3.9.x will end* 10 May 2021 (12 months).
  • Bug fixes for security issues in 3.9.x will end* 8 May 2023 (36 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]

   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   * Manager for media files
  19   *
  20   * @package   core_media
  21   * @copyright 2016 Marina Glancy
  22   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  defined('MOODLE_INTERNAL') || die();
  26  
  27  /**
  28   * Manager for media files.
  29   *
  30   * Used in file resources, media filter, and any other places that need to
  31   * output embedded media.
  32   *
  33   * Usage:
  34   * $manager = core_media_manager::instance();
  35   *
  36   *
  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
  41   */
  42  final class core_media_manager {
  43      /**
  44       * Option: Disable text link fallback.
  45       *
  46       * Use this option if you are going to print a visible link anyway so it is
  47       * pointless to have one as fallback.
  48       *
  49       * To enable, set value to true.
  50       */
  51      const OPTION_NO_LINK = 'nolink';
  52  
  53      /**
  54       * Option: When embedding, if there is no matching embed, do not use the
  55       * default link fallback player; instead return blank.
  56       *
  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.
  60       *
  61       * To enable, set value to true.
  62       */
  63      const OPTION_FALLBACK_TO_BLANK = 'embedorblank';
  64  
  65      /**
  66       * Option: Enable players which are only suitable for use when we trust the
  67       * user who embedded the content.
  68       *
  69       * At present, this option enables the SWF player.
  70       *
  71       * To enable, set value to true.
  72       */
  73      const OPTION_TRUSTED = 'trusted';
  74  
  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.
  78       *
  79       * To enable, set value to true.
  80       */
  81      const OPTION_BLOCK = 'block';
  82  
  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>
  86       *
  87       * Players that support other HTML5 features such as tracks may find them in this option.
  88       */
  89      const OPTION_ORIGINAL_TEXT = 'originaltext';
  90  
  91      /** @var array Array of available 'player' objects */
  92      private $players;
  93  
  94      /** @var string Regex pattern for links which may contain embeddable content */
  95      private $embeddablemarkers;
  96  
  97      /** @var core_media_manager caches a singleton instance */
  98      static private $instance;
  99  
 100      /** @var moodle_page page this instance was initialised for */
 101      private $page;
 102  
 103      /**
 104       * Returns a singleton instance of a manager
 105       *
 106       * Note as of Moodle 3.3, this will call setup for you.
 107       *
 108       * @return core_media_manager
 109       */
 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;
 115          }
 116          if (self::$instance === null || ($page && self::$instance->page !== $page)) {
 117              self::$instance = new self($page);
 118          }
 119          return self::$instance;
 120      }
 121  
 122      /**
 123       * Construct a new core_media_manager instance
 124       *
 125       * @param moodle_page $page The page we are going to add requirements to.
 126       * @see core_media_manager::instance()
 127       */
 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);
 134              }
 135          } else {
 136              debugging('Could not determine the $PAGE. Media plugins will not be set up', DEBUG_DEVELOPER);
 137          }
 138      }
 139  
 140      /**
 141       * @deprecated since Moodle 3.3. The setup is now done in ::instance() so there is no need to call this.
 142       */
 143      public function setup() {
 144          throw new coding_exception('core_media_manager::setup() can not be used any more because it is done in ::instance()');
 145      }
 146  
 147      /**
 148       * Resets cached singleton instance. To be used after $CFG->media_plugins_sortorder is modified
 149       */
 150      public static function reset_caches() {
 151          self::$instance = null;
 152      }
 153  
 154      /**
 155       * Obtains the list of core_media_player objects currently in use to render
 156       * items.
 157       *
 158       * The list is in rank order (highest first) and does not include players
 159       * which are disabled.
 160       *
 161       * @return core_media_player[] Array of core_media_player objects in rank order
 162       */
 163      private function get_players() {
 164          // Save time by only building the list once.
 165          if (!$this->players) {
 166              $sortorder = \core\plugininfo\media::get_enabled_plugins();
 167  
 168              $this->players = [];
 169              foreach ($sortorder as $name) {
 170                  $classname = "media_" . $name . "_plugin";
 171                  if (class_exists($classname)) {
 172                      $this->players[] = new $classname();
 173                  }
 174              }
 175          }
 176          return $this->players;
 177      }
 178  
 179      /**
 180       * Renders a media file (audio or video) using suitable embedded player.
 181       *
 182       * See embed_alternatives function for full description of parameters.
 183       * This function calls through to that one.
 184       *
 185       * When using this function you can also specify width and height in the
 186       * URL by including ?d=100x100 at the end. If specified in the URL, this
 187       * will override the $width and $height parameters.
 188       *
 189       * @param moodle_url $url Full URL of media file
 190       * @param string $name Optional user-readable name to display in download link
 191       * @param int $width Width in pixels (optional)
 192       * @param int $height Height in pixels (optional)
 193       * @param array $options Array of key/value pairs
 194       * @return string HTML content of embed
 195       */
 196      public function embed_url(moodle_url $url, $name = '', $width = 0, $height = 0,
 197                                $options = array()) {
 198  
 199          // Get width and height from URL if specified (overrides parameters in
 200          // function call).
 201          $rawurl = $url->out(false);
 202          if (preg_match('/[?#]d=([\d]{1,4}%?)x([\d]{1,4}%?)/', $rawurl, $matches)) {
 203              $width = $matches[1];
 204              $height = $matches[2];
 205              $url = new moodle_url(str_replace($matches[0], '', $rawurl));
 206          }
 207  
 208          // Defer to array version of function.
 209          return $this->embed_alternatives(array($url), $name, $width, $height, $options);
 210      }
 211  
 212      /**
 213       * Renders media files (audio or video) using suitable embedded player.
 214       * The list of URLs should be alternative versions of the same content in
 215       * multiple formats. If there is only one format it should have a single
 216       * entry.
 217       *
 218       * If the media files are not in a supported format, this will give students
 219       * a download link to each format. The download link uses the filename
 220       * unless you supply the optional name parameter.
 221       *
 222       * Width and height are optional. If specified, these are suggested sizes
 223       * and should be the exact values supplied by the user, if they come from
 224       * user input. These will be treated as relating to the size of the video
 225       * content, not including any player control bar.
 226       *
 227       * For audio files, height will be ignored. For video files, a few formats
 228       * work if you specify only width, but in general if you specify width
 229       * you must specify height as well.
 230       *
 231       * The $options array is passed through to the core_media_player classes
 232       * that render the object tag. The keys can contain values from
 233       * core_media::OPTION_xx.
 234       *
 235       * @param array $alternatives Array of moodle_url to media files
 236       * @param string $name Optional user-readable name to display in download link
 237       * @param int $width Width in pixels (optional)
 238       * @param int $height Height in pixels (optional)
 239       * @param array $options Array of key/value pairs
 240       * @return string HTML content of embed
 241       */
 242      public function embed_alternatives($alternatives, $name = '', $width = 0, $height = 0,
 243                                         $options = array()) {
 244  
 245          // Get list of player plugins.
 246          $players = $this->get_players();
 247  
 248          // Set up initial text which will be replaced by first player that
 249          // supports any of the formats.
 250          $out = core_media_player::PLACEHOLDER;
 251  
 252          // Loop through all players that support any of these URLs.
 253          foreach ($players as $player) {
 254              $supported = $player->list_supported_urls($alternatives, $options);
 255              if ($supported) {
 256                  // Embed.
 257                  $text = $player->embed($supported, $name, $width, $height, $options);
 258  
 259                  // Put this in place of the 'fallback' slot in the previous text.
 260                  $out = str_replace(core_media_player::PLACEHOLDER, $text, $out);
 261  
 262                  // Check if we need to continue looking for players.
 263                  if (strpos($out, core_media_player::PLACEHOLDER) === false) {
 264                      break;
 265                  }
 266              }
 267          }
 268  
 269          if (!empty($options[self::OPTION_FALLBACK_TO_BLANK]) && $out === core_media_player::PLACEHOLDER) {
 270              // In case of OPTION_FALLBACK_TO_BLANK and no player matched do not fallback to link, just return empty string.
 271              return '';
 272          }
 273  
 274          // Remove 'fallback' slot from final version and return it.
 275          $fallback = $this->fallback_to_link($alternatives, $name, $options);
 276          $out = str_replace(core_media_player::PLACEHOLDER, $fallback, $out);
 277          $out = str_replace(core_media_player::LINKPLACEHOLDER, $fallback, $out);
 278          if (!empty($options[self::OPTION_BLOCK]) && $out !== '') {
 279              $out = html_writer::tag('div', $out, array('class' => 'resourcecontent'));
 280          }
 281          return $out;
 282      }
 283  
 284      /**
 285       * Returns links to the specified URLs unless OPTION_NO_LINK is passed.
 286       *
 287       * @param array $urls URLs of media files
 288       * @param string $name Display name; '' to use default
 289       * @param array $options Options array
 290       * @return string HTML code for embed
 291       */
 292      private function fallback_to_link($urls, $name, $options) {
 293          // If link is turned off, return empty.
 294          if (!empty($options[self::OPTION_NO_LINK])) {
 295              return '';
 296          }
 297  
 298          // Build up link content.
 299          $output = '';
 300          foreach ($urls as $url) {
 301              if (strval($name) !== '' && $output === '') {
 302                  $title = $name;
 303              } else {
 304                  $title = $this->get_filename($url);
 305              }
 306              $printlink = html_writer::link($url, $title, array('class' => 'mediafallbacklink'));
 307              if ($output) {
 308                  // Where there are multiple available formats, there are fallback links
 309                  // for all formats, separated by /.
 310                  $output .= ' / ';
 311              }
 312              $output .= $printlink;
 313          }
 314          return $output;
 315      }
 316  
 317      /**
 318       * Checks whether a file can be embedded. If this returns true you will get
 319       * an embedded player; if this returns false, you will just get a download
 320       * link.
 321       *
 322       * This is a wrapper for can_embed_urls.
 323       *
 324       * @param moodle_url $url URL of media file
 325       * @param array $options Options (same as when embedding)
 326       * @return bool True if file can be embedded
 327       */
 328      public function can_embed_url(moodle_url $url, $options = array()) {
 329          return $this->can_embed_urls(array($url), $options);
 330      }
 331  
 332      /**
 333       * Checks whether a file can be embedded. If this returns true you will get
 334       * an embedded player; if this returns false, you will just get a download
 335       * link.
 336       *
 337       * @param array $urls URL of media file and any alternatives (moodle_url)
 338       * @param array $options Options (same as when embedding)
 339       * @return bool True if file can be embedded
 340       */
 341      public function can_embed_urls(array $urls, $options = array()) {
 342          // Check all players to see if any of them support it.
 343          foreach ($this->get_players() as $player) {
 344              // First player that supports it, return true.
 345              if ($player->list_supported_urls($urls, $options)) {
 346                  return true;
 347              }
 348          }
 349          return false;
 350      }
 351  
 352      /**
 353       * Obtains a list of markers that can be used in a regular expression when
 354       * searching for URLs that can be embedded by any player type.
 355       *
 356       * This string is used to improve peformance of regex matching by ensuring
 357       * that the (presumably C) regex code can do a quick keyword check on the
 358       * URL part of a link to see if it matches one of these, rather than having
 359       * to go into PHP code for every single link to see if it can be embedded.
 360       *
 361       * @return string String suitable for use in regex such as '(\.mp4|\.flv)'
 362       */
 363      public function get_embeddable_markers() {
 364          if (empty($this->embeddablemarkers)) {
 365              $markers = '';
 366              foreach ($this->get_players() as $player) {
 367                  foreach ($player->get_embeddable_markers() as $marker) {
 368                      if ($markers !== '') {
 369                          $markers .= '|';
 370                      }
 371                      $markers .= preg_quote($marker);
 372                  }
 373              }
 374              $this->embeddablemarkers = $markers;
 375          }
 376          return $this->embeddablemarkers;
 377      }
 378  
 379      /**
 380       * Given a string containing multiple URLs separated by #, this will split
 381       * it into an array of moodle_url objects suitable for using when calling
 382       * embed_alternatives.
 383       *
 384       * Note that the input string should NOT be html-escaped (i.e. if it comes
 385       * from html, call html_entity_decode first).
 386       *
 387       * @param string $combinedurl String of 1 or more alternatives separated by #
 388       * @param int $width Output variable: width (will be set to 0 if not specified)
 389       * @param int $height Output variable: height (0 if not specified)
 390       * @return array Array of 1 or more moodle_url objects
 391       */
 392      public function split_alternatives($combinedurl, &$width, &$height) {
 393          global $CFG;
 394          $urls = explode('#', $combinedurl);
 395          $width = 0;
 396          $height = 0;
 397          $returnurls = array();
 398  
 399          foreach ($urls as $url) {
 400              $matches = null;
 401  
 402              // You can specify the size as a separate part of the array like
 403              // #d=640x480 without actually including a url in it.
 404              if (preg_match('/^d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
 405                  $width  = $matches[1];
 406                  $height = $matches[2];
 407                  continue;
 408              }
 409  
 410              // Can also include the ?d= as part of one of the URLs (if you use
 411              // more than one they will be ignored except the last).
 412              if (preg_match('/\?d=([\d]{1,4})x([\d]{1,4})$/i', $url, $matches)) {
 413                  $width  = $matches[1];
 414                  $height = $matches[2];
 415  
 416                  // Trim from URL.
 417                  $url = str_replace($matches[0], '', $url);
 418              }
 419  
 420              // Clean up url.
 421              $url = fix_utf8($url);
 422              include_once($CFG->dirroot . '/lib/validateurlsyntax.php');
 423              if (!validateUrlSyntax($url, 's?H?S?F?R?E?u-P-a?I?p?f?q?r?')) {
 424                  continue;
 425              }
 426  
 427              // Turn it into moodle_url object.
 428              $returnurls[] = new moodle_url($url);
 429          }
 430  
 431          return $returnurls;
 432      }
 433  
 434      /**
 435       * Returns the file extension for a URL.
 436       * @param moodle_url $url URL
 437       */
 438      public function get_extension(moodle_url $url) {
 439          // Note: Does not use core_text (. is UTF8-safe).
 440          $filename = self::get_filename($url);
 441          $dot = strrpos($filename, '.');
 442          if ($dot === false) {
 443              return '';
 444          } else {
 445              return strtolower(substr($filename, $dot + 1));
 446          }
 447      }
 448  
 449      /**
 450       * Obtains the filename from the moodle_url.
 451       * @param moodle_url $url URL
 452       * @return string Filename only (not escaped)
 453       */
 454      public function get_filename(moodle_url $url) {
 455          // Use the 'file' parameter if provided (for links created when
 456          // slasharguments was off). If not present, just use URL path.
 457          $path = $url->get_param('file');
 458          if (!$path) {
 459              $path = $url->get_path();
 460          }
 461  
 462          // Remove everything before last / if present. Does not use textlib as / is UTF8-safe.
 463          $slash = strrpos($path, '/');
 464          if ($slash !== false) {
 465              $path = substr($path, $slash + 1);
 466          }
 467          return $path;
 468      }
 469  
 470      /**
 471       * Guesses MIME type for a moodle_url based on file extension.
 472       * @param moodle_url $url URL
 473       * @return string MIME type
 474       */
 475      public function get_mimetype(moodle_url $url) {
 476          return mimeinfo('type', $this->get_filename($url));
 477      }
 478  
 479  }