You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
236 lines
9.2 KiB
236 lines
9.2 KiB
2 years ago
|
<?php
|
||
|
// This file is part of Moodle - http://moodle.org/
|
||
|
//
|
||
|
// Moodle is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// Moodle is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU General Public License
|
||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
/**
|
||
|
* Media plugin filtering
|
||
|
*
|
||
|
* This filter will replace any links to a media file with
|
||
|
* a media plugin that plays that media inline
|
||
|
*
|
||
|
* @package filter
|
||
|
* @subpackage mediaplugin
|
||
|
* @copyright 2004 onwards Martin Dougiamas {@link http://moodle.com}
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
|
||
|
defined('MOODLE_INTERNAL') || die();
|
||
|
|
||
|
/**
|
||
|
* Automatic media embedding filter class.
|
||
|
*
|
||
|
* It is highly recommended to configure servers to be compatible with our slasharguments,
|
||
|
* otherwise the "?d=600x400" may not work.
|
||
|
*
|
||
|
* @package filter
|
||
|
* @subpackage mediaplugin
|
||
|
* @copyright 2004 onwards Martin Dougiamas {@link http://moodle.com}
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
class filter_mediaplugin extends moodle_text_filter {
|
||
|
/** @var bool True if currently filtering trusted text */
|
||
|
private $trusted;
|
||
|
|
||
|
/**
|
||
|
* Setup page with filter requirements and other prepare stuff.
|
||
|
*
|
||
|
* @param moodle_page $page The page we are going to add requirements to.
|
||
|
* @param context $context The context which contents are going to be filtered.
|
||
|
*/
|
||
|
public function setup($page, $context) {
|
||
|
// This only requires execution once per request.
|
||
|
static $jsinitialised = false;
|
||
|
if ($jsinitialised) {
|
||
|
return;
|
||
|
}
|
||
|
$jsinitialised = true;
|
||
|
|
||
|
// Set up the media manager so that media plugins requiring JS are initialised.
|
||
|
$mediamanager = core_media_manager::instance($page);
|
||
|
}
|
||
|
|
||
|
public function filter($text, array $options = array()) {
|
||
|
global $CFG, $PAGE;
|
||
|
|
||
|
if (!is_string($text) or empty($text)) {
|
||
|
// non string data can not be filtered anyway
|
||
|
return $text;
|
||
|
}
|
||
|
|
||
|
if (stripos($text, '</a>') === false && stripos($text, '</video>') === false && stripos($text, '</audio>') === false) {
|
||
|
// Performance shortcut - if there are no </a>, </video> or </audio> tags, nothing can match.
|
||
|
return $text;
|
||
|
}
|
||
|
|
||
|
// Check SWF permissions.
|
||
|
$this->trusted = !empty($options['noclean']) or !empty($CFG->allowobjectembed);
|
||
|
|
||
|
// Looking for tags.
|
||
|
$matches = preg_split('/(<[^>]*>)/i', $text, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
|
||
|
|
||
|
if (!$matches) {
|
||
|
return $text;
|
||
|
}
|
||
|
|
||
|
// Regex to find media extensions in an <a> tag.
|
||
|
$embedmarkers = core_media_manager::instance()->get_embeddable_markers();
|
||
|
$re = '~<a\s[^>]*href="([^"]*(?:' . $embedmarkers . ')[^"]*)"[^>]*>([^>]*)</a>~is';
|
||
|
|
||
|
$newtext = '';
|
||
|
$validtag = '';
|
||
|
$tagname = '';
|
||
|
$sizeofmatches = count($matches);
|
||
|
|
||
|
// We iterate through the given string to find valid <a> tags
|
||
|
// and build them so that the callback function can check it for
|
||
|
// embedded content. Then we rebuild the string.
|
||
|
foreach ($matches as $idx => $tag) {
|
||
|
if (preg_match('|</'.$tagname.'>|', $tag) && !empty($validtag)) {
|
||
|
$validtag .= $tag;
|
||
|
|
||
|
// Given we now have a valid <a> tag to process it's time for
|
||
|
// ReDoS protection. Stop processing if a word is too large.
|
||
|
if (strlen($validtag) < 4096) {
|
||
|
if ($tagname === 'a') {
|
||
|
$processed = preg_replace_callback($re, array($this, 'callback'), $validtag);
|
||
|
} else {
|
||
|
// For audio and video tags we just process them without precheck for embeddable markers.
|
||
|
$processed = $this->process_media_tag($validtag);
|
||
|
}
|
||
|
}
|
||
|
// Rebuilding the string with our new processed text.
|
||
|
$newtext .= !empty($processed) ? $processed : $validtag;
|
||
|
// Wipe it so we can catch any more instances to filter.
|
||
|
$validtag = '';
|
||
|
$processed = '';
|
||
|
} else if (preg_match('/<(a|video|audio)\s[^>]*/', $tag, $tagmatches) && $sizeofmatches > 1 &&
|
||
|
(empty($validtag) || $tagname === strtolower($tagmatches[1]))) {
|
||
|
// Looking for a starting tag. Ignore tags embedded into each other.
|
||
|
$validtag = $tag;
|
||
|
$tagname = strtolower($tagmatches[1]);
|
||
|
} else {
|
||
|
// If we have a validtag add to that to process later,
|
||
|
// else add straight onto our newtext string.
|
||
|
if (!empty($validtag)) {
|
||
|
$validtag .= $tag;
|
||
|
} else {
|
||
|
$newtext .= $tag;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Return the same string except processed by the above.
|
||
|
return $newtext;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Replace link with embedded content, if supported.
|
||
|
*
|
||
|
* @param array $matches
|
||
|
* @return string
|
||
|
*/
|
||
|
private function callback(array $matches) {
|
||
|
$mediamanager = core_media_manager::instance();
|
||
|
|
||
|
global $CFG, $PAGE;
|
||
|
// Check if we ignore it.
|
||
|
if (preg_match('/class="[^"]*nomediaplugin/i', $matches[0])) {
|
||
|
return $matches[0];
|
||
|
}
|
||
|
|
||
|
// Get name.
|
||
|
$name = trim($matches[2]);
|
||
|
if (empty($name) or strpos($name, 'http') === 0) {
|
||
|
$name = ''; // Use default name.
|
||
|
}
|
||
|
|
||
|
// Split provided URL into alternatives.
|
||
|
$urls = $mediamanager->split_alternatives($matches[1], $width, $height);
|
||
|
|
||
|
$options = [core_media_manager::OPTION_ORIGINAL_TEXT => $matches[0]];
|
||
|
return $this->embed_alternatives($urls, $name, $width, $height, $options);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Renders media files (audio or video) using suitable embedded player.
|
||
|
*
|
||
|
* Wrapper for {@link core_media_manager::embed_alternatives()}
|
||
|
*
|
||
|
* @param array $urls Array of moodle_url to media files
|
||
|
* @param string $name Optional user-readable name to display in download link
|
||
|
* @param int $width Width in pixels (optional)
|
||
|
* @param int $height Height in pixels (optional)
|
||
|
* @param array $options Array of key/value pairs
|
||
|
* @return string HTML content of embed
|
||
|
*/
|
||
|
protected function embed_alternatives($urls, $name, $width, $height, $options) {
|
||
|
|
||
|
// Allow SWF (or not).
|
||
|
if ($this->trusted) {
|
||
|
$options[core_media_manager::OPTION_TRUSTED] = true;
|
||
|
}
|
||
|
|
||
|
// We could test whether embed is possible using can_embed, but to save
|
||
|
// time, let's just embed it with the 'fallback to blank' option which
|
||
|
// does most of the same stuff anyhow.
|
||
|
$options[core_media_manager::OPTION_FALLBACK_TO_BLANK] = true;
|
||
|
|
||
|
// NOTE: Options are not passed through from filter because the 'embed'
|
||
|
// code does not recognise filter options (it's a different kind of
|
||
|
// option-space) as it can be used in non-filter situations.
|
||
|
$result = core_media_manager::instance()->embed_alternatives($urls, $name, $width, $height, $options);
|
||
|
|
||
|
// If something was embedded, return it, otherwise return original.
|
||
|
if ($result !== '') {
|
||
|
return $result;
|
||
|
} else {
|
||
|
return $options[core_media_manager::OPTION_ORIGINAL_TEXT];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Replaces <video> or <audio> tag with processed contents
|
||
|
*
|
||
|
* @param string $fulltext complete HTML snipped "<video ...>...</video>" or "<audio ...>....</audio>"
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function process_media_tag($fulltext) {
|
||
|
// Check if we ignore it.
|
||
|
if (preg_match('/^<[^>]*class="[^"]*nomediaplugin/im', $fulltext)) {
|
||
|
return $fulltext;
|
||
|
}
|
||
|
|
||
|
// Find all sources both as <video src=""> and as embedded <source> tags.
|
||
|
$urls = [];
|
||
|
if (preg_match('/^<[^>]*\bsrc="(.*?)"/im', $fulltext, $matches)) {
|
||
|
$urls[] = new moodle_url($matches[1]);
|
||
|
}
|
||
|
if (preg_match_all('/<source\b[^>]*\bsrc="(.*?)"/im', $fulltext, $matches)) {
|
||
|
foreach ($matches[1] as $url) {
|
||
|
$urls[] = new moodle_url($url);
|
||
|
}
|
||
|
}
|
||
|
// Extract width/height/title attributes and call embed_alternatives to find a suitable media player.
|
||
|
if ($urls) {
|
||
|
$options = [core_media_manager::OPTION_ORIGINAL_TEXT => $fulltext];
|
||
|
$width = core_media_player_native::get_attribute($fulltext, 'width', PARAM_INT);
|
||
|
$height = core_media_player_native::get_attribute($fulltext, 'height', PARAM_INT);
|
||
|
$name = core_media_player_native::get_attribute($fulltext, 'title');
|
||
|
return $this->embed_alternatives($urls, $name, $width, $height, $options);
|
||
|
}
|
||
|
return $fulltext;
|
||
|
}
|
||
|
}
|