PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/filter/mediaplugin/filter.php

https://bitbucket.org/moodle/moodle
PHP | 235 lines | 107 code | 28 blank | 100 comment | 28 complexity | cca998b57476be46d3a2f45dc1700e48 MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  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. * Media plugin filtering
  18. *
  19. * This filter will replace any links to a media file with
  20. * a media plugin that plays that media inline
  21. *
  22. * @package filter
  23. * @subpackage mediaplugin
  24. * @copyright 2004 onwards Martin Dougiamas {@link http://moodle.com}
  25. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26. */
  27. defined('MOODLE_INTERNAL') || die();
  28. /**
  29. * Automatic media embedding filter class.
  30. *
  31. * It is highly recommended to configure servers to be compatible with our slasharguments,
  32. * otherwise the "?d=600x400" may not work.
  33. *
  34. * @package filter
  35. * @subpackage mediaplugin
  36. * @copyright 2004 onwards Martin Dougiamas {@link http://moodle.com}
  37. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38. */
  39. class filter_mediaplugin extends moodle_text_filter {
  40. /** @var bool True if currently filtering trusted text */
  41. private $trusted;
  42. /**
  43. * Setup page with filter requirements and other prepare stuff.
  44. *
  45. * @param moodle_page $page The page we are going to add requirements to.
  46. * @param context $context The context which contents are going to be filtered.
  47. */
  48. public function setup($page, $context) {
  49. // This only requires execution once per request.
  50. static $jsinitialised = false;
  51. if ($jsinitialised) {
  52. return;
  53. }
  54. $jsinitialised = true;
  55. // Set up the media manager so that media plugins requiring JS are initialised.
  56. $mediamanager = core_media_manager::instance($page);
  57. }
  58. public function filter($text, array $options = array()) {
  59. global $CFG, $PAGE;
  60. if (!is_string($text) or empty($text)) {
  61. // non string data can not be filtered anyway
  62. return $text;
  63. }
  64. if (stripos($text, '</a>') === false && stripos($text, '</video>') === false && stripos($text, '</audio>') === false) {
  65. // Performance shortcut - if there are no </a>, </video> or </audio> tags, nothing can match.
  66. return $text;
  67. }
  68. // Check permissions.
  69. $this->trusted = !empty($options['noclean']) or !empty($CFG->allowobjectembed);
  70. // Looking for tags.
  71. $matches = preg_split('/(<[^>]*>)/i', $text, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
  72. if (!$matches) {
  73. return $text;
  74. }
  75. // Regex to find media extensions in an <a> tag.
  76. $embedmarkers = core_media_manager::instance()->get_embeddable_markers();
  77. $re = '~<a\s[^>]*href="([^"]*(?:' . $embedmarkers . ')[^"]*)"[^>]*>([^>]*)</a>~is';
  78. $newtext = '';
  79. $validtag = '';
  80. $tagname = '';
  81. $sizeofmatches = count($matches);
  82. // We iterate through the given string to find valid <a> tags
  83. // and build them so that the callback function can check it for
  84. // embedded content. Then we rebuild the string.
  85. foreach ($matches as $idx => $tag) {
  86. if (preg_match('|</'.$tagname.'>|', $tag) && !empty($validtag)) {
  87. $validtag .= $tag;
  88. // Given we now have a valid <a> tag to process it's time for
  89. // ReDoS protection. Stop processing if a word is too large.
  90. if (strlen($validtag) < 4096) {
  91. if ($tagname === 'a') {
  92. $processed = preg_replace_callback($re, array($this, 'callback'), $validtag);
  93. } else {
  94. // For audio and video tags we just process them without precheck for embeddable markers.
  95. $processed = $this->process_media_tag($validtag);
  96. }
  97. }
  98. // Rebuilding the string with our new processed text.
  99. $newtext .= !empty($processed) ? $processed : $validtag;
  100. // Wipe it so we can catch any more instances to filter.
  101. $validtag = '';
  102. $processed = '';
  103. } else if (preg_match('/<(a|video|audio)\s[^>]*/', $tag, $tagmatches) && $sizeofmatches > 1 &&
  104. (empty($validtag) || $tagname === strtolower($tagmatches[1]))) {
  105. // Looking for a starting tag. Ignore tags embedded into each other.
  106. $validtag = $tag;
  107. $tagname = strtolower($tagmatches[1]);
  108. } else {
  109. // If we have a validtag add to that to process later,
  110. // else add straight onto our newtext string.
  111. if (!empty($validtag)) {
  112. $validtag .= $tag;
  113. } else {
  114. $newtext .= $tag;
  115. }
  116. }
  117. }
  118. // Return the same string except processed by the above.
  119. return $newtext;
  120. }
  121. /**
  122. * Replace link with embedded content, if supported.
  123. *
  124. * @param array $matches
  125. * @return string
  126. */
  127. private function callback(array $matches) {
  128. $mediamanager = core_media_manager::instance();
  129. global $CFG, $PAGE;
  130. // Check if we ignore it.
  131. if (preg_match('/class="[^"]*nomediaplugin/i', $matches[0])) {
  132. return $matches[0];
  133. }
  134. // Get name.
  135. $name = trim($matches[2]);
  136. if (empty($name) or strpos($name, 'http') === 0) {
  137. $name = ''; // Use default name.
  138. }
  139. // Split provided URL into alternatives.
  140. $urls = $mediamanager->split_alternatives($matches[1], $width, $height);
  141. $options = [core_media_manager::OPTION_ORIGINAL_TEXT => $matches[0]];
  142. return $this->embed_alternatives($urls, $name, $width, $height, $options);
  143. }
  144. /**
  145. * Renders media files (audio or video) using suitable embedded player.
  146. *
  147. * Wrapper for {@link core_media_manager::embed_alternatives()}
  148. *
  149. * @param array $urls Array of moodle_url to media files
  150. * @param string $name Optional user-readable name to display in download link
  151. * @param int $width Width in pixels (optional)
  152. * @param int $height Height in pixels (optional)
  153. * @param array $options Array of key/value pairs
  154. * @return string HTML content of embed
  155. */
  156. protected function embed_alternatives($urls, $name, $width, $height, $options) {
  157. // Allow trusted content (or not).
  158. if ($this->trusted) {
  159. $options[core_media_manager::OPTION_TRUSTED] = true;
  160. }
  161. // We could test whether embed is possible using can_embed, but to save
  162. // time, let's just embed it with the 'fallback to blank' option which
  163. // does most of the same stuff anyhow.
  164. $options[core_media_manager::OPTION_FALLBACK_TO_BLANK] = true;
  165. // NOTE: Options are not passed through from filter because the 'embed'
  166. // code does not recognise filter options (it's a different kind of
  167. // option-space) as it can be used in non-filter situations.
  168. $result = core_media_manager::instance()->embed_alternatives($urls, $name, $width, $height, $options);
  169. // If something was embedded, return it, otherwise return original.
  170. if ($result !== '') {
  171. return $result;
  172. } else {
  173. return $options[core_media_manager::OPTION_ORIGINAL_TEXT];
  174. }
  175. }
  176. /**
  177. * Replaces <video> or <audio> tag with processed contents
  178. *
  179. * @param string $fulltext complete HTML snipped "<video ...>...</video>" or "<audio ...>....</audio>"
  180. * @return string
  181. */
  182. protected function process_media_tag($fulltext) {
  183. // Check if we ignore it.
  184. if (preg_match('/^<[^>]*class="[^"]*nomediaplugin/im', $fulltext)) {
  185. return $fulltext;
  186. }
  187. // Find all sources both as <video src=""> and as embedded <source> tags.
  188. $urls = [];
  189. if (preg_match('/^<[^>]*\bsrc="(.*?)"/im', $fulltext, $matches)) {
  190. $urls[] = new moodle_url($matches[1]);
  191. }
  192. if (preg_match_all('/<source\b[^>]*\bsrc="(.*?)"/im', $fulltext, $matches)) {
  193. foreach ($matches[1] as $url) {
  194. $urls[] = new moodle_url($url);
  195. }
  196. }
  197. // Extract width/height/title attributes and call embed_alternatives to find a suitable media player.
  198. if ($urls) {
  199. $options = [core_media_manager::OPTION_ORIGINAL_TEXT => $fulltext];
  200. $width = core_media_player_native::get_attribute($fulltext, 'width', PARAM_INT);
  201. $height = core_media_player_native::get_attribute($fulltext, 'height', PARAM_INT);
  202. $name = core_media_player_native::get_attribute($fulltext, 'title');
  203. return $this->embed_alternatives($urls, $name, $width, $height, $options);
  204. }
  205. return $fulltext;
  206. }
  207. }