MDL-69240 tool_moodlenet: Clean MoodleNet profile field
[moodle.git] / filter / mediaplugin / filter.php
CommitLineData
4317f92f 1<?php
35716b86 2// This file is part of Moodle - http://moodle.org/
6de17fde 3//
35716b86
PS
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.
6de17fde 8//
35716b86
PS
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.
6de17fde 13//
35716b86
PS
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 */
6de17fde 28
35716b86 29defined('MOODLE_INTERNAL') || die();
6de17fde 30
fcd2cbaf
PS
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 */
35716b86 42class filter_mediaplugin extends moodle_text_filter {
daefd6eb 43 /** @var bool True if currently filtering trusted text */
44 private $trusted;
fcd2cbaf 45
a174f247
FM
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
1abd4376
JO
60 // Set up the media manager so that media plugins requiring JS are initialised.
61 $mediamanager = core_media_manager::instance($page);
a174f247
FM
62 }
63
daefd6eb 64 public function filter($text, array $options = array()) {
65 global $CFG, $PAGE;
fcd2cbaf
PS
66
67 if (!is_string($text) or empty($text)) {
9e3f34d1 68 // non string data can not be filtered anyway
69 return $text;
70 }
daefd6eb 71
fab11235
MG
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.
e68789c5
PS
74 return $text;
75 }
9e3f34d1 76
daefd6eb 77 // Check SWF permissions.
dc88f669 78 $this->trusted = !empty($options['noclean']) or !empty($CFG->allowobjectembed);
9e3f34d1 79
75cb0fff 80 // Looking for tags.
5329d84f 81 $matches = preg_split('/(<[^>]*>)/i', $text, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
75cb0fff
ZD
82
83 if (!$matches) {
fcd2cbaf 84 return $text;
9e3f34d1 85 }
86
75cb0fff 87 // Regex to find media extensions in an <a> tag.
fab11235
MG
88 $embedmarkers = core_media_manager::instance()->get_embeddable_markers();
89 $re = '~<a\s[^>]*href="([^"]*(?:' . $embedmarkers . ')[^"]*)"[^>]*>([^>]*)</a>~is';
75cb0fff
ZD
90
91 $newtext = '';
92 $validtag = '';
fab11235 93 $tagname = '';
75cb0fff
ZD
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) {
fab11235 100 if (preg_match('|</'.$tagname.'>|', $tag) && !empty($validtag)) {
75cb0fff
ZD
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) {
fab11235
MG
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 }
75cb0fff
ZD
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 = '';
fab11235
MG
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.
75cb0fff 121 $validtag = $tag;
fab11235 122 $tagname = strtolower($tagmatches[1]);
75cb0fff
ZD
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.
fcd2cbaf
PS
135 return $newtext;
136 }
fcd2cbaf 137
daefd6eb 138 /**
139 * Replace link with embedded content, if supported.
140 *
141 * @param array $matches
142 * @return string
143 */
144 private function callback(array $matches) {
fab11235
MG
145 $mediamanager = core_media_manager::instance();
146
daefd6eb 147 global $CFG, $PAGE;
148 // Check if we ignore it.
149 if (preg_match('/class="[^"]*nomediaplugin/i', $matches[0])) {
150 return $matches[0];
fcd2cbaf 151 }
fcd2cbaf 152
daefd6eb 153 // Get name.
154 $name = trim($matches[2]);
155 if (empty($name) or strpos($name, 'http') === 0) {
156 $name = ''; // Use default name.
fcd2cbaf 157 }
fcd2cbaf 158
daefd6eb 159 // Split provided URL into alternatives.
fab11235 160 $urls = $mediamanager->split_alternatives($matches[1], $width, $height);
7e64d361 161
fab11235
MG
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) {
ce5dc36e 179
daefd6eb 180 // Allow SWF (or not).
181 if ($this->trusted) {
fab11235 182 $options[core_media_manager::OPTION_TRUSTED] = true;
fcd2cbaf
PS
183 }
184
daefd6eb 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.
fab11235 188 $options[core_media_manager::OPTION_FALLBACK_TO_BLANK] = true;
fcd2cbaf 189
daefd6eb 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.
fab11235 193 $result = core_media_manager::instance()->embed_alternatives($urls, $name, $width, $height, $options);
ce5dc36e 194
daefd6eb 195 // If something was embedded, return it, otherwise return original.
196 if ($result !== '') {
197 return $result;
fcd2cbaf 198 } else {
fab11235
MG
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);
fcd2cbaf 232 }
fab11235 233 return $fulltext;
fcd2cbaf 234 }
7e64d361 235}