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